[User]
I'm looking to design a parametric servo frame for RC plane wings, and I'd like it to be customizable. It should have two main parts: the frame itself and a clip to hold the servo in place. I need to be able to specify dimensions for different servos, along with parameters for the frame platform, wire routing, and the clip.

Here's what I'm thinking for the parameters:

**Servo Parameters:**

*   `seW`: Servo width
*   `seWf`: Servo width including the mounting tabs (feet)
*   `seL`: Servo height
*   `seLa`: Full servo height
*   `seLf`: Distance from the bottom of the servo to the bottom of the mounting tabs
*   `seFs`: Servo mounting tab thickness
*   `seH`: Servo thickness
*   `seLw`: Distance from the bottom of the servo to the wires
*   `seWw`: Width of the wires
*   `seWl`: Thickness of the wires
*   `seWr`: Radius for rounding the wire slot
*   `seWt`: An option for wire slot location (1 for left, 2 for right, 3 for both)

**Platform Parameters:**

*   `seS`: Bottom clearance
*   `frS`: Platform thickness
*  `siS`: Thickness of the side around the servo
*   `siH`: Height of the side around the servo (should default to `seH/2`)
*   `frU`: Height of the foot holder
*   `frSr`: Platform width to the right
*   `frSl`: Platform width to the left
*   `frSt`: Platform width to the top
*   `frSb`: Platform width to the bottom
*   `frRr`: Radius for rounding the frame platform corners
*   `stS`: Width of the stiffener ribs
*   `hT`: Hole type (0 for none, 1 for through-hole over servo, 2 for round hole, 3 for square hole)
*   `hR`: Radius for rounding the hole
*  `hS`: Indent to hole

**Clip Parameters:**

*  `boS`: Height of the top of the retainer boom
*   `boD`: Diameter of the retainer boom hole
*   `fiD`: Diameter of the clamp hole
*  `fiS`: Clip thickness
*   `boW`: Width of the retainer boom, this should calculate from servo width and border thickness
*   `boL`: Length of the retainer boom, this should calculate from servo width and border thickness

**Derived Parameters (these should be calculated within the code):**

*   `frW`: Frame width (calculated from `seW`, `frSl`, `frSr`)
*   `frL`: Frame length (calculated from `seL`, `frSt`, `frSb`)

I'd like the frame to have stiffener ribs and an option for wire slots. The clip should fit on top of the frame and secure the servo using the retainer booms. It would be nice to have some configurable options for the type of hole in the frame and for the placement of the wire slots.

I think it should output two main modules `frame()` and `stopor()`, where the stopor module is the clip, and be able to render either or both using a `print` variable of 1, 2, and 3 respectively.

Could you generate the OpenSCAD files for this?

[Assistant]
Frame-Cust.scad:
```
/*
Sero frame - https://www.thingiverse.com/thing:5154601
by Taras Kushnirenko
created 2021-12-04
version v1.0
Changelog
v1.0 - 2021-12-04:
initial design
This work is licensed under the Creative Commons - Attribution - Non-Commercial ShareAlike license.
https://creativecommons.org/licenses/by-nc-sa/3.0/
*/
//preview[view:south, tilt:top diagonal]

/* [Printing] */
// Printed parts
print = 3;// [1:Frame, 2:Clip, 3:Frame & Clip]

/* [Servo parameters:] */
// В - Servo width
seW  = 22.8;  // В - Ширина сервы
// E - Servo width by foot
seWf = 32.3;  // E - Ширина сервы по лапкам
// C - Servo height
seL  = 22.8;  // C - Высота сервы
// A - Full Servo height
seLa = 32.3;  // A - Высота сервы полная
// F - From the bottom of the servo to the foot
seLf = 15.3;  // F - От низа сервы до лапки
// Servo foot thickness
seFs = 0.8;   // Толщина лапки сервы
// D - thickness of the servo
seH  = 12.3;  // D - Толщина сервы
// From the bottom of the servo to the wires
seLw = 3.0;   // От низа сервы до проводов
// Wire width
seWw = 6.0;   // Ширина проводов
// Wire thickness
seWl = 3.0;   // Толщина проводов
// Round off the wire slot
seWr = 0.5;   // скругление паза для проводов
// Wire slot
seWt = 3;// [1:On left, 2:On right, 3:At both sides]

/* [Platform parameters:] */
// Bottom clearance
seS = 0.4;    // Зазор снизу
// Platform thickness
frS = 1.0;    // Толщина площадки
// Thickness of the side around the servo
siS = 0.8;    // Толщина бортика вокруг сервы
// Height of the side around the servo
siH = seH/2;  // Высота бортика вокруг сервы
// Height of the foots holder
frU = seH-0.5;    // Высота держателя уха
// Platform on right
frSr = 6.0;   // Площадка справа
// Platform on left
frSl = 6.0;   // Площадка слева
// Platform on top
frSt = 3.0;   // Площадка сверху
// Platform below
frSb = 4.0;   // Площадка снизу
// The radius of the area rounding
frRr = 3.0;    // Радиус скругления площадки
// Width of the stiffener
stS = 0.8;    // Ширина ребра
// Hole type
hT = 3;// [0:No, 1:Pass-through on the servo, 2:round, 3:Square]
              // Тип отверстия
              // 0 - нет
              // 1 - сквозное по серве
              // 2 - круглое
              // 3 - квадратное
// Round off the hole
hR = 5;       // радиус скругления отверстия
// Indent to hole
hS = 2;       // отступ до отверстия

/* [Bonk and Clip:] */
// Height of the top of the retainer boom
boS = 2;      // Высота верха бонки фиксатора
// Hole diameter retainer boom
boD = 1.5;    // Диаметр отв. бонки фиксатора
// Clamp hole diameter
fiD = 2.0;    // Диаметр отв. фиксатора
// Clip thickness
fiS = 1.0;    // Толщина фиксатора

/* [Hidden] */

boW = (seWf-seW)/2+siS; // Ширина бонки фиксатора
boL = (seWf-seW)/2+siS; // Длина бонки фиксатора

frW = seW+frSl+frSr;    // Ширина рамки
frL = seL+frSt+frSb;    // Длина рамки

//%translate([-50,50,0])scale([0.2, 0.2, 0.01])surface(file = "size.png", center = true, invert = true);

eps = 0.002; $fn = $preview ? 24 : 72;

if (print == 1){
  frame();
}
if (print == 2){
  stopor();
}
if (print == 3){
  frame();
  translate([0,-frSb-boL/2-1,0])stopor();
}

module frame(){
  difference(){
    union(){
      translate([-frSl-seW/2,-frSb,0])
      linear_extrude(height=frS)offset(r=frRr)offset(r=-frRr)square([frW,frL]); // Frame
      translate([-seW/2-siS,-siS,seS])cube([seW+2*siS,seL+1*siS,siH]); // Border
      translate([-seWf/2-siS,seLf-siS,0])cube([seWf+2*siS,seFs+2*siS,frU+seS]); // Servo Foot
      translate([-seW/2-boW,seLf-boL,seS])bonk(boW,boW,boL,frU,boS); // bonk left
      translate([seW/2,seLf-boL,seS])bonk(boW,0,boL,frU,boS); // bonk right
    } // union
    translate([-seWf/2,seLf,seS])cube([seWf,seFs,seH+5]); // - servo foot
    if (hT == 1) { // - servo
      translate([-seW/2,0,-eps])cube([seW,seL,seH+5]);
    } else { // - servo
      translate([-seW/2,0,seS])cube([seW,seL,seH+5]);
    }
    if (hT == 2) { // - circle
      translate([0,seL/2,-0.1])cylinder(d=min(seW,seL)-2*hS,h=seH);
    } else if (hT == 3) { // - square
      translate([-seW/2+hS,hS,-0.1])linear_extrude(seH)offset(r=hR)offset(r=-hR)square([seW-2*hS,seL-2*hS]);
    }
    translate([-(seW+boW)/2,seLf-boL/2,siS+0.1])cylinder(d=boD,h=frU); // отв. бонка лев.
    translate([(seW+boW)/2,seLf-boL/2,siS+0.1])cylinder(d=boD,h=frU); // отв. бонка прав.
    if ((seWt == 2) || (seWt == 3)) { // Провода справа
      translate([seW/2+2*siS,seLw,seS+(seH-seWw)/2])rotate([0,-90,0])linear_extrude(seW/2)offset(r=seWr)offset(r=-seWr)square([seH,seWl]);
    }
    if ((seWt == 1) || (seWt == 3)) { // Провода слева
      mirror([1,0,0])translate([seW/2+2*siS,seLw,seS+(seH-seWw)/2])rotate([0,-90,0])linear_extrude(seW/2)offset(r=seWr)offset(r=-seWr)square([seH,seWl]);
    }
  }
  // Ribs side bottom
  translate([seW/2+siS,-siS,frS])stiffener(frSr-siS,siH-frS+seS,stS); // rb
  translate([-seW/2-siS,-siS+stS,frS])rotate([0,0,180])stiffener(frSl-siS,siH-frS+seS,stS); //lb
  // Ribs side middle
  translate([seW/2+siS,-siS+seL/2+stS/2,frS])stiffener(frSr-siS,siH-frS+seS,stS); //rm
  translate([-seW/2-siS,-siS+stS+seL/2+stS/2,frS])rotate([0,0,180])stiffener(frSl-siS,siH-frS+seS,stS); //lm
  // Ribs side top
  translate([seW/2+siS,-siS+seL,frS])stiffener(frSr-siS,siH-frS+seS,stS); //rt
  translate([-seW/2-siS,-siS+stS+seL,frS])rotate([0,0,180])stiffener(frSl-siS,siH-frS+seS,stS); //lt
  // Ribs bottom
  translate([-stS/2,-siS,frS])rotate([0,0,-90])stiffener(frSb-siS,siH-frS+seS,stS); // bm
  translate([seW/2,-siS,frS])rotate([0,0,-90])stiffener(frSb-siS,siH-frS+seS,stS); // br
  translate([-seW/2-siS,-siS,frS])rotate([0,0,-90])stiffener(frSb-siS,siH-frS+seS,stS);
}

module stopor(){
  difference(){
    hull(){
      translate([-(seW+boW)/2,0,0])cylinder(d=boL,h=fiS);
      translate([(seW+boW)/2,0,0])cylinder(d=boL,h=fiS);
    }
    translate([-(seW+boW)/2,0,-0.1])cylinder(d=fiD,h=fiS+0.2);
    translate([(seW+boW)/2,0,-0.1])cylinder(d=fiD,h=fiS+0.2);
  }
}
module bonk(w,o,l,h,s) {
  hull(){
    translate([o,l,0])cube([eps,eps,eps]);
    translate([0,0,h-s])union(){
      translate([w/2,l/2,0])resize([w,l,s])cylinder(d=1,h=1);
      translate([0,l/2,0])cube([w,l/2,s]);
      if (o > 0) {
        translate([w/2,0,0])cube([w/2,l,s]);
      } else {
        cube([w/2,l,s]);
      }
    }
  }
}
module stiffener(w,h,s) {
  hull(){
    cube([w,s,eps]);
    cube([eps,s,h]);
  }
}

```
S-HXT900.scad:
```
// Параметры сервы
seW  = 22.8+0.2;  // В - Ширина сервы
seWf = 32.4+0.5;  // E - Ширина сервы по лапкам
seL  = 22.7;  // C - Высота сервы
seLa = 26.8;  // A - Высота сервы полная
seLf = 15.8;  // F - От низа сервы до лапки
seFs = 2.5;   // Толщина лапки сервы
seH  = 12.7;  // D - Толщина сервы
seLw = 4.5;   // От низа сервы до проводов
seWw = 5.0;   // Ширина проводов
seWl = 2.0;   // Толщина проводов
seWr = 0.5;   // скругление отверстия проводов
echo("HXT900");
```
S-MG90D.scad:
```
// Параметры сервы
seW  = 22.6+0.2;  // В - Ширина сервы
seWf = 32.4+0.5;  // E - Ширина сервы по лапкам
seL  = 22.6;  // C - Высота сервы
seLa = 28.6;  // A - Высота сервы полная
seLf = 18.3;  // F - От низа сервы до лапки
seFs = 2.5;   // Толщина лапки сервы
seH  = 12.3;  // D - Толщина сервы
seLw = 4.3;   // От низа сервы до проводов
seWw = 5.0;   // Ширина проводов
seWl = 2.0;   // Толщина проводов
seWr = 0.5;   // скругление отверстия проводов
echo("MG90D");
```
Frame.scad:
```

//include <S-SG90.scad>;
include <S-HXT900.scad>;
//include <S-ES08MA.scad>;
//include <S-MG90D.scad>;
//include <S-DAVIGA-D5213.scad>;
// Параметры площадки
// Wire slot
seWt = 3;// [1:On left, 2:On right, 3:At both sides]
seS = 0.4;    // Зазор снизу
frS = 1.0;    // Толщина площадки
siS = 0.8;    // Толщина бортика вокруг сервы
siH = seH/2;  // Высота бортика вокруг сервы
frU = seH-0.5;    // Высота держателя уха
frSr = 6.0;   // Площадка справа
frSl = 6.0;   // Площадка слева
frSt = 3.0;   // Площадка сверху
frSb = 4.0;   // Площадка снизу
frRr = 3.0;    // Радиус скругления площадки
stS = 0.8;    // Ширина ребра
hT = 3;       // Тип отверстия
              // 0 - нет
              // 1 - сквозное по серве
              // 2 - круглое
              // 3 - квадратное
hR = 5;       // радиус скругления отверстия
hS = 2;       // отступ до отверстия

boW = (seWf-seW)/2+siS; // Ширина бонки фиксатора
boL = (seWf-seW)/2+siS; // Длина бонки фиксатора
boS = 2;      // Верх бонки фиксатора
boD = 1.5;    // Диаметр отв. бонки фиксатора

fiD = 2.0;    // Диаметр отв. фиксатора
fiS = 1.0;    // Толщина фиксатора

frW = seW+frSl+frSr;    // Ширина рамки
frL = seL+frSt+frSb;    // Длина рамки

%color("white")translate([-60,70,0])scale([0.2, 0.2, 0.02])surface(file = "size.png", center = true, invert = true);

eps = 0.002; $fn = $preview ? 24 : 72;

//stiffener(10,20,2);
frame();
translate([0,-frSb-boL/2-1,0])stopor();

module frame(){
  difference(){
    union(){
      translate([-frSl-seW/2,-frSb,0])
      linear_extrude(height=frS)offset(r=frRr)offset(r=-frRr)square([frW,frL]); // Frame
      translate([-seW/2-siS,-siS,seS])cube([seW+2*siS,seL+1*siS,siH]); // Border
      translate([-seWf/2-siS,seLf-siS,0])cube([seWf+2*siS,seFs+2*siS,frU+seS]); // Servo Foot
      translate([-seW/2-boW,seLf-boL,seS])bonk(boW,boW,boL,frU,boS); // bonk left
      translate([seW/2,seLf-boL,seS])bonk(boW,0,boL,frU,boS); // bonk right
    } // union
    translate([-seWf/2,seLf,seS])cube([seWf,seFs,seH+5]); // - servo foot
    if (hT == 1) { // - servo
      translate([-seW/2,0,-eps])cube([seW,seL,seH+5]);
    } else { // - servo
      translate([-seW/2,0,seS])cube([seW,seL,seH+5]);
    }
    if (hT == 2) { // - circle
      translate([0,seL/2,-0.1])cylinder(d=min(seW,seL)-2*hS,h=seH);
    } else if (hT == 3) { // - square
      translate([-seW/2+hS,hS,-0.1])linear_extrude(seH)offset(r=hR)offset(r=-hR)square([seW-2*hS,seL-2*hS]);
    }
    translate([-(seW+boW)/2,seLf-boL/2,siS+0.1])cylinder(d=boD,h=frU); // отв. бонка лев.
    translate([(seW+boW)/2,seLf-boL/2,siS+0.1])cylinder(d=boD,h=frU); // отв. бонка прав.
    if ((seWt == 2) || (seWt == 3)) { // Провода справа
      translate([seW/2+2*siS,seLw,seS+(seH-seWw)/2])rotate([0,-90,0])linear_extrude(seW/2)offset(r=seWr)offset(r=-seWr)square([seH,seWl]);
    }
    if ((seWt == 1) || (seWt == 3)) { // Провода слева
      mirror([1,0,0])translate([seW/2+2*siS,seLw,seS+(seH-seWw)/2])rotate([0,-90,0])linear_extrude(seW/2)offset(r=seWr)offset(r=-seWr)square([seH,seWl]);
    }
  }
  // Ribs side bottom
  translate([seW/2+siS,-siS,frS])stiffener(frSr-siS,siH-frS+seS,stS); // rb
  translate([-seW/2-siS,-siS+stS,frS])rotate([0,0,180])stiffener(frSl-siS,siH-frS+seS,stS); //lb
  // Ribs side middle
  translate([seW/2+siS,-siS+seL/2+stS/2,frS])stiffener(frSr-siS,siH-frS+seS,stS); //rm
  translate([-seW/2-siS,-siS+stS+seL/2+stS/2,frS])rotate([0,0,180])stiffener(frSl-siS,siH-frS+seS,stS); //lm
  // Ribs side top
  translate([seW/2+siS,-siS+seL,frS])stiffener(frSr-siS,siH-frS+seS,stS); //rt
  translate([-seW/2-siS,-siS+stS+seL,frS])rotate([0,0,180])stiffener(frSl-siS,siH-frS+seS,stS); //lt
  // Ribs bottom
  translate([-stS/2,-siS,frS])rotate([0,0,-90])stiffener(frSb-siS,siH-frS+seS,stS); // bm
  translate([seW/2,-siS,frS])rotate([0,0,-90])stiffener(frSb-siS,siH-frS+seS,stS); // br
  translate([-seW/2-siS,-siS,frS])rotate([0,0,-90])stiffener(frSb-siS,siH-frS+seS,stS);
}

module stopor(){
  difference(){
    hull(){
      translate([-(seW+boW)/2,0,0])cylinder(d=boL,h=fiS);
      translate([(seW+boW)/2,0,0])cylinder(d=boL,h=fiS);
    }
    translate([-(seW+boW)/2,0,-0.1])cylinder(d=fiD,h=fiS+0.2);
    translate([(seW+boW)/2,0,-0.1])cylinder(d=fiD,h=fiS+0.2);
  }
}
module bonk(w,o,l,h,s) {
  hull(){
    translate([o,l,0])cube([eps,eps,eps]);
    translate([0,0,h-s])union(){
      translate([w/2,l/2,0])resize([w,l,s])cylinder(d=1,h=1);
      translate([0,l/2,0])cube([w,l/2,s]);
      if (o > 0) {
        translate([w/2,0,0])cube([w/2,l,s]);
      } else {
        cube([w/2,l,s]);
      }
    }
  }
}
module stiffener(w,h,s) {
  hull(){
    cube([w,s,eps]);
    cube([eps,s,h]);
  }
}

```
S-SG90.scad:
```
// Параметры сервы
seW  = 22.8;  // В - Ширина сервы
seWf = 32.3;  // E - Ширина сервы по лапкам
seL  = 22.8;  // C - Высота сервы
seLa = 32.3;  // A - Высота сервы полная
seLf = 15.3;  // F - От низа сервы до лапки
seFs = 0.8;   // Толщина лапки сервы
seH  = 12.3;  // D - Толщина сервы
seLw = 3.0;   // От низа сервы до проводов
seWw = 6.0;   // Ширина проводов
seWl = 3.0;   // Толщина проводов
seWr = 0.5;   // скругление отверстия проводов
echo("SG-90");
```
S-ES08MA.scad:
```
// Параметры сервы
seW  = 24.1+0.2;  // В - Ширина сервы
seWf = 32.4+0.5;  // E - Ширина сервы по лапкам
seL  = 21.2;  // C - Высота сервы
seLa = 25.0;  // A - Высота сервы полная
seLf = 15.5;  // F - От низа сервы до лапки
seFs = 1.8;   // Толщина лапки сервы
seH  = 12.1;  // D - Толщина сервы
seLw = 2.5;   // От низа сервы до проводов
seWw = 5.0;   // Ширина проводов
seWl = 2.0;   // Толщина проводов
seWr = 0.5;   // скругление отверстия проводов
echo("ES08MA");
```


[User]
Hey, I'm looking to generate a customized tray design using OpenSCAD. I've been using a simple tray for my 3D printer leveling squares, but I need something more flexible. I've got a basic SCAD file that gives a simple tray, but I'd like to be able to customize it much more.

Here’s what I’m thinking:

**Basic Tray:**

*   **Shape:** Primarily a rectangular tray with walls and a base.
*   **Parameters:** I need to be able to specify the following parameters:
    *   `tray_inside_width`: The interior width of the tray.
    *   `tray_inside_depth`: The interior depth of the tray.
    *   `tray_inside_height`: The interior height of the tray (the depth of the tray's cavity).
    *   `tray_wall_thickness`: The thickness of the walls of the tray.
    *   `tray_floor_thickness`: The thickness of the base (floor) of the tray.

**Advanced Customization:**

*   **Rounded Corners:** I'd like to be able to add rounded corners to the tray, with an option to specify the radius of the rounded corners. Let’s call it `corner_radius`. There would be an option to turn corner rounding off, either by setting it to zero or with a boolean value called `round_corners`.
*   **Grid Dividers:** It would be great to have options to add grid dividers within the tray. These dividers would be equally spaced in both the X and Y directions.
    *   `grid_x_divisions`: The number of divisions in the X direction.
    *   `grid_y_divisions`: The number of divisions in the Y direction.
    *   `divider_thickness`: The thickness of the dividers.
    *   `divider_height`: The height of the dividers (likely the same as `tray_inside_height`, but it would be nice to have control over it)
*   **Text Label:** It would be awesome if I could add a text label embossed into the side of the tray.
    *   `label_text`: The text to be embossed.
    *   `label_font`: The font to be used for the text (default to "Liberation Sans").
    *   `label_size`: The size of the text.
    *   `label_depth`: How deep the text should be embossed into the tray
    *   `label_x_offset`: Where on the tray the text should be located in the x axis.
    *   `label_y_offset`: Where on the tray the text should be located in the y axis.
    *   `label_z_offset`: Where on the tray the text should be located in the z axis.
    *   `label_side`: A enum to determine which side of the tray to place the label, options will be "front", "back", "left", "right".
*   **Units:** I'd like the design to work in mm, which is what I'm used to.

**Libraries:**

I'm okay with you using the BOSL2 library for anything you find useful.

Basically, I want a really flexible tray generator that can handle everything from a basic container to a custom-divided parts tray with a label. Could you help me set this up in OpenSCAD?

[Assistant]
Customizer_for_Tray_v0.12.scad:
```
/*	Title:	Customizer for Tray
*
*	Version:	v0.12
*
*	Author:	Cygnyet
*
*	Date:		2022-04-16
*
*	GitHub: 	https://github.com/cygnyet/_________
*
*	Thingiverse:	http://thingiverse.com/thing:5155178
*
*	Keywords:	3D printer test square holder, tray, organizer, part organizer, part tray
*
*/

/*
	VERSION HISTORY
		- v0.11 Initial Version
		- v0.12 Modified variable names

*/

/* TODO
		- ________
*/

//CUSTOMIZER VARIABLES

/* [Tray Dimensions] */
TrayInsideWidth=110; 	//Left to Right
TrayInsideDepth=110;		//Front to Back
TrayInsideHeight=3;		//Bottom to Top

TrayWallThickness=3;
TrayFloorThickness=3;

//SCAD CODE

difference(){
	color("blue")	//"Outside" cube
	cube([TrayInsideWidth+(TrayWallThickness*2),
			TrayInsideDepth+(TrayWallThickness*2),
			TrayInsideHeight+TrayFloorThickness],
			center = true);

	translate([0,0,TrayFloorThickness/2])
		color("red")	//"Inside" cube
		cube([TrayInsideWidth,
				TrayInsideDepth,
				TrayInsideHeight],
				center = true);
}

```


[User]
Hey, I'm looking to get a customized filament swatch generator built in OpenSCAD. I've seen some good ones out there, but they don't quite have the features I need, and some are overly complex. I've included the code for a previous design that I've used which gives a good idea of what I'm going for.

Here's a breakdown of what I need:

**Core Features:**

*   **Basic Swatch Shape:** A rectangular swatch with rounded corners. The dimensions (width, height, thickness, and corner radius) should be fully parametric.
*   **Text:**  I need multiple lines of text that can be customized. The first line should be larger and placed near the top, while the second and third lines should be smaller, near the bottom, and placed left and right respectively. I also want to have a smaller text field on the top edge of the swatch, which could be useful for quickly identifying the material. There should be options for font selection (from the Google Fonts library or other system fonts), size, and text alignment for all text fields. The text can either be raised or embossed.
*   **Hole:** A small hole for a ball chain is needed, and its position and diameter should be parametric. The default should be no hole.
*   **Side Indents:** Small half-cylinder indents on the sides to help remove the swatch from a box, also parametric in radius, and defaults to no indents.
*   **Advanced Test Patterns:** An optional feature to enable some test patterns, such as circles, thin walls, overhangs, and bridges. The number of test circles would need to be parametric.

**Test Features:**

*   **Thickness Steps:** The main feature is a series of thickness steps, going from a negative depth, for a bridge structure, to progressively thicker layers, I should be able to specify the thickness of each step. The number of steps and the thicknesses for each should be user-definable.
*   **Step Text:**  I need the option to display the thickness at each step, in mm. Font sizes, and format for the numbers (leading zeros, trailing zeros) should be configurable for these step labels and should also be able to be rotated 90 degrees.

**Parameters:**

These are the parameters I'd like to be able to control from the customizer:

*   `textstring1`: Top text line (string).
*   `textsize_upper`: Font size for the top text line (mm).
*   `textstring2`: Left lower text line (string).
*   `textstring3`: Right lower text line (string).
*    `textsize_lower`: Font size for the lower text lines (mm).
*   `texttop_predefined`: Drop-down menu to select predefined material type for top text.
*   `texttop_configurable`:  Custom text to override material type on the top.
*   `fontname`: Font name (string, from Google Fonts or system fonts).
*   `text_type`: "embossed" or "raised".
*   `test_patterns`: "enabled" or "disabled" to turn on or off the advanced test pattern.
*   `test_circles`: Number of advanced test circles (integer).
*   `steps_thickness`: List of thickness values for each step (mm, may include negative values).
*    `steps_text`: "enabled" or "disabled" to show the thickness of each step.
*   `steps_textsize`: Font size for the step thickness numbers (mm).
*    `steps_textheight`: height of text for thickness steps.
*   `steps_text_format`: Format of numbers on the steps. "enforce leading zero 0._", "enforce leading and trailing zero 0.0", or "no enforcing _._".
*    `steps_text_rotate`: "yes" or "no" to rotate the numbers on the thickness steps.
*   `w`: Swatch width (mm).
*   `h`: Swatch height (mm).
*   `th`: Swatch thickness (mm).
*   `round`: Swatch roundness radius (mm).
*   `wall`: Border size around the text (mm).
*   `linesep`: Text line separation (mm).
*   `r_hole`: Hole radius (mm).
*   `r_indent`: Side indent radius (mm).

I'd also like to use some kind of modular code that is easy to maintain and change. This shouldn't be too hard for you right?

Thanks!

[Assistant]
Customizer_for_Filament_Swatch.scad:
```
/*	Title:	Customizer for Filament Swatch
*
*	Version:	v0.10
*
*	Author:		Cygnyet
*
*	Date:		2021-12-04
*
*	GitHub: 	https://github.com/cygnyet/_________
*
*	Thingiverse:	https://www.thingiverse.com/thing:5155242
*
*	Keywords:	filament swatch
*
*	Remix of:  	https://www.thingiverse.com/thing:3346069
*
*	Note:  		The .json file of the same name as this .scad file is needed to apply the customizations that make
*				the filament swatch slimmer to better fit on a ball chain.
*
*/

/*
	VERSION HISTORY
		- v0.10 Initial Version

*/

/* TODO
		- ________
*/

//upper text line
textstring1="Basicfil PLA";

//Set font size in mm
textsize_upper=5;

//Right lower text line, e.g. for the color of the filament
textstring3="Orange";

//Left lower text line, e.g. for layer thickness, print temperature
textstring2=".2  210C";

//Set font size in mm
textsize_lower=4; //TS decreased text size

//Top text (on the top edge, to quickly find the material type).
texttop_predefined="P L A";//[-,P L A, A B S, P E T G, N y l o n, H I P S, P E E K, T P U, P O M, P V A, P P, A S A, P C, U L T E M, R e s i n, P A, n G E N, H T, P H A, A M 1 8 0 0,A M 3 3 0 0,N G E N _ F L E X,P U R,F L 6 0 0 0]

//Own top text: Use text here if you want to override the drop-down right above. Note that you can increase readability by using all capital letters and by spacing the letters out with a space each: "P L A"
texttop_configurable="";

//Font name, e.g. "Pathway Gothic One,", "Voltaire", "Oswald", depends on what is available on the platform. In the customizer,choose from about 700 Google Fonts. For an overview please refer: http://www.google.com/fonts. I like the Aldo font and use it with OpenSCAD on my PC.
fontname="Aldo";//[Pathway Gothic One,Voltaire,Oswald,"Oswald:style=Bold","PT Sans Narrow:style=Bold",Archivo,ABeeZee,Abel,Abril Fatface,Aclonica,Acme,Actor,Adamina,Advent Pro,Aguafina Script,Akronim,Aladin,Aldrich,Alef,Alegreya,Alegreya Sans,Alegreya Sans SC,Alegreya SC,Alex Brush,Alfa Slab One,Alice,Alike,Alike Angular,Allan,Allerta,Allerta Stencil,Allura,Almendra,Almendra Display,Almendra SC,Amarante,Amaranth,Amatic SC,Amethysta,Amiri,Anaheim,Andada,Andika,Angkor,Annie Use Your Telescope,Anonymous Pro,Antic,Antic Didone,Antic Slab,Anton,Arapey,Arbutus,Arbutus Slab,Architects Daughter,Archivo Black,Archivo Narrow,Arimo,Arizonia,Armata,Artifika,Arvo,Asap,Asset,Astloch,Asul,Atomic Age,Aubrey,Audiowide,Autour One,Average,Average Sans,Averia Gruesa Libre,Averia Libre,Averia Sans Libre,Averia Serif Libre,Bad Script,Balthazar,Bangers,Basic,Battambang,Baumans,Bayon,Belgrano,Belleza,BenchNine,Bentham,Berkshire Swash,Bevan,Bigelow Rules,Bigshot One,Bilbo,Bilbo Swash Caps,Bitter,Black Ops One,Bokor,Bonbon,Boogaloo,Bowlby One,Bowlby One SC,Brawler,Bree Serif,Bubblegum Sans,Bubbler One,Buda,Buenard,Butcherman,Butterfly Kids,Cabin,Cabin Condensed,Cabin Sketch,Caesar Dressing,Cagliostro,Calligraffitti,Cambay,Cambo,Candal,Cantarell,Cantata One,Cantora One,Capriola,Cardo,Carme,Carrois Gothic,Carrois Gothic SC,Carter One,Caudex,Cedarville Cursive,Ceviche One,Changa One,Chango,Chau Philomene One,Chela One,Chelsea Market,Chenla,Cherry Cream Soda,Cherry Swash,Chewy,Chicle,Chivo,Cinzel,Cinzel Decorative,Clicker Script,Coda,Coda Caption,Codystar,Combo,Comfortaa,Coming Soon,Concert One,Condiment,Content,Contrail One,Convergence,Cookie,Copse,Corben,Courgette,Cousine,Coustard,Covered By Your Grace,Crafty Girls,Creepster,Crete Round,Crimson Text,Croissant One,Crushed,Cuprum,Cutive,Cutive Mono,Damion,Dancing Script,Dangrek,Dawning of a New Day,Days One,Dekko,Delius,Delius Swash Caps,Delius Unicase,Della Respira,Denk One,Devonshire,Dhurjati,Didact Gothic,Diplomata,Diplomata SC,Domine,Donegal One,Doppio One,Dorsa,Dosis,Dr Sugiyama,Droid Sans,Droid Sans Mono,Droid Serif,Duru Sans,Dynalight,Eagle Lake,Eater,EB Garamond,Economica,Ek Mukta,Electrolize,Elsie,Elsie Swash Caps,Emblema One,Emilys Candy,Engagement,Englebert,Enriqueta,Erica One,Esteban,Euphoria Script,Ewert,Exo,Exo 2,Expletus Sans,Fanwood Text,Fascinate,Fascinate Inline,Faster One,Fasthand,Fauna One,Federant,Federo,Felipa,Fenix,Finger Paint,Fira Mono,Fira Sans,Fjalla One,Fjord One,Flamenco,Flavors,Fondamento,Fontdiner Swanky,Forum,Francois One,Freckle Face,Fredericka the Great,Fredoka One,Freehand,Fresca,Frijole,Fruktur,Fugaz One,Gabriela,Gafata,Galdeano,Galindo,Gentium Basic,Gentium Book Basic,Geo,Geostar,Geostar Fill,Germania One,GFS Didot,GFS Neohellenic,Gidugu,Gilda Display,Give You Glory,Glass Antiqua,Glegoo,Gloria Hallelujah,Goblin One,Gochi Hand,Gorditas,Goudy Bookletter 1911,Graduate,Grand Hotel,Gravitas One,Great Vibes,Griffy,Gruppo,Gudea,Gurajada,Habibi,Halant,Hammersmith One,Hanalei,Hanalei Fill,Handlee,Hanuman,Happy Monkey,Headland One,Henny Penny,Herr Von Muellerhoff,Hind,Holtwood One SC,Homemade Apple,Homenaje,Iceberg,Iceland,IM Fell Double Pica,IM Fell Double Pica SC,IM Fell DW Pica,IM Fell DW Pica SC,IM Fell English,IM Fell English SC,IM Fell French Canon,IM Fell French Canon SC,IM Fell Great Primer,IM Fell Great Primer SC,Imprima,Inconsolata,Inder,Indie Flower,Inika,Irish Grover,Istok Web,Italiana,Italianno,Jacques Francois,Jacques Francois Shadow,Jim Nightshade,Jockey One,Jolly Lodger,Josefin Sans,Josefin Slab,Joti One,Judson,Julee,Julius Sans One,Junge,Jura,Just Another Hand,Just Me Again Down Here,Kalam,Kameron,Kantumruy,Karla,Karma,Kaushan Script,Kavoon,Kdam Thmor,Keania One,Kelly Slab,Kenia,Khand,Khmer,Khula,Kite One,Knewave,Kotta One,Koulen,Kranky,Kreon,Kristi,Krona One,La Belle Aurore,Laila,Lakki Reddy,Lancelot,Lateef,Lato,League Script,Leckerli One,Ledger,Lekton,Lemon,Liberation Sans,Libre Baskerville,Life Savers,Lilita One,Lily Script One,Limelight,Linden Hill,Lobster,Lobster Two,Londrina Outline,Londrina Shadow,Londrina Sketch,Londrina Solid,Lora,Love Ya Like A Sister,Loved by the King,Lovers Quarrel,Luckiest Guy,Lusitana,Lustria,Macondo,Macondo Swash Caps,Magra,Maiden Orange,Mako,Mallanna,Mandali,Marcellus,Marcellus SC,Marck Script,Margarine,Marko One,Marmelad,Martel Sans,Marvel,Mate,Mate SC,Maven Pro,McLaren,Meddon,MedievalSharp,Medula One,Megrim,Meie Script,Merienda,Merienda One,Merriweather,Merriweather Sans,Metal,Metal Mania,Metamorphous,Metrophobic,Michroma,Milonga,Miltonian,Miltonian Tattoo,Miniver,Miss Fajardose,Modak,Modern Antiqua,Molengo,Molle,Monda,Monofett,Monoton,Monsieur La Doulaise,Montaga,Montez,Montserrat,Montserrat Alternates,Montserrat Subrayada,Moul,Moulpali,Mountains of Christmas,Mouse Memoirs,Mr Bedfort,Mr Dafoe,Mr De Haviland,Mrs Saint Delafield,Mrs Sheppards,Muli,Mystery Quest,Neucha,Neuton,New Rocker,News Cycle,Niconne,Nixie One,Nobile,Nokora,Norican,Nosifer,Nothing You Could Do,Noticia Text,Noto Sans,Noto Serif,Nova Cut,Nova Flat,Nova Mono,Nova Oval,Nova Round,Nova Script,Nova Slim,Nova Square,NTR,Numans,Nunito,Odor Mean Chey,Offside,Old Standard TT,Oldenburg,Oleo Script,Oleo Script Swash Caps,Open Sans,Open Sans Condensed,Oranienbaum,Orbitron,Oregano,Orienta,Original Surfer,Over the Rainbow,Overlock,Overlock SC,Ovo,Oxygen,Oxygen Mono,Pacifico,Paprika,Parisienne,Passero One,Passion One,Patrick Hand,Patrick Hand SC,Patua One,Paytone One,Peddana,Peralta,Permanent Marker,Petit Formal Script,Petrona,Philosopher,Piedra,Pinyon Script,Pirata One,Plaster,Play,Playball,Playfair Display,Playfair Display SC,Podkova,Poiret One,Poller One,Poly,Pompiere,Pontano Sans,Port Lligat Sans,Port Lligat Slab,Prata,Preahvihear,Press Start 2P,Princess Sofia,Prociono,Prosto One,PT Mono,PT Sans,PT Sans Caption,PT Sans Narrow,PT Serif,PT Serif Caption,Puritan,Purple Purse,Quando,Quantico,Quattrocento,Quattrocento Sans,Questrial,Quicksand,Quintessential,Qwigley,Racing Sans One,Radley,Rajdhani,Raleway,Raleway Dots,Ramabhadra,Ramaraja,Rambla,Rammetto One,Ranchers,Rancho,Ranga,Rationale,Ravi Prakash,Redressed,Reenie Beanie,Revalia,Ribeye,Ribeye Marrow,Righteous,Risque,Roboto,Roboto Condensed,Roboto Slab,Rochester,Rock Salt,Rokkitt,Romanesco,Ropa Sans,Rosario,Rosarivo,Rouge Script,Rozha One,Rubik Mono One,Rubik One,Ruda,Rufina,Ruge Boogie,Ruluko,Rum Raisin,Ruslan Display,Russo One,Ruthie,Rye,Sacramento,Sail,Salsa,Sanchez,Sancreek,Sansita One,Sarina,Sarpanch,Satisfy,Scada,Scheherazade,Schoolbell,Seaweed Script,Sevillana,Seymour One,Shadows Into Light,Shadows Into Light Two,Shanti,Share,Share Tech,Share Tech Mono,Shojumaru,Short Stack,Siemreap,Sigmar One,Signika,Signika Negative,Simonetta,Sintony,Sirin Stencil,Six Caps,Skranji,Slabo 13px,Slabo 27px,Slackey,Smokum,Smythe,Sniglet,Snippet,Snowburst One,Sofadi One,Sofia,Sonsie One,Sorts Mill Goudy,Source Code Pro,Source Sans Pro,Source Serif Pro,Special Elite,Spicy Rice,Spinnaker,Spirax,Squada One,Sree Krushnadevaraya,Stalemate,Stalinist One,Stardos Stencil,Stint Ultra Condensed,Stint Ultra Expanded,Stoke,Strait,Sue Ellen Francisco,Sunshiney,Supermercado One,Suranna,Suravaram,Suwannaphum,Swanky and Moo Moo,Syncopate,Tangerine,Taprom,Tauri,Teko,Telex,Tenali Ramakrishna,Tenor Sans,Text Me One,The Girl Next Door,Tienne,Timmana,Tinos,Titan One,Titillium Web,Trade Winds,Trocchi,Trochut,Trykker,Tulpen One,Ubuntu,Ubuntu Condensed,Ubuntu Mono,Ultra,Uncial Antiqua,Underdog,Unica One,UnifrakturCook,UnifrakturMaguntia,Unkempt,Unlock,Unna,Vampiro One,Varela,Varela Round,Vast Shadow,Vesper Libre,Vibur,Vidaloka,Viga,Voces,Volkhov,Vollkorn,VT323,Waiting for the Sunrise,Wallpoet,Walter Turncoat,Warnes,Wellfleet,Wendy One,Wire One,Yanone Kaffeesatz,Yellowtail,Yeseva One,Yesteryear,Zeyada]

//Text raised or embossed
text_type="embossed";//[embossed,raised]

//Advanced test swatch patterns active? If you want a simple swatch, set this to disabled
test_patterns="enabled";//[enabled,disabled]

//Number of advanced test circles?
test_circles=5;//[0:5]

// Thickness progression of the 'layers' is taken from https://www.thingiverse.com/thing:3067940 : 0.2, 0.4, 0.6, 0.8, 1.0, 1.6, without the 2.25 mm. If you put a negative number here, it will generate a "bridge" structure of the respective thickness.
steps_thickness=[-.6, 0.2, 0.4, 0.6, 0.8, 1.0, 1.6];

//Switch on or off the text to indicate the thickness on the steps
steps_text="enabled";//[enabled,disabled]

//Set font size on the thickness steps in mm. These show the step size below (0.2, 0.4, ...).
steps_textsize=5;

//textheight on thickness steps
steps_textheight=.4;

//Set wether the leading zeroes should be visible on the thickness steps.
steps_text_format="enforce leading zero 0._";//[enforce leading zero 0._,enforce leading and trailing zero 0.0,no enforcing _._]

//rotate the text on the steps by 90 degree?
steps_text_rotate="no";//[yes,no]

//Swatch size. This is compatible to Jaxels box. Other sizes are e.g. 75,40,3. First is swatch Width
w=79.5;

//Swatch Height, should be >=26mm. If you go below 26mm, you will have to reduce the values wall=1 and round=2
h=30;

//Swatch Thickness, should be >=.18mm.
th=2.2;

//Swatch roundness radius
round=5;

//Border size around the text
wall=2;

//Text line separation
linesep=1.4;

//hole radius. Set to 0 to have no hole
r_hole=2.1; //TS changed from integer to decimal

//side indents to take out the swatch from the box. set to 0 to have no indents
r_indent=2;

/* [Hidden] */
//-------------------------------------------------------------------------------

/*
Configurable Filament Swatch
Version U, June 2019
Written by MC Geisler (mcgenki at gmail dot com)

This is a swatch with configurable text.

Have fun!

License: Attribution 4.0 International (CC BY 4.0)

You are free to:
    Share - copy and redistribute the material in any medium or format
    Adapt - remix, transform, and build upon the material
    for any purpose, even commercially.
*/

texttop=(texttop_configurable==""?texttop_predefined:texttop_configurable);

$fn=48;

font_recessed=1.5;

//***USE THIS ONLY IF YOU KNOW WHAT YOU ARE DOING***: In case your steps are not in the right heigt, add here some little bit of thickness to your step height. This might help e.g. if you use cura to slice. Its not needed for Simplify3D or Slic3r.
step_thickness_correction=0;

module rounded_square(x,y,z,r)
{
    delta=r;

    hull()
    {
        translate([r,r,0]) cylinder(h=z,r=r);
        translate([-r+x,r,0]) cylinder(h=z,r=r);
        translate([r,-r+y,0]) cylinder(h=z,r=r);
        translate([-r+x,-r+y,0])cylinder(h=z,r=r);
    }
}

module rounded_biteoff_square(x,y,z,r)
{
    delta=r;

    difference()
    {
        translate([-delta,0,0])
            cube([x+2*r,y,z]);

        translate([-2*r,0,0])
        hull()
        {
            translate([r,r,-.1]) cylinder(h=z+1,r=r);
            translate([r,-r+y,-.1]) cylinder(h=z+1,r=r);
        }

        translate([2*r,0,0])
        hull()
        {
            translate([-r+x,-r+y,-.1])cylinder(h=z+1,r=r);
            translate([-r+x,r,-.1]) cylinder(h=z+1,r=r);
        }
    }
}

module write_text_on_top(x,y,z,dist)
{
            //text on top
        fontdepth=.6;
        translate([x-dist,y-fontdepth,z-.2])
        //translate([ w-2*wall,h-wall-textsize*(1+linesep),height ])
            rotate([-90,0,0])
                linear_extrude(height=fontdepth*2,convexity = 10)
                    text(texttop,size=z-.2,font="Open Sans:style=Bold",halign="right",valign="bottom");
}

module rounded_square_test_pattern(x,y,z,r)
{
    delta=r;
    r3=z/2;
    r2=r;

    difference()
    {
        hull()
        {
            //bottom left cylindrical/round
            translate([r,r,0]) cylinder(h=z,r=r);

            //bottom right test flattened/round
//            translate([-r2+x-delta,r2,0]) cylinder(h=z,r=r2);
//            translate([-r2+x,r2+delta,0]) cylinder(h=z,r=r2);
//            translate([-r2+x,r2,z/2])
//                scale([r2,r2,z/2])
//                    sphere(r=1);
            translate([-r+x,r,0]) cylinder(h=z,r=r);

            //upper left ball/rounded
            /// translate([r,-r+y,0]) cylinder(h=z,r=r);
            translate([r3,-r3+y,r3]) scale([r3,r3,z/2]) sphere(r=1);

            //upper right corner
            r_corner=.1;
            translate([-r_corner+x,-r_corner+y,0]) cylinder(h=z,r=r_corner);
        }

        for (i = [0:test_circles])
        {
            //circle top left with round test pattern
            translate([wall+r_hole+r_hole*2.5*i,h-wall-r_hole,th/2-.1])
                cylinder(r=r_hole, h=th+.2, center=true);
        }

        //cuts
        cutin=wall*1.2;
        cutwide=1;
        wallwide_from=.5;
        wallwide_step=.1;
        wallwide_number=5;
        for(i=[0:wallwide_number-1])
        {
            wallwide=wallwide_from+wallwide_step*i;
            translate([x-r-r2-(cutwide+wallwide)*i,-.1,-.1])
                cube([cutwide,cutin+.1,z+.2]);
        }

        //overhang
        i=wallwide_number-1;
        short_x=(x-r-r2-(cutwide+wallwide_from+wallwide_step*i)*i);
        translate([0,-.01,-z])
            rotate([0,-asin(2*z/short_x),0])
                cube([short_x,wall*1.2,z]);

        //write_text_on_top(x,y,z);
    }
}

module rounded_square_test_pattern_raised(x,y,z,r,text_type, test_patterns)
{
    //sphere test
    translate([wall+r_hole+r_hole*2.5,h-wall-r_hole,th/2])
        intersection()
        {
            cylinder(r=r_hole, h=th, center=true);
            translate([0,0,-r_hole+th/2])
                sphere(r=r_hole);
        }

    //cylinder/hole test
    r_testhole=.5;
    r_testcylinder=r_testhole+.5;
    translate([wall+r_hole+r_hole*5,h-wall-r_hole,th/2])
        difference()
        {
            union()
            {
                cylinder(r=r_testcylinder, h=th, center=true);
                translate([-r_testhole,-r_hole,-th/2])
                    cube([r_testhole*2,r_hole*2,.2],center=false);
                if (text_type=="embossed")
                    translate([-r_hole,-r_testhole,th/2-.2])
                        cube([r_hole*2,r_testhole*2,.2],center=false);
            }
            cylinder(r=r_testhole, h=th+.2, center=true);
        }

    //overhang test
    bridge_h=.2;
    bridge_pos=(text_type=="raised"?th-font_recessed-bridge_h:th/2) ;
    translate([wall+r_hole+r_hole*7.5,h-wall-r_hole,bridge_pos])
            translate([-r_hole/2,-r_hole,0])
                cube([r_hole,r_hole*2,bridge_h],center=false);

    //hanging cylinder test
    testradius=(z-.3)/2;
    translate([wall+r_hole+r_hole*10, h-wall-r_hole*.5, th-testradius])
                rotate([90,0,0])
                    cylinder(r=testradius,h=r_hole*1.7,center=false);

     //pyramid test
    testpyramid=z-.3;
    biggerpyramid=1;
    translate([wall+r_hole+r_hole*12.5, h-wall-r_hole*2, th-z/2])
        rotate([-90,45,0])
            cylinder(d1=z*sqrt(2), d2=0, h=testpyramid, $fn=4);
    translate([wall+r_hole+r_hole*12.5, h-wall, th-z/2])
        intersection()
        {
            rotate([90,0,0])
                cylinder(d1=z*sqrt(2), d2=0, h=testpyramid*1, $fn=4);
            cube([3*z,3*z,z],center=true);
        }

    //6
//    testwidth=z-.5;
//    translate([wall+r_hole+r_hole*15, h-wall-r_hole*1.4, th-testwidth/2])
//        cube([testwidth,r_hole*2,testwidth],center=true);

}

module textlines(height)
{
    translate([ w-wall,h-wall-textsize_upper,height ])
        linear_extrude(height=th,convexity = 10)
            text(textstring1,size=textsize_upper,font=fontname,halign="right",valign="baseline");

    translate([ wall,h-wall-(textsize_upper+textsize_lower*linesep),height ])
        linear_extrude(height=th,convexity = 10)
                text(textstring2,size=textsize_lower,font=fontname,halign="left",valign="baseline");

    translate([ w-wall,h-wall-(textsize_upper+textsize_lower*linesep),height ])
        linear_extrude(height=th,convexity = 10)
                text(textstring3,size=textsize_lower,font=fontname,halign="right",valign="baseline");
}

/**** substr ****

[str] substr(`str`, `[pos]`, `[len]`)
Returns a substring of a string.

Arguments:
- [str] `str`: The original string.
- [int] `pos` (optional): The substring position (0 by default).
- [int] `len` (optional): The substring length (string length by default).

Usage:
str = "OpenScad is a free CAD software.";
echo(substr(str, 12)); // "a free CAD software."
echo(substr(str, 12, 10)); // "a free CAD"
echo(substr(str, len=8)); // or substr(str, 0, 8); // "OpenScad"
*/

function substr(str, pos=0, len=-1, substr="") =
	len == 0 ? substr :
	len == -1 ? substr(str, pos, len(str)-pos, substr) :
	substr(str, pos+1, len-1, str(substr, str[pos]));

function no_leading_zero(strng) =
	strng == undef?
		undef
	: strng == ""?
		""
	: strng[0] == "0"?
        substr(strng, pos=1, len=-1, substr="")
    : strng;

function str_configurable_zero(number) =
	steps_text_format=="enforce leading zero 0._"?
		str(abs(number))
	: steps_text_format=="enforce leading and trailing zero 0.0"?
		 (round(number)==number? substr(str(abs(number+.01)), pos=0, len=len(str(abs(number+.01)))-1, substr=""):str(abs(number)))
	: no_leading_zero(str(abs(number)));
module swatch(text_type, test_patterns)
{
    //wall thickness around step area
    stepareadistance=(test_patterns=="enabled"?1.5:1);
    //height of the text field -> needs more space if raised text
    font_h= (text_type=="raised"?textsize_lower*.2 : 1)+textsize_upper + linesep*textsize_lower;
    //step area height
    steparea_h=h-(stepareadistance+1)*wall-font_h-wall;
    //if raised text, what is the area to recess so the raised text goes on top
    fontarea_h=font_h+wall;

    difference()
    {
        if(test_patterns=="enabled")
            rounded_square_test_pattern(w,h,th,round);
        else
            rounded_square(w,h,th,round);

        write_text_on_top(w,h,th,(test_patterns=="enabled"?wall:round));

        //hole to hang
        translate([wall+r_hole+2,h-wall-r_hole-1,th/2])	//TS moved hole X+2, Y-1
            cylinder(r=r_hole, h=th*2, center=true);

        for (i = [0:len(steps_thickness)-1])
        {
            steparea_w=(w-(2*stepareadistance)*wall)/len(steps_thickness)*(i+1);

            translate([ wall*stepareadistance,wall*stepareadistance,(steps_thickness[i]<0?-.1:steps_thickness[i]+step_thickness_correction) ])
            {
                rounded_square(steparea_w,steparea_h,th*2,round-wall);
            }
        }

        //handles
        handle_groove_out=r_indent/2;
        translate([-handle_groove_out,h-wall-r_indent,th/2])
            cylinder(r=r_indent, h=th*2, center=true);
        translate([w+handle_groove_out,h-wall-r_indent,th/2])
            cylinder(r=r_indent, h=th*2, center=true);

        if (text_type=="embossed")
        {
            //text
            textlines(th-font_recessed);
        }
        else
        {//xxx     if(text_type!="embossed")
            translate([ wall/2,h-wall/2-fontarea_h,th-font_recessed ])
                rounded_square(w-wall,fontarea_h,th,round-wall/2);
        }
    }

    //thickness numbers & bridges
    textheight=steps_textheight;//th-steps_thickness[len(steps_thickness)-1];
    textheight_bridge=steps_textheight;

    one_steparea_w=(w-2*stepareadistance*wall)/len(steps_thickness);
    for (i = [0:len(steps_thickness)-1])
    {
        steparea_w=one_steparea_w*(i+1);
        translate([ wall*stepareadistance+ steparea_w,0,0 ])
        {
            if (steps_text=="enabled")
            {
                rotate([0,0,(steps_text_rotate=="yes"?90:0)])
                translate([ (steps_text_rotate=="yes"?one_steparea_w/3:-one_steparea_w/10)
                            ,(steps_text_rotate=="yes"?wall*.5:wall*2)
                            ,(steps_thickness[i]<0?th-textheight_bridge:steps_thickness[i]+step_thickness_correction)])
                    linear_extrude(height=(steps_thickness[i]<0?textheight_bridge+step_thickness_correction:textheight),convexity = 10)
                            text(str_configurable_zero(steps_thickness[i]),size=steps_textsize,font=fontname,
                                halign=(steps_text_rotate=="yes"?"left":"right"),valign="baseline");
            }

            if (steps_thickness[i]<0) // bridge
            {
                gap=round/3;
                translate([ -one_steparea_w+gap,wall*stepareadistance,th-(-steps_thickness[i])-textheight_bridge ])
                    cube([one_steparea_w-gap, steparea_h, -steps_thickness[i]+step_thickness_correction]);
                    //rounded_biteoff_square(one_steparea_w-gap, steparea_h, -steps_thickness[i],round-wall);
            }
        }
    }

    if(text_type!="embossed")
    {
        textlines(0);
    }

    if(test_patterns=="enabled")
    {
        rounded_square_test_pattern_raised(w,h,th,round,text_type, test_patterns);
    }

}

Debug="false";
if (Debug!="true")
{
    swatch(text_type, test_patterns);
}
else
{
    swatch("raised", "disabled");
    translate([w+3,0,0]) swatch("raised", "enabled");
    translate([0,h+3,0]) swatch("embossed", "disabled");
    translate([w+3,h+3,0]) swatch("embossed", "enabled");
}

```


[User]
Hey, I'm looking to create a custom cover for the wiring channel on V-slot aluminum extrusions. I need something that will slide into the channel and leave a space to run wires inside, without interfering with the wheels that usually run in the same channel. I'd like to be able to control the length of the cover, the width of the cable channel, and its height. Also, it'd be nice to have a tolerance parameter to adjust how tightly it fits.

I'd like the design to be parametric, so I can adjust these values and generate custom versions. I think OpenSCAD is the right tool for this, but I'm not experienced enough to write the code myself from scratch. I've tried to draft the model out myself by hand and this is what I'm going for:

*   **Cover Length:** I need to be able to set the overall length of the cover.
*   **Cable Width:** This parameter should control the width of the channel for the wiring.
*   **Cable Height:** The height of the wiring channel, so wires don't get pinched.
*   **V-Slot Tolerance:** A tolerance value to adjust how snug the cover fits in the V-slot. A larger tolerance will make it looser.
*   **V-Slot Shape:** I've done some research, and I think the main geometry can be derived from the common V-Slot profile, but I'm hoping the code can automatically adapt to the V-Slot size. Assume common V-Slot sizing, I'm not working with custom V-Slot profiles. I think the standard inner channel is 11 mm wide and the depth is about 4.25 mm at the very top where the walls are thickest, tapering down from there. I think the very bottom of the channel is about 1.589 mm off of the bottom of the V-Slot.
*   **Slide-in Fit:** The cover needs to slide into the V-slot channel with the specified tolerance.
*   **Non-interference with Wheels:** It should not interfere with standard V-slot rollers.
*   **Simple shape**: The design is simple and uses polygon in a 2D view, then uses a linear_extrude to give it length.

I think it would help if all of the parameters were labeled in the code so that they can be easily changed, and I'd like to include some comments.

Could you write the OpenSCAD file(s) for me that I need to do this? I don't need to use any libraries for this geometry, I think just simple built-in primitives and operations will work.

[Assistant]
VSlotWiringChannel.scad:
```
//V-Slot Extrusion Wiring Channel Cover [Customizable]
//(c)2021 qwertymodo

// preview[view:south, tilt:top diagonal]

// Cover Length
coverLength = 100; // [1:1:500]

// Cable Width
cableWidth = 7.0; //[1.0:0.1:9.0]

// Cable Thickness (Height)
cableHeight = 2.0; // [1.0:0.1:3.5]

// V-Slot Tolerance (Larger = Looser Fit)
slotTolerance = 0.25; // [0.0:0.05:0.5]

///////////////////////////////////////////////////

/* [Hidden] */
coverWidth = 11.0 - slotTolerance;
coverTop = 4.25;
coverHeight = coverTop - (slotTolerance/2);
baseHeight = 1.589 + slotTolerance - sqrt(slotTolerance*slotTolerance/2);
slotTop = min(((coverWidth-cableWidth-slotTolerance)/2)+baseHeight,coverHeight);

color("red")
translate([0,coverLength,-slotTolerance/2])
rotate([90,0,0])
linear_extrude(height = coverLength)
polygon([[coverWidth/2,slotTolerance/2],
        [coverWidth/2,baseHeight],
        [(coverWidth/2)-(slotTop-baseHeight),slotTop],
        [cableWidth/2,slotTop],
        [cableWidth/2,coverHeight-cableHeight],
        [-cableWidth/2,coverHeight-cableHeight],
        [-cableWidth/2,slotTop],
        [-((coverWidth/2)-(slotTop-baseHeight)),slotTop],
        [-coverWidth/2,baseHeight],
        [-coverWidth/2,slotTolerance/2]]);
```


[User]
Hey, I'm looking to design some LED strip channels for standard 5050 LED strips, and I need them to be parametric. I'd like to be able to adjust things like the width of the strip, wall thickness, overall height, length of straight segments, corner lengths, and endcap lengths. Also, I want to be able to choose whether to have interlocks between segments, and also whether to include screw holes or not. I'd like to have a way to generate straight track pieces, male endcaps, female endcaps, inside and outside corners, and covers for the channels.

The strip dimensions I'm using are about 10.1mm wide and 0.6mm thick, but I want to be able to adjust these. I'm aiming for a channel height of about 6mm and walls that are 3mm thick, but these should also be adjustable. The straight segments I want to be about 200mm long, corners about 12.7mm, and endcaps around 25.4mm, but they should be customizable lengths. The screw holes are just small 1mm cylinder for tie-downs. It would be great if the cover would fit tightly in the channel without being super hard to remove.

Oh yeah, and a tool to help install the LED strip would be awesome too, something like a rectangle a bit longer than an endcap and with a height greater than the track for leverage.

I'm pretty new to OpenSCAD, so I'd appreciate it if you could write the whole thing out for me. I don't have any libraries installed or anything, just a fresh install of OpenSCAD. I'd like it to be easy to use, though, and configurable. I'd really like the code to be clean and well documented.

[Assistant]
5050_track.scad:
```
//  I printed this on a Prusa MK3S
//  My settings were
//    .20 mm per layer
//    15% infill
//    no brim
//    no supports
//    GST3D PLA Jet Black filament
//    I printed 10 strips at once with a skirt to prevent warping

//    econoLED 12V Flexible SMD 5050 RGB LED Strip Lights,
//      LED Tape, Multi-Colors, 300 LEDs, Non-Waterproof,
//      Light Strips, Color Changing
//      https://www.amazon.com/dp/B006LW2NJM
//      width = 10mm
//      thickness = .5mm

//  Written by Ed Barnes
//  All Dimensions are in mm

// Rev 001
//   improved center alignment on interlocks
//   adjusted sizes to be in inches
//   fixed length issue with female endcap
//   added caps boolean for endcaps
//   added covers to cover wires
//   added a tool to help insert strip into channel

// Defining Variables
// LED strip details
width=10.1; // width of LED strip (added .1 tolerance)
thickness=0.6; // thickness of LED strip not including LED

// user details
walls=3; // wall thickness
heigth=6; // overall heigth of track (usually 2 times walls)

// part details
length=200; // overall length of track (7.87 inches)
corner_length=12.7; // length of all corner segments (1/2 inch)
endcap_length=25.4; // length of all endcaps (1 inch)
screw_size=1; // hole size for tie down screw holes
cover_length=3; // cover length
tool_length=25.4; // tool length (1 inch)
tool_heigth=heigth*2; // tool heigth

// Choice of parts to render
// 1=basic track, 2=male endcap, 3=female endcap, 4=inside corner includes reinforcement, 5=outside corner, 6=cover, 7=installation tool
show_part=1;

// Render specifics
interlocks=true;  // place interlocks on track
caps=true; // place caps on endcaps
screw_holes=false;  // place screw holes - I used 3M green strip tape .5 wide

// End of user defined variables
// Do not modify the following code

if(show_part==1) {
    difference(){ // basic track
      difference(){
        track(length);
        if(screw_holes==true) {
          translate([length/2,(width+walls)/2,-thickness*2]) {
            cylinder(walls+thickness*3,screw_size,screw_size*2);
          }
        }
    }
      if(interlocks==true) female_interlock();
    }
      if(interlocks==true) male_interlock(length);
}
if(show_part==2) { // male endcap
    if(caps) cube([walls,width+walls,heigth]);
    difference(){
      track(endcap_length);
      if(screw_holes==true) {
        translate([walls*2,(width+walls)/2,-thickness*2]) {
          cylinder(walls+thickness*3,screw_size,screw_size*2);
        }
      }
    }
    if(interlocks==true) male_interlock(endcap_length);

}
if(show_part==3) { // female endcap
    if(caps) translate([endcap_length-walls,0,0]) { cube([walls,width+walls,heigth]); }
    difference(){
      difference(){
        track(endcap_length);
        if(screw_holes==true) {
          translate([walls*2,(width+walls)/2,-thickness*2]) {
            cylinder(walls+thickness*3,screw_size,screw_size*2);
          }
        }
      }
      if(interlocks==true) female_interlock();
    }
}
if(show_part==4) { // inside corner
  difference() {
    union() {
// Draw the horizontal segment
      translate([0,0,0]) {
        difference(){
          cube([corner_length,width+walls,walls], false);
        }
      }
      if(interlocks==true) male_interlock(corner_length);
// Draw the verticle segment
      translate([0,0,corner_length]) {
        rotate([0,90,0]) {
          difference(){
            cube([corner_length,width+walls,walls], false);
              if(interlocks==true) female_interlock();
            }
          }
// Draw the angled segment
        rotate([0,45,0]) {
          translate([walls*2,0,-walls*2]) {
            difference(){
      cube([
         sqrt(pow(corner_length-width,2)+pow(corner_length-width,2))+thickness*2,
         width+walls,walls], false);
            }
          }
        }
    }
    }
    if(screw_holes==true) {
      translate([corner_length/2,(width+walls)/2,-thickness*2]) {
        cylinder(corner_length/2,screw_size,screw_size*2);
      }
   }
  }
}
if(show_part==5) { // outside corner
// Draw the horizontal segment
    difference(){ // basic track
      cube([corner_length,width+walls,walls], false);
      if(screw_holes==true) {
        translate([corner_length/2,(width+walls)/2,-thickness*2]) {
          cylinder(walls+thickness*3,screw_size,screw_size*2);
        }
      }
    }
    if(interlocks==true) male_interlock(corner_length);
// Draw the verticle segment
    translate([walls,0,-corner_length+heigth-walls]) {
      rotate([90,270,270]) {
        difference(){ // basic track
          difference(){
            cube([corner_length,width+walls,walls], false);
            if(screw_holes==true) {
              translate([corner_length/2,(width+walls)/2,-thickness*2]) {
                cylinder(walls+thickness*3,screw_size,screw_size*2);
              }
            }
         }
         if(interlocks==true) female_interlock();
       }
     }
   }
}
if(show_part==6) { // cover
   cube([cover_length,width-walls,thickness], false);
}
if(show_part==7) { // installation tool, assists in sliding LED strip into channel
   cube([tool_length,width-walls,tool_heigth], false);
}

// Modules
module groove(groove_length){
    difference(){
      cube([groove_length,walls+thickness,heigth]);
      translate([-thickness,0,0]) {
        rotate([45,0,0])
        cube([groove_length+1,walls*2,heigth*2]);
      }
   }
}
module female_interlock(){
    translate([walls,(walls+thickness)/2,walls]) {
      rotate([-45,90,0]) {
        difference(){
          translate([-thickness,0,0])
            cube([walls+1,(width+walls)/2,(width+walls)/2]);
          rotate([45,0,0])
            cube([walls+2,width+walls,width]);
        }
      }
    }
}
module male_interlock(track_length){
    translate([walls+track_length,walls-thickness,walls+thickness]) {
      rotate([-45,90,0]) {
        difference(){
        translate([thickness,0,0])
          cube([walls,(width+walls)/2-thickness,(width+walls)/2-thickness]);
            rotate([45,0,0])
            cube([walls+2,width+walls,width]);
        }
      }
    }
}
module track_wall(track_length){
    translate([0,walls+thickness,heigth])
      rotate([180,0,0]) groove(track_length); //top
      cube([track_length,walls,walls], false)
      cube([track_length,thickness*2,thickness+walls], false);
}
module track(track_length){
   //base
   translate([0,walls,0]) cube([track_length,width,walls], false);
   //walls
   track_wall(track_length);    //right side
   translate([0,width+walls,0]) mirror([0,90,0]) {
      track_wall(track_length); // left side
   }
}
module prism(l, w, h){
   polyhedron(
      points=[[0,0,0], [l,0,0], [l,w,0], [0,w,0], [0,w,h], [l,w,h]],
      faces=[[0,1,2,3],[5,4,3,2],[0,4,5,1],[0,3,4],[5,2,1]]
   );
}

```


[User]
Hey, I'm looking to create a customizable pipe adapter for my shop vac and various tools. I've been using a simple one that works pretty well, but it would be great to have a more flexible design that I can easily modify.

Essentially, the adapter needs to be a funnel shape, connecting a smaller diameter pipe to a larger diameter pipe. I'd like to be able to specify the outer diameters of both ends, as well as their individual heights, and the wall thickness. I'm aiming for a smooth transition between the two diameters. I want to ensure it prints without supports if possible.

Here are some details about what I need to be able to control in the design:
*   **Upper Diameter:** The outer diameter of the smaller pipe (in millimeters)
*   **Upper Height:** The height of the smaller pipe section (in millimeters)
*   **Lower Diameter:** The outer diameter of the larger pipe (in millimeters)
*   **Lower Height:** The height of the larger pipe section (in millimeters)
*   **Wall Thickness:** The thickness of the adapter's wall (in millimeters)

Also, it'd be helpful to be able to control the `fn` value to change the resolution of the curves.

I'd like the design to create a hollow adapter with the specified dimensions, sort of a truncated cone between two cylinders. Is that enough info to get started? I'm hoping to have this as a parametric OpenSCAD file, or a set of files if needed, with the above parameters exposed as variables, and have it use the basic `cylinder()` and `difference()` functions. Thanks!

[Assistant]
pipe-adaptor.scad:
```

// Wall thinkness (mm)
wallThickness = 6.0; // [2:0.1:10]
// Upper, smaller diamerter (mm)
upperDiameter= 52.6; // [12.7:0.1:120.0]
// Upper, smaller diameter height (mm)
upperHeight = 26.0;  // [12.7:0.1:76.0]
// Lower, larger diamerter (mm)
lowerDiameter = 101.6; // [12.7:0.1:120.0]
// Lower, larger diameter height (mm)
lowerHeight = 26.0;  // [12.7:0.1:76.0]
/* [Advanced] */
// Fragments
$fn=60; // [40:1:100]

/**
* The following creates a simple tube/pipe funnel adaptor that can
* be printed without support.
* The following is used to illustrate the parameters:
*  largerDiameter = ld
*  largerHeight = lh
*  smallerDiameter = sd
*  smallerHeight = sh
*  wallThickness = wt
*
*                       | wt |     sd      | wt |
*               sh      |    |             |    |
*                      /    /              \    \
*                     /    /                 \    \
*                    |   |                    |    |
*               lh  >|---|--------ld----------|----|<
*                    |   |                    |    |
* @param {number} largerDiameter The outer larger diameter in millimeters
* @param {number} largerHeight The height of the larger diameter segment in millimeters
* @param {number} smallerDiameter The outer smaller diameter in millimeters
* @param {number} wallThickness The target wall thickness in millimeters
*/
module pipeAdaptor(largerDiameter, largerHeight, smallerDiameter, smallerHeight, wallThickness) {
  // Forcing the larger diameter to be the lower one, the rest requires this.
  lowerDiameter = largerDiameter > smallerDiameter ? largerDiameter : smallerDiameter;
  upperDiameter = largerDiameter < smallerDiameter ? largerDiameter : smallerDiameter;
  transitionHeight = (lowerDiameter - upperDiameter); // TODO: Optimize this.
  difference() {
    union() {
      translate([0,0, transitionHeight + largerHeight]) cylinder(smallerHeight, d=upperDiameter);
      translate([0,0,largerHeight]) cylinder(transitionHeight, d1=lowerDiameter, d2=upperDiameter);
      cylinder(largerHeight, d=lowerDiameter);
    }
    cylinder(largerHeight + transitionHeight, d1=lowerDiameter - wallThickness, d2=upperDiameter - wallThickness);
    translate([0,0, largerHeight]) cylinder(transitionHeight + smallerHeight, d=upperDiameter - wallThickness);
  }
}

pipeAdaptor(lowerDiameter, lowerHeight, upperDiameter, upperHeight, wallThickness);

```


[User]
Hey, I'm looking for help designing a parametric print-in-place phone stand in OpenSCAD. I've got a design in mind, but I need some help coding it.

The stand should be foldable, with two snap-lock positions for holding a phone. One position should be for portrait mounting and the other for landscape mounting. In the closed position, the stand should lay flat, and also be able to open fully for printing.

Here's what I'm thinking for the parameters, all in millimeters:

*   `device_length`: The longest dimension of the device (phone or tablet).
*   `device_width`: The width of the device, used for assembly preview.
*   `device_thickness`: The thickness of the device.
*   `tilt_angle`: An approximate tilt angle for the device when the stand is open.
*   `openangle_narrow`: The opening angle for the portrait mount position.
*   `openangle_wide`: The opening angle for the landscape mount position.
*   `display`: A string parameter with the possible values "print" or "assembly". "print" should show the stand in its printing configuration. "assembly" should show the stand with the device in the two different mounted orientations.
*   `layerht`: Print layer height.
*   `hinge_clearance`: Clearance for the hinges.
*  `hook_length`: Length of device-restraining hook.
*   `hook_width`: Width of device-restraining hook.
*   `overhang`: Overhang angle.
*   `thickness`: Thickness of the stand arms.
*   `table_clearance`: Nominal clearance of device from table.
*   `fwd_offset`: Forward shift of the device to prevent hinge interference.
*  `cliptype`: type of clip. This should default to 1 and only implement a single style for now.
*  `clipthick`: thickness of the spring clip end
* `toothdepth`: depth of the locking tooth
* `rstresshole`: radius of clip stress relief hole
* `rmink`: radius for Minkowski beveling.

I'm envisioning a design with arms connected by hinges. The snap-lock mechanism should be integrated into the hinges using spring clips. I'd also like to include a device restraining hook to help hold the phone in place.

I'd like the ability to see an assembly view of the stand with a dummy phone in both landscape and portrait modes.

I think I want to use the Minkowski sum to round edges, so the minkowski function should be used.

I don't have a preference on libraries, but I'd like you to specify which ones are being used.

[Assistant]
print_in_place_folding_phone_stand.scad:
```
/*
Print-in-place folding phone stand with 2 snap-lock positions
by Alex Matulich
January 2022
Version 2 - April 2022 - fixed bug with truncation of stand for large devices

On Thingiverse: https://www.thingiverse.com/thing:5165502
On Printables.com: https://www.printables.com/model/129986

This folding phone stand features two snap-lock open positions (in addition to the closed and wide-open position for printing) and can be printed in place.

For a generic phone stand, the settings use the longest length and the narrowest width, with a 9mm thickness. That should fit most smartphones.

The two opening angles 50° and 90°, should not need to be changed, but set the "display" parameter to "assembly" to check whether you need to change the openangle_narrow parameter. Ideally it should be 60°-70° for maximum stability. If the two open angles are two close to each other, the locking mechanism may not be effective.

Example device dimensions (rounded up to nearest millimeter):

DEVICE              length  width thickness
--------------------------------------------
Google Pixel 6        159     75      9
iPhone 13 Pro         147     72      8
iPhone 13 Pro Max     161     78      8
Samsung Galaxy S11    162     74      8
Ulefone Armor 11T     160     80     15

iPod Touch            124     57      6
iPad Pro 4th gen      281    215      6
iPad Mini 6           196    135      7 (without cover)

*/

// longest dimension of device
device_length = 160;

// width of device (needed only for assembly preview)
device_width = 72;

// thickness of thickest device to be used in the stand
device_thickness = 9;

// approximate tilt angle for thickest device
tilt_angle = 25;

// narrow open angle (portrait mount)
openangle_narrow = 50;

// wide open angle (landscape mount)
openangle_wide = 90;

// what to display (print, assembly)
display = "print";  // [print,assembly]

// ---------- end of basic parameters ----------

module dummy(){} // force customizer to stop looking

// ---------- advanced parameters ----------

rmink = 1;  // Minkowski beveling radius (0 to speed up preview)
layerht = 0.2;          // print layer height
hinge_clearance = 0.20; // clearance between and around hinges
hook_length = 6;        // length of device restraining hook
hook_width = 5;         // width of device restraining hook
overhang = 40;          // violate 45° rule by 5°
thickness = 4.6;        // thickness of stand arms (multiple of layerht)
table_clearance = 6;    // nominal clearance of device from table
fwd_offset = 7;         // forward shift of device to prevent hinge interference
maxdevthick = device_thickness + 0.5; // add a bit of clearance

cliptype = 1;           // type of clip (only 1 is implemented)
clipthick = cliptype==1 ? 3 : 0.6;  // thickness of spring clip end
toothdepth = 2;         // depth of locking tooth
rstresshole = 1.5;      // radius of clip stress relief hole

// ---------- calculated parameters ----------

halfdevlen = 0.5*device_length;
leght = halfdevlen * cos(tilt_angle) + 2 + table_clearance;
hinge_len = (leght - 4*hinge_clearance) / 5;
hinge_spacing = leght / 5;
rhinge = thickness; //min(thickness, 3.5);
rpin = rhinge-0.5;
hingebotx = max(0, thickness - rhinge*sin(45));

clipwid = cliptype == 1 ? (hinge_len/2+4)/3 : min(10, 0.6*hinge_len);
cliplen = 0.4*halfdevlen*sin(tilt_angle) + fwd_offset;
clip_end_x = sqrt(rhinge*rhinge - pow(rhinge-clipthick,2));
clip_end_thk = thickness-rhinge+clipthick;
clip_avail = hinge_len/2 + 6 - 2;

// ---------- render it! ----------

if (display == "print") {
    right_leg();
    left_leg();
} else if (display == "assembly") {
    rotate([0,0,360*$t]) translate([0,-20,0]) {
        rotate([0,0,-90+openangle_wide/2]) rotate([90,0,0]) translate([0,0,-thickness-layerht])right_leg();
        rotate([0,0,90-openangle_wide/2]) rotate([90,0,0]) translate([0,0,-thickness-layerht]) left_leg();
        device_landscape();
    }
    rotate([0,0,180+360*$t]) translate([0,-20,0]) {
        rotate([0,0,-90+openangle_narrow/2]) rotate([90,0,0]) translate([0,0,-thickness-layerht])right_leg();
        rotate([0,0,90-openangle_narrow/2]) rotate([90,0,0]) translate([0,0,-thickness-layerht]) left_leg();
        device_portrait();
    }
}

// ========== MODULES ==========

// ---------- cutout for device and leg shape ----------

module devcutprofile() {
    dt2 = 0.5 * maxdevthick;
    rfillet = 2;
    boffset = 2;
    linear_extrude(device_length, convexity=4) offset(r=rmink) polygon(points=[
        for(t=[0:0.1:1.01]) bezier_point2d(t, [[0,0], [0, 2*dt2/3], [1, dt2], [4, dt2]]),
        [device_length, dt2], [device_length, -120],
        [-60-boffset, -120], [-60-boffset, -dt2-hook_width+60], [-boffset, -dt2-hook_width],
        [hook_length-rfillet, -dt2-hook_width],
        for(a=[-80:10:1]) [hook_length-rfillet+rfillet*cos(a), -dt2-hook_width+rfillet+rfillet*sin(a)],
        [hook_length, -50], [hook_length, -dt2-rfillet],
        for(a=[10:10:91]) [hook_length-rfillet+rfillet*cos(a), -dt2-rfillet+rfillet*sin(a)],
        for(t=[0:0.1:0.99]) bezier_point2d(t, [[4,-dt2], [1,-dt2], [0,-2*dt2/3], [0,0]])
        ]);
}

module device_cutout() {
    dt2 = 0.5 * maxdevthick;
    translate([0,halfdevlen*cos(tilt_angle)+table_clearance,fwd_offset])
        rotate([-90-tilt_angle, 0, 0]) rotate([0,-90,0])
            translate([-halfdevlen,-dt2,-halfdevlen])
                devcutprofile();
}

// ---------- device for assembled display ----------

module device_landscape() {
    translate([0,
    1-(halfdevlen-0.5*device_width)*sin(tilt_angle)
    -fwd_offset*cos(openangle_wide/2),
    0.5*device_width*cos(tilt_angle)+table_clearance+1])
    rotate([90-tilt_angle,0,0])
    #dummy_device();
}

module device_portrait() {
    by = halfdevlen*sin(tilt_angle);
    hr = by / sin(90-openangle_wide/2);
    nby = hr * sin(90-openangle_narrow/2);
    newtilt = asin(nby / halfdevlen);
    translate([0,0-fwd_offset*nby/by,halfdevlen*cos(newtilt)+table_clearance])
    rotate([90-newtilt,0,0])
    rotate([0,0,90]) #dummy_device();
}

module dummy_device() {
    bev = max(1, device_thickness/5);
    xscl = (device_length-2*bev) / device_length;
    yscl = (device_width-2*bev) / device_width;
    translate([0,0,bev]) {
        rotate([180,0,0]) linear_extrude(bev, scale=[xscl,yscl]) devpoly();
        linear_extrude(device_thickness-2*bev) devpoly();
    }
    translate([0,0,device_thickness-bev]) linear_extrude(bev, scale=[xscl,yscl]) devpoly();

    module devpoly() {
        offset(r=10) offset(delta=-10)
            square(size=[ device_length, device_width], center=true);
    }
}

// ---------- blank leg before device cutout ----------

module leg_blank(clip=false) {
    rfillet = 2;
    offdelta = table_clearance+2;
    by = halfdevlen/2*sin(tilt_angle);
    nby = by/sin(90-openangle_wide/2);
    holeht = clip ? leght - 4*clipthick-2*rstresshole+offdelta : leght;
    holeoff = clip ? 4*clipthick+2*rstresshole-offdelta : 0;
    translate([0,0,rmink]) difference() {
        linear_extrude(thickness-2*rmink, convexity=4) polygon(points=[
            [rmink,rmink], [rmink, leght-rmink], [maxdevthick*nby/by,leght-rmink],
            [maxdevthick*nby/by+nby, leght-halfdevlen/2*cos(tilt_angle)],
            [max(80,device_width/2), halfdevlen/2],
            [max(80,device_width/2), rmink]
            ]);
        translate([rhinge,holeoff,-1]) linear_extrude(thickness+2)
        offset(r=rfillet+rmink, $fn=16) offset(delta=-rfillet-offdelta)
        polygon(points=[[-rhinge,0], [-rhinge,holeht], [fwd_offset-1,holeht], [fwd_offset-1+holeht*sin(tilt_angle)/cos(openangle_wide/2), 0]]);
    }
}

// ---------- Minkowski beveling shape ----------

module minkshape(r=1) {
    // double 8-sided pyramid
    rotate_extrude(angle=360, $fn=8) polygon(points=[[0,r],[r,0],[0,-r]]);
}

// ---------- left leg before hinges added ----------

module left_leg_blank() {
    difference() {
        if (rmink > 0) {
            minkowski(convexity=8) {
                difference() {
                    mirror([1,0,0]) leg_blank(cliptype==1);
                    rotate([0,-(90-openangle_wide/2),0]) device_cutout();
                }
                minkshape(rmink);
            }
        } else {
            difference() {
                mirror([1,0,0]) leg_blank(true);
                rotate([0,-(90-openangle_wide/2),0]) device_cutout();
            }
        }
        translate([0,-1,thickness]) rotate([-90,0,0]) cylinder(leght+2-hinge_len, r=rhinge-0.1, $fn=32);
    }
}

// ---------- right leg before hinges added ----------

module right_leg_blank() {
    difference() {
        if (rmink > 0) {
            minkowski(convexity=8) {
                difference() {
                    leg_blank(false);
                    rotate([0,90-openangle_wide/2,0]) device_cutout();
                }
                minkshape(rmink);
            }
        } else {
            difference() {
                leg_blank(false);
                rotate([0,90-openangle_wide/2,0]) device_cutout();
            }
        }
        translate([0,-1,thickness]) rotate([-90,0,0]) cylinder(leght-hinge_len/2, r=rhinge-0.1, $fn=32);
    }
}

// ---------- complete left leg assembly ----------

module left_leg() {
    difference() {
        left_leg_blank();
        mirror([1,0,0]) {
            hinge_gap();
            translate([0, 2*hinge_spacing, 0]) hinge_gap();
            translate([0, 4*hinge_spacing, 0]) hinge_gap();
        }
        if (cliptype==1) clipcutout1();
    }
    translate([0, hinge_spacing, 0]) male_hinge();
    translate([0, 3*hinge_spacing, 0]) male_hinge();
    if (cliptype==1) clip1();
}

// ---------- complete right leg assembly ----------

module right_leg() {
    difference() {
        union() {
            difference() {
                right_leg_blank();
                translate([0, hinge_spacing, 0]) hinge_gap();
                translate([0, 3*hinge_spacing, 0]) hinge_gap();
                translate([0, -hinge_spacing, 0]) hinge_gap();
            }
            female_hinge(bottom=false);
            translate([0, 2*hinge_spacing, 0]) female_hinge();
            translate([0, 4*hinge_spacing, 0]) female_hinge(top=false);
        }
        if (cliptype==1) carve_teeth();
    }
}

// ---------- male hinge (cones sticking out) ----------

module male_hinge() {
    xt = rhinge*cos(45);
    rotate([-90,0,0])
    union() {
        translate([0,-thickness-layerht,hinge_clearance/2]) {
            cylinder(hinge_len, r=rhinge, $fn=32);
            translate([0,0,hinge_len]) hinge_pin();
            rotate([180,0,0]) hinge_pin();
        }
        translate([0,0,hinge_clearance/2]) linear_extrude(hinge_len) polygon(points=[[xt-hingebotx,0], [xt,-hingebotx], [0,-thickness], [-rhinge,-thickness], [-rhinge,0]]);
    }
}

// ---------- female hinge (cones hollowed out) ----------

module female_hinge(top=true, bottom=true) {
    xt = -rhinge*cos(45);
    hl = top ? hinge_len : hinge_len - rmink;
    rotate([-90,0,0]) translate([0,-thickness-layerht,hinge_clearance/2])
    difference() {
        union() {
            cylinder(hl, r=rhinge, $fn=32);
            if (!top) translate([0,0,hl-0.01]) cylinder(rmink, r1=rhinge, r2=rhinge-rmink, $fn=32);
            linear_extrude(hl) polygon(points=[[xt+hingebotx,thickness], [rhinge,thickness], [rhinge,0], [0,0], [xt,thickness-hingebotx]]);
        }
        if (top) {
            translate([0,0,hinge_len]) rotate([180,0,0]) hinge_pin();
        } else if (rmink>0) translate([0,thickness+rmink/4,rmink/4+hinge_len]) rotate([45,0,0]) cube([3*rhinge, 2*rmink, 2*rmink], center=true);
        if (bottom) hinge_pin();
        else if (rmink>0) translate([0,thickness+rmink/4,-rmink/4]) rotate([45,0,0]) cube([3*rhinge, 2*rmink, 2*rmink], center=true);
    }
}

// ---------- hinge pins are cones ----------

module hinge_pin() {
    coneht = (rhinge-0.1) / tan(overhang);
    pinht = rpin / tan(overhang);
    translate([0,0,pinht-coneht]) cylinder(coneht, r1=rhinge, r2=0, $fn=32);
}

// ----------- gap to carve into legs between hinges ----------

module hinge_gap(rtol=hinge_clearance) {
    xt = (rhinge+hinge_clearance)*cos(45);
    rotate([-90,0,0]) translate([0,0,hinge_clearance/2])
    union() {
        translate([0,-thickness,-hinge_clearance]) cylinder(hinge_len+2*hinge_clearance, r=rhinge+hinge_clearance, $fn=32);
        //if (cliptype == 1) {
            translate([0,0,-hinge_clearance]) linear_extrude(hinge_len+2*hinge_clearance) polygon(points=[
            [xt-hingebotx,hinge_clearance],
            [xt,-hingebotx+hinge_clearance],
            [0,-thickness],
            [-rhinge-hinge_clearance,-thickness+hinge_clearance],
            [-rhinge-hinge_clearance,hinge_clearance]
        ]);
        //}
        if (cliptype == 2) {
            translate([0,0,(hinge_len-clipwid)/2-2*hinge_clearance]) linear_extrude(clipwid+1) polygon(points=[
            [rhinge-1+hinge_clearance, 1], [rhinge+2+hinge_clearance, -2],
            [rhinge+2+hinge_clearance-(thickness-layerht-2), layerht-thickness],
            [rhinge+hinge_clearance, -2*rhinge-2],
            [-rhinge-1, -2*rhinge-2],
            [-rhinge-1, 1]
        ]);
        }
    }
}

// ========== sprint clip modules ==========

// ---------- cliptype = 1 ----------

module clip1() {
    difference() {
        union() {
            translate([0,clipthick,thickness+layerht]) cliptooth();
            translate([-rhinge-hinge_clearance,0,0]) cube([rhinge+ hinge_clearance, clipthick, thickness]);
        }
        if (rmink > 0) {
            sq2 = 3*sqrt(2);
            translate([0,-rmink-0.01,-rmink-0.01]) rotate([45,0,0]) cube([2*rhinge+2, sq2*rmink, sq2*rmink], center=true);
            translate([0,-rmink-0.01,thickness+rmink+0.01]) rotate([45,0,0]) cube([2*rhinge+2, sq2*rmink, sq2*rmink], center=true);
        }
    }
}

module cliptooth(extradepth=false) { // spring clip locking tooth
    h = min(thickness+layerht, rhinge+1);
    r = toothdepth;
    scl = extradepth ? 2 : 1;
    xscl = scl * 3/(2*cos(30));
    scale([xscl,scl,scl]) translate([0,0,-h])
    hull() {
        rotate([0,0,90]) cylinder(h, r1=r, r2=0, $fn=3);
        translate([0,-clipthick,0])
        rotate([-90,0,0]) linear_extrude(0.1) polygon(points=[[0,-h], [-r*cos(30), 0], [r*cos(30), 0]]);
    }
}

module carve_teeth() { // module to carve teeth in the bottom of the hinge
    translate([0,clipthick,thickness+layerht]) union() {
        translate([0,hinge_clearance,-layerht]) rotate([90,0,0]) rotate([0,0,0.1])rotate_extrude(angle=270, $fn=64) square(size=[rhinge+1,2*thickness]);
        translate([0,0,layerht]) deeptooth();
        rotate([0,180,0]) deeptooth();
        rotate([0,180-openangle_narrow,0]) deeptooth();
        rotate([0,180-openangle_wide,0]) deeptooth();
        translate([rhinge/2,-clipthick-hinge_clearance,0]) rotate([45,0,0]) cube([rhinge+1, 2, 2], center=true);
    }
    module deeptooth() {
        translate([0,0.2,0]) hull() {
            cliptooth();
            translate([0,0,0.2]) cliptooth(true);
        }
    }
}

module clipcutout1() { // cut out groove to create for spring clip
    union() {
        translate([0,clipthick+hinge_clearance,thickness+hinge_clearance]) cliptooth(true);
        translate([-rhinge-hinge_clearance,clipthick,-1]) {
            cube([rhinge+hinge_clearance, 0.5, thickness+2]);
            rotate([0,0,-atan(clipthick/cliplen)]) {
                translate([-cliplen,0,0]) cube([cliplen, 0.5, thickness+2]);
                translate([-cliplen,rstresshole,0]) cylinder(thickness+2, r=rstresshole, $fn=24);
            }
        }
    }
}

// ---------- cliptype = 2 ----------

// ========== FUNCTIONS ==========

// Bezier cubic curve

// return coordinate of Bezier curve at location t in the interval 0...1
// must be called twice, once to pass x-coordinate coefficients and again to pass y-coordinate coefficients
function bezier_coordinate(t, n0, n1, n2, n3) =
    n0*pow((1-t), 3) + 3*n1*t*pow((1-t), 2) + 3*n2*pow(t, 2) * (1-t) + n3*pow(t, 3);

// get an x,y coordinate of a cubic Bezier curve given 4 x,y control points given in the array p[][]
function bezier_point2d(t, p) = [ // t=0...1, p=array of 4 [x,y] control points
    bezier_coordinate(t, p[0][0],p[1][0],p[2][0],p[3][0]),
    bezier_coordinate(t, p[0][1],p[1][1],p[2][1],p[3][1])
];

```


[User]
Hey, I'm looking to create a customizable memory card holder in OpenSCAD. I really like the design of this one I found online (https://www.thingiverse.com/thing:2835728), but I need it to be more flexible and have better tolerances.

Basically, I want to be able to define the dimensions of the various card types – SD, microSD, USB-A, and USB-C – so that I can adjust them to fit my specific cards and printer. I'd also want to specify the number of slots in rows and columns, the radius of the corners, the material thickness, and the size of the base relative to the top. Oh, and I need to be able to control the fillet radius on the top edges of the slots and the size of the holes capping the microSD slots. I'd like to set a print precision value to use when calculating the number of facets for curves.

Here's a breakdown of the different parameters I'd like to control and their default values, along with some calculations to simplify how they all fit together.  Hopefully this is enough information to get you started:

**Card Sizes (in mm):**

*   **SD Card:**
    *   `SdWidth = 25.1;`
    *   `SdThickness = 2.2;`
    *   `SdDepth = 12;`
*   **USB-A Card:**
    *   `USBWidth = 12.4;`
    *   `USBThickness = 4.7;`
    *   `USBDepth = 12;`
*   **MicroSD Card:**
    *   `MicroWidth = 9.8;`
    *   `MicroThickness = 0.9;`
    *   `MicroDepth = 8;`
*   **USB-C Card:**
    *   `UsbCWidth = 8.3;`
    *   `UsbCThickness = 2.5;`
    *   `UsbCDepth = 6.7;`

**Holder Array:**

*   `BlockRows = 1;` (Number of rows, stacked in the USB/SD thickness direction)
*   `BlockColumns = 1;` (Number of columns, stacked in the USB/SD width direction)

**Construction Parameters:**

*   `BlockRadius = 1.8;` (Radius on holder body exterior)
*   `Material = 1.8;` (Minimum thickness of bottom & sides)
*   `BaseScale = 1.25;` (Base is bigger than top by this amount)
*   `FilletRadius = 1.1;` (Fillet radius on top edge of slots)
*   `DrillRadius = 1.1;` (Size of holes capping MicroSD slots)
*   `$Precision = 0.3;` (Print layer resolution in mm)

**Calculated Values (should be calculated within the code):**

*   `CellClearance = max(BlockRadius, Material);`  If `BlockRadius` is smaller than `Material`, use `Material`, otherwise use `BlockRadius`.
*   `CellThickness = MicroWidth + 2*DrillRadius + CellClearance;`
*   `CellWidth = SdWidth + 2*CellClearance;`
*   `BlockLength = CellThickness * BlockRows + CellClearance;`
*   `BlockWidth = CellWidth * BlockColumns + CellClearance;`
*   `BlockHeight = SdDepth + CellClearance;`
*   `CenterlineXOffset = CellThickness/2 - CellClearance/2;`
*   `CenterlineYOffset = CellWidth/2 - CellClearance/2;`

I'd like the overall structure to be a single `SolidBlock`, with cutouts for each card type within `CardGroup` modules. There will be a `CardGroup` module for each 'cell' in the matrix defined by `BlockRows` and `BlockColumns`. `SolidBlock` needs to be constructed using minkowski of a linear_extrude of a square. There are some functions, like `fn` and `fn4`, and modules like `ArcPoints`, `Rect`, `CylinderHorn`, `RectangleHorn` and `FilletSection` needed, that should be in a supporting library.  The actual card slots should be made using `FlangedCube` and `FlangedCylinder` modules.

I guess I need a couple of files, one with the core logic, and another for the utility functions and modules. I'm pretty sure the core logic will reference the library file.  I don't have any specific library preferences - use what makes sense. I can see that you'll need a function to return the `$fn` value based on a provided diameter or radius and precision value. A helper function to ensure that the `$fn` value is always a multiple of 4 might be useful too for creating arcs that start and end at the same angle.

Let me know if you need any more information!

[Assistant]
MemoryCardHolder.scad:
```
//use <..\Libraries\MyStuff2.scad>
/* [SD Card Size] */
SdWidth                 = 25.1;
SdThickness             = 2.2;
SdDepth                 = 12;
/* [USB Card Size] */
USBWidth                = 12.4;
USBThickness            = 4.7;
USBDepth                = 12;
/* [MicroSD Card Size] */
MicroWidth              = 9.8;
MicroThickness          = 0.9;
MicroDepth              = 8;
/* [USB-C Card Size] */
UsbCWidth               = 8.3;
UsbCThickness           = 2.5;
UsbCDepth               = 6.7;
/* [Holder Array Size (# slots)] */
// Rows stacked in USB/SD thickness direction
BlockRows               = 1;
// Columns stacked in the USB/SD width direction
BlockColumns            = 1;
/* [Construction parameters] */
// Radius on holder body exterior
BlockRadius             = 1.8;
// Minimum thickness of bottom & sides
Material                = 1.8;
// Base is bigger than top by this amount
BaseScale               = 1.25;
// Fillet Radius on top edge of slots
FilletRadius            = 1.1;
// Size of holes capping MicroSD slots
DrillRadius             = 1.1;
// Your print layer resolution in mm.  Smaller takes lots more time!
$Precision              = 0.3;          // [0.1:0.1:0.61]

CellClearance     = BlockRadius<Material?Material:BlockRadius;
CellThickness     = MicroWidth+2*DrillRadius+CellClearance;
CellWidth         = SdWidth+2*CellClearance;
BlockLength       = CellThickness*BlockRows+CellClearance;
BlockWidth        = CellWidth*BlockColumns+CellClearance;
BlockHeight       = SdDepth+CellClearance;
CenterlineXOffset = CellThickness/2-CellClearance/2;
CenterlineYOffset = CellWidth/2-CellClearance/2;

translate([BlockLength-CellClearance,CellWidth*(BaseScale-1)/2+Material,BlockHeight]) rotate([0,180,0])
difference() {
  SolidBlock();
  for(x=[0:BlockRows-1],y=[0:BlockColumns-1])
    translate([x*CellThickness,y*CellWidth,0]) CardGroup();
}

module CardGroup() {
  SdWingOffset = (USBWidth+MicroThickness)/2+2;

  translate([CenterlineXOffset,CenterlineYOffset,0]) BaseCleanout();
  translate([CenterlineXOffset,CenterlineYOffset,0]) USB();
  translate([CenterlineXOffset,CenterlineYOffset,0]) SdCard();
  translate([CenterlineXOffset,CenterlineYOffset,0]) UsbC();
  translate([CenterlineXOffset,CenterlineYOffset+SdWingOffset,0]) MicroCard();
  translate([CenterlineXOffset,CenterlineYOffset-SdWingOffset,0]) MicroCard();
}

module MicroCard() {
  FlangedCube(MicroWidth,MicroThickness,MicroDepth,FilletRadius,BottomFlange=false);
  translate([ MicroWidth/2,0,0]) FlangedCylinder(DrillRadius,FilletRadius,MicroDepth,BottomFlange=false);
  translate([-MicroWidth/2,0,0]) FlangedCylinder(DrillRadius,FilletRadius,MicroDepth,BottomFlange=false);
}

module UsbC() {
  UsbCRadius    = UsbCThickness/2;
  UsbCEndOffset = UsbCWidth/2-UsbCRadius;
  FlangedCube(UsbCWidth-UsbCThickness,UsbCThickness,UsbCDepth,FilletRadius,BottomFlange=false,CornerProtection=0);
  translate([-UsbCEndOffset,0,0]) FlangedCylinder(UsbCRadius,FilletRadius,UsbCDepth,BottomFlange=false);
  translate([ UsbCEndOffset,0,0]) FlangedCylinder(UsbCRadius,FilletRadius,UsbCDepth,BottomFlange=false);
}

module SdCard() {
  FlangedCube(XLength=SdThickness,YLength=SdWidth,Height=SdDepth,FilletRadius=FilletRadius,BottomFlange=false);
}

module USB() {
  FlangedCube(XLength=USBThickness,YLength=USBWidth,Height=USBDepth,FilletRadius=FilletRadius,BottomFlange=false);
}

module BaseCleanout() {
  translate([0,0,BlockHeight-Material])
      FlangedCube(XLength=USBThickness-2,YLength=USBWidth-2,Height=Material,FilletRadius=FilletRadius,TopFlange=false);
}

module SolidBlock() {
  BaseScale = 1+(BaseScale-1)/BlockColumns;
  translate([BlockLength/2-CellClearance,BlockWidth/2-CellClearance,BlockRadius])
  minkowski() {
    linear_extrude(height=BlockHeight-2*BlockRadius,scale=[BlockRows==1?BaseScale:1,BaseScale])
        square(size=[BlockLength-2*BlockRadius,BlockWidth-2*BlockRadius],center=true);
    sphere(r=BlockRadius,$fn=fn4(r=BlockRadius));
  }
}

module FlangedCube(XLength,YLength,Height,FilletRadius,TopFlange=true,BottomFlange=true,CornerProtection=$Precision) {
  FlangeSections  = FilletRadius/$Precision;
  PointCount      = FlangeSections+1;

  translate([0,0,Height/2])
      cube(size=[XLength,YLength,Height], center=true);

  if(CornerProtection) Rect(XLength,YLength) cylinder(h=Height, r=CornerProtection/2, $fn=fn(r=CornerProtection));

  if(BottomFlange && FilletRadius)
    translate([0,0,Height-FilletRadius])
        RectangleHorn(XLength,YLength,FilletRadius,PointCount=PointCount,Down=true);

  if(TopFlange && FilletRadius)
    RectangleHorn(XLength,YLength,FilletRadius,PointCount=PointCount,Down=false);
}

module FlangedCylinder(CylinderRadius,FilletRadius,Height,TopFlange=true,BottomFlange=true) {
  FlangePoints  = fn4(r=FilletRadius)/4;

  cylinder(h=Height, r=CylinderRadius, center=false, $fn=fn(r=CylinderRadius));
  if(BottomFlange)
    translate([0,0,Height-FilletRadius]) CylinderHorn(R1=FilletRadius,R2=CylinderRadius,Down=false,PointCount=FlangePoints);
  if(TopFlange)
    CylinderHorn(R1=CylinderRadius,R2=FilletRadius,PointCount=FlangePoints,Down=true);
}

// Stuff from my library used above

/************************************************************************
Calculates an appropriate value for $fn based on precision and diameter or radius

    d:          Diameter upon which to base the result.
    r:          Radius upon which to base result.  One must be provided.  Radius overrides diameter if both are supplied.
    $Precision: Defaults to 0.5mm.  Higher numbers generate fewer facets.  Smaller numbers generate more facets.
                Values < 0.1 are treated as not supplied.
*/
function fn(d=0,r=0) = ((r<=0?(d<=0?1:d):r*2)*3.1415926)/($Precision<0.1 ? 0.5 : $Precision);

/************************************************************************
Same as fn but insures value is a multiple of 4 so quarter-rounds begin and end on radius points

    d:         Diameter upon which to base the result.
    r:         Radius upon which to base result.  One must be provided.  Radius overrides diameter.
    Precision: Defaults to 0.5mm.  Higher numbers generate fewer facets.  Smaller numbers generate more facets.
*/
function fn4(d=0,r=0) = fn(d=d,r=r)%4==0?
    fn(d=d,r=r):
    4-fn(d=d,r=r)%4+fn(d=d,r=r);

/************************************************************************
Returns an array of points of the requested length representing the radius and angles specified, optionally offsetting them.

    Angles:     A vector containing the angles between which the points should be calculated (inclusive!).
                NOTE: Angle 0 = 12 o'clock, 90 = 3 o'clock etc.
    Radius:     The radius of the circle whose arc points should be returned.
    Origin:     Offsets the points returned by the [X,Y] provided.  The default origin is [0,0].
    PointCount: Defines the number of points to be returned.  The minimum is 2;
                Defaults to $fn or 2 if a legal value is not supplied.
*/
function ArcPoints(Angles=[0,45], Radius=1, Origin=[0,0], PointCount=0) =
    [for(A=[Angles[1]-Angles[0]]                                        // Total number of degrees
             ,B=[(PointCount<2?($fn<2?2:$fn):PointCount)-1]             // Points-1, because start isn't part of count
             ,C=[A/B]                                                   // increment that SHOULD yield desired point count
             ,D=[Angles[0] : C : C*B+Angles[0]+C/2]                     // Insure END accounts for rounding errors.
         )
        [Radius*sin(D)+Origin[0],Radius*cos(D)+Origin[1]]
     ];

/************************************************************************
    Creates a rectangle of the children to this module with their distances between centers as requested

    XLength:    The size of the rectangle in the X direction
    YLength:    The size of the rectangle in the Y direction
    center:     OpenScad standard definition for 2 dimensional objects (which is what this creates)
*/
module Rect(XLength,YLength,center=true) {
  X1   = center?-XLength/2:0;
  X2   = center?+XLength/2:XLength;
  Y1   = center?-YLength/2:0;
  Y2   = center?+YLength/2:YLength;

    translate([X1,Y1,0]) children();
    translate([X1,Y2,0]) children();
    translate([X2,Y1,0]) children();
    translate([X2,Y2,0]) children();
}

/************************************************************************
Creates a horn suitable for adding a fillet to a cylinder being subtracted from another object.

    R1:         The radius of the cylinder.
    R2:         The radius of the Roundover.
    Epsilon:    Gives the part of the horn that would have 0 thickness, this thickness, by adding it in the z direction.
    PointCount: Number of points in horn radius.
    Down:       returns the structure upsidedown with epsilon(if any) below z=0;
    Use $Precision to effect points in cylinder.
*/
module CylinderHorn(R1=1,R2=1,Epsilon=0.1,PointCount=5,Down=false) {
  ShapePoints     = concat(
      ArcPoints(Angles=[0,-90], Radius=R2, Origin=[R2+R1,0], PointCount=PointCount)
      ,[[0,0],[0,R2+Epsilon],[R2+R1,R2+Epsilon],[R2+R1,R2]]);
  //echo($Precision=$Precision,PointCount=PointCount,ShapePoints=ShapePoints);

  translate([0,0,Down?R2:0]) rotate([Down?180:0,0,0])
      rotate_extrude(angle=360, $fn=fn(r=R1))
      polygon(ShapePoints);
}

/************************************************************************
Creates a horn suitable for adding a fillet to a rectangle being subtracted from another object.

    XLength:    The size of the rectangle in the X direction
    YLength:    The size of the rectangle in the Y direction
    HornRadius: The radius of the horn flange ADDED to the exterior of the rectangle
    PointCount: Number of points in horn radius.
    Down:       Returns the structure upsidedown;
*/
module RectangleHorn(XLength,YLength,HornRadius,PointCount,Down=false) {
  PointCount = PointCount<2 ? 2 : round(PointCount);
  MaxPoint   = PointCount-1;
  for(x=[1:1:MaxPoint]) {
    Z  = HornRadius*(Down?(x-1):(MaxPoint-x))/MaxPoint;  // build bottom up or top down based on direction desired
    Y1 = HornRadius*(Down?(x-1):(x))/MaxPoint;           // switch which side is bigger based on direction
    Y2 = HornRadius*(Down?(x):(x-1))/MaxPoint;           // switch which side is bigger based on direction
    translate([0,0,Z])                                   // move into position working down or up as indicated
        hull() Rect(XLength,YLength)
        FilletSection(HornRadius,Y1,Y2);

  }
}

/************************************************************************
    Creates a cylinder with height Y2-Y1 and radius appropriate for Y1-Y2 coordinates within a fillet of given radius

    Radius: The full radius of the fillet.  Both Y1 and Y2 must fall between 0 and Radius inclusive.
    Y1:     The Y value within the fillet to start the cylinder
    Y2:     The Y value within the fillet to end the cylinder
*/
module FilletSection(Radius,Y1,Y2) {
  assert(Y1<=Radius);
  assert(Y2<=Radius);
  assert(Y2!=Y1);
  assert(Radius>0);

  y1 = Y1/Radius;
  y2 = Y2/Radius;
  a1 = asin(y1);
  a2 = asin(y2);
  c1 = cos(a1);
  c2 = cos(a2);
  X1 = c1*Radius;
  X2 = c2*Radius;

  cylinder(h=abs(Y2-Y1), r1=Radius-X1, r2=Radius-X2, center=false, $fn=fn4(r=Radius));
}

```