[User]
I'm looking to create a customizable system of stackable storage boxes, similar to the "Drawer Boxes Customizer" on Thingiverse by 1sPiRe, but I need a few specific things included. The key features I want are parametric control over the box and grid dimensions, stackability, and optional dividers inside the boxes.

First, I need the system to be based on a grid system, with parameters for:

*   **Inside Drawer Dimensions:** The overall width, length, and height of a container that the boxes will fit within. Let's call these `ID_W`, `ID_L`, and `ID_H`, respectively.
*   **Matrix Grid Dimensions:** The number of grid units that fit within the inside drawer dimensions along the width, length and height (e.g. 6x8x1). Let's call these `MAT_W`, `MAT_L`, and `MAT_H`.
*   **Tolerance:** The spacing between boxes, lets call this `Tol`.
*  **Grid Thickness:** The radius of the cylinders composing the grid (and half the width), let's call this `G_rad`.
*   The grid should be generated based on the matrix grid dimensions and inside drawer dimensions.

Next, I need parameters to control the individual boxes:

*   **Wall Thickness:** The thickness of the box walls, called `B_WT`.
*  **Bottom Thickness:** The thickness of the box bottom, called `B_BT`.
*   **Inside Corner Radius:** The radius of the inside corners of the box, called `B_rad`.
*   **Sticker Lip:** An optional lip on the front of the box for labels, called `ST` (0 for no, 1 for yes).
    *   If the sticker lip is enabled, the width and height of the lip should be parameters, `ST_W` and `ST_H`.
*   **Stackable:** An option to make the boxes stackable, `STK` (0 for no, 1 for yes). Stackable boxes should have a lip on top that matches the bottom of the box.
*   **Grid Inlay:** An option to add the grid on the bottom of the box, let's call this `GI` (0 for no, 1 for yes).
*   **Box Size in Grid Units:** The width, length, and height of a box in terms of grid units, called `BTP_W`, `BTP_L`, `BTP_H`.

I also need parameters for dividers, which should be inside of the boxes, and should be optional.

*   **Duplicate Sticker Lip:**  A parameter to add more sticker lips within the box, `DST` (0 for no, 1 for yes).
*   **Separators:** The number of separators to add inside the box along the width and length. Let's call these `BTP_SW` and `BTP_SL`. The separators should be thin walls going from the bottom to the top.

Finally, I need some parameters for previewing and rendering:

*   **Definition:** The `$fn` value for smooth curves.
*   **Preview Mode:** A parameter to select what to display: the overall drawer with grid, the grid to print, the setup preview or just the box.
 * `0`: Drawer and grid preview, `1`: Grid to print, `2`: Setup Preview, `3`: Box to print.
*   **Grid to Print Dimensions:** The width and length of the grid to print in number of grid units (independent of the matrix grid), let's call these `GTP_W`, and `GTP_L`

I think that covers it. Could you create an OpenSCAD file (or files) that generates the described system? I am not familiar with OpenSCAD libraries, but make sure not to use any libraries.

[Assistant]
DrawerBoxesCustomizerStackableV1.scad:
```
///Designed by 1SPIRE, inspired from ALEXENDRE CHAPPEL assortment system boxes///

//V1 - Stackable boxes now work when MAT_H > 1, thanks Andreas!

/* [Drawer Parameters] */
// Inside Drawer Width
ID_W=335;
// Inside Drawer Length
ID_L=430;
// Inside Drawer Height
ID_H=80;

/* [Matrix and Grid Parameters] */
//Matrix Drawer Width
MAT_W=6; //[1:1:20]
//Matrix Drawer Length
MAT_L=8; //[1:1:20]
//Matrix Drawer Height
MAT_H=1; //[1:1:6]
//Tolerance (Space between boxes)
Tol=0.4;
//Grid Tickness (radius value = height = half of the width)
G_rad=2;

/* [Boxes Parameters] */
//Wall Thickness Box
B_WT=1.6;
//Bottom Thickness Box
B_BT=1.25;
//Radius inside box
B_rad=2.5;
//Sticker Lip
ST=1; //[0:No,1:Yes]
//Sticker Lip Width
ST_W=30;
//Sticker Lip Height
ST_H=9;
//Definition
$fn=60; //[20:120]
//Stackable
STK=0; //[0:No,1:Yes]

/* [Grid to print, see ECHO for the exact size] */
//Number of grid sections on the width, must be < Matrix Drawer Width
GTP_W=3; //[1:1:20]
//Number of grid sections on the length, must be < Matrix Drawer Length
GTP_L=4; //[1:1:20]

/* [Box to print, Size of the box] */
//Grid Inlay
GI=1; //[0:No,1:Yes]
//Coef of matrix along width, must be < Matrix Drawer Width
BTP_W=1; //[1:1:20]
//Coef of matrix along length, must be < Matrix Drawer Length
BTP_L=1; //[1:1:20]
//Coef of matrix along height, must be < Matrix Drawer Height
BTP_H=1; //[1:1:6]

/* [Box to print, Separators] */
//Duplicate sticker lip
DST=0; //[0:No,1:Yes]
//Box separators, Coef of matrix along width, must be < BTP_W
BTP_SW=0; //[0:1:20]
//Box separators, Coef of matrix along length, must be < BTP_L
BTP_SL=0; //[0:1:20]

/* [Preview and Render] */
Preview=2; //[0:Drawer and grid preview,1:Grid to print,2:Setup Preview,3:Box to print]

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

MAT_W_UNIT=ID_W/MAT_W;
MAT_L_UNIT=ID_L/MAT_L;
MAT_H_UNIT=ID_H/MAT_H + (STK ? (MAT_H-1) * sqrt(G_rad) : 0);
echo("Matrix Width unit=",MAT_W_UNIT);
echo("Matrix Length unit=",MAT_L_UNIT);
echo("Matrix Height unit=",MAT_H_UNIT);
echo("Grid to print Width=",GTP_W*MAT_W_UNIT);
echo("Grid to print Length=",GTP_L*MAT_L_UNIT);

module Drawer() {
    difference() {
        translate([-ID_W/2-10,-ID_L/2-10,-5]) cube([ID_W+20,ID_L+20,ID_H+5]);
        translate([-ID_W/2,-ID_L/2,0]) cube([ID_W,ID_L,ID_H+5]);
    }
}

module GridUnit(orientation,length) {
    if(orientation=="L") {
        difference() {
            rotate([90,0,0]) cylinder(d=2*G_rad,h=length,$fn=4,center=true);
            translate([0,0,-1.5*G_rad]) cube([3*G_rad,length+2,3*G_rad],center=true);
        }
    }

    else if (orientation=="W") {
        difference() {
            rotate([0,90,0]) cylinder(d=2*G_rad,h=length,$fn=4,center=true);
            translate([0,0,-1.5*G_rad]) cube([length+2,3*G_rad,3*G_rad],center=true);
        }
    }
}

module Grid() {
    intersection() {
        union() {
            for(i=[0:1:MAT_W]) translate([i*MAT_W_UNIT-ID_W/2,0,0]) GridUnit("L",ID_L);
            for(i=[0:1:MAT_L]) translate([0,i*MAT_L_UNIT-ID_L/2,0]) GridUnit("W",ID_W);
        }
        cube([ID_W,ID_L,3*G_rad],center=true);
    }
}

module BoxStackable(DIV_W,DIV_L,DIV_H) { //DIV=coef matrix (width, length, height)
    difference() {
        hull() {
            for(i=[-1,1],j=[-1,1]) {
                translate([i*(DIV_W*MAT_W_UNIT/2-B_rad-B_WT-Tol/2),j*(DIV_L*MAT_L_UNIT/2-B_rad-B_WT-Tol/2),G_rad+0.2]) cylinder(r=B_rad+B_WT,h=DIV_H*MAT_H_UNIT-G_rad-0.2);
                translate([i*(DIV_W*MAT_W_UNIT/2-B_rad-B_WT-Tol/2-G_rad),j*(DIV_L*MAT_L_UNIT/2-B_rad-B_WT-Tol/2-G_rad),0]) cylinder(r=B_rad+B_WT,h=B_BT);
            }
        }
    if (GI==1) {
            for(i=[1:1:DIV_W-1]) {
                if (i/(DIV_W/2) == 1) {
                    translate([i*MAT_W_UNIT-DIV_W*MAT_W_UNIT/2,20,0]) rotate([90,0,0]) cylinder(d=2*G_rad+0.4,h=DIV_L*MAT_L_UNIT+2,$fn=4,center=true);
                    for(i=[1:1:DIV_L-1]) translate([0,i*MAT_L_UNIT-DIV_L*MAT_L_UNIT/2,0]) rotate([0,90,0]) cylinder(d=2*G_rad+0.4,h=DIV_W*MAT_W_UNIT+2,$fn=4,center=true);
                } else {

                translate([i*MAT_W_UNIT-DIV_W*MAT_W_UNIT/2,0,0]) rotate([90,0,0]) cylinder(d=2*G_rad+0.4,h=DIV_L*MAT_L_UNIT+2,$fn=4,center=true);
                    for(i=[1:1:DIV_L-1]) translate([0,i*MAT_L_UNIT-DIV_L*MAT_L_UNIT/2,0]) rotate([0,90,0]) cylinder(d=2*G_rad+0.4,h=DIV_W*MAT_W_UNIT+2,$fn=4,center=true);
                }
            }
        }
    }
}

module Box(DIV_W,DIV_L,DIV_H) { //DIV=coef matrix (width, length, height)

    difference() {
        hull() {
            for(i=[-1,1],j=[-1,1]) {
                translate([i*(DIV_W*MAT_W_UNIT/2-B_rad-B_WT-Tol/2),j*(DIV_L*MAT_L_UNIT/2-B_rad-B_WT-Tol/2),G_rad+0.2]) cylinder(r=B_rad+B_WT,h=DIV_H*MAT_H_UNIT-G_rad-0.2);
                translate([i*(DIV_W*MAT_W_UNIT/2-B_rad-B_WT-Tol/2-G_rad),j*(DIV_L*MAT_L_UNIT/2-B_rad-B_WT-Tol/2-G_rad),0]) cylinder(r=B_rad+B_WT,h=B_BT);
            }
        }

        if (GI==1) {
            for(i=[1:1:DIV_W-1]) translate([i*MAT_W_UNIT-DIV_W*MAT_W_UNIT/2,0,0]) rotate([90,0,0]) cylinder(d=2*G_rad+0.4,h=DIV_L*MAT_L_UNIT+2,$fn=4,center=true);
            for(i=[1:1:DIV_L-1]) translate([0,i*MAT_L_UNIT-DIV_L*MAT_L_UNIT/2,0]) rotate([0,90,0]) cylinder(d=2*G_rad+0.4,h=DIV_W*MAT_W_UNIT+2,$fn=4,center=true);
        }

        difference() {
            hull() {
                for(i=[-1,1],j=[-1,1]) translate([i*(DIV_W*MAT_W_UNIT/2-B_rad-B_WT-Tol/2),j*(DIV_L*MAT_L_UNIT/2-B_rad-B_WT-Tol/2),G_rad+0.2]) cylinder(r=B_rad,h=DIV_H*MAT_H_UNIT);
                for(i=[-1,1],j=[-1,1]) translate([i*(DIV_W*MAT_W_UNIT/2-B_rad-B_WT-Tol/2-G_rad),j*(DIV_L*MAT_L_UNIT/2-B_rad-B_WT-Tol/2-G_rad),B_BT]) cylinder(r=B_rad,h=DIV_H*MAT_H_UNIT);
            }

            if (GI==1) {
                translate([0,0,0]) {
                    for(i=[1:1:DIV_W-1]) translate([i*MAT_W_UNIT-DIV_W*MAT_W_UNIT/2,0,0]) rotate([90,0,0]) cylinder(d=2*G_rad+0.4+2*B_BT,h=DIV_L*MAT_L_UNIT+2,$fn=4,center=true);
                    for(i=[1:1:DIV_L-1]) translate([0,i*MAT_L_UNIT-DIV_L*MAT_L_UNIT/2,0]) rotate([0,90,0]) cylinder(d=2*G_rad+0.4+2*B_BT,h=DIV_W*MAT_W_UNIT+2,$fn=4,center=true);
                }
            }

            if (ST==1) {
                if (DST==0) {
                    translate([0,-DIV_L*MAT_L_UNIT/2,DIV_H*MAT_H_UNIT]) difference() {
                        hull() {
                                for(i=[-1,1],j=[-B_rad*2,ST_H-B_rad*2+B_WT]) {
                                    translate([i*(ST_W/2-B_rad),j-(-B_rad-Tol/2),-0.5]) cylinder(r=B_rad,h=1);
                                    translate([i*(ST_W/2-B_rad),-B_rad*2-(-B_rad-Tol/2),-1.5*ST_H-0.5]) cylinder(r=B_rad,h=1);
                                }
                        }
                        translate([-ST_W/2-1,-5,-1.5*ST_H-1]) cube([ST_W+2,B_WT+3.5,1.5*ST_H+2]);
                    }
                }
                if (DST==1) {
                        for(i=[0.5:1:BTP_SW+1],j=[0:1:BTP_SL]) translate([i*BTP_W*MAT_W_UNIT/(BTP_SW+1)-BTP_W*MAT_W_UNIT/2,j*BTP_L*MAT_L_UNIT/(BTP_SL+1)-BTP_L*MAT_L_UNIT/2,DIV_H*MAT_H_UNIT]) difference() {
                            hull() {
                                    for(i=[-1,1],j=[-B_rad*2,ST_H-B_rad*2+B_WT]) {
                                        translate([i*(ST_W/2-B_rad),j-(-B_rad-Tol/2),-0.5]) cylinder(r=B_rad,h=1);
                                        translate([i*(ST_W/2-B_rad),-B_rad*2-(-B_rad-Tol/2),-1.5*ST_H-0.5]) cylinder(r=B_rad,h=1);
                                    }
                            }
                            translate([-ST_W/2-1,-5,-1.5*ST_H-1]) cube([ST_W+2,B_WT+3.5,1.5*ST_H+2]);
                        }

                }
            }
            else{}

            for(i=[1:1:BTP_SW]) translate([i*BTP_W*MAT_W_UNIT/(BTP_SW+1)-BTP_W*MAT_W_UNIT/2,0,0]) cube([B_WT,BTP_L*MAT_L_UNIT,2.5*MAT_H_UNIT*DIV_H],center=true);
            for(i=[1:1:BTP_SL]) translate([0,i*BTP_L*MAT_L_UNIT/(BTP_SL+1)-BTP_L*MAT_L_UNIT/2,0]) cube([BTP_W*MAT_W_UNIT,B_WT,2.5*MAT_H_UNIT*DIV_H],center=true);

        }
        if (STK==1) {
            translate([0,0,MAT_H_UNIT * BTP_H - G_rad - Tol / 2]) BoxStackable(DIV_W,DIV_L,DIV_H);
        }
    }
}

module GridToPrint(width,length) {
    intersection() {
        union() {
            for(i=[0:1:width]) translate([i*MAT_W_UNIT-width*MAT_W_UNIT/2,0,0]) GridUnit("L",length*MAT_L_UNIT);
            for(i=[0:1:length]) translate([0,i*MAT_L_UNIT-length*MAT_L_UNIT/2,0]) GridUnit("W",width*MAT_W_UNIT);
        }
        cube([width*MAT_W_UNIT,length*MAT_L_UNIT,2*G_rad],center=true);
    }
}

module Rendering(Render) {
    if (Render==0) {
        %Drawer();
        Grid();
    }
    else if (Render==1) {
        //GridToPrint(GTP_W,GTP_L);
        BoxStackable(BTP_W,BTP_L,BTP_H);
    }
    else if (Render==2) {
        %Drawer();
        if (GI==1) {translate([0,0,-0.1]) Grid();}
        for(i=[0:BTP_W:MAT_W-BTP_W],j=[0:BTP_L:MAT_L-BTP_L]) translate([-ID_W/2+MAT_W_UNIT/2*BTP_W+i*MAT_W_UNIT,-ID_L/2+MAT_L_UNIT/2*BTP_L+j*MAT_L_UNIT,0]) Box(BTP_W,BTP_L,BTP_H);
    }
    else if (Render==3) {
        Box(BTP_W,BTP_L,BTP_H);
    }
}

Rendering(Preview);

```


[User]
Hey, I'm looking for an OpenSCAD design for a customizable hard drive holder. I've got a mix of drive types, so it needs to be really flexible.

Here's what I need it to do:

*   **Drive Support:** It needs to handle 2.5" and 3.5" drives, along with all the standard M.2 sizes (12, 16, 22, and 30mm widths with 16, 26, 30, 38, 42, 60, 80, and 110mm lengths). It should also have an option for custom drive dimensions using length, width, and height.
*   **Slotting:** The design should allow for a variable number of slots. I want to be able to set the height of each slot as a percentage of the maximum height. I need an option to tilt the slots (adjustable in degrees), and a way to define the space around each drive (the 'gap').
*   **Notches:**  I want a notch option to help with drive removal. The height and width of the notch need to be adjustable (as percentages of the maximum).
*   **Base:** It needs a base with an adjustable radius, and I want a feature to add topple support at the rear, adjustable by percentage for both the height and width.
*   **Preview:** It would be really helpful to have preview options to show drives – either the first, last, all, or none. Also the preview should be able to toggle the drives and center markers.
*   **Material Thickness:** It should have a global parameter for the material thickness.

The design should use standard OpenSCAD modules, so no libraries needed I think. I'm pretty comfortable with modifying the base design, so you don't have to focus on the anti-topple feature too much, just as long as it's included.

Could you generate the SCAD file for me?

[Assistant]
codeandmake.com_Hard_Drive_Holder_v1.2.scad:
```
/*
 * Copyright 2021-2022 Code and Make (codeandmake.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/*
 * Hard Drive Holder by Code and Make (https://codeandmake.com/)
 *
 * https://codeandmake.com/post/hard-drive-holder
 *
 * Hard Drive Holder v1.2 (19 January 2022)
 */

/* [Drive] */

// The type of drive
Drive_Type = 2; // [0:2.5 inch - 5mm height, 1:2.5 inch - 7mm height, 2:2.5 inch - 9.5mm height, 3:2.5 inch - 12.5mm height, 4:2.5 inch - 15mm height, 5:2.5 inch - 19mm height, 6:3.5 inch - 19.99mm height, 7:3.5 inch - 26.11mm height, 8:M.2 1216 - 12mm width & 16mm length, 9:M.2 1226 - 12mm width & 26mm length, 10:M.2 1230 - 12mm width & 30mm length, 11:M.2 1238 - 12mm width & 38mm length, 12:M.2 1242 - 12mm width & 42mm length, 13:M.2 1260 - 12mm width & 60mm length, 14:M.2 1280 - 12mm width & 80mm length, 15:M.2 12110 - 12mm width & 110mm length, 16:M.2 1616 - 16mm width & 16mm length, 17:M.2 1626 - 16mm width & 26mm length, 18:M.2 1630 - 16mm width & 30mm length, 19:M.2 1638 - 16mm width & 38mm length, 20:M.2 1642 - 16mm width & 42mm length, 21:M.2 1660 - 16mm width & 60mm length, 22:M.2 1680 - 16mm width & 80mm length, 23:M.2 16110 - 16mm width & 110mm length, 24:M.2 2216 - 22mm width & 16mm length, 25:M.2 2226 - 22mm width & 26mm length, 26:M.2 2230 - 22mm width & 30mm length, 27:M.2 2238 - 22mm width & 38mm length, 28:M.2 2242 - 22mm width & 42mm length, 29:M.2 2260 - 22mm width & 60mm length, 30:M.2 2280 - 22mm width & 80mm length, 31:M.2 22110 - 22mm width & 110mm length, 32:M.2 3016 - 30mm width & 16mm length, 33:M.2 3026 - 30mm width & 26mm length, 34:M.2 3030 - 30mm width & 30mm length, 35:M.2 3038 - 30mm width & 38mm length, 36:M.2 3042 - 30mm width & 42mm length, 37:M.2 3060 - 30mm width & 60mm length, 38:M.2 3080 - 30mm width & 80mm length, 39:M.2 30110 - 30mm width & 110mm length, 40:Custom - Use settings below]

// Custom drive length (Z axis)
Custom_Drive_Length = 100; // [50:0.01:200]

// Custom drive width (X axis)
Custom_Drive_Width = 69.85; // [50:0.01:200]

// Custom drive height (Y axis)
Custom_Drive_Height = 9.5; // [1:0.01:50]

/* [General] */

// The thickness of the material
Material_Thickness = 2; // [1:0.1:5]

/* [Slot] */

// The number of drive slots
Number_of_Slots = 6; // [1:1:20]

// Height of each slot as percentage of maximum possible
Slot_Height_Percent = 20; // [5:1:100]

// Degrees of tilt
Slot_Tilt = 5; // [0:1:45]

// Amount of space around all sides of each drive
Drive_Gap = 0.5; // [0:0.1:10]

// Notch height as percentage of maximum possible (set to '0' for no notch)
Notch_Height_Percent = 65; // [0:1:100]

// Notch Width as percentage of maximum possible
Notch_Width_Percent = 80; // [0:1:100]

/* [Base] */

// The radius of the base
Base_Radius = 2; // [0:1:10]

// Amount of additional topple support to add as percentage of maximum possible
Additional_Topple_Support_Percent = 20; // [0:1:100]

// Width of the topple support to add as percentage of maximum possible
Topple_Support_Width_Percent = 33; // [0:1:100]

/* [Preview] */

// Which drives to show in preview mode
Drives_to_Show = 3; // [0:None, 1:First, 2:Last, 3:All]

// Show drives in preview mode
Show_Drives = 1; // [0:No, 1:Yes]

// Show center markers in preview mode
Show_Center_Markers = 1; // [0:No, 1:Yes]

module hardDriveHolder() {
  $fn = 100;

  driveLength25 = 100;
  driveWidth25 = 69.85;
  driveLength35 = 146.99;
  driveWidth35 = 101.85;

  // M.2
  driveHeightM2 = (1.5 * 2) + (0.8 * 1.1);
  driveWidthsM2 = [12, 16, 22, 30];
  driveLengthsM2 = [16, 26, 30, 38, 42, 60, 80, 110];

  driveLengths = [driveLength25, driveLength25, driveLength25, driveLength25, driveLength25, driveLength25, driveLength35, driveLength35, for(m2w = [0 : (len(driveWidthsM2) - 1)]) for(m2l = [0 : (len(driveLengthsM2) - 1)]) driveLengthsM2[m2l], Custom_Drive_Length];
  driveWidths = [driveWidth25, driveWidth25, driveWidth25, driveWidth25, driveWidth25, driveWidth25, driveWidth35, driveWidth35, for(m2w = [0 : (len(driveWidthsM2) - 1)]) for(m2l = [0 : (len(driveLengthsM2) - 1)]) driveWidthsM2[m2w], Custom_Drive_Width];
  driveHeights = [5, 7, 9.5, 12.5, 15, 19, 19.99, 26.11, for(i = [0 : (len(driveWidthsM2)) * (len(driveLengthsM2)) - 1]) driveHeightM2, Custom_Drive_Height];

  driveLength = driveLengths[Drive_Type];
  driveWidth = driveWidths[Drive_Type];
  driveHeight = driveHeights[Drive_Type];

  skewedSlotHeight = (driveLength * (Slot_Height_Percent / 100)) * cos(Slot_Tilt);
  M = [
    [ 1, 0 , 0 , 0 ],
    [ 0, 1 / cos(Slot_Tilt), tan(Slot_Tilt), 0 ],
    [ 0 , 0, 1 , 0 ],
    [ 0, 0 , 0, 1 ]
  ];

  // amount to shift along z-axis before rotating
  driveShiftUp = tan(Slot_Tilt) * driveHeight / 2;

  module driveProfile() {
    translate([-driveWidth / 2, -driveHeight / 2, 0]) {
      square([driveWidth, driveHeight]);
    }
  }

  module slots() {
    multmatrix(M) {
      for (i=[0:Number_of_Slots - 1]) {
        translate([0, (((driveHeight + (Drive_Gap * 2) + Material_Thickness)) * i), 0]) {
          difference() {
            linear_extrude(height = skewedSlotHeight, convexity = 10) {
              difference() {
                offset(r = Drive_Gap + Material_Thickness) {
                  driveProfile();
                }

                offset(r = Drive_Gap) {
                  driveProfile();
                }
              }
            }

            if(Notch_Height_Percent) {
              notches();
            }
          }
        }
      }
    }
  }

  module notches() {
    rotate([90, 0, 0]) {
      // main cutout
      linear_extrude(height = driveHeight + (Drive_Gap * 2) + (Material_Thickness * 2) + 1, center = true, convexity = 10) {
        translate([0, (skewedSlotHeight - Material_Thickness) * (1 - (Notch_Height_Percent / 100)), 0]) {
          hull() {
            // bottom left
            translate([-max(((driveWidth - (Material_Thickness * 2)) * (Notch_Width_Percent / 100)) / 2, Material_Thickness) + (Material_Thickness), (Material_Thickness * 2), 0]) {
              circle(r = Material_Thickness);
            }

            // bottom right
            translate([max(((driveWidth - (Material_Thickness * 2)) * (Notch_Width_Percent / 100)) / 2, Material_Thickness) - (Material_Thickness), (Material_Thickness * 2), 0]) {
              circle(r = Material_Thickness);
            }

            // top
            translate([-max(((driveWidth - (Material_Thickness * 2)) * (Notch_Width_Percent / 100)) / 2, Material_Thickness), driveLength, 0]) {
              square([max((driveWidth - (Material_Thickness * 2)) * (Notch_Width_Percent / 100), Material_Thickness * 2), 1]);
            }
          }
        }
      }

      // top corner bevel
      translate([0, skewedSlotHeight - min(Material_Thickness, skewedSlotHeight - (Material_Thickness * 2) - (skewedSlotHeight - Material_Thickness) * (1 - (Notch_Height_Percent / 100))), 0]) {
        linear_extrude(height = driveHeight + (Drive_Gap * 2) + (Material_Thickness * 2) + 1, center = true, convexity = 10) {
          // left
          translate([-max((driveWidth - (Material_Thickness * 2)) * (Notch_Width_Percent / 100) / 2, Material_Thickness) - Material_Thickness, 0, 0]) {
            difference() {
              square([Material_Thickness + 0.1, Material_Thickness + 0.1]);
              circle(r = Material_Thickness);
            }
          }

          // right
          translate([max((driveWidth - (Material_Thickness * 2)) * (Notch_Width_Percent / 100) / 2, Material_Thickness) + Material_Thickness, 0, 0]) {
            difference() {
              translate([-Material_Thickness - 0.1, 0, 0]) {
                square([Material_Thickness + 0.1, Material_Thickness + 0.1]);
              }

              circle(r = Material_Thickness);
            }
          }
        }
      }
    }
  }

  module base() {
    translate([0, 0, -Material_Thickness]) {
      linear_extrude(height = Material_Thickness, convexity = 10) {
        hull() {
          offset(r = Base_Radius) {
            projection(cut = true) {
              multmatrix(M) {
                linear_extrude(height = 1, center = true, convexity = 10) {
                  // first
                  offset(r = Drive_Gap + Material_Thickness) {
                    driveProfile();
                  }

                  // last
                  translate([0, (((driveHeight + Material_Thickness + (Drive_Gap * 2))) * (Number_of_Slots - 1)), 0]) {
                    offset(r = Drive_Gap + Material_Thickness) {
                      driveProfile();
                    }
                  }
                }
              }
            }
          }

          // rear support
          minY = (((driveHeight + Material_Thickness + (Drive_Gap * 2)) / cos(Slot_Tilt)) * (Number_of_Slots -1))
            + (driveLength * sin(Slot_Tilt) * 0.5)
            - Material_Thickness;
          maxY = (((driveHeight + Material_Thickness + (Drive_Gap * 2)) / cos(Slot_Tilt)) * (Number_of_Slots -1))
            + (driveHeight / cos(Slot_Tilt) / 2)
            + (driveLength * sin(Slot_Tilt));
          range = maxY - minY;

          translate([0, minY + (range * (Additional_Topple_Support_Percent / 100)), 0]) {
            // left
            translate([-((driveWidth / 2) + Drive_Gap + Material_Thickness) * (Topple_Support_Width_Percent / 100), 0, 0]) {
              circle(r = Material_Thickness);
            }

            // right
            translate([((driveWidth / 2) + Drive_Gap + Material_Thickness) * (Topple_Support_Width_Percent / 100), 0, 0]) {
              circle(r = Material_Thickness);
            }
          }
        }
      }
    }
  }

  module drive() {
    rotate([-Slot_Tilt, 0, 0]) {
      %translate([0, 0, driveShiftUp]) {
        rotate([90, 0, 0]) {
          cornerRadius = 2;

          if (Show_Drives) {
            hull() {
              // bottom left
              translate([(-driveWidth / 2) + cornerRadius, cornerRadius, 0]) {
                cylinder(r = cornerRadius, h=driveHeight, center = true);
              }

              // bottom right
              translate([(driveWidth / 2) - cornerRadius, cornerRadius, 0]) {
                cylinder(r = cornerRadius, h=driveHeight, center = true);
              }

              // top left
              translate([(-driveWidth / 2) + cornerRadius, driveLength - cornerRadius, 0]) {
                cylinder(r = cornerRadius, h=driveHeight, center = true);
              }

              // top right
              translate([(driveWidth / 2) - cornerRadius, driveLength - cornerRadius, 0]) {
                cylinder(r = cornerRadius, h=driveHeight, center = true);
              }
            }
          }
        }
      }

      if (Show_Center_Markers) {
        // center
        #translate([0, 0, driveLength / 2]) {
          sphere(d = min(driveLength, driveWidth, driveHeight) + 1);
        }
      }
    }
  }

  slots();
  base();

  if($preview && Drives_to_Show) {
    for (i = [0:Number_of_Slots - 1]) {
      if ((Drives_to_Show == 1 && i == 0) || (Drives_to_Show == 2 && i == Number_of_Slots - 1) || Drives_to_Show == 3) {
        translate([0, ((driveHeight + Material_Thickness + (Drive_Gap * 2)) / cos(Slot_Tilt)) * i, 0]) {
          drive();
        }
      }
    }
  }
}

hardDriveHolder();

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

```


[User]
I'd like to create a customizable dog poop bag dispenser, and I'm looking for an OpenSCAD design that allows for easy modification. I want it to be a two-part design: a container and a lid. The container should have a cylindrical shape with a bottom, and the lid should screw onto the top of the container.

Here are some key features I'm looking for:

**Container:**
*   **Inner Diameter:**  The container should have an adjustable inner diameter, specified in millimeters. I need to be able to set this to match the size of the bag roll I'm using.
*   **Height:**  The container's height should also be adjustable in millimeters.
*   **Compartments:** I'd like an option to divide the container into compartments using radial dividers. The number of compartments should be configurable.
*   **Opening:** The container needs an opening for pulling out bags. This opening's width should be adjustable. It should also have a "dog bone" shape which allows for easier removal of the first bag.
*   **Engraving:** The container should allow for engraving a short name and an additional line of text on the outside of the cylinder.
*   **Chamfers:** Both the inside and outside edges of the bottom of the container should have configurable chamfers. I'd like to control how much of a chamfer to apply as a percentage of the diameter.
*   **Wall Thickness:** The wall thickness for the container should be adjustable, and I'd like to have separate control of the divider walls' thickness. There should be a default value for this based on the diameter.
*   **Bottom Thickness:** The bottom of the container should have a configurable thickness, or use the default wall thickness if not specified.
*   **Solid Container:** I would like an option to build a solid container, without infill, top layer, but with a thicker bottom, designed to be printed with special settings to save filament and provide higher quality prints. This could be a boolean variable.
*   **Attachment Ring:**  There should be a ring attachment feature near the top for attaching the dispenser to a leash.

**Lid:**

*   **Screw Thread:** The lid should have a screw thread to securely attach to the container.
*   **Knurling:** I'd like different lid options, including left knurled, right knurled, vertical knurled, double knurled, and a simple non-knurled lid.
*   **Lid Top Thickness:** The top of the lid should also have a configurable thickness, or default to the wall thickness if not specified.
*   **Backlash:** There needs to be an adjustable backlash value for the screw thread to control fit.

**Overall Design:**

*   **Parametric:** The design should be entirely parametric, allowing easy adjustment of all dimensions and features.
*   **Quality:** There should be an adjustable quality parameter to control the number of facets used when generating the shapes.
*   **Parts:** I should be able to select which part I'm generating (container or lid).

I am looking for a comprehensive set of .scad files to generate these parts, including all the necessary modules. I do not need any additional libraries beyond standard OpenSCAD capabilities. Please provide the code in a well-commented and structured format. Let me know if you need more details.

[Assistant]
dispenser.scad:
```
// 'Dog Poop Bag Dispenser' by ahaque

// Credit for 'Container with Knurled Lid' to wstein
// Base container can be found at https://www.thingiverse.com/thing:612709

/* [Dimensions] */

// inner diameter (for outer dimension add 2x wall thickness) - minimum = 8.0mm
diameter=34.0;

// inner height (for outer dimension add 2x wall thickness)
height=62.5;

// Select how many compartments there should be in the container - default is 1.
compartments = 1; //[1:8]

// Select width of opening for bags.
opening_width = 7; //[5:15]

/* [Appearance] */

// Name to engrave, designed for short (up to 6 letters) name.
name="Mocha";

// Second line to engrave, designed to fit 10 digit number. Leave blank to engrave nothing.
additional_line="";

// Container outside chamfer in percent of diameter. Use 0 for a flat outside bottom - default is 5%
outside_chamfer=5.0;

// Container inside chamfer in percent of diameter. Use 0 for a flat inner bottom - default is 2.5%
inside_chamfer=2.5;

/* [Thickness] */

// default value -1 means 2mm up to size 60, 2.5mm from 61 - 100. 3mm for larger.
wall_thickness=-1;

// default value -1 means take 1/2 of the container Wall Thickness
divider_wall_thickness=-1;

//thickness off container bottom. If you use the solid container model use the same value as setup in the slicer. default: -1 (use wall thickness)
container_bottom_thick=-1;

//thickness off lid top. Lid is always normal printing, so nothing special here, except reset the slicer to normal settings. default: -1 (use wall thickness)
lid_top_thick=-1;

/* [Expert] */

// to show? Only create once! You will get always all parts!
part = "container_"; // [container_:Container,lid_left_knurled_:Lid - Left Knurled, lid_right_knurled_:Lid - Right Knurled, lid_vertical_knurled_: Lid - Vertical Knurled, lid_double_knurled_:Lid - Double Knurled, lid_simple_non_knurled_:Lid - Simple Non Knurled]

// default value -1 means take 1/4 of Wall Thickness
backlash=-1;

//build a solid container, this is the key for high quality prints and also spare filament. You must print it with special settings (no infill, no top layer, bottom layer 1mm, shell thickness 1mm - must be about half of wall thickness). Works fine with Slic3r but I failed to setup Cura correct. Don't forget to reactivate top layer and infill before printing lid!
solid_container=0; //[1:true, 0:false]

quality=100; // [80:good, 100:high, 120:very high,150:best]

/* [Hidden] */

// ensure some minimal values
function diameter()=max(8,diameter);

function wall_thickness()=(
	wall_thickness!=-1 ? max(1.0,wall_thickness) : (
	diameter() <= 20 ? 1.5 : (
	diameter() <= 60 ? 2.0 : (
	diameter() <= 100 ? 2.5 :
   3.0))));

function get_divider_wall_thickness()=(divider_wall_thickness==-1?wall_thickness()/2:divider_wall_thickness);

bottom_thick=(container_bottom_thick<0?wall_thickness()/-container_bottom_thick:container_bottom_thick);
top_thick=(lid_top_thick<0?wall_thickness()/-lid_top_thick:lid_top_thick);

function backlash()=backlash==-1?wall_thickness()/4:max(0.2,backlash);
thread_height=(diameter()>20?diameter()/6:20/6);
function height()=max(2*(thread_height+bottom_thick),height);

$fn=quality;

lid_r=diameter()/2+wall_thickness()/2+backlash();
lid_r0=(diameter()>=20?lid_r:20/2+wall_thickness()/2+backlash()); // virtual radius, for diameter() < 20

intersection()
{
	assign(offset=(part=="both"?diameter()*.6+2*wall_thickness():0))
	union()
	{
		if(part=="container_")
		translate([0,-offset,0])
		container(diameter(),height());
		else
		translate([0,offset,(part=="cross"?height()-thread_height-wall_thickness()+bottom_thick+.1:thread_height+top_thick+wall_thickness())])
		rotate([0,(part=="cross"?0:180),0])
		lid(lid_r,lid_r0,thread_height+wall_thickness());
	}
	if(part=="cross")
	cube(200);
}

module lid(r,r0,h)
union()
{
	intersection()
	{
		translate([0,0,wall_thickness()])
		thread(r,thread_height,outer=true);

		cylinder(r=r+wall_thickness()/2,h=h);
	}

	difference()
	{
		assign(a=1.05,b=1.1)
		assign(ra=a*r+b*wall_thickness(),ra0=a*r0+b*wall_thickness(),h1=h+top_thick)

        if(part=="lid_right_knurled_")
        knurled(ra,ra0,h1,[1]);
        else if(part=="lid_left_knurled_")
        knurled(ra,ra0,h1,[-1]);
        else if(part=="lid_vertical_knurled_")
        knurled(ra,ra0,h1,[0]);
        else if(part=="lid_double_knurled_")
        knurled(ra,ra0,h1,[-1,1]);
        else
        cylinder(r=ra,h=h1);

		translate([0,0,-.1])
		cylinder(r=r+wall_thickness()/2,h=h+.1);

	}

    // Have ring on top of lid

    //translate([0, 0, r+wall_thickness()]) rotate([0, -90, 0]) linear_extrude(wall_thickness()) ring(r, wall_thickness());
}

knurled_h=[
0,
0.1757073594,
0.5271220782,
0.7116148055,
0.9007198511,
1];
knurled_rdelta=[.2,0,0.1,.3,.6,1.0];
shape_n=5;

module knurled(r,r0,height,degs)
assign(n=round(sqrt(120*(r+2*wall_thickness())*2)/shape_n)*shape_n)
assign(h_steps=len(knurled_h)-1)
assign(r_delta=-1.6*wall_thickness()/2)
//translate([0,0,height])
//rotate([180,0,0])
intersection_for(a=degs)
for(i=[0:h_steps-1])
assign(hr0=knurled_h[i],hr1=knurled_h[i+1])
assign(hr=hr1-hr0)
assign(rr0=knurled_rdelta[i],rr1=knurled_rdelta[i+1])
assign(rr=rr1-rr0)
translate([0,0,hr0*height])
rotate([0,0,35*height/r*hr0*a])
linear_extrude(convexity=10,height=hr*height,twist=-35*height/r*hr*a,scale=(r+r_delta*rr1)/(r+r_delta*rr0))
for(j=[0:n/4-1])
rotate([0,0,360/n*j])
circle(r=r+r_delta*rr0,$fn=5);

module container(r,h)
difference()
{
union()
{

	difference()
	{
		union()
		{
			container_hull(diameter()/2+wall_thickness(),height()+bottom_thick,outside_chamfer,[wall_thickness()/2,thread_height+wall_thickness()/2]);

			translate([0,0,height()-thread_height-wall_thickness()/2+bottom_thick])
			thread(diameter()/2+wall_thickness(),thread_height,outer=false);
		}

		if(!solid_container)
			translate([0,0,bottom_thick])

		container_hull(diameter()/2,height()+bottom_thick,inside_chamfer);
	}

	if(compartments > 1)
	{
        echo(get_divider_wall_thickness());
		for(a = [0 : 1 : compartments])
		{
			rotate((360 / compartments) * a)
            translate([-(get_divider_wall_thickness() / 2), 0, bottom_thick])
            cube([get_divider_wall_thickness(), diameter() / 2, height()]);
		}
	}
    translate([0, -diameter + 4*wall_thickness(), h/2]) rotate([-90, 0, -90]) linear_extrude(2*wall_thickness()) ring(diameter/3, 2*wall_thickness());
};
translate([0, 0, h/2]) rotate([90, 0, 180]) curve_shape(1.1*wall_thickness() + diameter()/2, 1.2*wall_thickness(), h=h) dog_bone(opening_width, h);
linear_extrude(wall_thickness()) flower(r);
translate([0, 0, h/2]) rotate([90, 0, 160]) curve_shape(1.1*wall_thickness() + diameter()/2, .6*wall_thickness(), h=h) rotate([0, 0, 90]) text(name, size=5, halign="center");
if (additional_line != "") translate([0, 0, h/2]) rotate([90, 0, 205]) curve_shape(1.1*wall_thickness() + diameter()/2, .6*wall_thickness(), h=h) rotate([0, 0, 90]) text(additional_line, size=2, halign="center");
}

module container_hull(r,h,chamfer,thread_notch=[0,0])
{
	xn=thread_notch[0];
	yn=thread_notch[1];

	rotate_extrude(convexity=10)
	assign(x1=r*(50-chamfer)/50,x2=r,x3=r-xn,y1=r*chamfer/50,y2=h,y3=h-yn,y4=h-yn+xn)
	polygon([[0,0],[x1,0],[x2,y1],[x2,y3],[x3,y4],[x3,y2],[0,y2]]);
}

module thread(r,h,outer=false)
assign($fn=100)
{
	linear_extrude(height=h,twist=-250,convexity=10)
	for(a=[0:360/2:359])
	rotate([0,0,a])
	if(outer)
	{
		difference()
		{
			translate([0,-r])
			square(2*r);

			assign(offset=.8)
			translate([r*(1-1/offset),0])
			circle(r=1/offset*r);
		}
	}
	else
	{
		assign(offset=.8)
		translate([(1-offset)*r,0,0])
		circle(r=r*offset);
	}
}

module dog_bone(l, h) {
circle_radius=2*l/3;
shift_x = l/2;
shift_y = h/4;
union(){
    square([l, h/2], center=true);
    translate([shift_x,shift_y,0]) circle(circle_radius);
    translate([-shift_x,shift_y,0]) circle(circle_radius);
    translate([shift_x,-shift_y,0]) circle(circle_radius);
    translate([-shift_x,-shift_y,0]) circle(circle_radius);
}
}

module flower(r) {
    d= r / 10;
    union(){
        circle(d);
        for(a=[0:6]) {
            rotate(a*60) translate([d, 0, 0]) scale([3, 1, 1]) circle(d/2);
        }
    }
}

module ring(r, t) {
    union() {
        difference(){
            translate([-r, -r, 0]) square([r, 2*r]);
            translate([-r, -r, 0]) square([r/3, 2*r]);
            circle(r);
        }
        difference() {
            circle(r);
            circle(r - t);
            translate([-r*5/3, 0, 0]) square(2*r, center=true);
        }
}
}

module curve_shape(r, t, h=100) {
// Curve 2d shape onto cylinder.
    intersection() {
        difference() {
            rotate([0,90,90]) cylinder(h, r, r, center=true);
            rotate([0,90,90]) cylinder(h, r-t, r-t, center=true);

        }
    linear_extrude(r) children();
    }

}

```


[User]
Hey, I'm looking for some help designing a customizable text object in OpenSCAD. I've seen a lot of people struggle with the Thingiverse customizer and I'd like to make a more robust solution.

I'd like the user to be able to specify the text to write, the size of the text, and the color of the text. The text should be extruded linearly, say by 1 unit. The text should also be centered horizontally and vertically.

Ideally, I'd like to keep the code concise and easy to read. No external libraries are needed. I've attached the example I wrote for reference, but I'm hoping for something better written.

[Assistant]
Example_File.scad:
```
/* [Text Settings] */
// Write some text
text_to_write = "Hello";

// Font size
size_of_text = 10; //[1:0.5:30]

// Preview Color
text_color = "White"; //[White,Black,Red,Blue,Green,Yellow,Orange,Pink,Purple]

linear_extrude(1) color(text_color) text(text_to_write, size_of_text, halign = "center", valign = "center");

```


[User]
Hi, I'm looking for a customizable OpenSCAD design for a tombo stick, which is a tool used in pottery for measuring the dimensions of wheel-thrown pieces. I've seen a design that does most of what I want, but I need it made parametric from the ground up so I can tweak it more easily. The tool should have a horizontal arm, a vertical arm, and a hook-shaped handle, and it needs to account for clay shrinkage.

Here's a breakdown of the features I need:

**Overall Dimensions:**
*   I need to be able to specify the *finished* dimensions of the pottery piece I want to measure, not the dimensions of the tombo itself.
*   The design should automatically calculate and adjust for clay shrinkage using a percentage I provide.
*   All units should be in millimeters.

**Horizontal Arm:**
*   This arm will measure the outside and inside diameters of the piece.
*   It should have two "lines" or features for marking the inside and outside edges.
*   I need to be able to control the thickness of the arm.
*   I need to be able to control the width of the arm and the lines/features on the arm.
*   The horizontal arm should have rounded ends where they meet the lines.
*   There should be an option to add text to the horizontal arm, with adjustable size, font, spacing, depth and position.

**Vertical Arm:**
*   This arm will measure the inside depth of the piece.
*   It should have a line/feature at the bottom to mark the correct depth.
*    I need to control the thickness and width of the vertical arm.
*   The vertical arm should have a rounded end where it meets the line.

**Foot Measurement:**
*   I want to be able to specify the outer and inner diameter of the foot of the pot.
*   The tool should include "lines" or features for marking these measurements, similar to the inside/outside diameters.
*   The lines for the foot should be able to be removed or at least minimized if I don't have a foot.

**Handle:**
*   The handle should be a hook shape.
*   I need to be able to control the length of the straight part of the handle.
*    I need to be able to control the diameters of the hook.
*   I need to be able to control the thickness of the handle.

**Parameters to include:**

*   **Piece Dimensions:**
    *   `tombo_text`: Text to carve into the jig (optional).
    *   `outside_width_raw`: Finished outside rim diameter.
    *   `inside_width_raw`: Finished inside rim diameter.
    *   `inside_height_raw`: Finished inside depth of the piece to the top of the rim.
    *   `foot_width_outer_raw`: Finished outer diameter of the foot.
    *   `foot_width_inner_raw`: Finished inner diameter of the foot.
    *   `foot_height_raw`: Height of the foot from the outside of the piece.
    *   `clay_shrinkage`: Percentage of clay shrinkage (e.g., 10 for 10%).

*   **Model Constants:**
    *   `thickness`: Thickness of the tombo parts.
    *   `shaft_width`: Width of the arms.
    *   `line_width`: Width of the lines/features on the tombo.
    *    `line_depth`: Depth of the lines/features on the tombo.
    *   `handle_length`: Length of the straight part of the handle.
    *   `Hook_Diameter_Outer`: Outer diameter of the hook.
    *   `Hook_Diameter_Inner`: Inner diameter of the hook.

*   **Text Options:**
    *   `text_size`: Size of the text.
    *   `text_depth`: Depth of the text.
    *   `text_spacing`: Spacing between letters in the text.
    *   `text_buffer`: Small buffer for the text (for manifold issues).
    *   `text_font`: Font used for the text.

I don't need any fancy libraries for this, just the standard OpenSCAD functions. Please let me know if you have any questions. I'm really excited to see what you come up with!

[Assistant]
Tombo-O-Matic_v1.01.scad:
```
   // title      : Tombo-O-Matic
   // author     : Patrick Esmonde
   // contact    : patrick@esmonde.com
   // revision   : 1.01
   // license    : Attribution-NonCommercial-ShareAlike 4.0 International CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/

   //FINISHED PIECE DIMENSIONS.
   //Units are in millimeters
   //This should be what you want your finished piece to be--not the size when working on the wheel.  The shrinkage calculation below will enlarge the tombo.

    /*[ Piece Dimensions ]*/
        tombo_text="1200G   LARGE BOWL   S.R.13%";  //Enter a label to carve into the jig - Leave empty if you want unlabeled
        outside_width_raw = 203; //Outside rim diameter
        inside_width_raw = 192;  //Inside rim diameter

        inside_height_raw = 70;  //Inside depth of piece to the top of the rim
        foot_width_outer_raw=92; //Outer diameter of the foot.  Must be >= 2*shaft_width otherwise it would be too small
        foot_width_inner_raw=77; //Inner diameter of the foot.  Must be >= 2*shaft_width otherwise it would be too small
        foot_height_raw=11;      //Height of foot from the outside of the piece

        clay_shrinkage=13.0;   //Enter the percentage your clay body shrinkage from plastic to final firing.  The most accurate way to get this percent is to make a clay ruler out of your plastic clay body.  Take a measurement at plastic, then after final firing.  Calculate the % and then enter it below.  For Example - A body that shrinks 10% would be expressed as 10 (not 0.1)  Enter 0 to make exact size without any reduction.

    //MODEL DIMENSIONS - These help tweak your model further, but are generally not required to change

      //SIZES

         /*[ Model Constants ]*/
        thickness= 3;  //If you want a stronger (and thicker) tool, increase this.
        shaft_width=10;  //This is the width of the horizontal and vertical arms.
        line_width=.75;  //Width of the marks for the foot and inner rim.  Shouldn't need to change this unless your printer is having trouble with the thin walls.  Increase in small amounts only.
        line_depth=0.75;  //Depth of all lines on the model.  This should be less than 1/3rd the thickness to make sure you don't weaken the part
        handle_length=15;   //If you want a longer handle, make this bigger.
        Hook_Diameter_Outer=10;  //Default: 10  Must be larger than Hook_Diameter_Inner
        Hook_Diameter_Inner=5;  //Default: 5   Must be smaller than Hook_Diameter_Outer

      //TEXT OPTIONS
        text_size=shaft_width*0.6;  //Default: 0.6  Make this smaller if you need to fit more text.  Too small and it won't print though!
        text_depth=0.75;     // Default: 0.75  This should be less than 1/3rd the thickness to make sure you don't weaken the part
        text_spacing=1.1;           // (Default: 1.1) - This is the spacing between letters, aka "tracking" for you design folks.
        text_buffer=0.1;            // (Default: 0.1) - This is used to prevent manifold issues.  Remove this and it all breaks lol.  Do not remove.  Ok, fine, go ahead and remove it then post a comment on the model wondering why it doesn't work anymore.  You will see this screenshot posted in my reply.  You have been warned.
        text_font="Arial:style=Bold";  //To change the font, click Help-->Font List.  Once you find the font and style you want, click "Copy to Clipboard" and then paste that value between the quotation marks.

    //Math is Hard, but necessary.

        inside_width_calc = (inside_width_raw / (1-(clay_shrinkage/100)));  //Adjustment for shrinkage
        outside_width_calc = (outside_width_raw / (1-(clay_shrinkage/100)))-(shaft_width*2);  //Adjustment for shrinkage
        width_cylinder_translate = (outside_width_raw / (1-(clay_shrinkage/100))/2)-shaft_width;

        inside_height_calc = (inside_height_raw / (1-(clay_shrinkage/100)))-(shaft_width/2);  //Adjustment for shrinkage
        height_shaft_translate = ((inside_height_calc*-1)/2)-(shaft_width/2);
        height_cylinder_translate = ((inside_height_calc*-1)+(shaft_width/2))-shaft_width;

        handle_translate=(handle_length/2)+(shaft_width/2);
        hook_translate=(handle_length)+(Hook_Diameter_Outer);

        foot_width_outer_calc = (foot_width_outer_raw / (1-(clay_shrinkage/100))); //Adjustment for shrinkage
        foot_width_inner_calc = (foot_width_inner_raw / (1-(clay_shrinkage/100))); //Adjustment for shrinkage
        foot_height_calc = (foot_height_raw / (1-(clay_shrinkage/100))); //Adjustment for shrinkage

    //Where the Magic Happens

        //Horizontal Arm

        difference(){
        color([1,0,0])

        union(){
        cube([outside_width_calc,shaft_width,thickness],center=true);  //Horizontal Arm

        //Width Arm Curve 1
        translate([width_cylinder_translate,-(shaft_width/2),0])
        rotate(90)
         difference() {
           cylinder(r=shaft_width, h=thickness, center=true);
           translate([-shaft_width,0,0]) cube(shaft_width*2, center=true);
         }

        //Width Arm Curve 2
        translate([width_cylinder_translate*-1,-(shaft_width/2),0])
        rotate(90)
         difference() {
           cylinder(r=shaft_width, h=thickness, center=true);
           translate([-shaft_width,0,0]) cube(shaft_width*2, center=true);
         }

        }

        //Text

        translate([0,0,(thickness/2)-line_depth])
        linear_extrude(height=text_depth+text_buffer)
            text(font=text_font, text=tombo_text, size=text_size, spacing=text_spacing, halign="center", valign="center", $fn=100);

     //Foot Width Cutout

        //Inside Foot Line
        if(foot_width_outer_raw>=(shaft_width*2)){

        $fn=200;
        translate([0,0,-(thickness/2)+line_depth/2])

        difference(){
        cylinder(r=foot_width_outer_calc/2, h=line_depth, center=true);
        cylinder(r=(foot_width_outer_calc/2)-line_width, h=line_depth, center=true);
          }

      }

       //Outside Foot Line
       if(foot_width_inner_raw>=(shaft_width*2)){

        $fn=200;
        translate([0,0,-(thickness/2)+line_depth/2])

        difference(){
        cylinder(r=(foot_width_inner_calc/2)+line_width, h=line_depth, center=true);
        cylinder(r=foot_width_inner_calc/2, h=line_depth, center=true);
          }

      }

      //Inside Rim
         translate([0,0,(thickness/2)-line_depth/2])
         difference(){
         cube([inside_width_calc,shaft_width,line_depth],center=true);
         cube([inside_width_calc-(line_width*2),shaft_width,line_depth],center=true);
         }
        }

     //Vertical arm for height

        //Arm
        color([0,1,0])

        difference(){

        union(){
        translate([0,height_shaft_translate,0])
        rotate(90)
        cube([inside_height_calc,shaft_width,thickness],center=true);

        //Shaft End
        $fn=200;
        translate([0,height_cylinder_translate,0])
        cylinder(h=thickness,r=shaft_width/2,center=true);

        }

        //Foot Height Line

        translate([0,-(inside_height_calc+shaft_width-foot_height_calc+(line_width/2)),-(thickness/2)+line_depth/2])
        cube([shaft_width,line_width,line_depth],center=true);

        }

    //Hook

        //Shaft
        color([0,0,1])
        translate([0,handle_translate,0])
        rotate(90)
        cube([handle_length,shaft_width,thickness],center=true);

        //Hook

        $fn=200;
        difference() {
        translate([0,hook_translate,0])
        cylinder(r=Hook_Diameter_Outer, h=thickness, center=true);
        translate([0,hook_translate,0])
        cylinder(r=Hook_Diameter_Inner, h=thickness*2, center=true);

    }

```


[User]
I'm looking to create a coffee grounds distribution tool, similar to what you might see baristas use. I have a basic idea, and I'd like to use OpenSCAD to make it fully customizable.

Essentially, it's a handle with several needles protruding from the bottom. The needles are used to break up clumps in the coffee grounds before tamping. I'd like to be able to control the number of needles, their diameter, and how far they stick out from the handle.

For the handle itself, I'm picturing a sort of tapered hexagonal shape. I'd like to define the width of the hexagon at the top and bottom, and also the total height of the handle. The handle should be hollowed out and have the needles attached at the base.

I need a fully parametric OpenSCAD design that allows me to control:

*   **Needle Specifics:**
    *   The number of needles.
    *   The diameter of each needle.
    *   The depth the needles are inserted into the handle (and thus how far they protrude).
*   **Handle Specifics:**
    *   The height of the handle.
    *   The width of the hexagon at the bottom.
    *   The width of the hexagon at the top.

The needles should be arranged in a circle, evenly spaced around the center of the handle's base.

I'm familiar with using the `MCAD` library, specifically for the `regular_shapes.scad` file, which contains a hexagon module. I would like the design to incorporate that as well.

[Assistant]
espresso_distributor.scad:
```
include <MCAD/regular_shapes.scad>

/*[Needle Specifics:]*/
// Number of needles to use
numHoles = 8;
// Diameter of the needles
needleDiameter = 1;
// Needle insertion depth
needleDepth = 10;

/*[Handle Specifics:]*/
// Height of the handle
handleHeight = 20;
// Width of the base hexagon
hexWidthBottomInput = 10;
// Width of the the top hexagon
hexWidthTopInput = 12.5;

/*[HIDDEN]*/
// Internal value calculations
hexWidthBottom = hexWidthBottomInput / 2;
hexWidthTop = hexWidthTopInput / 2;
scaleFactor = hexWidthTop / hexWidthBottom;
diameterNeedleHoles = (hexWidthTop * 2) *.55;

// Defines the needle holes
module needles(){
    // Shift the needle holes down from the top of the handle because
    // linear extrusions have to be in the positive direction along the Z-Axis
    translate([0, 0, handleHeight - needleDepth]){
        linear_extrude(needleDepth){
            // Place numHoles holes along a circle
            for(i = [-1 : numHoles]){
                rotate(i * (360 / numHoles)){
                    translate([0, diameterNeedleHoles / 2]){
                        circle(d = needleDiameter, $fn = 50);
                    }
                }
            }
        }
    }
}

// Defines the handle object
module handle(){
    translate([0,0,0]){
        // Create a hexagon and extrude it 'into' the hexagon of the other size
        linear_extrude(height = handleHeight, scale = scaleFactor){
            hexagon(hexWidthBottom);
        }
    }
}

// Create the final object
// by 'subtracting' the holes from the handle
difference(){
    handle();
    needles();
}
```


[User]
Hi, I'm looking to generate a parametric design for 3D-printable boxes, specifically for printing in vase or spiral mode. I've seen some designs that use panels for extra rigidity, and I'd like to be able to customize that as well. I need the design to generate a box and a lid, with control over their dimensions and tolerances between the two.

Here's what I have in mind for the customizable parameters:

*   **Box Dimensions:**
    *   `boxw1`: Width of one side of the box.
    *   `boxw2`: Width of the other side of the box.
    *   `boxh`: Total height of the box including the lid.
*   **Panels:**
    *   `npan1`: Number of panels on the first side.
    *   `npan2`: Number of panels on the second side.
    *   `pandepth`: Depth of the panels, expressed as a multiple of the nozzle width.
*   **Printing:**
    *   `nozzw`: Nozzle width to be used for printing.
    *   `lyrh`: Layer height.
*   **Other:**
    *   `output`: Option to generate only the box, only the lid, or both.
    *   `tol`: Tolerance between the box and lid.
    *    `smooth`: Smoothness of curves, controlling the number of facets used for circles.

I'd like the design to handle calculating the lid height automatically (maybe around 25% of the total box height, with some reasonable limits). Also, the corner radius should be calculated automatically, as a percentage of the shortest side. Also, I'd like the panel size to be calculated automatically, making sure they can't be smaller than what the nozzle can make.

The model should generate solids which are then printed in vase/spiral mode in a slicer. This means the slicer will determine the wall thickness and create the single-wall extrusion when generating gcode from the STL.

Could you generate the appropriate OpenSCAD code for this?

[Assistant]
Vasecontainer1.scad:
```
//**********************************************************
//By Coat, 11-july-2021
//Licensed under the Creative Commons - Attribution license.
//
//Make boxes in Vase or Spiral-mode. The result here is
//a solid and not a box. That is a requirement for Vase-
//printing. In your slicer (preview) in vase/spiral-mode,
//the top will be open, the walls will have the thickness
//of your nozzle and the bottom will be there according to
//your slicer-settings. 3 bottomlayers gives a good result,
//but 2 is also possible. Happy printing :)
//**********************************************************

//primary parameters
/* [Dimensions of the box] */
//Width of first side in mm
boxw1 = 50;
//Width of other side in mm
boxw2 = 75;
//Total height of box with lid
boxh = 50;
/* [Panels] */
//Number of "panels" first side (recalculated if exceeds max possible)
npan1=1;        // [0:1:10]
//Number of "panels" other side (recalculated if exceeds max possible)
npan2=2;        // [0:1:10]
//depth panels in times nozzle width (if nozzw=0.6 and pandepth=2 -> 1.2mm)
pandepth = 2;   // [1:1:3]
/* [Printing Vasemode] */
//Nozzle width you are going to use
nozzw=0.6;
//Layer height you are going to use
lyrh = 0.3;
/* [Other] */
//split in parts, 1=only box, 2 = only lid, 3 = both
output= 3;      // [1:3]
//tolerance in mm between box and lid (all sides)
tol = 0.15;     //[0:0.03:0.3]
//smoothness (nr of facets in circle)
smooth = 32;    // [4:4:64]

//----------------------------------------------------------
module basebox(hh,ww1,ww2,rra,rrb)
{
  tt1 = ww1/2 - rra;
  tt2 = ww2/2 - rra;
  hull()
  {
    translate([-tt1,-tt2,0])
      cylinder(h=hh,r1=rra,r2=rrb);
    translate([-tt1,tt2,0])
      cylinder(h=hh,r1=rra,r2=rrb);
    translate([tt1,-tt2,0])
      cylinder(h=hh,r1=rra,r2=rrb);
    translate([tt1,tt2,0])
      cylinder(h=hh,r1=rra,r2=rrb);
  }
}
//----------------------------------------------------------
module paneling(ww,tty,nnp,rr)
{
  pproom = ww-4*crad;
  ppdist = crad;   //Distance between panels
  ttz = (boxh-hlid-th)/2;
  pph= (boxh-hlid-th) -2*crad;
  ppd= pandepth *nozzw;

  //calculating if given nr of panels exceeds the max possible and recalculate if so.
    ppwtest0= (pproom - ((nnp-1)*ppdist))/nnp;
    ppwmin = ppd*sqrt(3)*2;
    ppwtest1= ppwtest0 >ppwmin? ppwtest0:ppwmin;
    nnp2= ppwtest1>ppwmin? nnp : floor((pproom+ppdist)/(ppwtest1+ppdist));
    ppw = (pproom - ((nnp2-1)*ppdist))/nnp2;
  //

  for (i=[0:nnp2-1])
  {
    ttx = (pproom/2-ppw/2) - i*(ppdist+ppw); //start right and count down to the left
    rotate([0,0,rr])
      translate([0,tty/2,ttz])
        rotate([90,0,0])
          translate([ttx,0,0])
            basebox(ppd,ppw,pph,2*ppd,(2*ppd)-ppd*sqrt(3));
  }
}

//----------------------------------------------------------
module mainbox()
{
  difference()
  {
    union()
    {
      basebox(boxh-hlid-th,boxw1,boxw2,crad,crad);
      translate([0,0,boxh-hlid-th])
        basebox(th,boxw1,boxw2,crad,crad-nzw2);
      translate([0,0,boxh-hlid])
        basebox(hlid-4*lyrh,boxw1-nzw2*2,boxw2-nzw2*2,crad-nzw2,crad-nzw2);
    }
    if (npan1>0)paneling(boxw1,boxw2,npan1,0);
    if (npan1>0)paneling(boxw1,boxw2,npan1,180);
    if (npan2>0)paneling(boxw2,boxw1,npan2,90);
    if (npan2>0)paneling(boxw2,boxw1,npan2,270);
  }
}

//----------------------------------------------------------
module mainlid()
{
  translate([1.25*boxw1,0,0])
    basebox(hlid,boxw1,boxw2,crad,crad);
}

//----------------------------------------------------------
//-----main-------------------------------------------------
//----------------------------------------------------------

//secondary parameters
//calculated height of lid, 25% of boxh, restricted between 10 and 20mm
hlid = boxh/4<10? 10: (boxh/4>20?20:boxh/4);
//calculated radius of corners 10% shortest side, restricted to 4mm minimum. Also used as space between panels (see ppdist in paneling())
crad = (min(boxw1,boxw2)/10<4? 4:min(boxw1,boxw2)/10);

nzw2 = nozzw+tol;         //nozzlewidth with tolerance
th = nzw2*sqrt(3);        //height chamfer (30 degrees up) when moving to other level

//-----The box and lid-----------------------------------------------------
$fn = smooth;                 //smoothness

if (output ==1 || output==3)
  mainbox();
if (output >1)
  mainlid();

```


[User]
Hi, I'm looking to design a custom wedding favor, a fridge magnet, using OpenSCAD. I'd like it to be a heart shape with an arrow through it, and have the couple's initials and the wedding date on the front. I also want a cut-out on the back for a magnet.

Here are the details:

*   **Overall Shape:** The main shape should be a heart. I'd like this to be slightly 3D, with the top being a curved surface. I think layering and scaling a 2D heart shape would work best to make the curved top. I have an SVG file of the heart shape which I would like to use, but I will need a Bezier curve function to make it work, or a version of OpenSCAD that handles the SVG import properly. The overall size of the magnet should be parameterized, let's say with a starting size of about 45mm wide, 40mm high, and 5mm thick.
*   **Arrow:** An arrow should pierce the heart, at an angle. I want the arrow to be a cylinder with an arrowhead at one end, and fletchings at the other end. The arrow should also have parameters for the shaft length and radius, and the fletching size and angle.
*   **Text:** The couple's initials should be centered on the heart, along with the wedding date, in a layered manner.  I need to control the font, size, and depth of the text. I want the initials, the month and day, and then the year.
*   **Magnet Cutout:** I want to be able to choose between a rectangular cutout for a strip magnet, or two circular cutouts for cylindrical magnets.  The size of the cutouts should be parametric based on the magnet type.
*   **Customization:** I want all of the dimensions and text to be parameters, so I can adjust them easily.

I am including the bezier functions from the example code I found to aid with handling the SVG, but I would prefer that you use the import() function for the SVG if possible.

I want to be able to specify parameters like:
*  `Heart_size = [45, 40, 5];` (width, height, thickness of the heart)
* `Text_depth = 2.25;` (depth of the engraved text)
* `Layer_height = 0.25;` (layer height for the curved top)
* `Initials_font = "Veranda:style=Bold Italic";`
* `Initials = "E&C";`
* `Initials_font_size = 10;`
* `Date_font = "Libre Baskerville:style=Bold";`
* `Date = "Oct 9";`
* `Date_size = 5;`
* `Year = "2021";`
* `Year_size = 5;`
* `Magnet_type = "cylinder"; // [cylinder, strip]`
* `Magnet_strip = [20, 13.7, 1.75];`
* `Magnet_cylinder = [4.25, 2];` (diameter, height)
* `Number_of_fletchings = 1;`
*   `Fletching_size = [4, 2];`

Can you provide the OpenSCAD file(s) for this?  I'm not planning on using any external libraries at this time, but if you think it would make sense I can take a look. I'm hoping the customizer will work properly.

[Assistant]
WeddingMagnet.scad:
```
// Overall size of the heart (x/y/z)
Heart_size = [45, 40, 5];

// Depth of the letters from the top of the heart
Text_depth = 2.25;

// The layer height is used for curving the top of the heart
Layer_height = 0.25;

Initials_font = "Veranda:style=Bold Italic";

// Initials
Initials = "E&C";
Initials_font_size = 10;

Date_font = "Libre Baskerville:style=Bold";

// Date (month day)
Date = "Oct 9";
Date_size = 5;

// Year (four digits)
Year = "2021";
Year_size = 5;

// Type of magnet. Cylinder magnets have two holes on the bottom for them, strip magnets have a single rectangle cut for them
Magnet_type = "cylinder"; // [cylinder, strip]

// Size (x/y/z)
Magnet_strip = [20, 13.7, 1.75];

// Size (diameter/height)
Magnet_cylinder = [4.25, 2];
mc_rad = Magnet_cylinder.x / 2;

Number_of_fletchings = 1;

sr = Heart_size.z / 3;                 // Shaft radius

// Per each fletching.  Angle is set by Y value. (x/y, units of arrow shaft radius)
Fletching_size = [4, 2];

module __Customizer_Limit__ () {}
// Round the circles
$fa=1; $fs = 0.5;

/**
 * Stripped down version of "bezier_v2.scad".
 * For full version, see: https://www.thingiverse.com/thing:2170645
 */

function BEZ03(u) = pow((1-u), 3);
function BEZ13(u) = 3*u*(pow((1-u),2));
function BEZ23(u) = 3*(pow(u,2))*(1-u);
function BEZ33(u) = pow(u,3);

function bezier_2D_point(p0, p1, p2, p3, u) = [
        BEZ03(u)*p0[0]+BEZ13(u)*p1[0]+BEZ23(u)*p2[0]+BEZ33(u)*p3[0],
        BEZ03(u)*p0[1]+BEZ13(u)*p1[1]+BEZ23(u)*p2[1]+BEZ33(u)*p3[1]
];

function bezier_coordinates(points, steps) = [
        for (c = points)
                for (step = [0:steps])
                        bezier_2D_point(c[0], c[1], c[2],c[3], step/steps)
];

module bezier_polygon(points) {
        steps = $fn <= 0 ? 30 : $fn;
        polygon(bezier_coordinates(points, steps));
}

module svg_heart() {
    if (version_num() >= 20190500) {
        import(file = "heart.svg", center = true, dpi = 96);
    }
    else {
        rotate(a = [0, 0, 180])
        translate([-Heart_size.x/1.475, -Heart_size.y / 1.8, -hs.z])
        resize([Heart_size.x, Heart_size.y])
        bezier_polygon([[[201.41571, 24.14587], [135.1757, 24.14587], [81.41575599999999, 77.905884], [81.41575599999999, 144.14587]], [[81.41575599999999, 144.14587], [81.41575599999999, 278.90139999999997], [217.34892, 314.23315], [309.97821, 447.45392]], [[309.97821, 447.45392], [397.55242999999996, 315.05053999999996], [538.54071, 274.59933], [538.54071, 144.14587]], [[538.54071, 144.14587], [538.54071, 77.905884], [484.78082, 24.145870000000002], [418.54071, 24.145870000000002]], [[418.54071, 24.145870000000002], [370.4928, 24.145870000000002], [329.1391, 52.516289], [309.97821, 93.333366]], [[309.97821, 93.333366], [290.81741, 52.516288], [249.46371, 24.14587], [201.41572, 24.14587]],[[201.41572, 24.14587], [201.41572, 24.14587], [201.41571, 24.14587], [201.41571, 24.14587]], [[201.41571, 24.14587],[135.1757, 24.14587], [201.41571, 24.14587], [201.41571, 24.14587]]]);
        /**/
    }
}

module heart() {
    hhz = Heart_size.z / 2; // Half of heart height
    qhz = Heart_size.z / 4; // Quarter of heart height
    curved_layers = hhz / Layer_height  + 1;
    function scaled_size(x) = Heart_size * sin((curved_layers - x) * 90 / (curved_layers));
    function layer_z(x) = (x - 0.5) * Layer_height;

    union() {
        // Base first at half height
        translate([0, 0, -qhz]) {
                linear_extrude(height = hhz, center = true, scale = 1)
                resize([Heart_size.x, Heart_size.y])
                svg_heart();
        };

        // Then a sine scaled version
        for (i = [1 : 1 : hhz / Layer_height]) {
            ss = scaled_size(i);
            translate([0, 0, layer_z(i)]) {
                resize([ss.x, ss.y, Layer_height])
                linear_extrude(height = Layer_height, center = true, scale = 1)
                svg_heart();
            };
        }
    };
}

module fin(pos, sz, h, fw) {
    dszx = sz.x * 2;
    translate(pos)
        rotate(a = [0, 270, fw ? 180 : 0])
        linear_extrude(h)
        polygon([[sz.x, 0], [0, sz.y], [sz.x, sz.y], [dszx, 0], [sz.x, 0]]);
}

module arrowhead(sr, sl2) {
    // Arrowhead - this would have been easier to do as a hull().
    polyhedron(points=[[0, (sr * 2), sl2], [-(sr * 1.5), 0, sl2], [0, -(sr * 2), sl2], [0, 0, sl2 + sr * 5]], faces = [[0, 1, 2], [0, 3, 1], [1, 3, 2], [0, 2, 3]]);
}

module arrow() {
    sr2 = sr * 2;
    sl = max(Heart_size.x, Heart_size.y) * 1.5; // Shaft length
    sl2 = sl / 2;
    union() {
        // Now the arrow...
        translate([0, 0, -Heart_size.z/2])
        rotate(a = [0, 90, 35])
        difference() {
            union() {
                //first the shaft
                cylinder(h = sl, r = sr, center = true);
                arrowhead(sr, sl2);
                scaled_sz = Fletching_size * sr;
                pitch = sr * 1.2;
                h = sr / 4;
                for (i = [0 : Number_of_fletchings - 1]) {
                    fz = -sl2 + i * pitch;
                    fin([0, 0, fz], scaled_sz, h, false);
                    fin([-h, 0, fz], scaled_sz, h, true);
                }
            };

            // Bisect
            dhs = Heart_size * 2;
            hhz = Heart_size.z / 2;
            translate([0, -Heart_size.x, -Heart_size.y])
                cube([hhz, dhs.x, dhs.y]);
        };
    };
}

module caption() {
    initialy = (Heart_size.y / 11) - (Text_depth / 2);
    datey = initialy - Date_size * 1.2;
    yeary = datey - Year_size * 1.2;
    textz = (Heart_size.z - Text_depth) / 2;

    union() {
        // Initials
        translate([0, initialy, textz]) {
            linear_extrude(height=Text_depth, center=true)
            text(Initials, font=Initials_font, halign = "center", size = Initials_font_size, spacing=1.2);
        };

        // Date
        translate([0, datey, textz]) {
            linear_extrude(height=Text_depth, center=true)
            text(Date, font=Date_font, halign = "center", size = Date_size);
        };

        // Year
        translate([0, yeary, textz]) {
            linear_extrude(height=Text_depth, center=true)
            text(str(Year), font=Date_font, halign = "center", size = Year_size);
        };
    };
}

module magnet() {
    // Magnet cut-outs
    if (Magnet_type == "strip") {
        mz = (Heart_size.z - Magnet_strip.z) / -2;
        translate([0, 0, mz])
            cube(size=Magnet_strip, center=true);
    }
    else {
        // For cylindrical magnets, we want two.
        mz = 0 - (Heart_size.z / 2) + (Magnet_cylinder.y / 2);
        qhs = Heart_size / 4;
        translate([qhs.x, qhs.y, mz])
            cylinder(h = Magnet_cylinder.y, r = mc_rad, center = true);
        translate([-qhs.x, qhs.y, mz])
            cylinder(h = Magnet_cylinder.y, r = mc_rad, center = true);
    }
}

difference() {
    union() {
        heart();
        arrow();
    };
    caption();
    magnet();
};
```


[User]
Hey, I'm looking to get some OpenSCAD code generated for a vacuum hose adapter. I've got a pretty specific design in mind, and I need it to be fully parametric.

Essentially, the adapter will have two cylindrical ends connected by a curved section. Here are the parameters I need to be able to control:

*   **Large End:**
    *   `LargeEndMeasured`:  A dropdown to select whether `LargeEndDiameter` is an *inside* or *outside* measurement.
    *   `LargeEndDiameter`: The diameter of the large end in mm.
    *   `LargeEndLength`: The length of the large end in mm.

*   **Small End:**
    *   `SmallEndMeasured`: A dropdown to select whether `SmallEndDiameter` is an *inside* or *outside* measurement.
    *   `SmallEndDiameter`: The diameter of the small end in mm.
    *   `SmallEndLength`: The length of the small end in mm.

*   **Taper:**
    *   `TaperLength`: The length of the tapered section connecting the large and small ends in mm.

*   **Inside Stop:**
    *   `InsideStop`: A dropdown (yes/no) to add a small lip inside the small end to act as a stop.

*   **General:**
    *   `WallThickness`: The thickness of the adapter walls in mm.

*   **Curve:**
    *   `CurveAngle`: The angle of the curved section in degrees.
    *   `CurveRadius`: The radius of the curve in mm.

So, the adapter starts with a cylinder for the large end, then it tapers down to the small end using another cylinder of decreasing diameter. After that is a curved section defined by the angle and radius. Finally there is the cylinder for the small end itself. The "inside stop" would basically be a small lip at the very end of the small end cylinder on the *inside* that keeps things from sliding all the way through.

I'd like to be able to select "inside" or "outside" measurement for both ends because sometimes I'm measuring the hose itself and sometimes I'm measuring the fitting. So the code should automatically adjust the actual cylinder diameters used for the model, based on the given `WallThickness`.

I think I'll also need to set `$fn` so the curves are smooth, maybe with ` $fn=60 *1;`.

I'm hoping for a single .scad file for this, and I don't think I need any special libraries for something like this, just the standard OpenSCAD functions. Can you help me with that?

[Assistant]
Vacuum_Hose_Adapter_Curved.scad:
```
//FollyMaker
//Vacuum Hose Adapter Beta 0.2 Testing

//Modified by gianniqq on 23/07/2021: added a customizable curved section; reorganized the code a bit
//Gather the parameters
/* [Parameters] */

LargeEndMeasured = "inside"; //[outside, inside]
//Diameter of Large End? (mm)
LargeEndDiameter = 31.5;
// Length of Large End? (mm)
LargeEndLength = 25;
//For the small end, is the measurement the adapter's outside or inside diameter?
SmallEndMeasured = "outside"; //[outside, inside]
//Diameter of Small End? (mm)
SmallEndDiameter = 30;
// Length of Small End? (mm)
SmallEndLength = 30;
// Length of Taper Section? (mm)
TaperLength = 3;
// Create an Inside Stop at the lower end of smaller end of this adapter? (To keep the connected fitting from being sucked in.)
InsideStop= "no"; //[no,yes]
//What is the thickness of the adapter's walls?
WallThickness = 2;

//Curved Part Angle
CurveAngle = 45;
//Curved Part Radius
CurveRadius = 2;

 $fn=60 *1;

 WT=WallThickness;
 LL=LargeEndLength;
 SL=SmallEndLength;
 TL=TaperLength;

 LD=  LargeEndMeasured =="inside" ? LargeEndDiameter + 2*WT : LargeEndDiameter;
 SD=  SmallEndMeasured =="inside" ? SmallEndDiameter + 2*WT : SmallEndDiameter;
 LID = LD - 2*WT;
 SID = SD - 2*WT;
 DR = LD + 3*WT;

module TransCylinder(D1, D2, H, Z)
{
    translate([0, 0, Z])
    {
        cylinder(d1=D1, d2=D2, h=H, center=false);
    }
}

module TransCurvedCylinder(D_EXT, D_INT, ANGLE, CURVE_RADIUS, Z)
{
    translate([-D_EXT/2 - CURVE_RADIUS, 0, Z])
    rotate([90, 0, 0])
    {
        rotate_extrude(convexity = 10, angle=ANGLE)
        difference()
        {
            translate([D_EXT/2 + CURVE_RADIUS, 0, 0])
            circle(r = D_EXT/2);

            translate([D_EXT/2 + CURVE_RADIUS, 0, 0])
            circle(r = D_INT/2);
        }
    }
}

module LargeEndSection()
{
    difference()
    {
        union()
        {
            TransCylinder(LD, LD, LL, 0);
            TransCylinder(LD, DR, WT, LL);
            TransCylinder(DR, DR, WT, LL + WT);

        }
        union()
        {
            TransCylinder(LID, LID, LL + 2*WT, 0);
        }
    }
}

module SmallEndSection()
{
    difference()
    {
        union()
        {
            TransCylinder(LD, SD, TL, 0);
            TransCylinder(SD, SD, SL, TL);
        }
        union()
        {
            TransCylinder(LID, SID, TL, 0);
            //TransCylinder(SID, SID, SL, TL);

            if (InsideStop=="yes")
            {
                TransCylinder(SID, SID-(2*WT), WT, TL);
                TransCylinder(SID, SID, SL, TL + WT);
            }
            else
            {
                TransCylinder(SID, SID, SL, TL);
            }
        }
    }
}

//rotating and translating individual sections back and forth to facilitate putting them together
module AssembleAdapterSections()
{
    translate([-LD/2 - CurveRadius, 0, LL + 2*WT])
        rotate([0, -CurveAngle, 0])
            translate([LD/2 + CurveRadius, 0, 0])
                {
                    translate([-LD/2 - CurveRadius, 0, 0])
                        rotate([0, CurveAngle, 0])
                            translate([LD/2 + CurveRadius, 0, -LL - 2*WT])
                                {
                                    LargeEndSection();
                                    TransCurvedCylinder(LD, LD-2*WT, CurveAngle, CurveRadius, LL + 2*WT);
                                }
                    translate([0, 0, -0.001]) //not doing this causes a "WARNING: Object may not be a valid 2-manifold and may need repair!" warning. This barely overlaps the smaller end section to the curved section
                        SmallEndSection();
                }
}

AssembleAdapterSections();

```


[User]
Hey, I'm looking to design a customizable triangular light fixture, and I'd like to use OpenSCAD for it. I've got a pretty specific idea in mind, so I'll try to be thorough.

Basically, the light will be made of a main body, two end caps, and joining pieces. The body is an equilateral triangle with a certain side length and depth.  The end caps will attach to the body using dovetail joints, and I need to be able to adjust the tolerance between the joints and the body. I also want the joints to have an overlap flap that extends a little past the joint and over the end cap, with customizable length and thickness. And the joints should be designed so they "click" in, that is, the body-facing part of the joint should have a slightly smaller overall width than the cap-facing part - this should be configurable by a scale factor.

On the body, I'd like to have cylindrical pegs along each edge, also with customizable dimensions like height, radius, and a chamfer on top.  These pegs need a hole through them to allow for wiring. I also need to adjust how far the pegs are inset from the edge, and how many pegs are on each side. And I want the light to be able to be wall-mounted, which means a mounting peg (also with adjustable parameters) on the back.

Finally, I want to cut out a word or letter on the face of the body using the text() function, with adjustable size, font, rotation, and the ability to add crossbars and downbars over the letters - these would actually be small extrusions on the front face of the light.

For a bit more detail, here are parameters that I need to be able to customize:

*   **Body:**
    *   `TRIANGLE_SIDE`: Length of a side of the equilateral triangle (including end caps).
    *   `DEPTH_TOP`: Depth of the top surface of the main body.
    *   `TRIANGULAR_CAP_SIZE`: Size of the triangular cap to the start of the dovetail joint.
    *   `WALL_MOUNT`: A boolean yes/no to have a wall mount on the back.

*   **Joints:**
    *   `DOVETAIL_DEPTH`: The depth of the dovetail joint into the body.
    *   `DOVETAIL_BREDTH`: Width of the widest part of the dovetail joint.
    *   `DOVETAIL_TOLLERANCE`: The tolerance between the body and the joint.
    *   `JOINT_OVERLAP_HEIGHT`: Thickness of the overlap flap on the joint.
    *   `JOINT_OVERLAP_LENGTH`:  Length of the overlap flap beyond the joint.
    *   `JOINT_SCALE`: A scaling factor for the joint, used to make them click together.

*   **Pegs:**
    *   `PEG_HEIGHT`: Length of a peg.
    *  `PEG_RADIUS`: Radius of the peg's stem
    *   `PEG_CHAMPHER_HEIGHT`:  Height of the chamfered part on top of the peg.
    *   `PEG_HOLE_THICKNESS`: Thickness of the hole in the peg for wiring.
    *  `PEG_INSET`: How far the peg is inset from the edge.
    *  `PEGS_PER_SIDE`:  Number of pegs on each side of the triangle.
*   **Cutout:**
    *   `CUTOUT_LETTER`:  The letter or word to cut out.
    *   `CUTOUT_LETTER_SIZE`: Size of the cutout text.
    *   `CUTOUT_FONT`: Font for the text.
    *  `CUTOUT_ROTATION`: Rotation of the text
    *   `CUTOUT_CROSSBARS`: Number of crossbars over the letters.
    *   `CUTOUT_DOWNBARS`: Number of downbars over the letters.

I'd like to be able to select which part of the light to generate (main body, cap, or joint) using a parameter. So, for example, I could set `PART = "body"` to generate just the body, `PART = "cap"` for the end caps, etc. I think I would want this to be the default parameter.

I don't think this needs any libraries - just the base OpenSCAD functionality.

Can you help me design this?

[Assistant]
triangularLIght.scad:
```
 /*

Customizable triangular light
Stretchicus
2021-07-22

versions:
    1 - Initial design

*/

/* [Part] */
// Part to show
PART="body"; // [body:Main body,cap:End cap,join:2 piece join]

/* [Body] */

// the length of a side of the triangle including triangular caps
TRIANGLE_SIDE = 175;

// depth of top surface
DEPTH_TOP = 3.2;

// triangular cap size (to the start of the dovetail)
TRIANGULAR_CAP_SIZE = 32.8;

WALL_MOUNT = "Yes"; // [Yes:Yes,No:No]

/* [Joints] */
// the depth of the joint part into the body
DOVETAIL_DEPTH=6.5;

// the width of the joints longest side
DOVETAIL_BREDTH=19.1;

// the tollerence between body and joint
DOVETAIL_TOLLERANCE=0.7;

// the thickness of overlap flap
JOINT_OVERLAP_HEIGHT=2;

//the length of the overlap flap that extends beyond the joint
JOINT_OVERLAP_LENGTH=4;

// joint overhang scale
JOINT_SCALE=0.95;

/* [Pegs] */
// Length of a peg
PEG_HEIGHT=12;

// The radius of a pegs stem
PEG_RADIUS=4.5;

// the hight of the champhered part of the peg on top of the stem
PEG_CHAMPHER_HEIGHT=2;

// the thickness of the hole in the peg for the lighting to run through
PEG_HOLE_THICKNESS=1.6;

// inset of peg in from edge
PEG_INSET=18;

// pegs per side
PEGS_PER_SIDE=2;

/* [Cutout] */

// Letter/Word to cutout, Be wary of showing the RGB
CUTOUT_LETTER = "";

// Letter Size
CUTOUT_LETTER_SIZE = 50;

// Font for the cutout letter - if you copy from font list (HELP -> Font List) remove the double quotes
CUTOUT_FONT="Arial";

// Cutout Rotation
CUTOUT_ROTATION=0; // [0:360]

// Number of cross bars over letters
CUTOUT_CROSSBARS=3;

// Number of down bars over letters
CUTOUT_DOWNBARS=3;

/* [Hidden] */

$fn = 32;

TRIANGLE_HEIGHT = (TRIANGLE_SIDE*sqrt(3))/2;

if (PART=="body"){
    drawBody();
} else if (PART=="cap") {

    drawCap();
} else if (PART=="join"){
    drawJoin();
}
//drawCornerCutout();

module  drawBody(){

    difference(){
        drawETriangle(side=TRIANGLE_SIDE,thickness=DEPTH_TOP);
         ///left
        translate([-0.01,-0.01,-0.01])
            scale([1,1,1.5])
                drawCornerCutout(scale=JOINT_SCALE);
        // right
        translate([TRIANGLE_SIDE+(0.01*3/2),-0.01,-0.01])
            scale([1,1,1.5])
                rotate([0,0,120])
                    drawCornerCutout(scale=JOINT_SCALE);
        // top
        translate([(TRIANGLE_SIDE-0.01)/2,TRIANGLE_HEIGHT+0.01, -0.01])
            scale([1,1,1.5])
                rotate([0,0,240])
                    drawCornerCutout(scale=JOINT_SCALE);

           bodyCutout();
    }

    drawCutoutMesh();

    // bottom
    translate([0,PEG_INSET,0])
    drawSideOfPegs();

    // right
    pix1=cos(30)*PEG_INSET;
    piy1=sin(30)*PEG_INSET;

    translate([TRIANGLE_SIDE - pix1,-piy1])
    rotate(120)
    drawSideOfPegs();

    // left
    pix2=cos(150)*PEG_INSET;
    piy2=sin(150)*PEG_INSET;

    translate([((TRIANGLE_SIDE)/2)-pix2,TRIANGLE_HEIGHT-piy2])
        rotate(240)
            drawSideOfPegs();

    if (WALL_MOUNT=="Yes"){
    // wall mount
        translate([(TRIANGLE_SIDE)/2,(TRIANGLE_SIDE)/2,DEPTH_TOP])
            drawMount();
    }
}

module drawCap(){
    t=DOVETAIL_TOLLERANCE*2;

    translate([0,0,JOINT_OVERLAP_HEIGHT])
        drawCornerCutout(-t,scale=(1-JOINT_SCALE)+1);
    if (JOINT_OVERLAP_HEIGHT>0){
    ol = TRIANGULAR_CAP_SIZE+JOINT_OVERLAP_LENGTH+DOVETAIL_DEPTH;
        drawETriangle(side=ol, thickness=JOINT_OVERLAP_HEIGHT);
    }
}

module drawJoin(){
    translate([0,0,JOINT_OVERLAP_HEIGHT])
        drawJoinDovetail(scale=(1-JOINT_SCALE)+1);
    t=DOVETAIL_TOLLERANCE;

    if (JOINT_OVERLAP_HEIGHT>0){
        x = -(DOVETAIL_DEPTH+JOINT_OVERLAP_LENGTH);
        y = -(DOVETAIL_BREDTH/2)+(DOVETAIL_TOLLERANCE);
        translate([x,y,0])
          cube([JOINT_OVERLAP_LENGTH,DOVETAIL_BREDTH-(2*t),JOINT_OVERLAP_HEIGHT]);
        translate([0,0,0])
            drawJoinDovetail(scale=1);
        x2=DOVETAIL_DEPTH;
        translate([x2,y,0])
          cube([JOINT_OVERLAP_LENGTH,DOVETAIL_BREDTH-(2*t),JOINT_OVERLAP_HEIGHT]);
    }
}

module drawJoinDovetail(scale=1){
    t=DOVETAIL_TOLLERANCE;
    t_height= (((TRIANGULAR_CAP_SIZE-DOVETAIL_TOLLERANCE) * sqrt (3))/2);
    d_height = (DOVETAIL_DEPTH);

    difference(){
        translate([-t_height,0,0])
            rotate([0,0,-30])
                drawCornerCutout(-t,scale=scale);
        translate([-t_height+.001,-(TRIANGULAR_CAP_SIZE+t)/2,-1])
            cube([t_height,TRIANGULAR_CAP_SIZE+t,TRIANGULAR_CAP_SIZE+t]);
        }
        rotate([0,0,180]){
        difference(){
            translate([-t_height,0,0])
                rotate([0,0,-30])
                    drawCornerCutout(-t,scale=scale);
            translate([-t_height+.001,-(TRIANGULAR_CAP_SIZE+t)/2,-1])
                cube([t_height,TRIANGULAR_CAP_SIZE+t,TRIANGULAR_CAP_SIZE+t]);
        }
    }
}

module drawSideOfPegs(){
    o=TRIANGULAR_CAP_SIZE + DOVETAIL_DEPTH+PEG_INSET;
    fw = TRIANGLE_SIDE-(2*o);
    pegs=PEGS_PER_SIDE-1;
    ofs=(fw/(PEGS_PER_SIDE-1)) ;//* (pegs/pps);
    for (i=[0:PEGS_PER_SIDE-1]){
        translate([o+(i*ofs),0])
            rotate([0,0])
                drawPeg();
    }

}

module drawCornerCutout(padding=0, scale=1){
    t_height = ((TRIANGULAR_CAP_SIZE+padding) *sqrt(3))/2;
    d_height = ((DOVETAIL_BREDTH+padding) *sqrt(3))/2;
    rotate([0,0,30]) {
        rotate([0,0,-30])
            drawETriangle(side=TRIANGULAR_CAP_SIZE+padding, thickness=DEPTH_TOP);
        translate([t_height-d_height+DOVETAIL_DEPTH,0])
            rotate([0,0,-30])
                drawETriangle(side = DOVETAIL_BREDTH+padding, thickness=DEPTH_TOP, scale=scale);
    }

}

module drawETriangle(side=10,thickness=10,scale=1.0){
    linear_extrude(height=thickness, scale=scale)
        polygon([[0,0],[side,0],[(side)/2,((side) * sqrt (3))/2],[0,0]]);

}
module createSolidPeg(){
    translate([0,0,PEG_HEIGHT])
        linear_extrude(PEG_CHAMPHER_HEIGHT, scale=1.4)
            circle(PEG_RADIUS);
        linear_extrude(PEG_HEIGHT)
            circle(PEG_RADIUS);
}

module drawPeg(){
    difference(){
        translate([0,0,DEPTH_TOP])
            createSolidPeg();

        translate([-PEG_RADIUS*1.4,-PEG_HOLE_THICKNESS/2,DEPTH_TOP*1.1])
            cube([PEG_RADIUS*2.8,PEG_HOLE_THICKNESS,PEG_CHAMPHER_HEIGHT+PEG_HEIGHT]);

    }
}

module drawMount(){
    rotate([0,0,-90])
    difference(){
        createSolidPeg();
        translate([0,0,-1])
            cylinder(h=PEG_CHAMPHER_HEIGHT+PEG_HEIGHT+12,r=PEG_HOLE_THICKNESS);
        translate([PEG_RADIUS,0])
            cylinder(h=PEG_HEIGHT+2,r=PEG_RADIUS);
        translate([0,-PEG_HOLE_THICKNESS/2,DEPTH_TOP*1.1])
            cube([PEG_RADIUS*2.8,PEG_HOLE_THICKNESS,PEG_CHAMPHER_HEIGHT+PEG_HEIGHT]);
            translate([PEG_RADIUS+PEG_HOLE_THICKNESS,0,PEG_HEIGHT +PEG_CHAMPHER_HEIGHT + (PEG_RADIUS/2)])
            sphere((PEG_RADIUS*1.4));

    }
}

module bodyCutout(){

        translate([TRIANGLE_SIDE/2, (TRIANGLE_HEIGHT/3), -DEPTH_TOP])
                linear_extrude(DEPTH_TOP*5)
                    rotate([0,180,CUTOUT_ROTATION])
                        text(CUTOUT_LETTER, size=CUTOUT_LETTER_SIZE, halign="center", valign="center",font=CUTOUT_FONT);

}

module drawCutoutMesh(){

    l = CUTOUT_LETTER_SIZE*len(CUTOUT_LETTER);
    translate([TRIANGLE_SIDE/2, (TRIANGLE_HEIGHT/3)])
    rotate([0,0,CUTOUT_ROTATION]){
        for(i=[0:CUTOUT_LETTER_SIZE/(CUTOUT_CROSSBARS+1):CUTOUT_LETTER_SIZE]){
            if (!((i==0)||(i==CUTOUT_LETTER_SIZE))){
                   translate([-(l/2),-(CUTOUT_LETTER_SIZE/2)+(i)])
                    cube([l,DEPTH_TOP/2,DEPTH_TOP]);
            }

            for(i=[0:l/(CUTOUT_DOWNBARS+1):l]){
                if (!((i==0)||(i==CUTOUT_LETTER_SIZE))){
                    translate([(-l/2)+i,-(CUTOUT_LETTER_SIZE/2)])
                        cube([DEPTH_TOP/2,CUTOUT_LETTER_SIZE,DEPTH_TOP]);
                }
            }
        }
    }
}
```


[User]
Hi there, I'm looking to design a dummy battery for my Nikon Coolpix P610 camera. The camera uses an EN-EL23 battery, and I've got a scad file that models it, but I want a more parametric design from scratch. I plan to power the camera with an external power source rather than a standard EN-EL23.

Essentially, I need a dummy battery that has the same physical dimensions as the EN-EL23, including the key dimensions, contact positions, and the cutaway at the top. Specifically:

*   **Main Body:** The main body of the battery should be a rounded rectangle with dimensions that match the actual EN-EL23.
    *   X-axis width (ENEL23\_X): 35.0 mm
    *   Y-axis length (ENEL23\_Y): 10.5 mm
    *   Z-axis height (ENEL23\_Z): 47.2 mm
*   **Contacts:** There are three contacts on top of the battery.
    *   X-axis width of each contact (ENEL23\_CONTACT\_X): 2.3 mm
    *   Y-axis length of each contact (ENEL23\_CONTACT\_Y): 3.5 mm
    *   Z-axis recess of each contact (ENEL23\_CONTACT\_Z): 1.0 mm
    *   Position of the contact centers (from the left edge):
        *   Contact 1 (ENEL23\_CONTACT\_X\_POSITION\_1): 22.5 mm
        *   Contact 2 (ENEL23\_CONTACT\_X\_POSITION\_2): 26.3 mm
        *   Contact 3 (ENEL23\_CONTACT\_X\_POSITION\_3): 30.1 mm

*   **Cutaway:** There's a cutaway at the top of the battery.
    *   It starts 3mm from each edge along the X axis, 1.5mm from the bottom along the Y axis, and 2mm from the top of the body on the Z axis
    *   It has a height of 2mm along the Z axis, length of ENEL23\_Y-3 along the Y axis, and width of ENEL23\_X-6 along the X axis.
*   **Wire Channels:** I need channels for wires from the contact pads to run to the base of the model, perhaps at the edge. These don't need to be precise representations of actual wires, but the channels need to be there.
    *   These should lead from the center of each of the three contacts to the bottom of the dummy battery.
*   **Cable Exit:** There should also be a channel for the main external cable to exit, probably from the Y axis edge, near the first contact. This should be large enough to fit a small cable.
*   **Text:** I want to add text "- T +" near the contacts, rotated to lie flat on the top of the battery. This should be near the first contact and offset on the Y axis.
*   **Parametric Design:** I'd like all these parameters defined at the beginning of the file(s) so I can tweak them later if needed.
*   **Preview/Render Quality:** Use `$fn = 24` for preview mode and `$fn = 96` for rendering.

I'd like to use BOSL2 for the design and the BOSL2 text library if you use text. I'd prefer it if the design was as modular as possible. Could you help me create this?

[Assistant]
EN-EL23_dummy.scad:
```
// by MegaSaturnv

/////////////////////////////
// Customizable Parameters //
/////////////////////////////
/* [Basic] */

/* [Advanced] */
//Width of the EL-EL23 (X axis)
ENEL23_X = 35.0;
//Length of the EL-EL23 (Y axis)
ENEL23_Y = 10.5;
//Height of the EL-EL23 (Z axis)
ENEL23_Z = 47.2;

//Width of the contact (X axis)
ENEL23_CONTACT_X = 2.3;
//Length of the contact (Y axis)
ENEL23_CONTACT_Y = 3.5;
//How much the contact is recessed into the EN-EL23
ENEL23_CONTACT_Z = 1.0;

//Distance between left edge and centre of 1st contact
ENEL23_CONTACT_X_POSITION_1 = 22.5;
//Distance between left edge and centre of 2nd contact
ENEL23_CONTACT_X_POSITION_2 = 26.3;
//Distance between left edge and centre of 3nd contact
ENEL23_CONTACT_X_POSITION_3 = 30.1;

//Use $fn = 24 if it's a preview. $fn = 96 for the render. Increase 96 to produce a smoother curve.
$fn = $preview ? 24 : 96;

/////////////
// Modules //
/////////////
module ENEL23_dummy() {
	difference() {
		union() {
			//Main battery body
			translate([ENEL23_Y/2, ENEL23_Y/2, 0]) cylinder(r=ENEL23_Y/2, h=ENEL23_Z-2);
			translate([ENEL23_Y/2, 0, 0]) cube([ENEL23_X-ENEL23_Y, ENEL23_Y, ENEL23_Z-2]);
			translate([ENEL23_X - ENEL23_Y/2, ENEL23_Y/2, 0]) cylinder(r=ENEL23_Y/2, h=ENEL23_Z-2);
			//Cutaway at top
			translate([3, 1.5, ENEL23_Z-2]) cube([ENEL23_X-6, ENEL23_Y-3, 2]);
		}

		translate([0, ENEL23_Y/2, ENEL23_Z - ENEL23_CONTACT_Z]) {
			//Contact pads
			translate([ENEL23_CONTACT_X_POSITION_1 - ENEL23_CONTACT_X/2, -ENEL23_CONTACT_Y/2, 0]) cube([ENEL23_CONTACT_X, ENEL23_CONTACT_Y, ENEL23_CONTACT_Z]);
			translate([ENEL23_CONTACT_X_POSITION_2 - ENEL23_CONTACT_X/2, -ENEL23_CONTACT_Y/2, 0]) cube([ENEL23_CONTACT_X, ENEL23_CONTACT_Y, ENEL23_CONTACT_Z]);
			translate([ENEL23_CONTACT_X_POSITION_3 - ENEL23_CONTACT_X/2, -ENEL23_CONTACT_Y/2, 0]) cube([ENEL23_CONTACT_X, ENEL23_CONTACT_Y, ENEL23_CONTACT_Z]);

			//Contact wire end
			translate([ENEL23_CONTACT_X_POSITION_1, ENEL23_CONTACT_Y/2, -ENEL23_Z/6]) cylinder(r=ENEL23_CONTACT_X/2, h=ENEL23_Z/6);
			translate([ENEL23_CONTACT_X_POSITION_2, ENEL23_CONTACT_Y/2, -ENEL23_Z/6]) cylinder(r=ENEL23_CONTACT_X/2, h=ENEL23_Z/6);
			translate([ENEL23_CONTACT_X_POSITION_3, ENEL23_CONTACT_Y/2, -ENEL23_Z/6]) cylinder(r=ENEL23_CONTACT_X/2, h=ENEL23_Z/6);

			//Contact wire path
			translate([ENEL23_CONTACT_X_POSITION_1, -ENEL23_CONTACT_Y/2, -ENEL23_Z]) cylinder(r=ENEL23_CONTACT_X/2, h=ENEL23_Z);
			translate([ENEL23_CONTACT_X_POSITION_2, -ENEL23_CONTACT_Y/2, -ENEL23_Z]) cylinder(r=ENEL23_CONTACT_X/2, h=ENEL23_Z);
			translate([ENEL23_CONTACT_X_POSITION_3, -ENEL23_CONTACT_Y/2, -ENEL23_Z]) cylinder(r=ENEL23_CONTACT_X/2, h=ENEL23_Z);
		}

		//Cable path
		translate([ENEL23_CONTACT_X_POSITION_1 - ENEL23_CONTACT_X/2, ENEL23_Y/2, 0]) scale([1, 1, 5]) rotate([0, 90, 0]) cylinder(r=ENEL23_CONTACT_X/1.5, h=ENEL23_X);

		//Text
		translate([ENEL23_CONTACT_X_POSITION_1 - 5, 1, ENEL23_Z - 8]) rotate([90, 0, 0]) linear_extrude(2) text("- T +", 5);
	}
}

////////////////
// Main Model //
////////////////
translate([0, -ENEL23_Y/2, 0]) ENEL23_dummy();

```