[User]
I'm looking to generate a customizable drag chain in OpenSCAD. I've seen some examples online, but I'd like to be able to tweak a lot of parameters to get the exact look and feel I need.

Here's what I'm envisioning, with the key parameters that I'd like to control:

*   **Link Dimensions:** I need to set the overall width and height of each link, let's call these `LinkWidth` and `LinkHeight`, both in millimeters.
*   **Material Thickness:** The thickness of the material should be configurable. I'd like a `MaterialThickness` parameter, also in mm. It should also be able to automatically scale to the size of the links for less fuss.
*   **Link Clearance:** There needs to be some clearance between links so they can move. I want a `LinkClearance` parameter in mm for that.
*   **Snapper Width:** The width of the snapping mechanism will be adjustable with a `SnapperWidth` parameter. It should also be able to automatically size itself.
*    **Snapper Clearance**: There should be a `SnapperClearance` parameter in mm which lets me define the gap between snappers.
*   **Separators:** I need the option to add vertical separators within each link to keep the cables organized. I'd like a `SeparatorCount` parameter for the number of separators, which could be zero to omit them.
*    **Additional Bottom Strength**: I want to be able to add a bit of extra thickness on the bottom of the links to make them stronger, with a `AdditionalBottomStrength` parameter in mm.
*   **Hinge/Snapper Choice**: I need the option of having two different types of attachment. I need the option to have either a hinge/snapper combo or a snapper/snapper combo using the variable `SnapHinge1`.
*   **Edge/Pin:** The design also needs to have either just edges, pins, or both, which would be set by an `EdgePin` parameter.
*    **Degree of Freedom**: I need the option to control the degree of freedom between links with the parameter `DegreeFreedom`.
*   **Pin Offset**: And lastly, I need to be able to offset the location of the pin with a parameter called `PinOffset`, in mm.

I also need it to be parametric with the ability to select between a carrier, a snapper, or both to be viewed.
*   **Selectable Part**: The `SelectPart` parameter should allow me to view all parts (0), just the carrier(1), or just the snapper(2).

The design should be parametric, and ideally, you would generate a couple of functions that create either a full link, just the carrier portion of a link, or just the snapper portion of a link.

I don't need any particular libraries, and the `$fn` variable should be set to a decent value to produce a good resolution. I don't need to be able to control it directly.

[Assistant]
Drag_Chain.scad:
```
/* [Parameters] */
// Which one would you like to see?
SelectPart = 1 ; //[0:All,1:Carrier,2:Snapper]
// Width in mm
LinkWidth = 30 ;
// Heigth in mm
LinkHeight = 20 ;
// Vertical Separator count
SeparatorCount= 0 ; //[0:10]
SnapHinge1= 1 ; //[1:Hinge/Snapper,2:Snapper/Snapper]

/* [Advanced] */
// in mm 0=Auto
MaterialThickness = 0 ;
AdditionalBottomStrength = 0 ;
// in mm
LinkClearance= 0.3 ;
// in mm 0=Auto
SnapperWidth= 0 ;
// in mm
SnapperClearance= 0.2 ;
// Edge or Pin(for limiting the angle)
EdgePin= 1 ; //[0:Edge,1:Pin,2:edge and pin ]
// Degree of freedom between two links
DegreeFreedom= 45 ;
PinOffset= 0 ;

/* [Hidden] */
$fn= 80 ;
module DragChain(selectPart,linkWidth,linkHeight,materialThickness,linkClearance,snapperWidth,snapHinge1,snapperClearance,separatorCount,edgePin,additionalBottomStrength,degreeFreedom,pinOffset){

    snapperWidth=snapperWidth ? snapperWidth : linkHeight/2;
    bottomWidth=snapperWidth; //if snapperwidth < max width
    linkLength=snapperWidth+linkHeight*2;
    volume=linkLength*linkWidth*linkHeight;
    materialThickness= materialThickness ? materialThickness : 1+pow(volume,1/3)/30;
        //echo ("material: ",materialThickness);

    degree=degreeFreedom+materialThickness/((PI*(linkHeight-(materialThickness+linkClearance)))/360); //+((PI*linkHeight)/360)*materialThickness);
    //echo( volume, pow(volume,1/3),1+pow(volume,1/3)/30);
    //echo(degree);

if(selectPart==0||selectPart==1){
difference(){
union(){
    // Grundplatte
    translate([0, materialThickness+linkClearance/2, 0]) cube([bottomWidth+linkHeight/2, linkWidth-materialThickness*2-linkClearance, materialThickness+additionalBottomStrength]);
    // Trennstege
    if(separatorCount>=1){
        for(i=[materialThickness+(linkWidth-4*materialThickness)/(separatorCount+1)+materialThickness/(separatorCount+1):(linkWidth-4*materialThickness)/(separatorCount+1)+materialThickness/(separatorCount+1):linkWidth-3*materialThickness]){
            //echo(i);
            difference() {
                translate([0, i, 0]) cube([snapperWidth, materialThickness, linkHeight]);
                translate([materialThickness, 0, linkHeight-materialThickness]) cube([snapperWidth-materialThickness*2, linkWidth, materialThickness+1]);
            }
        }
    }

difference(){
    union(){
    //INNEN
    difference(){
        translate([0, materialThickness, 0]) cube([linkHeight/2+snapperWidth, materialThickness, linkHeight]);
        translate([snapperWidth+linkHeight/2, materialThickness+linkClearance/2, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=materialThickness+linkClearance/2, r=linkHeight/2+linkClearance/2);
        translate([snapperWidth+linkHeight/2, materialThickness+linkClearance/2, linkHeight/2]) rotate([0,-degreeFreedom,180])cube([linkHeight/2, materialThickness+linkClearance/2, linkHeight]);
        translate([snapperWidth+linkHeight/2, materialThickness+linkClearance/2, linkHeight/2]) rotate([0,160,180])cube([linkHeight/2, materialThickness+linkClearance/2, linkHeight]);

    }
    //AUSSEN
    difference() {
        translate([-linkHeight/2, 0, 0]) cube([linkHeight+snapperWidth, materialThickness, linkHeight]);
        translate([snapperWidth+linkHeight/2, linkWidth+0.5, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=linkWidth+1, r=linkHeight/2+linkClearance/2);
        translate([snapperWidth+linkHeight/2, materialThickness+linkClearance/2, linkHeight/2]) rotate([0,-degreeFreedom,180])cube([linkHeight/2, materialThickness+linkClearance/2, linkHeight]);
        translate([snapperWidth+linkHeight/2, materialThickness+linkClearance/2, linkHeight/2]) rotate([0,160,180])cube([linkHeight/2, materialThickness+linkClearance/2, linkHeight]);
        translate([-linkHeight/2, materialThickness+1, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=linkWidth+linkWidth/2, r=linkHeight/4+linkClearance);
    }

    difference() { // Schnapper vorne
        translate([-linkHeight/2, materialThickness, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=materialThickness, r=linkHeight/2);
        translate([-linkHeight/2, materialThickness+1, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=100, r=linkHeight/4+linkClearance);
        translate([-linkHeight, 0, linkHeight/4,]) rotate([0, 0, 20]) cube ([linkHeight/2,linkHeight/2,linkHeight/2]);
    }
    // Schnapper hinten
    difference(){
        translate([snapperWidth+linkHeight/2, materialThickness*2, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=materialThickness-snapperClearance/2, r=linkHeight/2);
        translate([snapperWidth+linkHeight/2, materialThickness+linkClearance/2, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=materialThickness+linkClearance/2, r=linkHeight/2+linkClearance/2);

    }
    //
    translate([snapperWidth+linkHeight/2, materialThickness+linkClearance/2, linkHeight/2]) rotate([90, 0, 0])difference(){
        rotate([0,0,-90])difference(){
            cylinder(h=materialThickness+linkClearance/2, r=linkHeight/4);
            translate([-(materialThickness)-linkClearance+linkHeight/4,-linkHeight/4,materialThickness+linkClearance])rotate([0,45,0])cube([linkHeight/2,linkHeight/2,materialThickness]);
        }
        //enhancement for clipping together
        translate([-(materialThickness)+linkHeight/4,-linkHeight/4,materialThickness*1.8])rotate([0,45,0])cube([linkHeight/2,linkHeight/2,materialThickness]);
    }
    }
    // cutout for Pin
    if(edgePin==1 || edgePin==2){
        translate([snapperWidth+linkHeight/2, materialThickness*2, linkHeight/2]) rotate([90, 0, 0])intersection(){
            difference(){
                union(){
                    cylinder(h=materialThickness+1, r=linkHeight/2);
                    translate([-linkHeight/2,0,0])cube([linkHeight/2,linkHeight/2,materialThickness+1]);
                }
                cylinder(h=materialThickness+1, d=linkHeight-(materialThickness+linkClearance/2)*2);
            }
            difference(){
                union(){
                    cube([linkHeight,linkHeight,materialThickness+1]);
                    translate([-materialThickness-1,0,0])cube([materialThickness+1,linkHeight,materialThickness+1]);
                    }
                rotate([0,0,degree])cube([linkHeight,linkHeight,materialThickness+1]);
                }
            }
        }

}
difference(){
    union(){
    //INNEN
    difference(){
        translate([0, linkWidth-materialThickness*2, 0]) cube([snapperWidth+linkHeight/2, materialThickness, linkHeight]);
        translate([snapperWidth+linkHeight/2, linkWidth, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=materialThickness+linkClearance/2, r=linkHeight/2+linkClearance/2);
        translate([snapperWidth+linkHeight/2, linkWidth, linkHeight/2]) rotate([0,-degreeFreedom,180])cube([linkHeight/2, materialThickness+linkClearance/2, linkHeight]);
                    translate([snapperWidth+linkHeight/2, linkWidth, linkHeight/2]) rotate([0,160,180])cube([linkHeight/2, materialThickness+linkClearance/2, linkHeight]);

    }
    // AUSSEN
        difference() {
            translate([-linkHeight/2,linkWidth-materialThickness,0]) cube([linkHeight+snapperWidth, materialThickness, linkHeight]); // Länge Breite Höhe
            translate([snapperWidth+linkHeight/2, linkWidth+0.5, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=linkWidth+1, r=linkHeight/2+linkClearance/2);
            translate([snapperWidth+linkHeight/2, linkWidth, linkHeight/2]) rotate([0,-degreeFreedom,180])cube([linkHeight/2, materialThickness+linkClearance/2, linkHeight]);
            translate([snapperWidth+linkHeight/2, linkWidth, linkHeight/2]) rotate([0,160,180])cube([linkHeight/2, materialThickness+linkClearance/2, linkHeight]);
            translate([-linkHeight/2, linkWidth+1, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=100, r=linkHeight/4+linkClearance);
        }

        difference() { // Schnapper vorne
            translate([-linkHeight/2, linkWidth, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=materialThickness, r=linkHeight/2);
            translate([-linkHeight/2, linkWidth+1, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=100, r=linkHeight/4+linkClearance);
            translate([-linkHeight, linkWidth, linkHeight/4,]) rotate([0, 0,-110]) cube ([linkHeight/2,linkHeight/2,linkHeight/2]);
        }
        // Schnapper hinten
        difference(){
        translate([snapperWidth+linkHeight/2, linkWidth-materialThickness, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=materialThickness, r=linkHeight/2);
        translate([snapperWidth+linkHeight/2, linkWidth, linkHeight/2]) rotate([90, 0, 0]) cylinder(h=materialThickness+linkClearance/2, r=linkHeight/2+linkClearance/2);
        }
        //
        translate([snapperWidth+linkHeight/2, linkWidth-materialThickness-linkClearance/2, linkHeight/2]) rotate([90, 0, 0]) rotate([0,180,90])rotate([0,0,-90])difference(){
            rotate([0,0,90])difference(){
                cylinder(h=materialThickness+linkClearance/2, r=linkHeight/4);
                translate([-(materialThickness)-linkClearance+linkHeight/4,-linkHeight/4,materialThickness])rotate([0,45,0])cube([linkHeight/2,linkHeight/2,materialThickness]);
            }
            //enhancement for clipping together
            translate([-(materialThickness)+linkHeight/4,-linkHeight/4,materialThickness*1.8])rotate([0,45,0])cube([linkHeight/2,linkHeight/2,materialThickness]);
        }
    }
    // cutout for Pin
    if(edgePin==1 || edgePin==2){
        translate([snapperWidth+linkHeight/2, linkWidth-materialThickness, linkHeight/2]) rotate([90, 0, 0])intersection(){
            difference(){
                union(){
                    cylinder(h=materialThickness+1, r=linkHeight/2);
                    translate([-linkHeight/2,0,0])cube([linkHeight/2,linkHeight/2,materialThickness+1]);
                }
            cylinder(h=materialThickness+1, d=linkHeight-(materialThickness+linkClearance/2)*2);
            }
            difference(){
                union(){
                    cube([linkHeight,linkHeight,materialThickness+1]);
                    translate([-materialThickness-1,0,0])cube([materialThickness+1,linkHeight,materialThickness+1]);
                }
            rotate([0,0,degree])cube([linkHeight,linkHeight,materialThickness+1]);
            }
        }
    }
}

//Drehschutz Ecken am Schnapper
if(edgePin==0 || edgePin==2){
translate([snapperWidth+linkHeight/2, linkWidth-materialThickness*2, 0]) cube([linkHeight/2, materialThickness-linkClearance/2, linkHeight/2]);
translate([snapperWidth+linkHeight/2, materialThickness+linkClearance/2, 0]) cube([linkHeight/2, materialThickness-linkClearance/2, linkHeight/2]);
}
//Pin
if (edgePin==1 || edgePin==2){
translate([-materialThickness+linkClearance/2, linkWidth-materialThickness*2, linkHeight/2+linkClearance/2+pinOffset]) cube([materialThickness-linkClearance/2, materialThickness, materialThickness-linkClearance]);
translate([-materialThickness+linkClearance/2, materialThickness, linkHeight/2+linkClearance/2+pinOffset]) cube([materialThickness-linkClearance/2, materialThickness, materialThickness-linkClearance]);
}
}//union end

//Schließer oben DREHPUNKT
translate([materialThickness, 0, linkHeight-materialThickness]) cube([snapperWidth-materialThickness*2, linkWidth, materialThickness]);
//Scharnier
if(snapHinge1==1){ //
translate([materialThickness, linkWidth-materialThickness*2, linkHeight-materialThickness*2]) cube([snapperWidth-materialThickness*2,materialThickness*2, materialThickness]);
translate([0, linkWidth-materialThickness, linkHeight-materialThickness-materialThickness/4]) cube([snapperWidth,materialThickness, materialThickness/2]);
translate([0, linkWidth-materialThickness, linkHeight-materialThickness]) rotate([0, 90, 0]) cylinder(h=materialThickness+0.5, r=materialThickness/2+0.2);
translate([snapperWidth-materialThickness, linkWidth-materialThickness, linkHeight-materialThickness]) rotate([0, 90, 0]) cylinder(h=materialThickness+0.5, r=materialThickness/2+0.2);
}
//schnapper 1
if(snapHinge1==3){ //
translate([materialThickness/2, 0, linkHeight-materialThickness*3]) cube([snapperWidth-materialThickness, materialThickness, materialThickness*2]);
translate([snapperWidth/3, 0, linkHeight-materialThickness*3.5]) cube([snapperWidth/3, materialThickness, materialThickness/2]);
}
//schnapper 2
if(snapHinge1==1||snapHinge1==2){ //
translate([materialThickness/2, 0, linkHeight-materialThickness*3]) cube([snapperWidth-materialThickness, materialThickness, materialThickness*3]);
translate([materialThickness/2, materialThickness, linkHeight-materialThickness*3]) cube([snapperWidth-materialThickness, materialThickness/2, materialThickness]);
translate([snapperWidth/3, 0, linkHeight-materialThickness*3.5]) cube([snapperWidth/3, materialThickness*1.5, materialThickness/2]);

//translate([snapperWidth/3, 0, linkHeight-materialThickness*3.5]) cube([snapperWidth/3, materialThickness, materialThickness/2]);
}
if(snapHinge1==2){ //
translate([materialThickness/2, linkWidth-materialThickness, linkHeight-materialThickness*3]) cube([snapperWidth-materialThickness, materialThickness, materialThickness*3]);
translate([materialThickness/2, linkWidth-materialThickness*1.5, linkHeight-materialThickness*3]) cube([snapperWidth-materialThickness, materialThickness/2, materialThickness]);
translate([snapperWidth/3, linkWidth-materialThickness*1.5, linkHeight-materialThickness*3.5]) cube([snapperWidth/3, materialThickness*1.5, materialThickness/2]);

//translate([snapperWidth/3, linkWidth, linkHeight-materialThickness*3.5]) cube([snapperWidth/3, materialThickness, materialThickness/2]);
}
}
}
if (selectPart==0||selectPart==2){
//Schliesser oben
rotate([0,180,0])translate ([-linkWidth+15,-10,-materialThickness])union(){
    difference() { //untere Platte vom Schliesser Schleppkette
        union(){
            translate([0,0, 0]) rotate([0, 0, -90]) cube([snapperWidth, linkWidth, materialThickness]);
            if(snapHinge1==1){ // hinge
            translate([materialThickness, 0, 0]) rotate([90, 0, 0]) cylinder(h=snapperWidth, r=materialThickness);
            }
            if(snapHinge1==1||snapHinge1==2){
            translate([linkWidth,-materialThickness/2-snapperClearance/2, -materialThickness*2]) rotate([90, 0, -90]) cube([snapperWidth-materialThickness-snapperClearance,materialThickness*3, materialThickness]);
            translate([linkWidth-materialThickness,-materialThickness/2-snapperClearance/2, -materialThickness*1.5-snapperClearance]) rotate([0, 90, -90]) cylinder(h=snapperWidth-materialThickness-snapperClearance, r=materialThickness/2-snapperClearance);

        }
            if(snapHinge1==2){
            translate([materialThickness,-materialThickness/2-snapperClearance/2, -materialThickness*2]) rotate([90, 0, -90]) cube([snapperWidth-materialThickness-snapperClearance,materialThickness*3, materialThickness]);
            translate([materialThickness,-materialThickness/2-snapperClearance/2, -materialThickness*1.5-snapperClearance]) rotate([0, 90, -90]) cylinder(h=snapperWidth-materialThickness-snapperClearance, r=materialThickness/2-snapperClearance);

        }
        }
        if(snapHinge1==1){//hinge
        translate([materialThickness*2+snapperClearance*3, -snapperWidth-0.1, -materialThickness]) rotate([0, 0, 90]) cube([materialThickness+0.1, materialThickness*2.5, materialThickness*3]); // seite wo stab
        translate([materialThickness*2+snapperClearance*3, -materialThickness-0.1, -materialThickness]) rotate([0, 0, 90]) cube([materialThickness+0.1, materialThickness*2.5, materialThickness*3]); // seite wo stab
        }
    if(snapHinge1==1||snapHinge1==2){
    translate([linkWidth-materialThickness*2-snapperClearance/2, -snapperWidth, 0]) rotate([0, 0, 0]) cube([materialThickness+snapperClearance, materialThickness+snapperClearance/2, materialThickness]); //
    translate([linkWidth-materialThickness*2-snapperClearance/2, -materialThickness-snapperClearance/2, 0]) rotate([0, 0, 0]) cube([materialThickness+snapperClearance, materialThickness+snapperClearance/2, materialThickness]); //
    translate([linkWidth-materialThickness, -snapperWidth, 0]) rotate([0, 0, 0]) cube([materialThickness+snapperClearance/2, materialThickness/2+snapperClearance/2, materialThickness]); //
    translate([linkWidth-materialThickness, -materialThickness/2-snapperClearance/2, 0]) rotate([0, 0, 0]) cube([materialThickness+snapperClearance/2, materialThickness/2+snapperClearance/2, materialThickness]); //
    }
    if(snapHinge1==2){
    translate([materialThickness-snapperClearance/2, -snapperWidth, 0]) rotate([0, 0, 0]) cube([materialThickness+snapperClearance, materialThickness+snapperClearance/2, materialThickness]); //
    translate([materialThickness-snapperClearance/2, -materialThickness-snapperClearance/2, 0]) rotate([0, 0, 0]) cube([materialThickness+snapperClearance, materialThickness+snapperClearance/2, materialThickness]); //
    translate([materialThickness-materialThickness, -snapperWidth, 0]) rotate([0, 0, 0]) cube([materialThickness+snapperClearance/2, materialThickness/2+snapperClearance/2, materialThickness]); //
    translate([materialThickness-materialThickness, -materialThickness/2-snapperClearance/2, 0]) rotate([0, 0, 0]) cube([materialThickness+snapperClearance/2, materialThickness/2+snapperClearance/2, materialThickness]); //
    }
//Seperator
    if(separatorCount>=1){
        for(i=[materialThickness+(linkWidth-4*materialThickness)/(separatorCount+1)+materialThickness/(separatorCount+1):(linkWidth-4*materialThickness)/(separatorCount+1)+materialThickness/(separatorCount+1):linkWidth-3*materialThickness]){
            //echo(i);
                translate([i-snapperClearance/2, -materialThickness-snapperClearance/2, 0]) cube([materialThickness+snapperClearance, materialThickness+snapperClearance/2, materialThickness]);
                translate([i-snapperClearance/2, -snapperWidth, 0]) cube([materialThickness+snapperClearance, materialThickness+snapperClearance/2, materialThickness]);
        }
    }

}
//Stab des Schliessers
if(snapHinge1==1){ //hinge
difference() {
translate([materialThickness,0,0]) rotate([90, 0, 0]) cylinder(h=snapperWidth, r=materialThickness/2-0.1);
translate([materialThickness-materialThickness/4,-snapperWidth, -materialThickness/2]) rotate([0, 0, 90]) cube([snapperWidth, materialThickness/4, materialThickness]); // seite wo stab / Abtrennung zum einschieben
translate([materialThickness+materialThickness/2,-snapperWidth, -materialThickness/2]) rotate([0, 0, 90]) cube([snapperWidth, materialThickness/4, materialThickness]); // seite wo stab / Abtrennung zum einschieben
}
}

// Haltesteg des Stabes
//#translate([linkWidth/1.5-linkWidth+linkWidth/18, -snapperWidth+linkWidth/10-linkWidth/10, 0]) rotate([0, 0, 90]) cube([linkWidth/4, linkWidth/80, linkWidth/20]); // seite wo stab

//translate([linkWidth/1.5-linkWidth+linkWidth/18, -snapperWidth+linkWidth/10-linkWidth/10, 0]) rotate([0, 0, 90]) cube([linkWidth/4, linkWidth/80, linkWidth/20]); // seite wo stab
//translate([linkWidth/1.5-linkWidth+linkWidth/18, -snapperWidth+linkWidth/10-linkWidth/10, 0]) rotate([0, 0, 90]) cube([linkWidth/4, linkWidth/80, linkWidth/20]); // seite wo stab

}
}
}

DragChain(SelectPart,LinkWidth,LinkHeight,MaterialThickness,LinkClearance,SnapperWidth,SnapHinge1,SnapperClearance,SeparatorCount,EdgePin,AdditionalBottomStrength,DegreeFreedom,PinOffset);

```


[User]
Hey, I'm looking to create a replacement handle for 5-gallon buckets, and I'd like to design it in OpenSCAD. I've got a specific set of requirements and some ideas for how it should work.

Basically, the handle needs to be a comfortable grip, with finger indents, and have a slot that snaps onto the steel wire handle of a standard 5-gallon bucket. I want to be able to adjust the dimensions so I can customize it for different hand sizes and wire thicknesses.

Here's what I have in mind:

*   **Handle Grip:** I'd like a cylindrical grip with a diameter that's adjustable, say `handleOD`, defaulting to 30mm. I also want to have a flat part that rests against the bucket, so it doesn't roll around in my hand. The depth of this cut (`zOffset`) should be adjustable, maybe defaulting to 3mm.
*   **Finger Indents:** I want four finger indents spaced out evenly along the handle. These should be created using a sort of "torus" or circular cut-out that is scaled and positioned. There should be parameters to adjust the overall knuckle width (`knuckleWidth`, default 85mm), the diameter of the middle finger knuckle (`fingerD2`, default 15.5mm) so the finger indent shape/size can be adjusted.
*   **Handle Endcaps:** The ends of the handle should have flat ends with a radius fillet along the cylinder. A parameter should adjust the thickness of the ends (`baseThickness`, default 5mm). The radius of the fillet should be equal to the thickness (`baseThickness`).
*   **Wire Slot:** The slot that snaps onto the bucket's steel wire handle needs to have a few key dimensions. The opening of the slot (`slotWidest`) should be slightly wider than the wire, say 5mm by default, and should have a radius edge (`slotWideR`, default 2mm). The narrowest part of the slot (`slotNarrowest`) should be adjustable and should default to about 2.9mm, so that it grips the wire tightly, since the wires can vary in diameter from 3.5mm to 3.75mm.
*   **General Parameters:** I also want to include a `detail` parameter that controls the number of points used to generate curves, so that I can control print quality versus rendering time. Maybe default to 150, and set the `$fn` variable based on this, as I did in the included file.
*   **Libraries**: Please only use standard OpenSCAD functions, no libraries.

I've attached a .scad file that I've been working with, but it's a bit of a mess, and I think it can be made more modular and easier to customize. I'm aiming for a single file output, if that's possible. Can you help me build a cleaner parametric version of this?

[Assistant]
BucketHandle.scad:
```
// how far the flat portion of the handle (printbed side) should cut into the cylindrical handle profile
zOffset = 3;
// opening at its widest, not counting the rounded edge
slotWidest = 5;
// rounded edge radius at slot opening
slotWideR = 2; // fillet at opening of slot
// narrowst part of slot, should be slightly smaller than the bucket handle steel wire diameter
slotNarrowest = 2.9;

// Rough distance across all four knuckles, allowing some space between fingers
knuckleWidth = 85;
// The thickness of the ring finger middle knuckle
fingerD2 = 15.5;

// Diameter of grip
handleOD = 30;
// Thickness of each end of the handle, a fillet of same radius will be applied on the ends
baseThickness = 5;

// How many points to generate along curves, higher number makes smoother models / larger files / slower render
detail = 150;

// [Hidden]
fingerD = knuckleWidth/4;

$fn = detail;
err = 0.002;

torusD1 = handleOD + fingerD2;
torusD2 = handleOD + fingerD2 * (1 - 0.75);
L = knuckleWidth+2*baseThickness;

intersection() {
  rotate([90,0]) handle();
  slot(slotWidest,slotWideR,slotNarrowest,0,zOffset);
}

module handle() {
  difference() {
    rotate_extrude() polygon(
      concat(
        [[0,0]],
        arcPath(r=baseThickness, angle=90, offsetAngle=270, c=[handleOD/2-baseThickness,baseThickness]),
        arcPath(r=baseThickness, angle=90, offsetAngle=  0, c=[handleOD/2-baseThickness,L-baseThickness]),
        [[0,L]]
      )
    );

    // finger indents
    translate([0,0,baseThickness]) scale([1,torusD2/torusD1])
      rotate_extrude2(angle=180, convexity=4, size=L+1) translate([torusD1 / 2, err*2]) {
        for (i=[0:3])
          translate([0,i * fingerD+fingerD/2])
            scale([fingerD2/fingerD,1]) circle(d=fingerD,$fn=2*$fn/5);
        square([fingerD,knuckleWidth]);
      }

  }
}

module slot(widest, rWide, narrowest, rNarrow, zOffset) {
  W = handleOD;
  H = handleOD*2/3-zOffset;
  diff = (widest-narrowest)/2;

  // change from upright to print plate orientation
  rotate([0,180,90]) translate([-err,0,0]) {
    difference() {
      for (j=[0,1]) translate([0,0,handleOD/2-zOffset-H]) mirror([0,j])
        union() {
          translate([-err,1-(diff+narrowest/2+rWide+W),-H]) cube([L+2*err,diff+narrowest/2+rWide+W,H]);

          // rounded straight edge along length of opening
          translate([-err,0,0]) rotate([90,0,90]) linear_extrude(L+2*err, convexity=2) {
            translate([-handleOD/2-rWide-diff-narrowest/2,0])
              double_fillet(h=H, R=rWide+handleOD/2,r1=rWide,r2=rNarrow,xoffset=diff);
          }
        }
      translate([-1,0,handleOD*(1/2-2/3)+rNarrow]) rotate([0,90,0]) cylinder(d=widest,h=L+2);
    }
  }
}

// based on get_fragments_from_r documented on wiki
// https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language
function fragments(r=1) = ($fn > 0) ?
  ($fn >= 3 ? $fn : 3) :
  ceil(max(min(360.0 / $fa, r*2*PI / $fs), 5));

// Draw a circular arc with center c, radius r, etc.
// "center" parameter centers the sweep of the arc about the offsetAngle (half to each side of it)
function arcPath(r=1, angle=360, offsetAngle=0, c=[0,0], center=false) =
  let (
    fragments = ceil((abs(angle) / 360) * fragments(r,$fn)),
    step = angle / fragments,
    a = offsetAngle-(center ? angle/2 : 0)
  )
  [ for (i = [0:fragments] ) let(a2=i*step+a) c+r*[cos(a2), sin(a2)] ];

// older versions of OpenSCAD do not support "angle" parameter for rotate_extrude
// this module provides that capability even when using older versions (such as thingiverse customizer)
module rotate_extrude2(angle=360, convexity=2, size=200) {

  module angle_cut(angle=90,size=1000) {
    x = size*cos(angle/2);
    y = size*sin(angle/2);
    translate([0,0,-size])
      linear_extrude(2*size) polygon([[0,0],[x,y],[x,size],[-size,size],[-size,-size],[x,-size],[x,-y]]);
  }

  // support for angle parameter in rotate_extrude was added after release 2015.03
  // Thingiverse customizer is still on 2015.03
  angleSupport = false;//(version_num() > 20150399) ? true : false; // Next openscad releases after 2015.03.xx will have support angle parameter
  // Using angle parameter when possible provides huge speed boost, avoids a difference operation

  if (angleSupport) {
    rotate_extrude(angle=angle,convexity=convexity)
      children();
  } else {
    rotate([0,0,angle/2]) difference() {
      rotate_extrude(convexity=convexity) children();
      angle_cut(angle, size);
    }
  }
}

// make a smooth radiused transition between a "lower" and "upper" section
// with a fillet at beginning and end
module double_fillet(h, R, r1, r2, xoffset=0) {
  intersection() {
    square([R+r2+max(xoffset,0),h]);

    xL = r1 + r2 + xoffset;
    yL = h - r1 - r2;
    L = max(sqrt(xL * xL + yL * yL), r1 + r2);
    ratio = (r1 + r2) / L;
    //echo("ratio", ratio);
    //echo("xl",xL," yL",yL, "r1+r2",r1+r2," L",L);
    if (ratio > 1 || yL < 0) {
      c = r1+r2;
      a = r2 > h-r1 ? r2-h+r1 : h - r1 - r2;
      a1 = a * (r2 > h-r1 ? r1 : r2) / (r1 + r2);
      b = sqrt(c*c - a*a);
      h1 = (r2 > h-r1 ? h - r1 + a1 : r2 + a1);
      //echo(a,a1,h,h1,b,c);

        union() {
          if (R > r1) square([R-r1,h]);
          translate([R-r1,h-r1]) circle(r=r1);
          difference() {
            square([R-r1+b, h1]);
            translate([R-r1+b, r2]) circle(r=r2);
          }
        }
    } else {
      a1 = acos( ratio );
      angle1 = yL < 0 ? a1 : a1;
      a2 = atan( abs(yL) / xL );
      //echo("xl",xL," yL",yL, "r1+r2",r1+r2," L",L, " angle1",angle1, " angle2:",angle2);
      angle2 = (a2 < 0) ? 180 + a2 : a2;
      x1 = r1 * cos(angle1 - angle2) - r1;
      y1 = r1 * sin(angle1 - angle2) + (h - r1);
      x2_ = L - r2 * cos(angle1);
      y2_ = - r2 * sin(angle1);
      x2 = x2_ * cos(-angle2) - y2_ * sin(-angle2) - r1;
      y2 = x2_ * sin(-angle2) + y2_ * cos(-angle2) + (h - r1);
      //echo("x1,y1=", x1, y1, "x2,y2=", x2,y2, "x2_,y2_", x2_, y2_);

      union() {
        translate([R - r1,h - r1]) circle(r = r1);
        translate([R,0])
        difference() {
          if (r1 > R) {
            polygon([[-R, 0], [xoffset+r2, 0], [xoffset+r2, r2], [x2, y2], [x1, y1], [-r1, h - r1]]);
          } else {
            polygon([[-R, 0], [xoffset+r2, 0], [xoffset+r2, r2], [x2, y2], [x1, y1], [-r1, h - r1], [-r1, h], [-R, h]]);
          }
          translate([r2 + xoffset, r2]) circle(r=r2);
          //translate([-r1,h-r1]) %rotate(-angle2) translate([L,0]) circle(r=r2);
        }
      }
    }
  }
}

```


[User]
I need a parametric OpenSCAD design for a wall mount for network gear, like switches or small routers. I want to be able to specify the width, height, and depth of the device I'm mounting, and the design should automatically adjust.

The mount needs to have a base with two arms that go up the sides of the device and two more that come up from the bottom to hold it in place, with some wiggle room. The main body should have two holes for screws to attach to the wall. I'd prefer that those holes be reinforced with a larger diameter counter bore or countersink to distribute the load around the screws. It would be great if the design used OpenSCAD's built-in functions and maybe some libraries if necessary, but let me know what they are so I can add them.

[Assistant]
parametric_network_gear_wallmount.scad:
```
//itemwidth
itemwidth = 50;
//itemheight
itemheight = 50;
//itemdepth
itemdepth = 15;

difference() {
    union() {
    translate([0,0,1.5])
        cube([(itemwidth+6),15,3], center=true);
    translate([0,0,1.5])
        cube([15,(itemheight+3),3], center=true);
}
translate([0,(itemheight/3.5),1.5])
cylinder(h=3.1,r1=2.5,r2=4.5,center=true);
translate([0,-(itemheight/3.5),1.5])
cylinder(h=3.1,r1=2.5,r2=4.5,center=true);
}

translate([((itemwidth/2)+3),-7.5,0])
    cube([3,15,(itemdepth+3)]);

translate([-((itemwidth/2)+6),-7.5,0])
    cube([3,15,(itemdepth+3)]);

translate([-7.5,-((itemheight/2)+4.5),0])
    cube([15,3,(itemdepth+3)]);

translate([-7.5,-((itemheight/2)+4.5),(itemdepth+3)])
    cube([15,6,3]);

translate([-(itemwidth/2),-7.5,(itemdepth+3)])
rotate(90)
    cube([15,6,3]);

translate([(itemwidth/2)+6,-7.5,(itemdepth+3)])
rotate(90)
    cube([15,6,3]);

```


[User]
Hey, I'm looking to design a customizable wire spool holder, and I'd like to use OpenSCAD. I've got a pretty good idea of what I need, but I'm not great at writing the code myself, so I was hoping you could help generate the necessary files.

Essentially, I want a box that can hold multiple spools of wire, arranged in rows. Each spool should have a bottom and a top piece, and they should sit on a rod that runs through the box. The box itself will need dividers between the spools, and I want to be able to add a door to the front of the box that has holes for dispensing the wires. It's important that the design be parametric so I can easily change the number of spools, the size of the spools, and the wire diameters.

Here are the basic parameters that I'd like to be able to control:

*   **Number of Spools:**
    *   `spoolsPerRow`: The number of spools in each row.
    *   `spoolRows`: The number of rows of spools.

*   **Spool Size:**
    *   `outerDiameter`: The overall diameter of the spool.
    *   `spoolWidth`: The width of the spool.
    *   `innerDiameter`: The inner diameter of the main spool body.
    *   `holeDiameter`: The diameter of the hole in the center of the spool.
    *  `spoolThickness`: The thickness of the side plate of the spool.

*   **Wire Size:**
    *   `wireHoleDiameter`: The diameter of the holes for the wires, both in the spools and in the door.

*   **Printing Tolerances:**
    *   `spoolHoleTolerance`: Extra room for the spool hole on the rod.
    *   `spoolFitTolerance`: Extra room for fitting the two halves of the spool together.
    *   `spacerTolerance`: Extra room between the divider walls and the spools.
    *   `doorTolerance`: Extra room between the box and the door.
    *  `doorHingeTolerance`: Extra room for fitting the door hinge and closer.
    *  `doorCloserTolerance`: Tolerance for the pegs for closing the door.
    *  `doorCloserDiameter`: The diameter of the peg that keeps the doors closed.

*   **Fine-Tuning:**
    *   `boxThickness`: The thickness of the box walls.
    *   `spacerThickness`: The thickness of the divider walls between spools.
    *   `boxSpacer`: Space between spools and box sides.
    *   `doorThickness`: Thickness of the door.

I'm imagining this will involve a few modules: one for the main box, one for the individual spools (including both top and bottom pieces), one for the rod, and one for the door. The door should have a simple handle and the pegs to keep it shut and the hinge. It would also be great if there's an option to not print the door if it's not needed.

I would like to be able to pick to render any individual part or the whole assembly.

I think that covers everything. Could you generate the OpenSCAD code for this? I'm not using any external libraries so you don't have to worry about including any libraries, but you can use the `$fn` variable for smoothness of cylinders, etc.

[Assistant]
WireSpoolHolder.scad:
```
//Created by Bram Vaessen 2017

//Which one would you like to see?
part = "box"; //[box:The Box, spoolBottom:Spool Bottom, spoolTop:Spool Top, rod:Rod, door:Door]

/* [Number of Spools] */

//Number of spools per row
spoolsPerRow=5;
//Number of rows with spools
spoolRows = 2;

/* [Spool Size] */
//The diameter of the spools
outerDiameter=50;
//The width of the spools
spoolWidth=30;

/* [Wire Size] */
//the diameter of the holes for the wires
wireHoleDiameter=2;

/* [Printing Tolerances] */
//extra room foor the spool hole size (applied to the rod)
spoolHoleTolerance=0.5;
//extra room for the fitting between top an bottom of spool (applied to spool bottom)
spoolFitTolerance=0.1;
//extra room between the divider walls and the spools (applied to box)
spacerTolerance=1;
//the extra room between the box and the door (applied to door)
doorTolerance=0.8;
//extra room for fitting the door hinge and door closer(applied to box)
doorHingeTolerance=0.2;
//toerlance for the pegs for closing the door (applied to door)
doorCloserTolerance=0.2;
//the diameter of the little peg for keeping the doors closed (applied to door)
doorCloserDiameter=1.6;

/* [Fine Tune] */
//thickness of the box
boxThickness=3;
//the thickness of the walls dividing the spools
spacerThickness=2;
//the amount of space between spools and the sides of the box
boxSpacer=1;

//Inner Diameter of the spools
innerDiameter=18;
//Diameter of the holes of the spools
holeDiameter=13;
//thickness of the side of the spools
spoolThickness=2;

//the thickness of the door
doorThickness = 2;

print_part();

module print_part()
{
	if (part == "box")
    	Box();
	else if (part == "spoolBottom")
		SpoolBottom();
	else if (part == "spoolTop")
		SpoolTop();
    else if (part == "rod")
		Rod();
    else if (part == "door")
		Door();
}

midDiameter=(holeDiameter+innerDiameter)/2;

circ = outerDiameter*3.1415;
insertCount = floor(circ/3);

spoolSpace = spoolWidth + spacerThickness + spacerTolerance;
totalSpace = spoolSpace * spoolsPerRow - spacerThickness;

spoolCenterThickness = innerDiameter - holeDiameter;

xInside = totalSpace;
yInside = outerDiameter + boxSpacer*2;
zInside = outerDiameter + boxSpacer*2 + 1+ doorThickness;

xOutside = xInside + boxThickness*2;
yOutside = yInside + boxThickness*2;
zOutside = zInside + boxThickness;

zMiddle = boxThickness+boxSpacer+outerDiameter/2;

doorX = xInside-doorTolerance;
doorY = yInside-doorTolerance;

//cube that is centered on x and y, but not z
module _CenterCube(size)
{
    translate([0,0,size[2]/2]) cube(size, center=true);
}

//a complete spool, for display purpopse only
module _Spool()
{
    SpoolBottom();
    translate([0,0,spoolWidth]) rotate([180,0,0]) SpoolTop();
}

//a row of spools, for display purposes only
module _SpoolRow()
{
    translate([0,0,spacerTolerance/2])
        for (z=[0:spoolsPerRow-1]) translate([0,0,z*spoolSpace])
                _Spool();
}

//the frame for the box, without holes
module _BoxFrame()
{
    //main box
    difference()
    {
        _CenterCube([xOutside, yOutside, zOutside], center=true);
        translate([0,0,boxThickness])
            _CenterCube([xInside, yInside, zOutside], center=true);
    }

    //the spacers in between
    if (spacerThickness>0)
    {
        startX = -xInside/2+spacerTolerance + spoolWidth + spacerThickness/2;
        spacerHeight = zInside-boxThickness*3;
        for (x=[0:spoolsPerRow-2])
            translate([startX+ x*spoolSpace, 0, boxThickness+spacerHeight/2])
                cube([spacerThickness, yInside, spacerHeight], center=true);
    }

}

//box with 1 row of compartments
module _BoxPart()
{
    hingeW = doorThickness*2/3;
    hingeR = doorThickness/2;
    hingeY = -yInside/2+hingeR + doorTolerance/2;
    hingeZ = zOutside-1-doorThickness/2;

    difference()
    {
        _BoxFrame();
        //hole for the end of the rod
        translate([-totalSpace/2-boxThickness/2,0,zMiddle]) rotate([0,90,0])
            cylinder(r=holeDiameter/2-1+spoolHoleTolerance/2, h=boxThickness+1,
                     center=true, $fn=128);
        //hole for the start of the rod
        translate([totalSpace/2+boxThickness/2,0,zMiddle]) rotate([0,90,0])
            cylinder(r=holeDiameter/2+spoolHoleTolerance/2, h=boxThickness+1,
                     center=true, $fn=128);
        //holes for the middle of the rod
        translate([0,0,zMiddle]) rotate([0,90,0])
            cylinder(r=holeDiameter/2+spoolHoleTolerance/2,
                h=totalSpace-0.1, center=true, $fn=128);

        for (x=[0,1]) mirror([x,0,0])
        {
            //the hinge insert witht the ramped part
            translate([xInside/2-1,hingeY,hingeZ])
                rotate([0,90,0])
                    cylinder(r=hingeR+doorHingeTolerance/2,
                             h=1+hingeW+doorHingeTolerance, $fn=32);
            translate([xInside/2-1,hingeY,hingeZ])
                linear_extrude(height=hingeR*2+doorHingeTolerance, center=true)
                    polygon(points=[[0,0],[1+hingeW/2,0],[0,yInside]]);

            //the insert for the keeping the door closed
            translate([xInside/2,-hingeY,hingeZ])
                    sphere(r=hingeR+doorCloserTolerance/2, $fn=32);
        }
    }

}

//the handle for on the door
module _DoorHandle(size, length)
{
    translate([0,0,size*1.25]) rotate([0,90,0])
    {
    hull()
    {
        translate([-size, 0, 0]) cube([size/2,size,length], center=true);
        cube([size/2,size/4,length], center=true);
    }
    hull()
    {
        translate([size, 0, 0]) cube([size/2,size*1.5,length], center=true);
        cube([size/2,size/4,length], center=true);
    }
    }

}

//little edge on the rod to faster the rod in the box
module _Edge()
{
    edgeWidth = spoolHoleTolerance*0.6;
    rotate([90,0,0]) linear_extrude(height=0.8, center=true)
        polygon(points=[[-edgeWidth/2,0], [edgeWidth+spoolHoleTolerance/2,0],
                        [edgeWidth+spoolHoleTolerance/2,boxThickness*0.5],
                        [-edgeWidth/2,boxThickness]]);

}

//base plate for a spool
module _SpoolPlate()
{
    difference()
    {
        //flat cylinder with hole
        cylinder(r=outerDiameter/2, h=spoolThickness, $fn=256);
        translate([0,0,-1])
            cylinder(r=holeDiameter/2+spoolHoleTolerance/2, h=spoolWidth+2, $fn=128);
        //little cutouts on th eside
        for (r=[0:360/insertCount:359.9]) rotate([0,0,r])
            translate([outerDiameter/2,0,-1]) cylinder(r=1, h=spoolThickness+2, $fn=16);
    }
}

//for display purpose only
module _Assembly()
{
    Box();
    translate([-totalSpace/2,0,zMiddle]) rotate([0,90,0]) _SpoolRow();
    translate([-totalSpace/2,yOutside - boxThickness,zMiddle]) rotate([0,90,0]) _SpoolRow();
    translate([0, 0, 55]) Door();
    translate([0, yOutside - boxThickness, 55])
        translate([0,(-doorY+1)/2,0]) rotate([50,0,0])  translate([0,(doorY/2-1),0])
            Door();
}

//the completed box
module Box()
{
    //add one or more rows to complete the box
    for (y=[0:spoolRows-1]) translate([0,y*(yOutside-boxThickness),0])
        _BoxPart();
}

//bottom part of the spool
module SpoolBottom()
{
    _SpoolPlate();
    difference()
    {
        //middle part of the spool
        cylinder(r=innerDiameter/2, h=spoolWidth-spoolThickness, $fn=128);
        translate([0,0,-1]) cylinder(r=holeDiameter/2, h=spoolWidth+2, $fn=128);
        //the cutout for fitting the spools pieces together
        translate([0,0,spoolWidth-spoolThickness-2.5]) difference()
        {
            cylinder(r=midDiameter/2+spoolCenterThickness/10+spoolFitTolerance, h=3, $fn=256);
            translate([0,0,-1])
                cylinder(r=midDiameter/2-spoolCenterThickness/10-spoolFitTolerance, h=5, $fn=256);
        }
        //the little holes for keeping the wire in place
        translate([0,0,spoolThickness+wireHoleDiameter/2+0.25]) rotate([90,0,0])
            cylinder(r=wireHoleDiameter/2, h=outerDiameter, center=true, $fn=32);

    }
}

//top part of the spool
module SpoolTop()
{
    _SpoolPlate();
    translate([0,0,spoolThickness])
    //the extra cylinder to fit the spool parts together
    difference()
    {
        cylinder(r=midDiameter/2+spoolCenterThickness/10, h=2, $fn=256);
        translate([0,0,-1])
            cylinder(r=midDiameter/2-spoolCenterThickness/10, h=4, $fn=256);
    }
}

//one door
module Door()
{

    //main door with holes
    difference()
    {
        _CenterCube([doorX, doorY, doorThickness]);
        startX = -xInside/2 + spacerTolerance/2 - spacerThickness/2 + spoolSpace/2;
        for (x=[0:spoolsPerRow-1])
            translate([startX+x*spoolSpace, -doorY/4, -1])
                cylinder(r=wireHoleDiameter, h=doorThickness+2, $fn=32);

    }

    //the pegs for the hinges
    translate([0, -doorY/2+doorThickness/2,doorThickness/2])
        rotate([0,90,0])
            cylinder(r=doorThickness/2-0.01, h=doorX+doorThickness*4/3 + doorTolerance,
                      center=true, $fn=64);

    //the pegs for keeping the door closed
    for (x=[0,1]) mirror([x,0,0])
        translate([doorX/2-doorCloserTolerance,
                    doorY/2-doorThickness/2,doorThickness/2])
            sphere(r=doorCloserDiameter/2, $fn=64);

    //add the door handle
    handleSize=max(3,doorY/20);
    translate([0,doorY*.35,doorThickness]) _DoorHandle(size=handleSize, length=doorX*0.85);

}

//a rod for rotating the spools on
module Rod()
{

    edgeWidth=1;
    //main cylinder + extra part for the end
    cylinder(r=holeDiameter/2-spoolHoleTolerance/2, h=xInside+boxThickness-0.5, $fn=128);
    translate([0,0,xInside+boxThickness])
        cylinder(r=holeDiameter/2-1-spoolHoleTolerance/2, h=boxThickness+0.7, $fn=128);
    //the edges to keep it in place
    for (r=[0:360/4:359]) rotate([0,0,r])
    {
        translate([holeDiameter/2-spoolHoleTolerance/2,0,0])
            _Edge();
    }

}

```


[User]
I'd like to design a parametric enclosure for a webcam PCB. I'm thinking of something similar to the enclosure I've already designed, but I need a fresh, more robust design, and I'd like it to be fully parametric so I can easily adjust it for different sized PCBs in the future.

Here are the basic requirements I have in mind:

*   **Enclosure Body:** The main enclosure should have a rectangular base with rounded corners, the size should be easily defined by parameters.
*   **PCB Dimensions:** I need to be able to specify the width, height, and thickness of the PCB that the enclosure will hold, along with a wall thickness for the enclosure itself.
*   **Cam Hole:** There should be a hole for the camera lens, with its position and diameter being parametrically adjustable. I'd also like to specify a 'finish' value that adds a slight roundness to the opening.
*   **Cable Hole:** A hole for the USB cable needs to be included on either the left or right side of the enclosure, with its diameter also configurable.
*   **Mounting Holes:** Two mounting holes are required for securing the PCB, with independent offsets and diameters.
*   **Cap:** A cap that snugly fits over the enclosure with a small bevel to ensure an easy fit.
*   **Rounded Edges:** The main enclosure should have a fillet effect on its outer edges to give it a cleaner look, I need to define the radius for the fillet.

I'm thinking we can use OpenSCAD for this. I'm not too picky on how the code should be structured, but I would like to be able to alter all of the above from parameters in the file. I want to avoid dependencies on external libraries if possible. I'd like it if the mounting holes had a slightly smaller internal diameter than the external diameter, to make a more secure hold. And can the code include a render_mode variable that lets me render the model quickly in low resolution, and then with all the details when I'm ready?

[Assistant]
cam_enclosure.scad:
```
/* Cam Enclosure for Cams saved from old Notebook Displays to use as
normal USB Device

Copyright (c) 2017 Michael Sauer
*/

/* [Basic] */

render_mode = 1; // [0:off, 1:on]

// Thickness of outer walls
wall_thickness = 2; // [1:0.1:4]
// Width dimension of your cam pcb
pcb_width = 70;  // [30:0.1:100]
// Height dimension of you cam pcb
pcb_height = 10; // [10:0.1:50]
// Thickness of your cam pcb including the cam sensor
pcb_thickness = 7; // [3:0.1:15]
// Finish of the enclosure (round edges)
enclosure_finish = 1; // [0:0.1:3]

// Offset of the cam hole from left side (normally half of pcb_width)
cam_hole_offset_left = pcb_width / 2; // [5:0.1:90]

// Offset of the cam hole from top
cam_hole_offset_top = pcb_height / 2 + wall_thickness; // [2:0.1:48]
// Diameter of cam hole
cam_hole_diameter = 4; // [1:0.1:6]
// Finish of cam hole
cam_hole_finish = 0.8; // [0:0.1:2]

// Side of cable hole
cable_hole_side = 0; // [0:left, 1:right]
// Diameter of cable hole
cable_hole_diameter = 3; // [1:0.1:6]

// Diamter of mount hole 1
mount1_diameter = 1.8; // [1:0.1:6]
// Offset 1 for hole base from left of pcb
mount1_offset_left = 60.3; // [5:0.1:90]
// Offset 1 for hole base from top of pcb
mount1_offset_top = 7.5; // [2:0.1:48]

// Diameter of mount hole 2 (mostly same as mount 1)
mount2_diameter = mount1_diameter; // [1:0.1:6]
// Offset 2 for hole base from left of pcb
mount2_offset_left = 10; // [5:0.1:90]
// Offset 2 for hole base from top of pcb
mount2_offset_top = 3.2; // [2:0.1:48]

/* [Hidden] */

// Circle resolution
$fn = render_mode? 10: 50;

/* Begin Model Code */
enclosure(); // Enclosure
translate([(wall_thickness / 2), pcb_height + 10, 0])
    cap(); // Cap

module enclosure() {
    difference() {
        // Main enclosure frame with rounded corners
        minkowski() {
            cube([pcb_width + (2 * wall_thickness),
                  pcb_height + (2 * wall_thickness),
                  pcb_thickness + wall_thickness]);
            cylinder(r=enclosure_finish,h=2);
        }
        // Inner cut
        translate([wall_thickness, wall_thickness, wall_thickness])
            cube([pcb_width,
                  pcb_height,
                  pcb_thickness + wall_thickness]);

        // Bevel for cap
        translate([(wall_thickness / 2),
                   (wall_thickness / 2),
                   pcb_thickness + wall_thickness])
            cube([pcb_width + wall_thickness,
                  pcb_height + wall_thickness,
                  pcb_thickness]);

        // Camera hole
        translate([cam_hole_offset_left + wall_thickness,
                   cam_hole_offset_top,
                   0])
            cylinder(h = pcb_thickness + wall_thickness, d = cam_hole_diameter); // hole

        translate([cam_hole_offset_left + wall_thickness,
                   pcb_thickness,
                   -3.2])
            sphere(d = (cam_hole_diameter * 2) + cam_hole_finish); // finish

        // Cable hole
        if (cable_hole_side == 0) { // Left side hole
            translate([-3,
                   pcb_thickness,
                   (pcb_height / 2)])
                rotate([0,90,0])
                    cylinder(h = wall_thickness + 3, d = cable_hole_diameter);
        } else if (cable_hole_side == 1) {
            translate([pcb_width + (wall_thickness * 2)-3,
                   pcb_thickness,
                   (pcb_height / 2)])
                rotate([0,90,0])
                    cylinder(h = wall_thickness + 3, d = cable_hole_diameter);
        } else {
            // No cable hole if out of constrain
        }
    }

    // Mounting cylinders
    // left
    translate([wall_thickness + mount1_offset_left, mount1_offset_top + wall_thickness, wall_thickness])
        cylinder(h = pcb_thickness - 1, d = mount1_diameter * 2);
    translate([wall_thickness + mount1_offset_left, mount1_offset_top + wall_thickness, wall_thickness + 1])
        cylinder(h = pcb_thickness + 1, d = mount1_diameter);

    // right
    translate([wall_thickness + mount2_offset_left, mount2_offset_top + wall_thickness, wall_thickness])
        cylinder(h = pcb_thickness - 1, d = mount2_diameter * 2);
    translate([wall_thickness + mount2_offset_left, mount2_offset_top + wall_thickness, wall_thickness + 1])
        cylinder(h = pcb_thickness + 1, d = mount2_diameter);

}

module cap() {
    difference() {
        union() {
            // Main cap plate
            cube([pcb_width + wall_thickness,
                  pcb_height + wall_thickness,
                  (wall_thickness / 2)]);

            // Leading plate for cap
            translate([(wall_thickness / 2),
                       (wall_thickness / 2),
                       (wall_thickness / 2)])
                    cube([pcb_width,
                          pcb_height,
                          (wall_thickness / 2)]);
        }
    }
}

```


[User]
Hey! I'm looking to get a custom fidget spinner designed, and I'm hoping you can help me out. I've got a pretty good idea of what I want, based on a design I saw, but I need it to be fully parametric so I can tweak it easily.

Basically, it's a fidget spinner with cat shapes as the arms, and weights at the end of each cat. I want the main parameters to be:

*   **Number of Cats:**  I want to be able to select 2, 3, 4, or 5 cats for the spinner.
*   **Weight Type:**  The weights at the end of each cat arm can be different things. I want to select from:
    *   608 bearing
    *   5/16" hex nut
    *   3/8" hex nut
    *   1/2" hex nut
    *   M8 hex nut
    *   M10 16mm hex nut
    *   M10 17mm hex nut
    *   M12 18mm hex nut
    *    M12 19mm hex nut
    *   M4 square nut
    *   penny (US)
    *   nickel (US)
    *   dime (US)
    *   quarter (US)
    *  5/16" ball bearing
    *  3/8" ball bearing
    *  1/2" ball bearing
    *  5/8" ball bearing
    *   "none" - for just a cat shape, no weight (I need a cat size parameter if this is selected!)
*  **Cat Size:** If the weight type is "none" then I want a size for the cat shape. This should default to 21mm.
*   **Center Bearing:** This should be selectable as either a 608 bearing, or another user defined bearing size. If "other" is selected, I need parameters for:
    *   **Other Bearing Outside Diameter:** (default = 22mm)
    *   **Other Bearing Height (Thickness):** (default= 7mm)
*   **Cat Rotation:** I'd like a toggle between rotating the cats 90 degrees or no rotation from a default, where they would be facing more or less directly out from the center.

I'm thinking there should be some hidden parameters as well, to allow for glue grooves for the center bearing, and for the weights, though those aren't really used right now. These should be:
*   **Bearing Glue Groove:** A thickness parameter for a glue groove. default to 0.
*   **Weights Glue Groove:** A thickness parameter for a glue groove for the weights. default to 0.

I was also hoping that the design would use Minkowski sums and simple shapes, rather than a ton of polygons.

Could you generate a parametric OpenSCAD file (or files if needed) that would make this design? I'm not using any special libraries, so no need for that, thanks!

[Assistant]
cat-spinner-custom-2017-09-29.scad:
```
// cat spinner
// lucina
// 2017-09-29
/* [cat spinner settings] */
// number of cats for the spinner
cats = 3;  //[2, 3, 4, 5]
// type of weight
whichWeight = "3/8 hex nut";   //[608 bearing, 5/16 hex nut, 3/8 hex nut, 1/2 hex nut, M8 hex nut, M10 16 hex nut, M10 17 hex nut, M12 18 hex nut, M12 19 hex nut, M4 square nut, penny, nickel, dime, quarter, 5/16 ball bearing, 3/8 ball bearing, 1/2 ball bearing, 5/8 ball bearing, none]
// type of center bearing
whichCenterBearing = "608 bearing"; //[608 bearing, other]
// other bearing outside diameter
otherBearingOutsideDiameter = 22;  //[8:1:30]
// other bearing height (thickness)
otherBearingHeight = 7;   //[4:1:10]
// size of cat in mm if type of weight is none
catSize = 21; //[15:1:24]
// rotate each cat 90 degrees
rotateCat = "no";   //[yes, no]

/* [Hidden] */
// horizontal center bearing glue groove
bearingGlueGroove = 0;   //[0, .5, 1.0, 1.5]
// horizontal ray weights glue groove
weightsGlueGroove = 0;   //[0, .5, 1.0, 1.5]

$fn = 60;
x = 0; y = 1; z = 2; id = 3;
thick = 3;
bearing608 = [22, 22, 7, 8];
sae = [.577*25.4, .5 * 25.4, 7]; // 5/16 hex nut
sae38 = [.650*25.4,.562*25.4, 7];	// 3/8
sae12 = [.866*25.4, .75*25.4, 7];	// 1/2
metric8 = [15, 13, 7];   // M8 hex nut
metric10_16 = [17.77, 16, 7];   // M10 16 hex nut
metric10_17 = [19.6, 17, 7];   // M10 17 hex nut
metric12_18 = [20.03, 18, 7];   // M12 18 hex nut
metric12_19 = [21.9, 19, 7];   // M12 19 hex nut
metric4sq = [7, 7, 3.2];
penny = [19.05, 19.05,1.55];  //x4
nickel = [21.21, 21.21, 1.95]; //x3
dime = [17.91, 17.91, 1.35]; //x5
quarter = [24.26, 24.26, 1.75];  // x4
ball516 = [5/16*25.4, 5/16*25.4, 5/16*25.4];
ball38 = [3/8*25.4, 3/8*25.4, 3/8*25.4];
ball12 = [1/2*25.4, 1/2*25.4, 1/2*25.4];
ball58 = [5/8*25.4, 5/8*25.4, 5/8*25.4];
none = [catSize, catSize, 7];	// no weight

step = 360 / cats;
stop = (cats-1) * step;
weight = (whichWeight == "608 bearing") ? bearing608 :
   (whichWeight == "5/16 hex nut") ? sae :
   (whichWeight == "3/8 hex nut") ? sae38 :
   (whichWeight == "1/2 hex nut") ? sae12 :
   (whichWeight == "M8 hex nut") ? metric8 :
   (whichWeight == "M10 16 hex nut") ? metric10_16 :
   (whichWeight == "M10 17 hex nut") ? metric10_17 :
   (whichWeight == "M12 18 hex nut") ? metric12_18 :
   (whichWeight == "M12 19 hex nut") ? metric12_19 :
   (whichWeight == "M4 square nut") ? metric4sq :
   (whichWeight == "penny") ? penny :
   (whichWeight == "nickel") ? nickel :
   (whichWeight == "dime") ? dime :
   (whichWeight == "quarter") ? quarter :
   (whichWeight == "5/16 ball bearing") ? ball516 :
   (whichWeight == "3/8 ball bearing") ? ball38 :
   (whichWeight == "1/2 ball bearing") ? ball12 :
   (whichWeight == "5/8 ball bearing") ? ball58 : none;
echo(whichWeight, weight[x], weight[y], weight[z]);
resolution = (whichWeight == "5/16 hex nut") ? 6 :
(whichWeight == "3/8 hex nut") ? 6 :
(whichWeight == "1/2 hex nut") ? 6 :
(whichWeight == "M8 hex nut") ? 6 :
(whichWeight == "M10 16 hex nut") ? 6 :
(whichWeight == "M10 17 hex nut") ? 6 :
(whichWeight == "M12 18 hex nut") ? 6 :
(whichWeight == "M12 19 hex nut") ? 6 :
(whichWeight == "M4 square nut") ? 4 :
(whichWeight == "none") ? 0 : 60;
echo("resolution=", resolution);

other = [otherBearingOutsideDiameter,
 otherBearingOutsideDiameter,
 max(4.01, otherBearingHeight)];
bearing = (whichCenterBearing == "608 bearing") ? bearing608 : other;
echo(whichCenterBearing, bearing[x], bearing[y], bearing[z]);

catAngle = (rotateCat == "no") ? -90 : -180;
filRad = 2;
ff = .56;
ff2 = .35;
space = (resolution == 60) ? thick : .75*thick;
module fcylinder(z, x, rad) {
   translate([0, 0, rad-z/2])
      minkowski() {
         cylinder(h=z-2*rad, d=x-2*rad);
         sphere(rad);
      }
}
module ftriangle(z, x, rad) {
   translate([0, 0, rad-z/2])
      minkowski() {
         cylinder(h=z-2*rad, d=x-2*rad, $fn = 3);
         sphere(rad);
      }
}
module cat(wid) {
   difference() {
      union() {
         fcylinder(bearing[z], wid, filRad);
         translate([-(wid/4), wid/3, 0])
            difference() {
               ftriangle(bearing[z], wid*ff, filRad);
               union() {
                  translate([0, 0, bearing[z]/2])
                     linear_extrude(height=2, convexity=10, center=true)
                        circle(d=wid*ff2, $fn=3);
                  translate([0, 0, -bearing[z]/2])
                     linear_extrude(height=2, convexity=10, center=true)
                        circle(d=wid*ff2, $fn=3);
               }
            }
         translate([wid/4, wid/3, 0])
            rotate([0, 0, -60])
            difference() {
              ftriangle(bearing[z], wid*ff, filRad);
              union() {
                  translate([0, 0, bearing[z]/2])
                     linear_extrude(height=2, convexity=10, center=true)
                        circle(d=wid*ff2, $fn=3);
                  translate([0, 0, -bearing[z]/2])
                     linear_extrude(height=2, convexity=10, center=true)
                        circle(d=wid*ff2, $fn=3);
              }
         }
      }
      union() {
         if ( resolution == 0 ) {
            translate([0, -.12*wid, 0])
               whiskers(wid);
            nose(wid);
            eyes(wid);
            mouth(wid);
         }
         else {
            whiskers(wid);
            cylinder(h=bearing[z]*2, d=weight[x], $fn=resolution, center=true);
            if ( weightsGlueGroove > 0 )
               glueGroove(weightsGlueGroove, weight[x], resolution);
         }
      }
   }

}
module nose(wid) {
   translate([0, -.10*wid, bearing[z]/2])
      difference() {
      cylinder(h=2, d=.16*wid, center=true);
         union() {
            translate([-.04*wid, -.07*wid, 0])
               cylinder(h=4, d=.06*wid, center=true);
            translate([.04*wid, -.07*wid, 0])
               cylinder(h=4, d=.06*wid, center=true);
         }
      }
   translate([0, -.10*wid, -bearing[z]/2])
      difference() {
      cylinder(h=2, d=.16*wid, center=true);
         union() {
            translate([-.04*wid, -.07*wid, 0])
               cylinder(h=4, d=.06*wid, center=true);
            translate([.04*wid, -.07*wid, 0])
               cylinder(h=4, d=.06*wid, center=true);
         }
      }
}
module eyes(wid) {
   union() {
      translate([-.15*wid, .08*wid, bearing[z]/2])
         intersection() {
            translate([.05*wid, 0, 0])
            cylinder(h=2, d=.2*wid, center=true);
            translate([-.05*wid, 0, 0])
            cylinder(h=2, d=.2*wid, center=true);
         }
      translate([.15*wid, .08*wid, bearing[z]/2])
         intersection() {
            translate([.05*wid, 0, 0])
            cylinder(h=2, d=.2*wid, center=true);
            translate([-.05*wid, 0, 0])
            cylinder(h=2, d=.2*wid, center=true);
         }
      translate([-.15*wid, .08*wid, -bearing[z]/2])
         intersection() {
            translate([.05*wid, 0, 0])
            cylinder(h=2, d=.2*wid, center=true);
            translate([-.05*wid, 0, 0])
            cylinder(h=2, d=.2*wid, center=true);
         }
      translate([.15*wid, .08*wid, -bearing[z]/2])
         intersection() {
            translate([.05*wid, 0, 0])
            cylinder(h=2, d=.2*wid, center=true);
            translate([-.05*wid, 0, 0])
            cylinder(h=2, d=.2*wid, center=true);
         }
   }
}
module mouth(wid) {
   translate([-.07*wid, -.20*wid, bearing[z]/2])
   difference() {
      cylinder(h=2, d=.18*wid, center=true);
      union() {
         translate([0, .1*wid, 0])
            cube([.22*wid, .2*wid, 4], center=true);
         scale([1.6, 1, 1])
         cylinder(h=2, d=.18*wid-2*1.5, center=true);
      }
   }
   translate([.07*wid, -.20*wid, bearing[z]/2])
   difference() {
      cylinder(h=2, d=.18*wid, center=true);
      union() {
         translate([0, .1*wid, 0])
            cube([.22*wid, .2*wid, 4], center=true);
         scale([1.6, 1, 1])
         cylinder(h=2, d=.18*wid-2*1.5, center=true);
      }
   }
   translate([-.07*wid, -.20*wid, -bearing[z]/2])
   difference() {
      cylinder(h=2, d=.18*wid, center=true);
      union() {
         translate([0, .1*wid, 0])
            cube([.22*wid, .2*wid, 4], center=true);
         scale([1.6, 1, 1])
         cylinder(h=2, d=.18*wid-2*1.5, center=true);
      }
   }
   translate([.07*wid, -.20*wid, -bearing[z]/2])
   difference() {
      cylinder(h=2, d=.18*wid, center=true);
      union() {
         translate([0, .1*wid, 0])
            cube([.22*wid, .2*wid, 4], center=true);
         scale([1.6, 1, 1])
         cylinder(h=2, d=.18*wid-2*1.5, center=true);
      }
   }
}
module whiskers(wid) {
   difference() {
      union() {
         translate([0, 0, bearing[z]/2])
            union() {
               cube([wid+2*thick, 1.5, 2], center=true);
               rotate([0, 0, 20])
                  cube([wid+2*thick, 1.5, 2], center=true);
               rotate([0, 0, -20])
                  cube([wid+2*thick, 1.5, 2], center=true);
            }
         translate([0, 0, -bearing[z]/2])
            union() {
               cube([wid+2*thick, 1.5, 2], center=true);
               rotate([0, 0, 20])
                  cube([wid+2*thick, 1.5, 2], center=true);
               rotate([0, 0, -20])
                  cube([wid+2*thick, 1.5, 2], center=true);
            }
      }
      cylinder(h=bearing[z], d=.5*wid, center=true);
   }
}
module glueGroove(grooveHt, diam, res) {
   cylinder(h=grooveHt, d=diam+2*grooveHt, $fn=res, center=true);
   echo("resolution", res);
}

difference() {
   union() {
      fcylinder(bearing[z], bearing[x] + 2*thick, filRad);
      // cats
      step = 360 / cats;
      stop = (cats-1) * step;
      for ( angle = [0 : step : stop] ) {
         rotate([0, 0, angle])
            translate([(weight[x]+bearing[x])/2 + space, 0, 0])
               rotate([0, 0, catAngle])
               cat(weight[x]+thick*2);
      }
   }
   union() {
      cylinder(h=2*bearing[z], d=bearing[x], center=true);
      if ( bearingGlueGroove > 0 )
         glueGroove(bearingGlueGroove, bearing[x], 60);
   }
}

```


[User]
Hey, I'm looking to get a customizable paint holder designed in OpenSCAD. I've got a basic idea but need it parameterized so I can tweak it for different size paints.

The basic structure is a grid of cylindrical holes. Each hole should have a slightly larger diameter than the paint bottle to allow for some wiggle room, and the holes should extend most of the way through the holder with a small base at the bottom, maybe just a millimeter or two. The holder itself is essentially a flat slab, but I'd like an option to have it angled - basically a diagonal cut through it.

Here's the parameters I'd like to be able to control:

*   `diameter`: The diameter of the paint bottles (so I can make the holes slightly bigger).
*   `height`: The overall height of the holder.
*  `thickness`: The thickness of the walls of each cylinder, i.e the ring of plastic around each hole.
*   `rows`: The number of rows in the grid.
*   `columns`: The number of columns in the grid.
*   `cut`: A boolean flag (0 or 1) to enable or disable the angled cut.

The `thickness` parameter is especially important as I'd like to experiment with thicker walls, so the calculations for the offsets and overall size of the base should take that into account. I imagine you'll be using modules for the main holder and probably also for the individual holes. Also, if there are any helper modules that can handle the calculations for hole spacing based on the diameters that'd be super useful.

I don't have any specific preferences about libraries, but I'd like the design to be clean and efficient.

[Assistant]
paints_holder.scad:
```
/*
|--------------------------------------------------------------------------
| Hobby Paints Holder Customizer
|--------------------------------------------------------------------------
| This project was mainly for my Vallejo hobby paints,
| but It works with any cylindrical shape, of course
*/

// CUSTOMIZER VARIABLES

// The hole diameter
diameter = 24.5;
// How tall is the holder
height = 10;
// How thick is the holder
thickness = 1; // [1, 2, 3, 4]
rows = 2; // [1, 2, 3, 4, 5]
columns = 4; // [1, 2, 3, 4, 5]
// Cut the holder in angle?
cut = 1; // [0:no, 1:yes]]

// CUSTOMIZER VARIABLES END

// Adds an extra gap to the hole

tolerance = 0.4 * 1;
inner_radius = (diameter + tolerance) / 2;
outer_radius = inner_radius + thickness;
outer_diameter = outer_radius * 2;
holder_offset = outer_diameter - thickness;

module holder() {
	difference() {
		cylinder(h = height, r = outer_radius);
		// hole
		translate([0, 0, 1])
		cylinder(h = height, r = inner_radius);
		// base
		translate([0, 0, -0.5])
		cylinder(h = 2, r = inner_radius - 1);

		if (cut) {
			cube_height = height / 2;
			translate([-outer_radius, -outer_radius , cube_height])
			rotate([atan2(cube_height, outer_diameter), 0, 0])
			cube([outer_diameter, sqrt(pow(cube_height, 2) + pow(outer_diameter, 2)), cube_height]);
		}
	}
}

translate([outer_radius, outer_radius, 0])
for (m = [0:1:rows - 1])
	translate([0, m * holder_offset, 0])
	for (n = [0:1:columns - 1])
		translate([n * holder_offset, 0, 0])
		holder();

```


[User]
I want to design a tea portioner that measures out a specific volume of tea leaves, similar to the one described in the provided text. Here's what I'm looking for:

**Functionality:**

*   The portioner should consist of two main parts: a measuring cup and a funnel that fits onto it.
*   The measuring cup should have a defined internal volume specified by the user. The default should be 55 cubic centimeters (55 ml). The user should be able to adjust this value.
*   The funnel should have a wide opening to receive tea, and a chute to return excess tea back to the tea tin when the funnel is removed from the measuring cup.
*   When the funnel is placed on top of the cup and the cup is overfilled, then the excess tea should be able to flow back into the container the tea is being poured from using the chute, leaving the right amount of tea in the cup.
* The funnel should rest on the top of the cup with an increased wall thickness for stability.

**Specific Design Parameters (User-Adjustable):**

*   **Volume:** The target volume of the measuring cup in cubic centimeters (ml). (e.g., `volume = 55;`). Should have a range from 3 to 250 cm³, with steps of 0.1.
*   **Wall Thickness:** The thickness of the walls of the measuring cup and funnel. Should be adjustable and default to `1.6 mm`. There should be a dropdown menu with the options `1.2, 1.5, 1.6, 1.8 mm`.
*   **Roundness:** The radius of the spherical fillets used to round the corners of the design. Should be adjustable and default to `5 mm`.
* **Chute Angle:** The angle of the chute relative to the horizontal. Default to `55 degrees`.
* **Funnel Angle:** The angle of the funnel’s sides relative to the vertical. Default to `60 degrees`.
* **Minimum Funnel Top Width:** A hard constraint, ensure the top of the funnel is not too small. Use `66.66667 mm`.
* **Units:** I want to use metric units (mm and ml). Please don't include anything about those "customary units".
*   **Smoothness:** The design should have smooth corners. Use `$fa` and `$fs` or `$fn`. The default can be to use `$fa = 5; $fs = 0.5;`.

**Implementation Details:**

*   The measuring cup should be based on a cube with rounded corners. The volume of the cup needs to account for these rounded corners in its calculations. The description mentions a somewhat complex calculation using Wolfram Alpha. Please generate this calculation for the volume to be accurate.
* The design should avoid odd edges. Use a small `odd_offset = 0.1;` to mitigate issues arising from rounding errors during computation.
*   The funnel should have a base that fits precisely on top of the measuring cup.
* The chute should be angled to allow excess tea leaves to fall out of the cup as the funnel is removed. It should also blend into the top of the measuring cup.
* The inner dimensions of the cup should be calculated to get the volume correct.

**Libraries:**

*   I don’t need to specify libraries or anything like that.

Could you create a parametric OpenSCAD file (or files if you think it makes sense) to generate this tea portioner?

[Assistant]
old_portioner.scad:
```
// -*- mode: SCAD ; c-file-style: "ellemtel" ; coding: utf-8 -*-
//
// Tea portioner
//
// Copyright 2017 Roland Sieker <ospalh@gmail.com>
// Licence: CC-BY-SA 4.0
// Using material (or inspirations) by
// Soren_Furbo (https://www.thingiverse.com/thing:43899/)
//

// How many cubic centimetres (ml) for one portion
volume = 55;  // [3:0.1:250]

// Don't
use_american_customary_units = 0;  // [1:Yes please. What's a meter?, 0:Of course not! Use SI units.]

module end_customizer()
{
   // This is a dummy module so stop users messing with the values below.
}

// TODO: labels
// TODO 2: Braile labels
// label_style = 1; [0:No label, 1:Raised Text, 2:Colored Text, 3:Braile];

min_funnel_top_width = 66.66667;

// Define values for $fa and $fs (or $fn) to get smoother corners.
// N.B.: Rendering of the funnel gets quite slow for small $fs and $fa values.
// $fa = 5;
// $fs = 0.5;

// This avoids odd edges caused by rounding errors. Use a smaller value
// here when you switched to smoother above.
odd_offset = 0.1;

// Should be a multiple of your nozzle diameter
wall_thickness = 1.6; // [1.2, 1.5, 1.6, 1.8]

// pi is still wrong. Even if we use the area of a circle below. Use tau.
tau = 2 * PI;

// Curvature of sides (Sphere radius)
roundness = 5;
inner_radius = roundness-wall_thickness;

chute_angle = 55;  // Degrees
funnel_angle = 60;

v_cmm = volume * 1000;

// Crazy math to get the volume with the rounding right Basically we
// have V = l^3 - lost_volume, with lost volume = the fillets in the
// edges and corners, 1/2 (cube - sphere), and a bit, depending on l,
// (square rod - cylinder), with no l^2 term.  Transformed to 0 = l^3 +
// al + b, plugged into Wolfram Alpha, put in here.
a = -8*(4-tau/2)*inner_radius*inner_radius;
b = -v_cmm +
      ((4-tau/3) -12 * (4 - tau/2)) * inner_radius*inner_radius*inner_radius;
// echo(a);
// echo(b);
// The solution of x^3+ax+b=0, accordinrg to Wolfram alpha
cube_root_bit = pow(sqrt(3) * sqrt(4*a*a*a + 27*b*b) - 9*b, 1/3);
inner_box_dimension = cube_root_bit / (pow(2, 1/3) * pow(3, 2/3)) -
   pow(2/3, 1/3) * a / cube_root_bit;
// echo(inner_box_dimension);
// echo(pow(v_cmm,1/3));
// Quick tests showed reasonable values, so i'll keep this version
// until somebody spots an error.

// End crazy math.

some_distance = 1.95 * inner_box_dimension + 10 * wall_thickness;

if (use_american_customary_units)
{
   how_rude();
}
else
{
   measure();
   chute();

   translate([0,some_distance,0])
   {
      rotate(180)
      {
         funnel();
      }
   }
}

if (false)
{
   // Demo. Check that the funnel fits
   translate(
      [wall_thickness, wall_thickness, inner_box_dimension + wall_thickness])
   {
      rotate(180)
      {
         funnel();
      }
   }

}

module how_rude()
{
   linear_extrude(height = 0.5) {
      text(text="Please visit", size=5);
      translate([0,-5,0])
      {
         text(text="metric4us.com", size=5);
      }
   }
}

// Length of the inner cube
icl_xy = inner_box_dimension - 2 * inner_radius;
// Measure shell dimension
icl_z = inner_box_dimension - inner_radius;
msd = inner_box_dimension + 2*wall_thickness;

module measure()
{
   // The precisely shaped box

   icl_z_plus =  icl_z + 2*wall_thickness;
   difference()
   {
      union()
      {
         translate([0,0,icl_z_plus/2+roundness])
         {
            difference()
            {
               union()
               {
                  minkowski()
                  {
                     // This is the outer shell
                     cube([icl_xy, icl_xy, icl_z_plus], center=true);
                     sphere(roundness);
                  }
                  // The thicker walls at the top
                  delta_x = (inner_box_dimension +4*wall_thickness)/
                     (inner_box_dimension + 2*wall_thickness);
                  translate([0,0,icl_z/2-4*wall_thickness])
                  {
                     linear_extrude(height=2*wall_thickness,scale=delta_x)
                     {
                        offset(r=roundness)
                        {
                           square([icl_xy,icl_xy],center=true);
                        }
                     }
                  }
                  translate([0,0,icl_z/2-2*wall_thickness])
                  {
                     linear_extrude(height=3*wall_thickness)
                     {
                        offset(r=roundness)
                        {
                           square([icl_xy+2*wall_thickness,icl_xy+2*wall_thickness],center=true);
                        }
                     }
                  }
               }
               minkowski()
               {
                  // This is the main hollow shell
                  cube([icl_xy, icl_xy, icl_z_plus], center=true);
                  sphere(inner_radius);
               }
               // Cut off the top. A big enough cube. important is the
               // lower face height.
               translate([-msd/2,-msd/2,icl_z_plus/2])
               {
                  cube([msd,msd,2*roundness]);
               }
            }
         }
      } // The measuring cup, with thick walls
      // Cutting out space where to put on the funnel. (No rails, just
      // lay it on top.)
      xyl_extra = 5*wall_thickness;
      xyl = inner_box_dimension+2*wall_thickness-2*roundness;
      translate([xyl_extra/2, xyl_extra/2, wall_thickness+inner_box_dimension])
      {
         linear_extrude(2.5*wall_thickness)
         {
            offset(roundness)
            {
               square([xyl + xyl_extra, xyl + xyl_extra],center=true);
            }
         }
      }
   }

}

module chute()
{
   // The length were done by hand, rather than
   // calculated. looks good, is good enough.
   w = inner_box_dimension + 2*wall_thickness - odd_offset;
   h = inner_box_dimension + wall_thickness;
   l = h / sin(chute_angle);
   p = l * cos(chute_angle);
   o = w/2 + p/2 + wall_thickness;
   difference()
   {
      union()
      {
         rotate([0,0,180])
         {
            translate([0,-o, h/2])
            {
               rotate(a=[chute_angle, 0, 0])
               {
                  cube([w, l, wall_thickness], center=true);
                  translate([w/2-wall_thickness/2,0,2.5*wall_thickness])
                  {
                     cube([wall_thickness, 2*l, 5*wall_thickness], center=true);
                  }
                  translate([-w/2+wall_thickness/2,0,2.5*wall_thickness])
                  {
                     cube([wall_thickness, 2*l, 5*wall_thickness], center=true);
                  }
               }
            }
            // Better connection between chute and measure
            difference()
            {
               translate([0,-w/2,
                          inner_box_dimension - inner_radius + roundness])
               {
                  rotate(a=[-chute_angle, 0, 0])
                  {
                     translate([-w/2, -wall_thickness, -2 * roundness + 1 * wall_thickness])
                     {
                        cube([w, 0.85*h, 2 * roundness]);
                     }
                  }
               }
               translate([0,-o, h/2])
               {
                  rotate(a=[chute_angle, 0, 0])
                  {
                     translate([0, 3*wall_thickness, 5*wall_thickness])
                     {
                        cube([w-odd_offset, l, 10*wall_thickness], center=true);
                     }
                  }
               }
               // I wanted to round the connection off. Too much work
               // to position the cylinder to do it.
               // translate([0,0,0])
               // {
               //    rotate(a=[0,90,0])
               //    {
               //       translate([0,0,-w/2])
               //       {
               //          cylinder(r=inner_radius, h=w);
               //       }
               //    }
               // }
            }
         }
      }
      // Honking big boxes to cut off too large stuff
      translate([0,0,-10*inner_box_dimension])
      {
         cube(20*inner_box_dimension,center=true);
      }
      translate(
         [0,0,10*inner_box_dimension + inner_box_dimension -
          inner_radius +roundness])
      {
         cube(20*inner_box_dimension,center=true);
      }
      cube([inner_box_dimension+wall_thickness/2,inner_box_dimension+wall_thickness/2,3*inner_box_dimension],center=true);
   }
}

module funnel()
{
   // First the base
//   xo = inner_box_dimension+2*wall_thickness-2*roundness-2*slider_tolerance;
   xyo = inner_box_dimension+3*wall_thickness-2*roundness;

//   xm = inner_box_dimension                 -2*roundness-2*slider_tolerance;
   xym = inner_box_dimension+2*wall_thickness-2*roundness;

//   xi = inner_box_dimension-2*wall_thickness-2*roundness-2*slider_tolerance;
   xyi = inner_box_dimension+0*  wall_thickness-2*roundness;
   difference()
    {
       translate([0.5*wall_thickness, 0.5*wall_thickness, 0])
       {
          union()
          {
             linear_extrude(wall_thickness)
             {
                offset(roundness)
                {
                   square([xyo,xyo],center=true);
                }
             }
             // Half of this is hidden in the bit below. This covers some edges
             translate([0,0, wall_thickness])
             {

                linear_extrude(height=1.5*wall_thickness, scale=xym/xyo)
                {
                   offset(roundness)
                   {
                      square([xyo,xyo],center=true);
                   }
                }
             }
          }
       }
       translate([wall_thickness, wall_thickness, -0.5*wall_thickness])
       {
          linear_extrude(4*wall_thickness)
          {
             offset(roundness)
             {
                square([xyi, xyi],center=true);
             }
          }
       }

    }

   difference()
    {
       translate([wall_thickness, wall_thickness, wall_thickness])
       {
          linear_extrude(1.5*wall_thickness)
          {
             offset(roundness)
             {
                square([xym, xym],center=true);
             }
          }
       }
       translate([wall_thickness, wall_thickness, -0.5*wall_thickness])
       {
          linear_extrude(4*wall_thickness)
          {
             offset(roundness)
             {
                square([xyi, xyi],center=true);
             }
          }
       }
    }
   // funnel_angle is typically 60 degrees, tan(60degrees) == sqrt(3)
   funnel_offset = wall_thickness / sin(funnel_angle);
   funnel_z_offset = roundness * sin(funnel_angle);
//   xf = xm + roundness * cos(funnel_angle);
   xyfb = xym + roundness * cos(funnel_angle);
   xyft = max(xyfb, min_funnel_top_width/2);
   funnel_height = (xyft - xyfb/2) * tan(funnel_angle);
   translate([wall_thickness, wall_thickness,
              2*wall_thickness + funnel_z_offset])
   {
       difference()
       {
           hull()
           {
               translate([xyfb/2, xyfb/2, 0])
               {
                   sphere(roundness);
               }
               translate([-xyfb/2, xyfb/2, 0])
               {
                   sphere(roundness);
               }
               translate([xyfb/2, -xyfb/2, 0])
               {
                   sphere(roundness);
               }
               translate([-xyfb/2, -xyfb/2, 0])
               {
                   sphere(roundness);
               }
               translate([0,0,funnel_height])
               {
                   translate([xyft, xyft, 0])
                   {
                       sphere(roundness);
                   }
                   translate([-xyft, xyft, 0])
                   {
                       sphere(roundness);
                   }
                   translate([xyft, -xyft, 0])
                   {
                       sphere(roundness);
                   }
                   translate([-xyft, -xyft, 0])
                   {
                       sphere(roundness);
                   }
               }
           }
           translate([0,0,funnel_offset])
           {
               hull()
               {
                   translate([xyfb/2, xyfb/2, 0])
                   {
                       sphere(roundness);
                   }
                   translate([-xyfb/2, xyfb/2, 0])
                   {
                       sphere(roundness);
                   }
                   translate([xyfb/2, -xyfb/2, 0])
                   {
                       sphere(roundness);
                   }
                   translate([-xyfb/2, -xyfb/2, 0])
                   {
                       sphere(roundness);
                   }
                   translate([0,0,funnel_height])
                   {
                       translate([xyft, xyft, 0])
                       {
                           sphere(roundness);
                       }
                       translate([-xyft, xyft, 0])
                       {
                           sphere(roundness);
                       }
                       translate([xyft, -xyft, 0])
                       {
                           sphere(roundness);
                       }
                       translate([-xyft, -xyft, 0])
                       {
                           sphere(roundness);
                       }
                   }
               }
           }
           translate([0,0,-2*roundness])
           {
               linear_extrude(4*roundness)
               {
                   offset(roundness)
                   {
                       square([xyi, xyi],center=true);
                   }
               }
           }
           translate([0,0,funnel_height-roundness])
           {
               linear_extrude(2*roundness)
               {
                   offset(roundness)
                   {
                       square([3*xyfb, 3*xyfb],center=true);
                   }
               }
           }

       }
   }
}

```


[User]
I'd like to design a customizable name tag using OpenSCAD, and I need some help getting started. I'm envisioning a rectangular base with rounded corners, with raised text on top, and optionally a small rim around the outside to help protect the text.

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

**Base:**

*   **xSize:** The length of the base (e.g., 50mm).
*   **ySize:** The width of the base (e.g., 15mm).
*   **zSize:** The thickness of the base (e.g., 2mm).
*   **holeDia:** The diameter of mounting holes (if any) (e.g. 2mm). If this is 0, no holes should be generated
*   **cornerRad:** The radius of the rounded corners (e.g. 1mm). If this is 0, the corners should be sharp.
*  **holeOffset:** The distance from the edge of the base to the center of the hole (e.g. 1mm).
*   **baseColor:** A color for the base (just for previewing in OpenSCAD). I'd like options like "yellow", "green", "blue", "red", "silver", "black"

**Rim (Optional):**

*   **show_rim:** A yes/no flag to indicate if a rim should be included (default to "yes").
*   **rimThickness:** The thickness of the rim (e.g., 1mm).

**Text:**

*   **textHeight:** The height of the raised text above the base (e.g., 1mm).
*   **textSize:** The size of the text (e.g., 9mm).
*   **myText:** The actual text to print (e.g., "Name").
*   **textColor:** A color for the text (also just for previewing in OpenSCAD), with the same color options as `baseColor`.
*  **myFont:** The font of the text. I'd like to be able to choose from a variety of fonts including many of the ones available via Google Fonts, such as "Liberation Mono", "Abril Fatface", "Aclonica", etc. (I can provide a list if needed).

I'd also like to have the following hidden parameters:

*  **$fn:**  A variable set to 48 to force circles to be rendered fully.
*  **ruler_unit:** A variable set to 10 for cm or 25.4 for inch. (This is just for my own purposes within the customizer.)
* **holeRad:** calculated from `holeDia`
* **fudge:** calculated as `holeRad` + `holeOffset`
* **myTextStr:** converted to a string to avoid issues with numbers as input.

The design should include a `makeTag()` module to generate the whole thing, and also have the modules `holes()`, `base()`, `rim()`, and `textExtrude()` to break down the design process. I'd like to include a `roundedBox()` library that I found online. I'd also like the mounting holes to be generated at two corners of the base (opposite corners along the width, not the length). Finally, could you please make sure the text is centered on the base?

[Assistant]
NameTag.scad:
```
makeTag();

 /* [Base] */
     xSize = 50; // [10:100]
     ySize = 15; // [10:60]
     zSize = 2; // [1:0.2:6]
// 0 = No hole
     holeDia = 2; // [0:0.5:5]
// 0 = right angle
     cornerRad = 1.0; // [0:0.5:10.0]
// Distance from edge to hole
     holeOffset = 1; // [0:0.5:10]
// Color will not print
     baseColor = "yellow"; // [green, yellow, blue, red, silver, black]

/* [Rim]  */
// Add a rim?
    show_rim = "yes"; // [yes,no]
// Rim Thickness
    rimThickness = 1.0; // [0:0.2:5.0]

/* [Text] */
// Height of text over base
    textHeight = 1; // [0.2:0.2:6]
// Size of text
    textSize = 9; // [1:0.5:25]
// Printed text
    myText = "Name";
// Color will not print
    text(t="OpenSCAD", font = "Symbol");
    textColor = "green"; // [green, yellow, blue, red, silver, black]
// Pick your favorite font - check out Google fonts
    myFont="Liberation Mono"; //[Abril Fatface,Aclonica,Acme,Advent Pro,Akronim,Aladin,Alegreya,Alegreya Sans,Alegreya SC,Alex Brush,Alfa Slab One,Alice,Amaranth,Architects Daughter,Archivo Black,Archivo Narrow,Arimo,Arvo,Asap,Astloch,Asul,Averia Libre,Averia Serif Libre,Bangers,Basic,Belgrano,Berkshire Swash,Bigshot One,Bilbo Swash Caps,Black Ops One,Bonbon,Bowlby One SC,Brawler,Bubblegum Sans,Butterfly Kids,Cabin Condensed,Caesar Dressing,Cagliostro,Calligraffitti,Capriola,Carter One,Changa One,Chango,Chelsea Market,Cherry Cream Soda,Chewy,Chicle,Chivo,Clicker Script,Coming Soon,Concert One,Condiment,Cookie,Courgette,Covered By Your Grace,Crimson Text,Dancing Script,DejaVu Sans,DejaVu Serif,Dhurjati,Doppio One,Dosis,Droid Sans,Eagle Lake,Electrolize,Emilys Candy,Encode Sans,Encode Sans Compressed,Euphoria Script,Exo,Exo 2,Faster One,Federo,Finger Paint,Fjord One,Fontdiner Swanky,Freckle Face,Fruktur,Gabriela,Geo,Germania One,Give You Glory,Gloria Hallelujah,Goudy Bookletter 1911,Graduate,Grand Hotel,Great Vibes,Griffy,Hanalei Fill,Happy Monkey,Henny Penny,Hind,IM Fell English SC,Indie Flower,Irish Grover,Italianno,Jacques Francois Shadow,Jolly Lodger,Josefin Slab,Joti One,Judson,Just Another Hand,Kalam,Kameron,Karma,Kavoon,Knewave,Kranky,Kristi,Laila,Lakki Reddy,Lancelot,Lato,Leckerli One,Ledger,Lekton,Lemon One,Liberation Sans,Libre Caslon Text,Life Savers,Lilita One,Lily Script One,Limelight,Lobster,Lobster Two,Londrina Outline,Londrina Shadow,Londrina Solid,Lora,Love Ya Like A Sister,Loved by the King,Lovers Quarrel,Luckiest Guy,Lusitana,Macondo,Macondo Swash Caps,Mako,Marck Script,Margarine,Marko One,Maven Pro,McLaren,MedievalSharp,Merienda One,Merriweather,Mervale Script,Metal Mania,Metrophobic,Milonga,Miltonian Tattoo,Miss Fajardose,Molle,Montez,Montserrat,Mr De Haviland,Mystery Quest,Neucha,New Rocker,Niconne,Nosifer,Nothing You Could Do,Noto Sans Oriya,Noto Serif,Nova Square,Nunito,Old Standard TT,Oleo Script,Oleo Script Swash Caps,Orbitron,Oregano,Orienta,Original Surfer,Oswald,Over the Rainbow,Overlock,Oxygen,Pacifico,Paprika,Parisienne,Patrick Hand SC,Paytone One,Peralta,Permanent Marker,Piedra,Pirata One,Play,Playball,Poetsen One,Poppins,Press Start 2P,Princess Sofia,PT Mono,Qwigley,Racing Sans One,Raleway,Rancho,Ranga,Ribeye,Roboto,Roboto Condensed,Roboto Slab,Rochester,Rock Salt,Rubik One,Sail,Salsa,Sansita One,Sarina,Satisfy,Schoolbell,Seaweed Script,Sevillana,Shadows Into Light,Shadows Into Light Two,Share,Six Caps,Skranji,Source Sans Pro,Spicy Rice,Stardos Stencil,Stoke,Syncopate,Teko,Terminal Dosis,The Girl Next Door,Tillana,Timmana,Titillium Web,Ubuntu,Ultra,Underdog,UnifrakturCook,UnifrakturMaguntia,Vampiro One,Vidaloka,Viga,Voces,Volkhov,Vollkorn,Voltaire,Waiting for the Sunrise,Wallpoet,Wellfleet,Wendy One,Yellowtail,Yesteryear,Zeyada]

/* [Hidden] */
$fn = 48; //force full circle to be rendered using 48 fragments
holeRad = holeDia/2;
fudge = holeRad + holeOffset; //Distance from center of hole to edge
// Ruler is only shown in Customizer.
ruler_unit=10; //[10:cm, 25.4:inch]
// Convert input text to string
myTextStr=str(myText);

module holes() {
    // translate([-xSize/2, ysize/2, 0])
    // create a cylinder with 36 segments that is 2 times the thickness of the base
    translate([-(xSize/2)+ fudge, ySize/2-fudge, 0])
      color(baseColor) cylinder(r = holeRad, h = 2*zSize, , center = true);

    translate([xSize/2-fudge, ySize/2-fudge, 0])
      color(baseColor) cylinder(r = holeRad, h = 2*zSize, $fn = 48, center = true);

}
module base() {
     color(baseColor) translate ([0,0,0]) roundedBox ([xSize, ySize, zSize], radius = cornerRad, sidesonly = 1);
}

module rim() {
    difference(){
    color(baseColor) translate([0,0,-textHeight/2])
        roundedBox([xSize+rimThickness*2, ySize+rimThickness*2, (zSize+textHeight+.001)], radius = cornerRad, sidesonly = 1);

    color(baseColor) translate([0,0,-textHeight/2])
        roundedBox([xSize, ySize, zSize + textHeight*2], radius = cornerRad, sidesonly = 1);
    }
}

module textExtrude() {
  // the following four lines are all actually one command that ends with the ';'
    color(textColor)
    linear_extrude(height = textHeight)
    text(myText, halign = "center", valign = "center", size = textSize,
      font = myFont);
    if (show_rim=="yes"){
    rim();
    }

}

module makeTag(){
difference() {
        union(){
            base();
            translate([0, 0, zSize/2]) textExtrude();
        }
        holes();
    }
}

// Library: boxes.scad
// Version: 1.0
// Author: Marius Kintel
// Copyright: 2010
// License: BSD
// roundedBox([width, height, depth], float radius, bool sidesonly);
// EXAMPLE USAGE:
// roundedBox([20, 30, 40], 5, true);
// size is a vector [w, h, d]

module roundedBox(size, radius, sidesonly)
{
  rot = [ [0,0,0], [90,0,90], [90,90,0] ];
  if (sidesonly) {
    cube(size - [2*radius,0,0], true);
    cube(size - [0,2*radius,0], true);
    for (x = [radius-size[0]/2, -radius+size[0]/2],
           y = [radius-size[1]/2, -radius+size[1]/2]) {
      translate([x,y,0]) cylinder(r=radius, h=size[2], center=true);
    }
  }
  else {
    cube([size[0], size[1]-radius*2, size[2]-radius*2], center=true);
    cube([size[0]-radius*2, size[1], size[2]-radius*2], center=true);
    cube([size[0]-radius*2, size[1]-radius*2, size[2]], center=true);
    for (axis = [0:2]) {
      for (x = [radius-size[axis]/2, -radius+size[axis]/2],
             y = [radius-size[(axis+1)%3]/2, -radius+size[(axis+1)%3]/2]) {
        rotate(rot[axis])
          translate([x,y,0])
          cylinder(h=size[(axis+2)%3]-2*radius, r=radius, center=true);
      }
    }
    for (x = [radius-size[0]/2, -radius+size[0]/2],
           y = [radius-size[1]/2, -radius+size[1]/2],
           z = [radius-size[2]/2, -radius+size[2]/2]) {
      translate([x,y,z]) sphere(radius);
    }
  }
}

```