[User]
I'd like to design a parametric propeller, and I'm having some trouble getting started. I've got the UIUC Airfoil Coordinates database, so I know I'll need to load a `.dat` file and generate the `.scad` to define the foil profile. I also need a main file to do the propeller design.

Here's what I have in mind:

The propeller should be defined by these parameters:
*   `propeller_diameter`: Overall diameter of the propeller.
*   `pitch`: The pitch of the propeller.
*   `rendering_steps`: The number of slices used to create each blade. This is for rendering quality and should be adjustable.
*   `blades`: The number of blades on the propeller.
*   `length_factor`: A scaling factor for the length of the airfoil profile.
*  `height_factor`: A scaling factor for the height of the airfoil profile.
*   `offset`: An offset to shift the profile so it's centered on the rotation axis.
*   `turning_direction`: The turning direction of the propeller, either "left" or "right".

I want options for how to attach the propeller to a motor shaft. Specifically, I want either:

*   **Cylinder option:** A cylinder at the center of the propeller, with these parameters:
    *   `middle_diameter`: Diameter of the cylinder.
    *   `middle_height`: Height (thickness) of the cylinder.
    *   `middle_bore`: Diameter of a hole through the center of the cylinder.
*   **Spinner option:** A rounded spinner that blends into the base of the propeller, with these parameters:
    *   `spinner_diameter`: Maximum diameter of the spinner.
    *   `spinner_height`: Height of the spinner.
    *   `spinner_power`: A power value for the shape of the spinner, making it more or less parabolic.
    *   `spinner_offset`: Offset of the spinner relative to the blade position.

Finally, I need an option to choose the print setting: either "cylinder", "spinner", or "spinner2". The "spinner2" shape should be similar to "spinner", but with a parabola opening in the direction of the propeller blades rather than the rotation axis.

I'm using the OpenSCAD standard library, and I want you to create the `.scad` files. I'd like the main file to be `propeller.scad` and the foil file to be `foil.scad`. I also included a sample `foil.scad` file in case you want to use it for testing.

[Assistant]
foil.scad:
```
coordinates = [  [0.0000000, 0.0000000], [0.0005000, 0.0023390], [0.0010000, 0.0037271], [0.0020000, 0.0058025], [0.0040000, 0.0089238], [0.0080000, 0.0137350], [0.0120000, 0.0178581], [0.0200000, 0.0253735], [0.0300000, 0.0330215], [0.0400000, 0.0391283], [0.0500000, 0.0442753], [0.0600000, 0.0487571], [0.0800000, 0.0564308], [0.1000000, 0.0629981], [0.1200000, 0.0686204], [0.1400000, 0.0734360], [0.1600000, 0.0775707], [0.1800000, 0.0810687], [0.2000000, 0.0839202], [0.2200000, 0.0861433], [0.2400000, 0.0878308], [0.2600000, 0.0890840], [0.2800000, 0.0900016], [0.3000000, 0.0906804], [0.3200000, 0.0911857], [0.3400000, 0.0915079], [0.3600000, 0.0916266], [0.3800000, 0.0915212], [0.4000000, 0.0911712], [0.4200000, 0.0905657], [0.4400000, 0.0897175], [0.4600000, 0.0886427], [0.4800000, 0.0873572], [0.5000000, 0.0858772], [0.5200000, 0.0842145], [0.5400000, 0.0823712], [0.5600000, 0.0803480], [0.5800000, 0.0781451], [0.6000000, 0.0757633], [0.6200000, 0.0732055], [0.6400000, 0.0704822], [0.6600000, 0.0676046], [0.6800000, 0.0645843], [0.7000000, 0.0614329], [0.7200000, 0.0581599], [0.7400000, 0.0547675], [0.7600000, 0.0512565], [0.7800000, 0.0476281], [0.8000000, 0.0438836], [0.8200000, 0.0400245], [0.8400000, 0.0360536], [0.8600000, 0.0319740], [0.8800000, 0.0277891], [0.9000000, 0.0235025], [0.9200000, 0.0191156], [0.9400000, 0.0146239], [0.9600000, 0.0100232], [0.9700000, 0.0076868], [0.9800000, 0.0053335], [0.9900000, 0.0029690], [1.0000000, 0.0005993], [1.0000000, -.0005993], [0.9900000, -.0009666], [0.9800000, -.0013339], [0.9700000, -.0017011], [0.9600000, -.0020683], [0.9400000, -.0028028], [0.9200000, -.0035373], [0.9000000, -.0042718], [0.8800000, -.0050063], [0.8600000, -.0057408], [0.8400000, -.0064753], [0.8200000, -.0072098], [0.8000000, -.0079443], [0.7800000, -.0086788], [0.7600000, -.0094133], [0.7400000, -.0101478], [0.7200000, -.0108823], [0.7000000, -.0116169], [0.6800000, -.0123515], [0.6600000, -.0130862], [0.6400000, -.0138207], [0.6200000, -.0145551], [0.6000000, -.0152893], [0.5800000, -.0160232], [0.5600000, -.0167572], [0.5400000, -.0174914], [0.5200000, -.0182262], [0.5000000, -.0189619], [0.4800000, -.0196986], [0.4600000, -.0204353], [0.4400000, -.0211708], [0.4200000, -.0219042], [0.4000000, -.0226341], [0.3800000, -.0233606], [0.3600000, -.0240870], [0.3400000, -.0248176], [0.3200000, -.0255565], [0.3000000, -.0263079], [0.2800000, -.0270696], [0.2600000, -.0278164], [0.2400000, -.0285181], [0.2200000, -.0291445], [0.2000000, -.0296656], [0.1800000, -.0300490], [0.1600000, -.0302546], [0.1400000, -.0302404], [0.1200000, -.0299633], [0.1000000, -.0293786], [0.0800000, -.0284595], [0.0600000, -.0271277], [0.0500000, -.0260452], [0.0400000, -.0245211], [0.0300000, -.0226056], [0.0200000, -.0202723], [0.0120000, -.0169733], [0.0080000, -.0142862], [0.0040000, -.0105126], [0.0020000, -.0078113], [0.0010000, -.0059418], [0.0005000, -.0046700], [0.0000000, 0.0000000] ];
```
propeller.scad:
```
include <foil.scad>;

/* [ Main Properties of the Propeller ] */
// diameter of the propeller
propeller_diameter = 70;
radius = propeller_diameter/2;
// pitch of the propeller
pitch = 50;
// rendering steps (slices) for one propeller blade; start with 10 and increase to get better results (but will take longer to render)
rendering_steps = 10;
// number of blades
blades = 3;
// length scaling factor; scale the length of the profile up or down
length_factor = 15;
// height scaling factor; scale the height of the profile up or down
height_factor = 30;
// shift the profile to the middle of the axis (typically -0.5 as the UIUC Airfoil Data ranges from 0 to +1)
offset = -0.5;
// turning direction of the propeller
turning_direction = "left"; // [left: Left, right: Right]

/* [ Middle cylinder part of the propeller -- needs to be adapted manually -- only valid for the cylinder option ] */
// diameter of the middle part
middle_diameter = 18;
// height (thickness) of the middle part
middle_height = 12;

/* [ Middle cylinder part of the propeller -- needs to be adapted manually -- only valid for the cylinder option ] */
// bore diameter in the middle part
middle_bore = 3;

/* [ Spinner -- needs to be adapted manually -- only valid for the spinner option ] */
// Maximum diameter of the spinner
spinner_diameter = 20;
// spinner radius is half the diameter
spinner_radius= spinner_diameter/2;
// Height of the spinner
spinner_height = 30;
// Spinner shape will follow a function with a certain power of the spinner radius
spinner_power = 2;
// Coefficient is calculated automatically to fit the height/diameter settings
spinner_coeff = (spinner_height)/(pow(spinner_radius,spinner_power));
spinner_coeff2 = (spinner_radius)/(pow(spinner_height,spinner_power));
// Spinner position relative to the blade position
spinner_offset = -6.3;

// points for a spinner where the parabola / polynom opens in the direction of the propeller main axis
points_spinner = [ [0,spinner_height-spinner_coeff*pow(spinner_radius,spinner_power)], [0,0], for (i=[0:0.2:spinner_radius]) [i,spinner_height-spinner_coeff*pow(i,spinner_power)] ];

// points for a spinner where the parabola / polynom opens in the direction of the blades

points_spinner2 = [ [spinner_radius-spinner_coeff2*pow(spinner_height,spinner_power),0], [0,0], for (i=[0:0.2:spinner_height]) [spinner_radius-spinner_coeff2*pow(i,spinner_power),i] ];

/* [ Print settings ] */
// How do you want to print it?
Print_setting="cylinder"; // [cylinder: print with middle cylinder, spinner: print with spinner -- polynom opens in direction of propeller main axis, spinner2: print with spinner -- polynom opens in direction of propeller blades]

/* [ Rendering] */
// exactness of the rendering
$fn = 80;

// beta is the angle fo the propeller blade at a certain radius
beta = [ for (i = [0:rendering_steps]) atan(pitch/(2*PI*radius*i/rendering_steps)) ];

scaling_factor = [length_factor,height_factor]; // length and thickness of profile

if (Print_setting == "cylinder") {
  intersection() {
      // intersect the actual propeller with a suitable cylinder

  difference() { // subtract the superfluous parts of the blade
    union() {
      get_all_blades();

      rotate([90,0,0])  // add middle cylinder
        cylinder(r=middle_diameter/2,h=middle_height,center=true);
    }
    difference() {
      rotate([90,0,0])  // take a big middle cylinder
        cylinder(r=middle_diameter/2,h=4*middle_height,center=true);
      rotate([90,0,0])  // subtract the actual middle cylinder therefrom
        cylinder(r=middle_diameter/2,h=middle_height,center=true);
    }
    rotate([90,0,0])  // subtract middle bore
      cylinder(r=middle_bore/2,h=middle_height,center=true);
  }
    rotate([90,0,0])
      cylinder(r=radius, h=10*middle_height, center=true);
}
}

if (Print_setting == "spinner") {
difference() {
  union() {
    get_all_blades();
    get_spinner();
  }
  get_spinner_subtraction();
}
}

if (Print_setting == "spinner2") {
difference() {
  union() {
    get_all_blades();
    get_spinner2();
  }
  get_spinner_subtraction();
}
}

// generate all the blades
module get_all_blades() {
union() {
  for (i=[1:blades]) {
    rotate([0,360/blades*i,0])
       get_one_blade();
  }
}
}

module blade_from_profile() {
  union() {
  for (i =[0:rendering_steps-1]) {
    // this is a discretisation of the blade shape into some rendering steps
    // two consecutive slices are combined by a convex hull operation
    hull() {
    translate([0,0,radius*i/rendering_steps])
      rotate([0,0,(turning_direction=="left"?-1:1)*beta[i]])children(0);
    translate([0,0,radius*(i+1)/rendering_steps])
      rotate([0,0,(turning_direction=="left"?-1:1)*beta[i+1]])children(0);
    }
  }
}

}

// generate one propeller blade out of the airfoil
module get_one_blade() {

   blade_from_profile()get_profile();

}

// generates the airfoil from a certain profile with the appropriate direction, offset and scaling factor(s)
module get_profile() {

    linear_extrude(0.01, scale = scaling_factor) // small extrusion to make a 3D object out of the 2D profile
    translate([(turning_direction=="left"?1:-1)*offset,0,0]) // airfoil will range from 0 to 1, that's why an offset of -0.5 is necessary (or +0.5, respectively -- for the other direction)
    rotate([0,(turning_direction=="left"?0:180)]) // rotation necessary for the turning direction of the propeller
    polygon(coordinates);

}

module get_spinner() {
// make the spinner
translate([0,spinner_offset,0])
rotate([-90,0,0])
rotate_extrude()polygon(points_spinner);
}

module get_spinner2() {
echo(points_spinner2);
// make the spinner
translate([0,spinner_offset,0])
rotate([-90,0,0])
rotate_extrude()polygon(points_spinner2);
}

module get_spinner_subtraction() {
  // the bottom cylinder for the superfluous blade parts
  translate([0,spinner_offset-10,0])
  rotate([90,0,0])
    cylinder(r=50, h=20, center = true);

  rotate([90,0,0]) {
  translate([0,0,0])
    cylinder(r=middle_bore/2,h=200,center=true); // middle bore
    translate([0,0,-spinner_offset-1])
      cylinder(r1=middle_bore/2,r2=middle_bore/2+0.5,h=1); // cone to fit middle bore
  }
}

// Johann Schuster, August 2020, v 0.04

```


[User]
I'm looking to create a box secured by multiple combination locks, kind of like a cryptex. I'd like the design to be parametric so I can easily adjust its dimensions and the number of locks.

Here's what I have in mind:

*   **Basic Structure:** The design should consist of a main container, a lid, and the combination lock mechanisms.
*   **Container:**
    *   The container should be a polygon (I'd like to choose the number of sides), and I need to set the radius from the center to each corner.
    *   I need to set the wall thickness and a base/top thickness.
    *   I'd like to be able to add extra height to the container for aesthetics.
*   **Lid:** The lid should fit snugly on top of the container, and I’d like its base thickness to match the container's.
*   **Combination Locks:**
    *   Each lock should have a rotating disc with symbols on it. I'd like to define the number of discs per lock.
    *   The symbols (letters) can be either extruded or cut into the disc. The letters should be on each side of the hexagonal discs.
    *   I'd like to control the height and radius of each disc, as well as the depth of the letters and their string of symbols.
    *   There will be an inner core that goes into each lock with a small nose on each disc.
    *   The container lid should have the complementary "teeth" to prevent rotation when the combination isn't aligned.
    *   I'd like to set a core radius for the center of the lock, and a gap between the core and the surrounding wall.
*   **Clearance:** I need a clearance parameter for 3D printing to adjust for printer tolerances.
*   **Test Print:** It would be extremely helpful to have a test print module that creates small sections of the container wall, lock core (both inner and outer), disc, and disc lid, so I can test fit and clearances quickly before printing the whole model.
*   **Stopper:** I want to have a stopper that goes on the inside top of the lock wall so that the discs can't slide out of the lock.

I’d like all dimensions to be in millimeters.  Could you generate the necessary OpenSCAD file(s) for this? I'm not planning on using any external libraries like MCAD, BOSL or anything else.

[Assistant]
combination_lock_safe.scad:
```
// Which part would you like to see?
part = "container"; // [container, container_lid, disc, disc_lid, stopper, test]

/* [general settings] */
// thickness of base/top (mm)
baseSize = 2;
// thickness of walls (mm)
wallSize = 1.2;
// your printers clearance (mm)
clearance = 0.3;

/* [container dimensions] */
// sides of the container
containerSides = 4; // [3,4,5,6,7,8]
// radius of the container (mm) (measured as center to corner)
containerRadius = 20;
// increase the container height by (mm)
extraHeight = 4;

/* [lock dimensions] */
// lock core radius (mm)
lockCoreRadius = 3;
// gap between lock core and wall (mm)
lockInnerGap = 0.8;

/* [combination disc dimensions] */
// how many discs does each lock have (determines how high the container is)
numberOfDiscsPerLock = 3; // [1,2,3,4,5,6,7,8,9]
// height of a combination disc (mm)  (determines how high the container is)
discHeight = 10;
// radius of a combination disc (mm)
discRadius = 9;

/* [lock symbols] */
// thickness of letters (mm)
letterDepth = 0.8;
// letters extruded or cut into the disc?
letterExtrude = false;
// one letter per disc side
discString = "ABCDEF";

print_part();

module print_part(){
    if(part == "container"){
        container();
    } else if (part == "container_lid"){
        lid();
    } else if (part == "disc"){
        discWithLetters();
    } else if (part == "disc_lid"){
        cryptexDiscLid();
    } else if (part == "stopper"){
        stopper();
    } else if(part == "test") {
        testPrint();
    }
}

function GetDiscHoleRadius() = lockCoreRadius + lockInnerGap + wallSize + clearance;
function GetNoseHeight() = (discHeight - (2 * baseSize) - 2);
function GetShortestToDiscWall(r, s) =  (r-wallSize) * cos(180/s);
function GetNoseLength() = sqrt(pow(lockCoreRadius / 4, 2) + pow(GetShortestToDiscWall(discRadius, 6),2)) - 1;
function GetContainerHeight() = numberOfDiscsPerLock * discHeight + 2 * baseSize;

module containerLock(){
        union(){
            translate([0,-containerRadius + discRadius * 0.3,baseSize/2]) cube([discRadius,discRadius * 3,baseSize], center = true);
            translate([0,-containerRadius - discRadius * 1.2,0]) lockCoreOuter();
    }
}

module testPrint(){
    // test lock outer
    translate([2.5 * discRadius, 0, 0]) {
        difference(){
            union(){
                difference(){
                    cylinder(h= baseSize,r=discRadius,$fn=6);
                    translate([0,-GetShortestToDiscWall(discRadius,6) - wallSize/2,0]) cube([wallSize,wallSize,discHeight], center = true);
                }
                difference(){
                    cylinder(h = 2 * baseSize + discHeight, r = lockCoreRadius + lockInnerGap + wallSize);
                    cylinder(h = 2 * baseSize + discHeight, r = lockCoreRadius + lockInnerGap);
                    translate([0, -(GetNoseLength()+1)/2,discHeight/2 + 1.5 * baseSize]) cube([lockCoreRadius * 2, GetNoseLength()+1, baseSize + discHeight], center = true);
                }
            }
            translate([0, 0, discHeight + baseSize]) stopper();
        }
    }
    // test lock inner
    translate([-2.5 * discRadius, 0, 0]) {
        union(){
            cylinder(h= baseSize,r=discRadius,$fn=6);
            translate([0,-GetNoseLength()/2,(discHeight / 2) + 2 * baseSize]) cube([lockCoreRadius,GetNoseLength(),GetNoseHeight()],  center = true);
            cylinder(h = discHeight + 2 * baseSize, r = lockCoreRadius);
        }
    }
    // test disc
    translate([0, 2.5 * discRadius, 0]) {
        discWithLetters();
    }
    // test disc lid
    translate([0, -2.5 * discRadius, 0]) {
        cryptexDiscLid();
    }
    stopper();
}

// thing the inner part of the lock slides into
module lockCoreOuter (){
    difference(){
        union(){
            difference(){
                cylinder(h=baseSize + extraHeight,r=discRadius,$fn=6);
                translate([0,-GetShortestToDiscWall(discRadius,6) - wallSize/2,0]) cube([wallSize,wallSize,GetContainerHeight()], center = true);
            }
            difference(){
                cylinder(h = GetContainerHeight() + extraHeight, r = lockCoreRadius + lockInnerGap + wallSize);
                cylinder(h = GetContainerHeight() + extraHeight, r = lockCoreRadius + lockInnerGap);
                translate([0,0,extraHeight]) lockNoseGap();
            }
        }
        translate([0,0,GetContainerHeight() - baseSize + extraHeight]) stopper();
    }
}

// glue on top to stop discs from sliding off
module stopper(){
    union(){
        difference(){
            cylinder(h=baseSize,r=discRadius,$fn=6);
            cylinder(h=discHeight+1,r=lockCoreRadius + lockInnerGap + wallSize,center = false);
            lockNoseGap();
        }
        difference(){
            for(i = [3:5]){
                rotate([0,0,90*i]) translate([0, discRadius/3, baseSize/2]) cube([3,discRadius,baseSize], center = true);
            }
            cylinder(h = baseSize, r = lockCoreRadius + lockInnerGap);
        }
    }
}

// gap for the noses of the inner core
module lockNoseGap(){
    translate([0, -(GetNoseLength()+1)/2,GetContainerHeight()/2]) cube([lockCoreRadius * 2, GetNoseLength()+1, GetContainerHeight()], center = true);
}

// create the inner part of the lock
module lockCoreInner(){
    union(){
        for(i = [0:(numberOfDiscsPerLock-1)]) translate([0,-GetNoseLength()/2,(discHeight / 2) + i * discHeight + 2 * baseSize]) cube([lockCoreRadius,GetNoseLength(),GetNoseHeight()],  center = true);
        cylinder(h = discHeight * numberOfDiscsPerLock + 2 * baseSize, r = lockCoreRadius);
    }
}

// thing where you put stuff into...
module container () {
    union(){
        for(i = [0:containerSides-1]){
            if(containerSides/2==round(containerSides/2)){
                rotate([0,0,-90 + i * 360/containerSides]) containerLock(); // rotate the stuff above
            } else {
                rotate([0,0,-90 + (i+0.5) * 360/containerSides]) containerLock(); // rotate the stuff above
            }
        }
    }
    difference(){
        cylinder(h=GetContainerHeight() + extraHeight, r=containerRadius, $fn = containerSides);
        translate([0,0,baseSize]) cylinder(h=GetContainerHeight() + extraHeight, r=containerRadius-wallSize, $fn = containerSides);
    }
}

module lidLock(){
    union(){
        translate([0,-containerRadius + discRadius * 0.3,baseSize/2]) cube([lockCoreRadius * 2,discRadius * 3,baseSize], center = true);
        translate([0,-containerRadius - discRadius * 1.2,0])  lockCoreInner(); // rotate the stuff above

    }
}

// the container lid
module lid () {
        for(i = [0:containerSides-1]){
            if(containerSides/2==round(containerSides/2)){
                rotate([0,0,-90 + i * 360/containerSides]) lidLock();
            }
            else{
                rotate([0,0,-90 + (i+0.5) * 360/containerSides]) lidLock();
            }
        }
    union(){
        cylinder(h=baseSize, r=containerRadius, $fn = containerSides);
    }
}

// cryptex disc
module cryptexDisc (){
    difference(){
        cylinder(h=discHeight,r=discRadius,$fn=6);
        translate([0,0,baseSize]) cylinder(h=discHeight-baseSize,r=discRadius-wallSize,$fn=6);
        cylinder(h=discHeight+1,r=GetDiscHoleRadius(),center = false);
        lockNoseGap();
        translate([0,0,discHeight - baseSize]) cryptexDiscLid();
    }
}

// add letters to disc
module discWithLetters(){
    if(letterExtrude){
        union(){
            cryptexDisc();
            discLetters();
        }
    } else{
        difference(){
            cryptexDisc();
            discLetters();
        }
    }
}

// create letters to print on the disc
module discLetters(){
    for(i = [0:5]){
        rotate([0,0,i * 360/6]) translate([0,-GetShortestToDiscWall(discRadius, 6)  - wallSize/2,discHeight/2]) rotate([90,0,0]) letter(discString[i]);
    }
}

// one letter
module letter(l){
    linear_extrude(height = letterDepth*2){
        text(l, size = discHeight - (2 * baseSize), halign = "center", valign = "center");
    }
}

// lid for the disc
module cryptexDiscLid(){
    difference(){
        cryptexDiscBase();
        for(i = [1:5]) rotate([0,0,i * 360/6]) translate([0,-GetShortestToDiscWall(discRadius, 6)  - wallSize/2,baseSize/2]) cube([5,wallSize,baseSize], center = true);
    }
}

module cryptexDiscBase (){
    difference(){
        cylinder(h=baseSize,r=discRadius,$fn=6);
        cylinder(h=discHeight+1,r=GetDiscHoleRadius(),center = false);
        lockNoseGap();
    }
}

```


[User]
Hey, I'm looking to create a customizable spool drawer system for 3D printer filament spools, and I'd like to design it with OpenSCAD. I have a good idea of the parameters I'd like to control, and I'd like to be able to generate individual drawers, lids, or a combined drawer and lid, as well as testing pieces, mounting pins, and templates.

Here's the breakdown of what I need:

**Overall Structure:**

*   The design should be based around a circular shape defined by the spool's outer and inner diameters, with a specified inner height for the spool.
*   The system should be segmented into a number of drawers around the circle, and I need to be able to define how many drawers, and how many cells within each drawer, that there should be.
*   A lid should be included that can fit on top of the drawers, and it should have a lip.
*   I'd like to create a separate mounting pin that could be used to secure the system, and it should have a head.

**Parameters:**

Here's a list of all the parameters I'd like to be able to customize, along with the default values I'm envisioning:

*   **Spool Dimensions:**
    *   `spool_od = 200;` (outer diameter of the spool)
    *   `spool_id = 87.8;` (inner diameter of the spool)
    *   `spool_ih = 55.5;` (inner height of the spool)
*   **Drawer:**
    *   `drawer_hgap = 1;` (horizontal gap between the drawer and the spool)
    *   `drawer_vgap = 0.5;` (vertical gap between the drawer and the lid/base)
    *   `drawer_fthick = 2;` (thickness of the drawer floor)
    *   `drawer_wthick = 1.6;` (thickness of the drawer walls)
    *   `n_drawer = 4;` (number of drawers)
*   **Drawer Cells:**
    *   `cell_wheight = 100;` (height of cell walls as a percentage, min 20, max 100)
    *  `cell_wthick = 1.1;` (thickness of cell walls)
    *   `n_cell = 2;` (number of cells per drawer)
*   **Lid:**
    *   `lid_vgap = 0.6;` (vertical gap between the lid and the drawer)
    *   `lid_tthick = 1.5;` (thickness of the lid top)
    *   `lip_thick = 0.7;` (thickness of the lid's lip)
    *   `lip_height = 5;` (height of the lid's lip)
*   **Mounting Pin:**
    *   `pin_gap = 0.35;` (gap around the pin hole)
    *   `pin_dia = 4;` (diameter of the pin)
    *   `pin_adj = 0.1;` (adjustable pin diameter for tight fits)
    *   `pin_hdia = 5.5;` (diameter of the pin's head)
    *   `pin_hthick = 1.5;` (thickness of the pin's head)
    *   `pin_length = 12;` (length of the pin)
	*   `pin_wthick = 2.3;` (thickness of the pin hole wall)
*   **Tab Handle:**
    *   `tab_width = 3.9;` (width of the tab handle)
    *   `tab_length = 9;` (length of the tab handle)
*   **Other:**
    *   `ep = 0.001;` (epsilon value for small differences)
    *   `$fn = 48;` (number of fragments for circles)
	*   `template_thick = 1.2;` (thickness of the template parts)
*   **Mode:**
    *   `mode = 6;` (determines which parts are rendered.
        * `1`: drawer only.
        * `2`: lid only.
        * `3`: drawer and lid.
        * `4`: mirrored drawer and lid.
        * `5`: mounting pin.
        * `6`: tester.
        * `7`: template

**Specific Design Features:**

*   The drawers should have a floor and walls and tabs for handles.
*   The lid should fit snugly onto the drawers, with the lip providing additional overlap for stability.
*   The mounting pin should have a head to keep it in place, and the pin itself should have a variable diameter, so the user can account for tolerances.

**What I'm Looking For:**

I'd like to receive one or more OpenSCAD files that can generate the described spool drawer system using these parameters. I would like to be able to change the mode variable and render different parts of the spool drawer system, including the individual drawer, the lid, or a combination of both as well as a test piece, mounting pin, or template.

I'm not looking for any external libraries - all the geometry should be built directly with OpenSCAD primitives. Let me know if you have any questions, and thank you!

[Assistant]
SpoolDrawer.scad:
```
//// File: Spooldrawer.scad
//// Version: OpenSCAD 2015.03-2
//// Author: Kijja Chaovanakij
//// Date: 2020.7.02

//==== X3D 1 kg. ====//
//spool_od=200;
//spool_id=87.8;
//spool_ih=55.5;

//==== eSUN 1 kg. ====//
//spool_od=200;
//spool_id=91;
//spool_ih=59.5;

//var
$fn=48;
ep=0.001;

/*[mode]*/
mode=6; //[1:drawer, 2:lid, 3:drawer+lid, 4:mirror(drawer+lid), 5:mounting pin, 6:tester, 7:template]

/*[custom spool]*/
//the outer diameter of the spool
spool_od=200;
//the inner diameter of the spool
spool_id=87.8;
//the inner height of the spool
spool_ih=55.5;

/*[drawer]*/
//horizontal gap of the drawer
drawer_hgap=1;
//vertical gap of the drawer
drawer_vgap=0.5;
//drawer floor thickness
drawer_fthick=2;
//drawer wall thickness[1.3]
drawer_wthick=1.6;
//number of the drawer
n_drawer=4; //[2:1:8]

/*[drawer cell]*/
//height of the cell's wall in % (min:20,max:100)
cell_wheight=100; //[20:10:100]
//thickness of cell's wall
cell_wthick=1.1;
//number of cell
n_cell=2; //[1:1:4]

/*[lid]*/
//vertical gap of the lid
lid_vgap=0.6;
//lid top thickness
lid_tthick=1.5;
//thickness of the lip
lip_thick=0.7;
//height of the lip
lip_height=5;

/*[pin]*/
//gap of the pin or the screw
pin_gap=0.35;
//diameter of the pin or the screw
pin_dia=4;
//Adjustable pin diameter for tight-fitting holes
pin_adj=0.1; //[-0.3:0.05:0.2]
//diameter of the pin's head
pin_hdia=5.5;
//thickness of the pin's head
pin_hthick=1.5;
//length of the pin
pin_length=12;
//wall thickness of pin's hole
pin_wthick=2.3;

/*[tab hanndle]*/
//width of the tab
tab_width=3.9;
//length of the tab
tab_length=9;

/*[Hidden]*/
//spool
spool_or=spool_od/2;
spool_ir=spool_id/2;
//pin
pin_hole=pin_dia+pin_gap*2;
pin_sleeve=pin_hole+pin_wthick*2;
//template
template_thick=1.2;

//================ main ================//
//arc();
module arc(ext_rad=spool_or, int_rad=spool_ir, angle=360/n_drawer) {
  points_ext=[
    for(i=[0:1:angle])
      [ext_rad*cos(i), ext_rad*sin(i)]];
  points_int=[
    for(i=[0:1:angle])
      [int_rad*cos(i), int_rad*sin(i)]];
  difference(){
    polygon(concat([[0, 0]], points_ext));
    polygon(concat([[0, 0]], points_int));
  }//d
}//m
//

//profile();
module profile(){
  minkowski(){
    circle(r=pin_sleeve/2);
    offset(-drawer_hgap-pin_sleeve/2)
      arc();
  }//m
}//m
//

//wall_profile(drawer_wthick);
module wall_profile(thick){
  difference(){
    profile();
    offset(-thick)
      profile();
  }//d
}//m
//

//pin();
module pin(dia=pin_hole){
  translate([spool_or-pin_sleeve/2-drawer_hgap*1.2, pin_sleeve/2+drawer_hgap, 0])
    circle(d=dia);
}//m
//

//tab_handle();
module tab_handle(){
  angle=360/n_drawer-5;
  dtab=spool_or-drawer_hgap-drawer_wthick+tab_width/2;
  hull(){
    translate([dtab*cos(angle), dtab*sin(angle)])
      circle(d=tab_width);
    translate([(dtab+tab_length)*cos(angle), (dtab+tab_length)*sin(angle)])
      circle(d=tab_width);
  }//h
}//m
//

//cell_divider();
module cell_divider(){
  angle=360/n_drawer/n_cell;
  rad=spool_or/2+spool_ir/2;
  wid=spool_or-spool_ir-drawer_hgap*2-drawer_wthick;
  if (n_cell>1 && cell_wheight>=20)
  if (n_cell<=4 && cell_wheight<=100)
    for (i=[1:1:n_cell-1])
      rotate([0, 0, i*angle])
      translate([rad, 0])
      linear_extrude((spool_ih-drawer_vgap*2-lid_tthick-lip_height)*cell_wheight/100)
        square([wid, cell_wthick], true);
}//m
//

//lid_top();
module lid_top(){
  minkowski(){
    circle(r=pin_sleeve/2);
    difference(){
      offset(-drawer_hgap-pin_sleeve/2)
        arc();
      translate([spool_or-pin_sleeve/2-drawer_hgap*1.2, pin_sleeve/2+drawer_hgap, 0])
        minkowski(){
          circle(r=pin_sleeve);
          square([(pin_sleeve+lid_vgap-pin_sleeve)*2, (pin_sleeve+lid_vgap-pin_sleeve)*2], true);
        }//m
    }//d
  }//m
}//m
//

//drawer();
module drawer(){
  difference(){
    union(){
      //floor
      linear_extrude(drawer_fthick)
        profile();
      //wall
      linear_extrude(spool_ih-drawer_vgap-lid_tthick)
        wall_profile(drawer_wthick);
      //post
      linear_extrude(spool_ih)
        pin(dia=pin_sleeve);
      //handle
      linear_extrude(spool_ih/2)
        tab_handle();
      cell_divider();
    }//u
    //pin hole
    translate([0, 0, -ep])
      linear_extrude(spool_ih+ep*2)
        pin(dia=pin_hole);
  }//d
}//m
//

//lid();
module lid(){
  //lid top
  linear_extrude(lid_tthick)
    lid_top();
  //lid lip
  linear_extrude(lid_tthick+lip_height)
    difference(){
      offset(-drawer_wthick-lid_vgap)
        lid_top();
      offset(-drawer_wthick-lid_vgap-lip_thick)
        lid_top();
    }//d
}//m
//

//tester();
module tester(){
  difference(){
    linear_extrude(template_thick)
    union(){
      wall_profile(drawer_wthick*2);
      pin(dia=pin_sleeve);
      tab_handle();
    }//u
    translate([0, 0, -ep])
      linear_extrude(template_thick+ep*2)
        pin(dia=pin_hole);
  }//d
}//m
//

//template();
module template(){
  linear_extrude(template_thick)
  difference(){
    union(){
      for (i=[0:1:n_drawer-1])
        rotate([0, 0, i*360/n_drawer])
        hull(){
          translate([spool_or-pin_sleeve/2-drawer_hgap*1.2,  pin_sleeve/2+drawer_hgap, 0])
            circle(d=pin_hole+6);
          circle(d=pin_hole+2);
        }//h
      arc(spool_or, spool_or-7, 360);
      arc(spool_ir+7, spool_ir, 360);
    }//u
    union(){
      for (i=[0:1:n_drawer-1])
        rotate([0, 0, i*360/n_drawer])
        translate([spool_or-pin_sleeve/2-drawer_hgap*1.2,  pin_sleeve/2+drawer_hgap, 0])
          circle(d=pin_hole);
          circle(d=spool_id);
    }//u
  }//d
}//m
//

//mounting_pin();
module mounting_pin(){
  linear_extrude(pin_hthick)
    circle(d=pin_hdia);
  linear_extrude(pin_length)
    circle(d=pin_dia+pin_adj);
  hull(){
    translate([0, 0, pin_length])
    linear_extrude(ep)
      circle(d=pin_dia+pin_adj);
    translate([0, 0, pin_length+0.5])
    linear_extrude(ep)
      circle(d=pin_dia+pin_adj-0.6);
  }//h
}//m
//

//======== mode ========//
if (mode==1 || mode==3)
  color("Aqua")
    drawer();

if (mode==2 || mode==3)
  color("Magenta")
    translate([0, 0, spool_ih-drawer_vgap+15])
    rotate([180, 0, 0])
    mirror([0, 1, 0])
      lid();

if (mode==4){
  color("Aqua")
    mirror([0, 1, 0])
      drawer();
  color("Magenta")
    translate([0, 0, spool_ih-drawer_vgap+15])
      rotate([180, 0, 0])
        lid();
}//i
//

if (mode==5)
  color("DarkGray")
    mounting_pin();

if (mode==6)
  color("Orange")
    tester();

if (mode==7)
  color("LightGreen")
    template();

```


[User]
I'd like to create a customizable steering wheel center plate for a Berg Buddy go-kart, similar to the one described in the provided code. I need a parametric design where I can easily change the name displayed on the button and the text size.

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

*   **Button Shape:** The button should have a distinct, somewhat irregular shape, similar to what's produced by the `buttonShape` module, with a flat top section that can hold the name and a rounded profile with some cutouts.
*   **Text:**  The name should be displayed on the flat surface of the button, centered, using a bold font (like Tahoma Bold as in the example), and the size should be adjustable.
*   **Pin Shapes:** There should be three pin shapes, evenly spaced around the button, that connect the button to the steering wheel. Each pin should have a base that's part of a rotated extrusion and a rectangular connection for extra stability.
*   **Parameters:** I need parameters to control:
    *   `name`: The text displayed on the button.
    *   `textSize`: The font size of the text.
    *   `buttonDiameter`: The diameter of the main button body.
    *   `pinBaseRadius`: The distance of the pin base from the center of the button.
    *   `pinHeight`: The height of the pins.

The design should be suitable for 3D printing, and I'm planning on sanding and painting the final product, so a smooth surface is not critical. The overall size should match the Berg Buddy steering wheel center, but I will give precise measurements later. I don't expect you to know those. Could you generate an OpenSCAD file or files for this? I don't need any external libraries, though you're welcome to use them if you think it would make the code more readable.

[Assistant]
210116_BergLenkrad.scad:
```
//---------------------------
// CUSTOMIZER
//---------------------------
// name on the button
name = "SIMON";

// font size of the name
textSize = 11; //[2:0.5:20]

//-----------------------------------------------------------------------------
/* [Hidden] */
$fn = 100;

//-----------------------------------------------------------------------------
module buttonShape(radius = 29.4) {
    pts = [[0, 2.5], [0, 4],
           [radius-2, 4],  [radius, 2],
           [radius, 0],    [radius-1.3, 0], [radius-1.4, 1],
           [radius-5, 1],  [radius-6, 2],
           [radius-10, 2], [radius-11, 2.5]];
    union() {
        polygon(pts);
        translate([radius-2, 2, 0]) difference () {
            circle(r=2);
            translate([-3, -6, 0]) square(6);
        }
    }
}

//-----------------------------------------------------------------------------
module pinShape() {
    pts = [[0, 0], [2.7, 0], [3, 1.5],
           [3, 8.6], [0, 8.6]];
    polygon(pts);
}

//-----------------------------------------------------------------------------
module button(name, textsize) {
    dia = 58.8;
    translate([0, 0, 6]) difference() {
        rotate_extrude(convexity = 10) buttonShape(dia/2);

        rotate([0, 0, 90]) translate([0, 0, 3.8]) linear_extrude(height = 2, center = true, convexity = 10) {
            text(name, font = "Thaoma:style=Bold", halign="center", valign="center", size=textsize);
        }
        translate([dia/2, 0, 0.5]) cube([10, 7, 3], center=true);
    }

    for(a = [0:120:360]) {
        x = cos(a) * 23.4;
        y = sin(a) * 23.4;
        translate([x, y, 0]) {
            union() {
                rotate_extrude(convexity = 10) pinShape();
                translate([0, 0, 7.7]) rotate([0, 0, 90+a]) cube([7, 6.2, 1.4], center=true);
            }
        }

    }
}

//-----------------------------------------------------------------------------
button(name, textSize);

```


[User]
Hey, I'm looking to get a parametric pet bowl riser designed in OpenSCAD. My cat just got neutered and is having a hard time reaching his food bowl with the cone on. I've got some measurements of the current bowl, but I want the design to be flexible enough to use with other bowls too.

So, here's what I need:

*   **Bowl Parameters:** The design should allow for the user to specify the top and bottom diameters of the bowl, as well as its height. There should also be a parameter for the bowl rim thickness, so I can adjust for that when modeling the hole.

*   **Lift and Tilt:** I need a parameter to specify how high the bowl is lifted off the ground and how much it's tilted. I would also like an offset for the bowl from the center.

*  **Base Parameters:** I need a parameter for the diameter of the stand. I'm thinking a cylinder for the base.

*   **Customization:** I'd really like the whole thing to be parametric.

*   **Functionality:** The design should have a hollowed out area that fits the bowl, so the bowl sits securely on top, and should not intersect with the base. The base should be a solid cylinder.

I think a simple hull around a minkowski operation to create the bowl hole, and a difference to remove the interior part of the bowl would be sufficient.

I don't need anything super fancy, just something functional and customizable for different bowl sizes and heights. Let me know if you need any other info, or if this is something you can do. Also, what library would be useful for this? I'm not sure if I should be using any specific ones or just stick to vanilla OpenSCAD.

[Assistant]
pet_bowl_riser.scad:
```
bowl_diameter_top = 105;
bowl_diameter_bottom = 90;
bowl_height = 35;
bowl_rim = 5;
bowl_lift_height = 50;
bowl_tilt = 25;
bowl_offset = 10;
stand_diameter = 130;
$fn=80;

module bowl(offset, translation) {
    translate([bowl_offset, 0, bowl_lift_height]) {
        rotate([0, bowl_tilt, 0]) {
            translate([0,0,translation]) cylinder(d2=bowl_diameter_top+offset, d1=bowl_diameter_bottom, h=bowl_height);
        }
    }
}

difference() {
    hull() {
        minkowski() {
            bowl(bowl_rim, 0);
            sphere(d=10);
        }
        cylinder(d=stand_diameter, h=5);
    }
    bowl(0, 10.1);
}
```


[User]
Hey, I'm looking to design a customizable initiative tracker for my DM screen, and I'd like to use OpenSCAD to make it parametric. I've got a good idea of what I want, and I've even got a base design in mind, though it's a little bit clunky. I'd love for your assistance in creating the OpenSCAD files.

Essentially, the tracker will consist of a base, a cover (optional), and plates that slide into the base. I've already designed the base and cover, and would only like your assistance with the custom plates. Each plate needs to have some basic parameters, like width, length, and height (defaulting to 19x40x1). It also needs to have a 'hold' which is used to slide it into the main base, which should default to length 23, width 1.7, and height 1.7. Text is going to be printed onto the plates.

Here are the parameters I'm thinking about for the customizable plates:

**Card Settings:**
*   `card_length`: The length of the plate.

**Text Settings:**
*   `font_family`: The font family (e.g., "Sans", "Serif").
*   `font_style`: The font style (e.g., "Bold", "Italic", "Bold Italic").
*   `text_align`: Text alignment. "H" for Horizontal, "V" for Vertical.
*   `text_lines`: Number of text lines (1-4).
*   `line_1_text`, `line_2_text`, `line_3_text`, `line_4_text`: The content for each line.
*   `line_1_text_size`: The size for each text line (one size for each line, each between 1-20).
*   `text_offset`: Custom offset for the text (one offset value per line, each between -20 and 20).

**Hidden parameters** (these are defaults to use)
*   `card_width`: 19.
*   `card_height`: 1.
*   `hold_length`: 23.
*   `hold_width`: 1.7.
*   `hold_height`: 1.7
* `text_height`: 0.4

The text should be generated using the `text()` function, and the overall plate should be a combination of `cube()` for the base, and text generated with `linear_extrude()`. The text needs to be placed on the card, taking `text_lines` into account. This means there needs to be a `for()` loop, and some logic for placement. I'm thinking the x and y coordinates should be generated using `text_pos`, based on how many text lines we're generating, and that it should be moved and rotated for both vertical and horizontal orientations.

It'd be great if you could generate a single `tracker_plate_custom.scad` file with these parameters all incorporated.

I don't think I need any special libraries for this particular design. Thanks in advance!

[Assistant]
tracker_plate_custom.scad:
```
/* [Card Settings] */
// Length
card_length = 40;

/* [Text Settings] */
// Font Family
font_family="Sans"; // [Sans, Serif]
// Font Style
font_style="Bold"; // [Bold, Italic, Bold Italic]
// Text Alignment
text_align = "H"; // [V:Vertical, H:Horizontal]
// Lines of Text
text_lines = 1; // [1:4]
// Line 1 Content
line_1_text = "foo";
// Line 2 Content
line_2_text = "foo";
// Line 3 Content
line_3_text = "foo";
// Line 4 Content
line_4_text = "foo";

// Font Sizes
line_1_text_size = [7,7,7,7];  // [1:20]
// Custom Text Offset
text_offset = [0, 0, 0, 0];  // [-20:20]

/* [Hidden] */
card_width = 19;
card_height = 1;
hold_length = 23;
hold_width = 1.7;
hold_height = 1.7;
text_height = 0.4;

font = str(font_family, ":style=", font_style);

cube([card_width, card_length, card_height]);
translate([-2, -1.7, 0]) cube([hold_length, hold_width, hold_height]);

cube([card_width, card_length, card_height]);
cube([card_width, 2, hold_height]);

for (i = [1:1:text_lines]) {
  echo(i);
  text_content = (i == 1) ? line_1_text : (i == 2) ? line_2_text : (i == 3) ? line_3_text : line_4_text;

is_vertical = text_align == "H";

rot_z = is_vertical ? 180 : 90;
text_pos = ((is_vertical ? card_length : card_width) / (text_lines + 1)) * i;
trans_x = !is_vertical ? text_pos + text_offset[i-1] : card_width / 2;
trans_y = !is_vertical ? card_length / 2 : text_pos + text_offset[i-1];
color("red") translate([trans_x, trans_y, card_height])
rotate([0, 0, rot_z])
  linear_extrude(height = text_height) {
       text(text = text_content, size = line_1_text_size[i - 1], valign = "center", halign = "center", font = font);
  }
}
```


[User]
I need an OpenSCAD design for a bed level test print. It should consist of a series of nested rectangles, and then a set of intersecting lines to help visualize the area.

Here are the key parameters I want to control:

*   **x_max:** The width of the outermost rectangle.
*   **y_max:** The height of the outermost rectangle.
*   **smallest_scale:** A value between 0 and 1 that determines the size of the innermost rectangle relative to the outermost one. 0 means no inner rectangle and 1 means only the outer rectangle.
*   **additional_rects:** The number of rectangles to draw between the innermost and outermost ones.
*   **line_width:** The width of a single line used to create a rectangle. This should match my nozzle size.
*   **line_count:** The number of lines making up each side of a rectangle. The total width of the rectangle's "line" will be `line_width * line_count`.
*   **line_height:** The height of the print (i.e. layer height).
*   **star_count:** the number of mirrored pairs of intersecting lines (2 = 4 lines total).
*   **star_start_rotation:** The rotation in degrees of the intersecting lines. Only useful if star_count is 1.

The rectangles should all be centered. The intersecting lines should be a "star" pattern. They should also be centered, and the inner bounding area for the rectangles should be cut out. The innermost rectangle area should also be cutout from the rectangle set. The "star" lines should extend beyond the rectangles. The rectangles and "star" should all be the same height (line_height).

I'm not sure if this needs to be in one file or multiple, but I'd like to have the design be as easy to customize as possible so I can dial in my printer. Let me know if you have any questions!

[Assistant]
bed_level_test_rectangles.scad:
```

x_max = 100;
y_max = 50;
smallest_scale = 0.1;
fixed_scale =(smallest_scale > 1)?1:(smallest_scale<=0)?0:smallest_scale;

x_min = x_max *fixed_scale;
y_min = y_max * fixed_scale;

echo(fixed_scale);

additional_rects = 2;
line_width = 0.4;
line_count = 4;
line_height = 0.2;

total_width = line_count * line_width;
// As we start from 0 we only need to add the outer rect
total_rects = additional_rects +1;

for (i=[0:total_rects]) {
    difference(){
        x_area = x_max - x_min;
        rect_x = x_min + x_area/total_rects * i;

         y_area = y_max - y_min;
        rect_y = y_min + y_area/total_rects * i;

        cube([rect_x,rect_y,line_height], center = true);
        translate([0,0])
        cube([rect_x-total_width*2,rect_y-total_width*2,3*line_height], center = true);
    }
}

size = x_max * y_max;

difference(){
intersection(){

union(){
    for (i=[0:2]) {

        rotate([0,0,180/2 * i])
        cube([size*2, total_width, line_height], center = true);
    }
    for (j=[-1:2:1]) {
        start_angle = atan2(y_max, x_max);
        echo(start_angle);
        rotate([0,0,start_angle * j])
        cube([size*2, total_width, line_height], center = true);
    }
}

    cube([x_max,y_max,line_height], center = true);
}

cube([x_min,y_min,line_height*3], center = true);
}

```


[User]
Hey, I need to design a dust collection adapter that will connect to a standard shop vac hose on one end and insert into a 27mm opening on the other. The shop vac hose connection needs to be customizable for different hose sizes, and I need a tapered section to help guide the hose on.

I have an STL of a similar design, but it's not parametric. I'd like an OpenSCAD file that can do this. The idea is that the shop vac hose will connect to a cylindrical section and there will be a tapered section that leads to the connection. The end that goes into the opening just needs to be a straight cylinder that's 27mm in diameter.

So, for the parameters, I need:

*   `nozzle_inner_diam`: The inner diameter of the shop vac hose or connector you want to attach to the adapter.
*   `taper_diam`: The amount the plug end tapers down.

The shop vac hose connection section should have a length of 50mm. The tapered section should have a length of 5mm. The part that inserts into the 27mm opening should be 80mm long.
I'd also like the adapter to be centered at the origin.

Could you generate the OpenSCAD code for this? I'm not sure if it's necessary, but I'd also like the OpenSCAD code to make use of the `MCAD` library.

Thanks!

[Assistant]
Inset_dust_tube.scad:
```

nozzle_inner_diam=37.5; // Inner diameter of the hose/connecter you want to connect.
taper_diam=2;           // How far the plug end should taper in.

import("./Inset_dust_tube.stl");
translate([-15.3,47,0]){
    difference(){
        union(){
            rotate([90,0,0]){
                cylinder(h=50,d=nozzle_inner_diam, $fn=200);
            }
            translate([0,0,0]){
                rotate([270,0,0]){
                    cylinder(h=5,d1=nozzle_inner_diam,d2=nozzle_inner_diam-taper_diam,$fn=200);
                }
            }
        }
        translate([0,10,0]){
            rotate([90,0,0]){
                cylinder(h=80,d=27, $fn=200);
            }
        }
    }
}
```


[User]
I'd like to design a parametric holder for round cells, like batteries. I'm envisioning a grid-like structure, where you can specify the number of rows and columns of cells. Each cell will sit in its own pocket.

Here's what I need to be able to control parametrically:

*   **Number of Rows:** The number of rows of cells in the holder.
*   **Number of Columns:** The number of columns of cells in the holder.
*   **Pocket Diameter:** The diameter of the circular pocket holding the cell.
*   **Pocket Height:** The depth of the pocket in which the cell sits.
*   **Stacking Pocket Height:** The height of a shallow lip at the top of each pocket that's designed to interlock with another holder of the same dimensions when stacked.
*   **Floor Thickness:** The thickness of the base of the holder.
*   **Wall Thickness:** The thickness of the walls between each cell pocket.

I'd like the design to be created in OpenSCAD, and for it to be able to generate a model where each cell pocket is surrounded by a small lip to facilitate stacking the holders. I'm not picky about the approach, but if the stacking lip is designed as an extension of the cylinder, then that would be ideal.

I'm not experienced with OpenSCAD, so I would like for the code to be well-commented. I would also prefer to keep the design as a single .scad file if possible.

I would like to use a single file. I don't know if you'll need any external libraries for something this simple, but if you do please specify them.

[Assistant]
Rundzellen_Halter_stapelbar.scad:
```
//Pockets in y diretion
rows = 3;
//Pockets in x direction
cols = 3;
//Pocket diameter
dia = 19;
//Pocket height
pocket_height = 10;
//Stack pocket height
stack_pocket_height = 5;
//Floor thickness
f_thickness = 1;
//Wall thickness
w_thickness = 1;

for(c = [1:cols]){
    for(r = [1:rows]){
        translate([c*(w_thickness+dia)-dia/2,r*(w_thickness+dia)-dia/2,0]){
            difference(){
                cylinder(pocket_height+stack_pocket_height+f_thickness,w_thickness+dia/2,w_thickness+dia/2);
                translate([0,0,f_thickness+stack_pocket_height])cylinder(pocket_height+1,dia/2,dia/2);
                translate([0,0,-1])cylinder(stack_pocket_height+1,dia/2,dia/2);
            }
        }
    }
}
```


[User]
Hey, I'm looking to generate some Eurorack panels with OpenSCAD, and I need a bit of help getting started. I've seen some existing code, but I'd like something a little more structured and easier to customize for various projects.

Basically, I need to generate a rectangular panel with mounting holes based on the Eurorack standard. The main thing I want to control is the horizontal pitch (HP), which determines the width of the panel. I'd also like to be able to adjust the hole tolerance for my printer.

Here's a breakdown of the parameters that need to be customizable:

*   **`Horizontal_Pitch`**: The width of the panel in HP units (1 HP = 5.08mm). This should be an integer value.
*   **`Hole_Tolerance`**: A value added to the hole diameter for easier fitting.
*   **`Panel_Height`**:  The height of the panel (standard is 128.5mm, but it might be nice to adjust).
*   **`Panel_Thickness`**: The thickness of the panel (standard is 2mm, but I'd like to be able to adjust this).
*   **`Hole_Diameter`**: The diameter of the mounting holes (standard is 3.2mm).
*   **`Hole_Horizontal_Offset`**: The distance from the left/right edge of the panel to the center of the nearest mounting hole (standard is 7.5mm).
*    **`Hole_Vertical_Offset`**: The vertical distance from the top/bottom edge of the panel to the center of the nearest mounting hole (standard is 3.0mm).
*   **`Panel_Width_Tolerance`**:  A tolerance applied to the panel width - it's a small amount taken away from the ideal width to make it fit easier.

I'm also expecting the program to use the following Eurorack standard parameters, which should not need to be changed normally. I'd still like to have them as parameters just in case I need to create panels for non-standard cases:

*   **`HP_Unit`**: Base unit distance for horizontal pitch (Eurorack standard = 5.08 mm)

The program should calculate:
*   **`Panel_Width`**: Calculated based on `Horizontal_Pitch`, `HP_Unit`, and `Panel_Width_Tolarance`
*   **`ideal_hole_distance`**: Calculated width available for hole placement
*   **`Hole_Horizontal_Pitch`**: The integer number of HP units between the holes.
*   **`Hole_Distance`**: The calculated distance between mounting holes based on `Hole_Horizontal_Pitch` and `HP_Unit`
*    **`use_4_holes`**: This should be a boolean that is true if `Horizontal_Pitch` is greater than 10, false otherwise
*   **`center_holes`**: This should be a boolean that is true if `Horizontal_Pitch` is less than 2, false otherwise

The program should also use `final_hole_diam = Hole_Diameter + Hole_Tolarance` to create the holes.

The panel should have mounting holes on the top and bottom. For panels with a `Horizontal_Pitch` of 10 or less, there should only be two mounting holes each on the top and bottom rows. For panels with more than 10HP, there should be four mounting holes on the top and bottom. For panels less than 2HP, the holes should be centered. I'd like to see the variables printed into the console so I can debug.

It would be great if the code was modular and easy to follow. I'm open to using any libraries if they make it easier, but I'm not sure which ones would be useful. I don't have any specific preferences.

Can you help me generate the OpenSCAD file(s)?

[Assistant]
eurorack_panel_gen.scad:
```
// Eurorack Panel Cover Generator
// (C) 2020 by Sonic Athame (nimaid). License: CC By SA
// https://www.thingiverse.com/nimaid
// Eurorack panel specifications taken from Doepfer:
// http://www.doepfer.de/a100_man/a100m_e.htm

/* [Main Parameters] */

// Horizontal pitch of the panel (HP)
Horizontal_Pitch = 2; // [1:0.5:60]

// Amount of tolerance to add to the holes
Hole_Tolarance = 0.2; // [0:0.01:1]

/* [Eurorack Standard Parameters (Advanced)] */

// Vertical height of the panel (Eurorack standard = 128.5 mm)
Panel_Height =  128.5; // [120.0:0.1:137.0]

// Thickness of panel (Eurorack standard = 2 mm)
Panel_Thickness = 2; // [0.3:0.1:5]

// Hole diameter (Eurorack standard = 3.2 mm)
Hole_Diameter = 3.2; // [2:0.1:5]

// Horizontal distance from left edge to hole (Eurorack standard = 7.5 mm)
Hole_Horizontal_Offset = 7.5; // [2.0:0.1:15.0]

// Vertical distance from top edge to hole (Eurorack standard = 3.0 mm)
Hole_Vertical_Offset = 3.0; // [1.0:0.1:5]

// How much tolerance to use for the panel width (Eurorack standard = 0.3 mm)
Panel_Width_Tolarance = 0.3; // [0:0.1:1]

// Base unit distance for horizontal pitch (Eurorack standard = 5.08 mm)
HP_Unit = 5.08;

// Calculated parameters
Panel_Width = (Horizontal_Pitch * HP_Unit) - Panel_Width_Tolarance;
echo(Panel_Width=Panel_Width);

ideal_hole_distance = Panel_Width - Hole_Diameter - (2 * Hole_Horizontal_Offset);

Hole_Horizontal_Pitch = ceil(ideal_hole_distance / HP_Unit);
Hole_Distance = Hole_Horizontal_Pitch * HP_Unit;

use_4_holes = Horizontal_Pitch <= 10 ? false : true;
center_holes = Horizontal_Pitch < 2 ? true : false;

if(use_4_holes) {
    echo("Panel is > 10HP, using 4 holes");
    echo(Hole_Horizontal_Pitch=Hole_Horizontal_Pitch);
    echo(Hole_Distance=Hole_Distance);
} else {
    echo("Panel is <= 10HP, using 2 holes");
}

final_hole_diam = Hole_Diameter + Hole_Tolarance;

// Define preview default view
$vpr = [45, 0, 15];
$vpt = [0, -15, 0];
$vpd = 400;

// Define 3D quality
$fa = 3;
$fs = 0.5;

// The 3D model
translate([0, 0, Panel_Thickness/2]) {
    difference() {
        cube([Panel_Width, Panel_Height, Panel_Thickness], center=true);

        start_x_offset = (Panel_Width / 2) - Hole_Horizontal_Offset;
        y_offset = (Panel_Height / 2) - Hole_Vertical_Offset;

        for(y_off = [-y_offset, y_offset]) {
            if(center_holes) {
                translate([0,y_off,0]) {
                    cylinder(h = Panel_Thickness+1, r = final_hole_diam/2, center=true);
                }
            } else {
                translate([-start_x_offset,y_off,0]) {
                    cylinder(h = Panel_Thickness+1, r = final_hole_diam/2, center=true);
                }
            }
            if(use_4_holes) {
                translate([-start_x_offset + Hole_Distance,y_off,0]) {
                    cylinder(h = Panel_Thickness+1, r = final_hole_diam/2, center=true);
                }
            }
        }
    }
}
```


[User]
Hey, I'm looking to generate some Bahtinov masks for my camera lenses and telescopes, and I'd like to do it using OpenSCAD with a flexible, customizable design. I've seen a few different versions floating around, but none quite do everything I need in a single script.

Here's what I'm after:

1.  **Parametric Design:** The design should be entirely parametric, so I can easily adjust all the important dimensions through a customizer-like interface. This includes:
    *   **Lens/Scope Dimensions:**
        *   Focal length (mm): used to calculate slot size
        *   Outer diameter (mm)
        *   Clear aperture (mm): this is the opening where light passes through, this may be smaller than the outer diameter
        *   Center hole diameter (mm): If there's a secondary mirror obstruction, this should be adjustable.
    *   **Mounting/Retaining Features:**
        *   Retaining ring height (mm): for mounting on the outside of the lens/scope. This should be disableable when mounting internally.
        *   Retaining ring thickness (mm)
        *   Retaining ring tab offset (mm): Allows tabs to grip the outside or inside of the lens/scope
        *   Retaining ring number of tabs
        *   Retaining ring tab angle (degrees): Allows for less than full retaining rings.
        *   Retaining ring tab rotation (degrees): Allows for alignment.
        *   Handle height (mm): This will be used for mounting inside of optical tube or for ease of use (should be disableable if rings are used).
        *   Handle diameter (mm)
    *   **Mask Properties:**
        *   Mask thickness (mm)
        *   Bahtinov factor (used to determine gap spacing)
        *   Minimum printable gap/spoke width (mm) - this should switch to 3rd order calculation if the resulting 1st order gap would be too narrow.

2.  **Automatic 1st/3rd Order Switching:** The design should automatically switch to a 3rd-order Bahtinov pattern if the calculated 1st-order gaps are too small for printing based on the "minimum printable gap" parameter.

3.  **Retaining Rings/Handle:** The mask should support either retaining rings for mounting over the *outside* of the scope/lens or a handle for mounting *inside* the scope, but not both at the same time.

4. **Chamfers:** Should include chamfers to leading edges of retaining fingers for ease of use.

5.  **Structural Bars:** should include a horizontal and a vertical structural bar on the mask to make it more sturdy.

I'd like the design to be flexible enough to handle both camera lenses (with retaining rings) and telescope tubes (with handles). It should be able to handle a range of diameters, apertures, and focal lengths.

Could you please generate the OpenSCAD files needed to implement this? I'm not using any external libraries currently, so a basic, pure OpenSCAD implementation should work fine. I'm looking for one or more *.scad files, and don't worry about external .json or library files since that's not what I use right now.

[Assistant]
bahtinov_universal.scad:
```
/********************************************************/
/* OpenSCAD 2019.5 or later Required!                   */
/********************************************************/

/*                                                 -*- c++ -*-
 * A Bahtinov mask generator fo DSLR lenses.
 * Units in mm,
 *
 * Copyright 2013-2018, Brent Burton, Eric Lofgren, Robert Rath
 * License: CC-BY-SA
 *
 * Updated: Brent Burton 2014-06-23
 */

/*
 * Modified by Eric Lofgren 2016-07-18 to add external tabs.
 *
 * This is a modification of brentb's Bahtinov mask generator.
 * See his post for details. I added a way to have tabs extend on the outside
 * of my telescope. The number and size of tabs are parametrized in the scad
 * file. Be sure to specify the outer diameter of your telescope to make it
 * fit--the tabs are added beyond the specified diameter.
 * A pull-handle is not necessary and would make printing very difficult,
 * so I just commented it out.
 */

/*
 * Modified by Robert Rath 2018-03-12 to
 *
 * a) Tabulated dimensions to suit a variety of DSLR Lenses.
 * b) Added chamfers to tabs.
 * c) Corrected the maths to allow arbitrary tab thickness.
 *
 * This modification extends Eric Lofgren's modifications to Brent Burton's
 * original Bahtinov mask generator specifically targeting a collection of
 * DSLR lenses rather than a single lens.
 *
 * A table of lenses and their attributes is the source for mask creation.
 * Simply uncomment the lens you wish to create a mask for or add new
 * lenses to the table as per the layout in the SCAD file.
 *
 * Updated : Robert Rath 2018-03-12 : Added Sigma Sport 150-600mm
 *
 * Please let me know if you add new lenses and I will update this file.
 */

/*
 * Modified by Tim Tait  2020-08-25 to
 *
 *  - Add PL100-400 f6.3 lens to list
 *  - Add "Customizer" graphical support for Thingiverse and OpenSCAD 2019.5 or later
 *      Selection is done though pulldown lists and entry boxes
 *  - Add "custom" lens option to build mask dynamically from input
 *  - Auto adjust from 1st order to 3rd order Bahtinov is gaps are too small
 *  - rename to "bahtinov_universal.scad"
 *  - Fixed broken central clearance ring option
 *  - Add mask Z thickness option
 *  - Restore "handle" functionality option when retaining ring is disabled
 *  - Remove lens array from scad file, push pre-configured lenses to customizer "presets" stored in JSON file
 */
/*
 * Modified by Tim Tait  2021-11-18 to
 *
 *  - Change minimum vane count check to be 8 rather than 16
 *  - Change error message for vane count check to be more clear
 *  - Make lenFocalLength a slider
*/

//////////////////////////////////////////////////////////////////////////////
//
// Customerizer parameters
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/* [Lens Mask Parameters] */

// Focal Length (mm) (used to calculate size of mask vanes and gaps):
lensFocalLength = 400; // [1:4000]

// Lens Diameter (mm) (OD of front of lens, scope or hood if this will mount over it, ID of optical tube to mount inside):
lensDiameter = 88.5; // 0.5

// Clear Aperture (mm), must be at least 2mm less than lensDiameter (This determines the extent of the slots in the mask. For mounting on Lens: use filter thread size. Scope: use clear aperture. Hood/Dew Shield: inner diameter):
lensAperture = 78;

// The clearance diameter (mm) of the secondary mirror holder, use 0 if none.
centerHoleDiameter = 0; // 0.5

// Retaining Finger Height (mm), 0 if unused (for mounting mask on outside of lens/scope):
ringHeight = 15;

// Handle Height (mm), 0 if unused (for mounting inside of optical tube. Must specify 0 for ringHeight also):
handleHeight = 0;

// Bahtinov Factor, use 150 to 200
bahtinovFactor = 150; // [150:200]

//////////////////////////////////////////////////////////////////////////////
/* [Mask Options] */

// Retaining fingers offset (mm) from lens diameter. (0 makes fingers inner face flush against lensDiameter. Postive values to fit inside, negative values to fit outside):
ringTabInterference = -1.2;     // -1.2 to oversize and use 1mm felt pads on fingers

// The Z thickness (mm) of the mask:
maskThickness = 2; //

// The thickness (mm) of the retaining fingers:
ringThickness = 1.6; //

// The number of retaining fingers:
ringPieces = 3; //

// The Angle (deg) of the each retaining fingers (use 360/ringPieces for a solid ring):
ringPiece = 30; //

// The Rotation (deg) of the retaining tabs with respect to the mask:
ringRotation = 0;

// Handle Diameter (mm)
handleDiameter = 5; // [4:6]

// Minimum printable Gap/Spoke Width (mm) (Adjust to printer capability, must be at least nozzle dia. If 1st order mask gap/spoke would be less, 3rd order will be used)
minGapWidth = 0.8;  // [0.5:0.1:1.0]

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

// Old lens data
//
//// Lens Database Details      [diameter, aperture, slot, ringheight, handleheight]
//sigmaArt50mmDetails         = [ 85.3,     74.0,     0.50,  15.0,    0.0 ];
//canon100mmF28LDetails       = [ 76.7,     50.0,     0.50,  15.0,    0.0 ];
//canon70to20mmF28L2Details   = [ 87.9,     76.0,     1.00,  15.0,    0.0 ];
//sigmaSport150to600mmDetails = [117.9,     96.0,     1.50,  15.0,    0.0 ];
//PL100to400mmDetails         = [ 88.5,     75.0,     1.33,  15.0,    0.0 ];

//////////////////////////////////////////////////////////////////////////////
//
// End of Customizer parameters, can't put any after if statements or modules
//
//////////////////////////////////////////////////////////////////////////////

outerDiameter = ringHeight > 0 ? lensDiameter - ringTabInterference : lensDiameter;
aperture      = lensAperture;

// Calculate mask results
gap1x = (lensFocalLength / bahtinovFactor) / 2;          // 1st order slots
gap3x = (3 * lensFocalLength / bahtinovFactor) / 2;      // 3rd order slots in case 1x too small
echo(gap1x=gap1x);
echo(gap3x=gap3x);

if (gap1x < minGapWidth)
    echo("Using 3rd order bahtinov mask");
else
    echo("Using 1st order bahtinov mask");
// Can't set "variables" inside of if statement, so do this instead:
gap = gap1x < minGapWidth ? gap3x : gap1x;  // use 3rd order as first would be too thin

echo();
echo("**********************");
echo("*** Key Parameters ***");
echo("**********************");
echo(outerDiameter=outerDiameter);
echo(aperture=aperture);
echo(centerHoleDiameter=centerHoleDiameter);
echo(gap=gap);
echo(ringHeight=ringHeight);
echo(ringThickness=ringThickness);
echo(ringTabInterference=ringTabInterference);
echo(handleHeight=handleHeight);
echo("*********************");
echo();

// Sanity checks on mask
assert((handleHeight == 0) || (ringHeight == 0), "*** Cannot have ring AND handle *** Set handleHeight to 0 for an outside mounted mask, or set ringHeight to 0 for an inside optical tube mounted mask.");

assert ((outerDiameter + (ringHeight > 0 ? ringThickness : 0) - lensAperture) >= 3, "*** RIM OF MASK IS TOO THIN ***, Try reducing the lensAperture");
assert ((lensAperture - centerHoleDiameter) >= (8 * 2 * gap), "*** LESS THAN 8 VANES ***, Check the centerHoleDiameter and lensAperture. Increase bahtinovFactor if needed.");
assert (gap >= minGapWidth, "*** UNABLE TO MAKE SLOT WIDTH THICK ENOUGH ***, Mask may not be printable, you can try to  decrease the minGapWidth setting if you think it wise.");

/* create a series of bars covering roughly one half of the
 * scope aperture. Located in the +X side.
 */
module bars(gap, width, num=5) {
    num = round(num);
    for (i=[-num:num]) {
        translate([width/2,i*2*gap]) square([width,gap], center=true);
    }
}

module bahtinovBars(gap,width) {
    numBars = aperture/2 / gap / 2 -1;
    // +X +Y bars
    intersection() {
        rotate([0,0,30]) bars(gap, width, numBars);
        square([outerDiameter/2, outerDiameter/2], center=false);
    }
    // +X -Y bars
    intersection() {
        rotate([0,0,-30]) bars(gap, width, numBars);
        translate([0,-outerDiameter/2]) square([outerDiameter/2, outerDiameter/2], center=false);
    }
    // -X bars
    rotate([0,0,180]) bars(gap, width, numBars);
}

$fn=200;
module bahtinov2D() {
    width = aperture/2;
    difference() {                          // overall plate minus center hole
        union() {
            difference() {                  // trims the mask to aperture size
                circle(r=aperture/2+1);
                bahtinovBars(gap,width);
            }
            difference() {                  // makes the outer margin
                circle(r=outerDiameter/2+(ringHeight == 0 ? 0 : ringThickness));  // Re-written to make the ringThickness independent.
                circle(r=aperture/2);
            }
            // Add horizontal and vertical structural bars:
            square([gap,2*(aperture/2+1)], center=true);
            translate([aperture/4,0]) square([aperture/2+1,gap], center=true);
            // Add center hole margin ring if needed:
            if (centerHoleDiameter > 0 && centerHoleDiameter < outerDiameter) {
                circle(r=(centerHoleDiameter/2)+minGapWidth);
            }
        }
        // subtract center hole if needed
        if (centerHoleDiameter > 0 && centerHoleDiameter < outerDiameter) {
            circle(r=centerHoleDiameter/2);
        }
    }
}

module pie_slice(r = 10, deg = 30) {
    degn = (deg % 360 > 0) ? deg % 360 : deg % 360;
    //echo(degn=degn);
    difference() {
        circle(r);
        if (degn > 180) intersection_for(a = [0, 180 - degn]) rotate(a) translate([-r, 0, 0]) #square(r * 2);
        else union() for(a = [0, 180 - degn]) rotate(a) translate([-r, 0, 0]) square(r * 2);
    }
}

module retainingring(){
    difference()
    {
        cylinder(h=ringHeight+maskThickness,d=outerDiameter+2*ringThickness);
        translate([0,0,-1])cylinder(h=ringHeight+maskThickness+2,d=outerDiameter);
        for(angle=[0:360/ringPieces:359])
        {
            rotate([0,0,angle])
            linear_extrude(height=ringHeight+maskThickness+2)
            pie_slice(r=outerDiameter/2+2*ringThickness,deg=360/ringPieces-ringPiece);
        }

        // chamfer the leading edges of the clips
        translate([0,0,ringHeight+maskThickness-2+(ringThickness/2)+(outerDiameter-sqrt(10*sqrt(2)))/2])
        {
            sphere(sqrt(2*((ringThickness/2)+(outerDiameter-sqrt(10*sqrt(2)))/2)*((ringThickness/2)+(outerDiameter-sqrt(10*sqrt(2)))/2)));
        }
    }
}

union() {
    linear_extrude(height=maskThickness) bahtinov2D();
    // add a little handle
    if (handleHeight > 0)
        translate([outerDiameter/2-handleDiameter/2,0,0])
            cylinder(r=handleDiameter/2, h=handleHeight+maskThickness);

    //add a retaining ring
    if (ringHeight > 0)
        rotate([0,0,ringRotation]) retainingring();
}

```


[User]
Hey, I'm looking to design some dividers for my Stanley bin system and Sortmaster organizer, and I'd like to use OpenSCAD. I've seen some designs online but I want something more customizable.

Essentially, I need two main types of dividers: one for the bin system and one for the sortmaster.

For the bin system divider, I need:
*   A customizable total `HEIGHT`.
*   A customizable `THICKNESS` to fit the holder.
*   A customizable total `WIDTH`.
*   A customizable `BORDER_WIDTH` that specifies how far into the holder the divider will go on each side.
*   A customizable `WALL_THICKNESS` that specifies the material thickness. It should always be less than or equal to the `THICKNESS`.
*   An option to add `REINFORCEMENT` for larger plates, which would just connect the corners.
*   A `FILLET` radius for rounding the outer edges, to make printing cleaner.

The Sortmaster divider is a bit different; it's not a simple rectangle and has a handle.
*   It needs a customizable `TOP_WIDTH` as it is slightly conical.
*   A `BOTTOM_WIDTH`, since it's conical.
*   The same `HEIGHT` and `THICKNESS` as the other design.
*   A `HANDLE_WIDTH` that's smaller than the plate's width.
*   A small `HANDLE_HEIGHT` at the top corner.
*   A `HANDLE_THICKNESS`, to control how much the handle sticks out.
*   Radii for the outer ridges on top (`TOP_RIDGE`) and bottom (`BOTTOM_RIDGE`).
*   A `TOP_FILLET` for rounding the top edges.
*   A `BOTTOM_FILLET` for rounding the bottom edges.

I also need a `ROUNDNESS` parameter, used for `$fn`, to control the level of detail for curved areas. I'd like to be able to set this to 24, 48, or 96 - 48 should probably be the default.

It would be great if the design included presets for common sizes, like `BS_50x30`, `BS_107x50`, and `SM_80x77` so I don't need to define all the sizes each time. I need two custom presets that allow all the parameters to be controlled.

Could you create the necessary OpenSCAD files for this? I don't need any external libraries, and it would be awesome to get a single .scad file if possible.

[Assistant]
stanley-divider-plates.scad:
```
// Drawer dividers for Stanley bin systems and organizer.
// Stanley, Sortmaster are registered trademarks of Stanley Black & Decker Inc.
//
// This 3D model is published under CC BY-SA 4.0 by Stefan Schönberger <mail@sniner.net>

/* [General parameters] */

// Choose a preset divider (BS=Bin system; SM=Sortmaster) or create your own
OBJECT_TO_PRINT = "custom_drawer"; //[custom_drawer,custom_sortmaster,BS_50x30,BS_107x50,SM_80x77,none]

// Total height of the divider plate.
HEIGHT = 30; //[20:0.5:100]

// Thickness that is needed to fit the divider plate into the holder.
THICKNESS = 2.0; //[0.8:0.1:4.0]

/* [Bin system specific parameters] */

// Total width of the divider plate.
WIDTH = 50; //[30:0.5:150]

// Applies to the area that fits into the holder.
BORDER_WIDTH = 2.0; //[0.8:0.1:3.0]

// Material thickness of the divider plate (must be less than or equal to THICKNESS)
WALL_THICKNESS = 1.2; //[0.8:0.1:4.0]

// Apply a reinforcing structure (only necessary for large divider plates)
REINFORCEMENT = false; //[true,false]

// For rounding the outer edges
FILLET = 1.2; //[0:0.1:2]

/* [Sortmaster specific parameters] */

// The Sortmaster plate has a slightly conical shape.
TOP_WIDTH           = 81.0; //[60:0.5:120]

// Therefore the bottom width can be set individually.
BOTTOM_WIDTH        = 79.0; //[40:0.5:120]

// The width of the handle must be smaller than the width of the plate.
HANDLE_WIDTH        = 66.0; //[10:0.5:100]

// Height of the small handle at the top corner.
HANDLE_HEIGHT       =  1.2; //[0.8:0.1:2]

// Determines how much the small handle at the top should stick out.
HANDLE_THICKNESS    =  2.0; //[0:0.1:4]

// Radius of the outer ridge at the top
TOP_RIDGE           =  3.0; //[0:0.1:5]

// Radius of the outer ridge at the bottom
BOTTOM_RIDGE        =  3.0; //[0:0.1:5]

// For rounding the outer edges at the upper side.
TOP_FILLET          =  1.0; //[0:0.1:4]

// For rounding the outer edges at the bottom side.
BOTTOM_FILLET       =  3.0; //[0:0.1:5]

/* [OpenSCAD parameters] */

// 48 is a good compromise between speed and accuracy.
ROUNDNESS = 48; //[24,48,96]

// ==== Utilities ===========================================================

module copy_mirror(v=[1, 0, 0])
{
    children();
    mirror(v=v) children();
}

module rounded_cube(width, depth, height, fillet, center=false)
{
    translate(center ? [0, 0, -height/2] : [width/2, depth/2, 0])
    linear_extrude(height) {
        hull() {
            copy_mirror(v=[0, 1, 0]) copy_mirror()
            translate([width/2-fillet, depth/2-fillet, 0]) circle(r=fillet);
        }
    }
}

// ==== Bin System ==========================================================

module _bin_system_drawer_divider(plate_width, plate_height, plate_thickness,
    border_width, wall_thickness, reinforcement, fillet)
{
    intersection() {
        union() {
            linear_extrude(wall_thickness)
                square([plate_width, plate_height]);
            linear_extrude(plate_thickness) {
                square([border_width, plate_height]);
                translate([plate_width-border_width, 0, 0]) square([border_width, plate_height]);
                if (reinforcement) {
                    hull() {
                        translate([border_width/2, border_width, 0]) circle(d=border_width);
                        translate([plate_width-border_width/2, plate_height-border_width, 0]) circle(d=border_width);
                    }
                    hull() {
                        translate([border_width/2, plate_height-border_width, 0]) circle(d=border_width);
                        translate([plate_width-border_width/2, border_width, 0]) circle(d=border_width);
                    }
                }
            }
        }
        if (fillet>0) {
            translate([0, 0, -1]) rounded_cube(plate_width, plate_height,wall_thickness+border_width+2, fillet);
        }
    }
}

module bin_system_drawer_divider(
    plate_width     = WIDTH,
    plate_height    = HEIGHT,
    plate_thickness = THICKNESS,
    border_width    = BORDER_WIDTH,
    wall_thickness  = WALL_THICKNESS,
    reinforcement   = REINFORCEMENT,
    fillet          = FILLET,
    center          = false
) {
    translate(center ? [-plate_width/2, -plate_height/2, 0] : [0, 0, 0])
    _bin_system_drawer_divider(
        plate_width = max(plate_width, fillet*2),
        plate_height = max(plate_height, fillet*2),
        plate_thickness = plate_thickness,
        border_width = min(border_width, plate_width/2),
        wall_thickness = min(wall_thickness, plate_thickness),
        reinforcement = reinforcement,
        fillet = min(fillet, border_width)
    );
}

// ==== Sortmaster ==========================================================

module _sortmaster_divider_plate(
    top_width       = 80.80,
    bottom_width    = 79.50,
    height          = 77.00,
    thickness       =  1.50,
    top_fillet      =  1.00,
    bottom_fillet   =  3.00,
) {
    dw = (top_width - bottom_width)/2.0;
    tf = max(top_fillet, 0.001);
    bf = max(bottom_fillet, 0.001);
    linear_extrude(thickness) {
        hull() {
            translate([tf, tf, 0]) circle(r=tf);
            translate([top_width-tf, tf, 0]) circle(r=tf);
            translate([bf+dw, height-bf, 0]) circle(r=bf);
            translate([bottom_width-bf+dw, height-bf, 0]) circle(r=bf);
        }
    }
}

module _slot_ridge(top_radius, bottom_radius, height)
{
    rotate([-90, 0, 0]) {
        cylinder(r1=top_radius, r2=bottom_radius, h=height);
        sphere(r=top_radius);
        translate([0, 0, height]) sphere(r=bottom_radius);
    }
}

// This divider plate for Sortmaster does not resemble the original part,
// it is optimized for FDM 3D printers.
module _stanley_sortmaster(
    top_width           = 80.8,
    bottom_width        = 79.5,
    height              = 77.0,
    thickness           =  1.5,
    handle_thickness    =  2.0,
    handle_height       =  1.5,
    handle_width        = 66.0,
    top_ridge           =  3.0,
    bottom_ridge        =  3.0,
    top_fillet          =  1.0,
    bottom_fillet       =  3.0,
) {
    intersection() {
        union() {
            _sortmaster_divider_plate(top_width=top_width, bottom_width=bottom_width,
                thickness=thickness, height=height,
                top_fillet=top_fillet, bottom_fillet=bottom_fillet);
            // The original handle is 3.5 mm wide, 66 mm long and 1.6 mm high
            translate([(top_width-handle_width)/2.0, handle_height, thickness])
            rotate([90, 0, 0])
            linear_extrude(handle_height) {
                hull() {
                    translate([handle_thickness/2, 0, 0]) circle(d=handle_thickness);
                    translate([handle_width-handle_thickness/2, 0, 0]) circle(d=handle_thickness);
                }
            }
            // The original bead is triangular and symmetrical, here it is part of a cylinder
            _slot_ridge(top_radius=top_ridge, bottom_radius=bottom_ridge, height=height*0.9);
            translate([top_width, 0, 0])
                _slot_ridge(top_radius=top_ridge, bottom_radius=bottom_ridge, height=height*0.9);

        }
        _sortmaster_divider_plate(top_width=top_width, bottom_width=bottom_width,
            thickness=max(top_ridge, bottom_ridge, handle_thickness)+1, height=height,
            top_fillet=top_fillet, bottom_fillet=bottom_fillet);
    }
}

module sortmaster_divider_plate(
    top_width       = TOP_WIDTH,
    bottom_width    = BOTTOM_WIDTH,
    height          = HEIGHT,
    thickness       = THICKNESS,
    handle_thickness= HANDLE_THICKNESS,
    handle_height   = HANDLE_HEIGHT,
    handle_width    = HANDLE_WIDTH,
    top_ridge       = TOP_RIDGE,
    bottom_ridge    = BOTTOM_RIDGE,
    top_fillet      = TOP_FILLET,
    bottom_fillet   = BOTTOM_FILLET,
    center          = false
) {
    translate(center ? [-top_width/2, -height/2, 0] : [0, 0, 0])
    _stanley_sortmaster(
        top_width       = top_width,
        bottom_width    = min(bottom_width, top_width),
        height          = height,
        thickness       = thickness,
        handle_thickness= handle_thickness,
        handle_height   = handle_height,
        handle_width    = min(handle_width, top_width-4*top_ridge),
        top_ridge       = top_ridge,
        bottom_ridge    = bottom_ridge,
        top_fillet      = min(top_fillet, top_width/2),
        bottom_fillet   = min(bottom_fillet, bottom_width/2)
    );
}

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

$fn = ROUNDNESS;

if (OBJECT_TO_PRINT=="BS_50x30")
    // Small drawer
    bin_system_drawer_divider(
        plate_width     = 50,
        plate_height    = 30,
        plate_thickness =  2.0,
        wall_thickness  =  1.2,
        border_width    =  2.0,
        reinforcement   = false,
        fillet          = FILLET
    );
else if (OBJECT_TO_PRINT=="BS_107x50")
    // Large drawer
    bin_system_drawer_divider(
        plate_width     = 107,
        plate_height    =  50,
        wall_thickness  =   0.9,
        plate_thickness =   1.5,
        reinforcement   = true,
        fillet          = FILLET
    );
else if (OBJECT_TO_PRINT=="SM_80x77")
    // The only Sortmaster divider plate that I know of
    sortmaster_divider_plate(
        top_width       = 80.80,
        bottom_width    = 79.50,
        height          = 77.00,
        thickness       =  1.50,
        handle_thickness=  3.50,
        handle_height   =  1.50,
        handle_width    = 66.00,
        top_ridge       =  3.00,
        bottom_ridge    =  3.00,
        top_fillet      =  1.00,
        bottom_fillet   =  3.00
    );
else if (OBJECT_TO_PRINT=="custom_drawer")
    // Depends completely on customizer settings
    bin_system_drawer_divider();
else if (OBJECT_TO_PRINT=="custom_sortmaster")
    // Depends completely on customizer settings
    sortmaster_divider_plate();
```


[User]
I'm looking to create a parametric battery cover generator in OpenSCAD. I've seen some existing examples, but I need one that is highly customizable.

Here are the parameters I need to control:

**Basic Dimensions:**

*   `length`:  The overall length of the cover.
*   `width`: The overall width of the cover.
*   `thickness`: The thickness of the main body of the cover.
*   `corner_radius`: The radius for rounding the corners of the rectangular cover.

**Hook Features:**

*   `hook_width`: The width of the hook tabs.
*   `hook_length`: The length of the hook tabs.
*   `hook_offset`: How far the hooks are set in from the edge of the battery cover. This could be the same as `hook_length` but sometimes needs to be different.
*   `hook_spacing`: The distance between the inside edges of the two hooks. If you want one large hook, this should be 0.
*   `hook_case_thickness`: How thick the case is where the hooks attach, which dictates the height of the hole in the hook.
*  `hook_back_thickness`:  The thickness of the back part of the hooks.
* `hook_top_thickness`: The thickness of the top part of the hooks.

**Clip Features:**

*   `clip_width`: The overall width of the clip.
*   `clip_tooth`: The depth of the clip's overhang (how far it "catches").
*   `clip_length`: The length of the clip.
*   `clip_width_sides`:  The width of the narrower portion of the clip, for side-engagement clips.
*   `clip_length_catch`: The length of the catch portion for front-engagement clips. Should be set to zero if unused.
*   `clip_offset`: How far the clip is set in from the edge of the battery cover.
*   `clip_case_thickness`:  How far down into the battery case the clip engages (should often be the same as the base `thickness`).
*    `clip_tab_length`: The length of the finger tab for pressing the clip.
*   `clip_thickness`: The overall thickness of the clip (likely shouldn't need to change).
*  `clip_width_margin`: The spacing between the clip and the body for movement.
*  `clip_spring_thickness`:  The thickness of the spring portion of the clip.

**Support Gussets:**

*   `gusset_orientation`: An option to choose if the gussets should run along the *length* or *width* of the cover - let's call these `clip_lengthwise` and `clip_widthwise`, with an enumeration.
*   `gusset_thickness`: The thickness of the gussets (set to 0 to disable).
*   `gusset_height`:  The height of the gussets.
*   `gusset_spacing`: How far apart the gussets are spaced.

**Advanced:**
*   `$fn`: standard OpenSCAD resolution control

I'd like the generator to work correctly for side-engagement clips (where the `clip_width_sides` parameter is used). I've also seen some issues with `clip_offset` being larger than `clip_length`, so a fix for that would be important.

Could you generate the OpenSCAD code for this? No external libraries are required.

[Assistant]
battery_cover_creator_2.1.scad:
```
// This thing generates custom battery covers
//
// v2.0
//
// Licenced under Creative Commons: Attribution, Share-Alike
//		http://creativecommons.org/licenses/by-sa/3.0/
// For attribution you can direct people to the original file on thingiverse:
// 		http://www.thingiverse.com/thing:243269

// ================ variables

//CUSTOMIZER VARIABLES

/* [Basic] */

// how long the battery cover is.  (This dimension is in the clip-to-hooks direction.)
length=50.0;

// how wide the battery cover is
width=30.0;

// how thick the battery cover is
thickness=0.75;

// radius for rounding over the corners of the battery cover
corner_radius=2.0;

/* [Hooks] */

// how wide the hook tabs are
hook_width=5.0;

// how long the hook tabs are
hook_length=3.0;

// How far the hooks are in/out relative to the edge of the battery cover (often the same as hook_length).
hook_offset=3.0;

// how far apart the hooks are (on the inside).  (To create one wide hook, just set this to 0.)
hook_spacing=10.0;

// How thick the case is where the hooks attach, aka, how high the hole in the hook should be.
hook_case_thickness=2.0;

// how thick the back of the hooks are
hook_back_thickness=1.0;

// how thick the top part of the hooks are
hook_top_thickness=1.0;

/* [Clip] */

// How wide the clip is
clip_width=10.0;

// How deep the clip is (overhang)
clip_tooth=1.0;

// How long the clip is
clip_length=3.0;

// This is used for a side-engagement clip.  Most clips are this way now.  It measures how wide the small part of the clip is.
clip_width_sides=8.0;

// This is for a front-engagement clip.  If unused, set it to 0, otherwise, how long the catch will be (usually about 2-3mm)
clip_length_catch=1.0;

// How far the clip is in/out relative to the edge of the battery cover.
clip_offset=0.0;

// This is how far down into the battery case the clip engages.  Usually it should be the same as "thickness" on the "basic" tab
clip_case_thickness=3.0;

// The thickness of the finger tab
clip_tab_length=1.0;

// how thick the clip material is (probably never need to change)
clip_thickness=1.0;

// spacing between the clip and the body for movement (probably never need to change)
clip_width_margin=0.0;

// how thick to make the spring part of the clip (probably never need to change)
clip_spring_thickness=0.75;

/* [Support gussets] */

// Which direction the support gussets go
gusset_orientation=0.0; // [0:clip_lengthwise,1:clip_widthwise]

// How thick the gussets are (set to 0 to turn off gussets)
gusset_thickness=0.0;

// How high the gussets are
gusset_height=1.0;

// How far apart the gussets are
gusset_spacing=10.0;

/* [Advanced] */

// Resolution
$fn=30.0;

/* [hidden] */

scoche=0.01;
halfscoche=scoche/2;

module clip(clip_width=15,clip_width_sides=10,clip_length=1,clip_case_thickness=5,clip_length_catch=3,clip_tab_length=3,clip_spring_thickness=0.75) {
	slope=0.33; // this is only a ballpark
	top_r=clip_length/3;
	top_r_x=clip_length-top_r;
	top_r_y=clip_length+clip_case_thickness/slope/2;
	overlap_tab_w=2;
	inside_void_d1=top_r*2-clip_spring_thickness*2;
	inside_void_d2=top_r*2-clip_spring_thickness*2;
	inside_void_x=top_r_x;
	clip_tab_thickness=1;
	rotate([90,0,0]) difference () {
        linear_extrude(clip_width) difference () {
            union(){ // the outside shape
                hull () {
                    translate([0,clip_tab_thickness]) circle(r=clip_tab_thickness); // outside radius
                    translate([clip_length-scoche,0]) square([scoche,clip_tab_thickness]); // inside corner
                    translate([top_r_x,top_r_y]) circle(r=top_r); // top
                    if(clip_length_catch>0){
                        translate([-clip_length_catch+0.1,clip_case_thickness]) circle(r=scoche); // add a front overhang
                    }
                }
                translate([-overlap_tab_w,0]) square([overlap_tab_w,clip_tab_thickness]); // the overlap tab
                if(clip_offset > clip_length) {
                    translate([clip_length,0]) square([clip_offset-clip_length,thickness]);
                }
            }
            union(){
                translate([inside_void_x,0]) hull() { // The inside void
                    translate([0,top_r_y]) circle(r=inside_void_d1/2);
                    translate([-inside_void_d2/2,-clip_tab_length-scoche]) square([inside_void_d2,scoche]);
                    // a hollow for being able to push the thing over (especially when opening is less than the hook)
                    translate([-clip_tooth,0]) square([clip_tooth,clip_case_thickness]);
                }
                // trim the tab overhang
                translate([-clip_tab_length-clip_tooth,clip_tab_thickness]) square([clip_tooth*2,clip_case_thickness-clip_tab_length]);
            }
        }
        if(clip_width_sides > 0) {
            union() {
                clip_cut_width = (clip_width-clip_width_sides)/2;
                translate([-clip_length_catch-clip_tab_length-2, -1, -1]) cube([clip_length_catch+clip_tab_length+clip_length-(clip_spring_thickness*2)+2,clip_case_thickness+1,clip_cut_width+1]);
                translate([-clip_length_catch-clip_tab_length-2, -1, clip_width-clip_cut_width]) cube([clip_length_catch+clip_tab_length+clip_length-(clip_spring_thickness*2)+2,clip_case_thickness+1,clip_cut_width+1]);
            }
        }
	}
}

module hook(size,hook_back_thickness,hook_top_thickness){
	difference(){
		cube(size);
		translate([hook_back_thickness,-size[1]/2,-hook_top_thickness]) cube([size[0],size[1]*2,size[2]]);
	}
}

module hooks(length,hook_length,thickness,hook_spacing,hook_width,hook_case_thickness,hook_back_thickness,hook_top_thickness,hook_offset){
	size=[hook_length+hook_back_thickness,hook_width,hook_case_thickness+hook_top_thickness+thickness];
	                translate([length/2-hook_back_thickness-hook_offset,hook_spacing/2,0]) hook(size,hook_back_thickness,hook_top_thickness);
	mirror([0,1,0]) translate([length/2-hook_back_thickness-hook_offset,hook_spacing/2,0]) hook(size,hook_back_thickness,hook_top_thickness);
}

module flatBase(length,width,corner_radius,thickness,clip_width,clip_length,clip_thickness,clip_width_margin=0.5,clip_offset=0,clip_spring_thickness=0.75){
	clip_hole=[clip_length+scoche-clip_offset-clip_spring_thickness,clip_width+clip_width_margin*2];
	linear_extrude(thickness) difference(){
		// the basic shape
		translate([-length/2,-width/2]) minkowski(){
			translate([corner_radius,corner_radius]) square([length-corner_radius*2,width-corner_radius*2]);
			if(corner_radius>0) circle(r=corner_radius);
		}
		// a hole for the clip
		translate([-length/2-scoche/2,-clip_hole[1]/2]) square(clip_hole); // the hole for the clip
	}
}

module gussets(width,length,gusset_orientation=0,gusset_thickness=1,gusset_height=3,gusset_spacing=5){
	if(gusset_orientation==0){ // lengthwise
		translate([-length/2,gusset_spacing/2,0]) cube([length,gusset_thickness,gusset_height]);
		mirror([0,1,0])translate([-length/2,gusset_spacing/2,0]) cube([length,gusset_thickness,gusset_height]);
	}else{ // crosswise
		translate([gusset_spacing/2,-width/2,0]) cube([gusset_thickness,width,gusset_height]);
		mirror([1,0,0])translate([gusset_spacing/2,-width/2,0]) cube([gusset_thickness,width,gusset_height]);
	}
}

module cover(width=30,length=60,thickness=0.5,hook_case_thickness=3.9,
	gusset_orientation=0,gusset_thickness=1,gusset_height=3,gusset_spacing=5,
	corner_radius=1,hook_width=7.5,hook_spacing=29,clip_width=15,clip_width_sides=10,
	clip_length=1,clip_length_catch=3,clip_case_thickness=5,hook_length=5.8,clip_tab_length=3,
	clip_width_margin=0.5,clip_offset=0,hook_back_thickness=0.5,hook_top_thickness=0.5,clip_spring_thickness=0.75,
	hook_offset=0)
{
	gussetInset=max(hook_length,clip_length-clip_offset); // need to make sure it's short enough not to interfere with other parts
	flatBase(length,width,corner_radius,thickness,clip_width,clip_length,clip_thickness,clip_width_margin,clip_offset,clip_spring_thickness);
	hooks(length,hook_length,thickness,hook_spacing,hook_width,hook_case_thickness,hook_back_thickness,hook_top_thickness,hook_offset);
	gussets(width,length-gussetInset*2,gusset_orientation,gusset_thickness,gusset_height+thickness,gusset_spacing);
	translate([-length/2-clip_offset,clip_width/2,0]) clip(clip_width,clip_width_sides,clip_length,clip_case_thickness,clip_length_catch,clip_tab_length,clip_spring_thickness);
}

cover(width,length,thickness,hook_case_thickness,
	gusset_orientation,gusset_thickness,gusset_height,gusset_spacing,
	corner_radius,hook_width,hook_spacing,clip_width,clip_width_sides,
	clip_length,clip_length_catch,clip_case_thickness,hook_length,clip_tab_length,
	clip_width_margin,clip_offset,hook_back_thickness,hook_top_thickness,clip_spring_thickness,
	hook_offset);
```


[User]
I'd like to create a parametric version of a T-square, specifically one designed for drawing parallel lines. I've seen a version on Thingiverse, but I want to make one from scratch that is easily printable.

The basic design should consist of two main parts: a handle and a measurement bar.

The handle should be a rounded shape, with two cylindrical sections connected by a hull. There should be two smaller cylinders defining the end-points of the hull. Within the two larger cylindrical sections of the handle, there should be holes, basically smaller diameter cylinders going through the handle's larger cylinders. The handle should be positioned to the left of the measurement bar, and should be attached to it.

The measurement bar should be a long, rectangular shape with a rounded end. It has a series of small holes along its length. These holes should be arranged in a grid pattern. Additionally, there are small text markers indicating units along the length of the bar. These are numeric values printed on the bar as text, near the holes. At the far right end of the measurement bar, the bar rounds out into an end-cap with two small cylinders acting like posts.

I'd like to control several aspects of the design with parameters: the overall length of the measurement bar, the width of the holes, the spacing between the holes, and the size of the handle. It should be designed with a flat bottom to make it easy to print.

Could you generate the OpenSCAD code for this? I'd like the whole model to rotate by 180 degrees around the X axis, and the handle to be positioned at the left end of the measurement bar, so that the flat printing surface of the T-Square is pointing down. I won't be using any external libraries - all of the code should be generated from scratch. I'd like to make the handle and measurement bar each their own `module` for easy reuse.

[Assistant]
T-Lehre.scad:
```
/**
 * simple angled measurement to draw perfect parallel lines
 * modelled after https://www.thingiverse.com/thing:4186423 by Sven Kettmann
 *
 *
*/

// The length of the measurement part. Take note that the resulting object will be larger.
length = 115; // [20:5:135]
// Size of the holes in
holeWidth = 2.5; // [1:0.5:4]

/* [Hidden] */
$fn=32;

module handle() {
    difference() {
        hull() {
            translate([-10, -25, 1]) cylinder($fn=64, r=10, h=8, center=true);
            translate([-10, 15, 1]) cylinder($fn=64, r=10, h=8, center=true);
            translate([-19, -34, 1]) cylinder(r=1, h=8, center=true);
            translate([-19, 24, 1]) cylinder(r=1, h=8, center=true);
        }
        translate([-11, -25, 0]) cylinder($fn=64, r=5, h=11, center=true);
        translate([-11, 15, 0]) cylinder($fn=64, r=5, h=11, center=true);
    }
}

module measureHoles() {
    for (i=[5:5:length]) {
        for (j=[0:4]) {
            translate([ i+j, 5-j*5, 1 ]) cylinder(r=holeWidth/2, h=10, center=true);
        }
    }
}

module measureUnits() {
    for (i=[5:5:length]) {
        translate([ i, 8.2, 4.5 ]) linear_extrude(height=1) text(text=str(i*0.1), size=2, halign="center");
    }
}

module measure() {
    difference() {
        hull() {
            translate([0,-19,2]) cube(size=[length, 30, 3], center=false);
            translate([length+1.5, 10, 3.5]) cylinder(r=1, h=3, center=true);
            translate([length+7, -18, 3.5]) cylinder(r=1, h=3, center=true);
        }
        measureHoles();
        translate([0, 7, 4.5]) cube(size=[length+8.1, 0.5, 1.1], center=false);
        measureUnits();
    }
}

// use only if you have T-Lehre_60x150mm_v2.stl from https://www.thingiverse.com/thing:4186423/
// translate([-20,-35,-1]) #import("./T-Lehre_60x150mm_v2.stl");

rotate([180, 0, 0]) {
    handle();
    measure();
}

```