[User]
I'm looking to create a parametric tool for my Dremel rotary tool workstation, specifically the 220-01 model. It's designed to help with circular cutouts, providing a stable center point. I'll need this to fit snugly into the center hole of the workstation, and ideally I'd like to be able to use either a nail or a screw as the center guide.

Here's what I'm thinking:

The design will have a main body that fits into the workstation's center hole. This body should have a lip or flange at the top. Inside that, I need a central hole for the guide. I'd like to be able to adjust the diameter of this hole for different nail or screw sizes. I will need to be able to change the diameter of the inner ring and the outside diameter to be able to make it fit different tools. Finally, I will also want to make radial support arms.

Here's a breakdown of the parameters I want to control:

*   `holeDiameter`: Diameter of the center hole (for the nail or screw).
*   `innerDiameter`: Diameter of the inner cylindrical section
*   `innerHeight`: Height of the inner cylindrical section
*   `topDiameter`: Diameter of the top flange/lip.
*   `topHeight`: Height of the top flange/lip.
*   `radialSupportCount`: The number of radial supports.
*    `radialSupportOffset`: Offset distance of radial support cylinder from inner cylinder.
*    `radialSupportWidth`: Width of radial support cylinder.
*    `radialSupportRadius`: Radius of radial support cylinder.

I would need to be able to change the number of supports and the distance from the inner diameter. The top lip should overhang the inner cylinder.

I'm thinking that these parameters should be easily modifiable in the OpenSCAD customizer. It would be great if the OpenSCAD code was well organized and commented so I could understand how the different parts are put together.

I'd also like it to use the `$fn` variable to control the smoothness of the curves. Please make the default `$fn` 360.

Finally, I'm not sure if this is needed, but I'm thinking I will use BOSL2 to make this since I've heard that it is very good with parametric designs.

[Assistant]
circular_cutter.scad:
```
holeDiameter = 2;
innerDiameter = 20.5;
innerHeight = 5;

topDiameter = 140;
topHeight = 5;

/* [Hidden] */

$fn = 360;

holeRadius = holeDiameter / 2;
innerRadius = innerDiameter / 2;
topRadius = topDiameter / 2;

difference() {
  group() {
    cylinder(r=topRadius, h=topHeight);
    cylinder(r=innerRadius, h=topHeight + innerHeight);
  }

  translate([0, 0, -1])
    cylinder(r=holeRadius, topHeight + innerHeight + 2);

  /*
  translate([0, 0, 2])
    cylinder(r=innerRadius - 2, topRingHeight + innerHeight + 2);
  */

  translate([0, 0, -1])
  intersection() {
    translate([innerRadius - 2.5, innerRadius - 2.5, 0])
      rotate([0, 0, 0])
        cube([topRadius, topRadius, topHeight + 2]);

    translate([innerRadius - 2.5, innerRadius - 2.5, 0])
      rotate([0, 0, 20])
        cube([topRadius, topRadius, topHeight + 2]);

    translate([innerRadius - 2.5, innerRadius - 2.5, 0])
      rotate([0, 0, -20])
        cube([topRadius, topRadius, topHeight + 2]);
  }

  for(i=[90:45:360]) {
    rotate([0, 0, i]) {
      translate([innerRadius + 10 + 1, 0, -1])
        cylinder(r=6, h=topHeight + 2);

      translate([innerRadius + 8 + 1 + 20 + 10, 0, -1])
        cylinder(r=16, h=topHeight + 2);
    }
  }
}

```


[User]
I'm looking to design a parametric model for a trombone mouthpiece using OpenSCAD. I want to be able to customize various aspects of the mouthpiece, including the rim, cup, shank, and overall design. The mouthpiece should be printable in either one or two pieces, and I'd like the option to add support structures for printing.

Here's a breakdown of the features I'd like to include:

**Basic Dimensions:**

*   **Rim Diameter:**  This will be the primary measurement of the mouthpiece size and should be adjustable. I'd like to be able to specify common sizes like 12C, 6.5AL, 5G, 4G, 1.5G, and others. Assume measurements are in millimeters with options ranging from 20mm to 30mm.
*   **Cup Depth:** This is the distance from the bottom of the inner bite curve to the venturi along the Z-axis. I'd like options ranging from 20mm to 32mm.
*   **Cup Curve:** This parameter will control the shape of the cup's inner curve, with larger values resulting in a more bowl-shaped cup. Range from 15mm to 29mm.
*   **Drill (Venturi):** The diameter of the narrowest portion of the mouthpiece. I'd like to use standard drill sizes, such as those used by Bach (A, B, C, etc.) and have them range from 5.309mm to 8.611mm.
*   **Shank:** I need options for both "large" and "small" shank sizes. Additionally, include an option for the shank backbore, with options for large, medium, and small.

**Rim Details:**

*   **Rim Curvature:**  This will control the roundness of the rim's surface. I'd like options for flat, normal, curved, and very curved. Use a range from 1.5 to 4.
*   **Inside Bite:** Controls the sharpness of the inner edge of the rim. Options should include very sharp, sharp, medium, medium-soft, and soft, with values from 0.1 to 0.4.
*   **Rim Thickness:** This controls the thickness of the rim. Options should include very narrow, narrow, normal, slightly wide, and wide, with values ranging from 5.7mm to 8mm.
*   **Outside Bite:** Similar to the inside bite, but for the outer edge of the rim. Options should include sharp, medium, and soft, with values from 0.2 to 0.4.

**Miscellaneous:**

*   **Part:** Options for printing the mouthpiece as "two\_piece" (cup and shank separate), "one\_piece" (cup and shank together), "cup\_only", "shank\_only", or "profiles\_only" (just the 2D profiles).
*   **Printer Spooge Factor:** A small value (0-50, in mm\*100) that can be added to the venturi and rim ID to compensate for printer inaccuracies.
*   **Hex Nut Recesses:** Boolean options to include hexagonal recesses for nuts in the cup and/or shank.
*   **Taper Cup Interface:** A boolean option to taper the interface between the cup and shank when printing in two pieces.
*   **Cup Base Thickness:**  The thickness of the base of the cup, with options ranging from 1.68mm (4 walls) to 4.2mm (10 walls).
*   **Print Support:** A boolean option to include support structures for printing.
*   **Layer Height:**  The layer height to use when printing (used for support generation).
*   **Resolutions:** I'd like to be able to control the smoothness of the model by adjusting the resolution of the outer side, rim, and cup using `$fn`, `outer_resolution`, `rim_resolution`, and `cup_resolution`.
*   **Outer Design:** Options for the overall shape of the mouthpiece, including "rounded", "straight", "straight\_with\_text\_rim", and "concave" (similar to Marcinkiewicz/Dennis Wick designs).
*   **Write Parameters:**  A boolean option to write parameters onto the outside of the cup using the `Write.scad` library. This is not a commonly used library, but is still available on GitHub.
*   **Name:** A customizable string to include in the parameters written on the cup.

**Additional Details:**

*   The overall height of the mouthpiece should be determined by the shank size (78mm for small, 83mm for large).
*   The shank length should also be determined by the shank size (33mm for small, 38mm for large).
*   The cup should transition to the shank at a Z-height determined by `overallHeight - shank_length - cupOZ`, where `cupOZ` is a fixed value of 35mm.
*   The outer rim should have a height of 2.5mm.
*   The outside shoulder can be sloped, with options for no slope, slight slope, sloped, and very sloped.
*   The taper of the shank should be 0.025 (standard Morse taper).
*   The code should use Bezier curves to define the profiles of the rim, cup, and outer shape.
*   I'd like the option to include a raft along the top of the shank when printing in one piece to help prevent the mouthpiece from falling over during printing.

Could you generate a parametric OpenSCAD file or files that incorporate all of these features and options? I would like to be able to generate STLs for printing various mouthpiece designs based on these parameters. This file or files should utilize the `Write.scad` library.

[Assistant]
further_improved_mouthpiece.scad:
```
//use <Write.scad>

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

/*
12C:   23    / 22.5 / 23-AS
6.5AL: 24    / 25   / 20-EL
5G:    24.5  / 28   / 25-JS
4G:    25    / 29.5 / 23-JL
1.5G:  26.5  / 30   / 22-JS
S59:   27.25 / 30.5 / 21-MS
More or less Marcinkiewicz ETB-3: 28 / 28 / 22.8-KL
*/

/* [Basic] */
// measured from the lower end of the inside bite curve. Most commercial mouthpieces are measured somewhere along the bite curve, and the published diameters are consequently 0.5-1mm larger than this rim diameter parameter.
rim_diameter = 28; // [ 20: 0.787in / 20mm, 20.5: 0.807in / 20.5mm, 21: 0.827in / 21mm, 21.5: 0.846in / 21.5mm, 22: 0.866in / 22mm, 22.5: 0.886in / 22.5mm, 23: 0.906in / 23mm (12C), 23.5: 0.925in / 23.5mm, 24: 0.945in / 24mm (6.5AL), 24.5: 0.965in / 24.5mm (5G), 25: 0.984in / 25mm (4G), 25.5: 1.004in / 25.5mm (3G), 26: 1.024in / 26mm (2G), 26.5: 1.043in / 26.5mm (1.5G), 27: 1.063in / 27mm (S59), 27.5: 1.083in / 27.5mm, 28: 1.102in / 28mm, 28.5: 1.122in / 28.5mm, 29: 1.142in / 29mm, 29.5: 1.161in / 29.5mm, 30: 1.181in / 30mm ]

// specifies the distance from the bottom of the inner bite curve to the venturi along the Z axis.
cup_depth = 28; // [20: 0.787in / 20mm, 20.5: 0.807in / 20.5mm, 21: 0.827in / 21mm, 21.5: 0.846in / 21.5mm, 22: 0.866in / 22mm, 22.5: 0.886in / 22.5mm (12C), 23: 0.906in / 23mm, 23.5: 0.925in / 23.5mm, 24: 0.945in / 24mm, 24.5: 0.965in / 24.5mm, 25: 0.984in / 25mm (6.5AL), 25.5: 1.004in / 25.5mm, 26: 1.024in / 26mm, 26.5: 1.043in / 26.5mm, 27: 1.063in / 27mm, 27.5: 1.083in / 27.5mm, 28: 1.102in / 28mm (5G), 28.5: 1.122in / 28.5mm, 29: 1.142in / 29mm, 29.5: 1.161in / 29.5mm (4G), 30: 1.181in / 30mm (1.5G), 30.5: 1.201in / 30.5mm (S59), 31: 1.22in / 31mm, 31.5: 1.24in / 31.5mm, 32: 1.26in / 32mm]

// specifies the location of a control point for the inner cup curve. Large values mean a more bowl-shaped cup.
cup_curve = 22.8; // [ 15: 0.591in / 15mm, 16: 0.63in / 16mm, 17: 0.669in / 17mm, 18: 0.709in / 18mm, 19: 0.748in / 19mm, 20: 0.787in / 20mm (6.5AL), 21: 0.827in / 21mm (S59), 22: 0.866in / 22mm (1.5G), 22.8: 22.8mm, 23: 0.906in / 23mm (12C/4G), 24: 0.945in / 24mm, 25: 0.984in / 25mm (5G), 26: 1.024in / 26mm, 27: 1.063in / 27mm, 28: 1.102in / 28mm, 29: 1.142in / 29mm ]

/* DRILL */
// or venturi specifies the diameter of the narrowest portion of the mouthpiece.
drill = 7.137; //   [5.309: 4 0.209 in / 5.309 mm, 5.410: 3 0.213 in / 5.410 mm, 5.613: 2 0.221 in / 5.613 mm, 5.791: 1 0.228 in / 5.791 mm, 5.944: A 0.234 in / 5.944 mm (12C), 6.045: B 0.238 in / 6.045 mm, 6.147: C 0.242 in / 6.147 mm, 6.248: D 0.246 in / 6.248 mm, E 0.250 in / 6.350 mm (6-1/2AL), 6.528: F 0.257 in / 6.528 mm,         6.629: G 0.261 in / 6.629 mm (Bach 5GS),    6.756: H 0.266 in / 6.756 mm,         6.909: I 0.272 in / 6.909 mm,               7.036: J 0.277 in / 7.036 mm (1.5G/4G/5G), 7.137: K 0.281 in / 7.137 mm,             7.366: L 0.290 in / 7.366 mm,                        7.493: M 0.295 in / 7.493 mm (S59), 7.671: N 0.302 in / 7.671 mm,                        8.026: O 0.316 in / 8.026 mm,     8.204: P 0.323 in / 8.204 mm,                        8.433: Q 0.332 in / 8.433 mm,               8.611: R 0.339 in / 8.611 mm ]

shank = "large"; // [large, small]
shank_backbore = 2; // [2: Large, 3: medium, 4: small]

/* [Rim Details] */
// sets the position of a couple bezier control points on the rim curve (higher value means that the surface that meets your embouchure is more rounded):
rim_curvature = 4; // [1.5:flat, 2.5:normal, 3.5:curved, 4: very curved]

// is also affected by the rim curvature.
inside_bite = 0.35; // [0.1:very sharp, 0.2:sharp, 0.3:medium, 0.35: medium-soft, 0.4: soft]

// and the following parameters control the profile of the outer portion of the rim
rim_thickness = 5.7; // [5.7: very narrow, 6.5:narrow, 7:normal, 7.5: slightly wide, 8:wide]
outside_bite = 0.4; // [0.2:sharp, 0.3:medium, 0.4:soft]

/* [Misc] */

part = "one_piece"; // [two_piece: Two-piece Cup and Shank, one_piece: One-piece Cup and Shank, cup_only: Cup only, shank_only: Shank only, profiles_only: Just profiles (do not extrude)]

// in mm*100. This value is added to venturi and rim ID values to try and make the critical dimensions of the printed mouthpiece match the desired values.
printer_spooge_factor = 0;//[0:50]

cup_hex_nut = 0; // [1:yes, 0:no]
shank_hex_nut = 0; // [1:yes, 0:no]

// when printing in two pieces. Don't taper if you want to use the cup with a Elliott shsnk.
taper_cup_interface = 1; // [1:yes, 0:no]

// must be sturdy enough to support the mouthpiece without sagging if you're printing in two pieces and finishing with an acetone vapor bath.
cup_base_thickness = 1.68; // [ 1.68: 4 walls,  2.1: 5 walls,  2.52: 6 walls,  2.94: 7 walls,  3.36: 8 walls,  3.78: 9 walls,  4.2: 10 walls ]

// if printing two pieces to accommodate the 90 deg overhang, or if printing in one piece, print a raft.
print_support = 0; // [0:no, 1:yes]

// is just used when printing support.
layer_height = 0.1;

// controls how smooth the rotate_extrude is.
$fn = 360;
// Controls the resolution of the outer side of the mouthpiece. Higher = smoother
outer_resolution = 60;
// Controls the resolution of the rim of the mouthpiece. Higher = smoother
rim_resolution = 200;
// Controls the resolution of the cup of the mouthpiece. Higher = smoother
cup_resolution = 200;

//the outer design of the mouthpiece
outer_design = "concave"; // [rounded: A rounded outer design, straight: a straight outer design,straight_with_text_rim: A straight design with a vertical thing to write  text on, concave: a concave Marcinkiewicz/Dennis Wick like design]

/* [Hidden] */
// onto the outside of the cup
write_parameters = 0; // [0:no, 1:yes]

my_name = "Pieter";

overallHeight = (shank == "small") ? 78 : 83;

// in mm of the tapered portion of the shank:
shank_length = (shank == "small") ? 33 : 38; // [30:50]

cupOZ = 35;

cupShankTransitionZ = overallHeight - shank_length - cupOZ;

outer_rim_height = 2.5;

outside_shoulder_slope = 0; // [0:not sloped, 0.3:slight slope, 0.65:sloped, 0.99:very sloped]

// 0.025 is the standard Morse taper. You probably dont want to change this.
taper = 0.025;

twoPiece = (part == "one_piece") ? 0 : 1;

venturiR = drill/2 + printer_spooge_factor/100;
rimIR = rim_diameter/2 + printer_spooge_factor/100;
rimOR = rimIR + rim_thickness;

insideX = rim_thickness*inside_bite;
outsideX = rim_thickness*outside_bite;

cupIZ = cupOZ;

rimWid = rim_thickness * (1 - inside_bite - outside_bite) * 0.25;

extY = (cupIZ - insideX) - cup_depth;

cupCtlY1 = (cupIZ - insideX) - (cup_curve+1.2);
cupCtlY2 =  14;

pCup = [
  [rimIR, (cupIZ - insideX)],
  [rimIR, cupCtlY1+extY],
  [venturiR-0.5, cupCtlY2+extY],
  [venturiR, extY]
];

pRim = [
  [rimOR, cupIZ - outsideX], // 0
  [rimOR - (outsideX*outside_shoulder_slope), cupIZ+rim_curvature/2], // 1
  [rimIR + insideX, cupIZ+rim_curvature/2], // 2
  [rimIR, cupIZ], // 3
  [rimIR, cupIZ+(1-inside_bite)*1], // 4
  [rimIR, cupIZ-insideX] // 5
];

shankTopOR = ((shank == "large") ? 6 : 5);
shankEndOR = ((shank == "large") ? 12.7 : 11)/2;
shankTaperOR = shankEndOR+(shank_length * taper);

outerBot = PointAlongBez4(pCup, 0.85);
outerBotX = outerBot[0] + cup_base_thickness;
outerBotY = min(outerBot[1], 7);

outerTopX = pRim[0][0] - rim_thickness * 0.5;
outerTopY = pRim[0][1] - outer_rim_height - rim_thickness * 0.5;

midPt = PointAlongBez4(pCup, 0.6);

print_part();

module cup_profile() {
    rim = [for (i = [0:1:rim_resolution]) PointAlongBez6(pRim, i/rim_resolution)];
    cup = [for (i = [0:1:cup_resolution]) PointAlongBez4(pCup, i/cup_resolution)];
    venturi = [[venturiR, 0]]   ;

    outer_rim_bottom = [[pRim[0][0], pRim[0][1] - outer_rim_height]];

    pRimWithTextBottom = [outerTopX, min(midPt[1]*1.5, outerTopY-2)];
    if(outer_design == "rounded") {

        pOuter = [
          [outerBotX, outerBotY],
          [midPt[0] + cup_base_thickness*1.5, min( midPt[1], outerTopY - 8)],
          [outerTopX, min(midPt[1]*1.5, outerTopY - 2)],
          [outerTopX, outerTopY],
        ];
        outer = [for (i = [0:1:outer_resolution]) PointAlongBez4(pOuter, i/outer_resolution)];
        polygon(concat(rim, cup, venturi, outer, outer_rim_bottom));

    } else if (outer_design == "straight" ){

        polygon(concat(rim, cup, venturi, outer_rim_bottom));

    } else if(outer_design == "straight_with_text_rim") {

        straight_with_text_rim_outer = [
            [outerBotX, outerBotY],
         //   [midPt[0], 14],
            [midPt[0] + cup_base_thickness*1.5, min( midPt[1], outerTopY - 15)],
            pRimWithTextBottom,
            [outerTopX, outerTopY]
        ];
        polygon(concat(rim, cup, venturi, straight_with_text_rim_outer, outer_rim_bottom));

    } else if (outer_design == "concave") {
        pConcaveOuter = [
          [outerBotX+1, outerBotY+0.5],
          [midPt[0], min(midPt[1], outerTopY-14)],
          pRimWithTextBottom,
          pRimWithTextBottom
        ];

        concave_outer_bottom = [for (i = [0:1:outer_resolution]) PointAlongBez4(pConcaveOuter, i / outer_resolution)];

        concave_outer_top = [
            pRimWithTextBottom,
            [outerTopX, outerTopY]];
        polygon(concat(rim, cup, venturi, concave_outer_bottom, concave_outer_top, outer_rim_bottom));

    } else if (outer_design == "concave_2") {
        pConcaveOuter = [
            [outerBotX+1, outerBotY+0.5],
        [pCup[3][0]+5, pCup[3][1]],
        [pCup[2][0]+5, pCup[2][1]],

        pRimWithTextBottom
        ];

        concave_outer_bottom = [for (i = [0:1:outer_resolution]) PointAlongBez4(pConcaveOuter, i / outer_resolution)];

        concave_outer_top = [
            pRimWithTextBottom,
            [outerTopX, outerTopY]];
        polygon(concat(rim, cup, venturi, concave_outer_bottom, concave_outer_top, outer_rim_bottom));

    }

}
  // outscribeR = shankInterfaceWidePoint[0]/cos(180/fn);

shankInterfaceWidePoint = (taper_cup_interface && twoPiece) ?
  [shankTopOR+0.75+cup_base_thickness+7*taper*4   , -7 ] :
  [(twoPiece ? shankTopOR+cup_base_thickness : shankTopOR) , -7 ]; //shankTaperOR

module cupShankInterface() {
  if (taper_cup_interface && twoPiece) {
    polygon([
      [venturiR,0],
      [shankTopOR+0.75, 0],
      [shankInterfaceWidePoint[0]-cup_base_thickness, shankInterfaceWidePoint[1] ],
      shankInterfaceWidePoint,
      [shankInterfaceWidePoint[0], shankInterfaceWidePoint[1] + outerBotY],
      [outerBotX, outerBotY]
      ]);
  } else {
    polygon([
      [venturiR,0],
      [shankTopOR+(twoPiece ? printer_spooge_factor/100 : 0), 0],
      [shankTopOR+(twoPiece ? printer_spooge_factor/100 : 0), shankInterfaceWidePoint[1]],
      shankInterfaceWidePoint,
      [shankInterfaceWidePoint[0], shankInterfaceWidePoint[1] + outerBotY],
      [outerBotX, outerBotY],
      ]);
  }
}

shankCupInterfaceTaperLen = (taper_cup_interface && twoPiece) ? 9 : (twoPiece) ? 8 : 0.1;
shankTaper = (taper_cup_interface && twoPiece) ? shankCupInterfaceTaperLen*taper*4 : 0;
shankWidePoint = [shankTaperOR, -shankCupInterfaceTaperLen - (shankTaperOR-(shankTopOR+shankTaper))];
shankFlatLen = 7;
shankSpooge = (taper_cup_interface && twoPiece) ? 0 : printer_spooge_factor/100;

pShank = [
    [venturiR, 0],
    [shankTopOR - shankSpooge, 0],
    [shankTopOR + shankTaper - shankSpooge, -shankCupInterfaceTaperLen ],
     shankWidePoint,
    [shankWidePoint[0],                      shankWidePoint[1] - shankFlatLen],
    [shankEndOR,                             shankWidePoint[1] - shankFlatLen - shank_length],
    [shankEndOR - (0.42*shank_backbore),     shankWidePoint[1] - shankFlatLen - shank_length],
  ];

module shank_profile() {
  polygon(pShank);
}

dl = (drill == 5.309) ? "4" :
  (drill == 5.410) ? "3" :
  (drill == 5.613) ? "2" :
  (drill == 5.791) ? "1" :
  (drill == 5.944) ? "A" :
  (drill == 6.045) ? "B" :
  (drill == 6.147) ? "C" :
  (drill == 6.248) ? "D" :
  (drill == 6.350) ? "E" :
  (drill == 6.528) ? "F" :
  (drill == 6.629) ? "G" :
  (drill == 6.756) ? "H" :
  (drill == 6.909) ? "I" :
  (drill == 7.036) ? "J" :
  (drill == 7.137) ? "K" :
  (drill == 7.366) ? "L" :
  (drill == 7.493) ? "M" :
  (drill == 7.671) ? "N" :
  (drill == 8.026) ? "O" :
  (drill == 8.204) ? "P" :
  (drill == 8.433) ? "Q" :
  (drill == 8.611) ? "R" :
  (drill == 8.839) ? "S" :
  (drill == 9.093) ? "T" :
  "X";

string = str(my_name, " ", rim_diameter, "/", cup_depth, , "/", cup_curve, "-", dl, (shank == "small" ? "S" : "L"));
echo(str("Name = ", string));

module rotatedCup() {
  txtHeight = 3; // 4.5 - 2.5*cup_shape;
  difference() {
    rotate_extrude() union() {cup_profile();     cupShankInterface();}
    if (write_parameters) {
      writecylinder(
        text=string,
        where=[0,0,outerTopY],
        down=txtHeight+0.5,
        radius=outerTopX,
        height=txtHeight, h=txtHeight, t = 1.75);
    }
  }
  fn = 6;
  outscribeR = shankInterfaceWidePoint[0]/cos(180/fn);
  if (cup_hex_nut) {
    rotate_extrude($fn = fn) polygon([
      shankInterfaceWidePoint,
      [outscribeR, shankInterfaceWidePoint[1] + 2],
      [outscribeR, shankInterfaceWidePoint[1] + (part == "one_piece" ? 10 : 8)],
      [outscribeR-2.5, shankInterfaceWidePoint[1] + (part == "one_piece" ? 12: 10)],

    ]);
  }
}

module rotatedShank() {
  rotate_extrude() shank_profile();
  fn = 6;
  outscribeR = shankWidePoint[0]/cos(180/fn);
  if (shank_hex_nut) {
    rotate_extrude($fn = fn) polygon([
      shankWidePoint,
      [outscribeR, shankWidePoint[1] - 1.2],
      [outscribeR, shankWidePoint[1] - shankFlatLen+1.2],
      [shankWidePoint[0], shankWidePoint[1] - shankFlatLen],
    ]);
  }
}

module print_part() {
  // include <4G.scad>;
  if (part == "two_piece") {
    translate([0,  0,  7 ]) rotatedCup();
    if (print_support) {
      translate([0,  0,  7 ]) support();
    }
    translate([rim_diameter + 5,  0,  0 ])
    rotate([  0,       180,      0])
      rotatedShank();

  } else if (part == "one_piece") {
    translate([0,  0,  -pShank[6][1]+2 ]) rotatedCup();
    translate([0,  0,  -pShank[6][1]+2 ]) rotatedShank();
    if (print_support) {
      translate([0,  0,  -pShank[6][1]+2 ]) raft();
    }

  } else if (part == "cup_only") {
    translate([0,  0,  7 ]) rotatedCup();
    if (print_support) {
      translate([0,  0,  7 ]) support();
    }

  } else if (part == "shank_only") {
    rotate([  0,       180,      0])
      rotatedShank();

  } else if (part == "profiles_only") {
    rotate([90,0,0]) union() {cup_profile();     cupShankInterface();}
    translate([rim_diameter + 5,  0,  -7 ])
    rotate([  0,       180,      0])
    rotate([90,0,0]) shank_profile();
    for ( i = [0 : 3] ) {
      rotate([90,0,0]) translate(pCup[i]) color("Blue",0.5) circle(r = 0.3);
    }
    for ( i = [0 : 3] ) {
      rotate([90,0,0]) translate(pOuter[i]) color("Red",0.5) circle(r = 0.3);
    }
    for ( i = [0 : 5] ) {
      rotate([90,0,0]) translate(pRim[i]) color("Green",0.5) circle(r = 0.2);
    }
    for (i = [0.25:0.25:1-0.25]) {
    rotate([90,0,0]) translate(PointAlongBez4(pCup, i)) color("Purple",0.5) circle(r = 0.3);
    }
  }
}

module support() {
  supportR = shankTopOR - 0.3;
  for ( i = [0 : 45 : 180] )
  {
    rotate([0,0,i])
    translate([0,0,-7/2])
    cube([0.3, (supportR)*2, 7 - layer_height*1.25], center = true);
  }
  translate([0,0,-7])  cylinder(r = supportR, h = 0.3);
}

module raft() {
    //a mid-air raft to prevent the mouthpiece from falling-over
    //especially when calibrating is just very slightly off
    //set your slice to lightly generate support under this raft
  rx = pShank[6][0];
  ry = pShank[6][1];
  raft_up = 4;
    raft_height = 0.5;
  rotate_extrude($fn = 20) //speed up compile time slightly
    polygon([
  [pShank[4][0], pShank[4][1]+raft_up],
  [pShank[4][0], pShank[4][1]+raft_up],
 [pShank[4][0], pShank[4][1] - 1 + raft_up],
 [rim_diameter/1.75, pShank[4][1] - 1 + raft_up],
 [rim_diameter/1.75, pShank[4][1] - 1 - raft_height+ raft_up],
 [pShank[4][0], pShank[4][1] - 1 - raft_height + raft_up],
]);
}

//=======================================
// Functions
//=======================================
function BEZ03(u) = pow((1-u), 3);
function BEZ13(u) = 3*u*(pow((1-u),2));
function BEZ23(u) = 3*(pow(u,2))*(1-u);
function BEZ33(u) = pow(u,3);

function PointAlongBez4(p,u) = [
  BEZ03(u)*p[0][0]+BEZ13(u)*p[1][0]+BEZ23(u)*p[2][0]+BEZ33(u)*p[3][0],
  BEZ03(u)*p[0][1]+BEZ13(u)*p[1][1]+BEZ23(u)*p[2][1]+BEZ33(u)*p[3][1]
  ];

function Bez06(t) =            pow(1-t, 5);
function Bez16(t) = 5*pow(t,1)*pow(1-t, 4);
function Bez26(t) = 10*pow(t,2)*pow(1-t, 3);
function Bez36(t) = 10*pow(t,3)*pow(1-t, 2);
function Bez46(t) = 5*pow(t,4)*pow(1-t, 1);
function Bez56(t) =   pow(t,5);
function PointAlongBez6(p,u) = [
  Bez06(u)*p[0][0]+Bez16(u)*p[1][0]+Bez26(u)*p[2][0]+Bez36(u)*p[3][0]+Bez46(u)*p[4][0]+Bez56(u)*p[5][0],
  Bez06(u)*p[0][1]+Bez16(u)*p[1][1]+Bez26(u)*p[2][1]+Bez36(u)*p[3][1]+Bez46(u)*p[4][1]+Bez56(u)*p[5][1]
  ];

```


[User]
Hey, I'm looking to get a parametric v-belt pulley designed in OpenSCAD. I have a few specific dimensions in mind, and I also want to make sure it's compatible with a v-belt design I already have.

Here are the basic parameters I need to control:

*   **Rim Diameter:** This is the outer diameter of the pulley, where the belt actually makes contact.
*   **Groove Diameter:** This is the diameter of the bottom of the v-shaped groove.
*   **Arbor Diameter:** This is the diameter of the hole in the center for the shaft.
*   **Rim Width:** This is the thickness of the pulley rim itself (not including the groove).
*  **Rim Edge Width:** This is the thickness of the small rim that extends outwards beyond the grove on either side.
*   **Groove Width:** This is the width of the v-shaped groove.
*   **Screw Diameter:** This is the diameter of the screw holes, which will be for set screws, likely.

I envision the pulley as having a central hub with the arbor hole, and then the two rims extending outwards, with the v-shaped groove in the middle. The rim edges extend outward slightly past where the groove ends on each side.

I want to be able to adjust all of these via customizer variables.

Additionally, I need two set screw holes, one parallel and one perpendicular to the bore. I want them to be centered on the bore.

For compatibility, I'm using a v-belt design that has an internal diameter at the v of about 8 mm, but the groove depth isn't really that critical.

It's really important that all the dimensions are parametric. I'm looking for a modular design that I can adjust on the fly. I don't have any preference for libraries, though. Could you help me with this?

[Assistant]
v-belt-pully.scad:
```
Rim_Diameter=10;
Groove_Diameter=8;
Arbor_Diameter=8;
Rim_Width=2;
Rim_Edge_Width=1;
Groove_Width=4;
Screw_Diameter=2.8;

module build()
{
    Rim_Diameter=Rim_Diameter*2;
Groove_Diameter=Groove_Diameter*2;
    Arbor_Diameter=Arbor_Diameter*2;
union() {
 cylinder(h=Rim_Width, r1=Groove_Diameter, r2=Rim_Diameter, center=false,$fn=100);
    translate(v = [0, 0, Groove_Width*-1]) {rotate(a=[0,180,0]) {
  cylinder(h=Rim_Width, r1=Groove_Diameter, r2=Rim_Diameter, center=false,$fn=100);}

 translate(v = [0, 0, Groove_Width]) {rotate(a=[0,180,0]) {
  cylinder(h=Groove_Width, r1=Groove_Diameter, r2=Groove_Diameter, center=false,$fn=100);}

 translate(v = [0, 0, Rim_Width - Rim_Edge_Width + Rim_Edge_Width ]) {
  cylinder(h=Rim_Edge_Width, r1=Rim_Diameter, r2=Rim_Diameter, center=false,$fn=100);}

  translate(v = [0, 0, Groove_Width  *-1 - Rim_Edge_Width- Rim_Width]) {
  cylinder(h=Rim_Edge_Width, r1=Rim_Diameter, r2=Rim_Diameter, center=false,$fn=100);}

    }}}}

  difference()
    {
    build();
    translate(v = [0, 0, -50]) {
         cylinder(h=100, r1=Arbor_Diameter, r2=Arbor_Diameter, center=false,$fn=100); }

      translate(v = [0, 0, -Groove_Width/2]) rotate(a=[0,90,0]) { cylinder(h=100, r1=Screw_Diameter, r2=Screw_Diameter, center=false,$fn=100);
        rotate(a=[0,90,90])  cylinder(h=100, r1=Screw_Diameter, r2=Screw_Diameter, center=false,$fn=100);
      }
    }

```


[User]
I need a parametric OpenSCAD design for a cylindrical distance sleeve, something like a reducer for bike parts. I'm thinking it should have a few key adjustable parameters:

*   **`outer_diameter`**: The outside diameter of the sleeve.
*   **`inner_diameter`**: The inside diameter of the sleeve.
*   **`height`**: The overall height of the sleeve.
*   **`collar_diameter`**:  A wider diameter collar at one end for extra stability/alignment.
*   **`slot_width`**: A slot running along the side of the cylinder, useful for clamping or fitting around existing parts.

I'd like the slot to be a rectangular cutout and run the full length of the sleeve height, and I want the collar to be flush with the main cylinder body on the outside diameter - basically, a cylinder of `outer_diameter` with a small section at one end that is `collar_diameter`. Also, for the slot, could you make it so that it's centered relative to the Y axis (like on the provided example)?

I'd like to use the BOSL2 library for this. I'm expecting you to generate an entirely new SCAD file or files; I will not be working with the file I initially gave you.

[Assistant]
Distanzhuelle_27.scad:
```
$fn=100;
hoehe=50;
dm=32;
idm=27;
kragen=36;
schlitz=2;
difference(){
union(){
cylinder(hoehe+3,d=dm,d=dm);
cylinder(3,d=kragen,d=kragen);
}
union() {
cylinder(hoehe*2,d=idm,d=idm);
translate( [0,-schlitz*0.5, 0]) { cube([kragen,schlitz,hoehe*2]);}}
}

```


[User]
Hey there, I'm looking to get an OpenSCAD design for a card holder, specifically for standard-sized cards like those in the board game Talisman. I've seen some designs out there but I'd like a parametric version where I can adjust different aspects easily.

Here's what I'm thinking:

*   **Card Size:** The standard card dimensions are 63mm long and 41mm wide. I want this to be a base parameter.
*   **Tolerance:**  I need some clearance, say 1mm, on all sides of the cards.
*   **Wall Thickness:** I'd like to be able to specify wall thickness in terms of nozzle diameter and number of layers. So, maybe parameters for nozzle size (e.g., 0.4mm) and number of layers (e.g., 3 layers). The actual wall thickness would be calculated from these.
*   **Skirt:** I want a small outside skirt to help with bed adhesion, probably 2mm wide around the base of the holder.
*   **Wall Height:** This should be adjustable and dictate how many cards the holder can hold (e.g., 53mm for a larger capacity, perhaps another parameter to toggle between a "tall" and "short" option with pre-set heights).
*   **Wall Length:** The length of the walls on each corner, making them extend inward to hold the cards - say, 13mm.
*   **Base Thickness:**  I need a base at the bottom to hold the cards, probably 2mm thick.
*   **Cutout:** I want cutouts on the long sides of the base, say 80% of the card width.
*   **Support Structures:** I need some curved support structures along the walls on each side to make it sturdy. I want parameters for the height of these supports, as well as the radius and offset of the cylindrical cutouts on each side of the support, as well as the offset for the support along the width. The radius and offset should also be parametric, so I can adjust them.
*   **Parametric Support Curves:**  I need to be able to adjust the shape of the curve support. I want a parameter to adjust the radius of the circular cutout on the ends, and the position of the hole in relation to the cylinder, but also the offset of each support along the width.
*  **Width Ratio:** The shape of the curved support depends on the WidthRatio, which is CardWidth/CardLength.

Could you generate the OpenSCAD code for this? I don't need any fancy libraries, just the basic OpenSCAD functionality is fine. Let me know if any of this is unclear, I'd be happy to clarify!

[Assistant]
TalismanCardHolder.scad:
```
// Card Holder

// Card Length
CardLength = 63;

// Card Width
CardWidth = 41;

// Tolerance
Tolerance = 1;

// Size Of Nozzle
SizeOfNozzle = 0.4;

// Outside Skirt
OutsideSkirt = 2;

// Wall Thickness In Layers
WallThicknessInLayers = 3;

// Wall Height
WallHeight = 53;

// Wall Length
WallLength = 13;

// BottomThickness
BottomThickness = 2;

// Cut Out Percentage
CutOutPercentage = 80;

// Length Support
SupportHeight = 20;
HoleRadiusChange = 0.5;
HoleOffset = 4.5;

// Width Support (change Length Support first!)
WidthRadiusChange = 0.67; // percentage as a fraction
WidthOffset = -4.5;
WidthHeightChange = -7;

WallThickness = WallThicknessInLayers * SizeOfNozzle;
Rim = CardWidth * ((100 - CutOutPercentage) / 2) / 100;
CutOutLength = (CardLength - 3 * Rim) / 2;
CutOutWidth = CardWidth * CutOutPercentage / 100;
HoleRadius = (CardLength - 2 * WallLength + 2* Tolerance)/2 + HoleRadiusChange;
WidthRatio = CardWidth/CardLength;

// Bottom Layer
difference(){
	translate([	0 - Tolerance - OutsideSkirt,
					0 - Tolerance - OutsideSkirt,
					0])
	cube(size = [	CardLength + 2 * (Tolerance + OutsideSkirt),
						CardWidth + 2 * (Tolerance + OutsideSkirt),
						BottomThickness]);

	// Cut Out
	translate([	0 + Rim,0 + Rim,0])
	cube(size = [	CutOutLength,
						CutOutWidth,
						BottomThickness]);

	translate([	0 + 2 * Rim + CutOutLength,0 + Rim,0])
	cube(size = [	CutOutLength,
						CutOutWidth,
						BottomThickness]);

}

// Walls(LL)

	translate([	0 - Tolerance - WallThickness,
					0 - Tolerance - WallThickness,
				   BottomThickness])
	cube(size = [	WallThickness,
						WallThickness + WallLength,
						WallHeight]);

	translate([	0 - Tolerance - WallThickness,
					0 - Tolerance - WallThickness,
				   BottomThickness])
	cube(size = [	WallThickness + WallLength,
						WallThickness,
						WallHeight]);

// Walls(UL)

	translate([	0 - Tolerance - WallThickness,
					CardWidth + Tolerance - WallLength,
				   BottomThickness])
	cube(size = [	WallThickness,
						WallThickness + WallLength,
						WallHeight]);

	translate([	0 - Tolerance - WallThickness,
					CardWidth + Tolerance,
				   BottomThickness])
	cube(size = [	WallThickness + WallLength,
						WallThickness,
						WallHeight]);

// Walls(LR)

	translate([	CardLength + Tolerance - WallLength,
					0 - Tolerance - WallThickness,
				   BottomThickness])
	cube(size = [	WallThickness + WallLength,
						WallThickness,
						WallHeight]);

	translate([	CardLength + Tolerance,
					0 - Tolerance - WallThickness,
				   BottomThickness])
	cube(size = [	WallThickness,
						WallThickness + WallLength,
						WallHeight]);

// Walls(UR)

	translate([	CardLength + Tolerance - WallLength,
					CardWidth + Tolerance,
				   BottomThickness])
	cube(size = [	WallThickness + WallLength,
						WallThickness,
						WallHeight]);

	translate([	CardLength + Tolerance,
					CardWidth + Tolerance - WallLength,
				   BottomThickness])
	cube(size = [	WallThickness,
						WallThickness + WallLength,
						WallHeight]);

 // Width Support

    difference() {
        translate([	CardLength + Tolerance, WallLength - Tolerance, BottomThickness]) cube(size = [WallThickness, CardWidth+2*Tolerance-2*WallLength, SupportHeight+WidthHeightChange]);
        translate([CardLength + Tolerance, (CardWidth)/2, (SupportHeight*WidthRatio+BottomThickness+HoleOffset)+WidthOffset]) rotate([0, 90, 0]) cylinder($fn=180, WallThickness, HoleRadius*WidthRatio*WidthRadiusChange, HoleRadius*WidthRatio*WidthRadiusChange, false);
    }

    difference() {
        translate([	0 - WallThickness - Tolerance, WallLength - Tolerance, BottomThickness]) cube(size = [WallThickness, CardWidth+2*Tolerance-2*WallLength, SupportHeight+WidthHeightChange]);
        translate([0 - WallThickness - Tolerance, (CardWidth)/2, (SupportHeight*WidthRatio+BottomThickness+HoleOffset)+WidthOffset]) rotate([0, 90, 0]) cylinder($fn=180, WallThickness, HoleRadius*WidthRatio*WidthRadiusChange, HoleRadius*WidthRatio*WidthRadiusChange, false);
    }

// Length Support

    difference() {
        translate([	WallLength - Tolerance, 0 - Tolerance - WallThickness, BottomThickness]) cube(size = [CardLength - 2 * WallLength + 2* Tolerance, WallThickness, SupportHeight]);
        translate([CardLength/2, 0-Tolerance, SupportHeight+BottomThickness+HoleOffset]) rotate([90, 0, 0]) cylinder($fn=180, WallThickness, HoleRadius, HoleRadius, false);
    }

    difference() {
        translate([	WallLength - Tolerance, CardWidth+Tolerance, BottomThickness]) cube(size = [CardLength - 2 * WallLength + 2* Tolerance, WallThickness, SupportHeight]);
        translate([CardLength/2, CardWidth+Tolerance+WallThickness, SupportHeight+BottomThickness+HoleOffset]) rotate([90, 0, 0]) cylinder($fn=180, WallThickness, HoleRadius, HoleRadius, false);
    }

```


[User]
I'm looking to create a set of test holes for 3D printing, specifically for testing friction fits. I've been using a simple OpenSCAD script to do this, but I'd like something more robust and user-friendly, possibly split into multiple files. I'd also like to include some features I haven't implemented yet.

Here's the core functionality I need:

*   **Basic Test Block:** The design should start with a rectangular block of a specified thickness.
*   **Multiple Circular Holes:** The block should contain a series of circular holes, each progressively increasing in diameter.
*   **Parametric Control:** I need to be able to control the following parameters:
    *   The diameter of the smallest hole.
    *   The incremental step size between hole diameters.
    *   The total number of holes.
    *   The spacing between the holes.
    *   The overall thickness of the block.
    *   The number of facets to use for each circle.
*   **Arrangement:** The holes should be arranged in a single row along the length of the block, with spacing between each hole.
*   **Clearance:** I want to specify the distance from the edge of the hole to the edge of the block. Currently, the edge is simply determined from the widest hole, I'd like to have explicit edge padding.
*   **Labels:** It would be great if each hole was labeled with its diameter. I'd like these labels to be raised and easily visible.

I am not using any third party libraries, but I am happy if you include any that you think might be useful. It would be helpful if you could generate multiple .scad files to keep it modular and well organized, I'm thinking a main file, and a few for parts. I'm mostly concerned with the features I've described, and I'd like the code to be easy to modify later if I need to add more features. Could you help with this?

[Assistant]
testholes.scad:
```
// Number of facets on each hole.
facets=100;

// The diameter of the smallest hole.
base_hole = 12.0;

// How much to increase the size of each hole.
step = 0.1;

// How many holes to make.
count = 4;

// How much space between each hole.
pad = 5;

// How thick should the block be.
block_thickness = 5;

// How wide the block should be.
// Big enough for the widest hole plus padding on each side.
block_width = base_hole + step*count + pad*2;

// How long the block should be.
// Size of the smallest hole * the number of holes +
// 1 pad for each hole (plus 1 more for the end) +
// the accumulated growth of each hole (sum of arithemetic sequence).
block_length = (base_hole + pad + (step + step*count)/2) * count + pad;

difference() {
  // The block
  cube([block_length,block_width,block_thickness], false);

  // The holes
  union() {
    for(i=[0:count-1]) {
      // Offset for each hole is the same as the length calculation
      // except for the current index instead of the total.
      translate([(base_hole + pad + (step + step*i)/2)*i + base_hole/2 + pad,block_width/2,-1]) {
        // Hole size is just the smallest hole size plus the step times
        // index.
        cylinder(d=base_hole+step*i, h=block_thickness+2, $fn=facets);
      }
    }
  }
}

```


[User]
I'd like to create a custom case for a Seed Technology Voltmeter I purchased. The part number is 114990164, and it looks like [this](http://www.digikey.at/product-detail/de/seeed-technology-co-ltd/114990164/1597-1237-ND/5487639) on the Digi-Key website.

The case should have a top and a bottom, with the display and board mounted inside. The display dimensions are 22.6mm x 10mm x 6.3mm (length x width x height), but note that the height doesn't include the board. The board is 30mm x 11mm x 2.5mm. The case should have an opening on the top for the display to be visible, and the board should fit snugly into the bottom section. There should be an opening on the top for the wires to exit.

For customization, I'd like to be able to adjust:

*   **Height of the border:** This is the height of the bottom section of the case.
*   **Wall thickness:** I'd like separate parameters for the top/bottom walls and the left/right walls of the bottom section. There should also be a separate parameter for the wall thickness of the top lid.
*   **Spacing/tolerance:** I need to be able to control the tolerance between the display/board and the case, and also the tolerance between the top and bottom sections of the case.

The design should be fully parametric, so I can adjust these values easily. I'm also open to using any of the common OpenSCAD libraries like MCAD, BOSL, BOSL2, dotSCAD, NopSCADlib, or BOLTS, if they make the design easier or more efficient. Can you generate an OpenSCAD file that creates this customizable case?

[Assistant]
seed_technology_voltmeter.scad:
```
/**
 * Available displays:
 *
 * Seed Technology:
 * + http://media.digikey.com/pdf/Data%20Sheets/Seeed%20Technology/114990163_Web.pdf
 */

/* [Settings] */

// Height of the border
height = 4;

// Wall thickness border top and bottom.
walls = 1.5;
// Wall thickness border left and right.
wallsSide = 5;
// Wall thickness lid.
wallsLid = 1;

// Tolerance for the display.
spacing = 0.25;
// Tolerance for the lid.
spacingLid = 0.025;

/* [Display measurements] */

// Display length
displayLength = 22.6;
// Display width.
displayWidth = 10;
// Display height without the board.
displayHeight = 6.3;

// Board length.
boardLength = 30;
// Board width.
boardWidth = 11;
// Board height including components on the back.
boardHeight = 2.5;

/* [Hidden] */

$fn = 360;

screwHoleDiameter = 2;

segmentLength = 7;
paddingSegment = 2.2;

//seedVoltMeter(displayColor, "green", holes);
//seedVoltMeter(displayColor, "yellow", holes);
//seedVoltMeter(displayColor, "red", holes);
//seedVoltMeter(displayColor, "blue", holes);
holder(height);

module holder(height) {
  heightLid = wallsLid + height + boardHeight + 1;

  outerLength = displayLength + (wallsSide + spacing ) * 2;
  outerWidth = displayWidth + (walls + spacing ) * 2;

  innerLength = displayLength + spacing * 2;
  innerWidth = displayWidth + spacing * 2;

  outerLengthTop = outerLength + (wallsLid + spacing + spacingLid ) * 2;
  outerWidthTop = outerWidth + (wallsLid + spacing + spacingLid) * 2;
  innerLengthTop = outerLengthTop - wallsLid * 2;
  innerWidthTop = outerWidthTop - wallsLid * 2;

  outletWidth = 4;
  xOffsetOutlet = outerLength + spacing + walls - outletWidth - 4.75;

  module bottom() {
    difference() {
      cube([outerLength, outerWidth, height]);

      translate([wallsSide, walls, -1])
        cube([innerLength, innerWidth, height + 2]);
    }
  }

  module top() {
    difference() {
      cube([outerLengthTop, outerWidthTop, heightLid]);

      translate([wallsLid, wallsLid, wallsLid])
        cube([innerLengthTop, innerWidthTop, heightLid  + 1]);

      translate([xOffsetOutlet, -1, wallsLid])
        cube([outletWidth, walls + 2, heightLid + 1]);
    }
  }

  translate([-outerLengthTop / 2, -outerWidthTop - 1, 0])
    top();

  translate([-outerLengthTop / 2, 1, 0])
    bottom();
}

module seedVoltMeter(displayColor="white", ledColor="green", holes=true) {
  screwHoleRadius = screwHoleDiameter /2 ;
  padding = (boardLength - displayLength) / 2;

  segmentWidth = (displayLength - paddingSegment * 4) / 3;

  segments = [
    [padding + 1.5, 1.5, boardHeight],
    [padding + segmentWidth + paddingSegment * 2, 1.5, boardHeight],
    [padding + (segmentWidth + paddingSegment) * 2 + paddingSegment, 1.5, boardHeight]
  ];

  translate([-boardLength / 2, -displayWidth / 2, 0]) {
    color("green") {
      difference() {
        cube([boardLength, boardWidth, boardHeight]);

        width = boardWidth / 3 + 1;
        length = (boardLength - displayLength) / 2 + 1;

        translate([-1, -1, -1])
          cube([length, width, boardHeight + 2]);

        translate([boardLength - length + 1, -1, -1])
          cube([length, width, boardHeight + 2]);

        translate([boardLength - length + 1, boardWidth -width + 1, -1])
          cube([length, width, boardHeight + 2]);

        translate([-1, boardWidth -width + 1, -1])
          cube([length, width, boardHeight + 2]);

        translate([length / 2 - 0.5, boardWidth / 2, -1])
          cylinder(r=screwHoleRadius, h = boardHeight + 2);
        translate([boardLength -length / 2 + 0.5, boardWidth / 2, -1])
          cylinder(r=screwHoleRadius, h = boardHeight + 2);
      }
    }
    color(displayColor)
      group() {
        difference() {
          translate([padding, 0, boardHeight])
            cube([displayLength, displayWidth, displayHeight]);

          if(holes)
            for(pos=segments)
              translate(pos)
                segment(displayHeight + 1);
        }
      }

      if(holes)
        for(pos=segments)
          translate(pos)
            color(ledColor)
              segment(displayHeight);
  }

  module segment(height, color="white") {
    length = segmentWidth - 2;
    width = 0.5;
    horizontal = [
      [1, 0, 0],
      [1, (segmentLength - width) / 2, 0],
      [1, segmentLength - width, 0]
    ];

    vertical = [
      [0, width, 0],
      [segmentWidth - width, width, 0],
      [0, width + (segmentLength - width * 3) / 2 +width, 0],
      [segmentWidth - width, width + (segmentLength - width * 3) / 2 +width, 0]
    ];

    group() {
      for(pos=horizontal)
        translate(pos)
          cube([length, width, height]);

      for(pos=vertical)
        translate(pos)
          cube([width, (segmentLength - width * 3) / 2, height]);

      translate([segmentWidth + width * 1.5, 0, 0])
        cube([width, width, height]);
    }
  }
}
```


[User]
I'd like to create an extruder cleaner for my 3D printer, similar to the one described on Thingiverse (thing:1701300). It's essentially a thin, flat tool with a hole for scraping the nozzle. I'd like to be able to customize it for different nozzle sizes, label lengths, and thickness.

Here's what I'm thinking:

*   **Shape:** It should be a rectangular solid.
*   **Scraping Hole:** A circular hole near one end, sized to fit around the extruder nozzle.  I'd like this to be parametric, `cleaning_hole`, and accept a float.
*   **Thickness:** The thickness of the entire object, also parametric: `thickness`, a float.
*   **Dimensions:**  I need to be able to control the length (`length`) and the height of the rectangle (`height`), both floats.
*   **Labels:** It should have two lines of text on it, "Extruder" and "Cleaner", or similar, with the text being parametric via `label1` and `label2` (both strings). The text size should be a parameter `text_size`, a float. There needs to be some kind of adjustment for how long the label takes up using `override_text_len` (a float); if this value is zero, then automatically compute the text length from text\_size.

I also want a module that will take the quantity as an input `quantity` (an integer) and output a set of these objects arrayed in a way that is efficient to print on a bed. So the module should be called `MakeSet` and take the parameter `quantity`. This should use a similar approach to that used in the attached document, and attempt to fit as many of the tools as possible on the bed.

Please provide a parametric OpenSCAD file to generate this, and make sure it's clear how to configure all the parameters. I don't need any external libraries to be used.

[Assistant]
extruder_cleaner.scad:
```
// Created in 2016 by Ryan A. Colyer.
// This work is released with CC0 into the public domain.
// https://creativecommons.org/publicdomain/zero/1.0/
//
// http://www.thingiverse.com/thing:1701300

quantity = 1;

cleaning_hole = 5;
thickness = 2.0;

length = 55.0;
height = 15.0;

text_size = 5;
override_text_len = 0;

label1 = "Extruder";
label2 = "Cleaner";

text_len = (override_text_len > 0) ? override_text_len : (text_size * 6.8);

module ExtruderCleaner() {
//  translate([thickness, length, 0])
//  rotate([90, 0, -90])
  union() {
    difference () {
      cube([length, height, thickness]);

      translate([length - height/2, height/2, -0.01])
        cylinder(h = thickness + 0.02, r=cleaning_hole/2+0.4);
      translate([length - height/2 - cleaning_hole/2 - text_len,
        (2*height - text_size)/3, thickness/2])
        linear_extrude(height=thickness/2+0.01)
        text(label1, size=text_size, spacing=1.2);
      translate([length - height/2 - cleaning_hole/2 - text_len,
        (height - 2*text_size)/3, thickness/2])
        linear_extrude(height=thickness/2+0.01)
        text(label2, size=text_size, spacing=1.2);
    }
  }
}

// This creates an array of the specified number its children, arranging them
// for the best chance of fitting on a typical build plate.
module MakeSet(quantity=1, x_len=30, y_len=30) {
  bed_yoverx = 1.35;
  x_space = ((x_len * 0.2) > 15) ? (x_len * 0.2) : 15;
  y_space = ((y_len * 0.2) > 15) ? (y_len * 0.2) : 15;
  function MaxVal(x, y) = (x > y) ? x : y;
  function MaxDist(x, y) = MaxVal((x_len*x + x_space*(x-1))*bed_yoverx,
    y_len*y + y_space*(y-1));
  function MinExtentX(x) = ((x >= quantity) ||
    (MaxDist(x+1, ceil(quantity/(x+1))) > MaxDist(x, ceil(quantity/x)))) ?
    x : MinExtentX(x+1);
  colmax = MinExtentX(1);

  for (i=[1:quantity]) {
    translate([(x_len + x_space)*((i-1)%colmax),
      (y_len + y_space)*floor((i-1)/colmax), 0])
      children();
  }
}

MakeSet(quantity, length, height)
ExtruderCleaner();

```


[User]
I need to create a parametric OpenSCAD model for a universal connector that joins two rods together. This model should allow for a lot of customization. Here are the specific features and parameters I'd like to include:

**Main Parameters:**

*   `Distance`: The overall distance between the centers of the two rods when they are fully extended and aligned.
*   `fn`: The number of fragments for generating curved surfaces (affects the smoothness of the model).

**Top Rod (Rod 2) Parameters:**

*   `Knuckle_Rotate`, `Knuckle_Pitch`, `Knuckle_Roll`: Euler angles controlling the orientation of the second rod relative to the first.
*   `R2`: Radius of the main part of the second rod.
*   `L2`: Length of the main part of the second rod.
*   `Nut_R2`: Radius of the enlarged "nut" section at the end of the second rod.
*   `Nut_H2`: Height of the "nut" section of the second rod.
*   `Fat_R2`: Additional radius added to the "nut" section for fattening.
*   `Fat_L2`: Additional length added to the ends of the second rod for fattening.
*   `Corner2`: Corner radius for rounding the edges of the second rod.

**Bottom Rod (Rod 1) Parameters:**

*   `R1`: Radius of the main part of the first rod.
*   `L1`: Length of the main part of the first rod.
*   `L1offset`: x-offset for the first rod.
*   `Nut_R1`: Radius of the "nut" section at the end of the first rod.
*   `Nut_H1`: Height of the "nut" section of the first rod.
*   `Fat_R1`: Additional radius added to the "nut" section for fattening the first rod.
*   `Fat_L1`: Additional length added to the ends of the first rod for fattening.
*   `Corner1`: Corner radius for rounding the edges of the first rod.
*   `Knuckle_RotateB`, `Knuckle_PitchB`, `Knuckle_RollB`: Euler angles controlling the orientation of the first rod, in cases of symmetry.

**Symmetry Options:**

*   `Zmirror`: A boolean flag to enable or disable mirroring the entire assembly along the Z-axis.
*   `Ang`: Rotation angle for the mirrored half when `Zmirror` is enabled.
*   `Copies`: The number of copies to create using rotational symmetry around the Z-axis (when `Copies` is non-zero, `Zmirror` is ignored).

**Detail Parameters:**

*   `wristoffset1`, `wristoffset2`: Control the position of the "wrist" sections (narrower parts) along the connector.
*   `struttslimr1`, `struttsliml1`, `struttslimr2`, `struttsliml2`: Control the slimming of the rods in the "wrist" sections for both radius and length.
*   `roundratio1`, `roundratio2`: Control the rounding of inner and outer corners of the dumbbell shapes.

**Experimental Features:**

*   `clamp1`, `clamp2`: Boolean flags to enable or disable clamps at the ends of the first and second rods, respectively. These clamps should be integrated into the design and should have adjustable sizes.

**Design Requirements:**

1. The connector should consist of two main rod sections, each with customizable radius, length, and orientation.
2. Each rod should have an enlarged "nut" section at its end for securing it to other components.
3. The connector should have a "wrist" section where the rods narrow, creating a more flexible joint.
4. The model should support optional mirroring along the Z-axis and rotational symmetry around the Z-axis.
5. The model should include optional clamps at the ends of the rods.
6. The design should be smooth and aesthetically pleasing, with rounded edges where appropriate.
7. Use the `MCAD` library for the `rotate_extrude` function.

The OpenSCAD files should be well-structured, commented, and easy to modify.

[Assistant]
RodJobv20.scad:
```
/* [main] */
Distance=160;//
//sides
fn=30;//
/* [Top Rod(2)] */
Knuckle_Rotate=60;//
Knuckle_Pitch=45;//
Knuckle_Roll=0;//
R2=20;//
L2=40;//
Nut_R2=30;//
Nut_H2=20;//
Fat_R2=10;//
Fat_L2=10;//
Corner2=5;//
/* [Bottom Rod(1)] */
R1=25;//
L1=50;//
L1offset=0;
Nut_R1=30;//
Nut_H1=20;//
//Fattening of Radius
Fat_R1=10;//
//Fattening of end caps
Fat_L1=10;//
//Corner Radius
Corner1=5;//
Knuckle_RotateB=0;//
Knuckle_PitchB=0;//
Knuckle_RollB=0;//
/* [Symmetry] */
Zmirror=0;//[1,0]
Ang=30;//
// Rotational duplication
Copies=0;//
/* [Details] */

wristoffset1=0.6;//
wristoffset2=0.6;//

struttslimr1=0.9;//
struttsliml1=0.75;//
struttslimr2=0.9;//
struttsliml2=0.75;//
roundratio1=0.1;//
roundratio2=0.1;//

/* [Experimental] */
clamp1=1;//[1,0]
clamp2=1;//[1,0]
/* [Hidden] */

wrist1=Distance/3*wristoffset1;
wrist2=Distance/3*wristoffset2;
ic=(R1+R2)/2*roundratio1;//
oc=(Nut_R1+Nut_R2)/2*roundratio2;//
if(Zmirror==1)
{

union (){rotate([Ang,0,0])RodJob();mirror([0,0,1])rotate([Ang,0,0])RodJob(0,0,Knuckle_RollB);}}
else if(Copies!=0)
{union(){
Knuckle_Rotate1=0;//
Knuckle_Pitch1=0;//
Knuckle_Roll1=Knuckle_RollB;//

minang=max(Ang,1);
for (i=[0:minang:minang*Copies])rotate([i,0,0]) RodJob(0,0,Knuckle_RollB);}}
else {

RodJob(Knuckle_RotateB,Knuckle_PitchB,Knuckle_RollB);
}
module RodJob(Knuckle_Rotate1,Knuckle_Pitch1, Knuckle_Roll1){
difference(){
color("LightGrey")union(){
hull(){
rotate([Knuckle_Roll1,Knuckle_Pitch1,Knuckle_Rotate1]) translate([L1offset,0,0]) rotate([0,90,0])   Rcylinder(Nut_R1+Fat_R1,L1+Fat_L1,Corner1,fn);

rotate([Knuckle_Roll1,Knuckle_Pitch1,Knuckle_Rotate1])translate([L1offset,0,wrist1]) rotate([0,90,0])     Rcylinder(Nut_R1*struttslimr1,L1*struttsliml1,Corner1,fn);

color("LemonChiffon") rotate([Knuckle_Roll1,Knuckle_Pitch1,Knuckle_Rotate1])translate([L1offset,0,wrist1]) rotate([0,90,0])     Rcylinder(Nut_R1*struttslimr1*0.5,L1*struttsliml1*0.5,0,fn);
}

hull(){
rotate([Knuckle_Roll1,Knuckle_Pitch1,Knuckle_Rotate1])translate([L1offset,0,wrist1]) rotate([0,90,0])     Rcylinder(Nut_R1*struttslimr1,L1*struttsliml1,Corner1,fn);

translate([0,0,Distance])rotate([Knuckle_Roll,Knuckle_Pitch,Knuckle_Rotate]) translate([0,0,-wrist2]) rotate([0,90,0]) Rcylinder(Nut_R2*struttslimr2,L2*struttsliml2,Corner2,fn);
}
color("LemonChiffon")hull(){
rotate([Knuckle_Roll1,Knuckle_Pitch1,Knuckle_Rotate1])translate([L1offset,0,wrist1]) rotate([0,90,0])  Rcylinder(Nut_R1*struttslimr1*0.5,L1*struttsliml1*0.5,0,fn);

translate([0,0,Distance])rotate([Knuckle_Roll,Knuckle_Pitch,Knuckle_Rotate]) translate([0,0,-wrist2]) rotate([0,90,0]) Rcylinder(Nut_R2*struttslimr2*0.5,L2*struttsliml2*+.5,0,fn);
}

hull(){
color("LemonChiffon")translate([0,0,Distance])rotate([Knuckle_Roll,Knuckle_Pitch,Knuckle_Rotate]) translate([0,0,-wrist2]) rotate([0,90,0]) Rcylinder(Nut_R2*struttslimr2*0.5,L2*struttsliml2*0.5,0,fn);

translate([0,0,Distance])rotate([Knuckle_Roll,Knuckle_Pitch,Knuckle_Rotate]) translate([0,0,-wrist2]) rotate([0,90,0]) Rcylinder(Nut_R2*struttslimr2,L2*struttsliml2,Corner2,fn);

translate([0,0,Distance])rotate([Knuckle_Roll,Knuckle_Pitch,Knuckle_Rotate]) translate([0,0,0]) rotate([0,90,0]) {Rcylinder(Nut_R2+Fat_R2,L2+Fat_L2,Corner2,fn);
}

}
if (clamp1==1&&Zmirror!=1&&Copies==0){
translate([0,0,0])rotate([Knuckle_Roll1,Knuckle_Pitch1,Knuckle_Rotate1]) translate([L1offset,0,-Nut_R1*1.1]) rotate([0,90,90]){
rotate([0,0,45]) Rcylinder(((Nut_R1+Fat_R1)-R1)*.8,Nut_R1*1.6,oc,fn);
}}
if (clamp2==1){
translate([0,0,Distance])rotate([Knuckle_Roll,Knuckle_Pitch,Knuckle_Rotate]) translate([0,0,Nut_R2*1.1]) rotate([0,90,90]){
rotate([0,0,45])Rcylinder(((Nut_R2+Fat_R2)-R2)*0.8,Nut_R2*1.6,oc,fn);
}}

}

rotate([Knuckle_Roll1,Knuckle_Pitch1,Knuckle_Rotate1]) translate([L1offset,0,0])rotate([0,90,0]){#Dbell(R1,L1,Nut_R1,Nut_H1 ,ic,oc,fn);
}

translate([0,0,Distance])rotate([Knuckle_Roll,Knuckle_Pitch,Knuckle_Rotate])

rotate([0,90,0]){#Dbell(R2,L2,Nut_R2,Nut_H2 ,ic,oc,fn);}

if (clamp1==1&&Zmirror!=1&&Copies==0){
translate([0,0,0])rotate([Knuckle_Roll1,Knuckle_Pitch1,Knuckle_Rotate1]) translate([L1offset,0,-Nut_R1*1.1]) rotate([0,90,90]){
rotate([0,0,45]) Rcylinder(Nut_R1+Fat_L1+Fat_R1,R1/2,0,4);
#rotate([0,0,30])Dbell(((Nut_R1+Fat_R1)-R1)/4,Nut_R1*1.5,((Nut_R1+Fat_R1)-R1)/1.5,Nut_R1*1 ,ic/10,oc/10,fn);
}}
if (clamp2==1){
translate([0,0,Distance])rotate([Knuckle_Roll,Knuckle_Pitch,Knuckle_Rotate]) translate([0,0,Nut_R2 *1.1]) rotate([0,90,90]){
rotate([0,0,45])Rcylinder(Nut_R2+Fat_L2+Fat_R2,R2/2,0,4);
#rotate([0,0,30])Dbell(((Nut_R2+Fat_R2)-R2)/4,Nut_R2*1.5,((Nut_R2+Fat_R2)-R2)/1.5,Nut_R2*1 ,ic/10,oc/10,fn);
}}

}

rotate([Knuckle_Roll1,Knuckle_Pitch1,Knuckle_Rotate1]) translate([L1offset,0,0])rotate([0,90,0]){%cylinder(L1*5,R1,R1,$fn=fn,center=true);}

translate([0,0,Distance])rotate([Knuckle_Roll,Knuckle_Pitch,Knuckle_Rotate])

rotate([0,90,0]){%cylinder(L2*5,R2,R2,$fn=fn,center=true);}
}

module Rcylinder(radius,lenght,rcorner,fn=32){
rotate_extrude(angle = 360, convexity = 4,$fn=fn)
{
intersection(){
offset(r =rcorner)  {offset(r = -rcorner) {square([radius*2,lenght],center=true);}}
translate([max(lenght/2,radius),0,0])square([max(lenght,radius*2),max(lenght,radius*2)],center=true);
}
}
}module Dbell(r,l,nr,nl ,ic=1,oc=1,fn=32){
rotate_extrude(angle = 360, convexity = 4,$fn=fn)
{
intersection(){
translate([nr,0,0])square([nr*2,l+nl*2],center=true);
offset(r =oc)  offset(r = -(ic+oc)) offset(r =ic)union(){
translate([0,0,0])square([r*2,l],center=true);
translate([0,l/2+nl*0.5,0])square([nr*2,nl],center=true);
translate([0,-(l/2+nl*0.5),0])square([nr*2,nl],center=true);}

//#offset(r =ic)  {offset(r = -ic) {square([r*2,l],center=true);}}
}
}
}

```


[User]
Hey, I'm looking to get a parametric cartridge case designed for my Commodore 64. I've got an existing design I'm trying to base it on, but it has some things that need to be fixed and I'd like it to be a lot more flexible.

The cartridge case should be in two parts: a top and a bottom, with the ability to be rendered as either one or both halves together. It should have a standard edge connector opening for the C64, and include screw holes and posts for securing the PCB. I'd like to be able to adjust the overall length of the cartridge to accommodate different PCB sizes, and also be able to change the location and size of the post, or even remove it entirely if needed.

It also needs the option to have debossed stripes along the side, and to have a hole punched out for a label. I also think it would be nice to have the option to include support feet at the back for longer and heavier cartridges.

I know that there's some existing code for this kind of thing already (I included it below for reference, but please do not try to simply modify the existing design), but I'd like this new version to have some improvements:
- The edge connector opening needs to be much smoother.
- The screw holes could be improved.
- I'd like to include support for heat set inserts.
- It would be great to add truly parametric hole support, not just for the PCB post.
- It also needs to support side bulges (like the REU) and top bulges (like the 1541 Ultimate).

I don't know if it's possible, but could it also include configurable fillets, chamfers, or both? It would also be amazing if it could be driven entirely by a configuration object to make modifications easier.

I'm thinking the code should be in OpenSCAD, and that it would be good to use a library like BOSL2 for some of the advanced features.

```
// Copyright ©2016 Laurence Gonsalves <laurence@xenomachina.com>
//
// Code licensed under the Creative Commons - Attribution - Share Alike license.

// TODO: Fix port so it's smooth
// TODO: improve screw holes
// TODO: add heat set insert support
// TODO: add parametric hole support
// TODO: add side-bulge support (eg: REU)
// TODO: add top-bulge support (eg: 1541 Ultimate)

// Which part do you want to see? Note that Customizer's preview may time out if you choose "both".
part = "top"; // [top:Top,bottom:Bottom,both:Both]

/* [Appearance] */
// Would you like debossed stripes?
debossed_stripes = 1; // [1:Yes, 0:No]

// Would you like a hole punched out for the label?
label_hole = 0; // [1:Yes, 0:No]

// Add feet to support the back end of the cartridge? These are useful for longer or heavier cartridges.
add_feet = 0; // [1:Yes, 0:No]

/* [Internals] */
// Length of PCB (including edge connector). Adjusting this also ajusts the overall length of the case accordingly. Defaults to length used by standard Commodore cartridges.
PCB_length = 84; // [50: 200]

// Distance between edge connector and *center* of post + screw-hole. Defaults to correct distance for standard Commodore cartridges.
post_y = 43.36; // [20.00: 180.00]

// Size of the post hole in PCB. Use 0 to remove post and screw-hole entirely.
post_diameter = 5; // [0 : 15]

/* [Hidden] */

$fs=1.2;
$fa=10;
EPSILON = 1/128;

ORIGINAL_PCB_LEN = 84;
ORIGINAL_POST_Y = 43.36;
ORIGINAL_POST_DIA = 5;

// Good values for Gyruss (Parker Brothers) cart:
// post_y = 21.67
// PCB_length = 64;
// post_diameter = 9

module frustum(x, y, z, x2, y2) {
    linear_extrude(height=z, scale=[x2/x, y2/y]) {
        square([x, y], center=true);
    }
}

module torus(r1, r2) {
    rotate_extrude(convexity = 10)
    translate([r2 + r1, 0, 0])
    circle(r=r2);
}

module round_cylinder(fillet, r, h) {
    for(z=[fillet, h-fillet])
        translate([0,0,z])
            torus(r-2*fillet, fillet);
    cylinder(r=r-fillet, h);
    translate([0,0,fillet]) cylinder(r=r, h=h-2*fillet);
}

module partition() {
    union() {
        difference(){
            children(0);
            children(1);
        }
        intersection(){
            children(1);
            children(2);
        }
    }
}

HOLE_DEPTH = 18;
COUNTERSINK_DIAMETER = 6.25;
COUNTERSINK_TOP_DEPTH = .5;
SCREW_HEAD_DIAMETER = 4.93;
SCREW_OUTER_DIAMETER = 2.5;
SCREW_INNER_DIAMETER = 2.3;
SCREW_HEAD_HEIGHT = 1.67;

PCB_WIDTH = 58.12;

CART_DEPTH = 20;
CART_WIDTH = 68.38;
CART_LENGTH = PCB_length + 4.5;

PCB_THICKNESS = 1.5748;
SKIN = 2;
EDGE_R = 2.4;
END_R = 6;

COUNTERSINK_HEIGHT = (COUNTERSINK_DIAMETER - SCREW_OUTER_DIAMETER) / (SCREW_HEAD_DIAMETER - SCREW_OUTER_DIAMETER) * SCREW_HEAD_HEIGHT;

module hole() {
    $fn = 36;
    translate([0, 0, -CART_DEPTH/2])
    union() {
        cylinder(r=COUNTERSINK_DIAMETER/2, h=COUNTERSINK_TOP_DEPTH);
        translate([0, 0, COUNTERSINK_TOP_DEPTH]) {
            cylinder(r1=COUNTERSINK_DIAMETER/2, r2=SCREW_OUTER_DIAMETER/2, h=COUNTERSINK_HEIGHT);
        }
        cylinder(r=SCREW_OUTER_DIAMETER/2, h=CART_DEPTH/2);
        cylinder(r=SCREW_INNER_DIAMETER/2, h=HOLE_DEPTH);
    }
}

POST_HEIGHT = CART_DEPTH - 2*SKIN;
LEDGE_DIAMETER = post_diameter + 1;
POST_FILLET_R = EDGE_R * 2;
POST_FLEX_GAP = .2;

module post() {
    $fn = 36;
    difference() {
    translate([0,0,-POST_HEIGHT/2])
    union() {
        cylinder(r=LEDGE_DIAMETER/2, h=POST_HEIGHT);

        // fillet top and bottom
        for(i=[0,1])
            rotate([0,180*i,0])
            translate([0,0,-POST_HEIGHT*i])
            difference() {
                cylinder(r=LEDGE_DIAMETER/2 + POST_FILLET_R, h=POST_FILLET_R);
                translate([0,0,POST_FILLET_R]) torus(LEDGE_DIAMETER/2, POST_FILLET_R);
            }
    }
    // This gap helps keep the PCB snug
    cube([
         LEDGE_DIAMETER + 2*POST_FILLET_R,
         LEDGE_DIAMETER + 2*POST_FILLET_R,
         POST_FLEX_GAP], center=true);
    }
}

module pcb() {
    difference() {
        translate([-PCB_WIDTH / 2, 0, -PCB_THICKNESS])
            cube([PCB_WIDTH, PCB_length, PCB_THICKNESS]);
        if (post_diameter > 0) {
            translate([0, post_y, -PCB_THICKNESS-EPSILON])
                // - .25 is to give a little bit of tolerance
                cylinder(r=post_diameter/2 - .25, h=PCB_THICKNESS + 2* EPSILON);
        }
    }
}

WALL_THICKNESS = 1.6;
CONNECTOR_LENGTH = 10;
DEBOSS_DEPTH = .6;

STRIPE_START = CART_LENGTH - 35.5;
STRIPE_WIDTH = 2;
STRIPE_COUNT = 6;
STRIPE_END = STRIPE_START + (STRIPE_COUNT * 2 - 1) * STRIPE_WIDTH;

LABEL_WIDTH = 54.5;
LABEL_R = STRIPE_WIDTH;

module stripes() {
    for(i=[0:STRIPE_COUNT-1])
        translate([-CART_WIDTH/2-EPSILON, STRIPE_START + i*STRIPE_WIDTH*2, -CART_DEPTH/2-EPSILON])
            cube([CART_WIDTH + 2*EPSILON, STRIPE_WIDTH, CART_DEPTH + 2*EPSILON]);
}

module round_rect(r, w, d, h) {
    union() {
        for (x=[0,w])
            for (y=[0,d])
                translate([x, y, 0]) cylinder(r=r, h=h);
        translate([-r,0,0]) cube([w + 2*r, d, h]);
        translate([0,-r,0]) cube([w, d + 2*r, h]);
    }
}

module label(y=0, h=CART_DEPTH) {
    // label indent
    translate([-LABEL_WIDTH/2+LABEL_R, STRIPE_START + 2* STRIPE_WIDTH + LABEL_R, y])
        round_rect(
                   LABEL_R,
                   LABEL_WIDTH - 2* LABEL_R,
                   (STRIPE_COUNT *2 -3) * STRIPE_WIDTH - 2* LABEL_R - 2*STRIPE_WIDTH,
                   h);
}

module deboss_mask() {
    difference() {
        stripes();

        // label border
        translate([-LABEL_WIDTH/2+LABEL_R, STRIPE_START + 2* STRIPE_WIDTH + LABEL_R, 0])
            round_rect(
                       STRIPE_WIDTH + LABEL_R,
                       LABEL_WIDTH - 2* LABEL_R,
                       (STRIPE_COUNT *2 -3) * STRIPE_WIDTH - 2* LABEL_R - 2*STRIPE_WIDTH,
                       CART_DEPTH);
    }
    label();
}

module shell(inset) {
    $fn=36;
    translate([-CART_WIDTH/2, CART_LENGTH-END_R, 0])
    union() {
        // curved edges at end
        for(z=[-1, 1])
            translate([inset,0,z*(CART_DEPTH/2-END_R)])
                rotate([0, 90, 0])
                round_cylinder(EDGE_R-inset, END_R-inset, CART_WIDTH-2*inset);

        for(x=[0,CART_WIDTH-2*EDGE_R])
            // curved end side edges
            translate([x+EDGE_R, END_R-EDGE_R, -CART_DEPTH/2+END_R]) {
                rotate([0,0,90]) // line up faces
                    cylinder(r=EDGE_R-inset, h=CART_DEPTH-END_R*2);

                // all 4 long edges
                for(z=[0,1])
                    translate([0, -END_R+EDGE_R, z*(CART_DEPTH - 2 * EDGE_R)-END_R+EDGE_R])
                        rotate([90,90+z*90,0])
                        cylinder(r=EDGE_R-inset, h=CART_LENGTH-END_R-inset);
            }

        // top and bottom face
        translate([EDGE_R, inset-CART_LENGTH+END_R, -CART_DEPTH/2+inset])
            cube([CART_WIDTH-2*(EDGE_R), CART_LENGTH-END_R-inset, CART_DEPTH-2*inset]);

        // side face to end
        translate([inset, inset-CART_LENGTH+END_R, -CART_DEPTH/2+END_R])
            cube([CART_WIDTH-2*inset, CART_LENGTH-EDGE_R-inset, CART_DEPTH-2*END_R]);

        // side face to edges
        translate([inset, inset+END_R-CART_LENGTH, -CART_DEPTH/2+EDGE_R])
            cube([CART_WIDTH-2*inset, CART_LENGTH-END_R-inset, CART_DEPTH-2*EDGE_R]);

        // end face
        translate([EDGE_R, inset-CART_LENGTH+END_R, -CART_DEPTH/2+END_R])
            cube([CART_WIDTH-2*EDGE_R, CART_LENGTH-2*inset, CART_DEPTH-2*END_R]);
    }
}

CONNECTOR_MAX_WIDTH = 66.5;
CONNECTOR_MAX_HEIGHT = 15;
CONNECTOR_MIN_WIDTH = 64.1;
CONNECTOR_MIN_HEIGHT = 12.9;

module box() {
    union() {
        // main shell
        difference() {
            if (debossed_stripes) {
                partition() {
                    shell(0);
                    deboss_mask();
                    shell(DEBOSS_DEPTH);
                }
            } else {
                shell(0);
            }
            union() {
                difference() {
                    shell(SKIN);

                    // edge connector wall
                    translate([-CART_WIDTH/2, CONNECTOR_LENGTH, -CART_DEPTH/2])
                        cube([CART_WIDTH, WALL_THICKNESS, CART_DEPTH]);
                }

                // bevelled opening
                translate([0, -EPSILON, -PCB_THICKNESS/2])
                    rotate([270, 0, 0])
                    frustum(
                            CONNECTOR_MAX_WIDTH,
                            CONNECTOR_MAX_HEIGHT,
                            SKIN + 2*EPSILON,
                            CONNECTOR_MIN_WIDTH,
                            CONNECTOR_MIN_HEIGHT);
            }
        }
    }
}

FOOT_DEPTH = 7.6;
FOOT_R = FOOT_DEPTH/2;

module foot() {
    module foot_ball() {
        rotate([0, 90, 0]) // align poles with x-axis
            sphere(r=FOOT_R);
    }
    hull() {
        foot_ball();
        translate([0, 0, FOOT_DEPTH]) foot_ball();
        translate([0, -FOOT_DEPTH, FOOT_DEPTH]) foot_ball();
    }
}

module cart() {
    difference() {
        union() {
            if (post_diameter > 0) {
                translate([0, post_y, 0]) post();
            }
            if (add_feet) {
                difference() {
                    for(i=[-1,1])
                        translate([i * (CART_WIDTH/2 - EDGE_R - FOOT_R -
                        STRIPE_WIDTH),
                                  STRIPE_END - STRIPE_WIDTH - FOOT_R,
                                  -CART_DEPTH/2 - FOOT_DEPTH + FOOT_R])
                            foot();

                    translate([-CART_WIDTH/2, 0, -CART_DEPTH/2 + SKIN - EPSILON])
                        cube([CART_WIDTH, CART_LENGTH, CART_DEPTH/2]);
                }
            }
            box();
        }
        union() {
            if (post_diameter > 0) {
                translate([0, post_y, 0]) hole();
            }
            pcb();
        }
    }
}

module half(top) {
    module top_mask() {
        translate([-CART_WIDTH/2-EPSILON, -EPSILON, -EPSILON])
            difference() {
                cube([CART_WIDTH + 2*EPSILON, CART_LENGTH + 2*EPSILON, CART_DEPTH + 2*EPSILON]);

                // top/bottom interlock
                translate([SKIN/2, CONNECTOR_LENGTH + WALL_THICKNESS + EPSILON, 0])
                    difference() {
                        xl = CART_WIDTH - SKIN;
                        yl = CART_LENGTH - CONNECTOR_LENGTH - WALL_THICKNESS - SKIN/2 - EPSILON;
                        zl = SKIN;
                        cube([xl, yl, zl]);
                        translate([SKIN/2+1, -1, -1]) {
                            cube([xl - SKIN-2*1, yl - SKIN/2-1, zl + 2*1]);
                        }
                    }
            }
    }
    if (top) {
        difference() {
            intersection() {
                cart();
                top_mask();
            }
            if (label_hole) {
                label();
            }
        }

    } else {
        difference() {
            cart();
            top_mask();
        }
    }
}

module main() {
    rotate([90, 0, 0]) // rotate so edge connector is on build-plate
    if (part == "top") {
        half(true);
    } else if (part == "bottom") {
        half(false);
    } else {
        // both
        half(true);
        translate([0, 0, -12])
            half(false);
    }
}

module mockup() {
    color([0.2,0.2,0.2,1])
    cart();

    color([.8,.8,.85,1])
    label(y=CART_DEPTH/2 - DEBOSS_DEPTH, h=DEBOSS_DEPTH);
}

main();
```

[Assistant]
c64-cartridge.scad:
```
// Copyright ©2016 Laurence Gonsalves <laurence@xenomachina.com>
//
// Code licensed under the Creative Commons - Attribution - Share Alike license.

// TODO: Fix port so it's smooth
// TODO: improve screw holes
// TODO: add heat set insert support
// TODO: add parametric hole support
// TODO: add side-bulge support (eg: REU)
// TODO: add top-bulge support (eg: 1541 Ultimate)

// Which part do you want to see? Note that Customizer's preview may time out if you choose "both".
part = "top"; // [top:Top,bottom:Bottom,both:Both]

/* [Appearance] */
// Would you like debossed stripes?
debossed_stripes = 1; // [1:Yes, 0:No]

// Would you like a hole punched out for the label?
label_hole = 0; // [1:Yes, 0:No]

// Add feet to support the back end of the cartridge? These are useful for longer or heavier cartridges.
add_feet = 0; // [1:Yes, 0:No]

/* [Internals] */
// Length of PCB (including edge connector). Adjusting this also ajusts the overall length of the case accordingly. Defaults to length used by standard Commodore cartridges.
PCB_length = 84; // [50: 200]

// Distance between edge connector and *center* of post + screw-hole. Defaults to correct distance for standard Commodore cartridges.
post_y = 43.36; // [20.00: 180.00]

// Size of the post hole in PCB. Use 0 to remove post and screw-hole entirely.
post_diameter = 5; // [0 : 15]

/* [Hidden] */

$fs=1.2;
$fa=10;
EPSILON = 1/128;

ORIGINAL_PCB_LEN = 84;
ORIGINAL_POST_Y = 43.36;
ORIGINAL_POST_DIA = 5;

// Good values for Gyruss (Parker Brothers) cart:
// post_y = 21.67
// PCB_length = 64;
// post_diameter = 9

module frustum(x, y, z, x2, y2) {
    linear_extrude(height=z, scale=[x2/x, y2/y]) {
        square([x, y], center=true);
    }
}

module torus(r1, r2) {
    rotate_extrude(convexity = 10)
    translate([r2 + r1, 0, 0])
    circle(r=r2);
}

module round_cylinder(fillet, r, h) {
    for(z=[fillet, h-fillet])
        translate([0,0,z])
            torus(r-2*fillet, fillet);
    cylinder(r=r-fillet, h);
    translate([0,0,fillet]) cylinder(r=r, h=h-2*fillet);
}

module partition() {
    union() {
        difference(){
            children(0);
            children(1);
        }
        intersection(){
            children(1);
            children(2);
        }
    }
}

HOLE_DEPTH = 18;
COUNTERSINK_DIAMETER = 6.25;
COUNTERSINK_TOP_DEPTH = .5;
SCREW_HEAD_DIAMETER = 4.93;
SCREW_OUTER_DIAMETER = 2.5;
SCREW_INNER_DIAMETER = 2.3;
SCREW_HEAD_HEIGHT = 1.67;

PCB_WIDTH = 58.12;

CART_DEPTH = 20;
CART_WIDTH = 68.38;
CART_LENGTH = PCB_length + 4.5;

PCB_THICKNESS = 1.5748;
SKIN = 2;
EDGE_R = 2.4;
END_R = 6;

COUNTERSINK_HEIGHT = (COUNTERSINK_DIAMETER - SCREW_OUTER_DIAMETER) / (SCREW_HEAD_DIAMETER - SCREW_OUTER_DIAMETER) * SCREW_HEAD_HEIGHT;

module hole() {
    $fn = 36;
    translate([0, 0, -CART_DEPTH/2])
    union() {
        cylinder(r=COUNTERSINK_DIAMETER/2, h=COUNTERSINK_TOP_DEPTH);
        translate([0, 0, COUNTERSINK_TOP_DEPTH]) {
            cylinder(r1=COUNTERSINK_DIAMETER/2, r2=SCREW_OUTER_DIAMETER/2, h=COUNTERSINK_HEIGHT);
        }
        cylinder(r=SCREW_OUTER_DIAMETER/2, h=CART_DEPTH/2);
        cylinder(r=SCREW_INNER_DIAMETER/2, h=HOLE_DEPTH);
    }
}

POST_HEIGHT = CART_DEPTH - 2*SKIN;
LEDGE_DIAMETER = post_diameter + 1;
POST_FILLET_R = EDGE_R * 2;
POST_FLEX_GAP = .2;

module post() {
    $fn = 36;
    difference() {
    translate([0,0,-POST_HEIGHT/2])
    union() {
        cylinder(r=LEDGE_DIAMETER/2, h=POST_HEIGHT);

        // fillet top and bottom
        for(i=[0,1])
            rotate([0,180*i,0])
            translate([0,0,-POST_HEIGHT*i])
            difference() {
                cylinder(r=LEDGE_DIAMETER/2 + POST_FILLET_R, h=POST_FILLET_R);
                translate([0,0,POST_FILLET_R]) torus(LEDGE_DIAMETER/2, POST_FILLET_R);
            }
    }
    // This gap helps keep the PCB snug
    cube([
         LEDGE_DIAMETER + 2*POST_FILLET_R,
         LEDGE_DIAMETER + 2*POST_FILLET_R,
         POST_FLEX_GAP], center=true);
    }
}

module pcb() {
    difference() {
        translate([-PCB_WIDTH / 2, 0, -PCB_THICKNESS])
            cube([PCB_WIDTH, PCB_length, PCB_THICKNESS]);
        if (post_diameter > 0) {
            translate([0, post_y, -PCB_THICKNESS-EPSILON])
                // - .25 is to give a little bit of tolerance
                cylinder(r=post_diameter/2 - .25, h=PCB_THICKNESS + 2* EPSILON);
        }
    }
}

WALL_THICKNESS = 1.6;
CONNECTOR_LENGTH = 10;
DEBOSS_DEPTH = .6;

STRIPE_START = CART_LENGTH - 35.5;
STRIPE_WIDTH = 2;
STRIPE_COUNT = 6;
STRIPE_END = STRIPE_START + (STRIPE_COUNT * 2 - 1) * STRIPE_WIDTH;

LABEL_WIDTH = 54.5;
LABEL_R = STRIPE_WIDTH;

module stripes() {
    for(i=[0:STRIPE_COUNT-1])
        translate([-CART_WIDTH/2-EPSILON, STRIPE_START + i*STRIPE_WIDTH*2, -CART_DEPTH/2-EPSILON])
            cube([CART_WIDTH + 2*EPSILON, STRIPE_WIDTH, CART_DEPTH + 2*EPSILON]);
}

module round_rect(r, w, d, h) {
    union() {
        for (x=[0,w])
            for (y=[0,d])
                translate([x, y, 0]) cylinder(r=r, h=h);
        translate([-r,0,0]) cube([w + 2*r, d, h]);
        translate([0,-r,0]) cube([w, d + 2*r, h]);
    }
}

module label(y=0, h=CART_DEPTH) {
    // label indent
    translate([-LABEL_WIDTH/2+LABEL_R, STRIPE_START + 2* STRIPE_WIDTH + LABEL_R, y])
        round_rect(
                   LABEL_R,
                   LABEL_WIDTH - 2* LABEL_R,
                   (STRIPE_COUNT *2 -3) * STRIPE_WIDTH - 2* LABEL_R - 2*STRIPE_WIDTH,
                   h);
}

module deboss_mask() {
    difference() {
        stripes();

        // label border
        translate([-LABEL_WIDTH/2+LABEL_R, STRIPE_START + 2* STRIPE_WIDTH + LABEL_R, 0])
            round_rect(
                       STRIPE_WIDTH + LABEL_R,
                       LABEL_WIDTH - 2* LABEL_R,
                       (STRIPE_COUNT *2 -3) * STRIPE_WIDTH - 2* LABEL_R - 2*STRIPE_WIDTH,
                       CART_DEPTH);
    }
    label();
}

module shell(inset) {
    $fn=36;
    translate([-CART_WIDTH/2, CART_LENGTH-END_R, 0])
    union() {
        // curved edges at end
        for(z=[-1, 1])
            translate([inset,0,z*(CART_DEPTH/2-END_R)])
                rotate([0, 90, 0])
                round_cylinder(EDGE_R-inset, END_R-inset, CART_WIDTH-2*inset);

        for(x=[0,CART_WIDTH-2*EDGE_R])
            // curved end side edges
            translate([x+EDGE_R, END_R-EDGE_R, -CART_DEPTH/2+END_R]) {
                rotate([0,0,90]) // line up faces
                    cylinder(r=EDGE_R-inset, h=CART_DEPTH-END_R*2);

                // all 4 long edges
                for(z=[0,1])
                    translate([0, -END_R+EDGE_R, z*(CART_DEPTH - 2 * EDGE_R)-END_R+EDGE_R])
                        rotate([90,90+z*90,0])
                        cylinder(r=EDGE_R-inset, h=CART_LENGTH-END_R-inset);
            }

        // top and bottom face
        translate([EDGE_R, inset-CART_LENGTH+END_R, -CART_DEPTH/2+inset])
            cube([CART_WIDTH-2*(EDGE_R), CART_LENGTH-END_R-inset, CART_DEPTH-2*inset]);

        // side face to end
        translate([inset, inset-CART_LENGTH+END_R, -CART_DEPTH/2+END_R])
            cube([CART_WIDTH-2*inset, CART_LENGTH-EDGE_R-inset, CART_DEPTH-2*END_R]);

        // side face to edges
        translate([inset, inset+END_R-CART_LENGTH, -CART_DEPTH/2+EDGE_R])
            cube([CART_WIDTH-2*inset, CART_LENGTH-END_R-inset, CART_DEPTH-2*EDGE_R]);

        // end face
        translate([EDGE_R, inset-CART_LENGTH+END_R, -CART_DEPTH/2+END_R])
            cube([CART_WIDTH-2*EDGE_R, CART_LENGTH-2*inset, CART_DEPTH-2*END_R]);
    }
}

CONNECTOR_MAX_WIDTH = 66.5;
CONNECTOR_MAX_HEIGHT = 15;
CONNECTOR_MIN_WIDTH = 64.1;
CONNECTOR_MIN_HEIGHT = 12.9;

module box() {
    union() {
        // main shell
        difference() {
            if (debossed_stripes) {
                partition() {
                    shell(0);
                    deboss_mask();
                    shell(DEBOSS_DEPTH);
                }
            } else {
                shell(0);
            }
            union() {
                difference() {
                    shell(SKIN);

                    // edge connector wall
                    translate([-CART_WIDTH/2, CONNECTOR_LENGTH, -CART_DEPTH/2])
                        cube([CART_WIDTH, WALL_THICKNESS, CART_DEPTH]);
                }

                // bevelled opening
                translate([0, -EPSILON, -PCB_THICKNESS/2])
                    rotate([270, 0, 0])
                    frustum(
                            CONNECTOR_MAX_WIDTH,
                            CONNECTOR_MAX_HEIGHT,
                            SKIN + 2*EPSILON,
                            CONNECTOR_MIN_WIDTH,
                            CONNECTOR_MIN_HEIGHT);
            }
        }
    }
}

FOOT_DEPTH = 7.6;
FOOT_R = FOOT_DEPTH/2;

module foot() {
    module foot_ball() {
        rotate([0, 90, 0]) // align poles with x-axis
            sphere(r=FOOT_R);
    }
    hull() {
        foot_ball();
        translate([0, 0, FOOT_DEPTH]) foot_ball();
        translate([0, -FOOT_DEPTH, FOOT_DEPTH]) foot_ball();
    }
}

module cart() {
    difference() {
        union() {
            if (post_diameter > 0) {
                translate([0, post_y, 0]) post();
            }
            if (add_feet) {
                difference() {
                    for(i=[-1,1])
                        translate([i * (CART_WIDTH/2 - EDGE_R - FOOT_R -
                        STRIPE_WIDTH),
                                  STRIPE_END - STRIPE_WIDTH - FOOT_R,
                                  -CART_DEPTH/2 - FOOT_DEPTH + FOOT_R])
                            foot();

                    translate([-CART_WIDTH/2, 0, -CART_DEPTH/2 + SKIN - EPSILON])
                        cube([CART_WIDTH, CART_LENGTH, CART_DEPTH/2]);
                }
            }
            box();
        }
        union() {
            if (post_diameter > 0) {
                translate([0, post_y, 0]) hole();
            }
            pcb();
        }
    }
}

module half(top) {
    module top_mask() {
        translate([-CART_WIDTH/2-EPSILON, -EPSILON, -EPSILON])
            difference() {
                cube([CART_WIDTH + 2*EPSILON, CART_LENGTH + 2*EPSILON, CART_DEPTH + 2*EPSILON]);

                // top/bottom interlock
                translate([SKIN/2, CONNECTOR_LENGTH + WALL_THICKNESS + EPSILON, 0])
                    difference() {
                        xl = CART_WIDTH - SKIN;
                        yl = CART_LENGTH - CONNECTOR_LENGTH - WALL_THICKNESS - SKIN/2 - EPSILON;
                        zl = SKIN;
                        cube([xl, yl, zl]);
                        translate([SKIN/2+1, -1, -1]) {
                            cube([xl - SKIN-2*1, yl - SKIN/2-1, zl + 2*1]);
                        }
                    }
            }
    }
    if (top) {
        difference() {
            intersection() {
                cart();
                top_mask();
            }
            if (label_hole) {
                label();
            }
        }

    } else {
        difference() {
            cart();
            top_mask();
        }
    }
}

module main() {
    rotate([90, 0, 0]) // rotate so edge connector is on build-plate
    if (part == "top") {
        half(true);
    } else if (part == "bottom") {
        half(false);
    } else {
        // both
        half(true);
        translate([0, 0, -12])
            half(false);
    }
}

module mockup() {
    color([0.2,0.2,0.2,1])
    cart();

    color([.8,.8,.85,1])
    label(y=CART_DEPTH/2 - DEBOSS_DEPTH, h=DEBOSS_DEPTH);
}

main();

```


[User]
I'd like to design a customizable cookie cutter using OpenSCAD. The core idea is to take a text input and create a cookie cutter from it. I need parameters for all of the following:

*   **Text Input:**  A string of text that will form the basis of the cookie cutter.
*   **Text Size:**  The overall size/height of the text.
*   **Letter Spacing:**  The space between letters, expressed as a percentage. I want to be able to reduce the letter spacing so letters can connect to each other.
*   **Underline:** An option to include an underline under the text, to better help connect letters, which should be adjustable.
    *   **Underline Height:** How high the underline is relative to the font size.
*   **Wall Height:** The vertical height of the cookie cutter wall.
*   **Wall Thickness:** The thickness of the cookie cutter wall.
*   **Top Support Overhang:** The horizontal overhang of a top support structure.
*   **Top Support Thickness:** The thickness of the top support structure.
*   **Number of Support Lines:** The number of horizontal lines within the top support.
*   **Support Line Height:** The height of each support line, as a percentage of the font size.
*   **Support Line Offset:** The vertical offset of the support lines, also as a percentage of the font size.

I'd also like a preview option to visualize what the cookie cutter shape will look like, and also a final version with the walls and top support. It would be great to use the Helvetica Bold font for the text. I think that's everything. Please let me know if I forgot anything!

[Assistant]
text_cookie_v2.1.scad:
```
// (recommended ALL UPPERCASE/CAPITAL LETTERS)
text = "TEXT";

text_size = 50; // [5:1:300]

// (less than 100% helps letters stay connected)
letter_spacing_percentage = 80; // [10:1:200]

// (also helps to keep letters joined)
include_underline = 1; // [0:No, 1:Yes]

underline_height_percentage = 15; // [1:1:50]

// (millimeters)
wall_height = 20; // [1:0.1:40]

// (millimeters)
wall_thickness = 1; // [0:0.1:10]

// (millimeters)
top_support_overhang = 2; // [0:0.1:10]

// (millimeters)
top_support_thickness = 1; // [0:0.1:10]

number_of_support_lines = 3; // [0:1:10]

support_line_height_percentage = 18; // [0:1:50]

support_line_offset_percentage = 12; // [-100:1:100]

// Render a preview of the cookie (turn this OFF before saving/printing)
cookie_preview = 0; // [0:Off, 1:On]

// str2vec from Nathanael Jourdane (nathanael@jourdane.net)
// http://www.thingiverse.com/thing:1237203
function str2vec(str, v=[], i=0) =
	i == len(str) ? v :
	str2vec(str, concat(v, str[i]), i+1);

module Text2D(text, size, underline=0, underline_height=0.16, spacing=0.8, radius=0) {
    offset(r=radius) {
        text(str(text),
            size=size,
            font="Helvetica:style=Bold",
            halign="center",
            spacing=spacing);
        if (underline == 1) {
            descenders = len([ for (a = search(str2vec("qpjg"), str2vec(str(text)))) if (a > 0) a ]) > 0;
            descender_correction = descenders ? 0.3 * size * underline_height : 0;
            translate([0, -1 * descender_correction, 0]) projection(cut = false) scale([1, -1 * underline_height, 1]) hull() BoundingBox() linear_extrude(height=1, convexity=4) {
                text(str(text),
                    size=size,
                    font="Helvetica:style=Bold",
                    halign="center",
                    spacing=spacing);
            }
        }
    }
}

module OutlineText(text, size, underline=0, underline_height=0.16, spacing=0.8, height=3, thickness=1) {
    linear_extrude(height=height, convexity=4) {
        difference() {
            Text2D(text, size, underline, underline_height, spacing, thickness);
            translate([0, 0, 0.1]) scale([1, 1, 1.5]) Text2D(text, size, underline, underline_height, spacing, 0);
        }
    }
}

module OutlineTextSupports(text, size, underline=0, spacing=0.8, thickness=1, height=3, overhang=10, number_of_support_lines=3, support_line_height=0.15, support_line_offset=0, underline_height=0.16) {
    linear_extrude(height=height, convexity=4) union() {
        difference() {
            offset(overhang) projection(cut = false) hull() BoundingBox() linear_extrude(height=1, convexity=4) Text2D(text, size, underline, underline_height, spacing, thickness);
            Text2D(text, size, underline, underline_height, spacing, 0);
        }

        descenders = len([ for (a = search(str2vec("qpjg"), str2vec(str(text)))) if (a > 0) a ]) > 0;
        descender_correction = descenders ? 0.3 * size * underline_height : 0;
        underline_size = (underline == 1) ? size * underline_height + descender_correction : 0;
        v_size = size + underline_size;
        v_space = v_size / number_of_support_lines;

        for (s = [0 : 1 : number_of_support_lines - 1]) {
            translate([0, s * v_space - underline_size + (v_space / 2) - (support_line_height * size / 2) + (support_line_offset * v_space), 0]) projection(cut = false) scale([1, support_line_height, 1]) hull() BoundingBox() linear_extrude(height=1, convexity=4) Text2D(text, size, underline, underline_height, spacing, thickness);
        }
    }
}

module BoundingBox() {
    intersection() {
        translate([0,0,0])
        linear_extrude(height = 1000, center = true, convexity = 10, twist = 0)
        projection(cut=false) intersection() {
            rotate([0,90,0])
            linear_extrude(height = 1000, center = true, convexity = 10, twist = 0)
            projection(cut=false)
            rotate([0,-90,0])
            children(0);
            rotate([90,0,0])
            linear_extrude(height = 1000, center = true, convexity = 10, twist = 0)
            projection(cut=false)
            rotate([-90,0,0])
            children(0);
        }
        rotate([90,0,0])
        linear_extrude(height = 1000, center = true, convexity = 10, twist = 0)
        projection(cut=false)
        rotate([-90,0,0])
        intersection() {
            rotate([0,90,0])
            linear_extrude(height = 1000, center = true, convexity = 10, twist = 0)
            projection(cut=false)
            rotate([0,-90,0])
            children(0);
            rotate([0,0,0])
            linear_extrude(height = 1000, center = true, convexity = 10, twist = 0)
            projection(cut=false)
            rotate([0,0,0])
            children(0);
        }
    }
}

// Lower the minimum fragment size for smoother edges
$fs = 0.1;

if (cookie_preview == 1) {
    linear_extrude(height=4, convexity=4) {
        Text2D(text, text_size, include_underline, underline_height_percentage / 100.0, letter_spacing_percentage / 100.0, 0);
    }
} else {
    translate([0, 0, wall_height + top_support_thickness - 0.01]) scale([-1, 1, -1]) union() {
        OutlineText(text, text_size, include_underline, underline_height_percentage / 100.0, letter_spacing_percentage / 100.0, wall_height + top_support_thickness, wall_thickness);
        translate([0, 0, wall_height]) OutlineTextSupports(text, text_size, include_underline, letter_spacing_percentage / 100.0, wall_thickness, top_support_thickness, top_support_overhang, number_of_support_lines, support_line_height_percentage / 100.0, support_line_offset_percentage / 100.0, underline_height_percentage / 100.0);
    }
}

```


[User]
Okay, I'm looking to create a customizable plaque with some interesting, almost organic, line work on the surface. I have a sort of inspiration in mind, and I'm hoping you can help me generate the OpenSCAD code.

Essentially, I want a rectangular plaque with a slightly raised border. Then, on the main surface of the plaque, I'd like to generate curves that are inspired by magnetic field lines. These lines should be created procedurally, not drawn by hand. I'd like to control the overall shape and thickness of those lines with parameters. Specifically, I'm imagining the lines as somewhat calligraphic - like thick and thin depending on their direction.

Here's a more detailed breakdown of what I'm looking for:

1.  **Plaque Shape:**
    *   The base of the design should be a rectangular plaque.
    *   I need parameters for the overall `plate` dimensions (length and width), `thickness` of the plate, and a small `fudge` factor to adjust the vertical position of the plate's top layer and its border.
    *   I'd like to create a raised border around the plaque that is slightly offset from the plate's edges.

2.  **Curve Generation:**
    *   The curves should be generated algorithmically, using a spiral function or a similar approach that simulates the effect of a magnetic field on a moving particle. This means they should have a sort of organic, flowing look to them.
    *   I need a `seed` value to allow for repeatable results. I also need to be able to change it to produce a different looking plaque.
    *   I want to be able to have multiple "runs" of curve generation with slight parameter variations each time for aesthetic reasons.
    *   I want each run to generate multiple individual curves.

3. **Curve Appearance:**
    *   The curves should be generated using a line module, which itself uses a `hull` of scaled and rotated spheres.
    *   I would like the curves to have a `Calligraphic` thickness controlled by a variable.
    *   I'd also like the curves to have a `CalligraphicSlant` for extra aesthetic customization.
    *   I need to be able to adjust the overall `Oscale` factor of the decorative curves independently from the plate.
    *   The curves should also appear at slightly different heights on the face of the plate, so they have a sort of layered effect.

4.  **Color:**
    *   I'd like to be able to specify a set of colors, and then have the curves randomly assigned one of these colors.

I'm not using any specific libraries beyond standard OpenSCAD functions. So that should make things easier. I'm really looking forward to seeing what you come up with!

[Assistant]
Plaque2.scad:
```
Seed=83533115397;//
Calligraphic=1.9;//
CalligraphicSlant=45;//
Oscale=[1.1,1];//
plate=[85,45];//
thickness=3;
fudge=0.35;//
/*[Hidden]*/
dir=[rnd(-0,-0.5,Seed-435),0.5,0]*10;
golds=["DarkGoldenrod","Goldenrod"];
difference(){
color("DarkGoldenrod")translate([0,plate[1]*fudge,-thickness]) linear_extrude(height = thickness +1,  convexity = 10, scale=0.97,$fn=20)offset(r=3, chamfer=true)offset(r=-3, chamfer=false) square(plate,center=true);
color("Goldenrod")mirror([0,0,1])translate([0,plate[1]*fudge,-1.99]) linear_extrude(height = 2,  convexity = 10, scale=0.97,$fn=20)offset(delta=3, chamfer=true)offset(r=-5, chamfer=false) square(plate,center=true);
}

up=[0,0,-2];
intersection(){
color("DarkGoldenrod")translate([0,plate[1]*fudge,-thickness]) linear_extrude(height = thickness +1,  convexity = 10, scale=0.97,$fn=20)offset(r=3, chamfer=true)offset(r=-3, chamfer=false) square(plate,center=true);
scale([Oscale[0],Oscale[1],1]){
// first run
 for(t=[rnd(-40,-10,Seed+421):rnd(15,20,Seed+763):rnd(10,60,Seed+432)]){
translate([0,0,-rnd(0.2,0.0,Seed+123+t)])color(golds[round(rnd(1,0,Seed-t))]) spiral([-1,0,0],dir,t);
translate([0,0,-rnd(0.2,0.0,Seed+123+t)])color(golds[round(rnd(1,0,Seed-t))])mirror([1,0,0])spiral([-1,0,0],dir,t);
}
//second run
dir2=[-0.5,-0.1-rnd(0.7,0,Seed+344),0]*10;
 { for(t=[rnd(-40,-10,Seed):rnd(15,20,Seed+548):rnd(10,60,Seed+556)]){
translate([0,0,-rnd(0.1,0.0,Seed+123+t)])color(golds[round(rnd(1,0,Seed+t))])spiral([-1,0,0],dir2,t);
translate([0,0,-rnd(0.1,0.0,Seed+123+t)])color(golds[round(rnd(1,0,Seed+t))])mirror([1,0,0])spiral([-1,0,0],dir2,t);
}}

}}
// modules
module spiral(op,dir,t, i=0){
magfield=[0,0,cos(i*t)*3];
ndir=dir*0.85+cross(dir,magfield)*0.1;// blend dirction with force ( nonscientific)
np=op+ndir;
line(op,np);
if(i<25){spiral(np,ndir,t,i+1);}

}

module line(p1, p2) {
  hull() {
    translate(p1) rotate([-5,5,-CalligraphicSlant]) scale([1/Calligraphic,Calligraphic,1])sphere(1,$fn=6);
    translate(p2) rotate([-5,5,-CalligraphicSlant])scale([1/Calligraphic,Calligraphic,1])sphere(1,$fn=6);
  }
}
function rnd(a = 0, b = 1,S) = (rands(min(a, b), max(a, b), 1,S)[0]);

```