[User]
I want to design a customizable chessboard that uses the "toolpath painting" technique. Basically, this means the first layer of the print will have patterns within the squares, and I'll be printing in multiple colors to achieve the desired effect. The main idea is to have a grid of squares, and each square will have an optional inner shape (a star in the provided example) with the outer squares being a different color from the inner star shapes. I need the generated code to support a few key parameters.

Here's what I'm looking for in the OpenSCAD code:

*   **Square Size:** A variable for the side length of each square, in millimeters.
*   **Board Thickness:** A variable for the overall thickness of the board, plus a separate variable for the thickness of just the first layer (the patterned layer). The rest of the board will be a solid fill, kind of like a backing.
*   **Border Thickness:** An adjustable thickness for the outer border of the chessboard, as well as a separate thickness for the lines separating the squares themselves.
*   **Star Customization:** I need the inner pattern to be a star, but I'd like a variable for the number of points on the star. I'll also need a variable to shrink the size of the star relative to the square, so it fits nicely inside the square without touching the edges.
*   **Grid Size:** Variables to control the number of squares along the X and Y axes of the board.
*   **Color Control:** I want to control which squares and stars get rendered with which colors (e.g., color 1 stars on color 2 squares, or vice versa). Specifically I want to choose color 1, color 2 or both.
*    **First Layer Sections:** I need a way to specify if I'm rendering just the square outline, the star inside the square, both, or the inner lines between squares.
*   **Quadrant Control:** I want to render each of the four board quadrants separately for the border (since this will be a multi-part print) as well as the main body. I also want to optionally be able to print all four quadrants as one or skip the border or main body entirely for easier visualization and test prints.
*   **Registration Dots:** The code should include small registration dots at the corners of the board that can be used to align the different prints. I will need variables for controlling the spacing between the dots, the thickness of the dots and the thickness of the dots and their associated gaps

I also need a few modules: one for making the individual squares with the star, one for the boarder, another for the main body (to fill in the rest of the board), and a final one for the registration points.

The code should be structured so that I can easily select which parts to render to match my slicer workflow (e.g., first layer with pattern, then the main board body). I'd like to be able to use parameters at the top of the file to set my color choices for both the stars and the squares. Also to chose which quadrants to render and which sections of the first layer.

I think that's everything. It's a little complex, so let me know if you need clarification. Please make sure that the code is well-commented and easy to follow, as I'm still getting comfortable with OpenSCAD.

[Assistant]
ChessBoard_multi_color-1-4-web.scad:
```
//Mark Peeters ChessBoard multi color
// CC-BY-NC-SA Mark G. Peeters 3-17-2018
//https://www.youmagine.com/designs/chess-checkers-board-toolpath-painting
//https://www.thingiverse.com/thing:2853484
/*
I used a bit of CC0 Public Domain code to make my stars from here:
http://files.openscad.org/examples/Functions/recursion.html
 Written in 2015 by Torsten Paul <Torsten.Paul@gmx.de>
*/
/******************************************************
----------------
the idea is to print in at least 3 colors.
----------------
the registation dots are so that the slicer will place the different STL files in the exact same place on the bed.
----------------
When printing the star inside you can choose high shell count in cura and get a cool pattern.
----------------
When printing the background use low shell count like 1 and you will get basic lines
----------------
Backing will be printed in quarters on top of face parts
----------------
all face parts the same for the 4 quasters
----------------
the inner boarder is the same on all and can be trimmed off treated like a brim before assembly
----------------
Different colored stars should face in different directions, so I'm flipping them 180deg
----------------
note on code: I used a few %2. That's modulus division, which is handy for a number of things. Basically, you divide the number, and the remainder is what you have. So it's a great way to check if even or odd.
----------------

*******************************************************/

//size of squares (mm)
side=41;
//the surface thickness the board (mm) (1st layer, set in slicer)
thick=0.6;
//thickness of the whole board (mm)(including 1st layer)
thickFull=3;
//outer boarder thickness (mm)
edge_thick=5;
//inner boarder thickness, lines between squares(mm)normally 2*nozzle
lines=1.6;//nice if this is a multiple of the nozzle,
//number of star points
start_points=5;
//star shrink (mm) so start fits inside the squares
star_shrink=0.8*4;//lines*4
//register dots spacing (mm) gap for registering dots
regspace=4;
//x num for grid (how many squares in X direction)
xnum=4;
//ynum for grid (how many squares in Y direction)
ynum=4;

//render? (1st layer) Choose which color squares to render
color2make=3;// [0:color#1 (stars no rotation), 1:color#2 (stars 180deg rotation), 3:both colors]
//render? (1st layer) Choose which inner part to render
part2make=4;//[1:square outer, 2:square inner (stars), 4:inner boarder lines, 3:all inner parts (for visualization) , 5:no 1st layer inner objects]
//render? (1st layer) Choose which outer boarder quadrant to render
quadrant=5;//[1:Q1 outer boarder 1st layer,2:Q2 outer boarder 1st layer,3:Q3 outer boarder 1st layer,4:Q4 outer boarder 1st layer,5:all corners (1st layer), 6:no 1st layer outer boarder]
//render? (body, 2nd layer and up) Choose which quadrant for main part
quadrantMain=6;//[1:Q1 body 2nd layer and up,2:Q2 body 2nd layer and up,3:Q3 body 2nd layer and up,4:Q4 body 2nd layer and up,5:all corners body 2nd layer and up, 6:no body 2nd layer and up]

//YOU WILL NEED TO RENDER THE FOLLOWING PARTS FOR PRINTING
/*
[color2make,part2make,quadrant,quadrantMain]
visualize surface render=[3,3,5,6]
color1_outer=[0,1,6,6] //same for all quadrants
color1_inner=[0,2,6,6] //same for all quadrants
color2_outer=[1,1,6,6] //same for all quadrants
color2_inner=[1,2,6,6] //same for all quadrants
lines surface=[3,4,5,6] //same for all quadrants trim extra
body_Q1=[?,5,6,1] //1st quardant (first variable is ignored)
body_Q2=[?,5,6,2] //1st quardant (first variable is ignored)
body_Q3=[?,5,6,3] //1st quardant (first variable is ignored)
body_Q4=[?,5,6,4] //1st quardant (first variable is ignored)
*/

//////////////////////////////////////////////////////////////
////////////////make things///////////////////////////////////
//////////////////////////////////////////////////////////////
makeSquares(xnum,ynum,color2make,part2make);//(1st layer) make inner parts
make_boarder(quadrant,thick);//(1st layer) add the outer boarders
mainPart(quadrantMain);//(body, 2nd layer and up) main body part
reg_points();//(1st layer) registration points

//////////////////////////////////////////////////////////////
////////////////modules    ///////////////////////////////////
//////////////////////////////////////////////////////////////
module makeSquares(xnum,ynum,color2make,part2make){
for (x = [0:xnum-1]) {
for (y = [0:ynum-1]) {
 //  echo(x+y,(x+y)%2,180*((x+y)%2));
    if (3==color2make){//start if for all visual

    if ((x+y)%2==0) {
translate([x*side,y*side,0]) rotate([0,0,180*((x+y)%2)])
section(side,thick,start_points,part2make); }
else
   {
translate([x*side,y*side,0]) rotate([0,0,180*((x+y)%2)])
section1(side,thick,start_points,part2make); }
}//end if for all visual
else
if ((x+y)%2==color2make) {//start if make colors
translate([x*side,y*side,0]) rotate([0,0,180*((x+y)%2)])
section(side,thick,start_points,part2make); }//end if make colors
    }//end Y
}//end X
}//end modules make squares

module mainPart(quadrantMain){
//make main part 2nd layer and up
    if (quadrantMain!=6){
translate([-side/2,-side/2,thick])
cube([side*xnum,side*ynum,thickFull-thick]);}
translate([0,0,thick])
make_boarder(quadrantMain,thickFull-thick);
}//end module main part

module make_boarder(quad,z){
if ((quad==3) || (quad==5)) LB_boarder(z);
if ((quad==4) || (quad==5)) RB_boarder(z);
if ((quad==2) || (quad==5)) LT_boarder(z);
if ((quad==1) || (quad==5)) RT_boarder(z);}

//modules to make boarders/////////////
module LB_boarder(z){
 //bottom edge
translate([-side/2,-edge_thick-side/2,0])
color("black")cube([side*xnum,edge_thick,z]);
  //left edge
translate([-edge_thick-side/2,-side/2,0])
color("black")cube([edge_thick,side*ynum,z]);
  //bottom-left corner
translate([-edge_thick-side/2,-edge_thick-side/2,0])
color("black")intersection(){
    cube([edge_thick,edge_thick,z]);
    translate([edge_thick,edge_thick,0])cylinder(r=edge_thick,h=z);}
    }//end mod LB_boarder

module RB_boarder(z){
 //bottom edge
translate([-side/2,-edge_thick-side/2,0])
color("black")cube([side*xnum,edge_thick,z]);
  //right edge
translate([side*(xnum-0.5),-side/2,0])
color("black")cube([edge_thick,side*ynum,z]);
   //bottom-right corner
translate([side*(xnum-0.5),-edge_thick-side/2,0])
color("black")intersection(){
    cube([edge_thick,edge_thick,z]);
    translate([0,edge_thick,0])cylinder(r=edge_thick,h=z);}
}//end mod RB_boarder

module LT_boarder(z){
  //top edge
translate([-side/2,side*(ynum-0.5),0])
color("black")cube([side*xnum,edge_thick,z]);
   //left edge
translate([-edge_thick-side/2,-side/2,0])
color("black")cube([edge_thick,side*ynum,z]);
   //top-left corner
translate([-edge_thick-side/2,side*(ynum-0.5),0])
color("black")intersection(){
    cube([edge_thick,edge_thick,z]);
    translate([edge_thick,0,0])cylinder(r=edge_thick,h=z);}
}//end mod LT_boarder

module RT_boarder(z){
  //top edge
translate([-side/2,side*(ynum-0.5),0])
color("black")cube([side*xnum,edge_thick,z]);
   //right edge
translate([side*(xnum-0.5),-side/2,0])
color("black")cube([edge_thick,side*ynum,z]);
   //top-right corner
translate([side*(xnum-0.5),side*(ynum-0.5),0])
color("black")intersection(){
    cube([edge_thick,edge_thick,z]);
    translate([0,0,0])cylinder(r=edge_thick,h=z);}
}//end mod LB_boarder

module reg_points(){
//register points for squares/////////////////////////////////////////
//bottom-left corner
translate([-lines*2-regspace-edge_thick-side/2,-lines*2-regspace-edge_thick-side/2,0])cube([lines*2,lines*2,thick]);
//top-left register
translate([-lines*2-regspace-edge_thick-side/2,edge_thick+regspace+(ynum-0.5)*side,0])cube([lines*2,lines*2,thick]);
//bottom-right register
translate([regspace+edge_thick+(xnum-0.5)*side,-lines*2-regspace-edge_thick-side/2,0])cube([lines*2,lines*2,thick]);
//top-right register
translate([regspace+edge_thick+(xnum-0.5)*side,edge_thick+regspace+(ynum-0.5)*side,0])cube([lines*2,lines*2,thick]);

echo("print bed size needed (mm) [x,y]",regspace+edge_thick+(xnum-0.5)*side+lines*2+regspace+edge_thick+side/2,
lines*2+regspace+edge_thick+side/2+edge_thick+regspace+(ynum-0.5)*side);
}//end mod reg_points

module section(side,thick,start_points,what){
    //what=1 outer, 2 inner, 3 every thing
if (what==1){
difference(){
translate([0,0,thick/2])cube([side-lines,side-lines,thick],center=true);
translate([0,0,-1])star(start_points,side/2-star_shrink,side/4-star_shrink,thick+2);}}//end if what==1

if (what==2){
intersection(){
translate([0,0,thick/2])cube([side,side,thick],center=true);
translate([0,0,-1])star(start_points,side/2-star_shrink,side/4-star_shrink,thick+2);}}//end if what==2

if (what==3){
color("black")translate([0,0,thick/2])cube([side,side,thick],center=true);
translate([0,0,thick/2])color("WhiteSmoke")cube([side-lines,side-lines,thick+0.2],center=true);
color("white")translate([0,0,0])star(start_points,side/2-star_shrink,side/4-star_shrink,thick+0.4);
    }//end if what==3

if (what==4){
color("black")difference(){
translate([0,0,thick/2])cube([side,side,thick],center=true);
translate([0,0,thick/2])cube([side-lines,side-lines,thick+2],center=true);
    }//end diff
}//end if what=4
}//end module section

//module section1 is just to get a color change when rendering all
module section1(side,thick,start_points,what){
    //what=1 outer, 2 inner, 3 every thing
if (what==1){
difference(){
translate([0,0,thick/2])cube([side-lines,side-lines,thick],center=true);
translate([0,0,-1])star(start_points,side/2-star_shrink,side/4-star_shrink,thick+2);}}//end if what==1

if (what==2){
intersection(){
translate([0,0,thick/2])cube([side,side,thick],center=true);
translate([0,0,-1])star(start_points,side/2-star_shrink,side/4-star_shrink,thick+2);}}//end if what==2

if (what==3){
color("black")translate([0,0,thick/2])cube([side,side,thick],center=true);
translate([0,0,thick/2])color("DodgerBlue")cube([side-lines,side-lines,thick+0.2],center=true);
color("DeepSkyBlue")translate([0,0,0])star(start_points,side/2-star_shrink,side/4-star_shrink,thick+0.4);
    }//end if what==3

if (what==4){
color("black")difference(){
translate([0,0,thick/2])cube([side,side,thick],center=true);
translate([0,0,thick/2])cube([side-lines,side-lines,thick+2],center=true);
    }//end diff
}//end if what=4
}//end module section

module star(points, outer, inner,height) {
// With recursive functions very complex results can be generated,
// e.g. calculating the outline of a star shaped polygon. Using
// default parameters can help with simplifying the usage of functions
// that have multiple parameters.
function point(angle) = [ sin(angle), cos(angle) ];
function radius(i, r1, r2) = (i % 2) == 0 ? r1 : r2;
function starP(count, r1, r2, i = 0, result = []) = i < count
    ? starP(count, r1, r2, i + 1, concat(result, [ radius(i, r1, r2) * point(360 / count * i) ]))
    : result;

    translate([0, 0, 0])
        linear_extrude(height = height)
            polygon(starP(points*2, outer, inner));

//http://files.openscad.org/examples/Functions/recursion.html
// Written in 2015 by Torsten Paul <Torsten.Paul@gmx.de>
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to the
// public domain worldwide. This software is distributed without any
// warranty.
//
// You should have received a copy of the CC0 Public Domain
// Dedication along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
}//end module star
```


[User]
I'd like to create a customizable maker coin, similar to the one I saw online, but with a few specific differences and additions I have in mind. I want to be able to control various aspects of the coin, like the overall shape, edge details, and center design.

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

**Coin Shape:**
   *   I'd like to choose between a few different base shapes for the coin: a rounded coin with a soft, curved edge, a simple cylinder, and a torus shape.

**Edge Notches:**
   *   I want to be able to add notches to the edge. This should be optional.
   *   If enabled, I want to choose between rounded notches, twisted notches, and globe-like (sphere) notches.
   *   I need control over the width of each notch and how many notches are around the edge.

**Center Design:**
   *   The center area should have optional designs.
   *   I need to be able to select between no design at all, a sun design (with a center circle and triangle rays extending outwards), custom text, or an imported image.
    * For the sun design, I want to be able to adjust the diameter of the sun circle and the height and number of triangle rays around the circle.
    *   With text, I need control over the text string itself, the font, and text size.
    *   If it's a design, I want to control if it's raised above the coin surface or cut into it.

**Circular Text:**
   *   I'd like to have the option to add text in a circle around the coin's edge. This should be separate from the center text.
    *   I need to be able to specify the text, its font, the rotation angle of the entire text, and the distance of the text from the inner circle.

**General Settings**
*  Of course, I need to be able to control the diameter and thickness of the entire coin.
* I'd also like to be able to control the diameter of the inner circle that the inner design sits inside (or the distance to it from the outer edge of the coin). This should be a variable separate from the inner design itself.

I'm hoping to use OpenSCAD for this so I'd like it to be designed parametrically so all of these aspects can be adjusted and modified.

I'm aware of the `text_on.scad` library that was used in the reference example. I think it would work fine if included as part of the overall design, although I'd like to ensure that it integrates properly with the other parameters and avoids potential conflicts or issues. I don't think it would be necessary to generate the `text_on.scad` file separately.

[Assistant]
customizable_maker_coin.scad:
```
// Customizable Maker Coin
// Copyright: Olle Johansson 2018
// License: CC BY-SA

//CUSTOMIZER VARIABLES

/* [Coin] */

// Type of coin
type_of_coin = 2; // [0:Rounded, 1:Cylinder, 2:Torus]

// Add design in middle
type_of_inner_design = 1; // [0:None, 1:Sun, 2:Text, 3:Image]

// Should inner design be raised or cutout?
raise_inner_design = "yes"; // [yes,no]

// Diameter of coin in mm
diameter_of_coin = 50; // [40:60]

// Thickness of coin in mm
thickness_of_coin = 10; // [6:12]

// Distance of inner circle from outer edge
distance_to_inner_circle = 15; // [5:20]
diameter_of_inner_circle = diameter_of_coin - distance_to_inner_circle;

/* [Notches] */

// Add notches on edge of coin
display_notches = "yes"; // [yes, no]

// Type of notches
type_of_notch = 1; // [0:Rounded, 1:Twisted, 2:Globe]

// Width in mm of each notch on edge
width_of_notches = 10; // [5:15]

// Number of notches around edge
number_of_notches = 12; // [6:16]

/* [Inner Sun] */

// Diameter of sun in mm (excluding rays)
diameter_of_sun = 17; // [10:25]

// Height in mm of sun ray triangles
height_sun_ray = 5; // [2:8]

// Number of triangle rays on sun
number_of_sun_rays = 48; // [8:99]

/* [Inner text] */

// Text in middle of coin
inner_text = "OLLEJ";

// Font to use on the inner text
font_on_inner_text = "Helvetica";

// Size of inner text
size_of_inner_text = 5; // [1:15]

/* [Inner image] */

// Image to show in center of coin
inner_image = "logo.png"; // [image_surface:150x150]

/* [Circle text] */

// Show text in circle around edge of coin
display_circle_text = "yes"; // [yes, no]

// Text to print in a circle around the coin
text_around_coin = "Exalted Printing";

// Font to use on the circle text
font_on_circle_text = "Helvetica";

// Rotation angle in degrees of circle text
rotation_of_circle_text = 100; // [0:360]

// Distance of text in mm from inner circle
distance_of_text = 3; // [1:5]
// TODO: Allow text to be on outer circle?

//CUSTOMIZER VARIABLES END

/* [Hidden] */

$fn=100;

/* ** Utility modules ** */

module circled_pattern(number) {
    rotation_angle = 360 / number;
    last_angle = 360 - rotation_angle;
    for(rotation = [0 : rotation_angle : last_angle])
        rotate([0, 0, rotation])
        children();
}

/* ** Modifications ** */

module circle_text() {
    translate([0, 0, thickness_of_coin / 4])
    text_on_circle(
        t = text_around_coin,
        r = diameter_of_inner_circle / 2 - distance_of_text,
        font = font_on_circle_text,
        spacing = 1.7,
        extrusion_height = thickness_of_coin / 2,
        rotate = rotation_of_circle_text,
        center = true);
}

module inner_circle() {
    translate([0, 0, thickness_of_coin / 2])
    scale(v = [1, 1, 0.25])
    sphere(d = diameter_of_inner_circle);
}

/* ** Base coin ** */

module coin() {
    if (type_of_coin == 0) rounded_coin();
    else if (type_of_coin == 1) cylinder_coin();
    else if (type_of_coin == 2) torus_coin();
}

module cylinder_coin() {
    cylinder(h = thickness_of_coin, d = diameter_of_coin, center = true);
}

module rounded_coin() {
    intersection() {
        cylinder(h = thickness_of_coin, d = diameter_of_coin, center = true);
        scale([1, 1, 0.3])
        sphere(h = thickness_of_coin * 2, d = diameter_of_coin + 1, center = true);
    }
}

module torus_coin() {
    hull() {
        rotate_extrude(convexity = 10)
        translate([diameter_of_coin / 2 - thickness_of_coin / 2, 0, 0])
        circle(d = thickness_of_coin, center = true);

        cylinder(h = thickness_of_coin, d = diameter_of_coin - thickness_of_coin, center = true);
    }
}

/* ** Notches ** */

module edge_notches() {
    circled_pattern(number_of_notches)
        notch();
}

module notch() {
    if (type_of_notch == 0) rounded_notch();
    else if (type_of_notch == 1) twisted_notch();
    else if (type_of_notch == 2) sphere_notch();
}

module sphere_notch() {
    translate([diameter_of_coin / 2, 0, 0])
    scale([0.9, 1, 1])
    sphere(d = width_of_notches, center = true);
}

module rounded_notch() {
    translate([diameter_of_coin / 2, 0, - thickness_of_coin / 2])
    scale([0.9, 1, 1])
    cylinder(h = thickness_of_coin, d = width_of_notches);
}

module twisted_notch() {
    translate([diameter_of_coin / 2, 0, - thickness_of_coin / 2])
    scale([0.9, 1, 1])
    linear_extrude(height = thickness_of_coin, convexity = 1, twist = 180, slices = 100)
    circle(d = width_of_notches, $fn = 6);
}

/* ** Inner design ** */

module inner_design(raised = false) {
    if (type_of_inner_design == 1) inner_design_sun();
    else if (type_of_inner_design == 2) inner_design_text(raised);
    else if (type_of_inner_design == 3) inner_design_image();
}

module inner_design_image() {
    scale_xy = 0.25 * diameter_of_coin / 80;
    translate([0, 0, thickness_of_coin / 4])
    //scale([0.14, 0.14, 0.06])
    scale([scale_xy, scale_xy, 0.06])
    surface(file = inner_image, center = true, convexity = 10, invert = true);
}

module inner_design_text(raised = true) {
    if (raised == true) {
        translate([0, 0, thickness_of_coin / 8])
        linear_extrude(thickness_of_coin / 4)
        text(inner_text, size = size_of_inner_text, font = font_on_inner_text, halign = "center", valign = "center");
    } else {
        translate([0, 0, - thickness_of_coin / 4])
        linear_extrude(thickness_of_coin / 2)
        text(inner_text, size = size_of_inner_text, font = font_on_inner_text, halign = "center", valign = "center");
    }
}

module inner_design_sun() {
    translate([0, 0, - thickness_of_coin / 2])
    linear_extrude(height = thickness_of_coin - thickness_of_coin / 4)
    union() {
        circle(d = diameter_of_sun, center = true);

        // Triangle rays on outside of circle
        circled_pattern(number_of_sun_rays)
            ray_triangle();
    }
}

module ray_triangle(diameter) {
    //height_sun_ray = sqrt(side_sun_ray * side_sun_ray + side_sun_ray/2 * side_sun_ray/2);
    side_sun_ray = (2 * height_sun_ray) / sqrt(height_sun_ray);
    translate([diameter_of_sun / 2 + height_sun_ray / 2, 0, 0])
    rotate([0, 0, 135])
    polygon(points=[[0, 0], [side_sun_ray, 0], [0, side_sun_ray]], paths = [[0, 1, 2]]);
}

/* ** Main object ** */

module maker_coin() {
    union() {
        difference() {
            coin();

            inner_circle();
            if (display_circle_text == "yes") circle_text();
            if (display_notches == "yes") edge_notches();
            if (raise_inner_design == "no") inner_design();
        }

        if (raise_inner_design == "yes") inner_design(raised = true);
    }
}

maker_coin();

// Placed last to preserve line numbers when debugging.
//include <text_on.scad>

// ** Include text_on.scad inline to work on Thingiverse ** */

/*  text on ....
    This is a rework of version 3 of write.scad to use the new OpenSCAD internal text() primitive.
    All credit to Harlan Martin (harlan@sutlog.com) for his great effort on the original.
    Great thanks to t-paul (and the OpenSCAD dev team) on adding the new text() primitive.
*/

// Defaults
// Defaults for all modules
default_t = "text_on";
default_size = 4; // TODO - Can we make this 10?? To match internal size? There is an underlying value in text() -- This masks that.
default_font = "Liberation Mono";
default_spacing = 1; // Spacing between characters. There is an underlying value in text() -- This masks that. We try and match it here.
default_rotate = 0; // text rotation (clockwise)
default_center = true; //Text-centering
default_scale = [1,1,1];
default_extrusion_height = 2; //mm letter extrusion height

//Defaults for Cube
default_cube_face = "front"; // default face (top,bottom,left,right,back,front)
default_sphere_rounded = false; // default for rounded letters on text_on_sphere
default_cube_updown = 0; // mm up (-down) from center on face of cube
default_cube_rightleft = 0; // mm right(-left) from center on face of cube

//Defaults for Sphere
default_sphere_northsouth = 0;
default_sphere_eastwest = 0;
default_sphere_spin = 0; // TODO:Different to rotate? or up/down. Better name?

//Defaults for Cylinder (including circle as it is on top/bottom)
default_circle_middle = 0; // (mm toward middle of circle)
default_circle_ccw = false; // write on top or bottom in a ccw direction
default_circle_eastwest = 0;
default_cylinder_face = "side";
default_cylinder_updown = 0;

// Internal values - don't play with these :)
// This is much more accurate than the PI constant internal to Openscad.
internal_pi = 3.1415926535897932384626433832795028841971693993751058209;
internal_pi2 = internal_pi * 2;

// Internal values - You might want to play with these if you are using a proportional font
internal_space_fudge = 0.80; // Fudge for working out lengths (widths) of strings

debug = true;

function width_of_text_char(size, spacing) = size * internal_space_fudge * spacing;
function width_of_text_string_num_length(length, size, spacing) = width_of_text_char(size, spacing) * length;
//function width_of_text_string(t, size, spacing) = width_of_text_string_num_length(len(t), size, spacing);

//Rotate 1/2 width of text if centering
//One less -- if we have a single char we are already centred..
function rotation_for_center_text_string(t, size, spacing, r, rotate, center) = (center) ? (width_of_text_string_num_length(len(t) - 1, size, spacing) / 2 / (internal_pi2 * r) * 360) : 0;

//Angle that measures width of letters on perimeter of a circle (and sphere and cylinder)
function rotation_for_character(size, spacing, r, rotate = 0) = (width_of_text_char( size, spacing ) / (internal_pi2 * r)) * 360 * (1 - abs(rotate) / 90);

function default_if_undef(val, default_val) = (val != undef) ? val : default_val;

module text_on_circle(t = default_t,
                      //Object-specific
                      locn_vec = [0, 0, 0],
                      r,
                      eastwest = default_circle_eastwest,
                      middle = default_circle_middle,
                      ccw = default_circle_ccw,

                      //All objects
                      extrusion_height = default_extrusion_height,
                      rotate = default_rotate,
                      center = default_center,

                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      halign = undef,
                      valign = undef,
                      language = undef,
                      script = undef,
                      spacing = default_spacing) {
//    echo (str("text_on_circle:","There are " ,len(t) ," letters in t" , t));
//    echo (str("text_on_circle:","rotate=" , rotate));
//    echo (str("text_on_circle:","eastwest=" , eastwest));

    if((halign != undef) || (halign != undef)) {
        echo(str("text_on_circle:","WARNING " , "halign and valign are NOT supported."));
    }

    ccw_sign = (ccw == true) ? 1 : -1;
    rtl_sign = (direction == "rtl") ? -1 : 1;
    ttb_btt_inaction = (direction == "ttb" || direction == "btt") ? 0 : 1;
    rotate_z_outer = -rotate + ccw_sign * eastwest;
    rotate_z_inner = -rtl_sign * ccw_sign * ttb_btt_inaction * rotation_for_center_text_string(t, size, spacing, r-middle, rotate, center);
    rotate(rotate_z_outer, [0, 0, 1] )
    rotate(rotate_z_inner, [0, 0, 1] )
    translate(locn_vec)
    for(l = [0 : len(t) - 1]) {
        //TTB/BTT means no per letter rotation
        rotate_z_inner2 = -ccw_sign * 90 + ttb_btt_inaction * rtl_sign * ccw_sign * l * rotation_for_character(size, spacing, r - middle, rotate = 0);   //Bottom out=-270+r
        //TTB means we go toward center, BTT means away
        vert_x_offset = (direction == "ttb" || direction == "btt") ? (l * size * ((direction == "btt") ? -1 : 1)) : 0;
        rotate(rotate_z_inner2, [0, 0, 1])
        translate([r - middle - vert_x_offset, 0, 0])
        rotate(-ccw_sign * 270, [0, 0, 1]) // flip text (botom out = -270)
        text_extrude(t[l],
                center = true,
                font = font,
                size = size,
                rotate = undef,
                spacing = spacing,
                direction = undef, //We don't pass direction ( misaligns inside text() ). TODO: Investigate why
                language = language,
                script = script,
                halign = halign,
                valign = valign,
                extrusion_height = extrusion_height);
    }
}

// Print a single character or a string at the desired extrusion height
// Passes on values to text() that are passed in
// TODO: Add x,y,z rotations get text facing the right way from the start)
module text_extrude( t = default_t,
                     extrusion_height = default_extrusion_height,
                     center = default_center, // Fudgy. YMMV. // TODO:center_extrusion, or extrusion offset??
                     rotate = default_rotate,
                     scale = default_scale, // For scaling by different on axes (for widening etc)
                     // Following are test() params (in addition to t=)
                     font = default_font,
                     size = default_size,
                     direction = undef,
                     halign  = undef,
                     valign = undef,
                     language = undef,
                     script = undef,
                     spacing = default_spacing) {
    //echo (str("text_extrude:","There are " ,len(t) ," letters in text" , t));
    //echo (str("text_extrude:","extrusion_height=" , extrusion_height));

    if(center == true) {
        if((halign != undef) || (valign != undef)) {
            echo(str("text_extrude:","WARNING " , "When center is true, then halign and valign are NOT supported."));
        }
    }

    // As arguments are explicitly set as undef (from higher functions) as they come down (they don't get defaults in this function as they are set as something)
    // we need to check and replace any that we don't want to be undef
    t = default_if_undef(t, default_t);
    font = default_if_undef(font, default_font);
    extrusion_height = default_if_undef(extrusion_height, default_extrusion_height);
    center = default_if_undef(center, default_center);
    rotate = default_if_undef(rotate, default_rotate);
    spacing = default_if_undef(spacing, default_spacing);
    size = default_if_undef(size, default_size);

    halign = (center) ? "center" : halign ;
    valign = (center) ? "center" : valign ;
    extrusion_center = (center) ? true : false ;

    scale(scale)
    rotate(rotate, [0, 0, -1]) //TODO: Do we want to make this so that the entire vector can be set?
    linear_extrude(height = extrusion_height, convexity = 10, center = extrusion_center)
    text(text = t,
            size = size,
            $fn = 40,
            font = font,
            direction = direction,
            spacing = spacing,
            halign = halign,
            valign = valign,
            language = language,
            script = script);
}

```


[User]
Hey there, I'm looking to create a tool caddy that I can attach to the frame of my 3D printer. The frame is made of 20x20 aluminum extrusion, so I'd like it to have a way to attach to that. I want to be able to customize which tools it holds, so I was hoping for a parametric design.

The caddy should be basically a long, narrow block with cutouts for different tools. I'd like to be able to specify the overall height, width, and length. Let's call them `caddyHeight`, `caddyWidth`, and `caddyLength`.

For the tool cutouts, I'll need space for:

*   A copper brush. Let's specify the cutout using `brushWidth` and `brushLength`. The cutout should be shaped like a rounded rectangle with a hole in the middle (sort of like an eyelet, but without the fillets.)

*   Flush cut pliers. Same as the brush, an eyelet with `pliersWidth` and `pliersLength`.

*   A set of hex keys. I'd like to specify if each hex key size is included or not with boolean parameters like `m6HexKey`, `m5HexKey`, etc. I'm thinking diameters for the hex key holes would need to be calculated from an array of standard sizes (`standardHexKeySizes`), but I'll start with just M6, M5, M4, M3, M2.5, and M2 keys. These cutouts can just be simple cylinders.

*   Optionally, a small hole for an acupuncture needle. controlled with a boolean param `acupunctureNeedle`.

*   Large and small wrenches, with parameters `largeWrenchWidth`, `largeWrenchLength`, `smallWrenchWidth` and `smallWrenchLength`. The cutouts for these can be rectangular.

*   A cylindrical cutout for a screwdriver defined by a diameter `screwDriverDia`.

*   A rectangular cutout for tweezers, defined by `tweezersWidth` and `tweezersLength`.

*   A hook for a scriber tool, specified using a diameter (`scraberHookDia`) and depth (`scraberHookDepth`). The hook needs a spherical end to stop the scriber from falling out and a short cylinder to connect to the tool caddy. I need to be able to disable it completely (so if the `scraberHookDia` is 0, no hook is made).

I think that covers everything.  I'd like to use the `RoundedRectsModule.scad` library if possible (and I am providing it), since I can use it to create the rounded rectangles I need for the eyelets, but I'm fine with generating my own module for that if necessary. It might be nice to add a little bit of a rounded edge to the bottom of the caddy, and a cut-out to the side, too. Also the design should be mirrored, so it can be installed to the right and left side of the frame.

Thanks!

[Assistant]
toolCaddy.scad:
```
/*
*  Copyright (c)..: 2018 janpetersen.com
*
*  Creation Date..: April. 7 2018
*  Discription....: Tool Caddy for 20x20 extrusion
*
*  Rev 1: Developed Model
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  If used commercially attribution is required (janpetersen.com)
*
*
*   Thingiverse Customizer documentation:
*   http://customizer.makerbot.com/docs
*
*
*/

/* ----- customizer view ----- */
//preview[view:south, tilt:top]

/* ----- parameters ----- */

/* [Hidden] */
$fn=60; // recomended value for smooth circle is 60
standardHexKeySizes = [
    ["M6", 5],
    ["M5", 4],
    ["M4", 3],
    ["M3", 2.5],
    ["M2.5", 2],
    ["M2", 1.5]
];

/* [Global] */
caddyHeight = 20; // [10:1:30]
caddyWidth = 10;  // [5:1:15]
caddyLength = 100; // [80:5:100]

/* [tool slot dimensions] */
brushWidth = 18;
brushLength = 16;
pliersWidth = 13;
pliersLength = 17;
largeWrenchWidth = 3;
largeWrenchLength = 21.5;
smallWrenchWidth = 2.5;
smallWrenchLength = 10.5;
tweezersWidth = 3;
tweezersLength = 7;
screwDriverDia = 4;
m6HexKey = 0;   // [1:yes,0:no]
m5HexKey = 1;   // [1:yes,0:no]
m4HexKey = 1;   // [1:yes,0:no]
m3HexKey = 1;   // [1.5:yes,0:no]
m25HexKey = 1;  // [1:yes,0:no]
m2HexKey = 1;   // [1:yes,0:no]
acupunctureNeedle = "yes"; // [yes, no]
scraberHookDia = 5; // [0:No Hook, 5:Slim Hook, 8:Normal Hook]
scraberHookDepth = 28;

/* ----- libraries ----- */

use <RoundedRectsModule.scad>;

/* ----- execute ----- */

hexKeySelection = [ m6HexKey, m5HexKey, m4HexKey, m3HexKey, m25HexKey, m2HexKey ];
rotate([ 0, 0, 180 ])
    mainModule();

/* ----- modules ----- */

module mainModule() {
    difference() {
        union() {
            caddy();
            translate([ 0, -caddyWidth / 2 - 1.5, 0 ])
                cube([ caddyLength - 10, 3, 6 ], center=true );
        }
        translate([ 12, 0, 0 ]) {
            translate([ 0, -caddyWidth / 2 - 2.1, 0 ])
                cube([ 20, 4.2, 6.2 ], center=true );
            rotate([ 90, 0, 0 ]) {
                cylinder( d=5.2, h=caddyWidth + 0.2, center=true );
                translate([ 0, 0, - caddyWidth / 2 - 0.1 ])
                    cylinder( d=8.5, h=( caddyWidth + 0.2 )/ 2 );
            }
        }
    }
}

module caddy() {

    difference() {
        hull()
            for ( i = [ -1:2:1 ] )
                translate( [ i * caddyLength / 2, 0, 0 ] )
                    cylinder( d=caddyWidth, h=caddyHeight, center=true );

        // Hex Keys
        for (i = [ 0:len(hexKeySelection) - 1 ]) {
            if ( hexKeySelection[i] == 1 ) {
                translate( [ caddyLength / 2 - calcHexKeyOffset(i, 0), 0, -0.05] )
                    cylinder( d=standardHexKeySizes[i][1] + 1, h=caddyHeight + 0.2, center=true, $fn=6 );
                }
            }

        // acupuncture needle
        if ( acupunctureNeedle == "yes" ) {
            translate( [ caddyLength / 2 - calcHexKeyOffset(5, 0) - 5, 0, -0.05] )
                cylinder( d=0.8, h=caddyHeight + 0.2, center=true );
        }

        // large wrength
        translate([ -5, 0, 0 ])
            cube([ largeWrenchLength, largeWrenchWidth, caddyHeight + 0.2 ], center=true );
        // small wrength
        translate([ -30, 0, 0 ])
            cube([ smallWrenchLength, smallWrenchWidth, caddyHeight + 0.2 ], center=true );
        // screw driver
        translate([ -42, 0, 0 ])
            cylinder( d=screwDriverDia, h=caddyHeight + 0.2, center=true );
        // tweezers
        translate([ -48, 0, 0 ])
            cube([ tweezersWidth, tweezersLength, caddyHeight + 0.2 ], center=true);
    }

    // copper brush
    translate([
        0,
        ( brushWidth + 4 + caddyWidth ) / 2 - 0.1,
        ( caddyHeight - 3 ) / 2
    ]) {
        eyelet( brushLength + 4, brushWidth + 4, 3, 3, brushLength, brushWidth, 2 );
    }

    // flush cut pliers
    translate([
        -( caddyLength / 2 - ( pliersLength + 4 + 5 ) / 2 ),
        ( pliersWidth + 4 + caddyWidth ) / 2 - 0.1,
        ( caddyHeight - 3 ) / 2
    ])
        eyelet( pliersLength + 4, pliersWidth + 4, 3, 3, pliersLength, pliersWidth, 2 );

    // scraber hook
    if ( scraberHookDia > 0 ) {
        translate([ 35, caddyWidth / 2 - 0.01, -scraberHookDia ]) {
            rotate([ -90, 0, 0 ])
                scale([ 1, 1.2, 1 ]) cylinder( d=scraberHookDia, h=scraberHookDepth );
            translate([ 0, scraberHookDepth, 0 ]) {
                scale([ 1, 1, 1.2 ]) sphere( d=scraberHookDia );
                cylinder( d=scraberHookDia, h=scraberHookDia * 1.5 );
                translate([ 0, 0, scraberHookDia * 1.5 ])
                    sphere( d=scraberHookDia );
            }
        }
    }
}

module eyelet(_x, _y, _z, _radius, _x1, _y1, _radius1) {
    difference() {
        union() {
            translate([ ( _x + _radius) / 2, - ( _y - _radius ) / 2, 0 ])
                rotate([ 0, 0, 180 ])
                    fillet(_radius, _radius );
            translate([ - ( _x + _radius) / 2, - ( _y - _radius ) / 2, 0 ])
                rotate([ 0, 0, -90 ])
                    fillet( _radius, _radius );
            hull() {
                translate([  0, -_radius, 0 ])
                    cube([ _x, _y - _radius * 2 - 0.1, _radius ], center=true );
                translate([ _x / 2 - _radius, _y / 2 - _radius, 0 ])
                    cylinder( r=_radius, h=_z, center=true );
                translate([ - _x / 2 + _radius, _y / 2 - _radius, 0 ])
                    cylinder( r=_radius, h=_z, center=true );
            }
        }
        roundedRect( _x1, _y1, _z + 0.2, 2 );
    }
}

module fillet(_radius, _height) {
    difference() {
        translate([ -0.01, -0.01, 0 ])
            cube([ _radius + 0.01, _radius + 0.01, _height ], center=true );
        translate([ -_radius / 2, -_radius / 2, 0 ])
            cylinder( r=_radius, h=_height + 0.2, center=true );
    }
}

function calcHexKeyOffset( lastKey = 5, currentKey = 0, first = 0 ) = (
    currentKey > lastKey  ? 0 : hexKeySelection[currentKey] * first * ( standardHexKeySizes[currentKey][1] / 2 + 4 ) + calcHexKeyOffset( lastKey, currentKey + 1, max( first, hexKeySelection[currentKey] ) )
);

// End Modules
```


[User]
Hey, I'm looking to design a tray for acetone vapor smoothing of ABS parts, and I need help creating the OpenSCAD files. I've got a basic idea, sort of inspired by chinman's design, but I'd like to make it parametric so I can adjust it easily. I'd like to be able to reuse as much of the code as possible, so a good object-oriented approach would be appreciated.

Here’s what I have in mind:

**Basic Tray Structure:**

*   The tray should be rectangular, with adjustable width (X), depth (Y), and height (Z). Let’s call them `TrayX`, `TrayY`, and `TrayZ`.
*   It should have a frame around the perimeter and a single cross member in the center.
*   The frame will be created with a double offset to create a void in the middle.
*   The cross member will be thinner than the frame (about 10% of the height) and have the same width as the tray.

**Hole Details:**

*   I'll need holes in the corners and in the middle of each side, positioned a set distance from the edges. Let's use the same distance from the edge for all.
*   These holes are for inserting threaded rods, specifically 1/4" rods (or a close metric equivalent; `Rod_D`).
*   The hole diameter will be exactly the same as the rod diameter.
*   All the holes will go all the way through the `TrayZ` height.

**Slat/Grid Structure:**

*   I want vertical slats going across the tray, sort of like a rack or grid for parts.
*   These slats should be evenly spaced, and the distance between each slat is approximately `Rod_D * 1.5`, and the length and offset should be tied to this as well.
*   The width of each slat should be adjustable, let's call it `SlatWidth`, but could be defaulted to be `Rod_D/1.5`, or a little more.
*   The slats should run the length of the TrayY, and extend the full height of the tray (`TrayZ`).

**Text Engraving**

*   I'd like to include some text along the top and bottom of the main tray frame.
*   The text should be aligned center both vertically and horizontally, and should include the current `Version` of the tray.
*   The text font should be adjustable, and default to "Bauhaus 93:style=Regular", but should allow the use of other common fonts (Arial, etc).

**Parametrization:**

*   All the dimensions (`TrayX`, `TrayY`, `TrayZ`, `Rod_D`, `SlatWidth`, etc.) should be parameters so I can easily modify them in OpenSCAD.
*   The offset for the frame (`Offset1`) should also be adjustable.

**Other:**

*   The design should be suitable for printing in PLA (which is resistant to acetone).
*   I’m aiming to have a tray about 200mm x 160mm but easily adjustable to different sizes.

For clarity, I'm not trying to improve on any existing code, or integrate this with other design files. I just need the OpenSCAD files that create this object. Also, I'm not planning to use any external libraries for this project.

Can you help with generating the OpenSCAD file(s)?

[Assistant]
Tray1-1.scad:
```
$fn=60;
//Version Text
Version = "Acetone Vape Tray v1.1";
FontFace = "Bauhaus 93:style=Regular"; //["Agency FB:style=Bold", "Arial:style=Black", "Arial:style=Bold", "Arial Rounded MT Bold:style=Regular", "Bauhaus 93:style=Regular", "Belgium:style=Regular", "Bermuda Script:style=Regular", "Bradley Hand ITC:style=Regular", "Brush Script MT:style=Italic", "Harlow Solid Italic:style=Italic", "HP Simplified:style=Bold", "Stencil:style=Regular", "Orbitron:style=Black"]
//Tray width(x)
TrayX = 200;
//depth(y)
TrayY = 160;
//tray height(z)
TrayZ= 6;
//rod diameter
Rod_D = 6.40;  //6.35 = 1/4" rod

//Rounding value in mm
Offset1 = 4.0;

//Slat Width (currently rod diameter)
SlatWidth = 4;

difference(){
  union(){
    //frame
    linear_extrude(TrayZ){
      difference(){
        offset(Offset1) offset(-Offset1) square([TrayX, TrayY],center=true);
        offset(Offset1) offset(-Offset1) square([TrayX -Rod_D*4, TrayY -Rod_D*4],center=true);
      }
    }
    //cross member
    linear_extrude(TrayZ/2, scale=[1.0, 0.1]){
      square([TrayX, Rod_D], center=true);
    }
  }
  //holes Can this be optimized?
  color("red")
  linear_extrude(TrayZ){
//<text>
    translate([0, -TrayY/2 +Rod_D, 0]) rotate([0,0,0]) text(Version, font=FontFace, size=8, valign="center", halign="center");
    translate([0, +TrayY/2 -Rod_D, 0]) rotate([0,0,0]) text(Version, font=FontFace, size=8, valign="center", halign="center");
//</text>
    for(x = [-1, 1]) for(y = [-1, 0, 1]){
      translate([x*(TrayX/2 -Rod_D -Offset1/2), y*(TrayY/2 -Rod_D -Offset1/2), 0]) circle(d=Rod_D);
    }
  }
}

//slats
for (x = [-Rod_D*13 : Rod_D*1.5 : +Rod_D*13]){
  translate([x,0,0]){
    linear_extrude(TrayZ, scale=[0.1, 1.0]){
      square([SlatWidth, TrayY], center=true);
    }
  }
}
```


[User]
Okay, I'm looking to design a customizable ring with text on both the outside face and the inside. I'd like the design to be parametric so I can easily change the ring size, thickness, and text.

Specifically, I need the following:

*   **Ring Sizing:** The ring should be sized based on a diameter, rather than a US ring size directly. I'll provide this diameter as a parameter (`ring_diameter`).
*   **Ring Thickness:**  I need to be able to control the thickness of the ring (`ring_thickness`).
*   **Ring Width:**  I need to be able to control the width of the ring at its widest point on the back of the ring (`ring_width`). I would like to control the angle of the ring face such that the front of the ring can be thinner. The angle is `ring_angle`.
*   **Face Text:** The ring should have text on the outside face. I want parameters to control the text (`text_face`), the text size (`text_size_face`), and the spacing between the characters (`space_size_face`).
*  **Interior Inscription:** The ring should also have an inscription on the inside of the ring. I need parameters for the inscription text (`text`), its size (`text_size_interior`), and its character spacing (`space_size`).
*   **Text Placement:** The interior inscription should be placed on the inner surface of the ring. The external text should be on the face.
*   **Text Font:** The text should be created using a dxf font. I would like to be able to import a dxf file to use for this. I need to be able to set the font as a parameter (`font`).
*  **View Selection**: I would like to be able to choose between rendering only the ring or only the inscription, for debugging. It should be a variable (`part`) that takes the value "ring" or "inscription".

I need two `.scad` files: One is for the ring itself, and the other is a `Write.scad` file that handles all of the text writing logic using `import()` with dxf files. The `Write.scad` file needs to have the `write()`, `writecube()`, `writesphere()`, and `writecylinder()` modules. Also it needs the variables and parameters like `bold`, `center`, `h`, `t`, `space`, `rotate`, and `font` along with the others listed in the code I sent.

Could you generate the code for these two files? It's important that the text rendering module `Write.scad` is separate from the ring generation file.

[Assistant]
Custom_Ring_v7_Remix.scad:
```
use <Write.scad>

// Original file can be found at: https://www.thingiverse.com/thing:59100

// Version 7 April 8, 2018
// Fixed code for link missing locations issue
// Included the necessary write.dxf and default Letters.dxf file found at https://www.thingiverse.com/thing:16193/ files and a . If they are missing search and place them in same dir as scad file. - TheBeyonder https://www.thingiverse.com/thing:2854075

// Version 6 March 11, 2013 Changes
// Fixed a problem with not properly handling the case when there is no text but we are generating the "inscription" part

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

//CUSTOMIZER VARIABLES

// Sizing for the finger

// Chart taken from http://www.thingiverse.com/thing:40704
//(US Ring Sizes)
ring_size = 24.43;//[11.63:0,11.84:0.25,12.04:0.5,12.24:0.75,12.45:1,12.65:1.25,12.85:1.5,13.06:1.75,13.26:2,13.46:2.25,13.67:2.5,13.87:2.75,14.07:3,14.27:3.25,14.48:3.5,14.68:3.75,14.88:4,15.09:4.25,15.29:4.5,15.49:4.75,2215.9:5.25,16.1:5.5,16.31:5.75,16.51:6,16.71:6.25,16.92:6.5,17.12:6.75,17.32:7,17.53:7.25,17.73:7.5,17.93:7.75,18.14:8,18.34:8.25,18.54:8.5,18.75:8.75,18.95:9,19.15:9.25,19.35:9.5,19.56:9.75,19.76:10,19.96:10.25,20.17:10.5,20.37:10.75,20.57:11,20.78:11.25,20.98:11.5,21.18:11.75,21.39:12,21.59:12.25,21.79:12.5,22:12.75,22.2:13,22.4:13.25,22.61:13.5,22.81:13.75,23.01:14,23.22:14.25,23.42:14.5,23.62:14.75,23.83:15,24.03:15.25,24.23:15.5,24.43:15.75,24.64:16]

c_r=ring_size/2;

// Thickness of the ring
thick=2;//[2:4]
// Up and down width at the back of the ring
ring_width=5;//[5:7]
// Angle from back to front. 0 is uniform width
ring_angle=15;//[0:20]

// Text on the face
text_face="Front Txt";
// Text size on the face
text_size_face = 3; // [3:9]
// Text spacing on the face
space_size_face=1.0*1;

// Text of the interior inscription
text="Inside Txt";
// Text Size on interior inscription
text_size_interior =  3;  // [3:5]
// Text spacing on the interior
space_size =1.0*1;

// Which one would you like to see?
part = "ring"; // [ring:Ring,inscription:Inscription]

s_r=c_r+2*thick;
x_offset=-thick;
y_b=ring_width/2;
x_b=sqrt(s_r*s_r-y_b*y_b);

angle=ring_angle;

translate([c_r+thick,0,0])
rotate([0,angle,0])
translate([-x_b,0,y_b])
{
  if(part=="ring")
  difference() {
    difference() {
      difference() {
        translate([0,0,0]) sphere(s_r,$fn=100,center=true);
        union() {
          translate([-x_offset,0,0]) cylinder(60,c_r,c_r,$fn=100,center=true);
          translate([+x_b,0,y_b]) rotate([0,angle,0]) translate([-x_b-s_r,-s_r,0]) cube([2*s_r,s_r*2,s_r]);
          translate([+x_b,0,-y_b]) rotate([0,-angle,0]) translate([-x_b-s_r,-s_r,-s_r]) cube([2*s_r,s_r*2,s_r]);
        }
      }
      translate([-s_r-c_r,-s_r,-s_r]) cube([s_r,2*s_r,2*s_r]);
    }
    translate([-x_offset,0,0]) mirror([ 0, 1, 0 ]) writesphere(text,[0,0,0],c_r,east=90,t=thick,h=text_size_interior,font="Letters.dxf", space=space_size,center=true);
      translate([-c_r,0,0]) rotate([90,0,-90]) write(text_face,h=text_size_face,t=thick,font="Letters.dxf",space=space_size_face,center=true);
  }

  if(part=="inscription") {

    if(len(text))
    color("red")
    intersection() {
      difference() {
        translate([-x_offset,0,0]) cylinder(10,c_r+thick,c_r+thick,$fn=100,center=true);
        translate([-x_offset,0,0]) cylinder(10,c_r,c_r,$fn=100,center=true);
      }
      translate([-x_offset,0,0]) mirror([ 0, 1, 0 ]) writesphere(text,[0,0,0],c_r,east=90,t=thick,h=text_size_interior,font="Letters.dxf", space=space_size,center=true);
    } // End of intersection() for the interior text

    if(len(text_face))
    color("red")
    intersection() {
      translate([-c_r,-s_r,-s_r]) cube([thick,2*s_r,2*s_r]);
      translate([-c_r,0,0]) rotate([90,0,-90]) write(text_face,h=text_size_face,t=thick,font="Letters.dxf",space=space_size_face,center=true);
    } // End of intersection() for the text on the face

  } // End of part=="inscription"
}

```
Write.scad:
```
/* 	Version 4
	Added bold property  bold=0 (not bold) bold=1(bolder by 1.1) bold=2(bolder by 1.2)
	Added support for font selection (default is Letters.dxf)
	Added WriteCube module
	Added Rotate for text (rotates on the plane of the text)
	Added writesphere
	Added space=     (spacing between characters in char widths) def=1
	Added writecylinder()

 By Harlan Martin
 harlan@sutlog.com
 January 2012

 (The file TestWrite.scad gives More usage examples)
 (This module requires the file Letters.dxf to reside in the same folder)
 (The file Letters.dfx was created with inkscape..Each letter is in its own layer)
 (This module seperates each letter in the string and imports it from Letters.dfx)

*/

	pi=3.1415926535897932384626433832795028841971693993751058209;
	pi2=pi*2;

// These control the default values for write() writesphere() writecube()
// if the parameters are not included in the call. Feel free to set your own
// defaults.

//default settings
	bold=0;
	center=false;
	h = 4;			 //mm letter height
	t = 1; 			//mm letter thickness
	space =1; 			//extra space between characters in (character widths)
	rotate=0;			// text rotation (clockwise)
	font = "Letters.dxf";	//default for aditional fonts

// write cube defaults
	face = "front";	 // default face (top,bottom,left,right,back,front)
	up =0;		 //mm up from center on face of cube
	down=0;
	right =0;		 //mm left from center on face of cube
	left=0;

// write sphere defaults
	rounded=false;	 //default for rounded letters on writesphere
	north=0; 		// intial text position (I suggest leave these 0 defaults)
	south=0;
	east=0;
	west=0;
	spin=0;
// writecylinder defaults
	middle=0;     //(mm toward middle of circle)
	ccw=false;   //write on top or bottom in a ccw direction
 	r1=0; 	//(not implimented yet)
	r2=0;	 	//(not implimented yet)

// Contact me if your interested in how to make your own font files
// Its tedious and time consuming, but not very hard

module writecylinder(text,where,radius,height){
	wid=(.125* h *5.5 * space);
	widall=wid*(len(text)-1)/2;
	//angle that measures width of letters on sphere
	function NAngle(radius)=(wid/(pi2*radius))*360;
	//angle of half width of text
	function mmangle(radius)=(widall/(pi2*radius)*360);

	if ((face=="top")||(face=="bottom") ){
		if (face=="top" ){
			if (center==true){
				writecircle(text,where+[0,0,height/2],radius-h,rotate=rotate,font=font,h=h,t=t,
				space=space,east=east,west=west,middle=middle,ccw=ccw,bold=bold);
			}else{
				writecircle(text,where+[0,0,height],radius-h,rotate=rotate,font=font,h=h,t=t,
				space=space,east=east,west=west,middle=middle,ccw=ccw,bold=bold);
			}
		}else{
			rotate(180,[1,0,0])
			if (center==true){
				writecircle(text,where+[0,0,height/2],radius-h,rotate=rotate,font=font,h=h,t=t,
				space=space,east=east,west=west,middle=middle,ccw=ccw,bold=bold);
			}else{
				writecircle(text,where+[0,0,0],radius-h,rotate=rotate,font=font,h=h,t=t,
				space=space,east=east,west=west,middle=middle,ccw=ccw,bold=bold);
			}
		}

	}else{
//		if (radius>0){
			if (center==true)  {
				rotate(-mmangle(radius)*(1-abs(rotate)/90),[0,0,1])
				translate(where)
				writethecylinder(text,where,radius,height,r1=radius,r2=radius,h=h,
					rotate=rotate,t=t,font=font,face=face,up=up,down=down,
					east=east,west=west,center=center,space=space,rounded=rounded,bold=bold);
			} else{
				rotate(-mmangle(radius)*(1-abs(rotate)/90),[0,0,1])
				translate(where+[0,0,height/2])
					writethecylinder(text,where,radius,height,r1=radius,r2=radius,h=h,
					rotate=rotate,t=t,font=font,face=face,up=up,down=down,
					east=east,west=west,center=center,space=space,rounded=rounded,bold=bold);
			}
// the remarked out code is for cone shaped cylinders (not complete)
//		}else{
//			if (center==true)  {
//				rotate(-mmangle(radius)*(1-abs(rotate)/90),[0,0,1])
//				translate(where)
//				writethecylinder(text,where,radius,height,r1=r1,r2=r2,h=h,
//					rotate=rotate,t=t,font=font,face=face,up=up,down=down,
//					east=east,west=west,center=center,space=space,rounded=rounded);
//			} else{
//				rotate(-mmangle(radius)*(1-abs(rotate)/90),[0,0,1])
//				translate(where+[0,0,height/2])
//					writethecylinder(text,where,radius,height,r1=r1,r2=r2,h=h,
//					rotate=rotate,t=t,font=font,face=face,up=up,down=down,
//					east=east,west=west,center=center,space=space,rounded=rounded);
//			}
//		}
	}
}
module writecircle(text,where,radius){
	wid=(.125* h *5.5 * space);
	widall=wid*(len(text)-1)/2;
	//angle that measures width of letters on sphere
	function NAngle(radius)=(wid/(pi2*radius))*360;
	//angle of half width of text
	function mmangle(radius)=(widall/(pi2*radius)*360);

	if (ccw==true){
		rotate(-rotate+east-west,[0,0,1]){
			rotate(-mmangle(radius-middle),[0,0,1]){
			translate(where)
				for (r=[0:len(text)-1]){
					rotate(-90+r*NAngle(radius-middle),[0,0,1]) // bottom out=-270+r
					translate([radius-middle,0,0])
					//rotate(90,[1,0,0])
					//rotate(90,[0,1,0])
					rotate(-270,[0,0,1])  // flip text (botom out = -270)
					write(text[r],center=true,h=h,t=t,font=font,bold=bold);
				}
			}
		}
	}else{
		rotate(-rotate-east+west,[0,0,1]){
			rotate(mmangle(radius-middle),[0,0,1]){
			translate(where)
				for (r=[0:len(text)-1]){
					rotate(90-r*NAngle(radius-middle),[0,0,1]) // bottom out=-270+r
					translate([radius-middle,0,0])
					//rotate(90,[1,0,0])
					//rotate(90,[0,1,0])
					rotate(270,[0,0,1])  // flip text (botom out = -270)
					write(text[r],center=true,h=h,t=t,font=font,bold=bold);
				}
			}
		}
	}

}
module writethecylinder(text,where,radius,height,r1,r2){
	wid=(.125* h *5.5 * space);
	widall=wid*(len(text)-1)/2;
	//angle that measures width of letters on sphere
	function NAngle(radius)=(wid/(pi2*radius))*360*(1-abs(rotate)/90);
	//angle of half width of text

	function mmangle(radius)=(widall/(pi2*radius)*360);
			translate([0,0,up-down])
			rotate(east-west,[0,0,1])
			for (r=[0:len(text)-1]){
				rotate(-90+(r*NAngle(radius)),[0,0,1])
				translate([radius,0,-r*((rotate)/90*wid)+(len(text)-1)/2*((rotate)/90*wid)])
				rotate(90,[1,0,0])
				rotate(90,[0,1,0])
				write(text[r],center=true,h=h,rotate=rotate,t=t,font=font,bold=bold);
		//echo("zloc=",height/2-r*((rotate)/90*wid)+(len(text)-1)/2*((rotate)/90*wid));
			}

}

module writesphere(text,where,radius){
	wid=(.125* h *5.5 * space);
	widall=wid*(len(text)-1)/2;

	echo("-----------------",widall,wid,mmangle(radius));
	//angle that measures width of letters on sphere
	function NAngle(radius)=(wid/(pi2*radius))*360;
	//angle of half width of text
	function mmangle(radius)=(widall/(pi2*radius)*360);

	rotate(east-west,[0,0,1]){
	rotate(south-north,[1,0,0]){
	rotate(spin,[0,1,0]){
	rotate(-mmangle(radius),[0,0,1]){
		if ( rounded== false ){
			translate(where)
			for (r=[0:len(text)-1]){
				rotate(-90+r*NAngle(radius),[0,0,1])
				translate([radius,0,0])
				rotate(90,[1,0,0])
				rotate(90,[0,1,0])
				write(text[r],center=true,h=h,rotate=rotate,t=t,font=font,bold=bold);
			}
		}else{
			difference(){
				translate(where)
				for (r=[0:len(text)-1]){
					rotate(-90+r*NAngle(radius),[0,0,1])
					translate([radius,0,0])
					rotate(90,[1,0,0])
					rotate(90,[0,1,0])
					write(text[r],center=true,h=h,rotate=rotate,t=t*2+h,font=font,bold=bold);
				}
				difference(){ //rounded outside
					sphere(radius+(t*2+h)*2);
					sphere(radius+t/2);
				}
				sphere(radius-t/2); // rounded inside for indented text
			}
		}
	}
}}}
}

module writecube(text,where,size){
	if (str(size)[0] != "["){
		// its a square cube (size was not a matrix so make it one)
		writethecube(text,where,[size,size,size],h=h,rotate=rotate,space=space,
		t=t,font=font,face=face,up=up,down=down,right=right,left=left,bold=bold);

	}else{
		// its not square
		writethecube(text,where,size,h=h,rotate=rotate,space=space,
		t=t,font=font,face=face,up=up,down=down,right=right,left=left,bold=bold);
	}
}
// I split the writecube module into 2 pieces.. easier to add features later
module writethecube(text,where,size){
		if (face=="front") {
			translate([where[0]+right-left,where[1]-size[1]/2,where[2]+up-down])
			rotate(90,[1,0,0])
			write(text,center=true,h=h,rotate=rotate,t=t,font=font,space=space,bold=bold);
		}
		if (face=="back") {
			translate([where[0]+right-left,where[1]+size[1]/2,where[2]+up-down])
			rotate(90,[1,0,0])   // rotate around the x axis
			rotate(180,[0,1,0])  // rotate around the y axis (z before rotation)
			write(text,center=true,h=h,rotate=rotate,t=t,font=font,space=space,bold=bold);
		}
		if (face=="left") {
			translate([where[0]-size[0]/2,where[1]-right+left,where[2]+up-down ])
			rotate(90,[1,0,0])   // rotate around the x axis
			rotate(90,[0,-1,0])  // rotate around the y axis  (z before rotation)
			write(text,center=true,h=h,rotate=rotate,t=t,font=font,space=space,bold=bold);
		}
		if (face=="right") {
			translate([where[0]+size[0]/2,where[1]+right-left,where[2] +up-down])
			rotate(90,[1,0,0])   // rotate around the x axis
			rotate(90,[0,1,0])  // rotate around the y axis  (z before rotation)
			write(text,center=true,h=h,rotate=rotate,t=t,font=font,space=space,bold=bold);
		}
		if (face=="top") {
			translate([where[0]+right-left,where[1]+up-down,where[2]+size[2]/2 ])
			write(text,center=true,h=h,rotate=rotate,t=t,font=font,space=space,bold=bold);
		}
		if (face=="bottom") {
			translate([where[0]+right-left,where[1]-up+down,where[2]-size[2]/2 ])
			rotate(180,[1,0,0])
			write(text,center=true,h=h,rotate=rotate,t=t,font=font,space=space,bold=bold);
		}
}

module write(word){

	echo (h);
	echo (word);
	echo ("There are " ,len(word) ," letters in this string");
//	echo ("The second letter is ",word[1]);
//	echo (str(word[0],"_"));
minkowski() {
rotate(rotate,[0,0,-1]){

	for (r = [0:len(word)]){   // count off each character
		// if the letter is lower case, add an underscore to the end for file lookup
		if ((word[r] == "a" ) || (word[r]== "b")  || (word[r]== "c")
	 	  || (word[r]== "d") || (word[r]== "e") || (word[r]== "f")
	 	  || (word[r]== "g") || (word[r]== "h")  || (word[r]== "i")
       	  	  || (word[r]== "j") || (word[r]== "k") || (word[r]== "l")
       	 	  || (word[r]== "m") || (word[r]== "n") || (word[r]== "o")
       	 	  || (word[r]== "p") || (word[r]== "q") || (word[r]== "r")
	 	  || (word[r]== "s") || (word[r]== "t") || (word[r]== "u")
       	 	  || (word[r]== "v") || (word[r]== "w") || (word[r]== "x")
       	 	  || (word[r]== "y" )|| (word[r]== "z")){
			if (center == true)  {
				translate([0,-h/2,0]){
					scale([.125*h,.125*h,t]){
						translate([ (-len(word)*5.5*space/2) + (r*5.5*space),0,0])
						//offset(delta = 20, join_type = "round") {
						linear_extrude(height=1,convexity=10,center=true){
							import(file = font,layer=str(word[r],"_"));
						}//}
					}
				}
			}else{
				translate([0,0,t/2]){
					scale([.125*h,.125*h,t]){
						translate([r*5.5*space,0,0])
						//offset(delta = 20, join_type = "round") {
						linear_extrude(height=1,convexity=10,center=true){
							import(file = font,layer=str(word[r],"_"));
						}//}
					}
				}
			}

		}else{
			if (center == true)  {
				translate([0,-h/2,0]){
					scale([.125*h,.125*h,t]){
						translate([ (-len(word)*5.5*space/2) + (r*5.5*space),0,0])
						//offset(delta = 20, join_type = "round") {
						linear_extrude(height=1,convexity=10,center=true){
							import(file = font,layer=str(word[r]));
						}//}
					}
				}
			}else{
				translate([0,0,t/2]){
					scale([.125*h,.125*h,t]){
						translate([r*5.5*space,0,0])
						//offset(delta = 20, join_type = "round") {
						linear_extrude(height=1,convexity=10,center=true){
							import(file = font,layer=str(word[r]));
						}//}
					}
				}
			}
		}
	}
}
cube([bold*.1*h,bold*.1*h,.00001]);
}
}

/*writecylinder test
translate([0,0,0])
%cylinder(r=20,h=40,center=true);
color([1,0,0])
writecylinder("rotate=90",[0,0,0],20,40,center=true,down=0,rotate=90);
writecylinder("rotate = 30,east = 90",[0,0,0],20,40,center=true,down=0,rotate=30,east=90);
writecylinder("ccw = true",[0,0,0],20,40,center=true,down=0,face="top",ccw=true);
writecylinder("middle = 8",[0,0,0],20,40,h=3,center=true,down=0,face="top",middle=8);
writecylinder("face = top",[0,0,0],20,40,center=true,down=0,face="top");
writecylinder("east=90",[0,0,0],20,40,h=3,center=true,down=0,face="top",east=90);
writecylinder("west=90",[0,0,0],20,40,h=3,center=true,down=0,face="top",ccw=true,west=90);
writecylinder("face = bottom",[0,0,0],20,40,center=true,down=0,face="bottom");
*/
/*writesphere test
sphere(20);
color([1,0,0])
writesphere("Hello World",[0,0,0],20,t=1,h=6);
*/
/* writecube test
translate([30,30,30])
cube([10,15,30],center=true);
write("hello",center=true,rotate =30);
color([1,0,0])
writecube( "front",[30,30,30],[10,15,30],h=5,rotate=-90);
color([0,1,0])
writecube( "back",[30,30,30],size=[10,15,30],h=5,face="back",rotate=90,t=4);
color([0,0,1])
writecube( "left",[30,30,30],[10,15,30],h=5,face="left",up=5);
color([1,1,0])
writecube( "right",where=[30,30,30],size=[10,15,30],h=5,face="right",rotate=55);
color([1,0,1])
writecube( "top",where=[30,30,30],size=[10,15,30],h=5,face="top");
color([1,1,1])
writecube( "bttm",where=[30,30,30],size=[10,15,30],h=5,face="bottom",rotate=90);
*/

```


[User]
I'd like to design a parametric flower pot using OpenSCAD. I need the following parameters to be easily adjustable: the number of sides (or faces) of the pot, the radius of the base, the radius of the top opening, and the overall height of the pot. I want it to smoothly transition between the base and top radii. I also want to make sure that the number of vertical layers is automatically calculated based on the size of the faces, which should be a little under twice the minimum face size. The layers shouldn't all be equal.

I also need to include flat top and bottom caps.

I'd really appreciate it if the OpenSCAD code you provide calculates all of this, including the layer heights, such that when the number of faces or the height/radii are changed, everything will scale appropriately. It should be one file. It would be great if you included comments to explain how it works, too.

[Assistant]
Parametric_Flower_Pots.scad:
```
/* [Global] */
nFaces = 8; // [1:100]
bottomRadius = 50; // [10:200]
topRadius = 90;
height = 80;

/* [Hidden] */
step = 360/nFaces;
minRadius = min(topRadius, bottomRadius);
minFaceSize = minRadius*sin(step/2)/2;
maxNLayers = floor(height / minFaceSize);

stepZ = height/maxNLayers;

deltaRadius = topRadius - bottomRadius;

// --- Utils --- //

// compute sum of vector from i to s
function sumv(v,i,s=0) = (i==s ? v[i] : v[i] + sumv(v,i-1,s));

// flatten vector
function flatten(l) = [ for (a = l) for (b = a) b ] ;

// invert triangle
function invertTriangle(t) = [ t[0], t[2], t[1]  ] ;

// invert triangles
function invertTriangles(triangles) = [ for(t=triangles) invertTriangle(t)] ;

// --- Compute points of a polygon / circle --- //

function points(step, radius, z, offset) = [ for (i = [0 : step : 360-step])[radius*cos(i+offset),radius*sin(i+offset),z]];

// --- Compute z step (height amount for each step) --- //

// the height of a layer is half the length of a side of the polygon / circle
function computeHeightSteps() = [ for(i=[0:maxNLayers]) let ( r = bottomRadius+(i/maxNLayers)*deltaRadius, faceSize = 2*r*sin(step/2)) faceSize*sqrt(3)/4 ];

// compute the heights by summing the height steps
function computeHeights(heights) = [ for(i=[0:maxNLayers]) sumv(heights, i, 0) ];

// compute the number of step necessary to reach the required height
function computeNZSteps(heights) = sumv([ for(h=heights) h<height ? 1 : 0 ], maxNLayers, 0);

// compute ideal heights (if the z increment equals perfectly faceSideSize / 2)
heightSteps = computeHeightSteps();
idealHeights = computeHeights(heightSteps);
nLayers = computeNZSteps(idealHeights);
idealHeight = idealHeights[nLayers-1];

// scale each step so that the total height equals the required height
heightRatio = height / idealHeight;

heights = [for(h=idealHeights) h*heightRatio];

// --- Compute all points --- //

// for each layer : compute a list of points
layerPoints = [ for (i = [0 : nLayers]) let ( r = bottomRadius+(i/nLayers)*deltaRadius, faceSize = 2*r*sin(step)) points(step, r, i==0 ? 0 : heights[i-1], i%2 == 0 ? 0 : step/2) ];

// flatten the list of points for each layers
p = flatten(layerPoints);

// --- Create layers --- //

// for each layer :

//  bottom faces
function faces1(nFaces, layerIndex, offset) = [ for (i = [0 : nFaces-1])[i+layerIndex*nFaces,i < nFaces-1 ?  nFaces+i+offset+layerIndex*nFaces : nFaces+(1-offset)*(nFaces-1)+layerIndex*nFaces,i < nFaces-1 ? i+1+layerIndex*nFaces : 0+layerIndex*nFaces]];

// top faces
function faces2(nFaces, layerIndex, offset) = [ for (i = [0 : nFaces-1])[nFaces+i+layerIndex*nFaces, i < nFaces-1 ? nFaces+i+1+layerIndex*nFaces : nFaces+layerIndex*nFaces, i < nFaces-1 ? i+1-offset+layerIndex*nFaces : 0+offset*(nFaces-1)+layerIndex*nFaces]];

// contact bottom faces and top faces
function faces(nFaces, layerIndex, offset) = concat(faces1(nFaces, layerIndex, offset), faces2(nFaces, layerIndex, offset));

// create face indices (invert faces)
layerFaces = [ for (i = [0 : nLayers-1]) invertTriangles(faces(nFaces, i, i%2 == 0 ? 0 : 1)) ];

// flatten faces indices
f = flatten(layerFaces);

// create layer faces
polyhedron( points = p, faces = f, convexity = 1);

// --- Create top and bottom faces --- //

// create bottom center
p1 = concat(p, [[0, 0, 0]]);
bottomIndex = len(p1)-1;

// create top center
p2 = concat(p1, [[0, 0, height]]);
topIndex = len(p2)-1;

bottomFace = [for (i = [0 : nFaces-1]) invertTriangle([i, i < nFaces-1 ? i+1 : 0, bottomIndex]) ];
polyhedron( points = p2, faces = bottomFace, convexity = 1);

topFace = [for (i = [0 : nFaces-1]) [i+nLayers*nFaces, i < nFaces-1 ? i+1+nLayers*nFaces : 0+nLayers*nFaces, topIndex] ];
polyhedron( points = p2, faces = topFace, convexity = 1);

```


[User]
Okay, I'm looking for some OpenSCAD code to generate a parametric model of an aluminum extrusion, kind of like a 1515 profile, but fully customizable.

I need to be able to specify the length of the extrusion, the size of the main square profile (e.g. 15mm for a 1515), and the radius of the corners for a rounded square. I'd also like to have some parameters to control the T-slot and the nut pocket dimensions. Specifically, I want the slot to have a width and height, and the nut pocket to have a depth and a flat section.

The nut pocket needs to be designed to work with a standard M3 or M4 nut, with a flat for the nut to press against (or be retained within). Also, the main profile needs to have a chamfer on the corners, or a radius, whatever is easier for you.

Ideally, the code would create the base profile using a 2D shape and then extrude it to the specified length. I think using the hull function might be helpful for the rounded square profile, which needs to be parametric, too. I'd like the nut pocket and T-slot to be cut out with difference().

So, to recap, I need parameters for:

*   `LENGTH`: Length of the extrusion
*   `EXTERIOR_SIZE`: Side length of the main square profile
*   `CHAMFER`: Chamfer or fillet radius
*   `NUT_CUTOUT`: Width of the nut pocket
*   `NUT_HEIGHT`: Height of the nut pocket
*   `SLOT_HEIGHT`: Height of the T-slot
*   `CUT_DEPTH`: Depth of the nut pocket below the slot base
*  `SLOT_OPENING`: Width of the T-Slot opening
* `$fs`: For fragment size.

It would be really helpful if this were well-commented for future modifications, and I'd also prefer that all the parameters be declared at the beginning of the file. I do not need any libraries for this model.

[Assistant]
microbeam.scad:
```
LENGTH = 100;
NUT_CUTOUT = 6;
NUT_HEIGHT = 2.7;
SLOT_HEIGHT = 1.2;
CUT_DEPTH = 1.5;
EXTERIOR_SIZE = 15;
CHAMFER = 1.5;
SLOT_OPENING = 3.4;
$fs = 0.1;

module rounded_square(size, fillet){

    translation = size/2 - fillet/2;

    hull(){
        translate([translation,translation,0])
            circle(d=fillet);
         translate([translation,-translation,0])
            circle(d=fillet);
        translate([-translation,translation,0])
            circle(d=fillet);
        translate([-translation,-translation,0])
            circle(d=fillet);
    }

}

module extrusion_cutout(){
    translate([0,EXTERIOR_SIZE/2 - SLOT_HEIGHT,0])
        square([SLOT_OPENING, SLOT_HEIGHT*2], true);
    translate([0,EXTERIOR_SIZE/2-SLOT_HEIGHT,0])
        polygon([[-NUT_CUTOUT/2,0],[NUT_CUTOUT/2,0],[NUT_CUTOUT/2, -NUT_HEIGHT],[0, -NUT_HEIGHT - CUT_DEPTH],[-NUT_CUTOUT/2, -NUT_HEIGHT]]);
}

module extrusion(){
    difference(){
        rounded_square(EXTERIOR_SIZE, CHAMFER);
        extrusion_cutout();
        rotate([0,0,90])
            extrusion_cutout();
        rotate([0,0,180])
            extrusion_cutout();
        rotate([0,0,270])
            extrusion_cutout();
    }
}

linear_extrude(LENGTH){
    extrusion();
}
```


[User]
I'd like to create a parametric kitchen knife sheath, and I'm looking for an OpenSCAD file to handle it. I've seen a few examples online, but I want to make sure it has the right features and is fully customizable.

Here's what I'm envisioning:

*   **Basic Sheath Shape:** The sheath should be a basic, roughly rectangular shape that conforms to the blade, with a rounded tip.
*   **Parametric Dimensions:** I need to be able to specify the following:
    *   `knife_length`: The length of the knife blade, from handle to tip.
    *   `knife_width`: The maximum width of the knife blade.
    *   `knife_thickness`: The thickness of the knife blade.
*   **Window Option:** I'd like an option to include a window on one side of the sheath. This window should allow me to see part of the blade without removing the knife. It should be a simple rectangular cutout, and I should be able to control whether this window is present or not through a `knife_show` parameter.
*   **Holding Ridges:** There should be an option to add ridges to the inside of the sheath. These ridges would provide some friction to help hold the knife securely. This should be enabled with a parameter like `has_ridges`.
*   **Grip Cutoff Angle:** The opening of the sheath at the handle should not be perfectly vertical, but instead cut at an angle, creating a mouth that angles towards the tip of the blade. This angle should be specified with the `grip_cutoff_angle` parameter.
*   **Wall Thickness:** The sheath needs a `wall` parameter for the overall thickness of the sheath walls.
*   **Padding:** There also should be a `pad` parameter for the extra tolerance around the blade's dimensions.
*   **Rotation:** The final object should be rotated 45 degrees around the z-axis. This makes printing larger sheaths easier on my printer.
*   **Units:** All dimensions should be in millimeters.
*   **Functionality:** The file should define a `parasheath` module that takes all of these parameters and generates the appropriate sheath.

I'm hoping that the design will be relatively simple to understand and modify if needed. It would be awesome if you could generate the entire SCAD code. I am not using any libraries for now.

[Assistant]
Parametric_Kitchen_Knife_Sheath.scad:
```
// parametric knife sheath
// original by Steve Pendergrast (vorpal)
// https://www.thingiverse.com/thing:125109
//
// remix by BikeCyclist: Introducing holding ridge and oblique cut-off at grip
// https://www.thingiverse.com/thing:2856699

// length: the length of the blade from handle to tip
// maxwidth: the maximum width of the blade
// tipwidth:  the width near the tip, zero means it comes to a point
// thickness: maximum thickness of the blade
// show:  0 = do not put a window to show the blade style
// has_ridges: 0 = do not add ridges to the sheath to hold the knife by friction
// grip_cutoff_angle:  the angle of the mouth of the sheet at the grip (off the vertical)
//
// all measurements are in mm

//CUSTOMIZER VARIABLES

//	Knife length, mm
knife_length = 115;	//	[25:350]

//	Knife Max Blade width, mm
knife_width = 18;	//	[10:100]

//	Knife thickness, mm
knife_thickness = 1.5;	//	[1:5]

// Show knife through window?
knife_show = 1; // [0:No, 1:Yes]

// Sheath has ridges to hold the knife more securely?
has_ridges = 1; // [0:No, 1:Yes]

// Angle of the sheath at the grip (0 = vertical) [0:75]
grip_cutoff_angle = 20;

//CUSTOMIZER VARIABLES END

$fn = 80*1;  // while debugging reduce this to 20 to save time

wall = 2*1;
pad = 2*1;

rotate([0,0,45])
	parasheath(length=knife_length, maxwidth=knife_width, thickness=knife_thickness, show=knife_show);

module parasheath(length=200, maxwidth=25, thickness=2, show=1) {

    difference() {

        union () {

            difference() {

                // first make an oversized blade, oversized by 2 times the wall thickness

                blade(len=length+wall+pad,maxwid=maxwidth+pad+2*wall,thick=thickness+2*wall);

                translate([wall+0.2,-0.1,wall])
                    blade(len=length+0.1+pad, maxwid=maxwidth+pad, thick=thickness);

            }

            if (has_ridges)
            for (i = [-1, 1])
                translate([wall + 0.2 + knife_thickness/2, 0, wall + knife_width/2 + pad/2])
                    translate ([i * knife_thickness/2, 0, 0])
                        rotate ([-90, 0, 0])
                            scale ([1, 2, 1])
                                cylinder (d = knife_thickness / 2, h = length/2);

        }

        if (show)
            translate([thickness+wall,length-2*maxwidth,wall])
                    cube([100,wall+maxwidth,maxwidth]);

        rotate ([-grip_cutoff_angle, 0, 0])
            translate([0, -100, 0])
                    cube([200,200,200], center=true);
    }
}

module blade(len=200, maxwid=25, tipwid=5, thick=2) {

	union() {
		intersection() {
				// first make a cube
				cube([thick, len, maxwid]);

				translate([0,len-maxwid,maxwid])
					rotate([0,90,0])
						cylinder(r=maxwid,h=thick*3,center=true);
		}

		cube([thick,len-maxwid,maxwid]);
	}
}
```


[User]
Hey, I'm looking to get a parametric bike mount designed for my phone, and I'm hoping you can help generate the OpenSCAD code for it. I really like the design of the mount [yyh1002](https://www.thingiverse.com/yyh1002/about) made (it's called "Customizable Bike Mount for Modular Mounting System"), but I'd like to add a few tweaks and have it be fully parametric. I'll be 3D printing this so I'd like it to be robust.

Here's a breakdown of what I need:

**Core Structure:**

*   **Phone Cradle:** The mount should have two arms that wrap around the sides of the phone, with a gap to accommodate a phone of variable length. I'll need parameters to control both the length of the phone being held and the height of the mount.
*   **Handlebar Clamp:** A clamp mechanism that attaches the mount to the bike's handlebar is needed. I want this to be designed with a cylinder that clamps the handlebar and a flat portion which interfaces with the cradle. I'll need a parameter to adjust the handlebar diameter. The clamp will use two cylinders (one for the clamp, the other to represent the volume of the material of the clamp) and three flat plates that secure the two cylinders together.
*   **Arm Thickness and Width:** Parameters to control both the thickness and width of the arms that hold the phone should be included.

**Modifications:**

*   **Filleted Corners:** The corners where the arms wrap around the phone should have a fillet for added strength. I'll need a parameter to adjust the fillet radius.
*   **Bulbous Tips:** I'd like a slight bulge on the inner tips of the arms that wrap the phone. These are purely for extra security in holding the phone. I'd like a parameter for the radius and the position of these bumps.
*   **Internal Braces:** I need internal braces within the arms to add support and reduce the need for long bridges. I need parameters to control the width of these braces, and the maximum space between them. This should be done in such a way as to maximize the number of braces, and space them evenly across the length.

**Parameters:**

I'd like the following to be adjustable parameters:

*   `mountHeight`: The total height of the mount
*   `phoneLength`: Length of the phone or phone case.
*   `armThickness`: Thickness of the arms.
*   `armWidth`: Width of the arms.
*   `maxBridge`: Maximum distance between braces in the arms.
*   `phoneThickness`:  Height of the tabs on the ends of the phone.
*   `filletRadius`: Radius of the fillets.
*   `barDiameter`: Diameter of the bike handlebar.
*   `finThickness`: Thickness of the three plates.
*    `cylinderThickness`: Thickness of the cylinder forming the clamp.
*   `tolerance`: Tolerance for fitting the hex clamp on the flat part of the mount.
*   `offsetDepth`: Depth of the mount from the center of the handlebar.

**General Notes:**

*   I'd like the code to be well-commented so I can understand how it works.
*   I'm looking for a single OpenSCAD file that encapsulates the design. Please avoid using libraries like MCAD or BOSL, I'd prefer to keep it a bit more simple. I'd like to use a `for` loop in the design.

Let me know if you have any questions!

[Assistant]
Bike_mount_MMS.scad:
```
//circle resolution
$fn=100;

//total width of the mount
mountHeight = 24;

//length of your phone or case
phoneLength = 150;

//thickness of the arm
armThickness = 4;

//width of each arm
armWidth = 8;

//maximum distance between braces in the arms
maxBridge = 13;

//height of the tab on the two ends of phone
phoneThickness = 9.5;

//radius of the fillets
filletRadius = 2;

//diameter of the handlebar on your bike
barDiameter = 22.2;

//thickness of the three plates
finThickness = 3;

//thickness of the arc surface
cylinderThickness = 3;

//offset of the cutout grabing the phone clamp, larger is looser
tolerance = 0.05;

offsetDepth = 16+cylinderThickness+barDiameter/2;

hexPts = [
    [0,-15.08-tolerance], //0
    [0,15.08+tolerance], //1
    [4.5,17.68+tolerance], //3
    [8.2,15.54+tolerance], //5
    [8.2,-15.54-tolerance], //4
    [4.5,-17.68-tolerance], //2
    ];

module hex () {
    linear_extrude(height=mountHeight) polygon(hexPts);
}

finPts = [
    [armThickness,25],
    [armThickness,-25],
    [offsetDepth,-barDiameter/2-cylinderThickness],
    [offsetDepth,barDiameter/2+cylinderThickness],
];

module hexClamp (){
    difference (){
        linear_extrude(height=mountHeight) polygon(finPts);
        translate ([11,-25,0]){
            cube([offsetDepth,50,mountHeight],center=false);
        }
    }

}

module fin () {
    linear_extrude(height=finThickness) polygon(finPts);
}

module arm () {
translate([0,-phoneLength/2,0]) {
    cube(size = [armThickness,phoneLength,mountHeight], center = false);
    }
}

module armHollow(){
    translate ([0,25,armWidth]) {
    difference() {
        cube ([armThickness,phoneLength/2-25,mountHeight-armWidth*2],center=false);
        armBraces();
    }
}
}

module armBraces(){
    braceWidth   = max(2, armWidth/3);
    hollowLength = phoneLength/2 - 25;
    hollowWidth  = mountHeight-(2*armWidth);
    numBraces    = floor((hollowLength/maxBridge - 1) / (1 - braceWidth/maxBridge));
    bridgeLength = (hollowLength/(numBraces+1)) - (braceWidth*numBraces/(numBraces+1));
    echo("Brace Width: ", braceWidth);
    echo("number of Braces: ", numBraces);
    echo("bridge length: ", bridgeLength);

    for (i = [1:1:numBraces]) {
        translate([0,bridgeLength*i + braceWidth*(i-1)])
        cube ([armThickness, braceWidth, hollowWidth], center=false);
    }
}

module tab(){
    translate([-phoneThickness,phoneLength/2,0]) {
        cube(size=[armThickness+phoneThickness,armThickness,mountHeight], center = false);
        //fillet
        translate([phoneThickness-filletRadius,-filletRadius]) {
            difference() {
                cube(size = [filletRadius, filletRadius, mountHeight], center = false);
                union() {
                    cylinder(mountHeight, filletRadius, filletRadius, center=false);
                    translate([0, 0, armWidth]) {
                        cube(size = [
                            filletRadius,
                            filletRadius,
                            mountHeight - 2 * armWidth
                        ], center = false);
                    }
                }
            }
        }
        //bump
        difference() {
            union() {
                translate([-(cos(30)*filletRadius+filletRadius),0]) {
                    cube([cos(30)*filletRadius+filletRadius, armThickness, mountHeight], center = false);
                }
                translate([-filletRadius,sin(30)*filletRadius]) {
                    cylinder(mountHeight, filletRadius, filletRadius, center = false);
                }
            }
            translate([-2*(cos(30)*filletRadius+filletRadius),0]) {
                cube([cos(30)*filletRadius+filletRadius, armThickness, mountHeight], center = false);
            }
        }
    }
}

module tabAll() {
tab();
mirror ([0,1,0]){
    tab();
}
}

module finAll () {
translate([0,0,0]) {
    fin();
}
translate([0,0,mountHeight-finThickness]) {
    fin();
}
translate([0,0,mountHeight/2-finThickness/2]) {
    fin();
}
}

module smallCylinder () {
translate([offsetDepth,0,0]) {
cylinder(h=mountHeight, r=barDiameter/2);
}
}

module largeCylinder () {
translate([offsetDepth,0,0]) {
cylinder(h=mountHeight, r=barDiameter/2+cylinderThickness);
}
}

module positive (){
union () {
        finAll();
        tabAll();
        arm();
        largeCylinder();
        hexClamp();
}
}

module negative(){
    union () {
        smallCylinder();
        translate([offsetDepth,-20,0]) {
            cube(size=[offsetDepth,40,mountHeight], center = false);
        }
        hex();
        translate ([-7,0,0]){
            hex();
            }
        armHollow();
        mirror ([0,1,0]){
            armHollow();};
    }
}

difference (){
    positive();
    negative();
}
```


[User]
I'm looking for an OpenSCAD design for a honeycomb-style storage container. I'd like it to be parametric, so I can easily change the size of the overall container and the individual cells.

Here are the specific requirements:

*   **Honeycomb Structure:** The main structure should be a grid of hexagonal cells.
*   **Cell Size:** Each cell should be a cylinder, hollowed out to create storage space. The outer diameter and inner diameter of the cylinders need to be adjustable parameters.
*   **Overall Dimensions:** The width/length and height of the container should be adjustable parameters. The width and length dimensions should always be equal, and the container should be square in the X and Y dimensions.
*   **Number of Cells:** The number of cells may depend on the overall dimensions and cell size, however, the overall dimensions should take priority. I would prefer to always have a full hexagon grid, even if there are empty cells around the edges of the container.
*   **Base:** The bottom of the container should be a solid base to contain the storage cells.

Essentially, I want to input the container's overall dimensions, the outer and inner diameter of a cell, and have OpenSCAD generate a honeycomb container that fits those requirements. I think the design from the file `hivejer.scad` shows the basic geometry I want, but I want it generalized using parameters.

It would be really helpful if this design used the `BOSL2` library for some of the math functions, especially anything to do with generating the hexagonal grids or creating the arrays of cylinders.

[Assistant]
hivejer.scad:
```
//Hive storage by jeremie rioux
$fn = 6;

//variable en millimetre
//the width and length have to be the same number; if not, it will create a stretched hive.
width_length=0;
height=0;

resize(newsize=[width_length,width_length,height]){
difference(){
cylinder(r=25, h=100);
cylinder(r=23, h=100);

}
difference() {
translate([37.5,21.5,0])
cylinder(r=25,h=100);
translate([37.5,21.5,0])
cylinder(r=23,h=100);
}
translate([0,43,0]){
difference(){
cylinder(r=25, h=100);
cylinder(r=23, h=100);

}
difference() {
translate([37.5,21.5,0])
cylinder(r=25,h=100);
translate([37.5,21.5,0])
cylinder(r=23,h=100);
}
}
translate([-37.5,64.5,0]){
difference() {
translate([37.5,21.5,0])
cylinder(r=25,h=100);
translate([37.5,21.5,0])
cylinder(r=23,h=100);
}
}
translate([37,21.5,0]){
difference() {
translate([37.5,21.5,0])
cylinder(r=25,h=100);
translate([37.5,21.5,0])
cylinder(r=23,h=100);
}
}
}
```


[User]
I'm looking to create a customizable cable manager for external hard drives, similar to the one I found on Thingiverse (https://www.thingiverse.com/thing:969831) but with a few key improvements. I'd like to have it completely parametrically designed in OpenSCAD so I can tweak the dimensions easily.

Here are the parameters I need to be able to control:

*   **`length`**: The overall length of the cable manager.
*   **`cable_diameter`**: The diameter of the cable that will be held, plus a bit of play.
*   **`width`**: The overall width of the cable manager.
*   **`thickness`**: The thickness of the main walls of the body.
*  **`cable_insert_width`**: The width of the slot where the cable is inserted, which should be a bit wider than the `cable_diameter`.
*   **`middle_wall_thickness`**: The thickness of the dividing wall between the two cable holding channels.

The design should consist of two main 'channels' for holding the cable, separated by a central dividing wall. There should be a slot on the top of each channel to allow insertion of the cable. I'd also like to have a method of using rubber bands to hold the cable in place. This will involve small slots on each side of the cable manager, with small cylinders or half-spheres to act as anchors for the rubber band. Finally, all the edges and corners should be nicely rounded and smooth. I would like these rounded corners to be specified using a single `radius` parameter for the sake of simplicity. The rubber band slot should have a width specified by a `rubber_slot_width` parameter, and the radius of the cylinder/sphere should be specified by a `rubber_radius` parameter. I would like to use a `attache_size` parameter that controls the width of the band anchor.

Please generate the necessary OpenSCAD file or files to achieve this. I expect that the core rounding will be handled with a `roundbox_all` module.

[Assistant]
HDD_cable_manager.scad:
```
/*
 HDD cable manager --
 Pierre Delore - pierre@techandrun.com
 CC-BY-SA
*/

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

// variable description

//Total length of the part (in mm)
length=70; //[20:200]
//Cable diameter (in mm). Add at least 0.5mm of play.
cable_diameter=6.0; //[1:0.5:20]
//Width of the part (in mm)
width=15; //[1:50]
//Tickness of the walls (in mm)
tickness=2.5; //[1:0.5:20]
//Slot width to insert the cable (in mm). Minimum: The cable diameter.
cable_insert_width=7.5;  //[1:0.5:20]
//Tickness of the middle wall (in mm)
middle_wall_tickness=3; //[1:10]

/* [Hidden] */
//Internal variables
$fn=64;
ep=0.01;
ep2=0.02;

attache_size=6;
height=cable_diameter+tickness*2;

//Round box on all sides
module roundbox_all(wx, wy, wz, radius) {
	hull() {
        translate([radius, radius, radius])
			sphere(r=radius);

		translate([radius, radius, wz-radius])
			sphere(r=radius);

        //--
		translate([wx-radius, radius, radius])
			sphere(r=radius);

		translate([wx-radius, radius, wz-radius])
			sphere(r=radius);

        //--
		translate([wx-radius, wy-radius,radius])
			sphere(r=radius);

		translate([wx-radius, wy-radius, wz-radius])
			sphere(r=radius);

        //--
		translate([radius,wy-radius,radius])
			sphere(r=radius);

		translate([radius, wy-radius, wz-radius])
			sphere(r=radius);
	}
}

//Go!
difference() {
  union() {
    //Top body
    translate([attache_size,0,0])
      roundbox_all(length-(attache_size*2),height,width,2);
    //Bottom body
    roundbox_all(length,height-5,width,2);
  }

  //Calculate slot size
  sl=(length-(attache_size*2)-(tickness*2)-middle_wall_tickness)/2;

  //Left slot to store the cable
  translate([attache_size+tickness, tickness,-2])
    roundbox_all(sl, height-(tickness*2), width+2+2,2);
  //Right slot to store the cable
  translate([attache_size+tickness+sl+middle_wall_tickness, tickness, -2])
    roundbox_all(sl, height-(tickness*2), width+2+2,2);

  //Left slot to insert the cable
  translate([attache_size+tickness+sl-cable_insert_width-middle_wall_tickness, height-tickness-1,-ep])
    cube([cable_insert_width, tickness+2, width+ep2]);

  //Right slot to insert the cable
  translate([attache_size+tickness+sl+(middle_wall_tickness*2), height-tickness-1,-ep])
    cube([cable_insert_width, tickness+2, width+ep2]);

  //Left rubber band slot
  r=1.75; //Radius of the rubber cylinder
  a=2.5; //Width of rubber slot (smaller than the "r*2")
  translate([attache_size-a, tickness+r,-ep])
    cube([a, height, width+ep2]);
  translate([attache_size-r, tickness+r,-ep])
    cylinder(d=r*2,h=width+ep2);

  //Top left chamfer
  translate([attache_size-r, tickness+r, width-1])
    cylinder(d1=r*2-1,d2=r*2+2,h=2);
  //Bottom left chamfer
  translate([attache_size-r, tickness+r, -1])
    cylinder(d1=r*2+2,d2=r*2-1,h=2);

  //Right rubber band slot
  translate([length-attache_size, tickness+r,-ep])
    cube([a, height, width+ep2]);
  translate([length-attache_size+r, tickness+r,-ep])
    cylinder(d=r*2,h=width+ep2);

  //Top right chamfer
  translate([length-attache_size+r, tickness+r, width-1])
    cylinder(d1=r*2-1,d2=r*2+2,h=2);
  //Bottom right chamfer
  translate([length-attache_size+r, tickness+r, -1])
    cylinder(d1=r*2+2,d2=r*2-1,h=2);
}
```


[User]
Hey, I'm looking for an OpenSCAD design for a plant holder, kind of like a stick with a grabber on top. I've got some specific dimensions in mind but I'd like it to be parametric so I can tweak it later. I need to be able to change the stick length, the grabber length, the stick side length, the grabber diameter, the opening diameter for the plant stem, and the plastic thickness.

Here's what I'm picturing:

*   The grabber part should be a cylinder with a hole down the center for the plant stem, and a slit running down one side so you can actually fit the stem in.
*   The stick should be a square rod attached to the bottom of the grabber.

Here are the initial values I have in mind:

*   `stick_length = 150;`
*   `Grabber_length = 5;`
*   `stick_side_length = 3;`
*   `grabber_diameter = 10;`
*   `Opening_diameter = 4;`
*   `plastk_thickness = 2;`

Could you generate an OpenSCAD file for this? I don't need any fancy libraries, just the basic OpenSCAD functionality. Thanks!

[Assistant]
Plant_holder.scad:
```
//!OpenSCAD

stick_length = 150;
Grabber_length = 5;
stick_side_length = 3;
grabber_diameter = 10;
Opening_diameter = 4;
plastk_thickness = 2;
union(){
  linear_extrude( height=Grabber_length, twist=0, scale=[1, 1], center=false){
    difference() {
      circle(r=((grabber_diameter + plastk_thickness) / 2));

      circle(r=(grabber_diameter / 2));
      translate([(Opening_diameter / -2), -1000, 0]){
        square([Opening_diameter, 1000], center=false);
      }
    }
  }
  linear_extrude( height=stick_length, twist=0, scale=[1, 1], center=false){
    translate([0, ((grabber_diameter / 2 + plastk_thickness) + (stick_side_length / 2 - plastk_thickness)), 0]){
      square([stick_side_length, stick_side_length], center=true);
    }
  }
}
```


[User]
I'm looking to get an OpenSCAD design for a parametric nozzle stand. I'd like it to hold a grid of nozzles, with each slot having a bore for the nozzle thread, a recess for the hex nut, and a chamfer to guide the nozzle in.

Here are the parameters I'd like to control:

*   **`n_per_size`**: The number of nozzle places from front to back, along one dimension of the grid (range: 1-32, default: 3)
*   **`n_sizes`**: The number of different size nozzle places from left to right, along the other dimension of the grid (range: 1-32, default: 5)
*   **`d_bore`**: The diameter of the bore for the nozzle thread (e.g., 6.6 for M6 nozzles) (range: 1-26, default: 6.6)
*   **`d_wrench`**: The wrench size for the hex nut in mm (typically 7) (range: 1-36, default: 7)
*   **`w_chamfer`**: The width of the chamfer, in mm (range: 0-4, default: 0.5)
*  **`separation`**: The spacing between individual nozzle recesses in the grid, in mm (range: 1.5-6, default: 1.5)
*   **`roundness`**: The radius of the corner rounding for the stand (range: 0-24, default: 5)
*   **`l_bore`**: The depth of the bore for the nozzle thread (range: 0-24, default: 8)
*   **`l_hex`**: The depth of the recess for the hex nut (range: 0-10, default: 1)
*   **`l_bottom`**: The thickness of the base of the stand (range: 0-10, default: 1)

The stand should be a single solid piece. The nozzle slots should be created by subtracting cylinders from a solid rectangular prism with rounded corners using minkowski sum. The hex recesses should be true hexagons, not just circles with a number of sides. I'd also like chamfers to be created between the bore and the hex and between the hex and the top surface of the base.

Please use only vanilla OpenSCAD code, no external libraries. Thanks!

[Assistant]
Parametric_Nozzle_Stand.scad:
```
$fn = 16;

//CUSTOMIZER VARIABLES

//Bastion radius in playset raster size multiples

n_per_size = 3;         // Number of nozzle places per size (front to back) [1:32]
n_sizes = 5;            // Number of nozzle places for different sizes (left to right) [1:32]

d_bore = 6.6;           // Size of bore for nozzles (choose 6.6 for M6) [1:26]
d_wrench = 7;           // Wrench size in mm (typical value is 7) [1:36]

w_chamfer = 0.5;        // Chamfer width in mm [0:4]

separation = 1.5;       // Separation of nozzle recesses [1.5:6]

roundness = 5;          // Nozzle stand corner radius [0:24]

l_bore = 8;             // Depth of bore for nozzle thread [0:24]
l_hex = 1;              // Depth of recess for hex nut [0:10]
l_bottom = 1;           // Thickness of stand bottom [0:10]

//CUSTOMIZER VARIABLES END

d_hex = d_wrench / 0.866 + w_chamfer;

difference ()
{
    translate ([roundness, roundness, 0])
        linear_extrude (l_bore + l_hex + l_bottom, convexity = 10)
            minkowski ()
            {
                square ([n_sizes * (d_hex + separation) - 2 * roundness, n_per_size  * (d_hex + separation) - 2 * roundness]);
                circle (d = 2 * roundness);
            }

    for (i = [1:n_per_size])
        for (j = [1:n_sizes])
            translate ([(j - 0.5) * (d_hex + separation), (i - 0.5) * (d_hex + separation), 0])
        {
            translate ([0, 0, l_bottom])
                cylinder (d = d_bore, h = 2 * l_bore);

            translate ([0, 0, l_bottom + l_bore - w_chamfer])
                cylinder (d1 = d_bore, d2 = d_bore + w_chamfer, h = w_chamfer + 0.01);

            translate ([0, 0, l_bottom + l_bore])
                cylinder (d = d_hex, h = l_bore, $fn = 6);

            translate ([0, 0, l_bottom + l_bore + l_hex - w_chamfer])
                cylinder (d1 = d_hex, d2 = d_hex + w_chamfer, h = w_chamfer + 0.01, $fn = 6);

        }
}
```