[User]
Hey, I'm looking to design a parametric battery storage box in OpenSCAD. I've got some ideas, but I'm not super familiar with writing code for it. I need it to be highly customizable, so I can adjust it to fit different battery types and mounting needs.

Here's the basic idea:

*   **Battery Types:** It needs to handle AAA, AA, C, D, and 9V batteries.
*   **Layout:** I want to arrange the batteries in rows, and be able to control how many rows of each battery type and how many slots are in each row.
*   **Tilting:**  I'd like the batteries to be held at a 45-degree angle, for easy access whether the box is standing or hanging.
*   **Padding:**  There needs to be adjustable spacing between the batteries and the edges of the box.
*   **Rounding:** The box's edges should be rounded with an adjustable bevel.
*  **Bottom Padding:**  I need to be able to add some extra padding at the bottom to make sure the bottom of the battery cavities don't touch the back of the box.
*   **Mounting:** I need the option for mounting holes on the sides, top, and bottom edges, with adjustable screw hole diameters and mounting tab sizes. I plan on using a screw that will fit a 5mm hole so I should keep the screw hole diameter 5mm unless I say otherwise.
*   **Resolution:**  The smoothness of curves should be adjustable by defining detail level.

I also want to be able to manually override the row and count parameters if I want to have a custom layout or add another battery type. So, it should be possible to specify the battery type and slots per row directly in a list that bypasses the usual customizer settings for each row.

I don’t think I will be using any existing libraries.

Could you help me generate the OpenSCAD code for this? Let me know if you need any more details!

[Assistant]
Parametric_Battery_Box_v02.scad:
```
///////
//Start of Customizer Parameters
///////

/* [General Design] */
// Level of detail (medium print was smooth to the touch)
Detail_level = 64; // [32:Low, 64:Medium, 128:High]
// Space between a battery any another (or an edge)
Padding = 2; // [0:10]
// Amount of rounding on edges of the box
Bevel = 2; // [1:10]
// Extra padding for bottom that prevents a battery cavity touching the back
Buttom_pad = 0.4; //[0.0:0.1:5.0]

/* [Battery Rows/Counts] */
AAA_rows = 2;
AAA_slots_per_row = 8;
AA_rows = 2;
AA_slots_per_row = 6;
C_rows = 1;
C_slots_per_row = 3;
D_rows = 0;
D_slots_per_row = 2;
9V_rows = 1;
9V_slots_per_row = 5;

/* [Mounting Options] */
// Screw holes on the sides of the box
Side_mounts = true;
// Screw holes on top edge
Top_mounts = false;
// Screw holes on bottom edge
Bottom_mounts = false;
//Diameter of screw holes
Screw_hole_diameter = 5; //[1:8]
//Mounting tab size (should be at least 2x larger than the screw hole)
Mounting_tab_size = 12; //[4:24]

///////
//End of Customizer parameters
//You can manually tweak the below parameters as well if desired
//You can add battery shapes or override the customizer battery row/count parameters with the manual table
///////
module __Customizer_Limit__ () {}

//Battery types. [Diameter, height]
//If there is a 3'rd parameter, it is is depth. Used for rectangles like the 9volt
AAA = [11, 45];
AA = [15, 48];
C = [27,50];
D = [33.5,61];
9V = [27,48.5,18];

//This can be enabled (set variable to true) to manually make rows
//Useful if you want to do multiple AA rows with different counts or use a custom sized element
use_rowDataList_overwrite = false;
rowDataList_overwrite = [
    [AAA, 8],
    [AAA, 8],
    [AA, 6],
    [AA, 6],
    [C, 3],
    [9V, 5]
];

//The angles the batteries tilt at. I hard-coded 45 degrees into all the geometry. It would need to be altered if the degree angle is
angle=[45,0,0];

///////
//End of parameters. Everything else below here is code-level edits
///////

// Create the row data for how many rows of what type there will be
// Format can be found in rowDataList_overwrite, and you can use the manual table instead
rowDataList = use_rowDataList_overwrite ?
    rowDataList_overwrite :
    concat(
        createRDL_AAA(),
        createRDL_AA(),
        createRDL_C(),
        createRDL_D(),
        createRDL_9V()
    );
// Makes rows of values unless the row count was == 0
function createRDL_AAA() = AAA_rows==0?[] : [for(i=[0:AAA_rows-1]) [AAA,AAA_slots_per_row]] ;
function createRDL_AA() = AA_rows==0?[] : [for(i=[0:AA_rows-1]) [AA,AA_slots_per_row]] ;
function createRDL_C() = C_rows==0?[] : [for(i=[0:C_rows-1]) [C,C_slots_per_row]] ;
function createRDL_D() = D_rows==0?[] : [for(i=[0:D_rows-1]) [D,D_slots_per_row]] ;
function createRDL_9V() = 9V_rows==0?[] : [for(i=[0:9V_rows-1]) [9V,9V_slots_per_row]] ;

// Parameters set from the customizer
$fn = Detail_level; //# sides on rounded objects
padding = Padding; //space between batteries and/or sides of box
bevelSize = Bevel; //edge bevel distance
bottomPad = Buttom_pad; //space padding on bottom
mounts_top = Top_mounts;
mounts_bottom = Bottom_mounts;
mounts_sides = Side_mounts;

holeDiameter = Screw_hole_diameter; //mount hole diameter
tabSizeX = Mounting_tab_size; //size of tab

// Create a list of the diameters requested in rowDataList. Used in spacing models
rdlDiameters = [for(i=[0:len(rowDataList)-1]) rowDataList[i][0][0]]; //diameters
rdlHeights = [for(i=[0:len(rowDataList)-1]) rowDataList[i][0][1]]; //heights
rdlRadius = [for(i=[0:len(rowDataList)-1]) rowDataList[i][0][0]/2]; //radius sins
rdlHeightsHalf = [for(i=[0:len(rowDataList)-1]) rowDataList[i][0][1]/2];

difference(){
    batteryBox();
    cylinderRowSetup();
}
addMountingBrackets();

module addMountingBrackets(){
    // Create bar and holes
    sizeX = tabSizeX;
    sizeY = sizeX*3;
    sizeZ = 4;

    if (mounts_sides) {
        // Offsetting to put bracket at top/bottom of box
        offsetX = (getBoxWidth()/2) + (sizeX/2);
        offsetY_center = (getBoxDepth()/2) - bevelSize - padding;
        offsetY_near = offsetY_center - (getBoxDepth()/2) + (sizeY/2) + bevelSize;
        offsetY_far = offsetY_center + (getBoxDepth()/2) - (sizeY/2) - bevelSize;
        offsetZ = -getBoxHeight() + (sizeZ/2);

        mountingBracket(sizeX,sizeY,sizeZ,holeDiameter,offsetX,offsetY_near,offsetZ, [0,0,0]);
        mountingBracket(sizeX,sizeY,sizeZ,holeDiameter,offsetX,offsetY_far,offsetZ, [0,0,0]);
        mountingBracket(sizeX,sizeY,sizeZ,holeDiameter,-offsetX,offsetY_near,offsetZ, [0,0,180]);
        mountingBracket(sizeX,sizeY,sizeZ,holeDiameter,-offsetX,offsetY_far,offsetZ, [0,0,180]);
    }

    if (mounts_top) {
        // Offsetting to put bracket at top/bottom of box
        offsetX = - (getBoxWidth()/2) + (sizeY/2) + bevelSize;
        offsetY_center = (getBoxDepth()/2) - bevelSize - padding;
        offsetY = offsetY_center - (getBoxDepth()/2) - (sizeX/2);
        offsetZ = -getBoxHeight() + (sizeZ/2);

        mountingBracket(sizeX,sizeY,sizeZ,holeDiameter,offsetX,offsetY,offsetZ, [0,0,270]);
        mountingBracket(sizeX,sizeY,sizeZ,holeDiameter,-offsetX,offsetY,offsetZ, [0,0,270]);
    }

    if (mounts_bottom) {
        // Offsetting to put bracket at top/bottom of box
        offsetX = - (getBoxWidth()/2) + (sizeY/2) + bevelSize;
        offsetY_center = (getBoxDepth()/2) - bevelSize - padding;
        offsetY = offsetY_center + (getBoxDepth()/2) + (sizeX/2);
        offsetZ = -getBoxHeight() + (sizeZ/2);

        mountingBracket(sizeX,sizeY,sizeZ,holeDiameter,offsetX,offsetY,offsetZ, [0,0,90]);
        mountingBracket(sizeX,sizeY,sizeZ,holeDiameter,-offsetX,offsetY,offsetZ, [0,0,90]);
    }

}

module mountingBracket(sizeX, sizeY, sizeZ, holeDiameter, offsetX, offsetY, offsetZ, flipAngle){
    //Left
    translate([offsetX,offsetY,offsetZ])
    rotate(flipAngle)
    difference(){
        intersection(){
            cube([sizeX,sizeY,sizeZ], center=true);
            translate([-sizeX/2,0,0])
            rotate([0,0,45])
            cube([(sizeY)*sin(45),(sizeY)*sin(45),(sizeY)*sin(45)], center=true);
        }
        cylinder(d=holeDiameter, h=20, center=true);
    }

}

module batteryBox(){
    //SIZING
    //Width
    //allWidth = [
    //    for (i=[0:len(rowDataList)-1])
    //       len(rowDataList[i][0]) == 2 ?
    //            ((rowDataList[i][0][0]+padding)*rowDataList[i][1])+padding :
    //            ((rowDataList[i][0][2]+padding)*rowDataList[i][1])+padding
    //];
    boxWidth = getBoxWidth();
    //Depth
    //allDiameters = add(rdlDiameters) * 2 * sin(45);
    //lastBatteryHeight = (rdlHeights[len(rdlHeights)-1] - rdlDiameters[len(rdlDiameters)-1]) * sin(45);
    //paddingAddition = padding * (len(rowDataList)+1);
    //bevelAddition = bevelSize * 2;
    boxDepth = getBoxDepth();
    //Height
    boxHeight = getBoxHeight();

    //POSITIONS
    yOffset = (boxDepth/2) - bevelSize - padding;
    zOffset = -boxHeight/2 ;

    //CREATE BOX
    translate([0,yOffset,zOffset])
    bevelBox([boxWidth,boxDepth,boxHeight], bevelSize);

}

//Gets how tall the box is off of its back
function getBoxHeight() = (max(rdlHeights) * sin(45)) + bottomPad;

function getBoxWidth() = max(getAllWidth()) + (bevelSize*2);

function getAllWidth() = [
    for (i=[0:len(rowDataList)-1])
        len(rowDataList[i][0]) == 2 ?
            ((rowDataList[i][0][0]+padding)*rowDataList[i][1])+padding :
            ((rowDataList[i][0][2]+padding)*rowDataList[i][1])+padding
];

function getBoxDepth() =
    (add(rdlDiameters) * 2 * sin(45)) +
    ((rdlHeights[len(rdlHeights)-1] - rdlDiameters[len(rdlDiameters)-1]) * sin(45)) +
    (padding * (len(rowDataList)+1)) +
    (bevelSize * 2);

module cylinderRowSetup(){
    for (i=[0:len(rowDataList)-1]) {
        // Get list of all diameters so far and yOffset required because of it
        dSoFar = partial(rdlDiameters,0,i);
        hSoFar = partial(rdlRadius,0,i);
        rSoFar = partial(rdlRadius,0,i);

        // Type
        size = rowDataList[i][0];
        height = rdlHeights[i];
        // Count
        count = rowDataList[i][1];
        // Z Offset
        zOffset = -(rdlHeightsHalf[i] - rdlRadius[i]) * sin(45);
        // Y Offset
        yOffset = ((add(dSoFar)*2) - (rdlRadius[i]*3) + rdlHeightsHalf[i]) * sin(45) + (padding*i-1);

        // Build row a cylinders
        cylinderRow(size, count, yOffset, zOffset);
    }
}

// Make a beveled box with a square bottom
// d is dimensions, b is bevel size
module bevelBox(d, b){
    do = [for (i=[0:2]) (d[i]/2) - b];
    hull(){
        // Sheres in top corners
        translate([do.x, do.y, do.z]) sphere(b);
        translate([-do.x, -do.y, do.z]) sphere(b);
        translate([-do.x, do.y, do.z]) sphere(b);
        translate([do.x, -do.y, do.z]) sphere(b);
        // Cylinders on sides
        translate([do.x, do.y, 0-(b/2)]) cylinder(h=d.z-(b), r=b, center=true);
        translate([-do.x, -do.y, 0-(b/2)]) cylinder(h=d.z-(b), r=b, center=true);
        translate([do.x, -do.y, 0-(b/2)]) cylinder(h=d.z-(b), r=b, center=true);
        translate([-do.x, do.y, 0-(b/2)]) cylinder(h=d.z-(b), r=b, center=true);
        // Cylinders across the top parts
        translate([do.x, 0, do.z]) rotate([90,0,0]) cylinder(h=d.y-(2*b), r=b, center=true);
        translate([-do.x, 0, do.z]) rotate([90,0,0]) cylinder(h=d.y-(2*b), r=b, center=true);
        translate([0, do.y, do.z]) rotate([0,90,0]) cylinder(h=d.x-(2*b), r=b, center=true);
        translate([0, -do.y, do.z]) rotate([0,90,0]) cylinder(h=d.x-(2*b), r=b, center=true);
    }
}

// Creates a row of simple cylinders
module cylinderRow(size, count, yOffset=0, zOffset=0) {
    d = size[0];
    h = size[1];
    depth = (len(size)>2) ? size[2] : -1;
    //Positioning for row elements if cylinder
    distBetween = d + padding;
    distTotal = ((count-1)*d) + ((count-1)*padding);
    distHalf = distTotal/2;
    //Position for row elements if rectangular
    distBetweenDepth = depth + padding;
    distTotalDepth = ((count-1)*depth) + ((count-1)*padding);
    distHalfDepth = distTotalDepth/2;
    // Create row of elements
    for (i=[0:count-1]){
        if (depth == -1){
            translate([(distBetween*i)-distHalf,yOffset,zOffset])
            rotate(angle)
            cylinder(h=h, d=d, center=true);
        }
        else {
            translate([(distBetweenDepth*i)-distHalfDepth,yOffset,zOffset])
            rotate(angle)
            cube([depth,d,h], center=true);
        }
    }
}

// Create a partial list from a larger list
function partial(list,start,end) = [for (i = [start:end]) list[i]];

// Simple additive function that sums values in a list
function add(v, i = 0, r = 0) = i < len(v) ? add(v, i + 1, r + v[i]) : r;

```


[User]
Hey, I'm looking to design a broadfork, and I have a pretty good idea of what I want, but I need some help with the OpenSCAD code to make it parametric. I want to be able to adjust the number of tines, the length and angle of the tines, the overall width of the fork, and the handle length. I'm planning on making it out of cut sheet steel and pipe, so those dimensions need to be configurable too. I've got some basic ideas about construction, and I would like the code to follow these ideas.

Here’s a breakdown of what I’m envisioning:

*   **Tines:** I want tines that are essentially flat plates, but with a rounded leading edge that tapers back into the flat plate. The tines will be angled forward relative to the handle using a configurable angle. I need to be able to control the length of these tines and how far they are from the horizontal pipe. I'm thinking there should be two different shapes of tines: an inner shape that is just the flat tine, and an outer shape that also has a mounting bracket on it for the horizontal pipe.
*  **Horizontal Pipe:** The horizontal part of the fork is a single pipe, and I need to be able to adjust the diameter of this pipe. The two outermost tines attach with the mounting bracket, and the inner tines just connect directly to this pipe.
*   **Handles:** Two pipes are used for handles, attached on either side of the horizontal pipe. These handle pipes need to be a configurable diameter and a configurable length.
*   **Parametric Customization:** I need to be able to specify:
    *   `pipe_diameter`: The nominal diameter of the pipe used for the horizontal span and the handles (e.g., 1", 1.25")
    *   `sheet_thickness`: The thickness of the sheet metal used for the tines (e.g., 3/8", 1/2").
    *   `tines`: The total number of tines.
    *   `rake_angle`: The angle of the tines relative to the handle.
    *   `tine_length`: The length of the tines.
    *  `fork_width`: The overall width of the horizontal pipe and tine arrangement.
    *   `handle_length`: The length of the handle pipes.
    *  `tine_moment_radius_factor` - a multiplier to adjust how broad the tines are.
    *  `arm_moment_radius_factor` - a multiplier to adjust the width of the mounting brackets for the outer tines.
    *   `tolerance`: A small tolerance to apply for fitment.

I'd like two modes, one that renders the whole assembly together, and one that shows the individual parts to be cut out from steel sheet. I'm not sure of the best way to do this, but I think it would involve `projection()` or `linear_extrude()` in some way?

Could you generate the code to do this? I don't need any external libraries for this project. Thanks.

[Assistant]
uberfork3.scad:
```
//
// Copyright Jesse Parker August 2022
//
// UberFork is a big, brawny broadfork for turning soil
//
// Designed to be cut from sheet steel (plasma, waterjet) and welded
// together. Standard steel pipe comprises the other parts

// Customizer settings

render = "assembly"; // ["assembly", "cut"]

pipe_diameter = 1.305; // [1.050:"NPS 3/4\"", 1.305:"NPS 1\"", 1.660:"NPS 1 1/4\""]
sheet_thickness = 0.375; // [0.375:"3/8\"", 0.438:"7/16\"", 0.5:"1/2\"", 0.563:"9/16\"", 0.625:"5/8\""]
tines = 4; // [ 2 : 10 ]
rake_angle = 15; // [ 0 : 30 ]
tine_length = 300;
fork_width = 500;
handle_length = 1200;

tine_moment_radius_factor = 0.7; //[0.6 : .1: 2]
arm_moment_radius_factor = 0.9; //[0.6 : .1: 2]

tolerance = 1.5; // [1:.5:5]

/* [Hidden] */
$fa = 2;
$fs = 2;

in = 25.4; // mm/inch

span_d = pipe_diameter * in;
span_w = fork_width;
span_moment_r = span_d * tine_moment_radius_factor ; // cross sectional moment of inertia at horizontal span
bracket_moment_r = span_d * arm_moment_radius_factor;

tine_l = tine_length;

sheet_t=sheet_thickness * in;
//arm_d = 1*in;
arm_l = handle_length;

bracket_top_r = span_d * tine_moment_radius_factor;
bracket_l = span_d*4;
tol = tolerance;

tine_bottom_r = 3;
tine_bevel_r = span_moment_r/2;
tine_angle = rake_angle;

pipe_schedule = [
    [40,    3/4,    1.050,  0.824],
    [40,    1,  1.315   ,1.049],
];

function pipe_od (schedule = 40, nominal = 1) = 10;

module tine_bottom(d = span_d, l = tine_l, t = 3/8*in, tol = tol) {
    hull() {
            cylinder(r=span_moment_r, h=t, center=true);
            translate([0,l-tine_bottom_r,0])
                cylinder(r=tine_bottom_r, h=t, center=true);
    }
}

module tine(d = span_d, l = tine_l, t = 3/8*in, tol = tol) {
    //translate([0,0,-t/2])
    //linear_extrude(height=t)
    difference() {
        //translate([-span_moment_r,0])
        //    polygon([
        //    [0,0],
        //    [0,l],
        //    [10,l],
        //    [span_moment_r*2,0],
        //    [0,0]
        //]);
        rotate(tine_angle, [0,0,1])
        hull() {
            translate([-span_moment_r+tine_bevel_r,-d/2+tine_bevel_r,0])
                cylinder(r=tine_bevel_r, h=t, center=true);
            translate([span_moment_r-tine_bevel_r,-d/2+tine_bevel_r,0])
                cylinder(r=tine_bevel_r, h=t, center=true);
            difference() {
                tine_bottom(d, l, t, tol);
                translate([0,-span_moment_r,0])
                    cube([span_moment_r*2,span_moment_r*2,t+1], center=true);
            }
        }
        cylinder(r=d/2+tol/2, h = t+1, center=true);
    }
}
//tine();

module tine_with_bracket(d = span_d, l = tine_l, t = sheet_t, tol = tol) {
    difference() {
        union() {
            rotate(tine_angle, [0,0,1])
                tine_bottom(bracket_moment_r, l, t);
            hull() {

                translate([0,-bracket_l+bracket_top_r,0])
                    cylinder(r=bracket_top_r, h=t, center=true);
                cylinder(r=bracket_moment_r, h=t, center=true);

            }
            cylinder(r=bracket_moment_r, h=t, center=true);
//translate([0,-d*3,0])
            //cube([d*2, d*6, t], center=true);
        }
        translate([0,-bracket_l/2,0])
            cube([d+tol,bracket_l+.01,t+1],center=true);
        cylinder(r=d/2 + tol/2, h=t+1, center=true);
    }
}
//!tine_with_bracket();

module span(d = span_d, w = span_w) {
    cylinder(r=d/2, h = span_w, center=true);
}

module bracket_tines(w = span_w, t= sheet_t) {
    translate([0,0,w/2 - t])
        tine_with_bracket();
    translate([0,0,-w/2 + t])
        tine_with_bracket();
}

module arms(d= span_d, l = arm_l, w= span_w, t = sheet_t) {
    translate([0,-l/2-d/2,w/2-t])
        rotate(90,[1,0,0])
            cylinder(r = d/2, h = l, center=true);
    translate([0,-l/2-d/2,-w/2+t])
        rotate(90,[1,0,0])
            cylinder(r = d/2, h = l, center=true);
}

module center_tines(num = tines, w = span_w, t = sheet_t) {
    space = (w - 2*t) / (num-1);
    for ( i = [0:num-3]) {
        translate([0,0,-w/2 + t +space + i * space])
        tine();
    }
}
//tine();
//tine_with_bracket();
module print() {
    for(i = [0:tines-3]) {
        translate([0,tine_l*(i%2),0])
        rotate(180*(i%2), [1,0,0])
        translate([(span_moment_r+6)*i+span_moment_r*2.5,0,0])
            rotate(-tine_angle, [0,0,1])
                tine();
    }
rotate(-tine_angle, [0,0,1])
    tine_with_bracket();
translate([bracket_moment_r,tine_l+bracket_moment_r,0])
    rotate(180,[1,0,0])
        rotate(-tine_angle, [0,0,1])
            tine_with_bracket();
}
//projection()

//print();
module ass() {

    center_tines();

    arms();

    span();
    bracket_tines();

}

//ass();

if (render == "assembly")  ass(); else projection() print();

```


[User]
Okay, I'm looking to design a customizable planetary gear bearing, and I've got some specific requirements. I want to be able to control the outer diameter and thickness of the ring, as well as the clearance between the gears. It needs to have a variable number of planet gears and teeth on those planets. I'd also like to specify an *approximate* number of teeth on the sun gear since I understand the actual number might be slightly different to make the gears mesh properly. I'd like to adjust the pressure angle, and the number of teeth to use for the herringbone twist angle. I need to also specify the width of the center hexagonal hole that will house a drive axle. Finally, I need to control the *maximum* depth of the teeth.

On the outer perimeter of the ring, I want to include some debossed text. I need to specify two separate messages, each with its own arc angle to control the length of the text, and a start angle for the second message so it can be positioned properly. I also need to set the font size for those messages. I'd also like to include an SVG image on the outer perimeter. I need to be able to specify the SVG's path, as well as its aspect ratio, and its start angle.

I'd like all of this to be generated into parametric OpenSCAD files. I am expecting that a helper library will be created to handle the involute gear generation that can be imported into the main file. In general, please make the code as legible as possible with comments so I can understand the generated code.

[Assistant]
bearing_with_text.scad:
```
// Planetary gear bearing (customizable)

// outer diameter of ring
D=51.7;
// thickness
T=15;
// clearance
tol=0.15;
number_of_planets=5;
number_of_teeth_on_planets=7;
approximate_number_of_teeth_on_sun=9;
// pressure angle
P=45;//[30:60]
// number of teeth to twist across
nTwist=1;
// width of hexagonal hole
w=6.7;

DR=0.5*1;// maximum depth ratio of teeth

// First text to wrap around outside of spinner
message_01 = "STARBASE INDY";
// How far around the outside to spread the message, in degrees
arc_angle_01 = 140; // [0:1:359]
// Second text to wrap around outside of spinner
message_02 = "IMAGINE ANEW";
// How far around the outside to spread the message, in degrees
arc_angle_02 = 140; // [0:1:359]
// How far to shift the start position of the second message, in degrees
arc_start_02 = 97; // [0:1:359]
// Font size
font_size = 6; // [1:1:20]
// SVG image path
image_path = "Starbase.svg";
// Ratio of image width/height
image_with_ratio = 0.6829;
// How far to shift the start position of the image, in degrees
arc_start_03 = 300; // [0:1:359]

m=round(number_of_planets);
np=round(number_of_teeth_on_planets);
ns1=approximate_number_of_teeth_on_sun;
k1=round(2/m*(ns1+np));
k= k1*m%2!=0 ? k1+1 : k1;
ns=k*m/2-np;
echo(ns);
nr=ns+2*np;
pitchD=0.9*D/(1+min(PI/(2*nr*tan(P)),PI*DR/nr));
pitch=pitchD*PI/nr;
echo(pitch);
helix_angle=atan(2*nTwist*pitch/T);
echo(helix_angle);

phi=$t*360/m;

translate([0,0,T/2]){
	difference(){
		cylinder(r=D/2,h=T,center=true,$fn=100);
		herringbone(nr,pitch,P,DR,-tol,helix_angle,T+0.2);
        add_text(message_01, (T/4), arc_angle_01, 0);
        add_text(message_02, -(T/4), arc_angle_02, arc_start_02);
        add_logo();
	}
	rotate([0,0,(np+1)*180/ns+phi*(ns+np)*2/ns])
	difference(){
		mirror([0,1,0])
			herringbone(ns,pitch,P,DR,tol,helix_angle,T);
		cylinder(r=w/sqrt(3),h=T+1,center=true,$fn=6);
	}
	for(i=[1:m])rotate([0,0,i*360/m+phi])translate([pitchD/2*(ns+np)/nr,0,0])
		rotate([0,0,i*ns/m*360/np-phi*(ns+np)/np-phi])
			herringbone(np,pitch,P,DR,tol,helix_angle,T);
}

module rack(
	number_of_teeth=15,
	circular_pitch=10,
	pressure_angle=28,
	helix_angle=0,
	clearance=0,
	gear_thickness=5,
	flat=false){
addendum=circular_pitch/(4*tan(pressure_angle));

flat_extrude(h=gear_thickness,flat=flat)translate([0,-clearance*cos(pressure_angle)/2])
	union(){
		translate([0,-0.5-addendum])square([number_of_teeth*circular_pitch,1],center=true);
		for(i=[1:number_of_teeth])
			translate([circular_pitch*(i-number_of_teeth/2-0.5),0])
			polygon(points=[[-circular_pitch/2,-addendum],[circular_pitch/2,-addendum],[0,addendum]]);
	}
}

module add_logo() {
  rotate([0, 0, arc_start_03]){
    translate([(D/2)*0.97,-T/2*image_with_ratio,-(T/2)])
      rotate([90,0,90])
        linear_extrude(1)
          resize([0,T*0.90,0], auto=true)
            import(file = image_path);
  }
}

module add_text(message, y_offset, arc_angle, arc_start){
  msg_len = len(message);
  for(i=[0:1:msg_len]){
    rotate([0, 0, arc_start + i * arc_angle/msg_len]){
      translate([(D/2)*0.97,0,y_offset])
        rotate([90,0,90])
          linear_extrude(1)
            text(message[i],size=font_size,valign="center",halign="center",font="Liberation Mono:style=Bold");
    }
  }
}

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

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

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

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

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

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

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

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

// Mathematical Functions
//===============

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

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

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

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

```


[User]
Hi, I'm looking for some OpenSCAD code to generate sine wave patterns for 3D printing, specifically for bed adhesion and leveling tests. I'd like a design similar to what's described in the provided text, which is based on a Thingiverse design (thing:5516205) and also inspired by "Bed Level Stripes Test" by PD_224.

Here's a breakdown of what I need:

1.  **Sine Wave Generation:** I want to generate 2D sine wave patterns using a parametric approach. The main idea is that there would be a function that generates y-values based on a sine function, given an x value. I'd like to be able to easily adjust the sine wave's amplitude, frequency (related to the length of one cycle of the sine wave), and the total length of the wave. I am expecting the wave to be drawn from the left edge to the right edge in the x-direction.

2.  **Multiple Sine Wave Traces:** I need the ability to generate multiple, parallel sine wave traces. These traces should be arranged on the y axis, spaced apart with a configurable gap between them. So, I'd expect all the sine waves to have the same x-values but be placed at different y-values.

3.  **Line Width Control:** The thickness of each sine wave line should be adjustable. This is essentially the width that the line occupies as it's drawn. I'll be printing these, so the line width should be controllable as a parameter.

4.  **Layer Height:** The overall height of the generated shapes (essentially the extrusion height for printing) also needs to be a configurable parameter.

5.  **Resolution Control:** I need some control over the "smoothness" of the sine wave. This translates to the number of points used to approximate the wave when drawing. So, I'd like a parameter that controls the number of segments used to draw each sine wave.

6.  **2D Drawing Function:** I would like to have a module that actually traces the lines in OpenSCAD. I want to generate the 2D curves, and then use linear_extrude() to raise the shape to a specified height.

7.  **Include 2Dgraphing Library:** I see that the original implementation used a `2dgraphing.scad` library, so I'd like to use a similar structure that handles the low-level tracing. I am expecting the "2dgraph" module to be defined in the library along with other helper functions. You can assume I have the `2dgraphing.scad` library available in my include path, so please include it and use any helper functions as needed. (I am not providing you the library source in this message; please do not write any code for the 2dgraphing library.)

8.  **Parametric Design:** I want to easily modify all the relevant parameters (amplitude, frequency, total length of wave, spacing between sine wave traces, line width, layer height, number of points) through variables at the top of the `.scad` file.

9. **Radians/Degrees:** I see in the example that the code converts degrees to radians before using sine(). I'd like to specify the frequency in degrees so it's easier to set the wavelength (e.g. 360 would give a full wave).

10. **Units:** The design should be in millimeters (mm).

Could you generate the necessary OpenSCAD code for this? I'm looking for a main `.scad` file and then the associated `2dgraphing.scad` helper library file.  Thanks!

[Assistant]
sinewaves.scad:
```
/* Cartesian equations */
/* Sine wave bed adhesion/bed leveling test */
// https://www.thingiverse.com/thing:5516205
// CC BY Drf5n drf5na@gmail.com 2022-09-16

// Put the 2dgraphiing.scad file in your OpenSCAD include path per
// https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Libraries
include <2dgraphing.scad> // http://www.thingiverse.com/thing:11243

height = 0.2; // 0.1
width = 0.3; // 0.1
amplitude = 20; // 1
points = 500; // 1

pi = 3.14159;
function _f(x) = amplitude*sin((1/8)*x);
function f(x) = _f(d2r(x)); // called by function

linear_extrude(height=height)
      2dgraph([-100, 100], width, steps=points);

translate([0,40,0]) linear_extrude(height=height)
      2dgraph([-100, 100], width, steps=points);

translate([0,80,0]) linear_extrude(height=height)
      2dgraph([-100, 100], width, steps=points);

translate([0,-40,0]) linear_extrude(height=height)
      2dgraph([-100, 100], width, steps=points);

translate([0,-80,0]) linear_extrude(height=height)
      2dgraph([-100, 100], width, steps=points);
```
2dgraphing.scad:
```
/*************
* Examples *
*************/
//
// https://www.thingiverse.com/thing:11243/files
//
// To render an example, remove the comments
pi = 3.1415926536;
e = 2.718281828;
// function to convert degrees to radians
function d2r(theta) = theta*360/(2*pi);

/* Cartesian equations */
/* Sine wave */
//pi = 3.14159;
//function _f(x) = 20*sin((1/4)*x);
//function f(x) = _f(d2r(x));
//linear_extrude(height=5)
//	2dgraph([-50, 50], 3, steps=50);

/* Parabola */
//function f(x) = pow(x, 2)/20;
//linear_extrude(height=5)
//	2dgraph([-50, 50], 3, steps=40);

/* Ellipsoid - use a cartesion equation for a half ellipse, then rotate extrude it */
//function f(x) = sqrt((2500-pow(x, 2))/3);
//rotate_extrude(convexity=10, $fn=100)
//	rotate([0, 0, -90]) 2dgraph([-50, 50], 3, steps=100);

/* Blancmange curve (an approximation)
works best to export to dxf first, then extrude that.
the 2d render takes a long time at this resolution */
//function blancmange(x, n) = abs(round(pow(2, n)*x)-pow(2, n)*x)/pow(2, n);
//function f(x) = blancmange(x, 0) + blancmange(x, 1) + blancmange(x, 2) + blancmange(x, 3) + blancmange(x, 4) + blancmange(x, 5) + blancmange(x, 6);
////scale([100, 100, 100]) linear_extrude(height=0.125) import_dxf("blancmange.dxf");
//2dgraph([0, 1], 0.02, steps=480);

/* Polar equations */
/* Rose curve */
//function r(theta) = cos(4*theta);
//scale([20, 20, 20]) linear_extrude(height=0.15)
//	2dgraph([0, 720], 0.1, steps=160, polar=true);

/* Archimedes spiral */
//function r(theta) = theta/2*pi;
//scale([0.02, 0.02, 0.02]) linear_extrude(height=150)
//	2dgraph([0, 360*3], 50, steps=100, polar=true);

/* Golden spiral */
//function r(theta) = pow(e, 0.0053468*theta);
//linear_extrude(height=50)
//	2dgraph([0, 7*180], 1, steps=300, polar=true);

/* Parametric equations */
/* 9-pointed star */
//function x(t) = cos(t);
//function y(t) = sin(t);
//scale([10, 10, 10]) linear_extrude(height=0.25)
//	2dgraph([10, 1450], 0.1, steps=9, parametric=true);

/* Squiggly */
//function _x(t) = sin(t)*cos(t) + sin(2*t)*cos(2*t) + sin(3*t)*cos(3*t) + sin(4*t)*cos(4*t);
//function _y(t) = sin(t)*cos(2*t) + sin(3*t)*cos(4*t) + sin(5*t)*cos(6*t) + sin(7*t)*cos(8*t);
//function x(t) = _x(d2r(t));
//function y(t) = _y(d2r(t));
//scale([20, 10, 20]) linear_extrude(height=0.15)
//	2dgraph([0, 3*pi], 0.06, steps=400, parametric=true);

/* Butterfly curve! */
//function _x(t) = sin(t)*(pow(e, cos(t))-2*cos(4*t)-pow(sin(t/12), 5));
//function _y(t) = cos(t)*(pow(e, cos(t))-2*cos(4*t)-pow(sin(t/12), 5));
//function x(t) = _x(d2r(t));
//function y(t) = _y(d2r(t));
////scale([20, 20, 20]) linear_extrude(height=0.125) import_dxf("butterflycurve.dxf");
//2dgraph([0, 22*pi], 0.06, steps=2000, parametric=true);

/*********************
* End of examples *
*********************/

// These functions are here to help get the slope of each segment, and use that to find points for a correctly oriented polygon
function diffx(x1, y1, x2, y2, th) = cos(atan((y2-y1)/(x2-x1)) + 90)*(th/2);
function diffy(x1, y1, x2, y2, th) = sin(atan((y2-y1)/(x2-x1)) + 90)*(th/2);
function point1(x1, y1, x2, y2, th) = [x1-diffx(x1, y1, x2, y2, th), y1-diffy(x1, y1, x2, y2, th)];
function point2(x1, y1, x2, y2, th) = [x2-diffx(x1, y1, x2, y2, th), y2-diffy(x1, y1, x2, y2, th)];
function point3(x1, y1, x2, y2, th) = [x2+diffx(x1, y1, x2, y2, th), y2+diffy(x1, y1, x2, y2, th)];
function point4(x1, y1, x2, y2, th) = [x1+diffx(x1, y1, x2, y2, th), y1+diffy(x1, y1, x2, y2, th)];
function polarX(theta) = cos(theta)*r(theta);
function polarY(theta) = sin(theta)*r(theta);

module nextPolygon(x1, y1, x2, y2, x3, y3, th) {
	if((x2 > x1 && x2-diffx(x2, y2, x3, y3, th) < x2-diffx(x1, y1, x2, y2, th) || (x2 <= x1 && x2-diffx(x2, y2, x3, y3, th) > x2-diffx(x1, y1, x2, y2, th)))) {
		polygon(
			points = [
				point1(x1, y1, x2, y2, th),
				point2(x1, y1, x2, y2, th),
				// This point connects this segment to the next
				point4(x2, y2, x3, y3, th),
				point3(x1, y1, x2, y2, th),
				point4(x1, y1, x2, y2, th)
			],
			paths = [[0,1,2,3,4]]
		);
	}
	else if((x2 > x1 && x2-diffx(x2, y2, x3, y3, th) > x2-diffx(x1, y1, x2, y2, th) || (x2 <= x1 && x2-diffx(x2, y2, x3, y3, th) < x2-diffx(x1, y1, x2, y2, th)))) {
		polygon(
			points = [
				point1(x1, y1, x2, y2, th),
				point2(x1, y1, x2, y2, th),
				// This point connects this segment to the next
				point1(x2, y2, x3, y3, th),
				point3(x1, y1, x2, y2, th),
				point4(x1, y1, x2, y2, th)
			],
			paths = [[0,1,2,3,4]]
		);
	}
	else {
		polygon(
			points = [
				point1(x1, y1, x2, y2, th),
				point2(x1, y1, x2, y2, th),
				point3(x1, y1, x2, y2, th),
				point4(x1, y1, x2, y2, th)
			],
			paths = [[0,1,2,3]]
		);
	}
}

module 2dgraph(bounds=[-10,10], th=2, steps=10, polar=false, parametric=false) {
	step = (bounds[1]-bounds[0])/steps;
	union() {
		for(i = [bounds[0]:step:bounds[1]-step]) {
			if(polar) {
				nextPolygon(polarX(i), polarY(i), polarX(i+step), polarY(i+step), polarX(i+2*step), polarY(i+2*step), th);
			}
			else if(parametric) {
				nextPolygon(x(i), y(i), x(i+step), y(i+step), x(i+2*step), y(i+2*step), th);
			}
			else {
				nextPolygon(i, f(i), i+step, f(i+step), i+2*step, f(i+2*step), th);
			}
		}
	}
}

```


[User]
I need a set of OpenSCAD files to generate custom PCI brackets.  I'd like the core bracket to be parametric, so I can easily adjust the size and hole positions. I'll provide a breakdown of the features and their parameters:

**Core Bracket:**

*   **`resolution`**:  This should control the `$fn` value for smooth curves.
*   **`low_profile`**: A boolean flag (0 or 1) to choose between a low-profile or standard-height bracket. This will influence the overall length.
*   **`bracket_thickness`**:  The base thickness of the bracket itself.
*   **`angled_bottom_tab`**: A boolean that enables or disables angled edges of the bottom tab.
*   **`decorative_hole`**: A boolean flag to include an optional decorative hole in the bracket.

**Screw Mount Tabs:**

*   **`first_screw_tab_enabled`**, **`second_screw_tab_enabled`**: Booleans to enable or disable the two screw mount tabs.
*   **`screw_mounts_on_bottom_of_pcb`**:  Boolean flag indicating whether the screw mounts are on the bottom or top of the PCB.
*   **`screw_mount_width`**, **`screw_mount_depth`**: Dimensions of the screw mount tabs.
*   **`screw_mount_hole_r`**: Radius for the screw hole.
*   **`screw_mount_thread_distance_base`**: Distance from the base of the screw mount to the start of the thread.
*   **`screw_mount_inset_r`**: Radius for the bolt head inset.
*   **`screw_mount_inset_depth`**: Depth of the bolt head inset.
*   **`screw_mount_caulking`**: amount of caulking.
*   **`first_mount_x`**: X position of the first screw mount.
*  **`mount_hole_distance`**: Distance between first and second screw mount.
*   **`screw_mount_y`**: Y position of the screw mount from the top of the PCB.
*   **`second_mount_x`**: X position of the second screw mount.

**Holes:**

I'll need support for multiple circular holes, rectangular holes, and standard D-sub connector cutouts. Also need a DVI cutout.  These should be parametrically defined, with each hole having its own enable flag. I'll need the following for each hole:
 *   **`hole_N_enabled`**: Boolean flag to enable the hole. (where `N` is a number from 1-6 for both round and rectangular holes, 1-3 for D-sub holes)
    *   **`hole_N_x`**: X position of the center of the hole, relative to the first screw hole
    *   **`hole_N_y`**: Y position of the center of the hole, relative to the top of the PCB
    *   **`hole_N_d`**: Diameter of the circular holes.
    *    **`rectangular_hole_N_w`**: Width of the rectangular holes.
    *   **`rectangular_hole_N_h`**: Height of the rectangular holes.
    *   **`dsub_hole_N_type`**: Select from a dropdown menu of 0 for none, 15.46 for DB9 (VGA/Serial), 23.79 for DB15 (Gameport) and 37.51 for DB25 (Parallel).
     *    **`dsub_hole_N_invert`**: Boolean (0 or 1) whether to invert the dsub hole
   *   **`dvi_hole_enabled`**: Boolean to enable a DVI cutout.
     * **`dvi_hole_x`**: X offset from first screw hole.
     * **`dvi_hole_y`**: Y offset from the top of the pcb.
    *  **`dvi_hole_invert`**: Boolean (0 or 1) to invert the DVI cutout

**Other:**

*   **`pcb_thickness`**: Thickness of the PCB for reference.
*   **`top_tab_thickness`**: Thickness of top tab
*   **`isa_slot`**: Boolean indicating whether this bracket is for ISA slot (vs PCI/PCIe)
*   **`shown_by_customizer`**: Not entirely sure what this does - it should likely be configurable but its default value is 0.

I'd also appreciate if the code uses a library for the dsub cutouts if possible. It doesn't matter which one.

Could you generate the OpenSCAD files for me using these parameters? I will start with a test config to see if it fits my board, but I should be able to modify the values easily.

[Assistant]
bracket.scad:
```
resolution=32; // $fn
low_profile=0; // [0:false, 1:true]
bracket_thickness = 1.4; // base bracket depth

angled_bottom_tab = 1; // [0:false, 1:true]
decorative_hole=0; // [0:false, 1:true]
isa_slot=0; // [0:false, 1:true]
pcb_thickness=1.6;
top_tab_thickness = 3;
/* [Screw mount tabs] */

first_screw_tab_enabled = 1; // [0:false, 1:true]
second_screw_tab_enabled = 1; // [0:false, 1:true]
screw_mounts_on_bottom_of_pcb = 0; // [0:false, 1:true]
screw_mount_width = 7.5;
screw_mount_depth = 2.5;
screw_mount_hole_r = 1.55; // m3 bolt
screw_mount_thread_distance_base = 8;
screw_mount_inset_r = 2.9; // m3 bolt head
screw_mount_inset_depth = 0;
screw_mount_caulking = 0.5;

first_mount_x = 10.11;
mount_hole_distance = 85.4;
// If on top of the PCB, it is recommended to have this be at least the thickness of the PCB
screw_mount_y = 2.7;
second_mount_x = first_mount_x + mount_hole_distance;

/* [Round Holes] */
hole_1_enabled = 1; // [0:false, 1:true]
// X is offset from the first screw hole (i.e. the screw hole closest to the case mount cutout)
hole_1_x = 11.01;
// Y is offset from top of pcb
hole_1_y = 6.5;
hole_1_d = 9.5;

hole_2_enabled = 1; // [0:false, 1:true]
hole_2_x = 24.34;
hole_2_y = 6.5;
hole_2_d = 9.5;

hole_3_enabled = 1; // [0:false, 1:true]
hole_3_x = 63.41;
hole_3_y = 7;
hole_3_d = 6;

hole_4_enabled = 1; // [0:false, 1:true]
hole_4_x = 76.12;
hole_4_y = 7;
hole_4_d = 6;

hole_5_enabled = 0; // [0:false, 1:true]
hole_5_x = 37.69;
hole_5_y = 6.5;
hole_5_d = 9.5;

hole_6_enabled = 0; // [0:false, 1:true]
hole_6_x = 51.04;
hole_6_y = 6.5;
hole_6_d = 9.5;

/* [Rectangular Holes] */
// hole is at center of x,y positions
rectangular_hole_1_enabled = 0; // [0:false, 1:true]
// X is offset from the first screw hole (i.e. the screw hole closest to the case mount cutout)
rectangular_hole_1_x = 11.01;
// Y is offset from top of pcb
rectangular_hole_1_y = 6.5;
rectangular_hole_1_w = 10.5;
rectangular_hole_1_h = 10.5;

rectangular_hole_2_enabled = 0; // [0:false, 1:true]
rectangular_hole_2_x = 24.34;
rectangular_hole_2_y = 6.5;
rectangular_hole_2_w = 10.5;
rectangular_hole_2_h = 10.5;

rectangular_hole_3_enabled = 0; // [0:false, 1:true]
rectangular_hole_3_x = 63.41;
rectangular_hole_3_y = 7;
rectangular_hole_3_w = 10.5;
rectangular_hole_3_h = 10.5;

rectangular_hole_4_enabled = 0; // [0:false, 1:true]
rectangular_hole_4_x = 76.12;
rectangular_hole_4_y = 7;
rectangular_hole_4_w = 10.5;
rectangular_hole_4_h = 10.5;

rectangular_hole_5_enabled = 0; // [0:false, 1:true]
rectangular_hole_5_x = 37.69;
rectangular_hole_5_y = 6.5;
rectangular_hole_5_w = 10.5;
rectangular_hole_5_h = 10.5;

rectangular_hole_6_enabled = 0; // [0:false, 1:true]
rectangular_hole_6_x = 51.04;
rectangular_hole_6_y = 6.5;
rectangular_hole_6_w = 10.5;
rectangular_hole_6_h = 10.5;
// values taken from https://content.norcomp.net/rohspdfs/Connectors/PanelCutOuts/Panel_Cutout_D-Sub-Single.pdf

/* [D-Sub holes (Serial/VGA/Gameport)] */
// hole is at center of x,y positions
dsub_hole_1_type = 0; // [0:None, 15.46:DB-9(VGA/Serial), 23.79:DB-15(Gameport), 37.51:DB-25(Parallel)]
// X is offset from the first screw hole (i.e. the screw hole closest to the case mount cutout)
dsub_hole_1_x = 37.6;
// Y is offset from top of pcb
dsub_hole_1_y = 7.1;
dsub_hole_1_invert = 0; // [0:false, 1:true]

dsub_hole_2_type = 0; // [0:None, 15.46:DB-9(VGA/Serial), 23.79:DB-15(Gameport), 37.51:DB-25(Parallel)]
dsub_hole_2_x = 37.6;
dsub_hole_2_y = 7.1;
dsub_hole_2_invert = 0; // [0:false, 1:true]

dsub_hole_3_type = 0; // [0:None, 15.46:DB-9(VGA/Serial), 23.79:DB-15(Gameport), 37.51:DB-25(Parallel)]
dsub_hole_3_x = 37.6;
dsub_hole_3_y = 7.1;
dsub_hole_3_invert = 0; // [0:false, 1:true]

/* [DVI Hole]] */

dvi_hole_enabled = 0; // [0:false, 1:true]
dvi_hole_x = 37.6;
dvi_hole_y = 6;
dvi_hole_invert = 0; // [0:false, 1:true]

module __Customizer_Limit__ () {}  // This actually works
shown_by_customizer = 0; // [0:false, 1:true]

$fn = resolution;
bracket_width_length = low_profile? 71.46 : 112.78;
bracket_length = low_profile ? 79.20 : 120.02;
bracket_width = 18.42 + 0;
bottom_tab_width = 10.19 + 0;
bracket_bottom_tab_angle_length = (bracket_width - bottom_tab_width) / 2;

bracket_reinforce_width = bracket_width * .2;
bracket_reinforce_length = bracket_length * .78;

bracket_hole_dia = decorative_hole ? 4.75 : 0;
bracket_hole_distance = low_profile ? 66.19 : 107.01;

screw_mount_thread_distance = screw_mount_thread_distance_base + bracket_thickness;
screw_mount_length = screw_mount_thread_distance * 1.4 + bracket_thickness;

bracket_reinforce_depth = 0;
bracket_reinforce_offset_x = -6;
bracket_reinforce_offset_y = -5;

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

// top notch
notch_width = 2.54;
notch_length = 3.94-.86; // 3.94 - specified thickness of .86mm (vs actual thickness)

top_tab_width = 19.05; // had to fudge this
top_tab_length = 11.43;
top_tab_x = 0;
top_tab_y = 3.17; // 21.59 - 18.42
top_tab_screw_groove_dia = 4.42;
top_tab_screw_groove_fudge = -0.4; // kinda came out looking wrong so I'm faking it a bit here
top_tab_screw_groove_center = 3.18 + top_tab_screw_groove_fudge;
top_tab_screw_groove_distance = 6.35;
top_tab_screw_groove_translate_z = top_tab_length - top_tab_screw_groove_dia / 2 - top_tab_screw_groove_distance;

top_tab_useless_notch_length = 3.05;
top_tab_useless_notch_width = top_tab_screw_groove_distance - top_tab_screw_groove_dia / 2;
top_tab_useless_notch_z = top_tab_length - top_tab_useless_notch_width;
top_tab_useless_notch_y = top_tab_useless_notch_width / 2 + 8.05 - top_tab_useless_notch_length / 2;

translate([-top_tab_thickness + top_tab_x, -top_tab_y, 0]) difference() {
  cube([top_tab_thickness, top_tab_width, top_tab_length + bracket_thickness]);
  translate([-.5, -1, top_tab_screw_groove_translate_z + bracket_thickness]) {
    cube([top_tab_thickness + 1, top_tab_screw_groove_center * 2, top_tab_screw_groove_dia]);
    translate([1, top_tab_screw_groove_center * 2, top_tab_screw_groove_dia / 2])
      rotate([0, 90, 0])
      cylinder(r=top_tab_screw_groove_dia / 2, h=top_tab_thickness * 2, $fn=20, center=true);
  }
  translate([-.5, top_tab_useless_notch_y, top_tab_useless_notch_z + bracket_thickness]) {
    cube([top_tab_thickness + 1, top_tab_useless_notch_length, top_tab_useless_notch_width + 1]);
  }
}

// screw mounts
module screwmount(x) {

  translate([0, isa_slot? -screw_mount_y - screw_mount_depth : -bracket_width + screw_mount_y, 0]) // easier to be explicit here than factor it in the below statement
  translate([x - screw_mount_width / 2, bracket_width, -screw_mount_length + bracket_thickness])
    // cut out the threads
    difference() {
      union() {
        cube([screw_mount_width, screw_mount_depth, screw_mount_length]);
        //top caulking
        if ((isa_slot && bracket_width-screw_mount_y +screw_mount_caulking < bracket_width) || (!isa_slot &&  screw_mount_y + screw_mount_depth+ screw_mount_caulking < bracket_width)){
        difference(){
        translate([screw_mount_width, screw_mount_depth-.05, screw_mount_length-bracket_thickness-screw_mount_caulking +.05])
          rotate([90,0,180])
          prism(screw_mount_width,screw_mount_caulking+.05,screw_mount_caulking+.05);
        }
        }
        //bottom caulking
        if ((isa_slot && bracket_width-screw_mount_y -screw_mount_depth -screw_mount_caulking > 0) || (!isa_slot && screw_mount_y - screw_mount_caulking > 0)){
        translate([0, 0, screw_mount_length-bracket_thickness-screw_mount_caulking +.05])
          rotate([90,0,0])
          prism(screw_mount_width,screw_mount_caulking+.05,screw_mount_caulking+.05);
        }
      }
       translate([screw_mount_width / 2, -2, screw_mount_length - screw_mount_thread_distance])
         rotate([0, 90, 90]) cylinder(r=screw_mount_hole_r, h=10, $fn=20);
      //inset
      translate([screw_mount_width / 2, ((isa_slot && screw_mounts_on_bottom_of_pcb) || (!isa_slot && !screw_mounts_on_bottom_of_pcb))?(screw_mount_depth - screw_mount_inset_depth + 0.01):(-0.01),
        screw_mount_length - screw_mount_thread_distance])
          rotate([0, 90, 90])
          cylinder(r=screw_mount_inset_r, h=screw_mount_inset_depth, $fn=20);
    }
}

if (first_screw_tab_enabled){
  screwmount(first_mount_x);
}
if (second_screw_tab_enabled) {
  screwmount(second_mount_x);
}

// bottom notch
shiftover_width = top_tab_y;
shiftover_length = 2.92;

// build up the shiftover
translate([0, -shiftover_width, 0]) cube([shiftover_length, shiftover_width + .1, bracket_thickness]);
translate([
  shiftover_length,
  -shiftover_width,
  bracket_thickness])
  rotate([0, 90, 0])
  prism(bracket_thickness, shiftover_width, shiftover_width);

module translate_hole(hole_x,hole_y){
  y_offset_b = screw_mounts_on_bottom_of_pcb ? pcb_thickness + screw_mount_depth : 0;
  y_offset = !isa_slot ?
    hole_y + screw_mount_y + y_offset_b
    : bracket_width - (hole_y + screw_mount_y + y_offset_b);
  translate([first_mount_x+ hole_x, y_offset, -bracket_thickness])
    children();
}
module round_hole(hole_x,hole_y,hole_d){
  translate_hole(hole_x,hole_y)
    cylinder(10,hole_d/2,hole_d/2);
}
module rectangular_hole(hole_x,hole_y,hole_w,hole_h){
  translate_hole(hole_x,hole_y)
    cube([10, hole_w, hole_h], center=true);
}

module dsub_hole(hole_x,hole_y,c_sz, invert = false){
  translate_hole(hole_x,hole_y)
    dsub_cutout(c_sz, invert);
}
module dsub_cutout(c_sz, invert=false){
  $fn=64;
  rotate([0,0,90]){
    top_x_coord = !invert ? c_sz/2 : (c_sz-2)/2;
    bottom_x_coord = !invert ? (c_sz-2)/2 : c_sz/2;
    hole_coord = (c_sz+9.53)/2;
    y_distance = 5.66;
    y_coord = y_distance/2;
    translate([0,hole_coord,0])
        cylinder(r=1.55,h=10);
    translate([0,-hole_coord,0])
        cylinder(r=1.55,h=10);
    hull(){
      translate([y_coord,-top_x_coord,0]){
      cylinder(r=2.51,h=10);
      }
      translate([y_coord,top_x_coord,0]){
      cylinder(r=2.51,h=10);
      }
      translate([-y_coord,-bottom_x_coord,0]){
      cylinder(r=2.51,h=10);
      }
      translate([-y_coord,bottom_x_coord,0]){
      cylinder(r=2.51,h=10);
      }
    }
  }
}
module dvi_hole(hole_x,hole_y, invert = false){
  translate_hole(hole_x,hole_y)
    dvi_cutout(invert);
}
module dvi_cutout(invert=false){
  $fn=64;
  rotate([0,0,90]){
    a_sz = 26.1;
    top_r_sz = 1.2;
    bottom_r_sz = 3;
    c_sz = a_sz - (top_r_sz *2);
    e_sz = c_sz - ((bottom_r_sz - top_r_sz) * 2);
    hole_coord = 32.5 /2;
    y_distance = 9.6 - (top_r_sz+ bottom_r_sz);

    top_x_coord = !invert ? c_sz/2 : e_sz/2;
    bottom_x_coord = !invert ? e_sz/2 : c_sz/2;
    top_r = !invert ? top_r_sz : bottom_r_sz;
    bottom_r = !invert ? bottom_r_sz: top_r_sz ;

    y_coord = y_distance/2;

    translate([0,hole_coord,0])
        cylinder(r=1.6,h=10);
    translate([0,-hole_coord,0])
        cylinder(r=1.6,h=10);
    hull(){
      translate([y_coord,-top_x_coord,0]){
      cylinder(r=top_r,h=10);
      }
      translate([y_coord,top_x_coord,0]){
      cylinder(r=top_r,h=10);
      }
      translate([-y_coord,-bottom_x_coord,0]){
      cylinder(r=bottom_r,h=10);
      }
      translate([-y_coord,bottom_x_coord,0]){
      cylinder(r=bottom_r,h=10);
      }
    }
  }
}
// Main bracket part
difference() {
  // the base bracket
  cube([bracket_length, bracket_width, bracket_thickness]);

  // carve out the angles
  translate([
    bracket_width_length,
    bracket_bottom_tab_angle_length - .05,
    2])
    rotate([0, 90, 0])
    rotate([180, 0, 0])
    prism(bracket_bottom_tab_angle_length, bracket_bottom_tab_angle_length, bracket_bottom_tab_angle_length + 0.1);
  // other side
  translate([
    bracket_width_length + 0.1,
    bracket_bottom_tab_angle_length + bottom_tab_width + .05,
    -2])
    rotate([0, 90, 0])
    rotate([180, 0, 180])
    prism(bracket_bottom_tab_angle_length, bracket_bottom_tab_angle_length, bracket_bottom_tab_angle_length + 0.1);

  // ornamental hole
  translate([bracket_hole_distance, bracket_width / 2, -1.5])
    cylinder(r=bracket_hole_dia / 2, h=3, $fn=20);

  // notch out the shiftover
  translate([-0.95, bracket_width - notch_width, -1])
    cube([notch_length + 1, notch_width + .1, bracket_thickness + 2]);
  translate([
    notch_length,
    bracket_width - notch_width,
    2])
    rotate([0, 90, 0])
    prism(notch_width, notch_width + .2, notch_width);
  // shape the tab at the bottom
  if (angled_bottom_tab){
    translate([bracket_width_length - 0.05, -.05, -.5])
      cube([bracket_length - bracket_width_length + 0.1, bottom_tab_width+ bracket_bottom_tab_angle_length * 2 + .1, bracket_thickness + 1.05]);
  } else {
    translate([bracket_width_length - 0.05, -.05, -.5])
      cube([bracket_length - bracket_width_length + 0.1, bracket_bottom_tab_angle_length + .1, bracket_thickness + 1.05]);
    translate([bracket_width_length - 0.05, bracket_bottom_tab_angle_length + bottom_tab_width,-.5])
      cube([bracket_length - bracket_width_length + 0.1, bracket_bottom_tab_angle_length + 0.1, bracket_thickness + 1]);

  }

  y_offset_b = screw_mounts_on_bottom_of_pcb ? pcb_thickness + screw_mount_depth : 0;
  // holes

  //round holes
  if (hole_1_enabled){
    round_hole(hole_1_x,hole_1_y,hole_1_d);
  }
  if (hole_2_enabled){
    round_hole(hole_2_x,hole_2_y,hole_2_d);
  }
  if (hole_3_enabled){
    round_hole(hole_3_x,hole_3_y,hole_3_d);
  }
  if (hole_4_enabled){
    round_hole(hole_4_x,hole_4_y,hole_4_d);
  }
  if (hole_5_enabled){
    round_hole(hole_5_x,hole_5_y,hole_5_d);
  }
  if (hole_6_enabled){
    round_hole(hole_6_x,hole_6_y,hole_6_d);
  }
  //rectangular holes
  if (rectangular_hole_1_enabled){
    rectangular_hole(rectangular_hole_1_x,rectangular_hole_1_y,rectangular_hole_1_w, rectangular_hole_1_h);
  }
  if (rectangular_hole_2_enabled){
    rectangular_hole(rectangular_hole_2_x,rectangular_hole_2_y,rectangular_hole_2_w, rectangular_hole_2_h);
  }
  if (rectangular_hole_3_enabled){
    rectangular_hole(rectangular_hole_3_x,rectangular_hole_3_y,rectangular_hole_3_w, rectangular_hole_3_h);
  }
  if (rectangular_hole_4_enabled){
    rectangular_hole(rectangular_hole_4_x,rectangular_hole_4_y,rectangular_hole_4_w, rectangular_hole_4_h);
  }
  if (rectangular_hole_5_enabled){
    rectangular_hole(rectangular_hole_5_x,rectangular_hole_5_y,rectangular_hole_5_w, rectangular_hole_5_h);
  }
  if (rectangular_hole_6_enabled){
    rectangular_hole(rectangular_hole_6_x,rectangular_hole_6_y,rectangular_hole_6_w, rectangular_hole_6_h);
  }
  if (dsub_hole_1_type > 0){
    dsub_hole(dsub_hole_1_x, dsub_hole_1_y, dsub_hole_1_type, dsub_hole_1_invert);
  }
  if (dsub_hole_2_type > 0){
    dsub_hole(dsub_hole_2_x, dsub_hole_2_y, dsub_hole_2_type, dsub_hole_2_invert);
  }
  if (dsub_hole_3_type > 0){
    dsub_hole(dsub_hole_3_x, dsub_hole_3_y, dsub_hole_3_type, dsub_hole_3_invert);
  }
  if (dvi_hole_enabled){
    dvi_hole(dvi_hole_x, dvi_hole_y, dvi_hole_invert);
  }
}

if (angled_bottom_tab){
  difference(){
    translate([bracket_width_length-0.05, bracket_bottom_tab_angle_length-0.1,0])
    rotate([0,-5,0])
    translate([-0.2,0,0])
    cube([bracket_length - bracket_width_length + 0.1, bottom_tab_width+.3, bracket_thickness ]);

    translate([bracket_width_length-0.3, bracket_bottom_tab_angle_length-0.2,-bracket_thickness])
    cube([bracket_length - bracket_width_length, bottom_tab_width+.4,1.4]);
  }
}

// reinforce bracket body

//translate([
//  bracket_length / 2 - bracket_reinforce_length / 2 + bracket_reinforce_offset_x,
//  (isa_slot? (bracket_width  - bracket_width / 2 - bracket_reinforce_width / 2 + bracket_reinforce_offset_y) : bracket_width+bracket_reinforce_offset_y),
//  -bracket_reinforce_depth])
//  cube([bracket_reinforce_length, bracket_reinforce_width, bracket_reinforce_depth]);
////if (cf_slot_enabled) {
////  translate([
////    bracket_length / 2 - bracket_reinforce_length / 2 + bracket_reinforce_offset_x,
////    bracket_width - bracket_reinforce_width,
////    -cf_reinforce_screwmount_depth])
////    cube([bracket_reinforce_length, bracket_reinforce_width, cf_reinforce_screwmount_depth]);
////}
//

```


[User]
Hey, I'm looking to design some custom wall plates using OpenSCAD, and I need something pretty flexible. I've been using a modified version of the WALLY wall plate customizer, but I'd like to start fresh with a more modular design. I'm hoping you can help me generate a parametric OpenSCAD file (or files, if needed) from scratch.

Here's what I need:

**Core Functionality:**

*   **Variable Plate Width:** The wall plate needs to be able to accommodate 1 to 5 "gangs", which are basically slots for various modules or devices.
*   **Multiple Plate Sizes:** I need three plate size options: "Standard", "Junior-Jumbo", and "Jumbo". Each size will have different dimensions overall, as well as offsets for the modules and screw holes.
*   **Modular Cutouts:** The plate should support various module cutouts, which will include:
    *   Full Plate Types: These will fill the entire gang and include options like a standard toggle switch, a long toggle switch, a standard duplex outlet, a rocker/designer plate, and a mounting for an NSPanel.
    *   Individual Ports: These will occupy a smaller area within the gang and include a keystone jack, VGA port, HDMI port, DVI-I port, DisplayPort, Cat5e/Cat6 port, USB-A port, USB-B port, Firewire (IEEE 1394) port, DB-09 port, PS2 port, F-Type/Coaxial port, S-Video port, and a stereo headphone jack.
    *   "None" and "Blank": "None" will center any other selected module within the plate, while "Blank" will simply leave the slot empty.
*   **Top and Bottom Ports:** Each gang should have the option of a primary port and a bottom port. The bottom port is intended to go below the primary port in a single gang. However, note that certain modules (Toggle Switch, Long Toggle Switch, Duplex Outlet, and Rocker/Designer plate) should always be considered "full plate" options - if they are selected in the top or bottom, they should occupy the entire gang.
*   **NSPanel Support:** NSPanel cutouts need to be properly placed within the plate. There should be an option called "odd\_nspanel" that, when off, places the NSPanel centered between the selected gang and the previous gang. When on, it centers the NSPanel on the selected gang.
*   **Screw Holes:** Screw holes need to be included for attaching the plate to a standard electrical box, and be placed depending on the plate type (toggle, rocker, NSPanel, or plain).

**Customizer Settings (parameters):**

*   `plate_width`: Integer from 1 to 5 that determines the number of gangs.
*   `plate_size`: Integer, 0 for "Standard", 1 for "Junior-Jumbo", 2 for "Jumbo".
*   `1st_plate` to `5th_plate`: String, dropdown to select the top module type for each of the gangs (using the modular cutouts above).
*   `1st_plate_bottom_hole` to `5th_plate_bottom_hole`: String, dropdown to select the bottom module type for each of the gangs (using the modular cutouts above).
*   `odd_nspanel`: Boolean, turns the NSPanel slot centering on or off.

**Additional Notes:**

*   I'd like the design to be parametric and easily customizable.
*   I'm not aiming to use any existing libraries for now.

Let me know if you need any more details.

[Assistant]
WALLY_Customizer_NSPanel_v1.scad:
```
// WALLY - Wall Plate Customizer v1.3
// by TheNewHobbyist 2013 (http://thenewhobbyist.com)
// http://www.thingiverse.com/thing:47956
//
// Most of the connectors used with this thing are panel mount connectors from
// www.DataPro.net I can't vouch for their quality but they did have easy to
// find dimensions for their products so I used them.
//
// This could be tightened up considerably but I thought I'd get it out there for people
// to experiment with before I lose my mind staring at it too long. Also this is my
// first OpenSCAD model so cut me some slack ;)
//
//   Notes:
//
// * The plug types "Duplex Outlet", "Long_Toggle Switch", "Toggle Switch", and "Rocker"
//   will all use the entire plate when selected in top or bottom configuration. The
//   other selection (if not "none" will be selected.
//
// * "None" and "Blank should not be considered equal. if "None" is selected for one of
//   the two holes on a plate the other selection will be centered on that plate. If
//   "Blank" is selected that plug location will remain blank but the other selection
//   will NOT move into a centered position.
//
//	Change Log:
//
//	v1.1
//	UPDATED: Screw hole locations for designer and toggle plate types.
//	UPDATED: Hole size for designer plate type.
//	UPDATED: Outlet hole spacing
//
//	v1.2
//	ADDED: Long Toggle Switch for laird
//
//	v1.3
//	ADDED: 3 new sizes for plate export: Standard, Junior-Jumbo, and Jumbo.
//
//  v1.4
//  ADDED: Proper parametric NSPanel support
//  REFACTOR: Refactored plate1-plate5 modules into a single module with parameters,
//           allows modifying hole code in one place to keep it all consistent

  //////////////////////////
 // Customizer Settings: //
//////////////////////////

//How big are we talkin' here?
plate_width = 2; //	[1:5]

// Bigger hole in your wall? Try this
plate_size = 0; // [0:Standard, 1:Junior-Jumbo, 2:Jumbo]

//Pick a plug or full plate type
1st_plate = "rocker";  //	["none":None, "blank":Blank Port, "toggle":Full Plate - Toggle Switch, "long_toggle":Full Plate - Long Toggle Switch, "outlet":Full Plate - Duplex Outlet, "rocker":Full Plate - Rocker/Designer Plate, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack, "nspanel":NSPanel]
//Pick a bottom plug type or leave as none
1st_plate_bottom_hole = "none";  //	["none":None, "blank":Blank Port, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a plug or full plate type
2nd_plate = "none";  //	["none":None, "blank":Blank Port, "toggle":Full Plate - Toggle Switch, "long_toggle":Full Plate - Long Toggle Switch, "outlet":Full Plate - Duplex Outlet, "rocker":Full Plate - Rocker/Designer Plate, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack, "nspanel":NSPanel]
//Pick a bottom plug type or leave as none
2nd_plate_bottom_hole = "none";  //	["none":None, "blank":Blank Port, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a plug or full plate type
3rd_plate = "none";  //	["none":None, "blank":Blank Port, "toggle":Full Plate - Toggle Switch, "long_toggle":Full Plate - Long Toggle Switch, "outlet":Full Plate - Duplex Outlet, "rocker":Full Plate - Rocker/Designer Plate, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack, "nspanel":NSPanel]
//Pick a bottom plug type or leave as none
3rd_plate_bottom_hole = "none";  //	["none":None, "blank":Blank Port, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a plug or full plate type
4th_plate = "none";  //["none":None, "blank":Blank Port, "toggle":Full Plate - Toggle Switch, "long_toggle":Full Plate - Long Toggle Switch, "outlet":Full Plate - Duplex Outlet, "rocker":Full Plate - Rocker/Designer Plate, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack, "nspanel":NSPanel]
//Pick a bottom plug type or leave as none
4th_plate_bottom_hole = "none";  //	["none":None, "blank":Blank Port, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a plug or full plate type
5th_plate = "none";  //["none":None, "blank":Blank Port, "toggle":Full Plate - Toggle Switch, "long_toggle":Full Plate - Long Toggle Switch, "outlet":Full Plate - Duplex Outlet, "rocker":Full Plate - Rocker/Designer Plate, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack, "nspanel":NSPanel]
//Pick a bottom plug type or leave as none
5th_plate_bottom_hole = "none";  //	["none":None, "blank":Blank Port, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]

//Center NSPanel in odd sized plates
odd_nspanel = false;

  //////////////////////
 // Static Settings: //
//////////////////////

module GoAwayCustomizer() {
// This module is here to stop Customizer from picking up the variables below
}

hole_vector=[1st_plate, 2nd_plate, 3rd_plate, 4th_plate, 5th_plate];
hole_bottom_vector=[1st_plate_bottom_hole, 2nd_plate_bottom_hole, 3rd_plate_bottom_hole, 4th_plate_bottom_hole, 5th_plate_bottom_hole];

l_offset = [34.925,39.6875,44.45];
r_offset = [34.925,39.6875,44.45];

spacer = [0,0,46.0375,92.075,138.1125,184.15];
solid_plate_width = l_offset[plate_size] + spacer[plate_width] + r_offset[plate_size];

height_sizes = [114.3,123.825,133.35];

height = 114.3; //plate height (static)
gang_width=[0,69.86,115.824,161.925,206.375,254]; // Plate widths (standard)

edgewidth = solid_plate_width + 10; // Bevel setting for top and bottom
rightbevel = solid_plate_width - 4; // Bevel for right side (scales)

left_offset = 34.925; // Offset first hole position
switch_offset = 46.0375; // Offset all additional holes
thinner_offset=[0,0.92,0.95,0.96,0.97,0.973]; // Manual fix for right side wackiness

positions=[height_sizes[plate_size]/2,height_sizes[plate_size]/2 - 14.25,height_sizes[plate_size]/2 + 14.25];

// preview[view:north, tilt:bottom]

  ///////////////////
 // Hole Control: //
///////////////////

module plate_hole(type, bottom_type, index){
    hole_offset = switch_offset * (index-1);
	if (type == "toggle" || bottom_type == "toggle"){
		translate([0,l_offset[plate_size] + hole_offset,0]) toggle_screws();
		translate([0,l_offset[plate_size] + hole_offset,0]) hole("toggle");
	}
	else if (type == "long_toggle" || bottom_type == "long_toggle"){
		translate([0,l_offset[plate_size] + hole_offset,0]) toggle_screws();
		translate([0,l_offset[plate_size] + hole_offset,0]) hole("long_toggle");
	}
	else if (type == "rocker" || bottom_type == "rocker"){
		translate([0,l_offset[plate_size] + hole_offset,0]) rocker_screws();
		translate([0,l_offset[plate_size] + hole_offset,0]) hole("rocker");
	}
    else if (type == "nspanel" || bottom_type == "nspanel"){
		translate([0,l_offset[plate_size] - switch_offset + hole_offset,0]) box_screws();
        translate([0,l_offset[plate_size] + hole_offset,0]) box_screws();
        if(odd_nspanel) {
            translate([0,l_offset[plate_size] + hole_offset,0]) nspanel_screws();
            translate([0,l_offset[plate_size] + hole_offset,0]) hole("nspanel");
        }
        else {
            translate([0,l_offset[plate_size] - switch_offset/2 + hole_offset,0]) nspanel_screws();
            translate([0,l_offset[plate_size] - switch_offset/2 + hole_offset,0]) hole("nspanel");
        }
	}
	else if (type == "outlet" || bottom_type == "outlet") {
        translate([0,l_offset[plate_size] + hole_offset,0]) hole("outlet");
	}
	else if (bottom_type == "none" || type == "none") {
		translate([0,l_offset[plate_size] + hole_offset,0]) box_screws();
		translate([positions[0],l_offset[plate_size] + hole_offset,0]) hole(type);
	}
	else {
        translate([0,l_offset[plate_size],0]) box_screws();
        translate([positions[1],l_offset[plate_size] + hole_offset,0]) hole(type);
        translate([positions[2],l_offset[plate_size] + hole_offset,0]) hole(bottom_type);
	}
}

  /////////////////
 // SolidWorks: //
/////////////////

module plate_solid(type, bottom_type, index){
    solid_offset = switch_offset * (index-1);
    if (type == "keystone1" && bottom_type == "none") {
        translate([height_sizes[plate_size]/2 + 14.3,l_offset[plate_size] - 11.5 + solid_offset,-3.9]) hole("keystone_solid");
    }
    else if (type == "keystone1" && bottom_type != "outlet" && 1st_plate_bottom_hole != "toggle" && 1st_plate_bottom_hole != "rocker") {
        translate([height_sizes[plate_size]/2,l_offset[plate_size] - 11.5 + solid_offset,-3.9]) hole("keystone_solid");
    }
    if (bottom_type == "keystone1" && type == "none") {
        translate([height_sizes[plate_size]/2 + 14.3,l_offset[plate_size] - 11.5 + solid_offset,-3.9]) hole("keystone_solid");
    }
    else if (bottom_type == "keystone1" && type != "outlet" && type != "toggle" && type != "rocker") {
        translate([height_sizes[plate_size]/2 + 28.5,l_offset[plate_size] - 11.5 + solid_offset,-3.9]) hole("keystone_solid");
    }
}

  ///////////////////
 // PlateStation: //
///////////////////

//Plate size and bevel
module plate() {
	difference() {
		cube([height_sizes[plate_size],solid_plate_width,6]);
		translate([-4.3,-5,6.2]) rotate([0,45,0]) cube([6,edgewidth,6]); //Top Bevel
		translate([height_sizes[plate_size]-4.2,-5,6.25]) rotate([0,45,0]) cube([6,edgewidth,6]); //Bottom Bevel
		translate([height_sizes[plate_size]+10,-4.4,6.1]) rotate([0,45,90]) cube([6,height_sizes[plate_size]+20,6]); //Left Bevel (doesn't change)
		translate([height_sizes[plate_size]+10,rightbevel,6]) rotate([0,45,90]) cube([6,height_sizes[plate_size]+10,6]); //Right Bevel (scales right)
	}
}

// Thinning Plate
module plate_inner() {
	scale([0.95,thinner_offset[plate_width],1]){
        translate([3,3,0]){
            difference() {
                cube([height_sizes[plate_size],solid_plate_width,6]);
                translate([-4.3,-5,6.2]) rotate([0,45,0]) cube([6,edgewidth,6]); //Top Bevel
                translate([height_sizes[plate_size]-4.2,-5,6.25]) rotate([0,45,0]) cube([6,edgewidth,6]); //Bottom Bevel
                translate([height_sizes[plate_size]+10,-4.4,6.1]) rotate([0,45,90]) cube([6,height_sizes[plate_size]+20,6]); //Left Bevel (doesn't change)
                translate([height_sizes[plate_size]+10,rightbevel,6]) rotate([0,45,90]) cube([6,height_sizes[plate_size]+10,6]); //Right Bevel (scales right)
            }
        }
    }
}

// Box screw holes
module box_screws(){
	 translate([height_sizes[plate_size]/2 + 41.67125,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 + 41.67125,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
	 translate([height_sizes[plate_size]/2 - 41.67125,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 - 41.67125,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
}

// Rocker/Designer screw holes
module rocker_screws(){
	 translate([height_sizes[plate_size]/2 + 48.41875,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 + 48.41875,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
	 translate([height_sizes[plate_size]/2 - 48.41875,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 - 48.41875,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
}

// Rocker/Designer screw holes
module nspanel_screws(){
	 translate([height_sizes[plate_size]/2,-42.5,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2,-42.5,3.5]) cylinder(r1=2, r2=3.3, h=3);
	 translate([height_sizes[plate_size]/2,42.5,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2,42.5,3.5]) cylinder(r1=2, r2=3.3, h=3);
}

// Toggle screw holes
module toggle_screws(){
	 translate([height_sizes[plate_size]/2 + 30.1625,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 + 30.1625,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
	 translate([height_sizes[plate_size]/2 - 30.1625,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 - 30.1625,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
}

  ///////////////
 // Portland: //
///////////////

// Hole Cutout definitions
module hole(hole_type) {

// Toggle switch hole
	if (hole_type == "toggle") {
		translate([height_sizes[plate_size]/2,0,0]) cube([23.8125,10.3188,15], center = true);
	}

// Toggle switch hole and screw holes
	if (hole_type == "long_toggle") {
		translate([height_sizes[plate_size]/2,0,0]) cube([43.6563,11.9063,15], center = true);
	}
// Rocker switch plate
    if (hole_type == "rocker") {
        translate([height_sizes[plate_size]/2,0,0]) cube([67.1,33.3,15], center = true);
    }

// Nspanel switch plate
	if (hole_type == "nspanel") {
		translate([height_sizes[plate_size]/2,0,0]) cube([54.0,54.0,15], center = true);
    }

// Duplex power outlet plate or dual side toggles
	if (hole_type == "outlet" || hole_type == "dualsidetoggle") {
		translate([height_sizes[plate_size]/2 + 19.3915,0,0]) {
			difference() {
				cylinder(r=17.4625, h=15, center = true);
				translate([-24.2875,-15,-2]) cube([10,37,15], center = false);
				translate([14.2875,-15,-2]) cube([10,37,15], center = false);
			}
		}
		translate([height_sizes[plate_size]/2 - 19.3915,0,0]){
			difference(){
				cylinder(r=17.4625, h=15, center = true);
				translate([-24.2875,-15,-2]) cube([10,37,15], center = false);
				translate([14.2875,-15,-2]) cube([10,37,15], center = false);
			}
		}
		translate([height_sizes[plate_size]/2,0,-1]) cylinder(r=2, h=10);
		translate([height_sizes[plate_size]/2,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
	}

// Blank plate
	if (hole_type == "blank") { }

// VGA & DB09 plate
// VGA Fits http://www.datapro.net/products/vga-dual-panel-mount-f-f-cable.html
// DB09 Fits http://www.datapro.net/products/db9-serial-panel-mount-male-extension.html
	if (hole_type == "vga" || hole_type == "db09") {

        translate([0,-12.5,3]) cylinder(r=1.75, h=10, center = true);
        translate([0,12.5,3]) cylinder(r=1.75, h=10, center = true);
        difference(){
            cube([10,19,13], center=true);
            translate([-5,-9.2,1]) rotate([0,0,-35.6]) cube([4.4,2.4,15], center=true);
            translate([.9,-11.2,0]) rotate([0,0,9.6]) cube([10,4.8,15], center=true);
            translate([4.6,-8.5,0]) rotate([0,0,37.2]) cube([4.4,2.4,15], center=true);
            translate([-5,9.2,1]) rotate([0,0,35.6]) cube([4.4,2.4,15], center=true);
            translate([0.9,11.2,0]) rotate([0,0,-9.6]) cube([10,4.8,15], center=true);
            translate([4.6,8.5,0]) rotate([0,0,-37.2]) cube([4.4,2.4,15], center=true);
        }
    }

// HDMI plate
// Fits http://www.datapro.net/products/hdmi-panel-mount-extension-cable.html
	if (hole_type == "hdmi") {
		translate([0,-13,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,13,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([6,16,10], center=true);
	}

// DVI-I plate
// Fits http://www.datapro.net/products/dvi-i-panel-mount-extension-cable.html
	if (hole_type == "dvi") {
		translate([0,-16,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,16,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([10,26,10], center=true);
	}

// DisplayPort plate
// Fits http://www.datapro.net/products/dvi-i-panel-mount-extension-cable.html
	if (hole_type == "displayport") {
		translate([0,-13.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,13.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,0]){
			difference(){
				translate([0,0,3]) cube([7,19,10], center=true);
				translate([2.47,-9.37,3]) rotate([0,0,-54.6]) cube([3,5,14], center=true);
			}
		}
	}

// USB-A Plate
// Fits http://www.datapro.net/products/usb-panel-mount-type-a-cable.html
	if (hole_type == "usb-a") {
		translate([0,-15,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,15,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([8,16,10], center=true);
	}

// USB-B Plate
// Fits http://www.datapro.net/products/usb-panel-mount-type-b-cable.html
	if (hole_type == "usb-b") {
		translate([0,-13,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,13,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([11,12,10], center=true);
	}

// 1394 Firewire Plate
// Fits http://www.datapro.net/products/firewire-panel-mount-extension.html
	if (hole_type == "firewire") {
		translate([0,-13.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,13.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([7,12,10], center=true);
	}

// F-Type / Cable TV Plate
// Fits http://www.datapro.net/products/f-type-panel-mounting-coupler.html
	if (hole_type == "f-type") {
		translate([0,0,3]) cylinder(r=4.7625, h=10, center=true);
	}

// Cat5e & Gat6 plate
// Cat5e Fits http://www.datapro.net/products/cat-5e-panel-mount-ethernet.html
// Cat6 Fits hhttp://www.datapro.net/products/cat-6-panel-mount-ethernet.html
	if (hole_type == "cat5e" || hole_type == "cat6") {
		translate([0,-12.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,12.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([15,15,10], center=true);
	}

// S-Video & PS2 plate
// S-Video Fits hhttp://www.datapro.net/products/cat-6-panel-mount-ethernet.html
// PS2 http://www.datapro.net/products/ps2-panel-mount-extension-cable.html
	if (hole_type == "svideo" || hole_type == "ps2") {
		translate([0,-10,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,10,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cylinder(r=5, h=10, center=true);
	}

// Stereo / 1/4" headphone jack coupler
// Stereo coupler Fits http://www.datapro.net/products/stereo-panel-mount-coupler.html
	if (hole_type == "stereo") {
		translate([0,0,3]) cylinder(r=2.985, h=10, center=true);
	}

//Keystone1 hole
//Hole for 1 Keystone Jack
	if (hole_type == "keystone1") {
		translate([0,0,5]) cube([16.5,15,10], center = true);
	}

//Keystone Solid
	if (hole_type == "keystone_solid") {
		rotate([0,0,90]) {
			difference(){
				translate([0,0,.1]) cube([23,30.5,9.8]);
                translate([4,4,0]){
                    difference(){
                        cube([15,22.5,10]);
                        translate([-1,-0.001,3.501]) cube([17,2,6.5]);
                        translate([-1,2.5,-3.40970]) rotate([45,0,0]) cube([17,2,6.5]);
                        translate([-1,18.501,6.001]) cube([17,4,4]);
                        translate([-1,20.5,0]) rotate([-45,0,0]) cube([17,2,6.5]);
                    }
                }
			}
		}
	}

//End of module "hole"
}

  ////////////////////////
 // Number One ENGAGE: //
////////////////////////

// Rotate so it sits correctly on plate (whoops) and make upside down
rotate([0,180,90]){
// put plate at 0,0,0 for easier printing
    translate([-height_sizes[plate_size]/2,-solid_plate_width/2,-6]){
        difference()
        {
            plate();
            translate([0,0,-3]) plate_inner();
            for(hole_index = [1:plate_width]){
                plate_hole(hole_vector[hole_index-1], hole_bottom_vector[hole_index-1], hole_index);
            }
        }
        union()
        {
            for(hole_index = [1:plate_width]){
                plate_solid(hole_vector[hole_index-1], hole_bottom_vector[hole_index-1], hole_index);
            }

        }
    //End Translate
    }
//End Rotate
}
```


[User]
I'd like to design a parametric dog tag in OpenSCAD, shaped like a bone, with text on both sides.

Here's what I'm envisioning:

**Overall Shape:** The tag should be a bone shape, composed of a center rectangle with two circles on each end.

**Dimensions (all parametric):**

*   `bone_thickness`:  The overall thickness of the bone.
*   `bone_center_height`: The length of the rectangular center section.
*   `bone_center_width`: The width of the rectangular center section.
*   `bone_circle_radius`: The radius of the circles at the bone ends.

**Text:**

*   There should be text on both the front and back of the tag.
*   `front_text`:  A list of strings for the front (e.g., pet's name).
*   `back_text`: A list of strings for the back (e.g., owner's name and phone number).
*   `font_front`: The font to use for the front text (default to "Consolas").
*   `font_back`:  The font to use for the back text (default to "Consolas:style=Bold").
*   `font_thickness`: The thickness of the extruded text.
*   `font_size`: The font size.
*   `font_width`:  The width of the font character.
*  `font_height`: The height of the font character.

**Text Placement and Orientation:**

*   Front text should be on the front surface of the bone.
*   Back text should be on the back surface of the bone, rotated 180 degrees about the y-axis.
*  Both text blocks should be centered on the bone using the font size and string lengths.
*   Multiple lines of text in `front_text` or `back_text` should be stacked vertically.

**Attachment Hole:**

*   A cylindrical hole should be added at one end for attaching to a collar. It should have a slightly smaller counterbore to help avoid non-manifold geometry.
*  The hole should be parametrically defined so that changing its dimensions won't result in a non-manifold model.

**Features:**

*   The text should be extruded *inward* from the surface.
*   I would prefer this to be designed in a way that avoids non-manifold geometry errors.

I think that covers it. I don't need any specific libraries for this, I think it can all be done with built-in OpenSCAD functions.  Let me know if you have any questions!

[Assistant]
Bone_Dog_Tag.scad:
```
$fa = 1;
$fs = 1;

// Front tag text
front_text = ["Pet Name"];
// Back tag text
back_text = ["Owner Name", "5555555555"];

// Font variables
font_front = "Consolas";
font_back = "Consolas:style=Bold";
font_thickness = 2;
font_size = 6;
font_width = 5;
font_height = 6;

// Bone variables
bone_thickness = 5;
bone_center_height = 20;
bone_center_width = 50;
bone_circle_radius = 10;

module print_text(text_array, front_text) {
    for (i = [ 0 : len(text_array) - 1 ]) {
        text = text_array[i];
        tmp_font = front_text ? font_front : font_back;
        text_vertical_align = ((len(text) * font_width) / 2);

        translate_x = len(text_array) == 1 ? -(font_height / 2) : -((font_height / 2) * ((((font_height / 2) * -i) + (font_height / 4)) + 1));
        translate_y = front_text ? -text_vertical_align : text_vertical_align;
        translate_z = front_text ? bone_thickness : font_thickness;

        rotate_x = 0;
        rotate_y = front_text ? 0 : 180;
        rotate_z = 90;

        translate([translate_x, translate_y, translate_z])
            rotate([rotate_x, rotate_y, rotate_z])
            linear_extrude(height = font_thickness)
            text(text, font_size, font = tmp_font);
    }
}

// Pet tag
union() {

    // Bone with text
    difference() {
        //bone
        union() {
            //left side of bone
            translate([-(bone_circle_radius) + 1,-(bone_center_width / 2),0]) {
                translate([(bone_circle_radius * 2) - 2,0,0]) cylinder(h = bone_thickness, r = bone_circle_radius);
                cylinder(h = bone_thickness, r = bone_circle_radius);
            };

            //right side of bone
            translate([-(bone_circle_radius) + 1,(bone_center_width / 2),0]) {
                translate([(bone_circle_radius * 2) - 2,0,0]) cylinder(h = bone_thickness, r = bone_circle_radius);
                cylinder(h = bone_thickness, r = bone_circle_radius);
            };

            //center of bone
            translate([-(bone_center_height / 2),- (bone_center_width / 2),0]) cube([bone_center_height, bone_center_width, bone_thickness]);

            //tag attachment
            difference() {
                translate([-10,0,0]) cylinder(r = 6, h = bone_thickness / 2);
                //prevents non-manifold
                translate([-10,0,-1]) cylinder(r = 3, h = ((bone_thickness / 2) + 2));
            }
        }

        // Tag text
        translate([6,0,-(font_thickness - 1)]) {
            union() {
                print_text(front_text, true);
                print_text(back_text, false);
            }
        }
    }
}
```


[User]
I'd like to design a project box using OpenSCAD, and I'm looking for a parametric design that's easy to customize. I'm envisioning something similar to a typical electronics enclosure, but with a few specific features and customizable dimensions.

Here's a breakdown of what I need:

**General Structure:**

*   The box should consist of two main parts: a base and a lid. Both should be able to be rendered separately or together.
*   I need to be able to specify the overall external dimensions (width, length, height) or internal dimensions and wall thickness, with an option to calculate the other from that.
*   The box should have rounded corners with a customizable radius for both internal and external corners.
*   I want to control the thickness of the walls, floor, and lid separately.
*   There should be a small, customizable tolerance between the lid and the base for a snug fit. This tolerance should be adjustable in both the XY plane and in the Z direction to make sure the lid doesn't just slide off easily.
*   An interior lip feature should be included, with customizable width, to provide an extra seal for the lid, and extra stability on the bottom. This lip is also important in making sure the lid doesn't slide in the X-Y direction, and doesn't pop off.
*   The design should include support pillars for the lid, placed at the corners, for extra support and registration, but with an option to disable them. The pillars should also have adjustable dimensions.

**PCB Mounting:**

*   I need the option to include mounting posts/feet for a PCB inside the enclosure, with customizable distances (X and Y).
*   The mounting posts should have a customizable height and diameter.
*   There should be a hole in each post for mounting screws, with an adjustable diameter.
*   The position of the PCB mounting posts should have configurable offsets from either the corner or center of the interior walls.

**Holes and Cable Management:**

*   The design needs to accommodate holes on the sides of the box for cable entry.
*   The hole edges should have an option to be octagonal instead of circular for improved printability, though I'd like to keep the circular hole for internal tolerances.
*   I need the option to add cable stress reliefs near the cable entry holes. These should be small, removable inserts that are connected at 2 points for each side.
*   I should be able to specify the number, diameter, and spacing of the cable holes on the front and rear of the enclosure separately.

**Mounting Flaps:**

*   I want to be able to add mounting flaps to the sides of the box, with customizable thickness, and optional number. These flaps should extend outwards from the edges and have mounting holes.
*   The mounting hole diameter should also be customizable.

**Details:**

*   I'd like to be able to set `$fa` and `$fs` parameters for the overall design.
*   All dimensions should be in millimeters.
*   I do not need any external libraries used.

I think that covers most of what I need. It's a pretty comprehensive list, so let me know if you have any questions. I'm hoping this will result in a single file or group of files that I can use as my project box template for the future.

[Assistant]
project-box.scad:
```

/* [general] */

assemble=false;
show_lid=true;
show_bot=true;

// relevant if holes/enable_cable_stress_relief=true
show_cable_stress_relief_bars=true;

/* [tactical dimensions] */

// total wall thickness
wall_th=1.001;

// floor thickness
flo_th=1.001;

// lid thickness
lid_th=1.001;

// box width
box_w=70.001;

// box length
box_l=110.001;

// box height
box_h=35.001;

// base corner radius
corner_r=1.001;

// measure box dimensions from inside or outside walls
measure_from="int"; // ["int", "ext"]

// if lip_w is very large then pilar may be omitted
enable_lid_pillar=true;

// lid pillar size
lid_pillar_s=9;

// hole diameter of lid
lid_hole_d=3.3;

// tolerance between lid and box
lid_tol_xy=0.3001;

// reduces height of support pillars holding the lid
lid_tol_z=0.2001;

// interior lip to help seal the lid
enable_lip=true;

// width of interior lip
lip_w=3.001;

/* [PCB feet] */

enable_pcb=true;

// hole distance x
pcb_foot_dx=40.001;

// hole distance y
pcb_foot_dy=82.001;

// height
pcb_foot_h=10.001;

// foot diameter
pcb_foot_d=9.001;

// hole diameter
pcb_foot_hole=2.001;

// offset
pcb_offset=[0.001, 0.001];

// measure offset from interior wall or from center
pcb_offset_from="center"; // ["corner", "center"]

/* [holes] */

// turn holes on the walls into octagons
side_hole_printability_help=true;

// cable stress reliefs for front/rear cable holes. uses lid_hole_d.
enable_cable_stress_relief=true;

// set to nonzero to enable front holes
front_hole_count=0;
front_hole_d=12.001;
front_hole_spacing=18.001;

// set to nonzero to enable rear holes
rear_hole_count=0;
rear_hole_d=12.001;
rear_hole_spacing=15.001;

/* [mounting] */

// set to nonzero to enable mounting
mount_flaps_count=0;
mount_flaps_th=2.001;
mount_hole_d=4.601;

/* [detail level] */
$fa=10;
$fs=0.4;

// end params

int_w= measure_from=="int" ? box_w : (box_w - 2*wall_th);
int_l= measure_from=="int" ? box_l : (box_l - 2*wall_th);
int_h= measure_from=="int" ? box_h : (box_h - lid_th - flo_th);
ext_w= int_w + 2*wall_th;
ext_l= int_l + 2*wall_th;
ext_h= int_h + lid_th + flo_th;

module rsquare(dim, r=1, center=false) {
	hull()
	translate(center?[0,0]:dim/2)
	for(p=[[-1,-1],[-1,1],[1,-1],[1,1]]) {
		c=[p[0]/2*dim[0], p[1]/2*dim[1]];
		intersection() {
			translate(c-r*p) circle(r=r);
			translate(c/2) square(dim/2, center=true);
		}
	}
}

module ext_body(d=0, h=ext_h) {
	linear_extrude(height=h, convexity=2)
	translate([-wall_th,-wall_th])
	offset(delta=d)
	rsquare([ext_w, ext_l], r=corner_r + wall_th);
}

module int_body(d=0, h=int_h) {
	translate([0,0,flo_th])
	linear_extrude(height=h, convexity=2)
	offset(delta=d)
	rsquare([int_w, int_l], r=corner_r);
}

module pcb_foot() {
	let(h=0.01 + pcb_foot_h)
	let(d=pcb_foot_d)
	let(a=min(h-0.1, d*0.25)) // base bevel size
	difference() {
		union() {
			cylinder(d=d, h=h);
			cylinder(d1=d + 2*a, d2=d, h=a);
		}
		translate([0,0,-0.01 + h/2])
		cylinder(d=pcb_foot_hole, h=h+10, center=true);
	}
}

module at_corners(s=[int_w,int_l]) {
	for(i=[0,1]) for(j=[0,1]) translate([i*s[0], j*s[1]])
	mirror([i,0]) mirror([0,j]) children();
}

module pcb_feet() {
	let(s=[pcb_foot_dx, pcb_foot_dy])
	translate(pcb_offset_from == "corner" ? [0,0] : [int_w,int_l]/2 - s/2)
	translate([0,0,flo_th])
	translate(pcb_offset)
	at_corners(s)
	pcb_foot();
}

module lid_support_pillar_2d() {
	let(s=lid_pillar_s)
	let(r=min(corner_r, s/2))
	let(a=s+r)
	let(f=wall_th/3)
	difference() {
		translate(-[f,f])
		union() {
			rsquare([s, s] + [f,f], r=r);
			square([s, s]/2 + [f,f]);
			square([a, r] + [f,f]);
			square([r, a] + [f,f]);
		}
		translate([a, r]) circle(r=r+.01);
		translate([r, a]) circle(r=r+.01);
	}
}

module lip(s, r) {
	for(y=[0,s[1]])
	translate([0,y])
	rotate(90, [0,1,0])
	cylinder(h=s[0], r=r, $fn=4);

	for(x=[0,s[0]])
	translate([x,0])
	rotate(90, [-1,0,0])
	cylinder(h=s[1], r=r, $fn=4);
}

module lid_holes() {
	at_corners([int_w, int_l])
	translate([.5,.5]*lid_pillar_s)
	circle(d=lid_hole_d);
}

module spaced_items(n, dx) {
	if (n ==1) children();
	else if (n>0)
	translate([(n-1)*dx/-2, 0])
	for(i=[0:n-1])
	translate([i*dx,0])
	children();
}

module cable_stress_relief(hole_count, hole_spacing, hole_d) {
	let(g=lid_pillar_s)
	let(a=wall_th/3)
	let(s=[2*a+int_w, a+g, ext_h/2 - hole_d/2])
	difference() {
		translate([-a, -a])
		cube(s);

		translate([int_w/2, g/2, -5])
		spaced_items(hole_count, hole_spacing)
		spaced_items(2, hole_d + 4 + lid_hole_d)
		cylinder(d=lid_hole_d, h=s[2] + 10);
	}
}

module cable_stress_relief_bar(hole_d) {
	let(dx=hole_d + 4 + lid_hole_d)
	linear_extrude(height=2, convexity=4)
	difference() {
		rsquare([dx+10, lid_pillar_s-0.6], corner_r, center=true);

		spaced_items(2, dx)
		circle(d=lid_hole_d);
	}
}

module parts_clipped_inside() {
	if (enable_pcb) {
		color("orange")
		pcb_feet();
	}

	if (enable_lid_pillar || enable_lip)
	color("pink")
	difference() {
		union() {
			if (enable_lid_pillar)
			linear_extrude(height=int_h - lid_tol_z, convexity=8)
			at_corners([int_w, int_l])
			lid_support_pillar_2d();

			if (enable_lip)
			intersection() {
				translate([0,0,int_h - lid_tol_z])
				mirror([0,0,1])
				lip([int_w, int_l], lip_w);

				linear_extrude(height=int_h - lid_tol_z, convexity=2)
				offset(delta=wall_th/3)
				square([int_w,int_l]);
			}
		}

		translate([0,0,-5])
		linear_extrude(height=int_h + 10, convexity=4)
		lid_holes();
	}

	if (enable_cable_stress_relief) {
		if (front_hole_count > 0) {
			cable_stress_relief(front_hole_count, front_hole_spacing, front_hole_d);
		}
		if (rear_hole_count > 0) {
			translate([0,int_l]) mirror([0,1])
			cable_stress_relief(rear_hole_count, rear_hole_spacing, rear_hole_d);
		}
	}

	// join the floor and walls with a nice bevel
	translate([0,0,flo_th])
	lip([int_w, int_l], min(corner_r,2.5));
}

function ngon_circumcircle_radius(Ri,theta) = Ri / cos(theta/2);

module side_hole(r,h,center=false) {
	if (side_hole_printability_help) {
		rotate(360/8/2, [0,0,1])
		cylinder(r=ngon_circumcircle_radius(r, 360/8), h=h, center=center, $fn=8);
	} else {
		cylinder(r=r,h=h,center=center);
	}
}

module extra_holes() {
	translate([int_w/2, -wall_th/2, ext_h/2])
	spaced_items(front_hole_count, front_hole_spacing)
	rotate(90, [1,0,0])
	side_hole(r=front_hole_d/2, h=wall_th+.02, center=true);

	translate([int_w/2, int_l+wall_th/2, ext_h/2])
	spaced_items(rear_hole_count, rear_hole_spacing)
	rotate(90, [1,0,0])
	side_hole(r=rear_hole_d/2, h=wall_th+.02, center=true);
}

module mount_flaps() {
	for(i=[0,1]) translate([ext_w*i,0]) mirror([i,0])
	linear_extrude(height=mount_flaps_th, convexity=2)
	let(s=max(10,min(mount_hole_d*3,mount_hole_d+14)))
	translate([0, ext_l/2 - wall_th])
	rotate(90,[0,0,1])
	spaced_items(mount_flaps_count, (ext_l-s)/(mount_flaps_count-1))
	difference() {
		translate([-s/2,-s]) rsquare([s, 2*s], corner_r*2);
		translate([0,s/2]) circle(d=mount_hole_d);
	}
}

module the_box() {
	translate(measure_from == "int" ? [0,0] : [wall_th,wall_th])
	difference(convexity=10) {
		union() {
			ext_body();
			mount_flaps();
		}
		difference(convexity=10) {
			int_body(h=ext_h);
			parts_clipped_inside();
		}
		extra_holes();
	}
}

module the_lid() {
	translate(measure_from == "int" ? [0,0] : [wall_th,wall_th])
	linear_extrude(height=lid_th, convexity=2)
	difference() {
		translate([1,1] * lid_tol_xy)
		rsquare([int_w, int_l] - [2,2]*lid_tol_xy, corner_r - lid_tol_xy);

		offset(delta=0.5)
		lid_holes();
	}
}

module main() {
	if (assemble) {
		if (show_bot)
		the_box();

		if (show_lid)
		translate([0,0,ext_h - lid_th])
		%the_lid();

		if (enable_cable_stress_relief && show_cable_stress_relief_bars) {
			if (front_hole_count > 0)
			translate([int_w/2, lid_pillar_s/2, ext_h/2 + front_hole_d/2])
			cable_stress_relief_bar(front_hole_d);

			if (rear_hole_count > 0)
			translate([0, int_l])
			translate([int_w/2, -lid_pillar_s/2, ext_h/2 + rear_hole_d/2])
			cable_stress_relief_bar(rear_hole_d);
		}
	} else {
		if (show_bot)
		the_box();

		if (show_lid)
		translate([-ext_w - 25,0,0])
		the_lid();

		if (enable_cable_stress_relief && show_cable_stress_relief_bars) {
			if (front_hole_count > 0)
			translate([0, -lid_pillar_s - 10])
			cable_stress_relief_bar(front_hole_d);

			if (rear_hole_count > 0)
			translate([0, -2*lid_pillar_s - 20])
			cable_stress_relief_bar(rear_hole_d);
		}
	}
}

main();

```