[User]
I'd like to design a parametric shopping cart token that can be customized with text and scaled to different coin sizes. The current version I'm working off of is attached, but I'd like to start from scratch with a clean file (or files).

Here are the features I need:

*   **Coin Dimensions:** The design should be easily customizable for different coin diameters and heights. I need parameters for `coin_Diameter` and `coin_Height`. The default values should be 23.2mm for diameter and 2.3mm for height.

*   **Text Imprint:** I need to be able to add custom text to the token. The text should be imprinted into the surface. Parameters should include:
    *   `text_Text`:  For the actual text string (default: "Schaffenburg")
    *   `text_Size`: The size of the text (default: 5.1)
    *   `text_ImprintDepth`: How deep the text is imprinted into the token (default: 0.6).
    *   `text_Style`: The font style (default: "Arial:style=Bold Italic")
    *   `text_Spacing`: The spacing of the text (default: 1)
    *  `text_Alignment`: Alignment of the text (left, center, right), default "center"

*   **Keyhole:** There should be a hole for attaching to a keyring. The hole needs a `keyhole_Diameter` parameter (default: 14.5mm).

*   **Overall Shape:** The token should be generally circular with an extended tab (similar to the provided code) to make it easier to grip when inserting/removing.

*   **Parametric Design:** All dimensions should be calculated using the `coin_Diameter` parameter as a base so that it scales properly with changes to this variable.

*   **OpenSCAD Files:** Please provide the complete OpenSCAD code, so I don't need to figure out if anything is missing.

I am not using any external libraries.

I'm looking for a design that is robust and easy to customize. I don't mind if it is split across a few files or kept all in one, so long as it is well-organized.

[Assistant]
V4_FullScaling_280119.scad:
```
/* [Hidden] */
$fn = 200;  //200

/* [Coin Size] */
coin_Diameter = 23.2; //23.2
coin_Height = 2.3;  //2.3

/* [Text] */
text_Text = "Schaffenburg";
text_Size = 5.1;  //5.1
text_ImprintDepth = 0.6;  //0.6
text_Style = "Arial:style=Bold Italic"; //"Arial:style=Bold Italic"
text_Spacing = 1; //1
text_Alignment = "center"; //[left,center,right] //"center"

/* [Hole Size] */
keyhole_Diameter = 14.5; //14.5

module NAME()
{
    translate([coin_Diameter / 2 / 11.6 * 12.5,0,coin_Height - text_ImprintDepth])
    {
        linear_extrude(height=text_ImprintDepth)
        {
            text(text_Text, size=coin_Diameter / 2 / 11.6 * text_Size, valign="center", halign=text_Alignment, font = text_Style, spacing=text_Spacing);
        }
    }
}

module TAG()
{
    cylinder(r=coin_Diameter / 2 / 11.6 * 11.6, h=coin_Height);

    translate([coin_Diameter / 2 / 11.6 * 45,0,0])
    {
        cylinder(r=coin_Diameter / 2 / 11.6 * 11.6, h=coin_Height);
    }

    difference()
    {
        translate([0,coin_Diameter / 2 / 11.6 * -11.6,0])
        {
            cube([coin_Diameter / 2 / 11.6 * 45,coin_Diameter / 2 / 11.6 * 23.2,coin_Height]);
        }

        translate([coin_Diameter / 2 / 11.6 * 22.5,coin_Diameter / 2 / 11.6 * 109.2943,0])
        {
            cylinder(r=coin_Diameter / 2 / 11.6 * 100, h=coin_Height);
        }

        translate([coin_Diameter / 2 / 11.6 * 22.5,coin_Diameter / 2 / 11.6 * -109.2943,0])
        {
            cylinder(r=coin_Diameter / 2 / 11.6 * 100, h=coin_Height);
        }
    }
}

module KEYHOLE()
{
    translate([coin_Diameter / 2 / 11.6 * 45,0,0])
    {
        cylinder(r=coin_Diameter / 2 / 11.6 * keyhole_Diameter / 2, h=coin_Height);
    }
}

difference()
{
    TAG();
    NAME();
    KEYHOLE();
}
```


[User]
Hi! I'd like a parametric OpenSCAD design for a stack of three cylindrical shapes. I'm trying to create a visual demonstration of inscribed and circumscribed polygons.

Specifically, I want three cylinders stacked vertically. The middle cylinder will be a true cylinder, while the top and bottom will approximate polygons. The bottom cylinder will be a polygon whose vertices touch the *inside* of a circle of a specified radius, and the top will be a polygon whose vertices touch the *outside* of that circle (i.e. the circumscribed polygon). The number of sides for these polygons should be a single adjustable parameter, lets call it `N`. The radius of the circle that all these shapes approximate should also be parametric, we'll call it `radius`. I'd also like a height parameter for each cylinder, so `height_bottom`, `height_middle` and `height_top`.

To reiterate, I'm looking for something that shows that relationship visually. So, the bottom one is the "inner" polygon, the middle one is the circle, and the top one is the "outer" polygon.

I'd like to use the following parameter defaults and value ranges:

*   `N = 5` with possible values `[3,4,5,6,7,8,9,10,11,12,13]`
*   `radius = 10` with possible values `[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]`
*   `height_bottom = 5` with possible values `[1,2,3,4,5,6,7,8,9,10]`
*  `height_middle = 5` with possible values `[1,2,3,4,5,6,7,8,9,10]`
*  `height_top = 5` with possible values `[1,2,3,4,5,6,7,8,9,10]`

I'd like each shape to be a different color for clarity - specifically, using the colors "RoyalBlue" for the bottom, "DeepSkyBlue" for the middle, and "Aqua" for the top cylinder. I think this matches the existing script in the documentation.

Also, I'm assuming you'll generate a single .scad file and not use any libraries.

[Assistant]
ScribedCircles.scad:
```
/*
Trying out the customizer with a minimal script that
generates a stack of polygons with a circle in the middle.
The top and bottom polygons are the circumscribed and
inscribed respectively. Uses a single parameter (number
of sides) as shown here:

http://customizer.makerbot.com/docs

// variable description
variable name = default value; // possible values

*/

// Number of Sides
N=5; // [3,4,5,6,7,8,9,10,11,12,13]

color("RoyalBlue") cylinder(r=10/cos(180/N), h=5, $fn=N);
color("DeepSkyBlue") translate([0,0,5]) cylinder(r=10, h=5, $fn=12*N);
color("Aqua") translate([0,0,10]) cylinder(r=10, h=5, $fn=N);

```


[User]
I'm looking to create a parametric filament sample holder, similar to the design I found from Tech3Design. I'd like the design to be generated in OpenSCAD, so I can easily customize it for different sample sizes.

My current design is based on a central hub with several arms extending outwards. The ends of the arms have a pivoting hook for holding the filament sample, with a sort of partial-cylinder shape.

Here are the parameters I’d like to be able to adjust:

*   **`inner_diameter`**: The diameter of the central spool holder area.
*   **`hook_diameter`**: The diameter the filament hook should be positioned at; basically, this is the diameter of a circle formed by the hook arms.
*   **`outside_diameter`**: The overall diameter of the holder.
*   **`spindle_depth`**: The depth/thickness of the entire holder.
*   **`wall_thickness`**: The thickness of the flat parts.
*   **`num_arms`**: The number of arms extending out from the center.
*   **`arm_width`**: The width of each arm.

The hub should be hollow in the center. The arms are flat extrusions with a circular end. The hooks should have a sort of partial-cylinder shape that extends up, allowing the filament to swivel in place. I've included the OpenSCAD code I've been playing with to give you a sense of the specific geometry I am aiming for; maybe that'll help.

I'd like the code to be clean and well-organized, and it should use comments to make things clear. I'm not using any special libraries, but I'm open to suggestions if you think it would make the code easier to read or maintain. I’m also imagining that I could export the design as an .stl file for printing.

[Assistant]
sample-holder.scad:
```
// spool holder diameter (mm)
inner_diameter = 60;

// filament hooks diameter (this is the diameter of your filament) (mm)
hook_diameter = 100;

// the size of the whole holder
outside_diameter = 160;

// depth of the holder
spindle_depth = 25;

// how thick all the 'flat' parts should be
wall_thickness = 3;

// how many arms on your holder? (3+)
num_arms = 3;

arm_width = 20;

module Hub() {

    difference() {
        cylinder(h=spindle_depth, r=inner_diameter/2);
        cylinder(h=spindle_depth, r=inner_diameter/2 - wall_thickness);

            for(a= [0:3]) {
            rotate(a=45*a, v=[0,0,1]) translate([-inner_diameter*0.6,0,spindle_depth/2]) rotate(a=90, v=[0,1,0]) cylinder(h=inner_diameter*1.2, r= (spindle_depth - 2*wall_thickness)/2);
        }

    }

}

module Arm() {

    translate([-arm_width/2,0,0]) cube([arm_width,(outside_diameter)/2 , wall_thickness]);
    translate([0,outside_diameter/2,0]) cylinder(r=arm_width/2, h=wall_thickness);

    difference() {
        translate([0,hook_diameter/2,0]) cylinder(r=4, h=spindle_depth);
        translate([0,hook_diameter/2,0]) cylinder(r=2, h=spindle_depth,$fn=50);
    }

}

module Hook() {

    translate([0,hook_diameter/2,0]) cylinder(r=1.9, h=spindle_depth*0.7,$fn=50);

    hull () {
        translate([0,hook_diameter/2,0]) cylinder(r=arm_width/2, h=wall_thickness);
        translate([0,outside_diameter/2,0]) cylinder(r=arm_width/2, h=wall_thickness);
    }
}

difference() {

    union() {
        Hub();

        for (a = [1:num_arms]) {
            angle = a * (360/num_arms);
            rotate(a=angle, v=[0,0,1]) Arm();
            rotate(a=angle, v=[0,0,1]) translate([0,(outside_diameter)/2,0]) Hook();

        }
    }
// punch a hole back through the middle
cylinder(h=spindle_depth, r=inner_diameter/2 - wall_thickness);
}

```


[User]
I'd like a parametric fidget spinner design, please. It should have a central bearing and then a number of weighted "arms" or sections.

Here's what I need to be able to customize:

*   **Number of sections:** I'd like to be able to choose between 1 and 5 sections/arms.
*   **Weight type:** The weights at the end of each section should be customizable. I need options for a 608 bearing (which is the central bearing, but that's fine), or various hex nuts, specifically: 5/16", 3/8", 1/2", M8, M10 (both 16mm and 17mm), and M12 (both 18mm and 19mm). Also, I want the option for loose ball bearings: 3/8", 1/2", and 5/8". I understand that the nuts might stick out a bit from the frame, and that's okay.
*   **Edge Roundness:** I need a way to control the roundness of the edges of the spinner. A radius value should work well here.

It would be great if the code is well-commented, so I can follow along easily. I'm hoping for an easy-to-use design with clearly named parameters. No extra bells and whistles are necessary. I'd like the final product to be a single OpenSCAD file; I don't want multiple files.

[Assistant]
section-spinner-2017-09-29.scad:
```
// section spinner
// lucina
// 2017-09-09
// preview[view:south, tilt:top]

// number of sections for the spinner
sections = 4;  //[1, 2, 3, 4, 5]
// type of weight
whichWeight = "3/8 hex nut";   //[608 bearing, 5/16 hex nut, 3/8 hex nut, 1/2 hex nut, M8 hex nut, M10 16 hex nut, M10 17 hex nut, M12 18 hex nut, M12 19 hex nut, M4 square nut, 5/16 ball bearing, 3/8 ball bearing, 1/2 ball bearing, 5/8 ball bearing]
// edge roundness
radius = 1.5; //[0:.5:3]
/* [Hidden] */
//$fn = 16;    //debug
$fn = 60;
x = 0; y = 1; z = 2;
thick = 3;
bearing = [22, 22, 7]; //608 bearing
sae = [.577*25.4, .5 * 25.4, 7]; // 5/16 hex nut
sae38 = [.650*25.4,.562*25.4, 7];	// 3/8
sae12 = [.866*25.4, .75*25.4, 7];	// 1/2
metric8 = [15, 13, 7];   // M8 hex nut
metric10_16 = [17.77, 16, 7];   // M10 16 hex nut
metric10_17 = [19.6, 17, 7];   // M10 17 hex nut
metric12_18 = [20.03, 18, 7];   // M12 18 hex nut
metric12_19 = [21.9, 19, 7];   // M12 19 hex nut
metric4sq = [7, 7, 3.2];
ball38 = [3/8*25.4, 3/8*25.4, 3/8*25.4];
ball12 = [1/2*25.4, 1/2*25.4, 1/2*25.4];
ball58 = [5/8*25.4, 5/8*25.4, 5/8*25.4];
weight = (whichWeight == "608 bearing") ? bearing :
   (whichWeight == "5/16 hex nut") ? sae :
   (whichWeight == "3/8 hex nut") ? sae38 :
   (whichWeight == "1/2 hex nut") ? sae12 :
   (whichWeight == "M10 16 hex nut") ? metric10_16 :
   (whichWeight == "M10 17 hex nut") ? metric10_17 :
   (whichWeight == "M12 18 hex nut") ? metric12_18 :
   (whichWeight == "M12 19 hex nut") ? metric12_19 :
   (whichWeight == "M4 square nut") ? metric4sq :
   (whichWeight == "5/16 ball bearing") ? ball516 :
   (whichWeight == "3/8 ball bearing") ? ball38 :
   (whichWeight == "1/2 ball bearing") ? ball12 : ball58;
echo(whichWeight, weight[x], weight[y], weight[z]);
resolution = (whichWeight == "5/16 hex nut") ? 6 :
(whichWeight == "3/8 hex nut") ? 6 :
(whichWeight == "1/2 hex nut") ? 6 :
(whichWeight == "M8 hex nut") ? 6 :
(whichWeight == "M10 16 hex nut") ? 6 :
(whichWeight == "M10 17 hex nut") ? 6 :
(whichWeight == "M12 18 hex nut") ? 6 :
(whichWeight == "M12 19 hex nut") ? 6 :
(whichWeight == "M4 square nut") ? 4 : 60;
space = 16;
a = (weight[x]+space);

module fcylinder(z, x, rad) {
   translate([0, 0, rad-z/2])
      minkowski() {
         cylinder(h=z-2*rad, d=x-2*rad);
         sphere(rad);
      }
}
module ftriangle(z, x, y, rad) {
   rotate([0, 0, -90])
      translate([-rad, 0, 0])
         minkowski() {
            intersection() {
               translate([-(x-2*rad), 0, 0])
                  cylinder(h=z-2*rad, r=x-2*rad, $fn = 3, center=true);
               cylinder(h=z-2*rad, r=x-2*rad, center=true);
            }
            sphere(r=rad, center=true);
         }
}
module section(rad) {
   ftriangle(bearing[z], a, a, rad);
}

difference() {
   union() {
      fcylinder(bearing[z], bearing[x] + 2*thick, radius);
      // sections
      step = 360 / sections;
      stop = (sections-1) * step;
      for ( angle = [0 : step : stop] ) {
         rotate([0, 0, angle])
            rotate([0, 0, -90])
                  section(radius);
      }
   }
   // holes
   union() {
      cylinder(h=2*bearing[z], d=bearing[x], center=true);
      step = 360 / sections;
      stop = (sections-1) * step;
      for ( angle = [0 : step : stop] ) {
         rotate([0, 0, angle])
            translate([(weight[x]+bearing[x])/2+thick, 0, 0])
               cylinder(h=2*bearing[z], d=weight[x], center=true, $fn=resolution);
      }
   }
}

```


[User]
I'm looking to create a parametric funnel with an optional stand, designed primarily for filling things like tea filters where a wide opening and less of a sharp, narrow neck are desired. I'd like to be able to adjust the following:

*   **Outer Neck Diameter:** This would be the diameter of the very bottom, thinnest part of the funnel. I'd like to set this in centimeters and have it be adjustable, probably something like 0.3 to 8 cm, with 0.1 cm steps.

*   **Inner Rim Diameter:** This is the diameter of the top, wide opening of the funnel, also in centimeters, and I'd like to adjust it from maybe 3 to 15 cm, in 0.1 cm steps.

*   **Neck Length:** The length of the thin neck part of the funnel, from the end of the cone to the very bottom tip, in centimeters, with adjustability between 0.3 and 8 cm in 0.1 cm increments.

*   **Funnel Angle:** The angle of the main cone part of the funnel, in degrees, with the base starting from the neck, so the higher this number, the more vertical the cone will be. It would be great to set this between 30 and 75 degrees.

*   **Neck Tip Angle:** This is the angle of the point at the very end of the neck, in degrees. 0 would make the neck end flat, and I'd like the option to make it sharp. I'd like to adjust this between 0 and 60 degrees in increments of 0.5.

*   **Tip Rotation:** I'd like to be able to rotate the orientation of the neck tip, just to be able to play around with it. I can set this in degrees between 0 and 359 degrees, and maybe 45-degree steps is okay.

*   **Stand Style:** I need to be able to set whether to make a stand and if so what kind. I'd like the options to be no stand, a simple stand, and a tripod stand.

*   **Extra Height:** This would determine how high the top of the funnel would sit above the base of the stand. It's adjustable in cm between 1 and 15, in 1 cm steps.

*   **Joint Smoothness Factor:** This number controls how smoothly the neck connects to the conical part, which needs to be adjustable. I'm not sure what the actual units would be here but a value from 0.25 to 6, in steps of 0.25 should be good.

I would like the option to view a "preview" with low detail (low poly count) but for final output I need it to be high detail. So you can put the preview and final rendering control into a variable or function.

I think the design should produce separate STL files when I use the "stand" option so I can print the stand separately from the funnel. If I choose the "funnel" option only the funnel should be produced.

Could you generate the OpenSCAD files for this? I don't need any fancy libraries or anything - just plain vanilla OpenSCAD.

[Assistant]
funnel-and-stand.scad:
```
// -*- mode: SCAD ; c-file-style: "ellemtel" ; coding: utf-8 -*-
//
// Parametric funnel with stand
//
// Copyright 2017 - 18 Roland Sieker <ospalh@gmail.com>
// Licence: CC-BY-SA 4.0
//

// ... to preview. You will get both parts, if you select a stand
part = "funnel"; // [funnel: funnel, stand: stand or nothing]

// in cm. The neck is the thin bottom part
outer_neck_diameter = 2.8;  // [0.3:0.1:8]

// in cm. This is the top part
inner_rim_diameter = 7;  // [3:0.1:15]

// in cm. The sharped tip is added to this length
neck_length = 2; // [0.3:0.1:8]

// Slope of the main conical part, in  degrees. Beware of printing problems below 45 degrees.
funnel_angle = 60;  // [30:75]

// Cut off angle to give the funnel a sharpened tip. 0 degrees means flat bottom.
neck_tip_angle = 22.5;  // [0:0.5:60]

// Create just the funnel, or a stand to go with it, with one or three supports
tip_rotation = 180; // [0:45:359]

// Create just the funnel, or a stand to go with it, with one or three supports
stand_style = 0;  // [0:Just funnel, 1:Funnel and simple stand, 3:Funnel and tripod stand]

// Hight added  to the stand, in cm. The hight of the top of the funnel will be the length of your pencil plus this.
extra_hight = 1; // [1:15]

// Measure for the smoothness of the neck/funnel joint
joint_r_factor = 1.75;  // [0.25:0.25:6]

// Set this to "render" and click on "Create Thing" when done with the setup.
preview = 1; // [0:render, 1:preview]

/* [Hidden] */

// Some of the values below can be carefully tweaked, changing others is a
// bad idea. Try, and undo if it didn't work.

xy_factor = 1/tan(funnel_angle);
// To get from a hight to a horizontal width inclined correctly
z_factor = tan(funnel_angle);  // The other way around
// w = 1.6;

// w = 1.6;
w = 1.2;  // To get four perimeters in slic3r, we have to add a bit here. WTF?
// Wall thickness.  When you measure the conical part along the surfaces it
// will appear thinner.

es_h = 20; // Extra support/stabilizer hight
es_w = 0.8;
// Extra support/stabilizer width. Need not be as stable as a normal
// wall

function strake_w() = (stand_style) ? 0 : 0.8;
handle_br = 0.8;  // Hanle border radius

r_nst = outer_neck_diameter * 5;  // diameter at the bottom *with* strakes
r_n = r_nst - w - strake_w();  // inner neck radius in mm
r_r = inner_rim_diameter * 5;  // inner rim radius in mm
l_n = neck_length * 10;  // neck_length in mm
heh = extra_hight * 5; // Half the extra hight, in mm
ond = outer_neck_diameter * 10;
ncl = ond / cos(neck_tip_angle) * 1.05;
nth = ond * tan(neck_tip_angle);

ms = 0.01; // Muggeseggele

fua = funnel_angle;
fua_b = 90 - fua;

r_j = r_n * joint_r_factor;

// How much space for the joint
joint_x = r_j * (1 - cos(fua_b));
joint_h = r_j * sin(fua_b);

cone_x = r_r - r_n - joint_x;
cone_h = cone_x * z_factor;
// The small radius of the support pencil, from center to center of face
wiggle_room_factor = 1.05;

// The small radius of the support pencil, from center to center of face
r_p_s = 3.2 * wiggle_room_factor;
// The flat-to-flat diameter of a standard pencil is 6.8 mm. Do quite a
// bit more.
// The max. radius (center to edge).  Also the
// width of one pencil face.
r_p_l = r_p_s * 2 / 3 * sqrt(3);

// Handle sizes:

handle_w = 40;  // Wide eonugh for index and middle finger
handle_l = 30;  // Long enough for the distal segments
handle_cr = 2;  // Corner radius
// Border radius: see below
handle_handle_ch_ratio = 0.5;
ch = (r_r - r_n) / tan(fua_b);

// Used to calculate how heigh the lug that holds the handle is

// Tip angle
tip_a = 21;  //  degrees. Apparently standard in Germany
// tip_a = 19;  //  degrees. There is a German "Long Point" pencil sharpnener with 19  degrees.
// There are also rumors that American pencil are pointier than German ones.
// tip_a = 24 //  degrees. Special color pencil sharpener.

// The tip angle is apparently from one side to the other side, not from
// one side to the center line.

// Size of the "big pencil" connector. Twice the area, half of it hollow.
r_bp_s = sqrt(2) * r_p_s;
r_bp_l = r_bp_s * 2 / 3 * sqrt(3);

sp_h = 8 * r_p_s;  // Hight of the sharpend pencil cylindrical connector bit
usp_h = 10 * r_p_s;  // Hight of the unsharpend pencil connector bit

bp_s_h = r_bp_s / tan(tip_a/2);

// Hight of the sleeve at the bottom (as printed) of the "big pencil"
// connector. The length of a pencil tip. (The angle of the outer wall
// should come out slightly below tip_a, as the walls get thinner. We
// (try to) maintain cross section area, not wall strength.)
// Here we need the angle from one side to the center line.

mh = cone_h + joint_h + l_n;

r_h_w = handle_w - 2*handle_cr;
e_h_l = handle_l - 2*handle_cr + r_r+w;
e_h_l_2 = handle_l + r_r+w;

some_distance = 2 * (r_r + w) + 13 * w;

// fn for differently sized objects and fs, fa; all for preview or rendering.
pna = 40;
pnb = 15;
pa = 5;
ps = 1;
rna = 180;
rnb = 30;
ra = 2;
rs = 0.25;
function na() = (preview) ? pna : rna;
function nb() = (preview) ? pnb : rnb;
$fs = (preview) ? ps : rs;
$fa = (preview) ? pa : ra;

// *******************************************************
// End setup

// *******************************************************
// Generate the parts

if (part == "p")
{
   preview_parts();
}

if (part == "s")
{
   stack_parts();
}

if (part == "t")
{
   if (0 == stand_style)
   {
      intersection()
      {
         %solid_funnel(w+strake_w(),0);
         #funnel_strakes();

      }
   }
   // solid_funnel(w,0);
}

print_part();

// I used this cylinder as a modifier to set the infill to higher
// values under the central bit of the portioner sphere, to get less
// sag.
// cylinder(r=0.7*r, h=r,$fn=na());

module print_part()
{
   if (part == "funnel")
   {
      funnel();
   }
   if (part == "stand" && stand_style)
   {
      stand();
   }
}

module preview_parts()
{
   funnel();
   translate([some_distance, 0, 0])
   {
      if (stand_style)
      {
         stand();
      }
   }
}

module stack_parts()
{
   // intersection()
   {
      color("yellow")
      {
         stand();
      }
      rotate([0,180,0])
      {
         translate([0, 0, -3*l_n])
         {
            color("red")
            {
               funnel();
            }
         }
      }
   }
}

// *******************************************************
// Code for the parts themselves

// And the definitions
module funnel()
{

   // The funnel proper
   difference()
   {
      outer_funnel();
      solid_funnel(0,ms);
      funnel_neck_cutoff();

   }

   //
   difference()
   {
      union()
      {
         if (stand_style > 0)
         {
            for (sr=[0:120:(stand_style-1)*120])
            {
               rotate(sr)
               {
                  funnel_support();
               }
            }
         }
         else
         {
            funnel_grip();

         }
      }
      solid_funnel(w/2, 2*ms);
      funnel_neck_cutoff();
   }
}

module outer_funnel()
{
   if (0 == stand_style)
   {
      intersection()
      {
         solid_funnel(w+strake_w(),-ms);
         funnel_strakes();

      }
   }
   solid_funnel(w,0);
}

module funnel_strakes()
{
   for (i=[0, 120, 240])
   {
      rotate(i+30)
      {
         translate([0,-strake_w()/2,-2*ms])
         {
            cube([r_r+w+strake_w()+3*ms, strake_w(), mh+l_n+4*ms]);
         }
      }
   }
}

module funnel_grip()
{
   translate([-r_h_w/2, handle_cr, 0])
   {
      minkowski()
      {
         cube([r_h_w, e_h_l, w]);
         cylinder(r=handle_cr, h=ms);
      }
   }

   rotate([0, -90, 0])
   {
      translate([0,0,-handle_br/2])
      {
         linear_extrude(handle_br)
         {
            polygon(
               [[w,0],
                [ch*handle_handle_ch_ratio+w,0],
                [w, e_h_l_2]]);
         }
      }
   }
   translate([0,0,w])
   {
      translate([0,e_h_l_2-handle_br])
      {
         rotate([0,90,0])
         {
            cylinder(h=r_h_w, r=handle_br, center=true);
         }
      }
      side_cylinder();
      mirror([1, 0, 0])
      {
         side_cylinder();
      }

   }
}

module side_cylinder()
{
   translate([handle_w/2-handle_br,0,0])
   {
      rotate([-90,0,0])
      {
         cylinder(h=e_h_l+handle_cr, r=handle_br);
      }
      translate([handle_br-handle_cr, e_h_l_2-handle_cr, 0])
      {
         difference()
         {
            rotate_extrude()
            {
               translate([handle_cr-handle_br,0])
               {
                  circle(r=handle_br);
               }
            }
            translate([-handle_cr,-2*handle_cr,0])
            {
               cube([2*handle_cr,2*handle_cr, handle_br]);
            }
            translate([-2*handle_cr,-handle_cr,0])
            {
               cube([2*handle_cr,2*handle_cr, handle_br]);
            }
         }
      }
   }

}

module solid_funnel(x_o, z_o)
{
   // New style. Stack a cone, a negative ring segment and a cylinder.
   //             cone_x = r_r - r_n - joint_x;
   r_j_c = r_n+joint_x + x_o;
   translate([0,0, -z_o])
   {
      cylinder(r1=r_r+x_o, r2=r_j_c, h=cone_h+z_o, $fn=na());
   }
   rotate_extrude(convexity=6, $fn=na())
   {
      translate([0, cone_h-ms])
      {
         difference()
         {
            square([r_j_c+r_j-ms, joint_h+ms]);
            translate([r_n+x_o+r_j, joint_h])
            {
               circle(r_j, $fn=na());
            }
         }
      }
   }
   translate([0, 0, cone_h+joint_h-ms])
   {
      cylinder(r=r_n+x_o, h=l_n+nth+z_o, $fn=na());
   }
}

module funnel_neck_cutoff()
{
   // The bit that creates the slant an the neck
   rotate([0,0,tip_rotation])
   {
      translate([-ond/2-ms, -ond/2-ms, mh])
      {
         rotate(a=neck_tip_angle, v=[1, 0, 0])
         {
            cube([ond+2*ms, ncl, ncl]);
         }
      }
   }
}

module funnel_support()
{

   // The holder lug. The Poly is bigger than needed, and we
   // subtract a bit later.
   lp_poly = [
      [r_bp_s,r_r + w - 3*w],
      [r_bp_s,r_r + w + 1.5 * r_bp_l],
      [0,r_r + w + 2*r_bp_l],
      [-r_bp_s,r_r + w + 1.5 * r_bp_l],
      [-r_bp_s,r_r + w - 3*w],
      ];
   linear_extrude(w)
   {
      // Holder plate
      polygon(lp_poly);
   }
   // Extra support
   translate([-es_w/2,r_n+w+0.5*(r_bp_l-r_p_l),0])
   {
      cube([es_w,r_r-r_n, es_h]);
   }
   bp_s_f = (r_bp_l) / r_p_l;
   translate([0,r_r+w+r_bp_l, w])
   {
      rotate(30)
      {
         difference()
         {
            cylinder(h=usp_h+heh, r=r_bp_l, $fn=6);
            // Hollow it out
            translate([0,0, heh])
            {
               cylinder(h=usp_h+1,r=r_p_l, $fn=6);
            }
         }
      }
   }

}

module stand()
{
   // hex_r = r_r + w + r_p_s;
   hex_r = r_r+ w + r_bp_l;
//   hex_r_l = hex_r_s * 2 / 3 * sqrt(3);
   difference()
   {
      union()
      {
         // Construct the stand ring symmetric to xy plane ...
         rotate(30)
         {
            rotate_extrude($fn=6)
            {
               translate([hex_r, 0, 0])
               {
                  circle(r=r_p_l, $fn=6);
               }
            }
         }
      }
      // ... and subtract everything below the xy plane.
      translate([0,0,-l_n])
      {
         cylinder(r=1.5*r_r, h=l_n);
      }
   }
   // Fill the base plate
   rotate(30)
   {
      cylinder(r=hex_r, h=es_w, $fn=6);
   }
   for (sr=[0:120:(stand_style-1)*120])
   {
      rotate(sr)
      {
         translate([0, hex_r, 0])
         {
            // The stand.

            rotate(30)
            {
               difference()
               {
                  union()
                  {
                     // Three segments of linear extrusion
                     // The extra
                     cylinder(h=heh, r=r_p_l,$fn=6);
                     // The sharpend bit
                     translate([0, 0 , heh])
                     {
                        cylinder(h=bp_s_h, r1=r_p_l, r2=r_bp_l,$fn=6);
                     }
                     // The shaft bit
                     translate([0, 0 , bp_s_h+heh])
                     {
                        cylinder(h=sp_h, r=r_bp_l, $fn=6);
                     }
                     // The extra plates. Should not interfere with the stand
                     // without this. Anyway.
                     rotate(-60)
                     {
                        extra_plate();
                     }
                     rotate(180)
                     {
                        extra_plate();
                     }
                  }
                  // Hollow it out
                  translate([0, 0 , heh])
                  {
                     cylinder(h=bp_s_h, r1=0, r2=r_p_l, $fn=6);
                  }
                  translate([0, 0 , bp_s_h+heh])
                  {
                     cylinder(h=sp_h+1,r=r_p_l, $fn=6);
                  }

               }
            }
         }

      }
   }

   module extra_plate()
   {
      es_s_p = [
         [0, 0],
         [es_h + r_p_l/2, 0],
         [0, es_h + r_p_l/2]
         ];
      rotate([90, 0, 0])
      {
         translate([r_p_s, 0.5*r_p_l, 0])
         {
            linear_extrude(es_w)
            {
               polygon(es_s_p);
            }
         }
      }
   }
}

```


[User]
Hi, I'm looking to design a parametric OpenSCAD module support frame. I've been using something I found online ([http://www.thingiverse.com/thing:2147533](http://www.thingiverse.com/thing:2147533)) as a starting point, but I'd really like to be able to customize it better.

The basic idea is a frame that can hold either 1, 2, or 3 modules (think like electrical switch modules). These modules, and the covers that go over them, are from CBE, but I'd like to make this customizable so it could be adapted to other things.

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

*   **Number of Modules:** I want to be able to easily switch between 1, 2, or 3 module bays.
*   **Module Cutouts:** I need to be able to toggle whether the frame has the cutouts for the module panels or if it's just a blank frame. This means the inner walls that the modules sit within need to exist and be a configurable option.
*   **Overall Dimensions:** The frame should have a height and width that can be specified. There are rounded corners and curved ends that should be configurable.
*   **Thickness:** I need to be able to adjust the overall thickness/depth of the frame, along with the wall thickness of the frame.
*   **Mounting Holes:** The frame should have mounting holes that can be adjusted for size, inset distance from the edge, and also the diameter and inset distance for the bolt head cutout for flush mounting.
*   **Module Locators:** There should be an option for small pegs and support blocks within the module cutouts to help locate/secure the modules. These also need to be customizable for size and depth.
*   **Clip Locations:** There needs to be "clip" features at the top and bottom of the frame to help secure it into place when mounted. These clips need to be configurable, in terms of their height, depth and width, as well as how far they protrude. The additional material behind each clip (as seen in the referenced Thingiverse project) also needs to be configurable in depth and width. These clips also need to be correctly spaced when using 2 or 3 module frames.
*   **End Locators:** Small rectangular teeth on either end to interlock frames. These need to be configurable in height, width and outset.
*   **Rendering Resolution:** The resolution of the curved surfaces should be adjustable, so I can control the smoothness and speed of the renders. I will want to render at lower resolution while designing.

I don't need to use any specific libraries, so whatever you think is the best approach for this would be great. I'd like the final design to be parametric and easily adjustable for future use.

To be clear, I want the design to be from scratch, I'm not expecting you to just modify the existing design.

[Assistant]
module_support_frame.scad:
```
/*
 * module_support_frame.scad
 * v1.0.1 6th April 2017
 * Written by landie @ Thingiverse (Dave White)
 *
 * This script is licensed under the Creative Commons - Attribution - Non-Commercial license.
 *
 * http://www.thingiverse.com/thing:2147533
 *
 * This library/project allows you to create 1, 2 and 3 module size support frames,
 * suitable for use with CBE covers and CBE and C-Link modules.
 * This can then be customised to support your own modules or printed
 * in whatever colour suits your installation. The standard CBE covers fit over the module
 * and optionally it can take CBE and/or C-Link modules.
 * Please read the hull_layer_thickness instructions CAREFULLY !
 */

// Variables you may want to adjust...

// The size of the frame when used standalone
numberOfCells = 1; //[1:Single,2:Double,3:Triple]

// Should cutout module panels
cutoutModules = 1; // [1:Yes,0:No]

// This should probably be set to the layer height you intend to print with or higher. The stepping effect is
// completely hidden by the CBE cover so  it's up to you really how long you want to wait for it to render !
// Setting it to a high value (2, for example) will make rendering MUCH quicker but reduce quality
// Setting it to a finer value than your printer layer height will make it look prettier on screen but take a LONG time to render
// 1 is a good compromise while working on your design in OpenScad, 0.2 is good for printing to give a pleasing finish
// even if nobody ever sees it !
// Smaller value gives a smoother finish but will take much longer to process, not visible when the frame is on.
hull_layer_thickness = 1; //[0.1,0.2,0.4,0.6,0.8,1]

/* [Hidden] */

// The following variables probably won't need adjusting unless you want to adapt the frame for use with a different vendor's cover.
corner_rad = 8; // the radious of the outer corner of the frame
end_rad = 200; // the curve used at each end of the frame
thickness = 8; // the overall depth of the frame

end_thickness = 13; // used to calculate the overall width of the frame, added on to the cell width for each end
end_corner_inset = 10; // how far in form the end of the frame do the outer corner centres sit

hull_wall_thickness = 3; // the wall thickness of the basic frame

cell_width = 54; // the total width allowed for each module
cell_height = 54; // the total height allowed for each module

height = 72; // The overall height of the frame base

cell_corner_radius = 8; // The corner radius for the module cutouts
cell_plate_width = 47; // the width of the module cutouts
cell_plate_height = 47; // the height of the module cutouts

mounting_bolt_support_dia = 7; // the diameter of the tube that the mounting screws/bolts go through
mounting_bolt_dia = 3.5; // the diameter of the hole for the screws to go through
mounting_bolt_cutout_dia = 6; // the diameter of the cutout for the screw head
mounting_hole_inset = 4; // How far in from the edge should the screw centre be ?

end_locator_spacing = 50; // the space between centres of the two round cutouts at each end of the frame

// These are all used for the creation of the clips that are cut into the top/bottom of the frame
clip_cutout_top = 20; // The width of the main cutout at the top of the frame
clip_cutout_bottom = 16; // The width of the main cutout at the bottom of the frame
clip_cutout_depth = 4; // The depth of the main cutout in the frame
clip_extra_bottom = 12.5; // The bottom width of the additional cutout below the clip "tooth"
clip_extra_top = 10; // The top width of the additional cutout below the clip "tooth"
clip_extra_height = 6; // The height of the additional cutout for the clip "tooth"
clip_extra_depth = 1; // The depth of the additional cutout for the clip "tooth"
clip_backing_width = 22; // the width of the additional material that is added to form the back of the clip
clip_backing_depth = 7; // the depth of the additional material that is added to form the back of the clip
clip_tooth_width = 10; // The width of the tooth in the clip
clip_tooth_depth = 2; // The thickness of teh tooth
clip_tooth_inset = 2; // The gap between the edge of the tooth and teh edge of the frame
clip_tooth_height = 4; // The hight of the bottom of the tooth

// these variables are used to build the small locating rectangles that stick out at each end of the frame
location_tooth_width = 9; // the width of the rectangle
location_tooth_height = 1.5; // the height of the rectangle
location_tooth_outset = 3; // How far out the rectangle sticks from the frame

// These variables are used to create the locating pegs and supports for the modules
module_location_distance = 46; // The spacing between each peg position
module_location_peg_dia = 3; // The diameter of the actual peg
module_location_peg_depth = 4; // How hight the peg sticks above the face
module_location_standoff_depth = 1; // The height of the small rectangular supports
module_location_standoff_width = 1; // The width of the small rectangular supports
module_location_standoff_height = 7; // The length of the small rectangular supports

// These variables are used to space out the clips when more than one per side are used
clip_spacing_3 = 107; // Spacing between the clips when there are 3 module positions
clip_spacing_2 = 78; // Spacing between the clips when there are 2 module positions

// the default smoothing for round surfaces
$fn = 65;

// You should really comment this out if using as an included library
// you can then call it from your source code
// This call creates the frame with the first parameter being the number of cells (1,2 or 3)
// The second parameter (true or false) determines if the frame should include module positions or just be blank
support_frame(numberOfCells, (cutoutModules == 1));

module support_frame(cells = numberOfCells, with_modules = (cutoutModules == 1), hull_resolution = hull_layer_thickness) {
    if (cells > 0 && cells < 4) {
        difference() {
            union() {
                basic_hull(cells, hull_resolution);
                addons(cells);
                if (with_modules)
                    cell_addons(cells);
            }
            cutouts(cells);
            if (with_modules)
                cell_cutouts(cells);
        }
    }
}

module basic_hull(cells = 1, hull_resolution = hull_layer_thickness) {
    //render() {
        difference() {
            basic_3d_hull(cells, hull_resolution, 0);
            basic_3d_hull(cells, hull_resolution, hull_wall_thickness, hull_wall_thickness);
            plate_cutout(cells);
        }
    //}
}

module basic_3d_hull(cells = 1, step = 1, extra_inset = 0, cap = 0) {
    //hull() {
        for (x = [0:step:thickness - step - cap]) {
            if (x == 0) {
                linear_extrude(step) basic_2d_hull(cells, extra_inset);
            } else if (x == thickness - step) {
                translate([0,0,thickness - step])
                linear_extrude(step) basic_2d_hull(cells, thickness / 2 + extra_inset);
            } else {
                angle = (90 / thickness) * x;
                hypotenuse = thickness;
                inset = (thickness - (hypotenuse * cos(angle))) / 2;
                translate([0,0,x])
                linear_extrude(step) basic_2d_hull(cells, inset + extra_inset);
            }
        }
    //}
}

module basic_2d_hull(cells = 2, inset = 0) {
    $fn = 100;
    corner_centre_width = cell_width * cells + (end_thickness - end_corner_inset) * 2;
    corner_centre_height = height - corner_rad * 2;
    overall_width = cell_width * cells + end_thickness * 2;
    offset(delta = inset * -1)
    hull() {
        translate([-corner_centre_width / 2, corner_centre_height / 2]) circle(r = corner_rad, center = true);
        translate([corner_centre_width / 2, corner_centre_height / 2]) circle(r = corner_rad, center = true);
        translate([-corner_centre_width / 2, -corner_centre_height / 2]) circle(r = corner_rad, center = true);
        translate([corner_centre_width / 2, -corner_centre_height / 2]) circle(r = corner_rad, center = true);
        intersection() {
            color("red") translate([end_rad - overall_width / 2,0,0]) circle(r = end_rad, centre = true);
            color("blue") translate([-end_rad + overall_width / 2,0,0]) circle(r = end_rad, centre = true);
            translate([0,0,0])square([overall_width + 50, height - corner_rad * 2], center = true);
        }
    }
}

module plate_cutout(depth = thickness + 1, offset = 0) {
    $fn = 100;
    actual_plate_width = cell_plate_width + offset;
    actual_plate_height = cell_plate_height + offset;
    hull() {
        translate([actual_plate_width / 2 - cell_corner_radius, actual_plate_height / 2 - cell_corner_radius, depth / 2])
            cylinder(r = cell_corner_radius, h = depth, center = true);
        translate([-actual_plate_width / 2 + cell_corner_radius, actual_plate_height / 2 - cell_corner_radius, depth / 2])
            cylinder(r = cell_corner_radius, h = depth, center = true);
        translate([actual_plate_width / 2 - cell_corner_radius, -actual_plate_height / 2 + cell_corner_radius, depth / 2])
            cylinder(r = cell_corner_radius, h = depth, center = true);
        translate([-actual_plate_width / 2 + cell_corner_radius, -actual_plate_height / 2 + cell_corner_radius, depth / 2])
            cylinder(r = cell_corner_radius, h = depth, center = true);
    }
}

module location_pegs() {
    translate([module_location_distance / 2, module_location_distance / 2, thickness - hull_wall_thickness])
        location_peg();
    translate([-module_location_distance / 2, -module_location_distance / 2, thickness - hull_wall_thickness])
        location_peg();
    translate([-module_location_distance / 2, module_location_distance / 2, thickness - hull_wall_thickness])
        rotate([0,0,90])
        location_peg(false);
    translate([module_location_distance / 2, -module_location_distance / 2, thickness - hull_wall_thickness])
        rotate([0,0,90])
        location_peg(false);
}

module location_peg(with_peg = true) {
    if (with_peg) {
        translate([0,0,-module_location_peg_depth / 2])
        cylinder(d = module_location_peg_dia, h = module_location_peg_depth, center = true);
    }
    rotate([0,0,45])
    translate([0,0,-module_location_standoff_depth / 2])
    cube([module_location_standoff_width, module_location_standoff_height, module_location_standoff_depth], center = true);
}

module clip_cutout() {
    difference() {
        union() {
            // larger cutout
            hull() {
                translate([-clip_cutout_bottom / 2, -clip_cutout_depth, 0])
                cube([clip_cutout_bottom, clip_cutout_depth, 0.1], center = false);
                translate([-clip_cutout_top / 2, -clip_cutout_depth, thickness])
                cube([clip_cutout_top, clip_cutout_depth, 0.1], center = false);
            }
            // smaller cutout for clip
            hull() {
                translate([-clip_extra_bottom / 2, -clip_cutout_depth - clip_extra_depth, 0])
                cube([clip_extra_bottom, clip_cutout_depth, 0.1], center = false);
                translate([-clip_extra_top / 2, -clip_cutout_depth - clip_extra_depth, clip_extra_height])
                cube([clip_extra_top, clip_cutout_depth, 0.1], center = false);
            }
        }
        // clip peg
        hull() {
            peg_height = clip_cutout_depth + clip_extra_depth - clip_tooth_inset;
            translate([0,-peg_height/2 - clip_tooth_inset,0.05 + clip_tooth_height]) cube([clip_tooth_width, peg_height, 0.1], center = true);
            translate([0,-peg_height/2 - clip_tooth_inset * 2,0.05 + clip_tooth_height + clip_tooth_depth]) cube([clip_tooth_width, peg_height - clip_tooth_inset, 0.1], center = true);
        }
    }
}

module addons(cells = 1) {
    overall_width = cell_width * cells + end_thickness * 2;
    intersection() {
        basic_3d_hull(cells, hull_layer_thickness, 0);
        translate([overall_width / 2 - mounting_hole_inset, 0, thickness / 2])
            cylinder(d = mounting_bolt_support_dia, h = thickness, center = true);
    }
    intersection() {
        basic_3d_hull(cells, hull_layer_thickness, 0);
        translate([-overall_width / 2 + mounting_hole_inset, 0, thickness / 2])
            cylinder(d = mounting_bolt_support_dia, h = thickness, center = true);
    }

    if (cells == 3) {
        // add supports for center screw mounts
        intersection() {
            basic_3d_hull(cells, hull_layer_thickness, 0);
            translate([0, height / 2 - mounting_hole_inset, thickness / 2])
                cylinder(d = mounting_bolt_support_dia, h = thickness, center = true);
        }
        intersection() {
            basic_3d_hull(cells, hull_layer_thickness, 0);
            translate([0, -height / 2 + mounting_hole_inset, thickness / 2])
                cylinder(d = mounting_bolt_support_dia, h = thickness, center = true);
        }
    }

    clip_addons(cells);

    // location pegs
    translate([location_tooth_outset / 2 + overall_width / 2 - 1, 0, location_tooth_height / 2])
    cube([location_tooth_outset, location_tooth_width, location_tooth_height], center = true);
    translate([-location_tooth_outset / 2 - overall_width / 2 + 1, 0, location_tooth_height / 2])
    cube([location_tooth_outset, location_tooth_width, location_tooth_height], center = true);
}

module cutouts(cells = 1) {
    overall_width = cell_width * cells + end_thickness * 2;
    translate([overall_width / 2 - mounting_hole_inset, 0, thickness / 2])
            cylinder(d = mounting_bolt_dia, h = thickness, center = true);
    translate([-overall_width / 2 + mounting_hole_inset, 0, thickness / 2])
            cylinder(d = mounting_bolt_dia, h = thickness, center = true);

    translate([overall_width / 2 - mounting_hole_inset, 0, thickness / 2 + 3])
            cylinder(d = mounting_bolt_cutout_dia, h = thickness, center = true);
    translate([-overall_width / 2 + mounting_hole_inset, 0, thickness / 2 + 3])
            cylinder(d = mounting_bolt_cutout_dia, h = thickness, center = true);

   if (cells == 3) {
    // cutouts for center screw mounts
        translate([0, height / 2 - mounting_hole_inset, thickness / 2])
            cylinder(d = mounting_bolt_dia, h = thickness, center = true);
        translate([0, -height / 2 + mounting_hole_inset, thickness / 2])
            cylinder(d = mounting_bolt_dia, h = thickness, center = true);

       translate([0, height / 2 - mounting_hole_inset, thickness / 2 + 3])
            cylinder(d = mounting_bolt_cutout_dia, h = thickness, center = true);
        translate([0, -height / 2 + mounting_hole_inset, thickness / 2 + 3])
            cylinder(d = mounting_bolt_cutout_dia, h = thickness, center = true);
    }

    // small end cutouts
    translate([overall_width / 2, end_locator_spacing / 2, thickness / 2])
            cylinder(d = mounting_bolt_cutout_dia, h = thickness, center = true);
    translate([-overall_width / 2, -end_locator_spacing / 2, thickness / 2])
            cylinder(d = mounting_bolt_cutout_dia, h = thickness, center = true);
    translate([overall_width / 2, -end_locator_spacing / 2, thickness / 2])
            cylinder(d = mounting_bolt_cutout_dia, h = thickness, center = true);
    translate([-overall_width / 2, end_locator_spacing / 2, thickness / 2])
            cylinder(d = mounting_bolt_cutout_dia, h = thickness, center = true);

    clip_cutouts(cells);
}

module cell_cutouts(cells = 1) {
    if (cells == 1)
        plate_cutout();
    else if (cells == 2) {
        translate([cell_width / 2,0,0])
            plate_cutout();
        translate([-cell_width / 2,0,0])
            plate_cutout();
    } else if (cells == 3) {
        plate_cutout();
        translate([cell_width,0,0])
            plate_cutout();
        translate([-cell_width,0,0])
            plate_cutout();
    }
}

module cell_addons(cells = 1) {
    if (cells == 1)
        location_pegs();
    else if (cells == 2) {
        translate([cell_width / 2,0,0])
            location_pegs();
        translate([-cell_width / 2,0,0])
            location_pegs();
    } else if (cells == 3) {
        location_pegs();
        translate([cell_width,0,0])
            location_pegs();
        translate([-cell_width,0,0])
            location_pegs();
    }
}

module clip_addons(cells = 1) {
    if (cells == 1) {
        clip_addon_pair(cells);
    } else if (cells == 2) {
        translate([clip_spacing_2 / 2, 0, 0])
            clip_addon_pair(cells);
        translate([-clip_spacing_2 / 2, 0, 0])
            clip_addon_pair(cells);
    } else if (cells == 3) {
        translate([clip_spacing_3 / 2, 0, 0])
            clip_addon_pair(cells);
        translate([-clip_spacing_3 / 2, 0, 0])
            clip_addon_pair(cells);
    }
}

module clip_addon_pair(cells) {
    // clip hole backings
    intersection() {
        basic_3d_hull(cells, hull_layer_thickness, 0);
        translate([0, height / 2 - clip_backing_depth / 2, thickness / 2])
            cube([clip_backing_width,clip_backing_depth,thickness], center = true);
    }
    intersection() {
        basic_3d_hull(cells, hull_layer_thickness, 0);
        translate([0, -height / 2 + clip_backing_depth / 2, thickness / 2])
            cube([clip_backing_width,clip_backing_depth,thickness], center = true);
    }
}

module clip_cutouts(cells = 1) {
    if (cells == 1) {
        clip_pair();
    } else if (cells == 2) {
        translate([clip_spacing_2 / 2, 0, 0])
            clip_pair();
        translate([-clip_spacing_2 / 2, 0, 0])
            clip_pair();
    } else if (cells == 3) {
        translate([clip_spacing_3 / 2, 0, 0])
            clip_pair();
        translate([-clip_spacing_3 / 2, 0, 0])
            clip_pair();
    }
}

module clip_pair() {
    // clip cutouts/formers
    translate([0,height /2, 0])
    clip_cutout();
    translate([0,-height /2, 0])
    rotate([0,0,180])
    clip_cutout();
}
```


[User]
Hi, I'm looking to design some laser-cut cardboard planters, and I'd like to use OpenSCAD to generate the design. I have a basic idea of the shape and some parameters I want to control.

Essentially, the planter will be a flat piece of cardboard cut in a cross shape with tabs, that can then be folded up into a box. The dimensions I'd like to control are:

*   **Width:** The width of the main body of the planter (the square section).
*   **Height:** The height of the sides of the planter.
*   **Cardboard Thickness:** The thickness of the cardboard I'll be using.
*   **Tab Height Factor:**  A factor that determines how far up the side the tab starts. 0 would be at the bottom, 1 would be at the top.
*   **Tab Width Factor:**  A factor controlling the width of the tab as a portion of the height.
*  **Tab Width Tolerance**: A factor that determines how much larger or smaller the slot for the tab is, compared to the thickness of the cardboard.
* **Tab Height Tolerance**: A factor that determines how much taller or shorter the slot for the tab is compared to the height of the tab.
*   **Tab Angle:** The angle of the tab's sides relative to the edge it's attached to. I'd like to be able to adjust this to fine-tune the fit.

I'd also like to have the following features:

*   **Automatic Scaling:** The design should be scaled down if the requested dimensions are too large for a specified laser bed size (both width and length). I need to be able to specify the maximum X and Y dimensions of the laser bed.
*   **Clearance Cuts:** There should be cuts on the front and back sides that allow the left and right sides to fold up flush, so the corners do not collide.
*   **Guides:**  I'd like a debug mode to be available, so that I can visualize the overall size, tab locations and cutouts, with options to show/hide the guide lines. This would be useful for testing and fine-tuning before generating the final design. It would be great if the debug mode also displayed the sizes of the cutouts relative to the tabs (using some kind of bounding box visualization). It should also indicate where score lines should go (lines that cut through the top, and coregation, but not bottom layers).

I’m picturing that the design would have a central square for the base of the planter and the four side panels extending from this square. I need tabs on the sides that will lock into slots on the panels at right angles to them.

Ideally, the OpenSCAD file would have clearly defined modules for each of the major parts (base, tabs, cutouts, clearance cuts, debug lines, etc.). I'd really like it to have a function for doing floating-point rounding as well, and display as either a float, or a fraction.

Do you think you could generate this for me? I'd prefer to have it as a single .scad file instead of splitting it up into multiple files, if that's okay. I'm not using any specialized libraries at the moment.

[Assistant]
Cardboard_Planters.scad:
```
//Cardboard planters
//Ari M Diacou
//Feb 2017

//These are planters designed to be cut from carboard on a laser cutter. You then fold up ithe sides, and put soil in them. Up to you if you want to transplant them without removing the cardboard first.

////////////////// Parameters //////////////////////
////////////// User Parameters /////////////////////
//The maximum x dimension of your laser's bed (left-right)
maximum_bed_x=240;
//The maximum y dimension of your laser's bed (towards-away from user)
maximum_bed_y=240;
//debugging feature, turn off before full render/export
user_wants_guides=true; //[true,false]
//Width of the planter in mm, will be scaled if it causes a stupid design.
user_width=50;
//Height of the planter in mm, will be scaled if it causes a stupid design.
user_height=100;
//How high up the sides do you want the tabs?
tab_height_factor=.7;
//The tabs have a width that is this factor of the height
tab_width_factor=.2;
//The thickness in mm of the cardboard you are cutting
thickness=5;
//The tab cutouts will be this factor deeper than the thickness of the cardboard (recommend 0 to 1)
tab_width_tolerance=.2;
//The tab cutouts will be this factor shorter of the thickness of the cardboard (recommend -1 to 1)
tab_height_tolerance=.0;
//The angle that the sides of the tab make with the edges of the side the tab is on (90 is perpendicular, 0 is infinite)
tab_angle=75;
//////////// Derived Parameters ////////////////////
ep=0+0.05; //Epsilon, a small value
max_size=.9*min(maximum_bed_x,maximum_bed_y);
scale_factor=max_size/(user_width+2*user_height); echo(scale_factor);
user_dimensions_are_too_big=scale_factor < 1.0 ? true : false;
width=user_dimensions_are_too_big ? user_width*scale_factor : user_width;
height=user_dimensions_are_too_big ? user_height*scale_factor : user_height;
size=user_dimensions_are_too_big ? max_size : user_width+2*user_height;
tab_dimension_range=[tab_width_factor*height,2*thickness*cos(tab_angle)];//[min,max]
/////////////////// Main() /////////////////////////
// user_wants_guides=true;
if(user_wants_guides){ %debug(); }
print_info();
difference(){
    base();
    foldup_clearance();
    tab_cutout1();
    }
tabs(tab_angle);

///////////////// Functions ////////////////////////
module tab_cutout1(){
    double_mirror(.5*width*[1,1]+[thickness+ep,height*(tab_height_factor-tab_width_factor/2)])
        mirror([1,0])
            square([(1+tab_width_tolerance)*thickness,tab_width_factor*height+thickness*tab_height_tolerance]);
    }

module test_roundto(input,precision=1,fraction=false){
    ans=roundto(input, precision);
    int=roundto(input, 1);
    numerator=(ans-int)/precision;
    denominator=1/precision;
    if(!fraction)
        echo(str(input, ", rounded to the nearest ",precision," is ",ans));
    else
        echo(str(input, ", rounded to the nearest ",precision ," is ",int," ",numerator,"/",denominator));
    }

function roundto(input, precision)=round(pow(10,-log(precision))*input)*pow(10,log(precision));

module tabs(angle=75){
    double_mirror([width/2+tab_height_factor*height,width/2-ep]) tab1(angle=angle);
    }
module tab1(angle=75){
    w=tab_width_factor*height;
    A=[w/2,0];
    D=[-A[0],0];
    B=A+2*thickness*[cos(angle),1];
    C=[-B[0],B[1]];
    polygon([A,B,C,D]);
    }

module base(){
    difference(){
        square(size,center=true);
        double_mirror(dist=[size/2-height+ep+thickness,size/2-height+ep]) color("blue") square([height-thickness,height]);
        }
    }
module foldup_clearance(){
    //cuts in the front/back sides which allow the left right sides to fold up flush with the front/back sides
   double_mirror(dist=[width/2+ep,size/2-height+ep]) color("yellow") line(1.0*thickness);
    }
module line(l=1){
    //a way to create a "line" from a "square()" object
    line_thickness = user_wants_guides ? min(0.006*size, 0.25*l) : ep;
    square([l,line_thickness]);
    }
module double_mirror(dist=0){
    //Flips an object over the x, y and x+y axes to create 4 objects centered about the origin. I.e. what you would see if you placed an object between two orthogonal mirrors.
    //Can be used with a vector or scalar distance from the origin to move the object (i.e. translate() is built into the function).
    if(len(dist)>1){
        mirror([0,0]) translate(dist) children(0);
        mirror([1,0]) translate(dist) children(0);
        mirror([0,1]){
            mirror([0,0]) translate(dist) children(0);
            mirror([1,0]) translate(dist) children(0);
            }
        }
    else{
        mirror([0,0]) translate(dist*[1,1]) children(0);
        mirror([1,0]) translate(dist*[1,1]) children(0);
        mirror([1,0]) mirror([0,1]) translate(dist*[1,1]) children(0);
        mirror([0,1]) translate(dist*[1,1]) children(0);
        }
    }
module cutout(){
    //The base() function is a square, with 4 squares cut out at the corners to form a cross. These are those squares.
    shift=size/2+ep-height;
    translate(shift*[1,1]) square(height);
    }
module debug(){
    //Shows a square of "size", as well as some guide lines, and tab/hole relative sizes. The % used in calling this function keeps it from being displaed on F6/render.

    //Outside square
    square(size,center=true);
    //the distance of the center of the tabs
    center=width/2+tab_height_factor*height;
    //lines||x-axis
    #double_mirror(center*[0,1])
        line(l=size/2);
    //lines || y-axis
    #double_mirror(center*[1,0])
        rotate(90) line(l=size/2);
    //mapping of cutouts onto tabs
    double_mirror([center-tab_dimension_range[0]/2,width/2-ep])    #square([tab_width_factor*height+thickness*tab_height_tolerance,(1+tab_width_tolerance)*thickness]);
    //score lines (cut through, top, and coregation, but not bottom layer
    #double_mirror([width/2,0]) mirror([1,1])
        line(width/2);
    msg="←score here";
    //score here message,
    translate([width/2,0])
        #text(msg,size=len(msg)*tab_height_factor*.9);
    double_mirror(.5*width*[1,1]+[thickness+ep,height*(tab_height_factor-tab_width_factor/2)-tab_dimension_range[1]/2])
        mirror([1,0])
            #square([thickness,tab_dimension_range[0]+tab_dimension_range[1]]);

    }

module print_info(){
    size=str("Max size based on laser bed = ", max_size, ", h=",height,"mm, w=",width,"mm.");
    too_big_warning=str("You have entered dimensions (h=",user_height,",w=",user_width,")which are larger (h+2*w=",max_size/scale_factor,"mm) than 90% of your laser cutter's minimum dimenstion (",max_size,"mm). Your dimensions have been scaled to fit by -",100*(1-scale_factor),"%.");
    echo(size);
    if(user_dimensions_are_too_big)
        echo(too_big_warning);

    }
```


[User]
I'm looking to design a cyclone dust separator for my woodworking shop, similar to the one I saw on Thingiverse, but I want to generate the design in OpenSCAD. I need it to be parametric so I can easily adjust it for different hose sizes and other dimensions.

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

*   **vacuum\_hole\_diameter**: The diameter of the exhaust hole at the top where the vacuum hose connects (in mm).
*   **intake\_hole\_diameter**: The diameter of the intake hole where the dust-laden air enters (in mm).
*   **body\_height**: The height of the main cylindrical body (in mm).
*   **cone\_height**: The height of the conical cyclone chamber (in mm).
*   **hopper\_diameter**: The diameter of the hole at the bottom where the debris collector (hopper) attaches (in mm).
*   **wall\_thickness**: The thickness of all the walls of the design (in mm).
*   **mount\_plate\_space\_radius**: The small gap between the hopper connection and the mount plate to account for printer tolerances (in mm).
*   **bolt\_diameter**: The diameter of the mounting bolts (in mm).
*   **bolt\_length**: The length of the mounting bolts (in mm).
*   **bolt\_head\_diameter**: The diameter of the bolt heads (in mm).
*   **bolt\_head\_height**: The height of the bolt heads (in mm).
*   **mount\_plate\_ring\_radius**: The radius of the ring of the mount plate (in mm).
*   **debris\_container\_cover\_thickness**: The thickness of the debris container cover, which the mount plate will rest on (in mm).

I'd like the design to consist of the following parts:

1.  **Cyclone Cone**: The conical section where separation occurs. It should have a smooth transition between the hopper diameter and the body diameter.
2.  **Cyclone Body**: The cylindrical part above the cone that houses the outlet for the vacuum.
3.  **Cyclone Intake**: The inlet pipe that attaches to the side of the body, guiding incoming dust-laden air.
4.  **Hopper Connector**: The short cylindrical section connecting the bottom of the cone to the debris collector.
5.  **Mount Plate**: A flat plate with bolt holes for mounting the cyclone assembly. This plate should attach to the hopper connector.

I'd like the main body and the cyclone cone to be a single connected piece. Also, the intake pipe should be integrated with this connected piece and not be a separate part. The mount plate and hopper connector should be separate pieces. Please include bolt holes in the mounting plate and design them so that they have space for bolt heads.

Could you please generate the OpenSCAD files needed for this design, ensuring that all parts are parametric as described? I'm expecting to use standard OpenSCAD primitive operations to build this, and no specific external libraries should be required.

[Assistant]
Cyclone.scad:
```
// Cyclone Dust Collector for Woodworking
// by György Balássy 2017 (http://www.thingiverse.com/balassy)
// http://www.thingiverse.com/thing:
//
// Original design by Marius Gheorghescu, July 2013 (http://www.thingiverse.com/thing:124551).

// Diameter of the hose to the vacuum in mm. The internal diameter of the exhaust hole on the top.
vacuum_hole_diameter = 32;

// Internal diameter of the intake hole in mm.
intake_hole_diameter = 45;

// Height of the main body in mm.
body_height = 51;

// Height of the cyclone chamber in mm.
cone_height = 90;

// Diameter of the hopper connector (bottle neck) in mm. The internal diameter of the debris collector hole on the bottom.
hopper_diameter = 28;

// Thickness of the walls in mm.
wall_thickness = 2;

// Which part do you want to render?
partToRender = "ALL";  // [ALL, CYCLONE, MOUNT_PLATE]

// The distance between the outer radius of the hopper hole and the inner radius of the mount plate. Set to a negative value when printing in one piece, or a small positive value to tolerate the inaccuracy of your printer.
mount_plate_space_radius = 0.5;

/* [Hidden] */

$fa = 1;       // Default minimum facet angle.
$fs = 1;       // Default minimum facet size.
delta = 0.1;   // Ensures that no faces remains after boolean operations.

mainColor = [0.117, 0.565, 1];      // DodgerBlue: RGB(30, 144, 255)
highlightColor = [0.384, 0.726, 1]; // Lighter DodgerBlue: RGB(98, 185, 255)

body_diameter = vacuum_hole_diameter + 2 * intake_hole_diameter + 6 * wall_thickness;
vacuum_hole_radius = vacuum_hole_diameter / 2;
intake_hole_radius = intake_hole_diameter / 2;
body_radius = body_diameter / 2;
hopper_radius = hopper_diameter / 2;

bolt_diameter = 7;
bolt_length = 30;
bolt_head_diameter = 12;    // 11.8 = Tight fit for M6 bolt.
bolt_head_height = 5;

mount_plate_height = 2 * wall_thickness + bolt_head_height;
mount_plate_ring_radius = 20;

debris_container_cover_thickness = 3;

// Length of the hopper connector in mm.
hopper_height = mount_plate_height + debris_container_cover_thickness;

module cyclone_cone() {
	difference() {
		cylinder(r1=hopper_radius + wall_thickness, r2=body_radius + wall_thickness, h=cone_height);
    translate([0, 0, -delta])
		  cylinder(r1=hopper_radius, r2=body_radius, h=cone_height + 2 * delta);
	}
}

module cyclone_intake(createEmptyPipe) {
	intake_length = body_radius + vacuum_hole_radius;

	translate([0, intake_hole_radius + vacuum_hole_radius + 2 * wall_thickness, body_height - intake_hole_radius - wall_thickness * 2])//-intake_hole_radius + body_height])
    rotate([0, 90, 0])
      difference() {
        cylinder(r=intake_hole_radius + 2 * wall_thickness, h=intake_length);

        if (createEmptyPipe)
          translate([0, 0, -delta])
            cylinder(r=intake_hole_radius, h=intake_length + 2 * delta);  // Double wall size used here for strength.
      }
}

module cyclone_body() {
  // Calculate the length of the exhaust vacuum tube.
	inner_tube_height =  body_height + cone_height / 2.5;

	difference() {
    union() {
      // Main body.
      difference() {
        cylinder(r=body_radius + wall_thickness, h=body_height);

        translate([0, 0, -wall_thickness])
          cylinder(r=body_radius, h=body_height + delta - wall_thickness);
      }

      // Tube inside for the exhaust vacuum hose.
      translate([0, 0, body_height - inner_tube_height])
        cylinder(r=vacuum_hole_radius + wall_thickness, h=inner_tube_height);
    }

		// Hole for the exhaust vacuum hose.
    translate([0, 0, body_height - inner_tube_height - delta])
		  cylinder(r=vacuum_hole_radius, h=inner_tube_height + 2 * delta);
	}
}

module cyclone_top() {
	difference() {
		cyclone_body();
		cyclone_intake(false);
	}

	cyclone_intake(true);
}

module cyclone_hopper_connector() {
	difference() {
		cylinder(r=hopper_radius + wall_thickness, h=hopper_height);
    translate([0, 0, -delta])
		  cylinder(r=hopper_radius, h=hopper_height + 2 * delta);
	}
}

module cyclone_mount_plate() {
  bolt_position_radius = hopper_radius + wall_thickness + mount_plate_ring_radius / 2;
  bolt_position_distance = bolt_position_radius * cos(45);

  difference() {
    cylinder(r=hopper_radius + wall_thickness + mount_plate_ring_radius, h=mount_plate_height);
    translate([0, 0, -delta])
      cylinder(r=hopper_radius + wall_thickness + mount_plate_space_radius - delta, h=mount_plate_height + 2 * delta);

    // Bolts.
    translate([bolt_position_distance, bolt_position_distance, mount_plate_height + delta])
      cyclone_bolt();
    translate([bolt_position_distance, -bolt_position_distance, mount_plate_height + delta])
      cyclone_bolt();
    translate([-bolt_position_distance, bolt_position_distance, mount_plate_height + delta])
      cyclone_bolt();
    translate([-bolt_position_distance, -bolt_position_distance, mount_plate_height + delta])
      cyclone_bolt();
  }
}

module cyclone_bolt() {
  translate([0, 0, -bolt_head_height]) {
    // Head.
    cylinder(d = bolt_head_diameter, h = bolt_head_height, $fn = 6);

    // Body.
    translate([0, 0, -bolt_length + delta])
      cylinder(d = bolt_diameter, h = bolt_length);
  }
}

module cyclone() {
  if(partToRender == "MOUNT_PLATE" || partToRender == "ALL") {
    color(highlightColor)
      translate([0, 0, debris_container_cover_thickness])
        cyclone_mount_plate();
  }

  if(partToRender == "CYCLONE" || partToRender == "ALL") {
    color(mainColor) {
      cyclone_hopper_connector();

      translate([0, 0, hopper_height])
        cyclone_cone();

      translate([0, 0, hopper_height + cone_height])
        cyclone_top();
    }
  }
}

cyclone();

```


[User]
I'd like a parametric OpenSCAD design for a badge holder, based on some previous work I've done. I've had some issues with the clip tab being too thick, so I need it to be adjustable.

Here's what I'm looking for:

*   **Badge Holder:**
    *   It needs to hold a badge with adjustable `badge_width` and `badge_height`. The `badge_thickness` should also be parametric.
    *   The holder should have a wall around the badge, with a `holder_wall_size` parameter. There should also be a bezel around the edge, a certain distance from the main walls, controlled with `holder_bezel_size` parameter.
    *   The corners of the holder should be rounded with a `holder_radius`.
    *   The holder's total thickness should accommodate the badge thickness plus the walls.
*   **Clip Tab:**
    *   There should be a clip tab extending out the side of the holder.
    *   The `clip_tab_thickness` should be adjustable.
    *   There should be a clip hole centered on the tab, controlled by `clip_hole_width` and `clip_hole_height`.
*   **Parameters:** All of the following should be easily adjustable:
    *   `badge_width`
    *   `badge_height`
    *   `badge_thickness`
    *   `holder_wall_size`
    *   `holder_bezel_size`
    *  `holder_radius`
    *   `clip_tab_thickness`
    *   `clip_hole_width`
    *   `clip_hole_height`

I'd prefer if you used the BOSL2 library if possible.

[Assistant]
hid_badge_holder_thinned_tab.scad:
```
badge_thickness = 1;
clip_tab_thickness = 3;

/* [Hidden] */
badge_width = 56;
badge_height = 87;

holder_wall_size = 2;
holder_bezel_size = holder_wall_size + 3;

holder_height = badge_height + (holder_wall_size * 2);
holder_width = badge_width + (holder_wall_size * 2);
holder_thickness = badge_thickness  + (holder_wall_size * 2);
holder_radius = 4;

clip_hole_width = 15;
clip_hole_height = 4;

difference() {
  hull() {
    cube(size=[holder_height - holder_radius, holder_width, holder_thickness]);

    translate([holder_height - holder_radius, holder_radius, 0])
      cylinder(h=holder_thickness, r=holder_radius, $fn=200);
    translate([holder_height - holder_radius, holder_width - holder_radius, 0])
      cylinder(h=holder_thickness, r=holder_radius, $fn=200);
  }

  translate([holder_wall_size,holder_wall_size, holder_wall_size])
    cube(size=[badge_height, badge_width, badge_thickness]);

  translate([holder_wall_size,holder_wall_size, holder_wall_size])
    cube(size=[badge_height / 2.25, badge_width, holder_thickness * 2]);

  hull() {
    translate([holder_bezel_size + holder_radius, holder_bezel_size + holder_radius, 0])
      cylinder(h=holder_thickness, r=holder_radius, $fn=200);
    translate([holder_bezel_size + holder_radius, holder_width - holder_bezel_size - holder_radius, 0])
      cylinder(h=holder_thickness, r=holder_radius, $fn=200);
    translate([holder_height - holder_bezel_size - holder_radius, holder_bezel_size + holder_radius, 0])
      cylinder(h=holder_thickness, r=holder_radius, $fn=200);
    translate([holder_height - holder_bezel_size - holder_radius, holder_width - holder_bezel_size - holder_radius, 0])
      cylinder(h=holder_thickness, r=holder_radius, $fn=200);
  }
}

difference() {
  translate([holder_width - 8.05, holder_width / 2, holder_thickness / 2 - clip_tab_thickness / 2])
    cylinder(h=clip_tab_thickness, d=holder_width * 2, $fn=200);

  translate([0, holder_width * -1.5, -1])
    cube(holder_width * 3, holder_width * 3, 100);

  translate([(clip_hole_height / -2) - 1, (holder_width - (clip_hole_width + (clip_hole_height / 2))) / 2, -.5])
  hull() {
    translate([0,clip_hole_width,0])
      cylinder(h=holder_thickness + 1, d=clip_hole_height, $fn=100);
    cylinder(h=holder_thickness + 1, d=clip_hole_height, $fn=100);
  }
}
```


[User]
I'm looking to create a Bahtinov mask for my telescope, and I'd like it to be parametric so I can adjust it for different scopes in the future. I've seen a few examples, but I need something that is highly customizable and can handle a few specific features. I'm thinking of something that could be used for both my Celestron Edge HD 11" and a classic 11" SCT (both have removable secondaries), so I need to be able to adjust the center hole diameter.

Here are the main things I need to be able to control:

*   **Telescope Focus Length:** This is crucial for the slot calculation. I need to set this value in mm, probably with a range from 10 to 5000.
*   **Aperture Diameter:** The diameter of the telescope's light path in mm. Range likely from 50 to 400.
*   **Outer Diameter:** The overall diameter of the mask itself in mm, perhaps 51 to 500.
*   **Mask Height:** The thickness of the main mask structure in mm, adjustable from 1 to 10.
*   **Center Hole Diameter:** The diameter of the hole in the center to clear the secondary mirror holder. I need this to go down to zero since I sometimes use scopes with no secondary. Let's say the range is 0 to 90mm.
*   **Outer Border Height:** The height of the raised rim or border around the mask in mm; a range from 0 to 50.
*   **Inner Border Height:** A border around the center hole that could serve as a handle. I'd like this to be adjustable, with the possibility of being on the top or the bottom. I'd also like to be able to set it to zero if I don't want one. Range should be from -50 to +50.
*   **Split Distance:** The distance between the mask parts if I wanted to print in multiple sections. This would let me print a larger mask on a smaller printer. If I want it to be one piece, this needs to be settable to -1. Range: -1 to 20.

Then there are some advanced controls:

*   **Bahtinov Factor:** A value that affects the gap size of the slots. This is typically in the 150-200 range. Let's allow a range from 50 to 200.
*   **Slot Size:** This should be an explicit control for the width of the bars and gaps, overriding the value calculated by the factor. If set to 0 it should auto-calculate. Range: 0-10.
*   **Central Bars Width:** The width of the central set of bars, which can be different. If set to 0 it should use the same value as the other bars and gaps. Range 0-20
*   **Slot Orientation:** The angle of the slots. I'd like to set this between 20 and 45 degrees.
*   **Split Layer Width:**  When splitting the mask into multiple parts, I need to control the layer thickness used to calculate the distance between the parts. This should be something like 0.3mm and likely only needs to be a small range.

Finally, a few features:

*   **Split Mask Printing:**  I need to be able to print the mask as a whole or as four separate parts (top left, top right, bottom left, and bottom right) to fit on smaller print beds.
*   **Connectors:** If printing split, I need integrated connectors to help align and join the mask pieces.
*   **Locking Feature:** I'd like a locking lip on the outer and inner edges to be able to clip it onto the telescope.

I am hoping you can create an OpenSCAD file, or several, to create a customizable mask. Could you include comments in the code to explain the different parts and how they relate to the parameters? I think using MCAD might help simplify some of the math and geometry, so if you can use that library it would be great.

[Assistant]
mask.scad:
```
/*
 * A Bahtinov mask generator.
 * Units in mm, default values are for
 * a Celestron NexStar GPS 8.
 *
 * Copyright 2014, Ella Astashonok
 * License: CC-BY-SA
 *
 * Original design by Brent Burton
 * ver. 2.1.0
 */

/* [Global] */

//Slect part to print
part = "all"; //[tl:Top Left, tr:Top Right, bl:Bottom Left, br:Bottom Right, all: All parts]

/* [Primary] */

// The telescope focus lenght
focus = 2800; // [10:5000]

// The telescope light's path diameter.
aperture = 280; // [50:400]

// This is the diameter of the mask.
outer_Diameter = 304; // [51:500]

// Height of mask
mask_Height = 2; // [1:10]

// Diameter of secondary mirror holder. If no secondary, set to 0.
center_Hole_Diameter = 97; // [0:90]

// Height of outer border
outer_Height = 10; // [0:50]

//Inner border around center hole. Can be used as handle. 0 for no border. Negative values generate border on oposite side. Not recomended - hard to print.
inner_Height = 0; // [-50:+50]

// Split 4 parts distance (-1 for one pice mask)
split_Distance = -1; //[-1:20]

/* [Advanced] */

// Bahtinov factor used for calculate slot size. (should be from 150 to 200. You shouldn't need to tweak this unless your slots are too narrow for printing, in which case, divide the factor by 3)
bahtinov_factor = 150; // [50:200]

// Width of the gaps and the bars (0-for auto calculate).
slot_Size = 0; // [0:10]

// Width of central bars (0 foor same as bars and gaps)
central_bars_width = 0; //[0:20]

// Angle of slots
slots_Orientation = 30; // [20:45]

// Layer size used to calculate separation distance when making splitted mask
split_layer_width = 0.3;

outerDiameter = outer_Diameter;
maskHeight=mask_Height;
outerHeight=outer_Height;
handle = inner_Height;
bahtinov = bahtinov_factor;
slotSize = slot_Size;
slotsOrientation = slots_Orientation;
central_bars = central_bars_width;
split_layer = split_layer_width;

layer = split_Distance >= 0 || part != "all" ? split_layer : 0;
gap = slotSize > layer*2 ? slotSize : getGap(focus, bahtinov) < layer*2 ? getGap(focus, bahtinov / 3)  : getGap(focus, bahtinov);
cBars1 = central_bars > 0 ? (central_bars > gap ? central_bars : gap) : gap;
cBars = cBars1 > layer*5 || split_Distance < 0 ? cBars1 : layer*5;
centerHoleDiameter = center_Hole_Diameter > 0 && center_Hole_Diameter < cBars ? cBars : center_Hole_Diameter;
splitDistance = split_Distance > 0 && split_Distance < cBars ? cBars : split_Distance;
split = part != "all" && splitDistance < cBars*2 ? cBars*2 : (splitDistance < 0 ? 0 : splitDistance);
//split = splitDistance < 0 ? 0 : splitDistance;
handleHeight = abs(handle);
echo("Gap size: ", gap);

print_main(part);

function getGap(focus, bahtinov) = round(focus/bahtinov*100+0.5)/200;

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

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

$fn=72;
module bahtinov2D() {
    width = aperture/2+1;
    difference() {                          // overall plate minus center hole
        union() {
				difference(){							  // trims the mask to aperture size

					for( pos = [0:3]){
							rotate([0, 0, pos*90])
								translate([split, split])
									circle(r=aperture/2+1);
					}
	            bahtinovBars(gap,width, split);
				}

				for( pos = [0:90:359]){
					rotate([0, 0, pos])
						translate([split, split])
							intersection(){
								difference() {                  // makes the outer margin
	            				circle(r=outerDiameter/2);
	            				circle(r=aperture/2);
	            			}
								translate([0,layer/2,0]) square([outerDiameter/2, outerDiameter/2]);
							}
				}

            // Add center hole margin if needed:

            if (centerHoleDiameter > 0) {
					for(i = [0:90:359])
						rotate([0,0,i])
							translate([split, split])
                			intersection(){
									circle(r=(centerHoleDiameter+gap)/2);
									square([centerHoleDiameter/2+gap, centerHoleDiameter/2+gap]);
								}
            }

        	}//union

         // Add center hole if needed:
			if (centerHoleDiameter > 0) {
				for(i = [0:90:359])
					rotate([0,0,i]){
	               intersection(){
							translate([split, split])
								circle(r=centerHoleDiameter/2);
							square([centerHoleDiameter/2+split,centerHoleDiameter/2+split]);
						}
					}
			}

		}//difference

}

module fakeMask(){
	for(i=[0:90:359]){
		rotate([0,0,i])
			intersection(){
				translate([split,split,0])
					difference(){
						cylinder(r=outerDiameter/2, h=maskHeight);
						translate([0,0,-1])cylinder(r=centerHoleDiameter/2, h=maskHeight+2);
					}
				cube([outerDiameter/2+split,outerDiameter/2+split,maskHeight]);
			}
	}
}

module triangleX(x, y, z){
		translate([0, y/2,0])
       rotate([90,0,0])
			linear_extrude(height=y) polygon(points=[[0, 0],[x, 0],[0, z]], path=[[0,1,2]]);
}

module connector(width, height, depth, layer){
	w1 = width*3/2-layer*2;
	w2 = width/2-layer*2;
	h = height;
	translate([0,0,-depth/2])
		linear_extrude(height=depth)
			polygon(points=[[-w1/2, h/2], [w1/2, h/2], [w2/2, -h/2], [-w2/2, -h/2]], path=[0,1,2,3]);
}

module horisontalSplit(length,height,depth,connector_width,split){
		// Add split structural bars
		bar_hgt = height / 6;
		connector_hgt = height*2/3;
		union(){
			//%cube([cBars,layer,3],true);
			difference(){
				union(){
					translate([split+length/2,(split-bar_hgt/2+height/2), 0]) //bar1
						cube([length, bar_hgt, depth], true);
					translate([split+length/2, -(split-bar_hgt/2+height/2), 0]) //bar2
						cube([length, bar_hgt, depth], true);
				}
				if(centerHoleDiameter<=0)
					translate([split,0,-1]) //bar2
						cube([height*2/3, split*2+height, depth+2], true);
				else
					union(){
						translate([split-centerHoleDiameter/2+gap/2,split,-depth/2-1]) //bar2
							cylinder(r=centerHoleDiameter/2, h=depth+2);
						translate([split-centerHoleDiameter/2+gap/2,-split,-depth/2-1]) //bar2
							cylinder(r=centerHoleDiameter/2, h=depth+2);
					}
			}
			intersection(){
				for(i=[connector_width*3/2-bar_hgt*3:connector_width*2:length+connector_width]){
					translate([i+split,-(split-connector_hgt/2+height/2-bar_hgt+layer),0])
						connector(connector_width-layer/2, connector_hgt, depth, layer);
					//if(i+connector_width*2 <= length+connector_width*2/3)
					translate([i+split+connector_width,split-connector_hgt/2+height/2-bar_hgt+layer,0])
					rotate([0,0,180])
						connector(connector_width-layer/2, connector_hgt, depth, layer);
				}
				translate([split+length/2/*+height/2-bar_hgt-layer*/, 0, 0])
					cube([length, split*2+height, depth], true);

			}
		}

}

module cylinderLocks(dOut,dIn,w,lock_h,layer,split){
	//Making outer border lock
	assign(wall = (dOut-dIn)/2)
 		for(i=[0:90:359]){ //outer lock
	 		rotate([0,0,i])
	   	translate([split,split,0]){
				intersection(){ //OuterLock
					difference(){
						cylinder (r=dOut/2, h=lock_h); //Outer wall
						cylinder (r=dOut/2-(wall/2)+layer/2, h=lock_h); //Inner wall
	    			}
					translate([0,-w/3+layer,0])
						cube([dOut/2,w*2/3-layer,lock_h]);
				}
				rotate([0,0,90])
				intersection(){ //InterLock
					difference(){
						cylinder (r=dOut/2-(wall/2)-layer/2, h=lock_h); //Outer wall
						cylinder (r=dIn/2, h=lock_h); //Inner wall
	    			}
					translate([0,-w/3,0])
						cube([dOut/2,w*2/3-layer,lock_h]);
			}
		}
 }
}

module print_main(part="all"){
	echo ("Printing part:", part);

	translate([part!="all"?(outerDiameter/4+split/2):0,part!="all"?(outerDiameter/4+split/2):0,0])
	rotate([0,0,part=="tl"?90:part=="tr"?180:part=="br"?-90:0])
	intersection(){
		union() {
		difference(){
		union() {

		    linear_extrude(height=maskHeight) bahtinov2D();
			 //fakeMask();

			 //Outer Border
		    if(outerHeight > 0){
			 	for(i=[0:90:359]){
					rotate([0,0,i])
					  translate([split,split,maskHeight]){
							difference(){
								intersection(){
						    		union(){
										difference(){
											cylinder (r=outerDiameter/2, h=outerHeight-maskHeight); //Outer wall
											translate([0,0,-1])
												cylinder (r=outerDiameter/2-(maskHeight), h=outerHeight-maskHeight+2); //Inner wall
						    			}
									}
									cube([outerDiameter/2,outerDiameter/2,outerHeight-maskHeight]);
								}
							}
					  }
				}
			 }

			 //Inner border / handle
			 assign(ht=handle<0?handleHeight:handleHeight-maskHeight)
		    if(handleHeight > 0){
				if (centerHoleDiameter > 0){
					translate([0,0,handle<0?handle-maskHeight:0])
					union(){
						for(i=[0:90:395]){
							rotate([0,0,i])
							  translate([split,split,maskHeight]){
									difference(){
										intersection(){
							    			difference(){
												cylinder (r=(centerHoleDiameter+gap)/2, h=ht); //Outer wall
												translate([0,0,-1])
													cylinder (r=(centerHoleDiameter)/2, h=ht+2); //Inner wall
							    			}
											//translate([0,0,0])
											cube([centerHoleDiameter/2+gap/2,centerHoleDiameter/2+gap,ht]);
										}
									}
							  }
						}
					}//union
				}//holeDiameter > 0
			 }//Handle

		}//union
			//Split parts
			assign(maxpl= max(outerHeight,maskHeight,handle>0?handle:0))
			assign(maxh = max(maskHeight, handleHeight, outerHeight, maxpl-handle))
			{
				echo("maxh:",maxh," maxpl:",maxpl," handle:",handle);
				translate([0,0,(handle<0?(maxpl+handle)/2:maxpl/2)]){//(maxh/2)*sign(handle)+(handle<0?maskHeight:0)]){
					cube([outerDiameter+split*2,cBars*2/3+split*2,maxh+2],true);
					cube([cBars*2/3+split*2,outerDiameter+split*2,maxh+2],true);
				}
			}
		}//difference
			 //Make connectors
			 assign(
				con_delta = (centerHoleDiameter > 0 ? (centerHoleDiameter)/2-(cBars/4-cBars/12) : 0),
				con_length=outerDiameter/2-(centerHoleDiameter > 0 ? (centerHoleDiameter)/2 : 0)
			 )
			 for(i=[0:90:359]){
				rotate([0,0,i])
					intersection(){
						difference(){
							translate([con_delta,0,maskHeight/2]){
								if(splitDistance>=0 || part != "all")
									horisontalSplit(con_length, cBars, maskHeight, cBars, split);
								else
									translate([con_length/2,0,0])
										cube([con_length, cBars,maskHeight],true);

							}
							intersection(){
								union(){
									translate([split,split,-1])
										cylinder(r=(centerHoleDiameter+gap)/2+layer, h=maskHeight+2);
									translate([split,-split,-1])
										cylinder(r=(centerHoleDiameter+gap)/2+layer, h=maskHeight+2);
								}
								translate([split+outerDiameter/4,0,maskHeight/2])
									cube([outerDiameter/2,cBars*2/3+split*2,maskHeight+2],true);
							}
						}
						union(){
							translate([split,split,-1])
								cylinder(r=outerDiameter/2-maskHeight-layer, h=maskHeight+2);
							translate([split,-split,-1])
								cylinder(r=outerDiameter/2-maskHeight-layer, h=maskHeight+2);
						}
					}
			 }

			 //Making outer border lock
			 cylinderLocks(outerDiameter,outerDiameter-maskHeight*2,cBars,outerHeight > 0 ? outerHeight : maskHeight,layer,split);

			 //Making inner border lock
			 if(centerHoleDiameter > 0)
				assign(ht=handle<0?handleHeight:handleHeight-maskHeight)
				translate([0,0,handle<0?handle:0])
					assign(h=handle != 0 ? (maskHeight+(handle>0?handleHeight-maskHeight:handleHeight)) : maskHeight)
			 		cylinderLocks(centerHoleDiameter+gap,centerHoleDiameter,cBars,h,layer,split);

		}//union
		if (part != "all"){
			if (part == "tl"){
				translate([-(outerDiameter/2+1+split),0,handle < 0 ? handle : 0])
					cube([outerDiameter/2+1+split,outerDiameter/2+1+split,maskHeight+max(handleHeight, outerHeight, outerHeight-handle)]);
			}
			if (part == "tr"){
				translate([0,0,handle < 0 ? handle : 0])
					cube([outerDiameter/2+1+split,outerDiameter/2+1+split,maskHeight+max(handleHeight, outerHeight, outerHeight-handle)]);
			}
			if (part == "bl"){
				translate([-(outerDiameter/2+1+split),-(outerDiameter/2+1+split),handle < 0 ? handle : 0])
					cube([outerDiameter/2+1+split,outerDiameter/2+1+split,maskHeight+max(handleHeight, outerHeight, outerHeight-handle)]);
			}
			if (part == "br"){
				translate([0,-(outerDiameter/2+1+split),handle < 0 ? handle : 0])
					cube([outerDiameter/2+1+split,outerDiameter/2+1+split,maskHeight+max(handleHeight, outerHeight, outerHeight-handle)]);
			}

		}
	}//intersection

}//main_print

```


[User]
I need a custom box generator in OpenSCAD. I want to be able to specify the width, depth, and height of the box, as well as the material thickness. The corners should be rounded, and I want to be able to control the corner radius. The box should consist of a base and a cover plate. I'll also need screw holes in both the base and the cover plate to attach them together.

I need to be able to generate just the base, just the cover plate, or both parts together. I want to be able to specify the hole offset from the edges and the hole diameter.

Could you please generate the OpenSCAD files for this? I'd like it to use the MCAD library, specifically the `boxes.scad` file within that library.

[Assistant]
thingiverse-boxbuilder.scad:
```
use <MCAD/boxes.scad>

// Choose Part
part = "both"; // [first:Case Only,second:Cover Plate Only,both:Both]

// Width of the case
width = 270;

// Depth of the case
depth = 150;

// Height of the case
height = 75;

// Material thickness of the case and plates
material_thickness = 5;

// Corner radius of rounded corners
corner_radius = 5;

// Hole offset
hole_offset = 10; //[1:14]

// Hole diameter
hole_diameter = 10;

print_part();

module print_part() {
	if (part == "first") {
		base();
	} else if (part == "second") {
		plate();
	} else if (part == "both") {
		both();
	} else {
		both();
	}
}

module both(){
	base();
	translate([0,0,height])
		plate();
}

module base(){
	// All dimensions in mm
	x = width;
	y = depth;
	z = height;

	union(){
		for (i=[ [hole_offset,hole_offset, 0],
				[hole_offset,y-hole_offset, 0],
				[x-hole_offset,hole_offset, 0],
				[x-hole_offset,y-hole_offset, 0] ])
			{
			translate(i){
				linear_extrude(height=z){
					difference(){
						circle(r=(hole_diameter));
						circle(r=(hole_diameter/2));
					}
				}
			}
		}
		difference(){
			translate([x/2, y/2, height/2]){
			  roundedBox([x, y, height], 5, true);
			}
			translate([material_thickness, material_thickness, 0]) {
				linear_extrude(height=z)
				square(size = [x-(material_thickness*2), y-(material_thickness*2)], center = false);
		   	}
		}
	}
}

module plate(){
	// All dimensions in mm
	x = width;
	y = depth;
	z = material_thickness;

	difference() {
		// create the base
		translate([x/2, y/2, z/2]){
		  roundedBox([x, y, z], corner_radius, true);
		}
		// Create 4 holes
		for (i = [ [hole_offset,hole_offset, -10],
			[hole_offset,y-hole_offset, -10],
			[x-hole_offset,hole_offset, -10],
			[x-hole_offset,y-hole_offset, -10] ])
		{
			translate(i) {
				linear_extrude(height = z + 10)
				circle(r=(hole_diameter/2));
			}
		}
	}
}

```


[User]
I need a parametric spool holder designed for a 3D printer. I'm using a FlashForge Creator Pro, which has a somewhat unusual mounting setup for the spools, and I want to be able to easily adjust the design for different spools I might use.

The holder needs to attach to a lip on the printer frame. The lip has a width, depth, and height above the mounting point that I'll need to specify. The holder needs to have a plate that sits on top of the lip and then a section that extends out to hold the spool.

The spools themselves also have some key dimensions I need to account for: the diameter of the hole in the center, the width of the hole (not the total width of the spool, but how far the center hole extends), and an offset that describes the distance from the edge of the spool hole to the side of the spool closest to the printer.

I'm also going to need an expansion allowance. I’ve found that plastic parts don't always come out the exact size I design them, so I’ll want to be able to make the holder slightly smaller than the measured dimensions so it fits snugly.

Specifically, I need the design to allow me to specify the following parameters:
*   `expansion_allowance`: A value in mm, likely a small positive number, to account for material expansion.
*   `measured_lip_width`: The width of the lip on the printer frame in mm.
*   `measured_lip_depth`: The depth of the lip on the printer frame in mm.
*   `measured_lip_height`: The height of the lip on the printer frame, measured from the surface of the printer it extends from, in mm.
*   `measured_spool_hole_diameter`: The diameter of the center hole in the spool in mm.
*   `measured_inside_spool_width`: The width of the hole in the spool that will rest on the holder in mm.
*   `measured_spool_offset_on_inside`: The distance from the edge of the spool hole to the nearest edge of the spool itself, in mm.

The design should consist of a plate that rests on the lip, and a spindle to hold the spool. It might be beneficial to make the plate using a subtractive design, where you create the full width piece and then cut off the extra, so that the top of the plate can be slightly angled. And I'd want the spindle to have a lip on each side to keep the spool from sliding off.

Please create a parametric OpenSCAD design that achieves this, and use the default `$fn=40`. I am not using any specific external libraries.

[Assistant]
spool_holder_P1.scad:
```
// Parametric spool holder for MBI R2 by Woale modified by TheFisherOne

// preview[view:north west, tilt:top diagonal]

/* [General] */
// an allowance that has to be made because the plastic is likely to expand to larger than is desired, usually a small positive number of mm. ALL Dimensions are in mm, multiply inches by 25.4 to get mm.
expansion_allowance=0; // [0:0.1:2]

/* [Size of Hole and Lip that it has to fit into] */
// the width of the hole it has to fit into (note it will be built smaller than this)
measured_lip_width=51; // [23:1:75]
// the depth of the lip that it will be hanging on to
measured_lip_depth=7.1; // [5:0.1:25]
// how high the space is above the lip
measured_lip_height=16; // [5:0.1:40]

/* [ measurements of the spool that needs to hang on it] */
//the spool that wants to hang on it
// the diameter of the hole in the spool
measured_spool_hole_diameter = 53; // [23:0.5:75]
// how long the hole is (not the overall width of the spool, but the part that will sit on the holder)
measured_inside_spool_width=64; //[23:0.5:100]
// offset of the edge of the spool compared to the edge of the hole, note that this is on the inside facing the wall
measured_spool_offset_on_inside=0; //[0:0.1:25]

/* [Hidden] */
//corrections to allow for expansion
lip_max_width=measured_lip_width-expansion_allowance;
lip_height=measured_lip_height-expansion_allowance;
spool_hole_diameter=measured_spool_hole_diameter-expansion_allowance;
inside_spool_width=measured_inside_spool_width+expansion_allowance;
spool_offset=measured_spool_offset_on_inside+expansion_allowance;
lip_depth=measured_lip_depth+expansion_allowance;

//fixed
thickness=5;
plate_top_length=23;
spindle_plate_length=75;
spindle_lip_height=5;

//formulas
//need space to put one lip and the shaft through the hole
final_diameter=spool_hole_diameter-spindle_lip_height;
// narrow the width so that the spindle will have a flat side and it is easier to print without supports
lip_width=((final_diameter-thickness) < lip_max_width)? final_diameter-thickness:lip_max_width;

//fix
$fn=40;

//plate holder
module plt_hld()
{
	//box1 with the end cut at 45 degrees
    // the length is the length of the part that sticks above the bar plus the side of a triangle whose hypotonuse is the lip_height and the other side is approximated by a 45 triangle of size lip_depth
    temp_plate_len=plate_top_length+sqrt((lip_height*lip_height)-(lip_depth/sqrt(2))*lip_depth/sqrt(2));
    difference(){
	  cube([temp_plate_len,thickness,lip_width]);
        // cut off the end at a 45 degree angle, make it just a little longer in every dimension to make the cut clean
      translate([temp_plate_len,thickness,-1])
         rotate([0,0,225])
            cube([thickness*2, thickness*2, lip_width+2]);
    }
	//box2
	translate([plate_top_length,thickness,0])
        cube([thickness,lip_depth,lip_width]);
	//box3
	translate([plate_top_length,thickness+lip_depth,0])
        cube([spindle_plate_length,thickness,lip_width]);
}

//spool holder
module spl_hld()
{
    //the main holder
    y_offset_of_cylinder=2*thickness+lip_depth;
    x_offset_of_cylinder=plate_top_length+thickness+spindle_lip_height+final_diameter/2;
    translate([x_offset_of_cylinder,y_offset_of_cylinder , lip_width/2])
      rotate([-90,0,0])
      cylinder(h=inside_spool_width+spool_offset+thickness,
             d=final_diameter);
    // the inside lip
    translate([x_offset_of_cylinder-spindle_lip_height, y_offset_of_cylinder+spool_offset-thickness, lip_width/2])
      rotate([-90,0,0])
      cylinder(h=spindle_lip_height,
             d=final_diameter);
    // the outside lip
    translate([x_offset_of_cylinder-spindle_lip_height, y_offset_of_cylinder+spool_offset+inside_spool_width, lip_width/2])
      rotate([-90,0,0])
      cylinder(h=spindle_lip_height,
             d=final_diameter);

}
intersection(){
union(){
    spl_hld();
    plt_hld();
}
cube([plate_top_length+spindle_plate_length, 3*thickness+lip_depth+inside_spool_width+spool_offset, lip_width]);
}

```