[User]
I'm looking to create a stand for drying out large water bottles, like those 5-gallon jugs, upside down. I want it to be customizable for different bottle neck sizes and printer bed sizes. I have a general idea of the shape I want, kind of like a flared vase, with the bottle neck fitting snugly at the top.

Here's what I need:

*   **Parametric Design:** I want to be able to adjust the neck diameter and the base diameter via parameters.
*   **Bottle Neck Support:** The top needs to be designed to hold the bottle upside down securely. I don't want the bottle to just slip through. Maybe a lip or a taper that grabs the bottle neck at the right point.
*   **Base:** The base needs to be stable. A wider base would work well. I'd like to be able to specify the base diameter too.
*   **Test Print Feature:** It would be great to have a test piece option to check the neck size fit before printing the whole thing.  This test piece should be a simple cylinder that represents the inside of the neck-holding area at the top.
*   **Drainage:** I'd like to incorporate some sort of drainage features. Maybe small cutouts on the lip to allow air to flow. I also don't want the bottle neck to press flush against the bottom of the support so some additional gap there to let air get around that area too.
*   **Smooth Curves:** The overall shape should have smooth transitions, so no sharp corners. I think quad curves would be good for this.
*   **OpenSCAD:** I need this to be an OpenSCAD file (or multiple if that makes it easier). I'm familiar with basic OpenSCAD usage.

Here's how I was thinking the parameters should work:

*   `Neck_Size`: Diameter of the bottle neck opening.
*  `Base_Size`: The diameter of the base.
*   `Views`: an enum with values like 0 for the full stand and 1 for the test piece

Could you generate the necessary OpenSCAD code to accomplish this? Also, please assume I have no libraries, so please do not use them in your response.

[Assistant]
WaterBottleStand.scad:
```
/* [View] */
Views = 0; //[0:Full View,1:Test]
/* [Options] */
Neck_Size = 44; //[30:50]
Base_Size = 230; //[200:250]
/* [Hidden] */
span = (Base_Size-Neck_Size)/2;
offs = Base_Size - 230;
ep0 = [Neck_Size/2,24];
ep1 = [Neck_Size/2+span*.375,12];
ep2 = [Neck_Size/2+span*.81,12];
pts = concat([[0,0],[0,110]],quadcurve([0,110],[Neck_Size/2-.5,100],[14,110]),quadcurve(ep0,ep1,[Neck_Size/2+span*.189,22]),quadcurve(ep1,ep2,[Neck_Size/2+span*.675,-4]),quadcurve(ep2,[Base_Size/2,0],[Neck_Size/2+span*1.118,50]));

module waterStand(){
  difference(){
    rotate_extrude($fn=80)
    polygon(points=pts);
    for(i=[0:360/(Neck_Size<40 ? 5:6):360]){
      rotate([0,0,i])
      translate([Neck_Size/2-.5,0,24])
      downspout();
    }
  }
}

module downspout(){
  translate([0,0,3])
  union(){
    translate([0,0,80])
    rotate([180,0,0])
    linear_extrude(height=80,convexity=10,scale=[1,3],$fn=80)
    circle(3);
    translate([3,0,0])
    rotate([-90,0,180])
    rotate_extrude(angle=85,$fn=80)
    translate([3,0,0])
    scale([1,3])
    circle(3);
    translate([2.735,0,-2.98])
    rotate([0,95,0])
    scale([1,3,1])
    cylinder(25,3,4,$fn=80);
  }
}

module test(){
    translate([0,0,-22])
    union(){
        intersection(){
            waterStand();
            translate([0,0,23])
            cylinder(80,r=Neck_Size/2+1);
        }
        translate([0,0,22])
        cylinder(2,r=30);
    }
}

if(Views == 0){
    waterStand();
}else{
    test();
}

function quadcurve(start=[30,5],end=[0,10],control=[-8,-10]) = [for(t=[0.0:.05:1.0]) [(1 - t)*(1 - t)*start.x + 2*(1 - t)*t*control.x + t*t*end.x, (1 - t)*(1 - t)*start.y + 2*(1 - t)*t*control.y + t*t*end.y]];
```


[User]
I'd like to design a birdhouse, but I want it to be parametric and easily customizable. I envision a two-part construction: a main box and a separate faceplate that attaches with screws. I've got some basic dimensions in mind, but they should all be adjustable via parameters.

Here’s what I’m thinking:

**Overall Structure:**

*   **Main Box:** A rectangular prism with a hollowed-out interior. The main dimensions should be `bh_height`, `bh_width`, and `bh_length`. I'd like to define a wall thickness, `wall_thick`, which affects the interior space. I need a way to cut out the top face so the faceplate sits flush.
*   **Faceplate:**  A separate piece that fits into the opening of the box. It should have a matching outline to sit in the cutout of the main box. The height of the faceplate is `f_height`, the width is `f_width` which are derived from the base dimensions and wall thickness. I also need to add support 'flanges' that provide extra stability and will be screwed into the main box; I imagine these as angled cubes that protrude out the back of the faceplate.
*   **Attachment:**  The faceplate needs screw holes that align with the main box. The screws will have a diameter specified by `screw_r` and countersunk head diameter of `screwh_r` with height of `screwh_h`.

**Key Features:**

*   **Entrance Hole:** A circular opening in the faceplate, defined by `bh_opening` diameter and `bh_opening_height` to position it vertically from the base of the faceplate.
*   **Perch:** A small cylindrical perch extending from below the opening, with a radius of 5 and length of 50.
*   **Drainage:**  I'd like a few small drainage holes in the bottom of the main box, perhaps four holes at each corner of the base.
*   **Ventilation:**  I need a few ventilation holes, preferably on the upper sides. Three holes, horizontally aligned, would probably do.

**Parameters:**

I'd like all of the following to be user-definable variables:

*   `bh_height`: Overall height of the main box.
*   `bh_width`: Overall width of the main box.
*   `bh_length`: Overall length (depth) of the main box.
*   `bh_opening_height`: Vertical position of the center of the entrance hole relative to the base of the faceplate.
*   `bh_opening`: Diameter of the entrance hole.
*   `wall_thick`: Thickness of the walls of the box.
*   `screw_r`: Radius of the screw holes.
*   `screwh_r`: Radius of the countersunk screw head.
*    `screwh_h`: Height of the countersunk portion of the hole
*  `offset`: A parameter that defines the distance the faceplate is offset from the body.

**Output:**

I would like two SCAD files: one for the main box, and one for the faceplate, which should include the screw holes that align correctly.  Could you design these with those in mind?  I'm not using any external libraries, so everything should be written from scratch. Thanks!

[Assistant]
birdhouse.scad:
```

bh_height = 150;
bh_width = 100;
bh_length = 110;
bh_opening_height = 100;
bh_opening = 38;
wall_thick=10;
screw_r = 3.8/2;
screwh_r=4;
screwh_h=3;
offset=wall_thick+3;
f_height=bh_height-0.5*wall_thick;
f_width=bh_width-wall_thick;

////////////////////////////////
// Lets make some birdhouses //
//////////////////////////////
module birdhouse_solid(){
translate([-bh_height/2-wall_thick,-bh_width/2,0]) difference(){
        cube([bh_height, bh_width, bh_length]);
        //clear center
        translate([wall_thick,wall_thick,wall_thick+0.1]) resize([bh_height, bh_width-2*wall_thick, bh_length])  cube([bh_height, bh_width, bh_length]);
        //inset
//        translate([0.5*wall_thick,0.5*wall_thick,bh_length-1.5*wall_thick]) resize([bh_height-wall_thick, bh_width-wall_thick, bh_length])  cube([bh_height, bh_width, wall_thick]);
        //cut off face area
        translate([-1,-1,bh_length-wall_thick]) cube([bh_height+1, bh_width+2, bh_length]);
		}
	}

module birdhouse_face(){

}

module roof(){
	difference(){
		translate([0,0,0]) cube([15, bh_width, bh_length+40]);
		translate([22,-10,10]) rotate([0,-wall_thick,0]) cube([100, bh_width+30, bh_length+40]);
	}
}

/////////////////////////////
// Decide what to display //
///////////////////////////

module plate() {

                    //translate([0,-1,0]) rotate([0,-60,0]) cube([20, bh_width+2, 15]);
                    //main face plate
                    color("Red") difference(){
                        union(){
                            translate([0,0,0]) cube([f_height, f_width, wall_thick/2]);

                        }
                        //flanges - z by 2mm to get
                        translate([-1,0,2]) rotate([60,0,0]) cube([f_height+30, 15, 15]);
                        translate([-1,f_width,2]) rotate([30,0,0]) cube([f_height+30, 15, 15]);
                        translate([0,-1,2]) rotate([0,-60,0]) cube([20, f_width+2, 15]);
                    }
}

module face() {
    difference() {
        translate([0,0,bh_length-12]) translate([-0.5*bh_height-0.5*wall_thick,-0.5*f_width,0])
		difference(){
			union(){
                    plate();
                    scale([1,1,-1]) plate();
                    //surround
                    translate([bh_opening_height-50,f_width/4,wall_thick/2]) cube([80, f_width/2, 10]);
                    //perch
                    translate([bh_opening_height-40,f_width/2,wall_thick/2]) cylinder(r=5, h=50);
                    }

            //entry and extras
                //entry hole
                translate([bh_opening_height,f_width/2,-15]) cylinder(r=bh_opening/2, h=60);

				}
    //screw holes
    translate([0.5*bh_height-wall_thick-5,-0.5*bh_width+wall_thick+5,50]) cylinder(r=screw_r, h=900, center=true);
    translate([-0.5*bh_height+wall_thick-5,-0.5*bh_width+wall_thick+5,50]) cylinder(r=screw_r, h=900, center=true);
    translate([0.5*bh_height-wall_thick-5,0.5*bh_width-wall_thick-5,50]) cylinder(r=screw_r, h=900, center=true);
    translate([-0.5*bh_height+wall_thick-5,0.5*bh_width-wall_thick-5,50]) cylinder(r=screw_r, h=900, center=true);

    translate([0.5*bh_height-wall_thick-5,-0.5*bh_width+wall_thick+5,bh_length-8]) cylinder(r1=screw_r, r2=screwh_r, h=screwh_h, center=true);
    translate([-0.5*bh_height+wall_thick-5,-0.5*bh_width+wall_thick+5,bh_length-8]) cylinder(r1=screw_r, r2=screwh_r, h=screwh_h, center=true);
    translate([0.5*bh_height-wall_thick-5,0.5*bh_width-wall_thick-5,bh_length-8]) cylinder(r1=screw_r, r2=screwh_r, h=screwh_h, center=true);
    translate([-0.5*bh_height+wall_thick-5,0.5*bh_width-wall_thick-5,bh_length-8]) cylinder(r1=screw_r, r2=screwh_r, h=screwh_h, center=true);

	}
}

module old() {
					//Body Trim
					translate([-20,bh_width,bh_length-6]) rotate([45,0,0]) cube([bh_height+20, 10, 10]);
					translate([-20,0,bh_length-6]) rotate([45,0,0]) cube([bh_height+20, 10, 10]);
					translate([-wall_thick,0,bh_length+1]) rotate([0,45,0]) cube([10, bh_width+20, 10]);
					//Flange Trim
					translate([bh_opening_height+25,0,bh_length+11]) rotate([0,45,0]) cube([10, bh_width+20, 10]);
					translate([bh_opening_height-59,0,bh_length+11]) rotate([0,45,0]) cube([10, bh_width+20, 10]);
					translate([bh_opening_height-55,bh_width/4,bh_length+6]) rotate([45,0,0]) cube([90, 10, 10]);
					translate([bh_opening_height-55,bh_width/4*3,bh_length+6]) rotate([45,0,0]) cube([90, 10, 10]);

}

module box(){
	color("Turquoise"){
			union() {
                //screw anchors
 				difference(){
                    union(){
                        translate([-0.5*bh_height,bh_width/2-wall_thick-10,10]) cube([10,10,83]);
                        translate([-0.5*bh_height,-0.5*bh_width+wall_thick,10]) cube([10,10,83]);
                        translate([0.5*bh_height-wall_thick-10,bh_width/2-wall_thick-10,10]) cube([10,10,83]);
                        translate([0.5*bh_height-wall_thick-10,-0.5*bh_width+wall_thick,10]) cube([10,10,83]);

                        }
                    translate([0.5*bh_height-wall_thick-5,-0.5*bh_width+wall_thick+5,50]) cylinder(r=screw_r, h=90, center=true);
                    translate([-0.5*bh_height+wall_thick-5,-0.5*bh_width+wall_thick+5,50]) cylinder(r=screw_r, h=90, center=true);
                    translate([0.5*bh_height-wall_thick-5,0.5*bh_width-wall_thick-5,50]) cylinder(r=screw_r, h=90, center=true);
                    translate([-0.5*bh_height+wall_thick-5,0.5*bh_width-wall_thick-5,50]) cylinder(r=screw_r, h=90, center=true);

                    }

				difference(){
					birdhouse_solid();
                    //face recess
                    translate([-0.5*bh_height-0.5*wall_thick,-0.5*f_width,bh_length-9.9]) resize([f_height+0.4,f_width+0.4,wall_thick+0.4])
                    union() {
                        plate();
                        scale([1,1,-1]) plate();
                        }

                    //rear screw holes
 					translate([0.25*bh_height+5,0,0]) cylinder(r=screw_r, h=40, center=true);
 					translate([0,0,0]) cylinder(r=screw_r, h=40, center=true);
 					translate([-0.25*bh_height,0,0]) cylinder(r=screw_r, h=40, center=true);
                    translate([0.25*bh_height+5,0,9])  cylinder(r1=screw_r, r2=screwh_r, h=screwh_h, center=true);
                    translate([0,0,9])  cylinder(r1=screw_r, r2=screwh_r, h=screwh_h, center=true);
                    translate([-0.25*bh_height,0,9]) cylinder(r1=screw_r, r2=screwh_r, h=screwh_h, center=true);
                    //drain holes
 					translate([-0.5*bh_height,15,bh_length/2+15]) rotate([0,90,0]) cylinder(r=2, h=60, center=true);
 					translate([-0.5*bh_height,15,bh_length/2-15]) rotate([0,90,0]) cylinder(r=2, h=60, center=true);
 					translate([-0.5*bh_height,-15,bh_length/2+15]) rotate([0,90,0]) cylinder(r=2, h=60, center=true);
 					translate([-0.5*bh_height,-15,bh_length/2-15]) rotate([0,90,0]) cylinder(r=2, h=60, center=true);

					// Ventilation Holes
					translate([bh_height/2-30,bh_width+90,bh_length/2]) rotate([90,90,0]) cylinder(r=5, h=300);
					translate([bh_height/2-30,bh_width+90,bh_length/2+20]) rotate([90,90,0]) cylinder(r=5, h=300);
					translate([bh_height/2-30,bh_width+90,bh_length/2-20]) rotate([90,90,0]) cylinder(r=5, h=300);
				}

				translate([0.5*bh_height-wall_thick,-0.5*bh_width,0]) roof();
			}
		}
}

//	MAIN
//box();
face();

```


[User]
Hey, I'm looking for some help designing a customizable spice rack. I've got some specific requirements in mind, and I was hoping you could generate the necessary OpenSCAD files.

Essentially, I want a modular system that uses dovetail joints to connect shelves, uprights, and feet. I'd like to be able to adjust the design using parameters for the spice container dimensions, tolerances for the dovetail joints, the angle of the shelves, and spacing between shelves. I also want to be able to create individual parts (shelves, uprights, or feet) or the entire assembled rack.

Here's a breakdown of what I need:

**Parameters:**

*   **`Part`**: A string that lets me choose what to generate – "All" for everything, "Shelf" for just the shelf, "Uprights" for just the uprights, or "Foot" for just the foot.
*   **`Container_Diameter`**: The diameter of the spice containers.
*   **`Container_Height`**: The height of the spice containers.
*   **`Dovetail_Tolerance`**: A small value for adjusting the fit of the dovetail joints.
*   **`Shelf_Angle`**: The angle of the shelf.
*   **`spaceBetweenRows`**: How much space to leave above spice containers before the next row of shelves.

**Components:**

*   **Shelf:** A plank with a backstop and dovetails on the sides to connect to the uprights and other shelves. This shelf should have a hole to fit spice containers. It should also be angled, as per the `Shelf_Angle` parameter.
*   **Uprights:** Two upright pieces with dovetails on the bottom to connect to the feet and dovetails on the side to connect to the shelves, angled to match `Shelf_Angle`. The uprights need a female dovetail cut into the top to match the male dovetail at the bottom of the upright piece above.
*   **Foot:** A base piece that connects to the bottom of the uprights using dovetails.

**Specifics:**

*   Dovetails should be tapered, and you can use a fixed taper angle of 80 degrees.
*   I'd like the option to add a fillet to the dovetails to make them a bit smoother.
*   The shelf should have a backstop to keep containers from sliding off.
*   I need male and female dovetail modules with the ability to specify tolerance.
*   Please make sure to calculate all the measurements for the dovetails, the backstop, the shelf and uprights using the given parameters.
*   I also need all the elements to be assembled using a modular approach.

It'd be great if you could provide the design as a few different .scad files, one for each module (the basic shapes such as the dovetail), then others for assembling the parts (shelf, uprights, foot, and complete assembly). No specific library requests at this time.

Thanks!

[Assistant]
Parametric_Spice_Rack_v5.scad:
```
///////////////////////////////////////////////////////
// Parameters
///////////////////////////////////////////////////////

/* [Part selection] */
Part = "All"; //[All, Shelf, Uprights, Foot]

/* [Spice Container Measurements] */
Container_Diameter = 46;
Container_Height = 90;

/* [Tolerances] */
//Difference in dovetail parts to allow a better fit
Dovetail_Tolerance = 0.025;

/* [Other] */
//Degrees the spice shelf is angled by
Shelf_Angle = 10; //[0:45]

//End of parameters
module endParam(){} endParam();

///////////////////////////////////////////////////////
// Back-end params - edit with caution
///////////////////////////////////////////////////////

//epsilong, extra amount to avoid surface overlap
eps = 0.01;
//Level of detail on rounded parts
$fn = 64;

//Width of shelf, not counting dovetail
shelfWidth = Container_Diameter;
shelfLength = Container_Height;

//Thickness of shelf
//This shouldn't go lower than 10. The dovetails lose definition due to rounding
shelfThickness = 10;

//Thickness of backstop piece that stops spice container from sliding out
backstopThickness = 2;

//Space to leave around dovetails (total, not per side)
DovetailSpacing = 4; //changed from 2

//Dovetail sizing
dovetailMaxWidth = shelfThickness-DovetailSpacing;
dovetailMaxDepth = shelfThickness-DovetailSpacing; //changed from dovetailMaxWidth/DovetailSpacing
dovetailLength = shelfLength/2;

//How much space to add above spice containers before the next row
spaceBetweenRows = 5;

//Upright measurements
tiltedDovetailZWidth = DovetailSpacing + (dovetailMaxWidth*cos(Shelf_Angle));
uprightHeight = tiltedDovetailZWidth + (Container_Diameter / cos(Shelf_Angle)) + spaceBetweenRows;
uprightWidth = (shelfLength/2 + DovetailSpacing/2) * cos(Shelf_Angle);
uprightWidthTilted = uprightWidth / cos(Shelf_Angle);
uprightThickness = shelfThickness;

//Foot measurements
footWidth = uprightWidth;
footWidthTilted = uprightWidthTilted;
footThickness = uprightThickness;
footConnectorDepth = dovetailMaxDepth / cos(Shelf_Angle);
footLength = shelfLength*cos(Shelf_Angle);

///////////////////////////////////////////////////////
// Logic - what gets made
///////////////////////////////////////////////////////

if (Part=="All"){
    translate([shelfThickness*5,0,0])
    create_shelf();
    translate([shelfThickness+2,0,0])
    create_upright_maleSide();
    translate([-shelfThickness+2,0,0])
    create_upright_femaleSide();
    translate([-shelfThickness*3,-footLength/2,0])
    part_foot_connector();
}
else if(Part=="Shelf"){
    create_shelf();
}
else if(Part=="Uprights"){
    translate([shelfThickness+2,0,0])
    create_upright_maleSide();
    translate([-shelfThickness+2,0,0])
    create_upright_femaleSide();
}
else if(Part=="Foot"){
    part_foot_connector();
}

///////////////////////////////////////////////////////
// Create - create a physical printable element from 1 or more parts
///////////////////////////////////////////////////////

//Create basic shelf piece
module create_shelf(){
    translate([0,0,shelfLength/2])
    rotate([-90,0,0])
    union(){
        part_shelf_plank();
        part_shelf_backplate();
    }
}

//Create 1st of the 2 uprights
module create_upright_maleSide(){
    translate([0,uprightHeight/2,0])
    rotate([90,0,0])
    union(){
        part_upright_sidwall();
        part_upright_dovetailBottom();
        part_upright_dovetailSide_male();
    }
}

//Create 2nd of the 2 uprights
module create_upright_femaleSide(){
    translate([0,uprightHeight/2,0])
    rotate([90,0,0])
    union(){
        difference(){
            part_upright_sidwall();
            part_upright_dovetailSide_female();
        }
        part_upright_dovetailBottom();
    }
}

///////////////////////////////////////////////////////
// Parts - The individual elements that make up parts or sections of parts
///////////////////////////////////////////////////////

//Create male dovetail, has a small tolerance removed for a better fit
module part_dovetail_male(){
    dovetail(
        width_max=dovetailMaxWidth,
        depth=dovetailMaxDepth,
        length=dovetailLength,
        taper=true,
        tollerance=Dovetail_Tolerance
    );
}

//Create female dovetail, no tolerance change
module part_dovetail_female(){
    translate([0,-eps,-eps]) //offset for clean cuts (no parralell surface overlap)
    dovetail(
        width_max=dovetailMaxWidth,
        depth=dovetailMaxDepth,
        length=dovetailLength + 0.5, //little extra space to make sure parts sit flush
        taper=true
    );
}

// Backplate on a shelf to avoid container sliding out the back
module part_shelf_backplate(){
    translate([0,shelfLength/2,shelfThickness])
    rotate([90,0,0])
    linear_extrude(backstopThickness)
    rotate([0,0,180]) //poisition around x
    difference(){
        circle(d=shelfWidth);
        translate([-shelfWidth/2,0,0]) //center cutout
        square(shelfWidth);
    }
}

//Flat surface for the container shelf. Has dovetails
module part_shelf_plank(){
    difference(){
        union(){
            //Shelf piece
            linear_extrude(shelfThickness)
            square([shelfWidth, Container_Height],center=true);
            //dovetail (male)
            translate([
                shelfWidth/2,
                dovetailLength,
                dovetailMaxWidth/2 + DovetailSpacing/2
            ])
            rotate([90,90,0])
            part_dovetail_male();
        }
        //dovetail (female)
        translate([-shelfWidth,0,0]) //flip to correct side + eps adjustment
        translate([                      //position but on wrong side
            shelfWidth/2,
            dovetailLength,
            dovetailMaxWidth/2 + DovetailSpacing/2
        ])
        rotate([90,90,0])
        part_dovetail_female();
        // Container cutout
        translate([
            0,
            -backstopThickness,
            shelfWidth/2 + shelfThickness/2
        ])
        rotate([90,0,0])
        cylinder(d=shelfWidth, h=Container_Height, center=true);
    }
}

//The main body of the uprights with a dovetail cutout
module part_upright_sidwall(){
    difference(){
        //The main shape
        hull(){
            //bottom face
            rotate([Shelf_Angle,0,0])
            linear_extrude(0.0001)
            square([uprightThickness,uprightWidthTilted]);
            //top face
            translate([0,0,uprightHeight])
            rotate([Shelf_Angle,0,0])
            linear_extrude(0.0001)
            square([uprightThickness,uprightWidthTilted]);
        }
        //Cutout of dovetail on top
        translate([0,0,-dovetailMaxDepth / cos(Shelf_Angle)]) //lower cutout into the top by its tilted with
        translate([shelfThickness/2,0,uprightHeight])//position to top
        rotate([Shelf_Angle,0,0])
        translate([0,0,dovetailMaxDepth])
        rotate([-90,0,0])
        part_dovetail_female();
    }
}

//Dovetail on uprigh that connects one upright to another or to a foot
module part_upright_dovetailBottom(){
    difference(){
        //Dovetail
        translate([shelfThickness/2,0,-dovetailMaxDepth])
        rotate([Shelf_Angle,0,0])
        translate([0,0,dovetailMaxDepth])
        rotate([-90,0,0])
        part_dovetail_male();
        //Trimming block so the back is flat
        translate([0,0,1]) //Avoid surface overlap
        mirror([0,1,1]) //flip into position
        cube(dovetailMaxWidth*2);
    }
}

//Dovetail (male) that connects to the female side of a shelf
module part_upright_dovetailSide_male(){
    difference(){
        //Rail
        translate([0,0,DovetailSpacing/2]) //raise up to allow for spacing
        rotate([Shelf_Angle,0,0]) //tilt to final angle
        translate([0,0,dovetailMaxWidth/2])//raise up to be above z 0
        rotate([0,90,90])//rotate to face general direction
        part_dovetail_male();
        //Trimming block for cutoffm
        translate([eps,0,0]) //Avoid surface overlap
        mirror([1,1,0]) //flip into position
        cube(dovetailMaxWidth*2);
    }
}

//Dovetail (female) that connects to the male side of a shelf
module part_upright_dovetailSide_female(){
    translate([uprightThickness,0,0])
    translate([0,0,DovetailSpacing/2]) //raise up to allow for spacing
    rotate([Shelf_Angle,0,0]) //tilt to final angle
    translate([0,0,dovetailMaxWidth/2])//raise up to be above z 0
    rotate([0,90,90])//rotate to face general direction
    part_dovetail_female();
}

//Foot for bottom of uprights
module part_foot_connector(){
    translate([0,0,footConnectorDepth])
    difference(){
        //body
        union(){
            //body solid
            hull(){
                //Top
                rotate([Shelf_Angle,0,0])
                linear_extrude(0.0001)
                square([footThickness,footWidthTilted / cos(Shelf_Angle)]);
                //Bottom
                translate([0,0,-footConnectorDepth])
                linear_extrude(0.0001)
                square([footThickness,footWidthTilted]);
            }
            //body dovetail cutout
            translate([0,0,-footConnectorDepth])
            linear_extrude(footConnectorDepth)
            square([footThickness,footLength]);
        }
        //dovetail cutout
        translate([footThickness/2,0,-dovetailMaxDepth / cos(Shelf_Angle)])
        rotate([Shelf_Angle,0,0])
        translate([0,0,dovetailMaxDepth])
        rotate([-90,0,0])
        part_dovetail_female();
    }
}

///////////////////////////////////////////////////////
// Utility - library or helper functions/modules
///////////////////////////////////////////////////////

// Creates a straight dovetail for jointing, aligned to y=0 & z=0
// width_max   - number - width (x) at the widest point
// depth       - number - depth (y)
// length      - number - length (z)
// angle       - number - between 0 and 90, the angle (degrees) between min & max widths (default = 80)
// taper       - bool   - if it is 10% narrower at the top (default = no)
// tollerance  - number - amount to remove around all edges (default = 0)
module dovetail(width_max, depth, length, angle=80, taper=false, tollerance=0, filletRadius=0.4){
    x_max = width_max - (tollerance*2);
    x_min = width_max - (width_max * tan(90-angle)) - (tollerance*2);
    y = depth - tollerance;
    taperScale = taper ? [0.9,0.9] : [1,1]; //if dovetail is narrower at top

    linear_extrude(height=length, scale=taperScale)
    difference(){
        make_dovetail(); //make dovetail with base
        make_base(); //remove base off dovetail (used only for fillet)
    }

    // Rounded dovetail shape. It adds a base for the fillet
    module make_dovetail(){
        //Inner fillet
        offset(r=-filletRadius, $fn = 32)
        offset(r= filletRadius, $fn = 32)
        //Outer fillet
        offset(r= filletRadius, $fn = 32)
        offset(r=-filletRadius, $fn = 32)
        //Shape with base
        union(){
            polygon([
                [-x_min/2, 0],
                [ x_min/2, 0],
                [ x_max/2, y],
                [-x_max/2, y]
            ]);
            make_base();
        }
    }
    // Temporary square base on the dovetail, used to create the fillet then removed
    module make_base(){
        translate([0,-filletRadius*2,0])
        square([width_max+filletRadius*4,filletRadius*4], center=true);
    }
}
```


[User]
I need a parametric design for a drill dust collector, something that fits over the drill bit and catches dust when drilling upwards. I'm working from a design by xifle, with a few modifications by medooo, but I need a new version from scratch.

The basic shape is a hollow frustum, with a cylinder at the bottom where the drill bit goes in. The design should have these adjustable parameters:

*   `HEIGHT`: The total height of the frustum part.
*   `DRILL_DIA`: The diameter of the drill bit that will fit into the central hole.
*   `UPPER_DIAMETER`: The diameter of the larger opening of the frustum.
*   `LOWER_DIAMETER`: The diameter of the smaller opening of the frustum.
*   `BACK_FOLD_HEIGHT`: The height of the lip that extends outward at the top to keep the dust in.
*   `BACK_FOLD_DIAMETER`: The diameter of the outer edge of the lip.
*   `WALL`: The wall thickness of the collector itself.

It's really important to keep the wall thickness consistent throughout, and for that wall thickness to apply both to the frustum body _and_ the fold/lip at the top.

The design should use difference() and cylinder() commands to create the hollow shape, with the central hole for the drill bit. There should be a cylindrical hole that goes all the way through the frustum, of the diameter `DRILL_DIA`, and should be located centrally.

If `BACK_FOLD_HEIGHT` is greater than zero, there should also be a lip/fold at the top of the frustum. This lip should also use a `difference()` of cylinders to create a hollow shape with the same wall thickness as the frustum body. It should extend outward from the top of the frustum and be created by rotating a frustum with radius `UPPER_DIAMETER` to `BACK_FOLD_DIAMETER`. The lip's height should be controlled by `BACK_FOLD_HEIGHT`.

I'd like the code to be clean and use comments to explain what's going on. Please use `$fn=100` for a smooth circle. Can you do that?

[Assistant]
Drill_Dust_Collector.scad:
```
// Modded / Remixed by medooo
// - added back fold (to prevent dirt from spinning out)
// Licensed under the CC BY-NC-SA 3.0
// please refer the complete license here: http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode
// 28.07.2024
// v1.01
// 31.07.2024
// v1.02 (made back fold diameter 'hardcoded' for customizer apps

// Drill Dust Collector by xifle
// Licensed under the CC BY-NC-SA 3.0
// 10.04.2017
// v1

// Height of the Dust Collector
HEIGHT=16;

// Diameter of your Drill (add some clearing, 0.1-0.2mm should be fine)
DRILL_DIA=7.2;

// Upper Diameter of the Dust Collector
UPPER_DIAMETER=60;

// Lower Diameter of the Dust Collector
LOWER_DIAMETER=45;

// Upper Back fold height (put to 0 if not wanted)
BACK_FOLD_HEIGHT = 6;

// Upper Back fold Diameter
BACK_FOLD_DIAMETER = 50;

WALL=1;

$fn=100;

difference() {
  union() {
    difference() {
      cylinder(r1=LOWER_DIAMETER/2+WALL, r2=UPPER_DIAMETER/2+WALL, h=HEIGHT+WALL, center=false);
      translate([0,0,WALL+0.01]) cylinder(r1=LOWER_DIAMETER/2, r2=UPPER_DIAMETER/2, h=HEIGHT, center=false);
    }
  cylinder(r=DRILL_DIA/2+WALL,h=HEIGHT/2);
  }
  translate([0,0,-0.1]) cylinder(r=DRILL_DIA/2, h=HEIGHT);
}

if (BACK_FOLD_HEIGHT > 0)
  translate([0,0,HEIGHT+WALL])
    difference() {
      cylinder(r1=UPPER_DIAMETER/2+WALL, r2=BACK_FOLD_DIAMETER/2+WALL, h=BACK_FOLD_HEIGHT+WALL, center=false);
      translate([0,0,(-0.01)]) cylinder(r1=UPPER_DIAMETER/2, r2=BACK_FOLD_DIAMETER/2, h=BACK_FOLD_HEIGHT+WALL+0.02, center=false);
    }

```


[User]
I want to design a configurable button in OpenSCAD. It should be a cylindrical button with a recessed top portion and optional holes around the center. Here are the parameters I'd like to control:

*   **Total Height:** The overall height of the button.
*   **Inner Height:** The height of the recessed portion of the button. This should be less than the total height.
*   **Outer Size:** The diameter of the main button cylinder.
*   **Inner Size:** The diameter of the recessed portion. This should be smaller than the outer size.
*   **Number of Holes:** The number of holes to place around the recessed area. The holes should be evenly spaced and can be anywhere from 0 to 6.
*   **Hole Size:** The diameter of each hole.
*   **Hole Distance:** The distance of the center of each hole from the center of the button. This should be greater than half the hole size and less than half the inner size to keep the holes contained within the button's surface.
*   **Rounding:**  The radius for rounding the top edges of the button. If set to -1, it should be automatic based on the total height, say, 1/5th of the height. If set to 0, no rounding. Otherwise it's a manual radius for rounding.

I'd like the code to use the `minkowski()` operation for rounding so the result isn't too polygon-y. Please make sure the code is well-commented and easy to customize. It would be nice if there were a single main module to create the button. I'm not requesting any specific libraries, just plain OpenSCAD.

[Assistant]
ConfigurableButton.scad:
```
/////////////////Parameters
/* [Sizes] */
// total height
TotalHeight =3;
// inner height
InnerHeight = 2;
// outer size
OuterSize = 20;
// inner size
InnerSize = 16;
// How many holes?
NHoles=4; // [0:6]
// hole size
HoleSize = 2; //
// hole distance from center (to be more than HoleSize/2 and less than InnerSize/2)
HoleDistance = 3;
// rounding: -1 = automatic, 0 = no, >0 = radius of rounding
Rounding = -1;
/* [Hidden] */

knopf(TotalHeight,InnerHeight,OuterSize/2,InnerSize/2,NHoles,HoleDistance,HoleSize/2);

module knopf(h1,h2,r1,r2,nh,rh,ih,rr= -1) {
	irr = (rr<0) ? h1/5 : rr;
	difference() {
		rcyl(r1,h1,irr);
		translate([0,0,h2])
			cylinder(r=r2,h=(h1-h2+0.01),$fn=120);
		if (nh > 1) {
			for (i=[0:360/nh:360-0.01]) {
				translate([cos(i)*rh,sin(i)*rh,0])
					cylinder(r=ih,h=h1*2,center=true,$fn=60);
			}
		} else
			if (nh > 0)
				cylinder(r=ih,h=h1*2,center=true,$fn=60);
	}
}

module rcyl(cr,hh,rr,ce=false,fn=120) {
	if (rr > 0) {
		translate([0,0,rr])
			minkowski() {
				cylinder(r=cr-rr,h=hh-2*rr,center=ce,$fn=fn);
				sphere(rr,$fn=24);
			}
	} else {
		cylinder(r=cr,h=hh,center=ce,$fn=fn);
	}
}

```


[User]
Hey, I've got this idea for a stackable case system for Raspberry Pi-like boards, and I'd like to be able to customize it pretty extensively. I'm picturing something with support for different board sizes, different numbers of stacked boards, and a few different options for how the case actually connects the boards. The basic idea is that it's a layered system, with a base and then standoffs to hold up the boards, plus some options for pillars and an enclosure of varying degrees.

Here's what I'm looking for:

**Core Requirements:**

*   **Parametric Board Support:** I need to be able to specify the board dimensions (length, width, height) and the number of boards stacked. I'd like a gap parameter to control the spacing between multiple stacked boards in the X axis. Also need a board roundness parameter.
*   **Standoffs:** The design needs standoffs to elevate each board. These should be customizable in terms of X/Y distance, Z height, radius and base radius/height as well as an X/Y offset for the standoffs to allow different mounting hole locations. I also need to be able to enable or disable each of the four standoffs individually.
*   **Case Options:**
    *   **Minimal Case:** A simplified case design that only covers the edges and corners of the stack, using cube_round with a hull to create a shell. The X/Y size and the thickness should be parametric, and the corner roundness should be adjustable. This design should have some standoffs extending up from the base case to match the board standoffs.
    *   **Slab Case:** An option for a solid base slab that the boards sit on with a parameter for the overall size and thickness, with optional vents in a hexagonal pattern. The roundness of the corners should be customizable and the scale and offset of the vent pattern should also be configurable.
    *   **Case Pillars:** A set of pillars at the four corners of the case, also customizable with a radius, height, X/Y offset, with an additional dowel parameter for the pillar top.

**Customization Parameters (to be set in the OpenSCAD Customizer):**

*   **Board:**
    *   `board_size`: \[length, width, height]
    *   `board_n`: Number of stacked boards
    *   `board_r`: Roundness of board corners
    *   `board_n_space`: Gap between multiple stacked boards in X axis
*   **Standoffs:**
    *   `standoff_x`: Distance between standoffs in X
    *   `standoff_y`: Distance between standoffs in Y
    *   `standoff_z`: Height of standoffs
    *   `standoff_r`: Radius of standoffs
    *   `standoff_base_r`: Radius of standoff base
    *   `standoff_base_z`: Height of standoff base
    *   `standoff_offset_x`: Standoff distance from origin in X
    *   `standoff_offset_y`: Standoff distance from origin in Y
    *   `standoff_1_enabled`: Toggle standoff 1
    *   `standoff_2_enabled`: Toggle standoff 2
    *   `standoff_3_enabled`: Toggle standoff 3
    *   `standoff_4_enabled`: Toggle standoff 4
*   **Case:**
    *   `case_x`: Case size in X
    *   `case_y`: Case size in Y
    *   `case_z`: Case thickness
    *   `case_r`: Case corner roundness
    *    `case_slab_hole_scale`: Scale the air hole pattern if case slab and holes enabled
    *    `case_slab_hole_offset`: Offset for the hex air hole pattern in case slab
    *   `case_slab_enabled`: Toggle case slab
    *   `case_slab_holes`: Toggle hex air hole in case slab
    *   `case_minimal_enabled`: Toggle case minimal connectors
     *   `case_board_connect_enabled`: Toggle case standoff connectors
*   **Pillars:**
    *   `pillar_h`: Height of pillars
    *   `pillar_r`: Radius of pillars
    *   `pillar_offset_x`: Pillar offset from the corner on X axis
    *   `pillar_offset_y`: Pillar offset from the corner on Y axis
    *  `pillar_dowel_h`: Pillar top dowel height
    *   `pillar_dowel_r`: Pillar top dowel radius
    *   `pillars_enabled`: Toggle pillars

*   **Other:**
    *   `colour_a`: Colour of the case
    *   `colour_b`: Colour of the board
    *   `board_enabled`: Toggle board visibility

I'd like you to create a modular design for this, so that it's easier to reuse code and understand the structure. The design should use the `cube_round` module I've defined, but also break apart the rest of the code into reusable modules for standoffs, case components, etc.  I'm okay with using boolean operations like `difference` and `union`. Please generate all the necessary `.scad` files. I don't expect you to improve upon any existing modules like `cube_round` - assume that I'm providing that for the purposes of this request, and that you are generating all files from scratch.

[Assistant]
pi_like_stack.scad:
```
// pi-like-stack
// Stack pi-like boards on top of one another
// 85mm x 56mm x 20mm
// Standoffs 2.5mm diameter

// BUG (openscad): https://github.com/openscad/openscad/issues/3098
// Customizer has poor support for float input.
// Workaround: ``my_parameter = 1.0 // .01``
// Or:  ``my_parameter = 1.0 // [0:0.1:2]``
// See: "Unavailable customizations"
// https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Customizer

// ----------------------------------------
// Parameters
// ----------------------------------------

$fn=15;

colour_a = [0.3, 0.3, 1.0];
colour_b = [0.42, 0.81, 0.39];

// Toggle board visiblity
board_enabled = true;

// Toggle case pillar connectors
case_minimal_enabled = true;
// Toggle case standoff connectors
case_board_connect_enabled = true;
// Toggle case slab
case_slab_enabled = false;
// Toggle hex air hole in case slab
case_slab_holes = false;

// Toggle standoff 1
standoff_1_enabled = true;
// Toggle standoff 2
standoff_2_enabled = true;
// Toggle standoff 3
standoff_3_enabled = true;
// Toggle standoff 4
standoff_4_enabled = true;

// Toggle pillars
pillars_enabled = true;

/* [Board] */

// Standard size pi-like
board_size = [85.0, 65.0, 20.0];
board_n=1;
// Half size pi-like
//board_size = [30, 65, 20];
// Duplicate board in X
//board_n=2;
// Board roundness
board_r = 3.0;  // .01

// Duplicate board gap
board_n_space = 10.0;  // .01

/* [Standoffs] */

// Standard size pi-like
// Standoff distance in X
standoff_x = 58.0;  // .01
// Standoff distance in Y
standoff_y = 49.0;  // .01

// Half size pi-like
//standoff_x = 23;  // .01
// Standoff distance in Y
//standoff_y = 58;  // .01

// Standoff height
standoff_z = 10.0;  // .01
// Standoff radius
standoff_r = 1.25;  // .01
// Standoff base radius
standoff_base_r = 2.5;  // .01
// Standoff base height
standoff_base_z = 2.5;  // .01
// Standoff distance from origin in X
standoff_offset_x = 3.5;  // .01
// Standoff distance from origin in Y
standoff_offset_y = 3.5;  // .01

/* [Case] */

// Case size in X
case_x = 100.0;  // .01
// Case size in Y
case_y = 75.0;  // .01
// Case thickness
case_z = 3.0;  // .01
// Case corner roundness
case_r = 3.0;  // .01
// Scale the air hole pattern if case slab and holes enabled
case_slab_hole_scale = 1.025;  // .01
case_slab_hole_offset = 1.0;  // .01

/* [Case pillars] */

pillar_h = 25.0;  // .01
pillar_r = 3.0;  // .01
pillar_offset_x = 6.0;  // .01
pillar_offset_y = 6.0;  // .01
pillar_dowel_h = 5.0;  // .01
pillar_dowel_r = 1.25;  // .01

// ----------------------------------------
// Utilities
// ----------------------------------------

module cube_round(
    size=[1,1,1],
    r=0.25,
    center=false,
    hull=true,
    corners=[true, true, true, true]
) {
    module _corners(){
        if(corners[0]){
            translate([r, r, 0])
            cylinder(h=size[2], r=r, center=false);
        }
        if(corners[1]){
            translate([size[0]-2*r, 0, 0])
            translate([r, r, 0])
            cylinder(h=size[2], r=r, center=false);
        }
        if(corners[2]){
            translate([0, size[1]-2*r, 0])
            translate([r, r, 0])
            cylinder(h=size[2], r=r, center=false);
        }
        if(corners[3]){
            translate([size[0]-2*r, size[1]-2*r, 0])
            translate([r, r, 0])
            cylinder(h=size[2], r=r, center=false);
        }
    }
    if (hull){
        hull()
            _corners();
    } else {
        _corners();
    }
}

module pillars(
    size=[1,1,1],
    r=0.25,
    center=false,
    size_from_pillar=false,
    corners=[true, true, true, true],
    hull=false
){
    d = r * 2;
    _size = size_from_pillar ? [size[0]+d, size[1]+d, size[2]]: size;
    _translate = size_from_pillar ? [-r, -r, 0] : [0, 0, 0];
    translate(_translate){
        cube_round(
            size=_size,
            r=r,
            center=center,
            corners=corners,
            hull=hull
        );
    }
}

// ----------------------------------------
// Components
// ----------------------------------------

module board(size=board_size, r=board_r){
    % color(colour_b, 0.5) cube_round([size[0], size[1], 1.5], r=max(r, 0.1));
}

module standoffs(
    size=[standoff_x, standoff_y, standoff_z],
    r=standoff_r,
    r2=standoff_base_r,
    z2=standoff_base_z,
    size_from_pillar=true,
    offset=[standoff_offset_x, standoff_offset_y, 0],
    corners=[
        standoff_1_enabled,
        standoff_2_enabled,
        standoff_3_enabled,
        standoff_4_enabled
    ],
    hull=false,
    base=true,
){
    translate(offset){
        if(base){
            // base for board to sit on
            pillars(
                size=[size[0], size[1], z2],
                r=r2,
                size_from_pillar=size_from_pillar,
                corners=corners,
                hull=hull
            );
        }
        // main pillar
        pillars(
            size=size,
            r=r,
            size_from_pillar=size_from_pillar,
            corners=corners,
            hull=hull
        );
    }
}

module case_slab(
    size=[case_x, case_y, case_z],
    r=case_r,
    offset=[
        -(case_x - board_size[0]*board_n)/2 + board_n_space/2*(board_n-1),
        -(case_y - board_size[1])/2,
        -case_z
    ]
){
    // Main base
    translate(offset){
        cube_round(size, r=r);
    }
}

module case_minimal(
    size=[case_x, case_y, case_z],
    r=case_r,
    offset=[-(case_x - board_size[0]*board_n)/2 + board_n_space/2*(board_n-1), -(case_y - board_size[1])/2, -case_z]
){
    // Main Base
    translate(offset){
        cube_round(size, r=r, hull=true, corners=[true, false, false, true]);
        cube_round(size, r=r, hull=true, corners=[false, true, true, false]);
    }
}
module case_minimal_standoffs(
    size=[case_x, case_y, case_z],
    r=case_r,
    offset=[-(case_x - board_size[0]*board_n)/2, -(case_y - board_size[1])/2, -case_z]
){
    translate([0, 0, -case_z]){
        standoffs( size=[standoff_x, standoff_y, case_z], corners=[true, true, false, false], hull=true, base=false);
        standoffs( size=[standoff_x, standoff_y, case_z], corners=[false, true, false, true], hull=true, base=false);
        standoffs( size=[standoff_x, standoff_y, case_z], corners=[false, false, true, true], hull=true, base=false);
        standoffs( size=[standoff_x, standoff_y, case_z], corners=[true, false, true, false], hull=true, base=false);
    }
}

module case_pillars(
    size=[
        case_x - pillar_r * 2,
        case_y - pillar_r * 2,
        case_z + board_size[2]
    ],
    r=pillar_r,
    offset=[
        -(case_x - board_size[0]*board_n)/2+pillar_r + board_n_space/2*(board_n-1),
        -(case_y - board_size[1])/2+pillar_r,
        -case_z
    ],
    h2=pillar_dowel_h,
    r2=pillar_dowel_r
){
    translate(offset){
        // Pillar main
        pillars(size=size, r=r, size_from_pillar=true);
        // Pillar dowel
        pillars(size=[size[0], size[1], size[2] + h2], r=r2, size_from_pillar=true);
    }
}

module case_pillar_holes(
    size=[
        case_x - pillar_r * 2,
        case_y - pillar_r * 2,
        case_z + board_size[2]
    ],
    r=pillar_r,
    offset=[
        -(case_x - board_size[0]*board_n) / 2 + pillar_r + board_n_space/2*(board_n-1),
        -(case_y - board_size[1]) / 2 + pillar_r,
        -case_z + -0.1
    ]
){
    translate(offset){
        pillars(
            size=[
                size[0],
                size[1],
                pillar_dowel_h
            ],
            r=pillar_dowel_r + 0.4,
            size_from_pillar=true
        );
    }
}

module case_vents(
    size=[case_x, case_y, case_z],
    offset=[
        -(case_x - board_size[0]*board_n)/2 + board_n_space/2*(board_n-1) + case_slab_hole_offset,
        -(case_y - board_size[1])/2,
        -case_z
    ],
    r=case_z*3/2,
    s=case_slab_hole_scale
){
    scale([s,s,1]){
        translate(offset){
            for(i=[0:size[2]*5:size[0]*2]){
                for(j=[0:size[2]*3.25:size[1]*2]){
                    translate([i+size[2]*2.5,j+size[2]+1.5,-size[2]])
                    cylinder(d=r*2,h=size[2] * 3, $fn=6);
                translate([i,j,-size[2]])
                    cylinder(d=r*2,h=size[2] * 3, $fn=6);
                }
            }
        }
    }
}

// ----------------------------------------
// Assembly / Main
// Ran if     : ``include <filename>``
// Skipped if : ``use <filename>``
// ----------------------------------------

module main(){
    // Board
    for(i=[0:1:board_n-1]){
        translate([i * (board_size[0] + board_n_space), 0, 0]){
            if(board_enabled){ board();}
        }
    }

    // Case
    translate([0,0,-standoff_base_z]){
        color(colour_a){
            difference(){
                union(){
                    if(case_slab_enabled){
                        difference(){
                            case_slab();
                            if(case_slab_holes){
                                difference(){
                                    case_vents();
                                    case_pillars();
                                }
                            }
                        }
                    }
                    if(case_minimal_enabled){case_minimal();}
                    for(i=[0:1:board_n-1]){
                        translate([i * (board_size[0] + board_n_space), 0, 0]){
                            if(case_board_connect_enabled){case_minimal_standoffs();}
                            standoffs();
                        }
                    }
                    if(pillars_enabled){case_pillars();}
                }
                case_pillar_holes();
            }
        }
    }
}

main();
```


[User]
Okay, so I've got this idea for a really confusing screw and nut set. Basically, it's a bolt that has two different threads, one that works normally and one that's reversed. The idea is that you can screw one nut on clockwise and another counterclockwise, which makes for a real head-scratcher when someone tries to take them off. I'm thinking of calling it a "Magic Screw."

I'd like to be able to create a parametric OpenSCAD file for this. It needs to have a bolt, a regular nut, and a reverse nut. Here's a breakdown of the parameters I need to control:

*   **Bolt Outer Diameter:** I need to be able to set the outer diameter of the bolt, including the threads (e.g., 8mm).
*   **Bolt Height:** The height of the bolt *excluding* the hex head.
*   **Hex Height:** The height of the hex nut or bolt head.
*   **Wrench Size:** The wrench size for both the nuts and the bolt head (e.g., 13mm).
*   **Thread Pitch:** The pitch of both threads, in mm per 360-degree rotation (e.g., 2.03mm).
*   **Forward Thread Depth:** The depth of the forward, regular threads.
*   **Reverse Thread Depth:** The depth of the reverse threads.
*   **Forward Nut Gap:** The gap added to the inner diameter of the forward nut for tolerance, I guess.
*   **Reverse Nut Gap:**  The gap added to the inner diameter of the reverse nut for tolerance.
*   **Thread Offset:**  This is tricky. The reverse thread needs to be slightly offset to sit on top of the forward thread properly.  Maybe a way to have the reverse thread be centered on the top of the forward thread, or to have both threads meet at the same ceiling, or maybe a zero offset to have the threads share a radius.

I would also like a tolerance gap for the nuts, so I can specify how much looser the nuts are on the bolt. And lastly, I need to be able to control the resolution of the threads (for rendering time) -- something like `$fn`.

Ideally, the file would allow me to build all the parts together, or just one of them at a time (bolt, forward nut, reverse nut). I think it would be easiest if there was a variable that I could set to control that – like `part = 0` for all, `part = 1` for the bolt, `part = 2` for the forward nut, and `part = 3` for the reverse nut.

I'm not sure what would be best, but maybe we can use the `hull()` function for the reverse threads? I saw that recommended in the example. The bolt head should also have bevels on the top and bottom edges of the hex head. And the nuts should have bevels for the openings for easier threading.

I'm not asking you to improve on existing code, so there are no libraries I have included, but please use built-in OpenSCAD functionality or include library code (if any) in the files generated. Let me know if you have any questions!

[Assistant]
GetNutsMagicScrew_M8.scad:
```
// Wrong-Way bolt construction kit, adapted to OpenSCAD by Scott Elliott
// Pictures and videos on my blog at
//    http://mysd300.blogspot.com/search/label/screw
//    http://mysd300.blogspot.com/
// Thingiverse 13893
//
// MegaHertz67: Small changes to make parameters work with more flexibility,
// changes to optimize use of customizer, nicer shape of hex head

// To choose which part to build set part= to one of the following numbers:
// Part = 0:all / 1:bolt / 2:forward-turning nut / 3:reverse-turning nut
part = 0;
// Bolt outer diameter including thread (mm)
boltOuterDia = 8; //.1
// Height of bolt excluding hex head (mm)
boltHeight = 30; //.5
// Height of hex nut or bolt head (mm)
hexheight = 6.5; //.1
// Wrench size for hex nut or bolt head (mm)
wrenchSize = 13; //.1
// Pitch of thread (mm per 360° rotation)
threadpitch = 2.03; //.01
// degrees of twist per mm of linear length, e.g. 72
tpm = 360 / threadpitch;

// settings for forward threads and forward nut
//  - I suggest increasing fwdDepth to make the forward threads deeper and more distinct
//  - but large values of fwdDepth also make deeper overhangs, could cause building problems
// Depth of forward threads (mm), higher values make a deeper thread
fwdDepth = 0.56; //.02

// settings for reverse threads and reverse nut
//  - reverse threads are naturally shallower than forward threads with same settings because there are 2 starts (2 threads)
//  - lower values of revDepth will help hide the reverse threads on the bolt, but also controls
// Depth of reverse threads (mm)
revDepth = 0.42; //.02
// Gap for forward nut (mm): Extends inner diameter without moving outer diameter
fwdGap = 0.10 ; //.01
// Gap for reverse nut (mm): Extends inner diameter without moving outer diameter
revGap = 0.03; //.01

// offset_diffs  variable increases radius of reverse threads to ride over the top of the forward threads.
// Some examples of how it could be computed (comment out the ones you're not using)
//
// offset_diffs = 0.0; // zero difference centers both threads on same radius; bolt will exhibit 50/50 mix of thread valleys
//offset_diffs = fwdDepth; // reverse thread will be centered on upper edge of forward thread (half of back thread will be missing)
offset_diffs = fwdDepth - (revDepth / 2); // reverse thread and forward thread will have same ceiling
//offset_diffs = fwdDepth - revDepth; // reverse thread carved entirely below the top of conventional thread

// other misc settings

// Tolerance gap between bolt thread and nut threads (added to nut, subtracted from bolt)
tolGap = 0.04;

// Accuracy of thread (makes rendering time consuming)
$fn=60;

boltrad = boltOuterDia/2-fwdDepth+revDepth/2;

if (part ==1) bolt( boltrad ); // bolt with 2-way thread
if (part ==2) forwardnut( boltrad ); // conventional-turning nut
if (part ==3) reversenut( boltrad ); // reverse-turning nut
if (part ==0) {bolt(boltrad);forwardnut(boltrad);reversenut(boltrad);}

//// thread profiles ////
// These functions define the 2D cross-section for the threads, either forward or reverse.
// Edit these functions to change the thread pattern.
//
module reverse_screw_profile( radius, gap )
{
	hull() // convex hull ensures overhangs are connected at ends
	{
		rotate([0,0,180*0]) translate([revDepth - gap,0,0]) circle( r=(radius+offset_diffs) - revDepth + gap );
		rotate([0,0,180*1]) translate([revDepth - gap,0,0]) circle( r=(radius+offset_diffs) - revDepth + gap );
	}
}

module forward_screw_profile( radius, gap )
{
	translate([fwdDepth-gap,0,0]) circle( r=radius + gap );
}

//// 3d helix builders ////
// These modules transform the 2D thread profiles into helix shapes for carving screw threads.
// They probably don't need to be edited unless you want a more radical change.
//
// forward screw helix (raw material for cutting threads)
module fscrew( rad, ht, gap )
{
	// forward thread is twisted -tpm degrees per mm of height
	linear_extrude( height=ht, center=true, twist=-tpm*ht ) forward_screw_profile( rad, gap );
}

// reverse screw helix
module bscrew( rad, ht, gap )
{
	// reverse thread is twisted +tpm degrees per mm height
	linear_extrude( height=ht, center=true, twist=tpm*ht ) hull()
	{
		reverse_screw_profile( rad, gap );
	}
 }

//// parametric hex nut/bolt-head shape ////
module hex()
{
    intersection()
	{
	cylinder(d=wrenchSize/sin(60)-.07, h=hexheight, $fn=6, center=true);  // make a 6-sided cylinder (ie: hexagonal prism)
	cylinder(d=wrenchSize/sin(60)*.97-.07, h=hexheight+.02, $fn=60, center=true); //cut corners
    cylinder(h=hexheight+wrenchSize, d1=(wrenchSize+hexheight)*2, d2=0, center=true, $fn=60); //cut top corners
    mirror([0,0,1]) cylinder(h=hexheight+wrenchSize, d1=(wrenchSize+hexheight)*2, d2=0, center=true, $fn=60); //cut bottom corners

}
}

// turns the basic hex into a prototype for a nut with beveled openings
module hexnut( rad )
{
	difference()
	{
		hex();
		cylinder( h=hexheight/1.99, r1=rad/2, r2=rad+fwdDepth+revDepth, center=false ); // bevel top opening
		mirror([0,0,1]) cylinder( h=hexheight/1.99, r1=rad/2, r2=rad+fwdDepth+revDepth, center=false ); // bevel bottom opening
	}
}

// Bolt with hex head
//
// Hierarchy:
//  A. Builds a cylinder, bevels one end, and subtracts fscrew() and bscrew() helix shapes
//    a1. fscrew() makes use of forward_screw_profile module
//    a2. bscrew() makes use of reverse_screw_profile module
//  B. Adds a hex() head to the end of the screw
//
module bolt( rad )
{
  {
     union()
     {
       rotate([0,180,0]) translate([0,0,-boltHeight/2+-hexheight/2+.2])
         intersection()  // screw with bevel
       {
	fscrew( rad-tolGap, boltHeight, 0 ); // forward threads
	bscrew( rad-tolGap, boltHeight, 0 ); // reverse threads
	cylinder( r2=boltHeight+rad, r1=rad-3*fwdDepth, h=boltHeight, center=true ); // conical cylinder to bevel the end of the bolt
       }
       hex( );  // hex bolt head
     }
  }
}

// reverse Nut
//
// Hierarchy:
//  A. Builds a hexnut() and subtracts bscrew() threads from inner wall
//   a1. hexnut() uses hex() module and subtracts cones from top & bottom for beveled opening
//   a2. bscrew() uses reverse_thread_profile() module to generate helix
//
module reversenut( rad )
{
    translate ([0,-wrenchSize-2,0])
    difference()
     {
	hexnut( rad+tolGap );
	intersection()
	{
	     bscrew( rad+tolGap, hexheight, revGap );
	}
     }
}

// Conventional Nut
//
// Hierarchy:
//  A. builds a hexnut() and subtracts fscrew() threads from inner wall
//   a1. hexnut() uses hex() and subtracts cones from top & bottom for beveled opening
//   a2. fscrew() uses forward_thread_profile() module to generate helix
module forwardnut( rad )
{
    translate ([0,wrenchSize+2,0])
    difference()
     {
	hexnut( rad+tolGap );
	fscrew( rad+tolGap, hexheight, fwdGap ); // carve screw threads
     }
}

```


[User]
I'm looking to create a custom enclosure for an ESP32-S3 touchscreen board, specifically the 1.28-inch round display version. I've got some specific ideas for the design that I'd like to parameterize so I can easily adjust the fit and dimensions later if needed.

Essentially, I want a two-part case with a face that holds the screen and a back that screws into it. There will also be a spacer between the screen and battery.

Here's a breakdown of what I'm thinking:

**Face:**

*   The face will have a circular cutout to hold the 35.7mm diameter screen.
*   It'll also have a frame surrounding the display that is 39mm in diameter, and is 1mm thick. This frame needs a "nub" on one side which extends out, that is 25.3mm at its widest. The nub needs to have a flat end, that lines up with a point that is 7.48mm from the edge of the frame. I'm not sure how to calculate that last bit, but I'm hoping you can figure it out!
*   There will be a wall around the outer perimeter, approximately 6.87mm thick on all sides. This means that the total diameter of the face will be equal to the frame diameter, plus twice the wall thickness. I'm guessing you can calculate it.
*   The inner part of the face will have a depth of the same as the display frame's thickness of 1mm. The outer wall will have a depth of 10mm.
*   I'll need a small wall in front of the USB port, on the nub side, that is about 5mm shorter than the depth of the outer wall.
*   The face will have threads on the outside of the walls, that are 4.3mm high.

**Back:**

*   The back will be a simple disc with threads that match the face. It'll screw into the face to hold everything together.
*   The outer diameter of the back needs to match the outer diameter of the face's wall.
*   The back will have a thickness of 1mm and threads that are 4.3mm high.
*   The threads should be designed to print well, so I'd like to use the "Simple Printable Threads" module from OpenSCAD which I've seen floating around. I'm not sure if you have access to it or if you'll generate it, but that would be ideal.
*   There needs to be some thread tolerance so that the parts can fit together properly. Something around 0.2mm should be fine.

**Spacer:**

*   A spacer will be needed between the battery and the screen assembly to ensure proper fit, since the screen and the battery are very close to the same size.
*   The spacer should have a diameter that's slightly smaller than the display frame - I'm thinking about 0.2mm smaller.
*   The spacer will be 1mm thick and about 4mm in height.
*   I'll need some small pegs on one side of the spacer to hold it in place, and a cutout on one side of the disc for the battery cord. I have some very specific ideas on their placement, but that's fine, I'll just be more specific on the angles later, which should be easy enough for me to handle.

**Parameters:**

I want to be able to adjust the following parameters:

*   `Part`:  To select which part to render ("All", "Face", "Back", "Spacer").
*   `Battery_Thickness`: The thickness of the battery being used. Should default to 4.3mm
*  `Thread_Tollerance`:  Extra spacing for thread fit - default to 0.2mm
* `Spacer_Tolerance`: Spacing around spacer - default to 0.2mm.

**Libraries**:
I want to use the `threadChamfer` and `printableThread` from the Simple Printable Threads library.

I think that covers everything. Let me know if you need any clarification.

[Assistant]
Dice-Puck_Case.scad:
```
/////////////////////////////
// Parameters
/////////////////////////////

//Which part to print
Part = "All"; //[All,Face,Back,Spacer]
//Thickness of battery
Battery_Thickness = 4.3;
//Extra spacing needed around threads to allow for parts to fit together
Thread_Tollerance = 0.2;
//Extra spacing needed around spacer to allow it to fit in the face
Spacer_Tolerance = 0.2;

module endParams(){}endParams();

/////////////////////////////
// Back-end parameters
/////////////////////////////

$fn = 128;
eps = 0.007;

//thread twists
threadTwists = 0.75;

//Measurements of display screen and its frame
//display is the screen
display_diameter = 35.7;
displayFrame_diameter = 39; //38.6
displayFrame_thickness = 1;
//Nub is the extra bit that sticks out towards USB on the frame
displayNub_x_widest = 25.3;
displayNub_x_widest_rot45 = displayNub_x_widest * sin(45);
displayNub_y_from0 = findTSide(t="b", a=(displayNub_x_widest/2), c=(displayFrame_diameter/2));
displayNub_y_cutoff = displayNub_y_from0 + 7.48;
//faceWall is the walls around the face and circuit board
wallThickness = 6.87/2; // there is prob math for this but its HARD so eff it
faceWall_diameter = displayFrame_diameter + wallThickness*2;
faceWall_depth = 10;
//Wall behind threads
threadWall = 1;
threadHeight = Battery_Thickness;
thread_inner = displayFrame_diameter/2+threadWall;
thread_outer = faceWall_diameter/2-threadWall;

//Measurements for the back piece
back_diameter = faceWall_diameter;
back_thickness = 1;
back_threadHeight = threadHeight;
back_thread_offset = Thread_Tollerance;
back_thread_outer = back_diameter/2-threadWall+back_thread_offset;
back_thread_inner = back_thread_outer - (thread_outer-thread_inner);

//Measurements for the spacer
//This goes in the case between the device and the battery
spacer_thickness = 1;
spacer_diameter = displayFrame_diameter - Spacer_Tolerance;
spacer_height = 4; //may need to be +4?
spacer_width = 2;

/////////////////////////////
// Logic - what gets made
/////////////////////////////

if (Part=="Face"){
    part_face();
}
else if (Part=="Back"){
    part_back();
}
else if (Part=="Spacer"){
    part_spacer();
}
else if (Part=="All"){
    translate([-faceWall_diameter - 5,0,0])
    part_face();
    translate([faceWall_diameter + 5,0,0])
    part_back();
    part_spacer();
}

/////////////////////////////
// Parts - elements of models
/////////////////////////////

//Inner face 2d shape, without a display cutout
module part_2d_face_inner(){
    difference(){
        union(){
            //circle
            circle(d=displayFrame_diameter);
            //Nub
            translate([0,-displayNub_y_from0,0])
            rotate([0,0,45])
            square(displayNub_x_widest_rot45,center=true);
        }
        //cutoff for end of nub
        translate([0,-displayNub_y_cutoff-(displayFrame_diameter/2),0])
        square(displayFrame_diameter, center=true);
    }
}

//Outer face 2d shape, without a display cutout
module part_2d_face_outer(){
    difference(){
        difference(){
            //wall area
            circle(d=faceWall_diameter);
            //center cutout
            part_2d_face_inner();
            //End cutoff at nub
            translate([0,-displayNub_y_cutoff-(displayFrame_diameter/2)+eps,0])
            square(displayFrame_diameter, center=true);
        }
    }
}

//Create the face of the case
module part_face(){
    union(){
        //inner area of the face
        linear_extrude(displayFrame_thickness)
        difference(){
            //face
            part_2d_face_inner();
            //display cutout
            circle(d=display_diameter);
        }

        //outer wall of the face
        linear_extrude(faceWall_depth)
        part_2d_face_outer();

        //wall in front of usb
        part_face_usbWall();

        //threads
        part_face_threads();
    }
}

//Creates little wall in front of USB on the face
module part_face_usbWall(){
    linear_extrude(faceWall_depth-5)
    intersection(){
        //wall
        translate([0,1/2 - displayNub_y_cutoff,0])
        square([15,1],center=true);
        //cylinder
        circle(d=faceWall_diameter);
    }
}

// Threaded part of the face
module part_face_threads(){
    translate([0,0,faceWall_depth])
    difference(){
        //Threads
        difference(){
            //thread
            printableThread(
                r_int=thread_inner,
                r_ext=thread_outer,
                h=threadHeight,
                twist=-threadTwists,
                tc=2
            );
            //chamfer
            threadChamfer(
                r_int=thread_inner,
                h=threadHeight,
                size=2,
                angle=45,
                edge="top",
                type="subtract"
            );
        }
        //Cutout for middle
        translate([0,0,-eps/2])
        cylinder(d=displayFrame_diameter, h=threadHeight+eps);
        //Cutout for usb nub
        translate([0,0,-eps/2])
        linear_extrude(threadHeight+eps)
        part_2d_face_inner();
        //Cutout for the pointy thread remainder bits
        translate([0,0,-eps/2])
        rotate([0,0,-90-(55/2)])
        rotate_extrude(angle=55)
        translate([displayNub_y_cutoff-threadHeight,0,0])
        square(threadHeight+eps);
    }
}

//Create back part of model
module part_back(){
    difference(){
        //back and walls
        linear_extrude(back_thickness+back_threadHeight)
        circle(d=back_diameter);
        //thread cutout
        translate([0,0,back_thickness+eps]){
            printableThread(
                r_int=back_thread_inner,
                r_ext=back_thread_outer,
                h=back_threadHeight,
                twist=-threadTwists,
                tc=2
            );
            //thread chamfer cutout
            threadChamfer(
                r_int=back_thread_inner,
                h=back_threadHeight,
                size=2,
                angle=45,
                edge="top",
                type="add"
            );
        }

    }
}

//Create a peg for the spacer
//A is the # of degrees (out of 360) that the peg's arc takes
//r is the amount of z rotation used to position it around the spacer
module part_spacer_peg(a,r){
    rotate([0,0,r])
    rotate_extrude(angle=a)
    translate([spacer_diameter/2-spacer_width,0,0])
    square([spacer_width,spacer_height]);
}

// Create spacer
module part_spacer(){
    //translate([0,0,10])
    //rotate([0,180,0])
    translate([0,0,spacer_thickness])
    difference(){
        //Spacer
        union(){
            //Plate
            translate([0,0,-spacer_thickness])
            linear_extrude(spacer_thickness)
            circle(d=spacer_diameter);
            //Pegs start with the big one opposite of the USB and go clockwise
            part_spacer_peg(60,60);
            part_spacer_peg(20,145);
            part_spacer_peg(20,198);
            part_spacer_peg(20,198);
            part_spacer_peg(10,245);
            part_spacer_peg(7,304);
            part_spacer_peg(10,337);
            part_spacer_peg(10,376);
        }
        //Notch cutout for power cord
        translate([spacer_diameter/2,0,0])
        cube(5,center=true);
    }
    *
    color([0.6,0.6,0.6])
    translate([0,0,-7])
    rotate([0,180,0])
    import("ESP32-S3-Touch-LCD-1_28.stl");
}

/////////////////////////////
// Helpers - extra libraries and functions
/////////////////////////////

//Find the requested side given either 2 sides or 1 side + 1 angle
//Only works for right triangles
//t must be "a", "b", or "c"
//This is a new func I made for this project because I was tied of dealing with triangle math
//Haven't tested all the variations
function findTSide(t,a=0,b=0,c=0,A=0, B=0) =
    //2 legs known
    a!=0 && b!=0 ? (
        t=="c" ? sqrt(pow(a,2)+pow(b,2)) :
        undef
    ):
    //1 leg (a) and hypotenuse known
    a!=0 && c!=0 ? (
        t=="b" ? sqrt(pow(c,2)-pow(a,2)) :
        undef
    ):
    //1 leg (b) and hypotenuse known
    b!=0 && c!=0 ? (
        t=="a" ? sqrt(pow(c,2)-pow(a,2)) :
        undef
    ):
    //1 leg (a) and opposite angle known
    a!=0 && A!=0 ? (
        t=="b" ? a * tan(90-A) :
        t=="c" ? sqrt(pow(a,2)+pow(a*tan(90-A),2)) :
        undef
    ):
    //1 leg (b) and opposite angle known
    b!=0 && B!=0 ? (
        t=="a" ? b * tan(90-B) :
        t=="c" ? sqrt(pow(b,2)+pow(b*tan(90-B),2)) :
        undef
    ):
    //1 leg (a) and adjacent angle known
    a!=0 && B!=0 ? (
        t=="b" ? a * tan(B):
        t=="c" ? sqrt(pow(a,2)+pow(a*tan(B),2)):
        undef
    ):
    //1 leg (b) and adjacent angle known
    b!=0 && A!=0 ? (
        t=="a" ? b * tan(A):
        t=="c" ? sqrt(pow(b,2)+pow(b*tan(A),2)):
        undef
    ):
    //None found
    undef;

///////////////////////////////////////////////////////////
//              Simple Printable Threads
//            Created by MrStump, Aug 2023
///////////////////////////////////////////////////////////
/*
Instructions for use:
    - Copy all of this code (not including the example at the top) into your script
        - You can alternatively use this as a library using the "use" command
    - Use the "printableThread" function to create a threaded shape
    - Optionally, you can use "threadChamfer" to spawn a shape to create chamfers

Notes on printability:
    ~45 degrees is the steepest overhang you want on a model if you want to print with no supports. When making a thread, look at it from the side and make sure you aren't making an angle too steep. If you are, try less threads, less twist/angle, or less difference between your interior and exterior radius.

Notes on tolerance:
    In the example you can see tolerance added to the "socket". Without it, the "perfect fit" would never go together once printed. 0.25-0.5 differnece in the radius measurements between the "socket" and the "screw" is a good general range, but your printer, printer settings, material and more all impact the fit. A test print is always a good idea to find a measurement that works for you.
*/

// Creates a printable thread shape
//         ----REQUIRED PARAMETERS----
// r_int = Distance from point where threads are closest to center
// r_ext = Distance from point where threads are furthest from center
// h     = Height of the threaded shape (overall screw length)
//         ----MUST USE ONE PARAMERS FROM THESE 2----
// twist = Number of 360 twists across the entire length (turns). Negative numbers are "righty tighty"
// angle = Angle of threads. Negative numbers are "righty tighty".
//         ----OPTIONAL PARAMETERS----
// tc    = Number of threads. Even number recommended.
// dpc   = Dots per circumfrance, how many dots make up the shape
// $fn   = Level of detail (default recommended, or use 32 for better performance)
//
// EXAMPLE:
// printableThread(r_int=40, r_ext=45, twist=-1.5, h=100, tc=6);
module printableThread(r_int, r_ext, h, twist=0, angle=0, tc=2, dpc=100, $fn=64){
    if (twist==0 && angle==0){
        //Makes no screw due to no twist or angle
        echo("\n\n    ATTENTION:\nYou did not set a 'twist' or 'angle' param in printableThread. You must set one.\n\n");
    } else if (twist!=0 && angle!=0){
        //Makes no screw due to both twist and angle being selected
        echo("\n\n    ATTENTION:\nYou set both 'twist' and 'angle' params in printableThread. You must set only one.\n\n");
    } else {
        //Makes screw

        //Determine twist count by twist or angle param
        desiredTwist = twist!=0 ? twist : twistFromAngle(r_ext,h,angle);

        //Calcualte twist
        degreesToTwist = 360 * desiredTwist;

        // Generate threaded shape
        linear_extrude(h, twist=degreesToTwist, $fn=$fn)
            thread2dShape(r_int, r_ext, tc, dpc);
    }
}

// Creates a chamfer to make mating threaded bits easier
//         ----REQUIRED PARAMETERS----
// r_int = Distance from point where threads are closest to center
// h     = Height of the threaded shape (overall screw length)
// size  = How much chamfering to do
//         ----OPTIONAL PARAMETERS----
// angle = Angle of the chamfer (between 0 and 90. Overhangs > 45 may not print without support)
// edge  = "top" or "bottom", for what edge of the screw chamfer aligns to
// type  = "add" or "subtract", for if the chamfer removes from the screw or adds to it
//          - "add" is for when you need to create a threaded socket in another shape
// $fn   = Level of detail (default recommended, or use 32 for better performance)
//
// EXAMPLE:
// threadChamfer(r_int=40, h=100, size=5);
module threadChamfer(r_int, h, size, angle=45, edge="top", type="subtract", $fn=64){
    //Epsilon, used to ensure the difference cleanly cuts paralell surfaces
    eps = 0.007;
    //Extra X added to the chamfer size if subtracting. Lets low poly meshes not clip through
    sizePlus = type=="subtract" ? size*1.2 : size;

    // Create a list of 3 different x/y/z coords for shape creation and position
    // index 0 = translate that changes z
    // index 1 = translate that changes x pos for rotate_extrude
    // index 2 = rotation applied to triangle prior to rotate_extrude
    extrudeAndTranslateData =
        edge=="bottom" && type=="add" ?
            [ [0,0,0], [r_int,0,0], [0,0,0] ] :
        edge=="top" && type=="add" ?
            [ [0,0,h], [r_int,0,0], [180,0,0] ] :
        edge=="bottom" && type=="subtract" ?
            [ [0,0,-eps], [-(r_int+sizePlus),0,0], [0,0,0] ] :
        edge=="top" && type=="subtract" ?
            [ [0,0,h+eps], [-(r_int+sizePlus),0,0], [180,0,0] ] :
        "If you get error in below translate, it is cause of this";

    // Create/position shape
    translate(extrudeAndTranslateData[0])
        rotate_extrude()
        translate(extrudeAndTranslateData[1])
        rotate(extrudeAndTranslateData[2])
        //polygon([[0,0],[0,sin(angle)*size],[cos(angle)*size,0]]);
        polygon([[0,0],[0,sizePlus*tan(angle)],[sizePlus,0]]);
}

// Creates the shape which is extrusion twisted to make threads
//         ----REQUIRED PARAMETERS----
// r_int = Distance from point where threads are closest to center
// r_ext = Distance from point where threads are furthest from center
//         ----OPTIONAL PARAMETERS----
// tc    = Number of threads. Even number recommended. Negative #s are "righty tighty"
// dpc   = Dots per circumfrance, how many dots make up the shape
//
// EXAMPLE:
// thread2dShape(30, 35, tc=6);
module thread2dShape(r_int, r_ext, tc=2, dpc=200){
    //How much threads stick out
    threadLength = r_ext-r_int;
    //Midpoint between the 2 radius measurements
    r_mid = r_int + (threadLength/2);
    //The size of each "step" making the shape
    interval = 1/dpc;

    //Generate list of [X,Y] coords, each acting as a point to make the shape
    pl = [
        for (i=[0:interval:1-interval])[
            (r_mid + (threadLength/2) * sin(tc * i * 360)) * cos(i * 360),
            (r_mid + (threadLength/2) * sin(tc * i * 360)) * sin(i * 360)
        ]
    ];

    //Generate the 2d shape from the list of points
    polygon(points=pl);
}

//Helper function: get twist based on radius, height (length), and desired angle
function twistFromAngle(_r,_h,_a) = _h / ( (2*PI*_r) * tan(_a) );

```


[User]
I need an OpenSCAD design for a bit holder that mounts on a pegboard. I want to be able to customize the number of rows and columns of bits, the spacing of the bits themselves, and the spacing and size of the pegboard mounting pins.

Specifically, the design should include the following parameters:

*   **PinSpacing:** The distance between the centers of pegboard pins. I'd like to specify this so it can work with different pegboard standards (default: 25.4mm).
*   **BitSpacing:** The distance between the centers of the bit holes (default: 10mm).
*   **Columns:** The number of columns of bit holders (default: 5).
*   **Rows:** The number of rows of bit holders (default: 1).
*   **PinDiameter:** The diameter of the pegboard pins (default: 5mm).
*   **PinLength:** The length of the pegboard pins (default: 5mm).
*   **BitDiameter:** The diameter of the bit holes (default: 8mm).
*  **BitLength:** The depth of the bit holes (default: 15mm)

The pegboard pins should have a slight hook/nub on the end to help hold them in place on the pegboard. The main body of the bit holder should have an angled back for structural integrity. The bit holes should be cylindrical.

Could you generate the OpenSCAD file for this? I'm not using any external libraries at the moment, but would like to use a `__Customizer_Limit__()` empty module to keep track of all the customizer inputs.

[Assistant]
bitmount.scad:
```
// the spacing of the pegboard pins
PinSpacing = 25.4; // [10:.1:100]

// the spacing of the bits
BitSpacing = 10; // [10:1:100]

// the length of bits
Columns = 5; // [1:1:1000]

// the rows of bits
Rows = 1; // [1:1:10]

/* [Advanced Settings] */

// the diameter of the pegboard pins
PinDiameter = 5; // [1:10]

// the length of the pegboard pins
PinLength = 5; // [1:20]

// the diameter of the bits
BitDiameter = 8; // [1:20]

// the length of the bits
BitLength = 15; // [1:50]

module __Customizer_Limit__ () {}

wall = 1;

pind = PinDiameter;
pinl = PinLength;
pins = max(pind + wall, PinSpacing);

bitd = BitDiameter;
bitl = BitLength;
bits = max(bitd + wall, BitSpacing);

width = max(bits * Columns, pins * 2);
height = bits * Rows;
pinc = floor(width/pins);

union() {
    difference() { // Cut the bits
        union() { // Draw the body
            difference() { // Draw the back angle
                translate([0, bitl/2, 0]) {
                    rotate([45,0,0]) {
                        cube([width, cos(45)*height, cos(45)*height], center=true);
                    }
                }
                translate([0, -height/2, 0]) {
                    cube([width, height, height], center = true);
                }
            }
            cube([width, bitl, height], center = true); // Draw the front
        }
        for (row = [0:Rows-1]) {
            translate([-bits*Columns/2+bits/2,0,-bits*Rows/2+bits/2+row*bits]) {
                rotate([90,0,0]) {
                    for (col = [0:Columns-1]) {
                        translate([bits*col, 0, 0]) {
                            cylinder(h = bitl, d = bitd, center=true, $fn=6);
                        }
                    }
                }
            }
        }
    }
    translate([-pins*pinc/2+pins/2, bitl/2, height/2]) { // Position pins
        rotate([-45,0,0]) { // Angle pins
            for (i = [0:pinc-1]) {
                translate([pins*i, pind/2, 0]) {
                    union() { // Draw pins
                        cylinder(h = pinl, d = pind, center=false, $fn=32); // Draw pin body
                        translate([0,0,pinl]) {
                            sphere(d = pind, $fn=32); // Draw pin joint
                            rotate([45,0,0]) {
                                cylinder(h = pind, d = pind, center=false, $fn=32); // Draw pin hook
                            }
                        }
                    }
                }
            }
        }
    }
}
```


[User]
I'm looking to have a custom battery holder designed, something that I can 3D print. I need it to be parametric, so I can adjust it to fit different battery sizes and quantities. I'd like to be able to specify the number of batteries in a row and the number of rows, and also the diameter and length of the batteries themselves.

For battery contacts, I need flexibility. I'd like to choose between simple slots for wires to pass through, or mounting holes for screws, to attach wire ends or nickel strips. It should be easy to run wires from one compartment to the next, both along the length of the holder, and across the width.

The main thing is that I want integrated springs, so I don't need to source metal ones. These springs should be printed right into the design, and be flexible enough to hold the batteries snugly. I'm open to different spring designs, but a double accordion style seems like it would be pretty good.

Also, I want to be able to choose whether the polarity alternates for each cell. This would be helpful for serial battery packs. I'd also like an option to emboss the battery type in each cell and maybe add small nubs to hold the batteries in place. It would also be handy if I could adjust the dimensions of the battery compartments slightly, to get a nice fit.

I'm looking for an OpenSCAD design. Ideally, the design should be in multiple files to keep it organized. I'm familiar with the `MCAD` library and have used parts of it before, but I don't want any dependencies if possible. Can you do that?

[Assistant]
flexbatter.scad:
```
//////////////////////////////////////////////////////////////////////////////////////
///
///  FLEXBATTER: Flexing battery holder with built-in spring
///
///  This file generates battery holders for arbitrary cylindrical sizes.
///  The number of batteries and their size is fully parametrized.
///
///  The usual metallic spring at the minus pole is replaced by a
///  flexible printed spring, which is pressing the contacts firmly to
///  the battery.
///
///  The contacts for the plus and minus pole can easily be made by
///  a few windings of the uninsulated ends of the connecting wires,
///  or by 4 or 5mm nickel strips, commonly used to build battery packs, or
///  or by screws and nuts, with presumably ring terminals.
///
///  Each battery compartment contains are several holes to the outside
///  as well ad to the neighboring compartments, through which connecting
///  wires can be passed for easy configuring of parallel, serial or
///  balanced-serial battery packs.
///
///  The preconfigured battery sizes are:
///  AA, AAA, C, D, 18650(Li-Ion), 18650P(protected Li-Ion), CR123A(16340), 26650, 38120.  Entering
///  a custom size and on-the-fly length and width adjustments are also permitted.
///
///  Given that the printed plastic spring needs to be flexible, ABS is the material
///  of choice here.
///
//////////////////////////////////////////////////////////////////////////////////////
///
///  2014-09-09 Heinz Spiess, Switzerland
///  2024-08-11 hybot - Added customizer options, custom battery sizes, optional alteranting polarities,
///  battery labels, micro adjustments, serial batteries in same compartment, small battery hold-downs,
///  and a choice between slots and screw holes for connectors.
///
///  Changed spring to double accordion type, as thingiverse customizer was barfing on the cooler
///  spline springs. This also moved the springs inside the case, and permitted the slots to pass
///  through to the outer case.
///
///  Updated code to remove let(), but not use assign() as thingiverse customizer doesn't yet accept
///  assign(). Comments added, other minor bugs fixed, and code indented.
///
///  uses springSine3D by Phil Schatzmann, 2020-08-29
///
///  WARNING: It is possible to configure dangerous voltages and/or amperages. Be sure you are using
///  appropriately sized wiring, insulation, and fusing, and know your batteries and printing material.
///
///  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
///  NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
///  NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE
///  LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
///  OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
///
///  released under Creative Commons - Attribution - Share Alike licence (CC BY-SA)
//////////////////////////////////////////////////////////////////////////////////////

/* [Battery Selection] */
//Pre-defined battery types
batteryType = 6; // [0:AAA, 1:AA, 2:C, 3:D, 4:CR123A, 5:CR2, 6:18650, 7:18650P, 8:26650, 9:38120, 10:Custom -- set values below]

// sigh, thingiverse doesn't support this
// Custom battery [length, diam]
//custom = [50.5, 14.5]; //[1:100]

//Custom battery name
custName = "";

//Custom battery length
custLength = 50.5; //.1

//Custom battery width
custDiam = 14.5; //.1

/* [Multiple Batteries] */
// Number of batteries, width
wcount = 1; //[1:8]

// Number of batteries, length
vcount = 1; //[1:4]

// Alternate polarities, per chamber
alt = 1; // [0:False, 1:Reverse case, 2:Only Battery Symbol]

// Battery holddown nubs
holddowns = 1; //[0:False, 1:True]

/* [Spring] */

// Spring style
springStyle = 0; // [0:Accordian, 1:Sine Wave]

// Spring plate thickness
springThick = 1.288; //.01

/* [Contacts] */

// Contact style
contactStyle = 0; // [0:Slots, 1:Screw Holes]

// Width of slots
slotWidth = 4.1; //.1

// Diameter of screw holes
holeDiam = 3.1; //.1

/* [Fine Tuning] */

// Length +/- adjustment
lenAdjust = 0; //.01

// Diameter +/- adjustment
diamAdjust = 0; //.01

/* [Hidden] */
// some options no longer wanted in customizer
// these have issues, or are unecessary fluf, but i don't want to rule them
// out just yet.

// Lower sides for easier battery grip. Not available for reverse case
deepen = 0; //[0:False, .35:True]

module stopCustomizer() {}

// non-customizable globals

altP = (alt == 1) && wcount > 1;
altBatterySymbol = (alt == 2) && wcount > 1;

batteryDim = [
              // l,  d,     hf,  shd, eps, el, xchan, name
              [45.9, 10.45, .84, 2.0, .10, 1,  [1/2], "AAA"],
              [50.9, 14.4,  .80, 2.5, .28, .5, [1/4, 3/4], "AA"],
              [51.6, 26.4,  .75, 3,   .28, 0,  [1/4, 3/4], "C"],
              [61.7, 34.0,  .75, 3,   .28, 0,  [1/4, 3/4], "D"],
              [34.8, 16.7,  .75, 2.5, .28, 0,  [1/2], "CR123A"],
              [27.5, 15.6,  .75, 2,   .28, 0,  [1/2], "CR2"],
              [65.6, 18.4,  .75, 3,   .28, 0,  [1/4, 3/4], "18650"],
              [66.9, 18.4,  .75, 3,   .28, 0,  [1/4, 3/4], "18650P"],
              [67.8, 26.4,  .72, 3,   .28, 0,  [1/4, 3/4], "26650"],
              [120.8, 38.4, .72, 3,   .28, 0,  [1/4, 3/4], "38120"],
              [undef] // Custom
              ];

flexbatterCustomizer(batteryDim[batteryType]);

/** main entry point
 ** decode predefined battery types, and handle custom battery dimenisons,
 ** then pass the real work to the OG flexbatter() method.
 **
 ** Used by external scripts to generate test cases.
 **
 ** @param a - an array of battery dimension; AKA, one row of batteryDim[].
 */
module flexbatterCustomizer(a) {
  if(len(a) == 1) { // must be a custom battery

    // for custom batteries, auto-calc shd (screw hole diam) and xchan
    // (cross channels on bottom). every thing else defaults.
    // xchan depends on diam of battery, wcount, and
    // if alternating polarites is true.
    cust_xchan = custLength < 46 ? [.5] : [1/4, 3/4];

    flexbatter(n=wcount, l=vcount*custLength, d=custDiam,
               shd=(custLength < 20 ? 2 : 3),
               xchan=(altP ? [] : cust_xchan), name=sanitize(custName));
  } else {
    flexbatter(n=wcount, l=vcount*(a[0]+lenAdjust), d=a[1]+diamAdjust, hf=a[2], shd=a[3], eps=a[4], el=a[5],
               xchan=(altP ? [] : a[6]), name=a[7]);
  }
}

/** OG non-customizer main method.
 **
 **  FLEXBATTER:  Flexing battery holder with integrated plastic spring
 **
 ** @param a number of battery cells to generate
 ** @param l length of the battery - note that the holding cell will be longer
 ** @param d diameter of the battery
 ** @param hf relative height of cell based on diameter (0 - 1; 1=full diameter)
 ** @param r spring length, calculated from length of battery
 ** @param shd screw hole diamter
 ** @param eps extra dimater space
 ** @param el extra length in spring
 ** @param xchan relative position of bottom traversal wire channel(s), as a percentage of length
 ** @param name the battery type, embossed in each cell
 ** @param $fn number of curve segments
 */
module flexbatter(n=1,l=65,d=18,hf=0.70,r=4,shd=3,eps=0.28,el=0,xchan=[1/4,3/4],name="", $fn=24) {

  eh = 0.25; // extrusion height
  ew = 0.56;  // extrusion width
  w = 4*ew;  // case wall thickness
  ws = springThick;

  ch = w-ws; // edge chamfering
  r = min(14, max(8, l * .2)); // linear spring length, range limited to 8 - 14mm.

  difference() {
    for(i=[0:n-1])translate([altP && i%2 ? 0: l-r+w, i*(d+w+ws), 0]){ // generate n battery cases
        mirror(altP && i%2 ? [0,0,0] : [1,0,0]) {
          difference(){
            union(){
              difference(){
                // main body
                translate([-r,-w-d/2,0])
                  chamfered_cube([l+w+r,d+2*w,hf*d+w+ch],ch);
                // main cavity
                translate([-2-r+2*w,-d/2,w])cube([2+l+r-2*w,d,d+1]);
                // small cylindrical carving of walls and bottom
                if(eps>0)translate([-2-r+2*w,0,d/2+w])rotate([0,90,0]) cylinder(r=d/2+eps,h=l+r-w-ws/2);
                // remove bottom of spring area
                translate([-2-r+2*w,-d/2+w/2,-1])cube([r-2*w+1.5*ws,d-w,5]);
              }
              // plastic spring for minus pole
              springPlate(r, d+2*w-4*ws, ws, hf*d-4*ws-.05, w, ws, ew, springStyle);

              //remove top and bottom chamfer gap on multi packs. side chamfers remain.
              if(wcount > 1 && i>0) {
                translate([-r+ch,-w-d/2,hf*d+w]) cube([l+w+r-2*ch, ch, ch], ch);
                translate([-r+ch,-w-d/2,0]) cube([l+w+r-2*ch, ch, ch]);
              }
            }

            // lower and upper slots for contacts
            if(contactStyle == 0) {
              for(z=[-2*ws,2*ws]) {
                translate([-2*ws-r,-slotWidth/2,w-ws/2+d/2+z])cube([l+2*w+2*r,slotWidth,ws]);
              }
              // add a bottom slot for a connector strip to pass through,
              translate([l-ew,-slotWidth/2,-1])cube([2*ws,slotWidth,w+2]);
            } else if(contactStyle == 1) {
              translate([0,0,w + d/2]) rotate([0,90,0]) cylinder(r=holeDiam/2,h=l*w,center=true, $fn=24);
            }

            // longitudinal bottom wire channel
            translate([-2*ws-r,0,0])rotate([0,90,0])cylinder(r=w/2,h=l+w+2+r,$fn=5);

            // traversal bottom wire channels
            // not made here for alternating pack, see below
            for(x=l*xchan)translate([x,-d/2-w-1,eh]) rotate([-90,0,0])cylinder(r=w/2,h=d+2*w+ws+2,$fn=6);

            // grip deepening
            if(deepen>0 && !altP) {
              translate([w+l/2-0.5,-d/2-w-0.01,w+d+l]) {
                rotate([-90,0,0]) {
                  cylinder(r=l+deepen*d,h=d+2*w+2*ws+2,$fn=72);
                  if(i==0)cylinder(r1=l+deepen*d+ch,r2=l+deepen*d,h=ch+0.02,$fn=72);
                  if(i==n-1)translate([0,0,d+2*w-ch])cylinder(r2=l+deepen*d+ch,r1=l+deepen*d,h=ch+0.02,$fn=72);
                }
              }
            }

            // conical screw holes in corners
            for(x=[7+shd,l-2*shd])for(y=[-d/2+shd,d/2-shd]) {
              translate([x,y,-1]) {
                cylinder(r=shd/2,h=w+2);
                translate([0,0,w-shd/2+1])cylinder(r1=shd/2,r2=shd,h=shd/2+0.01);
              }
            }

            // holes for wires passing inside
            for(sy=[-1,1])scale([1,sy,1]){
              translate([-r-w,-d/2,w]) cube([l+r+2*w+ws,w,w]);
              for(x=[3,l-7])translate([x,-d/2-w-ws-1,w]) cube([3,w+ws+3,2]);
              translate([3,-d/2+w/2-0.75,-1]) cube([3,1.5,w+2]);
              translate([-0.5,-d/2+w/2,0])rotate([0,90,0]) cylinder(r=w/2,h=6.5,$fn=5);
            }

            for(j = [1:vcount]) {
              // engrave battery symbol
              if(altBatterySymbol && i%2) {
                translate([l*j/vcount+2*w, 0, eh/2]) mirror([1,0,0]) batterySymbol(w, l/vcount, d, eh, name, true);
                //if(j<vcount) translate([l-l*j/vcount, -d/2,w-ws/2]) color("blue") cube([1,d,ws],);
              } else {
                translate([l-l*j/vcount, 0,0]) batterySymbol(w, l/vcount, d, eh, name, altP && i%2);
                //if(j<vcount) translate([l-l*j/vcount, -d/2,w-ws/2]) color("blue") cube([1,d,ws],);
              }
            }
          }

          if(contactStyle == 0) {
            // horizontal contact bulges (+ and - pole)
            for(x=[-0.3,l])
              hull()for(y=[-3+el,3-el])
                translate([x,y,w+d/2])sphere(r=ws);
          }

          // battery hold-downs
          if(holddowns) {
            for(j = [1:vcount]) {
              translate([l*j/vcount -l/(vcount*2) -w, -d/2-ws/4, hf*d+w-.12])  sphere(r=ws/1.2);
              translate([l*j/vcount -l/(vcount*2) -w, d/2+ws/4, hf*d+w-.12])  sphere(r=ws/1.2);
            }
          }
        }
      }

    // for alternating pack, force width traversal channels and holes to 40%
    // (to avoid mounting holes), and go full width to pass through all chambers
    if(altP) {
      translate([l*.4,-d/2-w-1,eh]) rotate([-90,0,0])cylinder(r=w/2,h=wcount*(d+2*w+ws+2),$fn=6);
      translate([l*.4-w,-d/2-w-1,w+ws]) rotate([-90,0,0])cube([slotWidth, ws, wcount*(d+2*w+ws+2)]);
    }
  }
}

/**
 ** generate a battery symbol, with body, top cap, label, and polarity markers
 **
 ** @param w wall thickness
 ** @param l battery length
 ** @param d battery diameter
 ** @param eh extrusion height
 ** @param name label
 ** @param flipText boolean, needed for "reverse case" cells
 */
module batterySymbol(w,l,d,eh,name,flipText) {
  // battery body, with label
  translate([w+l/2,d/4+1,w]) {
    difference() {
      cube([l/4.5,d/4.5,4*eh],true);
      fontSize = (l<50) ? 1.2 : (l<68) ? 2.2 : 2.6;
      offset = textWidth(name, fontSize);

      translate([flipText?-offset/2:offset/2, -1, -4*eh])
        mirror([!flipText?1:0, 0,0])
        linear_extrude(4*eh)
        text(text=name, size=fontSize, font="Liberation Mono:style=bold");
    }
  }

  // battery top
  translate([w+l/2 + l/8.75,d/4+1,w])
    cube([w/2.6,d/10,4*eh],true);
  // plus symbol
  union() { // for short batteries +- on the side
    sy=(l<45)?-1:1;
    translate([w+l/2+l/(sy>0?5:10),sy*(d/4+1),w]) color("red") {
      cube([1,min(6,d/5),4*eh],true);
      cube([min(6, d/5),1,4*eh],true);
    }
    // engrave minus symbol
    translate([w+l/2-l/(sy>0?5:10),sy*(d/4+1),w])
      color("black") cube([1,min(6, d/5),4*eh],true);
  }
}

/**
 ** calc text width in mm, for monospaced fonts only.  this is used for centering or aligning text.
 ** we use "Liberation Mono", style=Bold.
 **
 ** each char in a mono font is one em wide. (conventially, an em is the width of the letter "M",
 ** though this is not exactly true.) since SCAD fontSize is given in
 ** milimeters, not points, no conversion is necessary and the width of a text string is just
 ** the number of characters in it times the width of an em (or close enough).
 **
 ** @param string the text
 ** @param fontSize the font size
 ** @return the approximate width, in mm.
 **/
function textWidth(string, fontSize) =  len(string) * fontSize;

/**
 ** sanitize a string by removing non-ASCII and control chars.
 ** may not be needed, customizer does a pretty good job of this for input fields
 **
 ** @param name the string to sanitize
 ** @return the sanitized string, with no 8 bit high characters
 **/
function sanitize(name) = chr([for (c = name) ord(c) < 127 && ord(c)>  31 ? ord(c) : ""]);

/**
 ** build a cube with chamfered edges
 **
 ** @param size cube size
 ** @param d chamfer size
 **/
module chamfered_cube(size,d=1){
  hull(){
    translate([d,d,0])cube(size-2*[d,d,0]);
    translate([0,d,d])cube(size-2*[0,d,d]);
    translate([d,0,d])cube(size-2*[d,0,d]);
  }
}

/**
 ** create a front plate with an accordion spring attached.
 ** the spring will be lifted off the floor.
 **
 ** @param len length of entire spring box
 ** @param width width of plate and spring
 ** @param height height of plate and spring
 ** @param w wall thickness
 ** @param ws spring wall thickness
 ** @param ew extrusion width
 */
module springPlate(len, width, thick, height, w, ws, ew, style) {
  //plate
  x = -width/2+ws/4;
  translate([-ws,x, 3*ws])cube([thick, width-ws/2, height+3.5*ws]);

  // spring
  translate([-len+2*thick-ws/2, 0, 3*ws]) {
    if(style == 0) {
      for(i=[0, 1]) {
        mirror([0,i,0]) springSine3D(length=len-2*thick-ew, height=height+3.5*ws, width=width-thick,
                                     windings=(len<12? 1 : 3), wireDiameter=thick/2.35);
      }
    } else {
      springSine3D(length=len-2*thick, height=height+3.5*ws, width=width-thick,
                   windings=2, wireDiameter=thick);
    }
  }
}

/////////////////////////////////////////////////
// springSine3D by Phil Schatzmann, 2020-08-29
/////////////////////////////////////////////////

/**
 ** An edge (or line) between 2 cylinders
 ** @param start starting point, [x,y,z]
 ** @param end ending point, [x,y,z]
 ** @param d diameter of line
 ** @param h height of line
 ** @param fn number of faces
 **/
module line3D(start, end,d=1,h=10, fn=4) {
  hull() {
    translate(start) cylinder(h=h, d=d, $fn = fn);
    translate(end) cylinder(h=h, d=d, $fn = fn);
  }
}

/**
 ** Generate a sinious spine for an accordion spring.
 **
 ** @param length
 ** @param height
 ** @param width
 ** @param step
 ** @param wireDiameter
 ** @param support generate a 1/4 witdth line for use with supports
 ** @param fn number of faces
 **/
module springSine3D(length=20, height=10, width=10, windings=4, step=10, wireDiameter=0.7, support=false, fn=36){
  dx = length / (360 * windings);
  for(i = [-step : step : 360 * windings]){
    x0 = (i-step) * dx;
    y0 = sin(i-step) * width/2;
    x = i * dx;
    y = sin(i) * width/2;

    if(support) {
      // do a thin line as support, at the bottom of the spring, not quite full width so
      // they don't join
      if(abs(y) <  .95*width/2)
        line3D([x0,y0,0],[x,y,0],d=wireDiameter/4,h=height, fn=fn);
    } else {
      line3D([x0,y0,0],[x,y,0],d=wireDiameter,h=height, fn=fn);
    }
  }
}

```