[User]
Hi, I'm looking to generate a customizable model of the Great Pyramid of Giza in OpenSCAD, similar to the design I saw on Thingiverse by Sarah Cartwright. I need a parametric design where I can adjust a few key things like the number of brick courses, the overall size of the pyramid, the level of "jiggle" or randomness in the placement of the bricks, and how many bricks appear to be missing. I'd like to have entrances on each side of the pyramid that can be toggled on or off. I also need a base that can be toggled on or off.

Here are the specific parameters I want to control:

*   **Number of Brick Courses:**  This will determine how many layers of bricks the pyramid has. The original pyramid has 210, but I recognize that rendering that many might be too much so I need to have a reasonable way to lower this number.
*   **Size Multiplier:** This will scale the entire model up or down, effectively changing the size of the bricks and the overall footprint of the pyramid.
*   **Brick Jiggle:** This will control how much random rotation is applied to each brick, simulating the imperfections of hand-laid stone.
*   **Missing Brick Factor:** This parameter will influence the probability of bricks being missing, with the lower layers losing bricks faster than the top.
*   **Make Front Entrance, Make Back Entrance, Make Left Entrance, Make Right Entrance:** These will be boolean flags (on/off) to create openings on each face of the pyramid.
*  **Draw Ground:** A boolean flag to add a base to the pyramid.

The pyramid should be built from individual stone pieces, and each piece should be placed in a way that it looks like actual masonry. The overall shape should match the dimensions of the Great Pyramid (a base of 440 cubits and a height of 280 cubits). I don't want to worry about cubits though, I'd like everything to be relative to the scaling factor.

I need this to be done in pure OpenSCAD; I don't want to use any additional libraries. I hope this makes sense, let me know if you have questions.

[Assistant]
Customizable_GreatPyramidMaker.scad:
```
// Great Pyramid Maker.
// Sarah Cartwright
// 2019/08/19
// Thingverse: "Customizable GreatPyramidMaker.scad"

// Side are 440 cubits long, and the height is 280 cubits high.
// https://www.cheops-pyramide.ch/khufu-pyramid/stonecourses-pyramid.html

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

/* [Pyramid size] */
// The number of levels of bricks in the pyramid. 210 is the number in the gret pyramid - OpenSCAD can't currently render it! (110 is the current maximum.)
Number_of_brick_courses = 7; //[3:210]
// As courses are added, the bricks are shrunk so the model stays the same size. This makes the model bigger if the bricks end up too tiny.
Size_multiplier = 1; //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100]
/* [Build accuracy and wear] */
// More jiggle makes the sides less smooth, and the bricks more visible.
Brick_jiggle = 3; //[0:0.5:10]
// The lower the number, the more bricks are missing - the lower area looses bricks faster then the top.
Missing_brick_factor = 3; //[0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1,1.5,2,2.5,3,3.5,4,4.5,5,5.5,6,6.5,7,7.5,8,8.5,9,9.5,10,12,14,16,18,20,25,30,35,40,45,50,500]

/* [Entrances and ground] */
// Create entrances for scale. The first course of brick on the Great Pyramid is 150cm high, therefore 6 foot is over 1 brick high.
Make_front_entrance = 1; //[0:No,1:Yes]
Make_back_entrance = 1; //[0:No,1:Yes]
Make_left_entrance = 1; //[0:No,1:Yes]
Make_right_entrance = 1; //[0:No,1:Yes]
//Draw a base for the pyramid to sit on.
Draw_ground = 1; //[0:No,1:Yes]

//////////////////////////////////////////////////////////////////////////////////////
// GLOBAL PARAMETERS - Do not change.
/* [Hidden] */
//Dimension ratio of the real pyramid.
pyramidBase = Size_multiplier * 440;
pyramidHeight = Size_multiplier * 280;
brickHeight = pyramidHeight / Number_of_brick_courses;
brickBase = pyramidBase / Number_of_brick_courses;

module cap() {
    translate([-brickBase / 2, -brickBase / 2, pyramidHeight - brickHeight]) {
        polyhedron(
            points=[[0, 0, 0], [brickBase , 0, 0], [0, brickBase, 0], [brickBase / 2, brickBase / 2, brickHeight], [brickBase, brickBase, 0]],
            faces=[[0, 1, 4, 2], [0, 3, 1], [0, 2, 3], [3, 4, 1], [2, 4, 3]]
        );
    }
}

module zigurat(n) {
    if(n<= 1){
        cube([brickBase, brickBase, brickHeight], center = true);
    } else {
        cube([n*brickBase, n*brickBase, brickHeight], center = true);
        translate([0, 0, brickHeight])
            zigurat(n-1);
    }
}

module edgeStone(width, length, height, center = false){
    centroid = center ? [-length / 2, -width, 0] : [0, 0, 0];
    translate(centroid)
        polyhedron(
            points=[[0,0,0], [length ,0,0], [length, width, 0], [0, width, 0], [0, width, height], [length, width, height]],
            faces=[[0,1,2,3],[5,4,3,2],[0,4,5,1],[0,3,4],[5,2,1]]
        );
}

module sideStoneFlat(width, length, height, center = false) {
    centroid = center ? [0, length / 2, 0] : [0, 0, 0];
    translate(centroid)
        rotate([0, 0, 180])
            polyhedron(
                points=[[0, 0, 0], [width , 0, 0], [0, 0, length], [0, height, 0]],
                faces=[[2, 1, 0], [3, 2, 0], [0, 1, 3], [1, 2, 3]]
            );
}

module sideStoneAngle(width, length, height, center = false) {
    centroid = center ? [-width / 2, -length / 2, 0] : [0, 0, 0];
    translate(centroid)
        polyhedron(
            points=[[0, 0, 0], [width , 0, 0], [0, length, 0], [0, 0, height], [width, length, 0]],
            faces=[[0, 1, 4, 2], [0, 3, 1], [0, 2, 3], [3, 4, 1], [2, 4, 3]]
        );
}

module makeEdges(Missing_brick_factor) {
    for(l=[0:1:Number_of_brick_courses-2]) {
        if(rands(0, Number_of_brick_courses, 1)[0] < (l+1) * Missing_brick_factor)
            translate([(-pyramidBase/2) + (l*(brickBase/2)) + (brickBase/2), (-pyramidBase/2)+(brickBase/2)-(-brickBase*l/2), (l-1)*brickHeight + brickHeight])
                rotate([0, 0, 180]) rotate(rands(-Brick_jiggle, Brick_jiggle, 1)) sideStoneAngle(brickBase/2, brickBase/2, brickHeight);
        if(rands(0, Number_of_brick_courses, 1)[0] < (l+1) * Missing_brick_factor)
            translate([(-pyramidBase/2) + (l*(brickBase/2)) + (brickBase/2), (pyramidBase/2)-(brickBase/2)+(-brickBase*l/2), (l-1)*brickHeight + brickHeight])
                rotate([0, 0, 90]) rotate(rands(-Brick_jiggle, Brick_jiggle, 1)) sideStoneAngle(brickBase/2, brickBase/2, brickHeight);

    }
    for(l=[0:1:Number_of_brick_courses-2]) {
        if(rands(0, Number_of_brick_courses, 1)[0] < (l+1) * Missing_brick_factor)
            translate([(+pyramidBase/2) - (l*(brickBase/2)) - (brickBase/2), (-pyramidBase/2)+(brickBase/2)-(-brickBase*l/2), (l-1)*brickHeight + brickHeight])
                rotate([0, 0, 270]) rotate(rands(-Brick_jiggle, Brick_jiggle, Brick_jiggle)) sideStoneAngle(brickBase/2, brickBase/2, brickHeight);
        if(rands(0, Number_of_brick_courses, 1)[0] < (l+1) * Missing_brick_factor)
            translate([(+pyramidBase/2) - (l*(brickBase/2)) - (brickBase/2), (pyramidBase/2)-(brickBase/2)+(-brickBase*l/2), (l-1)*brickHeight + brickHeight])
                rotate([0, 0, 0]) rotate(rands(-Brick_jiggle, Brick_jiggle, 1)) sideStoneAngle(brickBase/2, brickBase/2, brickHeight);
    }
}

module makeFrontSide(Missing_brick_factor, makeDoor) {
    for(l=[0:1:Number_of_brick_courses-2])
        translate([(-pyramidBase/2) + (l*(brickBase/2)), (-pyramidBase/2)+(brickBase)-(-brickBase*l/2), (l-1)*brickHeight + brickHeight])
            for(z=[0:1:2*(Number_of_brick_courses-l)-3])
                if(rands(0, Number_of_brick_courses, 1)[0] < (l+1) * Missing_brick_factor)
                    translate([0,z*brickBase/2,0])
                        rotate([0, 0, 270]) rotate(rands(-Brick_jiggle, Brick_jiggle, 1))  edgeStone(brickBase/2, brickBase/2, brickHeight);
}

module makeBackSide(Missing_brick_factor) {
    for(l=[0:1:Number_of_brick_courses-2])
        translate([(+pyramidBase/2) - (l*(brickBase/2)), (-pyramidBase/2)+(brickBase/2)-(-brickBase*l/2), (l-1)*brickHeight + brickHeight])
            for(z=[0:1:2*(Number_of_brick_courses-l)-3])
                if(rands(0, Number_of_brick_courses, 1)[0] < (l+1) * Missing_brick_factor)
                    translate([0,z*brickBase/2,0])
                        rotate([0, 0, 90]) rotate(rands(-Brick_jiggle, Brick_jiggle, 1)) edgeStone(brickBase/2, brickBase/2, brickHeight);
        }

module makeLeftSide(Missing_brick_factor) {
    for(l=[0:1:Number_of_brick_courses-2])
        translate([(+pyramidBase/2) - (l*(brickBase/2)) - brickBase, (-pyramidBase/2)-(-brickBase*l/2), (l-1)*brickHeight + brickHeight])
            for(z=[0:1:2*(Number_of_brick_courses-l)-3])
                if(rands(0, Number_of_brick_courses, 1)[0] < (l+1) * Missing_brick_factor)
                    translate([-z*brickBase/2,0,0])
                        rotate([0, 0, 0]) rotate(rands(-Brick_jiggle, Brick_jiggle, 1))  edgeStone(brickBase/2, brickBase/2, brickHeight);
}

module makeRightSide(Missing_brick_factor) {
    for(l=[0:1:Number_of_brick_courses-2])
        translate([(+pyramidBase/2) - (l*(brickBase/2)) - (brickBase / 2), (+pyramidBase/2)+(-brickBase*l/2), (l-1)*brickHeight + brickHeight])
            for(z=[0:1:2*(Number_of_brick_courses-l)-3])
                if(rands(0, Number_of_brick_courses, 1)[0] < (l+1) * Missing_brick_factor)
                    translate([-z*brickBase/2, 0, 0])
                        rotate([0, 0, 180]) rotate(rands(-Brick_jiggle, Brick_jiggle, 1))  edgeStone(brickBase/2, brickBase/2, brickHeight);
}

module makeDoor(){
    difference() {
        translate([(-pyramidBase/2), -brickBase/2, 0])
            cube([brickBase*2, brickBase, brickHeight+(brickHeight/1.5)], center=false);
        translate([(-pyramidBase/2)-1, (-brickBase/2)+(brickBase/4), 0])
            cube([brickBase*2,brickBase/2,brickHeight+(brickHeight/3)], center=false);
    }
}

module makeDoorHole(){
    translate([(-pyramidBase/2), -brickBase/2, 0])
        cube([brickBase*2, brickBase, brickHeight+(brickHeight/1.5)], center=false);
}

module makePyramid() {
    difference() {
        union() {
            difference() {
                union() {
                    color([1,1,0], 1) translate([0, 0, brickHeight/2]) zigurat(Number_of_brick_courses-1);

                    color([1,1,0], 1) makeFrontSide(Missing_brick_factor, makeDoor=1);
                    color([1,1,0], 1) makeBackSide(Missing_brick_factor);
                    color([1,1,0], 1) makeLeftSide(Missing_brick_factor);
                    color([1,1,0], 1) makeRightSide(Missing_brick_factor);

                    color([1,1,0], 1) makeEdges(Missing_brick_factor);
                    color([1,1,0], 1) cap();
                }
                if(Make_front_entrance == 1) makeDoorHole();
                if(Make_back_entrance == 1) rotate([0,0,180]) makeDoorHole();
                if(Make_left_entrance == 1) rotate([0,0,270]) makeDoorHole();
                if(Make_right_entrance == 1) rotate([0,0,90]) makeDoorHole();
            }
            if(Make_front_entrance == 1) makeDoor();
            if(Make_back_entrance == 1) rotate([0,0,180]) makeDoor();
            if(Make_left_entrance == 1) rotate([0,0,270]) makeDoor();
            if(Make_right_entrance == 1) rotate([0,0,90]) makeDoor();
        }
        // Flatten the base (if the ground is removed.)
        translate([0,0,(brickHeight / -2) + 2])
            cube([brickBase * (Number_of_brick_courses + 2), brickBase * (Number_of_brick_courses + 2), brickHeight], center = true);
    }
    // Add ground.
    if(Draw_ground == 1)
        translate([0,0,(brickHeight / -2) + 2])
            cube([brickBase * (Number_of_brick_courses + 2), brickBase * (Number_of_brick_courses + 2), brickHeight], center = true);
}

makePyramid();

```


[User]
Hey, I'm looking to get a custom sundial designed, specifically a square one like the attached "Sunclock_Square.scad" file, but I need it to be fully parametric so I can easily adjust all of the dimensions and locations, plus switch between Roman and Arabic numerals.

The core idea is a flat, square-ish base with a frame around it.  There's a gnomon (a spike) that sticks out to cast a shadow, and the time is marked by lines and numbers on the base. The file I've included gives a lot of the details, but it needs a complete redesign for more flexibility.

Specifically, I need to be able to control:

*   **Location:** Latitude, Longitude, and Time Zone (offset from GMT) should all be parameters. I want to enter these as degrees (or hours for time zones) and have everything calculate correctly.
*   **Text:** The sundial should have a name printed on it. I need to control:
    *   The text string itself.
    *   A boolean switch to toggle between using Roman numerals and standard Arabic numerals (1,2,3...).
    *   The width of the text panel/holder at the bottom of the sundial.
    *   The distance of the text from the frame.
    *   The size and height of the extruded text.
*   **Base Dimensions:** I need parameters for:
    *   Frame thickness
    *   Base thickness
    *   Left edge length
    *   Right edge length
    *   Top edge length
    *   Bottom edge length
    *   Margin around the base

* **Numeral Placement:** The hour lines and text should be calculated based on the location and the geometry of the sundial, following the logic used in the attached file.
    * It needs to draw lines out to where the numbers/hour markers are around the edges of the sundial.
    * I need to be able to set the offset of the text from that edge of the sundial.
    * I need to be able to set the size and height of the extruded text

The design logic from the provided file is a good starting point, but please make sure it's a clean, well-organized, modular parametric design. I'm not looking to recreate that file, but you can use the math it uses. Please use proper comments throughout. You can also use the `linear_extrude` function to create the 3D text.

Also, please generate the SCAD files, and do not use any libraries. I'm familiar with how to make these files but I'm not quite adept enough to write all this from scratch.

[Assistant]
Sunclock_Square.scad:
```
/* [Location] */
//Paste this in from a webpage that calculates it for a given location.
Latitude = 53.1934;
//Paste this in from a webpage that calculates it for a given location.
Longitude = 2.8931;
// Offset from GMT in hours
Time_zone = 0;

/* [Text] */
// Name to put on the panel.
Name = "Chester, UK";
// Select numbers, or letters.
Use_Roman_Numerals = 0; //[0:No,1:Yes]
// The width of the Bottom name panel.
Text_holder_width = 45;//[0:1:150]
// Distance of the text from the frame
Text_offset = 5.5;//[0.1:0.1:10]
// Text size
Text_size = 5;//[0.1:0.1:10]
// How tall to make the text.
Text_height = 1.5;//[0.1:0.1:5]

/* [Base dimensions] */
Frame_thickness = 1.5;//[0.1:0.1:5]
Base_thickness = 3;//[2:0.1:10]
Left_ = 21;//[10:1:100]
Right = 21;//[10:1:100]
Top = 40;//[10:1:100]
Bottom_ = 15;//[10:1:100]
Margin = 22;//[0:1:50]

//#######################################################
/* [Hidden] */

// From 12 noon anti-clockwise, 12 values
AM_Roman_numerals = (Use_Roman_Numerals == 1)?
    ["XII", "XI", "X", "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I"]
    :
    [str("12"), str("11"), str("10"), str("9"), str("8"), str("7"), str("6"), str("5"), str("4"), str("3"), str("2"), str("1")];
// From 1pm clockwise, 12 values
PM_Roman_numerals = (Use_Roman_Numerals == 1)?
    ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"]
    :
    [str("1"), str("2"), str("3"), str("4"), str("5"), str("6"), str("7"), str("8"), str("9"), str("10"), str("11"), str("12")];

frameBaseHeight = -0.8;

Left = -Left_;
Bottom = -Bottom_;

LongitudeTimezoneFixed = -Longitude + (Time_zone * 15.0);

bl = [Left, Bottom];
tl = [Left, Top];
tr = [Right, Top];
br = [Right, Bottom];
box = [bl, tl, tr, br];

blA = (360 - atan(Left / Bottom)) % 360;
tlA = (180 - atan(Left / Top)) % 360;
trA = (180 - atan(Right / Top)) % 360;
brA = (360 - atan(Right / Bottom)) % 360;
angles = [blA, tlA, trA, brA];

additionalHoursOnChart = (Latitude <= 29) ? 0 :
                         (Latitude > 29 && Latitude <= 48) ? 1 :
                         (Latitude > 48 && Latitude <= 57) ? 2 :
                         (Latitude > 57) ? 3 : 0;

// Frame
color([.4, .4, .4])
    translate([0, 0, Base_thickness + frameBaseHeight])
        drawFrame(box, Frame_thickness);

// Base
color([.6, .6, .6])
    translate([(-Left-Right) / -2, (-Top-Bottom) / -2, Base_thickness / 2])
        cube([Right-Left + Margin, Top-Bottom + Margin, Base_thickness], center=true);

// Gnomon (spike)
color([.5, .5, .5])
    translate([0, 0, 1 + Base_thickness])
        gnomonCylinder(Latitude, Frame_thickness);

// Text
translate([0, (Margin / -2) -2, 0]){

    color([.7, .7, .7])
        translate([0, Bottom - 1, (Base_thickness / 1.5) / 2.0])
            cube([Text_holder_width, 10, Base_thickness / 1.5], center=true);

    color([.1, .1, .1])
        translate([0, Bottom - 1.5, Base_thickness / 1.5])
            doText(Name, 5, Text_height);

}

// Midday hours
for(hour = [0 : 6]){
    makeLineAndText(hour, LongitudeTimezoneFixed, Latitude, angles, Top, Bottom, Left, Right, frameBaseHeight, Base_thickness);
}

// Morning / evening hours
for(hour = [6 : 7 + additionalHoursOnChart]){
    makeEarlyLineAndText(hour, LongitudeTimezoneFixed, Latitude, angles, Top, Bottom, Left, Right, frameBaseHeight, Base_thickness);
}

module makeLineAndText(hour, LongitudeTimezoneFixed, Latitude, angles, Top, Bottom, Left, Right, frameBaseHeight, baseThickness){
    AMHourAngle = findHourAngleAM(hour, LongitudeTimezoneFixed, Latitude);
    AMHourText = AM_Roman_numerals[hour];
    drawClockLine(AMHourAngle, AMHourText, frameBaseHeight, baseThickness);
    if(hour > 0){
        PMHourAngle = findHourAnglePM(hour, LongitudeTimezoneFixed, Latitude);
        PMHourText = str(PM_Roman_numerals[hour-1]);
        drawClockLine(PMHourAngle, PMHourText, frameBaseHeight, baseThickness);
    }
}

module makeEarlyLineAndText(hour, LongitudeTimezoneFixed, Latitude, angles, Top, Bottom, Left, Right, frameBaseHeight, baseThickness){
    AMHourAngle = findEarlyHourAngleAM(hour, LongitudeTimezoneFixed, Latitude);
    AMHourText = str(AM_Roman_numerals[hour]);
    drawClockLine(AMHourAngle, AMHourText, frameBaseHeight, baseThickness);

    PMHourAngle = findEarlyHourAnglePM(hour, LongitudeTimezoneFixed, Latitude);
    PMHourText = PM_Roman_numerals[hour-1];
    drawClockLine(PMHourAngle, PMHourText, frameBaseHeight, baseThickness);
}

module drawClockLine(hourAngle, hourText, frameBaseHeight, baseThickness){
    perimeterCoord = cordsFromAngle(hourAngle, angles, Top, Bottom, Left, Right);
    color([1, .5, .5])
        translate([0, 0, frameBaseHeight + baseThickness])
            line([0, 0], perimeterCoord, Frame_thickness);
    pos2 = cordsFromAngle(hourAngle, angles, Top + Text_offset, Bottom - Text_offset, Left - Text_offset, Right + Text_offset);
    color([1, .5, .5])
        translate(pos2)
            translate([0, 0, baseThickness - 0.1])
                doText(text = hourText, size = Text_size, thickness = Text_height);
}

module drawFrame(box, height){
    line(box[0], box[1], height);
    line(box[1], box[2], height);
    line(box[2], box[3], height);
    line(box[3], box[0], height);
}

module line(p1, p2, height){
    $fn = 10;
    hull(){
        hull(){
            translate(p1) sphere(1);
            translate(p2) sphere(1);
        }
        hull(){
            translate([0, 0, height]){
                translate(p1) sphere(1);
                translate(p2) sphere(1);
            }
        }
    }
}

module doText(text, size, thickness){
    linear_extrude(height = thickness, center = false, convexity = 10){
        text(text = text,
            size = size,
            font = "Arial",
            halign = "center",
            valign = "center",
            spacing = 1,
            direction = "ltr",
            language="en",
            script="latin",
            $fn = 60
        );
    }
}

module gnomonCylinder(lat, height){
    translate([0, 2.5, height/2 - 0.3]) cube([2, 6, height], center=true);
    translate([0, 0, height - 0.5]){
        difference(){
            rotate([270 + lat, 0, 0]){
                hull(){
                    cylinder($fn=20, r=1, h=40);
                    translate([0, 5, -10]) rotate([7, 0, 0]) cylinder($fn=20, r=1, h=40);
                }
            }
            translate([0, 0, -100]) cube([200, 200, 200], center=true);
        }
    }
}

function quadrantFromAngle(angle, angles) =
    let(quarter =
        (angle > angles[0] || angle < angles[3]) ? 0 :
        (angle > angles[1] && angle < angles[0]) ? 1 :
        (angle > angles[2] && angle < angles[1]) ? 2 :
        (angle > angles[3] && angle < angles[2]) ? 3 : undef
    )
    quarter;

function cordsFromAngle(angle, angles, Top, Bottom, Left, Right) =
    let(cor =
        (quadrantFromAngle(angle, angles) == 0)?
            [Bottom / tan(90 - (360 - angle)), Bottom] :
        (quadrantFromAngle(angle, angles) == 1)?
            [Left,  Left / tan(360 - angle) ] :
        (quadrantFromAngle(angle, angles) == 2)?
            [Top / tan(90 - (360-angle)), Top] :
        (quadrantFromAngle(angle, angles) == 3)?
            [Right,  Right / tan(360 - angle) ] : undef
    ) cor;

function findHourAngleAM(hour, LongitudeTimezoneFixed, Latitude) =
     (180 + (atan(tan((hour * 15) + LongitudeTimezoneFixed) * sin(Latitude)))) % 360;

function findHourAnglePM(hour, LongitudeTimezoneFixed, Latitude) =
     (180 - (atan(tan((hour * 15) - LongitudeTimezoneFixed) * sin(Latitude)))) % 360;

function findEarlyHourAngleAM(hour, LongitudeTimezoneFixed, Latitude) =
     (360 + (atan(tan((hour * 15) + LongitudeTimezoneFixed) * sin(Latitude)))) % 360;

function findEarlyHourAnglePM(hour, LongitudeTimezoneFixed, Latitude) =
     (360 - (atan(tan((hour * 15) - LongitudeTimezoneFixed) * sin(Latitude)))) % 360;

```


[User]
I'd like a parametric OpenSCAD design for the Deathly Hallows, based on the description and the provided file `Deathly_Hallows_v2.scad`. I want to be able to adjust the overall size, the thickness of the lines used to form the triangle, and the depth of the pyramid.

Here's a breakdown of the key elements and how I'd like them to be controlled:

*   **Overall Size:** I want a primary parameter for the height of the pyramid. Everything else should scale accordingly from this. I expect that if I set the pyramid height to 90, it should look like the provided example file in terms of relative proportions.

*   **Pyramid:** The pyramid is the triangular frame part of the Hallows symbol. I would like a parameter to control the depth or extrusion of this pyramid, expressed as a percentage of the pyramid's height. This parameter should scale all the sides equally so that it doesn't look distorted. The triangular shape should be composed of lines, not a solid pyramid. I need a parameter to control the thickness of these lines.

*  **Sphere:** The sphere is centered within the triangle. It should have a line thickness matching that of the pyramid's lines, so I'd like it to be derived from that thickness parameter. The internal hollow sphere should be offset from the outer sphere by an amount equal to line thickness, creating a shell.

*   **Spire:** The spire is made of two parts: a cylinder at the bottom and a cone at the top. The bottom cylinder's diameter should match the line thickness of the pyramid. The cylinder's height should equal the diameter of the sphere, and the cylinder will be placed such that it extends in both directions from center of the sphere. The top cone should start from the cylinder's top diameter, reducing to a point. The height of the cone should be a portion of the pyramid height, and I'd like that to be computed based on the position of the sphere.

*   **Parametric Control:**  I need parameters for:
    *   `pyramidHeight` (adjusts the overall size of the model)
    *   `spireThicknessPct` (adjusts the line thickness of the spire as a percentage of the lineThickness)
	*   `depthPct` (adjusts the extrusion of the pyramid, as a percentage of `pyramidHeight`).
	*	`resolution` (controls the resolution of circle rendering)

The design should look similar to the image generated by the `Deathly_Hallows_v2.scad` file. The core structure of the file should be to use the parameters to generate a difference operation between a union of the three Deathly Hallows elements (pyramid, sphere, and spire) with a subtraction object to cut away extra parts so that the overall shape and positioning of the elements are correct. This subtraction object should also be mirrored so the object is bilaterally symmetrical.

I do not expect you to use any specific libraries, but please note that I have the capacity to use BOSL, BOSL2, MCAD, dotSCAD, NopSCADlib, and BOLTS if appropriate.

Thanks!

[Assistant]
Deathly_Hallows_v2.scad:
```
/* [1 - Model] */
// Pyramid Height
pyramidHeight = 90;  // [30:15:300]
resolution = 150;

// Spire Thickness (% of Line Thickness)
spireThicknessPctPar = 100;
spireThicknessPct = spireThicknessPctPar / 100;

// Pyramid Depth (% of pyramidHeight)
depthPctPar = 45; // [5:5:100]
depthPct = depthPctPar / 100;

$fn = resolution;
lineThickness = pyramidHeight / 15;
spireThickness = lineThickness * spireThicknessPct;
pyramidSideLength = (2 * pyramidHeight) / sqrt(3);
pyramidDepth = pyramidHeight * depthPct;
modelRadius = pyramidSideLength * (sqrt(3) / 3);
circleRadius = (pyramidSideLength * sqrt(3)) / 6;
circleCutout = circleRadius - lineThickness;
modelZOffset = circleRadius;
cylinder2Height = pyramidHeight - (circleRadius * 2);

difference()
{
union()
	{
		pyramid();
		hollowSphere();
		spire();
	}
	cutout();
	mirror([0,1,0])
		cutout();
}
report();

module cutout()
{
	points = [
		[pyramidSideLength/2,pyramidDepth/2,0],
		[pyramidSideLength/2,0,pyramidHeight],
		[-(pyramidSideLength/2),0,pyramidHeight],
		[-(pyramidSideLength/2),pyramidDepth/2,0],
		[-(pyramidSideLength/2),circleRadius,0],
		[pyramidSideLength/2,circleRadius,0],
		[pyramidSideLength/2,circleRadius,pyramidHeight],
		[-(pyramidSideLength/2),circleRadius,pyramidHeight]
	];

	paths = [
		[0,1,2,3],
		[0,3,4,5],
		[0,5,6,1],
		[1,6,7,2],
		[2,7,4,3],
		[5,4,7,6]
	];

	polyhedron(points=points,faces=paths);
} // End cutout()

module pyramid()
{
	translate([0,pyramidDepth/2,modelZOffset])
		rotate([0,-90,90])
			linear_extrude(pyramidDepth)
				difference()
				{
					circle(r=modelRadius,center=true,$fn=3);
					circle(r=modelRadius-(lineThickness * 2),center=true,$fn=3);
				}
} // End pyramid()

module hollowSphere()
{
	translate([0,0,modelZOffset])
		difference()
		{
			sphere(r=circleRadius,center=true);
			sphere(r=circleCutout,center=true);
		}
} // End hollowSphere()

module spire()
{
	translate([0,0,circleRadius])
		cylinder(d=spireThickness,h=circleRadius*2,center=true);

	translate([0,0,(circleRadius*2)+(cylinder2Height/2)])
		cylinder(d1=spireThickness,d2=0,h=cylinder2Height,center=true);
} // End spire()

module report()
{
	echo(str("Pyramid - ",pyramidHeight," mm / ",pyramidSideLength," mm / ",pyramidDepth," mm [H/W/D]"));
	echo(str("Hollow Sphere - Outside Radius:  ",circleRadius," mm   Inside Radius:  ",circleCutout," mm"));
	echo("Spire");
	echo(str("- Bottom Section Height:  ",circleRadius*2," mm   Radius:  ",spireThickness," mm"));
	echo(str("- Top Section Height:  ",cylinder2Height," mm   Bottom Radius:  ",spireThickness," mm   Top Radius:  0 mm"));
} // End report()
```


[User]
Hey! I'm looking to create some customizable spool boxes for 3D printer filament, and I'm having some trouble getting started in OpenSCAD. I'd like a design that can handle various spool angles, especially angles less than 90 degrees, which seems to be a sticking point in most of the designs I've seen.

Specifically, I need a box that can accommodate a spool with:

*   A variable central **angle** (between 10 and 180 degrees).
*   An **inner radius** for the spool, adjustable between 10 and 100 mm.
*   An **outer radius** for the spool, adjustable between 20 and 200 mm.
*   A **height** for the spool, adjustable between 10 and 100 mm.
*   A variable **wall thickness** for the box, adjustable between 1 and 4 mm.
*  An **outer brim** on the spool. (Most spools have one which makes it difficult to drill through). This needs to be an adjustable distance from 1mm to 10 mm
*   Optionally, a **bolt hole** on the outside to connect the boxes. I need a **bolt radius** for this hole, adjustable between 1 and 10 mm, and a **bolt overhang** for it, adjustable between 1 and 10 mm.

For the box itself, I'd like to have a few options:

*   A "rounded" box style with a corner on the side furthest from the axis rounded for angles between 40 and 60 degrees.
*   A "normal" box type with square edges on the outside.
*  For angles lower than 40 degrees i want a drawer style box, with a flat side to fit the base of the angled part of the spool. I don't want the drawer to be part of the box and it should be it's own part, like the bolt.
*   A **handle** on the box, the dimensions of which I can control. I need a **handle width**, a **handle length** which is the handle going away from the central axis and a **handle size** which is the width of the handle that sits on the spool.

Finally, I need an overall **tolerance** setting to be adjustable between 0 and 0.5 mm. Also, the number of facets should be set to 100 via `$fn=100`

Can you help me generate an OpenSCAD file or files for this, or point me in the right direction with existing modules, that I should include? I'd prefer to use OpenSCAD's built-in functions and no external libraries if possible.

[Assistant]
SpoolBox.scad:
```
// preview[view:south east, tilt:bottom diagonal]
/* [Parts] */
makeBox=true;
makeBolt=false;
BoxType="rounded";//["rounded","normal"]
/* [Spool/Box Size] */
angle=60;//[10:180]
innerR=40;//[10:0.1:100]
outerR=100;//[20:0.1:200]
height=50;//[10:0.1:100]
wallThickness=2;//[1:0.1:4]
//At the outside of Spools there is most often a brim, wich is hard to Drill Through.
spoolOuterBrimm=5;//[1:0.1:10]
/* [Bolt Size] */
boltRadius=2;//[1:0.1:10]
boltOverhang=2;//[1:0.1:10]

/* [handle] */
handleSize=10;//[4:0.1:30]
handleWidth=handleSize;
handleLength=handleSize;
/* [Other Stuff] */
tolerance=0.2;//[0:0.01:0.5]
$fn=100;

module BaseBoxShape(alpha,tol,iR,oR,Th,bR,sOB){
		offset(r=-tol)
		{
			difference()
			{
				intersection()
				{
					circle(r=oR+tol);
					polygon([[0,0],[tol,2*oR],[2*sin(alpha/2)*oR,2*cos(alpha/2)*oR],[2*sin(alpha)*oR,2*cos(alpha)*oR]]);
				}
				circle(r=iR);
				if(bR!=0)translate([sOB+bR+tol,oR-sOB-tol-bR])circle(r=bR-tol);
				difference()
				{
					translate([-sOB-bR,oR-sOB-tol-bR])square([2*sOB+2*bR,2*sOB+2*bR]);
					translate([sOB+bR+tol,oR-sOB-tol-bR])circle(r=sOB+tol+bR);
				}
			}
		}
}
//
module RoundedBoxShape(alpha,tol,iR,oR,Th,bR,sOB){
	intersection()
	{
		BaseBoxShape(alpha,tol,iR,oR,Th,bR,sOB);
		union()
		{
			translate([sOB+bR+tol,oR-sOB-tol-bR])circle(r=sin(alpha)*(oR-sOB-tol-bR-(sOB+bR+tol)/tan(alpha))-tol);
			rotate(-alpha)translate([-oR,cos(alpha)*(oR-sOB-tol-bR-(sOB+bR+tol)/tan(alpha))+(sOB+bR+tol)/sin(alpha)])square([oR-tol,oR]);
		}
	}
}
//
module BoxShape(alpha,tol,iR,oR,Th,bR,sOB,type){
	if(angle>90)
		BaseBoxShape(alpha,tol,iR,oR,Th,bR,sOB);
	else if(type=="rounded")
		RoundedBoxShape(alpha,tol,iR,oR,Th,bR,sOB);
	else if(type=="normal")
		BaseBoxShape(alpha,tol,iR,oR,Th,bR,sOB);
	else
		assert(false, "select a valid Type");
}
//
module HandleShape(w,l,Th){
	union()
	{
		translate([-1,-Th/2])square([l+1,Th]);
		translate([l-Th,-w/2])square([Th,w]);
	}
}
//
module BoltInPart(tol,oR,Th,bR,sOB,h,bOver){
	rr=bR+sOB-Th;
	difference()
	{
		union()
		{
			translate([sOB+bR+tol,oR-sOB-tol-bR,0])cylinder(r=sOB-Th+bR+tol,h=h-2*tol);
			translate([sOB+bR+tol,oR-sOB-tol-bR,h-2*tol])cylinder(r=bR,h=bOver);
			translate([sOB+bR+tol,oR-sOB-tol-bR,0])linear_extrude(height=h-2*tol)polygon([[-rr,-rr],[-rr,rr*0.8],[-rr*0.8,rr],[rr,rr]]);
		}
		translate([sOB+bR+tol,oR-sOB-tol-bR,-1])cylinder(r=bR,h=3*bOver+1);
	}
}
//
module ExtraBoltUnderneath(bR,bOver){
	hull()
	{
		cylinder(r=bR,h=bOver);
		cylinder(r=bR*0.8,h=bOver*3);
	}
}
//
module Box(alpha,tol,iR,oR,h,Th,handleW,handleL,bR,sOB,bOver,type){
	difference()
	{
		union()
		{
			linear_extrude(height=h-2*tol)
			{
				difference()
				{
					BoxShape(alpha,tol,iR,oR,Th,bR,sOB,type);
					offset(r=-Th)BoxShape(alpha,tol,iR,oR,Th,bR,sOB,type);
				}
				rotate(90-alpha*0.9)translate([oR,0])HandleShape(handleW,handleL,Th);
			}
			linear_extrude(height=Th)BoxShape(alpha,tol,iR,oR,Th,bR,sOB,type);
			BoltInPart(tol,oR,Th,bR,sOB,h,bOver);
		}
		rotate_extrude(angle=360)
		{
			x=Th/sqrt(2);
			translate([iR,0])rotate(45)square([x,x],center=true);
			translate([iR,h-2*tol])rotate(45)square([x,x],center=true);
		}
	}
}
//
if (makeBox)
	Box(angle,tolerance,innerR,outerR,height,wallThickness,handleWidth,handleLength,boltRadius,spoolOuterBrimm,boltOverhang,BoxType);
if (makeBolt)
	ExtraBoltUnderneath(boltRadius,boltOverhang);

```


[User]
Hey, I'm looking to create some parametric clamps for building directional antennas using aluminum extrusion, specifically 1515 or MakerBeam XL type profiles. I'd like to be able to customize the design for different rod and dipole sizes, and I'd like it to be a single-screw mount to the T-slot. I have an existing design, but I'd prefer to start from scratch to really get what I need.

Here’s what I need in terms of parameters:

*   **`extrusion_size`**:  The size of the extrusion profile, for example 15mm for 1515.
*   **`extrusion_channel_width`**: The width of the T-slot channel, like 3.1mm for MakerBeam XL or 3.6mm for Misumi 1515.
*   **`rod_dia`**: The diameter of the rods used for the antenna elements. I'd like a bit of tolerance here, for example, 4.95mm for 3/16" rods.
*   **`num_rods`**: The number of rod clamps to generate in addition to the dipole clamp
*   **`dipole_height`**: The thickness (or height) of the dipole element, which is typically around 8.8mm.
*   **`dipole_length`**: The length (width) of the dipole element, usually something like 17.8 - 17.9mm.
*   **`dipole_cable_dia`**: The diameter of the dipole cable, such as 7.0mm.
*   **`offset`**: The vertical offset from the extrusion to the center of the rod. This typically is about dipole_height/2 but there may be a case where it needs to be more, so it needs to be a customizable parameter.
*   **`screw_dia`**:  The diameter of the mounting screw, for example 3.3mm for M3.
*   **`screw_len`**: The desired length of the mounting screw. I know that the actual length will need to be adjusted based on the other parameters, but this is a desired minimum for calculation purposes.
*   **`wall_thickness`**:  The thickness of the clamp walls.
*  **`notch_thickness`**: How deep is the notch that aligns to the extrusion.
*   **`gap_size`**: The gap between the clamp halves for tightening.

The design should include:

*   A single bracket model to be re-used for each rod and the dipole.
*   The rods should be held by a cylindrical hole and the dipole should be held by a rectangular cavity.
*   Each clamp should mount to the T-slot using a single screw.
*   The clamp should have a notch that aligns with the t-slot such that it won't slip out of place.
*   The rod clamps and dipole clamp need to be aligned in the same plane.
*   The design should be printable without supports.
*   A helpful guide/debug output when the `dipole` flag is set, which shows the general size and position of the dipole relative to the model.
*  A chamfer to the base of the rod clearance hole, such that a rod can enter smoothly, and not catch on a flat surface.

I'm hoping this can be done parametrically in OpenSCAD. I don't have any special library requirements, just the core OpenSCAD functionality should be fine. Thanks!

[Assistant]
antenna_clamps.scad:
```
// antenna clamps

// size of the extrusion (eg 15mm for 1515/MakerBeamXL)
extrusion_size = 15;

// width of the extrusion channel (typ: 3.1 for MakerBeam XL / 3.6 for Misumi 1515)
extrusion_channel_width = 3.1;

// rod diameter in mm (allow for some small hole tollerance here, eg 4.95mm for 3/16" rods)
rod_dia = 4.95;

// number of rod clamps to generate
num_rods = 5;

// thickness (height) of the dipole (typ: 8.8mm)
dipole_height = 8.8;

// length (width) of the dipole (typ 17.8 - 17.9mm)
dipole_length = 17.9;

// dipole cable diameter (typ: 6.8mm)
dipole_cable_dia = 7.0;

// displacement to rod center, typ this is dipole_height/2 (min can be: wall_thickness + rod radius)
offset = 4.4;

// mount screw diameter (eg 3.3mm for M3)
screw_dia = 3.3;

// mount screw desired length. Actual length will change depending on rest of parameters
screw_len = 6;

// thickness of the walls (rod clamp)
wall_thickness = 2.4;

// how deep (mm) is the notch that aligns with extrusion
notch_thickness = 1.6;

// gap for tightening (mm)
gap_size = 0.75;

/* [Hidden] */

//
// computed/fixed dimensions
//
bracket_len = extrusion_size;
$fn = 60;
epsilon = 0.001;

// get the offset that works for both dipole and rods, as well as preserve clamping strength
actual_offset = max(dipole_height/2 + wall_thickness, max(wall_thickness + rod_dia/2, offset));

actual_screw_len = max(screw_len, gap_size + 2*wall_thickness);

echo("M3 max screw length", actual_screw_len + 4);

// take out the gap and only leave enough screw to fill the M3 nut as opposed to the entire extrusion channel
echo("M3 ideal screw length", actual_screw_len-gap_size - 1 + 4);

module bracket(dipole=0)
{

    element_len = dipole?dipole_length:rod_dia;
    screw_offset = element_len/2 + wall_thickness + screw_dia;

    difference()
    {
        color("darkblue")
        union() {
            hull() {

                if (dipole) {
                    // clamp for dipole

                    translate([0, 0, (extrusion_size + 2*notch_thickness)/2])
                        cube([dipole_length + 2*wall_thickness, dipole_height + 2*wall_thickness + notch_thickness, extrusion_size + 2*notch_thickness - 1], center=true);

                    translate([0, 0, (extrusion_size + 2*notch_thickness)/2])
                        cube([dipole_length + 2*wall_thickness - 1, dipole_height + 2*wall_thickness + notch_thickness- 1, extrusion_size + 2*notch_thickness], center=true);

                } else {
                    // rod
                    translate([0, 0, (extrusion_size + 2*notch_thickness)/2])
                        cylinder(r=rod_dia/2 + wall_thickness, h=extrusion_size + 2*notch_thickness - 1, center=true);

                    translate([0, 0, (extrusion_size + 2*notch_thickness)/2])
                        cylinder(r=rod_dia/2 + wall_thickness - 0.5, h=extrusion_size + 2*notch_thickness, center=true);
                }

                // screw base plate
                translate([(bracket_len+rod_dia)/2 -wall_thickness - rod_dia/2, (2*wall_thickness + gap_size + notch_thickness)/2 -actual_offset - notch_thickness, (extrusion_size + 2*notch_thickness)/2])
                    cube([bracket_len+element_len, 2*wall_thickness + gap_size + notch_thickness, extrusion_size + 2*notch_thickness - 1], center=true);

                translate([(bracket_len+rod_dia)/2 -wall_thickness - rod_dia/2, (2*wall_thickness + gap_size + notch_thickness)/2 -actual_offset - notch_thickness, (extrusion_size + 2*notch_thickness)/2])
                    cube([bracket_len+element_len - 1, 2*wall_thickness + gap_size + notch_thickness - 1, extrusion_size + 2*notch_thickness], center=true);

                // screw support in case it's longer
                translate([rod_dia/2 + wall_thickness + screw_dia , -actual_offset, extrusion_size/2 + notch_thickness])
                rotate([90,0,0])
                {
                    translate([0,0,-epsilon/2])
                        cylinder(r=screw_dia, h=epsilon, center=true);
                }
            }

        }

        if (dipole) {
            cube([dipole_length, dipole_height, 100], center=true);

            translate([-dipole_length/2, 0, extrusion_size/2 + notch_thickness])
                rotate([0, 90, 0])
                hull() {
                    cylinder(r=dipole_cable_dia/2, h=dipole_length, center=true);

                    translate([-extrusion_size, 0,0])
                    cylinder(r=dipole_cable_dia/2, h=dipole_length, center=true);
                }

        } else {
            // rod clearance
            cylinder(r=rod_dia/2, h=100, center=true);

            // chamfer base
            translate([0,0, 0])
                cylinder(r1=rod_dia/2+0.5, r2=rod_dia/2, h=1, center=true);
        }

        // helper guide
        if (dipole) {

            %color("pink", 0.2) {
                translate([0,0, extrusion_size/2 + notch_thickness]) {
                    cube([dipole_length, dipole_height, 2*dipole_length], center=true);

                    translate([-dipole_length/2,0,0])
                    rotate([0, 90, 0])
                    cylinder(r=dipole_cable_dia/2, h=dipole_length, $fn=12, center=true);
                }

                cylinder(r=rod_dia/2, h=200, $fn=12, center=true);
            }

        } else {
            %color("pink", 0.3) cylinder(r=rod_dia/2, h=200, $fn=12, center=true);
        }

        // horizontal cut-out (parallel with extrusion)
        translate([(15+rod_dia)/2, -actual_offset + gap_size/2 + wall_thickness, extrusion_size/2 + notch_thickness])
                cube([15+element_len + epsilon, gap_size, extrusion_size + 2*notch_thickness + epsilon], center=true);

        // vertical cut-out (rod towards base / perpendicular to extrusion)
        translate([0, (wall_thickness - actual_offset)/2, extrusion_size/2 + notch_thickness])
            cube([gap_size, actual_offset - wall_thickness, extrusion_size + 2*notch_thickness + epsilon], center=true);

        // screw hole
        translate([screw_offset, -actual_offset + actual_screw_len, extrusion_size/2 + notch_thickness])
        rotate([90,0,0])
        {
            cylinder(r=screw_dia/2, h=100, center=true);

            translate([0,0,-100/2])
                cylinder(r=screw_dia, h=100, center=true);
        }

        // alignment notch - make this exactly extrusion_size to have two notches: top and bottom, but won't be printable without supports anymore
        alignment_notch_width = extrusion_size + 100;

        %translate([0, -15/2 - actual_offset, 15/2 + notch_thickness])
            cube([100, 15, 15], center=true);

        difference() {
            translate([0,-100/2 -actual_offset, 0])
                cube([100, 100, alignment_notch_width], center=true);

            // center - chamfered
            translate([0, -actual_offset, extrusion_size/2 + notch_thickness]) {
                hull() {
                    translate([0,-1, 1/4])
                        cube([100, epsilon, extrusion_channel_width-1/2], center=true);
                    translate([0, epsilon,0])
                        cube([100, epsilon, extrusion_channel_width], center=true);
                }
            }

            // top - chamfered
            chamfer_compensation = 0.5;
            translate([0, -actual_offset, extrusion_size + notch_thickness + notch_thickness/2]) {
                hull() {
                    translate([0,-1,0])
                        cube([100, epsilon, notch_thickness - 1], center=true);

                    translate([0, epsilon,0])
                        cube([100, epsilon, notch_thickness + chamfer_compensation], center=true);
                }
            }

            // bottom - flat
            translate([0, -actual_offset, -notch_thickness/2 + notch_thickness]) {
                hull() {
                    translate([0,-1, 1/4])
                        cube([100, epsilon, notch_thickness - 1/2], center=true);

                    translate([0, epsilon,0])
                        cube([100, epsilon, notch_thickness], center=true);
                }
            }

        }

    }
}

translate([0, -dipole_height - wall_thickness]) bracket(1);

for(i=[1:num_rods]) {
    translate([0, i*(actual_offset + notch_thickness  + wall_thickness + rod_dia/2 + 2),0])
        bracket(0);
}
```


[User]
I'm looking to design a replacement knob for my 3D printer's bed leveling screws. I need it to be larger than the original, so it's easier to grip, and I'd like it to have some visual indication of which way to turn it to move the bed up or down.

The original knob has a diameter of 14.5mm. I want the new knob to have an outer diameter of 20mm and an overall height of 12mm. The new knob should have an internal cavity that matches the original knob (14.5mm diameter) and it should be 7mm deep.  I'd also like the internal cavity's polygon resolution to be a bit rough (say, around 20 faces). This will help give it a snug fit.

I want raised helical features on the outside of the knob, kind of like threads, to help with grip.  I'd like 5 of these helices, each with a thickness of 5mm (measured perpendicular to the helix axis), and they should extend about 0.8mm from the surface of the main body. The slope of these helices should be 45 degrees, and I think a resolution of 0.5mm along the helix will work well.

Finally, I need arrows added to the top and bottom of each helix, near the outside diameter. These arrows should point in the direction the bed will move if the knob is turned that way. The arrows should be triangular and have a size of about 0.6 times the thickness of the helical feature. Also, the arrows should be placed so that one is near the top of the helical feature and the other is at the bottom, and they should be aligned with the helical path.

Could you generate the OpenSCAD code for this, making it parametric so I can easily tweak the dimensions if needed? I would prefer that you not rely on external libraries, if possible.

[Assistant]
Levelling_knob.scad:
```
//bed level knob

// - diameter of your existing knob
ID = 14.5;

// - diameter of the new knob
OD = 20;

// - overall height of the new knob
z = 12;

// - depth of internal cavity
inset = 7;

// - resolution of cavity (a rougher polygon helps create a tght fit
fInset = 20;

// - height of the helixes above surface
detDep = 0.8;

// - thickness of helixes
t = 5;

// - number of helixes
nDets = 5;

// - slope of helixes
slope = 45;

// - resolution of helixes
helixRes = 0.5; //mm

/* [hidden] */
//helix properties
c = PI*OD;
t_ = t/cos(slope);
linTwist = z*tan(slope);
angTwist = 360*(linTwist/c);

radius = OD/2+detDep;
angles = [-360*(t_/c)/2, 360*(t_/c)/2];
points = [
    for(a = [angles[0]:1:angles[1]]) [radius * cos(a), radius * sin(a)]
];
helixArc = concat([[0, 0]], points);

//arrow detail
szArr = t*0.6;
zArr = 0.5*szArr;
rotArr = zArr*tan(slope)*2;  //mm
angArr = -(360*rotArr)/c; //deg

difference() {
    union() {
        //main body
        cylinder(d=OD, z);
        for(j=[1:nDets])
        //construct helixes
        rotate((j-1)*(360/nDets))
        linear_extrude(z, twist=angTwist, slices=z/helixRes)
            polygon(helixArc);
    }

    //inset
    translate([0,0,z-inset])
        cylinder(d=ID, z, $fn=fInset);

    //direction arrows
    for(j=[1:nDets]) rotate((j-1)*(360/nDets)) {
        rotate(angArr)  //rotate to algn wth helx centr
        translate([OD/2, 0, zArr*2])  //move to circumference and up
        rotate([slope,0,0]) //align with helix slope
        rotate([0,90,0])  //lay along x axis
        scale([2,1,1]) //scale trangle to arrow
        cylinder(d=szArr, OD, $fn=3);  //make trangle

        rotate(-angArr-angTwist)  //rotate to algn wth helx centr
        translate([OD/2, 0, z-zArr*2])  //move to circumference and up
        rotate([slope+180,0,0]) //align with helix slope
        rotate([0,90,0])  //lay along x axis
        scale([2,1,1]) //scale trangle to arrow
        cylinder(d=szArr, OD, $fn=3);  //make trangle
    }
}

```


[User]
I'd like to design a stackable planter based on the idea of using a standard plastic cup as the inner container, similar to Tanya Akinora's Flower Pot, but I want it to be fully parametric and built in OpenSCAD.

Here are the key features I'm looking for:

1.  **Stackable Layers:** The planter should be composed of multiple identical layers that can be stacked on top of each other.  Each layer will have "feet" that rest on the layer below, and a recess for those feet, to create a stable stack. The bottom layer will not have a recess.

2.  **Parametric Dimensions:**  I need to be able to easily adjust the following parameters:
    *   **Number of Layers:**  The total number of layers in the stack.
    *   **Layer Spacing:** The vertical distance between layers.
    *   **Layer Height:**  The height of an individual layer.
    *   **Layer Side Length:** The length of the sides of the basic layer shape (which is a square with rounded corners.)
    *   **Corner Diameter:** The diameter of the rounded corners of the square base layer shape.
    *   **Hole Diameter:** The inner diameter of the central hole where the plastic cup will sit.
    *  **Foot Offset Radius:** The radius of the circle on which the centers of the feet are placed.
    *   **Foot Width Percent:** The width of the "feet" as a percentage of the layer spacing.
    *   **Foot Shape:** The shape of the foot, either "Round" or a polygon with a specified number of sides.
    *   **Polygon Foot Sides:** If "Polygon" is the foot shape, this is the number of sides.
    *   **Foot Rotation Angle:** If polygon is the foot shape, this is the rotation angle of each foot.
	*	**Tolerance Percent:** The amount of tolerance used to ensure that the feet fit into the recesses
    *   **Resolution:**  The number of facets for curves (e.g. for the circles and round feet).
	* **Recess Percent:** The percentage of the layer space used for the depth of the recess.
    * **Recess Z Offset Percent:** The percentage of the layer height used as an offset for the recess in the Z axis.

3.  **Layer Design Options:** I would like to be able to select from the following designs: "None", "Diamonds", "Grid", and "Sun Burst". If no design is chosen, then the design will be plain. If a design is chosen, it should be created only where there are recesses.

4.  **Top and Bottom Layer Options:** I should be able to mark a layer as the top and bottom layer. The bottom layer should not have a tapered foot. The top layer should not have a recess.

5.  **Plastic Cup:** The design should be based around a 16 oz Solo Cup, with a 92mm diameter at the top.

I don't need to use any external libraries for this project.  Can you generate the OpenSCAD code to accomplish this? I expect the files to be generated in a parametric way, using named variables that can be easily modified.  I don't need a specific file structure - one file will do.

[Assistant]
Plastic_Cup_Plant_Holder_v1.1.scad:
```
/* [1 - Parameters] */
// Bottom Layer Flag
FlagBottomLayer = "false"; // ["true","false"]

// Top Layer Flag
FlagTopLayer = "false"; // ["true","false"]

// Number of Layers
LCount = 7;

// Layer Spacing
LSpace = 13;

// Resolution
Resolution = 150; // [25:25:300]

// Tolerance %
TolerancePar = 3;
TolerancePct = TolerancePar / 100;

/* [2 - Layer] */
// Design
Design = "None"; // ["None","Diamonds","Grid","Sun Burst"]

// Layer Height (mm/100)
LHeightPar = 225;
LHeight = LHeightPar / 100;

// Layer Length
LSideLength = 125;

/* [3 - Feet] */
// Offset Radius / Distance From Center
FOffsetR = 63;

// Rotation Angle (Polygons Only)
FRotationA = 0;  // [0:5:115]

// Shape
FShape = "Round"; // ["Round","Polygon"]

// Number of Sides in the Polygon if not Round
FSides = 4;

// Width - % of Layer Space
FWidthPctPar = 50;
FWidthPct = FWidthPctPar / 100;

/* [4 - Hole and Corner] */
// Corner Diameter
CornerD = 35;

// Hole Diameter
HoleD = 92;

/* [Hidden] */
// Recess % - Height of the recess for the foot - Percentage of Layer Height
recessPct = .5;

// Reccess Z Offset - % of LayerH
recessZOffsetPct = .2;

$fn = Resolution;

width1 = LSideLength;
length1 = LSideLength - CornerD;
width2 = LSideLength - CornerD;
length2 = LSideLength;
facets = FShape == "Round" ? Resolution / 2 : FSides;
footD = LSpace * FWidthPct;
footTaperH = LSpace * recessPct;
recessZOffset = LHeight * recessZOffsetPct;
footH = LSpace + recessZOffset;
recessCircleD = footD * (1 + TolerancePct);
footCircleD = footD * (1 - TolerancePct);
DesignH = (LHeight + LSpace) * LCount;

echo(str("Design Height = ",DesignH," mm"));
echo(str("Layer Height = ",LHeight," mm"));
echo(str("Foot Diameter = ",footD," mm"));

layer();

module layer()
{
	difference()
	{
		union()
		{
			linear_extrude(LHeight)
				difference()
				{
					flatLayer();
					circle(d=HoleD,center=true);
				}
			feet();
		}
		if (FlagTopLayer == "false") recesses();
		if (Design == "Grid") grid();
		if (Design == "Diamonds")
			scale([2,1.1,1])
				rotate([0,0,45])
					grid(.75);
		if (Design == "Sun Burst") sunBurst();
	}
} // End layer()

module flatLayer()
{
	square([length1,width1],center=true);
	square([length2,width2],center=true);
	translate([width2/2,length1/2,0])
		circle(d=CornerD,center=true);
	translate([-(width2/2),length1/2,0])
		circle(d=CornerD,center=true);
	translate([width2/2,-(length1/2),0])
		circle(d=CornerD,center=true);
	translate([-(width2/2),-(length1/2),0])
		circle(d=CornerD,center=true);
} // End layerSolid()

module recesses()
{
	for (a=[45:90:315])
	{
	xOffset = cos(a) * FOffsetR;
	yOffset = sin(a) * FOffsetR;

	translate([xOffset,yOffset,(footTaperH/2)+recessZOffset])
		rotate([0,0,FRotationA])
			cylinder(d1=recessCircleD,d2=footD*.2,h=footTaperH,center=true,$fn=facets);
	translate([xOffset,yOffset,recessZOffset/2])
		rotate([0,0,FRotationA])
			cylinder(d=recessCircleD,h=recessZOffset,center=true,$fn=facets);
	}
} // End recesses()

module feet()
{
	zOffset1 = LHeight + (footH / 2);
	zOffset2 = LHeight + footH + (footTaperH / 2);

	for (a=[45:90:315])
	{
		xOffset = cos(a) * FOffsetR;
		yOffset = sin(a) * FOffsetR;

		translate([xOffset,yOffset,zOffset1])
			rotate([0,0,FRotationA])
				cylinder(d=footD,h=footH,center=true,$fn=facets);
		if (FlagBottomLayer == "false")
		translate([xOffset,yOffset,zOffset2])
			rotate([0,0,FRotationA])
				cylinder(d1=footD,d2=footD*.2,h=footTaperH,center=true,$fn=facets);
	}
} // End feet()

module grid(lineThickness=1.25)
{
	for(i=[5:5:LSideLength-5])
	{
		translate([i-(LSideLength/2),0,recessZOffset/2])
			cube([lineThickness,LSideLength,recessZOffset],center=true);
		translate([0,i-(LSideLength/2),recessZOffset/2])
			cube([LSideLength,lineThickness,recessZOffset],center=true);
	}
} // End grid()

module sunBurst()
{
	lineL = sqrt(2 * pow(LSideLength,2));

	for (i = [1:2:180])
	{
		rotate([0,0,i])
			cube([lineL,.75,recessZOffset],center=true);
	}
} // End sunBurst()
```


[User]
Hi, I'm looking to create a parametric design for a router table insert using OpenSCAD. I have a Bosch RA1181 router table, but I believe the insert should also fit the RA1171. I've been playing with an existing design, but I'd like to start fresh.

The insert needs to have the following features:

*   **Overall Shape:** A circular insert with a diameter of 97.3mm and a thickness of 2.5mm.
*   **Tabs:** It needs 4 tabs around the perimeter to hold it in place. The tabs should overhang the main body by 0.5mm, have a height of 6mm, and include a little triangular lip that angles out at 30 degrees.
*   **Ribs:** There should be ribs on the underside of the insert for added strength. These ribs should have a diameter of 92mm, a width of 4mm, and a height of 2.5mm. There should be 8 ribs total equally spaced. Notches in the perimeter need to be cut for both the tabs and the ribs, so the ribs need to be cut with a notch width of 7mm.
*   **Bushing Recess:** The key part is a recess to accommodate a guide bushing. This recess needs to be adjustable. I want to be able to set:
    *   The maximum diameter of the bushing that the insert is designed to accommodate. Let's call this `bitMaxDiameter_in` and it should be settable in inches.
    *   The minimum diameter of the bushing. Let's call this `bitMinDiameter_in` and also be settable in inches.
    *   The recess depth, or how thick the top of the bushing would sit down into the insert. Let's call this `bushingTopThickness_in` and also be in inches.
*   **Text:** I'd like the insert to have text on it showing the minimum bit diameter and material that the insert is made from. The text should be extruded and readable even when printed.

For a starting point let's use:

*   `bitMaxDiameter_in` = 1.3750
*   `bitMinDiameter_in` = 1.1875
*  `bushingTopThickness_in` = 0.09375
*   `material` = "Lazyman" (this should be a parameter)

I think that covers it all. Can you generate the necessary OpenSCAD files? I don't need any special libraries, but would like to make sure all relevant parameters are customizable and easily understandable.  I'm happy with the code being modular so each module can be modified independently.

[Assistant]
Bosch_Router_insert_with_bushing.scad:
```
// BoschRouterTableInserts.scad
//
// This file generates router table inserts for the Bosch RA1181 Benchtop Router Table
// (which I have), and presumably with the Bosch RA1171 Cabinet Style Router Table
// (which I do not).
//
//  Original code and inspiration from Thingiverse user ebredder
//  https://www.thingiverse.com/thing:1420998
//

//Bit or Bushing Max Diameter (inches)
bitMaxDiameter_in=1.3750;

//Bit or Bushing Min Diameter (inches)
bitMinDiameter_in=1.1875;

//Recess for Bushing's top (inches)
bushingTopThickness_in=0.09375;   // 0.10 for Leigh Jig Bushing

//Resolution
res=360;

//----- Insert Dimensions -----
// Insert Diameter (mm)
insertDiameter=97.3 ;

// Insert Plate Thickness (mm)
insertHeight=2.5 ;

//----- Tabs ------------------
// Overhang of tab
tabOverhang=0.5;

// Height of the Tab
tabHeight=6 ;

// Number of tabs
nTabs = 4 ;

//----- Ribs ------------------
// Rib Diameter (mm)
ribDiameter= 92;

// Rib Width (mm)
ribWidth=4.0 ;

// Rib Height (mm)
ribHeight=2.5 ;

// Notch Width (mm)
notchWidth = 7 ;

// Material Text
material="Lazyman" ;

//----- Calculations based on parameters
// Bit Diameter in mm
bitMaxDiameter_mm=bitMaxDiameter_in*25.4;
bitMinDiameter_mm=bitMinDiameter_in*25.4;
bushingTopThickness_mm=bushingTopThickness_in*25.4; //Convert to mm

// Number of Ribs
nRibs=nTabs*2 ;

// Basic shape of router table insert
module insert(){
            cylinder(insertHeight, r=insertDiameter/2, $fn=res);
}

// Define notches out of insert plate
module notch(){
    union(){
    // Cuts Ribs
    translate([ribDiameter/2,0,insertHeight+ribHeight/2])
       cube([ribWidth*3,2*notchWidth,insertHeight],true) ;
    // Cuts Insert
    translate([insertDiameter/2,0,insertHeight/2])
       cube([insertDiameter-ribDiameter,notchWidth,insertHeight],true) ;
    }
}

// Replicate number of notches depending on desired number of tabs
module notches(){
        for (z=[0:nTabs]){
            rotate([0,0,z*360/nTabs+360/nRibs/2])translate([0,0,0])notch() ;
        }
    }

// Define rib structure
module rib(){
    translate([bitMaxDiameter_mm/2,-ribWidth/2,0])cube([(ribDiameter-bitMaxDiameter_mm+ribWidth)/2-ribWidth,ribWidth,ribHeight]) ;
}

module innerRing(){
//    difference(){
        cylinder(ribHeight,r=(bitMaxDiameter_mm+ribWidth)/2,$fn=res);
//        cylinder(ribHeight,r=bitMaxDiameter_mm/2,$fn=res);
//    }
}

module outerRing(){
    difference(){
        cylinder(ribHeight,r=ribDiameter/2,$fn=res);
        cylinder(ribHeight,r=(ribDiameter-ribWidth*2)/2,$fn=res);
    }
}

// Combine components of the rib layer
module ribLayer(){
        union(){
            union(){
               // Make polar array of ribs
               for (z = [0:nRibs]) {
                  rotate([0,0,z*360/nRibs])
                  translate([0,0,insertHeight])
                     rib() ;
               }
               translate([0,0,insertHeight])
                  innerRing() ;
            }
            translate([0,0,insertHeight])
               outerRing() ;
        }

}

// Define tabs
module tab(){
   // The triangular lip on the tabs is a 30-60-90 triangle, so there's a little
   // trigonometry magic in this section.
   prismHeight =2*tabOverhang*sqrt(3) ;
   translate([ribDiameter/2-ribWidth,0,insertHeight])
   union() {
      translate([0,-notchWidth/2,0])
         cube([ribWidth,notchWidth, tabHeight]) ;

      translate([ribWidth,notchWidth/2,tabHeight-prismHeight])
      rotate([30,0,-90])
         prism(notchWidth,prismHeight/2,prismHeight/2*tan(60)) ;
   }
}

// Replicate the tabs
module tabs(){
   for (z=[0:nTabs]){
      rotate([0,0,z*360/nTabs+360/nRibs/2])
      translate([0,0,0])
         tab() ;
   }
}

// prism module taken from OpenSCAD documentation 07/29/2019
// https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Primitive_Solids
module prism(l, w, h){
      polyhedron(
         points=[[0,0,0], [l,0,0], [l,w,0], [0,w,0], [0,w,h], [l,w,h]],
         faces=[[0,1,2,3],[5,4,3,2],[0,4,5,1],[0,3,4],[5,2,1]]
         );
}
module bushingHole(){
      bushingHeight=insertHeight+ribHeight ;
      //Thickness of the bushing's top

      bushingDiameter=bitMaxDiameter_mm-4.7625; // (3/16*25.4);     //difference between top and sides of bushing
      bushingRingHeight=4.7625;   //3/16*25.4;                  //thickness of the retaining gap
    union() {   //
                    cylinder(h=bushingTopThickness_mm
                            , d=bitMaxDiameter_mm
                            , $fn=res);  //outer bushing size
                    translate ([0,0,bushingTopThickness_mm])  //threaded area
                            cylinder(h=bushingHeight-bushingTopThickness_mm
                                , d=bitMinDiameter_mm
                                , $fn=res);  //inner bushing size
                          //          }
                    }
        }

// Extrude text displaying the size of the opening (aka bitMinDiameter_in)
module writeBitSize(){
   font = "Liberation Sans:style=Bold";
   fontSize=4 ;

   // the 10 is an arbitrary number
   translate([ribDiameter/2-fontSize,0,insertHeight])

   rotate([0,0,-90])
   linear_extrude(height=2) {
      text(text=str(bitMinDiameter_in,"\""), font=font,size=fontSize, halign="center", valign="top") ;
   }
}

module writeMaterial(){
   font = "Liberation Sans:style=Bold";
   fontSize=4 ;

   // the 10 is an arbitrary number
   translate([ribDiameter/2-fontSize-2,0,insertHeight])

   rotate([0,0,-90])
   linear_extrude(height=2) {
      text(text=material
         , font=font
         , size=fontSize
         , halign="center"
         , valign="top") ;
   }
}

// Main:  Put all the pieces together
difference(){ // cut out center hole
              union() { // add Material Text
                        union() { // add Bit Size Text
                                 union() { // add tabs

                                           difference(){  // cut out notches
                                                          union(){ // add rib layer
                                                                   ribLayer() ;
                                                                   insert() ;
                                                                  }
                                                          notches() ;

                                                        }
                                           tabs() ;
                                          }
                                 rotate([0,0,-360/nRibs/2])
                                  	writeBitSize() ;
                                }
                         rotate([0,0,180-360/nRibs/2])
                          	writeMaterial() ;
                       }
               bushingHole();
             }

```


[User]
I want to design a crab-like robot drone for 3D printing. It should be parametric so I can easily change the size and proportions. Here's what I'm thinking:

**General Shape:**

*   The main body should be a somewhat rounded, boxy shape. It should have a slightly beveled front.
*   The overall shape will need to be able to support a number of legs.
*   It needs a small cylindrical antenna sticking out the top-front.
*   It will have a forward-facing camera "lens."
*   It should have a rounded bottom to look less blocky.
*   A central stem should protrude from the bottom to connect to a lower spherical portion of the body
*   The body shouldn't be solid, but should use a defined wall thickness, so that its hollow.

**Legs:**

*   Each leg should be made up of a series of connected spheres of different sizes, which results in an organic look.
*   The legs should be attached at the bottom of the main body.
*   I want to be able to choose between 4 or 6 legs. They should be evenly spaced.
*   The legs should be designed so that they're easy to print. The bottom of the legs should be cut off by a flat surface.

**Parameters:**

I want to be able to control these things:

*   `middle_legs`: A boolean to enable or disable the middle legs (true for 6 legs, false for 4 legs).
*   `antenna_height`: The height of the antenna.
*   `head_bevel_offset`: How far the bevel on the front of the body extends backward.
*   `ball_diameter`: The size of the sphere that makes up the bottom of the body.
*   `margin`: A small offset to be applied around joints.
*   `wall_thickness`: The wall thickness of the hollow body.
*   `capsule_height`: The amount to elongate the rounded portion at the bottom of the body.
*   Individual radii of the spheres in the leg.
*   Individual y-values for the offset of the spheres of the leg
*   Individual z-values for the offset of the spheres of the leg

**Output:**

I'd like the output to be a single OpenSCAD file. I am not using any libraries other than built-in OpenSCAD functions.

[Assistant]
crab-drone.scad:
```
$fn = 96;

middle_legs = 1; // [0:4-Legs, 1:6-Legs]

antenna_height = 15;

head_bevel_offset = 4;

ball_diameter = 11;
margin = 0.1;
wall_thickness = 2.4;
capsule_height = 0;

drone();

module leg() {
    $fn = 6;

    hull() {
        translate([0, 0, 0])
            sphere(r = 3);
        translate([0, 8, 0])
            sphere(r = 3);
    }
    hull() {
        translate([0, 8, 0])
            sphere(r = 3);
        translate([0, 15, 7])
            sphere(r = 3);
    }
    hull() {
        translate([0, 15, 7])
            sphere(r = 3);
        translate([0, 20, -2])
            sphere(r = 2);
    }
    hull() {
        translate([0, 20, -2])
            sphere(r = 2);
        translate([0, 20, -8])
            sphere(r = 2);
    }
}

module drone() {
    difference () {
        union() {
            //antenna
            translate([7.4, 3.5, 0])
                cylinder(r = 1.2, h = antenna_height);

            translate([0, 0, 0]) {
                // square sections
                minkowski() {
                    sphere(r = 2, $fn = 96);
                    hull() {
                        cube([14, 8, 9], center=true);
                        translate([0, 0, head_bevel_offset])

                            cube([6, 14, 4], center=true);
                    }
                }

                // camera lens
                translate([-7, 0, 0])
                rotate(90, [0, 1, 0])
                    capsule(4, 8);
            }

            // chin
            translate([0,0,-2])
                capsule((ball_diameter + margin * 2 + wall_thickness * 2) / 2.0, capsule_height);

            // Stem
            translate([0, 0, -12])
                capsule(4.5, 10);

            // four legs
            translate([0, 0, -13])
                for (i = [1:4]) {
                    rotate(45 + 90 * i, [0, 0, 1]) {
                        leg();
                    }
                }

            // middle legs
            if (middle_legs) {
                translate([0, 0, -13])
                    for (i = [1:2]) {
                        rotate(180 * i, [0, 0, 1]) {
                            leg();
                        }
                    }
            }

        }

        // Chop off the bottom of feet to make them easier to print
        translate([0,0,-27])
            cube([40,60, 10], center=true);

    }
}

module capsule(radius, height) {
    hull() {
        sphere(r = radius);
        translate([0, 0, height])
            sphere(r = radius);
    }

}
```


[User]
Hi there, I'm looking to create some custom frames for my PC's 5.25" drive bays, and I'd like to use OpenSCAD to do it. I've seen some existing designs, but I want something that's really flexible and customizable, with specific options for different kinds of bays.

Specifically, I'm imagining a system where I can select different main "types" of bay configurations: a simple front plate, a short or long front bay for 3.5" devices, a bay for mounting 3.5" hard drives (with optional front fans), and a bay for mounting 2.5" SSDs (also with optional front fans). I think a parametric design would be perfect for this.

Here are the parameters I have in mind, roughly broken down by category:

**General Parameters:**

*   **`menu`**: A dropdown menu to select the main type of bay: "FrontBay", "DiskBay35", or "DiskBay25".
*   **`frontThick`**: Thickness of the front plate/frame (e.g., 3mm).
*   **`sideThick`**: Thickness of the side walls of the frame (e.g., 2mm).
*  **`screwDia`**: Diameter of the screws to be used (e.g., 3mm).

**Front Bay Parameters (when `menu` is "FrontBay"):**

*   **`bayLength`**: A dropdown to select "long" or "short" for the bay's depth. This mainly affects how far the side walls extend inwards.

**Disk Bay Parameters (when `menu` is "DiskBay35" or "DiskBay25"):**

*   **`withFront`**: A dropdown that’s either "yes" or "no". "yes" means the frame should have a front plate, and "no" means it's just an internal frame.
*   **`numberOfFans`**: An integer allowing 0, 1, 2, or 3 for the number of 40mm fans to be mounted in front of the disk.
*   **`fanWidth`**: The width of a fan (e.g., 11mm).
*   **`openingPattern`**: A dropdown for "yes" or "no", to use a customizable pattern of holes in the front plate.
*   **`openingPatternDia`**: Diameter of the holes in the opening pattern.
*   **`openingPatternXSpc`**: Horizontal spacing between holes in the opening pattern.
*   **`openingPatternYSpc`**: Vertical spacing between holes in the opening pattern.
*   **`openingPatternShift`**: How much to shift the pattern horizontally per row.
*   **`openingPatternAngle`**: Rotation angle of the holes.
*   **`openingPatternFn`**: Number of facets for the cylindrical holes.
*  **`openingPatternXSpaceFromFrame`**: How far the holes should be from the sides of the front panel.
*   **`openingPatternYSpaceFromFrame`**: How far the holes should be from the top/bottom of the front panel.

**Other Parameters:**

*   **`spc`**: Spacing around front openings to give a little tolerance when mounting devices
*   **`$fn`**: For controlling the smoothness of curves when rendering (e.g., 100 for smoother curves).

I'm thinking the design should consider standard 5.25", 3.5", and 2.5" device mounting hole placements, to make it easy to attach them to the frame. I have included details on the screw positions for those bay types in the attached document.

I would like all the models to be printed without supports unless absolutely necessary, and the front bay model will most likely require some supports.

I'm not looking for anything too fancy with the code organization, but it should be clear and understandable. I would like to use the `longhole` module that is provided, and it would be nice to have the different parts broken into modules for clarity (e.g., modules for the main shapes, the screw holes, the fan mounts, etc).

It would also be super helpful if the generated model included some comments to help me understand what's going on.

Does that sound doable? Let me know if you have any questions, or if you need further clarification on anything.

[Assistant]
PCBayFrame.scad:
```
// ****************************************************************************
// Customizable PC Bay Frame
// Author: Peter Holzwarth
// ****************************************************************************
// https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language

/* [General] */

menu= "DiskBay35"; // [FrontBay,DiskBay35,DiskBay25]

frontThick= 3; // Thickness of the front side
sideThick= 2; // Thickness of the side walls
screwDia= 3; // Diameter of screws used, typically 3mm

/* [Front Bay] */
//  Use "short" for small front bays or plates only, else use "long"
bayLength= "long"; // [long,short]

/* [Disk Bay] */
// True to show a front, or false for an inner-only frame
withFront= "yes"; // [yes,no]
// Number of fans to install for disk bays
numberOfFans= 0; // [0:3]
fanWidth= 11;

openingPattern= "yes"; // [yes,no]
openingPatternDia= 3;
openingPatternXSpc= 2;
openingPatternYSpc= 2;
openingPatternShift= 2;
openingPatternAngle= 30;
openingPatternFn= 6;
openingPatternXSpaceFromFrame= 5;
openingPatternYSpaceFromFrame= 7;

/* [Other Parameters] */
spc= 0.5; // spacing around front opening
$fn=100; // rounding of larger curves

/* [Hidden] */

// 5.25" screw positions, see https://doc.xdevs.com/doc/Seagate/SFF-8551.PDF
F525_A2= 42.3; // front side height
F525_A3= 148; // front side width
F525_A5= 146.05; // inner part width
F525_A10= 47.4; // first side screw distance from front
F525_A11= 79.25; // second side screw distance from first
F525_A13= 10; // side screws height, mandatory lower one
F525_A14= 21.84;  // side screws height, optional upper one

// 3.5" screw positions, see https://doc.xdevs.com/doc/Seagate/SFF-8301.PDF
F35_A1_1= 17.8; // inner height - slim
F35_A1_2= 26.1; // inner height - regular
F35_A1_3= 42; // inner height - full height
F35_A2= 147; // length
F35_A2_panelOffset= 5; // additional length
F35_A3= 101.6; // width
F35_A4= 95.25; // screw distance side-to-side
F35_A6= 44.45; // bottom mid screw from rear screw
F35_A7= 41.28; // bottom rear screw from rear end
F35_A8= 28.5; // side rear screw from rear end
F35_A9= 41.6; // side mid screw from rear screw
F35_A9_2= 101.6; // side front screw from rear screw
F35_A13= 76.20; // bottom front screw from rear screw

// 2.5" screw positions, see https://doc.xdevs.com/doc/Seagate/SFF-8201.PDF
F25_A1_8= 7; // SSD height - typical SSD
F25_A4= 69.85; // SSD width
F25_A6= 100.45; // SSD length
F25_A6_shift= 3; // disk shift
F25_A23= 3; // screw height
F25_A52= 14; // rear screw from back end
F25_A53= 90.6; // front screw from back end

shiftForFans= (menu!="FrontBay" && numberOfFans>0) ? 20 : 0; // if fans are to be used, shift disks further inwards

F_x0= F525_A10+10 + (bayLength=="short"?0:F525_A11)+shiftForFans; // total length of inside
bsd= (F525_A5-F35_A3)/2; // space between 5.25" and 3.5" side walls

Menu();

module Menu() {
    difference() {
        union() {
            // Front side
            Front525();

            // 5.25 left and right sides
            difference() {
                Side525(menu=="DiskBay25");
                Side525ScrewMask();
            }
            translate([0,F525_A5-sideThick,0]) difference() {
                Side525(menu=="DiskBay25");
                Side525ScrewMask();
            }

            // Bottom stabilizers
            BottomStripe525();
            translate([0,F525_A5-bsd,0]) BottomStripe525();
            BottomStripe52LR(withFront);

            // special parts for the different models
            if (menu=="FrontBay") {
                FrontBay_add();
            } if (menu=="DiskBay35") {
                DiskBay35_add();
            } else if (menu=="DiskBay25") {
                DiskBay25_add();
            }
            if (menu!="FrontBay" && numberOfFans>0) {
                Fan_add();
            }
        }
        // special removals for the different models
        if (menu=="FrontBay") {
            FrontBay_rem();
        } if (menu=="DiskBay35") {
            DiskBay35_rem();
        } else if (menu=="DiskBay25") {
            DiskBay25_rem();
        }
        if (menu!="FrontBay" && numberOfFans>0) {
            Fan_rem();
        }
    }
}

// ********************************************************************************
// 5.25" modules
// ********************************************************************************

// 5.25" front side
module Front525() {
    translate([0,-(F525_A3-F525_A5)/2,0]) cube([frontThick, F525_A3, F525_A2]);
}

// For 5.25" front side w/o 3.5" space, the opening pattern
module FrontOpeningPattern() {
    for (x= [-F525_A5+openingPatternXSpaceFromFrame: openingPatternXSpc*2: F525_A5]) {
        for (y= [openingPatternYSpaceFromFrame: openingPatternYSpc*2: F525_A2]) {
            // position of next opening
            tx= x+y/(openingPatternYSpc*2)*openingPatternShift;
            ty= y;
            // is it visible?
            if (tx >= openingPatternXSpaceFromFrame && tx <= F525_A5-openingPatternXSpaceFromFrame) {
                if (ty >= openingPatternYSpaceFromFrame && ty <= F525_A2-openingPatternYSpaceFromFrame) {
                    // then draw it
                    translate([tx,ty,0])
                        rotate([0,0,openingPatternAngle])
                            cylinder(r=openingPatternDia/2, h= frontThick+0.02, $fn= openingPatternFn);
                }
            }
        }
    }
}

// 5.25" side wall
module Side525(fullHeight= false) {
    h= fullHeight ? F525_A2-1 : 29;
    translate([-F_x0,0,0]) cube([F_x0,sideThick,h]);
}

// 5.25" bottom stripe from left to right
module BottomStripe52LR(withFront="yes") {
    add= (withFront=="yes")?0:15;
    translate([-bsd-add+frontThick,0,0]) cube([bsd+add-frontThick,F525_A5,sideThick]);
}

// 5.25" bottom stripe front to back
module BottomStripe525() {
    translate([-F_x0,0,0]) cube([F_x0,bsd,sideThick]);
}

// 5.25" side screw masks
module Side525ScrewMask() {
    for (x = [-F525_A10,-F525_A10-F525_A11]) {
        for (z = [F525_A13,F525_A14]) {
            translate([x, -0.01, z]) rotate([-90,0,0]) cylinder(r=screwDia/2-0.2, h= sideThick+0.02, $fn=20);
        }
    }
}

// ********************************************************************************
// 3.5" front/ disk bay and its support modules
// ********************************************************************************

// Front bay shapes
module FrontBay_add() {
    FD35_add();
}

// Front bay openings
module FrontBay_rem() {
    // opening for 3.5" front panel
    Front35();
    FD35_rem();
}

// Disk bay shapes
module DiskBay35_add() {
    FD35_add();
}

module FD35_add() {
    translate([0,bsd-sideThick,0]) Mount35Side();
    translate([0,F525_A5-bsd,0]) Mount35Side();

    y3= (F525_A5-F35_A4)/2;
    translate([0,y3-4,0]) BottomStripe35_add();
    translate([0,F525_A5-y3-4,0]) BottomStripe35_add();
}

// Disk bay openings
module DiskBay35_rem() {
    if (openingPattern=="yes") {
        // opening pattern
        translate([-0.01,0,0]) rotate([90,0,0]) rotate([0,90,0]) FrontOpeningPattern();
    }
    FD35_rem();
    if (withFront!="yes") {
        translate([-15,-(F525_A3-F525_A5)/2-0.01,-0.01]) cube([15.01+frontThick, F525_A3+0.02, F525_A2+0.02]);
    }
}

module FD35_rem() {
    Mount35ScrewSupport();
    y3= (F525_A5-F35_A4)/2;
    translate([0,y3-4,0]) BottomStripe35_rem();
    translate([0,F525_A5-y3-4,0]) BottomStripe35_rem();
}

// 3.5" front side or mask for 5.25" front side
module Front35() {
    translate([-0.01,(F525_A5-F35_A3)/2-spc,(F525_A2-F35_A1_2)/2-spc])
        cube([frontThick+0.02, F35_A3+2*spc, F35_A1_2+2*spc]);
}

// screw side mount positions
S35_x1= F35_A2+F35_A2_panelOffset-F35_A8; // rear screw from front
S35_x2= S35_x1-F35_A9_2; // mid screw from front
S35_x3= S35_x1-F35_A9; // front screw from front

// Big holes for 5.25 side to let screw driver reach the 3.5" side mount positions
module Mount35ScrewSupport() {
    // skip holes that would invalidate a 5.25 screw mount
    xs= shiftForFans==0 ? [-S35_x3,-S35_x2] : [-S35_x3,-S35_x1];
    for (x= xs) {
        for (y= [-0.01, F525_A5-sideThick-0.01]) {
            translate([x-shiftForFans, y, 13]) rotate([-90,0,0]) cylinder(r=12/2,h=sideThick+0.02);
        }
    }
}

// 3.5" screw side mounts
module Mount35Side() {
    xmax= F525_A10+10 + (bayLength=="short"?0:F525_A11);
    for (x = [-S35_x3,-S35_x2,-S35_x1]) {
        if (x >= -xmax) {
            translate([x-shiftForFans, 0, 0]) Mount35Side1();
        }
    }
}

// one side piece to fix 3.5" screw
module Mount35Side1() {
    difference() {
        union() {
            translate([0,0,13]) rotate([-90,0,0]) cylinder(r=12/2,h=sideThick);
            translate([-12/2,0,0]) cube([12,sideThick,13]);
        }
        translate([-7/2,-0.01,13]) rotate([-90,0,0]) Longhole(7, 3.5, sideThick+0.02);
    }
}

// 3.5" bottom screws
// sh35= (F525_A2-F35_A1_2)/2-spc;
sh35= 7;

// the bottom stripes to mount 3.5" devices from the bottom
module BottomStripe35_add() {
    xx= F35_A2+F35_A2_panelOffset-F35_A7+5;
    xmax= F525_A10+10 + (bayLength=="short"?0:F525_A11);
    x= min(xx, xmax);
    translate([-x-shiftForFans,0,0]) cube([x+1,8,sh35]);
}

module BottomStripe35_rem() {
    translate([-shiftForFans,4,-0.01]) {
        // screw thread
        Mount35BottomScrewMasks();
        // screw head
        Mount35BottomScrewMasks(true);
    }
}

// three screw holes, used for threads and heads
module Mount35BottomScrewMasks(screwHeads= false) {
    x1= F35_A2+F35_A2_panelOffset-F35_A7; // rear screw from front
    x2= x1-F35_A6; // mid screw from front
    x3= x1-F35_A13; // front screw from front
    // for (x = [-32.5,-62.5,-102.5]) {
    for (x = [-x3,-x2,-x1]) {
        translate([x,0,0])
            cylinder(r=(screwHeads?screwDia+1.1:screwDia/2), h=sh35+(screwHeads?-2:0.02));
    }
}

// ********************************************************************************
// 2.5" disk bay and its support modules
// ********************************************************************************

// Disk bay shapes
module DiskBay25_add() {
    // middle part
    translate([0,(F525_A5-bsd)/2,0]) BottomStripe525();
    translate([0,(F525_A5-2)/2,0]) Side525(true); // has to be 2mm thick
    // back side stabilizer
    translate([-F_x0+bsd-frontThick,0,0]) BottomStripe52LR("yes");
    // cones to give SSDs their height position
    HalfCones25();
}

// Disk bay openings
module DiskBay25_rem() {
    if (openingPattern=="yes") {
        // opening pattern
        translate([-0.01,0,0]) rotate([90,0,0]) rotate([0,90,0]) FrontOpeningPattern();
    }
    if (numberOfFans > 0) {
        fanStep= F525_A5/numberOfFans;
        for (n= [0:numberOfFans-1]) {
            translate([0,fanStep/2+n*fanStep-20-sideThick,0]) Fan1_rem();
        }
    }
    ScrewHoles25();
    if (withFront!="yes") {
        translate([-15,-(F525_A3-F525_A5)/2-0.01,-0.01]) cube([15.01+frontThick, F525_A3+0.02, F525_A2+0.02]);
    }
}

// 2.5 disk screw holes
module ScrewHoles25() {
    zStep= F525_A2/3;
    for (x= [-F_x0+F25_A52+F25_A6_shift,-F_x0+F25_A53+F25_A6_shift]) {
        for (z= [sideThick+F25_A23:zStep:F525_A2]) {
            translate([x, -0.01, z]) rotate([-90,0,0])
                cylinder(r2=screwDia/2+0.25,r1=screwDia/2+sideThick+0.25,h=sideThick+0.02);
            translate([x, F525_A5-sideThick-0.01, z]) rotate([-90,0,0])
                cylinder(r1=screwDia/2+0.25,r2=screwDia/2+sideThick+0.25,h=sideThick+0.02);
        }
    }
}

// 2.5 disk support cones
module HalfCones25(flip=false) {
    zStep= F525_A2/3;
    for (x= [-F_x0+16:36:-F_x0+F25_A6]) {
        for (y= [sideThick/2,F525_A5/2]) {
            for (zi= [1,2]) {
                translate([x, y, zi*zStep-3]) HalfCone25(true);
                translate([x, y+F525_A5/2-1, zi*zStep-F25_A23]) HalfCone25(false);
            }
        }
    }
}

// one support cone
module HalfCone25(flip=false) {
    r2= sideThick/2+5;
    difference() {
        cylinder(r1= sideThick/2, r2= r2, h= 5);
        translate([-r2,flip?-r2:0,0]) cube([2*r2,r2,5.01,]);
    }
}

// ********************************************************************************
// Fan support modules
// ********************************************************************************

// fan mount brackets outside
module Fan_add() {
    fanStep= F525_A5/numberOfFans;
    for (n= [0:numberOfFans-1]) {
        translate([0,fanStep/2+n*fanStep-20-sideThick,0]) Fan1_add();
    }
}

// fan mount brackets openings
module Fan_rem() {
    fanStep= F525_A5/numberOfFans;
    for (n= [0:numberOfFans-1]) {
        translate([0,fanStep/2+n*fanStep-20-sideThick,0]) Fan1_rem();
    }
}

// one fan bracket outer shape
module Fan1_add() {
    translate([-fanWidth-2-spc,0,1]) cube([fanWidth+2+spc, 40+2*sideThick,40]);
}

// one fan bracket openings
module Fan1_rem() {
    translate([-fanWidth,sideThick,1]) cube([fanWidth+spc, 40,40.02]);
    translate([-fanWidth-2.01-spc,2*sideThick,1]) cube([fanWidth++2.01+spc, 40-2*sideThick,42]);
}

// ********************************************************************************
// Other support modules
// ********************************************************************************

// ////////////////////////////////////////////////////////////////////////////////
// fast longhole
// Longhole(20, 3, 2);
module Longhole(w, d, th) {
    translate([d/2, 0, 0]) cylinder(r=d/2, h= th, $fn=40);
    translate([w-d/2, 0, 0]) cylinder(r=d/2, h= th, $fn=40);
    translate([d/2, -d/2, 0]) cube([w-d, d, th]);
}

```


[User]
Hey, I need some help designing a tin can stacker. I have a basic design in mind, but I need it to be parametric so I can adjust it for different cans.

Basically, it's a ring with a lip on both the top and bottom that fits inside the can's rim, allowing the can to stack securely on another can. I'd like to control several parameters:

*   `Maximum_Inner_Can_Diameter`: This is the diameter of the *larger* of the two inner rims on the can, if they're different sizes.
*   `Inner_Diameter_Difference`: This is the difference in diameter between the top and bottom inner rims. Most cans don't have a difference, but some do.
*   `Can_Rim_Depth`: This is the depth of the can's rim.
*   `Can_Stacker_Thickness`: This is the thickness of the stacker itself.

The stacker should have a step on each side to fit the can rim, and a slightly smaller diameter at its center. Additionally, it needs a cutout to accommodate the can's pull tab. This cutout can be created with a linear extrusion of a triangle.

I'd like to generate this in OpenSCAD, can you generate the code for me? I don't have any specific libraries in mind, so use whatever you think would work best for this project.

[Assistant]
Tin_Can_Stacker.scad:
```
// Diameter of the inner rim of the can, in mm. If top and bottom diameters are different, put the largest of the two in here
Maximum_Inner_Can_Diameter=64;

// Difference between top and bottom inner rim diameters, in mm.
Inner_Diameter_Difference=0.5; // [0, 0.5, 1, 1.5]

// Depth of the can rim, in mm.
Can_Rim_Depth=3.5; // [2,2.5,3,3.5,4,4.5,5,5.5,6,6.5,7]

// Thickness of the stacker, in mm
Can_Stacker_Thickness=5;  // [3:10]

difference() {
    difference (){
        union() {
            cylinder(d1=Maximum_Inner_Can_Diameter, d2=Maximum_Inner_Can_Diameter-Inner_Diameter_Difference, h=(2 * Can_Rim_Depth)- 0.8, center=true, $fn=360);
            translate(v = [0,0,Can_Rim_Depth - 0.4]) {
                cylinder(d1=Maximum_Inner_Can_Diameter-Inner_Diameter_Difference, d2=Maximum_Inner_Can_Diameter - Inner_Diameter_Difference - 0.8, h=0.4, $fn=360);
            }
            translate(v = [0,0,-Can_Rim_Depth]) {
                cylinder(d2=Maximum_Inner_Can_Diameter, d1=Maximum_Inner_Can_Diameter - 0.8, h=0.4, $fn=360);
            }
        }
    cylinder(d=Maximum_Inner_Can_Diameter - (Can_Stacker_Thickness * 2), h=(4 * Can_Rim_Depth), center=true, $fn=360);
    };
    linear_extrude(h=(2 * Can_Rim_Depth), center=true) {
        polygon(points=[[0,0],[Maximum_Inner_Can_Diameter,2*Maximum_Inner_Can_Diameter],[-Maximum_Inner_Can_Diameter, 2*Maximum_Inner_Can_Diameter]]);
    }
}
```


[User]
Hey, I'm looking to create a parametric model of a small reciprocating engine that can be attached to a stepper motor. I have a good idea of the different parts I need, and how they fit together - I even have an example SCAD file of what I'm looking for that I can provide. I need to be able to adjust things like the number of cylinders, the distance between the stepper motor screw holes, and the tolerance between moving parts. I'd like to be able to generate the necessary files using OpenSCAD and then export them as STLs for 3D printing.

Here's what I'm thinking:

**Key Features:**

*   **Configurable Cylinder Count:**  I'd like to be able to switch between a single cylinder, a V-twin (two cylinders at an angle), a flat twin (two cylinders opposite each other), a three cylinder W formation, and a radial four cylinder arrangement. The angle between cylinders should be adjustable as well (90 for a V-Twin, 180 for a flat twin, and so on...). If a specific configuration is chosen, there needs to be an associated default.
*   **Motor Mount:** The engine block should attach to a standard NEMA 17 stepper motor using circular tabs that fit into the screw holes. I need to be able to adjust the diameter of these tabs to ensure a tight fit. The distance between the screw holes on the motor should also be a parameter.
*   **Tolerances:** I need to control the tolerance between moving parts like pistons and cylinders to ensure the final printed result works smoothly. It should be an adjustable parameter.
*   **Crankshaft:** The crankshaft should be designed to connect to the motor shaft, probably using a magnet. The crankshaft length needs to be configurable.
*   **Connecting Rods and Pistons:** These need to be generated according to the cylinder layout, and need to connect the crankshaft to the pistons. The connecting rods should be designed to flex slightly around the pin head. Piston size should be adjustable.
*   **Propeller (Optional):**  It would be great to have an optional propeller that attaches to the crankshaft. I'd like to be able to control the number of blades and the direction of rotation (clockwise or counter-clockwise).

**Parameters I Need to Control:**

*   `CONFIGURATION`: An integer value for specifying how many cylinders and the cylinder formation (1, 2, 3, 4). A value of 20 will indicate 2 cylinders in a flat configuration.
*   `MOTOR_SIZE`: The distance between the stepper motor screw holes (in millimeters).
*   `MOUNTING_TAB_SIZE`: The diameter of the mounting tabs (in millimeters).
*   `TOLERANCE`: The tolerance between moving parts (in millimeters).
*   `ENABLE_PROPELLER`: A binary (0 or 1) option to enable or disable the propeller.
*  `PROPELLER_BLADES`: An integer for the number of blades (1 to 8).
*  `PROPELLER_DIRECTION`: An integer of 1 (clockwise) or -1 (counter-clockwise) for rotation direction.

**Specific Modules/Parts:**

I'm envisioning the following modules:

*   `pin()`: A small cylinder to be used as a pin.
*   `crank()`: The crankshaft with a pin and attachment area.
*   `spacer()`: A small cylindrical spacer for the crankshaft.
*   `ring(height)`: A split ring that flexes over a pin.
*   `rod()`: The connecting rod that attaches the piston to the crankshaft.
*   `piston_body()`: The main body of the piston.
*   `piston()`: The complete piston including a pin.
*   `mount()`: A single mounting tab.
*   `mounts()`: Generates all the mounting tabs based on the chosen cylinder configuration.
*   `sleeve_outline()`: The outline of the cylinder sleeve.
*   `sleeve_cutout()`: The cutout of the cylinder sleeve.
*   `sleeve()`: The assembled cylinder sleeve.
*   `block()`: The main block of the engine that holds all the parts.
*   `propeller_blade()`: A single blade of the optional propeller.
*   `propeller()`: The complete propeller assembly.

**Libraries:**
I'm not sure if there are any standard libraries that would help with this. I don't think there are any required. Let me know what you think, and if this is something you can help me with! I'm happy to go into more detail or provide the sample code if it would be helpful.

[Assistant]
engine.scad:
```
// Number and angle of cylinders
CONFIGURATION = 2; // [1:1 Cylinder, 2:2 Cylinders (V), 20:2 Cylinders (Flat), 3:3 Cylinders, 4:4 Cylinders]

// Distance between the stepper motor screws in millimeters
MOTOR_SIZE = 31; // [20:0.1:50]

// Diameter in millimeters inside the motor screw holes
MOUNTING_TAB_SIZE = 6.15; // [4.5:0.01:7.5]

// Tolerance between moving parts
TOLERANCE = 0.3; // [0.1:0.05:0.5]

/* [Propeller] */

ENABLE_PROPELLER = 0; // [0:No, 1:Yes]
PROPELLER_BLADES = 2; // [1:1:8]
PROPELLER_DIRECTION = 1; // [1:Clockwise, -1:Counter Clockwise]

$fn     = 24 + 0;                // Curve resolution
PIN     = 2 + 0;                 // Pin radius
WALL    = 2 + 0;                 // Wall thickness
MOUNT   = MOUNTING_TAB_SIZE / 2; // Motor mount tab radius
SQRT2   = sqrt(2);               // Square root of 2
TOLHALF = TOLERANCE / 2;         // Half of the part tolerance
CRANK   = MOTOR_SIZE / 6;        // Crankshaft length
ROD     = MOTOR_SIZE / 2;        // Connecting rod length
PISTON  = MOTOR_SIZE / 3.1;      // Piston size
SLEEVE  = CRANK+ROD+PISTON/2+WALL+1;             // Cylinder length from center
CYLINDERS = CONFIGURATION == 20 ? 2 : CONFIGURATION; // Number of cylinders
CYLINDER_ANGLE = CONFIGURATION == 20 ? 180 : 90;     // Angle between cylinders
PIN_HEIGHT = CYLINDERS == 1 ? 4 : CYLINDERS+2;

/***************************************
 *  Layout all the parts for printing  *
 ***************************************
 */

translate([0,0,4])
	rotate(180, [0,1,0])
		block();

translate([0, CRANK/2, 0])
	crank();

translate([0, MOTOR_SIZE+PISTON, 0])
	spacer();

y = CYLINDERS == 4 ? MOTOR_SIZE : CRANK*3;
x = PISTON * 2;
offset = (CYLINDERS-1) * PISTON;

for (i = [0:CYLINDERS-1]) {
	translate([-offset + x*i, -y-PISTON, 0]) {
		piston();
		translate([0, -PISTON-ROD, 0])
			rod();
	}
}

if (ENABLE_PROPELLER) {
	translate([MOTOR_SIZE*2.5, 0, 0])
		propeller();
}

/**************************************************
 *  Modules for building each part of the engine  *
 **************************************************
 */

module pin() {
	// Pin body
	cylinder(r = PIN, h = PIN_HEIGHT);

	// Upper cone
	translate([0, 0, PIN_HEIGHT+0.5])
		cylinder(r1 = PIN + 0.5, r2 = PIN, h = 0.5);

	// Lower cone
	translate([0, 0, PIN_HEIGHT])
		cylinder(r1 = PIN, r2 = PIN + 0.5, h = 0.5);
}

module crank() {
	translate([0, -CRANK, 0])
		union() {
			pin();

			// Middle filler
			translate([-4, 0, 0])
				cube([8, CRANK, 2]);

			// Lower circle
			cylinder(r = 4, h = 2);

			// Upper circle
			translate([0, CRANK, 0])
				cylinder(r = 4, h = 2);

			if (ENABLE_PROPELLER) {
				p = PIN * 2 / SQRT2; // pin size
				translate([0, 0, PIN_HEIGHT+3])
					cube([p, p, 4], true);
			}
		}
}

module spacer() {
	cylinder(r = 4, h = 2); // crankshaft spacer
}

// Connecting rod ring with a split for flexing over the pin head
module ring(height) {
	difference() {
		cylinder(r = PIN+TOLHALF+1, h = height);
		translate([0, 0, -1])
			cylinder(r = PIN+TOLHALF, h = height+2);
		translate([-TOLERANCE/2, 0, -1])
			cube([TOLERANCE, PIN+TOLERANCE+1, height+2]);
	}
}

module rod() {
	union() {
		difference() {
			union() {
				// Half height ring
				rotate([0, 0, 180])
					ring(CYLINDERS == 1 ? 2 : 1);

				// Middle bar
				translate([-1, PIN+TOLERANCE+1, 0])
					cube([2, ROD-PIN*2-TOLERANCE*1.5-1, 2]);

				// Lower bar filler
				translate([-1, PIN+TOLHALF, 0])
					cube([2, 2, CYLINDERS == 1 ? 2 : 1]);
			}
		}

		// Full height ring
		translate([0, ROD, 0])
			ring(2);
	}
}

module piston_body() {
	w = PISTON + 2;
	a = (w-TOLERANCE*2) / SQRT2;
	s = PISTON - TOLERANCE*2;

	translate([0, 0, 1])
		difference() {
			rotate([0, 45, 0])
				cube([a, PISTON, a], true); // main piston body

			translate([0, 0, PISTON/2+1])
				cube([s, PISTON+1, PISTON], true); // flatten top

			translate([0, 0, -PISTON/2-1])
				cube([s, PISTON+1, PISTON], true); // flatten bottom
		}
}

module piston() {
	union() {
		pin();

		difference() {
			piston_body();

			// curved cutout
			translate([0, -CRANK*2+1, -1])
				cylinder(r = CRANK+1, h = 4, $fn = PISTON/10*$fn);
		}
	}
}

module mount() {
	d = MOUNT/2;
	w = PISTON + 2*WALL;

	translate([0, 0, -1])
		difference() {
			cylinder(r = MOUNT, h = 1); // outside diameter
			translate([0, 0, -1])
				cylinder(r = MOUNT-0.6, h = 3); // inside diameter
		}

	// upper crossmember
	translate([-w/2, MOUNT-2, 0])
		cube([w, 2, 1]);

	// lower crossmember
	translate([-w/2, -MOUNT, 0])
		cube([w, 2, 1]);

	// right support
	translate([MOUNT-1, -MOUNT/2, 0])
		cube([1, MOUNT, 0.5]);

	// left support
	translate([-MOUNT, -MOUNT/2, 0])
		cube([1, MOUNT, 0.5]);
}

module mounts() {
	s = MOTOR_SIZE / 2 * SQRT2;
	offset = (CYLINDERS-1) * CYLINDER_ANGLE/2;

	for (i = [0:CYLINDERS-1])
		rotate([0, 0, offset-i*CYLINDER_ANGLE])
			translate([0, s, 0])
				mount();
}

module sleeve_outline() {
	w = PISTON + 2*WALL;
	W = w + 2;
	a = W / SQRT2;

	if (WALL < 2)
		translate([0, SLEEVE/2, 3])
			intersection() {
				rotate([0, 45, 0])
					cube([a, SLEEVE, a], true); // piston track bulge

				cube([W, SLEEVE, 2], true); // flatten top
			}

	translate([-w/2, 0, 0])
		cube([w, SLEEVE, 4]);
}

module sleeve_cutout() {
	a = (PISTON+2) / SQRT2;

	translate([-PISTON/2, -WALL, -1])
		cube([PISTON, SLEEVE, 6]);

	translate([0, SLEEVE/2 - WALL, 3])
		rotate([0, 45, 0])
			cube([a, SLEEVE, a], true); // piston track grooves
}

module sleeve() {
	difference() {
		sleeve_outline();
		sleeve_cutout();
	}
}

module block() {
	offset = (CYLINDERS-1) * CYLINDER_ANGLE/2;
	union() {
		difference() {
			union() {
				for (i = [0:CYLINDERS-1])
					rotate([0, 0, offset-i*CYLINDER_ANGLE])
						sleeve_outline();

				// block housing outer wall
				cylinder(r = CRANK+5+WALL, h = 4, $fn = $fn*2);
			}

			for (i = [0:CYLINDERS-1])
				rotate([0, 0, offset-i*CYLINDER_ANGLE])
					sleeve_cutout();

			// block housing inner wall
			translate([0, 0, -1])
				cylinder(r = CRANK+5, h = 6, $fn = $fn*1.5);
		}

		mounts();
	}
}

module propeller_blade() {
	l = MOTOR_SIZE + 4; // length of propeller blade

	translate([l/2+l/10, 0, 0]) {
		linear_extrude(2) {
			hull() {
				scale([1, 0.25, 1])
					circle(d = l, $fn = $fn*2); // main body

				translate([-l/2-l/10, 0, 0])
					square(PROPELLER_BLADES>5 ? 1/PROPELLER_BLADES : 3, true);

				translate([l/2-l/40, -l/15*PROPELLER_DIRECTION, 0])
					circle(d = l/20); // wingtip
			}
		}
	}
}

module propeller() {
	p = (PIN * 2 + TOLHALF) / SQRT2; // pin size
	a = 360 / PROPELLER_BLADES;
	s = PIN*2+TOLERANCE*2; // pin slot extension size

	difference() {
		union() {
			cylinder(d = 8, h = 2); // center hub

			for (i = [0:PROPELLER_BLADES-1]) {
				rotate(a*i, [0,0,1])
					propeller_blade();
			}

			translate([CRANK-s/2, -s/2, 0])
				cube([s, s, 4]); // pin slot extension
		}

		translate([CRANK, 0, 2])
			cube([p+0.1, p+0.1, 6], true); // pin slot
	}
}

```