[User]
Hey, I'm looking to get some custom smartphone holders designed in OpenSCAD. I want to be able to mount my phone to a tripod, an action cam mount, or just have a simple stand. I have some specific requirements and would love to have a parametric model. I've tried using the customizer on Thingiverse, but sometimes it's just a bit clunky.

So, the main thing is the holder itself needs to be adjustable for different phone sizes. I need to specify the phone's width, height, thickness, corner rounding radius and edge rounding radius, and camera position (either mid, left, or right on the back of the phone) as well as the camera slot height when the camera is off-center. It should have those "arms" that wrap around the edges of the phone so it is held securely.

Then, I need to specify the mount point: either at the middle of the holder or at the bottom of the holder. I need to specify the bracket thickness and bottom thickness as parameters as well. If the mount position is the bottom, it should have an option for an action cam style mount or a tripod mount (standard 1/4" 20 thread). For the action cam mount, make sure to include the male pins for the cam mount receiver. If I choose a tripod mount, it should include a female threaded cylinder and a sphere below that for reinforcement.

Also, could you make sure there are holes for screws in the middle for mounting on a plate when the mount point is in the middle, and a way to put my phone's name on the holder? For the mounting plate in the middle, include the action cam male and female mounts on either side, like in the example code I've seen. If the mount position is bottom, then the phone name should be engraved into the arm itself.

I would prefer a design where the holder itself is a "frame" rather than a solid box, kind of like what you see in the provided example with the oval rings holding it together. There should be cutouts on the sides so that the camera doesn't get blocked, and the whole thing should be as compact as possible. The phone should be held securely so that it doesn't wobble while mounted. If the camera is on the left or right side, one side of the holder should be straight instead of rounded.

I'd also like some example phone definitions in there, like "PhoneSamsungS6" and "PhoneiPhone8", so it's easy to use and modify. And lastly, please try to add error checking for when someone inputs decimal numbers using commas instead of periods, with a text overlay that tells the user how to fix it.

I'm not requesting any particular libraries, just standard OpenSCAD functionality.

[Assistant]
SmartphoneHolder.scad:
```
// ****************************************************************************
// Customizable Smartphone Holders
// to mount your smartphone to a tripod or elsewhere using an action cam hook
// Author: Peter Holzwarth
// ****************************************************************************
// https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language

// for thingiverse:
SmartphoneHolder([PhoneName, PhoneWidth, PhoneHeight, PhoneDepth,
    PhoneCornerRoundingRadius, PhoneEdgeRoundingRadius,
    PhoneCamPos, CamSlotHeight], MountPos);

// for manual rendering:
// SmartphoneHolder(phone=PhoneiPhone8,mountPos="bottom");

// render only the middle hook
// MiddleHook("female");

// parameters for thingiverse customizer:

/* [Phone] */
// Name of the phone
PhoneName= "Samsung S8";
// Width of the phone
PhoneWidth= 68;
// Height of the phone
PhoneHeight= 149.5;
// Depth/ thickness of the phone - add 0,5mm here!
PhoneDepth= 8.5;
// Rounding radius of the four corners
PhoneCornerRoundingRadius= 12;
// Rounding radius of the edges at all sides
PhoneEdgeRoundingRadius= 2.5;
// Position of the camera, when looking from the front
PhoneCamPos= 0; // [0:Mid,1:Left,2:Right]
// Height of the space for the camera (only required if on left/right side):
CamSlotHeight= 22;

/* [Mounting] */
// Mount at bottom or mid of holder
MountPos= "mid"; // [bottom,mid,Tripod]
// Bracket thickness
th= 2;
// Bottom thickness
bt= 4;
// Precision of curves
$fn=75;

/* [Hidden] */
// constants for the camera position
CamPosMid= 0;
CamPosLeft= 1;
CamPosRight= 2;

// you may use these pre-defined models in the call below
PhoneSamsungS4= ["Samsung S4", 70, 137, 8, 12, 2, CamPosMid];
PhoneSamsungS5= ["Samsung S5", 73, 143, 8.5, 12, 2, CamPosMid];
PhoneSamsungS6= ["Samsung S6", 70, 144, 8, 12, 2, CamPosMid];
PhoneSamsungS7= ["Samsung S7", 71, 144, 8, 12, 2, CamPosMid];
PhoneSamsungS8= ["Samsung S8", 68, 149.5, 8.5, 12, 2.5, CamPosMid];
PhoneSamsungS10= ["Samsung S10", 70, 150, 8.5, 6, 2.5, CamPosMid];
PhoneiPhone4= ["iPhone 4", 59.6, 116.2, 10.3, 10, 2, CamPosRight,16];
PhoneiPhone5= ["iPhone 5", 59, 124, 8, 10, 2, CamPosRight,18];
PhoneiPhone6= ["iPhone 6", 67.5, 138.5, 7.5, 10, 2, CamPosRight,20];
PhoneiPhone7= ["iPhone 7", 67.5, 139, 8, 10, 2, CamPosRight,22];
PhoneiPhone8= ["iPhone 8", 68, 139, 8, 10, 2, CamPosRight,22];
PhoneiPhone8Plus= ["iPhone 8 Plus", 78.5, 159, 8, 10, 2, CamPosRight,22];
PhoneiPhoneX= ["iPhone X", 71, 144, 8, 10, 2, CamPosRight,38];
PhoneLG_G6= ["LG G6", 72, 149, 8.5, 8, 2, CamPosMid];
NintendoSwitch= ["Nintendo Switch", 102, 173.5, 15, 1.1, 0.5, CamPosMid];
PhoneiPhoneXS= ["iPhone XS wBatt", 88, 154, 18, 12, 5, CamPosMid];

/*
difference() {
    SmartphoneHolder(phone=NintendoSwitch,mountPos="mid");
    translate([0,0,-5]) cube([200,200,5]);
}
*/
// MiddleHook("female");

// ////////////////////////////////////////////////////////////////
// Holder
module SmartphoneHolder(phone=PhoneSamsungS6, mountPos="mid") {
    if (parametersHaveErrors(phone)) {
        rotate([45,0,45]) color("red") {
            translate([0,30,0]) text("You need to use dots '.'", size=5, valign="center", halign="center");
            translate([0,20,0]) text("instead of commas ','", size=5, valign="center", halign="center");
            translate([0,10,0]) text("for the decimal places,", size=5, valign="center", halign="center");
            translate([0,0,0]) text("or better, round the values up!", size=5, valign="center", halign="center");
        }
    } else {
        SmartphoneHolderI(phone, mountPos);
    }
}

function parametersHaveErrors(phone) =
    phone[1]+phone[2]+phone[3]+phone[4]+phone[5]==undef;

module SmartphoneHolderI(phone, mountPos) {
    // edge rounding radius should be a small value
    er= min(phone[5], phone[4]/2-0.01, 3);
    // the four corners
    // lower left
    if (phone[6]!=CamPosLeft) {
        HolderCorner(phone,er);
    } else {
        HolderEdge(phone,er);
    }
    // lower right
    translate([phone[2]+2*th,0,0]) rotate([0,0,90])
        HolderCorner(phone,er);
    // upper left
    if (phone[6]!=CamPosRight) {
        translate([0,phone[1]+2*th,0]) rotate([0,0,-90])
            HolderCorner(phone,er);
    } else {
        translate([0,phone[1]+2*th,0])
        mirror([0,1,0]) HolderEdge(phone,er);
    }
    // upper right
    translate([phone[2]+2*th,phone[1]+2*th,0]) rotate([0,0,180])
        HolderCorner(phone,er);

    difference() {
        union() {
            // the oval half rings
            rd= phone[4]+th;
            // left half ring
            difference() {
                translate([th+2,th+phone[1]/2,0]) rotate([0,0,-90])
                    halfOvalRing(phone[1]/2,phone[2]/2,bt+th,th,er);
                    // remove outer roundings from the oval rings
                    roundCut(phone);
                    translate([0,phone[1]+2*th,-0.01]) rotate([0,0,-90])
                        roundCut(phone);
            }
            // right half ring
            difference() {
                translate([th+phone[2]-2,th+phone[1]/2,0]) rotate([0,0,90])
                    halfOvalRing(phone[1]/2,phone[2]/2,bt+th,th,er);
                translate([phone[2]+2*th,0,-0.01]) rotate([0,0,90])
                    roundCut(phone);
                translate([phone[2]+2*th,phone[1]+2*th,-0.01]) rotate([0,0,180])
                    roundCut(phone);
            }
            // mounting support
            if (mountPos=="mid") {
                // plate in the middle
                translate([th+phone[2]/2,th+phone[1]/2,0])
                    translate([0,0,(th+bt)/2]) cubeR([26,26,th+bt],2, true);
            } else if (mountPos=="bottom" || mountPos=="Tripod") {
                translate([th+phone[2]/2,0,0]) {
                    // plate from mid to bottom
                    hull() {
                        translate([0,th+phone[1]/2,0]) fring(r=4, th=th+bt);
                        translate([0,0,(th+bt)/2])
                            cube([12,1,th+bt],center=true);
                    }
                    rotate([90,0,0]) hull() {
                        // translate([0,7.5,0]) fring(r=3.5, th=6);
                        translate([0,11,0]) scale([1,0.42,1]) cylinder(r=6, h=6);
                        translate([0,0.5,(th+bt)/2]) cube([12,1,6],center=true);
                    }
                    if (mountPos=="bottom") {
                        // action cam hook here
                        translate([0,-18,6]) rotate([90,0,0])
                            ActionCamRingsMale(16, true);
                    } else { // tripod
                        translate([0,-11,10]) rotate([-90,0,0])
                            cylinder(r=7/2,h=5);
                        translate([0,-11-20/2+1,10]) sphere(r=20/2);
                    }
                }
            }
        }
        // for camera at one side, remove tail of the arm
        if (phone[6]==CamPosLeft) {
            translate([0,0,-0.01]) cube([phone[7]+2,25,25]);
        } else if (phone[6]==CamPosRight) {
            translate([0,phone[1]-25+2*th,-0.01]) cube([phone[7]+2,25,25]);
        }
        if (mountPos=="mid") {
            // holes for screws
            translate([th+phone[2]/2,th+phone[1]/2,0]) {
                translate([0,0,-0.01]) fourScrews(16,16,3.3,th+bt+0.02);
                translate([0,0,(th+bt)/2]) fourScrews(16,16,6.3,th+bt+0.02);
            }
            // stamp phone name into mounting square
            translate([th+phone[2]/2,th+phone[1]/2,th+bt-0.5])
                linear_extrude(1)
                    text(phone[0], size=3, valign="center", halign="center");
        } else {
            // stamp phone name into mounting arm
            translate([th+phone[2]/2,th+phone[1]/2,th+bt-0.5]) rotate([0,0,-90])
                linear_extrude(1)
                    text(phone[0], size=4, valign="center", halign="left");
        }
    }
    if (mountPos=="mid") {
        translate([th+phone[2]/4,th+phone[1]/2,0]) MiddleHook("male");
        translate([th+phone[2]*3/4,th+phone[1]/2,0]) MiddleHook("female");
    }
}

module roundCut(phone) {
    rd= phone[4]+th;
    difference() {
        cube([rd, rd, rd+th+bt]);
        translate([rd,rd,-0.01]) cylinder(r=phone[4],h=rd+th+bt+0.02);
    }
}

module MiddleHook(hooks) {
    h= 20;
    difference() {
        union() {
            translate([0,0,(th+bt)/2]) cubeR([26,26,th+bt],2, true);
            if (hooks == "male") {
                translate([0,0,h]) ActionCamRingsMale(h, true);
            } else {
                translate([0,0,h]) ActionCamRingsFemale(h, true);
            }
        }
        translate([0,0,-0.01]) fourScrews(16,16,3,th+bt+0.02);
        translate([0,0,(th+bt)/2]) fourScrews(16,16,6.3,(th+bt)/2+2,6);
    }
}

// phone=PhoneSamsungS6;
// ovalRing(phone[1]/2,phone[2]/2,bt+th,2+th,phone[5],180);
// halfOvalRing(phone[1]/2,phone[2]/2,bt+th,2+th,phone[5],180);
// ////////////////////////////////////////////////////////////////
// Oval ring with a constant diameter
// Used to form the arms
module halfOvalRing(w, d, h, rth, rr) {
    difference() {
        difference() {
            scale([1,d/w,1]) fring(w-rth/2, h);
            translate([-0.01,-0.01,-0.01]) scale([1,(d-rth)/(w-rth),1]) difference() {
                cylinder(r=w-rth, h=h+0.02);
                translate([0,0,h/2]) ring(w-rth, h);
            }
        }
        translate([-w-th-0.01,-d-3*th-0.05,-0.01])
            cube([2*(w+th), d+3*th+0.05, h+0.03]);
    }
}

// ////////////////////////////////////////////////////////////////
// One corner of the holder
module HolderCorner(phone,er) {
    difference() {
        hull() {
            wl= max(phone[4]+th,10);
            tl= max(er,3);
            // corner
            translate([phone[4]+th,phone[4]+th,0]) rotate([0,0,180])
                qwheel(phone[4]+th,phone[3]+2*th+bt,er);
            // x longhole
            translate([wl,tl,phone[3]+er+bt]) rotate([0,90,0])
                longHole(radius=tl, length=phone[3]-2*tl+2*th+bt,
                         height=2);
            // y longhole
            translate([tl,wl,phone[3]+er+bt]) rotate([0,90,90])
                longHole(radius=tl, length=phone[3]-2*tl+2*th+bt,
                         height=2);
        }
        translate([th,th,th+bt]) Smartphone(phone,er);
    }
}

// ////////////////////////////////////////////////////////////////
// Edge, used for smartphones with the camera at one side
module HolderEdge(phone,er) {
    h= bt+th;
    y2= phone[2]/2-sqrt((phone[2]/2)*(phone[2]/2)-phone[7]*phone[7])+2*(bt+th);
    difference() {
        union() {
            // upper part - short to not cover the display much
            translate([phone[7],0,0]) cubeR([15,8,phone[3]+2*th+bt],2, false);
            // lower part - longer to cover the arm
            translate([phone[7],0,0]) cubeR([15,y2,h+2],2, false);
        }
        translate([th,th,th+bt]) Smartphone(phone,er);
    }
}

// ////////////////////////////////////////////////////////////////
// Smartphone model
module Smartphone(phone=PhoneSamsungS6, er=2) {
    hull() {
        translate([phone[2]-phone[4],phone[1]-phone[4],0])
            qwheel(phone[4],phone[3],er);
        translate([phone[2]-phone[4],phone[4],0]) rotate([0,0,-90])
            qwheel(phone[4],phone[3],er);
        translate([phone[4],phone[4],0]) rotate([0,0,180])
            qwheel(phone[4],phone[3],er);
        translate([phone[4],phone[1]-phone[4],0]) rotate([0,0,90])
            qwheel(phone[4],phone[3],er);
    }
}

// ////////////////////////////////////////////////////////////////
// a quater wheel with an outer wheel radius, an outer height and a rounding radius
module qwheel(wr, h, rr, center=false) {
    translate([0,0,center?-h/2:0]) {
        hull() {
            translate([0,0,h-rr]) qwheelBike(wr, rr);
            translate([0,0,rr]) qwheelBike(wr, rr);
        }
    }
}

// quater ring, also works with older OpenSCAD versions
module qwheelBike(wr, rr) {
    difference() {
        rotate_extrude(convexity= 2) translate([wr-rr, 0, 0]) circle(r=rr);
        translate([-wr-0.01,-wr-0.01,-rr-0.01]) cube([2*wr+0.02,wr+0.01,2*rr+0.02]);
        translate([-wr-0.01,-wr-0.01,-rr-0.01]) cube([wr+0.01,2*wr+0.02,2*rr+0.02]);
    }
}

// ////////////////////////////////////////////////////////////////
// rounded button - lower part is a cylinder, upper part like a filled ring
// r= outer radius
// h= height
// rr= upper rounding
module buttonRound(r, h, rr) {
    hull() {
        translate([0,0,h-rr]) wheelBike(r, rr);
        cylinder(r=r,h=h-rr);
    }
}

// ////////////////////////////////////////////////////////////////
// a ring with a radius and a thickness
// else same as wheelBike
// r - radius
// th - thickness
module ring(r, th) {
    rotate_extrude(convexity= 4, $fn= 100)
        translate([r, 0, 0])
            circle(r = th/2, $fn = 100);
}

// ////////////////////////////////////////////////////////////////
// filled ring - like a round cheese
// fring(20,4);
module fring(r, th) {
    rotate_extrude(convexity= 4, $fn= 100) {
        translate([r, th/2, 0])
            circle(r = th/2, $fn = 100);
        square([r,th]);
    }
}

// generic long hole
module longHole(radius, length, height) {
    hull() {
        cylinder(r=radius, h=height);
        translate([length,0,0]) cylinder(r=radius, h=height);
    }
}

// ////////////////////////////////////////////////////////////////
// cube with rounded corners
module cubeR(dims, rnd=1, centerR= false) {
    translate(centerR?[-dims[0]/2,-dims[1]/2,-dims[2]/2]:[]) {
        hull() {
            translate([rnd,rnd,rnd]) sphere(r=rnd);
            translate([dims[0]-rnd,rnd,rnd]) sphere(r=rnd);
            translate([rnd,dims[1]-rnd,rnd]) sphere(r=rnd);
            translate([dims[0]-rnd,dims[1]-rnd,rnd]) sphere(r=rnd);

            translate([rnd,rnd,dims[2]-rnd]) sphere(r=rnd);
            translate([dims[0]-rnd,rnd,dims[2]-rnd]) sphere(r=rnd);
            translate([rnd,dims[1]-rnd,dims[2]-rnd]) sphere(r=rnd);
            translate([dims[0]-rnd,dims[1]-rnd,dims[2]-rnd]) sphere(r=rnd);
        }
    }
}

// ////////////////////////////////////////////////////////////////
// two rings for action cam mount
module ActionCamRingsMale(l, base, adj=-0.25, center= true) {
    translate(center ? [(-9-adj)/2,0,0] : [0,6,l]) {
        if (base) {
            translate([0,-6,-l]) cube([9+adj, 12, 3]);
        }
        for (i= [0, 6]) {
            translate ([i, 0, 0]) ActionCamOneRing(l, adj);
        }
    }
}

// ////////////////////////////////////////////////////////////////
// three rings for action cam mount
module ActionCamRingsFemale(l, base, adj= -0.25, center= true) {
    translate(center ? [(-15-adj)/2,0,0] : [0,6,l]) {
        if (base) {
            translate([0,-6,-l]) cube([15+adj, 12, 3]);
        }
        for (i= [0, 6, 12]) {
            translate ([i, 0, 0]) ActionCamOneRing(l, adj);
        }
    }
}

// one ring, helper for action cam hooks
module ActionCamOneRing(l, adj) {
    rotate([90, 0, 90]) difference() {
        hull() {
            cylinder(r=6, h=3+adj);
            translate([-6, -l, 0]) cube([12,1,3+adj]);
        }
        translate([0,0,-0.01]) cylinder(r=3, h=3.02+adj);
    }
}

// four holes in a square
// x, y: distances of mid points
// d: screw diameter
// h: nut height
module fourScrews(x, y, d, h, fn=20) {
    translate([x/2,y/2,0]) cylinder(r=d/2,h=h,$fn=fn);
    translate([-x/2,y/2,0]) cylinder(r=d/2,h=h,$fn=fn);
    translate([x/2,-y/2,0]) cylinder(r=d/2,h=h,$fn=fn);
    translate([-x/2,-y/2,0]) cylinder(r=d/2,h=h,$fn=fn);
}

// ////////////////////////////////////////////////////////////////
// a pipe - a hollow cylinder
module pipe(outerRadius, thickness, height) {
    difference() {
        cylinder(h=height, r=outerRadius);
        translate([0, 0, -0.1]) cylinder(h=height+0.2, r=outerRadius-thickness);
    }
}

// ////////////////////////////////////////////////////////////////
// a half pipe
module halfPipe(outerRadius, thickness, height) {
    difference() {
        pipe(outerRadius, thickness, height);
        translate([-outerRadius, -outerRadius, -0.01])
            cube([2*outerRadius, outerRadius, height+0.02], center=false);
    }
}

// ////////////////////////////////////////////////////////////////
// a quarter of a pipe
module quarterPipe(outerRadius, thickness, height) {
    difference() {
        pipe(outerRadius, thickness, height);
        translate([-outerRadius, -outerRadius, -0.01])
            cube([2*outerRadius, outerRadius, height+0.02], center=false);
        translate([-outerRadius, 0, -0.01])
            cube([outerRadius, outerRadius, height+0.02], center=false);
    }
}
```


[User]
Hi, I'm looking to get a parametric OpenSCAD file created for generating flexible motor mounts for FPV quadcopter motors. I've already got one that works, but it's a single file and the code is a bit messy, so I'd like something more modular that's easy to understand and extend. I'm mainly looking at designs for 11xx and 22xx sized motors right now.

Here's what I need:

**Functionality:**

*   The script should generate a single motor mount, with options for how many to generate in a grid.
*   The mount should have a central hole for the motor shaft, four mounting screw holes, and a surrounding flexible material that acts as a vibration dampener.
*   The script should account for different screw head sizes (since there are variations in screw sizes/washer usage), and have configurable inner corners.

**Parameters:**

*   **General:**
    *   `count`: The number of mounts to generate (default 2), arrange in a square grid if multiple
    *   `nozzle_diameter`:  To round up critical dimensions to whole nozzle increments, since they are intended for FDM printers.
    *   `layer_thickness`: For adjusting the height based on a multiple of layer height.
    *   `layer_count`: Number of layers tall the mount should be
    *   `resolution`:  Resolution for circles (low, med, high)
*   **Motor Setup:**
    *   `center_hole_diameter`: Diameter of the center hole (should be configurable to account for the motor's c-clip)
    *   `screw_hole_diameter`: Diameter of the screw holes.
    *    `mounting_pattern_diameter_1`: Diameter between opposing screw holes in a standard pattern
    *   `mounting_pattern_diameter_2`: Diameter between the *other* pair of opposing screw holes.
    *   `screw_head_diameter`: Diameter of the screw heads
*   **Advanced**
    *   `inner_corner_multiplier`: Multiplier that effects the radius of the inner corner.

**Calculations:**
*   `height`: Calculated based on layer thickness and count
*   `motor_mount_max_r`: Calculated from max of mounting_pattern diameters divided by 2
*    `motor_mount_min_r`: Calculated from min of mounting_pattern diameters divided by 2
*    `screw_hole_diameter_rounded`: rounded up to nearest multiple of nozzle_diameter
*   `screw_head_diameter_rounded`: rounded up to nearest multiple of nozzle_diameter
*   `center_hole_diameter_rounded`: rounded up to nearest multiple of nozzle_diameter
*   `inner_corner_d`: Diameter of the inner corner, rounded up to nearest multiple of nozzle_diameter.
*   `corner_offset`: used to create the primary form of the mount

**Modules:**
*   `primary_form()`: creates the main mount shape as a difference of a central cube and four cylinders.
*   `center_cutout()`: creates a cylinder for the center hole.
*   `screws_cutout()`: creates the holes for the screws.
*   `soft_mount()`: Combines the above modules to create a single mount.

**Output**

The script should use these parameters to generate the mount with correct cutouts and rounded values.

The current script uses hull() to generate the rounded edges for the soft mount area - that's important! I'm not sure how else you would do it. I'd like to keep that. I'm also using a function `round_up_to_nearest_multiple` for rounding values for printing purposes.

The script also uses translate() and rotate() for positioning and orienting the shapes.

I don't have a preference of using libraries so it's okay if you use vanilla OpenSCAD functionality only.  I'd really like to see it broken up into logical modules and the calculations done separately.  Hopefully this is enough detail, thanks.

[Assistant]
motor_softmount_generator.scad:
```
/* [General] */

// Two per motor (one on each side of the frame) is recommended.
count = 2; // [1:16]

/* [Slicer Setup] */

// Try to match your printer's nozzle
nozzle_diameter = 0.4;

// Set to the layer thickness you will use in your slicer.
layer_thickness = 0.2;

// Recommended: 2 or 3 depending on layer thickness
layer_count = 2;

/* [Motor Setup] */

// Make sure it is big enough to not bind the motor c-clip.
center_hole_diameter = 7;

// Recommend adding 0.15-0.25 depending on your printer.
screw_hole_diameter = 3.15;

// This is center-to-center distance of two opposing screws
mounting_pattern_diameter_1 = 19.0;

// This is center-to-center distance of the other pair of screws
mounting_pattern_diameter_2 = 16.0;

// Recommend adding 0.25 or more if using washers
screw_head_diameter = 5.75;

/* [Advanced] */

// Resolution of circles (lower renders faster)
resolution = 16; // [16:Low, 32:Med, 64:High]

// Multiplier that effects the radius of the inner corner. 1.0 makes it the same as screw_head_diameter.
inner_corner_multiplier = 1.0;

/* [Hidden] */

$fn = resolution;

// Calculated

height = layer_thickness * layer_count;
motor_mount_max_r = max(mounting_pattern_diameter_1,mounting_pattern_diameter_2)/2;
motor_mount_min_r = min(mounting_pattern_diameter_1,mounting_pattern_diameter_2)/2;

// Round up to ensure no partial lines (based on nozzle_diameter)

function round_up_to_nearest_multiple(val, num) = num * ceil(val/num);
screw_hole_diameter_rounded = round_up_to_nearest_multiple(screw_hole_diameter, nozzle_diameter);
screw_head_diameter_rounded = round_up_to_nearest_multiple(screw_head_diameter, nozzle_diameter);
center_hole_diameter_rounded = round_up_to_nearest_multiple(center_hole_diameter, nozzle_diameter);

inner_corner_d = round_up_to_nearest_multiple((screw_head_diameter * inner_corner_multiplier), nozzle_diameter);
corner_offset = (screw_head_diameter_rounded + inner_corner_d) / 2;

module primary_form() {
  difference() {
    translate([0,0,height/2]) cube([corner_offset*2, corner_offset*2, height], center=true);

    for(v=[[corner_offset,corner_offset,-0.1],
            [corner_offset,-corner_offset,-0.1],
            [-corner_offset,corner_offset,-0.1],
            [-corner_offset,-corner_offset,-0.1]])
      translate(v) cylinder(d=inner_corner_d, h=height+0.2);
  }

  hull() {
    translate([motor_mount_max_r,0,0]) cylinder(d=screw_head_diameter_rounded, h=height);
    translate([-motor_mount_max_r,0,0]) cylinder(d=screw_head_diameter_rounded, h=height);
  }

  hull() {
    translate([0,motor_mount_max_r,0]) cylinder(d=screw_head_diameter_rounded, h=height);
    translate([0,-motor_mount_max_r,0]) cylinder(d=screw_head_diameter_rounded, h=height);
  }
}

module center_cutout() {
  cylinder(d=center_hole_diameter_rounded, h=height*2+0.1, center=true);
}

module screws_cutout() {
  for(i=[0:3]) {
    rotate([0,0,i*90]) {
      hull() {
        translate([motor_mount_max_r,0,0]) cylinder(d=screw_hole_diameter_rounded, h=height*2+0.1, center=true);
        translate([motor_mount_min_r,0,0]) cylinder(d=screw_hole_diameter_rounded, h=height*2+0.1, center=true);
      }
    }
  }
}

module soft_mount() {
  difference() {
    primary_form();
    center_cutout();
    screws_cutout();
  }
}

rows = ceil(sqrt(count));
cols = ceil(sqrt(count));
outside_d = motor_mount_max_r*2 + screw_head_diameter_rounded + 1;
for(i=[0:count-1]) {
  x_offset = floor(i/rows)*(outside_d+1);
  y_offset = floor(i % cols)*(outside_d+1);

  translate([x_offset, y_offset, 0]) soft_mount();
}

/* A few useful presets:
 *   11xx motors:
 *     - center_hole_diameter = 4;
 *     - screw_hole_diameter = 2.25;
 *     - mounting_pattern_diameter_1 = 9.0;
 *     - mounting_pattern_diameter_2 = 9.0;
 *     - screw_head_diameter = 4.25;
 *
 *   2204-2206 motors:
 *     - center_hole_diameter = 7;
 *     - screw_hole_diameter = 3.15;
 *     - mounting_pattern_diameter_1 = 16.0;
 *     - mounting_pattern_diameter_2 = 19.0;
 *     - screw_head_diameter = 5.75;
 */

```


[User]
I'd like to create a customizable 2D origami dragon design using OpenSCAD. It's based on an existing design I found, but I need it to be fully parametric so I can adjust its size and details.

Here's the basic idea: The dragon is defined by a series of outer points that form its overall shape, and inner polygons that are cut out. I need to be able to specify:

*   The maximum size of the longest side of the design, with the shorter side scaling proportionally.
*   The overall height of the model, which is basically the extrusion thickness.
*   The thickness of the outer outline.
*   The thickness of the inner outlines.
*   The radius of the rounded edges on the inner outlines.
*   An option to flip the model horizontally.

The design is defined by coordinates of various points for both the outer edges and the internal cutouts, and these should be scaled based on the given `max_size`. The inner points define a set of polygons for cutouts. I'll provide those coordinates, which are basically a copy-paste from the source. Here's the data:

**Outer Points:**

A1 = [68, 282];
A2 = [699, 497];
A3 = [681, 289];
A4 = [926, 494];
A5 = [1037, 165];
A6 = [1452, 755];
A7 = [1502, 730];
A8 = [1498, 359];
A9 = [1616, 241];
A10 = [1406, 109];
A11 = [1461, 104];
A12 = [1598, 169];
A13 = [1663, 172];
A14 = [1928, 243];
A15 = [1918, 346];
A16 = [1700, 373];
A17 = [1868, 1187];
A18 = [1658, 1579];
A19 = [1601, 1413];
A20 = [1418, 1482];
A21 = [1345, 1484];
A22 = [1144, 1440];
A23 = [1049, 1579];
A24 = [972, 1238];
A25 = [1020, 1075];
A26 = [778, 857];
A27 = [747, 706];

**Inner Points:**

B1 = [1690, 215];
B2 = [1661, 231];
B3 = [1492, 1115];
B4 = [1469, 849];
B5 = [1441, 806];
B6 = [1087, 1156];
B7 = [1213, 1310];
B8 = [721, 603];
B9 = [925, 967];

**Cut Polygons:**

C1 = [A1, A2, B8, A27];
C2 = [A3, A4, B5, B8, A2];
C3 = [B8, A27, A26, B5];
C4 = [A26, B9, B5];
C5 = [B9, A25, B5];
C6 = [A25, B6, B4, B5];
C7 = [A4, A6, B5];
C8 = [A6, A7, B5];
C9 = [A6, A5, A4];
C10 = [A10, A11, A12, A13, A14, B1, B2, A9];
C11 = [B2, B1, A14, A15, A16];
C12 = [A9, A8, A7, B3, A19, A18, A17, A16, B2];
C13 = [B5, B4, A20, A19, B3, A7];
C14 = [B4, B6, B7, A21, A20];
C15 = [A25, A24, A23, A22, B7];
C16 = [B7, A21, A22];

The final output should be a 2D extruded shape. I don't need any libraries for this, just vanilla OpenSCAD. Can you create the OpenSCAD file for this?

[Assistant]
customizable_origami_dragon.scad:
```
/*
 * Customizable Origami - Dragon - https://www.thingiverse.com/thing:2771446
 * by Dennis Hofmann - https://www.thingiverse.com/mightynozzle/about
 * created 2018-01-27
 * version v1.0
 *
 * Changelog
 * --------------
 * v1.0:
 *      - final design
 * --------------
 *
 * This work is licensed under the Creative Commons - Attribution - NonCommercial - ShareAlike license.
 * https://creativecommons.org/licenses/by-nc-sa/3.0/
 */

 // Parameter Section //
//-------------------//

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

// The maximum size of the longest side. The shorter side will be automatically resized in same ratio.
max_size_in_millimeter = 120; //[20:300]

// The height of the model.
model_height_in_millimeter = 5; //[0.2:0.2:100]

// Thickness of the outer outline.
outline_size_in_millimeter = 1.7; //[0.5:0.1:20]

// Thickness of the inner outline.
inline_size_in_millimeter = 0.8; //[0.5:0.1:20]

// Roundiness of the ends of the inner outlines.
inline_edge_radius_in_millimeter = 0.6; //[0.0:0.1:5]

// Flip model
flip_model = "no"; //[yes,no]

/*[hidden]*/
max_size = max_size_in_millimeter;
model_height = model_height_in_millimeter;
$fn=32;

// Outer Points
A1 = [68, 282];
A2 = [699, 497];
A3 = [681, 289];
A4 = [926, 494];
A5 = [1037, 165];
A6 = [1452, 755];
A7 = [1502, 730];
A8 = [1498, 359];
A9 = [1616, 241];
A10 = [1406, 109];
A11 = [1461, 104];
A12 = [1598, 169];
A13 = [1663, 172];
A14 = [1928, 243];
A15 = [1918, 346];
A16 = [1700, 373];
A17 = [1868, 1187];
A18 = [1658, 1579];
A19 = [1601, 1413];
A20 = [1418, 1482];
A21 = [1345, 1484];
A22 = [1144, 1440];
A23 = [1049, 1579];
A24 = [972, 1238];
A25 = [1020, 1075];
A26 = [778, 857];
A27 = [747, 706];

outline = [A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27];

// Inner Points
B1 = [1690, 215];
B2 = [1661, 231];
B3 = [1492, 1115];
B4 = [1469, 849];
B5 = [1441, 806];
B6 = [1087, 1156];
B7 = [1213, 1310];
B8 = [721, 603];
B9 = [925, 967];

// Polygons
C1 = [A1, A2, B8, A27];
C2 = [A3, A4, B5, B8, A2];
C3 = [B8, A27, A26, B5];
C4 = [A26, B9, B5];
C5 = [B9, A25, B5];
C6 = [A25, B6, B4, B5];
C7 = [A4, A6, B5];
C8 = [A6, A7, B5];
C9 = [A6, A5, A4];
C10 = [A10, A11, A12, A13, A14, B1, B2, A9];
C11 = [B2, B1, A14, A15, A16];
C12 = [A9, A8, A7, B3, A19, A18, A17, A16, B2];
C13 = [B5, B4, A20, A19, B3, A7];
C14 = [B4, B6, B7, A21, A20];
C15 = [A25, A24, A23, A22, B7];
C16 = [B7, A21, A22];

cut_polygons = [C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16];

min_x = A1[0];
min_y = A11[1];
max_x = A14[0];
max_y = A18[1];

x_size = max_x - min_x;
y_size = max_y - min_y;

x_factor = max_size / x_size;
y_factor = max_size / y_size;

inline_size = x_size > y_size ? inline_size_in_millimeter / x_factor / 2: inline_size_in_millimeter / y_factor / 2;
inline_edge_radius = x_size > y_size ? inline_edge_radius_in_millimeter / x_factor: inline_edge_radius_in_millimeter / y_factor;
outline_size = x_size > y_size ? outline_size_in_millimeter / x_factor - inline_size: outline_size_in_millimeter / y_factor - inline_size;

 // Program Section //
//-----------------//

if(x_size > y_size) {
    resize(newsize=[max_size, x_factor * y_size, model_height]){
        if(flip_model == "yes") {
            mirror([0,1,0]) {
                rotate([180,180,0]) {
                    create(outline, cut_polygons);
                }
            }
        } else {
            create(outline, cut_polygons);
        }
    }
} else {
    resize(newsize=[y_factor * x_size, max_size, model_height]){
        if(flip_model == "yes") {
            mirror([0,1,0]) {
                rotate([180,180,0]) {
                    create(outline, cut_polygons);
                }
            }
        } else {
            create(outline, cut_polygons);
        }
    }
}

 // Module Section //
//----------------//

module create() {
    linear_extrude(height=1) {
        difference() {
            offset(r = -0) {
                offset(r = +outline_size) {
                    polygon(points = outline, convexity = 10);
                }
            }

            for(cut_polygon = [0:len(cut_polygons)]) {
                offset(r = +inline_edge_radius) {
                    offset(r = -inline_size - inline_edge_radius) {
                        polygon(points = cut_polygons[cut_polygon], convexity = 10);
                    }
                }
            }
        }
    }
}

```


[User]
Hey, I'm looking to design a customizable rock tumbler drum and lid in OpenSCAD. I've got some basic parameters in mind, and I'd like the design to be parametric, so I can easily adjust the dimensions later. I'm thinking of a cylindrical drum with internal fins to help agitate the media and a lid that fits over the top.

Here are the key parameters I'd like to be able to control:

*   **Drum Size:** This should be defined by two values: the height and the radius of the cylinder. I want to be able to specify this as a vector (e.g., `[height, radius]`).
*   **Wall Thickness:** The thickness of the drum's walls.
*   **Number of Fins:** The number of agitator fins inside the drum.
*   **Lid Tolerance:**  A small tolerance value that adds to the inner radius of the lid, allowing me to make the fit looser or tighter.
*   **$fn:** This should be an adjustable variable to control the smoothness of the curves. I usually go with 60 for a decent balance between smooth curves and acceptable file size.

The fins should be evenly spaced around the inside of the drum and should protrude a certain amount into the interior. I was thinking they could be a fixed percentage of the drum's radius – something like 5% of the radius for the fin width and 10% of the radius for how far they stick out. I'd also want the lid to be a bit taller than the top of the drum, maybe 10% of the drum height, to ensure good coverage.

I need the design to be parametric so that if I change any one of these dimensions, the entire thing will update accordingly. Could you please generate the OpenSCAD files for this design? I don't need any external libraries or anything fancy, just plain vanilla OpenSCAD.

[Assistant]
Drum_Design_for_Tumbler.scad:
```
DrumSize = [100,50];
WallThick = 1.6;
finCnt = 8;
lidTol = 0.2;
$fn=60;

//Body of drum
module Drum(){
    difference(){
        cylinder(h=DrumSize[0],r=DrumSize[1],center=true);
        translate([0,0,WallThick])cylinder(h=DrumSize[0],r=DrumSize[1]-(WallThick),center=true);
}}

module fin(){
    union(){
        for (i = [0:finCnt-1]) {
            translate([sin(360*i/finCnt)*(DrumSize[1]-WallThick)*.95, cos(360*i/finCnt)*(DrumSize[1]-WallThick)*.95, 0 ])
            rotate([0,0,-(360/finCnt)*i])cube([DrumSize[1]*.05,DrumSize[1]*.1,DrumSize[0]],center=true);
}}}

module lid(){
    difference(){
        cylinder(h=(DrumSize[0]*.1)+WallThick,r=DrumSize[1]+(WallThick+lidTol),center=true);
        translate([0,0,WallThick])cylinder(h=(DrumSize[0]*.1)+WallThick,r=DrumSize[1]+lidTol,center=true);
}}
module CompDrum(){
    union(){
        Drum();
        fin();
    }
}
render(convexity=20){
    CompDrum();
    translate([DrumSize[0]+20,0,0])lid();
}
```


[User]
I'm looking to generate a customizable origami gorilla design using OpenSCAD. I've seen some existing examples, and I want to be able to control certain aspects of it parametrically.

Specifically, I need these customizable parameters:

*   **`max_size_in_millimeter`**:  The maximum dimension of the gorilla (either width or height), with the other dimension scaling proportionally. I need to be able to set this within a reasonable range, like maybe 20 to 300mm.

*   **`model_height_in_millimeter`**: The overall thickness or height of the 2D design.  This should be adjustable from, say, 0.2 to 100 mm.

*   **`outline_size_in_millimeter`**: The thickness of the outer border or outline of the design. I'd like to control this from 0.5 to 20 mm.

*   **`inline_size_in_millimeter`**:  The thickness of the inner lines that define the origami folds. This should also be controllable, say from 0.5 to 20mm.

*  **`inline_edge_radius_in_millimeter`**: To control the roundness of the ends of the inner lines, I need to be able to adjust this. A range from 0 to 5 mm should work.

*   **`flip_model`**:  A simple yes/no parameter that lets me mirror the design along the vertical axis.

The gorilla's shape is defined by a series of points for an outer perimeter and a list of interior polygons that get cut out. The points and polygons are given below as constants, for your reference. You can hardcode the numbers:

```
// Outer Points
A1 = [138, 366];
A2 = [272, 252];
A3 = [244, 236];
A4 = [339, 190];
A5 = [349, 213];
A6 = [536, 70];
A7 = [598, 89];
A8 = [678, 42];
A9 = [1038, 189];
A10 = [1514, 1023];
A11 = [1598, 1029];
A12 = [1626, 972];
A13 = [2065, 1024];
A14 = [2397, 1327];
A15 = [2366, 1864];
A16 = [2042, 2479];
A17 = [1573, 2427];
A18 = [1866, 2202];
A19 = [1705, 1941];
A20 = [1532, 2063];
A21 = [1314, 2147];
A22 = [1503, 2006];
A23 = [1434, 1848];
A24 = [1107, 1883];
A25 = [984, 1933];
A26 = [811, 2374];
A27 = [995, 2389];
A28 = [702, 2637];
A29 = [78, 2311];
A30 = [216, 1992];
A31 = [58, 1926];
A32 = [540, 985];
A33 = [623, 831];
A34 = [417, 795];

outline = [A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34];

// Inner Points
B1 = [412, 320];
B2 = [868, 478];
B3 = [721, 120];
B4 = [1623, 1032];
B5 = [1620, 1184];
B6 = [1613, 1304];
B7 = [1601, 1770];
B8 = [1637, 1832];
B9 = [635, 1004];
B10 = [621, 1040];
B11 = [706, 847];

// Polygons
C1 = [A7, A8, B3];
C2 = [A8, A9, B3];
C3 = [A6, B2, B11, A33, A34, B1, A5];
C4 = [A5, A4, A3, A2, B1];
C5 = [A1, A2, B1, A34];
C6 = [A6, A7, B3, A9, B2];
C7 = [A32, B9, B11, A33];
C8 = [A31, A30, B10, B9, A32];
C9 = [A9, A24, B10, B9, B11, B2];
C10 = [A29, A30, B10, A24, A25];
C11 = [A29, A25, A26];
C12 = [A29, A28, A27, A26];
C13 = [A21, A22, A20];
C14 = [A22, A23, B8, A19, A20];
C15 = [A24, B6, B7, B8, A23];
C16 = [A10, A11, B4, B5];
C17 = [A11, A12, B4];
C18 = [A9, A10, B5, B6, A24];
C19 = [A12, A13, A14, B7, B6, B5, B4];
C20 = [A14, A15, B7];
C21 = [B7, B8, A19, A18, A16, A15];
C22 = [A18, A17, A16];

cut_polygons = [C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16, C17, C18, C19, C20, C21, C22];
```
I need the final design to be created by:
1. Computing the smallest bounding box around the provided points (`outline`).
2. Scaling the whole design proportionally so that its longest side matches `max_size_in_millimeter`, and adjusting `inline_size` and `outline_size` relative to the scaling.
3. Generating the outer shape using an `offset()` of the scaled outline.
4. Generating the inner shapes using `offset()` operations to create the inner outlines.
5. Cutting out the inner shapes from the outer shape using `difference()`.
6. Extruding the final 2D design to the given `model_height_in_millimeter`.
7. Mirroring the result if `flip_model` is set to `"yes"`.
I do not need any external libraries to do this. Please provide all the code in a single SCAD file.

[Assistant]
customizable_origami_gorilla.scad:
```
/*
 * Customizable Origami - Gorilla - https://www.thingiverse.com/thing:2772576
 * by Dennis Hofmann - https://www.thingiverse.com/mightynozzle/about
 * created 2018-01-28
 * version v1.0
 *
 * Changelog
 * --------------
 * v1.0:
 *      - final design
 * --------------
 *
 * This work is licensed under the Creative Commons - Attribution - NonCommercial - ShareAlike license.
 * https://creativecommons.org/licenses/by-nc-sa/3.0/
 */

 // Parameter Section //
//-------------------//

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

// The maximum size of the longest side. The shorter side will be automatically resized in same ratio.
max_size_in_millimeter = 120; //[20:300]

// The height of the model.
model_height_in_millimeter = 5; //[0.2:0.2:100]

// Thickness of the outer outline.
outline_size_in_millimeter = 1.7; //[0.5:0.1:20]

// Thickness of the inner outline.
inline_size_in_millimeter = 0.8; //[0.5:0.1:20]

// Roundiness of the ends of the inner outlines.
inline_edge_radius_in_millimeter = 0.9; //[0.0:0.1:5]

// Flip model
flip_model = "no"; //[yes,no]

/*[hidden]*/
max_size = max_size_in_millimeter;
model_height = model_height_in_millimeter;
$fn=32;

// Outer Points
A1 = [138, 366];
A2 = [272, 252];
A3 = [244, 236];
A4 = [339, 190];
A5 = [349, 213];
A6 = [536, 70];
A7 = [598, 89];
A8 = [678, 42];
A9 = [1038, 189];
A10 = [1514, 1023];
A11 = [1598, 1029];
A12 = [1626, 972];
A13 = [2065, 1024];
A14 = [2397, 1327];
A15 = [2366, 1864];
A16 = [2042, 2479];
A17 = [1573, 2427];
A18 = [1866, 2202];
A19 = [1705, 1941];
A20 = [1532, 2063];
A21 = [1314, 2147];
A22 = [1503, 2006];
A23 = [1434, 1848];
A24 = [1107, 1883];
A25 = [984, 1933];
A26 = [811, 2374];
A27 = [995, 2389];
A28 = [702, 2637];
A29 = [78, 2311];
A30 = [216, 1992];
A31 = [58, 1926];
A32 = [540, 985];
A33 = [623, 831];
A34 = [417, 795];

outline = [A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34];

// Inner Points
B1 = [412, 320];
B2 = [868, 478];
B3 = [721, 120];
B4 = [1623, 1032];
B5 = [1620, 1184];
B6 = [1613, 1304];
B7 = [1601, 1770];
B8 = [1637, 1832];
B9 = [635, 1004];
B10 = [621, 1040];
B11 = [706, 847];

// Polygons
C1 = [A7, A8, B3];
C2 = [A8, A9, B3];
C3 = [A6, B2, B11, A33, A34, B1, A5];
C4 = [A5, A4, A3, A2, B1];
C5 = [A1, A2, B1, A34];
C6 = [A6, A7, B3, A9, B2];
C7 = [A32, B9, B11, A33];
C8 = [A31, A30, B10, B9, A32];
C9 = [A9, A24, B10, B9, B11, B2];
C10 = [A29, A30, B10, A24, A25];
C11 = [A29, A25, A26];
C12 = [A29, A28, A27, A26];
C13 = [A21, A22, A20];
C14 = [A22, A23, B8, A19, A20];
C15 = [A24, B6, B7, B8, A23];
C16 = [A10, A11, B4, B5];
C17 = [A11, A12, B4];
C18 = [A9, A10, B5, B6, A24];
C19 = [A12, A13, A14, B7, B6, B5, B4];
C20 = [A14, A15, B7];
C21 = [B7, B8, A19, A18, A16, A15];
C22 = [A18, A17, A16];

cut_polygons = [C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16, C17, C18, C19, C20, C21, C22];

min_x = A31[0];
min_y = A8[1];
max_x = A14[0];
max_y = A28[1];

x_size = max_x - min_x;
y_size = max_y - min_y;

x_factor = max_size / x_size;
y_factor = max_size / y_size;

inline_size = x_size > y_size ? inline_size_in_millimeter / x_factor / 2: inline_size_in_millimeter / y_factor / 2;
inline_edge_radius = x_size > y_size ? inline_edge_radius_in_millimeter / x_factor: inline_edge_radius_in_millimeter / y_factor;
outline_size = x_size > y_size ? outline_size_in_millimeter / x_factor - inline_size: outline_size_in_millimeter / y_factor - inline_size;

 // Program Section //
//-----------------//

if(x_size > y_size) {
    resize(newsize=[max_size, x_factor * y_size, model_height]){
        if(flip_model == "yes") {
            mirror([0,1,0]) {
                rotate([180,180,0]) {
                    create(outline, cut_polygons);
                }
            }
        } else {
            create(outline, cut_polygons);
        }
    }
} else {
    resize(newsize=[y_factor * x_size, max_size, model_height]){
        if(flip_model == "yes") {
            mirror([0,1,0]) {
                rotate([180,180,0]) {
                    create(outline, cut_polygons);
                }
            }
        } else {
            create(outline, cut_polygons);
        }
    }
}

 // Module Section //
//----------------//

module create() {
    linear_extrude(height=1) {
        difference() {
            offset(r = -0) {
                offset(r = +outline_size) {
                    polygon(points = outline, convexity = 10);
                }
            }

            for(cut_polygon = [0:len(cut_polygons)]) {
                offset(r = +inline_edge_radius) {
                    offset(r = -inline_size - inline_edge_radius) {
                        polygon(points = cut_polygons[cut_polygon], convexity = 10);
                    }
                }
            }
        }
    }
}

```


[User]
I'm looking to create a printable parametric bearing, specifically a 608-style bearing that can be printed in vase mode for smoother operation. I have some specific ideas for how I'd like to structure it. I want a main file to control parameters and call modules from other files. The bearing will consist of an outer race, an inner race, rollers, and retainers. I'm envisioning a design where the races have lips, the rollers have a lip on their top to match, and the retainers are separate pieces.

Here are the specific parameters that I think will be helpful:

*   **Overall Dimensions:**
    *   `outerRadius`: The overall radius of the bearing (e.g., 11mm)
    *   `innerRadius`: The radius of the inner hole (e.g., 4mm)
    *   `width`: The overall width of the bearing (e.g., 7mm)

*   **Wall Thickness:**
    *   `wallThickness`: The thickness of the walls for the races and rollers. (e.g., 0.45mm)

*   **Tolerances:**
    *   `tolerance`: Extra space between the rollers and races (e.g., 0.1mm)

*   **Race Lips (Outer & Inner):**
    *   `raceUpperLipWidth`: Width of the lip on the top of races
    *   `raceUpperLipHeight`: Height of the lip on the top of races, using a 60-degree angle for calculation.
    *   `raceLowerLipWidth`: Width of the lip on the bottom of races
    *   `raceLowerLipHeight`: Height of the lip on the bottom of races, using a 60-degree angle for calculation.

*   **Roller Lips:**
    *   `rollerRaceLipWidth`: Width of the lip at the top of the roller
    *   `rollerRaceLipHeight`: Height of the lip at the top of the roller using a 45-degree angle for calculation.

*   **Retainer:**
    *   `retainerBaseHeight`: Base height of the retainer clips
    *   `retainerBottomHeight`: Bottom height of the retainers
    *   `retainerUpperHeight`: Upper height of the retainers.

*  **Other important parameters:**
    * `numRollers`: number of rollers to place in the bearing
    * `rollerCentreDistance`: The radial distance between the centre of the bearing and the center of a roller.
    * `rollerRaceRadius`: The radius of the roller including the race lips
    * `rollerInnerRadius`: The radius of the roller excluding the race lips

I would also like to have a way to select which part to render.  I would like the option to render the `all` parts at once, or just the `outer` race, or just the `inner` race, or just `one` roller, or just `one` retainer or a `section` view.

Finally, I need the ability to set the `$fn` variable for rendering quality.

Can you generate a parametric OpenSCAD file, or files, that will do this for me? I think I'd also like the retainers to be separate top and bottom pieces. I'm assuming you'll be creating all the necessary code from scratch.

[Assistant]
vase_bearing.scad:
```
/* [Basic] */

// Which part would you like to see?
part = "section"; // [all:Everything, outer: Outer Race, inner:Inner Race, roller:One Roller, retainer: One Retainer, section:Cross section]

// Render quality
$fn=200; // [20:Fast viewing, 200:Quality printing]

// Outer Radius
outerRadius = 11;

// Inner Radius
innerRadius = 4;

// Thickness of walls
wallThickness = 0.45;

// Width
width = 7;

// Tolerance (extra space between rollers and races)
tolerance = 0.1;

/* [Advanced: Race Lip] */

raceUpperLipWidth = 1.2;

raceUpperLipHeight = raceUpperLipWidth * tan(60);

raceLowerLipWidth = 0.8;

raceLowerLipHeight = raceLowerLipWidth * tan(60);

/* [Advanced: Outer Race] */

outerRaceRadius = outerRadius - wallThickness;

/* [Advanced: Inner Race] */

innerRaceRadius = innerRadius + wallThickness;

/* [Advanced: Rollers] */

rollerRaceLipWidth = 1.4;

rollerRaceLipHeight = rollerRaceLipWidth * tan(45);

rollerRaceRadius = (outerRaceRadius - innerRaceRadius)/2 - tolerance;

rollerCentreDistance = innerRaceRadius + tolerance + rollerRaceRadius;

rollerBaseHeight = raceLowerLipHeight + tolerance;

rollerInnerRadius = rollerRaceRadius - rollerRaceLipWidth - wallThickness;

rollerMidsectionHeight = width - raceUpperLipHeight - raceLowerLipHeight - tolerance*2;

rollerTopHeight = rollerBaseHeight + rollerMidsectionHeight + rollerRaceLipHeight;

rollerHoleRadius = rollerRaceRadius - rollerRaceLipWidth - wallThickness;

// Number of rollers
numRollers = floor(PI * rollerCentreDistance * 2 / (rollerRaceRadius*2 + tolerance * 4));

echo (numRollers=numRollers);

/* [Advanced: Retainer] */

retainerBaseHeight = 0.4;

retainerBottomHeight = rollerBaseHeight - retainerBaseHeight - tolerance*2;

retainerUpperHeight = rollerTopHeight + retainerBaseHeight + tolerance*2;

//All();
//Section();
//Outer();
//Inner();
//Roller();
//Rollers();
//Retainer();

module Ring(height, lowerRadius, upperRadius, thickness){
    difference(){
        cylinder(height, lowerRadius, upperRadius);

        translate([0,0,-0.01])
        cylinder(height+0.02, lowerRadius - thickness, upperRadius - thickness);
    }
}

module OuterHalf(raceLipHeight, raceLipWidth){
    partWidth = width - raceUpperLipHeight - raceLowerLipHeight;

    //mid section
    translate([0, 0, raceLipHeight])
    Ring(partWidth, outerRadius, outerRadius, wallThickness);

    //lip
    Ring(raceLipHeight, outerRadius - raceLipWidth, outerRadius, wallThickness);
 }

module Outer(){
    difference(){
        union(){
            OuterHalf(raceLowerLipHeight, raceLowerLipWidth);

            mirror([0,0,1])
            translate([0,0,-width])
            OuterHalf(raceUpperLipHeight, raceUpperLipWidth);
        }

        // hole to slide in roller
        translate([rollerCentreDistance,0,width/2])
        cylinder(width, rollerRaceRadius, rollerRaceRadius);

    }

    // bottom
    Ring(wallThickness, outerRadius - raceLowerLipWidth, outerRadius - raceLowerLipWidth, wallThickness*3);
}

module InnerHalf(raceLipHeight, raceLipWidth){
    partWidth = width - raceUpperLipHeight - raceLowerLipHeight;

    //mid section
    translate([0,0,raceLipHeight])
    Ring(partWidth, innerRaceRadius, innerRaceRadius, wallThickness);

    //lip
    Ring(raceLipHeight, innerRaceRadius + raceLipWidth, innerRaceRadius, wallThickness);
}

module Inner(){
    difference(){
        union(){
            InnerHalf(raceLowerLipHeight, raceLowerLipWidth);

            mirror([0,0,1])
            translate([0,0,-width])
            InnerHalf(raceUpperLipHeight, raceUpperLipWidth);
        }

        // hole to slide in roller
        translate([rollerCentreDistance,0,width/2])
        cylinder(width, rollerRaceRadius, rollerRaceRadius);
    }

    // bottom
    Ring(wallThickness, innerRadius + raceLowerLipWidth, innerRadius + raceLowerLipWidth, raceLowerLipWidth);
}

module Roller(){
    //lip
    translate([0,0,rollerMidsectionHeight])
    difference(){
        cylinder(rollerRaceLipHeight, rollerRaceRadius, rollerRaceRadius -rollerRaceLipWidth);

        translate([0,0,-0.01])
        cylinder(rollerRaceLipHeight+0.02, rollerRaceRadius - wallThickness, rollerRaceRadius -rollerRaceLipWidth - wallThickness);
    }

    //mid section
    difference(){
        cylinder(rollerMidsectionHeight, rollerRaceRadius, rollerRaceRadius);

        translate([0,0,-0.01])
        cylinder(rollerMidsectionHeight+0.02, rollerRaceRadius - wallThickness, rollerRaceRadius - wallThickness);
    }

    //bottom
    Ring(wallThickness, rollerRaceRadius, rollerRaceRadius, rollerRaceRadius - rollerHoleRadius);
}

module Rollers(){
    translate([0,0,rollerBaseHeight])
    for (angle=[0:360/numRollers:360])
        rotate([0,0,angle])
        translate([rollerCentreDistance,0,0])
        Roller();
}

module RetainerClip(){
    clipWidth = wallThickness * 3;
    midClipHeight = (rollerRaceRadius-wallThickness)/cos(45)/2.2;

    //mid
    translate([0,0,retainerBaseHeight+wallThickness + tolerance*2])
    rotate([0,-45,0])
    translate([0,0,midClipHeight/2])
    cube([clipWidth,clipWidth,midClipHeight], center=true);

    baseWidth = wallThickness * 3;
    baseLength = 2*sqrt(pow(rollerHoleRadius -tolerance*3, 2) - pow(baseWidth/2, 2));
    baseHeight = wallThickness*3 + tolerance*2;
    // base
    translate([0,0,retainerBaseHeight+baseHeight/2])
    cube([baseWidth, baseLength, baseHeight], center=true);
}

module Retainer(){
    Ring(retainerBaseHeight, rollerCentreDistance+0.8, rollerCentreDistance+0.8, 1.6);

    for (angle=[0:360/numRollers:360])
        rotate([0,0,angle])
        translate([rollerCentreDistance,0,0])
        RetainerClip();
}

module All(){
    Outer();
    Inner();
    Rollers();
    translate([0,0,retainerBottomHeight])
    Retainer();

    translate([0,0,retainerUpperHeight])
    mirror([0,0,1])
    Retainer(isTop=false);
}

module Section(){
    difference(){
        All();

        translate([0,-500,-0.01])
        cube([500,500,500]);
    }
}

module PrintPart(){
    if (part == "all") {
		All();
	} else if (part == "outer") {
		Outer();
	}else if (part == "inner") {
		Inner();
    }else if (part == "roller") {
		Roller();
    }else if (part == "retainer") {
		Retainer();
    }else if (part == "section") {
		Section();
	}
}

PrintPart();

```


[User]
I'd like to create a customizable circle template for woodworking, specifically designed to work with a router and flush trim bits or plate bushings. The key feature is that I need to be able to choose if it's an *inner* or *outer* template.

Here's a breakdown of the features and parameters I need to control:

*   **Template Type:** A boolean option to select whether the template is for cutting an inner circle (where the template is outside the cut) or an outer circle (where the template is inside the cut).
*   **Circle Diameter:** The target diameter of the circle, specifiable in either inches or centimeters.
*   **Pen Offset:** A small offset to account for the pen when making the template on a CNC.
*   **Flange:** A "flange" (the actual circle guide part) with configurable height and width.
*   **Surround:** A wider "surround" that extends beyond the flange, also with configurable height and width. This part will likely have screw holes.
*   **Fillet:** A fillet where the flange and surround meet, with a configurable radius.
*   **Text:** Inset text indicating the diameter of the circle.
*   **Registration Marks:** Registration marks every 45 degrees and smaller marks every 15 degrees, scaled so they don't interfere with the mounting holes.
*   **Mounting Holes:** Screw mounting holes using either #8 imperial screws or M4 metric screws; 4 equally-spaced mounting holes. I want to be able to select which screw type to use and for there not to be holes if I select "none."

I’d like to be able to specify all of these dimensions in millimeters.

I think that's everything. I'd like a single OpenSCAD file, not multiple files, and I'm not planning to use any external libraries.

[Assistant]
circle_template.scad:
```
/*
 * Configurable Circle Template
 *
 * by 4iqvmk, 2018-02-03
 * https://www.thingiverse.com/4iqvmk/about
 *
 * Licenced under Creative Commons Attribution - Share Alike 4.0
 *
 * version 1.00 - initial version
 * version 1.01 - added screw holes
 * version 1.02 - added diameter text
 * version 1.03 - added fillet to flange
*/

/* [Hidden] */

inch2mm = 0.3048/12*1000;
mm2inch = 12/0.3048/1000;
$fn=360;

/* [Parameters] */

// True = inside circle, False = outside circle
isInner = 1;  // [1:true, 0:false]

circleDiam = 6; // [0.5:0.125:30]

circleUnits = 0; // [0:"inch", 1:"cm"]

// mm
penDiam = 0.0; // [0:0.1:1]

// mm
flangeHeight = 10; // [0:5:30]

// mm
flangeWidth = 5; // [0:1:10]

// mm
surroundHeight = 3; // [0:1:10]

// mm
surroundWidth = 20; // [0:5:30]

// mm
filletRadius = 4; // [0:1:10]

// mm
textSize = 7; // [0:1:20]

// mm
regMarkScale = 4; // [0:0.5:10]

screwType = 1; // [0:'None', 1:'#8', 2:'M4']

/////////////////////////////////////////////////////////////////////////////
// Calculated values...
/////////////////////////////////////////////////////////////////////////////

maxHeight = max(surroundHeight, flangeHeight);

if (isInner) {

    if (circleUnits == 0) {
        innerTemplate(circleDiam*inch2mm, str(circleDiam, " in"));
    } else {
        innerTemplate(circleDiam*10, str(circleDiam, " cm"));
    }

} else {
    if (circleUnits == 0) {
        outerTemplate(circleDiam*inch2mm, str(circleDiam, " in"));
    } else {
        outerTemplate(circleDiam*10, str(circleDiam, " cm"));
    }
}

module ring(inner, outer, height, insideFn, outsideFn) {
    difference() {

        cylinder(h=height, r=outer, $fn=outsideFn);
        cylinder(h=4*height, r=inner, center=true, $fn=insideFn);

    }
}

module regMarks(num, sc, radius) {
    // add registration marks
    for (step = [0:num-1]) {
        rotate( a = step*(360/num), v=[0, 0, 1]) {
            translate ( [radius, 0, 0] ) {
                scale([1*sc,0.5*sc,1]) {
                    rotate([0,0,45])
                        cube( [regMarkScale*2/sqrt(2), regMarkScale*2/sqrt(2), maxHeight*4], center=true);
                }
            }
        }
    }
}

module majorRegMarks(radius) {
    regMarks(8, 1, radius);
}

module minorRegMarks(radius) {
    regMarks(24, 0.5, radius);
}

module mountingHoles(num, screwType, offsetRadius) {

    // add registration marks
    for (step = [0:num-1]) {
        rotate( a = step*(360/num), v=[0, 0, 1]) {
            translate ( [offsetRadius, 0, 0] ) {

                if (screwType == 1) {
                    // #8
                    screwDiam = 4.1656;
                    cylinder(h=maxHeight*4, r=screwDiam/2, center=true, $fn=20, $fa=10);
                    translate ( [0,0, surroundHeight] ) {
                        cylinder(h=maxHeight*4, r=screwDiam, $fn=20, $fa=10);
                    }
                } else {
                    // M4
                    screwDiam = 4;
                    cylinder(h=maxHeight*4, r=screwDiam/2, center=true, $fn=20, $fa=10);
                    translate ( [0,0, surroundHeight] ) {
                        cylinder(h=maxHeight*4, r=screwDiam, $fn=20, $fa=10);
                    }
                }

            }
        }
    }
}

module addText(textSize, textStr, offsetRadius) {

    // add registration marks
    rotate( a = 22.5, v=[0, 0, 1]) {
        translate ( [offsetRadius, 0, surroundHeight-0.5] ) {
            linear_extrude(height = maxHeight*4) {
                rotate( a = -90, v=[0, 0, 1]) {
                    text(text = textStr, size = textSize, halign="center", valign="center", $fn=20);
                }
            }
        }
    }
}

module inFillet(height, radius, outerRadius) {
    difference() {
        ring(radius - filletRadius, (radius + outerRadius)/2 , filletRadius+height, 60, 60);

        translate([0, 0, filletRadius+height]) {
            torus(filletRadius, radius - filletRadius);
        }
    }
}

module outFillet(height, radius, innerRadius) {
    difference() {
        ring((radius+innerRadius)/2, radius + filletRadius, filletRadius+height, 60, 60);

        translate([0, 0, filletRadius+height]) {
            torus(filletRadius, radius + filletRadius);
        }
    }
}

// draw a torus about Z centered on the XY plane
module torus(radius, sweepRad) {
    rotate_extrude(convexity = 2, $fn = 60, $fa=5, $fs = 1) {
        translate([sweepRad, 0, 0]) {
            circle(radius, $fn = 20, $fa=20, $fs = 1);
        }
    }
}

module innerTemplate(circleDiamMM, textStr) {

    flangeInnerRadius = circleDiamMM/2 + penDiam/2;
    flangeOuterRadius = flangeInnerRadius + flangeWidth;

    surroundInnerRadius = flangeInnerRadius;
    surroundOuterRadius = flangeOuterRadius+surroundWidth;

    holeRadius = (flangeOuterRadius+surroundOuterRadius-regMarkScale)/2;
    markRadius = surroundOuterRadius;

    makeTemplate(flangeInnerRadius, flangeOuterRadius, surroundInnerRadius, surroundOuterRadius, holeRadius, markRadius, textStr);
}

module outerTemplate(circleDiamMM, textStr) {

    flangeOuterRadius = circleDiamMM/2 - penDiam/2;
    flangeInnerRadius = flangeOuterRadius - flangeWidth;

    surroundOuterRadius = flangeOuterRadius;
    surroundInnerRadius = flangeInnerRadius - surroundWidth;

    holeRadius = (flangeInnerRadius+surroundInnerRadius+regMarkScale)/2;
    markRadius = surroundInnerRadius;

    makeTemplate(flangeInnerRadius, flangeOuterRadius, surroundInnerRadius, surroundOuterRadius, holeRadius, markRadius, textStr);
}

module makeTemplate(flangeInnerRadius, flangeOuterRadius,
        surroundInnerRadius, surroundOuterRadius,
        holeRadius, markRadius, textStr) {

    // circle template
    difference() {
        // flange and surround
        union() {
            if (isInner) {
                ring(flangeInnerRadius, flangeOuterRadius, flangeHeight, 360, 60);
                ring(surroundInnerRadius, surroundOuterRadius, surroundHeight, 360, 60);
                outFillet(surroundHeight, flangeOuterRadius, surroundInnerRadius);
            } else {
                ring(flangeInnerRadius, flangeOuterRadius, flangeHeight, 60, 360);
                ring(surroundInnerRadius, surroundOuterRadius, surroundHeight, 60, 360);
                inFillet(surroundHeight, flangeInnerRadius, surroundOuterRadius);
            }

        }

        union() {
            majorRegMarks(markRadius);
            minorRegMarks(markRadius);
            mountingHoles(4, screwType, holeRadius, maxHeight);
            addText(textSize, textStr, holeRadius);
        }
    }
}

```


[User]
I'd like to create a customizable 2D origami cat design using OpenSCAD. I've seen an existing design and I like the overall structure, but I need it to be fully parametric, and I want to generate it from scratch.

Here are the requirements:

**Overall Structure:** The design is essentially a 2D shape of a cat created using an outer outline and inner cut-out shapes. The cat is defined by a set of coordinates that form the outer outline and a series of polygons that define the cut-out sections.

**Parameters:**
   *  `max_size_in_millimeter`:  The maximum dimension of the longest side of the cat design, which will determine the overall size while maintaining the aspect ratio.  It should allow a range of values, say 20 to 300mm.
   *  `model_height_in_millimeter`: The extrusion height of the 2D design, for example, between 0.2 to 100mm.
   *  `outline_size_in_millimeter`: The thickness of the outer border/outline of the cat. Should be adjustable, perhaps from 0.5 to 20mm in increments of 0.1mm.
   *  `inline_size_in_millimeter`: The thickness of the lines for the inner cut-outs, adjustable similar to the outline, with a range of 0.5 to 20mm in 0.1mm increments.
   *  `inline_edge_radius_in_millimeter`: The radius of the rounding on the edges of the inner cut-outs. The range should be from 0 to 5mm in 0.1mm increments.
   * `flip_model`: A boolean to flip the model along the y-axis (mirroring the design). Should allow for "yes" or "no" options.

**Geometry:**
   * The outer outline of the cat is defined by the following coordinates: A1 = [68, 207], A2 = [78, 167], A3 = [109, 164], A4 = [260, 305], A5 = [415, 507], A6 = [565, 759], A7 = [639, 940], A8 = [703, 849], A9 = [739, 833], A10 = [1187, 803], A11 = [1229, 835], A12 = [1339, 851], A13 = [1589, 444], A14 = [1623, 437], A15 = [1643, 455], A16 = [1632, 543], A17 = [1864, 642], A18 = [1907, 734], A19 = [1890, 763], A20 = [1706, 831], A21 = [1539, 1272], A22 = [1494, 1390], A23 = [1468, 1446], A24 = [1488, 1489], A25 = [1563, 1525], A26 = [1578, 1561], A27 = [1555, 1590], A28 = [1602, 1611], A29 = [1612, 1641], A30 = [1587, 1664], A31 = [1454, 1683], A32 = [1410, 1652], A33 = [1257, 1395], A34 = [1151, 1183], A35 = [786, 1415], A36 = [764, 1464], A37 = [604, 1595], A38 = [634, 1672], A39 = [631, 1701], A40 = [596, 1711], A41 = [538, 1696], A42 = [511, 1626], A43 = [480, 1595], A44 = [441, 1523], A45 = [435, 1463], A46 = [525, 1383], A47 = [534, 1334], A48 = [506, 1110], A49 = [445, 852], A50 = [376, 654].

   * The inner cut-out polygons are defined by these coordinates: B1 = [1201, 846], B2 = [637, 1381], B3 = [1445, 716], B4 = [1360, 840], B5 = [1591, 849], B6 = [1493, 644], B7 = [1469, 684], B8 = [1601, 607], B9 = [1611, 482], B10 = [1543, 563], B11 = [1634, 488], B12 = [1514, 1588].
   *  The cut-out polygons are as follows: C1 = [B6, B10, B9, B11, A16, B8], C2 = [A16, A17, A18, A19, A20, B5, B7, B6, B8], C3 = [B7, B3, A21, A20, B5], C4 = [B3, B4, A22, A21], C5 = [B1, A11, A12, B4, A22, A33, A34], C6 = [A33, A32, A31, A30, A29, A28, A27, B12, A24, A23, A22], C7 = [A24, A25, A26, A27, B12], C8 = [A36, B2, A43, A42, A41, A40, A39, A38, A37], C9 = [B2, A47, A46, A45, A44, A43], C10 = [A36, A35, A9, A8, A7, A48, A47, B2], C11 = [A10, B1, A11], C12 = [A10, B1, A34, A35, A9], C13 = [A12, A13, A14, A15, B11, B9, B10, B6, B7, B3, B4], C14 = [A7, A6, A5, A49, A48], C15 = [A49, A5, A4, A3, A2, A1, A50].

**Functionality:**

   1.  The design should first calculate the bounding box of the outer outline to determine the `x_size` and `y_size`.
   2.  It should then determine scaling factors based on `max_size_in_millimeter` and the calculated bounding box dimensions.
   3.  The `outline_size`, `inline_size`, and `inline_edge_radius` values should be scaled using the calculated factors. The scaling should account for the fact that inner cut-outs should get smaller when the design is scaled down.
   4.  The main part of the design will be a linear extrusion of a polygon.
   5.  The outer outline is generated from the 'outline' points with a specified `outline_size`.
   6.  The inner cut-outs are created by subtracting the polygons from the 'cut_polygons' list. The inner cut-outs should have rounded edges based on `inline_edge_radius` and be of thickness defined by `inline_size`.
   7.  If `flip_model` is "yes", the design should be mirrored along the y-axis *and* rotated 180 degrees in both x and y directions to preserve the design's orientation relative to the origin.
   8. It would be great if this could be generated from scratch.

I think that covers everything. I'm looking forward to the OpenSCAD file(s)!

[Assistant]
customizable_origami_cat.scad:
```
/*
 * Customizable Origami - Cat - https://www.thingiverse.com/thing:2773384
 * by Dennis Hofmann - https://www.thingiverse.com/mightynozzle/about
 * created 2018-01-28
 * version v1.0
 *
 * Changelog
 * --------------
 * v1.0:
 *      - final design
 * --------------
 *
 * This work is licensed under the Creative Commons - Attribution - NonCommercial - ShareAlike license.
 * https://creativecommons.org/licenses/by-nc-sa/3.0/
 */

 // Parameter Section //
//-------------------//

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

// The maximum size of the longest side. The shorter side will be automatically resized in same ratio.
max_size_in_millimeter = 120; //[20:300]

// The height of the model.
model_height_in_millimeter = 5; //[0.2:0.2:100]

// Thickness of the outer outline.
outline_size_in_millimeter = 1.4; //[0.5:0.1:20]

// Thickness of the inner outline.
inline_size_in_millimeter = 0.7; //[0.5:0.1:20]

// Roundiness of the ends of the inner outlines.
inline_edge_radius_in_millimeter = 0.3; //[0.0:0.1:5]

// Flip model
flip_model = "no"; //[yes,no]

/*[hidden]*/
max_size = max_size_in_millimeter;
model_height = model_height_in_millimeter;
$fn=32;

// Outer Points
A1 = [68, 207];
A2 = [78, 167];
A3 = [109, 164];
A4 = [260, 305];
A5 = [415, 507];
A6 = [565, 759];
A7 = [639, 940];
A8 = [703, 849];
A9 = [739, 833];
A10 = [1187, 803];
A11 = [1229, 835];
A12 = [1339, 851];
A13 = [1589, 444];
A14 = [1623, 437];
A15 = [1643, 455];
A16 = [1632, 543];
A17 = [1864, 642];
A18 = [1907, 734];
A19 = [1890, 763];
A20 = [1706, 831];
A21 = [1539, 1272];
A22 = [1494, 1390];
A23 = [1468, 1446];
A24 = [1488, 1489];
A25 = [1563, 1525];
A26 = [1578, 1561];
A27 = [1555, 1590];
A28 = [1602, 1611];
A29 = [1612, 1641];
A30 = [1587, 1664];
A31 = [1454, 1683];
A32 = [1410, 1652];
A33 = [1257, 1395];
A34 = [1151, 1183];
A35 = [786, 1415];
A36 = [764, 1464];
A37 = [604, 1595];
A38 = [634, 1672];
A39 = [631, 1701];
A40 = [596, 1711];
A41 = [538, 1696];
A42 = [511, 1626];
A43 = [480, 1595];
A44 = [441, 1523];
A45 = [435, 1463];
A46 = [525, 1383];
A47 = [534, 1334];
A48 = [506, 1110];
A49 = [445, 852];
A50 = [376, 654];

outline = [A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23, A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34, A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45, A46, A47, A48, A49, A50];

// Inner Points
B1 = [1201, 846];
B2 = [637, 1381];
B3 = [1445, 716];
B4 = [1360, 840];
B5 = [1591, 849];
B6 = [1493, 644];
B7 = [1469, 684];
B8 = [1601, 607];
B9 = [1611, 482];
B10 = [1543, 563];
B11 = [1634, 488];
B12 = [1514, 1588];

// Polygons
C1 = [B6, B10, B9, B11, A16, B8];
C2 = [A16, A17, A18, A19, A20, B5, B7, B6, B8];
C3 = [B7, B3, A21, A20, B5];
C4 = [B3, B4, A22, A21];
C5 = [B1, A11, A12, B4, A22, A33, A34];
C6 = [A33, A32, A31, A30, A29, A28, A27, B12, A24, A23, A22];
C7 = [A24, A25, A26, A27, B12];
C8 = [A36, B2, A43, A42, A41, A40, A39, A38, A37];
C9 = [B2, A47, A46, A45, A44, A43];
C10 = [A36, A35, A9, A8, A7, A48, A47, B2];
C11 = [A10, B1, A11];
C12 = [A10, B1, A34, A35, A9];
C13 = [A12, A13, A14, A15, B11, B9, B10, B6, B7, B3, B4];
C14 = [A7, A6, A5, A49, A48];
C15 = [A49, A5, A4, A3, A2, A1, A50];

cut_polygons = [C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15];

min_x = A1[0];
min_y = A3[1];
max_x = A18[0];
max_y = A40[1];

x_size = max_x - min_x;
y_size = max_y - min_y;

x_factor = max_size / x_size;
y_factor = max_size / y_size;

inline_size = x_size > y_size ? inline_size_in_millimeter / x_factor / 2: inline_size_in_millimeter / y_factor / 2;
inline_edge_radius = x_size > y_size ? inline_edge_radius_in_millimeter / x_factor: inline_edge_radius_in_millimeter / y_factor;
outline_size = x_size > y_size ? outline_size_in_millimeter / x_factor - inline_size: outline_size_in_millimeter / y_factor - inline_size;

 // Program Section //
//-----------------//

if(x_size > y_size) {
    resize(newsize=[max_size, x_factor * y_size, model_height]){
        if(flip_model == "yes") {
            mirror([0,1,0]) {
                rotate([180,180,0]) {
                    create(outline, cut_polygons);
                }
            }
        } else {
            create(outline, cut_polygons);
        }
    }
} else {
    resize(newsize=[y_factor * x_size, max_size, model_height]){
        if(flip_model == "yes") {
            mirror([0,1,0]) {
                rotate([180,180,0]) {
                    create(outline, cut_polygons);
                }
            }
        } else {
            create(outline, cut_polygons);
        }
    }
}

 // Module Section //
//----------------//

module create() {
    linear_extrude(height=1) {
        difference() {
            offset(r = -0) {
                offset(r = +outline_size) {
                    polygon(points = outline, convexity = 10);
                }
            }

            for(cut_polygon = [0:len(cut_polygons)]) {
                offset(r = +inline_edge_radius) {
                    offset(r = -inline_size - inline_edge_radius) {
                        polygon(points = cut_polygons[cut_polygon], convexity = 10);
                    }
                }
            }
        }
    }
}

```


[User]
I'm looking to create a custom test tube holder in OpenSCAD. It should be a rectangular block with a hole for the test tube, a cut-out in the front, and a small opening on the side. I want to be able to customize the dimensions of the block, the diameter of the test tube hole, the size of the front cut-out, and how far back that cut-out extends into the block. It's kind of like a block with a hole going down, a front cutaway, and a small cylinder cutting in from the side.

More specifically, I need the following parameters:

*   `laenge`: The length of the main block.
*   `breite`: The width of the main block.
*   `hoehe`: The height of the main block.
*   `glas`: The diameter of the test tube that will fit in the holder.
*   `toleranz`: A small tolerance value to add to the test tube diameter (likely for printing).
*   `auschnitt`: The width of the cut-out in the front.

The test tube hole should be centered on the length and width and be positioned one-third of the way up the main block, passing all the way through. The front cut-out should begin on the front face, be centered horizontally, and go through a depth equal to the `auschnitt` parameter. The small cylinder cut-out should come in from the side, be centered vertically, and intersect with the test tube hole. The size of this cut-out should be the same as the auschnitt. The `toleranz` should be added to the hole diameter for the test tube and to create a sphere that will also cut out that area to improve printing. I'd also like all circular objects to have a high fragment number, so they look nice and smooth. I don't want to be forced to specify the fragment number on every cylinder and sphere object. Maybe 96 fragments would be good.

Could you generate the appropriate OpenSCAD file(s) for this? I don't expect to use any external libraries for this simple project.

[Assistant]
reagenz_customizer.scad:
```

//Length of the cube:
laenge=50; // [10:100]
//heigth of the cube:
hoehe=60; // [10:100]
//heigth of the cube:
breite=50; // [10:100]
//diameter of the glass
glas=25; //[10:60]

toleranz=0.25;
//cut (front):
auschnitt=15; //[8:100]

//miko="N";

//Basis-Rechteck
difference(){

//if(miko="N"){

cube([laenge,breite,hoehe]);
//}
//else{
//$fn=50;
//minkowski()
//{
//  cube([laenge,breite,hoehe]);
//  cylinder(r=2,h=1);
//}
//}

union(){
translate([laenge/2,breite/2,hoehe/3])
cylinder(h = hoehe, d1 = glas+toleranz, d2 = glas+toleranz, $fn=96) ;
translate([laenge/2,breite/2,hoehe/3])
sphere(d = glas+toleranz, $fn=96);

translate([laenge/2-auschnitt/2,-10,hoehe/2.5])
cube([auschnitt,breite/2,hoehe]);

translate([laenge/2,breite/2,hoehe/2.5])
rotate([90,0,0])
cylinder(h = hoehe, d1 = auschnitt, d2 = auschnitt, $fn=96) ;
}
}
```


[User]
I need a custom hook design for hanging flat objects with a hole, like a cutting board. I'm using OpenSCAD. I have some specific requirements and I'd like the design to be parametric, so I can easily customize it.

Here's what I'm thinking:

**General Shape:**

*   The main part of the hook will be cylindrical, matching the hole in the object. Think of it as a short, fat cylinder.
*   It needs a rounded top that flares out a bit to make it easy to slide the hole on and off of it. This rounded part should be a partial torus shape.
*   The bottom of the hook needs to transition smoothly into a flat base that will sit against the wall.

**Parameters:**

*   `knob_thickness`:  The thickness of the main cylindrical part of the hook. This should be adjustable to match different object thicknesses.
*   `knob_diameter`: The diameter of the hole the hook is designed to fit in.
*   `knob_round`: The radius of the round part at the top. It should be a quarter-circle, and also affect how much the top of the knob is extruded out from the cylinder.
*   `support_thickness`: The thickness of the base that mounts to the wall.
*  `support_screw_diameter`: The diameter of the screw holes for mounting.
*  `support_screw_diameter_top`: The diameter of the screw head recess (countersink).
*   `support_extends_top`:  How far the base extends above the main cylinder.
*   `support_extends_bottom`: How far the base extends below the main cylinder.
*   `support_cutoff`: This is a bit trickier. It should control how far inward the hook reaches below its cylinder, using the cylinder's diameter as a starting point. A value of 0 would mean the hook is flat. A value of 1 would mean it extends inward as far as it extends out. A reasonable number is somewhere in between, around .5 to 0.7.

**Mounting:**

*   The base should have two counter-sunk screw holes, one above and one below the main hook cylinder.
*   The screw holes should be centered relative to the base.
*   The counter-sinking should allow the screw head to be flush or below the surface of the base.

**Libraries:**

I think that we'll need the `threads.scad` library to help with making the screw holes.

**Additional Notes:**

*   I want the hook to be fairly minimalistic and sleek, with no visible screws after mounting.
*   I'd like the rounded top to be generated via a rotate_extrude operation, and then be combined with the rest of the knob.
*   I think a good value for `fn` would be `50/1` to start.

Could you create an OpenSCAD file that captures these requirements, with all the parameters I've mentioned?

[Assistant]
knobhook.scad:
```
include <threads.scad>
$fn = 50 / 1;

// the thickness of the object you're hanging
knob_thickness = 25;

// the diameter of the hole
knob_diameter = 30;

// the diameter of the curve
knob_round = 5;

// the top diameter of the screw for countersinking

support_thickness = 3 / 1
support_screw_diameter = 4 / 1;
support_screw_diameter_top = 6.5 / 1;

// top base extension length
support_extends_top = 25;

// bottom base extension length
support_extends_bottom = 25;

support_cutoff = .6 / 1;

translate([0, 0, knob_thickness / 2]) {
    difference() {
        union() {
            translate([0, 0, knob_thickness / 2 + knob_round / 2]) {
                rotate_extrude(convexity = 20) {
                    //translate([0, knob_diameter / 2 + knob_round / 2, 0]) circle(knob_round / 2);
                    translate([knob_diameter / 2, 0, 0]) circle(knob_round / 2);
                }
                cylinder(knob_round, knob_diameter / 2, knob_diameter / 2, true);
            }
            cylinder(knob_thickness, knob_diameter / 2, knob_diameter / 2, true);
        }

        translate([knob_diameter * .9, 0, 0]) cube([knob_diameter, knob_diameter, 100], true);
        translate([-knob_diameter * .9, 0, 0]) cube([knob_diameter, knob_diameter, 100], true);
        translate([0, -knob_diameter * support_cutoff, 0]) cube([knob_diameter, knob_diameter, 100], true);
    }
}

hull() {
    translate([-knob_diameter * .8 / 2 + knob_round / 2, (-knob_diameter / 2) * support_cutoff + knob_round, (knob_thickness + knob_round) / 2]) cylinder(knob_thickness + knob_round, knob_round / 2, knob_round / 2, true);
    translate([knob_diameter * .8 / 2 - knob_round / 2, (-knob_diameter / 2) * support_cutoff + knob_round, (knob_thickness + knob_round) / 2]) cylinder(knob_thickness + knob_round, knob_round / 2, knob_round / 2, true);
}

translate([0, 0, -3]) {
    difference() {
        hull() {
            translate([0, -support_extends_bottom, 0]) cylinder(support_thickness, knob_diameter * .4, knob_diameter * .4);
            translate([0, support_extends_top, 0]) cylinder(support_thickness, knob_diameter * .4, knob_diameter * .4);
        }

        translate([0, support_extends_top, support_thickness / 2]) cylinder(3, support_screw_diameter / 2, support_screw_diameter_top / 2, true);
        translate([0, -support_extends_bottom, support_thickness / 2]) cylinder(3, support_screw_diameter / 2, support_screw_diameter_top / 2, true);
    }
}

//translate([0, knob_diameter / 2 + knob_round / 2, 0]) circle(knob_round / 2);

```


[User]
I'm looking to create a customizable honeycomb structure in OpenSCAD. I'd like it to be based on a 2D array of data, where each cell in the array corresponds to a position in the grid. A value of '1' in the array should result in a standard hexagonal cell being generated, and a value of '2' should trigger a more complex, multi-part fill of the same hexagonal space (details below).

If no data array is provided, the script should generate a random grid of hexagons using a random seed. I need parameters to control the overall size, hexagon diameter, wall thickness, and overall height of the structure as well as the height variation. Also I need to be able to specify a seed value for the random number generator. The possibility of whether a hexagon will be partially filled or not is also controlled by a parameter.

Here's a breakdown of what I'd like:

*   **Data Input:** The design should work with a 2D array called `data`. This array will determine what gets rendered in the honeycomb grid. If this `data` variable isn't present, use default values for width and length to generate a random grid.
*   **Hexagon Types:**
    *   **Type 1 (Value 1 in data):** A standard hexagon with a specified height.
    *   **Type 2 (Value 2 in data):**  This one is a bit more complex. Instead of a solid hexagon, it should have an outline hexagon, and that region filled with many smaller hexagons, some partially filled, some hollow. The outer edges should be partially filled or hollow depending on a probability. It should also have "limiters" on the edges made of solid hexagon.
*   **Parameters:**
    *   `diameter`: Diameter of the base hexagon.
    *   `wallThickness`: Thickness of the hexagon walls.
    *   `height`: Base height of the hexagons.
    *   `heightVariation`: Maximum amount of random height variation to add to each hexagon.
    *   `seed`: Seed value for the random number generator. If this value is set to -1, a random seed is used instead.
    *  `fillPossibility`: A value between 0-1 determining how likely the smaller hexagons within a Type 2 hexagon are partially filled.
    *   `defaultDataWidth`: Default width if no data is provided.
    *   `defaultDataLength`: Default length if no data is provided.

*   **Random Generation:** If no `data` array is supplied, the script should use `defaultDataWidth` and `defaultDataLength` to generate a 2D array of random 0,1, and 2 values using a seed (either the provided seed or a random seed).
*   **AMF Output:** Please make sure this is designed to export as an AMF file rather than STL.
*   **Libraries:** I would like to use the `MCAD` library for any math or helper functions you may find necessary.

I'm hoping for a clean, parametric design that I can modify easily. Let me know if you need any clarification!

[Assistant]
honeycombs.scad:
```

// Diameter of each starting hexagon
diameter = 16;

// Wall thickness of starting hexagons
wallThickness = 4;

// Base height of starting hexagons
height = 8;

// Seed value to seed use of Random with. Set to -1 to use random seed.
seed = -1;

//////////////////////////////////////////////////////////////////
// Used if no data is provided (targeting Thingiverse Customizer)

// Number of hexagons wide
defaultDataWidth = 8;
// Number of hexagons length
defaultDataLength = 8;
//////////////////////////////////////////////////////////////////

// Amount of height variation randomly added to each hexagon
heightVariation = 4;

// 0-1 possibility of filling a hexagon
fillPossibility = 0.4; // [0:.01:1]

actualSeed = seed == -1 ? round(getNextRand(100000)) : seed;
echo (str("SEED: ", actualSeed));

function getNextRand(limit = 1, seed = -1) =
	getNextRandMulti(1, limit, seed)[0];
function getNextRandMulti(count, limit = 1, seed = -1) =
	limit == 0 ? 0 : (seed == -1 ? rands(0, limit, count) : rands(0, limit, count, seed));

function getDataPoint(data, x, y) =
	let (xClipped = x < 0 || x >= len(data) ? undef : x)
	let (yClipped = xClipped == undef ? undef : (y < 0 || y >= len(data[xClipped]) ? undef : y))
	xClipped == undef || yClipped == undef ? undef : data[x][y];

function getLimiters(data, x, y) =
	[for (xx = [x - 1:x + 1]) (for (yy = [y - 1:y + 1]) getDataPoint(data, xx, yy))];

module hexFilled(height, diameter = diameter)
{
	cylinder(height, d1 = diameter, d2 = diameter, $fn = 6);
}

module hexPartialFilled(height, diameter = diameter, wallThickness = wallThickness)
{
	difference()
	{
		hexFilled(height, diameter + wallThickness / 2);
		translate([0, 0, height - height / 8])
			hexFilled(height * 2, diameter - wallThickness / 2);
	}
}

module hex(height, diameter = diameter, wallThickness = wallThickness)
{
	difference()
	{
		hexFilled(height, diameter + wallThickness / 2);
		translate([0, 0, -.1])
			hexFilled(height * 3, diameter - wallThickness / 2);
	}
}

module fillHex(data, height, seed, diameter = diameter, wallThickness = wallThickness)
{
	scale = .50;
	subDiameter = diameter * scale;
	subThickness = wallThickness * scale;

	xOffset = subDiameter * .75;
	yOffset = sqrt(3) / 2 * subDiameter;
	rep = (diameter / subDiameter) + 1;

	x1 = -(diameter * .75) / 2;
	y1 = -(sqrt(3) / 2 * diameter) / 2;

	translate([x1, y1, 0])
	union()
	{
		for (x = [0:rep - 1])
		{
			for (y = [0:rep - 1])
			{
				x2 = x * xOffset;
				y2 = y * yOffset + (x % 2) * (yOffset / 2);

				nextSeed = getNextRandMulti(4, 1, seed + x * (len(data) - 1) + y);
				isEdge = x == 0 || x == rep - 1 || y == 0 || y == rep - 1;
				if (!isEdge || nextSeed[0] < 0.5)
				{
					translate([x2, y2, 0])
					{
						if (nextSeed[1] <= fillPossibility)
							hexPartialFilled(height + heightVariation * nextSeed[2], subDiameter, subThickness);
						else
							hex(height + heightVariation * nextSeed[3], subDiameter, subThickness);
					}
				}
			}
		}
	}
}

module buildLimiters(data, height, offset)
{
	for (x = [0:2])
	{
		for (y = [0:2])
		{
			value = data[x * 3 + y];
			if ((value == 1) && (x != 1 || y != 1))
			{
				translate([x * xOffset, y * yOffset - (yOffset * offset) + ((x * (offset == 1 ? 1 : -1)) % 2) * (yOffset / 2), 0])
				{
					hexFilled(height);
				}
			}
		}
	}
}

module buildFromData(data, height, seed, triggerValue, fill = false)
{
	union()
	{
		for (x = [0:len(data) - 1])
		{
			for (y = [0:len(data[x]) - 1])
			{
				translate([x * xOffset, y * yOffset + (x % 2) * (yOffset / 2), 0])
				{
					value = data[x][y];
					nextSeed = getNextRandMulti(4, 1, seed + x * (len(data) - 1) + y);
					if (value == 1)
					{
						if (fill)
						{
							hexFilled(height);
						}
						else
						{
							if (nextSeed[0] <= fillPossibility)
								hexPartialFilled(height + heightVariation * nextSeed[0]);
							else
								hex(height + heightVariation * nextSeed[1]);
						}
					}
					else if (value == 2)
					{
						difference()
						{
							fillHex(data, height - heightVariation, nextSeed[3]);
							translate([-xOffset, -yOffset / 2, -.01])
								buildLimiters(getLimiters(data, x, y), height * 2, x % 2);
						}
					}
				}
			}
		}
	}
}

function getRandomData(seed) =
	let (values = getNextRandMulti(defaultDataWidth * defaultDataLength, 2, seed))
	[for (xx = [0:defaultDataWidth - 1]) ([for (yy = [0:defaultDataLength - 1]) (round(values[xx * defaultDataWidth + yy])) ]) ];

xOffset = diameter * .75;
yOffset = sqrt(3) / 2 * diameter;

if (data)
{
	translate([-len(data) * xOffset / 2, -len(data[0]) * yOffset / 2, 0])
		buildFromData(data, height, actualSeed, 1);
}
else
{
	randData = getRandomData(actualSeed);
	echo (randData);
	translate([-len(randData) * xOffset / 2, -len(randData[0]) * yOffset / 2, 0])
		buildFromData(randData, height, actualSeed, 1);
}

```


[User]
Hey, I'm looking to get a parametric spool holder designed in OpenSCAD. I'm a big fan of the TUSH spool holder but need something that can clamp onto my printer's control box, and also be customized for other uses. I need a design that can accommodate standard 608 bearings.

Here's what I'm looking for in terms of parameters:

*   **`inner_bracket_width`**: This should define the inner width of the bracket, kind of like the width of the control box it will attach to, or the outer width if there aren't any clamps used. This will also end up being the total width when no clamps are present. Default it to 152 mm (the current version's default), but make sure it's easily adjustable.
*   **`base_thickness`**: The base material thickness for all parts; set this default to 3.0 mm.
*   **`additional_thickness`**: I'd like an option to add some extra thickness for stability to the spool guide and clamp, with a default of 0.
*   **`screw_hole_diameter`**: It would be great to have the option to add a bolt hole, with a default of 0 (no hole) but the ability to set it to 4mm (or any number) for adding skewers or screws.
*   **`clamp_thickness`**: Let's have a parameter for the clamp thickness; if it's set to 0, no clamps are generated (so the bracket width will then become the outer width), with a default of 3mm.
*   **`clamp_pin_length`**: A parameter to control the length of the clamp pins; when set to 0, there are no clamps (same as `clamp_thickness`). Default this to 15mm.
*   **`bearing_padding_bottom`**: This should control how far the bearings sit from the surface, with a default of 2mm.
*   **`bearing_distance_offset`**: Controls the distance between the bearings; it can be used to lower or raise this distance. Default this to 0 but I may need to adjust it sometimes to lower this.
*   **`bearing_diameter`**: The diameter of the bearings (for the spool guide); default this to 22mm.
*   **`bearing_thickness`**: The thickness of the bearings for calculating the height of the part; set the default to 7mm.
*  **`bearing_bore`**: The bore diameter of the bearing; default to 8mm.
*  **`bearing_spacer_height_offset`**: Height of the bearing spacer, 0 for no spacer, default to 0.5mm.
*  **`bearing_spacer_radius_offset`**: Radius offset of the bearing spacer; default to 1.5mm.
*   **`spool_guide_offset`**: An offset to increase the diameter of the fixture to keep the spool on the bearings, default to 5mm.
*   **`resolution`**: Control the surface detail, I'd like a way to be able to set this between like 30 (draft) and 80 (smooth). Default to 50.

I think the design should consist of a core 'bearing fixture' module, a 'basic spool holder' which is essentially two bearing fixtures connected, and then a complete 'spool holder' module which adds clamps to the basic spool holder (or not). The clamps should be U-shaped. I'd also like to have two options for clamps, one that has hard edges, and a softer version that uses hulls between a cube and a cylinder.

The design should include a module for the bearing fixture which will include a spacer, a cylinder for the bearing, and an optional screw hole. It should also include a module for the basic spool holder which will include two bearing fixtures bridged by a cube. The final module should handle the optional clamps, and the placement of the main body.

I don't need any libraries, just a couple of OpenSCAD files that encapsulate all the above. Let me know if you need any other information.

[Assistant]
CUSH_-_Custom_Spool_Holder_v1.3.scad:
```
/* == Customizer Settings ================================================ */
/*[Gimbal Settings]*/

// in mm, e.g. control box width (with clamps) or outer dimensions (without clamps)
inner_bracket_width = 152; // v1.1: raised from 150 to gain some additional play
//inner_bracket_width = 125; // TUSH dimensions

// in mm, base material thickness of all parts
base_thickness = 3.0;

// in mm, optionally add some thickness to the spool guide and clamp to gain stability
additional_thickness = 0; // v1.3: added parameter, 0 for regular version
//additional_thickness = 7; // v1.3: broad version

// in mm, 0 for no hole
//screw_hole_diameter = 0.0;
screw_hole_diameter = 4; // v1.3: used for all new SLTs

// in mm, 0 for no clamps (inner bracket width will become outer width)
clamp_thickness = 3;

// in mm, 0 for no clamps
clamp_pin_length = 15;
//clamp_pin_length = 0; // TUSH dimensions

// in mm, raise bearings further from surface
bearing_padding_bottom = 2;

// in mm, lower/raise distance between bearings
bearing_distance_offset = 0;
//bearing_distance_offset = -33; // Small Spool (TUSH dimensions) for Tornado Box

// in mm, affects minimum spool guide diameter
bearing_diameter = 22;

// in mm, affects axis height
bearing_thickness = 7;

// in mm, affects axis diameter
bearing_bore = 8;

// in mm, affects height of bearing spacer, 0 for no spacer
bearing_spacer_height_offset = 0.5;

// in mm, affects radius of bearing spacer
bearing_spacer_radius_offset = 1.5;

// in mm, increases fixture diameter to keep spool on bearings
spool_guide_offset = 5;

// Surface detail level
resolution = 50; // [30:Draft, 50:Normal, 80:Smooth]

/* == Draw Spool Holder ================================================== */
spool_guide_diameter = bearing_diameter + spool_guide_offset;
SpoolHodler($fn=resolution);

/* == Modules ============================================================ */

/* Click-in (TUSH like) bearing fixture
 axis_diameter - inner diameter of the bearing
 axis_height - thickness of the bearing / 2
 guide_diameter - outer diameter of the spool guide
 padding_bottom - additional spacing between bearing and fixture base
 spacer_radius_offset - radius of the optional bearing spacer
 spacer_height_offset - height of the optional bearing spacer
 screw_diameter - diameter of the optional bolt hole
 thickness - thickness of the base material
 res - resolution
*/
module BearingFixture(axis_diameter = bearing_bore, axis_height = bearing_thickness/2,
                      guide_diameter = spool_guide_diameter,
                      padding_bottom = bearing_padding_bottom,
                      spacer_radius_offset = bearing_spacer_radius_offset,
                      spacer_height_offset = bearing_spacer_height_offset,
                      screw_diameter = screw_hole_diameter,
                      thickness = base_thickness, add_thickness = additional_thickness,
                      res = resolution) {
    difference() {
        union() {
            translate([guide_diameter/2, guide_diameter/2 + padding_bottom, 0]) {
                translate([0,0,thickness+add_thickness]) {
                    // axis
                    cylinder(h = spacer_height_offset + axis_height,
                             d = axis_diameter, $fn = res);
                    // bearing axis wheel spacer
                    cylinder(h = spacer_height_offset,
                             d = axis_diameter + spacer_radius_offset*2,
                             $fn = res);
                }
                // upper (rounded) base
                cylinder(h = thickness+add_thickness, d = guide_diameter, $fn = res);
            }
            // lower (rectangular) base
            cube([guide_diameter, guide_diameter/2 + padding_bottom, thickness+add_thickness]);
        }
        // axis bolt hole
        translate([guide_diameter/2, guide_diameter/2 + padding_bottom, -1]) {
            cylinder(h = thickness+add_thickness + axis_height + 2, d = screw_diameter, $fn = res);
        }
    }
}
//BearingFixture();

/* Basic spool holder comprised of two bridged BearingFixtures */
module BasicSpoolHoder() {
    thickness = base_thickness;
    base_width = inner_bracket_width + 2 * clamp_thickness - spool_guide_diameter
                 + bearing_distance_offset;
    base_height = max(clamp_thickness, thickness) + spool_guide_diameter/2
                  + bearing_padding_bottom;

    union() {
        // left bearing fixture
        translate([0,max(clamp_thickness, thickness),0]) {
            BearingFixture();
        }
        // bridge between bearing fixtures
        translate([spool_guide_diameter,0,0]) {
            cube([base_width - spool_guide_diameter, base_height, thickness]);
        }
        // spacer bettween two fixtures
        cube([base_width + spool_guide_diameter, max(clamp_thickness, thickness),
              thickness + bearing_thickness/2 + bearing_spacer_height_offset]);
        // reight bearing fixture
        translate([base_width,max(clamp_thickness, thickness),0]) {
            BearingFixture();
        }
    }
}
//BasicSpoolHoder();

/* Spool holder comprised of the BasicSpoolHoder and (optional) "clamp" brackets */
module SpoolHodler() {
    clamp_thickness_z = base_thickness + additional_thickness + bearing_thickness/2 + bearing_spacer_height_offset;

    union() {
        translate([-bearing_distance_offset/2,clamp_pin_length,0]) {
            BasicSpoolHoder();
        }
        if (bearing_distance_offset < 0 && bearing_distance_offset < -clamp_thickness) {
            UClampSoft(w = inner_bracket_width, h = clamp_pin_length, d = clamp_thickness,
                    dz = clamp_thickness_z);
        } else {
            UClamp(w = inner_bracket_width, h = clamp_pin_length, d = clamp_thickness,
                   dz = clamp_thickness_z);

        }
    }
}
//SpoolHodler();

/* U-shaped "clamp" (bracket) based on inner dimensions and thickness
 [w] inner width (space between two pins)
 [h] inner height (pin length)
 [d] clamp diameter
 [dz] clamp z diameter
*/
module UClamp(w, h, d, dz) {
    difference() {
        cube([w+2*d,h+d,dz]);
        translate([d,-1,-1]) {
            cube([w,h+1,dz+2]);
        }
    }
}
//UClamp(10,10,10,10);
//UClamp(20,10,15,8);

/* U-shaped "clamp" (bracket) based on inner dimensions and thickness
 [w] inner width (space between two pins)
 [h] inner height (pin length)
 [d] clamp diameter
 [dz] clamp z diameter (thtickness)
*/
module UClampSoft(w, h, d, dz) {
    module UCLeft() {
        union() {
            hull() {
                cube([d,h,dz]);
                translate([d/2,d/2+h,0]) cylinder(d=d, h=dz);
            }
            hull() {
                translate([d,h,0]) cube([w/2,d,dz]);
                translate([d/2,d/2+h,0]) cylinder(d=d, h=dz);
            }
        }
    }
   union() {
       UCLeft();
       translate([w+2*d,0,0]) mirror([1,0,0]) UCLeft();
   }
}
//UClampSoft(20,10,15,8);

```