[User]
Hi, I'd like a parametric OpenSCAD design for a small box with a lid. I've got some specific ideas about how it should be constructed.

Essentially, I want a box where the walls are made from two extruded shapes. The top and bottom edges should be chamfered at 45 degrees, and all the corners should be filleted. The box should be split into two pieces; a base and a lid. I want to be able to control the split position of the base and lid using a parameter as a ratio of the total height.

Here are the main parameters I need control over:
*   **bwidth**: The length of the box along the x-axis.
*   **bdepth**: The length of the box along the y-axis.
*   **height**: The total height of the assembled box.
*   **radius**: The radius of the fillets on the corners.
*   **chamfer**: The size of the chamfer.
*   **position**: A ratio value (between 0 and 1) indicating the split of the base and lid by height.
*   **overlap**: The length of the overlap between the base and the lid.
*   **tol**: The tolerance for the fitting between the lid and base.
*   **wall**: The thickness of the walls.
*   **facets**: The number of facets used in generating the curves for fillets and chamfers.

I also need to be able to turn off some debugging helper shapes.

The design should generate three main parts:
1.  **Endcaps**:  These form the chamfered top and bottom of the main box and the lid.
2.  **Main body**: The body section of the base and the lid.
3.  **Coupler**: A section to join the base and the lid together, with a small gap for the lid to fit into the base.

I'd like the design to be somewhat tolerant, meaning that if a bad combination of radii or chamfer values are provided, it shouldn't crash outright. Just fail gracefully, if possible. It should also have a good tolerance value that I can adjust in the parameters and allow a slip fit for most home 3D printers.

Ideally the code should be easy to read and well commented, with a clear separation of modules. I'm happy to use helper functions or modules if they help simplify the overall code. I think the code I provided will need a few of these to make the whole project a little more manageable. I'm also okay if some of the less critical calculation steps are hard coded.

I want the model to be rendered as a single .stl output, where the lid is placed next to the base on the X axis.

Thanks!

[Assistant]
ParametricBox.scad:
```
//
// Main user inputs
//

/* [Basic Dimensions] */
//x-axis length (mm)
bwidth = 40; // [10:200]
//y-axis length (mm)
bdepth = 25; // [10:200]
//z-axis length (mm)
height = 90; // [10:200]
//filet radius (mm)
radius = 3;   // [2.0:0.1:5.0]
//chamfer size (mm)
chamfer= 2.5; // [.8:.1:5]
// top/bottom split
position=.50; // [.1:.05:.9]

/* [Expert Settings] */
// Overlap (mm)
overlap = 20;
// Fit Tolerance (mm)
tol = .15;
// Wall Thickness (mm)
wall = .8;

// Number of facets in each filet. Try 1 for chamfered
// corners, more for smoother features.
// Caution, this can really increase caluclaiton time
// Facets
facets = 3;

/* [Hidden] */
// Turn off the extra debug shapes and cutouts
debug = false;

// Apply facets parameter to $fn. Forcing that to a
// multiple of 4 ensures symetry
$fn = 4*facets;

// Calculate the heights. Caution: if either goes
// below 1/2 the overlap there will be problems
bheight = height*position;
theight = height*(1-position);

// derive all the radii
r0 = radius;
r1 = radius - chamfer;
r2 = radius - (chamfer+.707*wall);
r3 = radius - .707*wall;
r4 = radius - wall;
r5 = radius - (2*wall+tol);  //inside r at coupler

//Echo these radii. If any go negative
//the shapes they define will fail
if (debug) echo(r0,r1,r2,r3,r4, r5);

//centers of all rounded corners (centered on origin)
rpos = [bwidth/2-r0, bdepth/2-r0];

//debug, show common centers for all rounded corners
if (debug) linear_extrude(bheight+overlap/2) rect_pattern(rpos) circle(.25);

// Form the base
endcap(chamfer);
main_body(bheight, chamfer);
coupler(bheight, overlap);

// Form the lid and move it over a bit
translate([bwidth+10, 0,0]) {
    endcap(chamfer);
    main_body(theight, chamfer);
}

// Coupling section for base and lid.
module coupler(h, lap) {
    difference() {
        // outside done in two sections to allow for tolerance
        union() {
            hull() {
                translate([0,0,h-lap/2]) rounded_pad(rpos, r4);
                translate([0,0,h]) rounded_pad(rpos, r4);
            }
            hull() {
                translate([0,0,h]) rounded_pad(rpos, r4-tol);
                translate([0,0,h+lap/2]) rounded_pad(rpos, r4-tol);
            }
        }
        // Remove the indide
        hull() {
            translate([0,0,h-lap/2]) rounded_pad(rpos, r5);
            translate([0,0,h+lap/2]) rounded_pad(rpos, r5);
        }
        // Remove the small overhang
        hull() {
            translate([0,0,h-lap/2]) rounded_pad(rpos, r4);
            translate([0,0,h-lap/2+(wall+tol)]) rounded_pad(rpos, r5);
        }
        if (debug) cube([bwidth,bdepth,h+lap]);
    }
}

// Main body given height and chamfer size
module main_body(h,c) {
    difference() {
        hull() {
            translate([0,0,c]) rect_pattern(rpos) cylinder (.001, r0, r0);
            translate([0,0,h]) rect_pattern(rpos) cylinder (.001, r0, r0);
        }
        hull() {
            translate([0,0,c]) rect_pattern(rpos) cylinder (.001, r4, r4);
            translate([0,0,h]) rect_pattern(rpos) cylinder (.001, r4, r4);
        }
        if (debug) cube([bwidth,bdepth,bheight+overlap]);
    }
}

// Create the rounded and chamfered endcaps
module endcap(c) {
    difference() {
        hull() {
            rect_pattern(rpos) cylinder (.01, r1, r1);
            translate([0,0,c]) rect_pattern(rpos) cylinder (.001, r0, r0);
            // extends the shape into the vertical wall so no need
            // to calculate the upper edge position on the inside
            translate([0,0,c+wall]) rect_pattern(rpos) cylinder (.001, r3, r3);
        }
        hull() {
            translate([0,0,wall]) rect_pattern(rpos) cylinder (.001, r2, r2);
            translate([0,0,c+wall]) rect_pattern(rpos) cylinder (.001, r3, r3);
        }
        if (debug) cube([bwidth,bdepth,bheight+overlap]);
    }
}

// place 4 round pads at the inner rectangles courners
// this always gets used with a hull, so can skip the
// inner hull() operation here. Might make it faster?
module rounded_pad(pos, rad) {
    //hull() rect_pattern(pos) cylinder (.001, rad, rad);
    rect_pattern(pos) cylinder (.001, rad, rad);
}

// places shapes in a rectangular pattern on xy-plane
module rect_pattern(p) {
    translate([ p.x, p.y]) children();
    translate([ p.x,-p.y]) children();
    translate([-p.x, p.y]) children();
    translate([-p.x,-p.y]) children();
}

```


[User]
Hey there! I'm looking to generate a set of Christmas-themed houses using OpenSCAD, and I need a parametric design that can produce a variety of styles. I’ve got some ideas, but I'm not a coding whiz, so I could use some help building the actual SCAD files.

Ideally, the system should have the following features:

**Base Structure:**

*   Each house will be based on a cube, the base length of which should be configurable as `cl`.
*   Wall thickness should be parametric (`th`), and I'd like to target about twice the nozzle diameter (1.2mm for example).
*   The height of each storey should be a fraction of `cl` (something like `wh = cl*0.80`).
*   The overall house height will vary between one and two storeys.
*   I'd like to use a random seed (`seed_value`) to generate the house variability.

**Walls and Windows:**

*   Walls should have options for several styles of windows.
*   I'm thinking of styles like single doors, two windows, a centered single window, three windows, and a style with no windows at all (perhaps with some small brick details).
*   The window frames (`tf`) should be a configurable thickness, possibly also a function of the nozzle diameter (`tf = cl*0.05`).
*   I'd like to be able to position windows by specifying their x, z, width, and height in a list structure.
*   I'd like to ensure that the window clears go all the way through the wall and have their own frame.
*   I'm trying to avoid any interior walls inside the houses, as they will be lit up.
*   There should be brick pattern details on the windowless walls.

**Roofs:**

*   I want to support two main roof types: hipped and pitched.
*   The roof should have a configurable thickness (`t_roof`).
*   It should also have an optional overhang (`overhang`), that is configurable. This might be useful as a print aid.
*   Dormers should also be supported. The dormers should be placed along the longer side of the roof and their size and spacing should be randomized
*   I'd like for the roof to sit nicely on the walls. I want to be able to specify an overhang, which may be helpful for printing.
*   I'd like to avoid any interior roof structures, so that a light can be placed inside.
*   A fillet should be added to the roof to make it printable without supports.

**Terrain and Extras:**

*   I’d like to include a base or terrain piece for each house, which should also be parametric and have a rounded shape. This terrain should provide a stable platform for each house.
*   The terrain should be configurable, perhaps by a brim width or other values, such that houses are either inset or offset from the base.
*   I'd like to add some simple features, like trees, logs, and barrels around the house. These should be randomized.
*   I want a control to turn on or off the extra features (`want_features`).

**Parametric Control:**
* There should be a random seed, that can be set with the variable `seed_value`. This should influence not just the wall, window and dormer styles, but all other aspects of the model.

**Technical Notes:**

*   I'm thinking that the house body should always be filled, but the roof may need an internal subtraction to prevent it from filling the entire house volume.
*   I'd like to use the `r_cube` module, to round the edges of the windows.
*  I’d like to use `minkowski` and `hull` to generate the terrain piece.
*   I’m interested in `linear_extrude`, `rotate_extrude` and `polyhedron` for more complex parts.
*   I don’t need any external libraries, this should all be doable in vanilla OpenSCAD.

Could you please generate the necessary OpenSCAD files for this project? I'm looking forward to seeing what you come up with!

[Assistant]
christmas_houses.scad:
```
// --------------------------------
// CONFIGURATION
// --------------------------------
// [mm] Cube Length for the house's base dimensions
cl = 50;

// [mm] Thickness of the wall. Put 2x your nozzle diameter here.
th = 1.2;

// [-] Seed value for random house generation.
seed_value = 7;

// [1/0] Want features around the house?
want_features=1;

module __Customizer_End__ () {}  //

$fn = 16;

// use <house_terrain.scad>

wh = cl*0.80;   // [mm] height of straight wall per storey
tf = cl*0.05;   // [mm] window frame thickness
storeys=rands(0,2,1, seed=seed_value)[0]>0.99 ? 1:2;

// ---------------------------------
// R_CUBE
// a box with rounded edges and corners.
// -size   : edge length, OR a 3D vector [x,y,z] for the dimension of the box
// -fr     : the fillet (edge rounding) radius
// -center : as in cube()
// ---------------------------------
module r_cube(size, fr=1, center=false){

    if(fr<=0){
        cube(size, center=center);
    }else{
        eps=fr/1000;
        vecinner = is_list(size)
            ? [max(eps, size[0]-2*fr), max(eps, size[1]-2*fr), max(eps, size[2]-2*fr)]
            : [max(eps, size-2*fr), max(eps, size-2*fr), max(eps, size-2*fr)];

        maxouter = is_list(size) ? max(size) : size;

        fr=min(fr, maxouter/2);
        vecmiddle = center ? -0.5*vecinner : [fr,fr,fr];
        minkowski(){
            translate(vecmiddle)
            cube(vecinner);

            sphere(r=fr);
        }
    }
}

//The top point will be centred
// https://github.com/orionrobots/openscad_parts_library
// http://creativecommons.org/licenses/by
module pyramid(w, l, h) {
	mw = w/2;
	ml = l/2;
	polyhedron(points = [
		[0,  0,  0],
		[w,  0,  0],
		[0,  l,  0],
		[w,  l,  0],
		[mw, ml, h]
	], faces = [
		[4, 1, 0],
		[4, 3, 1],
		[4, 2, 3],
		[4, 0, 2],
		//base
		[0, 1, 2],
		[2, 1, 3]
	]);
}

module wall_with_windows(wall_width = 100, wall_height = 60, wall_thickness = 10,
                         window_width = 40, window_height = 30, frame_thickness = 2,
                         window_positions ) {
    /*
    Parameters:
        wall_width, wall_height, wall_thickness - Dimensions of the wall.
        window_width, window_height - Dimensions of a single window.
        frame_thickness - Thickness of the window frame.
        window_positions - List of [x, z, w, h] of each window
    */
    eps=1e-3;

    // Main wall
    difference()
    {
        union(){
            cube([wall_width, wall_thickness, wall_height], center = false);
            // Window frames
            for (pos = window_positions) {
                translate([pos[0]
                           , -min(wall_thickness, frame_thickness/2)
                           , pos[1]-pos[3]])

                        difference(){
                            r_cube(fr=frame_thickness/3
                                 , size=[pos[2], wall_thickness, pos[3]]
                                 , center = false);

                           // cut part that would be inside the house
                           // translate([-eps, wall_thickness+min(wall_thickness, frame_thickness/2)-eps, -eps])
                           // cube([pos[2]+2*eps, frame_thickness, pos[3]+2*eps]);
                       }
            }
        }

        // Subtract each window clearing
        union(){
            for (pos = window_positions) {
                if(pos[2]>2*frame_thickness && pos[3]>2*frame_thickness){
                translate(    [pos[0] + frame_thickness
                            , -wall_thickness*2
                            , pos[1]-pos[3] + frame_thickness])
                    // translate([frame_thickness, frame_thickness, frame_thickness])
                    cube([pos[2]-2*frame_thickness, wall_thickness*2+frame_thickness, pos[3]-2*frame_thickness]
                        , center = false);
                    }
            }

        }

        // subract any bricks inside the house
        translate([-wall_width/2, wall_thickness, -wall_height/2]) cube([wall_width*2, wall_thickness+frame_thickness, wall_height*2], center=false);

    }
}

// roof part of the house, at x,y=0
// house body is filled
module roof_with_wall(house_length = 100, house_width = 60
                    , roof_thickness = 5, roof_overhang = 10, hipped=false) {

    fillet = 0.4; // (roof_overhang>0) ? min(roof_thickness * 0.5, roof_overhang/2) : 0.0;

    /*
    Parameters:
        house_length - Length of the house (Y direction).
        house_width - Width of the house (X direction).
        house_height - Height of the house (Z direction).
        roof_thickness - Thickness of the roof panels.
        roof_overhang - Overhang of the roof beyond the house walls.
    */

    // Triangular house body
    module house_body() {
        union(){
            translate([house_width/2, house_length, roof_overhang])
            rotate([90,0,0])
            linear_extrude(height = house_length)
                polygon(points = [[-house_width/2, 0], [house_width/2, 0], [0, house_width/2]]);
            cube([house_width, house_length, roof_overhang+0.001]); // roof knee height

        }
    }

    module roofL(){
        if(roof_overhang>0){
            translate([-roof_overhang,-roof_overhang,0])
            rotate([0,-45,0])
            r_cube(fr=fillet,
            // cube(
            [house_width*sqrt(2)/2 + (roof_overhang)*sqrt(2) + roof_thickness // from the eaves to the roof ridge
            , house_length + 2*roof_overhang // front face to center of main roof
            , roof_thickness], center=false);
        }else{
            translate([-roof_overhang,-roof_overhang,0])
            rotate([0,-45,0])
            translate([0,0,-roof_thickness*(4-1)])
            cube(
            [house_width*sqrt(2)/2 + (roof_overhang)*sqrt(2) + roof_thickness // from the eaves to the roof ridge
            , house_length + 2*roof_overhang // front face to center of main roof
            , roof_thickness*4], center=false);
        }

    }
    module roofR(){
        translate([house_width,0,0])
        scale([-1,1,1])
        roofL();

    }

    // Combine house body and roof
    union() {
        // always the hipped top, for printing overhangs
        translate([0,0,fillet]) // -roof_overhang+roof_thickness/2*sqrt(2)+fillet])
        minkowski(){
            side = house_width+2*roof_overhang-2*fillet;
            // roof(method="straight"){
            //    translate([-roof_overhang+fillet, -roof_overhang+fillet,0])
            //    square([side, side]);
            //}
            // w/o roof command
            translate([-roof_overhang+fillet, -roof_overhang+fillet,0]) pyramid(w=side, l=side, h=side/2);
            sphere(r = fillet);
        }
        if(hipped) {
            // hipped roof
            //translate([-roof_overhang, -roof_overhang,1.1])
            //roof(method="straight")
            //square([house_width+2*roof_overhang, house_length+2*roof_overhang]);
        } else {
            // pitched roof
            house_body();
            roofL();
            roofR();
        }

        // box for inner part of the house
        if(roof_overhang==0){
            h = (roof_overhang+roof_thickness*sqrt(2))*3;
            translate([0,0,-h+0.01])
            cube([house_width, house_length, h], center=false);
        }

        // this is the bottom of the roof, to make the overhang printable
        translate([0,0,fillet])
        difference(){
            // the same roof, but in negative z direction
            scale([1,1,-1])
            minkowski(){
                side = house_width+2*roof_overhang-2*fillet;
                translate([-roof_overhang+fillet, -roof_overhang+fillet,0]) pyramid(w=side, l=side, h=side/2);
                sphere(r = fillet);
            }

            // a large cube, cutting at the intersection roof/house
            translate([-roof_overhang,-roof_overhang,-(roof_overhang+fillet)])
            scale([1,1,-1])
            cube([house_width*2, house_length*2, house_width+house_length]);
        }
    }
}

// overhang=0: make a negative body to subtract from this roof
// overhang>0: Make a solid body (house filled) with an overlapping roof
// z=0: outer rim. There's an "overhang" high print aid added.
module make_roof(width, length, t_wall, t_roof, overhang=0, hipped=true){
    eps=0.1;
    if(overhang>0){
        roof_with_wall(house_length=length, house_width=width, roof_thickness=t_roof, roof_overhang = overhang, hipped=hipped);
    }else{
        translate([t_wall,t_wall, t_roof*sqrt(2) + t_roof - t_wall])
        roof_with_wall(house_length=length-2*t_wall, house_width=width-2*t_wall, roof_thickness=t_roof, roof_overhang = 0, hipped=hipped);
    }
}

// length=y direction. That's the side, where dormers will be added
module roof_with_dormers(width, length, t_wall, t_roof, dormer_width, overhang, hipped){
    dormer_spacing = dormer_width + 3*overhang; // axis space between dormers
    dormer_count = floor(length / dormer_spacing);

    dormer_offset = (length - dormer_count * dormer_spacing) /2 + overhang*sqrt(2);

    translate([0,0,overhang]) // move to face of wall (that's the printing aid below the roof)
    difference(){
        union(){
            // main roof
            make_roof(width, length, t_wall, t_roof, overhang, hipped=hipped);
            // dormers
            for(i = [0:dormer_count-1]){
                translate([width, dormer_offset + i * dormer_spacing, 0])
                rotate([0,0,90])
                make_roof(dormer_width, width/2, t_wall, t_roof, overhang, hipped=false);
            }
        }

        // same but negative
        union(){
            // main roof
            make_roof(width, length, t_wall, t_roof, overhang=0, hipped=hipped);
            // dormers
            for(i = [0:dormer_count-1]){
                translate([width, dormer_offset + i * dormer_spacing, 0])
                rotate([0,0,90])
                make_roof(dormer_width, width/2, t_wall, t_roof, overhang=0, hipped=false);
            }
        }
    }
}

module full_house(cl, wh, tf, th, nstorey) {

window_list=[
    // style 0 - the door
    [
      [cl*.10, wh*.90, cl*.30, cl*.40]
    , [cl*.60, wh*.90, cl*.30, wh*.90]
    // bricks
    , [tf*2, tf*2, tf*4,tf*2]
    , [tf*7, tf*2, tf*4,tf*2]
    , [tf*4, tf*5, tf*4,tf*2]
    ]

    // style 1 - two windows
    ,[
      [cl*.10, wh*.90, cl*.30, cl*.40]
    , [cl*.60, wh*.90, cl*.30, cl*.40]
    // bricks
    , [tf*2, tf*2, tf*4,tf*2]
    , [tf*7, tf*2, tf*4,tf*2]
    , [tf*4, tf*5, tf*4,tf*2]
    ]

    // style 2 - one center window
    ,[
     [cl*.35, wh*.90, cl*.30, cl*.40]
    // bricks
    , [tf*3, tf*2, tf*4,tf*2]
    , [tf*4, tf*5, tf*4,tf*2]
    , [tf*1, tf*8, tf*4,tf*2]
    , [cl-tf*(4+4), tf*2, tf*4,tf*2]
    , [cl-tf*(3+4), tf*5, tf*4,tf*2]
    , [cl-tf*(1+4), tf*8, tf*4,tf*2]
    ]

    // style 3 - three windows
    ,[
      [cl*(.30-0.1), wh*.90, cl*.2, cl*.60]
     ,[cl*(.50-0.1), wh*.90, cl*.2, cl*.60]
     ,[cl*(.70-0.1), wh*.90, cl*.2, cl*.60]
    // bricks
    , [tf*1, tf*2, tf*4,tf*2]
    , [tf*7, tf*2, tf*4,tf*2]
    ]

    // style 4 - no windows
    ,[
    // bricks
    [tf*3, tf*2, tf*4,tf*2]
    , [tf*4, tf*5, tf*4,tf*2]
    , [tf*1, tf*8, tf*4,tf*2]
    , [cl-tf*(4+4), tf*2, tf*4,tf*2]
    , [cl-tf*(3+4), tf*5, tf*4,tf*2]
    , [cl-tf*(1+4), tf*8, tf*4,tf*2]
    , [tf*2, tf*11, tf*4,tf*2]
    , [tf*7, tf*11, tf*4,tf*2]
    , [tf*4, tf*13, tf*4,tf*2]
    ]
];

    hipped = rands(0,2,1, seed=1+seed_value)[0]>0.99 ? true:false;

    union(){
        for(istorey = [1 : nstorey]) {
            randy = rands(1,len(window_list),4, seed=seed_value+istorey);
            styles = [(istorey==1) ? 0 : randy[3], randy[0], randy[1], randy[2]]; // one door, random windows

            translate([0,0, (istorey-1) * wh])
            translate([cl/2, cl/2, 0])
            for(n = [0:3]){
                rotate([0,0,90*n])
                translate([-cl/2, -cl/2,0])
                wall_with_windows(
                  wall_width=cl
                , wall_height=wh
                , wall_thickness=th
                , frame_thickness=tf
                , window_positions = window_list[styles[n]]);
            }
        }

        dormer_w = cl * rands(0.15, 0.6, 1, seed=seed_value)[0];
        translate([0,0,wh*nstorey])
        roof_with_dormers(width=cl, length=cl, t_wall=th, t_roof=tf, dormer_width=dormer_w, overhang=5, hipped=hipped);
    }

}

// Creates a terrain "rectangle" from 0,0 to sizex,sizey
module terrain(housex, housey, brim_width, height, hole=true){

    translate([-brim_width, -brim_width, 0]){

        sizex = housex + 2*brim_width;
        sizey = housey + 2*brim_width;
        // debug the base area
        // #cube([sizex, sizey, 0.01]);

        // two radi for the bottom geometry
        rmin = min(sizex, sizey) / 2 * 0.333;
        rmax = max(sizex, sizey) / 2 * 0.9;
        // a rounding radius for smoothing
        redge = height/2;

        difference(){

            minkowski(){
                // linear_extrude(height = 0.01)
                scale([1,1,height/rmax * 0.50])
                hull()
                {
                    // translate([x1+(x2-x1)*0.7, y1+(y2-y1)*0.7]) sphere(r = rmax);
                    r1 = rmin;
                    r2 = (rmax + rmin)/2;
                    r3 = rmin;
                    r4 = rmin;

                    f = 1.3; // inset factor to avoid a square base
                    // 3 4
                    // 1 2
                    translate([    f*r1+redge,          r1+redge,0]) sphere(r = r1);
                    translate([sizex-r2-redge,          r2+redge,0]) sphere(r = r2);
                    translate([      r3+redge,  sizey-  r3-redge,0]) sphere(r = r3);
                    translate([sizex-f*r4-redge,sizey-f*r4-redge,0]) sphere(r = r4);
                };

                scale([0.8, 1.2, 1.0]) sphere($fn=8, r = redge);
            }

            // bottom half
            translate([-1,-1,-height]) cube([sizex+2, sizex+2, height]);
            if(hole){
            translate([brim_width, brim_width,-0.01]) cube([housex, housex, height*2]);

            }
        }
    }
}

module tree(height, diameter){
    // radii
    rt = diameter*0.10; // trunk
    r1 = diameter*0.2;
    r2 = diameter*0.33; // tree
    r3 = diameter*0.50;
    dr = diameter*0.02; // inset at each z offset
    // heights
    h1 = height * 0.55;
    h2 = height * 0.35;
    h3 = height * 0.20;

    $fn = 24;
    rotate_extrude(){
    polygon([
        [0,height]
        ,[r1, h1], [r1-dr, h1-dr]
       , [r2, h2], [r2-dr, h2-dr]
      ,[r3, h3], [r3-dr, h3-dr]
     ,[rt, h3*0.10]
     ,[rt,0], [0,0]

    ]);
   }
}

module logs(size){
    edge = size/20;
    r = size / 6;

    module onelog(){
        minkowski(){
            rotate([270,0,0]) cylinder(h=size, r=r);
            sphere($fn=8, r = edge);
           }
    }

    translate([0,0,edge])
    union($fn=16){
        // 3x bottom     vvv Y = offset to make it look more natural
        translate([  r,     0,r]) onelog();
        translate([3*r, r*0.2,r]) onelog();
        translate([5*r,-r*0.3,r]) onelog();
        // 2x top
        translate([2*r, r*0.1,3*r]) onelog();
        translate([4*r,-r*0.1,3*r]) onelog();
    }
}

module barrel(height){
    rmid = height * 0.50;
    rtop = height * 0.40;
    hcircle = sqrt(rmid*rmid-rtop*rtop);
    scalez = height/(2*hcircle);

    translate([0,0,height/2])
    difference(){
        scale([1,1,scalez]) sphere(r = rmid);
        // cut top/bottom
        translate([-height,-height,height/2]) cube(2*height);
        translate([-height,-height,-2.5*height]) cube(2*height);

        // upper ring
        translate([0,0,height*0.45]) cylinder(h=height, r = rtop*0.9);
    }

    // rings
    translate([0,0,height * 0.70])
    cylinder(r1 = rmid, r2 = rmid*0.96, h = height*0.10);

    translate([0,0,height * 0.20])
    cylinder(r2 = rmid, r1 = rmid*0.96, h = height*0.10);

}

module house_extra_feature(height){

    // Function to check if a specific bit is set
function is_bit_set(value, bit) =
    floor(value / pow(2, bit)) % 2 == 1;

    rnd = floor(seed_value);
    if(is_bit_set(rnd, 0)){
        translate([-height/4, -height/4,0]) tree(height=height, diameter = height*0.7);
    }
    if(is_bit_set(rnd, 1)){
        translate([height*1.2, -height/8,0]) barrel(height=height*0.30);
    }
    if(is_bit_set(rnd, 2)){
        translate([height*1.1, height*0.30,0]) logs(size=height*0.30);
    }

}

// *** MAIN ***
// union(){
    full_house(cl=cl, wh=wh, tf=tf, th=th, nstorey = storeys);

    terrainw = cl/2; // extra terrain width
    terrain(housex = cl, housey=cl, brim_width=terrainw, height=tf, hole=true);

    if(want_features){
        translate([0,0,tf/2]) house_extra_feature(height = cl);
    }
//}

```


[User]
I'm looking to create a storage tray for some tokens, with a separate lid. The tokens are roughly cylindrical and I want to be able to customize the diameter of the token holder. I've got a basic design in mind, but I need some help writing the OpenSCAD code.

Specifically, I'd like:

*   **A tray with recessed areas to hold the tokens:** The tray will have a base, and then several cylindrical recesses. The recesses will be arranged in a grid pattern. The dimensions of the grid should be parametric, allowing for different numbers of tokens. I also want the overall tray size to adjust accordingly.
*   **A lid that fits snugly on the tray:** The lid should have a lip that fits over the outer wall of the tray. The lid's interior should be open; that is, it should not cover over the token recesses.
*   **Parametric token diameter:** I need a variable to set the diameter of the token recesses.
*   **Parametric lid margin:** I want to have some control over how much clearance there is between the lid and the tray, so there should be a parameter to adjust how "snug" the lid fits.
*   **Test piece option:** I'd like an option to generate a small test piece containing a single token recess. This is to help me dial in the token diameter before printing the entire tray and lid.
*   **Ability to print lid or tray separately, or both at once:** I'd like a module that uses a variable or some other setting to choose if I am printing only the lid, only the tray, or both.
*   **Rounded corners:** All edges of both the tray and the lid should have a rounded, filleted look to them.

I think I'd like to use the `hull()` function in OpenSCAD to generate the rounded corners. I don't have any specific libraries I'd like to use, but I'm open to suggestions. Also, I'm thinking maybe separate modules for the tray, lid, and test piece would be a good approach.

[Assistant]
MassEffect.scad:
```
part = "both"; // [first:Lid Only,second:Tray Only,both:Both Parts

// Token Diameter
tokenDiameter = 30.4; // [30:0.1:31]

// Lid Margin
lidMargin = 0.0; // [0:0.1:0.6]

// Make Small Test Lid?
test_size = 0; // [0:No, 1:Yes

module roundedCube(vec,rad) {
    hull() {
        translate([rad,rad,0]) cylinder(h=vec.z,r=rad);
        translate([rad,vec.y-rad,0]) cylinder(h=vec.z,r=rad);
        translate([vec.x-rad,vec.y-rad,0]) cylinder(h=vec.z,r=rad);
        translate([vec.x-rad,rad,0]) cylinder(h=vec.z,r=rad);
    }
}

module eliteTokens() {
    $fn=60;

    difference() {
        sp = 32;
        m = 4;
        cx = test_size? 1 : 4; cy = test_size? 1 : 3;
        roundedCube([sp*cx+m+m,sp*cy+m+m,2.6],5);
        for (j=[0:cy-1]) {
            for (i=[0:cx-1]) {
                translate([m+sp/2+sp*i,m+sp/2+sp*j,-1]) {
                    cylinder(h=3,d=20);
                    translate([0,0,1.6]) cylinder(h=5,d=tokenDiameter);
                }
            }
        }
    }
}

//eliteTokens();

module tray() {
    difference() {
        roundedCube([32*4+10,32*3+10,18],5);
        translate([1-lidMargin,1-lidMargin,16]) roundedCube([32*4+8+lidMargin*2,32*3+8+lidMargin*2,99],5);
        for (j=[0:2]) {
            for (i=[0:1]) {
                translate([1+34*i,1+35*j,0.6]) roundedCube([33,34,99],5);
            }
        }
        for (j=[0:1]) {
            for (i=[2:3]) {
                translate([1+34*i,1+52.5*j,0.6]) roundedCube([i==3?34:33,51.5,99],5);
            }
        }
        translate([-1,(32*3+10)/2,22]) rotate([0,90,0]) cylinder(h=5,d=20);
    }
}

//tray();

print_part();

module print_part() {
	if (part=="first" || test_size) eliteTokens();
	else if (part=="second") tray();
	else {
        translate([0,-60,0]) eliteTokens();
		translate([0,60,0]) tray();
	}
}

```


[User]
I need an OpenSCAD design for a cube with chamfered edges. The design needs to be parametric so I can easily adjust the size of the cube and the chamfer. I'd like to be able to specify the cube's size with a `cubeSize` parameter and the size of the chamfer with a `chamferSize` parameter.

The cube should be centered at the origin. The chamfers should be applied to all 12 edges of the cube. Each chamfer should be a triangular prism that is subtracted from the cube. I need to be able to control how much each chamfer overlaps with the cube using a parameter called `overlap`. I expect you to create all the necessary modules and functions in the main SCAD file, and I don't anticipate needing any additional libraries.

[Assistant]
cube-with-chamfers-02.scad:
```
// cube with chamfers, parametric

// cubeSize
cubeSize = 50;

// chamferSize
chamferSize = 10;

// overlap
overlap = 1;

module cubeWithChamfer(cubeSize, chamferSize) {
    difference() {
        // Main cube
        cube([cubeSize, cubeSize, cubeSize], center = true);

        // Lower edges
        chamferPrism(cubeSize, chamferSize, 1);
        chamferPrism(cubeSize, chamferSize, 2);
        chamferPrism(cubeSize, chamferSize, 3);
        chamferPrism(cubeSize, chamferSize, 4);

        // Vertical edges
        chamferPrism(cubeSize, chamferSize, 5);
        chamferPrism(cubeSize, chamferSize, 6);
        chamferPrism(cubeSize, chamferSize, 7);
        chamferPrism(cubeSize, chamferSize, 8);

        // Upper edges
        chamferPrism(cubeSize, chamferSize, 9);
        chamferPrism(cubeSize, chamferSize, 10);
        chamferPrism(cubeSize, chamferSize, 11);
        chamferPrism(cubeSize, chamferSize, 12);
    }
}

module chamferPrism(cubeSize, chamferSize, edgeNumber) {
    // Position of the chamfer depends on the edge number
    translate(edgePosition(cubeSize, chamferSize, edgeNumber))
        rotate(edgeRotation(edgeNumber))
            // Polygon for the chamfer with extrusion
            linear_extrude(height = cubeSize)
                polygon(points=[
                    [overlap, -overlap],
                    [-chamferSize-overlap, -overlap],
                    [overlap, chamferSize+overlap]
                ]);
}

// Function to calculate the position of the prism on the cube edge
function edgePosition(cubeSize, chamferSize, edgeNumber) =
// Lower edges
    edgeNumber == 1 ? [ cubeSize / 2, cubeSize / 2, -cubeSize / 2] : // Edge 2
    edgeNumber == 2 ? [ -cubeSize / 2, cubeSize / 2, -cubeSize / 2] : // Edge 3
    edgeNumber == 3 ? [ -cubeSize / 2, -cubeSize / 2, -cubeSize / 2] : // Edge 4
    edgeNumber == 4 ? [ cubeSize / 2, -cubeSize / 2, -cubeSize / 2] : // Edge 6 (new)

// Vertical edges
    edgeNumber == 5 ? [cubeSize / 2, -cubeSize / 2, -cubeSize / 2] : // Edge 1
    edgeNumber == 6 ? [cubeSize / 2, cubeSize / 2, -cubeSize / 2] : // Edge 5 (new)
    edgeNumber == 7 ? [-cubeSize / 2, -cubeSize / 2, -cubeSize / 2] :
    edgeNumber == 8 ? [-cubeSize / 2, cubeSize / 2, -cubeSize / 2] :

// Upper edges
    edgeNumber == 9 ?  [cubeSize / 2, -cubeSize / 2, cubeSize / 2] : // Edge 7
    edgeNumber == 10 ? [cubeSize / 2, cubeSize / 2, cubeSize / 2] : // Edge 8
    edgeNumber == 11 ? [-cubeSize / 2, cubeSize / 2, cubeSize / 2] : // Edge 9
    edgeNumber == 12 ? [-cubeSize / 2, -cubeSize / 2, cubeSize / 2] : // Edge 10
    [0, 0, 0];

// Function to calculate the rotation of the prism for each edge
function edgeRotation(edgeNumber) =

// Lower edges
    edgeNumber == 1 ? [90, 0, 0] :
    edgeNumber == 2 ? [90, 0, 90] :
    edgeNumber == 3 ? [90, 0, 180] :
    edgeNumber == 4 ? [90, 0, 270] :

// Vertical edges
    edgeNumber == 5 ? [0, 0, 0] :
    edgeNumber == 6 ? [0, 0, 90] :
    edgeNumber == 7 ? [0, 0, 270] :
    edgeNumber == 8 ? [0, 0, 180] :

// Upper edges
    edgeNumber == 9 ?  [270, 0, 0] :
    edgeNumber == 10 ? [270, 0, 90] :
    edgeNumber == 11 ? [270, 0, 180] :
    edgeNumber == 12 ? [270, 0, 270] :

    [0, 0, 0];

// Calling the function to create the cube with chamfer on edges
cubeWithChamfer(cubeSize, chamferSize);

```
cube-with-chamfers.scad:
```
// cube with chamfers, parametric

// cubeSize
cubeSize = 50;

// chamferSize
chamferSize = 10;

module cubeWithChamfer(cubeSize, chamferSize) {
    difference() {
        // Main cube
        cube([cubeSize, cubeSize, cubeSize], center = true);

        // Lower edges
        chamferPrism(cubeSize, chamferSize, 1);
        chamferPrism(cubeSize, chamferSize, 2);
        chamferPrism(cubeSize, chamferSize, 3);
        chamferPrism(cubeSize, chamferSize, 4);

        // Vertical edges
        chamferPrism(cubeSize, chamferSize, 5);
        chamferPrism(cubeSize, chamferSize, 6);
        chamferPrism(cubeSize, chamferSize, 7);
        chamferPrism(cubeSize, chamferSize, 8);

        // Upper edges
        chamferPrism(cubeSize, chamferSize, 9);
        chamferPrism(cubeSize, chamferSize, 10);
        chamferPrism(cubeSize, chamferSize, 11);
        chamferPrism(cubeSize, chamferSize, 12);
    }
}

module chamferPrism(cubeSize, chamferSize, edgeNumber) {
    // Position of the chamfer depends on the edge number
    translate(edgePosition(cubeSize, chamferSize, edgeNumber))
        rotate(edgeRotation(edgeNumber))
            // Polygon for the chamfer with extrusion
            linear_extrude(height = cubeSize)
                polygon(points=[
                    [0, 0],
                    [-chamferSize, 0],
                    [0, chamferSize]
                ]);
}

// Function to calculate the position of the prism on the cube edge
function edgePosition(cubeSize, chamferSize, edgeNumber) =
// Lower edges
    edgeNumber == 1 ? [ cubeSize / 2, cubeSize / 2, -cubeSize / 2] : // Edge 2
    edgeNumber == 2 ? [ -cubeSize / 2, cubeSize / 2, -cubeSize / 2] : // Edge 3
    edgeNumber == 3 ? [ -cubeSize / 2, -cubeSize / 2, -cubeSize / 2] : // Edge 4
    edgeNumber == 4 ? [ cubeSize / 2, -cubeSize / 2, -cubeSize / 2] : // Edge 6 (new)

// Vertical edges
    edgeNumber == 5 ? [cubeSize / 2, -cubeSize / 2, -cubeSize / 2] : // Edge 1
    edgeNumber == 6 ? [cubeSize / 2, cubeSize / 2, -cubeSize / 2] : // Edge 5 (new)
    edgeNumber == 7 ? [-cubeSize / 2, -cubeSize / 2, -cubeSize / 2] :
    edgeNumber == 8 ? [-cubeSize / 2, cubeSize / 2, -cubeSize / 2] :

// Upper edges
    edgeNumber == 9 ?  [cubeSize / 2, -cubeSize / 2, cubeSize / 2] : // Edge 7
    edgeNumber == 10 ? [cubeSize / 2, cubeSize / 2, cubeSize / 2] : // Edge 8
    edgeNumber == 11 ? [-cubeSize / 2, cubeSize / 2, cubeSize / 2] : // Edge 9
    edgeNumber == 12 ? [-cubeSize / 2, -cubeSize / 2, cubeSize / 2] : // Edge 10
    [0, 0, 0];

// Function to calculate the rotation of the prism for each edge
function edgeRotation(edgeNumber) =

// Lower edges
    edgeNumber == 1 ? [90, 0, 0] :
    edgeNumber == 2 ? [90, 0, 90] :
    edgeNumber == 3 ? [90, 0, 180] :
    edgeNumber == 4 ? [90, 0, 270] :

// Vertical edges
    edgeNumber == 5 ? [0, 0, 0] :
    edgeNumber == 6 ? [0, 0, 90] :
    edgeNumber == 7 ? [0, 0, 270] :
    edgeNumber == 8 ? [0, 0, 180] :

// Upper edges
    edgeNumber == 9 ?  [270, 0, 0] :
    edgeNumber == 10 ? [270, 0, 90] :
    edgeNumber == 11 ? [270, 0, 180] :
    edgeNumber == 12 ? [270, 0, 270] :

    [0, 0, 0];

// Calling the function to create the cube with chamfer on edges
cubeWithChamfer(cubeSize, chamferSize);

```


[User] I need an OpenSCAD program that can generate 3D text. I want to be able to set the text itself, the font, the font size, the extrusion height (thickness), and the spacing between characters. Crucially, I'd also like the option to render the text vertically instead of horizontally, where each letter would be individually rotated and positioned. It should be easy to modify the parameters I described. I don't need anything fancy, just the basic text extrusion and the ability to rotate it.

[Assistant]
Parametric_Text.scad:
```
// Created by TechMagic3D on Thingiverse

// User-defined text and font settings
text_to_render = "Thingiverse";
font_name = "DIN Alternate";  // Use your preferred font
font_size = 10;  // Set the font size
text_height = 3;  // Set the extrusion height (thickness)
spacing = 1;  // Spacing between characters
vertical = false;

// Additional Config

// Function to render text vertically or horizontally
module render_text() {
    if (vertical) {
        // Render text vertically with rotated individual letters
        for (i = [0 : len(text_to_render) - 1]) {
            // Extract each character
            letter = str(text_to_render[i]);
            // Position each letter vertically
            translate([i * (font_size + spacing), 0, 0]) {
                rotate([0, 0, 90]) {  // Rotate each letter 90 degrees around the Z-axis
                    linear_extrude(height = text_height, center = true)
                        text(letter, size = font_size, font = font_name, spacing = spacing);
                }
            }
        }
    } else {
        // Render horizontally (default behavior)
        linear_extrude(height = text_height, center = true)
            text(text_to_render, size = font_size, font = font_name, spacing = spacing);
    }
}

// Call the render_text function
render_text();

```


[User]
Okay, I'm looking for an OpenSCAD design for a modern-style birdhouse, something that can be wall-mounted. I'd like it to be customizable, so I can tweak the size and shape. It'd be great to have a few different styles, like a standard circular entrance hole, as well as a semi-open, tub-like entrance. I need the design to be parametric so that all parts will be properly dimensioned to fit together given different sizes.

Here's a breakdown of the features I'd like to control:

**Overall Style:**

*   **Assembly:** A parameter to switch between showing the birdhouse as an assembled model or as separate parts.
*   **Style:** A way to choose between different entrance types (round hole "cave", semi-open "semicave"). I would also be interested in a future option for an "elongated" entrance, though that may be more for experimentation at this time.
*   **Rear Wall Inscription:** The ability to add text to the back of the birdhouse, like a version number or some kind of inscription.

**Body:**

*   **Body Radius:** The overall radius of the birdhouse (from the center to the outer edge).
*   **Length:** The total length of the main body (excluding the collar).
*   **Wall Thickness:** The thickness of the side walls.

**Drainage:**

*   **Number of Holes:** The amount of drainage holes at the bottom.
*   **Hole Radius:** The size of each of those holes.
*   **Hole Spacing:** The distance from the center to each drainage hole.

**Roof/Porch (Collar):**

*   **Collar Extrusion:** How far the roof/porch extends out from the body.
*   **Collar Strength:** The thickness or strength of the roof/porch.

**Cuff:**
*   **Cuff Width:**  The width of any thickening of the body at the back.
*    **Cuff Thickness:** The strength of that rear cuff.

**Front:**

*   **Front Wall Strength:** The thickness of the front wall.
*   **Entrance Radius:** The size of the round entrance hole. This should be ignored for the semi-cave style.

**Tunnel (Optional):**

*   **Tunnel Wall Strength:** The thickness of the tunnel walls; setting this to zero would disable the tunnel.
*   **Protrusion:** How far the tunnel extends out from the front.
*   **Tunnel Shape:** Number of vertices for the tunnel entrance and shape.
*   **Chamfer:** Amount of chamfering for the tunnel base.

**Rear:**

*   **Rear Wall Strength:** The thickness of the back wall.

**Mounting Slots:**

*   **Slot Length:** The length of mounting slots on the rear of the birdhouse.
*   **Screw Width:**  The width of the mounting slots where the screw head fits.
*   **Factor:** A ratio to generate the screw head passing hole by the slot width.
*   **Distance:** The distance between mounting slots
*   **Descend:** Out of center positioning of the rear slots, in other words, how low the slots are relative to the center of the birdhouse back.

**Pins and Sockets:**

*   **Pin Radius:** The radius of pins on the front that insert into the main body (negative values would indicate screw holes instead of pins).
*   **Pin Length:** How far those pins insert into the body.
*   **Sparse:** A parameter to control pin count.

**Misc:**

*   **Round:** How round general arcs are (for slots, pins, sockets).
*   **Slack:** A small amount of clearance to help with fitment.

I think that covers it! I'd also like the SCAD code to be well-commented and easy to understand. I don't have any libraries I specifically need to use. Thanks in advance for your help!

[Assistant]
modern_bird_house.scad:
```
// OpenSCAD object, customizer:
// Bushmills' generator for modern wall-mount bird houses.

// 2024dec9 v0.0.5   negative pin radius produces screw holes.
//                   Fixed inner collar radius.
//                   New customizer parameter: assembled, for discrete or assembled view
// 2024dec9 v0.0.4   better support for other shapes than hexagonal.
//                   low entrance vertices counts are ok.
//                   Style select should work with thingiverse customizer now.
// 2024dec8 v0.0.3   addition of tunnel, some experimental control of shapes.
// 2024dec7 v0.0.2   fixed drainage.
// 2024dec7 v0.0.1   initial version.

cave = 1+0;
semicave = 2+0;
elongated = 3+0;              // cave with different entrance shape, unfinished
oval = 4+0;                   // cave with different entrance shape, unfinished

/* [style] */

// placement. 0: discrete components, 1: assembled.
assemble = 0;                 // [0, 1]

// style 1: cave (round hole as entrance), style 2: semicave (half open)
style = 1;                    // [1, 2]

// rear wall inscription
version = "v0.0.5";           // ["v0.0.5", "Hello, world!"]

// roundness of body shape (may need to adjust parameter "sparse" in section "pins and sockets")
shape = 8;                    // [4:2:16]

/* [body] */

// center to any outer nook, outer birdhouse radius. Inside breeding space is smaller!
body = 70;                    // [20:120]

// birdhouse length (without collar)
length = 120;                 // [30:200]

// side wall thickness (without collar)
wall = 10;                    // [2:30]

/* [body: drainage] */

// number of drainage holes (not counting the center hole)
holes = 8;                    // [2:24]

// drainage and circulation holes radius
drainage = 2;                 // [0:10]

// drainage holes spacing (distance from center hole to any surrounding hole)
spacing = 12;                 // [3:30]

/* [body: roof/porch] */

// roof and porch extrusion
collar = 30;                  // [0:80]

// roof and porch extrusion strength
collarstrength = 3;           // [0:10]

/* [body: cuff] */
// thickening around body at rear, where subjected to highest load.
cuff = 30;                    // [0:100]

// cuff thickness
cuffstrength = 2;             // [0:60]

/* [front] */

// front wall strength.
front = 3;                    // [1:12]

// radius of entrance. Irrelevant with semicave style
entrance = 17;                // [6:0.5:75]

/* [front: tunnel] */

// tunnel wall strength, or 0 for no tunnel.
tunnel = 2;                   // [0:0.1:12]

// how far does tunnel protrude outside.
protrude = 10;                // [0:64]

// number of entrance and tunnel vertices
tunnelshape = 90;             // [3:90]

// amount of chamfering at tunnel base.
chamfer = 0.5;                // [0:0.1:3]

/* [rear] */

// rear wall strength
rear = 5;                     // [1:20]

/* [rear: mounting slots] */
// length of mounting slots
slot = 6;                     // [0:32]

// width of mounting slots
screw = 4;                    // [0:0.1:24]

// ratio of screw head passing hole to slot width
factor = 2.5;                 // [1:0.1:5]

// slot distance
dist = 50;                    // [0:300]

// out of center positioning of rear slots
descend = 15;                 // [0:60]

/* [pins and sockets] */

// radius of pins on front which insert into body for holding both parts together. Negative values generate screw holes.
pin = 2.5;                    // [-4:0.1:12]

// length of pins. Insert this much into body
dip = 8;                      // [0:24]

// adjust pin/socket count, by rendering only every nth pin. With cave, (shape % sparse) should be 0. For semicave, select a meaningful value.
sparse = 2;                   // [1:24]

/* [misc] */

// grade of general arc roundness: slots, pins, sockets
round = 90;                   // [30:180]

// easier fit of pins into sockets, tunnel into entrance, front into body
slack = 0.15;                 // [0:0.05:1]

// -------------------------------------------------------------------------------------------------

slack2 = slack/2;
hole = tunnel+entrance;
lift = body-wall-slot-descend;
some2 = 0.02+0;
some = some2/2;
$fn = round;
outer = body+cuffstrength+collarstrength;

// -------------------------------------------------------------------------------------------------
//                                 general purpose modules
// -------------------------------------------------------------------------------------------------

module ring(outer, inner)  {
   difference()  {
      circle(outer);
      circle(inner);
   }
}

module slot(width, len, factor=2)  {
   $fn = round;
   width2 = width/2;
   circle(width2*factor);
   translate([-width2, 0])
   square([width, len]);
   translate([0, len])
   circle(width2);
}

module pie(r, a)  {
   for (i = [0:180:a-0.001])  {
      rotate([0, 0, i])
      intersection()  {
         rotate([0, 0, max(i-a, -180)])
         translate([-r, -r])   square([r, r+r]);
         translate([0, -r])    square([r, r+r]);
         circle(r);
      }
   }
}

module torus(ri, ro, a, blob)  {
   assert(ri >= ro);
   intersection()  {
      rotate_extrude()
      translate([ri, 0])
      circle(r = ro);
      if (! is_undef(a))  {
         assert(a <= 360);
         translate([0, 0, -ro])
         linear_extrude(ro+ro)
         pie(ri+ro, a);
      }
   }
   if (! is_undef(blob))  {
      for (i = [0, a])  {
         rotate([0, 0, -i])
         translate([0, ri])
         sphere(blob);
      }
   }
}

// -------------------------------------------------------------------------------------------------
//                                   component modules
// -------------------------------------------------------------------------------------------------

module pins(dist, r, sparse, arc=360)  {                       // arc void: arc=360
   arc = (arc != 0) ? arc :                                    // arc n: arc=n
   (style == semicave) ? 240 : 360;                            // arc 0: arc=240 or arc=360, depending on style
   step = 360/$fn;
   for (a = [0 : step*sparse : arc-some])  {
      x = cos(-a)*dist;
      y = sin(-a)*dist;
      translate([x, y])
      circle(r, $fn=round);
   }
}

module drainage(radius, spacing, len)  {
   cylinder(len, r=radius, $fn=round);                         // center drainage hole
   linear_extrude(len)
   pins(spacing, radius, 1);                                   // rim drainage holes
}

module body(len, width, wall, collar, strength)  {
   $fn = shape;
   linear_extrude(cuff)
   ring(body+strength+cuffstrength, width);                    // cuff
   linear_extrude(len+collar+front)
   ring(width+strength, width);                                // collar
   linear_extrude(len)
   ring(body+strength, width-wall);                            // body
}

module front(width, wall, strength, hole, pin, dip)  {
   $fn = shape;
   linear_extrude(strength)
   difference()  {
      circle(width-slack);                                     // front wall
      if (style == semicave)  {
         translate([-body, pin])
         square([body*2, body]);                               // hole
      }
      if (style == cave )  {
         translate([0, (width-wall)/3])
         rotate([0, 0, -90])
         circle(hole, $fn=tunnelshape);                        // hole
      }
//      if (style == elongated )  {
//         hull()  {
//            translate circle1
//            translate circle2
//         }
//      }
      if (pin < 0)  {
         pins(width-wall/2, abs(pin)*1.2, sparse, 0);          // bores
      }
   }
   if (pin > 0)  {
      linear_extrude(dip)
      pins(width-wall/2, pin, sparse, 0);                      // pins
   }
}

module tunnel()  {
   $fn = tunnelshape;
   chamfered = hole+tunnel-chamfer;
   intersection()  {
      linear_extrude(wall)
      ring(hole+tunnel, hole-slack);
      translate([0, 0, -chamfered])
      cylinder(tunnel+chamfered, 0, tunnel+chamfered);
   }
   h = front + tunnel + protrude;
   difference()  {
      linear_extrude(h)
      ring(hole-slack, entrance);
      translate([0, 0, -some])
      cylinder(entrance, entrance+chamfer);
   }
   tunnel2 = tunnel/2;
   translate([0, 0, h])
   rotate([0, 0, 180])
   torus(entrance+tunnel2-slack2 , tunnel2-slack2);
}

// -------------------------------------------------------------------------------------------------
//                                        main object
// -------------------------------------------------------------------------------------------------

module birdhouse()  {
   linear_extrude(rear)
   difference()  {
      circle(body, $fn=shape);                                 // rear wall
      for (dist = [-dist/2, dist/2])  {
         translate([dist, lift])
         slot(screw, slot, factor);                            // mounting slots
      }
   }
   translate([-body/2, 0, rear])                               // version
   linear_extrude(1)
   text(version, size=body/8);

   difference()  {
      body(length, body, wall, collar, collarstrength);        // body

      translate([0, 0, length/2])
      rotate([90, 0, 0])
      drainage(                                                // drainage
         drainage,
         spacing,
         outer+wall,
         $fn=holes
      );

      desc = dip+1;
      translate([0, 0, length-desc])
      linear_extrude(desc+some)
      pins(body-wall/2, abs(pin)+slack, sparse, 0, $fn=shape); // sockets
   }

}

clearance = 3;
gap = outer+clearance;

birdhouse();                                                   // body

if (assemble == 1)  {
   translate([0, 0, length+front])
   rotate([180, 0, 180])
   front(body, wall, front, hole, pin, front+dip-slack);       // front
} else {
   translate([0, -(body+gap)])
   front(body, wall, front, hole, pin, front+dip-slack);       // front
}

if (style == cave) {
   if (assemble == 1)  {

      translate([0, (body-wall)/3, length-tunnel])
      tunnel();                                                   // tunnel
   } else {
      translate([gap+2*tunnel, -gap])
      tunnel();                                                   // tunnel
   }
}

```


[User]
Hey, I'm looking to get a parametric design for a desk stand for my Roland SH-4d synth. I've got some ideas, and I'm hoping you can help me turn them into an OpenSCAD design.

I'd like the stand to consist of three printed parts: a left bracket, a right bracket, and a strut. So, I think we’ll need at least two files: one for the brackets, which are mirror images of one another, and another for the strut. The brackets are meant to hold the synth, and the strut acts as a connector between the two, setting the angle of the synth.

Here's what I have in mind for the design:

*   **Brackets:**
    *   The brackets should have an inner and outer structure. The inner part will make contact with the synth, and the outer structure provides the structural stability and mounting points. The inner part will essentially be a profile that matches the bottom of the synth (not exact, but close).
    *   The inner profile should have a relatively flat base with a rear wall. I've got measurements for the synth (approximate) I'd like to work from: an inner length at the bottom (inner_l_b, ~155mm), an inner length at the top (inner_l_t, ~153mm), a height for that rear wall (inner_rear_h, ~24mm), and an inner depth/thickness (inner_d, ~10mm).  There needs to be a small clearance between this and the actual synth, so a parameter for `inner_clearance` (~2mm) would be useful.
     *   The outer part should also have a flat base and rear wall, and I would need to be able to set its length (outer_l, ~190mm) and height (outer_rear_h, ~50mm).  Its depth should match the inner (outer_d, equal to inner_d). There should be an overall thickness to the bracket that includes both the inner and outer structures, plus additional thickness for stability (bracket_thickness ~20mm).
    *   The overall height of the bracket's rear wall needs to be adjustable; that is, the user needs to be able to specify `riser_height` (~20-100mm) to set the synth angle. This will affect the total height of the bracket’s rear wall (bracket_rear_h should be `riser_height` + `bracket_thickness` + `inner_clearance`).
    *   There should be mounting points for the strut. These should be cylindrical anchors with a width `anchor_w` (~10mm), a height `anchor_h` (~10mm) and a length `anchor_l`, with an adjustable parameter to set it (~20-100mm). The anchors should be spaced appropriately on the bracket.
    *   The bracket should have some rounded edges on the outer visible part. Maybe a `round` variable set to (~10mm) for the radius.
    * There should be a couple of holes in the side slab for weight reduction. The radius of these can be calculated proportionally from the height of the rear of the bracket.
    *   The bracket should be created using a hull function of cylinders to make the rounded edges.

*   **Strut:**
    *   The strut will have a length set by a `strut_length` parameter (~50-320mm).
    *   The strut needs to slide over the anchors on the brackets, so the strut will need a small clearance, defined by `strut_clearance` (~0-2mm), which needs to be configurable.
    *   The strut should also be created using a hull of cylinders.
    * The strut wall should have a configurable thickness using a `strut_wall` variable (~1.6mm)
    *   The actual connection point with the brackets needs to have an inner shape which matches the anchors on the bracket.

*   **Parameters:**
    *   `object` - to select which object to create (1: LeftBracket, 2: RightBracket, 3:Strut)
    *   `riser_height` (~20-100mm)
    *   `anchor_length` (~20-100mm)
    *   `strut_length` (~50-320mm)
    *   `strut_clearance` (~0-2mm)
    *   `$fn` for rendering quality (~100-360)

I think that covers the basics of what I'm after. Let me know if you need more details or if you have any questions. I don't need you to use any specific libraries for this, but if you feel you would benefit from doing so, please explain your reasoning. Thanks a lot!

[Assistant]
sh4d_stand.scad:
```
/*
Quick and dirty stand for the Roland SH-4d synthesizer/

Andy Anderson 2024

Requires: 1xLeftBracket, 1xRightBracket, 3xStrut.
Configure "anchor_length" & "strut_length" based on the size of your printer bed.abs
Configure the synth angle by selecting an appropriate "riser_height".
*/

$fn=360; // [100:360]

// Select the object to create.
object=1; // [1:LeftBracket,2:RightBracket,3:Strut]

// Height in mm of the rear of the bracket. This sets the angle - ignored for Strut.
riser_height = 100; // [20:100]

// Length in mm of the strut anchor points on the brackets - ignored for Strut.
anchor_length = 100; // [20:100]

// Length of the strut connector - ignored for LeftBracket & RightBracket.
strut_length = 260; // [50:320]

// Clearance in mm for allowing strut to slide over anchor - ignored for LeftBracket & RightBracket.
strut_clearance = 0.25; // [0:0.05:2]

/* [Hidden] */

strut_wall = 1.6;

wall=1.2;
inner_clearance=2;

inner_l_t=153;
inner_l_b=155;
inner_rear_h=24;
inner_d=10;

outer_l=190;
outer_rear_h=50;
outer_d=inner_d;

bracket_thickness = inner_d + 10;
bracket_rear_h = riser_height + bracket_thickness + inner_clearance;

anchor_w = 10;
anchor_h = 10;
anchor_l = anchor_length;

round = 10;

module inner() {
    translate([-wall,0,0])
    rotate([90,0,90])
    linear_extrude(inner_d+wall)
    polygon(points=[[-wall,-wall],[inner_l_b,-wall],[inner_l_t,inner_rear_h+inner_clearance], [-wall, inner_rear_h+inner_clearance]], paths=[[0,1,2,3]]);
}

module panel() {
    translate([0, -1, 0])
    cube([outer_d+bracket_thickness, outer_l+2, outer_rear_h]);
}

module synth_side() {
    difference() {
        translate([0, 3, -20])
        rotate([6,0,0])
        difference() {
            panel();
            inner();
        }

        translate([-(outer_d+bracket_thickness)/2, -outer_l/2, -outer_rear_h])
        cube([(outer_d+bracket_thickness)*2, outer_l*2, outer_rear_h]);
    }
}

module side_slab() {

    hole_r = bracket_rear_h*0.27;
    hole_f = hole_r/1.66;

    /*
        #polygon(points=[[0,0],
                        [inner_rear_h, 0],
                        [bracket_rear_h+inner_rear_h,outer_l],
                        [0,outer_l]],
                        paths=[[0,1,2,3]]);
    */

    difference() {
        translate([0, 0, inner_rear_h])
        rotate([0, 90, 0])
        hull() {
            translate([round, round, 0])
            cylinder(r=round,h=bracket_thickness);

            translate([-round/2, round, 0])
            translate([inner_rear_h, 0, 0])
            cylinder(r=round,h=bracket_thickness);

            translate([-round*2, -round, 0])
            translate([bracket_rear_h+inner_rear_h,outer_l, 0])
            cylinder(r=round,h=bracket_thickness);

            translate([+round, -round, 0])
            translate([0,outer_l,0])
            cylinder(r=round,h=bracket_thickness);
        }

        translate([-bracket_thickness/2, outer_rear_h*2.8, -(hole_r*1.25)])
        rotate([0, 90, 0])
        cylinder(r=hole_r, h=bracket_thickness*2);

        translate([-bracket_thickness/2, (outer_rear_h/2)*3.3, -(hole_f*1.3)])
        rotate([0, 90, 0])
        cylinder(r=hole_f, h=bracket_thickness*2);
    }
}

module strut_anchor() {
    r = 2.5;
    h = 1;
    d=r*2;

    rotate([0,90,0])
    hull() {
        translate([anchor_w-(d/2), -((anchor_h/2)-(d/2)), 0])
        cylinder(r=r, h=anchor_l);

        translate([0+d/2, 0, 0])
        cylinder(r=r, h=anchor_l);

        translate([anchor_w-(d/2), (anchor_h/2)-d/2, 0])
        cylinder(r=r, h=anchor_l);
    }

}

module bracket() {
    union() {
        difference() {
            side_slab();
            translate([-wall, 0, -inner_clearance])
            synth_side();
        }

        // My printer isn't big enough to print a single strut long enough, so integrate part of the strut into the bracket, then I can print ashorter joining peice.
        translate([bracket_thickness-1, outer_l/4, -bracket_rear_h/12])
        strut_anchor();

        translate([bracket_thickness-1, outer_l-anchor_w*1.2, -8])
        strut_anchor();

        translate([bracket_thickness-1, outer_l-anchor_w*1.2, -(bracket_rear_h-anchor_h*2.5)])
        strut_anchor();
    }
}

module strut() {

    r = 2.5;
    h = 1;
    d=r*2;

    outer_off = (strut_wall*0.5) + (strut_clearance*2);
    strut_w = anchor_w  + outer_off;
    strut_h = anchor_h  + outer_off;
    sr = 3.5;
    sd = sr*2;

    rotate([0, 90, 0])
    difference() {
    hull() {
        translate([strut_w-(sd/2), -(((strut_h+strut_wall)/2)-(sd/2)), 0])
        cylinder(r=sr, h=strut_length);

        translate([-outer_off+sd/2, 0, 0])
        cylinder(r=sr, h=strut_length);

        translate([strut_w-(sd/2), ((strut_h+strut_wall)/2)-sd/2, 0])
        cylinder(r=sr, h=strut_length);
    }

    translate([0, 0, -strut_length/3])
    hull() {
        translate([(anchor_w+strut_clearance)-(d/2), -(((anchor_h+strut_clearance)/2)-(d/2)), 0])
        cylinder(r=r, h=strut_length*2);

        translate([-strut_clearance+d/2, 0, 0])
        cylinder(r=r, h=strut_length*2);

        translate([(anchor_w+strut_clearance)-(d/2), ((anchor_h+strut_clearance)/2)-d/2, 0])
        cylinder(r=r, h=strut_length*2);
    }
}
}

if (object== 1) {
    bracket();
} else if (object == 2) {
    mirror([1,0,0])
    bracket();
} else if (object == 3) {
    strut();
}

```


[User]
I'd like to design a customizable storage tray system using OpenSCAD. I have some specific requirements, and I'd like the design to be parametric so I can adjust it easily.

The basic tray shape should be rectangular, with a lip around the top edge. The trays need to stack on top of one another, so I envision the bottom of the tray being slightly smaller than the top opening, which will allow them to nest. I'm thinking the overall tray height should be divided into three sections; the bottom section will have the smaller footprint for stacking, the middle section will be the main wall height, and the top section will form the lip with the full tray dimensions. I want to be able to control the overall width, depth, and height of the tray. The height should be a total value and the code will divide it by 3 for the different sections.

For the bottom of the tray, I want to have the ability to choose from several cutout patterns. I'm thinking about designs like snowflakes, spirals, a sunburst, and maybe some alternating circles. These cutouts should be on the underside of the tray, and I need to be able to choose from a variety of designs using a simple integer value to choose the design.

To summarize, here are the parameters I need:

*   `WIDTH`: The overall width of the tray.
*   `DEPTH`: The overall depth/length of the tray.
*   `HEIGHT`: The overall height of the tray.
*   `DESIGN`: An integer value to select a specific cutout design for the bottom of the tray. This will probably need a series of `if` statements or a `case` switch.

I'm not very experienced with advanced OpenSCAD, so I am not going to define any of the specific math or implementation. I would like the files to be written from scratch, and I'm not requesting any external library dependencies (such as MCAD or BOSL). I would like each piece of the code (e.g. tray, bottom, middle, top, and each individual design) to be separated into modules to help keep the code well organized. I'd like the $fn variable to be set to 10 when in preview mode and 200 when rendering. I think that's everything for the initial design. Let me know if you have any questions.

[Assistant]
StorageTray_V1.scad:
```
/* [Tray Dimensions] */
//The width of the tray
WIDTH = 100; // [50:1:1000]
//The depth/length of the tray
DEPTH = 100; // [50:1:1000]
//The height of the tray. The tray is divided into 3 sections. The top section uses the dimensions above while the bottom section is smaller to allow the trays to stack
HEIGHT = 15; // [10:1:500]

/* [Tray Design] */
//The bottom cutout design
DESIGN = 5; // [1:Snowflake 1, 2: Snowflake 2, 3: Spiral Snowflake, 4: Spiral, 5: Sun, 6: Alternating Circles]

/* [Hidden] */
//DON'T CHANGE ANYTHING BELOW
$fn = $preview ? 10 : 200;

WALL_THICKNESS = 2;
STANDARD_SIZE = 100;
SECTION_HEIGHT = HEIGHT / 3;

tray();

module tray() {
  difference() {
    union() {
      bottom_tray();
      middle_tray();
      top_tray();
    }

    if (DESIGN == 1) tray_underside_design_snowflake();
    else if (DESIGN == 2) tray_underside_design_snowflake_2();
    else if (DESIGN == 3) tray_underside_design_snowflake_spiral();
    else if (DESIGN == 4) tray_underside_design_spiral();
    else if (DESIGN == 5) tray_underside_design_sun();
    else if (DESIGN == 6) tray_underside_design_circles();
  }
}

module bottom_tray() {
  bottom_width = WIDTH - WALL_THICKNESS*2 - 1;
  bottom_depth = DEPTH - WALL_THICKNESS*2 - 1;
  adjusted_width = bottom_width - WALL_THICKNESS*2;
  adjusted_depth = bottom_depth - WALL_THICKNESS*2;

  translate([WALL_THICKNESS + 0.5, WALL_THICKNESS + 0.5, 0]) {
    difference() {
      minkowski() {
        cube([adjusted_width, adjusted_depth, SECTION_HEIGHT-1]);

        translate([WALL_THICKNESS, WALL_THICKNESS, 0])
              cylinder(r = WALL_THICKNESS, h = 1);
      }

      translate([WALL_THICKNESS, WALL_THICKNESS, WALL_THICKNESS])
        cube([bottom_width - WALL_THICKNESS*2, bottom_depth - WALL_THICKNESS*2, SECTION_HEIGHT]);
    }
  }
}

module middle_tray() {
  bottom_width = WIDTH - WALL_THICKNESS*2 - 1;
  bottom_depth = DEPTH - WALL_THICKNESS*2 - 1;
  union() {
    //Left
    translate([0, WALL_THICKNESS*2 + 0.5, SECTION_HEIGHT])
      side(bottom_depth, WALL_THICKNESS, SECTION_HEIGHT);

    //Front
    translate([bottom_width + 0.5, 0, SECTION_HEIGHT])
      rotate([0, 0, 90])
        side(bottom_width, WALL_THICKNESS, SECTION_HEIGHT);

    //Right
    translate([bottom_width + WALL_THICKNESS*2 + 1, bottom_depth + 0.5, SECTION_HEIGHT])
      rotate([0, 0, 180])
        side(bottom_depth, WALL_THICKNESS, SECTION_HEIGHT);

    //Back
    translate([WALL_THICKNESS*2 + 0.5, bottom_depth + WALL_THICKNESS * 2 + 1, SECTION_HEIGHT])
      rotate([0, 0, -90])
        side(bottom_width, WALL_THICKNESS, SECTION_HEIGHT);
  }
}

module side(length, width, height) {
  union() {
    translate([width + 0.5, 0, 0]) {
      side_half(length/1.75, width, height);

      translate([0, length-WALL_THICKNESS*2, 0])
      mirror([0, 1, 0]) side_half(length/1.75, width, height);
    }

    translate([WALL_THICKNESS*2 + 0.5, 0, 0])
      slanted_cylinder();
  }
}
module side_half(length, width, height) {
  _scale = 15 / HEIGHT;
  multmatrix([
    [1, 0, -0.5 * _scale, 0],
    [0, 1, -0.5 * _scale, 0],
    [0, 0, 1, 0]
  ]) {
    cube([width, length, height]);
  }
}
module slanted_cylinder() {
  _scale = 15 / HEIGHT;
  multmatrix([
    [1, 0, -0.5 * _scale, 0],
    [0, 1, -0.5 * _scale, 0],
    [0, 0, 1, 0]
  ]) {
    difference() {
      cylinder(h=SECTION_HEIGHT, r=WALL_THICKNESS, center=false);

      cube([WALL_THICKNESS, WALL_THICKNESS, SECTION_HEIGHT]);
    }
  }
}

module top_tray() {
  adjusted_width = WIDTH - WALL_THICKNESS*2;
  adjusted_depth = DEPTH - WALL_THICKNESS*2;

  translate([0, 0, SECTION_HEIGHT*2]) {
    difference() {
      minkowski() {
        cube([adjusted_width, adjusted_depth, SECTION_HEIGHT-1]);

        translate([WALL_THICKNESS, WALL_THICKNESS, 0])
              cylinder(r = WALL_THICKNESS, h = 1);
      }

      translate([WALL_THICKNESS, WALL_THICKNESS, -SECTION_HEIGHT])
        cube([WIDTH - WALL_THICKNESS*2, DEPTH - WALL_THICKNESS*2, SECTION_HEIGHT*3]);
    }
  }
}
module tray_underside_design_snowflake() {
  rotateDelta = 45;
  _scale = min(WIDTH, DEPTH)/STANDARD_SIZE;
  translate([WIDTH/2, DEPTH/2, -WALL_THICKNESS/1.25]) {
    for (k = [1:3:3*4]) {
      rotate([0, 0, 22.5*(k-1)]) {
        for (i = [0:rotateDelta:(360-rotateDelta)]) {
          rotate([0, 0, i]) translate([2*k*_scale, 2*k*_scale, 0])
            tray_underside_diamond(_scale);
        }
      }
    }
  }
}

module tray_underside_design_snowflake_2() {
  rotateDelta = 40;
  _scale = min(WIDTH, DEPTH)/STANDARD_SIZE;
  translate([WIDTH/2, DEPTH/2, -WALL_THICKNESS/1.25]) {
    for (k = [2:10]) {
      rotate([0, 0, 15*(k-1)]) {
        for (i = [0:rotateDelta:(360-rotateDelta)]) {
          rotate([0, 0, i]) translate([2*(k-1)*_scale, 2*(k-1)*_scale, 0])
            tray_underside_diamond(_scale * (k/7));
        }
      }
    }
  }
}

module tray_underside_design_snowflake_spiral() {
  rotateDelta = 60;
  _scale = min(WIDTH, DEPTH)/STANDARD_SIZE;
  translate([WIDTH/2, DEPTH/2, -WALL_THICKNESS/1.25]) {
    for (k = [1:7]) {
      rotate([0, 0, 15*k]) {
        for (i = [0:rotateDelta:(360-rotateDelta)]) {
          rotate([0, 0, i-((k*k)/1.5)]) translate([2*1.5*k*_scale, 2*1.5*k*_scale, 0])
            tray_underside_diamond(_scale);
        }
      }
    }
  }
}

module tray_underside_design_spiral() {
  rotateDelta = 25;
  _scale = min(WIDTH, DEPTH)/STANDARD_SIZE;
  translate([WIDTH/2, DEPTH/2, -WALL_THICKNESS/1.25]) {
    for (i = [23:91]) {
      rotate([0, 0, rotateDelta*i - 9*i]) translate([i*0.2*_scale, i*0.2*_scale, 0])
        tray_underside_diamond(_scale * (i/60));
    }
  }
}

module tray_underside_design_sun() {
  _scale = min(WIDTH, DEPTH)/STANDARD_SIZE;

  translate([WIDTH/2, DEPTH/2, -WALL_THICKNESS/1.25]) {
    difference() {
      cylinder(h=WALL_THICKNESS*2, r=WALL_THICKNESS*6*_scale, center=true);
      cylinder(h=WALL_THICKNESS*2, r=WALL_THICKNESS*4*_scale, center=true);
    }
    for (i = [0:15:(360 - 15)]) {
      rotate([0, 0, i]) translate([2*10*_scale, 2*5*_scale, 0])
        tray_underside_diamond(_scale);
    }

    for (i = [5:15:(360)]) {
      rotate([0, 0, i]) translate([2*12*_scale, 2*8*_scale, 0])
        tray_underside_diamond(_scale);
    }
  }
}

module tray_underside_design_circles() {
  _scale = min(WIDTH, DEPTH)/STANDARD_SIZE;
  rotateDeltaBase = 20;

  translate([WIDTH/2, DEPTH/2, -WALL_THICKNESS/1.25]) {
    for (k = [1:1:3]) {

      rotateDelta = rotateDeltaBase - (5 * (k-1));
      for (i = [0:rotateDelta:(360-rotateDelta)]) {
        xr = k % 2 == 0 ? k * 7 - 4 : k;
        yr = k % 2 == 1 ? k * 7 - 4 : k;
        rotate([0, 0, i]) translate([2*xr*_scale, 2*yr*_scale, 0])
          tray_underside_diamond(_scale);
      }
    }
  }
}

module tray_underside_diamond(size) {
  scale([size, size, 1]) {
    linear_extrude(height = WALL_THICKNESS, center = false) {
      polygon([[0, 0], [2*3, 2*2], [2*5, 2*5], [2*2, 2*3]]);
    }
  }
}
```


[User]
Hey, I'm looking to generate some custom skew dice for 3D printing, and I've seen some cool designs online, but I'd like to build a set based on user-defined parameters.

Basically, I want to be able to generate dice with adjustable size, skewness, stretch, and edge rounding. I also want a special feature where the pips can be created as holes, which I can then fill with short pieces of different colored filament after the print to give the dice a two-color look.

I'm thinking of something where I can specify:

*   **Size:** The overall size of the dice in millimeters.
*   **Phase:** This controls the skewness, basically how much the north edges are rotated with respect to the south edges. I'd like to be able to set it from 0 to 120 degrees, where 60 gives a standard cube and 30 is a recommended value for skewed dice.
*   **Stretch:** This lets me stretch the dice along one axis to make it longer, shorter, or set to a special 0 value to make the north and south poles right-angled.
*   **Rounding:** The percentage of rounding on the edges, like a radius.
*   **Depth of Pips:** The depth of the holes for the pips when using a fill-in method with another filament, as opposed to standard spherical pips. If zero, I would like the spheres.
*   **Diameter of Pips:** The diameter of the holes (if a depth is set) for inserting cut filament.
*   **Laydown:** A flag to choose if the dice should be generated lying down on a face on the print bed to allow printing without supports or not.
*   **Mirror Dice:** A flag to add a mirrored copy of the generated die to the scene for easy printing of dice pairs.

The pips should be arranged so that opposite sides sum to 7 like on regular dice. The numbers on the faces should also be adjustable, such that a user could create dice with any number of pips.

Could you generate an OpenSCAD file or files that lets me control these parameters?  I'm okay with multiple files if that makes the design cleaner. I'm not really familiar with any libraries, so I'm happy to go with what makes the most sense here.

[Assistant]
skew-dice_paramV2.scad:
```

// Author:  Carlos Luna (carlos.luna@mmaca.cat)
// Version: 2023-12-19
// License: Creative Commons By

// Modifications by MegaHertz67:
// Version: 2024-12-16
// Added Customizer interface and throw dice on print bed for easy printing
// Version V2: 2024-12-26
// Added Option of deep holes as pips to fill with cut pieces of other filament.
// In this simple way 2 color dice can be printed with standard printer.

/// MAIN ///////////////////////////////////////////////////////////

// Size of dice [mm]
size = 16; // [5:1:50]
// Phase shift [°]    30=recommended / 60=normal cube / 5=extreme case
phase = 30; // [0:1:120]
// Stretch one axis    1=normal / >1 longer axis / 0=special: two rectangle corners
stretch = 1; // [0:0.1:3]
// Rounding of edges [%]
rounding = 10; // [0:1:25]
// Depth of holes for pips to fill by cut filament parts [mm]    0=normal sphere pips
depthpips = 0; // [0:0.1:4]
// Diameter of holes for filament (for filling pip holes if depthpips>0) [mm]
diapips = 1.90; // [1.5:0.05:3.2]
// Lay down on print bed
laydown = 1; // [0:no, 1:yes]
// Add 2nd mirrored dice
addmirror = 0; // [0:no, 1:yes]

skew_dice(size, phase, stretch, rounding/100, depthpips, diapips, laydown);
if (addmirror) translate([size*2,0,0]) mirror([1,0,0]) skew_dice(size, phase, stretch, rounding/100, depthpips, diapips, laydown);

/*
FOUR PAIRS OF SAMPLE DICE
// Spherical Skew dice pair (spherical & non-right-angled):
translate([-16, 16,0]) mirror([0,0,0]) skew_dice(phase=30, stretch=1);
translate([-48, 16,0]) mirror([1,0,0]) skew_dice(phase=30, stretch=1);
translate([-32, 36,0]) linear_extrude(1) text(str("Spherical & Non-Right-angled"), halign="center", valign="center", size=3);

// Right Skew dice pair (non-spherical & right-angled):
translate([ 48,-16,0]) mirror([0,0,0]) skew_dice(phase=30, stretch=0);
translate([ 16,-16,0]) mirror([1,0,0]) skew_dice(phase=30, stretch=0);
translate([ 32,-36,0]) linear_extrude(1) text(str("Non-Spherical & Right-angled"), halign="center", valign="center", size=3);

// Normal dice pair (spherical & right-angled):
translate([-16,-16,0]) mirror([0,0,0]) skew_dice(phase=60, stretch=1);
translate([-48,-16,0]) mirror([1,0,0]) skew_dice(phase=60, stretch=1);
translate([-32,-36,0]) linear_extrude(1) text(str("Spherical & Right-angled"), halign="center", valign="center", size=3);

// Elongated dice pair (non-spherical & non-right-angled):
translate([ 48, 16,0]) mirror([0,0,0]) skew_dice(phase=10, stretch=1);
translate([ 16, 16,0]) mirror([1,0,0]) skew_dice(phase=60, stretch=2);
translate([ 32, 36,0]) linear_extrude(1) text(str("Non-Spherical & Non-Right-angled"), halign="center", valign="center", size=3);
*/

/*
// ALL VARIANTS AT ONCE:

// DICE
for (x = [0:5:120]) { // Skewness of the dice
    for (y = [0.5:0.1:2]) { // Stretchness of the dice
        translate([(x-60)*6,(y-1)*16*20,0])
        skew_dice(phase=x, stretch=y, rounding=0, faces=[0,0,0,0,0,0]);
    }
    translate([(x-60)*6,-0.7*16*20,0])
    skew_dice(phase=x, stretch=0, rounding=0, faces=[0,0,0,0,0,0]);
}

// AXIS
for (x = [0:5:120]) { // Skewness of the dice
    translate([(x-60)*6,-0.6*16*20,0]) linear_extrude(1)
    text(str(x,"º"), halign="center", valign="center", size=8);
}

for (y = [0.5:0.1:2]) { // Stretchness of the dice
    translate([(-65)*6,(y-1)*16*20,0]) linear_extrude(1)
    text(str(10*y/10), halign="center", valign="center", size=8);
}

translate([(-65)*6,-0.7*16*20,0]) linear_extrude(1)
text("0", halign="center", valign="center", size=8);

// LEGEND
translate([0,-0.8*16*20,0]) linear_extrude(1)
text("Auto-stretched to have right-angled N and S poles", halign="center", valign="center", size=8);
*/

/// AUXILIARY FUCNTIONS ////////////////////////////////////////////

//  Skew Dice (TM)
//
//
//  These dice are asymmetric trigonal trapezohedrons, the least
//  symmetric 6-sided face-transitive polyhedrons (they only have
//  rotational 120º-symmetry). The face-transitivity property
//  ensures that they are "fair by symmetry" in the sense of:
//
//      P. Diaconis & J. B. Keller, 1989 - "Fair Dice"
//      The American Mathematical Monthly, Vol. 96, No. 4.
//
//
//  The first commercial version of the Skew Dice was designed by
//  Robert Fathauer and Henry Segerman. Indeed, "Skew Dice" is a
//  trademark of their company "The Dice Lab":
//
//      https://www.mathartfun.com/thedicelab.com/index.html
//
//  I strongly encourage you to buy their wonderful products!
//
//
//  In the module below, 'size' is a positive parameter that
//  controls the size of the dice. More precisely, the radius
//  of the dice (before stretching, see details below) is:
//
//      'size * sqrt(3)/2' (in mm)
//
//  which is the same radius that a standard cubic dice of
//  'size' edge-length will have.
//
//      * size = 16 is the default, recommended setting.
//
//
//  The parameter 'phase', in the range [0,120], controls the
//  skewness of the dice: the rotation (in degrees) of the
//  three south edges with respect to the three north edges.
//
//      * phase = 0 & Phase = 120 are extreme degenerate cases.
//      * phase = 30 is the default, recommended setting.
//      * phase = 60 gives you a normal cube.
//
//
//  The parameter 'stretch' controls the vertical elongation of
//  the dice. By default, all the vertices of these dice lie on
//  a sphere. However, you can scale the dice vertically without
//  losing their face-transitivity (and, thus, their fairness).
//
//  For positive values of 'stretch', this value will be used as
//  the vertical scale factor. For the special 'stretch = 0' value,
//  the vertical scale factor will be automatically computed to
//  make the north and south poles of the dice right-angled.
//  This automatic 'stretch' depends on the 'phase' parameter and
//  ranges from '1/sqrt(2)' (for 'phase = 0' and 'phase = 120') to
//  '1' (for 'phase=60').
//
//      * stretch > 1 provides vertically elongated dice.
//      * stretch = 1 is the default, recommended setting.
//      * stretch < 1 provides vertically flattened dice.
//      * stretch = 0 enforces right-angled N & S poles.
//
//
//  The parameter 'rounding' determines the radius of the rounded
//  corners of the polyhedron. In particular, it will be the
//  radius (in mm) of the rounded corners of a die whose
//  vertices lie on a sphere of 1 mm of radius.
//
//      * rounding = 0.00 provides a sharp polyhedron.
//      * rounding = 0.05 is the default, slightly rounded, value.
//      * rounding = 0.20 provides a heavily rounded polyhedron.
//
//
//  The array 'faces' stores the numbers that will be engraved on
//  the dice faces. You can place from 0 to 9 pips on each face.
//  In the default setting of 'faces = [1,2,3,4,5,6]', the numbers
//  are placed so opposite sides add to 7 and so the north and
//  south caps are as balanced as possible (1+4+5 vs 2+3+6).
//
//      * faces = [0,0,0,0,0,0] for blank faces without pips.
//      * faces = [1,2,3,4,5,6] is the default, for normal dice.
//
//
//  The easiest way to print these dice is with the lowest pip-count
//  face (the "1" on normal dice) lying directly on the print bed.
//  But you can obtain more aesthetically pleasing results if you
//  print them in the provided orientation (with the aid of supports).
//
//
module skew_dice(size=16, phase=30, stretch=1, rounding=0.05, depthpips, diapips, laydown=1, faces=[1,2,3,4,5,6]) {

    // Sanity checks:
    assert(0 <= stretch);
    assert(0 <= rounding);
    assert(0 <= phase && phase <= 120);

    // Constants:
    K = sqrt(3);       //diameter in standard cube size 1.0
    R = size*K/2;      //radius of sphere around cube of chosen size (for final scaling up)
    C = cos(phase);
    S = sin(phase);

    // Coordinates of the main equatorial vertex: [X,0,Z]
    L = (C-1)*(S-K/2) - S*(C+1/2);  // <= Geometric Voodoo
    Z = L/(L-K);                    // <= Geometric Voodoo
    X = sqrt(1-Z*Z);

    // Vertical scaling factor:
    v_scale = (stretch <0.02) ? (X/((1-Z)*sqrt(2))) : stretch;

    // Derivation of all other vertices from the vertex: [X,0,Z]
    // results in a dice centered at [0,0,0] standing on one corner

    V = [[          0,            0,    1*v_scale],  // North
         [          X,            0,    Z*v_scale],  // Eq. 1
         [        C*X,          S*X,   -Z*v_scale],  // Eq. 2
         [         -X/2,        K*X/2,  Z*v_scale],  // Eq. 3
         [ -(C+S*K)*X/2,  (C*K-S)*X/2, -Z*v_scale],  // Eq. 4
         [         -X/2,       -K*X/2,  Z*v_scale],  // Eq. 5
         [ -(C-S*K)*X/2, -(C*K+S)*X/2, -Z*v_scale],  // Eq. 6
         [          0,            0,   -1*v_scale]]; // South

/*  To throw face "1" afterwards on print bed:
    Z-Offset = -Z*v_scale face1 points Eq.1 Eq.3 to z=0
    X-Offset = -0.25*X and Y-Offset = -K*X/4 to center these points around [0,0,0]
    afterwards rotate line Eq.1 Eq.3 along z-axis to align with X-axis
    finally throw it to bed by rotating around X-axis   */

    // Magic calculation of final rotation angle for throwing dice on print bed...
    a=X;b=norm([X/2,K*X/2]);c=norm([3*X/2,K*X/2]);s=.5*(a+b+c);d=2/c*sqrt(s*(s-a)*(s-b)*(s-c));ta=-90-atan(d/((1-Z)*v_scale));

    // Build the dice and throw it on the print bed:
    scale([R,R,R])
    rotate([laydown*ta,0,0])                       //throw face 1 down on print bed
    rotate([0,0,asin(K*X/2/norm([K*X/2,1.5*X]))]) //rotate Eq.1&Eq.3 line to X axis
    translate([-0.25*X,-K*X/4,-Z*v_scale])       //center Eq.1&Eq.3 of face "1" @ z=0

    difference() {
        if (rounding == 0) {
            polyhedron(V,[[0,3,2,1],[0,5,4,3],[0,1,6,5],   // N
                          [7,2,3,4],[7,4,5,6],[7,6,1,2]]); // S
        } else {
            shrink = 1/(2*rounding+1);
            scale([shrink,shrink,shrink]) hull() {
                for (v=V) { translate(v) sphere(rounding, $fn=32); }
            }
        }

        // Mark the pips on the faces:
        pips(faces[0], 1/12,  depthpips/R,diapips/R/2,  V[0],V[3],V[2],V[1]);
        pips(faces[1], 1/12,  depthpips/R,diapips/R/2,  V[7],V[6],V[1],V[2]);
        pips(faces[2], 1/12,  depthpips/R,diapips/R/2,  V[7],V[2],V[3],V[4]);
        pips(faces[3], 1/12,  depthpips/R,diapips/R/2,  V[0],V[1],V[6],V[5]);
        pips(faces[4], 1/12,  depthpips/R,diapips/R/2,  V[0],V[5],V[4],V[3]);
        pips(faces[5], 1/12,  depthpips/R,diapips/R/2,  V[7],V[4],V[5],V[6]);
    }
}

// Places 'N' spheres (with 'N' between 0 and 9) of radius 'R'
// on the (flat) 3D quadrilateral 'ABCD'
//
module pips(N, RP, DEP,DIAP, A,B,C,D, $fn=32) {

    // Sanity checks:
    assert(0 <= N && N <= 9);

    // Non linear transformation from unit square to ABCD
    //
    // [X,Y] -> M*[X,Y,XY]+C = (B-C)·X+(D-C)·Y+(A-B+C-D)·XY+C
    //
    // [1,1] -> M*[1,1, 1]+C = (B-C)·1+(D-C)·1+(A-B+C-D)·1+C = A
    // [1,0] -> M*[1,0, 0]+C = (B-C)·1+(D-C)·0+(A-B+C-D)·0+C = B
    // [0,0] -> M*[0,0, 0]+C = (B-C)·0+(D-C)·0+(A-B+C-D)·0+C = C
    // [0,1] -> M*[0,1, 0]+C = (B-C)·0+(D-C)·1+(A-B+C-D)·0+C = D

    M = [[B[0]-C[0], D[0]-C[0], A[0]-B[0]+C[0]-D[0]],
         [B[1]-C[1], D[1]-C[1], A[1]-B[1]+C[1]-D[1]],
         [B[2]-C[2], D[2]-C[2], A[2]-B[2]+C[2]-D[2]]];

    if (DEP < 0.1) {            // normal sphere pips
        if (N % 2) { translate(M*[2/4,2/4,4/16]+C) sphere(RP); }
        if (N > 3) { translate(M*[3/4,3/4,9/16]+C) sphere(RP);
                     translate(M*[1/4,1/4,1/16]+C) sphere(RP); }
        if (N > 1) { translate(M*[1/4,3/4,3/16]+C) sphere(RP);
                     translate(M*[3/4,1/4,3/16]+C) sphere(RP); }
        if (N > 5) { translate(M*[2/4,3/4,6/16]+C) sphere(RP);
                     translate(M*[2/4,1/4,2/16]+C) sphere(RP); }
        if (N > 7) { translate(M*[1/4,2/4,2/16]+C) sphere(RP);
                     translate(M*[3/4,2/4,6/16]+C) sphere(RP); }

      } else {                  // special holes for pips to fill with cut filament
        // calculate rotation angle (ra) for holes
        // based on vector perpendicular to plane (pv)
        // plane defined by direction vectors B-C and D-C
        // perpendicular vector pv = cross product of BC and DC
        pvx = (B[1]-C[1])*(D[2]-C[2])-(D[1]-C[1])*(B[2]-C[2]);
        pvy = -(B[0]-C[0])*(D[2]-C[2])+(D[0]-C[0])*(B[2]-C[2]);
        pvz = (B[0]-C[0])*(D[1]-C[1])-(D[0]-C[0])*(B[1]-C[1]);
        ray = acos(pvz/norm([pvx,pvy,pvz]))-180;
        raz = atan2(pvy,pvx);

        if (N % 2) { translate(M*[2/4,2/4,4/16]+C) rotate([0,ray,raz])cylinder(DEP*2,DIAP,DIAP,true); }
        if (N > 3) { translate(M*[3/4,3/4,9/16]+C) rotate([0,ray,raz])cylinder(DEP*2,DIAP,DIAP,true);
                     translate(M*[1/4,1/4,1/16]+C) rotate([0,ray,raz])cylinder(DEP*2,DIAP,DIAP,true); }
        if (N > 1) { translate(M*[1/4,3/4,3/16]+C) rotate([0,ray,raz])cylinder(DEP*2,DIAP,DIAP,true);
                     translate(M*[3/4,1/4,3/16]+C) rotate([0,ray,raz])cylinder(DEP*2,DIAP,DIAP,true); }
        if (N > 5) { translate(M*[2/4,3/4,6/16]+C) rotate([0,ray,raz])cylinder(DEP*2,DIAP,DIAP,true);
                     translate(M*[2/4,1/4,2/16]+C) rotate([0,ray,raz])cylinder(DEP*2,DIAP,DIAP,true); }
        if (N > 7) { translate(M*[1/4,2/4,2/16]+C) rotate([0,ray,raz])cylinder(DEP*2,DIAP,DIAP,true);
                     translate(M*[3/4,2/4,6/16]+C) rotate([0,ray,raz])cylinder(DEP*2,DIAP,DIAP,true); }
        }
}

////////////////////////////////////////////////////////////////////
```


[User]
I'd like to design a parametric, print-in-place tape measure winder using OpenSCAD. I need to be able to customize the design for different tape lengths, widths, and thicknesses. I've got some of the parameters in mind, but I'd like to make sure that it's robust and easy to use.

The basic design should include a central spindle for winding the tape, a shell that encloses the spindle and tape, and a handle on the spindle for winding. The spindle should have a small tab to grab the metal tab on the tape, and the handle should have some kind of teeth to make it easy to grip. I want to make this all print-in-place, so there needs to be some clearance between the moving parts.

Here are the initial parameters I'm thinking of:

**Tape Measure Parameters:**

*   `tab_length`: Length of the metal tab at the end of the tape (mm).
*   `tab_thickness`: Thickness of the metal tab (mm).
*   `tape_width`: Width of the tape (mm).
*   `tape_thickness`: Thickness of the tape (mm).
*   `tape_length`: Total length of the tape (mm).

**Spindle Parameters:**

*   `spindle_r`: Radius of the main part of the spindle (mm).
*   `handle_r`: Radius of the handle teeth (mm).
*   `handle_h`: Height of the handle (mm).
*   `gap`: Clearance between parts (mm), this should be around my nozzle diameter.

**Shell Parameters:**

*   `base_thickness`: Thickness of the base of the shell (mm).
*  `wall_thickness`: Thickness of the walls of the shell (mm)

The spindle should be centered, and the handle should also be centered on the top of the spindle. The shell should enclose the wound tape, but should have enough space so the tape doesn't bind when it's all wound up. The bottom of the spindle needs to be supported so it doesn't fall out.

I'd like the design to be fully parametric, so it should be easy to adjust those parameters in OpenSCAD to fit different tapes. I don't need to use any particular library, just standard OpenSCAD modules will do.

[Assistant]
tape_measure_roller_with_centered_handle_with_teeth.scad:
```
/* [Tape Measure Parameters] */
// How long, in mm, the metal tab at the end of the tape is.
tab_length = 10;
// How thick (front to back) the tab is.
tab_thickness = 1.1;
// How wide is the tape in mm?
tape_width = 20; // [5:1:40]

// Roughly how thick is the tape? (This is used to calculate how much space is left around the spindle for the tape to wind in.)
tape_thickness = 0.4;

// How long is the tape in mm, inclusive of any length after the markings stop.
tape_length = 1510; // [100:10:6000]

/* [Spindle Parameters] */
spindle_r = 14;
handle_r = 3;
handle_h = 15;

// How much play to give the spindle. Not bad to keep this at ~ your nozzle bore
gap = 0.4;

/* [Shell Parameters] */
base_thickness = 3.0;
wall_thickness = 1.5;

/* [Hidden] */
$fn = 120;

tape_r = sqrt((tape_length * tape_thickness) / PI + spindle_r * spindle_r) + 0.5;

module spindle() {
    translate([0, 0, base_thickness]) {
        difference() {
            // Spindle body
            cylinder(h = tape_width + 1, r = spindle_r);

            // Tab for the spindle
            translate([0, spindle_r - tab_length, -1])
                cube([tab_thickness, tab_length + 2, tape_width + 3]);
        }

        // Handle with teeth
        translate([0, 0, 0]) {
            for (i = [0:360/20:360]) { // 20 teeth
                rotate([0, 0, i])
                    translate([0, 0, 0])
                        cube([handle_r, handle_r, tape_width + 1 + handle_h]);
            }
        }
    }

    // Base cylinders for spindle
    cylinder(h = base_thickness, r = spindle_r - base_thickness / 3);
    cylinder(h = base_thickness / 3, r1 = spindle_r, r2 = spindle_r - base_thickness / 3);
    translate([0, 0, 2 * base_thickness / 3])
        cylinder(h = base_thickness / 3, r2 = spindle_r, r1 = spindle_r - base_thickness / 3);
}

spindle();

module shell() {
    difference() {
        // Outer shell of the tape measure
        cylinder(h = tape_width + 1 + base_thickness + gap, r = tape_r + wall_thickness);

        // Inner space for the spindle
        translate([0, 0, base_thickness]) {
            cylinder(h = tape_width + 2, r = tape_r);
            translate([-spindle_r, 0, 0])
                cube([spindle_r * 2, tape_r * 2, tape_width + 2]);
        }

        // Bottom part of the shell
        translate([0, 0, -0.1])
            cylinder(h = base_thickness + gap + 0.1, r = spindle_r - base_thickness / 3 + gap);
        translate([0, 0, -0.1])
            cylinder(h = 0.1 + base_thickness / 3, r2 = spindle_r - base_thickness / 3 + gap, r1 = spindle_r + gap + 0.1);
        translate([0, 0, 2 * base_thickness / 3])
            cylinder(h = 0.1 + base_thickness / 3, r1 = spindle_r - base_thickness / 3 + gap + 0.1, r2 = spindle_r + gap);
    }
}

shell();

```