[User]
Hi, I'd like to design a parametric radiator hook in OpenSCAD. I'm trying to improve upon existing designs, specifically the one at [https://www.thingiverse.com/thing:3342912](https://www.thingiverse.com/thing:3342912), and I have some specific ideas on how it should work. I'll also include a reference design, because it has some useful parts.

The basic idea is a hook that clamps onto a radiator, specifically those with round bars. Here are the parameters I want to be able to customize:

*   `diameter`: The diameter of the radiator bar.
*   `distance`: The distance between the centers of two adjacent radiator bars. This should include the diameters of both bars, that is to say: bar + gap + bar.
*   `thickness`: The wall thickness of the hook.
*   `space_h`: The distance the front of the hook should extend from the front of the radiator. This helps define the hook's shape.
*   `height_h`: The height of the vertical part of the hook, which sticks out away from the radiator.
*   `alpha_t`: The angle of the top part of the hook clamp, relative to horizontal.
*   `alpha_b`: The angle of the bottom part of the hook clamp, relative to horizontal.
*   `radius_r`: The rounding radius for the corners and the hook's bottom.
*   `width`: The width of the hook, along the z-axis.

I'm also working with a couple derived dimensions, so I'll include those here:

*   `radius_b`: The radius of the radiator bars (calculated as `diameter / 2`).
*   `distance_b`: The distance between the radiator bars, excluding the bar diameters (calculated as `distance - 2 * radius_b`).
*   `radius_h`: Half of the hook space (calculated as `space_h / 2`).
*  `shift_h`: Calculated to make the front hook occupy the space between the tubes, `0.5*diameter + thickness + 0.5*space_h`

The hook should consist of:

1.  **Top clamp:** A semi-circular piece that wraps around the top radiator bar, angled at `alpha_t`.
2.  **Straight section:** A vertical straight section connecting the top clamp to the bottom clamp.
3.  **Bottom clamp:** A semi-circular piece that wraps around the bottom radiator bar, angled at `alpha_b`.
4.  **Hook:** A piece that comes forward from the radiator, consisting of a rounded part and a vertical straight piece. The center of the arc of the rounded hook piece should be placed `space_h` away from the front of the radiator, along the `y` axis. The rounding of the hook's tip should be a cylinder, not a circle in the xy-plane (it should have a radius in the yz plane).
5.  **Support structure:** A curved part that connects the back of the front hook to the main body of the hook. This has a few different shapes, depending on the position of the hook relative to the radiator. The support needs to be a rounded connection so it doesn't have any sharp edges, and should be made using circles.

I have attached the reference SCAD file. It includes helper modules for some of the more complex calculations. I would also like to be able to tweak some aspects of the design, such as the position and size of the hook, so the design should be flexible. I'd also prefer that the code not rely on specific libraries, but if necessary, let me know if it can be done with BOSL2.

[Assistant]
radiator_hook.scad:
```
diameter=20.3; // measured : bar diameter
distance=20.3 + 19.7 + 20.3; // measured : distance between the bars (including bars)
thickness=4.5; // wall thickness
space_h=15; // front hook distance
shift_h=0.5*diameter + thickness + 0.5*space_h; // front hook shift,
   // calculated to make the front hook occupy the space between the tubes
height_h=10; // front hook height
alpha_t=210; // top hook angle
alpha_b=135; // bottom hook angle
radius_r=7.5; // rounding radius
$fn=100;
width=12.0; // width of the hook

radius_b=diameter/2;
distance_b=distance-2*radius_b;
radius_h=space_h/2;

module part_square(p_size,p_alpha) {
if (p_alpha < 0)
    mirror([0,1]) part_square(p_size,-p_alpha);
else if (p_alpha > 360)
    part_square(p_size,p_alpha-360);
else if (p_alpha > 180) {
        translate([-p_size,0]) square([2*p_size,p_size]);
        rotate(180) part_square(p_size,p_alpha-180);
    }
else if (p_alpha > 90) {
        square([p_size,p_size]);
        rotate(90) part_square(p_size,p_alpha-90);
    }
else if (p_alpha > 45)
    difference() {
        square([p_size,p_size]);
        mirror([1,-1]) part_square(p_size,90-p_alpha);
    }
else
    polygon([[0,0],[p_size,0],[p_size,tan(p_alpha)*p_size]]);
}

module part_ring(p_radius,p_thickness,p_alpha) {
    p_length=p_radius+p_thickness;
    intersection() {
        difference() {
            circle(p_length);
            circle(p_radius);
        }
        part_square(p_length,p_alpha);
    }
}

function vector(p_radius,p_alpha) = [p_radius*cos(p_alpha),p_radius*sin(p_alpha)];

function circle_intersection(c1,r1,c2,r2) =
let( x1=c1[0], y1=c1[1], x2=c2[0], y2=c2[1],
x0=x2-x1,                                      // difference in x coordinates
y0=y2-y1,                                      // difference in y coordinates
r=sqrt(pow(x0,2)+pow(y0,2)),                   // distance between centers
m1=(pow(r,2)+pow(r1,2)-pow(r2,2))/2/r,         // distance from center 1 to line
m2=sqrt(pow(r1,2)-pow(m1,2)))
   [x1+x0*m1/r+y0*m2/r,y1+y0*m1/r-x0*m2/r];    // right point
// [x1+x0*m1/r-y0*m2/r,y1+y0*m1/r+x0*m2/r];    // left point

module circle_2(p1,p2,c1,r1,c2,r2) {
    difference() {
        polygon([p1,p2,c1,c2]);
        translate(c1) circle(r1);
        translate(c2) circle(r2);
    }
}

module circle_3(c1,r1,c2,r2,c3,r3) {
    difference() {
        polygon([c1,[c1[0]+r1,c1[1]],[c2[0]-r2,c2[1]],c2,c3]);
        translate(c1) circle(r1);
        translate(c2) circle(r2);
        translate(c3) circle(r3);
    }
}

r_thickness=thickness/2;
r_length_b=radius_b+r_thickness;
r_length_h=radius_h+r_thickness;
v_center=circle_intersection(
    [0,0],radius_b+thickness+radius_r,
    [radius_b+radius_h+thickness,shift_h],radius_h+thickness+radius_r
);
y1_center=sqrt(4*radius_h*radius_r+thickness*(2*radius_h+2*radius_r+thickness));
y2_center=sqrt(4*radius_b*radius_r+thickness*(2*radius_b+2*radius_r+thickness));

linear_extrude(width){
    // Upper radiator tube clamp.
    translate([0,distance_b]) {
        part_ring(radius_b,thickness,alpha_t);
        translate(vector(radius_b+r_thickness,alpha_t)) circle(r_thickness);
    }

    // Vertical, straight part of the clamp.
    translate([radius_b,0]) square([thickness,max(distance_b,shift_h)]);

    if (shift_h < 0)
        translate([radius_b,0]) mirror([0,1]) square([thickness,-shift_h]);

    // Lower radiator tube clamp.
    part_ring(radius_b,thickness,-alpha_b);

    // Tip rounding of the lower end.
    translate(vector(radius_b+r_thickness,-alpha_b)) circle(r_thickness);

    // Rounded part of the hook.
    translate([radius_b+radius_h+thickness,shift_h]) rotate(180)
        part_ring(radius_h,thickness,180);

    // Vertical part of the hook.
    translate([radius_b+2*radius_h+thickness,shift_h]) square([thickness,height_h-0.5*width]);

    // Original tip rounding of the hook. Here replaced by rounding in the yz-plane.
    //translate([radius_b+2*radius_h+3*r_thickness,shift_h+height_h]) circle(r_thickness);

    // Radius to support the hook at the bottom.
    if (shift_h > radius_h+thickness+radius_r)
        circle_2 (
            [radius_b+thickness,shift_h-y1_center],
            [radius_b+thickness,shift_h],
            [radius_b+radius_h+thickness,shift_h],radius_h+thickness,
            [radius_b+radius_r+thickness,shift_h-y1_center],radius_r
        );
    else if (-shift_h > radius_b+thickness+radius_r)
        circle_2 (
            [radius_b,-y2_center],
            [radius_b,0],
            [0,0],radius_b+thickness,
            [radius_b-radius_r,-y2_center],radius_r
        );
    else
        circle_3 (
            [0,0],radius_b+thickness,
            [radius_b+radius_h+thickness,shift_h],radius_h+thickness,
            v_center,radius_r
        );
}

translate([radius_b+2*radius_h+3*r_thickness, shift_h+height_h-0.5*width, 0.5*width])
    rotate([0, 90, 0])
        difference() {
            cylinder(h = thickness, r = 0.5*width, center = true);
            translate([0, -0.25*width, 0]) cube([width, 0.5*width, thickness + 0.01], center = true);
        }
```


[User]
I'm looking to design a customizable nozzle holder for my E3D Revo nozzles, and I'd like it to be parametric so I can adjust the dimensions and layout easily. I'm imagining something similar to what I've seen online, where it's basically a rectangular block with holes for the nozzles, plus some text for labeling them.

Here's a breakdown of what I need:

*   **Nozzle Capacity:** I need to be able to specify how many nozzles it should hold, and they should be evenly spaced.
*   **Overall Dimensions:** I'd like to control the overall height, depth (thickness), and spacing between the nozzle holes.
*   **Nozzle Holder Geometry:** Each nozzle hole should have a slight stand-out or spacer on top for the nozzle to rest on. This spacer should have a base and top diameter.
*   **Nozzle Hole Size:** I need to be able to adjust the diameter of the nozzle holes.
*   **Text Labels:** I'd like to add text labels above and below each nozzle, as well as group labels with separators. The text size, content, and position should be customizable.
*   **Chamfers/Bevels:** I want the edges of the main body to have a slight chamfer or bevel, and I'd like to control how far back from the edge this starts.
*   **Screw Mounts:** I need options for adding screw mounts. I would like three types of screw mounts: none, side mounts, or embedded/inside screw mounts. The screw head diameter and threading diameter should be adjustable, as well as the offset. If using side mounts, I'd like to adjust the width.
*   **Font:**  I'd prefer to be able to select the font style for all of the text.

I'd also like to be able to control the thickness of all the extruded text on the design.

For this, I would like you to provide a set of parametric OpenSCAD files. I'm not familiar with any existing libraries, so I'd like to have everything included in these new files, please. I believe this should be all of the needed functionality for the project.

[Assistant]
Customizable_E3D_Revo_Nozzle_Holder.scad:
```
/* [Dimensions] */

// Number of nozzle holders.
Nozzle_Count = 7;

// Height of the whole object, excluding the text.
Height = 35;

// Depth/Thickness
Depth = 20;

// The spacing between nozzle holders (center).
Nozzle_Spacing = 18;

// The height of the nozzle spacer.
Nozzle_Spacer_Height = 2;

// The base diameter of the nozzle spacer on top of the holder.
Nozzle_Spacer_Base_Diameter = 12;

// The top diameter of the nozzle spacer on top of the holder.
Nozzle_Spacer_Top_Diameter = 8;

// The depth offset of the nozzle. 0 is in the middle.
Nozzle_Depth_Offset = 1;

// The diameter of the nozzle holder.
Nozzle_Diameter = 6.4;

// The distance from the edge, where 45 degree chamfer/bevel begins. Set to 0 if you dont want a chamfer/bevel.
Chamfer_Edge_Distance = 1.5;

/* [Screw mount options] */

// The type of screw mount.
Screw_Mount_Type = "None"; // [None, Sides, Inside]

// Distance from top of screw head and backside. If screw mount is "Sides", this also control the thickness of the screw mount.
Screw_Mount_Thickness = 4;

// Diameter of screw head hole, make this slightly bigger than head so that it fits.
Screw_Head_Diameter = 6.5;

// Diameter of screw threading hole
Screw_Threading_Diameter = 3.5;

// The X Offset of the screw holes. Is mirrored on the other side.
Screw_X_Offset = 0;

// The Y Offset of the screw holes.
Screw_Y_Offset = 0;

// Only applicable for screw mount type "Sides". Will control the width of a single screw mount on either side.
Screw_Mount_Width = 10;

/* [Top and Middle texts] */

// The thickness of the text on the object.
Text_Thickness = 0.6;

// Font/style used for all texts
Text_Font = "Liberation Sans:style=Bold";

// The top texts used for each nozzle. Should have the same number of elements as Nozzle_Count. Use empty string if not needed.
Top_Texts = ["0.15","0.25","0.4","0.6","0.8","0.4","0.6"];

// The font size of the top texts.
Top_Text_Size = 5;

// The offset of the top texts from the bottom edge.
Top_Text_Offset = 26;

// The middle texts used for each nozzle. Should have the same number of elements as Nozzle_Count. Use empty string if not needed.
Middle_Texts = ["P","Y","R","B","G","R","B"];

// The font size of the middle texts.
Middle_Text_Size = 8;

// The offset of the middle texts from the bottom edge.
Middle_Text_Offset = 16;

/* [Group texts and separator] */

// The bottom texts used for each nozzle group. Should have fewer elements than Nozzle_Count. Use empty string or delete if not needed.
Group_Texts = ["Brass","ObXidian"];

// The number of nozzles in each group. The text will be put in the middle of the group.
Group_Nozzle_Count = [5,2];

// The offset (number of nozzles) of the group. Should have the same number of elements as Group_Texts.
Group_Offsets = [0,5];

// The font sizes of each group. Should have the same number of elements as Group_Texts.
Group_Text_Sizes = [7,5];

// The offset of the group texts from the bottom edge.
Group_Text_Offset = 4;

// Enable/disable separator line between groups.
Group_Separator_Enabled = true;

// The width of the separator between each group.
Group_Separator_Width = 0.8;

// The length of the separator between each group. Should be smaller than Height.
Group_Separator_Length = 28;

Width = Nozzle_Count*Nozzle_Spacing;

// Calculate length of countersunk head, with 45* slope
screw_head_length = (Screw_Head_Diameter - Screw_Threading_Diameter) / 2;

screw_Y = Height/2+Screw_Y_Offset;

difference() {
    // The body, including text and screw mounts. But no holes.
    body_with_texts_and_screw_mounts();

    // The holes for the nozzles
    for (i=[0:Nozzle_Count-1]) {
        translate([Nozzle_Spacing/2+Nozzle_Spacing*i,1,Depth/2+Nozzle_Depth_Offset]) {
            rotate([270,0,0])
            {
                cylinder(h = Height+10, d = Nozzle_Diameter, $fn = 50);
            }
        }
    }

    // Screw mounts, if enabled
    if (Screw_Mount_Type == "Inside") {
        translate([Nozzle_Spacing-Screw_X_Offset,screw_Y,0]) {
            countersunk_screw_hole();
        }
        translate([Width-Nozzle_Spacing+Screw_X_Offset,screw_Y,0]) {
            countersunk_screw_hole();
        }
    }
    if (Screw_Mount_Type == "Sides") {
        translate([-(Screw_Mount_Width-Chamfer_Edge_Distance)/2-Screw_X_Offset,screw_Y,0]) {
            countersunk_screw_hole();
        }
        translate([Width+(Screw_Mount_Width-Chamfer_Edge_Distance)/2+Screw_X_Offset,screw_Y,0]) {
            countersunk_screw_hole();
        }
    }
}

module box_with_fillet(width, height, depth) {
    // Intersect 3 profiles including the fillet.
    intersection() {
        rotate([0,90,0]) {
            linear_extrude(width){
                polygon(points=[
                    [0,0],
                    [0,height],
                    [-depth+Chamfer_Edge_Distance,height],
                    [-depth,height-Chamfer_Edge_Distance],
                    [-depth,Chamfer_Edge_Distance],
                    [-depth+Chamfer_Edge_Distance,0]]);
            }
        }
        translate([0,height*5,0]) {
            rotate([90,90,0]) {
                linear_extrude(height*10){
                    polygon(points=[
                        [0,0],
                        [0,width],
                        [-depth+Chamfer_Edge_Distance,width],
                        [-depth,width-Chamfer_Edge_Distance],
                        [-depth,Chamfer_Edge_Distance],
                        [-depth+Chamfer_Edge_Distance,0]]);
                }
            }
        }
        linear_extrude(height*10){
            polygon(points=[
                [0,Chamfer_Edge_Distance],
                [0,height-Chamfer_Edge_Distance],
                [Chamfer_Edge_Distance,height],
                [width-Chamfer_Edge_Distance,height],
                [width,height-Chamfer_Edge_Distance],
                [width,Chamfer_Edge_Distance],
                [width-Chamfer_Edge_Distance,0],
                [Chamfer_Edge_Distance,0]]);
        }
    }
}

module body_with_texts_and_screw_mounts() {
    union() {
        // Main body
        box_with_fillet(Width, Height, Depth);

        // Screw mount, excluding holes
        if (Screw_Mount_Type == "Sides") {
            translate([-Screw_Mount_Width,0,0]) {
                box_with_fillet(Width+2*Screw_Mount_Width, Height, Screw_Mount_Thickness);
            }
        }

        for (i=[0:Nozzle_Count-1]) {
            // Standouts under nozzle
            translate([Nozzle_Spacing/2+Nozzle_Spacing*i,Height,Depth/2+Nozzle_Depth_Offset]) {
                rotate([270,0,0])
                {
                    cylinder(h = Nozzle_Spacer_Height, r1 = Nozzle_Spacer_Base_Diameter/2, r2 = Nozzle_Spacer_Top_Diameter/2, $fn = 50);
                }
            }
            // Top Text
            translate([Nozzle_Spacing/2+Nozzle_Spacing*i,Top_Text_Offset,Depth]) {
                linear_extrude(Text_Thickness) {
                    text(Top_Texts[i], size = Top_Text_Size, halign = "center", font = Text_Font);
                }
            }
            // Middle Text
            translate([Nozzle_Spacing/2+Nozzle_Spacing*i,Middle_Text_Offset,Depth]) {
                linear_extrude(Text_Thickness) {
                    text(Middle_Texts[i], size = Middle_Text_Size, halign = "center", font = Text_Font);
                }
            }
        }

        if (len(Group_Texts) > 0) {
            // Group Texts
            for (i=[0:len(Group_Texts)-1]) {
                translate([Group_Offsets[i]*Nozzle_Spacing + Group_Nozzle_Count[i]*Nozzle_Spacing/2,Group_Text_Offset,Depth]) {
                    linear_extrude(Text_Thickness) {
                        text(Group_Texts[i], size = Group_Text_Sizes[i], halign = "center", font = Text_Font);
                    }
                }
            }

            // Group separators
            if (Group_Separator_Enabled && len(Group_Texts) > 1) {
                for (i=[1:len(Group_Texts)-1]) {
                    translate([Group_Offsets[i]*Nozzle_Spacing-Group_Separator_Width/2,Height/2-Group_Separator_Length/2,Depth]) {
                        cube([Group_Separator_Width,Group_Separator_Length,Text_Thickness]);
                    }
                }
            }
        }
    }
}

module countersunk_screw_hole() {
    // Hole for embedding screw head inside
    translate([0,0,Screw_Mount_Thickness]) {
        cylinder(h = Height+10, d = Screw_Head_Diameter, $fn = 50);
    }

    // Countersunk screw head
    translate([0,0,Screw_Mount_Thickness-screw_head_length]) {
        cylinder(h = screw_head_length, r1 = Screw_Threading_Diameter/2, r2 = Screw_Head_Diameter/2, $fn = 50);
    }

    // Screw threading
    translate([0,0,-1]) {
        cylinder(h = Screw_Mount_Thickness+2, d = Screw_Threading_Diameter, $fn = 50);
    }
}
```


[User]
I'm looking for a parametric OpenSCAD design for a portable dice tower that also functions as a storage container. I'd like the design to be split into three main pieces: a "long" piece that contains the main chamber and part of the backing, a "short tray" piece, and a "short chamber" piece. These three pieces should be able to be combined in a few ways: one for storage, one for use as a tower, and then also as distinct pieces when printing.

The design should be fully parametric, with user-adjustable parameters including:

*   **Dice Diameter:** The maximum diameter of the dice to be used, this should be used to size other aspects of the design.
*   **Wall Thickness:** Minimum thickness for the walls.
*   **Backing Thickness:**  Extra thickness added to the backing walls, relative to the regular wall thickness.
*   **Split Type:** How the model is split along its depth, either "Half" (splitting at the midpoint) or "MagnetHeight" (splitting based on the magnet depth, plus some extra wall thickness).
*   **Magnet Type:** Either "Cube" or "Cylinder".
*   **Magnet Diameter:** The width/length of the magnet. It should be automatically constrained to be less than half the dice diameter.
*   **Magnet Depth:** The height of the magnet.
*   **Exterior Chamfer:** Enable/disable chamfering of the outer edges of all three pieces
*  **Tray Chamfer:** Enable/disable chamfering of the interior edges of the tray
*   **Chamfer Amount:** How much to chamfer the edges. It should be automatically constrained to be less than the wall thickness.

Internally, the design should include a central post in the tower chamber that diverts the dice as they roll. The chamber itself should be based on an octagon shape with chamfered edges. The opening for the dice should be wider than the dice diameter. Also the pieces should include magnetic connection points for secure attachment during both the storage and tower configurations. I would prefer that it be created without any outside libraries (BOSL, MCAD, etc.).

I'd like the design to include the following modules:
* `create_shortChamber()`: Generates the short chamber piece.
* `create_shortTray()`: Generates the short tray piece.
* `create_long()`: Generates the long piece.
* A `part_magnetCutout()` to cut out a shape for a magnet, either cube or cylinder
* A `part_magnetHolder()` to surround a cutout of a magnet
* A `part_chamber()` that generates the hollow chamber for dice rolling
* A `part_chamber_core()` to create the main body of the rolling chamber.
* A `part_chamber_post()` to create the post in the chamber that divets the dice
* A `part_chamber_access()` to create the entry and exits for the dice.
* A `part_chamferPiece()` to create a chamfered edge
* A `part_chamferPiece_corner()` to create a chamfered corner
* A `part_chamferPiece_cornerBig()` to create a larger chamfered corner.
* A `2d_octogon()` that creates an octogon from two points.
* `getOctogonPoints()` that returns the points used to create the octogon
* `getScaleFactorForExtrude()` to return a scale factor when extruding, based on slope/angle

The program should generate the parts, the full 'assembled' storage model, and the full 'assembled' tower model. The parts should also have visual spacing between them. I think that covers it.

[Assistant]
ParametricPortableDiceTower_v16.scad:
```
//////////////////////////////////
// Parameters
//////////////////////////////////

/* [Part Generation] */
//Choose what parts get created
partToGenerate = "All 3 pieces"; // [Piece - Long, Piece - Short tray, Piece - Short chamber, All 3 pieces, Demo - Storage, Demo - Tower ]

/* [Overall Size] */
// Maximum diameter of dice
die_diameter = 27;
// Minimum thickness of "walls" of the model
wall_thickness = 2;
// Extra thickness added to the backing walls (0 means backing is equal to wall_thickness)
wall_extraBacking = 1;
// If the tray splits in half or at a min height (half suggested)
split_type = "Half"; //[Half, MagnetHeight]

/* [Magnets] */
// Magnet type (square/cylinder)
magnet_type = "Cube"; // [Cube, Cylinder]
// Magnet width/length, must be less than 1/2 of die_diameter
magnet_diameter = 8.1;
// Magnet height
magnet_depth = 8.1;

/* [Chamfering] */
// Enable or disable chamfering of outter edges
chamfer_exterior = "yes"; //[yes,no]
// Enable or disable chamfering of the tray
chamfer_tray = "yes"; //[yes,no]
// How much to chamfer the edges. Should be less than wall_thickness
chamfer_amount = 2;

// Stop anything below from showing in the Customizer in OpenSCAD
module __Customizer_Limit__ () {}

//////////////////////////////////
// Calculated Parameters - core measurements derived from user selected parameters
//////////////////////////////////

//Note: You can fiddle with these values a bit without the model breaking down, but larger changes can cause it to fall apart. Save before editing.

// Rendering details
eps = 0.007;
$fn=64;

// Post = the post that is in the middle of the tower's chamber that diverts the dice
post_slant = 30; //degrees
post_width = die_diameter;
post_height = post_width * tan(post_slant);

// Opening = the entrace/exit to the chamber
opening_width = die_diameter*1.25;

// Chamber = the chamber of the tower
chamber_wallSlant = 30;
chamber_octoPoints = getOctogonPoints(); //points of the octogon shape that makes the chamber
chamber_totalHeight = chamber_octoPoints[0].y*2;

// Overall = the entire model when it is in its "packed" form
overall_depth  = die_diameter  + wall_thickness*2 + wall_extraBacking*2;
overall_height = chamber_totalHeight * 2;
overall_width  = chamber_octoPoints[1].x*2 + wall_thickness*2;

// Split = the cut across the depth that opens up the model like an ice cream sandwhich
split_depth = split_type=="Half" ? overall_depth/2 : magnet_depth+wall_thickness+wall_extraBacking;

//////////////////////////////////
// Logic - determine what is getting made, calling the proper "create" modules
//////////////////////////////////

if (partToGenerate=="Piece - Long"){
    create_long();
}
else if (partToGenerate=="Piece - Short tray"){
    create_shortTray();
}
else if (partToGenerate=="Piece - Short chamber"){
    create_shortChamber();
}
else if (partToGenerate=="All 3 pieces"){
    gapSpaceDesired = 5;
    g = gapSpaceDesired/2;
    translate([overall_width/2+g,0,0])
        create_long();
    translate([-overall_width/2-g,chamber_totalHeight/2+g,0])
        create_shortChamber();
    translate([-overall_width/2-g,0-chamber_totalHeight/2-g,0])
        create_shortTray();
}
else if (partToGenerate=="Demo - Storage"){
    //Short tray
    translate([0,chamber_totalHeight/2,overall_depth])
        rotate([180,0,0])
        create_shortTray();
    //Short chamber
    translate([0,-chamber_totalHeight/2,overall_depth])
        rotate([180,0,0])
        create_shortChamber();
    //Long
    create_long();
}
else if (partToGenerate=="Demo - Tower"){
    //Short tray
    translate([0,-chamber_totalHeight/2,-chamber_totalHeight])
        rotate([0,0,0])
        create_shortTray();
    //Short chamber
    translate([0,-overall_depth+split_depth,chamber_totalHeight/2])
        rotate([90,0,180])
        create_shortChamber();
    //Long
    translate([0,split_depth,0])
        rotate([90,0,0])
        create_long();
}

//////////////////////////////////
// Create - Generate an entire model using a collection of parts
//////////////////////////////////

// "Short chamber" piece
module create_shortChamber(){
    //Calculations
    height = overall_depth-split_depth;
    magnetSpot_upperLeft = [
        -overall_width/2+magnet_diameter/2+wall_thickness,
        chamber_totalHeight/2-magnet_diameter/2-wall_thickness,
        height-magnet_depth
    ];
    magnetSpot_upperRight = [
        overall_width/2-magnet_diameter/2-wall_thickness,
        chamber_totalHeight/2-magnet_diameter/2-wall_thickness,
        height-magnet_depth
    ];
    magnetSpot_lowerLeft = [
        -overall_width/2+magnet_diameter/2+wall_thickness,
        -chamber_totalHeight/2+magnet_depth,
        magnet_diameter/2+wall_thickness+wall_extraBacking
    ];
    magnetSpot_lowerRight = [
        overall_width/2-magnet_diameter/2-wall_thickness,
        -chamber_totalHeight/2+magnet_depth,
        magnet_diameter/2+wall_thickness+wall_extraBacking
    ];

    difference(){
        //Overall chamber
        part_chamber();
        //Cutoff
        translate([0,0,height])
            linear_extrude(100)
            square([overall_width+eps,chamber_totalHeight+eps],center=true);
        //Magnet- upper left
        translate(magnetSpot_upperLeft)
            part_magnetCutout();
        //Magnet- upper right
        translate(magnetSpot_upperRight)
            part_magnetCutout();
        //Magnet- lower left
        translate(magnetSpot_lowerLeft)
            rotate([90,0,0])
            part_magnetCutout();
        //Magnet- lower right
        translate(magnetSpot_lowerRight)
            rotate([90,0,0])
            part_magnetCutout();

        //Chamfers
        if (chamfer_exterior=="yes"){
            //Backplate (x3)
            translate([overall_width/2,0,0])
                rotate([90,0,0])
                part_chamferPiece(chamber_totalHeight);
            translate([-overall_width/2,0,0])
                rotate([90,0,0])
                part_chamferPiece(chamber_totalHeight);
            translate([0,chamber_totalHeight/2,0])
                rotate([0,90,0])
                part_chamferPiece(overall_width);
            //Uprights (x2)
            translate([overall_width/2,chamber_totalHeight/2,height/2])
                part_chamferPiece(height);
            translate([-overall_width/2,chamber_totalHeight/2,height/2])
                part_chamferPiece(height);
            //Backplate corners
            translate([overall_width/2,chamber_totalHeight/2,0])
                part_chamferPiece_cornerBig();
            translate([-overall_width/2,chamber_totalHeight/2,0])
                part_chamferPiece_cornerBig();
        }
    }

}

// "Short tray" piece
module create_shortTray(){
    height = overall_depth-split_depth;
    trayInnerWidth = overall_width-wall_thickness*4-magnet_diameter*2;
    trayInnerLength = chamber_totalHeight-wall_thickness*2-magnet_diameter;
    magnetSpot_lowerLeft = [
        -overall_width/2+magnet_diameter/2+wall_thickness,
        -chamber_totalHeight/2+magnet_diameter/2+wall_thickness,
        height-magnet_depth
    ];
    magnetSpot_lowerRight = [
        overall_width/2-magnet_diameter/2-wall_thickness,
        -chamber_totalHeight/2+magnet_diameter/2+wall_thickness,
        height-magnet_depth
    ];
    magnetSpot_upperLeft = [
        -overall_width/2+magnet_diameter/2+wall_thickness,
        chamber_totalHeight/2-magnet_depth,
        magnet_diameter/2+wall_thickness+wall_extraBacking
    ];
    magnetSpot_upperRight = [
        overall_width/2-magnet_diameter/2-wall_thickness,
        chamber_totalHeight/2-magnet_depth,
        magnet_diameter/2+wall_thickness+wall_extraBacking
    ];

    difference(){
        //Tray shape
        linear_extrude(height)
            square([overall_width,chamber_totalHeight],center=true);
        //Cutout for tray
        translate([0,wall_thickness*2+magnet_diameter,wall_thickness+wall_extraBacking])
            linear_extrude(height)
            square([trayInnerWidth,chamber_totalHeight],center=true);
        //Magnet - lower left
        translate(magnetSpot_lowerLeft)
            part_magnetCutout();
        //Magnet - lower right
        translate(magnetSpot_lowerRight)
            part_magnetCutout();
        //Magnet- upper left
        translate(magnetSpot_upperLeft)
            rotate([-90,0,0])
            part_magnetCutout();
        //Magnet- upper right
        translate(magnetSpot_upperRight)
            rotate([-90,0,0])
            part_magnetCutout();

        //Chamfers
        if (chamfer_exterior=="yes"){
            //Backplate (x3)
            translate([overall_width/2,0,0])
                rotate([90,0,0])
                part_chamferPiece(chamber_totalHeight);
            translate([-overall_width/2,0,0])
                rotate([90,0,0])
                part_chamferPiece(chamber_totalHeight);
            translate([0,-chamber_totalHeight/2,0])
                rotate([0,90,0])
                part_chamferPiece(overall_width);
            //Uprights (x2)
            translate([overall_width/2,-chamber_totalHeight/2,height/2])
                part_chamferPiece(height);
            translate([-overall_width/2,-chamber_totalHeight/2,height/2])
                part_chamferPiece(height);
            //Backplate corners
            translate([overall_width/2,-chamber_totalHeight/2,0])
                part_chamferPiece_cornerBig();
            translate([-overall_width/2,-chamber_totalHeight/2,0])
                part_chamferPiece_cornerBig();
        }
        if (chamfer_tray=="yes"){
            //1 side
            weirdYOffset = -chamber_totalHeight/2+(wall_thickness*2+magnet_diameter);
            translate([0,weirdYOffset,height])
                rotate([0,90,0])
                part_chamferPiece(trayInnerWidth);
            //2 opposite sides
            translate([trayInnerWidth/2,-trayInnerLength/2+chamber_totalHeight/2,height])
                rotate([90,0,0])
                part_chamferPiece(trayInnerLength);
            translate([-trayInnerWidth/2,-trayInnerLength/2+chamber_totalHeight/2,height])
                rotate([90,0,0])
                part_chamferPiece(trayInnerLength);
            //2 inner corners
            translate([trayInnerWidth/2,weirdYOffset,height])
                part_chamferPiece_corner();
            translate([-trayInnerWidth/2,weirdYOffset,height])
                part_chamferPiece_corner();
        }
    }
}

// "Long part" piece
module create_long(){
    //Calculations
    height = split_depth;

    magnetSpot_upperLeft = [
        -overall_width/2+magnet_diameter/2+wall_thickness,
        chamber_totalHeight/2-magnet_diameter/2-wall_thickness,
        height-magnet_depth
    ];
    magnetSpot_upperRight = [
        overall_width/2-magnet_diameter/2-wall_thickness,
        chamber_totalHeight/2-magnet_diameter/2-wall_thickness,
        height-magnet_depth
    ];

    magnetSpot_lowerLeft = [
        -overall_width/2+magnet_diameter/2+wall_thickness,
        -chamber_totalHeight/2+magnet_diameter/2+wall_thickness,
        height-magnet_depth
    ];
    magnetSpot_lowerRight = [
        overall_width/2-magnet_diameter/2-wall_thickness,
        -chamber_totalHeight/2+magnet_diameter/2+wall_thickness,
        height-magnet_depth
    ];

    difference(){
        //Overall shape
        union(){
            // Top section (chamber)
            translate([0,chamber_totalHeight/2,0])
            difference(){
                //Overall chamber
                part_chamber();
                //Cutoff
                translate([0,0,height])
                    linear_extrude(100)
                    square([overall_width+eps,chamber_totalHeight+eps],center=true);

                //Magnet- upper left
                translate(magnetSpot_upperLeft)
                    part_magnetCutout();
                //Magnet- upper right
                translate(magnetSpot_upperRight)
                    part_magnetCutout();
            }

            // Bottom section (backing/tray)
            translate([0,-chamber_totalHeight/2,0])
            union(){
                //primary backing
                difference(){
                    //Bottom main shape
                    linear_extrude(height)
                        square([overall_width,chamber_totalHeight],center=true);
                    //Cutout to establish the permiter walls
                    translate([0,wall_thickness,wall_thickness+wall_extraBacking])
                        linear_extrude(height)
                        square([overall_width-wall_thickness*2,chamber_totalHeight],center=true);
                }
                //Magnets
                difference(){
                    union(){
                        //Magnet holder, lower left
                        translate(magnetSpot_lowerLeft - [0,0,magnetSpot_lowerLeft.z])
                            part_magnetHolder();
                        //Magnet holder, lower right
                        translate(magnetSpot_lowerRight - [0,0,magnetSpot_lowerLeft.z])
                            part_magnetHolder();
                    }
                    //Magnet, lower left
                        translate(magnetSpot_lowerLeft + [0,wall_extraBacking,0])
                            part_magnetCutout();
                        //Magnet, lower right
                        translate(magnetSpot_lowerRight + [0,wall_extraBacking,0])
                            part_magnetCutout();
                }
                //Ramp
                rampWidth = overall_width - magnet_diameter*2 - wall_thickness*4;
                rampPoints = [
                    [0,0], //innerCorner
                    [0,height-wall_thickness-wall_extraBacking], //top edge point
                    [magnet_diameter+wall_thickness,0]  //bottom edge point
                ];
                rampTranspose = [
                    -overall_width/2+magnet_diameter+wall_thickness*2,
                    -chamber_totalHeight/2+wall_thickness,
                    wall_thickness+wall_extraBacking
                ];
                translate(rampTranspose)
                    rotate([90,0,90])
                    linear_extrude(rampWidth)
                    polygon(rampPoints);
            }
        }
        //Chamfers
        if (chamfer_exterior=="yes"){
            //Backing edges
            translate([overall_width/2,0,0])
                rotate([90,0,0])
                part_chamferPiece(overall_height);
            translate([-overall_width/2,0,0])
                rotate([90,0,0])
                part_chamferPiece(overall_height);
            translate([0,overall_height/2,0])
                rotate([0,90,0])
                part_chamferPiece(overall_width);
            translate([0,-overall_height/2,0])
                rotate([0,90,0])
                part_chamferPiece(overall_width);
            //vertical edges
            translate([overall_width/2,overall_height/2,height/2])
                part_chamferPiece(height);
            translate([overall_width/2,-overall_height/2,height/2])
                part_chamferPiece(height);
            translate([-overall_width/2,overall_height/2,height/2])
                part_chamferPiece(height);
            translate([-overall_width/2,-overall_height/2,height/2])
                part_chamferPiece(height);
            //backing corners
            translate([overall_width/2,overall_height/2,0])
                part_chamferPiece_cornerBig();
            translate([overall_width/2,-overall_height/2,0])
                part_chamferPiece_cornerBig();
            translate([-overall_width/2,overall_height/2,0])
                part_chamferPiece_cornerBig();
            translate([-overall_width/2,-overall_height/2,0])
                part_chamferPiece_cornerBig();

        }
    }
}

//////////////////////////////////
// Parts - elements that are used to make up a section/element of the overall model
//////////////////////////////////

// Note: all parts are expected to have their source position (0,0,0 on the model) to be the bottom center of the model, not the geometric center of the model

// Creates shape of magnet, used to cut out slots for magnets (cube or cylinder)
module part_magnetCutout(){
    if (magnet_type=="Cube"){
        linear_extrude(magnet_depth + eps)
            square(magnet_diameter, center=true);
    } else if (magnet_type=="Cylinder") {
        linear_extrude(magnet_depth + eps)
            circle(d=magnet_diameter);
    }
}

// Creates shape of magnet, used to surround a cutout of a magnet
module part_magnetHolder(){
    height = split_depth;
    width = magnet_diameter+wall_thickness*2;
    depth = overall_depth-split_depth;
    difference(){
        //holder
        translate([0,depth/2-width/2,0])
            linear_extrude(height)
            square([width,depth], center=true);
        //Chamfering
        if (chamfer_tray=="yes"){
            translate([width/2,depth/2+(depth/2-width/2),height/2])
                part_chamferPiece(depth);
            translate([-width/2,depth/2+(depth/2-width/2),height/2])
                part_chamferPiece(depth);
        }
    }
}

// The complex that repesents the hollow chamber for dice rolling differenced out of a cube
module part_chamber(){
    difference(){
        linear_extrude(overall_depth)
                square([overall_width,chamber_totalHeight],center=true);
        translate([0,0,wall_thickness+wall_extraBacking])
            difference(){
                union(){
                    part_chamber_core();
                    part_chamber_access();
                }
                part_chamber_post();
            }
    }
}

// Create the main body of the rolling chamber
// Extrudes from larget to smaller (inside-to-out)
module part_chamber_core(){
    //Calculate sizing
    extrudeDistance = die_diameter/2;
    extScale = getScaleFactorForExtrude(chamber_octoPoints[1].x*2,extrudeDistance,chamber_wallSlant);
    // Create object
    translate([0,0,die_diameter/2])
    union(){
        rotate([0,180,0])
            linear_extrude(extrudeDistance, scale=extScale)
            2d_octogon(chamber_octoPoints[0], chamber_octoPoints[1]);
        linear_extrude(die_diameter/2, scale=extScale)
            2d_octogon(chamber_octoPoints[0], chamber_octoPoints[1]);
    }
}

// Create the post that appears through the middle of the rolling chamber
// Extrudes from larger to smaller (outside-to-in)
module part_chamber_post(){
    //Calculate sizing
    extrudeDistance = die_diameter/2+eps;
    extScale = getScaleFactorForExtrude(post_width,extrudeDistance,post_slant);
    // Create object
    union(){
        translate([0,0,-eps/2])
            linear_extrude(extrudeDistance, scale=extScale)
            resize([post_width,post_height])
            rotate([0,0,45])
            square(1, center=true);
        translate([0,0,extrudeDistance*2-eps-eps/2])
            rotate([0,180,0])
            linear_extrude(extrudeDistance, scale=extScale)
            resize([post_width,post_height])
            rotate([0,0,45])
            square(1, center=true);
    }
}

// Entry and exits for dice into the chamber (chutes and funnel)
module part_chamber_access(){
    //Width for entry chutes
    yWidestPoint = chamber_octoPoints[0].y;
    //Slant calculation for Y offset for funner placement
    slantFactor = die_diameter/2 * tan(chamber_wallSlant);
    newSize = yWidestPoint-slantFactor*2; //may not need the *2?
    funnelHeight = newSize+wall_thickness;

    //Funner slant calculation
    entryFunnelMinWidth = opening_width;
    entryFunnelMaxWidth = (chamber_octoPoints[1].x - magnet_diameter - wall_thickness*2) * 2;

    union(){
        //Top chute
        translate([0,yWidestPoint+wall_thickness,die_diameter/2])
            rotate([90,0,0])
            linear_extrude(yWidestPoint-post_height/2)
            square([opening_width,die_diameter],center=true);
        //TopFunnel
        translate([0,yWidestPoint-funnelHeight+wall_thickness,die_diameter/2])
            rotate([-90,0,0])
            linear_extrude(funnelHeight+eps, scale=[entryFunnelMaxWidth/entryFunnelMinWidth, 1])
            square([opening_width,die_diameter],center=true);
        //Bottom chute
        translate([0,-yWidestPoint-wall_thickness-eps,die_diameter/2])
            rotate([-90,0,0])
            linear_extrude(yWidestPoint-post_height/2+eps)
            square([opening_width,die_diameter],center=true);
    }
}

//Creates a chamfered piece for subtracting chamfers. IS CENTERED!
module part_chamferPiece(length){
    linear_extrude(length+eps,center=true)
        resize([chamfer_amount,chamfer_amount]*2)
        rotate([0,0,45])
        square(1, center=true);
}

//Creates a chamfered piece for subtracting chamfers. IS CENTERED!
module part_chamferPiece_corner(){

    linear_extrude(chamfer_amount,scale=0.0001)
        resize([chamfer_amount,chamfer_amount]*2)
        rotate([0,0,45])
        square(1, center=true);
    rotate([180,0,0])
        linear_extrude(chamfer_amount,scale=0.0001)
        resize([chamfer_amount,chamfer_amount]*2)
        rotate([0,0,45])
        square(1, center=true);
}

//Creates a double-sized corner chamfer, used for outter chamfers. IS CENTERED!
//Note, this was a lazy way to do this. I should re-position the normal one to fit corners, but this was just a bunch easier and I didn't feel like trying anymore. Plathag therum to place corners
module part_chamferPiece_cornerBig(){

    linear_extrude(chamfer_amount*2,scale=0.0001)
        resize([chamfer_amount,chamfer_amount]*4)
        rotate([0,0,45])
        square(1, center=true);
    rotate([180,0,0])
        linear_extrude(chamfer_amount*2,scale=0.0001)
        resize([chamfer_amount,chamfer_amount]*4)
        rotate([0,0,45])
        square(1, center=true);
}

//////////////////////////////////
// Shapes - custom primitive 2d or 3d shape generation
//////////////////////////////////

// Makes an octogon from the 2 upper right points, with the higher one (y axis) as p1
module 2d_octogon(p1, p2){
    // Determine points and
    points = [
        [ p1.x, p1.y],
        [ p2.x, p2.y],
        [ p2.x,-p2.y],
        [ p1.x,-p1.y],
        [-p1.x,-p1.y],
        [-p2.x,-p2.y],
        [-p2.x, p2.y],
        [-p1.x, p1.y],
    ];
    polygon(points);
}

//////////////////////////////////
// Functions - helper functions, usually to make geometry math easier
//////////////////////////////////

//Calculates 2 of the points that make up an octogot (upper right corner, positive x&y)
function getOctogonPoints() =
    let (
        topEdgeWidth = opening_width,
        topEdgeHeight = die_diameter*2 + post_height,
        rightEdgeWidth = die_diameter*2 + post_width,
        rightEdgeHeight = topEdgeHeight - (topEdgeWidth*tan(post_slant)*2)
    )
        [
            [topEdgeWidth/2,topEdgeHeight/2],
            [rightEdgeWidth/2,rightEdgeHeight/2]
        ];

// Determines a linear_extrude "scale" number based on desired height/slope
function getScaleFactorForExtrude(startSize,height,slope) =
    let (
        slantFactor = height * tan(slope),
        newSize = startSize-slantFactor*2 //may not need the *2?
    )
    newSize/startSize;

```


[User]
Hey, I'm looking to design some custom vases for 3D printing, and I'd like to use OpenSCAD to make them parametric. I've got some ideas for the general structure, but I'd appreciate some help putting it together as a usable design.

I'd want to be able to define a few key things: the vase's base diameter and overall height, of course. Then, I'd like to select from a few common vase "shapes", like a straight cylinder, a wide top, a skinny top, a "chubby" shape that bulges out in the middle, a "skinny" vase that tapers inward in the middle, a "classic" shape with a few outward bulges and tapers, and an elegant one, and also a goblet shape. I want the option to use my own custom shape by providing a list of points, each with their height (as a percentage of the total height) and diameter (as a percentage of the base diameter) that define a profile, so for example [[0,100], [50,160], [100,100]] would be that chubby shape. I'm aiming for vases that can be printed in "vase mode" or spiralize.

Other parameters would include the number of sides on the vase, the "facet" detail (which I imagine would determine how many segments there are in the circular extrusion for each vase section), and how many rotations a full twist does on the whole vase, both positive and negative numbers should be valid. I'd need to set the base thickness of the vase, and the wall thickness. So I think that's everything, the parameters are:

*   `vase_base_diameter`:  Width of the vase at its base (mm)
*   `vase_height`:  Height of the vase (mm)
*   `vase_design`: String. Choices: `Straight`, `Wide Top`, `Skinny Top`, `Skinny`, `Chubby`, `Classic`, `Elegant`, `Goblet`, `Custom`
*   `vase_customDesign`: List of lists. Each sublist is a \[height percentage, diameter percentage]
*   `wall_sideCount`: Number of sides of the vase
*   `wall_complexity`: Facet detail level (number of segments in the extrusion)
*   `twist_rotations`: Number of 360 twists over the vase's height
*   `base_thickness`: Thickness of the base (mm)
*   `wall_thickness`: Thickness of the wall (mm)

I think that's a pretty good starting point. It sounds like it needs to use `linear_extrude` and `circle`. Let me know if you have any questions!

[Assistant]
Parametric_Vase_v01.scad:
```
//////////////////////////////////
// Parameters - user-editable parameters
//////////////////////////////////

/* [Size] */
//How wide the vase is at its base (mm)
vase_base_diameter = 60;
//How tall the vase is (mm)
vase_height = 120;

/* [Design] */
// Choose a vase design or select custom to use your own
vase_design = "Classic"; //[Straight, Wide Top,Skinny Top,Skinny,Chubby,Classic,Elegant,Goblet,Custom]
// *Used only if you choose a Custom design. Each pair of numbers is a "distance from base percent" and "diameter percentage". Should always starts with [0,100] on the first level, and end with [100,###].
vase_customDesign = [[0,100], [50,160], [100,100]];

/* [Shape] */
// How many sides the vase has
wall_sideCount = 8;
// Fascet detail level (usually between 1x and 4x the wall_sideCount
wall_complexity = 16;
// How many times a full 360 twist is performed. 1 = 1 full rotation. Negative numbers accepted
twist_rotations = 0.5;

/* [Surface Thickness] */
//How thick the base is. Adds to overall height
base_thickness=1;
//How thick the walls of the vase are
wall_thickness=0.2;

// Stop anything below from showing in the Customizer in OpenSCAD
module __Customizer_Limit__ () {}

//////////////////////////////////
// Calculated parameters - parameters not intended to be edited
//////////////////////////////////

//Determing how much 1 unit of height of the vase should twist
twist_total = 360*twist_rotations;
twist_perUnit = twist_total/vase_height;

//////////////////////////////////
// Logic - determine what parts are assembled and how
//////////////////////////////////
if (vase_design=="Straight"){
    measurements = [
        [0,100],
        [100, 100]
    ];
    create_formVase(measurements);
}
else if (vase_design=="Wide Top"){
    measurements = [
        [0,100],
        [100, 150]
    ];
    create_formVase(measurements);
}
else if (vase_design=="Skinny Top"){
    measurements = [
        [0,100],
        [100, 50]
    ];
    create_formVase(measurements);
}
else if (vase_design=="Chubby"){
    measurements = [
        [0,100],
        [50,160],
        [100,100]
    ];
    create_formVase(measurements);
}
else if (vase_design=="Skinny"){
    measurements = [
        [0,100],
        [70,60],
        [100,100]
    ];
    create_formVase(measurements);
}
else if (vase_design=="Classic"){
    measurements = [
        [0,100],
        [20,130],
        [40,130],
        [60,100],
        [80,100],
        [100,130]
    ];
    create_formVase(measurements);
}
else if (vase_design=="Elegant"){
    measurements = [
        [0,100],
        [20,70],
        [40,70],
        [60,75],
        [80,85],
        [100,100]
    ];
    create_formVase(measurements);
}
else if (vase_design=="Goblet"){
    measurements = [
        [0,100],
        [20,70],
        [60,130],
        [100,140]
    ];
    create_formVase(measurements);
}

else if (vase_design=="Custom"){
    create_formVase(vase_customDesign);
}
else {echo("Oops, the selected design is not in the design table");}

//////////////////////////////////
// Parts - elements that make up the final model
//////////////////////////////////

// Create vase given the provided measurements. m=the list of measurements for the vase
module create_formVase(m){
    for (i=[0:len(m)-2]){
        //Calculate shape parameters for 1 section of vase
        startD = m[i][1]/100 * vase_base_diameter;
        endD = m[i+1][1]/100 * vase_base_diameter;
        height = m[i+1][0]/100*vase_height - m[i][0]/100*vase_height;
        //Calculate height offset for placement of this section
        overallHeight = m[i][0]/100*vase_height;
        overallRotation = -1 * overallHeight * twist_perUnit;
        // Generate and place wall section
        translate([0,0,overallHeight])
            rotate([0,0,overallRotation])
            part_vaseWall(startD, endD, height);
    }
    part_vaseBase();
}

//////////////////////////////////
// Parts - elements that make up the final model
//////////////////////////////////

// Create a simple base to the vase
module part_vaseBase(){
    translate([0,0,-base_thickness])
        linear_extrude(base_thickness)
        circle(d=vase_base_diameter, $fn=wall_sideCount);
}

// Create a vase wall section.
// Takes diameter at its beginning (bottom) and end (top), and how far apart they are on the Z axis
module part_vaseWall(startD, endD, height){
    //Calcualte scaling and twisting to match requires start/end diameters
    scale = endD/startD;
    twist = height*twist_perUnit;
    // Generate section of vase
    linear_extrude(height, scale=scale, twist=twist, $fn=wall_complexity)
    difference(){
        2d_circle(startD);
        2d_circle(startD-wall_thickness);

    }
}

//////////////////////////////////
// 2d Shapes - shape creation, made to be interchangable
//////////////////////////////////

//Simple circle
module 2d_circle(diameter){
    circle(d=diameter, $fn=wall_sideCount);
}

//Insert custom shapes here. This would be useful if I wanted to have thick-walled vases
//I ended up limiting this to simple models and didn't use this feature

//////////////////////////////////
// Functions - helper functions, usually to make geometry math easier
//////////////////////////////////

// Determines a linear_extrude "scale" number based on desired height/slope
function getScaleFactorForExtrude(startSize,height,slope) =
    let (
        slantFactor = height * tan(slope),
        newSize = startSize-slantFactor*2 //may not need the *2?
    )
    newSize/startSize;
```


[User]
I want to design a customizable object in OpenSCAD that lets me choose between a cube, a cylinder, or both, and control their sizes.

Specifically, I'd like to have a global setting called `part` with a dropdown menu that lets me select one of three options: "Cube Only", "Cylinder Only", or "Cube and Cylinder". If I select "Cube Only," I only want to see the cube, and if I select "Cylinder Only," I only want to see the cylinder. If I select "Cube and Cylinder" I want both of them rendered on the screen, side by side, with the cube on the left and the cylinder on the right. I'd also like to set a default if none are selected.

For the cube, I'd like to be able to control its size using a parameter called `cube_size`, which should have a slider ranging from 1 to 100. I'd also like a text parameter for the cube that doesn't affect the design, but that I can use to remind myself of what it's for. This parameter is called `cube_text`, and should default to "Foo".

Similarly, for the cylinder, I'd like to control its size with a parameter called `cylinder_size`, also ranging from 1 to 100. There will be a text parameter for the cylinder called `cylinder_text` that should default to "Bar" as well.

Additionally, I’d like to experiment with color highlighting. I’ll include another parameter called `preview_tab` that is for dev use only, and would ideally not be displayed in the UI. This will be a text string. When this parameter is set to "Cube", I want the cube object to be red, and when it's set to "Cylinder", I want the cylinder object to be red. I’d like this color change to happen in every use case, meaning the highlighting should also happen when `part` is set to "Cube and Cylinder".

Please generate the appropriate OpenSCAD code for this design. I'm not using any libraries here, so please don't include code relying on them.

[Assistant]
test.scad:
```
/* [Global] */

// Which one would you like to see?
part = "both"; // [first:Cube Only,second:Cylinder Only,both:Cube and Cylinder]

/* [Cube] */

cube_size = 10; // [1:100]
cube_text = "Foo";

/* [Cylinder] */

cylinder_size = 10; // [1:100]
cylinder_text = "Bar";

/* [Hidden] */

secret = 42;
preview_tab = "";

print_part();

module print_part() {
	if (part == "first") {
		mycube();
	} else if (part == "second") {
		mycylinder();
	} else if (part == "both") {
		both();
	} else {
		both();
	}
}

module both() {
	translate([-cube_size, 0, 0]) mycube();
	translate([cube_size, 0, 0]) mycylinder();
}

module mycube() {
	highlight("Cube") translate([0, 0, cube_size/2]) cube(cube_size, center=true);
}

module mycylinder() {
	highlight("Cylinder") cylinder(r=cube_size/2, h=cylinder_size);
}

module highlight(this_tab) {
  if (preview_tab == this_tab) {
    color("red") child(0);
  } else {
    child(0);
  }
}
```


[User]
I want to create a customizable pallet model with some optional boards that can be attached to it. The pallet itself will have a grid-like structure. Here's what I'm thinking:

The core pallet should consist of a series of parallel rectangular "planks" running along its length. There will be 8 of these, each 8mm wide, 99mm long, and 2mm thick, spaced 13mm apart. Then, perpendicular to those planks, I need three rectangular "support" pieces, 3mm thick, 99mm wide and 7.1mm tall, which will sit on top of the 2mm planks. These supports are also spaced at particular locations - 1mm, 48mm and 95mm along the length. The base of the support should align to the base of the main pallet.

Next, there should be two sets of rounded supports - these are really like small posts that raise part of the pallet higher. These are created by taking four cylinders, each with a radius of 3.5mm, and length of 101mm, rotating them by 90 degrees around the x-axis, and then creating a hull around the result. The center of the first pair should be at 11.5mm and 35.5mm along the width of the pallet, and the center of the second pair at 63.5mm and 87.5mm. All of these posts should also start at the base of the pallet. The top should be 9mm above the base.

Finally, there should be some smaller cylinders - just posts, really - at the intersections of the main planks and support pieces to help secure them. These need to be 3mm high with a 1mm radius. They should be at 2.5mm, 49.5mm, and 96.5mm along the length, and at 4mm and multiples of 13mm (4+13=17, 4+26=30, 4+39=43, etc.) along the width.

Oh, and I'd also like a text label that can be customized. It should sit on the top, near the front, rotated so that it can be read from the front, with the text rotated 90 degrees so it's aligned with the pallet. It should have a size of 5mm and be a stencil font. I need to be able to change the text. The label should be at [4,2,0.6] from the base, and rotated by 180 degrees around the X-axis. Then the text itself should be rotated by -90 around the Z-axis before the linear extrusion.

For the optional boards, they are similar to the planks, but are on their own, and should also be able to connect to the bottom of the main pallet. Each board is 8mm wide, 99mm long, and 2mm thick, just like the main planks, with cylinders on top of them which are 3mm high and 0.75mm radius and start at the base of the board. The boards are spaced in the same way as the planks, 13mm apart, but the position of the first board is shifted by 104mm along the width from the first plank in the main pallet. So the centers of the boards will be at 104mm, 117mm, 130mm, and 143mm along the width. The cylinders should be placed at the same y-coordinates as on the main part of the pallet - that is, at 2.5mm, 49.5mm and 96.5mm along the length.

Please make all dimensions and spacing parametric so I can change them later. I'd like to be able to adjust plank thickness, support thickness, cylinder radii, spacing between planks, everything.

The font used was "Stardos Stencil:style=Bold" and you can just assume I have it on my system. Don't worry about including font information. No external libraries are needed.

[Assistant]
pallet_99.scad:
```
name="USMC";

module pallet(name){
$fn=50;

difference(){
union(){
for(i=[0:7])
translate([i*13,0,0])
cube([8,99,2]);

for(j=[1,48,95])
translate([0,j,1.9])
cube([99,3,7.1]);
}

hull(){
for(k=[11.5,35.5])
translate([k,-0.5,9])
rotate([-90,0,0])
cylinder(r=3.5,h=101);
}

hull(){
for(k=[63.5,87.5])
translate([k,-0.5,9])
rotate([-90,0,0])
cylinder(r=3.5,h=101);
}

for(y=[2.5,49.5,96.5])
for(p=[0,3,4,7])
translate([p*13+4,y,7])
cylinder(r=1,h=3);

translate([4,2,0.6])
rotate([180,0,0])
rotate([0,0,-90])
linear_extrude(1)
text(name,size=5.0,valign="center",font="Stardos Stencil:style=Bold");
}
}

module board(){
$fn=50;
for(i=[0:3])
translate([i*13+104,0,0])
union(){
cube([8,99,2]);

for(y=[2.5,49.5,96.5])
translate([4,y,0])
cylinder(r=0.75,h=3);
}
}

pallet(name);
board();
```


[User]
Hey, I'm looking to generate some miniature bases and toppers for tabletop gaming, and I've got a pretty good idea of what I want. I'm envisioning a parametric design that uses OpenSCAD. I'd like to be able to adjust the size of the base and the pattern on top, and even choose from several pattern options.

Specifically, I need:

*   **Base Toppers:** These should be circular, and their diameter should be adjustable via a parameter. Let's call this `base_top_diameter`. I'd like a parameter, called `scale_factor`, that represents the miniature scale (e.g., 57.2 for 32mm scale).  The pattern will be scaled down based on this `scale_factor`.

*   **Patterns:** I need a few different pattern options for the top surface, which will be applied within a circular area. The pattern should be created by subtracting out gaps between the stones. I'm thinking these patterns:
    *   "none" - no pattern
    *   "straight" - simple straight lines
    *   "shifted" - offset rows
    *   "elbow" - a repeating pattern of "elbow"-shaped stones
    *   "windmill" - a "windmill" like stone pattern
    *   "bone" - a pattern of "bone"-shaped stones

*   **Base Options:** I'd like the option to add a simple base to the bottom of the topper, or no base. I'll need these options controlled by the parameter `base_type`:
    *   "none" - no base
    *   "flat" - a simple flat base. The bottom diameter, called `base_bottom_diameter` should be adjustable.
    *   "with_hole" - A flat base that also has a hole in the center.

I would want to control the amount of the pattern and the depth of the gaps between the stones, but a good starting point would be the sizes that are within the included code below. I'm hoping it can be as configurable as the code I'm including. This code has all the parameters I would like included as well as the patterns I would like to see.

```scad
/*
Generator for miniature bases and base toppers with patterns of european plaster stones/pavements.

Author: Dennis Schwarz (blacknpebble@gmail.com)
License: Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
For commercial use please contact the author
*/

/* [Global] */

/* [Topper] */
//Miniature scale, ("32 mm scale" would be 1/57.2, so enter 57.2 here), see https://en.wikipedia.org/wiki/Miniature_model_(gaming) for more examples. Recommended minimum is 30.
scale_factor=57.2;

//Diameter of the top surface
base_top_diameter=23; //[13:50]

//Pattern to be applied to the surface
pattern="shifted"; //[none, straigt, shifted, elbow, windmill, bone]

/* [Base] */
//Type of the base you want to add
base_type="none"; //[none, flat, with_hole]

//Bottom diameter of the base (usually ~2mm larger than the top)
base_bottom_diameter=25; //[15:52]

/* [Hidden] */
$fn= $preview?32:64;
stone_size=[200,100,60];
bone_stone_size=[200,165,60];
scale=1/scale_factor;

modifier=1 / 23*base_top_diameter / 57.2*scale_factor;

translate([0,0,1])
intersection(){
cylinder(h=4,d=base_top_diameter,center=true);
scale(scale)
difference(){
    cube([2000*modifier,2000*modifier,60],center=true);

    if(pattern=="bone")
        pattern_bone(13*modifier,19*modifier);

    if(pattern=="windmill")
        pattern_windmil(8*modifier,9*modifier);

    if(pattern=="elbow")
        pattern_elbow(20*modifier,20*modifier);

    if(pattern=="shifted")
        pattern_shifted(10*modifier,13*modifier);
    if(pattern=="straigt")
        pattern_straight(10*modifier,13*modifier);
}
}

if( base_type=="flat" ){
    cylinder(h=3-.5,d1=base_bottom_diameter,d2=base_top_diameter,center=true);
}
if(base_type=="with_hole" ){
    difference(){
    cylinder(h=3,d1=base_bottom_diameter,d2=base_top_diameter,center=true);
    cylinder(h=3.5,d=base_top_diameter,center=true);
    }
    translate([0,0,.5])
    cylinder(h=1,d=base_top_diameter,center=true);
}

module pattern_bone($count_x,$count_y){
translate([$count_x/2*-bone_stone_size[0]/2,$count_y/2*-(bone_stone_size[1]/2-17.5),0])
for ( x = [0:1:$count_x/2]) {
for ( y = [0:1:$count_y/2]) {
translate([bone_stone_size[0]*x+(bone_stone_size[0]/2*(y%2)),(bone_stone_size[1]-17.5)*y,0])
bone_stone();
}
}
}
module bone_stone(){
translate([0,0,bone_stone_size[2]/2]){
translate([-bone_stone_size[0]/2,0,0])rotate([0,45,0])cube([10,bone_stone_size[1]+10,10],center=true);
translate([66,-69,0])rotate([0,45,39])cube([10,bone_stone_size[0]/20*4,10],center=true);

translate([-66,-69,0])rotate([0,45,-39])cube([10,bone_stone_size[0]/20*4,10],center=true);

translate([-87.5,-bone_stone_size[1]/2,0])rotate([45,0,0])cube([bone_stone_size[0]/20*2.5,10,10],center=true);

translate([0,-bone_stone_size[1]/3,0])rotate([45,0,0])cube([bone_stone_size[0]/2+14.5,10,10],center=true);

translate([87.5,-bone_stone_size[1]/2,0])rotate([45,0,0])cube([bone_stone_size[0]/20*2.5,10,10],center=true);
}
}

/*
 |___| |___| |___| |___| |___| |___| |___|
_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |
___|_|___|_|___|_|___|_|___|_|___|_|___|_|
 |___| |___| |___| |___| |___| |___| |___|
_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |
___|_|___|_|___|_|___|_|___|_|___|_|___|_|
 |___| |___| |___| |___| |___| |___| |___|
_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |
___|_|___|_|___|_|___|_|___|_|___|_|___|_|
*/
module pattern_windmil($count_x,$count_y){
translate([-$count_x*stone_size[1]+stone_size[1],-$count_y*stone_size[1]+stone_size[1],0])
for ( x = [0:1:$count_x/2]) {
for ( y = [0:1:$count_y/2])
translate([x*(stone_size[0]+stone_size[1]),y*(stone_size[0]+stone_size[1]),0]){
translate([stone_size[0],stone_size[1],0]) rotate([0,0,-180]) corner_gap();
translate([stone_size[0],stone_size[1]*2,0]) rotate([0,0,-90])corner_gap();
translate([stone_size[1],stone_size[0],0]) corner_gap();
translate([stone_size[1],stone_size[1],0]) rotate([0,0,90])corner_gap();
translate([0,stone_size[1],0]) corner_gap();
translate([stone_size[0]+stone_size[1],stone_size[0],0]) rotate([0,0,180]) corner_gap();
translate([-stone_size[1],0,0]) y_gap();
translate([0,stone_size[0],0])  y_gap();
}}
}

/*
_|___| |_|___| |_|___| |_|___| |_|___| |_|___| |_|
___| |_|___| |_|___| |_|___| |_|___| |_|___| |_|
_| |_|___| |_|___| |_|___| |_|___| |_|___| |_|___|
 |_|___| |_|___| |_|___| |_|___| |_|___| |_|___| |
_|___| |_|___| |_|___| |_|___| |_|___| |_|___| |
___| |_|___| |_|___| |_|___| |_|___| |_|___| |_|
_| |_|___| |_|___| |_|___| |_|___| |_|___| |_|___|
 |_|___| |_|___| |_|___| |_|___| |_|___| |_|___| |
_|___| |_|___| |_|___| |_|___| |_|___| |_|___| |
*/
module pattern_elbow($count_x,$count_y){
translate([-stone_size[0]*($count_x/4+1),-stone_size[1]*($count_y/2+1),0])
for ( x = [0:1:$count_x]) {
for ( y = [0:1:$count_y]) {
  xshift=(stone_size[0]+stone_size[1]*2);
  if(x%3==0){
    translate([x/3*xshift+(y%4)*stone_size[1],y*stone_size[1],0]){
     x_gap();
     translate([-stone_size[0],stone_size[1],0])
     y_gap();
     translate([0,-stone_size[1],0])rotate([0,0,90])
     y_gap();
    }
  }else if(x%3==1 && y%2==0){
  translate([((x-1)/3)*xshift+(y%4)*stone_size[1],y*stone_size[1],0])
    translate([stone_size[0]+stone_size[1],0,0])rotate([0,0,90])
    x_gap();

  }else if(x%3==2 && y%2==0){
  translate([((x-2)/3)*xshift+(y%4)*stone_size[1],y*stone_size[1]+stone_size[1],0])
    translate([stone_size[0]+stone_size[1]*2,0,0])rotate([0,0,90])
    x_gap();
  }
}}
}

module pattern_shifted($count_x,$count_y){
    translate([-stone_size[0]*($count_x/2-1),-stone_size[1]*$count_y/2,0])
    for ( x = [0:1:$count_x]) {
        for ( y = [0:1:$count_y]) {
            shift=stone_size[0]/2*(y%2);
            translate([stone_size[0]*x-shift,stone_size[1]*y,0])
            y_gap();
            translate([stone_size[0]*x,stone_size[1]*y,0])
            x_gap();
           }
    }
}

module pattern_straight($count_x,$count_y){
translate([-stone_size[0]*($count_x/2-1),-stone_size[1]*$count_y/2,0])
    for ( x = [0:1:$count_x]) {
        for ( y = [0:1:$count_y]) {
            translate([stone_size[0]*x,stone_size[1]*y,0])
            corner_gap();
            if(x==$count_x)
              translate([stone_size[0]*($count_x+1),stone_size[1]*y,0])
              y_gap();
        }
    }

}

module corner_gap(){
    x_gap();
    y_gap();
}

module x_gap(){
    translate([stone_size[0]/2,stone_size[1],stone_size[2]/2])
    rotate([45,0,0])
    cube([stone_size[0],10,10],center=true);
}

module y_gap(){
    translate([stone_size[0],stone_size[1]/2,stone_size[2]/2])
    rotate([0,45,0])
    cube([10,stone_size[1],10],center=true);
}
```

I would greatly appreciate it if you could generate the OpenSCAD code for this. The `modifier` is also a value that would need to be calculated, so please include that as well.

[Assistant]
base_generator.scad:
```
/*
Generator for miniature bases and base toppers with patterns of european plaster stones/pavements.

Author: Dennis Schwarz (blacknpebble@gmail.com)
License: Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
For commercial use please contact the author
*/

/* [Global] */

/* [Topper] */
//Miniature scale, ("32 mm scale" would be 1/57.2, so enter 57.2 here), see https://en.wikipedia.org/wiki/Miniature_model_(gaming) for more examples. Recommended minimum is 30.
scale_factor=57.2;

//Diameter of the top surface
base_top_diameter=23; //[13:50]

//Pattern to be applied to the surface
pattern="shifted"; //[none, straigt, shifted, elbow, windmill, bone]

/* [Base] */
//Type of the base you want to add
base_type="none"; //[none, flat, with_hole]

//Bottom diameter of the base (usually ~2mm larger than the top)
base_bottom_diameter=25; //[15:52]

/* [Hidden] */
$fn= $preview?32:64;
stone_size=[200,100,60];
bone_stone_size=[200,165,60];
scale=1/scale_factor;

modifier=1 / 23*base_top_diameter / 57.2*scale_factor;

translate([0,0,1])
intersection(){
cylinder(h=4,d=base_top_diameter,center=true);
scale(scale)
difference(){
    cube([2000*modifier,2000*modifier,60],center=true);

    if(pattern=="bone")
        pattern_bone(13*modifier,19*modifier);

    if(pattern=="windmill")
        pattern_windmil(8*modifier,9*modifier);

    if(pattern=="elbow")
        pattern_elbow(20*modifier,20*modifier);

    if(pattern=="shifted")
        pattern_shifted(10*modifier,13*modifier);
    if(pattern=="straigt")
        pattern_straight(10*modifier,13*modifier);
}
}

if( base_type=="flat" ){
    cylinder(h=3-.5,d1=base_bottom_diameter,d2=base_top_diameter,center=true);
}
if(base_type=="with_hole" ){
    difference(){
    cylinder(h=3,d1=base_bottom_diameter,d2=base_top_diameter,center=true);
    cylinder(h=3.5,d=base_top_diameter,center=true);
    }
    translate([0,0,.5])
    cylinder(h=1,d=base_top_diameter,center=true);
}

module pattern_bone($count_x,$count_y){
translate([$count_x/2*-bone_stone_size[0]/2,$count_y/2*-(bone_stone_size[1]/2-17.5),0])
for ( x = [0:1:$count_x/2]) {
for ( y = [0:1:$count_y/2]) {
translate([bone_stone_size[0]*x+(bone_stone_size[0]/2*(y%2)),(bone_stone_size[1]-17.5)*y,0])
bone_stone();
}
}
}
module bone_stone(){
translate([0,0,bone_stone_size[2]/2]){
translate([-bone_stone_size[0]/2,0,0])rotate([0,45,0])cube([10,bone_stone_size[1]+10,10],center=true);
translate([66,-69,0])rotate([0,45,39])cube([10,bone_stone_size[0]/20*4,10],center=true);

translate([-66,-69,0])rotate([0,45,-39])cube([10,bone_stone_size[0]/20*4,10],center=true);

translate([-87.5,-bone_stone_size[1]/2,0])rotate([45,0,0])cube([bone_stone_size[0]/20*2.5,10,10],center=true);

translate([0,-bone_stone_size[1]/3,0])rotate([45,0,0])cube([bone_stone_size[0]/2+14.5,10,10],center=true);

translate([87.5,-bone_stone_size[1]/2,0])rotate([45,0,0])cube([bone_stone_size[0]/20*2.5,10,10],center=true);
}
}

/*
 |___| |___| |___| |___| |___| |___| |___|
_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |
___|_|___|_|___|_|___|_|___|_|___|_|___|_|
 |___| |___| |___| |___| |___| |___| |___|
_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |
___|_|___|_|___|_|___|_|___|_|___|_|___|_|
 |___| |___| |___| |___| |___| |___| |___|
_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |_|_| |
___|_|___|_|___|_|___|_|___|_|___|_|___|_|
*/
module pattern_windmil($count_x,$count_y){
translate([-$count_x*stone_size[1]+stone_size[1],-$count_y*stone_size[1]+stone_size[1],0])
for ( x = [0:1:$count_x/2]) {
for ( y = [0:1:$count_y/2])
translate([x*(stone_size[0]+stone_size[1]),y*(stone_size[0]+stone_size[1]),0]){
translate([stone_size[0],stone_size[1],0]) rotate([0,0,-180]) corner_gap();
translate([stone_size[0],stone_size[1]*2,0]) rotate([0,0,-90])corner_gap();
translate([stone_size[1],stone_size[0],0]) corner_gap();
translate([stone_size[1],stone_size[1],0]) rotate([0,0,90])corner_gap();
translate([0,stone_size[1],0]) corner_gap();
translate([stone_size[0]+stone_size[1],stone_size[0],0]) rotate([0,0,180]) corner_gap();
translate([-stone_size[1],0,0]) y_gap();
translate([0,stone_size[0],0])  y_gap();
}}
}

/*
_|___| |_|___| |_|___| |_|___| |_|___| |_|___| |_|
___| |_|___| |_|___| |_|___| |_|___| |_|___| |_|
_| |_|___| |_|___| |_|___| |_|___| |_|___| |_|___|
 |_|___| |_|___| |_|___| |_|___| |_|___| |_|___| |
_|___| |_|___| |_|___| |_|___| |_|___| |_|___| |
___| |_|___| |_|___| |_|___| |_|___| |_|___| |_|
_| |_|___| |_|___| |_|___| |_|___| |_|___| |_|___|
 |_|___| |_|___| |_|___| |_|___| |_|___| |_|___| |
_|___| |_|___| |_|___| |_|___| |_|___| |_|___| |
*/
module pattern_elbow($count_x,$count_y){
translate([-stone_size[0]*($count_x/4+1),-stone_size[1]*($count_y/2+1),0])
for ( x = [0:1:$count_x]) {
for ( y = [0:1:$count_y]) {
  xshift=(stone_size[0]+stone_size[1]*2);
  if(x%3==0){
    translate([x/3*xshift+(y%4)*stone_size[1],y*stone_size[1],0]){
     x_gap();
     translate([-stone_size[0],stone_size[1],0])
     y_gap();
     translate([0,-stone_size[1],0])rotate([0,0,90])
     y_gap();
    }
  }else if(x%3==1 && y%2==0){
  translate([((x-1)/3)*xshift+(y%4)*stone_size[1],y*stone_size[1],0])
    translate([stone_size[0]+stone_size[1],0,0])rotate([0,0,90])
    x_gap();

  }else if(x%3==2 && y%2==0){
  translate([((x-2)/3)*xshift+(y%4)*stone_size[1],y*stone_size[1]+stone_size[1],0])
    translate([stone_size[0]+stone_size[1]*2,0,0])rotate([0,0,90])
    x_gap();
  }
}}
}

module pattern_shifted($count_x,$count_y){
    translate([-stone_size[0]*($count_x/2-1),-stone_size[1]*$count_y/2,0])
    for ( x = [0:1:$count_x]) {
        for ( y = [0:1:$count_y]) {
            shift=stone_size[0]/2*(y%2);
            translate([stone_size[0]*x-shift,stone_size[1]*y,0])
            y_gap();
            translate([stone_size[0]*x,stone_size[1]*y,0])
            x_gap();
           }
    }
}

module pattern_straight($count_x,$count_y){
translate([-stone_size[0]*($count_x/2-1),-stone_size[1]*$count_y/2,0])
    for ( x = [0:1:$count_x]) {
        for ( y = [0:1:$count_y]) {
            translate([stone_size[0]*x,stone_size[1]*y,0])
            corner_gap();
            if(x==$count_x)
              translate([stone_size[0]*($count_x+1),stone_size[1]*y,0])
              y_gap();
        }
    }

}

module corner_gap(){
    x_gap();
    y_gap();
}

module x_gap(){
    translate([stone_size[0]/2,stone_size[1],stone_size[2]/2])
    rotate([45,0,0])
    cube([stone_size[0],10,10],center=true);
}

module y_gap(){
    translate([stone_size[0],stone_size[1]/2,stone_size[2]/2])
    rotate([0,45,0])
    cube([10,stone_size[1],10],center=true);
}
```


[User]
I'm looking to get a parametric design for a recessed flange-mount ball transfer, like the ones from McMaster-Carr but fully customizable. I want to be able to adjust the size of the main ball, the size of the support ball bearings, the mounting bolt size, the recess depth and elevation of the main ball. I also need to be able to choose if I use metric or imperial hole saw sizes.

I've seen a few designs out there, including one by braingame26, and while helpful, I need something more tailored to my needs. The basic concept is a main ball resting on smaller ball bearings, all within a recessed pocket and with a flange for mounting.

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

*   **Parametric Design**: Everything should be adjustable using variables.
*   **Ball Diameters**:
    *   A variable for the main ball diameter (D_ONE).
    *   A variable for the secondary ball bearing diameter (D_TWO).
*   **Elevation (ELEV)**: How high the surface of the main ball sits above the recessed surface.
*   **Bolt Size (DRL)**: Should have options for common metric (M3, M4, M5, M6) and imperial (#4, #6, #8, #10) bolt sizes.
*   **Recess Drill Size (R_DRL)**: I need the option to choose between Imperial or Metric standard drill sizes for the pocket.
*   **Bolt Head Coefficient (DRL_HD_COEF)**: A variable to control the size of the countersink for the bolt heads, calculated as a ratio of the bolt head diameter to the thread diameter. For example, M3 with a 6mm head would have a coefficient of 2.
*   **Primary Ball Tolerance (TOL)**: A variable to control the tolerance/gap around the main ball, to allow for slight variations in ball size or print accuracy.
*   **Maximum Flange Thickness (FLANGE_MAX)**: A maximum thickness for the flange part of the design.
*   **Minimum Exposed Ball (BALL_MIN)**:  Minimum distance from the top of the flange to the surface of the ball.
*   **Output Options**: It'd be great if the output could be broken into two modules: `recessed_portion` and `flange_top` in addition to an "assembly" view to see how they all fit together before printing. This will let me print the parts separately.
* **Libraries**: I don't think I need any special libraries for this beyond the built-in OpenSCAD functions.

I'm thinking the design should look something like this:

```
 Diagram:
      ┌                        ╔════╗
 ELEV │                      ┌╔╝    ╚╗┐
      └             ─────┬──/ ║      ║ \──┬──────
        ┌                │  │®╚╗    ╔╝®│  │
        │                │  │® ╚════╝ ®│  │
  depth │                │  \ ®®®®®®®® /  │
        │                │   ──────────   │
        │                │                │
        └                └────────────────┘

        ╔══╗
        ║  ║ = Roller ball (primary)
        ╚══╝
        ® = Ball bearing (secondary)
```

It needs to have the small ball bearings arranged to support the primary ball inside a circular pocket. There should also be recessed holes in the flange to attach the ball transfer to the mounting surface as well as to attach the flange to the recessed part.

I've included some example values below for a real-world use-case, just to give you a better idea:

*   `D_ONE = 12.7;` (0.5 inch)
*   `D_TWO = 4.5;`
*   `ELEV = 8;`
*   `DRL = "M3";`
*   `R_DRL = "imperial";`
*   `DRL_HD_COEF = 2.0;`
*   `TOL = 1.0;`
* `FLANGE_MAX = 3;`
* `BALL_MIN = 2;`

Finally, it would be good to have a report printed at the end of the render that states the drill hole size as well as the output quality settings I'm using.

Thanks!

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

Customizable Recessed Flange-Mount Ball Transfer - Oct 2021 - Wesley T. Honeycutt

LICENSE
This part is licensed as CC4.0-BY, so share/remix/use but be sure to acknowledge your source.
Full text of the license can be found here: https://creativecommons.org/licenses/by/4.0/legalcode.txt

NOTES

  - Assumes zero tolerance spheres (I used G25 BBs just fine).
  - 3D printed recessed ball transfers are probably limited to low capacity, I'm guessing less
  than 100lbs/45.35kg of force on a single transfer.  Experiment at your own risk.  The weak point will likely be the flange.
  - Recessed pocket uses standard hole saw or spade bit sizes
  - Diagram:
      ┌                        ╔════╗
 ELEV │                      ┌╔╝    ╚╗┐
      └             ─────┬──/ ║      ║ \──┬──────
        ┌                │  │®╚╗    ╔╝®│  │
        │                │  │® ╚════╝ ®│  │
  depth │                │  \ ®®®®®®®® /  │
        │                │   ──────────   │
        │                │                │
        └                └────────────────┘

        ╔══╗
        ║  ║ = Roller ball (primary)
        ╚══╝
        ® = Ball bearing (secondary)
*/

// Units are in mm

// Model Quality - This changes the model quality generated by the script.  Normal should be fine for most purposes and meets the resolution of most printers.  **Extreme is truly extreme, only use for large models**
MOD_QUAL = "normal"; // ["debug-low", "normal", "high", "extreme"]

// Whole Assembly or Part? - Do you want to render the entire assembly (not suitable for printing) or one of the individual parts?
PART = "assembly"; // ["assembly", "part1_recessed", "part2_flange"]

// Primary Ball Diameter - This is the size of the large ball which will act as the exposed surface. (all units are in mm unless otherwise stated)
D_ONE = 12.7;

// Secondary Ball Diameter - This is the size of your ball bearings.  "Normal" BBs are d=4.5mm
D_TWO = 4.5;

// Elevation - This value is how high the surface of the ball should sit above the recessed plane
ELEV = 8;

// Bolt Size - This is the size of bolts you will use to both connect the flange to the ball holder AND connect the flange to the mounting surface.
DRL = "M3"; // ["M3", "M4", "M5", "M6", "#4", "#6", "#8", "#10"]

// Drill hole Imperial or Metric? - Select from a set of standard drill sizes for the recessed hole from either imperial or metric standards. (does not effect other measurements)
R_DRL = "imperial"; // ["imperial", "metric"]

// Drill Head Size - This is a coefficient for the ratio of the bolt head diameter vs the thread diameter.  Ex. an M3 with a 6mm head uses a coefficient of 2.0.
DRL_HD_COEF = 2.0;

// Roller Tolerance - Tolerance given to the primary ball for a loose fit.  If your printer is crummy, increase this value.  Too high and the ball might fall out!
TOL = 1.0;

// Conditial drill hole sizing
DSIZE = DRL == "M3" ? 3 :
        DRL == "M4" ? 4 :
        DRL == "M5" ? 5 :
        DRL == "M6" ? 6 :
        DRL == "#4" ? 2.845 :
        DRL == "#6" ? 3.505 :
        DRL == "#8" ? 4.166 :
        DRL == "#10" ? 5.484 : 666;

// Head size of bolts
HSIZE = DRL_HD_COEF * DSIZE;

// Maximum size of the primary and secondary balls
MAXBALL = D_ONE + (2*D_TWO) + TOL;

// Loose Primary Diameter
LOOSEBALL = D_ONE + TOL;

// Radial extension dimension of connecting drill holes
R_EXT = DRL_HD_COEF <= 2 ? (MAXBALL/2) + (1.5*DSIZE) : (MAXBALL/2) + (1.5*HSIZE);

// Radial extension dimension of connecting drill holes
R_MAX = DRL_HD_COEF <= 2 ? (MAXBALL/2) + (3*DSIZE) : (MAXBALL/2) + (3*HSIZE);

// Depth of the recessed hole
R_DEPTH = 1.333*(MAXBALL - ELEV);

// Maximum Flange Thickness - Will throw an error if this is greater than ELEV - 2
FLANGE_MAX = 3;

// Minimum Exposed Ball - The minimum amount of space between the topmost print and the ball surface.
BALL_MIN = 2;

//if (DRL == "M3") {DSIZE = 3;}
//else if (DRL == "M4") {DSIZE = 4;}
//else {echo("FAIL");}

// Rough setting for prototyping
$fa = MOD_QUAL == "debug" ? 10 :
    "normal" ? 1 :
    "high" ? .1 :
    "extreme" ? .01 : 666;
$fs = MOD_QUAL == "debug" ? 10 :
    "normal" ? 1 :
    "high" ? .1 :
    "extreme" ? .01 : 666;
$fn = MOD_QUAL == "extreme" ? 500 : 0;

//$fa=1;
//$fs=1;

//$fa=.1;
//$fs=.1;

FLANGE_HEIGHT = ELEV >= FLANGE_MAX ? FLANGE_MAX : ELEV;
CAP_HEIGHT = ELEV - FLANGE_HEIGHT - BALL_MIN;
MIN_FLANGE_BOT = 1;

error_checking();
if (PART == "assembly" || PART == "part1_recessed") {
    recessed_portion();
}
if (PART == "assembly" || PART == "part2_flange") {
    flange_top();
}
report();

module error_checking() {
    assert(FLANGE_MAX < (ELEV - BALL_MIN), "FLANGE_MAX and/or BALL_MIN are oversized.  Must satisfy FLANGE_MAX < ELEV - BALL_MIN");
    assert(((LOOSEBALL/2) > FLANGE_MAX + BALL_MIN), "Current design does not expose the primary ball.  Change FLANGE_MAX and/or BALL_MIN");
}

module flange_top() {

    difference() {
        // Flange + Cap stack
        union() {
            // Flange
            translate([0, 0, (R_DEPTH/2)+(FLANGE_HEIGHT/2)]) {
                scale([1, 2, 1]) {
                    cylinder(h=FLANGE_HEIGHT, r=R_MAX, center=true);
                }
            }
            // Cap
            translate([0, 0, (R_DEPTH/2)+(FLANGE_HEIGHT/2)+(CAP_HEIGHT/2)]) {
                cylinder(h=CAP_HEIGHT, d=MAXBALL, center=true);
            }
        }
        // Ball
        translate([0, 0, (R_DEPTH/2) + ELEV - (LOOSEBALL/2)]) {
            sphere(d=LOOSEBALL);
        }
        // Connection drill holes + Countersink (flat bottom)
        for(i=[0:3]) {
            rotate([0, 0, 90*i]) {
                translate([R_EXT, 0, 0]) {
                    union() {
                        // Drill hole
                        cylinder(h=10*MAXBALL, d=DSIZE, center=true);
                        // Countersink
                        translate([0, 0, (R_DEPTH/2) + MIN_FLANGE_BOT + (FLANGE_HEIGHT-MIN_FLANGE_BOT)/2]) {
                            cylinder(h=(FLANGE_HEIGHT-MIN_FLANGE_BOT), d=(DSIZE*DRL_HD_COEF), center=true);
                        }
                    }
                }
            }
        }
        // Flange mount drill holes + Countersink (flat bottom)
        for(i=[0:1]) {
            rotate([0, 0, 180*i]) {
                translate([0, R_EXT*2, 0]) {
                    union() {
                        // Drill hole
                        cylinder(h=10*MAXBALL, d=DSIZE, center=true);
                        // Countersink
                        translate([0, 0, (R_DEPTH/2) + MIN_FLANGE_BOT + (FLANGE_HEIGHT-MIN_FLANGE_BOT)/2]) {
                            cylinder(h=(FLANGE_HEIGHT-MIN_FLANGE_BOT), d=(DSIZE*DRL_HD_COEF), center=true);
                        }
                    }
                }
            }
        }
    }
}

module recessed_portion() {
    difference() {
        // Body
        cylinder(R_DEPTH, r=R_MAX, center=true);
        // Connection drill holes
        for(i=[0:3]) {
            rotate([0, 0, 90*i]) {
                translate([R_EXT, 0, 0]) {
                    cylinder(h=10*MAXBALL, d=DSIZE, center=true);
                }
            }
        }
        // Ball and BBs
        translate([0, 0, MAXBALL/2]) {
            sphere(d=MAXBALL);
        }
    }

}

module report() {
    // This module generates a report at the end of the calculation for the user to confirm things.
    echo("~~~~~~~~~~~~~~~~~~~~~~~~ REPORT ~~~~~~~~~~~~~~~~~~~~~~~~");
    echo(
        str(
            "This model is generated with ",
            MOD_QUAL,
            " detail quality based on user settings"
        )
    );
    echo(
       str(
           "All holes designed for close fit ",
           DRL,
           " holes with a nominal diameter of ",
           DSIZE,
           " mm."
           )
       );
    echo(
        str(
            "The recessed pocket requires using a "

        )
    );

   echo("~~~~~~~~~~~~~~~~~~~~~~ END REPORT ~~~~~~~~~~~~~~~~~~~~~~");
}
```


[User]
I'm looking to create a highly customizable flagpole mount using OpenSCAD, and I need some help designing the code. I've got a pretty good idea of what I want, but I'm not sure how to put it all together parametrically.

Basically, I need a mount with a base plate that attaches to a surface, and then a post that holds the flagpole. The key is that everything should be adjustable using parameters. I want to be able to set the plate thickness, the pole inner diameter, the size of the mounting screws, the pivot bolt diameter, the angle of the pole, and a bunch of other stuff.

Here's a breakdown of what I need:

**Base Plate:**

*   **Thickness:** Should be adjustable. I'll probably want to use values like 3mm, 3.5mm, 4mm, 4.5mm, or 5mm.
*   **Corner Radius:**  I want to be able to round the corners, with the radius being adjustable from nearly 0 to at least 20mm.
*   **Extra Plate:** I'd like an option to extend the plate a bit on all sides, up to maybe 5mm.
*   **Mounting Holes:** Needs holes for mounting screws, with adjustable screw diameter and screw head diameter. The depth that the screw is set into the plate should also be adjustable. I'd like an option to bevel the screw holes if that's not too hard to implement.
*   **Bolt Hole:** There should also be a hole for a pivot bolt. The bolt diameter and head diameter should both be adjustable, and I want to be able to select how many sides the head has. It should be able to be set to a standard hex bolt if I want that.

**Post (Pole Holder):**

*   **Inner Diameter:** I need to be able to set the inner diameter of the pole holder; this will match the outer diameter of the flag pole.
*   **Pole Angle:** I need to be able to set the angle of the pole relative to the base plate, say from -80 to +80 degrees.
*   **Tube Length:** The length of the pole holder should be adjustable.
*   **Knurling:** I want a "knurled" interior surface to hold the pole securely. This should be generated as a series of small, angled nibs. I want to be able to select the number of nibs, and perhaps their depth.
*  **Band Ends:** I'd like to include end "bands" on the post, almost like a torus shape that projects a bit around the post, but also allows for a thin, scaled-down torus ring to continue the cylinder shape around the top and bottom.
*   **Band Mid:** Similar to band ends, a smaller "band" that is cylinder-shaped, in the middle of the pole holder. It should have a different height than the band ends, and be centered around a custom parameter.
*   **Retaining Bolt:** I need an option to include a retaining bolt or tab to secure the pole. This should include parameters for the diameter, length, gap, and tab thickness.
*   **Pin:** I need an option to include a radial pin hole, a small hole that goes all the way through the post to secure the pole at a chosen height, with options to set the depth and phase/rotation.
*   **Split:** I also want the ability to "split" the pole holder down the side, using a parameter to specify the depth of the split.
*    **Compression Fit:** I need a method to add compression fit tabs that allow for more secure clamping and more compression force around the pole, with a parameter to adjust the depth.
*    **Quantization:**  I want an option to quantize the pole angle to a fixed increment based on the number of nibs.

I'm imagining a few key modules or functions, like one to create the base plate, one to generate the pole holder, and one to create the knurling pattern.  I think it would be good to have a main module that brings everything together, and an overall 'preview' setting.

I don't really have any code to start with, but I was hoping to get your help to generate the SCAD file(s) from scratch.  I am generally familiar with OpenSCAD, so I can probably tweak it if needed, but I think most of what I need is there.  I think that includes most of the parameters I need to make it really customizable. I am not sure how many SCAD files this would require, and I'm okay with multiple files if it makes the code easier to read.

For the libraries, I think I'd like to use BOSL2 for some of the more advanced features if they're needed. I don't think I'll need anything else, but if you think I do please let me know!

[Assistant]
Flagpole_Mount.scad:
```
  //-----------------------//
 // Flag Pole Holder V1.0 //
//-----------------------//

// Use the OpenScad customizer

/* [Facets] */

$fn= ($preview)? 20: 60;

/* [Extrusion] */

$EC= 0.3;

/* [Body] */
Mode        = 0; // [0:All,1:Base,2:Post]
Plate_Thk   = 3; // [3,3.5,4,4.5,5]
Corner_R    = 4; // [0:.01:20]
Extra_Plate = 0; // [0:5]

Pole_ID   = 25.4;   // [5:40]

/* [Mounting Screws] */
Beveled     = false;

Screw_D     = 4; // [2:.01:4]
ScrewHead_D = 8; // [4:.01:8]
Screw_Inset = 3; // [-2:.01:10]

/* [Pivot Bolt] */
PB_Head    = true;
PB_Fancy   = true;

PB_D      = 5; // [4:.01:12]
PB_Head_D = 7; // [8:.01:24]
PB_Dr     = 1; // [.5:.01:2]
PB_Sides  = 6; // [3:60]
PB_Phase  = 0; // [-90:90]

/* [Retaining Bolt] */
RB_D   =  4; // [3:.01:6]
RB_L   = 14; // [10:20]
RB_Gap =  4; // [1:.01:5]
RB_Thk =  4; // [3:.01:6]

/* [Pole Support] */
Pin       = true;
Split     = true;
CompFit   = true;
Band_End  = true;
Band_Mid  = true;

Quantize_Ang  = true;

Pole_Ang    = 45;   // [-80:80]
Split_Depth = .5;   // [0:.01:1]
Split_Phase = 45;   // [0:90]
Tube_Len    = 76.2; // [10:100]

Pin_Depth = .5;   // [0:.01:1]
Pin_Phase = 45;   // [-90:90]

NIB_H     = .3;   // [.2:.01:2]
NibNum    = 16;   // [8:20]

Band_Mid_H   = 20;   // [0:26]

Post_D = Pole_ID+Plate_Thk*2;

Post_Pos = [0,0,Post_D/2+Plate_Thk+5];

Plate_X=Post_D/2+Plate_Thk/2+14;
//Plate_Y=Post_D/2+Plate_Thk/2+14;

Corner = (Plate_X+Extra_Plate-Corner_R)*[1,1];

t = Plate_X-(Corner_R*(1-.707)+Screw_D/2+Screw_Inset);
Screw_Corner = [t,t];

if (Mode <= 1) {
   difference() {
      Body();
      Holes();
   }
}

if (Mode == 0 || Mode == 2) Pole();

module MirrorCopy(M) {
   children();
   mirror(M) children();
}

module Torus(R,r,ang=360) {
   rotate_extrude(angle=ang)
   translate([R,0])
   children();

}

module Holes() {

   MirrorCopy([1,0])
   MirrorCopy([0,1])
   translate(Screw_Corner) {
      cylinder(d=Screw_D+$EC,h=Plate_Thk*3,center=true);
      if (Beveled)
      translate([0,0,Plate_Thk-(ScrewHead_D-Screw_D)/2])
      cylinder(d1=Screw_D+$EC,d2=ScrewHead_D+$EC,h=(ScrewHead_D-Screw_D)/2+.01);
   }

   translate(Post_Pos)
   Bolt_Hole();
}

module Bolt_Hole() {

   rotate([0,90])
   cylinder(d=PB_D+$EC,h=200,center=true);
}

module Pole() {
   Ang = (Quantize_Ang)? floor(Pole_Ang*NibNum/360)*360/NibNum: Pole_Ang;

   // Generate Cross Support
   translate(Post_Pos)
   rotate([($preview)? -Ang: 0,0]) Body();

   // Generate positive Pole support
   module Body() {
      difference() {
         union() {
            cylinder(d=Post_D,h=Tube_Len);

            if (Band_End)
            translate([0,0,Tube_Len-Plate_Thk])
            Torus(R=(Post_D)/2,r=Plate_Thk)
            scale([.25,1])
            circle(Plate_Thk);

            if (Band_Mid)
            translate([0,0,Tube_Len-Band_Mid_H])
            cylinder(d=Post_D+Plate_Thk,h=2,center=true);

            rotate([0,90])
            NIB_Pad(Cyl_R=Post_D/2,Cyl_L=Post_D+2,NIBS=NibNum);

            if (CompFit) Tabs();

         }
         Post_Holes();
      }
   }

   module Tabs() {
      D = 15;

      rotate(Split_Phase)
      MirrorCopy([1,0])
      translate([RB_Gap/2,-Pole_ID+Plate_Thk-RB_L+Pole_ID/2,Tube_Len-D/2])
      rotate([0,90]) {
         linear_extrude(RB_Thk,convexity=4)
         difference() {
            union() {
               translate([-D/2,0])
               square([D,RB_L-Plate_Thk/2]);
               circle(d=D);
            }
            circle(d=RB_D+$EC);
         }
      }
   }

   module Post_Holes() {
      L = (Pole_ID+Plate_Thk)*2;

      Bolt_Hole();

      // Pole Hole
      translate([0,0,Post_D/2]) {
         cylinder(d=Pole_ID,h=Tube_Len);
         translate([0,0,.01])
         mirror([0,0,1])
         cylinder(d1=Pole_ID+$EC,d2=0,h=5);
      }

      // Drain hole
      translate([0,0,Post_D/2])
      rotate([135,0])
      translate([0,0,-Post_D/2+5])
      cylinder(d=Post_D/4,h=Tube_Len);

      translate([-Pole_ID/8,-Pole_ID/2,5])
      cube([Pole_ID/4,Pole_ID/2,Pole_ID]);

      // Side slices
      if (Split)
         rotate(Split_Phase) {
         translate([-L/2,-1,Tube_Len*(1-Split_Depth)+.01])
         cube([L,2,Tube_Len]);
         translate([-1,-L/2,Tube_Len*(1-Split_Depth)+.01])
         cube([2,L,Tube_Len]);
      }

      // Compression cleft
      if (CompFit)
      rotate(Split_Phase)
         translate([-RB_Gap/2,-L,Tube_Len*(1-Split_Depth)])
         cube([RB_Gap,L,Tube_Len]);

      // Retaining pin
      if (Pin)
      translate([0,0,Tube_Len*(1-Pin_Depth)])
      rotate([0,90,Pin_Phase])
      cylinder(d=5,h=L,center=true);
   }

}

module Body() {

   linear_extrude(Plate_Thk)
   hull()
   MirrorCopy([1,0])
   MirrorCopy([0,1])
   translate(Corner) circle(r=Corner_R);

   MirrorCopy([1,0])
   translate([Post_D/2+3,0,Post_Pos.z])
   rotate([0,90])
   NIB_Pad(Cyl_R=Post_D/2,Cyl_L=3.4,NIBS=NibNum,NIB_Phase=1,Top=true);

   MirrorCopy([1,0]) {
      translate([Post_D/2+4,-Post_D/2])
      cube([Plate_Thk,Post_D,Post_Pos.z]);

      translate([Post_D/2+Plate_Thk/2+4,0,Plate_Thk-.01])
      linear_extrude(Plate_Thk+5,scale=[1-2/3,(Post_D/(Post_D+5))])
      square([Plate_Thk*3,Post_D+5],center=true);

      translate([Post_D/2+4,0,Post_Pos.z])
      rotate([0,90])
      cylinder(d=Post_D,h=Plate_Thk);
   }

   if (PB_Head)
   translate([Plate_Thk+Post_D/2+4,0,Post_Pos.z])
   rotate([0,90])
   difference() {
      linear_extrude(Plate_Thk,scale=.8,convexity=5)
      rotate(PB_Phase) {
         circle(d=PB_Head_D*(1+PB_Dr),$fn=PB_Sides);
         if (PB_Fancy)
            Rotate_Copy(3,120)
            square([PB_Head_D*2.4,PB_Head_D/2],center=true);
      }
      cylinder(d=PB_Head_D+$EC,h=Plate_Thk+1,$fn=6);
   }
}

module Rotate_Copy(n,ang=30) {
   for (a = [0:n-1]) rotate(ang*a) children();
}

//--------------------------------------------------------------
// Generate a circular NIB_PAD
//
// NIB_Pad(Cyl_R , Cyl_H , n, Top , Bot , NIB_H , NIB_Step)
//
// Call with Cyl_R   = Cylinder Radius
//           Cyl_H   = Cylinder Height
//           Cyl_H   = Cylinder Height
//           Nibs    = # of nibs
//           Hole_D  = Diameter of interior hole
//           Top     = true = Nibs on top of cylinder
//           Bot     = Nibs on bottom of cylinder
//           NIB_h   = Fractional Nib Height
//
module NIB_Pad(Cyl_R=10 , Cyl_L=2 , NIBS=NibNum, NIB_H=NIB_H , NIB_Phase=0, Top=false , Bot=false ) {

   D_N2      = 2*NIBS;
   c_rad     = 1.006*PI*Cyl_R/D_N2;
   stp_ang   = 360/D_N2;

   rotate([0,0,NIB_Phase * stp_ang])
   intersection() {
      cylinder(r=Cyl_R,h=Cyl_L+50,center=true);

      difference() {

         union() {

            difference() {
               cylinder(r=Cyl_R,h=Cyl_L,center=true);

               if (Bot==false) NIBS(0,-Cyl_L);               // Evem NIBS
               if (Top==false) NIBS(0,Cyl_L);
            }

            if (Bot==false) NIBS(1,-Cyl_L);                  // Odd NIBS
            if (Top==false) NIBS(1,h=Cyl_L);

         }

      }

   }

//---------------------
// Generate Even NIBS
//
   module NIBS(si=0,h) {
      translate([0,0,h/2])
      scale([1,1,NIB_H/c_rad]) {
         for (i=[si:2:D_N2-1]) {
            rotate([0,90,i*stp_ang])
            cylinder(r1=0,r2=c_rad,h=Cyl_R);
         }
      }
   }

}

```