[User]
I need a parametric ammo box designed in OpenSCAD. I've got a box in mind, and I'm hoping you can help me create the SCAD files for it.

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

*   **Number of Holes:**  I need to be able to specify the number of holes in the X and Y directions (like a grid). Let's call these `holesx` and `holesy`.
*   **Hole Size:** The individual holes, where the ammo sits, should be square and have a side length specified by the `bullethole` parameter.
*   **Spacing:** The space between the individual holes will be set by the `betweenhole` parameter. The holes are also set from the edge of the box by `bottom_outsidewall`.
*   **Bottom Box:** The bottom part of the box should have:
    *   An outside wall thickness `bottom_outsidewall`.
    *   A bottom taper or rim, specified by `bottom_taper`, that reduces the wall thickness at the base.
    *   A total height of the bottom box outer walls, specified by `bottom_height`.
    *   A divider height within the bottom box, set by `dividerheight` that creates the pockets for individual ammo.
    *   A rim with thickness `rim` and a height `rimheight`. If `rimheight` is set to 0, then there should be no rim.
    *   A base thickness specified by `bottomthick`.
*   **Top Box:** The top part of the box should have:
    *   An outer wall height set by `top_height`.
    *   A top thickness for the upper surface, set by `top_upperthinkness`.
    *   A wall thickness for the outer walls, set by `top_wallthinkness`.
*   **Corner Radius:** There should be a corner radius applied to both the top and bottom of the box, controlled by a single parameter called `corner_radius`.
*   **Number of Faces:** The number of faces for the rounded elements, can be set by `numfaces`.
*   **Hinges:** The box should have hinges on one side.
    *   The distance between the edge of the box and the placement of the hinge should be set by `hingeedge`.
    *   There should be a space between hinges controlled by `hingespace`.
    *   The outside radius of the hinges should be controlled by `outrad`.
    *   The pin hole radius for the top hinge should be set by `top_hingepinrad` and the lower by `hingepinrad`.
    *   The width of the side hinges is controlled by `wid`. The center hinge will be sized automatically.
*   **Clasp:** There should be a clasp on the opposite side of the hinges.
*   **Clearance:** There should be a small clearance parameter to avoid clipping, called `extraclear` .
*   **Visibility:** I would like the user to be able to render only the top, only the bottom, or both parts of the box by setting the `showtop` and `showbottom` variables to 1 or 0.
*   **Ammo Dimensions:** I would like to include a module to create an example bullet model for testing the fit. The dimensions for that should be specified by several parameters, such as the diameters at the top and bottom of the case, the taper lengths, the neck diameter, and the projectile diameter, as well as the overall bullet tip height.  I will include these in the code for clarity.

I think that covers all the parameters I need, and I need the code to generate the entire box. I would like you to generate the entire code based on what I said here - don't assume I have anything else created.

Thanks a lot.

[Assistant]
box_custom_762_39.scad:
```
// 1 = show 0 = hind
showtop=1;
// 1 = show 0 = hind
showbottom=1;
/*units are in millimetres(mm)*/
/* number of holes in the x and y direction */
//Number of bullets left to right
holesx=10;  //at 5 hinges have issue. default = 10
//Number of bullets front to back
holesy=5;  //default = 10
//Pocket size for one bullet (square)
bullethole =11.7;  //default for 223 = 10 (gives .4mm clearance)
//Wall size between pockets
betweenhole = .8;  //default = 1.25
//Thinkness of outside wall of bottom
bottom_outsidewall=3.5; //wall thickness. default = 3.5
//Differances between base and rim of the bottom
bottom_taper=1.5; //wall thinkness at bottom. default = 1.5
//Height of bottom outer wall not including rim
bottom_height=38; //total height of lower box. default = 47
//Height of bullet divider in bottom box
dividerheight=34; //height of bullet dividers. default = 28
//Thinkness of rim on bottom box
rim=1.5; //thickness of lid rim. default 1.5
//Height of rim on bottom box
rimheight=3.7; //height of lid rim. set to 0 to remove rim. default 3.7
//Thinkness of the base of the bottom box
bottomthick=1; // thickness of bottom of box. default 2
/* inside depth of box is bottom_height-bottomthick */
//Corner radius of the top and bottom box
corner_radius=3;
//Number of faces for round elements
numfaces=150;// number of faces on round objects
//Height of top outer wall
top_height=25; // default 30
//Thickness of upper part of top box
top_upperthinkness=1.5; //thickness of upper box top. default 1
//Thickness of wall part of top box
top_wallthinkness=1.5; //thickness of upper box wall. default 1.5

spacing = bullethole+betweenhole ;
outeredge = spacing-bottom_outsidewall;
outersizex=holesx*spacing-betweenhole+2*bottom_outsidewall;
outersizey=holesy*spacing-betweenhole+2*bottom_outsidewall;
topvoidx = holesx*spacing-betweenhole;
topvoidy = holesy*spacing-betweenhole;
//Distance between edge of box and hinges
hingeedge=8.5;//distance from edge that the hinges are placed. default 8.5
//Clearance space between hinges
hingespace=.4; //space between hinges. default .4
//Radius of hinges
outrad=2.5; //outside radius of hinges. default 2.5
//Hole for pin of upper hinge
top_hingepinrad=1.2; //Hole for pin of upper hinge. default 1.1
//Hole for pin of lower hinge
hingepinrad=1.2; //Hole for pin of lower hinge. default= 1.1
//Width of hinges
wid=16; // width of side hinges. default = 16
//Value added to remove clipping
extraclear=0.01;  //removes clipping between difference
//Not used
backerthick=1.8;

/*---output---*/
//to make STL of each half, comment out one half and render. repeat for the other half.
//**************************************************//
//**************************************************//
if (showtop && !showbottom) translate([outersizex,0,bottom_height+top_height]) rotate([0,180,0])top();
if (showtop && showbottom) top();
if (showbottom)bottom();
//color("orange") translate([bottom_outsidewall+bullethole/2,bottom_outsidewall+bullethole/2,bottomthick+0.2]) bullet(); //size test bullet. "#" the two halfs and uncomment the bullet to check fit.
//**************************************************//
//**************************************************//

module top(){
    union() {
      //translate([outersizex/2+2*(bullethole+betweenhole),outersizey-top_wallthinkness,bottom_height-rimheight]) rotate([180,270,0]) backer();
      //translate([outersizex/2-2*(bullethole+betweenhole),outersizey-top_wallthinkness,bottom_height-rimheight]) rotate([180,270,0]) backer();

      high_box();
      translate([wid/2+hingeedge+wid+hingespace,-outrad,bottom_height-rimheight-outrad]) rotate([0,270,0]) hinge_h(wid); //right hinge
      translate([outersizex-(hingeedge+wid/2+wid+hingespace),-outrad,bottom_height-rimheight-outrad]) rotate([0,270,0]) hinge_h(wid);//left hinge
      translate([outersizex/2,outersizey,bottom_height-rimheight]) rotate([180,270,0]) clasp();
    }
}
module high_box(){
    difference() {
        high_outside_base();
        high_inside_base();
    }
}
module bottom() {
    union() {
        low_box();

        translate([hingeedge,-outrad,bottom_height-rimheight-outrad]) rotate([0,90,0]) cylinder(h=1,r=outrad,center=false); //hinge plug on right
        translate([wid/2+hingeedge,-outrad,bottom_height-rimheight-outrad]) rotate([0,90,0]) hinge(wid); // back right

    c_wid=outersizex-4*wid-4*hingespace-2*hingeedge;

        translate([outersizex/2,-outrad,bottom_height-rimheight-outrad]) rotate([0,90,0]) hinge(c_wid); //center hinge

        translate([outersizex-hingeedge-wid/2,-outrad,bottom_height-rimheight-outrad]) rotate([0,90,0]) hinge(wid); //back left

        translate([outersizex/2,outersizey,bottom_height-rimheight]) rotate([0,90,0]) bump();
    }
}
module low_box(){
    difference() {
        low_outside_box();
        low_inside_box();
    }
}
module low_outside_box(){
    union() {
        low_outside_base();
        low_outside_rim();
    }
}
module low_inside_box(){
    low_inside_void();
    low_inside_walls();
}
module high_outside_base(){
    color("lime")
    hull(){

      translate([bottom_taper+corner_radius,bottom_taper+corner_radius,bottom_height+top_height])
        linear_extrude( 0.1 )
          offset(r=corner_radius,$fn=numfaces)
            square([outersizex-(2*bottom_taper)-(2*corner_radius),outersizey-(2*bottom_taper)-(2*corner_radius)],center=false);

      translate([corner_radius,corner_radius,bottom_height-rimheight])
       linear_extrude( 0.1 )
        offset(r=corner_radius,$fn=numfaces)
         square([outersizex-(2*corner_radius),outersizey-(2*corner_radius)],center=false);
    }
}
module high_inside_base(){
    color("blue")
    hull(){

      translate([bottom_taper+corner_radius+top_wallthinkness,bottom_taper+corner_radius+top_wallthinkness,bottom_height+top_height-top_upperthinkness])
        linear_extrude( 0.1 )
          offset(r=corner_radius,$fn=numfaces)
            square([outersizex-(2*bottom_taper)-(2*corner_radius)-2*top_wallthinkness,outersizey-(2*bottom_taper)-(2*corner_radius)-2*top_wallthinkness],center=false);

      translate([corner_radius+top_wallthinkness,corner_radius+top_wallthinkness,bottom_height-rimheight-0.1])
       linear_extrude( 0.1 )
        offset(r=corner_radius,$fn=numfaces)
         square([outersizex-(2*corner_radius)-2*top_wallthinkness,outersizey-(2*corner_radius)-2*top_wallthinkness],center=false);
    }
}
module low_outside_base(){
    color("green")
    hull(){

      translate([bottom_taper+corner_radius,bottom_taper+corner_radius,0])
        linear_extrude( 0.1 )
          offset(r=corner_radius,$fn=numfaces)
            square([outersizex-(2*bottom_taper)-(2*corner_radius),outersizey-(2*bottom_taper)-(2*corner_radius)],center=false);

      translate([corner_radius,corner_radius,bottom_height-rimheight-0.1])
       linear_extrude( 0.1 )
        offset(r=corner_radius,$fn=numfaces)
         square([outersizex-(2*corner_radius),outersizey-(2*corner_radius)],center=false);
    }
}
module low_outside_rim(){
    color("purple")
    translate([bottom_outsidewall-rim+corner_radius,bottom_outsidewall-rim+corner_radius,bottom_height-rimheight])
    linear_extrude( rimheight )
         offset(r=corner_radius,$fn=numfaces)
          square([topvoidx+(2*rim)-(2*corner_radius),topvoidy+(2*rim)-(2*corner_radius)],center=false);
}
module low_inside_void(){
    color("blue")
    translate([bottom_outsidewall+corner_radius,bottom_outsidewall+corner_radius,dividerheight+bottomthick])
    linear_extrude( bottom_height-dividerheight-bottomthick+extraclear )
         offset(r=corner_radius,$fn=numfaces)
          square([topvoidx-(2*corner_radius),topvoidy-(2*corner_radius)],center=false);
}
module low_inside_walls(){
    color("red")
    intersection(){
      for (i=[1:holesx]) {/* x copies */
        for (j=[1:holesy]) {/* y copies */
          translate([i*spacing-outeredge,j*spacing-outeredge,bottomthick])
          cube([bullethole,bullethole,dividerheight+extraclear], center=false);
        }
      }
      translate([bottom_outsidewall+corner_radius,bottom_outsidewall+corner_radius,bottomthick])
      linear_extrude( dividerheight+extraclear )
        offset(r=corner_radius,$fn=numfaces)
         square([topvoidx-(2*corner_radius),topvoidy-(2*corner_radius)],center=false);
    }
}

module hinge(width){
    dep=outrad;
    heig=12-outrad;

//math
//*
r_one=outrad;
p_one = [0,0];
p_zero = [heig,dep+1];
double_dist=(p_one[0]-p_zero[0])*(p_one[0]-p_zero[0])+(p_one[1]-p_zero[1])*(p_one[1]-p_zero[1]);
dist=sqrt(double_dist);
double_r_zero=(double_dist-r_one*r_one);
//r_zero=sqrt(double_r_zero);
a=(double_r_zero-r_one*r_one+double_dist)/(2*dist);
h=sqrt(double_r_zero-a*a);
stuff=[((p_one[0]-p_zero[0])/dist),((p_one[1]-p_zero[1])/dist)];
p_two=[(p_zero[0]+a*stuff[0]),(p_zero[1]+a*stuff[1])];
p_three=[[(p_two[0]+h*stuff[1]),(p_two[1]-h*stuff[0])],[(p_two[0]-h*stuff[1]),(p_two[1]+h*stuff[0])]];
//echo(p_three[0]);// intersection 1
//echo(p_three[1]);// intersection 2
//end of math//*//

    translate([0,0,-width/2])
    difference(){
        union(){
            cylinder(h=width,r=outrad,center=false,$fn=150);
            points = [ [-outrad,0], p_three[1], p_zero, [-outrad,dep+1] ];
            linear_extrude(width)
            polygon(points);
        }
        translate([0,0,-0.1])
        cylinder(h=width+0.2,r=hingepinrad,center=false,$fn=150);
    }
}
module hinge_h(width){
    dep=outrad;
    heig=12-outrad;

//math
//*
r_one=outrad;
p_one = [0,0];
p_zero = [heig,dep+1];
double_dist=(p_one[0]-p_zero[0])*(p_one[0]-p_zero[0])+(p_one[1]-p_zero[1])*(p_one[1]-p_zero[1]);
dist=sqrt(double_dist);
double_r_zero=(double_dist-r_one*r_one);
//r_zero=sqrt(double_r_zero);
a=(double_r_zero-r_one*r_one+double_dist)/(2*dist);
h=sqrt(double_r_zero-a*a);
stuff=[((p_one[0]-p_zero[0])/dist),((p_one[1]-p_zero[1])/dist)];
p_two=[(p_zero[0]+a*stuff[0]),(p_zero[1]+a*stuff[1])];
p_three=[[(p_two[0]+h*stuff[1]),(p_two[1]-h*stuff[0])],[(p_two[0]-h*stuff[1]),(p_two[1]+h*stuff[0])]];
//echo(p_three[0]);// intersection 1
//echo(p_three[1]);// intersection 2
//end of math//*//

    translate([0,0,-width/2])
    difference(){
        union(){
            cylinder(h=width,r=outrad,center=false,$fn=150);
            points = [ [-outrad,0], p_three[1], p_zero,[outrad,outrad], [0,outrad] ];
            linear_extrude(width)
            polygon(points);
        }
        translate([0,0,-0.1])
        cylinder(h=width+0.2,r=top_hingepinrad,center=false,$fn=150);
    }
}
module bump(){
    c_wid=20.2;
    a_wid=20.6;
    b_wid=25.2;
    sb_points = [ [0,0], [2.7,2.4], [4.5,2.4], [8.5,-1] ];
    ib_points = [ [4,-0.1], [4.5,2.8], [8.6,0] ];
    translate([0,0,-b_wid/2])
    difference(){
        linear_extrude( b_wid )
        polygon(sb_points);

        translate([0,0,(b_wid-a_wid)/2])
        linear_extrude( a_wid )
        polygon(ib_points);
    }
}
module clasp(){
  c_wid=20.2;

  thin=0.5;
  c_points = [ [0,0], [9.5,0], [20.5,6.5], [9.5,5.5], [2.5,5.5], [0,3] ];
  c_points_2 = [ [9.5,5.5-thin], [9.5,3], [4.5,3], [4.5,5.5-thin] ];
  translate([-9.5,-5.5,-c_wid/2])
  linear_extrude( c_wid )
  difference(){
    polygon(c_points);
    polygon(c_points_2);
  }
}
module backer(){
    outrad=1.5;
    clearrim=bottom_outsidewall-2*rim+0.1;
    translate([0,rim,-backerthick/2])
    union(){
        translate([outrad,clearrim+outrad,0])
        cylinder(h=backerthick,r=outrad,center=false,$fn=150);

        points = [ [0,clearrim+outrad], [outrad,clearrim], [rimheight,clearrim], [2*rimheight,-rim], [top_height+rimheight,-rim], [top_height+rimheight,clearrim+2*outrad], [outrad,clearrim+2*outrad] ];

        linear_extrude( backerthick ) polygon(points);
    }
}
module bullet(){
    /*223 start
    b_case_dia_bot=9.56; // default=9.56
    b_case_dia_top=8.95; // default=8.95
    b_case_tape_bot=36.4; // default=36.4
    b_case_dia_neck_bot=6.32; // default=6.32
    b_case_tape_top=39.4; // default=39.4
    b_case_dia_neck_top=6.25; // default=6.25
    b_case_neck_top=44.4; // default=44.4
    b_pro_dia_base=5.69; // default=5.69
    b_bullet_tip=56; // default=56
    //*///223 end
    //*7.62x39 start
    b_case_dia_bot=11.35; // default=9.56
    b_case_dia_top=10.07; // default=8.95
    b_case_tape_bot=30.5; // default=36.4
    b_case_dia_neck_bot=8.6; // default=6.32
    b_case_tape_top=33; // default=39.4
    b_case_dia_neck_top=8.6; // default=6.25
    b_case_neck_top=38.7; // default=44.4
    b_pro_dia_base=7.92; // default=5.69
    b_bullet_tip=56; // default=56
    //*///7.62_39 end
    hull(){
      cylinder(h=.1,r=b_case_dia_bot/2,center=false,$fn=150);
      translate([0,0,b_case_tape_bot]) cylinder(h=.1,r=b_case_dia_top/2,center=false,$fn=150);
}
    hull(){
      translate([0,0,b_case_tape_bot]) cylinder(h=.1,r=b_case_dia_top/2,center=false,$fn=150);
      translate([0,0,b_case_tape_top]) cylinder(h=.1,r=b_case_dia_neck_bot/2,center=false,$fn=150);
    }
    hull(){
      translate([0,0,b_case_tape_top]) cylinder(h=.1,r=b_case_dia_neck_bot/2,center=false,$fn=150);
      translate([0,0,b_case_neck_top]) cylinder(h=.1,r=b_case_dia_neck_top/2,center=false,$fn=150);
    }
    hull(){
      translate([0,0,b_case_neck_top]) cylinder(h=.1,r=b_pro_dia_base/2,center=false,$fn=150);
      translate([0,0,b_bullet_tip]) cylinder(h=.1,r=1,center=false,$fn=150);
    }
}
```


[User]
I'd like to create a parametric design for a miniature base with a rotating wound tracker, kind of like a dial on the bottom. I need the ability to customize the base, and I'd prefer it if the design was broken down into modules for better organization and clarity.

Here's what I'm looking for in terms of parameters:

**Base Dimensions and Shape:**

*   Two diameters for the base, `Base_diameter_1` and `Base_diameter_2`. These will be used to create both round and oval bases.
*   A `base_height` parameter that controls the total height of the base.
*   A `slant_angle` that defines the angle of the base's side slant.

**Wound Tracker Dial:**

*   `Dial_numbers` should control the number of wounds (or numbers) on the rotating dial at the bottom. This parameter will affect text size and a top cut-out.
*   A `dial_height` parameter for the height of the rotating dial itself.
*   A `Max_font_size` parameter to limit the maximum font size of numbers on the dial. This will be especially useful on bases with a smaller number of wounds.

**Optional Features:**

*   A `Center_magnet_diameter` parameter to generate a hole in the center of the base for a magnet. Setting this to 0 should turn off the magnet hole generation.
*   `Center_magnet_height` to control the height of the magnet hole, ideally a multiple of the print layer height.
*   A `Integrated_peg` boolean parameter. If true, the peg connecting the base top to the rotating dial should be integrated (snap fit). If false, the peg should be a separate part that needs to be glued into the base top.
*   A `Clip` boolean parameter to generate a standee clip and the associated hole for it in the base. If this is enabled, it would be good if the center magnet hole is turned off or set to a larger diameter.

**Print Settings:**

*   A `Resolution` parameter defining the print layer height used in the design, especially for calculating the height of the peg and numbers on the dial.
*   A `Tolerance` parameter for a general tolerance in the design, to avoid too tight of fits.

**Rendering Options:**

*   An `Exploded_view` boolean parameter to separate the parts in the rendered view.
*   A `Final_rendering` boolean parameter to enable higher-quality rendering.

**Important Design Considerations:**

*   The font size of the numbers on the dial should be dynamically calculated based on the dial size and the number of wounds but be capped to `Max_font_size`.
*   The peg should have a snap-fit design (if `Integrated_peg` is true) or be a loose fit if false.
*   The standee clip should be parametric so it can be adjusted in future iterations.
*   All parts should be generated as separate parts when `Exploded_view` is true, preferably offset along the Y-axis.

It would be nice if the text on the dial was generated using the `text()` function and placed along a circular path.

Could you generate the OpenSCAD code for this? I would prefer if you didn't use any external libraries for this initial design.

[Assistant]
wound-tracker-base-v2.scad:
```
/// Miniature base with a rotating wound tracker
///
/// This model generates a miniature base (round or oval) with / a wound tracker
//at the bottom.

/* [Base parameters] */

// 1st diameter of the base.
Base_diameter_1 = 25; // [25:1:200]
// 2nd diameter of the base. Different from the 1st for oval bases.
Base_diameter_2 = 25; // [25:1:200]
// Number of wounds on the base dial. Impacts text size and top cut-out so experiment to get a proper base.
Dial_numbers = 10; // [2:1:30]
// Maximum font size (in mm) to use. Useful for limiting text size on bases with small number of wounds.
Max_font_size = 4; // [2.0:0.1:20]
// Generate a center holder for magnet of this diameter. 0 turns it off.
Center_magnet_diameter = 5; // [0:0.5:10]
// Height of the magnet to generate the hole for. Note that it has to be multiple of print layer height.
Center_magnet_height = 2; // [1:0.1:3]
// Set to false to get a 3-part design with a pushed/glued peg.
Integrated_peg = 1; // [0:False,1:True]
// Set this to true to generate a stand clip and holes for it. When using the clip, it's best to disable the center magnet hole or set it to at least 5.
Clip = 0; // [0:False,1:True]

/* [Print parameters] */

// Set this to the print layer height used to calculate heights the peg and numbers on the bottom dial.
Resolution = 0.2;
// General tolerance for places that should not be tight.
Tolerance = 0.1;

/* [Rendering parameters] */
// Set this to false to render an assembled view.
Exploded_view = 1; // [0:False,1:True]
// Set this to false for faster previews.
Final_rendering = 1; // [0:False,1:True]

/* [Hidden] */
// Below are untested parameters. Assume that changing them may break things.

// Total base height
base_height = 4;
// Dial height
dial_height = 2;
// Angle of the base slant
slant_angle = 15;

module __Customizer_end__ () {}  // empty module to hide vars from customizer

expl = Exploded_view;
final = Final_rendering;

center_magnet = [Center_magnet_diameter, Center_magnet_height];
$fa = 1;
$fs = final ? 0.01 : 0.1;
PI = 3.141592;

function f_sz(radius, nchars) = 2 * 3.14159 * radius / (nchars * 1.5);
function get_font_sz(r, nc) = f_sz(r, nc) > Max_font_size ? Max_font_size : f_sz(r, nc);

module prism(l, w, h) {
    polyhedron(
        points=[[0,0,0], [l,0,0], [l,w,0], [0,w,0], [0,w,h], [l,w,h]],
        faces=[[0,1,2,3],[5,4,3,2],[0,4,5,1],[0,3,4],[5,2,1]]
    );
}

// FIXME: Add maximum text size parameter to limit the font size...
module revolve_text(radius, chars) {
    chars_len = len(chars);
    font_size = get_font_sz(radius, chars_len);
    echo("Font size: ", font_size);
    step_angle = 360 / chars_len;
    for(i = [0 : chars_len - 1]) {
        rotate(i * step_angle)
            translate([-font_size / 2, -(radius+3*Resolution), 0.5])
				rotate(-90)
                text(
                    chars[i],
                    font = "Arial",
//2.//                    font = "Nimbus Mono PS:style=Bold",
//3.//                    font = "Verdana:style=Bold",
                    size = font_size,
                    valign = "bottom", halign = "right"
                );
    }
}

function peg_base_height() = 0.6;

module peg(diam, height, magnet_size = [0,0]) {
    r = diam / 2;
    res = Resolution;
    //echo(r=r);
    difference() {
        union() {
            cylinder(h = height, r = r - Tolerance);
            cylinder(h = height - Resolution, r = r);
            cylinder(h = peg_base_height(), r = r * 1.5);
        }
        if (magnet_size[0] > 0) {
            cylinder(d = magnet_size[0] - 6*res, height);
            cylinder(d = magnet_size[0] - 4*res, magnet_size[1] + 2*res);
            cylinder(d = magnet_size[0] - 2*res, magnet_size[1] + res);
            cylinder(d = magnet_size[0], magnet_size[1]);
        }
    }
}

function cap_outer_r(diam) = (diam / 2) + 1 + Tolerance;
module cap(diam, height) {
    r = diam / 2;
    //echo(r=r);
    difference() {
        cylinder(h = height, r = cap_outer_r(diam));
        // Make the hole slightly smaller for a push-fit.
        cylinder(h = height, r = r - Tolerance);
    }
}

function dial_txt(n) =
    [for (i = [1:n]) str(i)];

module number_dial(diam, n, height, slant_angle = 0) {
    txt = dial_txt(n);
    top_diam = diam - 2 * height * tan(slant_angle);
    difference() {
        cylinder(h = height, d1 = diam, d2 = top_diam);
        translate([0,0,height - 3 * Resolution])
            linear_extrude(height=1)
            revolve_text(top_diam/2 - 1, txt);
    }
}

// Old round-only design.
module base_top(d1, d2, height, top_thickness, n_chars, peg_diam,
                slant_angle = 15) {
    sd = min(d1, d2);
    ld = max(d1, d2);
	w = get_font_sz(sd/2 - 1, n_chars);
    difference() {
        hull() {
            resize([ld, sd, 0])
                cylinder(h=0.01, d = 1);
            translate([0, 0, height - 0.01])
                resize([ld - 2 * height * tan(slant_angle),
                        sd - 2 * height * tan(slant_angle),
                        0])
                cylinder(h=0.01, d = 1);
        }
        hull() {
            resize([ld - 2, sd - 2, 0])
                cylinder(h=0.01, d = 1);
            translate([0, 0, height - top_thickness])
                resize([ld - 2 - 2 * height * tan(slant_angle),
                        sd - 2 - 2 * height * tan(slant_angle),
                        0])
                cylinder(h=0.01, d = 1);
        }
        rotate([0,0,-90]) difference() {
            translate([0,-w/2-Resolution,-0.5])
                cube([sd/2+2, w+2*Resolution, height + 1], center=false);
            cylinder(h = height, r=sd/2 - (1+floor(log(n_chars)))*w*0.8-Resolution);
        }
    }
    cap(peg_diam, height);
}

module tracker_base(
        // 1st base diameter.
        d1,
        // 2nd base diameter.
        d2,
        // Number of wounds on the dial. Tweak to get a proper text size.
        n,
        // Total base height.
        total_h = 4,
        // Dial height. Modifying this was not throughly tested.
        dial_h = 2,
        // Set to true to include a standee clip.
        clip = true,
        // Set to true to render parts separately, spread over Y axis.
        exploded = false,
        // Base side slant angle.
        slant_angle = 15,
        // Set to true to integrate a snap-fit peg into the base top, otherwise
        // there will be a separate peg-part which should be glued in.
        int_peg = false,
        // Dimentions of the center hole for a magnet [diameter, height]. This
        // indirectly defines the diameter of the peg (1 + diameter). Height
        // cannot be larger than total_h - (1 + Resolution).
        magnet_hole = [0,0]) {
    sd = min(d1, d2);
    ld = max(d1, d2);
    sangl = slant_angle;
    n_chars = len(dial_txt(n));
    peg_wall = 1;
    peg_diam = max(magnet_hole[0], sd/4 - (peg_wall*4+1)) + peg_wall*4 + 1;
    top_height = (d1 == d2) ? total_h - dial_h : total_h;
    top_z_sh = (d1 == d2) ? dial_h : 0;
    top_d1 = (d1 == d2) ? d1 - 2 * top_height * tan(slant_angle) : d1;
    top_d2 = (d1 == d2) ? d2 - 2 * top_height * tan(slant_angle) : d2;
    top_thickness = 1;
    // Calculate angle at which inner wall of base_top will intersect
    // with the dial. Used only on non-round bases.
    // The angle is based on the bottom diameters which means extra
    // cut-out in the inner part of the wall. To have wider angle
    // and nice, sharp wall, add the following to ld/sd below:
    //  - 2 * (top_height-top_thickness) * tan(slant_angle)
    a = (ld - 2) / 2;
    b = (sd - 2) / 2;
    r = sd / 2;
    i_x = a * sqrt((r*r-b*b)/(a*a-b*b));
    i_y = b * sqrt((a*a-r*r)/(a*a-b*b));
    // The total window angle will be twice the intersection angle
    // since intersections are on both sides of the Y axis.
    w = 2 * atan(i_x/i_y);
    sh_y = exploded ? sd + 1: 0;
    clip_w = max(ld/3, 2*cap_outer_r(peg_diam));
    // First get the peg. It should be glued/fitted into the top
    if (!int_peg)
        color("green")
        translate([0,0*sh_y,0])
        peg(peg_diam, total_h - top_thickness, magnet_size = magnet_hole);
    // Then goes the number dial. It "should" fit snuggly on the
    // peg.
    color("yellow") translate([0,1*sh_y,0]) difference() {
		// This is a rotation to have the last number visible in the
        // cutout for visual inspection.
		rotate([0,0, 360 / n])
            number_dial(sd, n, dial_h, slant_angle = sangl);
        peg(peg_diam, total_h - top_thickness);
    }
    // Now a top of the base along with a slant.
    color("blue")
        translate([0,2*sh_y,top_z_sh])
        union() {
            difference() {
                    base_top(top_d1,
                             top_d2,
                             top_height,
                             top_thickness,
                             n_chars,
                             peg_diam,
                             slant_angle = slant_angle);
                // If this is an oval base, generate oval sides with a
                // cut-out for easier tracker rotation.
                if (d1 != d2) {
                    number_dial(sd, n, dial_h, slant_angle = sangl);
                    rotate([0, 0, -(90 + w/2)])
                        rotate_extrude(angle = w)
                        square([sd/2 + 2, dial_h], false);
                    rotate([0, 0, (90 - w/2)])
                        rotate_extrude(angle = w)
                        square([sd/2 + 2, dial_h], false);
                }
                if (clip) {
                    // Cut out a hole for the clip.
                    translate([-clip_w/2,-5,top_height + 0.01])
                        rotate([90,0,90])
                        resize([0,0,clip_w])
                        stand_clip(w=10, t=0.6, lh=10, lt=2);
                }
            }
            if (int_peg) translate([0,0,-total_h+top_height]) {
                pr = peg_diam/2;
                pcr = pr+1.5*Resolution;
                gsz = 2*PI*pr / 8;
                difference() {
                    union() {
                        // Main peg axis-cylinder.
                        cylinder(r=pr, h=total_h);
                        // Latch lip.
                        difference() {
                            union() {
                                translate([0,0,peg_base_height()/2])
                                    cylinder(r2=pr, r1=pcr,
                                             h=peg_base_height()/2);
                                cylinder(r2=pcr, r1=pr, h=peg_base_height()/2);
                            }
                            translate([-pcr/2-gsz/2,0,0])
                                cube([pcr, 2*pcr, total_h], center=true);
                            translate([pcr/2+gsz/2,0,0])
                                cube([pcr, 2*pcr, total_h], center=true);
                        }
                    }
                    cylinder(r=pr-peg_wall, h=total_h);
                    if (clip)
                        cube([clip_w, 2, 10], center=true);
                    // Cut-out in the axis-cylinder for the latch flex.
                    translate([-0.1-gsz/2,0,0])
                        cube([0.2, 2*pcr, total_h], center=true);
                    translate([+0.1+gsz/2,0,0])
                        cube([0.2, 2*pcr, total_h], center=true);
                }
                if (magnet_hole[0] > 0) {
                    // Magnet holder
                    difference() {
                        cylinder(d = magnet_hole[0] + 2 * peg_wall, total_h);
                        cylinder(d = magnet_hole[0] - peg_wall, total_h);
                        cylinder(d = magnet_hole[0], magnet_hole[1]);
                        if (clip)
                            cube([clip_w, 2, 10], center=true);
                    }
                }
            }
        }
    // And if requested, add a token clip.
    if (clip) {
        angl = exploded ? 0 : 90;
        translate([0,3*sh_y,0])
            translate([-clip_w/2,-5,total_h + 0.01])
            rotate([angl,0,angl])
            resize([0,0,clip_w])
            stand_clip(w=10, t=0.6, lh=top_thickness, lt=1.5);
    }
}

module stand_clip(w, t, lh, lt) {
    cube([w,t,1]);
    translate([(w - lt) / 2, -lh, 0]) cube([lt,lh,1]);
    top_angle = 15;
    module clip_side(w) {
        cw = 1 * w;
        translate([-Tolerance,cw/2,0]) {
            difference() {
                union() {
                    cylinder(h = 1, d = cw);
                    translate([0,t - cw/4,0])
                        rotate([180,-90,0])
                        prism(1, cw/4, cw/2);
                }
                translate([0,0,-0.1]) {
                    cylinder(h = 1.2, d = cw - 2*t);
                    rotate([0,0,top_angle])
                        cube([cw, cw/2, 1.2]);
                    translate([-cw/2, -cw/2, 0])
                        cube([cw/2 + Tolerance, cw, 1.2]);
                }
            }
            rotate([0,0,top_angle])
                translate([cw/2 - t/2,0,0])
                cylinder(h=1,d=t);
        }
    }
    clip_side(w);
    translate([w,0,0]) mirror([1,0,0]) clip_side(w);
}

tracker_base(d1 = Base_diameter_1, d2 = Base_diameter_2,
             n = Dial_numbers,
             exploded = expl,
             clip = Clip,
             total_h = base_height,
             dial_h = dial_height,
             slant_angle = slant_angle,
             int_peg = Integrated_peg,
             magnet_hole = center_magnet);
```
wound-tracker-base.scad:
```
/// Miniature base with a rotating wound tracker
///
/// This model generates a miniature base (round or oval) with / a wound tracker
//at the bottom.

/* [Base parameters] */

// 1st diameter of the base.
Base_diameter_1 = 25; // [25:1:200]
// 2nd diameter of the base. Different from the 1st for oval bases.
Base_diameter_2 = 25; // [25:1:200]
// Number of wounds on the base dial. Impacts text size and top cut-out so experiment to get a proper base.
Dial_numbers = 10; // [7:1:30]
// Generate a center holder for magnet of this diameter. 0 turns it off.
Center_magnet_diameter = 5; // [0:0.5:10]
// Height of the magnet to generate the hole for. Note that it has to be multiple of print layer height.
Center_magnet_height = 2; // [1:0.1:3]
// Set to false to get a 3-part design with a pushed/glued peg.
Integrated_peg = 1; // [0:False,1:True]
// Set this to true to generate a stand clip and holes for it. When using the clip, it's best to disable the center magnet hole or set it to at least 5.
Clip = 0; // [0:False,1:True]

/* [Print parameters] */

// Set this to the print layer height used to calculate heights the peg and numbers on the bottom dial.
Resolution = 0.2;
// General tolerance for places that should not be tight.
Tolerance = 0.1;

/* [Rendering parameters] */
// Set this to false to render an assembled view.
Exploded_view = 1; // [0:False,1:True]
// Set this to false for faster previews.
Final_rendering = 1; // [0:False,1:True]

/* [Hidden] */
// Below are untested parameters. Assume that changing them may break things.

// Total base height
base_height = 4;
// Dial height
dial_height = 2;
// Angle of the base slant
slant_angle = 15;

module __Customizer_end__ () {}  // empty module to hide vars from customizer

expl = Exploded_view;
final = Final_rendering;

center_magnet = [Center_magnet_diameter, Center_magnet_height];
$fa = 1;
$fs = final ? 0.01 : 0.1;
PI = 3.141592;

function get_font_sz(radius, nchars) = 2 * 3.14159 * radius / nchars;

module prism(l, w, h) {
    polyhedron(
        points=[[0,0,0], [l,0,0], [l,w,0], [0,w,0], [0,w,h], [l,w,h]],
        faces=[[0,1,2,3],[5,4,3,2],[0,4,5,1],[0,3,4],[5,2,1]]
    );
}

// FIXME: Add maximum text size parameter to limit the font size...
module revolve_text(radius, chars) {
    chars_len = len(chars);
    font_size = get_font_sz(radius, chars_len);
    step_angle = 360 / chars_len;
    for(i = [0 : chars_len - 1]) {
        rotate(i * step_angle)
            translate([0, -radius, 0.5])
                text(
                    chars[i],
                    font = "Courier New; Style = Bold",
                    size = font_size,
                    valign = "bottom", halign = "center"
                );
    }
}

function peg_base_height() = 0.6;

module peg(diam, height, magnet_size = [0,0]) {
    r = diam / 2;
    res = Resolution;
    //echo(r=r);
    difference() {
        union() {
            cylinder(h = height, r = r - Tolerance);
            cylinder(h = height - Resolution, r = r);
            cylinder(h = peg_base_height(), r = r * 1.5);
        }
        if (magnet_size[0] > 0) {
            cylinder(d = magnet_size[0] - 6*res, height);
            cylinder(d = magnet_size[0] - 4*res, magnet_size[1] + 2*res);
            cylinder(d = magnet_size[0] - 2*res, magnet_size[1] + res);
            cylinder(d = magnet_size[0], magnet_size[1]);
        }
    }
}

function cap_outer_r(diam) = (diam / 2) + 1 + Tolerance;
module cap(diam, height) {
    r = diam / 2;
    //echo(r=r);
    difference() {
        cylinder(h = height, r = cap_outer_r(diam));
        // Make the hole slightly smaller for a push-fit.
        cylinder(h = height, r = r - Tolerance);
    }
}

function dial_txt(n) =
    [for (i = [1:n]) for (s = str(i, " "), si = [0:1:len(s)-1]) s[si]];

module number_dial(diam, n, height, slant_angle = 0) {
    txt = dial_txt(n);
    top_diam = diam - 2 * height * tan(slant_angle);
    difference() {
        cylinder(h = height, d1 = diam, d2 = top_diam);
        translate([0,0,height - 3 * Resolution])
            linear_extrude(height=1)
            revolve_text(top_diam/2 - 1, txt);
    }
}

// Old round-only design.
module base_top(d1, d2, height, top_thickness, n_chars, peg_diam,
                slant_angle = 15) {
    w = 360 / n_chars * 2 + 2;
    echo(w);
    sd = min(d1, d2);
    ld = max(d1, d2);
    difference() {
        hull() {
            resize([ld, sd, 0])
                cylinder(h=0.01, d = 1);
            translate([0, 0, height - 0.01])
                resize([ld - 2 * height * tan(slant_angle),
                        sd - 2 * height * tan(slant_angle),
                        0])
                cylinder(h=0.01, d = 1);
        }
        hull() {
            resize([ld - 2, sd - 2, 0])
                cylinder(h=0.01, d = 1);
            translate([0, 0, height - top_thickness])
                resize([ld - 2 - 2 * height * tan(slant_angle),
                        sd - 2 - 2 * height * tan(slant_angle),
                        0])
                cylinder(h=0.01, d = 1);
        }
        rotate([0,0,-90]) difference() {
            translate([0,0,-0.5]) cylinder(d=sd+2, h=height+1);
            rotate([0,0,  w/2]) translate([-sd/2-1,0,-0.5])
                cube([sd+2, sd/2+1, height + 1], center=false);
            rotate([0,0, -w/2]) translate([-sd/2-1,-sd/2-1,-0.5])
                cube([sd+2, sd/2+1, height + 1]);
            cylinder(h = height, r=sd/2 - get_font_sz(sd, n_chars)/1.4);
        }
    }
    cap(peg_diam, height);
}

module tracker_base(
        // 1st base diameter.
        d1,
        // 2nd base diameter.
        d2,
        // Number of wounds on the dial. Tweak to get a proper text size.
        n,
        // Total base height.
        total_h = 4,
        // Dial height. Modifying this was not throughly tested.
        dial_h = 2,
        // Set to true to include a standee clip.
        clip = true,
        // Set to true to render parts separately, spread over Y axis.
        exploded = false,
        // Base side slant angle.
        slant_angle = 15,
        // Set to true to integrate a snap-fit peg into the base top, otherwise
        // there will be a separate peg-part which should be glued in.
        int_peg = false,
        // Dimentions of the center hole for a magnet [diameter, height]. This
        // indirectly defines the diameter of the peg (1 + diameter). Height
        // cannot be larger than total_h - (1 + Resolution).
        magnet_hole = [0,0]) {
    sd = min(d1, d2);
    ld = max(d1, d2);
    sangl = slant_angle;
    n_chars = len(dial_txt(n));
    peg_wall = 1;
    peg_diam = max(magnet_hole[0], sd/4 - (peg_wall*4+1)) + peg_wall*4 + 1;
    top_height = (d1 == d2) ? total_h - dial_h : total_h;
    top_z_sh = (d1 == d2) ? dial_h : 0;
    top_d1 = (d1 == d2) ? d1 - 2 * top_height * tan(slant_angle) : d1;
    top_d2 = (d1 == d2) ? d2 - 2 * top_height * tan(slant_angle) : d2;
    top_thickness = 1;
    // Calculate angle at which inner wall of base_top will intersect
    // with the dial. Used only on non-round bases.
    // The angle is based on the bottom diameters which means extra
    // cut-out in the inner part of the wall. To have wider angle
    // and nice, sharp wall, add the following to ld/sd below:
    //  - 2 * (top_height-top_thickness) * tan(slant_angle)
    a = (ld - 2) / 2;
    b = (sd - 2) / 2;
    r = sd / 2;
    i_x = a * sqrt((r*r-b*b)/(a*a-b*b));
    i_y = b * sqrt((a*a-r*r)/(a*a-b*b));
    // The total window angle will be twice the intersection angle
    // since intersections are on both sides of the Y axis.
    w = 2 * atan(i_x/i_y);
    sh_y = exploded ? sd + 1: 0;
    clip_w = max(ld/3, 2*cap_outer_r(peg_diam));
    // First get the peg. It should be glued/fitted into the top
    if (!int_peg)
        color("green")
        translate([0,0*sh_y,0])
        peg(peg_diam, total_h - top_thickness, magnet_size = magnet_hole);
    // Then goes the number dial. It "should" fit snuggly on the
    // peg.
    color("yellow") translate([0,1*sh_y,0]) difference() {
        rotate([0,0, (360 / n_chars * 2.5)])
            number_dial(sd, n, dial_h, slant_angle = sangl);
        peg(peg_diam, total_h - top_thickness);
    }
    // Now a top of the base along with a slant.
    color("blue")
        translate([0,2*sh_y,top_z_sh])
        union() {
            difference() {
                    base_top(top_d1,
                             top_d2,
                             top_height,
                             top_thickness,
                             n_chars,
                             peg_diam,
                             slant_angle = slant_angle);
                // If this is an oval base, generate oval sides with a
                // cut-out for easier tracker rotation.
                if (d1 != d2) {
                    number_dial(sd, n, dial_h, slant_angle = sangl);
                    rotate([0, 0, -(90 + w/2)])
                        rotate_extrude(angle = w)
                        square([sd/2 + 2, dial_h], false);
                    rotate([0, 0, (90 - w/2)])
                        rotate_extrude(angle = w)
                        square([sd/2 + 2, dial_h], false);
                }
                if (clip) {
                    // Cut out a hole for the clip.
                    translate([-clip_w/2,-5,top_height + 0.01])
                        rotate([90,0,90])
                        resize([0,0,clip_w])
                        stand_clip(w=10, t=0.6, lh=10, lt=2);
                }
            }
            if (int_peg) translate([0,0,-total_h+top_height]) {
                pr = peg_diam/2;
                pcr = pr+1.5*Resolution;
                gsz = 2*PI*pr / 8;
                difference() {
                    union() {
                        // Main peg axis-cylinder.
                        cylinder(r=pr, h=total_h);
                        // Latch lip.
                        difference() {
                            union() {
                                translate([0,0,peg_base_height()/2])
                                    cylinder(r2=pr, r1=pcr,
                                             h=peg_base_height()/2);
                                cylinder(r2=pcr, r1=pr, h=peg_base_height()/2);
                            }
                            translate([-pcr/2-gsz/2,0,0])
                                cube([pcr, 2*pcr, total_h], center=true);
                            translate([pcr/2+gsz/2,0,0])
                                cube([pcr, 2*pcr, total_h], center=true);
                        }
                    }
                    cylinder(r=pr-peg_wall, h=total_h);
                    if (clip)
                        cube([clip_w, 2, 10], center=true);
                    // Cut-out in the axis-cylinder for the latch flex.
                    translate([-0.1-gsz/2,0,0])
                        cube([0.2, 2*pcr, total_h], center=true);
                    translate([+0.1+gsz/2,0,0])
                        cube([0.2, 2*pcr, total_h], center=true);
                }
                if (magnet_hole[0] > 0) {
                    // Magnet holder
                    difference() {
                        cylinder(d = magnet_hole[0] + 2 * peg_wall, total_h);
                        cylinder(d = magnet_hole[0] - peg_wall, total_h);
                        cylinder(d = magnet_hole[0], magnet_hole[1]);
                        if (clip)
                            cube([clip_w, 2, 10], center=true);
                    }
                }
            }
        }
    // And if requested, add a token clip.
    if (clip) {
        angl = exploded ? 0 : 90;
        translate([0,3*sh_y,0])
            translate([-clip_w/2,-5,total_h + 0.01])
            rotate([angl,0,angl])
            resize([0,0,clip_w])
            stand_clip(w=10, t=0.6, lh=top_thickness, lt=1.5);
    }
}

module stand_clip(w, t, lh, lt) {
    cube([w,t,1]);
    translate([(w - lt) / 2, -lh, 0]) cube([lt,lh,1]);
    top_angle = 15;
    module clip_side(w) {
        cw = 1 * w;
        translate([-Tolerance,cw/2,0]) {
            difference() {
                union() {
                    cylinder(h = 1, d = cw);
                    translate([0,t - cw/4,0])
                        rotate([180,-90,0])
                        prism(1, cw/4, cw/2);
                }
                translate([0,0,-0.1]) {
                    cylinder(h = 1.2, d = cw - 2*t);
                    rotate([0,0,top_angle])
                        cube([cw, cw/2, 1.2]);
                    translate([-cw/2, -cw/2, 0])
                        cube([cw/2 + Tolerance, cw, 1.2]);
                }
            }
            rotate([0,0,top_angle])
                translate([cw/2 - t/2,0,0])
                cylinder(h=1,d=t);
        }
    }
    clip_side(w);
    translate([w,0,0]) mirror([1,0,0]) clip_side(w);
}

tracker_base(d1 = Base_diameter_1, d2 = Base_diameter_2,
             n = Dial_numbers,
             exploded = expl,
             clip = Clip,
             total_h = base_height,
             dial_h = dial_height,
             slant_angle = slant_angle,
             int_peg = Integrated_peg,
             magnet_hole = center_magnet);
```


[User]
Hi, I'm looking to design a sink strainer for my kitchen. I've seen some designs online but want something that's easily customizable for different sink sizes and preferences. I'm envisioning a design with a few key components:

1.  **Base:** This should be a shallow, sloped cylinder, like a truncated cone. The top diameter will be the main parameter. I need to be able to adjust the slope of the sides. I need to also be able to control the thickness/height of the base, and I need to be able to control how much of the outer edge slopes.

2.  **Holes:** There should be multiple rows of holes for drainage. I need to control the number of rings, the diameter of the holes, and their spacing. I'd like the holes to be uniformly distributed around each ring.

3.  **Curb:** I'd like the option of having a small curb around the outer edge of the base. This curb should be customizable in width and height. I'd like it to be optional, and I want to control whether it is present.

4.  **Shaft/Handle:** A central shaft extending upward from the base for easy lifting, with its length and diameter being configurable.

5.  **Knob:** A knob on top of the shaft, with customizable diameter and faceting, with high faceting (say 90 or higher) resulting in a more-or-less round knob.

Basically, everything should be parameterized. I'd like to be able to modify all of these key features in the Customizer. I'm not super familiar with OpenSCAD, so I'd need it to be clear and easy to use. I'm not sure if it's relevant, but I would prefer that the code uses libraries if that's convenient. I just need a single scad file. Can you help me create this?

[Assistant]
sink_strainer.scad:
```
// By Curt McDowell <maker@fishlet.com> 2022-01-23
// Licensed under the Creative Commons - Attribution - ShareAlike.
// https://creativecommons.org/licenses/by-sa/4.0/

// Main diameter (be sure to measure)
Base_Top_D = 79.375;    // [76.2: 3 inch (small), 79.375: 3 1/8 inch (standard), 83: 3 1/4 inch (InSinkErator)]
// Outer edge slope (90 for vertical edge)
Base_Overhang_Angle = 75;
// Thickness of strainer bottom
Base_H = 4.0;
// Number of rings of holes
Hole_Rings = 3;
// Diameter of small holes
Hole_D = 6.0;
// Whether to include a curb around the outside
Curb = true;
// Width of such curb
Curb_W = 3.0;
// Height of such curb
Curb_H = 5.0;
// Diameter of middle shaft
Shaft_D = 12.0;
// Length of middle shaft
Shaft_L = 10.0;
// Diameter of knob
Knob_D = 15.0;
// Faceting of knob (use large number like 90 for round)
Knob_Facets = 90;

module _end_params() {}

Base_Top_R = Base_Top_D / 2;
Base_Bottom_R = Base_Top_D / 2 - Base_H / tan(Base_Overhang_Angle);
Shaft_R = Shaft_D / 2;
Knob_R = Knob_D / 2;

// The hole radial spacing is calculated based on the number of rings,
// and the angular spacing is made approximately equal to that.
hole_spacing = (Base_Bottom_R - Shaft_R - (Curb ? Curb_W : 0)) / Hole_Rings;

$eps = 0.001;

module base()
    cylinder(r1 = Base_Bottom_R, r2 = Base_Top_R, h = Base_H, $fn = 180);

module hole(r, theta)
    translate([r * cos(theta), r * sin(theta), -$eps])
        cylinder(d = Hole_D, h = Base_H + 2 * $eps, $fn = 30);

module holes()
    for (row = [0 : Hole_Rings - 1])
        let (r = Shaft_R + (row + 0.5) * hole_spacing,
             n = round((2 * PI * r) / hole_spacing))
            for (hole = [0 : n - 1])
                hole(r, theta = 360 / n * hole);

module curb()
    translate([0, 0, Base_H - $eps])
        difference() {
            cylinder(r = Base_Top_R, h = Curb_H, $fn = 180);
            translate([0, 0, -$eps])
                cylinder(r = Base_Top_R - Curb_W, h = Curb_H + 2 * $eps, $fn = 180);
        }

module shaft()
    translate([0, 0, Base_H - $eps])
        cylinder(d = Shaft_D, h = Shaft_L + Knob_R, $fn = 90);

module knob()
    translate([0, 0, Base_H - $eps + Shaft_L + Knob_R])
        sphere(r = Knob_R, $fn = Knob_Facets);

module strainer() {
    difference() {
        base();
        holes();
    }
    if (Curb)
        curb();
    shaft();
    knob();
}

strainer();

```


[User]
Hi, I need help designing an adapter for a drain. I have a Somatherm brand drain flange kit, which normally fits PVC pipes with a diameter of either 32 or 40mm. I need to connect it to a larger diameter pipe, like 50mm, or possibly even larger in the future, so I'd like a parametric design.

The adapter should have a central hole matching the diameter of the larger pipe. The drain flange connection has an internal thread, specifically M27x2, and a cylindrical bore of 27mm. The flange itself attaches via two support arms on either side of the central pipe, so the adapter needs to have supports that match those with holes for screws.

Here's a breakdown of the dimensions I'm working with:

*   **`tuyau`:** This is the diameter of the *larger* pipe I'm trying to connect to. It should be a parameter. The default would be 50.
*   **`largeur`:** The width or length of the adapter (along the central pipe). This should be 42 by default.
*   **`epaisseur`:** The thickness of the collar (the part that forms the main ring around the large pipe). This should be 3mm by default.
*   **Drain connection:** The drain has an outer diameter of 34mm, an internal diameter of 27mm and a thread M27x2, all with a depth of 31mm.
*   **Support arms:** These are 17mm wide, 14mm deep, and `largeur` long. There are two of them, one on each side of the central pipe.
*   **Screw holes:** Each arm needs two 7mm diameter through-holes for screws. The holes are offset from the arm edges by 7/4mm horizontally and 1mm vertically, and are vertically placed at 1/4 and 3/4 of the adapter's `largeur`.

The design will basically be a central cylinder with a hole matching `tuyau`, two support arms, and a threaded cylinder for connecting to the drain kit. The drain connection will require the use of the `metric_thread` module from the `threads.scad` library, so please make sure that is included.

I'd like the final design to be in two parts: a top part and a bottom part, which are mirrored on the central plane.

[Assistant]
Adaptateur_vidange_-_Bride_de_vidange_Somatherm.scad:
```
use <threads.scad>;
tuyau = 50; // diamètre du tuyau

largeur=42; // largeur du support
epaisseur=3; // epaisseur du collier

$fn = 200; // pecision

haut();
translate([100,0,0])
      rotate([180,180,0])
            bas();

module haut()
{
difference()
{
    object(true);
    translate([-50,0,0])
        cube([100,100,100]);
}
}

module bas()
{
difference()
{
    object(false);
    translate([-50,-100,0])
        cube([100,100,100]);
}
}

module object(thread=true)
{
difference()
{

    union()
    {
    // cylindre central
    cylinder(d=tuyau+epaisseur*2, h=largeur);
    // cylindre de vidange
    translate([0,-tuyau/2+epaisseur*2,largeur/2])
        rotate([90,0,0])
        {
            difference()
            {
                cylinder(d=34, h=31);
                cylinder(d=27, h=31);
            }
        }

    // bras de support
    translate([-(tuyau/2+17/2-epaisseur/2),0,largeur/2])
        cube([17,14,largeur], center=true);
    translate([(tuyau/2+17/2-epaisseur/2),0,largeur/2])
        cube([17,14,largeur], center=true);
    }

 /*
   */
    if (thread)
    // pas de vis
      translate([0,-tuyau/2+epaisseur*2,largeur/2])
        rotate([90,0,0])
            metric_thread(27,2,31, inner=true);
    else // trou prise vidange
        translate([0,-tuyau/2+epaisseur*2,largeur/2])
            rotate([90,0,0])
                cylinder(d=27, h=31);
        //ScrewHole(27, 31, 0);
 /*translate([0,-tuyau/2+epaisseur*2,largeur/2])
    rotate([90,0,0])
        ScrewHole(27, 31, 0);
*/
cylinder(d=tuyau, h=largeur); // trou tuyau
// trous pour vis de support
translate([-(tuyau/2+17/2-epaisseur/2)-7/4,14/2+1,largeur/4])
    rotate([90,0,0])
        cylinder(d=7, h = 25);
translate([-(tuyau/2+17/2-epaisseur/2)-7/4,14/2+1,(largeur/4)*3])
    rotate([90,0,0])
        cylinder(d=7, h = 25);
translate([(tuyau/2+17/2-epaisseur/2)+7/4,14/2+1,largeur/4])
    rotate([90,0,0])
        cylinder(d=7, h = 25);
translate([(tuyau/2+17/2-epaisseur/2)+7/4,14/2+1,(largeur/4)*3])
    rotate([90,0,0])
        cylinder(d=7, h = 25);
}

}

```
threads.scad:
```
/*
 * ISO-standard metric threads, following this specification:
 *          http://en.wikipedia.org/wiki/ISO_metric_screw_thread
 *
 * Copyright 2021 Dan Kirshner - dan_kirshner@yahoo.com
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * See <http://www.gnu.org/licenses/>.
 *
 * Version 2.6.  2021-05-16  Contributed patches for leadin (thanks,
                             jeffery.spirko@tamucc.edu) and aligning thread
                             "facets" (triangulation) with base cylinder
                             (thanks, rambetter@protonmail.com).
 * Version 2.5.  2020-04-11  Leadin option works for internal threads.
 * Version 2.4.  2019-07-14  Add test option - do not render threads.
 * Version 2.3.  2017-08-31  Default for leadin: 0 (best for internal threads).
 * Version 2.2.  2017-01-01  Correction for angle; leadfac option.  (Thanks to
 *                           Andrew Allen <a2intl@gmail.com>.)
 * Version 2.1.  2016-12-04  Chamfer bottom end (low-z); leadin option.
 * Version 2.0.  2016-11-05  Backwards compatibility (earlier OpenSCAD) fixes.
 * Version 1.9.  2016-07-03  Option: tapered.
 * Version 1.8.  2016-01-08  Option: (non-standard) angle.
 * Version 1.7.  2015-11-28  Larger x-increment - for small-diameters.
 * Version 1.6.  2015-09-01  Options: square threads, rectangular threads.
 * Version 1.5.  2015-06-12  Options: thread_size, groove.
 * Version 1.4.  2014-10-17  Use "faces" instead of "triangles" for polyhedron
 * Version 1.3.  2013-12-01  Correct loop over turns -- don't have early cut-off
 * Version 1.2.  2012-09-09  Use discrete polyhedra rather than linear_extrude ()
 * Version 1.1.  2012-09-07  Corrected to right-hand threads!
 */

// Examples.
//
// Standard M8 x 1.
// metric_thread (diameter=8, pitch=1, length=4);

// Square thread.
// metric_thread (diameter=8, pitch=1, length=4, square=true);

// Non-standard: long pitch, same thread size.
//metric_thread (diameter=8, pitch=4, length=4, thread_size=1, groove=true);

// Non-standard: 20 mm diameter, long pitch, square "trough" width 3 mm,
// depth 1 mm.
//metric_thread (diameter=20, pitch=8, length=16, square=true, thread_size=6,
//               groove=true, rectangle=0.333);

// English: 1/4 x 20.
//english_thread (diameter=1/4, threads_per_inch=20, length=1);

// Tapered.  Example -- pipe size 3/4" -- per:
// http://www.engineeringtoolbox.com/npt-national-pipe-taper-threads-d_750.html
// english_thread (diameter=1.05, threads_per_inch=14, length=3/4, taper=1/16);

// Thread for mounting on Rohloff hub.
//difference () {
//   cylinder (r=20, h=10, $fn=100);
//
//   metric_thread (diameter=34, pitch=1, length=10, internal=true, n_starts=6);
//}

// ----------------------------------------------------------------------------
function segments (diameter) = min (50, max (ceil (diameter*6), 25));

// ----------------------------------------------------------------------------
// diameter -    outside diameter of threads in mm. Default: 8.
// pitch    -    thread axial "travel" per turn in mm.  Default: 1.
// length   -    overall axial length of thread in mm.  Default: 1.
// internal -    true = clearances for internal thread (e.g., a nut).
//               false = clearances for external thread (e.g., a bolt).
//               (Internal threads should be "cut out" from a solid using
//               difference ()).  Default: false.
// n_starts -    Number of thread starts (e.g., DNA, a "double helix," has
//               n_starts=2).  See wikipedia Screw_thread.  Default: 1.
// thread_size - (non-standard) axial width of a single thread "V" - independent
//               of pitch.  Default: same as pitch.
// groove      - (non-standard) true = subtract inverted "V" from cylinder
//                (rather thanadd protruding "V" to cylinder).  Default: false.
// square      - true = square threads (per
//               https://en.wikipedia.org/wiki/Square_thread_form).  Default:
//               false.
// rectangle   - (non-standard) "Rectangular" thread - ratio depth/(axial) width
//               Default: 0 (standard "v" thread).
// angle       - (non-standard) angle (deg) of thread side from perpendicular to
//               axis (default = standard = 30 degrees).
// taper       - diameter change per length (National Pipe Thread/ANSI B1.20.1
//               is 1" diameter per 16" length). Taper decreases from 'diameter'
//               as z increases.  Default: 0 (no taper).
// leadin      - 0 (default): no chamfer; 1: chamfer (45 degree) at max-z end;
//               2: chamfer at both ends, 3: chamfer at z=0 end.
// leadfac     - scale of leadin chamfer length (default: 1.0 = 1/2 thread).
// test        - true = do not render threads (just draw "blank" cylinder).
//               Default: false (draw threads).
module metric_thread (diameter=8, pitch=1, length=1, internal=false, n_starts=1,
                      thread_size=-1, groove=false, square=false, rectangle=0,
                      angle=30, taper=0, leadin=0, leadfac=1.0, test=false)
{
   // thread_size: size of thread "V" different than travel per turn (pitch).
   // Default: same as pitch.
   local_thread_size = thread_size == -1 ? pitch : thread_size;
   local_rectangle = rectangle ? rectangle : 1;

   n_segments = segments (diameter);
   h = (test && ! internal) ? 0 : (square || rectangle) ? local_thread_size*local_rectangle/2 : local_thread_size / (2 * tan(angle));

   h_fac1 = (square || rectangle) ? 0.90 : 0.625;

   // External thread includes additional relief.
   h_fac2 = (square || rectangle) ? 0.95 : 5.3/8;

   tapered_diameter = diameter - length*taper;

   difference () {
      union () {
         if (! groove) {
            if (! test) {
               metric_thread_turns (diameter, pitch, length, internal, n_starts,
                                    local_thread_size, groove, square, rectangle, angle,
                                    taper);
            }
         }

         difference () {

            // Solid center, including Dmin truncation.
            if (groove) {
               cylinder (r1=diameter/2, r2=tapered_diameter/2,
                         h=length, $fn=n_segments);
            } else if (internal) {
               cylinder (r1=diameter/2 - h*h_fac1, r2=tapered_diameter/2 - h*h_fac1,
                         h=length, $fn=n_segments);
            } else {

               // External thread.
               cylinder (r1=diameter/2 - h*h_fac2, r2=tapered_diameter/2 - h*h_fac2,
                         h=length, $fn=n_segments);
            }

            if (groove) {
               if (! test) {
                  metric_thread_turns (diameter, pitch, length, internal, n_starts,
                                       local_thread_size, groove, square, rectangle,
                                       angle, taper);
               }
            }
         }

         // Internal thread lead-in: take away from external solid.
         if (internal) {

            // "Negative chamfer" z=0 end if leadin is 2 or 3.
            if (leadin == 2 || leadin == 3) {

               // Fixes by jeffery.spirko@tamucc.edu.
               cylinder (r1=diameter/2 - h + h*h_fac1*leadfac,
                         r2=diameter/2 - h,
                         h=h*h_fac1*leadfac, $fn=n_segments);
               /*
               cylinder (r1=diameter/2,
                         r2=diameter/2 - h*h_fac1*leadfac,
                         h=h*h_fac1*leadfac, $fn=n_segments);
               */
            }

            // "Negative chamfer" z-max end if leadin is 1 or 2.
            if (leadin == 1 || leadin == 2) {
               translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {

                  cylinder (r1=tapered_diameter/2 - h,
                            h=h*h_fac1*leadfac,
                            r2=tapered_diameter/2 - h + h*h_fac1*leadfac,
                            $fn=n_segments);
                  /*
                  cylinder (r1=tapered_diameter/2 - h*h_fac1*leadfac,
                            h=h*h_fac1*leadfac,
                            r2=tapered_diameter/2,
                            $fn=n_segments);
                  */
               }
            }
         }
      }

      if (! internal) {

         // Chamfer z=0 end if leadin is 2 or 3.
         if (leadin == 2 || leadin == 3) {
            difference () {
               cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);

               cylinder (r2=diameter/2, r1=diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,
                         $fn=n_segments);
            }
         }

         // Chamfer z-max end if leadin is 1 or 2.
         if (leadin == 1 || leadin == 2) {
            translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {
               difference () {
                  cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);

                  cylinder (r1=tapered_diameter/2, r2=tapered_diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,
                            $fn=n_segments);
               }
            }
         }
      }
   }
}

// ----------------------------------------------------------------------------
// Input units in inches.
// Note: units of measure in drawing are mm!
module english_thread (diameter=0.25, threads_per_inch=20, length=1,
                      internal=false, n_starts=1, thread_size=-1, groove=false,
                      square=false, rectangle=0, angle=30, taper=0, leadin=0,
                      leadfac=1.0, test=false)
{
   // Convert to mm.
   mm_diameter = diameter*25.4;
   mm_pitch = (1.0/threads_per_inch)*25.4;
   mm_length = length*25.4;

   echo (str ("mm_diameter: ", mm_diameter));
   echo (str ("mm_pitch: ", mm_pitch));
   echo (str ("mm_length: ", mm_length));
   metric_thread (mm_diameter, mm_pitch, mm_length, internal, n_starts,
                  thread_size, groove, square, rectangle, angle, taper, leadin,
                  leadfac, test);
}

// ----------------------------------------------------------------------------
module metric_thread_turns (diameter, pitch, length, internal, n_starts,
                            thread_size, groove, square, rectangle, angle,
                            taper)
{
   // Number of turns needed.
   n_turns = floor (length/pitch);

   intersection () {

      // Start one below z = 0.  Gives an extra turn at each end.
      for (i=[-1*n_starts : n_turns+1]) {
         translate ([0, 0, i*pitch]) {
            metric_thread_turn (diameter, pitch, internal, n_starts,
                                thread_size, groove, square, rectangle, angle,
                                taper, i*pitch);
         }
      }

      // Cut to length.
      translate ([0, 0, length/2]) {
         cube ([diameter*3, diameter*3, length], center=true);
      }
   }
}

// ----------------------------------------------------------------------------
module metric_thread_turn (diameter, pitch, internal, n_starts, thread_size,
                           groove, square, rectangle, angle, taper, z)
{
   n_segments = segments (diameter);
   fraction_circle = 1.0/n_segments;
   for (i=[0 : n_segments-1]) {

      // Keep polyhedron "facets" aligned -- circumferentially -- with base
      // cylinder facets.  (Patch contributed by rambetter@protonmail.com.)
      rotate ([0, 0, (i + 0.5)*360*fraction_circle + 90]) {
         translate ([0, 0, i*n_starts*pitch*fraction_circle]) {
            //current_diameter = diameter - taper*(z + i*n_starts*pitch*fraction_circle);
            thread_polyhedron ((diameter - taper*(z + i*n_starts*pitch*fraction_circle))/2,
                               pitch, internal, n_starts, thread_size, groove,
                               square, rectangle, angle);
         }
      }
   }
}

// ----------------------------------------------------------------------------
module thread_polyhedron (radius, pitch, internal, n_starts, thread_size,
                          groove, square, rectangle, angle)
{
   n_segments = segments (radius*2);
   fraction_circle = 1.0/n_segments;

   local_rectangle = rectangle ? rectangle : 1;

   h = (square || rectangle) ? thread_size*local_rectangle/2 : thread_size / (2 * tan(angle));
   outer_r = radius + (internal ? h/20 : 0); // Adds internal relief.
   //echo (str ("outer_r: ", outer_r));

   // A little extra on square thread -- make sure overlaps cylinder.
   h_fac1 = (square || rectangle) ? 1.1 : 0.875;
   inner_r = radius - h*h_fac1; // Does NOT do Dmin_truncation - do later with
                                // cylinder.

   translate_y = groove ? outer_r + inner_r : 0;
   reflect_x   = groove ? 1 : 0;

   // Make these just slightly bigger (keep in proportion) so polyhedra will
   // overlap.
   x_incr_outer = (! groove ? outer_r : inner_r) * fraction_circle * 2 * PI * 1.02;
   x_incr_inner = (! groove ? inner_r : outer_r) * fraction_circle * 2 * PI * 1.02;
   z_incr = n_starts * pitch * fraction_circle * 1.005;

   /*
    (angles x0 and x3 inner are actually 60 deg)

                          /\  (x2_inner, z2_inner) [2]
                         /  \
   (x3_inner, z3_inner) /    \
                  [3]   \     \
                        |\     \ (x2_outer, z2_outer) [6]
                        | \    /
                        |  \  /|
             z          |[7]\/ / (x1_outer, z1_outer) [5]
             |          |   | /
             |   x      |   |/
             |  /       |   / (x0_outer, z0_outer) [4]
             | /        |  /     (behind: (x1_inner, z1_inner) [1]
             |/         | /
    y________|          |/
   (r)                  / (x0_inner, z0_inner) [0]

   */

   x1_outer = outer_r * fraction_circle * 2 * PI;

   z0_outer = (outer_r - inner_r) * tan(angle);
   //echo (str ("z0_outer: ", z0_outer));

   //polygon ([[inner_r, 0], [outer_r, z0_outer],
   //        [outer_r, 0.5*pitch], [inner_r, 0.5*pitch]]);
   z1_outer = z0_outer + z_incr;

   // Give internal square threads some clearance in the z direction, too.
   bottom = internal ? 0.235 : 0.25;
   top    = internal ? 0.765 : 0.75;

   translate ([0, translate_y, 0]) {
      mirror ([reflect_x, 0, 0]) {

         if (square || rectangle) {

            // Rule for face ordering: look at polyhedron from outside: points must
            // be in clockwise order.
            polyhedron (
               points = [
                         [-x_incr_inner/2, -inner_r, bottom*thread_size],         // [0]
                         [x_incr_inner/2, -inner_r, bottom*thread_size + z_incr], // [1]
                         [x_incr_inner/2, -inner_r, top*thread_size + z_incr],    // [2]
                         [-x_incr_inner/2, -inner_r, top*thread_size],            // [3]

                         [-x_incr_outer/2, -outer_r, bottom*thread_size],         // [4]
                         [x_incr_outer/2, -outer_r, bottom*thread_size + z_incr], // [5]
                         [x_incr_outer/2, -outer_r, top*thread_size + z_incr],    // [6]
                         [-x_incr_outer/2, -outer_r, top*thread_size]             // [7]
                        ],

               faces = [
                         [0, 3, 7, 4],  // This-side trapezoid

                         [1, 5, 6, 2],  // Back-side trapezoid

                         [0, 1, 2, 3],  // Inner rectangle

                         [4, 7, 6, 5],  // Outer rectangle

                         // These are not planar, so do with separate triangles.
                         [7, 2, 6],     // Upper rectangle, bottom
                         [7, 3, 2],     // Upper rectangle, top

                         [0, 5, 1],     // Lower rectangle, bottom
                         [0, 4, 5]      // Lower rectangle, top
                        ]
            );
         } else {

            // Rule for face ordering: look at polyhedron from outside: points must
            // be in clockwise order.
            polyhedron (
               points = [
                         [-x_incr_inner/2, -inner_r, 0],                        // [0]
                         [x_incr_inner/2, -inner_r, z_incr],                    // [1]
                         [x_incr_inner/2, -inner_r, thread_size + z_incr],      // [2]
                         [-x_incr_inner/2, -inner_r, thread_size],              // [3]

                         [-x_incr_outer/2, -outer_r, z0_outer],                 // [4]
                         [x_incr_outer/2, -outer_r, z0_outer + z_incr],         // [5]
                         [x_incr_outer/2, -outer_r, thread_size - z0_outer + z_incr], // [6]
                         [-x_incr_outer/2, -outer_r, thread_size - z0_outer]    // [7]
                        ],

               faces = [
                         [0, 3, 7, 4],  // This-side trapezoid

                         [1, 5, 6, 2],  // Back-side trapezoid

                         [0, 1, 2, 3],  // Inner rectangle

                         [4, 7, 6, 5],  // Outer rectangle

                         // These are not planar, so do with separate triangles.
                         [7, 2, 6],     // Upper rectangle, bottom
                         [7, 3, 2],     // Upper rectangle, top

                         [0, 5, 1],     // Lower rectangle, bottom
                         [0, 4, 5]      // Lower rectangle, top
                        ]
            );
         }
      }
   }
}

```


[User]
Hey, I'd like to get a parametric OpenSCAD design for generating gem shapes. I need to be able to control a few different aspects, like the overall diameter, how many sides it has (it should be a polygon, not a circle), and then have separate control over the heights and "cutoff" percentages of the top and bottom sections of the gem. I'd also want to control the height of a middle section if needed. Finally, it'd be really helpful if I could rotate the gem onto different faces for printing, and also have an option to preview the gem with the uncut ends of the top and bottom sections.

Here's a breakdown of what I'm looking for in terms of parameters:

*   **General:**
    *   `Diameter`: The widest diameter of the gem.
    *   `Sides`: The number of sides of the gem (should be a regular polygon).

*   **Bottom:**
    *   `Bottom_Height`: The uncut height of the bottom section.
    *   `Bottom_Cutoff_Percent`: Percentage to cut from the bottom section's height.

*   **Middle:**
    *   `Middle_Height`: The height of the middle section, which is a straight cylinder.

*   **Top:**
    *   `Top_Height`: The uncut height of the top section.
    *   `Top_Cutoff_Percent`: Percentage to cut from the top section's height.

*   **Printing:**
    *   `Orientation`: This should allow me to rotate the model into the following orientations:
        *   Upright (default)
        *   Bottom Side
        *   Side
        *   Top Side
        *   Upsidedown

*   **Preview:**
    *   `Show_Uncut_Ends`: Show the full, uncut cylinder parts of the top and bottom (to help visualize the cut).

I don't have any libraries I need to include, and I'm looking for a single .scad file containing all the code, please.

[Assistant]
codeandmake.com_Gem_Generator_v1.1.scad:
```
/*
 * Copyright 2022 Code and Make (codeandmake.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/*
 * Gem Generator by Code and Make (https://codeandmake.com/)
 *
 * https://codeandmake.com/post/gem-generator
 *
 * Gem Generator v1.1 (29 January 2022)
 */

/* [General] */

// Diameter at widest point
Diameter = 30; // [1:1:100]

// Number of sides
Sides = 6; // [3:1:12]

/* [Bottom] */

// Uncut height of bottom section
Bottom_Height = 30; // [1:1:100]

// Percentage to cut from bottom section
Bottom_Cutoff_Percent = 0; // [0:1:100]

/* [Middle] */

// Height of middle section
Middle_Height = 0; // [0:1:100]

/* [Top] */

// Uncut height of top section
Top_Height = 15; // [1:1:100]

// Percentage to cut from top section
Top_Cutoff_Percent = 50; // [0:1:100]

/* [Printing] */

// Rotate onto face for easier printing
Orientation = 1; // [0:Upright, 1:Bottom Side, 2:Side, 3:Top Side, 4:Upsidedown]

/* [Preview] */

// Show uncut ends in preview mode
Show_Uncut_Ends = 0; // [0:No, 1:Yes]

module GemGenerator() {
  radius = Diameter / 2;
  apothem = radius * cos(180 / Sides);

  if(Orientation == 0) {
    translate([0, 0, (Bottom_Height * ((100 - Bottom_Cutoff_Percent) / 100)) + Middle_Height]) {
      mirror([0, 0, 1]) {
        gem();
      }
    }
  } else if(Orientation == 1) {
    mirror([1, 0, 0]) {
      // rotate flat
      rotate([0, 180 - atan(Bottom_Height / apothem), 0]) {
        // shift
        translate([-apothem * ((Bottom_Cutoff_Percent) / 100), 0, -((Bottom_Height * ((100 - Bottom_Cutoff_Percent) / 100)) + Middle_Height)]) {
          // rotate onto flat side
          rotate([0, 0, 180 / Sides]) {
            gem();
          }
        }
      }
    }
  } else if(Orientation == 2) {
    mirror([1, 0, 0]) {
      // shift
      translate([Bottom_Height * ((Bottom_Cutoff_Percent) / 100), 0, apothem]) {
        // rotate flat
        rotate([0, 90, 0]) {
          // shift down
          translate([0, 0, -(Bottom_Height + Middle_Height)]) {
            // rotate onto flat side
            rotate([0, 0, 180 / Sides]) {
              gem();
            }
          }
        }
      }
    }
  } else if(Orientation == 3) {
    // rotate flat
    rotate([0, atan(Top_Height / apothem), 0]) {
      // shift
      translate([-apothem * ((Top_Cutoff_Percent) / 100), 0, Top_Height * ((100 - Top_Cutoff_Percent) / 100)]) {
        // rotate onto flat side
        rotate([0, 0, 180 / Sides]) {
          gem();
        }
      }
    }
  } else if(Orientation == 4) {
    translate([0, 0, Top_Height * ((100 - Top_Cutoff_Percent) / 100)]) {
      gem();
    }
  }

  module gem() {
    // bottom
    translate([0, 0, Middle_Height]) {
      if (Show_Uncut_Ends) {
        %translate([0, 0, Bottom_Height * ((100 - Bottom_Cutoff_Percent) / 100)]) {
          cylinder(d1=Diameter * ((Bottom_Cutoff_Percent) / 100), d2=0, h=Bottom_Height * ((Bottom_Cutoff_Percent) / 100), $fn=Sides);
        }
      }
      intersection() {
        cylinder(d1=Diameter, d2=0, h=Bottom_Height, $fn=Sides);
        cylinder(d=Diameter, h=Bottom_Height * ((100 - Bottom_Cutoff_Percent) / 100), $fn=Sides);
      }
    }

    // middle
    cylinder(d=Diameter, h=Middle_Height, $fn=Sides);

    // top
    rotate([0, 180, 0]) {
      mirror([1, 0, 0]) {
        if (Show_Uncut_Ends) {
          %translate([0, 0, Top_Height * ((100 - Top_Cutoff_Percent) / 100)]) {
            cylinder(d1=Diameter * ((Top_Cutoff_Percent) / 100), d2=0, h=Top_Height * ((Top_Cutoff_Percent) / 100), $fn=Sides);
          }
        }

        intersection() {
          cylinder(d1=Diameter, d2=0, h=Top_Height, $fn=Sides);
          cylinder(d=Diameter, h=Top_Height * ((100 - Top_Cutoff_Percent) / 100), $fn=Sides);
        }
      }
    }
  }
}

GemGenerator();

echo("\r\tThis design is completely free and shared under a permissive license.\r\tYour support is hugely appreciated: codeandmake.com/support\r");

```


[User]
Hey, I'm looking to create some slotted drawer boxes for organizing my workshop. I've seen a design that uses a stack of rectangles to create the basic box shape, with slots on the sides for dividers, and I'd like to create a parametric version of that.

Here are the key features I'm looking for:

*   **Parametric Dimensions:** The core idea is to build a stack of rectangles, each with its own width and height, and these should be customizable via parameters. The height of each individual rectangle will ultimately determine the slots and their positions, so that's pretty important.
*   **Wall Thickness:** A parameter should control the wall thickness of the box.
*   **Bottom Thickness:** A parameter for the thickness of the bottom of the box.
*   **Rounding:**  The outer corners of the box need to be rounded, and I'd like the inner edges to be rounded as well. I know that's trickier, and I know there's a library to do it, so make sure to include the necessary logic and use the `polyround.scad` from the [Round-Anything](https://github.com/Irev-Dev/Round-Anything) library for the rounding.
*   **Compartments:** I'd like to be able to specify rows and columns of compartments. The dividers should create "slots" inside the box.
*   **Box Configurations:** I will probably need a few pre-defined configurations that I can use as a starting point. This would be different combinations of rectangle widths and heights that make the slots. Examples of those are:
    *   "Normal width" boxes, both single and double slot.
    *   "Wide width" boxes, also single and double slot.
    *  Some boxes that use "long" slots.
   These should be defined using parameters, which would be the rectangle width and the slot height.

*   **Modularity:** I'd like to be able to easily add and remove slots, or change their position, by modifying a simple list of heights in the parameters for each box configuration. I should be able to adjust the height and depth of each compartment.

Basically, I want a core function that stacks the rectangle, creates the rounded polygon, offsets it to create the walls, extrudes to 3D, and then cuts out the slots for the internal dividers. I need the ability to easily adjust the overall height of a finished box, along with the number of rows and columns of compartments within the box. It would be great if it were as flexible as possible. Could you generate the OpenSCAD code that would do this?

[Assistant]
Slotted_Drawer_Boxes.scad:
```
//
//
// Slotted Drawer Boxes
//
// Author: Joerg Pressel
// Created: 28/01/2022
// License: Creative Commons - Attribution - Non-Commercial - Share Alike
//
//

//
// That's really the brain for rounding and some polygon operations!
// Author: IrevDev
//
include <Round-Anything-master/polyround.scad>

//
// Global stuff
//
t = 0.96;       //wall thickness
bt = 0.6*3;     //bottom thickness
height = 80;    //max height (you may render fractions)
r = 4;          //outer roundings radius

//
// Overwrites function contained within polyround.scad
// code is not by me, but by IrevDev
// I had to add a bug fix in the last line
//
function offsetPolygonPoints(points, offset=0)=
let(
  isCWorCCW=sign(offset)*CWorCCW(points)*-1,
  lp=len(points)
)
[for(i=[0:lp-1]) parallelFollow([
  points[listWrap(i-1,lp)],
  points[i],
  points[listWrap(i+1,lp)],
],thick=abs(offset),mode=isCWorCCW)];

//
// Returns the overall height (length) of a stack of rectangles
//
function stack_height(arr,i=0,res=0) =
    i < len(arr) ? stack_height(arr,i+1,res+arr[i][1]) : res;

//
// Returns the widest width in a stack of rectangles
//
function stack_width(arr,i=0,res=0) =
    i < len(arr) ? stack_width(arr,i+1,max(res,arr[i][0])) : res;

//
// Stacks multiple rectangles to one single combined polygon
//
// arr: array [width, height, optional:radius]
// r: if defined, use as radius for all points, if not the radius needs to be defined in the array
//
function stack_squares(arr, r = 0, i = 0, y = 0, res = [], dir = 1) =
    dir == 1 ?
        i < len(arr) ?
            stack_squares(arr, r, i + 1, y + arr[i][1],
            concat(res,
                [[-arr[i][0] / 2, y, r ? r : arr[i][2]],
                [-arr[i][0] / 2, y + arr[i][1], r ? r : arr[i][2]]])
            , 1)
        :
            stack_squares(arr, r, i - 1, y, res, -1)
    :
        i > -1 ?
            stack_squares(arr, r, i - 1, y - arr[i][1],
            concat(res,
                [[arr[i][0] / 2, y, r?r:arr[i][2]],
                [arr[i][0] / 2, y - arr[i][1], r?r:arr[i][2]]]),
            -1)
        :
            res;

//
// Creates a 3D object out of a stack of rectangles
//
// p: array with rectangles that will be stacked
// h: height to create
// rows: (optional) number of row compartments
// columns: (optional) number of column compartments
//
module render_module(p, h, rows=0, columns=0){
    a1 = stack_squares(p,r);
    a2 = offsetPolygonPoints(a1, -t);
    sh = stack_height(p);
    sw = stack_width(p);
    difference(){
        polyRoundExtrude(a1,h,0,0,fn=10);
        difference(){
            translate([0,0,bt]) polyRoundExtrude(a2,h,0,5,fn=20);
            if (rows > 1)    for(y=[0:rows-2]) translate([0,sh/rows*(y+1),bt+h/2]) cube([wide_w2,t,h],true);
            if (columns > 1) for(x=[0:columns-2]) translate([sw/columns*(x+1)-sw/2,sh/2,bt+h/2]) cube([t,sh,h],true);
        }
     }
}

//
// 2D version just for testing and documentation
//
module render_module_2d(p, rows=0, columns=0){
    a1 = stack_squares(p,r);
    a2 = offsetPolygonPoints(a1, -t);
    sh = stack_height(p);
    sw = stack_width(p);
    difference(){
        polygon((polyRound(a1,fn=20)));
        difference(){
            polygon((polyRound(a2,fn=20)));
            if (rows > 1)    for(y=[0:rows-2]) translate([0,sh/rows*(y+1)]) square([wide_w2,t],true);
            if (columns > 1) for(x=[0:columns-2]) translate([sw/columns*(x+1)-sw/2,sh/2]) square([t,sh],true);
        }
     }
}

//
// Slotted Drawer Box configurations
//
// arrays are a stack of rectangles [[width1,height1],[width2,height2]...]
//

normal_w1 = 40;
normal_w2 = 47;
wide_w1 = 65;
wide_w2 = 72;
standard_slot = 38;
long_slot = 62;

//normal width, double slot
module_normal_double = [[normal_w1,5.5],[normal_w2,standard_slot],[normal_w1,12],[normal_w2,standard_slot],[normal_w1,5.5]];

//normal width, single slot
module_normal_single = [[normal_w1,5.5],[normal_w2,standard_slot],[normal_w1,5.5]];

//normal width, single slot plus long slot
module_normal_single_plus_long = [[normal_w1,5.5],[normal_w2,standard_slot],[normal_w1,12],[normal_w2,long_slot],[normal_w1,5.5]];

//normal width, long slot
module_normal_single_long = [[normal_w1,5.5],[normal_w2,long_slot],[normal_w1,5.5]];

//wide width, single slot
module_wide_single = [[wide_w1,5.5],[wide_w2,standard_slot],[wide_w1,5.5]];

//wide width, double slot
module_wide_double = [[wide_w1,5.5],[wide_w2,standard_slot],[wide_w1,12],[wide_w2,standard_slot],[wide_w1,5.5]];

//wide width, single slot plus long slot
module_wide_single_plus_long = [[wide_w1,5.5],[wide_w2,standard_slot],[wide_w1,12],[wide_w2,long_slot],[wide_w1,5.5]];

//wide width, long slot
module_wide_single_long = [[wide_w1,5.5],[wide_w2,long_slot],[wide_w1,5.5]];

//
// Render something, here!
// simply pass the array of rectangles to render_module
// with height and number of rows and columns
//

render_module(module_wide_double, height/2, 2, 2);  //example with array of wide width double module, half hight and 2x2 compartments
//render_module(module_normal_double, height/2);    //example with array of normal width double module, half height, no compartments

```


[User]
Hey, I'm looking to design a customizable box with a lid using OpenSCAD. I've got a basic concept in mind, but I'd really appreciate it if you could help me generate the necessary SCAD files.

Essentially, I want a box with a rounded rectangular shape, and a lid that fits snugly. I want to be able to easily adjust the overall dimensions and wall thickness, as well as the rounding on the corners. The box and lid should have a lip to hold the lid in place, so that the lid doesn't slide off easily.  The box should be printed without supports.

Here are the specific parameters I'd like to control:

*   **Inner Box Width:** The width of the box's interior.
*   **Inner Box Depth:** The depth of the box's interior.
*   **Inner Box Height:** The total interior height of the combined box and lid.
*   **Wall Thickness:** The thickness of the box and lid walls.
*   **Wall Radius:** The radius of the rounding on the box and lid corners.
*   **Lip Thickness:** The thickness of the lip on the box.
*  **Lip Slack:** The slack between the lip and the matching surface on the lid.
*   **Box/Lid Height Ratio:**  A ratio that determines the proportion of the total interior height dedicated to the box vs the lid.

I think the most natural way to go about this is to define a `rounded_square` module which takes a 2D dimension and radius, and then use it to produce the box and lid shapes. I would also like a `box_walls` module which produces the rounded rectangular walls, `box_lip` which produces the lip, `box_floor` which produces the floor, and finally `box_body` which combines the floor and walls, and `box` and `lid` modules that combine all of the pieces in a way such that the box fits into the lid.

The box should be designed such that the lid fits into it without supports, and the parameters for the box describe its *internal* dimensions. This means that if I specify an internal width of 20, the box will fit around an object 20mm wide. I'm thinking the ratio parameter would define the proportion of the *total* height occupied by the main box body. So for example, if I set the ratio to 4/5, the main box takes up 4/5 of the interior box height and the lid takes up the remaining 1/5 (plus the thickness of the walls, floor, and lip, of course).

I think that covers it. Do you think you can handle that? I'm really looking forward to seeing what you come up with!

[Assistant]
box.scad:
```
// SPDX-License-Identifier: CC-BY-NC-SA-4.0

/*
 * Copyright (c) 2022 - Felipe Balbi <felipe@balbi.sh>
 */

/* [Hidden] */
// Number Of Faces
$fn = 100;

// Box & Lid lip
lip_thickness = 1;

// Box & Lip slack
lip_slack = 0.1;

/* [Parameters] */
// Total Box Width (mm)
box_width = 20;

// Total Box Depth (mm)
box_depth = 20;
// Total Box Height (mm)
box_height = 20;

// Wall Thickness (mm)
wall_thickness = 2;

// Wall radius (mm)
wall_radius = 1;

// Box vs Lid Ratio
ratio = 4/5;

module rounded_square(dims, r = 1) {
  function get_width(dims)	= dims[0];
  function get_depth(dims)	= dims[1];

  w = get_width(dims);
  d = get_depth(dims);

  points = [[-w / 2 + r, -d / 2 + r],
	    [-w / 2 + r,  d / 2 - r],
	    [ w / 2 - r, -d / 2 + r],
	    [ w / 2 - r,  d / 2 - r]];

  hull() {
    for (p = points) {
      translate(p) {
	circle(r);
      }
    }
  }
}

module box_walls(width, depth, height, thickness, radius) {
  outter_width	= width + 2 * thickness;
  outter_depth = depth + 2 * thickness;

  inner_width	= width;
  inner_depth	= depth;

  linear_extrude(height) {
    difference() {
      rounded_square([outter_width, outter_depth], r = radius);
      rounded_square([inner_width, inner_depth], r = radius);
    }
  }
}

module box_lip(width, depth, thickness, radius, lip) {
  total_width = width + 2 * lip;
  total_depth = depth + 2 * lip;

  linear_extrude(2 * thickness + lip) {
    rounded_square([total_width, total_depth], r = radius);
  }
}

module box_floor(width, depth, thickness, radius) {
  total_width = width + 2 * thickness;
  total_depth = depth + 2 * thickness;

  linear_extrude(thickness) {
    rounded_square([total_width, total_depth], r = radius);
  }
}

module box_body(width, depth, height, thickness, radius) {
  union() {
    box_floor(width, depth, thickness, radius);

    translate([0, 0, thickness]) {
      box_walls(width, depth, height, thickness, radius);
    }
  }
}

module box(width, depth, height, thickness, radius, lip = 1) {
  difference() {
    box_body(width, depth, height, thickness, radius);

    translate([0, 0, height - lip/2]) {
      box_lip(width, depth, thickness, radius, lip);
    }
  }
}

module lid(width, depth, height, thickness, radius, lip = 1, slack = 0.1) {
  difference() {
    box_body(width, depth, height, thickness, radius);

    translate([0, 0, height]) {
      box_walls(width + 2 * lip - slack, depth + 2 * lip - slack, height + lip,
		thickness, radius);
    }
  }
}

box(box_width, box_depth, box_height * ratio,
    wall_thickness, wall_radius);

translate([box_width + 2 * wall_thickness + 2 * wall_radius + 2, 0, 0]) {
  lid(box_width, box_depth, box_height * (1 - ratio) + wall_thickness,
      wall_thickness, wall_radius, lip_thickness, lip_slack);
}

```


[User]
I need an OpenSCAD design for a simple furniture joint, specifically for building shelving units. It should be a parametric design, so I can adjust the dimensions easily.

Here are the key features I need:

*   **Basic Shape:** It should essentially be a cylinder with two rectangular plates extending out, forming a sort of "T" shape when viewed from the side. These plates will allow the joint to connect to flat surfaces like shelves.
*   **Parameters:** I need to control the following:
    *   `inner_diam`: The inner diameter of the cylinder.
    *   `outer_diam`: The outer diameter of the cylinder.
    *   `height`: The height of the cylinder.
    *   `plate_thickness`: The thickness of the rectangular plates.
    *   `holes`: A boolean to toggle whether or not holes are generated
    *   `hole_diam`: The diameter of the generated holes.
*   **Holes (Optional):** The design should optionally include holes for mounting or further connections.
    *   If holes are enabled, they should be placed:
        *   Two holes on the cylindrical part, aligned with the plates and at about 1/4 and 3/4 of the height.
        *   Two holes, on the side faces of the plates.
*   **Construction:** The plates should be joined to the cylinder such that they are flush with each other on the bottom of the cylinder.

I don't need any external libraries for this design. The whole thing should be built with basic OpenSCAD primitives. I think this can all be done in a single .scad file.

[Assistant]
piece2.scad:
```
//////Customizer variables//////

//Inner diameter
inner_diam = 20.1;
//Outer diameter
outer_diam = 24.1;
//Height
height = 40.1;
//Plate thickness
plate_thickness = 5.1;
//Generate holes
holes = true;
//Holes diameter
hole_diam = 4.1;

///////////////////////////////

if(holes == false){
    difference(){
        union(){
            cylinder(height, outer_diam/2, outer_diam/2, $fn = 80);

            translate([-outer_diam/2,0,0])
            cube([outer_diam,3*outer_diam/2,2*plate_thickness]);

            rotate([0,0,90])
            translate([-outer_diam/2,0,0])
            cube([outer_diam,3*outer_diam/2,2*plate_thickness]);
        }
        union(){
            cylinder(height, inner_diam/2, inner_diam/2, $fn = 80);

            translate([-outer_diam/2,outer_diam/2+plate_thickness,plate_thickness/2])
            cube([outer_diam,outer_diam-plate_thickness,plate_thickness]);

            rotate([0,0,90])
            translate([-outer_diam/2,outer_diam/2+plate_thickness,plate_thickness/2])
            cube([outer_diam,outer_diam-plate_thickness,plate_thickness]);
        }
    }
}

if(holes == true){
    difference(){
        union(){
            cylinder(height, outer_diam/2, outer_diam/2, $fn = 80);

            translate([-outer_diam/2,0,0])
            cube([outer_diam,3*outer_diam/2,3*plate_thickness]);

            rotate([0,0,90])
            translate([-outer_diam/2,0,0])
            cube([outer_diam,3*outer_diam/2,3*plate_thickness]);
        }
        union(){
            cylinder(height, inner_diam/2, inner_diam/2, $fn = 80);

            translate([-outer_diam/2,outer_diam/2+plate_thickness,plate_thickness])
            cube([outer_diam,outer_diam-plate_thickness,plate_thickness]);

            rotate([0,0,90])
            translate([-outer_diam/2,outer_diam/2+plate_thickness,plate_thickness])
            cube([outer_diam,outer_diam-plate_thickness,plate_thickness]);

            translate([0,0,height/4]) rotate([0,90,-45])
            cylinder(outer_diam, hole_diam/2, hole_diam/2, $fn=40);

            translate([0,0,3*height/4]) rotate([0,90,-45])
            cylinder(outer_diam, hole_diam/2, hole_diam/2, $fn=40);

            translate([0,outer_diam+plate_thickness/2,0])
            cylinder(plate_thickness, hole_diam/2, hole_diam/2, $fn=40);

            translate([-(outer_diam+plate_thickness/2),0,0])
            cylinder(plate_thickness, hole_diam/2, hole_diam/2, $fn=40);
        }
    }
}
```