[User]
I'd like to design a customizable knob, building on the concepts from a design I've been playing with. I'd like it to have a few key features and be fully parametric. I'm thinking of something like a control knob, with a rounded top handle, a connecting piece, and a base that might be hollow or solid.

Here's what I'm envisioning:

**General Parameters**

*   **Resolution:** I need a variable to control the `$fn` value for the smoothness of the curves. Let's call it `resolution`.
*   **Tolerance:**  A `tolerance` variable, probably a small value like 0.2, for clearances between parts like the screw hole.
*   **Output Part:** A boolean (`output_part`) to switch between outputting the whole knob or just half, I believe this is useful for easier 3D printing.

**Knob Dimensions**

*   **Total Height:** The total height of the knob, `knob_height`.
*   **Handle Height:** Height of the top rounded handle part, `knob_handle_height`.
*   **Handle Diameter:** Diameter of the top handle, `knob_handle_dia`.
*   **Base Diameter:** Diameter of the base, `knob_base_dia`.
*   **Base Height Adjustment:** An extra amount `extra_height_base` to add to the base height, normally this is 0, but the user can add a positive number here.

**Connector Style**

*   A `connector_style` parameter. This should allow the connector between the top handle and the base to either bulge outwards (1), go inwards (-1) or remain straight (0). This should have a smooth, curved effect.

**Base Options**

*   A boolean parameter (`hollow_base`) which, when set to 1, will make the base hollow (for inserting a nut), and if set to 0 it will remain solid.

**Print Bed Removal**

*   I'd like a way to create multiple little nubs on the bottom for easier removal from the print bed.  Let's call this `plate_remover_amount`. It should be an integer to specify how many nubs you want (0 for none).
*   Also parameters called `plate_remover_width`, `plate_remover_height`, and `plate_remover_length` for the dimensions of those nubs.

**Backplate**

*   A boolean parameter `print_backplate` to optionally print a backplate which is attached to the knob.
*   A parameter called `backplate_height` to control the backplate's thickness.

**Screw Hole and Hex Nut**

*   A `screw_dia` parameter for the diameter of the screw hole.  I prefer to just use screws, not nuts, but I do want to make the hole deep enough to handle longer screws.
*   A boolean parameter `have_hex_nut_insert`. If true, I also need parameters for the hex nut: `hex_nut_fwidth` (width across faces), `hex_nut_height`, and `hex_nut_post` (its vertical position in the knob).

For the libraries, I think we might need to make use of `minkowski` from either BOSL or BOSL2 for making the backplate. We also may want to consider using `hull` for making the hex nut cavity.

I'm looking for you to provide the fully parameterized OpenSCAD code that creates these parts based on those parameters.

[Assistant]
a_in_use_customizable_knob.scad:
```
// resolution of the knob. higher value (100) will render very slow but create a smoother surface. low value (10) will create 'Low poly' effect.
$fn = 30;
// tolerance for the bolt and hex nut
tolerance = 0.2;

// output setting. 0 will output whole knob, 1 to output only half.
output_part = 0;

// total height of the knob
knob_height = 45;

// height of the top round knob
knob_handle_height = 20;
// diameter of the top round knob
knob_handle_dia = 55;

// diameter of the base
knob_base_dia = 60;

// typically base is half of the knob. you can add some positve value to increase size here. set to 0 for default
extra_height_base=2;

// style of base and top connector. -1 for inward, 1 for outward and 0 for straight
connector_style = 1;

// make base hollow. 0 for solid, 1 for hollow. You must set this to 1 if you want a hex-nut inserted
hollow_base=0;

// easy print plate remover. set to 0 to disable. set to 1,2,3 to add one or multiple removers
plate_remover_amount=2;
//the width of the whole to more easily remove the printed knob from the bed
plate_remover_width=16;
//the height of the whole to more easily remove the printed knob from the bed
plate_remover_height=4;
//length of the remover
plate_remover_length=6;

//print backplate. set to 1 if you want to print a backplate. set to 0 if you do not want a backplate
print_backplate=1;
//thickness of the backplate
backplate_height=4;

// screw hole diameter in mm. make it a bit larger (+0.6) than the screw you want to use if you don't want to cut threads
screw_dia = 2*1.45;
// set to TRUE if you want to insert a hex nut, set to FALSE if you don't.
have_hex_nut_insert = false;
// hex nut's width across faces. needed if you want to insert hex nut. According to ISO4032, M4 = 7, M5 = 8 and M6 = 10
hex_nut_fwidth = 7.9;
// hex nut's height. needed if you want to insert hex nut. Higher value will give you a bigger window to insert your hex nut when printing.
hex_nut_height = 14;
// hex nut's position. adjust according to your bolt's length
hex_nut_post = 13.5;

module connector() {
    step = 2;
    for (i=[0:step:knob_height - knob_handle_height/2])
        hull() {
          translate([0, 0, i])
            scale(f(i)*[1,1])
              cylinder(r=knob_handle_dia/4, h = .01);
          translate([0, 0, i+step])
            scale(f(i+step)*[1,1])
              cylinder(r=knob_handle_dia/4, h = .01);
        }
    function f(i) = 1 + connector_style*pow(i/knob_height,2);
}

// top round knob
module top_round_knob() {
        translate([0, 0, knob_height - knob_handle_height/2])
        resize(newsize=[knob_handle_dia, knob_handle_dia, knob_handle_height])
        sphere(r=knob_handle_dia);
}

module base() {
            difference() {
                translate([0, 0, -2])
                    resize(newsize=[knob_base_dia, knob_base_dia, knob_handle_height+2])
                    sphere(r=knob_base_dia);
                translate([0, 0, -knob_handle_height/2])
                    cube([knob_handle_dia * 2, knob_handle_dia * 2, knob_handle_height], center=true);
            }
}

// create hexagon, with width being the distance across flat faces.
module fhex(width, height) {
    hull() {
        cube([width/1.7,width,height],center = true);
        rotate([0,0,120])cube([width/1.7,width,height],center = true);
        rotate([0,0,240])cube([width/1.7,width,height],center = true);
    }
}

// create hexagon, with width being the distance across corners.
module ghex(width, height) {
    cylinder(d = width, h = height, $fn = 6);
}

// create screw hole and hex nut cavity
module screw_hole() {
    cylinder(d = screw_dia + tolerance, h = knob_height - 3);
    //cylinder(d = screw_dia + tolerance, h = knob_height - knob_handle_height/2);

    if (have_hex_nut_insert) {
        translate([0, 0 , hex_nut_post])
            fhex(hex_nut_fwidth + tolerance, hex_nut_height);
    }
}

module print_bed_remover () {
   if (plate_remover_amount>=1)
        for (a=[0:plate_remover_amount-1])
               rotate ([0,a*45,0])
                    translate ([0,0,-knob_base_dia/2+plate_remover_length])
                        rotate ([0,180,0])
                            resize (newsize=[plate_remover_width,plate_remover_height,0])
                                cylinder (r=3, h= plate_remover_length,$fn=20);
}

module backplate (radius,hoehe,minko) {
difference () {
    translate ([0,0,minko]) minkowski () {
        cylinder (r=radius-minko,h=hoehe-2*minko,$fn=100);
        sphere (r=minko, $fn=30);
        }
     cylinder (r=screw_dia/2+0.2,h=hoehe,$fn=20);
        rotate ([-90,0,0]) print_bed_remover ();
    } //end diff
}

// assemble all the parts together
module all () {
    difference() {
    union() {
        color("green")
            top_round_knob();
        color("blue")
            base();
        color("gray")
            connector();
    }
    if (hollow_base==1) translate([0, 0, -3.4]) base();
    screw_hole();
    if (output_part == 1) {
        cutting_cube_size = knob_height * 10;
        translate([cutting_cube_size / 2 , 0, 0])
            cube(cutting_cube_size, center=true);
    }
    rotate ([-90,0,0]) print_bed_remover ();
}
}

all ();
if (print_backplate==1) translate ([knob_base_dia+5,0,0]) backplate (knob_base_dia/2,backplate_height,1);
```


[User]
I'd like to design a set of nested pyramids for use with Ice House games, like the ones from Looney Labs. I want to be able to control the size and shape of each pyramid independently, but I'd also like for them to have a consistent style.

Specifically, I want a set of four pyramids, each smaller than the last. The largest pyramid should be defined by a base width and a height, and the other pyramids should be derived from this initial pyramid. I need to be able to control how much the base and height decrease between each pyramid. Also, all of the pyramids should have rounded corners where the faces meet; I'd like to specify a radius for this rounding. The pyramids should be hollow, and I'd like to specify the thickness of the pyramid walls as well as a minimum tolerance for the hollow section. Finally, I'd like to be able to control the spacing between the pyramids.

Here are the specific parameters I need to be able to set:

*   `p3Height`: The height of the largest pyramid.
*   `p3Base`: The base width of the largest pyramid (the base is a square).
*   `roundRadius`: The radius of the rounded corners.
*   `wallThickness`: The thickness of the pyramid walls.
*   `baseDecrease`: How much the base width decreases for each subsequent pyramid.
*   `heightDecrease`: How much the height decreases for each subsequent pyramid.
*   `hollowMinTolerance`: The minimum size of the hollow space within the pyramid.
*   `spread`: The space between each pyramid.
*   `includeP3`: A scaling factor for the largest pyramid, can be used to make it disappear.
*   `includeP2`: A scaling factor for the second largest pyramid, can be used to make it disappear.
*  `includeP1`: A scaling factor for the third largest pyramid, can be used to make it disappear.
* `includeP0`: A scaling factor for the smallest pyramid, can be used to make it disappear.

I expect the pyramids to nest, with the largest one defined by `p3Height` and `p3Base`, and each subsequent one getting smaller by `baseDecrease` and `heightDecrease`, respectively. Also, each should be offset from the previous one using spread. Each pyramid should be created using a `minkowski()` sum of the pyramid shape and a sphere for the corner rounding. The base shape of each pyramid should be created by intersecting four rotated cuboids and then subtracting a smaller interior shape to make it hollow. I want to be able to specify a `wallThickness` parameter for the hollowed out pyramid.

I'd like the code to be parametric, and I think it would make sense to organize this into a single OpenSCAD file with all of these parameters. I'd prefer it not to use any external libraries, just standard OpenSCAD functions.

[Assistant]
pyramids.scad:
```
$fn=100.0;
p3Height=46.5;
p3Base=25.9;
roundRadius=1.25;
wallThickness=2.3;
baseDecrease=5.25;
heightDecrease=9.52;
hollowMinTolerance=0.5;
spread=1.0;
includeP3=1.0;
includeP2=1.0;
includeP1=1.0;
includeP0=1.0;
translate([(p3Base/2.0),(p3Base/2.0),0.0]){
  scale([includeP3, includeP3, includeP3]){
    difference(){
      minkowski(){
        intersection(){
          translate([(-p3Base),(-p3Base),0.0]){
            cube([(2.0*p3Base), (2.0*p3Base), (2.0*p3Height)]);
          }
          intersection_for (r=[0.0:90.0:270.0]){
            rotate([0.0, 0.0, r]){
              translate([0.0,((-(-roundRadius))*sin(atan((p3Height/(p3Base/2.0))))),((-roundRadius)*cos(atan((p3Height/(p3Base/2.0)))))]){
                translate([0.0,((-p3Base)/2.0),0.0]){
                  rotate([(-atan(((p3Base/2.0)/p3Height))), 0.0, 0.0]){
                    translate([(-p3Base),0.0,(-p3Height)]){
                      cube([(2.0*p3Base), (2.0*p3Base), (3.0*p3Height)]);
                    }
                  }
                }
              }
            }
          }
        }
        intersection(){
          sphere(roundRadius);
          translate([(-roundRadius),(-roundRadius),0.0]){
            cube([(2.0*roundRadius), (2.0*roundRadius), (2.0*roundRadius)]);
          }
        }
      }
      intersection(){
        intersection(){
          translate([(-p3Base),(-p3Base),0.0]){
            cube([(2.0*p3Base), (2.0*p3Base), (2.0*p3Height)]);
          }
          intersection_for (r=[0.0:90.0:270.0]){
            rotate([0.0, 0.0, r]){
              translate([0.0,((-(-wallThickness))*sin(atan((p3Height/(p3Base/2.0))))),((-wallThickness)*cos(atan((p3Height/(p3Base/2.0)))))]){
                translate([0.0,((-p3Base)/2.0),0.0]){
                  rotate([(-atan(((p3Base/2.0)/p3Height))), 0.0, 0.0]){
                    translate([(-p3Base),0.0,(-p3Height)]){
                      cube([(2.0*p3Base), (2.0*p3Base), (3.0*p3Height)]);
                    }
                  }
                }
              }
            }
          }
        }
        translate([((-((p3Base-baseDecrease)+(2.0*hollowMinTolerance)))/2.0),((-((p3Base-baseDecrease)+(2.0*hollowMinTolerance)))/2.0),0.0]){
          cube([((p3Base-baseDecrease)+(2.0*hollowMinTolerance)), ((p3Base-baseDecrease)+(2.0*hollowMinTolerance)), (2.0*p3Height)]);
        }
      }
    }
  }
}
translate([((((p3Base-baseDecrease)/2.0)+p3Base)+spread),((((p3Base-baseDecrease)/2.0)+p3Base)-(p3Base-baseDecrease)),0.0]){
  scale([includeP2, includeP2, includeP2]){
    difference(){
      minkowski(){
        intersection(){
          translate([(-(p3Base-baseDecrease)),(-(p3Base-baseDecrease)),0.0]){
            cube([(2.0*(p3Base-baseDecrease)), (2.0*(p3Base-baseDecrease)), (2.0*(p3Height-heightDecrease))]);
          }
          intersection_for (r=[0.0:90.0:270.0]){
            rotate([0.0, 0.0, r]){
              translate([0.0,((-(-roundRadius))*sin(atan(((p3Height-heightDecrease)/((p3Base-baseDecrease)/2.0))))),((-roundRadius)*cos(atan(((p3Height-heightDecrease)/((p3Base-baseDecrease)/2.0)))))]){
                translate([0.0,((-(p3Base-baseDecrease))/2.0),0.0]){
                  rotate([(-atan((((p3Base-baseDecrease)/2.0)/(p3Height-heightDecrease)))), 0.0, 0.0]){
                    translate([(-(p3Base-baseDecrease)),0.0,(-(p3Height-heightDecrease))]){
                      cube([(2.0*(p3Base-baseDecrease)), (2.0*(p3Base-baseDecrease)), (3.0*(p3Height-heightDecrease))]);
                    }
                  }
                }
              }
            }
          }
        }
        intersection(){
          sphere(roundRadius);
          translate([(-roundRadius),(-roundRadius),0.0]){
            cube([(2.0*roundRadius), (2.0*roundRadius), (2.0*roundRadius)]);
          }
        }
      }
      intersection(){
        intersection(){
          translate([(-(p3Base-baseDecrease)),(-(p3Base-baseDecrease)),0.0]){
            cube([(2.0*(p3Base-baseDecrease)), (2.0*(p3Base-baseDecrease)), (2.0*(p3Height-heightDecrease))]);
          }
          intersection_for (r=[0.0:90.0:270.0]){
            rotate([0.0, 0.0, r]){
              translate([0.0,((-(-wallThickness))*sin(atan(((p3Height-heightDecrease)/((p3Base-baseDecrease)/2.0))))),((-wallThickness)*cos(atan(((p3Height-heightDecrease)/((p3Base-baseDecrease)/2.0)))))]){
                translate([0.0,((-(p3Base-baseDecrease))/2.0),0.0]){
                  rotate([(-atan((((p3Base-baseDecrease)/2.0)/(p3Height-heightDecrease)))), 0.0, 0.0]){
                    translate([(-(p3Base-baseDecrease)),0.0,(-(p3Height-heightDecrease))]){
                      cube([(2.0*(p3Base-baseDecrease)), (2.0*(p3Base-baseDecrease)), (3.0*(p3Height-heightDecrease))]);
                    }
                  }
                }
              }
            }
          }
        }
        translate([((-(((p3Base-baseDecrease)-baseDecrease)+(2.0*hollowMinTolerance)))/2.0),((-(((p3Base-baseDecrease)-baseDecrease)+(2.0*hollowMinTolerance)))/2.0),0.0]){
          cube([(((p3Base-baseDecrease)-baseDecrease)+(2.0*hollowMinTolerance)), (((p3Base-baseDecrease)-baseDecrease)+(2.0*hollowMinTolerance)), (2.0*(p3Height-heightDecrease))]);
        }
      }
    }
  }
}
translate([(((((p3Base-baseDecrease)-baseDecrease)/2.0)+p3Base)+spread),(((((p3Base-baseDecrease)-baseDecrease)/2.0)+p3Base)+spread),0.0]){
  scale([includeP1, includeP1, includeP1]){
    difference(){
      minkowski(){
        intersection(){
          translate([(-((p3Base-baseDecrease)-baseDecrease)),(-((p3Base-baseDecrease)-baseDecrease)),0.0]){
            cube([(2.0*((p3Base-baseDecrease)-baseDecrease)), (2.0*((p3Base-baseDecrease)-baseDecrease)), (2.0*((p3Height-heightDecrease)-heightDecrease))]);
          }
          intersection_for (r=[0.0:90.0:270.0]){
            rotate([0.0, 0.0, r]){
              translate([0.0,((-(-roundRadius))*sin(atan((((p3Height-heightDecrease)-heightDecrease)/(((p3Base-baseDecrease)-baseDecrease)/2.0))))),((-roundRadius)*cos(atan((((p3Height-heightDecrease)-heightDecrease)/(((p3Base-baseDecrease)-baseDecrease)/2.0)))))]){
                translate([0.0,((-((p3Base-baseDecrease)-baseDecrease))/2.0),0.0]){
                  rotate([(-atan(((((p3Base-baseDecrease)-baseDecrease)/2.0)/((p3Height-heightDecrease)-heightDecrease)))), 0.0, 0.0]){
                    translate([(-((p3Base-baseDecrease)-baseDecrease)),0.0,(-((p3Height-heightDecrease)-heightDecrease))]){
                      cube([(2.0*((p3Base-baseDecrease)-baseDecrease)), (2.0*((p3Base-baseDecrease)-baseDecrease)), (3.0*((p3Height-heightDecrease)-heightDecrease))]);
                    }
                  }
                }
              }
            }
          }
        }
        intersection(){
          sphere(roundRadius);
          translate([(-roundRadius),(-roundRadius),0.0]){
            cube([(2.0*roundRadius), (2.0*roundRadius), (2.0*roundRadius)]);
          }
        }
      }
      intersection(){
        intersection(){
          translate([(-((p3Base-baseDecrease)-baseDecrease)),(-((p3Base-baseDecrease)-baseDecrease)),0.0]){
            cube([(2.0*((p3Base-baseDecrease)-baseDecrease)), (2.0*((p3Base-baseDecrease)-baseDecrease)), (2.0*((p3Height-heightDecrease)-heightDecrease))]);
          }
          intersection_for (r=[0.0:90.0:270.0]){
            rotate([0.0, 0.0, r]){
              translate([0.0,((-(-wallThickness))*sin(atan((((p3Height-heightDecrease)-heightDecrease)/(((p3Base-baseDecrease)-baseDecrease)/2.0))))),((-wallThickness)*cos(atan((((p3Height-heightDecrease)-heightDecrease)/(((p3Base-baseDecrease)-baseDecrease)/2.0)))))]){
                translate([0.0,((-((p3Base-baseDecrease)-baseDecrease))/2.0),0.0]){
                  rotate([(-atan(((((p3Base-baseDecrease)-baseDecrease)/2.0)/((p3Height-heightDecrease)-heightDecrease)))), 0.0, 0.0]){
                    translate([(-((p3Base-baseDecrease)-baseDecrease)),0.0,(-((p3Height-heightDecrease)-heightDecrease))]){
                      cube([(2.0*((p3Base-baseDecrease)-baseDecrease)), (2.0*((p3Base-baseDecrease)-baseDecrease)), (3.0*((p3Height-heightDecrease)-heightDecrease))]);
                    }
                  }
                }
              }
            }
          }
        }
        translate([((-((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)+(2.0*hollowMinTolerance)))/2.0),((-((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)+(2.0*hollowMinTolerance)))/2.0),0.0]){
          cube([((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)+(2.0*hollowMinTolerance)), ((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)+(2.0*hollowMinTolerance)), (2.0*((p3Height-heightDecrease)-heightDecrease))]);
        }
      }
    }
  }
}
translate([((((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)/2.0)+p3Base)-(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)),((((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)/2.0)+p3Base)+spread),0.0]){
  scale([includeP0, includeP0, includeP0]){
    difference(){
      minkowski(){
        intersection(){
          translate([(-(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)),(-(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)),0.0]){
            cube([(2.0*(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)), (2.0*(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)), (2.0*(((p3Height-heightDecrease)-heightDecrease)-heightDecrease))]);
          }
          intersection_for (r=[0.0:90.0:270.0]){
            rotate([0.0, 0.0, r]){
              translate([0.0,((-(-roundRadius))*sin(atan(((((p3Height-heightDecrease)-heightDecrease)-heightDecrease)/((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)/2.0))))),((-roundRadius)*cos(atan(((((p3Height-heightDecrease)-heightDecrease)-heightDecrease)/((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)/2.0)))))]){
                translate([0.0,((-(((p3Base-baseDecrease)-baseDecrease)-baseDecrease))/2.0),0.0]){
                  rotate([(-atan((((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)/2.0)/(((p3Height-heightDecrease)-heightDecrease)-heightDecrease)))), 0.0, 0.0]){
                    translate([(-(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)),0.0,(-(((p3Height-heightDecrease)-heightDecrease)-heightDecrease))]){
                      cube([(2.0*(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)), (2.0*(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)), (3.0*(((p3Height-heightDecrease)-heightDecrease)-heightDecrease))]);
                    }
                  }
                }
              }
            }
          }
        }
        intersection(){
          sphere(roundRadius);
          translate([(-roundRadius),(-roundRadius),0.0]){
            cube([(2.0*roundRadius), (2.0*roundRadius), (2.0*roundRadius)]);
          }
        }
      }
      intersection(){
        intersection(){
          translate([(-(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)),(-(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)),0.0]){
            cube([(2.0*(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)), (2.0*(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)), (2.0*(((p3Height-heightDecrease)-heightDecrease)-heightDecrease))]);
          }
          intersection_for (r=[0.0:90.0:270.0]){
            rotate([0.0, 0.0, r]){
              translate([0.0,((-(-wallThickness))*sin(atan(((((p3Height-heightDecrease)-heightDecrease)-heightDecrease)/((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)/2.0))))),((-wallThickness)*cos(atan(((((p3Height-heightDecrease)-heightDecrease)-heightDecrease)/((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)/2.0)))))]){
                translate([0.0,((-(((p3Base-baseDecrease)-baseDecrease)-baseDecrease))/2.0),0.0]){
                  rotate([(-atan((((((p3Base-baseDecrease)-baseDecrease)-baseDecrease)/2.0)/(((p3Height-heightDecrease)-heightDecrease)-heightDecrease)))), 0.0, 0.0]){
                    translate([(-(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)),0.0,(-(((p3Height-heightDecrease)-heightDecrease)-heightDecrease))]){
                      cube([(2.0*(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)), (2.0*(((p3Base-baseDecrease)-baseDecrease)-baseDecrease)), (3.0*(((p3Height-heightDecrease)-heightDecrease)-heightDecrease))]);
                    }
                  }
                }
              }
            }
          }
        }
        translate([-50.0,-50.0,0.0]){
          cube([100.0, 100.0, (2.0*(((p3Height-heightDecrease)-heightDecrease)-heightDecrease))]);
        }
      }
    }
  }
}

```


[User]
I'd like to design a non-circular gear system, like that "Whatchamagear" thing on Thingiverse. I need to be able to adjust a few things parametrically.

Specifically, I need to control:

*   The axle diameter of the gears.
*   The thickness of the gears.
*   The eccentricity of the reference shape – something that makes the gear more or less circular. This should be a value between 0 and 1, where 0 is a circle and 1 is...well, something unbounded. The code I saw used 0.25 as a default, which looks good to me.
*   The number of teeth on each lobe of the gear.
*   The number of lobes on each of the two gears.

I want the program to be able to display the gears assembled, display each gear separately, and display the gears alongside the holder pieces in a way that's useful for 3D printing.

I also need the code to calculate some related values automatically, like the desired axle distance, backlash, axle clearance, and holder thickness. It would be great if I could also specify the pressure angle.

Basically, I want a really customizable non-circular gear generator, with easy-to-tweak parameters and multiple views, and the ability to adjust all of those parameters through OpenSCAD. I'm not really sure which libraries I would need to accomplish this, so it might be best if you could specify that in your response. I would prefer to avoid libraries that are too extensive, though. I don't think I'll need any modules from MCAD.

[Assistant]
whatchamagear.scad:
```
// "Whatchamagear" provided by Stellar Innovation
// http://www.thingiverse.com/stellarinnovation/about
// under Creative Commons License, Attribution (BY)
//
// Remix of "non-circular gear" by Stellar Innovation
//
// Changes:
// 2019-08-31 Original version
// 2019-08-31 "All Parts" view now correctly rotates "second gear"
// 2019-09-27 Increased distance between gears in "All Parts"

// Part to print
partToPrint = 0; // [0: show assembled, 1: all parts, 2: first gear, 3: second gear, 4 : holder]

// Axle diameter
axleDiameter = 30; // [20:50]

// Thickness of gear
gearThickness = 8; // [5:25]

// Eccentricity of reference gear (0=circle, 1 would be unbounded)
refEccentricity = 0.25; // [0:0.01:0.8]

// Number of teeth per lobe
numTeethPerLobe = 9;  // [5:2:49]

// Number of lobes, first gear
numLobes1 = 3; // [1:12]

// Number of lobes, second gear
numLobes2 = 4; // [1:12]

//
// More parameters (fixed in Customizer)
//

// desired axle distance
desiredAxleDistance = 1.6*axleDiameter;

// Normal backlash [mm] (there gotta be some!)
normalBacklash = 1*0.4; // [0:0.1:0.9]

// Axle clearance
axleClearance = 1*0.2;

// Holder thickness
holderWallThickness = 1*2.4;

// number of subdivisions of a half-lobe (when computing arc length)
Nsamples = 1*100;

// true to echo gear specification
printSpec = (1+1==2);

// Pressure angle
pressureAngle = 1*20;

rad2deg = 180/PI;
deg2rad = PI/180;

// Convert eccentricity and scale from reference gear to given num. lobes
refNumLobes = 1*1; // Reference gear has a single lobe
eccentricity1 = computeEccentricity(refEccentricity, refNumLobes, numLobes1);
eccentricity2 = computeEccentricity(refEccentricity, refNumLobes, numLobes2);
refScaleFactor1 = computeScaleFactor(refEccentricity, refNumLobes, eccentricity1, numLobes1);
refScaleFactor2 = computeScaleFactor(refEccentricity, refNumLobes, eccentricity2, numLobes2);

// reference min. radius
refMinRadius = min(refScaleFactor1/(1+eccentricity1), refScaleFactor2/(1+eccentricity2));

// modulus of the reference gear
refModulus = computeReferenceModulus(refEccentricity);

// At least a scale factor, s, such that
// s(minRadius - 1.25m) > axleDiameter/2 + margin
minScaleFactor = (0.5*axleDiameter + holderWallThickness + axleClearance)/max(refMinRadius - 1.25*refModulus, 0.001);

// reference axle distance
refAxleDistance = refScaleFactor1/(1+eccentricity1) + refScaleFactor2/(1-eccentricity2);

// Or, choose a scale factor to get the desiredAxleDistance
refScaleFactor = max(desiredAxleDistance/refAxleDistance, minScaleFactor);
scaleFactor1 = refScaleFactor*refScaleFactor1;
scaleFactor2 = refScaleFactor*refScaleFactor2;

axleDistance = refScaleFactor*refAxleDistance;

if (printSpec) {
  echo("num Lobes: ", numLobes1, numLobes2);
  echo("scale: ", scaleFactor1, scaleFactor2);
  echo("eccentricity: ", eccentricity1, eccentricity2);
  echo("min Radius: ", scaleFactor1/(1+eccentricity1), scaleFactor2/(1+eccentricity2));
  echo("max Radius: ", scaleFactor1/(1-eccentricity1), scaleFactor2/(1-eccentricity2));
  echo("axle Distance: ", axleDistance);
  echo("modulus: ", refScaleFactor*refModulus);
}

if (partToPrint==0)
  ShowAssembled();
else if (partToPrint==1)
  AllParts();
else if (partToPrint==2)
  color([0,1,0])
    NonCircularGearPart(scaleFactor1, eccentricity1, numLobes1);
else if (partToPrint==3)
  color([0,0,1])
    NonCircularGearPart(scaleFactor2, eccentricity2, numLobes2);
else
  color([0,0,0])
    HalfAHolderPart();

module AllParts() {
  isEven = (numLobes2 % 2) == 0;
  rot2 = (isEven)? 180/numLobes2 : 0;
  m = refScaleFactor*refModulus;
  maxR1 = scaleFactor1/(1-eccentricity1);
  maxR2 = scaleFactor2/(1-eccentricity2);
  maxR = max(maxR1, maxR2);
  axleR = 0.5*axleDiameter + holderWallThickness;
  andSome = 1;

  translate([-maxR1-m-andSome,0])
    rotate(180/numLobes1)
      color([0,1,0])
        NonCircularGearPart(scaleFactor1, eccentricity1, numLobes1);
  translate([maxR2+m+andSome,0])
    rotate(rot2)
      color([0,0,1])
        NonCircularGearPart(scaleFactor2, eccentricity2, numLobes2);
  y0 = axleR+maxR+m+andSome;
  for (y=[-y0,+y0])
    translate([-0.5*axleDistance, y])
      color([0,0,0])
        HalfAHolderPart();
}

module ShowAssembled() {
  z0 = holderWallThickness + 0.8;
  isEven = (numLobes2 % 2) == 0;
  rot2 = (isEven)? 180/numLobes2 : 0;

  color([0,0,0]) {
    HalfAHolderPart();
    translate([axleDistance,0,2*z0+gearThickness])
      rotate([180,0,180])
          HalfAHolderPart();
  }

  translate([0,0,z0])
    color([0,1,0])
      NonCircularGearPart(scaleFactor1, eccentricity1, numLobes1);
  translate([axleDistance,0,z0])
    color([0,0,1])
      rotate(rot2)
        NonCircularGearPart(scaleFactor2, eccentricity2, numLobes2);
}

module HalfAHolderPart() {
  HalfAHolder(axleDistance = axleDistance,
               axleDiameter = axleDiameter - axleClearance,
               axleLength = gearThickness,
               wallThickness = holderWallThickness);
}

module NonCircularGearPart(s, e, k) {
  linear_extrude(height = gearThickness) {
    NonCircularGear2D(s, e, k, 0.5*normalBacklash);
  }
}

// 2D-rendering of the gear

module NonCircularGear2D(s, e, k, jn=0) {
  arcLength = computeArcLength(s, e, k);
  hll = arcLength[Nsamples];
  pitch = 2*hll/numTeethPerLobe;
  m = pitch/PI;

  difference() {
    union() {
      intersection() {
        polygon(traceGear(arcLength, s, e, k, jn));
        polygon(curveWithOffset(s, e, k, dH=m));
      }
      polygon(curveWithOffset(s, e, k, dH=-1.25*m));
    }

    circle(d=axleDiameter + axleClearance, $fn=60);
  }
}

function hypot(x, y) = sqrt(x*x+y*y);

function polar2xy(r, phi) = let (a = phi*rad2deg) [r*cos(a), r*sin(a)];

// r(phi), radius of the elliptic curve, phi in radians
function radius(s, e, k, phi) = s/(1-e*cos(k*rad2deg*phi));

function pointXY(s, e, k, phi) = polar2xy(radius(s, e, k, phi), phi);

// Intersection point of the lines p1-p2 and q1-q2
function intersect(p1, p2, q1, q2) = let (
  // Solve p1 + t(p2 - p1) = q1 + u(q2 - q1)
  x1 = p1[0], y1 = p1[1],
  dx1 = p2[0] - x1, dy1 = p2[1] - y1,
  x2 = q1[0], y2 = q1[1],
  dx2 = q2[0] - x2, dy2 = q2[1] - y2,
  det = dx1*dy2 - dx2*dy1,
  t = (-dy2*(x1-x2) + dx2*(y1-y2))/det
)
  [ x1 + t*dx1, y1 + t*dy1];

// Tangent of the curve
function tangent(s, e, k, phi) = let (
  phi_deg = phi*rad2deg,
  r_s = 1/(1 - e*cos(k*phi_deg)),
  dr_s = -r_s*r_s*e*k*sin(k*phi_deg),
  cos_phi = cos(phi_deg),
  sin_phi = sin(phi_deg),
  dx = dr_s*cos_phi - r_s*sin_phi,
  dy = dr_s*sin_phi + r_s*cos_phi,
  u = 1/hypot(dx, dy)
)
  [u*dx, u*dy];

// Normal of the curve
function normal(s, e, k, phi) = let (t = tangent(s, e, k, phi))
  [t[1], -t[0]];

// Outset (dH>0) or Inset (dH<0) of the curve
function pointWithOffset(s, e, k, dH, phi) = let (
  Pref = pointXY(s, e, k, phi),
  N = normal(s, e, k, phi)
)
  [Pref[0] + dH*N[0], Pref[1] + dH*N[1]];

// Curve rendered with an offset of dH
function curveWithOffset(s, e, k, dH, $fn=100) = [
  for (i=[0:$fn-1]) let (phi=2*PI*i/$fn) pointWithOffset(s, e, k, dH, phi)
];

// Conversion from reference gear to different number of lobes
// ------------------------------------------------------------------------------

function computeEccentricity(e1, k1, k2) = let (
  C = k1*k1*e1*e1/(k2*k2*(1-e1*e1))
)
  sqrt(C/(1+C));

function computeScaleFactor(e1, k1, e2, k2) =
  (e2==0)? (k2/k1) : (e1*(1-e2*e2)/(1-e1*e1)/e2);

function computeReferenceModulus(e) = let (
  arcLength = computeArcLength(1.0, e, 1),
  hll = arcLength[Nsamples],
  pitch = 2*hll/numTeethPerLobe
)
  pitch/PI;

// Arc length
// -------------

// Computes Nsamples+2 samples of arc length
// arcLength[0] = 0
// arcLength[Nsamples] = arc length of a half lobe
// arcLength[Nsamples+1] = arc length, one subdivision beyond a half-lobe

function computeArcLength(s, e, k) = let (
  rollLine = [ for (i=[0:Nsamples+1]) let (
                 phi = PI/k * i/Nsamples,
                 r = radius(s, e, k, phi)
               )
                 polar2xy(r, phi)
             ],
  segLength = [ for (i=[0:Nsamples]) let (
                   x1 = rollLine[i][0],
                   y1 = rollLine[i][1],
                   x2 = rollLine[i+1][0],
                   y2 = rollLine[i+1][1]
                 )
                   hypot(x2-x1, y2-y1)
               ]
)
  accumulate(segLength, Nsamples+1);

// returns a list of the n first accumulative sums of the elemens in seg
// First element is zero, then seg[0], then seg[0]+seg[1] and so on
function accumulate(seg, n) = let (
    a = (n==0)? [] : accumulate(seg, n-1),
    s = (n==0)? 0 : a[len(a)-1] + seg[n-1]
  )
    concat(a, [s]);

// computes phi (angle in radians) that corresponds to given arc length
// arcLength is the result of computeArcLength (a list of arc lengths),
// halfLobePhi = PI/number-of-lobes
// length = the given arc length (for which phi is determined)

function interpolPhi(arcLength, halfLobePhi, length) = let (
  lobeLength = 2*arcLength[Nsamples],
  nPeriods = floor(length/lobeLength + 0.5),
  remainder = length - nPeriods*lobeLength,
  s = sign(remainder),
  i = findIndex(arcLength, abs(remainder), 0, Nsamples+2),
  f = (abs(remainder) - arcLength[i])/(arcLength[i+1] - arcLength[i])
)
  (2*nPeriods + s*(i + f)/Nsamples) * halfLobePhi;

// find index, i, in accLength such that accLength[i] <= length < accLength[i+1]
function findIndex(accLength, length, lo, hi) = let (m = floor((lo+hi)/2))
  (lo==m)? m
  : ((length < accLength[m])? findIndex(accLength, length, lo, m)
  : findIndex(accLength, length, m, hi));

// computes arc length of given phi
// arcLength is the result of computeArcLength (a list of arc lengths),
// halfLobePhi = PI/number-of-lobes
// phi is angle in radians
function interpolLength(arcLength, halfLobePhi, phi) = let (
  halfLobeLength = arcLength[Nsamples],
  nPeriods = floor(0.5*phi/halfLobePhi + 0.5),
  remainder = phi - nPeriods*2*halfLobePhi,
  s = sign(remainder),
  i = floor(abs(remainder)*Nsamples/halfLobePhi),
  f = abs(remainder)*Nsamples/halfLobePhi - i
)
  (2*nPeriods + s*(i+f)/Nsamples) * halfLobeLength;

// Trace rack around curve
// -------------------------------

// Computes the 2D-coordinates of an entire gear
// arcLength is the result of computeArcLength (a list of arc lengths),
// (s, e, k) are the parameters of the elliptic curve
// jn is the normal backlash
function traceGear(arcLength, s, e, k, jn) = let (
    pitch = 2*arcLength[Nsamples]/numTeethPerLobe,
    m = pitch/PI
)
  [ for (i=[0:k*numTeethPerLobe-1],
        p=traceTooth(arcLength, s, e, k, m, jn, i*pitch)) p ];

// Computes the 2D-coordinates of a single tooth
// arcLength is the result of computeArcLength (a list of arc lengths),
// (s, e, k) are the parameters of the elliptic curve
// m is the modulus
// jn is the normal backlash
// Ltip is the arc length at the tip of the tooth
function traceTooth(arcLength, s, e, k, m, jn, Ltip) = let (
  jt = 0.5*jn/cos(pressureAngle),
  halfMinus = traceHalfATooth(arcLength, s, e, k, m, Ltip+jt, -1),
  halfPlus =  traceHalfATooth(arcLength, s, e, k, m, Ltip-jt, +1),
  lastI = findTip(halfMinus, len(halfMinus)-1, halfPlus, len(halfPlus)-1),
  last1 = lastI[0],
  last2 = lastI[1],
  tip = (last1 < len(halfMinus)-1 && last2 < len(halfPlus)-1)?
       [ intersect(halfMinus[last1], halfMinus[last1+1],
                 halfPlus[last2], halfPlus[last2+1]) ]
        : []
  )
  concat([[0,0]], [ for (i=[0:last1]) halfMinus[i] ],  tip, [ for (i=[last2:-1:0]) halfPlus[i] ]);

// Finds the "last" indices prior to the tool tip
// Normally that is the last index of each traced half-tooth [end1, end2]
// In exceptional cases the traces intersect, in which case the two indices
// (one in ppt1, one in ppt2) immediately before the intersection is returned.
function findTip(ppt1, end1, ppt2, end2) = let (
  arg1 = atan2(ppt1[end1][1], ppt1[end1][0]),
  arg2 = atan2(ppt2[end2][1], ppt2[end2][0]),
  r1 = hypot(ppt1[end1][1], ppt1[end1][0]),
  r2 = hypot(ppt2[end2][1], ppt2[end2][0])
)
  ((arg1 < arg2 && arg1 > arg2-180) || arg1 > arg2+180)? [end1, end2]
  : ((r1 < hypot(ppt2[end2-1][1], ppt2[end2-1][0]))? findTip(ppt1, end1, ppt2, end2-1)
  : ((r2 < hypot(ppt1[end1-1][1], ppt1[end1-1][0]))? findTip(ppt1, end1-1, ppt2, end2)
  : findTip(ppt1, end1-1, ppt2, end2-1)));

// Computes the 2D-coordinates of a single tooth
// arcLength is the result of computeArcLength (a list of arc lengths),
// (s, e, k) are the parameters of the elliptic curve
// m is the modulus
// Ltip is the arc length at the tip of the tooth (+/- backlash)
// dir is the "direction" of the half-tooth (+1 or -1)
function traceHalfATooth(arcLength, s, e, k, m, Ltip, dir) = let (
  pitch = m*PI,
  Lref = Ltip + 0.25*dir*pitch,
  dTfull = m/tan(pressureAngle),
  dLfull = dTfull + m*tan(pressureAngle),
  ppt = [ for (h=[-1:0.1:+1]) let (
      dL = h*dir*dLfull,
      phi = interpolPhi(arcLength, PI/k, Lref - dL),
      Pt = polar2xy(radius(s, e, k, phi), phi)
    )
    pressurePoint(Pt, tangent(s, e, k, phi), dT=h*dir*dTfull, dH=h*m)
  ],
  i0 = findUndercut(ppt),
  h0 = -1 + 0.1*i0,
  ucp = (i0==0)? [] : traceUndercut(arcLength, s, e, k, m, Lref, dir, h0, hypot(ppt[i0][0], ppt[i0][1])),
  p0 = (len(ucp)==0)? ppt[0] : ucp[0],
  fp = traceFillet(s, e, k, m, 0.1*m, p0, dir)
)
  concat(fp, ucp, [ for (i=[i0:len(ppt)-1]) ppt[i] ]);

// Computes pressure point = Pt + dT*tangent + dH*normal
function pressurePoint(Pt, tangent, dT, dH) = let (
)
  [Pt[0] + dT*tangent[0] + dH*tangent[1],
   Pt[1] + dT*tangent[1] - dH*tangent[0]];

// Identifies and removes unused points from coordinate trace
// (these points are replaced by the result of traceUndercut)
function findUndercut(points, i=0, lastR=undef) = let (
  r0 = (i==0)? hypot(points[i][0], points[i][1]) : lastR,
  r1 = hypot(points[i][0], points[i][1])
 )
(r0 < r1)? i : findUndercut(points, i+1, r1);

// Traces the tip/corner of the rack to create an undercut tooth
function traceUndercut(arcLength, s, e, k, m, Lref, dir, h0, r0) = let (
  L0 = m*dir*tan(pressureAngle),
  dLfull = m/cos(pressureAngle)/sin(pressureAngle),
  ucp = [ for (h=[h0:-0.1:-1]) let (
      dL = h*dir*dLfull,
      phi = interpolPhi(arcLength, PI/k, Lref - dL),
      Pt = polar2xy(radius(s, e, k, phi), phi)
    )
    pressurePoint(Pt, tangent(s, e, k, phi), dT=L0+dL, dH=-m)
  ],
  ucpLen = undercutLength(ucp, r0)
)
  [ for (i=[0:ucpLen-1]) ucp[i] ];

// Number of points to use from the undercut trace
function undercutLength(ucp, r0, i=0) =
  (len(ucp) == i || hypot(ucp[i][0], ucp[i][1]) > r0)? i : undercutLength(ucp, r0, i+1);

// Trace fillet at the base of the tooth
function traceFillet(s, e, k, m, rf, p0, dir) = let (
  phi = findPhi(s, e, k, p0),
  Pref = pointXY(s, e, k, phi),
  T = tangent(s, e, k, phi),
  hD = 1.25*m,
  h1 = hD - rf
)
[ [Pref[0] + dir*rf*T[0] - hD*T[1], Pref[1] + dir*rf*T[1] + hD*T[0]],
  [Pref[0] - h1*T[1], Pref[1] + h1*T[0]] ];

// find phi, such that a given point, p, lies on the normal from
// that point on the elliptic curve
function findPhi(s, e, k, p) = let (
  phi0 = atan2(p[1], p[0])*deg2rad,
  phi1 = normalIter(s, e, k, phi0, p)
)
  normalIter(s, e, k, phi1, p);

// Iteration to improve an approximation of phi
function normalIter(s, e, k, phi, p) = let  (
  Pref = pointXY(s, e, k, phi),
  T = tangent(s, e, k, phi),
  dT = T[0]*(Pref[0] - p[0]) + T[1]*(Pref[1] - p[1])
)
  atan2(Pref[1] - dT*T[1], Pref[0] - dT*T[0])*deg2rad;

// Debug code
// --------------

// Position the tooth of a linear rack on the trace
// arcLength is the result of computeArcLength (a list of arc lengths),
// (s, e, k) are the parameters of the elliptic curve
// Ltip is the arc length at the tip of the tooth (+/- backlash)
// dir is the "direction" of the half-tooth (+1 or -1)
// -1 <= h <= +1 is the "height" of the pressure point (in units of
// the modulus). h=-1 corresponds to the tip/corner of the rack, which
// is closest to the center of the gear. h=+1 corresponds to the base of
// the rack, which is furthest away. h=0 corresponds to the reference
// line of the rack.

module RackTooth(arcLength, s, e, k, Ltip=0, dir=1, h=0) {
    pitch = 2*arcLength[Nsamples]/numTeethPerLobe;
    m = pitch/PI;
    Lref = Ltip + 0.25*dir*pitch;
    dH = h*m;
    dT = h*m*dir/tan(pressureAngle);
    dL = h*m*dir/cos(pressureAngle)/sin(pressureAngle);
    phi = interpolPhi(arcLength, PI/k, Lref - dL);
    Pt = polar2xy(radius(s, e, k, phi), phi);
    tau = tangent(s, e, k, phi);
    ny = normal(s, e, k, phi);

    translate(Pt)
    rotate(atan2(tau[1], tau[0]))
    translate([dL + (1+dir)*0.25*pitch, 0])
    {
      y1 = m;
      x1 = m*tan(pressureAngle);
      y2 = 1.25*m;
      x3 = 0.25*pitch;
      y3 = y2+2;
      x4 = -0.75*pitch;
      x5 = -0.5*pitch - x1;
      x6 = -0.5*pitch + x1;
      polygon([[-x1, y1], [x1, -y1], [x1, -y2], [x3, -y2], [x3, -y3],
               [x4, -y3], [x4, -y2], [x5, -y2],  [x5, -y1], [x6, y1]]);
    }

   // white = reference point (on the gear)
   phi0 = interpolPhi(arcLength, PI/k, Lref);
   translate(polar2xy(radius(s, e, k, phi0), phi0))
     color([1, 1, 1])
       circle(d=1, $fn=12);

    // black = top/corner of rack profile
    tt = dL+m*dir*tan(pressureAngle);
    translate([Pt[0] + tt*tau[0] - m*ny[0], Pt[1] + tt*tau[1] - m*ny[1]])
      color([0, 0, 0])
        circle(d=1, $fn=12);

    // green = tangent point
    translate(Pt)
     color([0, 1, 0])
       circle(d=1, $fn=12);

    // blue = projection of pressure point onto tangent
    translate([Pt[0] + dT*tau[0], Pt[1] + dT*tau[1]])
      color([0, 0, 1])
        circle(d=1, $fn=12);

    // red = pressure point
    translate([Pt[0] + dT*tau[0] + dH*ny[0], Pt[1] + dT*tau[1] + dH*ny[1]])
      color([1, 0, 0])
        circle(d=1, $fn=12);
  }

module HalfAHolder(axleDistance, axleDiameter, axleLength, wallThickness) {
  hexDiameter = axleDiameter - 2*wallThickness;
  twiceHexApothem = sqrt(0.75)*hexDiameter;
  innerDiameter = max(twiceHexApothem - 2*wallThickness, 0);
  discDiameter = axleDiameter + 2*wallThickness;

  rf = 0.25*(discDiameter-innerDiameter);
  r1 = 0.5*discDiameter + rf;
  y1 = 0.5*innerDiameter + rf;
  x1 = sqrt(r1*r1 - y1*y1);
  x2 = axleDistance - x1;
  difference() {
    union() {
      // Hubs
      Hub(innerDiameter, hexDiameter, axleDiameter, discDiameter, axleLength, wallThickness, socket=true);
      translate([axleDistance, 0])
        Hub(innerDiameter, hexDiameter, axleDiameter, discDiameter, axleLength, wallThickness, socket=false);
      // Bar
      translate([0, -0.5*innerDiameter])
        cube([axleDistance, innerDiameter, wallThickness]);
      // Fillets
      linear_extrude(height=wallThickness) {
        polygon([[0,0], [x1,-y1], [x1, y1]]);
        polygon([[axleDistance,0], [x2,y1], [x2, -y1]]);
      }
    }

    // Holes
    for (x=[0, axleDistance])
      translate([x,0])
        cylinder(d=innerDiameter, h=axleLength + 2*wallThickness + 2.6, $fn=120);
    // Fillets
    for (x=[x1, x2], y=[-y1,+y1])
      translate([x,y])
        cylinder(r=rf, h=wallThickness, $fn=30);
  }
}

module Hub(innerDiameter, hexDiameter, axleDiameter, discDiameter, axleLength, wallThickness, socket) {
  tol = 0.2;
  z0 = wallThickness;       // floor
  h1 = 0.6;                 // disc
  z1 = z0 + h1;
  h2 = 0.5*axleLength + tol; // axle
  z2 = z1 + h2;
  h3 = 2;                  // connector
  z3_plus = z2 + h3;
  andSome = 0.6;
  z3_minus = z2 - h3 - andSome;

  hex_tol = tol/sqrt(0.75);

  difference() {
    union() {
      cylinder(d=axleDiameter, h=z2, $fn=120);
      cylinder(d=discDiameter, h=z1, $fn=120);
      if (!socket)
        cylinder(d=hexDiameter - hex_tol, h=z3_plus, $fn=6);
    }

    if (socket)
      translate([0,0,z3_minus])
        cylinder(d=hexDiameter + hex_tol, h=z3_plus, $fn=6);
  }
}

```


[User]
Hey, I'm looking to create a parametric OpenSCAD model for a ring that has a twisted, Mobius-like shape. I have some existing code that I've been playing around with, but I'd like to start from scratch with a more flexible and robust version.

Here's a breakdown of what I need:

*   **Core Shape:** The ring should be formed by sweeping a 2D cross-section along a circular path while simultaneously twisting the cross-section around the minor axis, similar to a Mobius strip.

*   **Cross-Section Options:** The user should be able to choose between different cross-sections. I’m particularly interested in having at least these three:
    *   A **Reuleaux triangle** (a rounded triangle of constant width).
    *   A standard **triangle**.
    *   An **ellipse**.

*   **Parameters:** The following parameters need to be user-adjustable:
    *   `minor_radius`: The radius of the cross-section's size.
    *   `major_radius`: The radius of the ring itself (the circular path the cross-section follows).
    *   `z_scale`: A scaling factor for the height of the ring (the z-dimension of the cross-section).
    *   `twists`: The number of times the cross-section rotates as it travels around the ring. Fractional twists should be allowed.
    *   `ellipse_eccentricity`: This parameter should control how "sharp" the ellipse is, ranging from a circle (0.0) to a flatter ellipse (approaching 1.0). This parameter is only relevant when the cross section is "ellipse"

*   **Rendering:** The final shape should be rendered as a `polyhedron`.

*   **Libraries:** I'm not looking to use any external libraries for this design.

The key challenge will be the parameterization of the cross-sections, especially the Reuleaux triangle which is a bit more complex. Also, the twisting as the cross section is swept around the major axis needs to be handled correctly. I'd like the cross section to rotate `twists` times as it goes along the major axis circle.

I’m hoping the output of your work will be a single .scad file for the model. Let me know if you have any questions!

[Assistant]
mobius_ring.scad:
```

//pick one shape:
shape = "ellipse"; //["reuleaux", "triangle", "ellipse"]

// ring thickness radius in mm
minor_radius = 1;  //[0.5:0.2:4.0]

// ring main radius in mm
major_radius = 10.0; //[7.0:0.5:12.0]

// ratio of z height to minor axis diameter
z_scale = 2.5;  //[0.5:0.5:5.0]

//number of times the cross-section twists as it goes round the ring, fractional twists are possible for cross-sectional shapes with rotational symmetry.
twists = 2.5;  //[0.5:0.5:5]

//0: circle, to <1 sharper ellipse
ellipse_eccentricity= 0.5;  //[0.0:0.1:0.9]

function torus(az, theta) = let (
    // A torus with chosen cross-section:
    // See https://en.wikipedia.org/wiki/Reuleaux_triangle.
    //
    //Args:
    //    az: azimuth: angle around major axis in degrees
    //    theta: [0,360] angle around minor axis in degrees

    mod_theta = theta % 120 - 60,
    cos_theta = cos(mod_theta),
    twist = az*twists,

    // Use Polar equation of a circle from https://en.wikipedia.org/wiki/Polar_coordinate_system#Circle
    // R=1, r0=1/sqrt(3) (2/3 height of unit sided equilateral triangle, offset of centre of reuleaux arc)
    r_reuleaux = sqrt(4*cos_theta*cos_theta + 8)/2 - cos_theta,
    degrees = (twist+30) % 120 - 30,
    //echo(twist, degrees),
    r0_reuleaux = (degrees<30) ?
        sqrt(3)-cos(degrees) + sqrt(3)/2
        : cos(degrees-60) + sqrt(3)/2,

    // or triange cross-section:
    r_triangle = 1/cos_theta,
    r0_triangle = 0,

    // or ellipse:
    r_ellipse =  1/sqrt(1 - pow(ellipse_eccentricity * cos(theta),2)),
    r0_ellipse = 0,//-(1 + cos(az*twists)/eccentricity/eccentricity),

    r = shape=="reuleaux" ? r_reuleaux : shape=="triangle" ? r_triangle : shape=="ellipse" ? r_ellipse : error,
    r0 = shape=="reuleaux"  ? r0_reuleaux : shape=="triangle" ? r0_triangle : shape=="ellipse" ? r0_ellipse : error,

    theta2 = theta + twist,  //twist it!

    //based on parametric equation of a torus
    full_radius = major_radius + minor_radius * (r * cos(theta2) - r0),
    x = sin(az) * full_radius,
    y = cos(az) * full_radius,
    z = r * sin(theta2) * minor_radius * z_scale
    )
    [x, y, z];

az_step = 3.6+0;
theta_step = 10+0;

//for debugging, show slices of the surface: use 1 for continuous, or higher for just slices
zebra = 1;  //[1:4]

polyhedron(
    points = [for(az = [0:az_step*zebra:360+1], theta = [0:theta_step:360-1])
                torus(az, theta)],
    faces = let (step = floor(360/theta_step))
            concat(
                [for (i = [0:step:360/az_step/zebra*step], j=[0:360/theta_step])
                    [i+j,i+(j+1)%step,i+step+(j+1)%step]],
                [for (i = [0:step:360/az_step/zebra*step], j=[0:360/theta_step])
                    [i+step+(j+1)%step, i+step+j, i+j]]
            )
);

```


[User]
Hey, I'm looking to generate a custom wall plate design in OpenSCAD, and I'd really appreciate your help creating the necessary SCAD files! I'm trying to get something similar to the design in the attached `CustomizableWallPlate.scad` file, but I want it generated from scratch, so I can more easily customize it.

Here’s a breakdown of what I need:

**Overall Functionality:**
The design should be for customizable electrical wall plates. I need to be able to specify how many devices are on the plate, what kind of devices they are (like switches, outlets, etc.), the size of the plate, if it has a spacer to account for protruding boxes, and choose a border design.

**Key Parameters:**

*   **`device_count`**: An integer to set the number of devices (1 to 6) on the plate.
*   **`plate_height`**: An integer representing the plate height, which will correlate to overall size. It should have the following sizes:
    *   0: 4 1/2 inches
    *   1: 4 11/16 inches
    *   2: 4 7/8 inches
    *   3: 5 1/16 inches
    *   4: 5 1/4 inches
    *   5: 5 7/16 inches
    *   6: 5 5/8 inches
    The width should increase by the same amount.
*   **`spacer`**: An integer (0-10) that represents a spacer depth in increments of 1/16 of an inch, used to make the plate deeper for protruding boxes.
    *   0: none
    *   1: 1/16 inch
    *   2: 1/8 inch
    *   3: 3/16 inch
    *   4: 1/4 inch
    *   5: 5/16 inch
    *   6: 3/8 inch
    *   7: 7/16 inch
    *   8: 1/2 inch
    *   9: 9/16 inch
    *   10: 5/8 inch
*   **`offset_opening`**: A string that can be "none", "standard", or "narrow". It controls the positioning of the device openings and should default to "none".
*   **`border`**: A string that can be "rounded", "artdeco", "frame", "stud", or "diamond", defining the wall plate's border design. It should default to "rounded".
*   **`1st_device`, `2nd_device`, ..., `6th_device`**: String variables for each device on the plate. Each should be set by the user. They can be set to:
    *   "blankbox"
    *   "blankyoke"
    *   "toggle"
    *   "single"
    *   "duplex"
    *   "rocker"
    *   "rotary"
    *   "despard3"
    *   "despard2"
    *   "despard1"
    *    "despard1v"
    *   "button"
    *   "keystone1"
    *   "keystone2"
     *   "keystone4"
     *   "keystone6"
*    `stud_scale_factor`: A floating point number used to adjust the size of the stud border.

**Design Details:**

*   The plate dimensions should be based on standard US wall plate sizes as defined in the description of the file.
*   The code should use the MCAD library.
*   The plate should have screw holes for mounting to the electrical box.
*   Each device type should have its corresponding cutout on the plate. The device openings should also be offset based on the `offset_opening` setting and should be able to account for all the listed device types.
*  The keystone device types require additional shapes for a solid keystone jack for printing on a 3D printer. These should be created when one of the keystone type devices is used.
*   The "stud" border design should use a stud size that can be adjusted with the `stud_scale_factor`.

I'm not sure how to handle all the math for the different plate sizes and offsets, but I'm hoping you can take care of all the calculations. I think all of this should be included in a single scad file, and I'm hoping you can deliver this solution as a response to this request. Thanks!

[Assistant]
CustomizableWallPlate.scad:
```
/*
 Safety Warning
 ==============
 ALWAYS be particularly careful when working on electrical projects and with electrical equipment. Be sure to positively identify each circuit with an electrical test meter or instrument and Turn Off Electrical Circuits BEFORE proceeding with any electrical work.

 Compliance with Laws
 ====================
 ALWAYS follow electrical code requirements specific to your area, and before undertaking any home electrical project, contact your local electrical authority and your insurance company to ensure that you comply with all policies, warranties, regulations and authorities concerning this work. If you are at all unsure about completing any aspect of you wiring projects, consult a qualified electrical contractor to perform the service(s) for you.

 Legal Disclaimer
 ================
 This software is provided as is without warranties of any kind, either express or implied.  You acknowledge, by use of the software and documentation, that use of it is at your sole risk and that you assume full responsibility for any and all costs arising from use of the software and documentation, including but not limited to repairs or service costs.

 About Wall Plate sizing
 =======================
 "ANSI/NEMA WD 6-2016 Wiring Devices - Dimensional Specifications" defines standard dimensions for wall plates in the US. However it only defines a *minimum* size for the outer dimensions of 4.49" H x 2.74" W for a single-gage wallplate. Some manufacturers use this as their "standard" wall plate size and others go a little larger. There is no standardized naming for wall plate sizes.

 This program provides a range of sizes in increments of 3/16". This aligns closely with wall plates commonly available in North America and takes the guess work out of selecting a plate size. The width of the plate will grow by an equal amount.

Credits
=======
This program borrows the keystone elements from the WALLY Plate customizer
http://www.thingiverse.com/thing:47956
*/
use <MCAD/boxes.scad>

/* [Size] */
// The number of devices the electrical box contains side-by-side. Also known as a gang size.
device_count = 1; // [1:6]

// Use a larger plate to cover imperfections in the wall or paint, to protect the wall from dirt and smudges, or to add a decorative border. Plate heights are listed in increments of 3/16 inches. Plate width will also increase by the same amount. Some edge designs will increase the width or height further. There is no standarization in wall plate size names, the names given here are made up.
plate_height = 1; // [0:4 1/2 in. standard, 1:4 11/16 in. standard plus, 2:4 7/8 in. preferred, 3:5 1/16 in. midsize, 4:5 1/4 in. oversize, 5:5 7/16 in. jumbo, 6:5 5/8 in. super jumbo]
// [0:4 1/2 in. | 114.3 mm - standard, 1:4 11/16 in. | 119.1 mm - standard plus, 2:4 7/8 in. | 123.8 mm preferred, 3:5 1/16 in. | 128.6 mm - midsize, 4:5 1/4 in. | 133.4 mm - oversize, 5:5 7/16 in. | 138.1 mm - jumbo, 6:5 5/8 in. | 142.9 mm - super jumbo]

// For electrical boxes that protrude from the wall, add a spacer so there isn't a gap between the wall and the plate.
spacer = 0; //[0:none,1:1/16 in. | 1.6 mm,2:1/8 in. | 3.2 mm,3:3/16 in. | 4.8 mm, 4:1/4 in. | 6.4 mm, 5:5/16 in. | 7.9 mm, 6:3/8 in. | 9.5 mm, 7: 7/16 in. | 11.1 mm, 8: 1/2 in. | 12.7 mm, 9:9/16 in. | 14.3 mm, 10:5/8 in. | 15.9 mm]

// An offset opening can allow the plate to fit in tight spaces where there is some obstruction on one side. The standard offset keeps one edge at the standard 1-3/8 in. from edge to screw hole center while allowing you to increase the size of the other 3 edges with the plate height selection. A narrow offset shifts the openings over an additional 1/4 inch. The 1st device will always be the device closest to the narrow edge.
offset_opening = "none"; // [none:no offset,standard:standard offset 1-3/8 in. from edge to screw center, narrow:narrow offset 1-1/8 in. from edge to screw center]

/* [Design] */
// Controls the number of facets on curves. More facets are smoother, but take longer to render and product larger file sizes.
$fn = 24; // [0:48]

border = "rounded"; // [rounded:rounded, artdeco:art deco, frame:frame, stud:building brick studded border, diamond:diamond plate]

/* [Devices] */
1st_device = "rocker"; // [blankbox:blank - box mount,blankyoke:blank - yoke mount,toggle:toggle switch,single:single outlet 1.406 in. diameter opening,duplex:duplex outlet,rocker:rocker or decorative,rotary:rotary dimmer or fan control,despard3:triple Despard,despard2:double Despard,despard1:single horizontal Despard,despard1v:single vertical Despard,button:two pushbutton,keystone1:single keystone,keystone2:double keystone,keystone4:2x2 keystone,keystone6:2x3 keystone]
2nd_device = "blank"; // [blankbox:blank - box mount,blankyoke:blank - yoke mount,toggle:toggle switch,single:single outlet 1.406 in. diameter opening,duplex:duplex outlet,rocker:rocker or decorative,rotary:rotary dimmer or fan control,despard3:triple Despard,despard2:double Despard,despard1:single horizontal Despard,despard1v:single vertical Despard,button:two pushbutton,keystone1:single keystone,keystone2:double keystone,keystone4:2x2 keystone,keystone6:2x3 keystone]
3rd_device = "blank"; // [blankbox:blank - box mount,blankyoke:blank - yoke mount,toggle:toggle switch,single:single outlet 1.406 in. diameter opening,duplex:duplex outlet,rocker:rocker or decorative,rotary:rotary dimmer or fan control,despard3:triple Despard,despard2:double Despard,despard1:single horizontal Despard,despard1v:single vertical Despard,button:two pushbutton,keystone1:single keystone,keystone2:double keystone,keystone4:2x2 keystone,keystone6:2x3 keystone]
4th_device = "blank"; // [blankbox:blank - box mount,blankyoke:blank - yoke mount,toggle:toggle switch,single:single outlet 1.406 in. diameter opening,duplex:duplex outlet,rocker:rocker or decorative,rotary:rotary dimmer or fan control,despard3:triple Despard,despard2:double Despard,despard1:single horizontal Despard,despard1v:single vertical Despard,button:two pushbutton,keystone1:single keystone,keystone2:double keystone,keystone4:2x2 keystone,keystone6:2x3 keystone]
5th_device = "blank"; // [blankbox:blank - box mount,blankyoke:blank - yoke mount,toggle:toggle switch,single:single outlet 1.406 in. diameter opening,duplex:duplex outlet,rocker:rocker or decorative,rotary:rotary dimmer or fan control,despard3:triple Despard,despard2:double Despard,despard1:single horizontal Despard,despard1v:single vertical Despard,button:two pushbutton,keystone1:single keystone,keystone2:double keystone,keystone4:2x2 keystone,keystone6:2x3 keystone]
6th_device = "blank"; // [blankbox:blank - box mount,blankyoke:blank - yoke mount,toggle:toggle switch,single:single outlet 1.406 in. diameter opening,duplex:duplex outlet,rocker:rocker or decorative,rotary:rotary dimmer or fan control,despard3:triple Despard,despard2:double Despard,despard1:single horizontal Despard,despard1v:single vertical Despard,button:two pushbutton,keystone1:single keystone,keystone2:double keystone,keystone4:2x2 keystone,keystone6:2x3 keystone]

/* [Advanced] */
// When printing the studded border, adjust the stud size to achieve a snug fit with your bricks.
stud_scale_factor = 1.02;

/* [Hidden] */

devices = [1st_device, 2nd_device, 3rd_device, 4th_device, 5th_device, 6th_device];

// Original specifications are in inches, we will convert to mm
inch_to_mm = 25.4;
standard_height = 4.5 * inch_to_mm;
standard_width = 2.75 * inch_to_mm;
gang_width_increment = 1.812 * inch_to_mm;
plate_size_increment = (3/16) * inch_to_mm;
// offset openings keep left device a fixed distance from the edge while allow the other 3 edges to vary in size.
plate_width_factor = offset_opening == "none" ? 1 : 0.5;
plate_height_mm = standard_height + plate_height * plate_size_increment;
plate_width_mm = standard_width + plate_height * plate_size_increment*plate_width_factor + (gang_width_increment*(device_count-1));

// screw hole offsets are vertical from middle of plate
outer_yoke_mount_screw_hole_offset = (3.812/2) * inch_to_mm;
box_mount_screw_hole_offset = (3.281/2) * inch_to_mm;
inner_yoke_mount_screw_hole_offset = (2.375/2) * inch_to_mm;

// cutouts
error_allowance_mm = 0.5;
duplex_diameter = 1.343 * inch_to_mm +error_allowance_mm;
duplex_offset = (1.531/2) * inch_to_mm;
duplex_bounded_rectangle_height = 1.125 * inch_to_mm;
duplex_total_opening_height = duplex_bounded_rectangle_height*2+(2*duplex_offset-duplex_bounded_rectangle_height);

echo(duplex_total_opening_height=duplex_total_opening_height);
toggle_hole_width = 0.42 * inch_to_mm +error_allowance_mm;
toggle_hole_height = 0.95 * inch_to_mm +error_allowance_mm;

rocker_hole_width = 1.310 * inch_to_mm +error_allowance_mm;
rocker_hole_height = 2.630 * inch_to_mm +error_allowance_mm;
rocker_corner_radius = 0.094 * inch_to_mm;

despard_diameter = 0.916 * inch_to_mm +error_allowance_mm;
despard_bounded_rectangle_height = 0.674 * inch_to_mm +error_allowance_mm;

// used to prevent borders or designs from encroaching on the opening
opening_margin = 6;
opening_max_width = max(duplex_diameter,toggle_hole_width,rocker_hole_width,despard_diameter) + opening_margin;
opening_max_height = max(duplex_total_opening_height,toggle_hole_height,rocker_hole_height,despard_bounded_rectangle_height) + opening_margin;

device_max_width = 1.75 * inch_to_mm;
device_max_height = 2.812 * inch_to_mm;
device_depth = 1.5 * inch_to_mm; // arbitrary, for cutouts
device_radius = 0.437 * inch_to_mm;

yoke_max_width = 1.8 * inch_to_mm;
yoke_max_height = 4.2 * inch_to_mm;
yoke_depth = 0.09 * inch_to_mm;

plate_thickness = 6.3;
shell_thickness = 2.5;
spacer_mm = (spacer/16) * inch_to_mm;
total_thickness = plate_thickness + spacer_mm;
cutout_z = total_thickness*3;
corner_radius = (border == "rounded") ? 2.5 : (border == "stepped" ? 0.8 : 0);
echo(  corner_radius=corner_radius);

//----------------------------------------------
//          CUTOUTS
//----------------------------------------------

module device_cutout(){
    translate([0,0,-(yoke_depth+device_depth)/2+total_thickness-shell_thickness]) cube([yoke_max_height,gang_width_increment+0.1,yoke_depth+device_depth],center=true);
}

module duplex_hole(){
    intersection() {
        cylinder(h=cutout_z,d=duplex_diameter);
        cube([duplex_bounded_rectangle_height,40,cutout_z],center=true);
    }
}

module duplex_coutouts(){
    translate([duplex_offset,0,0]) duplex_hole();
    translate([-duplex_offset,0,0]) duplex_hole();
}

module toggle_cutouts(){
    cube([toggle_hole_height,toggle_hole_width,cutout_z],center=true);
}

module rocker_cutouts(){
    roundedBox([rocker_hole_height,rocker_hole_width,cutout_z],rocker_corner_radius);
}

module despard_hole(){
    intersection() {
        cylinder(h=cutout_z,d=despard_diameter);
        cube([despard_bounded_rectangle_height,40,cutout_z],center=true);
    }
}

module despard3_cutouts(){
    despard_offset = 0.922 * inch_to_mm;
    despard_hole();
    translate([-despard_offset,0,0]) despard_hole();
    translate([despard_offset,0,0]) despard_hole();
}

module despard2_cutouts(){
    despard_offset = 0.922 * inch_to_mm;
    translate([-despard_offset,0,0]) despard_hole();
    translate([despard_offset,0,0]) despard_hole();
}

keystone_clearance = 0.2;
keystone_width = 15 + keystone_clearance;
keystone_column_offset = 11.5;
keystone_height_offset = 14.3;
keystone_x_offset = 24.75/2;

module keystone_cutout(y_offset=0) {
    keystone_height = 16.5 + keystone_clearance;
    translate([0,y_offset,5]) {
        cube([keystone_height,keystone_width,10], center = true);
        translate([-5.5,0,0]) rotate([0, 45, 0]) cube([4,keystone_width,10], center = true);
    }
}

module keystone2_cutout() {
    keystone_cutout(-keystone_column_offset);
    keystone_cutout(keystone_column_offset);
}

module keystone4_cutout() {
    translate([keystone_x_offset,0,0]) keystone2_cutout();
    translate([-keystone_x_offset,0,0]) keystone2_cutout();
}

module keystone6_cutout() {
    translate([keystone_x_offset*2,0,0]) keystone2_cutout();
    keystone2_cutout();
    translate([-keystone_x_offset*2,0,0]) keystone2_cutout();
}

module keystone_solid(y_offset=0) {
  cube_height = 9.8;
  translate([keystone_height_offset,-11.5+y_offset,-cube_height+total_thickness-0.1]) rotate([0,0,90])
  {
    difference()
    {
      translate([2,2,.1]) cube([19,26.5,cube_height]);
      translate([4-keystone_clearance/2,4-+keystone_clearance/2,0])

      {
        difference()
        {
          cube([keystone_width,22.5+keystone_clearance,10]);
          translate([-1,3,-3.40970]) rotate([45,0,0]) cube([17,2,6.5]);
          translate([-1,21.0,0]) rotate([-45,0,0]) cube([17,2,6.5]);
        }
        translate([0,1.8,0]) cube([15+keystone_clearance,19.62+keystone_clearance,5]);
      }
    }
  }
}

module keystone2_solid() {
    keystone_solid(-keystone_column_offset);
    keystone_solid(keystone_column_offset);
}

module screw_hole() {
	translate([0,0,-1]) cylinder(r=2.3, h=cutout_z);
	translate([0,0,total_thickness-shell_thickness+0.01]) cylinder(r1=2.3, r2=3.6, h=3);
	translate([0,0,total_thickness-0.01]) cylinder(r=3.6, h=cutout_z);
}

module screw_hole_cutouts(offset) {
    translate([offset,0,0]) screw_hole();
    translate([-offset,0,0]) screw_hole();
}

module device_additions(device, y_offset){
    translate([0,y_offset,-0.01]){
        if (device == "keystone1"){
            keystone_solid();
        }
        if (device =="keystone2"){
            keystone2_solid();
        }
        if (device =="keystone4"){
            translate([keystone_x_offset,0,0]) keystone2_solid();
            translate([-keystone_x_offset,0,0]) keystone2_solid();
        }
        if (device =="keystone6"){
            translate([keystone_x_offset*2,0,0]) keystone2_solid();
            keystone2_solid();
            translate([-keystone_x_offset*2,0,0]) keystone2_solid();
        }
    }
}

module device_cutouts(device, y_offset){
    echo("Device: ",device," y_offset:",y_offset);

    translate([0,y_offset,-0.01]){
        device_cutout(); // hollows out the back of the panel
        if (device == "duplex"){
            duplex_coutouts();
            screw_hole();
        }
        if (device == "single"){
            singlediameter = 1.406* inch_to_mm;
            cylinder(h=cutout_z,d=singlediameter);
            screw_hole_cutouts(inner_yoke_mount_screw_hole_offset);
        }
        if (device == "toggle"){
            toggle_cutouts();
            screw_hole_cutouts(inner_yoke_mount_screw_hole_offset);
        }
        if (device == "rocker"){
            rocker_cutouts();
            screw_hole_cutouts(outer_yoke_mount_screw_hole_offset);
        }
        if (device == "rotary"){
            cylinder(r=5, h=cutout_z);
            screw_hole_cutouts(inner_yoke_mount_screw_hole_offset);
        }
        if (device == "despard3"){
            despard3_cutouts();
            screw_hole_cutouts(outer_yoke_mount_screw_hole_offset);
        }
        if (device == "despard2"){
            despard2_cutouts();
            screw_hole_cutouts(outer_yoke_mount_screw_hole_offset);
        }
        if (device == "despard1"){
            despard_hole();
            screw_hole_cutouts(outer_yoke_mount_screw_hole_offset);
        }
        if (device == "despard1v"){
            rotate([0,0,90]) despard_hole();
            screw_hole_cutouts(outer_yoke_mount_screw_hole_offset);
        }
        if (device == "button"){
            buttondiameter = 0.515 * inch_to_mm;
            buttonoffset = (0.922/2)* inch_to_mm;
            translate([-buttonoffset,0,0]) cylinder(h=cutout_z,d=buttondiameter);
            translate([buttonoffset,0,0]) cylinder(h=cutout_z,d=buttondiameter);
            screw_hole_cutouts(inner_yoke_mount_screw_hole_offset);
        }
        if (device == "blankbox"){
            screw_hole_cutouts(box_mount_screw_hole_offset);
        }
        if (device == "blankyoke"){
            screw_hole_cutouts(inner_yoke_mount_screw_hole_offset);
        }
        if (device == "keystone1"){
            keystone_cutout();
            screw_hole_cutouts(box_mount_screw_hole_offset);
        }
        if (device == "keystone2"){
            keystone2_cutout();
            screw_hole_cutouts(box_mount_screw_hole_offset);
        }
        if (device == "keystone4"){
            keystone4_cutout();
            screw_hole_cutouts(box_mount_screw_hole_offset);
        }
        if (device == "keystone6"){
            keystone6_cutout();
            screw_hole_cutouts(box_mount_screw_hole_offset);
        }
    }
}

function y_offset()= (offset_opening == "none") ? -(gang_width_increment/2)*(device_count-1)
    : ((offset_opening == "standard") ? -(plate_width_mm/2)+(1.375 * inch_to_mm) : -(plate_width_mm/2)+(1.125 * inch_to_mm));

module cutouts(){
    for (i = [0:device_count-1]){
        device_cutouts(devices[i], y_offset()+i*gang_width_increment);
    }
}

module additions(){
    for (i = [0:device_count-1]){
        device_additions(devices[i], y_offset()+i*gang_width_increment);
    }
}

//----------------------------------------------
//          PLATES
//----------------------------------------------

stud_diameter= 4.85;
stud_height= 1.8;
stud_spacing= 8;
cylinder_precision= 0.1;
module stud() {
    cylinder(r=(stud_diameter*stud_scale_factor)/2,h=stud_height,$fs=cylinder_precision);
}

module studded_plate(corner_radius=0.5){
    rounded_edge_plate(corner_radius);
    bounding_box = [plate_height_mm - 2 * corner_radius - stud_diameter,plate_width_mm - 2 * corner_radius- stud_diameter];
    stud_area_height = bounding_box.x -(bounding_box.x % stud_spacing);
    stud_area_width = bounding_box.y - (bounding_box.y % stud_spacing);
    y1 = y_offset()-opening_max_width/2;
    total_device_width = opening_max_width+gang_width_increment*(device_count-1);
    echo(total_device_width=total_device_width);
    for (y = [-stud_area_width/2:stud_spacing:stud_area_width/2]){
        for (x = [-stud_area_height/2:stud_spacing:stud_area_height/2]){
            if (!((y+stud_diameter/2 > y1) && (y-stud_diameter/2 < y1+total_device_width) && (abs(x)-stud_diameter/2 < opening_max_height/2))){
                translate([x,y,total_thickness]) stud();
            }
        }
    }
}

module rounded_edge_plate(corner_radius=2.5){
    difference() {
        union(){
        translate([0,0,total_thickness/2])
            roundedPanel([plate_height_mm,plate_width_mm,total_thickness],corner_radius);
        }
        translate([0,0,(total_thickness-shell_thickness)/2-0.02])
            roundedPanel([plate_height_mm-2*shell_thickness,plate_width_mm-2*shell_thickness,total_thickness-shell_thickness],corner_radius*0.5);
    }
}

module artdeco1_plate() {
    corner_radius = 0.8;
    size_increment = (3/8) * inch_to_mm;

    for (i = [1:3]){
        translate([0,0,(total_thickness*i/3)/2])
            roundedPanel([plate_height_mm-(3-i)*size_increment,plate_width_mm+(3-i)*size_increment,total_thickness*i/3],corner_radius);
    }
}

module framed_plate() {
    radius = 0.25;
    rounded_edge_plate(corner_radius=radius);
    layerthickness = 1;
    inset = 1;
    layers = 3;
    top_frame_edge_width = 12;
    for (layer = [0:layers-1]){
        frame_edge_width = top_frame_edge_width + 2*inset*(layers-layer-1);
        translate([0,0,total_thickness+layer*layerthickness])
            roundedFrame([plate_height_mm-layer*2*inset,plate_width_mm-layer*2*inset,layerthickness],[plate_height_mm-frame_edge_width,plate_width_mm-frame_edge_width,layerthickness],radius);

    }
}

module beveled_plate(x,y,z){
    points = [
        [ 0,  0,0],
        [ 0,  y,0],
        [ x,  y,0],
        [ x,  0,0],
        [ 0+z,  0+z,z],
        [ 0+z,  y-z,z],
        [ x-z,  y-z,z],
        [ x-z,  0+z,z]
    ];
    faces = [
        [0,3,2,1],
        [4,5,6,7],
        [0,1,5,4],
        [1,2,6,5],
        [2,3,7,6],
        [3,0,4,7]
    ];

    polyhedron(points,faces, convexity = 2);
}

module plate(border){
    difference() {
        union() {
            if (border == "rounded"){
                rounded_edge_plate();
            }
            if (border == "artdeco"){
                artdeco1_plate();
            }
            if (border == "frame"){
                framed_plate();
            }
            if (border =="stud"){
                studded_plate();
            }
            if (border =="diamond"){
                diamond_plate();
            }
        }
    }
}

module diamond(){
    height = 1.5;
    points = [
        [ 0,-10,0],
        [ 3,  0,0],
        [ 0, 10,0],
        [-3,  0,0],
        [ 0, -8,height],
        [ 2,  0,height],
        [ 0,  8,height],
        [-2,  0,height]
    ];
    faces = [
        [0,1,5],
        [0,5,4],
        [1,2,6],
        [6,5,1],
        [2,3,7],
        [7,6,2],
        [3,0,4],
        [4,7,3],
        [4,5,6,7],
        [0,3,2,1]
    ];

    polyhedron(points,faces, convexity = 2);
}

module diamond_pair(){
    rotate([0,0,45]) diamond();
    translate([12,12,0]) rotate([0,0,-45]) diamond();
}

module diamond_pattern(vector){
    translate([-vector.x/2,-vector.y/2,vector.z]) {
        intersection() {
            union() {
                for (y1 = [0:24:vector.y+10]){
                    for (x1 = [0:24:vector.x+10]){
                        translate([x1,y1,0]) diamond_pair();
                    }
                }
            }
            translate([0,0,-1]) beveled_plate(vector.x,vector.y,vector.z+2){
            }
        }
    }
}
module diamond_plate(corner_radius=1){
    difference() {
        union() {
            rounded_edge_plate(corner_radius);
            diamond_pattern([plate_height_mm - 2 * corner_radius,plate_width_mm - 2 * corner_radius,total_thickness]);
        }
        device_offset = (gang_width_increment*(device_count-1)/2)+y_offset();
        h = opening_max_height+2*total_thickness;
        w = opening_max_width+2*total_thickness+gang_width_increment*(device_count-1);
        echo(device_offset=device_offset);
        translate([0,device_offset,total_thickness-0.001]) mirror([0,0,1]) translate([-h/2,-w/2,-total_thickness]) beveled_plate(h,w,total_thickness);
    }
}

//----------------------------------------------
//          GENERAL SHAPES
//----------------------------------------------

// The rounded panel has rounded edges on the top and corners but not on the bottom
module roundedPanel(size, radius)
{
    r = min(radius,size.x/2,size.y/2);
    intersection(){
        union() {
            translate([0,0,-r/2]) cube([size.x, size.y-r*2, size.z-r], center=true);
            translate([0,0,-r/2]) cube([size.x-r*2, size.y, size.z-r], center=true);
            cube([size.x-r*2, size.y-r*2, size.z], center=true);

            // top edges
            for (x = [r-size.y/2, -r+size.y/2]) {
                y = -r+size.z/2;
                rotate([90,0,90])
                  translate([x,y,0])
                  cylinder(h=size.x-2*r, r=r, center=true);
              }

            for (y = [r-size.x/2, -r+size.x/2]) {
                x = r-size.z/2;
                rotate([90,90,0])
                  translate([x,y,0])
                  cylinder(h=size.y-2*r, r=r, center=true);
              }
            // sides
            for (x = [r-size.x/2, -r+size.x/2],
                     y = [r-size[1]/2, -r+size.y/2]) {
                  translate([x,y,-r/2])
                  cylinder(h=size.z-r, r=r, center=true);
              }
            // corners
            for (x = [r-size.x/2, -r+size.x/2],
                   y = [r-size.y/2, -r+size.y/2]) {
              translate([x,y,-r+size.z/2]) sphere(r);
            }
        }
        cube(size,center = true); // trims off spheres and cylinders
    }
}

// The inner_size can be a different thickness (z), in which
// case the top of the inner cutout will be aligned with the
// top of the outer frame.
module roundedFrame(outer_size,inner_size,radius){
    top_z = outer_size.z/2;
    r = min(radius,(outer_size.x-inner_size.x)/4,(outer_size.y-inner_size.y)/4);
    difference() {
        roundedPanel(outer_size, r);
        translate([0,0,top_z-(inner_size.z/2)]) cube(inner_size+[0,0,0.01],center=true);
        translate([0,0,top_z-r/2+0.01]) cube([inner_size.x+r*2,inner_size.y+r*2,r],center=true);
    }
    for (y = [-(r+inner_size.y/2),r+inner_size.y/2]) {
        translate([0,y,top_z-r]) rotate([90,0,90]) cylinder(h=inner_size.x+2*r,r=r,center=true);
    }
    for (x = [-(r+inner_size.x/2),r+inner_size.x/2]) {
        translate([x,0,top_z-r]) rotate([90,90,0]) cylinder(h=inner_size.y+2*r,r=r,center=true);
    }

}

//----------------------------------------------
//          MAIN ROUTINE
//----------------------------------------------

difference(){
    plate(border);
    cutouts();
}
additions();

```


[User]
I'm looking to create a parametric mount for attaching a flat bar to a curved monitor, specifically for mounting a Tobii Eye Tracker. I've got a basic idea down in my head, but I need it in OpenSCAD with some good parameters.

The main things I need are:

*   **Bar Mount:** This will be the main piece that attaches to the monitor. It should have a curved back to match the monitor's curvature and a flat front to which the bar will be mounted.
*   **Bar:** A simple rectangular bar that will be held by the mounts.
*   **Magnets:** I want to embed magnets into the mount to help hold the bar in place, so I need to define magnet locations and sizes.
*   **Logo Cutout:** I also need a space for a logo that can be cutout from the design for aesthetics
*   **Parameters:** This is crucial. I need to be able to control the following through parameters:
    *   `monitor_radius`: The radius of the monitor's curvature.
    *   `bar_w`: The width of the bar.
    *   `bar_h`: The height of the bar.
    *   `magnet_d`: The diameter of the magnets.
    *   `magnet_h`: The height (thickness) of the magnets.
    *  `magnet_offset`: Offset of the magnets from the center of the bar
    *  `plate_t`: The thickness of the mounting plate. This should be greater than the magnet height.
    * `logo_w`: The width of the logo cutout
    *  `center_section`: Boolean that switches between cutting out the center section or the left and right sections.
*   **Modular Design:** I need to be able to generate the entire mount (or just parts of it). Specifically, I want to be able to generate:
    *   The full mount.
    *   Only the left third of the mount.
    *   Only the center third of the mount.
    *   Only the right third of the mount.
    * A test center piece to test the logo cutout.

For libraries, I'm thinking I'd probably need something from the MCAD library for the curves, and potentially BOSL for more advanced features, but if there's a better option I'm open to suggestions. Ideally, I'd prefer everything within one or two .scad files to keep things simpler for me.

I'm not sure how to go about creating the curved back of the mount, maybe using a `rotate_extrude` operation would be useful? Anyway, I hope you can help!

[Assistant]
eye_bar_mount.scad:
```
part = "full"; // [test:center piece to test logo cutout,left:left third,center:center third,right:right third,full:whole bar mount]

/* [Global] */
logo_w = 20; // monitor logo width
magnet_o = 111.66; // offset from center of bar - default bar_w/3
magnet_d = 4; //diameter of magnet
magnet_h = 1; // height of magnet to embed
plate_t = 1.5; // thickness of mounting plate (should be > magnet_h)
bar_w = 335; // width of bar - default to match bar specs
bar_h = 6.6; // height of bar - default to match bar specs

section = bar_w/3;

module body() {
    difference() {
        union() {
            rotate([0,0,-5]) translate([-1890.5,-165,0]) rotate_extrude(angle=10) translate([1900,0,0]) union() {
                square([2,bar_h]);
                square([10,2]);
            }
            translate([-4.5-plate_t,-bar_w/2]) cube([plate_t,bar_w,bar_h]);
        }
        translate([-10-plate_t,-(logo_w/2),2.01]) cube([logo_w+plate_t,logo_w,bar_h]);
        translate([-6.1,-magnet_o,bar_h/2]) rotate([0,90,0]) cylinder(d=magnet_d,h=magnet_h+.2, $fn=32);
        translate([-6.1,magnet_o,bar_h/2]) rotate([0,90,0]) cylinder(d=magnet_d,h=magnet_h+.2, $fn=32);
    }
}

module left() {
    difference() {
        body();
        translate([0,-section,0]) cube([30,bar_w,30],center=true);
    }
}

module right() {
        difference() {
        body();
        translate([0,section,0]) cube([30,bar_w,30],center=true);
    }
}

module center() {
    intersection() {
        body();
        cube([30,section,30],center=true);
    }
}

if (part == "test") {
    size = logo_w*2/3;
    difference(){
        body();
        translate([-15,size,-0.1]) cube([30,bar_w/2, bar_h+1]);
        translate([-15,-size-bar_w/2,-0.1]) cube([30,bar_w/2, bar_h+1]);
    }
} else if (part == "right"){
    right();
} else if (part == "left"){
    left();
} else if (part == "center"){
    center();
} else {
    body();
}
```


[User]
Okay, I need to design a parametric drill stop for different drill bit sizes. I've seen some designs, but I'd like to be able to customize it myself, and I want to make it a bit more robust than some of the ones out there.

Here are the requirements:

*   **Parametric Drill Bit Size:** The design should accept a string input for the drill bit size. This string needs to handle fractions (e.g., "3/8 inches"), decimals (e.g., "4.5mm"), and integers (e.g., "5mm" or "1/2\""). If no units are specified, it should default to inches if the numerical value is less than 2, and mm otherwise. It needs to parse both the numerical value and the units correctly.
*   **Nut Height:** I need a parameter for the height of the nut that's going to be used to tighten the drill stop, that's the height if it's laying on a side. This will be used to determine bolt head and nut size, in addition to overall height.
*   **Bolt and Nut Integration:**  The drill stop should have an integrated bolt hole and nut recess for either a 6-32 or M4 bolt (whichever is appropriate for a given size), along with bolt head recess, so I can self-tap the bolt.  The hole for the bolt needs to have the correct pilot diameter so that it self-taps well.
*  **Overall Design:** I would like a cylinder with a clamping slot running the length of the cylinder. The bolt and nut recess should be perpendicular to the slot, and should be able to tighten the slot to grip the drill bit. I'd like to have the edges chamfered at the bottom. I'd like to have a small bevel on the top edges.
*   **Labeling:** A label of the size (from the inputted string parameter) needs to be on the side.
*   **Clearance:** There should be some small amount of clearance (around 0.2mm) between the drill bit and the inner diameter of the stop to ensure a good fit.
*   **Unit Tests**: I'd like unit tests so that I know that the parsing is working, and so that I can see how to use the parameters.

I'd like the design to be robust and work with PLA, but it should also be appropriate for ABS or PETG (even if PETG isn't great for high-heat situations).

Could you generate the OpenSCAD code for this?  I would like the code to be parametric and organized in a way that's easy to understand. If you use any libraries, please let me know which ones you're using.  I'd like the code to be in one .scad file, along with the unit tests in the same file.

[Assistant]
DrillDepthStop.scad:
```
// CUSTOMIZABLE DRILL STOP:
// by John Ulvr, 2019-09-06
// https://www.thingiverse.com/thing:3847368

// drill bit diameter (eg:  3/8, 3/8\", 4.5mm, etc)
//size="11/64 inches";
size="4.5mm";
//
// nut height in mm (hieght if stood on side)
nut_height = 8;

//--------------

module unused() {}

/* ord is not supported in older openscads... */
function char_2_num(ch="") =
    len(ch)<1  ? undef
    : ch[0]=="0" ? 0
    : ch[0]=="1" ? 1
    : ch[0]=="2" ? 2
    : ch[0]=="3" ? 3
    : ch[0]=="4" ? 4
    : ch[0]=="5" ? 5
    : ch[0]=="6" ? 6
    : ch[0]=="7" ? 7
    : ch[0]=="8" ? 8
    : ch[0]=="9" ? 9
    : undef;

// returns an array:  [ value, numchars ]
// if not a string, numchars is 0, and value is 0

function str_2_int_arr(string="0",i=0) =
    char_2_num(string[i])==undef ? [ 0 , 0 ]
    : let(r1=str_2_int_arr(string,i+1))
      [
        r1[0] + char_2_num(string[i])*pow(10,r1[1]),
        r1[1] + 1
      ];

function str_2_num_arr(string="0",i=0) =
    let(r1=str_2_int_arr(string,i))
    string[r1[1]] == "/" ?
        let(r2=str_2_int_arr(string,i+r1[1]+1))
        [ r1[0] / r2[0], r1[1]+1+r2[1] ]
    : string[r1[1]] == "." ?
        let(r2=str_2_int_arr(string,i+r1[1]+1))
        [ r1[0] + r2[0]*pow(10,-r2[1]), r1[1]+1+r2[1] ]
    : r1;

function str_2_num(string="0") = str_2_num_arr(string,0)[0];

function sub_str(string,s,e=256) =
              (s>e)||s>=len(string)
              ? ""
              : str(string[s],sub_str(string,s+1,e));

function strip(string) = (string[0]==" ") ? sub_str(string,1):string;

function unit_mult(string) =
    let(string1=strip(string))
    string1=="" ? 1
    : string1=="mm" ? 1
    : string1=="inches" || string=="\"" || string=="in" ? 25.4
    : string1=="cm" ? 10
    : undef;

// returns measurement in mm:   [measurment, unit]
function str_2_measurement_arr(string="0mm") =
    let(num=str_2_num_arr(string))
    let(mult=unit_mult(strip(sub_str(string,num[1]))))
    [ num[0]*mult , strip(sub_str(string,num[1])) ];

function str_2_measurement(string="0mm") = str_2_measurement_arr(string)[0];

// element 1: the actual value:
/*
echo(char_2_num("323"));
echo(char_2_num("hello"));

echo(str_2_int_arr(""));
echo(str_2_int_arr("1"));
echo(str_2_int_arr("12"));
echo(str_2_int_arr("123"));
echo(str_2_int_arr("1234"));
echo("-----");
echo(str_2_num("123"));
echo(str_2_num("10/100"));
echo(str_2_num("1.234"));
*/

/*function str_2_num_arr(str) = [
    if (char_2_num(str[0])
*/

in=25.4;

num_arr=str_2_num_arr(size);
num=num_arr[0];
unit_str=strip(sub_str(size,num_arr[1]));
echo (unit_str=unit_str);

mult = unit_str=="" ? num < 2 ? in : 1 : unit_mult(unit_str);
echo (mult=mult);
echo (num=num);

label = sub_str(size,0,num_arr[1]);

r=num*mult/2+0.2;
echo (r=r);

h=11;
w=6;
slot_width = 1.6;
nut_width=nut_height*1.1547005;
screw_length = 1/2*in - 3;

// doing for M4 or 6-32 bolt:
boltPilotR = 3.5/2-.2;
boltShankR = 4/2;
boltHeadR  = nut_width/2;
nutR = 4.5;

module cube_coords(tlf=[0,0,0], blb=[10,10,10]) {
    pos = ( [ min(blb[0],tlf[0]), min(blb[1],tlf[1]), min(blb[2],tlf[2]) ] );
    size = ( [ abs(blb[0]-tlf[0]), abs(blb[1]-tlf[1]), abs(blb[2]-tlf[2]) ] );
    translate(pos) cube(size=size);
}

//p1 and p2 are on the bottom of the trapazoid.
module trapazoidal_prism(p1=[0,0,0], p2=[0,1,0], bottom=4, top=4, height=4) {
    c = (p1+p2)/2;
    v1=p2-p1;
    vp = [-v1.y, v1.x, 0] / sqrt(v1.x*v1.x+v1.y*v1.y);  // perpendicular vector...
    pt1=p1+[0,0,height];
    pt2=p2+[0,0,height];

    vb=vp*bottom/2;
    vt=vp*top/2;

    echo(p1=p1,pt1=pt1,p2=p2,pt2=pt2);

    pts = [
        p1+vb, p1-vb, p2-vb, p2+vb,
        pt1+vt, pt1-vt, pt2-vt, pt2+vt
    ];
    faces = [
        [0,1,2,3],  // bottom
        [4,5,1,0],  // front
        [7,6,5,4],  // top
        [5,6,2,1],  // right
        [6,7,3,2],  // back
        [7,4,0,3]]; // left

    polyhedron( pts, faces );
}

if ((size < 2) || (size > 30)) {
    linear_extrude(1) text("Error -- Size out of range", size=8, valign="center", halign="center");
} else {
    $fn=30;
    difference() {
        union() {
            cylinder(h,r+w,r+w);
            translate([0,r+w/2,h/2]) rotate([0,90,0]) {
                cylinder(screw_length, boltPilotR+3, boltPilotR+3,center=true);
            }
        }

        translate([screw_length/2,r+w/2,h/2]) rotate([0,90,0]) {
            cylinder(nut_width, nutR, nutR, $fn=6);
        }

        translate([0,0,-.5]) cylinder(h+1,r,r);

        cube_coords([-slot_width/2,-r-2,-1],[slot_width/2,r+w+5,h+1]);

        translate([0,r+w/2,h/2]) rotate([0,90,0]) {
            cylinder(r+w, boltPilotR, boltPilotR);
        }
        translate([0,r+w/2,h/2]) rotate([0,-90,0]) {
            cylinder(r+w, boltShankR, boltShankR);
        }
        translate([-screw_length/2,r+w/2,h/2]) rotate([0,-90,0]) {
            cylinder(r+w, boltHeadR, boltHeadR);
        }
        color([1,0,0]) rotate([0,0,90]) translate([0,r+w*.4,h-.8])
            linear_extrude(1) text(label, size=4, font="Arial black", valign="center", halign="center");

        // bevel bottom edges
        color([1,0,0]) translate([0,0,-.5]) {
           difference() {
               cylinder(1.4,r+w+1,r+w+1);
               translate([0,0,-.1]) cylinder(1.6,r+w-1.4,r+w+.2);
           }
           cylinder(1.4,r+1,r-.1);
        }
        translate([0,0,h-.6])
           cylinder(1.4,r-.1,r+1.4);

        trapazoidal_prism( [0,-r-2,-.1],[0,r+w+1,-.1], slot_width+1.6,slot_width, .8);

    }

}

```


[User]
I'm looking to create a customizable book stand, or really a stand for any kind of flat object. I want to be able to adjust several dimensions easily using OpenSCAD parameters.

Here's a rough description of the design I have in mind: It’s essentially a flat base with a curved back support that allows a book or tablet to lean against it, and a small stopper at the bottom to prevent the object from sliding off.

Specifically, the parameters I need to control are:

*   **`stand_width`**: The width of the base of the stand (like the `w` variable in the example, which is linear extruded).
*   **`circle_radius`**: The radius of the circles that make up the basic shape (`c` in the example).
*   **`bend_radius`**: The radius of the curve of the back support (like `r` in the example).
*   **`stand_height`**: The total height of the back support (like `d` in the example)
*   **`support_thickness`**: The thickness of the flat base part (`t` in the example).
*   **`stopper_height`**: The height of the stopper part that holds the book (like `th` in the example)

I'd like the main structure to be created using `hull()` and `linear_extrude()`. I imagine this design would use unions of hulls. I'm envisioning a smooth, flowing shape made by these rounded sections.

Could you generate a parametric OpenSCAD file that allows me to adjust these dimensions? I don't need any complex features other than what I've described. Please make sure all the parameters are easily modifiable at the beginning of the file and that the output is a single solid object. Please do not use any external libraries, just standard OpenSCAD functions.

[Assistant]
round.scad:
```
//bookstand remixed for customizer by hamade 20190906

w = 50; //width of stand
c = 5; //circle
r = 130; //radius of bending
d = 100; //height of stand
t = 25; //thickness of something
th = 11; //height of stopper

linear_extrude(height = w, center = true)
union() {
hull() {
   translate([d,0,0]) circle(c);
   circle(c);
 }
translate([d,0,0])
rotate ([0,0,r])
hull() {
   translate([d,0,0]) circle(c);
   circle(c);
 }
hull() {
   translate([0,-t,0]) circle(c);
   circle(c);

 }
hull() {
   translate([0,-t,0]) circle(c);

translate([th,-t,0]) circle(c);}
}

```


[User]
Okay, I'm looking to have a parametric soap tray design created in OpenSCAD. I've seen a few designs out there, but I'd like to be able to fully customize the dimensions. The basic idea is a rectangular tray with a slatted base for drainage.

Here's what I need to be customizable:

*   **Overall Dimensions:** I'd like to be able to set the overall width (trayX), depth (trayY), and height (trayZ) of the tray.
*   **Wall Thickness:** I need a variable (wallThickness) to control how thick the outer walls of the tray are.
*   **Slat Dimensions:** I need control over the thickness of the slats/lines (lineThickness) as well as a minimum gap (lineGap) between them.
*   **Foot Height:** I also need a variable (foot) to raise the whole tray off the surface
*   **Wall Height:** Additionally there should be a variable (wall) to control the additional height the walls have above the base.
*   **Slats** The slats must not extend to the edge of the tray, there needs to be a wallThickness buffer on either side of the slats. The length of the slats/lines (lineLength) should be calculated as `trayY-(wallThickness*2)`. The module used to create the lines should be named `line`.

I'd prefer the slats to be evenly spaced, and the spacing should be calculated based on the line thickness, minimum gap, and the overall tray width, in that order. If the requested width means we cannot achieve the requested gap, the actual gap should be increased to allow for an integer number of lines with minimum gap between them.

Finally, the code should be well-commented so that I can understand how everything works, and I want to be able to adjust everything using variables at the top of the file.

Could you generate the OpenSCAD code for this?

[Assistant]
Customizable_Soap_Tray.scad:
```
/*
Customizable Soap Tray
Version 1.0
Written by @kenwebart
info@kenweb.art
https://kenweb.art

Please update your settings below.

License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode

You are free to:
Share — copy and redistribute the material in any medium or format
Adapt — remix, transform, and build upon the material
The licensor cannot revoke these freedoms as long as you follow the license terms.

Under the following terms:
Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
NonCommercial — You may not use the material for commercial purposes.
ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.

Notices:
You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation.
No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
*/

/*
Use at your own risk.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

//width in mm.
trayX = 160;

//depth in mm.
trayY = 80;

//height in mm.
trayZ = 15;

//wall thickness in mm.
wallThickness = 5;

//line thickness in mm.
lineThickness = 3;

//Min. line gap in mm.
lineGap = 10;

//foot in mm.
foot = 0;

//wall in mm.
wall = 10;

//line length
lineLength = trayY-(wallThickness*2);

// module for lines
module line(start, end, height, thickness) {
	color("blue")
	hull() {
		translate(start) linear_extrude(height = height) square(thickness);
		translate(end) linear_extrude(height = height) square(thickness);
	}
}

line([0,0], [0,trayY-wallThickness], trayZ+foot+wall, wallThickness);
line([trayX-wallThickness,trayY-wallThickness], [trayX-wallThickness,0], trayZ+foot+wall, wallThickness);
line([0,0], [trayX-wallThickness,0], trayZ+foot+wall, wallThickness);
line([0,trayY-wallThickness], [trayX-wallThickness,trayY-wallThickness], trayZ+foot+wall, wallThickness);

//Calc how many lines possible
countX = floor(((trayX-(wallThickness*2))/(lineThickness+lineGap)));
echo("countX:", countX);
calcGapX = ((trayX-(wallThickness*2))-((lineThickness+lineGap)*countX)+lineGap)/(countX-1);
echo("calcGapX:", calcGapX);

gap = (lineGap+calcGapX)*(countX-1)/(countX+1);
echo("gap:", gap);

// padding = lineThickness+lineGap+calcGapX;
// echo("padding:", padding);

padding = lineThickness+gap;
echo("padding:", padding);

for (i=[0:countX-1]) {
	color("Fuchsia")
	translate([wallThickness+(padding*i),wallThickness,0])
	translate([gap,0,foot])
	cube([lineThickness,lineLength,trayZ]);
}

```