[User]
I'd like to create a customizable walking mechanism, something like a small walking robot with multiple legs. I've seen some cool designs online and I'm hoping you can help me create an OpenSCAD file for it.

Here’s what I have in mind:

**Overall Structure:**

The walker will have several legs, each driven by a central crank. The legs will be composed of multiple segments connected by joints. I'm thinking of four pairs of legs to start, but I'd like to make that a variable, so I can add more or fewer if I want.

**Leg Components:**

Each leg will have the following parts, each with variable length:

*   **Foot (Crank to toe):** Let's call this segment *AB*.
*   **Foot (Toe to knee):** This will be *BC*.
*   **Foot (Knee to crank):** This segment will be *AC*.
*   **Thigh (Hip to knee):** This will be *CD*.
*   **Pelvis (Hip to navel):** Let’s call this *DG*.
*   **Pelvis (Navel to crank):** This will be *OG*.
*   **Crank (Shaft to crank):** Let’s make this *OA*.

All of those lengths should be adjustable in the customizer. I want a scaling factor too, so I can scale up or down all lengths in proportion to each other.

**Joints and Connections:**

I'm envisioning pins and threads to connect these parts together. All pin diameters should be a variable, as should the thickness of all the links and parts in general. I'll need some variables for tolerances too - a loose fit around the axles, and a press fit for pins, plus clearance between parts in general. Also, a thread clearance for the nuts.

The joints connecting these parts should have some additional material around them (a "knuckle"), and the crankshaft should have its own diameter variable. The crank will connect to the central shaft using threads.

**Additional parts**

I will need an end to the crankshaft with a handle, and some washers for the pins to connect the leg parts. Also, I want an end to the pelvis, with the holes for the leg connectors, a crank stopper to keep the crank arm from rotating all the way around, and an end to the crankshaft that has the correct dimensions.

**Features:**

*   The design should be fully parametric, using variables so that the dimensions can be adjusted easily. It should be customizable from the OpenSCAD customizer.
*   I’d like to have a way to visualize the path that the foot takes with a line when the crank rotates.
*   I need various layout options - one to show all the parts laid out flat, so they can be cut or printed easily. Also, I’d like a view that shows the legs together, how they'll be assembled, and then a view for individual parts.
*   The design should be printable with standard 3D printer.
*   I want to be able to adjust the starting angle of the crank, so I can experiment with different gait patterns.

**Specific Variables:**

Here are some specific parameters I want to be able to control, but please make the units of measurement editable, preferably using a parameter.

*   `legPairs`: Number of leg pairs (e.g., 4).
*   `theta`: Starting angle of the crank (degrees).
*   `length_A_B`, `length_B_C`, `length_A_C`, `length_D_G`, `length_O_G`, `length_O_A`, `length_C_D`: Lengths of the various leg and pelvis segments.
*   `scaleFactor`: Scaling factor for all lengths.
*   `linkWid`: Width of all links.
*   `layerThk`: Thickness of all parts.
*   `pinD`: Diameter of pins and threads.
*   `crankShaftD`: Diameter of the crankshaft.
*   `knuckleThk`: Additional material at joints.
*   `pressFit`, `looseFit`, `surfClr`, `threadClr`: Tolerances for fits and clearances.
*   `part`:  A selection to show different parts in customizer or different layouts. I listed all the layouts/parts I wanted earlier.
*  `highRes`, `lowRes`: resolution of joints and knuckles, respectively.
*  `handleLen`: Length of handle grip portion
*   `handleStep`:  handle axial offset

**Libraries:**

Please use functions from `polyScrewThread.scad` for the threads, nuts, and bolts.

I think that covers it. Let me know if you need any more information!

[Assistant]
YK42Walker.scad:
```
/*
Axle = O
Cam  = A
Toe  = B
Knee = C
Hip = D
Origin=G
*/

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

// - Sets of legs
legPairs = 4; //[1:1:12]
// - starting angle of crank
theta = 00; //[0]
/* [Link Lengths - mm] */

// - Foot (Crank to toe) (def:20)
length_A_B   = 24;  //[0:0.5:30]
// - Foot (Toe to knee) (def:12)
length_B_C   = 10;  //[0:0.5:30]
// - Foot (Knee to crank) (def:20)
length_A_C   = 18;  //[0:0.5:30]
// - Pelvis (Hip to navel) (def:11)
length_D_G   = 12;  //[0:0.5:30]
// - Pelvis (Navel to crank) (def:8)
length_O_G   = 10;  //[0:0.5:30]
// - Crank (Shaft to crank) (def:5)
length_O_A   = 4;   //[0:0.5:10]
// - Thigh (Hip to knee) (def:13)
length_C_D = 13;  //[0:0.5:30]

// - Scale factor for all above lengths (def:3)
scaleFactor = 3; //[1:1:10]

/* [Part Shapes] */
// - Y thickness of all links
linkWid = 4; //[2:10]
// - Z thickness of all parts
layerThk = 4; //[2:10]
// - Diameter of pins and threads
pinD = 6;  //[4:12]
// - Diameter of the crankshaft
crankShaftD = 10; //[4:12]
// - additional material at joints
knuckleThk = 3; //[1:10]

/* [Tolerances (mm)] */
// - material reduction for press fits
pressFit = 0.15; //[-.2:0.05:0.3]
// - material reduction around axles and joints
looseFit = 0.35; //[.05:0.05:0.6]
// - clearance between layers
surfClr = 0.10; //[.00:0.05:0.4]
// - clearance between nut and washer
threadClr = 0.60; //[.05:0.05:1]

/* [Display options] */
part = "layoutTest"; // [layoutTest:Test Layout,layoutStep:Step Profile,layoutAssembly:Assembly Layout,layoutPrintLeg:Leg Layout,legPelvis:Pelvis,crankShort:Crank (Short),crankLong:Crank (Long),legFoot:Foot,legThigh:Thigh,legNut:Nut,legWasher:Washer,layoutPrintEnd:End parts - print layout,endPelvis:End - Pelvis,endHandle:End - Crank Handle,endWasher:End - Washer,endHandleStop:End - Handle stopper,endShaft:End - Crank end]

/* [Hidden] */
highRes = 40; //resoluiton of joints
lowRes = 6; //resolution of knuckles
handleLen = 12;  //length of handle grip portion
handleStep = max(20, crankShaftD*2);  //handle axial offset.
handleUnits = handleLen/layerThk;
$fn = lowRes;
lowResCompFact = 1/cos(180/lowRes);
pinR = pinD/2;
knuckleIR = pinR+looseFit;
knuckleRFlat = knuckleIR +knuckleThk;
knuckleRCorn = knuckleRFlat*lowResCompFact;
crankShaftR = crankShaftD/2;
crankHoleR = crankShaftR+looseFit;
crankHubRFlat = crankHoleR +knuckleThk;
crankHubRCorn = crankHubRFlat*lowResCompFact;

angleConfig=[0, 0, 180, 180, 90, 144, 60, 154, 135, 160, 108, 164, 150];

hubLen = 2*layerThk +1*surfClr;  //length of pin hub
stemLen = 3*layerThk +5*surfClr; //length of pin stem
insLen = hubLen*.7; //insertable length of pin

//nut and bolt props
//bolt params
screwOD = pinD; //thread major diam                         od
screwThreadedLen = layerThk+surfClr; //threaded length      lt
screwThreadAngle = 45; //thread angle (overhang)            lf0
screwThreadPitch = tan(screwThreadAngle)*max(0.25*pinD,1); //thread pitch st
screwRotSym = 0.5; //thread rotational symmetry             rs
screwChamferStyle = 2; //chamfer style (1)/2/3              cs
screwHeadDiamCorn = knuckleRCorn*2; //                      df
screwHeadThk = layerThk; //head th                          hg
screwNTL = layerThk+surfClr; //shank/non-threaded length    ntd
screwShankDiam = 0; //shank diameter (keep as zero for best results)
//nut params
nutDiamCorn = knuckleRCorn*2;//                             df
nutThk = screwHeadThk;  //                                  hg
nutThreadPitch = screwThreadPitch; //thread pitch (distance between peaks)sth
nutThreadAngle = screwThreadAngle; //slope of threads       clf
nutID = pinD+threadClr; //internal diameter                cod
nutRotSymm= 0.5; //?thread rotational symmetry              crs

//Foot
AB   = length_A_B    *scaleFactor;  //crank to toe
BC   = length_B_C    *scaleFactor;  //toe to knee
AC   = length_A_C    *scaleFactor;  //knee to crank

//Hip.
DG   = length_D_G    *scaleFactor;  //hip to origin
OG   = length_O_G    *scaleFactor;  //origin to shaft

//Crank
OA   = length_O_A    *scaleFactor;   //shaft to crank

//Link
CD = length_C_D    *scaleFactor;  //knee to hip

//Calculate geometry for layout and for foot piece
    //(angles, lengths, and positons)
Ax = OA*sin(theta);
Ay = OA*cos(theta)+OG;
AngGDA = atan(Ay/(Ax-DG));
AD = sqrt(pow(Ay,2) + pow(Ax-DG,2));
AngADC = acos((AD*AD + CD*CD - AC*AC)/(2*AD*CD));
AngCAD = acos((AC*AC + AD*AD - CD*CD)/(2*AC*AD));
AngCAB = acos((AC*AC + AB*AB - BC*BC)/(2*AC*AB));
AngODC = AngGDA-AngADC;
LinkAng = AngODC;
FootAng = AngGDA + AngCAD + AngCAB;
AngABC = acos((AB*AB + BC*BC - AC*AC)/(2*AB*BC));
ToeAng = AngABC;

Bx = Ax-AB*cos(FootAng);
By = Ay+AB*sin(FootAng);
PointB=[Bx, -By];

Cx = DG+ CD*cos(180-(-AngGDA+AngADC));
Cy = CD*sin(180-(-AngGDA+AngADC));
PointC=[Cx, -Cy];

print_part();
module print_part() {
	if (part == "layoutTest") {
		FitConfig();
        Path();
	} else if (part == "layoutStep") {
		Path();
	} else if (part == "layoutAssembly") {
		FitConfig();
	} else if (part == "layoutPrintLeg") {
		PrintConfig();
	} else if (part == "legPelvis") {
		Pelvis();
	} else if (part == "crankShort") {
		CrankShort();
	} else if (part == "crankLong") {
		CrankLong();
	} else if (part == "legFoot") {
		Foot();
	} else if (part == "legThigh") {
		Thigh();
	} else if (part == "legNut") {
		 Nut();
	} else if (part == "legWasher") {
		PinWasher();
	} else if (part == "layoutPrintEnd") {
		EndParts();
	} else if (part == "endPelvis") {
		EndPelvis();
	} else if (part == "endWasher") {
		HandleWasher();
	} else if (part == "endHandle") {
		CrankHandle();
	} else if (part == "endHandleStop") {
		CrankEnd();
	} else if (part == "endShaft") {
		ShaftEnd();
	} else {
		FitConfig();
	}
}

//Thigh(); //CD
module Thigh() {
    //thicken 2D object and add thread
    difference() {
        //body
        union() {
          //link
          translate([0, -linkWid/2])
            cube([CD, linkWid, layerThk]);
          //near end (hip)
          cylinder(r=knuckleRCorn, h=layerThk*2+surfClr);
          translate([CD,0])
            hex_screw(  screwOD,
                        screwThreadPitch,
                        screwThreadAngle,
                        screwThreadedLen,
                        screwRotSym,
                        screwChamferStyle,
                        screwHeadDiamCorn,
                        screwHeadThk,
                        screwNTL,
                        screwShankDiam);
        }
        //near hole
        cylinder(r=knuckleIR, h=999, $fn=highRes, center=true);
    }
}

//!CrankLong();
module CrankShort() Crank(OA, 1, angleConfig[legPairs]);
module CrankLong()  Crank(OA, 2, 0);
module Crank(length=40, stemSz=1, rotation=0) {
    stemHAbovePlane=(layerThk+surfClr)*(stemSz+1);
    alignmentFeatureFactor = 0.65;
    AFF=alignmentFeatureFactor;
    slitWidth = 1;

    difference() {
        //main body
        union() {
            //length
            translate([length/2, 0, layerThk/2])
                cube([  length,
                        crankHubRFlat*2,
                        layerThk], center=true);
            //near end
            cylinder(r=crankHubRCorn, h=layerThk);
            //far end bodies inkl. stem
            translate([length,0,0]) {
                //flange
                cylinder(r=crankHubRCorn, h=layerThk);
                //stem
                cylinder(   r=crankShaftR,
                            h=stemHAbovePlane,
                            $fn=highRes);
                //plug
                rotate(rotation)
                translate([0,0,stemHAbovePlane])
                difference() {
                    //square
                    cylinder(   r=crankShaftR-pressFit,
                                h=layerThk,
                                $fn=4);
                    //notch
                    translate([ crankShaftR*AFF-pressFit,
                                -crankShaftR])
                        cube(999);
                }
            }
        }
        //socket
        rotate(90) difference() {
            cylinder(r=crankShaftR, h=999, $fn=4, center=true);
                translate([-length/2, crankShaftR*AFF])
                  cube(length*2);
       }
       //slit
       cube([crankHubRCorn*2, slitWidth, 999], center=true);
   }
}

//!Pelvis();
module Pelvis() {
//Pelvis Pin Sizes
    difference() {
        union() {
            //main  crossbeam
            translate([0,0,layerThk/2])
                cube([  2*DG,
                        knuckleRCorn*2*cos(30),
                        layerThk],
                        center=true);
            //vertical bit OG
            rotate(180) translate([-crankShaftR, 0])
                cube([2*crankShaftR, OG, layerThk]);
            //pins
            translate([DG, 0]) rotate(180) Pin();
            translate([-DG, 0]) Pin();
            //hub
            translate([0, -OG])
                cylinder(r=crankHubRCorn, h=layerThk);
        }
        //sockets
        translate([DG, 0]) PinSocket();
        translate([-DG, 0]) PinSocket();
        //CamHole A
        translate([0,-OG])
            cylinder(   r=crankShaftR+looseFit,
                        h=999,
                        center=true,
                        $fn=highRes);
    }
}

//!Foot();
module Foot() {
    linear_extrude(layerThk) {
        //AB
        difference() {
            union() {
                //CamShaftBoss
                circle(r=crankHubRCorn);
                //Member AB (from crank to toe)
                translate([0,-linkWid/2])
                    square([AB, linkWid]);
            }
            //CamHole Point A
            circle(d=crankShaftD+2*looseFit, $fn=highRes);
        }
        //BC
        translate([AB,0]) rotate(180-AngABC)
        difference() {
            union() {
                //Point C
                translate([BC,0])
                    circle(r=knuckleRCorn);
                translate([0,-linkWid/2])
                    square([BC, linkWid]);
            }
            //hole at point C
            translate([BC,0])
                circle(r=knuckleIR, $fn=highRes);
        }
    }
    //toe
    translate([AB,0])
        cylinder(d=linkWid, 1*layerThk, $fn=highRes);
}

//!PinWasher();
module PinWasher() Washer(knuckleRCorn, knuckleIR, layerThk);
module Washer(R=10, r=5, z=3) {
    difference() {
        //body
        cylinder(r=R, h=z);
        //hole
        cylinder(r=r, h=z*2+1, center=true, $fn=highRes);
    }
}

//!Pin();
module Pin() {
    slit = [999, pinD/6, 999];
    pinTotalLen = hubLen+stemLen+insLen    -surfClr;
    taper = pinD*0.1;

    difference() {
        union() {
            //hub
            cylinder(r=knuckleRCorn, h=hubLen);
            //stem
            cylinder(d=pinD, hubLen+stemLen, $fn=highRes);
            //insert. taper tip. slightly shortened
            cylinder(d=pinD, pinTotalLen-taper, $fn=8);
            translate([0,0,pinTotalLen-taper])
                cylinder(d1=pinD, d2=pinD-2*taper, taper, $fn=8);
        }
        //slit
        translate([-pinD/5, -slit[1]/2, pinTotalLen-insLen*1.5])
            cube(slit);
    }
}

module PinSocket() {
    cylinder(d=pinD, h=insLen, $fn=highRes);
    //tapered entrance
    taper = looseFit*2;
    cylinder(d1=pinD+2*taper, d2=pinD, taper*2, $fn=8);
}

//!Nut();
module Nut() {
    hex_nut(    nutDiamCorn,
                nutThk,
                nutThreadPitch,
                nutThreadAngle,
                nutID,
                nutRotSymm);
}

//EndParts();
module EndParts() {
    //reduced pelvis
    EndPelvis();
    //handle
    translate([-OG/2, crankHubRFlat*2])  CrankHandle();
    //washer
    translate([-crankHubRFlat*2, -crankHubRFlat*2])    HandleWasher();
    //Stopper
    translate([crankHubRCorn*2, -crankHubRFlat*2])  CrankEnd();
    translate([crankHubRCorn*4+1, -crankHubRFlat*2])  ShaftEnd();}
module EndPelvis()
    intersection() {Pelvis(); cube([999, 999, 2*hubLen], center=true);}
module CrankHandle() Crank(handleStep, handleUnits, 0);
module HandleWasher() Washer(crankHubRCorn, crankHoleR+looseFit, handleUnits*layerThk+(handleUnits-2)*surfClr);
module CrankEnd() union() {
        Crank(0,0,0);
        translate([(crankShaftR+crankHubRCorn)/2, 0])
            cylinder(d=crankHubRCorn-crankShaftR,layerThk);    }
module ShaftEnd() intersection() {
    translate([-99,0]) Crank(99, 1, 0);
    cylinder(r=crankHubRCorn, h=99); }

//Fit all the pieces together
module FitConfig() {
    //pelvis
    Pelvis();
    //crank &// cam assemby
    translate([0,-OG,layerThk+surfClr])
        rotate(-theta)
            CrankAssembly();
    //left thigh
    translate([-DG,0, 4*layerThk+5*surfClr])
        rotate(LinkAng) rotate([180,0])
            Thigh();
    //left foot
    translate([-Ax, -Ay, 2*layerThk+3*surfClr])
        rotate(FootAng) mirror() Foot();
    //washers
    translate([-DG, 0, 4*layerThk+5*surfClr])
        PinWasher();
    translate([DG, 0, 4*layerThk+5*surfClr])
        PinWasher();
    //nut
    translate([0, 0, layerThk*4 + surfClr*5])
    translate(PointC)
        Nut();
    translate([0, 0, layerThk*1 + surfClr*2])
    mirror() translate(PointC)
        mirror() Nut();

    if(theta==0) {
        //right linkage bar
        translate([DG,0, 2*layerThk+3*surfClr])
            rotate([0, 0, -LinkAng+180])
                Thigh();
        //right foot
        translate([-Ax, -Ay, 4*layerThk+4*surfClr])
            rotate([0, 180, -FootAng]) mirror() Foot();
    }
}

//!PrintConfig();
module PrintConfig() {
    Pelvis();
    translate([0, 2*knuckleRCorn]) Thigh();
    translate([0, 4*knuckleRCorn]) Thigh();
    translate([-2.2*knuckleRCorn, 2*knuckleRCorn]) Nut();
    translate([-2.2*knuckleRCorn, 4*knuckleRCorn]) Nut();
    translate([-3*crankHubRCorn,5*knuckleRCorn+crankHubRFlat]) Foot();
    translate([-5.2*knuckleRCorn/sin(AngABC),5.2*knuckleRCorn+2*crankHubRCorn]) Foot();
    translate([3*knuckleRCorn, -3*knuckleRCorn]) CrankLong();
    translate([-3*knuckleRCorn, -3*knuckleRCorn]) rotate(180) CrankShort();
    translate([-4.4*knuckleRCorn, 2*knuckleRCorn]) PinWasher();
    translate([-4.4*knuckleRCorn, 4*knuckleRCorn]) PinWasher();
}

//!CrankAssembly();
module CrankAssembly() {
    rotate(-90)
    translate([OA, 0, layerThk+surfClr])
    rotate([180, 0, 0]) {
        translate([-OA,0,-layerThk*3-surfClr*3])
            CrankLong();
        rotate(180)
            CrankShort();
    }
}

module ThreadFit() {
    difference() {
        union() {
            Nut();
            translate([-CD, 0, layerThk*2+2*surfClr+st]) Thigh();
        }
        union() {
            cube(100);
            rotate(90) cube(100);
        }
    }
}

//!Path();
module Path() {
    colorVect=["yellow","red","blue","green"];
    translate([0,0,layerThk*10]) {
        for(ang=[0:5:360]) {
            Ax = OA*sin(ang);
            Ay = OA*cos(ang)+OG;
            AngGDA = atan(Ay/(Ax-DG));
            AD = sqrt(pow(Ay,2) + pow(Ax-DG,2));
            AngADC = acos((AD*AD + CD*CD - AC*AC)/(2*AD*CD));
            AngCAD = acos((AC*AC + AD*AD - CD*CD)/(2*AC*AD));
            AngCAB = acos((AC*AC + AB*AB - BC*BC)/(2*AC*AB));
            AngODC = AngGDA-AngADC;
            LinkAng = AngODC;
            FootAng = AngGDA + AngCAD + AngCAB;
            AngABC = acos((AB*AB + BC*BC - AC*AC)/(2*AB*BC));
            ToeAng = AngABC;

            Bx = Ax-AB*cos(FootAng);
            By = Ay+AB*sin(FootAng);
            PointB=[Bx, -By];
            translate(PointB)
                color(colorVect[3-floor(ang/90)]) cylinder($fn=3);
        }
    }
    //crossair at center of rotation
    translate([0,-OG,layerThk*10]) {
        translate([-2,-2]) color(colorVect[0]) cylinder();
        translate([-2,+2]) color(colorVect[1]) cylinder();
        translate([+2,+2]) color(colorVect[2]) cylinder();
        translate([+2,-2]) color(colorVect[3]) cylinder();
    }
}

/*
     ---------------------------------------------------------
 *    polyScrewThread.scad    by aubenc @ Thingiverse
 *
 * This script contains the library modules that can be used to generate
 * threaded rods, screws and nuts.
 *
 * http://www.thingiverse.com/thing:8796
 *
 * CC Public Domain
    ----------------------------------------------------------
 */

module hex_nut(df,hg,sth,clf,cod,crs)
{

    difference()
    {
        hex_head(hg,df);

        hex_countersink_ends(sth/2,cod,clf,crs,hg);

        screw_thread(cod,sth,clf,hg,crs,-2);
    }
}

module hex_screw(od,st,lf0,lt,rs,cs,df,hg,ntl,ntd)
{
    ntr=od/2-(st/2+0.1)*cos(lf0)/sin(lf0);

    union()
    {
        hex_head(hg,df);

        translate([0,0,hg])
        if ( ntl == 0 )
        {
            cylinder(h=0.01, r=ntr, center=true);
        }
        else
        {
            if ( ntd == -1 )
            {
                cylinder(h=ntl+0.01, r=ntr, $fn=floor(od*PI/rs), center=false);
            }
            else if ( ntd == 0 )
            {
                union()
                {
                    cylinder(h=ntl-st/2,
                             r=od/2, $fn=floor(od*PI/rs), center=false);

                    translate([0,0,ntl-st/2])
                    cylinder(h=st/2,
                             r1=od/2, r2=ntr,
                             $fn=floor(od*PI/rs), center=false);
                }
            }
            else
            {
                cylinder(h=ntl, r=ntd/2, $fn=ntd*PI/rs, center=false);
            }
        }

        translate([0,0,ntl+hg]) screw_thread(od,st,lf0,lt,rs,cs);
    }
}

module hex_head(hg,df)
{
    cylinder(h=hg, r=df/2, $fn=6, center=false);
}

module hex_countersink_ends(chg,cod,clf,crs,hg)
{
    translate([0,0,-0.1])
    cylinder(h=chg+0.01,
             r1=cod/2,
             r2=cod/2-(chg+0.1)*cos(clf)/sin(clf),
             $fn=floor(cod*PI/crs), center=false);

    translate([0,0,hg-chg+0.1])
    cylinder(h=chg+0.01,
             r1=cod/2-(chg+0.1)*cos(clf)/sin(clf),
             r2=cod/2,
             $fn=floor(cod*PI/crs), center=false);
}

module screw_thread(od,st,lf0,lt,rs,cs)
{
    or=od/2;
    ir=or-st/2*cos(lf0)/sin(lf0);
    pf=2*PI*or;
    sn=floor(pf/rs);
    lfxy=360/sn;
    ttn=round(lt/st)+1;
    zt=st/sn;

    intersection()
    {
        if (cs >= -1)
        {
           # thread_shape(cs,lt,or,ir,sn,st);
        }

        full_thread(ttn,st,sn,zt,lfxy,or,ir);
    }
}

module thread_shape(cs,lt,or,ir,sn,st)
{
    if ( cs == 0 )
    {
        cylinder(h=lt, r=or, $fn=sn, center=false);
    }
    else
    {
        union()
        {
            translate([0,0,st/2])
              cylinder(h=lt-st+0.005, r=or, $fn=sn, center=false);

            if ( cs == -1 || cs == 2 )
            {
                cylinder(h=st/2, r1=ir, r2=or, $fn=sn, center=false);
            }
            else
            {
                cylinder(h=st/2, r=or, $fn=sn, center=false);
            }

            translate([0,0,lt-st/2])
            if ( cs == 1 || cs == 2 )
            {
                  cylinder(h=st/2, r1=or, r2=ir, $fn=sn, center=false);
            }
            else
            {
                cylinder(h=st/2, r=or, $fn=sn, center=false);
            }
        }
    }
}

module full_thread(ttn,st,sn,zt,lfxy,or,ir)
{
    for(i=[0:ttn-1])
    {
        for(j=[0:sn-1])
        {
            polyhedron(
                  points=[
                          [0,                  0,                  i*st-st            ],
                          [ir*cos((j+1)*lfxy), ir*sin((j+1)*lfxy), i*st+(j+1)*zt-st   ],
                          [ir*cos(j*lfxy),     ir*sin(j*lfxy),     i*st+j*zt-st       ],
                          [or*cos((j+1)*lfxy), or*sin((j+1)*lfxy), i*st+(j+1)*zt-st/2 ],
                          [or*cos(j*lfxy),     or*sin(j*lfxy),     i*st+j*zt-st/2     ],
                          [0,                  0,                  i*st+st            ],
                          [ir*cos((j+1)*lfxy), ir*sin((j+1)*lfxy), i*st+(j+1)*zt      ],
                          [ir*cos(j*lfxy),     ir*sin(j*lfxy),     i*st+j*zt          ]
                         ],
               faces=[
                          [0,1,2],
                          [5,6,3],[5,3,0],[0,3,1],
                          [3,4,1],[1,4,2],
                          [3,6,4],[4,6,7],
                          [0,2,4],[0,4,5],[5,4,7],
                          [5,7,6]
                         ],
               convexity=5);
        }
    }
}
```


[User]
I'm looking to create a customizable planetary gear bearing similar to the one described in the `NewOne3.scad` file I've provided. I really like the overall design, but I need a completely fresh implementation so I can better understand how all the pieces fit together.

Here’s what I need:

**Core Functionality:**

*   A planetary gear system with a central sun gear, multiple planet gears, and an outer ring gear.
*   The design should be parametric and driven by customizable variables for things like overall diameter, thickness, clearances, number of planets, teeth on planets and sun, pressure angle, and helix angle (twist).
*   I want to be able to control the depth ratio of the teeth and have an inset for gear blanks as well.
*   I also need the sun gear to have a central hole with configurable width and number of sides, and the planet gears to have similar holes, also configurable.
*   I would like a similar feature to the existing file where we can inset a text on the side of the ring gear.

**Specific Parameters to Control:**

*   `D`: Outer diameter of the ring gear.
*   `T`: Thickness of the gear system.
*   `tol`: Clearance between gears.
*   `number_of_planets`: Number of planet gears.
*   `number_of_teeth_on_planets`: Number of teeth on each planet gear.
*   `approximate_number_of_teeth_on_sun`: Approximate number of teeth on the sun gear (the exact number should be calculated based on the other parameters, similar to the way the attached file does).
*   `P`: Pressure angle of the gear teeth.
*   `nTwist`: Number of teeth to twist across (for helical gears).
*   `DR`: Depth ratio of the gear teeth.
*   `inset`: Inset for gear blanks.
*    `w`: Width of the sun gear hole.
*   `s`: Number of sides on the sun gear hole.
*   `w2`: Width of the planet gear holes.
*   `s2`: Number of sides on the planet gear holes.
*   `fspace`: spacing between letters
*   `fsize`: size of text
*   `fang`: angle to rotate letters

**Key Features:**

*   **Helical gears:** Implement a helical gear design with the specified twist.
*   **Herringbone Gears:** The gears should be a herringbone style.
*   **Text on Ring:** Allow me to define text strings that are rendered on the outer edges of the ring gear (like in the provided example, but with the ability to change the text).
*   **Parametric Gear Creation:** The gear creation should be parametric using calculations similar to the provided file. This includes calculating the pitch diameter, helix angle, root diameter, and outer diameter of the gears, and generating the 2D gear profile based on that.

**Implementation:**

*   I would prefer this to be built up from first principles rather than rely on external libraries, but I do understand that I may need access to some maths routines. Make sure you include functions for calculations such as involutes.
*   The code should be well-structured and easy to understand.
*   The final design should have no unneccessary dependencies on other included SCAD files.

Could you create the OpenSCAD files for this design? I'm hoping for a set of files that I can just copy and paste into OpenSCAD, adjust the variables at the top, and then render the bearing.

[Assistant]
NewOne3.scad:
```
//======================================================================
// Planetary gear bearing (customizable)
// Takes about 18 minutes to render.
// This is a remix/update of Emmett Lalish's gear bearing
// https://www.thingiverse.com/thing:53451
// To change the text you will need to edit the .scad file provided.
//
// DrTomFlint July 2020
//----------------------------------------------------------------------
// Customizer settings:
// outer diameter of ring
D=80;
// thickness
T=20;
// clearance
tol=0.16;
// number of planets
number_of_planets=8;
// teeth on planets
number_of_teeth_on_planets=12;
// teeth on sun
approximate_number_of_teeth_on_sun=36;
// pressure angle
P=22;
// number of teeth to twist across
nTwist=2.2;
// depth ratio of teeth
DR=0.5*1;
// inset for gear blanks
inset=1;
// width of sun hole
w=18.0;
// sides on sun hole
s=8;
// size of planet holes
w2=4.0;
// sides on plante holes
s2=8;

//-------------------------------------------------------------------
module null(){
  // just forces an end to the customizer params
}
//-------------------------------------------------------------------

axle=0;  // these are not used
axle2=0;
axle3=0;

// variables computed from the customizer params:
m=round(number_of_planets);                      // m planets
np=round(number_of_teeth_on_planets);           // np num tooth planet
ns1=approximate_number_of_teeth_on_sun;
k1=round(2/m*(ns1+np));
k= k1*m%2!=0 ? k1+1 : k1;
ns=k*m/2-np;                                        // ns num tooth sun
echo("Sun"); echo(ns);
nr=ns+2*np;                                         // nr num tooth ring
echo("Ring"); echo(nr);
pitchD=0.9*D/(1+min(PI/(2*nr*tan(P)),PI*DR/nr));
pitch=pitchD*PI/nr;
echo("pitch"); echo(pitch);                     // pitch circle of ring gear?
helix_angle=atan(2*nTwist*pitch/T);
echo("helix angle"); echo(helix_angle);

phi=$t*360/m;           // $t index loops over 0 to 1

//----------------------------------
// ring gear
if(1){
difference(){
    ring3(D/2-2,T);
    herringbone(nr,pitch,P,DR,-tol,helix_angle,T+0.2);

    translate([0,0,T/2-1])
    cylinder(r1=pitchD/2-2,r2=pitchD/2+2,h=4,center=true,$fn=200);
    translate([0,0,-T/2+1])
    cylinder(r2=pitchD/2-2,r1=pitchD/2+2,h=4,center=true,$fn=200);

if(1){
// Text on edges
fspace=1.2;
fsize=7;
fang=10;

word1=["E","A","R","T","H"];
for(i=[-2:2]){
color("blue")
rotate([0,0,i*fang]){
    translate([0,-D/2+1,0])
    rotate([90,0,0])
    linear_extrude(height=1,convexity=4)
    text(word1[i+2], font = "Open Sans:style=Bold", size=fsize,halign="center",valign="center",spacing=fspace);
    }
}

word2=["A","I","R"];
for(i=[-1:1]){
color("blue")
rotate([0,0,90+i*fang]){
    translate([0,-D/2+1,0])
    rotate([90,0,0])
    linear_extrude(height=1,convexity=4)
    text(word2[i+1], font = "Open Sans:style=Bold", size=fsize,halign="center",valign="center",spacing=fspace);
    }
}

word3=["F","I","R","E"];
for(i=[0:3]){
color("blue")
rotate([0,0,180-1.25*fang+i*fang]){
    translate([0,-D/2+1,0])
    rotate([90,0,0])
    linear_extrude(height=1,convexity=4)
    text(word3[i], font = "Open Sans:style=Bold", size=fsize,halign="center",valign="center",spacing=fspace);
    }
}

word4=["W","A","T","E","R"];
for(i=[-2:2]){
color("blue")
rotate([0,0,270+i*fang]){
    translate([0,-D/2+1,0])
    rotate([90,0,0])
    linear_extrude(height=1,convexity=4)
    text(word4[i+2], font = "Open Sans:style=Bold", size=fsize,halign="center",valign="center",spacing=fspace);
    }
}
}
}
}
//--------------------------
if(1){
// The sun gear
    // align ring so teeth mesh
    rotate([0,0,(np+1)*180/ns+phi*(ns+np)*2/ns])
    difference(){
	mirror([0,1,0])
	herringbone3(ns,pitch,P,DR,tol,helix_angle,T,inset,axle);

	rotate([0,0,180/s+3])
	cylinder(r=w,h=T+1,center=true,$fn=s);
    }
}

//--------------------------
if(1){
// The m planets
    for(i=[1:m])rotate([0,0,i*360/m+phi])translate([pitchD/2*(ns+np)/nr,0,0])
	rotate([0,0,i*ns/m*360/np-phi*(ns+np)/np-phi]){
	// TF add a 3.5mm hole in each planet
	difference(){
	    herringbone3(np,pitch,P,DR,tol,helix_angle,T,inset,axle2);
	    translate([0,0,-T]) cylinder(h=2*T, r1=w2, r2=w2, $fn=s2);
	}
    }
}
//------------------------------------------

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

module ring3 (
    radius=10,
    thick=5){

    top=10;

    color("red")
    rotate_extrude($fn=200,convexity=4)
    translate([radius,0,0])
    scale([0.2,1.0]) circle(r=thick*0.5);

    color("blue")
    difference(){
        cylinder(r=radius+0.01,h=thick+0.1,center=true,$fn=200);
        cylinder(r=radius-top,h=thick+2,center=true);
    }

}

//--------------------------------------------
module gear3 (
	number_of_teeth=15,
	circular_pitch=10,
	pressure_angle=28,
	depth_ratio=1,
	clearance=0,
	helix_angle=0,
	gear_thickness=5,
    inset=0,
    axle=0){
pitch_radius = number_of_teeth*circular_pitch/(2*PI);
twist=tan(helix_angle)*gear_thickness/pitch_radius*180/PI;

intersection(){
    flat_extrude(h=gear_thickness,twist=twist,flat=false)
        gear2D (
            number_of_teeth,  // number_of_teeth,
            circular_pitch,  // circular_pitch,
            pressure_angle,    // pressure_angle,
            depth_ratio,  // depth_ratio,
            clearance  // clearance
            );

    blank3(
        number_of_teeth,
        circular_pitch,
        pressure_angle,
        depth_ratio,
        clearance,
        gear_thickness,
        inset,
        axle);

    }

}

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

module blank3 (
	number_of_teeth,
	circular_pitch,
	pressure_angle,
	depth_ratio,
	clearance,
    T,
    inset,
    axle){

pitch_radius = number_of_teeth*circular_pitch/(2*PI);
base_radius = pitch_radius*cos(pressure_angle);
depth=circular_pitch/(2*tan(pressure_angle));
outer_radius = clearance<0 ? pitch_radius+depth/2-clearance : pitch_radius+depth/2;
root_radius1 = pitch_radius-depth/2-clearance/2;
root_radius = (clearance<0 && root_radius1<base_radius) ? base_radius : root_radius1;
backlash_angle = clearance/(pitch_radius*cos(pressure_angle)) * 180 / PI;
half_thick_angle = 90/number_of_teeth - backlash_angle/2;
pitch_point = involute (base_radius, involute_intersect_angle (base_radius, pitch_radius));
pitch_angle = atan2 (pitch_point[1], pitch_point[0]);
min_radius = max (base_radius,root_radius);

add_circle = pitch_radius+depth_ratio*circular_pitch/2-clearance/2;
ded_circle = max(root_radius,pitch_radius-depth_ratio*circular_pitch/2-clearance/2);
tooth = add_circle - ded_circle;

translate([0,0,0])
cylinder(r1=add_circle-tooth,r2=add_circle,h=2,$fn=80);
translate([0,0,2*inset])
cylinder(r=add_circle,h=T-4*inset,$fn=80);
translate([0,0,T-2*inset])
cylinder(r2=add_circle-tooth,r1=add_circle,h=2,$fn=80);

}

//------------------------------------------
module herringbone3(
	number_of_teeth=15,
	circular_pitch=10,
	pressure_angle=28,
	depth_ratio=1,
	clearance=0,
	helix_angle=0,
	gear_thickness=5,
    inset=0,
    axle=0){
//union(){
        gear3(number_of_teeth,
            circular_pitch,
            pressure_angle,
            depth_ratio,
            clearance,
            helix_angle,
            gear_thickness/2,
            inset,
            axle);

    mirror([0,0,1])
   	gear3(number_of_teeth,
			circular_pitch,
			pressure_angle,
			depth_ratio,
			clearance,
			helix_angle,
			gear_thickness/2,
            inset,
            axle);

//}
}

//------------------------------------
module fillet(r) {
   offset(r = -r) {
     offset(delta = r) {
       children();
     }
   }
}

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

module herringbone(
	number_of_teeth=15,
	circular_pitch=10,
	pressure_angle=28,
	depth_ratio=1,
	clearance=0,
	helix_angle=0,
	gear_thickness=5){
union(){
    gear(number_of_teeth,
        circular_pitch,
        pressure_angle,
        depth_ratio,
        clearance,
        helix_angle,
        gear_thickness/2);
    mirror([0,0,1])
   	gear(number_of_teeth,
        circular_pitch,
        pressure_angle,
        depth_ratio,
        clearance,
        helix_angle,
        gear_thickness/2);
    }
}

//------------------------------------
module gear (
	number_of_teeth=15,
	circular_pitch=10,
	pressure_angle=28,
	depth_ratio=1,
	clearance=0,
	helix_angle=0,
	gear_thickness=5,
	flat=false){
pitch_radius = number_of_teeth*circular_pitch/(2*PI);
twist=tan(helix_angle)*gear_thickness/pitch_radius*180/PI;

flat_extrude(h=gear_thickness,twist=twist,flat=flat)
	gear2D (
		number_of_teeth,
		circular_pitch,
		pressure_angle,
		depth_ratio,
		clearance);
}

//------------------------------------
module flat_extrude(h,twist,flat){
	if(flat==false)
		linear_extrude(height=h,twist=twist,slices=twist/6,convexity=10)
		children(0);
	else
		children(0);
}

//------------------------------------
module gear2D (
	number_of_teeth,
	circular_pitch,
	pressure_angle,
	depth_ratio,
	clearance){
pitch_radius = number_of_teeth*circular_pitch/(2*PI);
base_radius = pitch_radius*cos(pressure_angle);
depth=circular_pitch/(2*tan(pressure_angle));
outer_radius = clearance<0 ? pitch_radius+depth/2-clearance : pitch_radius+depth/2;
root_radius1 = pitch_radius-depth/2-clearance/2;
root_radius = (clearance<0 && root_radius1<base_radius) ? base_radius : root_radius1;
backlash_angle = clearance/(pitch_radius*cos(pressure_angle)) * 180 / PI;
half_thick_angle = 90/number_of_teeth - backlash_angle/2;
pitch_point = involute (base_radius, involute_intersect_angle (base_radius, pitch_radius));
pitch_angle = atan2 (pitch_point[1], pitch_point[0]);
min_radius = max (base_radius,root_radius);

intersection(){
	rotate(90/number_of_teeth)
		circle($fn=number_of_teeth*3,r=pitch_radius+depth_ratio*circular_pitch/2-clearance/2);
	union(){
		rotate(90/number_of_teeth)
			circle($fn=number_of_teeth*2,r=max(root_radius,pitch_radius-depth_ratio*circular_pitch/2-clearance/2));
		for (i = [1:number_of_teeth])rotate(i*360/number_of_teeth){
			halftooth (
				pitch_angle,
				base_radius,
				min_radius,
				outer_radius,
				half_thick_angle);
			mirror([0,1])halftooth (
				pitch_angle,
				base_radius,
				min_radius,
				outer_radius,
				half_thick_angle);
		}
	}
}}

//------------------------------------
module halftooth (
	pitch_angle,
	base_radius,
	min_radius,
	outer_radius,
	half_thick_angle){
index=[0,1,2,3,4,5];
start_angle = max(involute_intersect_angle (base_radius, min_radius)-5,0);
stop_angle = involute_intersect_angle (base_radius, outer_radius);
angle=index*(stop_angle-start_angle)/index[len(index)-1];
p=[[0,0],
	involute(base_radius,angle[0]+start_angle),
	involute(base_radius,angle[1]+start_angle),
	involute(base_radius,angle[2]+start_angle),
	involute(base_radius,angle[3]+start_angle),
	involute(base_radius,angle[4]+start_angle),
	involute(base_radius,angle[5]+start_angle)];

difference(){
	rotate(-pitch_angle-half_thick_angle)polygon(points=p);
	square(2*outer_radius);
}}

//------------------------------------
// Mathematical Functions

// Finds the angle of the involute about the base radius at the given distance (radius) from it's center.
//source: http://www.mathhelpforum.com/math-help/geometry/136011-circle-involute-solving-y-any-given-x.html

function involute_intersect_angle (base_radius, radius) = sqrt (pow (radius/base_radius, 2) - 1) * 180 / PI;

// Calculate the involute position for a given base radius and involute angle.

function involute (base_radius, involute_angle) =
[
	base_radius*(cos (involute_angle) + involute_angle*PI/180*sin (involute_angle)),
	base_radius*(sin (involute_angle) - involute_angle*PI/180*cos (involute_angle))
];

//===============================================

```


[User]
Okay, so I'm looking to create a parametric Danish town sign, or "byskilte", for 3D printing, and I'd like to use OpenSCAD. I'd like it to be similar in style to the example I've seen, with a rectangular sign with rounded corners and a back plate. The main sign part should have a border, the town name text in the center, and some stylized house shapes on the bottom.

Here are the specifics I have in mind:

*   **Sign Shape:** Rectangular with rounded corners. The corners should have a radius, and the rectangle should be parametrically adjustable in width and height.
*   **Back Plate:** A slightly larger rectangular piece with the same rounded corners, but a bit thinner, that acts as a backing for the sign.
*   **Border:** A border line around the main sign face. It should be a separate object or difference, so the sign can have a slightly different color.
*   **Text:** The town name should be centered horizontally on the sign face, a little bit above the bottom, and be parametric. The font to use should be "dvsp", which I have as a .ttf file, but I don't want that included as code in the response.
*   **Houses:** Three stylized house shapes on the bottom of the sign face, horizontally arranged. These don't have to be too fancy; think simple outline-type shapes. They should also be parametric.
*   **Parameters:** I need to be able to adjust the sign's overall width and height, the corner radius, text size, border thickness, house spacing, and the text string itself. It should also be easy to set the sign and back plate heights, possibly as a percentage of the overall sign height. I'd also like to be able to set the layer height.
*   **Libraries:** I would like to use the included `dvsp.ttf` for the font, but this is a font file, not code. Please use the standard OpenSCAD library.
*   **Coloring:** The back plate should be a different color than the sign face, in order to make it suitable for multi-color printing. I have been using white for the back plate and dark gray for the sign face, but I should be able to change that easily in OpenSCAD.

Essentially, I want to be able to customize the town name, dimensions, and colors easily, so it's important that the design be parametric. Could you generate the OpenSCAD file(s) to do this?

[Assistant]
town_sign.scad:
```
/*
 * Dansk Byskilt (Danish Town Sign)
 *
 * Copyright (c) 2020 Mathias V. Rasmussen
 * Released under the terms of the GPLv3 license.
 */

city_name = "Aarhus";

layer_height = 0.2;
layers = 24;

sign_height = layer_height * layers;
back_height = sign_height * 2/3;

width =  32 + 7 * max(7, len(city_name));
height = 40;

radius = 6.5;

// Font from http://ts.vejdirektoratet.dk
use <dvsp.ttf>

// Set fragment size to smoothen curves
$fs = 0.1;

module fillet (r, delta) {
    offset(r = r)
    offset(delta = delta ? delta : -r)
    children();
}

module sign_shape (inward = 0) {
    fillet(radius - inward, -radius)
    square([width, height], center=true);
}

// Back plate
color("white")
linear_extrude(back_height) {
    sign_shape();
}

// Sign
color("#222")
linear_extrude(sign_height) {
    // Border line
    thickness = 1.3;
    margin = 2.7;
    difference() {
        sign_shape(margin);
        sign_shape(margin + thickness);
    }

    // Text
    translate([0, 4])
    text(city_name, size = 8, font = "dvsp",
         halign = "center");

    // Houses
    spacing = 4;
    center = (30+36+18+spacing*2) / 2;
    fillet(0.25)
    scale(0.5)
    translate([-center, -28, 0])
    union() {
        polygon(points=house_points[0]);
        translate([30+spacing, 0]) {
            polygon(points=house_points[1]);
            translate([36+spacing, 0])
                polygon(points=house_points[2]);
        }
    }
}

house_points =
[ [
    [ 0,  0], [ 0,  8], [ 8, 16], [16,  8],
    [16,  5], [20,  5],
    [20, 12], [25, 30], [30, 12], [30,  0]
], [
    [ 0,  0], [ 0, 13], [ 3, 16], [17, 16], [20, 13],
    [20,  3], [24,  3],
    [24,  6], [30, 12], [36,  6], [36,  0]
], [
    [ 0,  0], [ 0, 15], [ 9, 24], [18, 15], [18,  0]
] ];

```


[User]
I'm looking for a way to generate customizable silverware drawer organizers, and I'd like to use OpenSCAD to do it. I want to be able to specify the dimensions of the slots and separators, and also have some control over the overall size and construction of the organizer.

Specifically, I need a system that can handle multiple rows (or "racks") of slots, one on top of the other. Each rack should have a configurable list of widths, alternating between slot width and separator width.  I’d like the ability to specify the thickness of each rack, and the distance between racks. The overall height of the slots should be adjustable, and I'd like to be able to add a small "footing" around each rack for stability, plus a base for the whole thing. I also want to be able to control the number of vertical connectors between racks, and how wide those connectors are.

So, to summarize, I need to be able to define:

*   **Rack Dimensions:** A list of widths for each rack, alternating between slot widths and separator widths, starting with a separator (and potentially a zero-width initial separator). I'd need a separate list for each rack.
*   **Rack Spacing:** The distance between each of the racks.
*   **Rack Thickness:** The thickness of each rack.
*   **Slot Height:** The overall height of the slots (i.e., how deep the drawer is).
*   **Footing:** A small lip/footing around the edges of each rack.
*   **Base Thickness:** The thickness of the base that each rack is sitting on.
*   **Connectors:** The number of vertical bars connecting the racks, and the width of those bars.

I don't need to use any external libraries, but I do want the design to be parametric so that I can easily adjust these parameters. I think it'd be helpful to have some comments in the code too, explaining what each part of the design is doing, so it's easier for me to change it later.

[Assistant]
SilverwareRack.scad:
```
// List of sizes of openings and separators, starting with an opening (likely zero).  All dimensions in mm.
Rack_1_dimensions = [0,5,8,35,8,35,8,35,8,5];
// As for rack1, but for an additional row.  Should total to the same width as rack1.
Rack_2_dimensions = [];
// And a third row, if you need it.
Rack_3_dimensions = [];
// Thickness of each rack
Distance_between_rack_1_and_rack_2 = 50;
Distance_between_rack_2_and_rack_3 = 50;
Thickness = 10;
// Height of each rack.  (How deep is your drawer?)
Height = 55;
// How much of a footing to put around each rack.  Helpful to stabilize if you have only one row.
Footing = 0;
// Thickness of the base.  1mm works, but you can make it thicker.
Base = 1;
// Number of bars connecting the racks.
Number_of_connectors = 3;
// Width of each bar.
Connector_width = 10;

racks();

function sum(a) = sumto(a, len(a)-1);

function sumto(a, i) = i < 0 ? 0 : a[i] + sumto(a, i-1);

function runningsum(a) = [ for (i=[0:len(a)-1]) sumto(a, i-1) ];

module racks() {
    rack1 = Rack_1_dimensions;
    rack2 = Rack_2_dimensions;
    rack3 = Rack_3_dimensions;
    lr2 = rack2 == "[]" ? 0 : len(rack2);
    lr3 = rack3 == "[]" ? 0 : len(rack3);
    nbars = Number_of_connectors;
    barw = Connector_width;
    w1 = sum(rack1);
    w2 = lr2 > 0 ? sum(rack2) : 0;
    w3 = lr3 > 0 ? sum(rack3) : 0;
    r1r2 = Distance_between_rack_1_and_rack_2;
    r2r3 = Distance_between_rack_2_and_rack_3;
    width = max(w1, w2, w3);
    totald = Thickness
        + (lr2 > 0 ? r1r2 + Thickness : 0)
        + (lr3 > 0 ? r2r3 + Thickness : 0);
    y1 = 0;
    y2 = y1 + Thickness + r1r2;
    y3 = y2 + Thickness + r2r3;

    rack(dims=rack1, d=Thickness, h=Height, base=Base);
    %translate([width+5, y1, 0]) linear_extrude(1) text(str(w1, "mm"));

    if (lr2 > 0) {
        translate([0,y2,0])
            rack(dims=rack2, d=Thickness, h=Height, base=Base);
        %translate([width+5, y2, 0]) linear_extrude(1) text(str(w2, "mm"));
    }
    if (lr3 > 0) {
        translate([0,y3,0])
            rack(dims=rack3, d=Thickness, h=Height, base=Base);
        %translate([width+5, y3, 0]) linear_extrude(1) text(str(w3, "mm"));
    }
    if (lr2 > 0 || lr3 > 0) {
        if (nbars > 1) {
            for (i=[0:nbars-1]) {
                space = (width - nbars*barw)/(nbars-1);
                translate([i*(space+barw),0,0]) cube([barw,totald,Base]);
            }
        } else {
            translate([(width-barw)/2,0,0]) cube([barw,totald,Base]);
        }
    }
    if (Footing > 0) {
        translate([0, -Footing, 0]) cube([width, Footing*2 + Thickness, Base]);
        if (lr2 > 0) {
            translate([0, y2-Footing, 0]) cube([width, Footing*2 + Thickness, Base]);
        }
        if (lr3 > 0) {
            translate([0, y3-Footing, 0]) cube([width, Footing*2 + Thickness, Base]);
        }
    }
}

module rack(dims, d, h, base) {
    xpositions = runningsum(dims);
    width=sum(dims);
    cube([width, d, base]);

    for (i=[1:2:len(dims)-1]) {
        translate([xpositions[i],0,0])
            cube([dims[i], d, h]);
    }
}
```


[User]
Hey, I'm looking to get a custom Eurorack cable organizer designed, and I'd like to use OpenSCAD to make it parametric. I have a basic design in mind, sort of like a bridge or arch that sits over the modules, letting you tuck cables underneath it.

Here are the main features I need:

*   **Bridge Shape:** It should basically be a series of connected cylinders, with an overall profile that can be defined by points. The points would define the X (horizontal, in HP units), Y (depth), and Z (height) of the profile.
*   **Length:** I need to be able to specify the overall length of the flat part of the bridge in HP units.
*   **Height:** The height of the bridge above the modules should be configurable in millimeters.
*   **Mounting Flanges:** It needs mounting flanges at each end to secure it to the Eurorack rails. The length of these flanges should also be configurable in HP units.
*   **Wedges:** I'd like the option of having angled sections (wedges) connecting the flanges to the flat part of the bridge. The length of these should also be configurable in HP units.
*   **Material Thickness:** The thickness of the material, in millimeters, should be adjustable.
*   **Mounting Holes:** I need mounting holes on the flanges, designed to be compatible with standard Eurorack rails. Ideally, these should work with M3 screws. I should be able to choose to include or not include the mounting holes, as well as choose if they are countersunk for flush mounting. I also need a mounting hole in the center of the bridge, to mount it from underneath if desired.
*   **Width:** The depth of the piece, in millimeters, should also be configurable.
*   **Debug View:** A debug view that shows the locations of all the Eurorack rail holes along the X axis would be really helpful. This should be a separate option, maybe a boolean.
*   **Parametric Design:** Everything should be parametric so I can adjust the various lengths, heights, thicknesses, and hole configurations easily through variables. I'm going to need to be able to adjust the shape itself to create new designs, but the basic bridge profile will remain the same - just changing the points that connect it.

For the mounting holes, I'd like them to use M3 screws by default, with an option for countersunk heads.  I need to be able to specify the diameter and depth of the countersink.

It'd also be good if the code was well-commented so I can follow what's going on. I don't need any fancy libraries or anything, just good, clear OpenSCAD code. Could you generate an OpenSCAD file (or files if it's too long) that can do this?

[Assistant]
punte-cablu-eurorack-v1.1.scad:
```
// designed by Richard/ARHA; arha.ro; 2020-07-10
// lungimea de sub punte, în HP
hp_length = 8;

// înălțimea punții, în mm
height_mm = 20;

// lungimea pantelor, in HP
length_wedge=0;

// lungimea flanșelor de montare, în HP
length_flange=2;

// grosimea materialului, în mm
thickness = 1.75;

// găuri de montare laterale
mounting_holes = true;

// gaură de montare centrală (se montează in jos)
center_hole = true;

// diametru găuri flanșa (M3)
mounting_holes_size = 3.05;

// countersunk sizes for M3 screws

// se montează cu șuruburi cu capete înecate?
countersunk_screws = true;

// diametru în mm, la față, ale capetelor bombate
countersunk_size = 6;

// înălțimea capetelor inecate, până la corp/filet
countersunk_h = 1.7;

// grosimea piesei
width = 10;

// afișează fiecare gaură de pe șină
debug_eurorack_rail = true;

// numărul de puncte calculate pe cerc (mai multe = mai lent)
$fn=128;

length = hp_length;
hp = 0.2 * 25.4;
pts = [ [0, 0, 0],
[hp * length_flange, 0, 0],
[hp * (length_flange + length_wedge), 0, height_mm],
[hp * (length_flange + length_wedge + length), 0, height_mm],
[hp * (length_flange + length_wedge*2 + length), 0, 0],
[hp * (length_flange*2 + length_wedge*2 + length), 0, 0]
];

difference()
{
    punte_eurorack(pts, width, thickness);
    if (mounting_holes)
    {
        translate([length_flange/2*hp, -width/2, -thickness*1.5])
        cylinder(d=mounting_holes_size, h=thickness*3);

        if (countersunk_screws)
        {
            translate([length_flange/2*hp, -width/2, thickness+0.01])
            rotate([180, 0, 0])
            cylinder(d2 = mounting_holes_size, d1 = countersunk_size, h=countersunk_h);
        }

        translate([hp * (length_flange * 1.5 + length_wedge*2 + length), -width/2, -thickness*1.5])
        cylinder(d=mounting_holes_size, h=thickness*3);

        if (countersunk_screws)
        {
            translate([hp * (length_flange * 1.5 + length_wedge*2 + length), -width/2, thickness+0.01])
            rotate([180, 0, 0])
            cylinder(d2 = mounting_holes_size, d1 = countersunk_size, h=countersunk_h);
        }
    }

    if (center_hole)
    {
        translate([hp * (length_flange + length_wedge + length/2), -width/2, -thickness*1.5])
        cylinder(d=mounting_holes_size, h=thickness*30);

        if (countersunk_screws)
        {
            translate([hp * (length_flange + length_wedge + length/2), -width/2, height_mm-thickness-0.01])
            cylinder(d1 = countersunk_size, d2 = mounting_holes_size, h=countersunk_h);
        }
    }
}

    if (debug_eurorack_rail)
    {
        for (i = [0:length + 2*length_wedge + 1*length_flange])
        {
            x = i * hp;
            translate([(length_flange)/2*hp + x, -width/2, -thickness*1.5])
            % cylinder(d=1, h=height_mm*2);
        }
    }

module punte_eurorack(points, width, thick)
{
        for (i = [0:4])
        {
            //hue = 0.25 + (i % 2) * 0.75;
            //hue = 0.5;
            //color([hue, hue, hue])
            hull()
            {
                p = points[i];
                translate(p)
                rotate([90, 0, 0])
                cylinder(r=thick, h=width);

                p2 = points[i+1];
                translate(p2)
                rotate([90, 0, 0])
                cylinder(r=thick, h=width);
            }
        }
}

```


[User]
I'd like to create a parametric drill bit and screwdriver bit holder that uses magnets. I have a basic idea and some parameters in mind, but I need help generating the OpenSCAD code for it.

Here's what I'm thinking:

The holder should have two main parts: a top part with holes for the bits, and a bottom part with space for magnets.

**Top Part:**

*   It should have a rectangular base, with a customizable length based on the number of holes for bits.
*   The number of bit holes should be a parameter, and must be even. Let's call it `num`.
*   The holes should be arranged in a single row.
*   The spacing between the centers of the holes should be a parameter, let's call it `step`.
*   The diameter of the holes for the bits should also be a parameter, `bit_hole_diameter`.
*   There should be a very thin base layer under the holes for extra stability when printing. Let's make this 0.4mm thick.
*   The overall height of the extruded section should be a parameter `height`.
* The width of the overall rectangular base should be a calculated parameter `width = step*(num-1)+7*2`

**Bottom Part:**

*   This is intended to house magnets.
*   It should also have a rectangular base, slightly larger than the top part, to allow for an attachment rim. This attachment rim will have a parameter we'll call `rim` that controls how far outside the top part it extrudes.
*   The rectangular base should be extruded to a height of 4mm.
*   There should be a small base layer with a thickness of 0.2mm extruded under the main magnet-housing extrusion.
*   There should also be a section in the middle that provides the magnet holes, which will be 2.2mm tall.
*   The magnet holes should be arranged in a single row, and centered under the drill bit holes above. For now, we'll assume the number of magnet holes will be half the number of bit holes, and each magnet hole is under the space between two bit holes.
*   The diameter of the magnet holes should be a parameter `magnet_hole_diameter`.

**General Parameters:**
* The rectangular "width" should be calculated.

So we'll have the following parameters: `num`, `height`, `rim`, `step`, `bit_hole_diameter`, `magnet_hole_diameter`
and a couple of internal calculation parameters `width`.

For the magnets I'm thinking of using 6mm diameter and 2mm tall magnets.

I'd like to be able to generate only the top part or only the bottom part. So I'd like a boolean parameter for both called `generate_top` and `generate_bottom`.

Could you generate the OpenSCAD code for me, using these parameters? I don't need any external libraries for this, I think, just the standard OpenSCAD operations. Thanks!

[Assistant]
drillbit_holder_stand.scad:
```
// Number of holes (must be even)
num=26; //[2:30]

height=12;

// Width of attachment structure
rim=1.7;

// Drillbit hole spacing
step=8.60;

// Generate the top part (for drillbits)
generate_top=true;
// Generate the bottom part (for magnets)
generate_bottom=false;

width=step*(num-1)+7*2;

if (generate_top) {
linear_extrude(height){
difference(){
square([width,14]);
for (i=[0:num-1]){
translate([i*step+7,7]){
circle($fn=6,d=7.8);
}
}
}
}
linear_extrude(0.4){
square([width,14]);
}
}

if (generate_bottom) translate([0,20,0]) {
linear_extrude(4){
difference(){
translate([-rim,-rim])square([width+2*rim,14+2*rim]);
square([width+0.03,14+0.03]);
}
}
linear_extrude(0.2){
translate([-rim,-rim])square([width+2*rim,14+2*rim]);
}
linear_extrude(0.2+2){
difference(){
translate([-rim,-rim])square([width+2*rim,14+2*rim]);
for (i=[0:num/2]){
translate([i*step*2+7+step/2,7]){
circle($fn=30,d=6.2);
}
}
}
}
}

```


[User]
I need an OpenSCAD design for a drill bit holder that can stand on its own and hold a variable number of drill bits. Here's what I'm thinking:

The main body should be a rectangular prism with a series of holes along its length. The holes should be sized to accommodate drill bits, with diameters ranging from 1mm to 10mm, in 0.5mm increments. I'd like to be able to specify how many holes (and thus drill bits) I want. The spacing between the holes should also be customizable.

For stability, I'd like a wider base. This base will also house the magnets that will hold it to a metal surface. The magnets should be 6mm in diameter and 2mm thick. The holes in the base for the magnets should match these dimensions. I'd like the magnet holes to line up with the drill bit holes.

There should be an option to have the main body with drill bit holes only or the main body with drill bit holes and an extra magnet-holding base. I’d also like to be able to customize the main body's height.

Here are the parameters that should be configurable:
*   `num`: The number of drill bit holes (and magnet holes if the base is generated).
*   `height`: The height of the main part of the drill bit holder.
*   `rim`: The width of the attachment structure on the base.
*   `step`: The spacing between the drill bit holes.
*   `generate_top`: Boolean flag to generate the top (drill bit holder).
*   `generate_bottom`: Boolean flag to generate the bottom (magnet holder).

I am envisioning a design similar to the one here, but with more parametric flexibility: https://www.thingiverse.com/thing:4539428

I'd like you to use the BOSL2 library, as it seems helpful for parametric designs.

[Assistant]
tall_drillbit_holder_stand.scad:
```
// Number of holes
num=19; //[1:30]

height=70;

// Width of attachment structure
rim=1.7;

// Drillbit hole spacing
step=11;

// Generate the top part (for drillbits)
generate_top=true;
// Generate the bottom part (for magnets)
generate_bottom=false;

width=step*(num-1)+7*2;

if (generate_top) {
linear_extrude(0.4){
square([width,14]);
}
intersection(){
linear_extrude(height+0.4,convexity=10){
difference(){
square([width,14]);
for (i=[0:num-1]){
translate([i*step+7,7]){
square(i/2+1+0.5,center=true);
}
}
}
}
union(){
rotate([90,0,0])mirror([0,0,1])linear_extrude(100){
polygon([[0,0],[0,25-10],[12*step+2,height-10],[width,height-10],[width,0]]);
}
rotate([90,0,0])mirror([0,0,1])linear_extrude(7){
polygon([[0,0],[0,25],[12*step+2,height],[width,height],[width,0]]);
}
}
}
}

if (generate_bottom) translate([0,20,0]) {
linear_extrude(4){
difference(){
translate([-rim,-rim])square([width+2*rim,14+2*rim]);
square([width+0.03,14+0.03]);
}
}
linear_extrude(0.2){
translate([-rim,-rim])square([width+2*rim,14+2*rim]);
}
linear_extrude(0.2+2){
difference(){
translate([-rim,-rim])square([width+2*rim,14+2*rim]);
for (i=[0:num]){
translate([i*step+7,7]){
circle($fn=30,d=6.2);
}
}
}
}
}

```


[User]
I'm looking to create a parametric design for a tool/pen holder based on a central hexagonal pot surrounded by up to four rings of similar pots. I've got a basic idea, but I need help making it properly parametric in OpenSCAD. I'd like to be able to control the following:

*   **Number of Rings:** This should be an integer value ranging from 0 to 4, indicating how many concentric rings of pots surround the center one.
*   **Center Pot Height:**  A single numerical value for the height of the central pot.
*   **Hex Pot Size (Across-Flats):** A single numerical value to determine the size of each pot (across the flats of the hexagon).
*   **Wall Thickness:** A single numerical value for the wall thickness of the pots.
*   **Base Thickness:** A numerical value for the thickness of the base of each pot.
*   **Minimum Pot Height:** A minimum height to constrain pot heights (all pots will be at least this tall).

For the individual rings, I need to control pot heights as follows:

*   **Ring 1:**
    *   A percentage scale for overall Ring 1 heights, which I will call `ZscaleR1`
    *   Six individual pot heights, named `Z1_1h` through `Z1_6h`, corresponding to the pots in the North, North-East, South-East, South, South-West, and North-West positions. These heights are scaled by `ZscaleR1`
    *   An array called `Enable6` where `[1,1,1,1,1,1]` means all pots are enabled, and setting an index to zero disables the pot in that particular position.
*   **Ring 2:**
    *   A percentage scale for overall Ring 2 heights, called `ZscaleR2`.
    *   Twelve individual pot heights, named `Z2_1h` through `Z2_12h` in a clockwise direction around the central pot, starting from the North. These heights are scaled by `ZscaleR2`
    *   An array called `Enable12` where `[1,1,1,1,1,1,1,1,1,1,1,1]` means all pots are enabled, and setting an index to zero disables the pot in that particular position.
*   **Ring 3:**
    *   A percentage value (`Ring3_Zstep_percent`) to determine height changes for Ring 3, relative to the height differences between Ring 1 and Ring 2.
    *   An array called `Enable18` where `[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]` means all pots are enabled, and setting an index to zero disables the pot in that particular position.
*   **Ring 4:**
    *  A percentage value (`Ring4_Zstep_percent`) to determine height changes for Ring 4, relative to the height differences between Ring 2 and Ring 3.
    *   An array called `Enable24` where `[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]` means all pots are enabled, and setting an index to zero disables the pot in that particular position.

I'd like the pots in rings 3 and 4 to have their heights calculated based on the heights of the inner rings, using the percentage change defined above (i.e. Ring3 height is calculated based on the *difference* between corresponding Ring 1 & 2 heights, and Ring 4 is based on the *difference* between Ring 2 and Ring 3). Also, the individual `Enable` arrays should allow me to disable individual pots in each ring (I am not asking for a way to change the number of pots). Each ring's pot arrangement starts with the north-most pot and proceeds clockwise around the center.

Could you provide me with a complete, parametric OpenSCAD design that covers these requirements? I'm not looking to use any external libraries, just standard OpenSCAD functionality.

[Assistant]
HexPot_v6.scad:
```

// No. of Rings
Rings = 1; // [0,1,2,3,4]

// Centre pot height
Z0 = 150;

// Hex Pot size across-flats (Outside)
AF = 31.5;

// Wall Thickness
WT = 1.5;

// Base Thickness
BT = 1.5;

// Minimum pot height
MinZ = 10;

/* [RING 1] */

// Zscale (percent) for Ring 1
ZscaleR1=100;

// North pot height
Z1_1h = 150;

// NorthEast pot height
Z1_2h = 140;

// SouthEast pot height
Z1_3h = 130;

// South pot height
Z1_4h = 120;

// SouthWest pot height
Z1_5h = 110;

// NorthWest pot height
Z1_6h = 100;

// Individual Enables
Enable6 = [1,1,1,1,1,1];

/* [RING 2] */

// Zscale (percent) for Ring 2
ZscaleR2=100;

//2nd ring North pot height
Z2_1h = 100;

//2nd ring NNE pot height
Z2_2h = 95;

//2nd ring ENE pot height
Z2_3h = 90;

//2nd ring East pot height
Z2_4h = 85;

//2nd ring ESE pot height
Z2_5h = 80;

//2nd ring SSE pot height
Z2_6h = 75;

//2nd ring South pot height
Z2_7h = 70;

//2nd ring SSW pot height
Z2_8h = 65;

//2nd ring WSW pot height
Z2_9h = 60;

//2nd ring West pot height
Z2_10h = 55;

//2nd ring WNW pot height
Z2_11h = 50;

//2nd ring NNW pot height
Z2_12h = 45;

// Individual Enables
Enable12 = [1,1,1,1,1,1,1,1,1,1,1,1];

/* [RING 3] */

//Z step percent (relative to ring1/ring2 Z change)
Ring3_Zstep_percent = 10;

// Individual Enables
Enable18 = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1];

/* [RING 4] */

//Z step percent (relative to ring2/ring3 Z change)
Ring4_Zstep_percent = 50;

// Individual Enables
Enable24 = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1];

AP = AF * 1.154701; // Across points dimension
HSL = AF * tan(30);
ACE = 1/1000;       // tiny offset to avoid coincident edges

module hexagon(AFlen,height) {
    ang = 360/6;
    w1 = tan(30) * AFlen;
    w2 = AFlen;
    rotate([0,0,2*ang])
    cube([w1,w2,height],true);
    rotate([0,0,1*ang])
    cube([w1,w2,height],true);
    cube([w1,w2,height],true);
    }

module hollow_hexagon(AFlen,height,WallThickness) {
    difference()
      {
      hexagon(AFlen,height);
      hexagon(AFlen-(WallThickness*2),height+1);
      }
    }

  module hexPotWithBase(AFlen,height,WallThickness) {
      union()
        {
         hollow_hexagon(AFlen-ACE,height,WallThickness);
        translate([0,0,height* -0.5]) hexagon(AFlen-ACE,BT);
        }
    }

module DrawRing(R,Zlist,Enable) {
    L = AF-WT; // Y axis step size considering wall thickness
    N=len(Zlist);
    NS=N/6;
    DY=L/2;
    DX = ((AP+HSL)/2)-(WT*1.155);
    // Initial Positions
    X1=0;         Y1=R;              Z1=0*NS; // N set X,Y & Zlist index
    X2=NS*DX;     Y2=R-(NS*DY);      Z2=1*NS; // NE set X,Y & Zlist index
    X3=NS*DX;     Y3=-(R-(NS*DY));   Z3=2*NS; // SE set X,Y & Zlist index
    X4=0;         Y4=-R;             Z4=3*NS; // S set X,Y & Zlist index
    X5=-(NS*DX);  Y5=-(R-(NS*DY));   Z5=4*NS; // SW set X,Y & Zlist index
    X6=-(NS*DX);  Y6=R-(NS*DY);      Z6=5*NS; // NW set X,Y & Zlist index
    // itteration multipliers for each set
    MX1=DX;        MY1=-DY;
    MX2=0;         MY2=-2*DY;
    MX3=-DX;       MY3=-DY;
    MX4=-DX;       MY4=DY;
    MX5=0;         MY5=2*DY;
    MX6=DX;        MY6=DY;

     for(i=[0:NS-1]) {
//      if ((Enable[i]*i)>0)  {
//          translate([X1+(i*MX1),Y1+(i*MY1),Zlist[Z1+i]*0.5])
//            hexPotWithBase(AF,Zlist[Z1+i],WT); // N set
//          translate([X2+(i*MX2),Y2+(i*MY2),Zlist[Z2+i]*0.5])
//            hexPotWithBase(AF,Zlist[Z2+i],WT); // NE set
//          translate([X3+(i*MX3),Y3+(i*MY3),Zlist[Z3+i]*0.5])
//            hexPotWithBase(AF,Zlist[Z3+i],WT); // SE set
//          translate([X4+(i*MX4),Y4+(i*MY4),Zlist[Z4+i]*0.5])
//            hexPotWithBase(AF,Zlist[Z4+i],WT); // S set
//          translate([X5+(i*MX5),Y5+(i*MY5),Zlist[Z5+i]*0.5])
//            hexPotWithBase(AF,Zlist[Z5+i],WT); // SE set
//          translate([X6+(i*MX6),Y6+(i*MY6),Zlist[Z6+i]*0.5])
//            hexPotWithBase(AF,Zlist[Z6+i],WT); // NE set
//      }
      DRcore(Enable[Z1+i],X1+(i*MX1),Y1+(i*MY1),Zlist[Z1+i],i,Z1+i);   // N set
      DRcore(Enable[Z2+i],X2+(i*MX2),Y2+(i*MY2),Zlist[Z2+i],i,Z2+i);   // NE set
      DRcore(Enable[Z3+i],X3+(i*MX3),Y3+(i*MY3),Zlist[Z3+i],i,Z3+i);   // SE set
      DRcore(Enable[Z4+i],X4+(i*MX4),Y4+(i*MY4),Zlist[Z4+i],i,Z4+i);   // S set
      DRcore(Enable[Z5+i],X5+(i*MX5),Y5+(i*MY5),Zlist[Z5+i],i,Z5+i);   // SE set
      DRcore(Enable[Z6+i],X6+(i*MX6),Y6+(i*MY6),Zlist[Z6+i],i,Z6+i);   // NE set
    }
  }

module DRcore(En,X,Y,Z,i,idx) {
  if(En>0) {
    translate([X,Y,Z/2])
      hexPotWithBase(AF,Z,WT);
    }
  }

function IP(P1,P2,POS) = P1 + (POS*(P2-P1));

function IZ3(Z1,Z2) = MAX(MinZ,Z2 + (((Z2-Z1)*Ring3_Zstep_percent)/100));

function IZ4(Z1,Z2) = MAX(MinZ,Z2 + (((Z2-Z1)*Ring4_Zstep_percent)/100));

function MAX(a,b) = a>b ? a : b;

module Report() {
  echo("Rings = ",Rings);
  echo("Z0 = ",Z0);
  echo("AF = ",AF);
  echo("WT = ",WT);
  echo("BT = ",BT);
  echo("MinZ = ",MinZ);
  echo("ZscaleR1 = ",ZscaleR1);
  echo("Z1_1 = ",Z1_1);
  echo("Z1_2 = ",Z1_2);
  echo("Z1_3 = ",Z1_3);
  echo("Z1_4 = ",Z1_4);
  echo("Z1_5 = ",Z1_5);
  echo("Z1_6 = ",Z1_6);
  echo("ZscaleR2 = ",ZscaleR2);
  echo("Z2_1 = ",Z2_1);
  echo("Z2_2 = ",Z2_2);
  echo("Z2_3 = ",Z2_3);
  echo("Z2_4 = ",Z2_4);
  echo("Z2_5 = ",Z2_5);
  echo("Z2_6 = ",Z2_6);
  echo("Z2_7 = ",Z2_7);
  echo("Z2_8 = ",Z2_8);
  echo("Z2_9 = ",Z2_9);
  echo("Z2_10 = ",Z2_10);
  echo("Z2_11 = ",Z2_11);
  echo("Z2_12 = ",Z2_12);
  echo("Ring3_Zstep_percent =",Ring3_Zstep_percent);
  echo("Ring4_Zstep_percent =",Ring4_Zstep_percent);
 }

//
//   Main Body
//
// HexPot_v5  (C) Tony Rowell 2020
//

$fn=100;
// clip inpu Z heights as required
Z1_1 = MAX(MinZ,(ZscaleR1*Z1_1h/100));
Z1_2 = MAX(MinZ,(ZscaleR1*Z1_2h/100));
Z1_3 = MAX(MinZ,(ZscaleR1*Z1_3h/100));
Z1_4 = MAX(MinZ,(ZscaleR1*Z1_4h/100));
Z1_5 = MAX(MinZ,(ZscaleR1*Z1_5h/100));
Z1_6 = MAX(MinZ,(ZscaleR1*Z1_6h/100));
Z2_1 = MAX(MinZ,(ZscaleR2*Z2_1h/100));
Z2_2 = MAX(MinZ,(ZscaleR2*Z2_2h/100));
Z2_3 = MAX(MinZ,(ZscaleR2*Z2_3h/100));
Z2_4 = MAX(MinZ,(ZscaleR2*Z2_4h/100));
Z2_5 = MAX(MinZ,(ZscaleR2*Z2_5h/100));
Z2_6 = MAX(MinZ,(ZscaleR2*Z2_6h/100));
Z2_7 = MAX(MinZ,(ZscaleR2*Z2_7h/100));
Z2_8 = MAX(MinZ,(ZscaleR2*Z2_8h/100));
Z2_9 = MAX(MinZ,(ZscaleR2*Z2_9h/100));
Z2_10 = MAX(MinZ,(ZscaleR2*Z2_10h/100));
Z2_11 = MAX(MinZ,(ZscaleR2*Z2_11h/100));
Z2_12 = MAX(MinZ,(ZscaleR2*Z2_12h/100));
// Ring 3 , key pots based on ring1 & ring2 heights
Z3_1 = IZ3(Z1_1,Z2_1);
Z3_4 = IZ3(Z1_2,Z2_3);
Z3_7 = IZ3(Z1_3,Z2_5);
Z3_10 = IZ3(Z1_4,Z2_7);
Z3_13 = IZ3(Z1_5,Z2_9);
Z3_16 = IZ3(Z1_6,Z2_11);
// interpolate for the other ring3 pots
Z3_2 = IP(Z3_1,Z3_4,1/3);
Z3_3 = IP(Z3_1,Z3_4,2/3);
Z3_5 = IP(Z3_4,Z3_7,1/3);
Z3_6 = IP(Z3_4,Z3_7,2/3);
Z3_8 = IP(Z3_7,Z3_10,1/3);
Z3_9 = IP(Z3_7,Z3_10,2/3);
Z3_11 = IP(Z3_10,Z3_13,1/3);
Z3_12 = IP(Z3_10,Z3_13,2/3);
Z3_14 = IP(Z3_13,Z3_16,1/3);
Z3_15 = IP(Z3_13,Z3_16,2/3);
Z3_17 = IP(Z3_16,Z3_1,1/3);
Z3_18 = IP(Z3_16,Z3_1,2/3);

// Ring 4, key pos based on ring 2 & ring3 heights
Z4_1 = IZ4(Z2_1,Z3_1);
Z4_5 = IZ4(Z2_3,Z3_4);
Z4_9 = IZ4(Z2_5,Z3_7);
Z4_13 = IZ4(Z2_7,Z3_10);
Z4_17 = IZ4(Z2_9,Z3_13);
Z4_21 = IZ4(Z2_11,Z3_16);
// interpolate for the other ring4 pots
Z4_2 = IP(Z4_1,Z4_5,1/4);
Z4_3 = IP(Z4_1,Z4_5,2/4);
Z4_4 = IP(Z4_1,Z4_5,3/4);
Z4_6 = IP(Z4_5,Z4_9,1/4);
Z4_7 = IP(Z4_5,Z4_9,2/4);
Z4_8 = IP(Z4_5,Z4_9,3/4);
Z4_10 = IP(Z4_9,Z4_13,1/4);
Z4_11 = IP(Z4_9,Z4_13,2/4);
Z4_12 = IP(Z4_9,Z4_13,3/4);
Z4_14 = IP(Z4_13,Z4_17,1/4);
Z4_15 = IP(Z4_13,Z4_17,2/4);
Z4_16 = IP(Z4_13,Z4_17,3/4);
Z4_18 = IP(Z4_17,Z4_21,1/4);
Z4_19 = IP(Z4_17,Z4_21,2/4);
Z4_20 = IP(Z4_17,Z4_21,3/4);
Z4_22 = IP(Z4_21,Z4_1,1/4);
Z4_23 = IP(Z4_21,Z4_1,2/4);
Z4_24 = IP(Z4_21,Z4_1,3/4);

union()
  {
  L = AF-WT; // Y axis step size considering wall thickness

  echo("Centre Pot");
  translate([0,0,Z0*0.5]) hexPotWithBase(AF,Z0,WT);

  if(Rings>0) {
    echo("Ring 1");
     DrawRing(1*L,[Z1_1, Z1_2, Z1_3, Z1_4, Z1_5, Z1_6],Enable6);
  }

  if(Rings>1) {
    echo("Ring 2");
    DrawRing(2*L,[Z2_1, Z2_2, Z2_3, Z2_4, Z2_5, Z2_6, Z2_7, Z2_8, Z2_9, Z2_10, Z2_11, Z2_12],Enable12);
  }

  if(Rings>2) {
    echo("Ring 3");
    DrawRing(3*L,[Z3_1, Z3_2, Z3_3, Z3_4, Z3_5, Z3_6, Z3_7, Z3_8, Z3_9, Z3_10, Z3_11, Z3_12, Z3_13, Z3_14, Z3_15, Z3_16, Z3_17, Z3_18],Enable18);
  }

  if(Rings>3) {
    echo("Ring4");
    DrawRing(4*L,[Z4_1, Z4_2, Z4_3, Z4_4, Z4_5, Z4_6, Z4_7, Z4_8, Z4_9, Z4_10, Z4_11, Z4_12, Z4_13, Z4_14, Z4_15, Z4_16, Z4_17, Z4_18, Z4_19, Z4_20, Z4_21, Z4_22, Z4_23, Z4_24],Enable24);
  }

 }

 Report();  // Print out the configurable variables

```


[User]
Hey, I'm looking to get a customisable utility key designed in OpenSCAD. I have some very specific needs for the design and I'm hoping you can help create a parametric model that I can easily tweak for my specific applications.

The key should start with a cylindrical base. I need to be able to control the height and diameter of this cylinder. I'd like to be able to adjust the height from 20mm to 70mm, stepping by 5mm, and the diameter between 11mm and 25mm, stepping by 0.5mm.

Next, I need a handle coming out of this cylinder. I want the ability to choose whether to have a single handle or a double handle. For single handles, the handle will extend outwards from the cylinder. For double handles, I'd like two handles to extend out from opposite sides of the cylinder (180 degrees apart). I need to be able to specify both the length of these handles, which should be measured from the wall of the cylinder, and their diameter. The handle length should be adjustable from 15mm to 80mm, in 5mm increments. The handle diameter should be adjustable from 7mm to 17mm, in 1mm increments.

The key will have a hole through the cylinder. I need to be able to choose either a square or triangular hole. I need to be able to control the hole's size, depth, and draft angle and also the triangle rotation, which means to rotate the triangle around its center. This size will be the diameter of a circle that is inscribed in the shape. The "depth" of this hole will go into the height of the cylindrical base, so be sure it accounts for that. For the square or triangle, the size should be adjustable from 6mm to 15mm, in 0.5mm increments. The depth should be adjustable from 5mm to 30mm, in 1mm increments, and the draft angle of the hole (how much the hole gets wider at the surface of the key) should be adjustable from 0 to 5, in 0.5mm increments. For the triangle, I would like to be able to rotate it by increments of 90 degrees from "up" (point up), to "left", "down", and "right".

I'd like to keep it printable so no need to worry about supports. I'm using millimeters so ensure that the whole model is working in that unit. I don’t need any external libraries for this, just standard OpenSCAD features.

[Assistant]
Customisable_Square_Triangle_key.scad:
```
/* [Model quality] */
$fn             =    0; // [0:Draft, 50:Good, 100:Ultra]
/* [Cylinder] */
Cylinder_height   =    35; // [20:5:70]
Cylinder_diameter =    16; // [11:.5:25]
/* [Handle] */
Layout            =     0; // [0:Single, 1:Double]
Handle_length     =    30; // [15:5:80]
Handle_diameter   =    10; // [7:17]
/* [Hole] */
Hole_shape        =     4; // [4:Square, 3:Triangle]
Triangle_rotation =     0; // [0:Up, 1:Left, 3:Right, 2:Down]
//Length of sides
Hole_size         =     9; // [6:.5:15]
Hole_depth        =    25; // [5:30]
Hole_draft        =     2; // [0:.5:5]

difference () {
    cylinder  (h = 10                ,
               d = Cylinder_diameter);
    translate ([0,0,4.5]) rotate ([0,90,0])
     cylinder (h      = 25.1 ,
               d      = 4.5  ,
               center = true);}

difference () {
    union () {
        cylinder  (h = Cylinder_height   ,
                   d = Cylinder_diameter);
        translate ([0,0,Handle_diameter/2])
         minkowski() {
             sphere(d=Handle_diameter);
             translate ([0,
                        +Handle_length*Layout
                        +Cylinder_diameter/2*Layout
                        -Handle_diameter/2*Layout
                        ,0])
             rotate ([0,0,180])
               cube([0.01,Handle_length-Handle_diameter/2
                    +(Handle_length-Handle_diameter/2)*Layout
                    +Cylinder_diameter/2
                    +Cylinder_diameter/2*Layout,
                    0.01]); } }
    translate ([0,0,Cylinder_height-Hole_depth])
     rotate ([0,0,45+135*Hole_shape+90*Triangle_rotation])
      cylinder (h   = Hole_depth+.1             ,
                d1  = (Hole_size-Hole_draft)
                      /cos((Hole_shape-1)*15.02),
                d2  = Hole_size
                      /cos((Hole_shape-1)*15.02),
                $fn = Hole_shape)               ;
    translate ([0,0,4.5]) rotate ([0,90,0])
      cylinder (h      = 20.1 ,
                d      = 4.5  ,
                center = true); }
```


[User]
I need a parametric OpenSCAD design for a simple whistle that can be used as a keychain or attached to a cord. The whistle should consist of a main body, a wind channel, and a sharp edge that creates the whistle sound, as well as a loop for attaching it to things.

I'd like to be able to control the overall length of the main body, the width of the body, and the radius of the fillets on the edges of the main body. The length parameter should control the overall length of the whistle *before* adding the loop, and the width parameter should control the dimensions of the main body's cross-section. The fillet radius should affect all the edges of the main body.

The wind channel should be created by cutting away a portion of the main body, and the sharp edge should also be cut into the main body, adjacent to the wind channel. I would like the size of these cuts to be proportional to the width of the body (e.g., the depth of the sharp edge could be 40% of the width, and the width of the wind channel could be 30% of the width). It also has some rounded edges, controlled by the same fillet radius parameter as the main body.

Finally, the loop should be attached to the side of the main body, and the loop's size should also be controlled by the width parameter. The loop should have a circular hole through it. I'd like the loop to have a consistent thickness, and it should be as wide as the main body. Its length should be related to the width as well - I'm not sure what value makes the most sense.

I expect this to be defined in a single OpenSCAD file. I'm looking for a design that is fully parametric. I want to be able to easily adjust all the parameters I have mentioned. I don't have any strong requirements for tolerances, but it should be designed so that it could be printed. I am not using any particular libraries, so just basic OpenSCAD commands should work fine. Thanks!

[Assistant]
whistle.scad:
```
// Length of the whistle without loop
length = 40; //[20:100]

// Width of both sides of the whistle
width = 10; //[6:20]

// Must be less or equal to half of the width
fillet_radius = 2; //[0:20]

module korpus_aussen(laenge=60, dicke=10, rundung=1.0)
{
    if (rundung>0) {
        distanz = dicke/2-rundung;
        if (distanz<=0) {
            translate([0, 0, laenge/2])
            cylinder(d=dicke, h=laenge, center=true);
        } else {
            linear_extrude(laenge)
            hull() {
                translate([-distanz,  distanz, 0]) circle(r=rundung);
                translate([ distanz,  distanz, 0]) circle(r=rundung);
                translate([ distanz, -distanz, 0]) circle(r=rundung);
                translate([-distanz, -distanz, 0]) circle(r=rundung);
            }
        }
    } else {
        translate([0, 0, laenge/2])
        cube([dicke, dicke, laenge], center=true);
    }
}

module korpus(laenge=60, dicke=10, wandstaerke=1.0, rundung=2.0)
{
    difference() {
        korpus_aussen(laenge=laenge, dicke=dicke, rundung=rundung);
        translate([0, 0, laenge/2])
        cylinder(d=dicke-2*wandstaerke, h=laenge+2*wandstaerke, center=true);
    }
}

module windkanal(laenge=10, dicke=10, oeffnung=1.0, rundung=2.0)
{
    intersection() {
        korpus_aussen(laenge=laenge, dicke=dicke, rundung=rundung);
        translate([-dicke/2, dicke/2, 0])
        rotate([90, 0, 0])
        linear_extrude(dicke)
        polygon([
            [dicke-oeffnung, 0],
            [0, laenge],
            [0, 0],
        ]);
    }
}

module schneidekante(laenge=6, dicke=10, toleranz=1, tiefe=4)
{
    translate([dicke/2-tiefe, -(dicke+toleranz)/2, 0])
    rotate([-90, 0, 0])
    linear_extrude(dicke+toleranz)
    polygon([
        [0, 0],
        [tiefe+toleranz, (tiefe+toleranz)*laenge/tiefe],
        [tiefe+toleranz, 0],
    ]);
}

module pfeife(laenge=50, dicke=10, windkanal_laenge=10, rundung=2)
{
    difference() {
        union() {
            korpus(laenge=laenge, dicke=dicke, rundung=rundung);
            translate([0, 0, laenge-windkanal_laenge])
            windkanal(laenge=windkanal_laenge, dicke=dicke, oeffnung=0.3*dicke, rundung=rundung);
        }
        translate([0, 0, laenge-windkanal_laenge])
        schneidekante(laenge=0.6*dicke, dicke=dicke, tiefe=0.4*dicke);
    }
}

module oese_seitenanschnitt(hoehe, rundung, dicke)
{
    translate([dicke/2, 0, 0])
    rotate([0, -90, 0])
    linear_extrude(dicke)
    hull() {
        translate([rundung, 0, 0]) circle(r=rundung);
        translate([hoehe+rundung, 0, 0]) circle(r=rundung);
    }
}

module oese(dicke=10, steg=4, rundung=2)
{
    hoehe = dicke + steg/2;
    anschnitt_r = (dicke-steg)/2;
    loch_r = (dicke-steg)/2;
    difference() {
        intersection() {
            difference() {
                korpus_aussen(laenge=hoehe, dicke=dicke, rundung=rundung);
                translate([0, anschnitt_r+steg/2, steg/2])
                oese_seitenanschnitt(hoehe=hoehe, rundung=anschnitt_r, dicke=2*dicke);
                translate([0, -anschnitt_r-steg/2, steg/2])
                oese_seitenanschnitt(hoehe=hoehe, rundung=anschnitt_r, dicke=2*dicke);
            }
            union() {
                translate([0, dicke/2, hoehe-dicke/2])
                rotate([90, 0, 0])
                cylinder(d=dicke, h=dicke);
                korpus_aussen(laenge=hoehe-dicke/2, dicke=dicke, rundung=rundung);
            }
        }
        translate([0, dicke/2, hoehe-loch_r-steg/2])
        rotate([90, 0, 0])
        cylinder(r=loch_r, h=dicke);
    }
}

module pfeife_mit_oese(laenge=40, dicke=10, rundung=2)
{
    rotate([0, 0, -90]) {
        translate([0, 0, laenge])
        rotate([180, 0, 0])
        pfeife(laenge=laenge, dicke=dicke, rundung=rundung);
        translate([0, 0, laenge])
        rotate([0, 0, 0])
        oese(dicke=dicke, rundung=rundung);
    }
}

pfeife_mit_oese(laenge=length, dicke=width, rundung=fillet_radius, $fn=24);
```


[User]
Hi, I'm looking to create a parametric 3D model for a plant pot and a blank cap, inspired by the "Lilo" indoor garden by Prêt à Pousser. I'd like the design to be fully customizable via OpenSCAD.

Here's a breakdown of what I need:

**Overall Structure:**

*   I need two distinct models: a "pot" and a "cap".
*   Both should have a rounded rectangular base with a cage-like structure extending upwards.
*   The overall shape of the cage should taper from a wider top to a narrower bottom.
*   I'd like all the dimensions to be parametric so I can easily adjust them.

**Pot Specifics:**

*   The pot will have a flat, rounded rectangular base. The sides will extend vertically upwards, then begin to slope inward toward the base (truncated cone shape).
*   The sides of the pot should be "cage-like" with horizontal bars and vertical slits, allowing airflow. The sides should have a certain wall thickness.
*   The number of horizontal bars and the bar thickness should be adjustable. Also, the amount of vertical space between the bars (vertical slits) should be configurable based on the wall thickness parameter.
*   I want to be able to choose whether or not to include support structures inside the pot, and if they are included, they should follow the shape of the cage walls.
*   I need an option to have openings at the bottom of the pot. These can be controlled by a "bottom openings" parameter, with options for "none", "some", and "more" and should look like pie-shaped wedges. If present, the openings should also include support structures similar to the horizontal bars from the main cage.
*  I'd also like a parameter to control the "roundness" of all rounded sections (the $fn parameter).
*   Please provide a way to generate the base, the cage, and the openings separately for easier manipulation.

**Cap Specifics:**

*   The cap also has a flat, rounded rectangular base that matches the pot and a cage structure on top.
*   The cap should be a simple, blank cap that fits the pot.
*  Like the pot, the cap should have a $fn parameter to control roundness.

**Parameters:**

I'd like to have the following parameters available for customization in the OpenSCAD file:

*   `CAP_LENGTH`
*   `CAP_WIDTH`
*   `CAGE_LENGTH`
*   `CAGE_WIDTH_TOP`
*   `CAGE_WIDTH_BOTTOM`
*   `ROUNDING_RADIUS`
*   `WALL_THICKNESS`
*   `BOTTOM_OPENINGS` (options: "none", "some", "more")
*   `GRILLE_HORIZONTAL_BAR`
*   `GRILLE_HORIZONTAL_COUNT`
*   `CAGE_HEIGHT`
*   `OBJECT_TO_PRINT` (options: "pot", "cap")
*   `SUPPORT_STRUCTURES` (boolean)
*   `ROUNDNESS` (This is for $fn)

I think that should cover everything. Can you generate the OpenSCAD file(s) for this, please? I don't need you to generate example usage in the code, I will handle that myself. I am expecting the code to use `hull()` and `linear_extrude()` to achieve the rounded rectangular shapes. I expect the code to not use any external libraries or includes.

[Assistant]
lilo.scad:
```
// Subject: Prêt à Pousser "Lilo" v2019, https://pretapousser.de/
//
// This 3D model is published under CC BY-SA 4.0 by Stefan Schönberger <mail@sniner.net>

// About presets:
// WALL_THICKNESS: 1.2 mm; slightly thicker for a better print quality, measured: 1.0 mm
// CAGE_WIDTH_TOP: 22.0 mm; reduced for a better fit, measured 22.4 mm
// CAGE_HEIGHT: 58.0 mm; enlarged because of dome shaped bottom, measured: 53.0 mm

/* [Hidden] */
CAP_LENGTH              =  101.2;
CAP_WIDTH               =   24.4;
CAGE_LENGTH             =   90.0;
CAGE_WIDTH_TOP          =   22.0;
CAGE_WIDTH_BOTTOM       =   19.3;
ROUNDING_RADIUS         =    5.0; //[4:9]

/* [Customization] */
WALL_THICKNESS          =    1.2; //[1.0:0.1:2.0]
BOTTOM_OPENINGS         = "some"; //[none,some,more]
GRILLE_HORIZONTAL_BAR   =    2.0; //[1.0:0.1:8.0]
GRILLE_HORIZONTAL_COUNT =      6; //[1:10]
CAGE_HEIGHT             =   58.0; //[55:70]

/* [Model parameter] */
OBJECT_TO_PRINT         =  "pot"; //[pot,cap]
SUPPORT_STRUCTURES      =   true; //[true,false]
ROUNDNESS               =     48; //[24,48,96]

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

function spacing__object_length(length, spacer_length, count) =
    count>0 ? (length - spacer_length*(count-1))/count : length;

module spacing__place_object(length, spacer_length, count, v=[1, 0, 0])
{
    obj_length = spacing__object_length(length, spacer_length, count);
    for (i=[0:count-1]) {
        translate(v*i*(obj_length+spacer_length)) children();
    }
}

module spacing__place_spacer(length, spacer_length, count, v=[1, 0, 0])
{
    if (count>1) {
        obj_length = spacing__object_length(length, spacer_length, count);
        for (i=[0:count-2]) {
            translate(v*(obj_length+i*(obj_length+spacer_length))) children();
        }
    }
}

module spacing__place_spacer_lr(length, spacer_length, count, v=[1, 0, 0])
{
    spacing__place_spacer(length, spacer_length, count, v) children();
    translate(v*(-spacer_length)) children();
    translate(v*length) children();
}

module spacing__place_objects(length, spacer_length, count, v=[1, 0, 0])
{
    obj_length = spacing__object_length(length, spacer_length, count);
    for (i=[0:count-1]) {
        translate(v*i*(obj_length+spacer_length)) children(0);
    }
    if (count>1)
        for (i=[0:count-2]) {
            translate(v*(obj_length+i*(obj_length+spacer_length))) children(1);
        }
}

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

module rounded_plate(length, width, height)
{
    translate([0, 0, -height/2])
    linear_extrude(height)
    hull() {
        translate([-length/2+width/2, 0, 0]) circle(d=width);
        translate([length/2-width/2, 0, 0]) circle(d=width);
    }
}

module cage_form(length, width_top, width_bottom, height, r=5)
{
    hull() {
        // top
        difference() {
            union() {
                translate([length/2-r, width_top/2-r, 0]) sphere(r=r);
                translate([-length/2+r, width_top/2-r, 0]) sphere(r=r);
                translate([length/2-r, -width_top/2+r, 0]) sphere(r=r);
                translate([-length/2+r, -width_top/2+r, 0]) sphere(r=r);
            }
            translate([0, 0, (r+2)/2]) cube([length+2, width_top+2, r+2], center=true);
        }
        // bottom
        r2 = width_bottom/2;
        translate([-length/2+r2, 0, -height+r2]) sphere(r=r2);
        translate([length/2-r2, 0, -height+r2]) sphere(r=r2);
    }
}

module cage_base(
    length=CAGE_LENGTH,
    width_top=CAGE_WIDTH_TOP,
    width_bottom=CAGE_WIDTH_BOTTOM,
    height=CAGE_HEIGHT,
    plate_length=CAP_LENGTH,
    plate_width=CAP_WIDTH,
    r=ROUNDING_RADIUS,
) {
    union() {
        difference() {
            union() {
                cage_form(length, width_top, width_bottom, height, r);
                translate([0, 0, WALL_THICKNESS/2])
                    rounded_plate(length=plate_length, width=plate_width, height=WALL_THICKNESS);
            }
            translate([0, 0, WALL_THICKNESS+0.001])
                cage_form(
                    length=length-2*WALL_THICKNESS,
                    width_top=width_top-2*WALL_THICKNESS,
                    width_bottom=width_bottom-2*WALL_THICKNESS,
                    height=height,
                    r=r-WALL_THICKNESS
                );
        }
    }
}

module cage_openings(
    length=CAGE_LENGTH-CAGE_WIDTH_BOTTOM,
    width=CAGE_WIDTH_TOP,
    height=CAGE_HEIGHT/2,
    vertical_slit=WALL_THICKNESS,
    vertical_count=CAGE_HEIGHT/WALL_THICKNESS/6,
    horizontal_bar=GRILLE_HORIZONTAL_BAR,
    horizontal_count=GRILLE_HORIZONTAL_COUNT
) {
    object_length = spacing__object_length(length, horizontal_bar, horizontal_count);
    translate([-length/2, -width/2, 0])
    spacing__place_object(height, 0, vertical_count, v=[0, 0, 1]) {
        spacing__place_object(length, horizontal_bar, horizontal_count)
            cube([object_length, width, vertical_slit]);
    }
}

module cage_support_part(width, thickness=GRILLE_HORIZONTAL_BAR)
{
    translate([0, 0, width/2])
    rotate([0, 90, 0])
    linear_extrude(thickness)
    difference() {
        intersection() {
            circle(d=width);
            translate([width/4, 0, 0]) square([width/2, width], center=true);
        }
        difference() {
            translate([0, 0, 0]) rotate([0, 0, 45]) square(width*sin(45), center=true);
            translate([width-1.5*WALL_THICKNESS, 0, 0]) square(width, center=true);
        }
    }
}

module cage_support(
    length=CAGE_LENGTH-CAGE_WIDTH_BOTTOM,
    width=CAGE_WIDTH_BOTTOM,
    count=GRILLE_HORIZONTAL_COUNT
) {
    translate([-length/2, 0, 0.5*WALL_THICKNESS])
    spacing__place_spacer_lr(length, GRILLE_HORIZONTAL_BAR, count)
        cage_support_part(width-0.5*WALL_THICKNESS);
}

module bottom_openings(
    length=CAGE_LENGTH-CAGE_WIDTH_BOTTOM,
    width=CAGE_WIDTH_BOTTOM,
    count=GRILLE_HORIZONTAL_COUNT,
    more=true
) {
    a = 360/16;
    angles = more ? [-a, -a*2, -a*3, -a*5, -a*6, -a*7] : [-a*2, -a*3, -a*5, -a*6];
    translate([-length/2, 0, 0])
    difference() {
        for (a = angles) {
            rotate([a, 0, 0]) translate([length/2, 0, 0])
                cube([length, width+2, WALL_THICKNESS], center=true);
        }
        spacing__place_spacer(length, GRILLE_HORIZONTAL_BAR, count) {
            translate([GRILLE_HORIZONTAL_BAR/2, 0, 0]) rotate([0, 90, 0])
            cylinder(d=width+4, h=GRILLE_HORIZONTAL_BAR, center=true);
        }
        translate([-1, -(width+6)/2, 0]) cube([length+2, width+6, width/2+3]);
    }
}

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

module pot(
    length=CAGE_LENGTH,
    width_top=CAGE_WIDTH_TOP,
    width_bottom=CAGE_WIDTH_BOTTOM,
    height=CAGE_HEIGHT,
    plate_length=CAP_LENGTH,
    plate_width=CAP_WIDTH,
    r=ROUNDING_RADIUS,
    support=SUPPORT_STRUCTURES,
    bottom_openings=BOTTOM_OPENINGS
)
{
    translate([0, 0, WALL_THICKNESS]) rotate([180, 0, 0])
    difference() {
        union() {
            difference() {
                cage_base(length, width_top, width_bottom, height, plate_length, plate_width, r);
                if (bottom_openings!="none") {
                    translate([0, 0, -height+width_bottom/2])
                        bottom_openings(
                            length=length-width_bottom,
                            width=width_bottom,
                            more=bottom_openings=="more"
                        );
                }
            }
            if (support) {
                intersection() {
                    translate([0, 0, -height])
                        cage_support(
                            length=length-width_bottom,
                            width=width_bottom
                        );
                    cage_form(
                        length-WALL_THICKNESS,
                        width_top-WALL_THICKNESS,
                        width_bottom-WALL_THICKNESS,
                        height-WALL_THICKNESS,
                        r=r-WALL_THICKNESS/2
                    );
                }
            }
        }
        translate([0, 0, -(height-width_bottom/2)])
            cage_openings(
                length=length-width_bottom,
                width=width_top,
                height=height/2,
                vertical_count=height/WALL_THICKNESS/6
            );
    }
}

module blank_cap(
    length=CAGE_LENGTH,
    width_top=CAGE_WIDTH_TOP,
    width_bottom=CAGE_WIDTH_BOTTOM,
    height=CAP_WIDTH,
    plate_length=CAP_LENGTH,
    plate_width=CAP_WIDTH,
    r=ROUNDING_RADIUS
)
{
    rotate([180, 0, 0])
    translate([0, 0, -WALL_THICKNESS/2])
    union() {
        rounded_plate(length=plate_length, width=plate_width, height=WALL_THICKNESS);
        cage_form(length, width_top, width_bottom, height, r);
    }
}

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

if (OBJECT_TO_PRINT=="cap") {
    blank_cap($fn=ROUNDNESS);
} else {
    pot($fn=ROUNDNESS);
}

```