[User]
Hey, I'm looking to design some flexible Lego-compatible tape, and I want to be able to customize it easily. I've seen some designs floating around, but I want to make sure I have full control over the parameters.

Basically, I need a way to generate a strip of Lego-compatible "tape" that can either have the studs (like the top of a normal Lego brick) or the anti-studs (like the underside of a normal Lego brick) on the surface. I'd also like an option to have flexible grooves between the studs/anti-studs to make it more bendable.

Here's the key stuff I need to be able to adjust:

*   **`block_width`**: The width of the tape, in studs. So, a value of '2' would be two studs wide.
*   **`block_length`**: The length of the tape, in studs. '4' would be four studs long.
*   **`Grooves`**: A boolean (`true`/`false`) to turn those flexible grooves on or off.
*   **`block_type`**: An integer (either `0` or `1`). `1` means it's a stud tape (top of Lego), and `0` means it's tile tape (bottom of Lego).
*   **`block_height_ratio`**: A ratio that scales the height of the tape relative to a standard Lego brick. So, '1' is a standard brick height, '1/3' is a standard plate height, '0.34' is for a tile tape, and so on. I need to be able to specify this as a floating-point number like `0.34` or `0.25`, not just fractions.

I would like all dimensions for the studs/anti-studs and spacing to be based on official lego dimensions.

 I'm not super familiar with OpenSCAD libraries, so it would be ideal if this could be achieved without relying on external libraries. I expect that this will be more than one .scad file, since it involves some degree of modularity. I would like the code to be as readable and easy to understand as possible for a beginner. I don't expect comments, but variable names that explain what they're used for, or function names that do the same would be ideal.

[Assistant]
flexy_Brick_Tape.scad:
```
// * LEGO®, the LEGO® logo, the Brick, DUPLO®, and MINDSTORMS® are trademarks of the LEGO® Group. ©2012 The LEGO® Group.
 //

/* [General] */

// Width of the block, in studs
block_width = 2;

// Length of the block, in studs
block_length = 4;

//Do you want the flexible grooves?
Grooves =false; //true or false

// What type of tape should this be? Stud or tile? (Stud is top side only & Tile is bottom side of lego only)
block_type = 0; // [1 = stud tape, 0 = tile tape]

// Height of the block. A ratio of "1" is a standard LEGO brick height; a ratio of "1/3" is a standard LEGO plate height; "1/2" is a standard DUPLO plate.
//RECOMENDED: Use 0.1 for stud tape and .34 for tile tape. This is left as user editible in case your filament is more/less flexible than mine.
//Tring .25mm for 7x7 hard top for cuitcuit cover, .3 thick for chassis
    block_height_ratio = .34; // [.33333333333:1/3, .5:1/2, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, 10:10]

/* Hidden */

knob_diameter=4.896*1.0;		//knobs on top of blocks

knob_height=1.8;
knob_spacing=8.0;
wall_thickness=1.45;
roof_thickness=.800;
block_height=9.6*block_height_ratio;
pin_diameter=3.2*1.00;		//pin for bottom blocks with width or length of 1
post_diameter=6.5137;
reinforcing_width=.6;
axle_spline_width=2.0;
axle_diameter=5;
cylinder_precision=0.05;
overall_length=block_length*8;
overall_width=block_width*8;

difference(){
    block(block_width ,block_length,1,axle_hole=false,reinforcement=true);
    if (Grooves == true) {
    grooves();
    }

       //These translated cubes cut out .1mm form the ends of each side of the brick for clearance to the next brick.
    translate([block_length*knob_spacing/2,0,0]){
        cube([.2,8*block_width+.01,block_height*2.01], center = true);
    }
     translate([-block_length*knob_spacing/2,0,0]){
        cube([.2,8*block_width+.01,block_height*2.01], center = true);
    }
     translate([0,-block_width*knob_spacing/2,0]){
        cube([8*block_length+.01,.2,block_height*2.01], center = true);
    }
     translate([0,block_width*knob_spacing/2,0]){
        cube([8*block_length+.01,.2,block_height*2.01], center = true);
    }
}

module grooves(height){
        	translate([-overall_length/2,-overall_width/2-.01,0]) {
        for (ycount=[1:block_width-1]){
							for (xcount=[1:block_length-1]) {
                                translate([xcount*knob_spacing,block_width*knob_spacing/2,block_height-1.5/4+.01]){
                                cube([.75,block_width*knob_spacing,1.5/2], center = true);
                                    //y axisgrooves
                                }
                                translate([block_length*knob_spacing/2,ycount*knob_spacing,block_height-1.5/4+.01]){
                                cube([block_length*knob_spacing,.75,1.5/2], center = true);
                                    //x axis grooves

                                }

                            }
                        }
                    }
                }

module block(width,length,height,axle_hole,reinforcement) {
    //overall_length=(length-1)*knob_spacing+knob_diameter/stud_rescale+wall_thickness*2;
	//overall_width=(width-1)*knob_spacing+knob_diameter/stud_rescale+wall_thickness*2;
    overall_length=block_length*8;
    overall_width=block_width*8;
	start=(knob_diameter/2+knob_spacing/2+wall_thickness);
	translate([-overall_length/2,-overall_width/2,0])
		union() {
			difference() {
				union() {
					cube([overall_length,overall_width,height*block_height]);
					translate([knob_diameter/2+wall_thickness,knob_diameter/2+wall_thickness,0])
						for (ycount=[0:width-1])
							for (xcount=[0:length-1]) {
                                translate([xcount*knob_spacing,ycount*knob_spacing,0])
                                cylinder(r=knob_diameter*block_type/2,h=block_height*height+knob_height,$fs=cylinder_precision);
						}
				}

                if(block_type == 0){
                    translate([wall_thickness,wall_thickness,-roof_thickness]) cube([overall_length-wall_thickness*2,overall_width-wall_thickness*2,block_height*height]);
              						}

				if (axle_hole==true)
					if (width>1 && length>1) for (ycount=[1:width-1])
						for (xcount=[1:length-1])
							translate([(xcount-1)*knob_spacing+start,(ycount-1)*knob_spacing+start,-block_height/2])  axle(height+1);
                        //grooves();

			}

			if (reinforcement==true && width>1 && length>1)
				difference() {
					for (ycount=[1:width-1])
						for (xcount=[1:length-1])
							translate([(xcount-1)*knob_spacing+start,(ycount-1)*knob_spacing+start,0]) reinforcement(height);
					for (ycount=[1:width-1])
						for (xcount=[1:length-1])
							translate([(xcount-1)*knob_spacing+start,(ycount-1)*knob_spacing+start,-0.5]) cylinder(r=post_diameter/2-0.1, h=height*block_height+0.5, $fs=cylinder_precision);
				}

			if (width>1 && length>1) for (ycount=[1:width-1])
				for (xcount=[1:length-1])
					translate([(xcount-1)*knob_spacing+start,(ycount-1)*knob_spacing+start,0]) post(height,axle_hole);

			if (width==1 && length!=1)
				for (xcount=[1:length-1])
					translate([(xcount-1)*knob_spacing+start,overall_width/2,0]) cylinder(r=pin_diameter/2,h=block_height*height,$fs=cylinder_precision);

            if (width==1 && length!=1)
				for (xcount=[1:length-1])
					translate([(xcount-1)*knob_spacing+start,overall_width/2,block_height/2]) cube([reinforcing_width,7,height*block_height],center=true);

			if (length==1 && width!=1)
				for (ycount=[1:width-1])
					translate([overall_length/2,(ycount-1)*knob_spacing+start,0]) cylinder(r=pin_diameter/2,h=block_height*height,$fs=cylinder_precision);
            if (length==1 && width!=1)
				for (ycount=[1:width-1])
                    translate([overall_length/2,(ycount-1)*knob_spacing+start,block_height/2]) cube([7,reinforcing_width,height*block_height],center=true);

            }
}

module post(height,axle_hole=false) {
	difference() {
		cylinder(r=post_diameter/2, h=height*block_height,$fs=cylinder_precision);
		translate([0,0,-0.5])
            cylinder(r=knob_diameter/2, h=height*block_height+1,$fs=cylinder_precision);

	}
}

module reinforcement(height) {
	union() {
		translate([0,0,height*block_height/2]) union() {
			cube([reinforcing_width,knob_spacing+knob_diameter+wall_thickness/2,height*block_height],center=true);
			rotate(v=[0,0,1],a=90) cube([reinforcing_width,knob_spacing+knob_diameter+wall_thickness/2,height*block_height], center=true);
		}
	}
}

module axle(height) {
	translate([0,0,height*block_height/2]) union() {
		cube([axle_diameter,axle_spline_width,height*block_height],center=true);
		cube([axle_spline_width,axle_diameter,height*block_height],center=true);
	}
}

```
Regular_Legos.scad:
```
// * LEGO®, the LEGO® logo, the Brick, DUPLO®, and MINDSTORMS® are trademarks of the LEGO® Group. ©2012 The LEGO® Group.
 //

/* [General] */

// Width of the block, in studs
block_width = 2;

// Length of the block, in studs
block_length = 4;

//Do you want the flexible grooves?
Grooves =false; //true or false

// Height of the block. A ratio of "1" is a standard LEGO brick height; a ratio of "1/3" is a standard LEGO plate height; "1/2" is a standard DUPLO plate.
//RECOMENDED: Use 0.1 for lego stud tape and .34 for tile tape. This is left as user editible in case your filament is more/less flexible than mine.
//Tring .25mm for 7x7 hard top for cuitcuit cover, .3 thick for chassis
    block_height_ratio = 1; // [.33333333333:1/3, .5:1/2, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, 10:10]

/* Hidden */
knobScale=1.0;
postScale=1.0;

knob_diameter=4.896*knobScale;		//knobs on top of blocks.
knob_height=1.8;
knob_spacing=8.0;
wall_thickness=1.45;
roof_thickness=.800;
block_height=9.6*block_height_ratio;
pin_diameter=3.2*postScale;		//pin for bottom blocks with width or length of 1.
post_diameter=6.5137*postScale; //The Post on the bottom of blocks.
reinforcing_width=.6;
axle_spline_width=2.0;
axle_diameter=5;
cylinder_precision=0.03;
overall_length=block_length*8;
overall_width=block_width*8;

difference(){
    block(block_width ,block_length,1,axle_hole=false,reinforcement=true);
    if (Grooves == true) {
    grooves();
    }
    //These translated cubes cut out .1mm form the ends of each side of the brick for clearance to the next brick.
    translate([block_length*knob_spacing/2,0,0]){
        cube([.2,8*block_width+.01,block_height*2.01], center = true);
    }
     translate([-block_length*knob_spacing/2,0,0]){
        cube([.2,8*block_width+.01,block_height*2.01], center = true);
    }
     translate([0,-block_width*knob_spacing/2,0]){
        cube([8*block_length+.01,.2,block_height*2.01], center = true);
    }
     translate([0,block_width*knob_spacing/2,0]){
        cube([8*block_length+.01,.2,block_height*2.01], center = true);
    }

}

module grooves(height){
        	translate([-overall_length/2,-overall_width/2-.01,0]) {
        for (ycount=[1:block_width-1]){
							for (xcount=[1:block_length-1]) {
                                translate([xcount*knob_spacing,block_width*knob_spacing/2,block_height-1.5/4+.01]){
                                cube([.75,block_width*knob_spacing,1.5/2], center = true);
                                    //y axisgrooves
                                }
                                translate([block_length*knob_spacing/2,ycount*knob_spacing,block_height-1.5/4+.01]){
                                cube([block_length*knob_spacing,.75,1.5/2], center = true);
                                    //x axis grooves

                                }

                            }
                        }
                    }
                }

module block(width,length,height,axle_hole,reinforcement) {
    overall_length=block_length*8;
    //overall_length=(length-1)*knob_spacing+knob_diameter/1+wall_thickness*2;
    overall_width=block_width*8;
	//overall_width=(width-1)*knob_spacing+knob_diameter/1+wall_thickness*2;
	start=(knob_diameter/2+knob_spacing/2+wall_thickness);
	translate([-overall_length/2,-overall_width/2,0])
		union() {
			difference() {
				union() {
					cube([overall_length,overall_width,height*block_height]);
					translate([knob_diameter/2+wall_thickness,knob_diameter/2+wall_thickness,0])
						for (ycount=[0:width-1])
							for (xcount=[0:length-1]) {
                                translate([xcount*knob_spacing,ycount*knob_spacing,0])
                                cylinder(r=knob_diameter/2,h=block_height*height+knob_height,$fs=cylinder_precision);
                                }
                         }

                    translate([wall_thickness,wall_thickness,-roof_thickness]) cube([overall_length-wall_thickness*2,overall_width-wall_thickness*2,block_height*height]);
                }

			if (reinforcement==true && width>1 && length>1)
				difference() {
					for (ycount=[1:width-1])
						for (xcount=[1:length-1])
							translate([(xcount-1)*knob_spacing+start,(ycount-1)*knob_spacing+start,0]) reinforcement(height);
					for (ycount=[1:width-1])
						for (xcount=[1:length-1])
							translate([(xcount-1)*knob_spacing+start,(ycount-1)*knob_spacing+start,-0.5]) cylinder(r=post_diameter/2-0.1, h=height*block_height+0.5, $fs=cylinder_precision);
				}

			if (width>1 && length>1) for (ycount=[1:width-1])
				for (xcount=[1:length-1])
					translate([(xcount-1)*knob_spacing+start,(ycount-1)*knob_spacing+start,0]) post(height,axle_hole);

			if (width==1 && length!=1)
				for (xcount=[1:length-1])
					translate([(xcount-1)*knob_spacing+start,overall_width/2,0]) cylinder(r=pin_diameter/2,h=block_height*height,$fs=cylinder_precision);

            if (width==1 && length!=1)
				for (xcount=[1:length-1])
					translate([(xcount-1)*knob_spacing+start,overall_width/2,block_height/2]) cube([reinforcing_width,7,height*block_height],center=true);

			if (length==1 && width!=1)
				for (ycount=[1:width-1])
					translate([overall_length/2,(ycount-1)*knob_spacing+start,0]) cylinder(r=pin_diameter/2,h=block_height*height,$fs=cylinder_precision);
            if (length==1 && width!=1)
				for (ycount=[1:width-1])
                    translate([overall_length/2,(ycount-1)*knob_spacing+start,block_height/2]) cube([7,reinforcing_width,height*block_height],center=true);

            }
}

module post(height,axle_hole=false) {
	difference() {
		cylinder(r=post_diameter/2, h=height*block_height,$fs=cylinder_precision);
		translate([0,0,-0.5])
            cylinder(r=knob_diameter/2, h=height*block_height+1,$fs=cylinder_precision);

	}
}

module reinforcement(height) {
	union() {
		translate([0,0,height*block_height/2]) union() {
			cube([reinforcing_width,knob_spacing+knob_diameter+wall_thickness/2,height*block_height],center=true);
			rotate(v=[0,0,1],a=90) cube([reinforcing_width,knob_spacing+knob_diameter+wall_thickness/2,height*block_height], center=true);
		}
	}
}

```


[User]
Hey, I'm looking to create some modular, customizable hoops for flying my mini quad through, and I'd like to use OpenSCAD to make the 3D printable parts. I'd like to be able to easily adjust the dimensions for different tubing and such.

Here's the basic idea: I want three different parts: a "Connector" to join the ends of the tubing, a "BatteryTray" to hold a battery for illumination, and a "SurfaceMount" to let me stick these hoops onto flat surfaces. All of them will need to be designed to fit over a dowel of some diameter, and have a certain wall thickness around it.

Here's a breakdown of each part and the parameters I'd like to control:

**Common Parameters:**

*   `holeCenter`: Diameter of the dowel the parts will fit over.
*   `wallThickness`: Thickness of the wall around the dowel hole.
*   `bottom`: Thickness of the top and bottom shells (for the Connector, this is essentially the height of the 'base', for others it's the shell/base thickness)

**Connector Part:**

*   `height`: Height of the connector piece (should be slightly less than the tubing diameter)
*   `holeZip`: Diameter of the hole for zip ties to secure the tubing

**Battery Tray Part:**

*   `batteryLength`: Length of the battery tray.
*   `batteryWidth`: Width of the battery tray.
*    `widthZip`: Width of the zip tie hole to attach the tray to the hoop.
*   `roundness`: Radius for rounded corners on the battery tray.

**Surface Mount Part:**

*   `flapWidth`: Width of the mounting flap (part that rests on the surface).
*   `mountHeight`: Height of the surface mount piece.

For all of these parts, I would like to have `part` as a variable where you can set what you want to display (`Connector`, `BatteryTray`, or `SurfaceMount`).

I'd like it to use `$fn = 64` for smooth curves.

Could you also add some internal variables and calculations based on the provided parameters, for example:

*   `holeRadius` = `holeCenter` / 2
*   `zipRadius` = `holeZip` / 2
* `xOffset` and `yOffset` for positioning the zip tie holes in the connector
* Calculate `totalHeight` ( `height` + `bottom`), `batteryHeight` (`bottom * 2 + zip`), and `batteryWidthOuter` (`batteryWidth + bottom * 2`)

Basically, I want the whole thing to be parametric so that I can adjust all the relevant dimensions and then print the parts. I think that is pretty straightforward but let me know if you need more information. Thanks!

[Assistant]
whoop_hoops.scad:
```
/* [Base] */
// Select part for display, change settings for each part below
part = "Connector"; // [Connector, BatteryTray, SurfaceMount]

// Diameter of your base dowel
holeCenter = 16.2; // [10:0.1:50]
// Thickness of the wall around the dowel
wallThickness = 2; // [0.8:0.1:10]
// Thickness of the top and bottom shells
bottom = 1.4; // [0.5:0.1:10]

/* [Connector] */
// Height should be a little bit less than the diameter of what you are using for the hoop
height = 16; // [5:1:100]
// Hole diameter for zip tie
holeZip = 6; // [0:1:10]

/* [Battery Tray] */
// Length of th battery tray
batteryLength = 15; // [2:1:100]
// Width of the battery tray
batteryWidth = 35; // [10:1:100]
// Width of Zip tie
widthZip = 4; // [0:1:10]
// Roundness of corners
roundness = 2; // [0:1:2]

/* [Surface mount] */
flapWidth = 15; // [5:1:100]
mountHeight  = 10; // [5:1:100]

/* [Hidden] */
connectorHeight = mountHeight;
tapeWidth = flapWidth;
hole = holeCenter;
zip = holeZip;
walls = wallThickness;
$fn = 64;
holeRadius = hole / 2;
zipRadius = zip / 2;
xOffset = 8;
yOffset = 9;

totalHeight = height + bottom;
batteryHeight = bottom * 2 + zip;
batteryWidthOuter = batteryWidth + bottom * 2;

if(part == "Connector") gate();
if(part == "BatteryTray") rotate([0, 0, 180]) battery();
if(part == "SurfaceMount") connector();

module connector() {
    group() {
        hull() {
            cylinder(r = holeRadius + walls, h = bottom);

            translate([tapeWidth + holeRadius + walls - 1, 0, bottom / 2])
                cube([1, 5, bottom], center = true);

            translate([-tapeWidth - holeRadius - walls + 1, 0, bottom / 2])
                cube([1, 5, bottom], center = true);
        }

        difference() {
            cylinder(r = holeRadius + walls, h = bottom + connectorHeight);

            translate([0, 0, bottom])
                cylinder(r = holeRadius, h = bottom + connectorHeight);
        }

        translate([tapeWidth + holeRadius + walls, 0, bottom])
            cube([bottom, 5, bottom * 2], center = true);

        translate([-tapeWidth - holeRadius - walls  , 0, bottom])
            cube([bottom, 5, bottom * 2], center = true);
    }
}

module battery() {
    difference(){
        difference() {
            hull() {
                cylinder(r = holeRadius + walls, h = batteryHeight);

                translate([-batteryWidthOuter / 2, holeRadius + walls, 0])
                    cube([batteryWidthOuter, batteryLength, batteryHeight / 2]);

                translate([-batteryWidthOuter / 2, holeRadius + walls + batteryLength - roundness, bottom * 2 + zip -  roundness])
                    rotate([0, 90, 0])
                        cylinder(r=roundness, h = batteryWidthOuter);

                translate([-batteryWidthOuter / 2, holeRadius + walls, batteryHeight / 2])
                        cube([batteryWidthOuter, batteryLength - roundness, batteryHeight / 2]);
            }

            translate([0, 0, -1])
                cylinder(r = holeRadius, h = totalHeight + 2);

            translate([-batteryWidthOuter / 2 + bottom, holeRadius + walls, bottom])
                    cube([batteryWidth, batteryLength + 1, batteryHeight]);
        }

         translate([0, 0, bottom]) {
             difference() {
                 translate([0, -holeRadius + walls + 1, 0])
                    cylinder(r = holeRadius * 2, h = zip);

                 translate([0, -holeRadius + walls + 1, 0])
                    cylinder(r = holeRadius * 2 - widthZip, h = zip);
            }
        }
    }
}

module gate() {
    difference() {
        hull() {
            cylinder(r = holeRadius + walls, h = totalHeight);

            translate([-xOffset, -yOffset, 0])
                cylinder(r = holeRadius + walls, h = totalHeight);

            translate([xOffset, -yOffset, 0])
                cylinder(r = holeRadius + walls, h = totalHeight);
        }

        translate([0, 0, bottom])
            cylinder(r = holeRadius, h = totalHeight + 2);

        translate([-zipRadius - holeRadius, -holeRadius  + zip / 2 - 0.5, -1])
            cylinder(r = zipRadius, h = totalHeight + 2);

        translate([holeRadius + zipRadius, -holeRadius  + zip / 2 - 0.5, -1])
            cylinder(r = zipRadius, h = totalHeight + 2);

        translate([-50, -100 - holeRadius - walls, bottom])
            cube([100, 100, totalHeight]);
    }
}

```


[User]
I need an OpenSCAD design that can generate a variety of connectors for square tubing. I'm looking for something similar to the "multi-joiners.scad" file I've been using, but I need it to be more flexible and easier to customize.

Specifically, I want to be able to specify:

*   The **outside dimension** of the square tubing.
*   The **wall thickness** of the tubing.
*   The **type of connector**: End cap, Inline coupler ("I"), Elbow ("L"), Tee, Corner (3-way, 90 degree, 2 legs on the same side of the third leg), Cross, Inline with one elbow ("I+L"), or a 6-way connector (like a cube corner with an extra leg going in the opposite direction of the corner).
*   The **amount of corner rounding** on the exposed part of the connector, from no rounding up to maximum rounding using a percentage slider.
*   Whether to add **cross grooves** or **lengthwise ribs** to the retaining tabs (or neither).
*   Whether to add a **relief groove** at the base of the tabs.
*  The **percentage that the connector will be hollowed out.** This should determine the size of the through hole in the connector.

I also need the design to handle:
* A build plate display to be controlled by a "display_build_plate" parameter, which takes the values of "0" or "1", that also displays either Replicator 2, Replicator, or Thingomatic build plate outlines, or a manual setting with editable build plate x and y dimensions.
* Error handling, that displays a message if the parameters are set such that no valid connector would be produced.

The units should be specified as either mm or inch, but the output should always be in mm.

The design uses the standard `polyhedron()`, `minkowski()`, and `linear_extrude()` functions. It also uses the `utils/build_plate.scad` library for the build plate display.

Could you generate the OpenSCAD files for this?

[Assistant]
_multi-joiners.scad:
```
// OpenSCAD file to create custom sized couplers for joining square tubes into
// various shapes.
// Setup for use with Thingiverse.com Customizer.
//
// preview[view:south west, tilt:bottom diagonal]
//
// Created Mar 22, 2017 by Allan M. Estes.
// Reuses earlier code developed for creating custom plugs and inline couplers
// (http://www.thingiverse.com/thing:1757352).
//
// Changes:
// Mar 30, 2017 Added extra length to tab parts to eliminate internal surfaces when
// joined. Simplification of place_tabs module for square tubing (originally derived
// from code supporting rectangular tube).
//
// Note: the +0 added to some assignments is to keep item from showing up in
// Thingiverse Customizer.

$fn=32+0;
// Following are parameter which are configurable through the customizer

// input_units exists to allow mm or inch values to be entered through customizer
// it is currently disabled and set to mm input;
// Units for customizer input (output is always mm)
input_units = 1+0; // [1:mm, 25.4:inch]

// Type of connector to produce
which = 4; // [0:End, 1:I coupler, 2:Ell, 3:Tee, 4:Corner, 5:Cross, 6:I+Ell, 7:6-Way]

// Outside dimension of square tube
tube_size = 25.54;
// How thick is tube wall
tube_wall = 1.67;
// Amount of rounding to apply to edges (0=no rounding, 50=default rounding, 100=maximum rounding)
corner_rounding = 50; // [0:100]

// Add cross grooves to retaining tabs?
groove_tabs = 1; // [0:no, 1:yes]
// Add length-wise ribs to retaining tabs?
ribs = 0; // [0:no, 1:yes]
// Relief groove at base of tabs?
tab_relief = 0; // [0:no, 1:yes]
// Percent block will be hollowed (none to maximum), size of through hole in coupler
recess_pct = 100; // [0:100]

// user control of viewable build plate in Customizer:
// Turn on viewable build plate?
display_build_plate = 0; // [0:no, 1:yes]
use <utils/build_plate.scad>
// For display only, doesn't contribute to final object
build_plate_selector = 3; //[0:Replicator 2,1: Replicator,2:Thingomatic,3:Manual]
// When Build Plate Selector is set to "manual" this controls the build plate x dimension
build_plate_manual_x = 100; //[100:400]
// When Build Plate Selector is set to "manual" this controls the build plate y dimension
build_plate_manual_y = 100; //[100:400]

// list of rotation mamatrices controling the type of joiner being generated.
form = [
    [[0,0,0]],                                              // cap
    [[0,0,0],[0,180.0]],                                    // I
    [[0,0,0],[0,90.0]],                                     // ell
    [[0,0,0],[0,90.0],[0,180,0]],                           // tee
    [[0,0,0],[0,90.0],[90,0,0]],                            // corner
    [[0,0,0],[0,90.0],[0,180,0],[0,270,0]],                 // cross
    [[0,0,0],[0,90.0],[0,180,0],[90,0,0]],                  // inline ell
    [[0,0,0],[0,90,0],[0,180,0],[90,0,0],[0,270,0],[270,0,0]] // nexus
    ];

// sanitize/convert parameters:
tubeSize = abs(tube_size) * input_units;
tubeWall = abs(tube_wall) * input_units;

fillet = tubeWall * (corner_rounding/25); // 0:4, default=2

tabThck = .7*sqrt(tubeSize);
tabLen = tubeSize/3;

// flag problems for bad data, used later to generate error "messages"
bad_wall = tubeWall<1;				// too thin
bad_ratio = tubeSize<2*tubeWall+3*tabThck;	// internal dimensions too small
bad_over = tubeSize>800 || tubeSize>800;	// really determined by build plate
									// it won't break the program but why bother
bad_input = tubeSize==undef || tubeSize==undef || tubeWall==undef || tubeSize==undef;

// Module definitions start here

// generic modules to copy and move (translate) an object or to copy and rotate
module duplicate_t(v) {
    children();
    translate(v) children();
}
module duplicate_r(v1,v2) {
    children();
    rotate(v1,v2) children();
}
// prism routine straight out of OpenSCAD online manual
module prism(l, w, h) {
   polyhedron(
           points=[[0,0,0], [l,0,0], [l,w,0], [0,w,0], [0,w,h], [l,w,h]],
           faces=[[0,1,2,3],[5,4,3,2],[0,4,5,1],[0,3,4],[5,2,1]]
           );
}

// Modules to deal with exposed/visible portion of the coupler (the block) -
// Block is generated from a cube with optionally rounded corners which can be
// hollowed; the amount is controllable and can be reduced down to
// zero (none).

// generate 2d outline used at base of tabs
module block_outline() {
    if (2*fillet>tubeSize) {
        circle(fillet);
    } else if (fillet>0) {
        minkowski() {
            square([tubeSize-(2*fillet),tubeSize-(2*fillet)], true);
            circle(fillet);
        }
    } else
        square([tubeSize,tubeSize], true);
}

// Hollow block by difference with a scaled down clone - maximum hollow must
// fit within tabs and will match with opening(s) created in  final assembly.
module hollow() {
    // keep recess within tabs (2.0* would be flush with tabs, 2.1* adds a tiny edge)
    hpct = (tubeSize-2.1*(tubeWall+tabThck))/tubeSize;  //max
    rhpct =  hpct * recess_pct/100;                     //user adjustment
    difference() {
        children();
        scale(v=[rhpct,rhpct,rhpct]) children();
    }
}

// create block - generated by combining cubes, cylinders, and spheres
module build_block(sz,r) {
    myX = sz-2*r;
    hollow()
    union() {
        for (i=[[0,0,0],[0,90,0],[90,0,0]])
            rotate(i) {
                cube([myX,myX,sz],center=true);
                for (j=[-1,1],k=[-1,1])
                    translate([j*myX/2,k*myX/2,0])
                        cylinder(h=myX,r=r,center=true);
            }
        for (j=[-1,1],k=[-1,1],l=[-1,1])
            translate([j*myX/2,k*myX/2,l*myX/2])
                sphere(r);
        for (j=[-1,1],k=[-1,1])
            translate([j*myX/2,k*myX/2,0])
                cylinder(h=myX,r=r,center=true);
    }
}

// Modules to deal with retaining tabs -
// Grooved tabs are created by building a 2D profile and extruding to "width".
// Ribbed tabs are created from a cube + cylinders. Ribs and grooves are optional.
// The tab tip is beveled to ease insertion into the tube. The tip bevel is only
// generated in the tab_grooves module, but an intersection of the two tab design
// preserves the tip bevel while allowing the groove and rib options to be
// combined. The resulting combined tab has the ends beveled for clearance. Tabs
// for X and Y directions are created, duplicated and moved into position

// tab w/tip bevel and (optional) cross grooves
module tab_grooves(size) {
//    sp = (tabLen-.75*tabThck-tabThck/2.5 )/3; // spacing for grooves
    sp = (tabLen-.75*tabThck-tabThck/5 )/3; // spacing for grooves

    linear_extrude(height=size,center=false)
    difference() {
        polygon([ [-.01,0], [tabLen-.75*tabThck,0], [tabLen,.6*tabThck],
                  [tabLen,tabThck], [-.01,tabThck] ]);
        if (groove_tabs) {
            for (i=[0:2]) {
                translate([tabLen-.75*tabThck-sp/2-i*sp,-sp/3.0])
                circle(sp*.6);
            }
        }
    }
}

// tab w/(optional) vertical ribs. .01 is added to tabLen to prevent
// internal surfaces when joined with main part.
module tab_ribs(size) {
    cnt=floor(size/tabThck/2) + 1;  // how many ribs to produce
    sp = (size-tabThck*2/3)/cnt; // spacing for ribs

    translate([tabLen,tabThck/3,0])
    rotate([0,-90,0]) {
        cube([size,tabThck*2/3,tabLen+.01]);
        if (ribs) {
            for (i=[0:cnt]) {
                translate([tabThck/3+i*sp,0,0])
                cylinder(h=tabLen+.01,r=tabThck/3);
            }
        } else {
            translate([0,-tabThck*2/3,0])
            cube([size,tabThck*2/3,tabLen+.01], center=false);
        }
    }
}

// Module to produce base/reinforcement for tabs. Base is simplification of
// a ribbed tab which is not beveled at ends/edges allowing the bases to
// join in the corners for extra strength. .01 is added to baseLen to prevent
// internal surfaces when joined with main part.
module tab_base(baseLen,size) {
    cnt=floor(size/tabThck/2) + 1;  // how many ribs to produce
    sp = (size-tabThck*2/3)/cnt; // spacing for ribs

    translate([baseLen,tabThck/3+.01,0]) // .01 offset for looser fit
    rotate([0,-90,0]) {
        cube([size,tabThck*2/3,baseLen+.01]);
        for (i=[0:cnt]) { // columns/ribs
            translate([tabThck/3+i*sp,0,0])
            cylinder(h=baseLen+.01,r=tabThck/3);
        }
        translate([0,-tabThck/3,0])
        prism(size, tabThck/3, tabThck/3);
    }
}

// create tab by combining optional styles, beveling ends, and tilting outward a little
module build_tab(size) {
    tabTilt  = min(tubeWall/tabLen/3, .04); // used to be sin(1.5)
    multmatrix([ [1,0,0,0], [-tabTilt,1,0,0], [0,0,1,0], [0,0,0,1] ]) // tilt
    // bevel ends by difference with rotated cubes
    difference() {
        intersection() { // combine the optional tab styles
            tab_grooves(size);
            tab_ribs(size);
        }
        duplicate_t([0,0,size])
        translate([tabLen/2,tabThck,0])
        rotate([45,0,0])
        cube([tabLen*1.1,tabThck,tabThck], true);
    }
}

// generates and places the four retaining tabs
module place_tabs(base) {
    tabWid = tubeSize-2*tubeWall-(tabThck*1.25);
    for (rot=[0,90,180,270]) rotate([0,0,rot])
    translate([tabWid/-2,tubeSize/-2+tubeWall,-base])
    rotate([0,90,0]) {
        if (base) {
            translate([-base,0,0])
            tab_base(base,tabWid);
        }
        build_tab(tabWid);
    }
}

// Create shapes to "subtract" from corner formed by cap and base/tab. The
// intent here is to allow the cap/coupler to sit better against tube even if
// there is some irregularity in the cap printing and/or tube end.
module plug_relief_shapes() {
    cDia = tabThck/4;
    cLen = tubeSize-2*tubeWall-(tabThck*1.25); //+.01;
   for (i=[0,90,180,270])
       rotate(i)
             translate([0,tubeSize/2-tubeWall,0]) {
                rotate([0,90,0])
                    cylinder(h=cLen,d=cDia,center=true);
                           translate([cLen/2-cDia/4,-(tubeSize-cLen)/4-cDia/2,0])
    rotate_extrude(angle=90)
             translate([(tubeSize-cLen)/4+cDia/2,0,0])
                 circle(d=cDia);
                       }
}

module reduce_opening() {
    // trimmed down copy of hollow(), used to reduce shape for creating opening
    // from tabs into block.
    hpctxy = (tubeSize-2.1*(tubeWall+tabThck))/tubeSize * recess_pct/100;
    scale(v=[hpctxy,hpctxy,1]) children();
}

// End of Modules

// Main "code" starts here, first portion deals with customizer errors. To be a little
// more user friendly, generate a "stop sign" with text describing error. In the event
// of multiple errors, just give a single most significant error.

s = 10 + 0; r=s*1.414+s; // points to create octagon
if (bad_input || bad_wall || bad_ratio || bad_over) {
    rotate([90,0,0]) {
        color("red")
        translate([0,0,-.01])
        polygon([[r,s],[s,r],[-s,r],[-r,s],[-r,-s],[-s,-r],[s,-r],[r,-s]]);
        text("Bad Parameters",halign="center",valign="bottom");
        if (bad_input) {
            text("Missing or non-numeric data",halign="center",valign="top");
            //echo("Missing or non-numeric data");
        } else if (bad_ratio) {
            text("Internal dimensions too small",halign="center",valign="top");
            //echo("Internal dimensions too small");
        } else if (bad_wall) {
            text("Wall too thin",halign="center",valign="top");
            //echo("Wall too thin");
        } else if (bad_over) {
            text("Overly large Tube dimensions",halign="center",valign="top");
            //echo("Overly large Tube dimensions");
        }
    }
} else {

    // If all is well, build the joiner ...

    if (display_build_plate)
        build_plate(build_plate_selector,build_plate_manual_x,build_plate_manual_y);

        translate([0,0,tubeSize/2+tabLen*2]) // position object on build plate
        difference() {
            union() {
                for (i=form[which]) // loop through rotation matrix
                    rotate(i)       // a rotation for each side with tabs
                        translate([0,0,-tubeSize/2]) {
                            // following creates "collar" to eliminate round edges
                            linear_extrude(height=fillet, center=false, convexity=10)
                                block_outline();
                            place_tabs(tabLen); // makes the actual joiner part
                        }
                        build_block(tubeSize,fillet); // block at "core" of coupler
            }
            // the following creates the opening into the block for each side w/tabs
            for (i=form[which])
                rotate(i)
                    translate([0,0,-tubeSize/2-.01]) {
                        reduce_opening()
                            linear_extrude(height=tubeSize/2, center=false)
                                    block_outline();
                       if (tab_relief) plug_relief_shapes();
                    }
        }
}
```


[User]
Okay, so I'm looking for a parametric OpenSCAD design for a spoked wheel, kind of like the one I already have, but I need something that's more robust and easier to modify. I've included my existing code, but please do not use it directly, treat it as a reference for requirements.

The basic idea is a wheel with a central hub, a rim, and spokes connecting them. Here's what I need to be able to control:

**Overall Wheel:**
*   **Wheel Diameter:** The overall diameter of the wheel.
*   **Axle Diameter:** The diameter of the hole for the axle. This needs to be a true hole.
*   **Wheel Width:** This will be the overall width of the wheel. I know that width will be the sum of both the hub width and the rim width and that I need to control the hub and rim widths independently.

**Rim:**
*   **Rim Thickness:** How thick the material of the rim is.
*   **Rim Width:** How wide the rim is. This will determine the width of the "tire" area.
*   **Rim Crown:** This is a percentage that controls how much of the rim thickness is curved. This should be a curve, not a flat surface, and needs to handle crown values from 0 to 1.

**Hub:**
*   **Hub Diameter:** The diameter of the hub.
*   **Hub Width:** How wide the hub is.

**Spokes:**
*   **Spoke Thickness:** How thick the spokes are.
*   **Spoke Count:** The number of spokes around the wheel.
*   **Web Ratio:** This should determine the thickness of the material connecting the hub to the rim (the 'web' material). It should be relative to the width of the rim, or the hub, whichever is largest and must work with any width. I am open to alternative ways of achieving the same web ratio.

**Important Considerations:**
*   The Hub diameter should never be less than the axle diameter, and the wheel diameter should never be less than the hub diameter.
*   I'd prefer if the spokes had a 'curved' or 'bulged' look instead of just being straight lines from hub to rim, with the widest part closer to the rim than the hub and have some kind of round end on both the rim and hub side.
*  I'm not going to specify specific radii for the spoke curves; I want that to be auto-generated in a pleasing way using only the given parameters.
*   I'd like to avoid using `linear_extrude`, if possible, as it doesn't look as good when rendering and it is my understanding that rotate_extrude is generally more performant.
*   I would like the code to utilize the `hull` function for the spoke connections where the spokes meet the hub and the rim.
*   I'd like the code to use `rotate_extrude` and `polygon` where appropriate for performance reasons.
*   I'm using this wheel to emulate LEGO parts, so the default sizes should allow this.
*   I need to make sure that the code is performant. I'd like to avoid any unnecessary operations.

Please be sure that the code is clean and well commented. I'd really like to be able to understand it.

I'm not looking to directly import any external libraries, as I want to try to keep the file as simple as possible for now.

Thanks!

[Assistant]
SpokedWheel.scad:
```
//George Miller
//3-24-17
//this is the latest version of my customizable spoked wheel.

//12-5-17 - revising for better preview display

//here are instructions for makign it customizable for thingiverse
// http://customizer.makerbot.com/docs

//user defined variables

/* [Global] */

/* [Wheel] */
//Outer diameter of the wheel
WheelDiameter = 50; //  [10:100]
//Diameter of the axle hole
AxleDiameter = 4.9; //  [1:.1:50]

/* [Rim] */
//Thickness of the wheel Rim.
RimThickness = 2; //  [1:50]
//Width of the wheel Rim.  Or "How wide is the tire:
RimWidth = 6.2; //  [1:.1:50]
//Percent of rim thickness dedicated to a crown.  This is used to calulate the rimthick and rounding of the wheel surface.  A crown helps to minimize contact surface.
Crown = 0.5;  //  [0:.01:1]

/* [Hub] */
//diameter of the wheel Hub
HubDiameter = 10; //  [1:50]
//Width of the wheel Hub
HubWidth = 6.2; //  [1:.1:50]

/* [Spokes] */
//Width of the spokes.
SpokeThick = 1.5; //  [1:.1:10]
//number of spokes for the wheel
SpokeCount = 15;  //  [5:25]
//Percentage of the average wheel width.  This is used to calculate wheel web thickness.
WebRatio = .5;  //  [.1:.01:.99]

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

//safety calculation
HubDia = HubDiameter<AxleDiameter ? AxleDiameter:HubDiameter;
WheelDia = WheelDiameter < HubDia ? HubDia : WheelDiameter;
RimThickn = RimThickness>((WheelDia-HubDia)/4)?((WheelDia-HubDia)/4):RimThickness;

//calculated variables
WRad = WheelDia/2;
ARad = AxleDiameter/2;
Hrad = HubDia/2;
innerRimRad = WRad-RimThickn;

//cut out spokes
union(){
    difference(){
        WheelBlank();
        SpokeCutoutThick = HubWidth>RimWidth?HubWidth+.1:RimWidth+.1;
        translate([0,0,-.01])
        SpokeCutoutArray(Hrad+(2/10)*HubWidth+(SpokeThick/2),innerRimRad-(SpokeThick/2)-(1/10)*RimWidth,SpokeCount,SpokeThick,SpokeCutoutThick);
    }
}

//SpokeCutoutArray();
module SpokeCutoutArray(innerRad=10,outerRad=50,Count=12,Spacing=1,Thick=5){
    linear_extrude(height=Thick, convexity = 10){
        S=sin(180/Count);
        //took a lot to work out this formula
        smallRad = (Spacing-2*S*innerRad)/(2*(S-1));
        largeRad = ((2*S*outerRad)-Spacing)/(2*(1+S));

        RotaryCluster(radius=0,number=Count)
        SpokeCutOut(sRad=smallRad,lRad=largeRad,iRad=innerRad,oRad=outerRad);
    }
}

//SpokeCutOut();
module SpokeCutOut(sRad=2,lRad=4,iRad=4,oRad=30){

    hull(){
    translate([iRad+sRad,0,0])
    circle(sRad);
    translate([oRad-lRad,0,0])
    circle(lRad);
    }
}

//WheelBlank();
module WheelBlank(){
    rotate_extrude(convexity = 10)
    WheelBlankProfile();
}
module WheelBlankProfile(){
    union(){
        //Hub
        translate([0,0,0]){
            //square([Hrad-ARad,HubWidth]);
            A = [ARad,0];
            B = [Hrad+(2/10)*HubWidth,0];
            C = [Hrad,HubWidth];
            D = [ARad,HubWidth];
            polygon([A,B,C,D]);
            }

        //Rim
        translate([WRad-RimThickn,0,0]){
            //square([(3/4)*RimThickn,RimWidth]);
            A = [-(1/10)*RimWidth,0];
            B = [(1-Crown)*RimThickn,0];
            C = [(1-Crown)*RimThickn,RimWidth];
            D = [0,RimWidth];
            polygon([A,B,C,D]);

            translate([(1-Crown)*RimThickn,0,0])
            rotate([0,0,-90])
            intersection(){
                CutCircle(RimWidth*2,(Crown)*RimThickn);
                translate([-RimWidth,0,0])
                square([RimWidth,(Crown)*RimThickn]);
            }
        }

        //Web
        A = [Hrad-.01,0];
        B = [WRad-RimThickn+.01,0];
        C = [WRad-RimThickn+.01,WebRatio*RimWidth];
        D = [Hrad-.01,WebRatio*HubWidth];
        polygon([A,B,C,D]);
    }
}

//CutCircle();
module CutCircle(Width=10,Height=2){
    Height = Height>(Width/2) ? (Width/2):Height;
    halfWidth = Width/2;
    CircleRad = ((Height*Height)+(halfWidth*halfWidth)) / (2*Height);
    intersection(){
        //Arc
        translate([0,Height-CircleRad,0])
        circle(CircleRad);

        //mask
        translate([-halfWidth,0,0])
        square([Width,Height+.01]);
    }
}

//shamelessly stolen from the Openscad Cheatsheet
//https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/User-Defined_Functions_and_Modules#Children
module RotaryCluster(radius=30,number=8)
    for (azimuth =[0:360/number:359])
      rotate([0,0,azimuth])
        translate([radius,0,0])children();

```


[User]
I'm looking to create a customizable linear bushing design in OpenSCAD, and I need help with the code. I want to be able to adjust several parameters, like the inner and outer diameters, the length, the number of perimeters, and the number of teeth around the bushing. Additionally, I want options for adding a chamfer with adjustable radius and angle, and also grooves with configurable length, diameter, and distance between them. The goal is to create a bushing that can be easily customized for different rod and housing sizes with precise control over the print settings, similar to the design I've found on Thingiverse (thing:2202854). I'm particularly interested in ensuring I can control the spacing of the teeth. I'm hoping the code will allow for easy adjustment of these parameters.

To be clear, I'd like the following parameters to be configurable:

*   **inner\_diameter\_in\_millimeter**: The inner diameter of the bushing (rod diameter + tolerance).
*   **outer\_diameter\_in\_millimeter**: The outer diameter of the bushing (housing diameter + tolerance).
*   **bushing\_length\_in\_millimeter**: The length of the bushing.
*   **extrusion\_width\_in\_millimeter**: The width of the extrusion for precise perimeter control.
*   **number\_of\_perimeters**: The number of perimeters to use when printing.
*   **number\_of\_teeth**: The number of teeth/gaps in the bushing structure.
*   **chamfer\_radius\_in\_millimeter**: The radius of the chamfer on the edges.
*   **chamfer\_angle\_in\_degree**: The angle of the chamfer on the edges.
*   **groove\_length\_in\_millimeter**: The length of the groove(s).
*   **groove\_diameter\_in\_millimeter**: The diameter of the groove(s).
*   **groove\_distance\_in\_millimeter**: The distance between the two grooves on the bushing.

It's important that the design creates a solid, printable bushing with these parameters.

I don't have any existing code except for the design's description, so please generate all of it from scratch. I don't need any external libraries or include files.

[Assistant]
customizable_linear_bushing__v1.8.scad:
```
 /*
 * Customizable Linear Bushing - https://www.thingiverse.com/thing:2202854
 * by Dennis Hofmann - https://www.thingiverse.com/mightynozzle/about
 * created 2017-03-25
 * updated 2018-02-15
 * version v1.8
 *
 * Changelog
 * --------------
 * v1.8:
 *      - groove option added
 * v1.7:
 *      - fixed inner diameter
 * v1.6:
 *      - added chamfer angle option
 * v1.5:
 *      - fixed graphic glitch
 * v1.4:
 *      - added chamfer option
 *
 * v1.1-1.3:
 *      - initial model with minor changes in description
 *
 * --------------
 *
 * This work is licensed under the Creative Commons - Attribution - Non-Commercial license.
 * https://creativecommons.org/licenses/by-nc/3.0/
 */

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

/* [Bushing Settings] */

// Equals dr in technical diagrams. Use the outer diameter of the rod + tolerance value of your printer. Do some test prints before print a whole set of bushings. Best way in my case is to choose the bushing where can push it in with some effort on the rod and slide it by hand without any tools. Then slide it back and forth along the rod, until the bushing has the expected behavior. I suggest you not to use any drill or heat. The sliding of the bushing generates enough heat from its friction against the rod. Standard values: 6.00 for LM6(L)UU, 8.00 (default 8.15) for LM8(L)UU, 10.00 for LM10(L)UU, 12.00 for LM12(L)UU, 16.00 for LM16UU, ...
inner_diameter_in_millimeter = 8.15;

// Equals D in technical diagrams. Use the inner diameter of housing where the bushing sits + tolerance value of your printer. Depends on the filament you use and the total wall thickness of the bushings, the bushing shouldn't fit to tight in the housing. Standard values: 12.0 for LM6(L)UU, 15.0 (default) for LM8(L)UU, 19.0 for LM10(L)UU, 21.0 for LM12(L)UU, 28.0 for LM16UU, ...
outer_diameter_in_millimeter = 15.0;

// Equals L in technical diagrams. Standard values: 19.0 for LM6UU, 24.0 for LM8UU, 29.0 for LM10UU, 30.0 for LM12UU, 35.0 for LM6LUU, 37.0 for LM16UU, 45.0 for LM8LUU, 55.0 for LM10LUU, 57.0 for LM12LUU, ...
bushing_length_in_millimeter = 24.0;

// Use a value to get straight primeters without any zigzag between the outer and inner perimeter. I use the value of 0.48 for 0.4 nozzle. This is the auto extrusion width of 0.48 of simplify3d. More information about this in the description of this design on thingiverse.
extrusion_width_in_millimeter = 0.48;

// Use even numbers. E.g. 4 for LM8(L)UU. Don't forget to set the perimeter number in your Slicer!
number_of_perimeters = 4; //[2:2:10]

// Choose a number to provide a gap between the teeth in the bushing. E.g. 8 or 10 for LM8(L)UU. The tooth width depends on the extrusion_width_in_millimeter and the number_of_perimeters.
number_of_teeth = 10; //[3:20]

// Radius of the chamfer. Set a chamfer if you want to insert the rod easier. This also helps if your prints tends to create elephant foot in the first few layers. A value between 0.3 and 1.0 might be a good value. Set a value above 0 might destroy the preview of the bushing. But the resulting stl-File will be correct.
chamfer_radius_in_millimeter = 0.0;

// Set the angle of the chamfer.
chamfer_angle_in_degree = 55.0; // [30:75]

// Equals W in technical diagram. 1.1 for LM6/8(L)UU, 1.3 for LM10/12/13(L)UU, 1.6 for LM16/20(L)UU, 1.85 for LM25/30(L)UU, 2.1 for LM35/40(L)UU, 2.6 for LM50(L)UU, 3.15 for LM60(L)UU
groove_length_in_millimeter = 0;

// Equals D1 in technical diagram. 11.5 for LM6(L)UU, 14.3 for LM8(L)UU, 18.0 for LM10(L)UU, 20.0 for LM12(L)UU, 22.0 for LM13(L)UU, 27.0 for LM16(L)UU, 30.5 for LM20(L)UU, 38.0 for LM25(L)UU, 43.0 for LM30(L)UU, 49.0 for LM35(L)UU, 57.0 for LM40(L)UU, 76.5 for LM50(L)UU, 86.5 for LM60(L)UU
groove_diameter_in_millimeter = 0;

// Equals B in technical diagram. Outer distance between both grooves. 13.5 for LM6UU, 17.5 for LM8UU, 22.0 for LM10UU, 23.0 for LM12UU, 26.5 for LM16UU, 27.0 for LM6LUU, 35.0 for LM8UU, 44.0 for LM10LUU, 46.0 for LM12LUU, 53.0 for LM16LUU
groove_distance_in_millimeter = 0;

/* [Hidden] */
inner_dia = inner_diameter_in_millimeter;
outer_dia = outer_diameter_in_millimeter;
bushing_l = bushing_length_in_millimeter;
extrusion_w = extrusion_width_in_millimeter;
perimeters = number_of_perimeters;
teeth = number_of_teeth;
chamfer_r = chamfer_radius_in_millimeter;
chamfer_h = tan(chamfer_angle_in_degree) * chamfer_r;
groove_l = groove_length_in_millimeter;
groove_dia = groove_diameter_in_millimeter;
groove_d = groove_distance_in_millimeter;

$fn=100;

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

color("HotPink") difference() {
    bushing();
    if(chamfer_r > 0) {
        chamfer_cut();
        translate([0, 0, bushing_l]) {
            rotate([180,0,0]) {
                chamfer_cut();
            }
        }
    }
    groove_cut();
}

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

module bushing() {
    translate([0, 0, bushing_l / 2]) {
        difference() {
            union() {
                difference() {
                    for(tooth = [0 : teeth - 1])
                        rotate([0, 0, 360 / teeth * tooth]) {
                            translate([outer_dia / 4, 0, 0]) {
                                cube([outer_dia / 2, extrusion_w * perimeters, bushing_l + 1], center = true);
                            }
                        }
                        translate([0, 0, -bushing_l / 2-1]) {
                            ring(outer_dia * 2, outer_dia, bushing_l + 2);
                        }
                    }
                    translate([0, 0, -bushing_l / 2 - 1]) {
                        ring(outer_dia, outer_dia - extrusion_w * perimeters * 2, bushing_l + 2);
                    }
            }
            cylinder(d = inner_dia, h = bushing_l + 2, center = true);
            translate([0, 0, bushing_l / 2]) {
                cylinder(d = outer_dia + 1, h = 2, $f = 16);
            }
            translate([0, 0, -(bushing_l / 2 + 2)]) {
                cylinder(d = outer_dia + 1, h = 2, $f = 16);
            }
        }
    }
}

module ring(d1, d2, height) {
    difference() {
        cylinder(d = d1, h = height, center = false);
        cylinder(d = d2, h = height, center = false);
    }
}

module chamfer_cut() {
    union() {
        difference() {
            translate([0, 0, -1]) {
                cylinder(d = outer_dia + 1, h = chamfer_h + 1);
            }
            intersection() {
                cylinder(d = outer_dia, h = chamfer_h);
                cylinder(d1 = outer_dia - chamfer_r * 2, d2 = outer_dia, h = chamfer_h);
            }
        }
        translate([0, 0, -chamfer_h]) {
            intersection() {
                cylinder(d = inner_dia + chamfer_r * 4, h = chamfer_h * 2);
                cylinder(d1 = inner_dia + chamfer_r * 4, d2 = inner_dia, h = chamfer_h * 2);
            }
        }
        translate([0, 0, -10]) {
            cylinder(d = outer_dia + 1, h = 10);
        }
    }
}

module groove_cut() {
    translate([0, 0, bushing_l / 2 - groove_d / 2]) {
        ring(outer_dia + 1, groove_dia, groove_l);
    }
    translate([0, 0, bushing_l - ((bushing_l / 2) - (groove_d / 2) + groove_l)]) {
        ring(outer_dia + 1, groove_dia, groove_l);
    }
}

```


[User]
Okay, I'm looking to design a fully parametric, printable Rubik's cube in OpenSCAD, and I want to make sure it's a significant improvement on my previous attempts. I've got a basic understanding of the mechanics, but I'm struggling with the finer details. My previous version, `rubik_V3.scad`, is included as a reference, but I'd like you to start from scratch with the design.

Here are the main requirements:

**Core Functionality:**

*   **Parametric Design:** Every dimension should be easily adjustable through variables, allowing for different cube sizes, gaps, and corner rounding, etc. I want to easily change the size of the overall cube, individual cubies, gaps, curvature radius of the edge and corners, and even the size of the axles.
*   **Standard 3x3x3:** The cube needs to function as a standard 3x3x3 Rubik's cube.
*   **Printable:** The design needs to be optimized for 3D printing with minimal supports. I really want to make it so that it prints as one object and only needs minimal supports to break it off of the print bed and from the supports that hold the cube together during printing.
*   **Spinning Mechanism:** I need a robust spinning mechanism that is less prone to catching and is smooth. My previous version, although functional, does catch quite a bit and I'm not sure what is causing it.
*   **Lettering/Stickers:** I need to be able to easily add letters or other markings to the faces of the cube. I would like to be able to swap letters for stickers if that makes more sense from a design perspective.
*   **Center Caps:** I want the center cubies to also be "caps" like the edge and corner pieces, but these would need to rotate on the main axles.

**Specific Design Elements (using parameters):**

*   `cube_side_length`: (float) The actual length of one side of a small cube (cubie).
*   `gap_size`: (float) The gap between the spinning parts, to allow for easy rotation.
*   `sphere_radius`: (float) The radius of the central sphere that everything rotates around, and this should be directly calculable based on the cube size.
*    `edge_curve_radius` and `corner_curve_radius`: (float) radius for curvature of the edges and corners. These should be distinct.

**Assembly & Printing:**

*   **Single Print:** Ideally, the entire cube should be printable as a single unit. My `rubik_V3.scad` version was able to achieve this, but the supports are a little hacky.
*  **Breakaway Supports:** Include thin supports to hold everything together during printing that can easily be broken away.
*   **Minimal Supports:** Design the parts to need minimal to no additional support material during printing.

**Libraries:**

*   I would prefer to rely on common and stable OpenSCAD libraries such as BOSL2 for some of the more advanced features. I'm fine if you need to use other common libraries if they are necessary.

**Output:**

*   I'd like the design split into logical modules, which should include:
    *   Core mechanics: central beams, end caps
    *   Cube components: center caps, edges, corners (each parameterized)
    *   Lettering module for engraving or making space for stickers
    *  Printing support module

I'm not too attached to the specific shape of the internal structure (e.g., my "centerBeams()" module); feel free to make any necessary changes to improve stability or printability. If you think it would be beneficial to include features not mentioned here, like magnets to help with alignment, feel free to suggest those.

I'm really looking for a design that not only works but is also a good base for future customization. It would be nice to eventually add things like chamfers and different edge designs. Please let me know if you have any questions about these requirements.

[Assistant]
rubik_V3.scad:
```

//the actual length of one side of each small cube
cle=19.7;
//the gap between spinning parts
gap=0.3;
//the sphere that everything rotates around
sph=23;
// the radius of curvature for the edges of some of the cubes
rad=2.5;

//[hidden]
//the space reserved for each cube
csp=cle+gap;

//stuff for the beams
b_beamR=cle/4;  //beam is half the size of a cube, and radius is half the diameter
b_spinR=1.5*b_beamR; //half of what is left
b_beamL=cle*3+gap*2-2*cle/4; // full length of the cube less half of an endcap

module endCapRotation(radius,height)
{
    hh=height/2;
    rotate_extrude()polygon(points=[[0,hh],[radius-hh,hh],[radius,0],[radius-hh,-hh],[0,-hh]]);
}

module centerBeams()
{

	union()
    {
        // these are the long axles
        rotate(90, [0, 1, 0])cylinder(h = b_beamL, r=b_beamR, center = true);
        rotate(90, [1, 0, 0])cylinder(h = b_beamL, r=b_beamR, center = true);
        rotate(90, [0, 0, 1])cylinder(h = b_beamL, r=b_beamR, center = true);

        // these are the cylinders that the end caps spin around
        translate([ 0, 0, b_beamL/2 ])                       endCapRotation(b_spinR,b_beamR);
        translate([ 0, 0, -b_beamL/2 ])                      endCapRotation(b_spinR,b_beamR);
        translate([ 0,b_beamL/2, 0])   rotate(90, [1, 0, 0]) endCapRotation(b_spinR,b_beamR);
        translate([ 0, -b_beamL/2, 0]) rotate(90, [1, 0, 0]) endCapRotation(b_spinR,b_beamR);
        translate([ b_beamL/2, 0, 0 ]) rotate(90, [0, 1, 0]) endCapRotation(b_spinR,b_beamR);
        translate([ -b_beamL/2, 0, 0 ])rotate(90, [0, 1, 0]) endCapRotation(b_spinR,b_beamR);
        // some support posts for #2 endcap above these will have to be broken off
        translate([0.5,3,-1.5*cle-gap])cube([2,1,4]);
        translate([1.0,1,-1.5*cle-gap])cube([1,5,0.6]);
        translate([0.5,-4,-1.5*cle-gap])cube([2,1,4]);
        translate([1.0,-6,-1.5*cle-gap])cube([1,5,0.6]);
	}

}

module caps()
{
    difference()
    {
        translate([ 0, 0, cle+gap+cle/4 ]) cube([cle, cle, cle/2], center = true);
        // the space for the centerBeam to spin in
        translate([ 0, 0, cle+gap+cle/4 ]) endCapRotation(b_spinR+gap,b_beamR+2*gap);
        translate([ 0, 0, cle+gap+cle/4-b_beamR/2 ]) cylinder(h = b_beamR, r =b_beamR+gap, center = true);
        sphere(r=sph+gap, center = true);
	}
}

module edges()
{
    _lrg=sph*2;
	union()
    {
		difference()
        {
            // this is the rotational sphere
			sphere(r=sph, center = true, $fn=30);
			//less the parts that are on other small cubes
            translate([-_lrg/2+cle/4+gap, 0, 0]) cube([_lrg,_lrg,_lrg], center = true);
			translate([0,-_lrg/2+cle/4+gap , 0]) cube([_lrg,_lrg,_lrg], center = true);
			translate([0, 0, _lrg/2+cle/4-gap]) cube([_lrg,_lrg,_lrg], center = true);
			translate([0, 0, -_lrg/2-cle/4+gap]) cube([_lrg,_lrg,_lrg], center = true);
            //some sloped edges in hope of making it not catch so much while you are turning it
            //translate([cle/2-2.2,cle, -cle/4])rotate([45,0,-20])cube([cle/4+.5,cle/8,1.5],center=true);
            //translate([cle/2-2.2,cle, cle/4])rotate([-45,0,-20])cube([cle/4+.5,cle/8,1.5],center=true);
            //translate([cle,cle/3+1.1, -cle/4+.4])rotate([45,0,-70])cube([cle/4+.5,cle/8,1.5],center=true);
            //translate([cle,cle/3+1.1, cle/4-.4])rotate([-45,0,-70])cube([cle/4+.5,cle/8,1.5],center=true);
            translate([0,cle/2+4,0.7])rotate([0,45,0])cube([cle,cle,1.5],center=true);
            translate([0,cle/2+4,-0.7])rotate([0,-45,0])cube([cle,cle,1.5],center=true);
            translate([cle/2+4,cle/4,cle/4-.4])rotate([45,0,0])cube([cle,cle/2,1.5],center=true);
            translate([cle/2+4,cle/4,-cle/4+.4])rotate([-45,0,0])cube([cle,cle/2,1.5],center=true);
		}
		difference()
        {
            translate([ cle+gap,cle+gap, 0 ]) cube([cle,cle,cle], center = true);
            sphere(r=sph+gap, center = true);
		}
        // this is the actual cube
        translate([cle/2+gap, cle/2+gap, -cle/4+gap]) cube([cle-gap, cle-gap, 2*(cle/4-gap)]);
	}
}

module edge_strength()
{
    translate([cle/2, cle/2-gap, -cle/4+gap]) cube([cle/2*.9, cle/2*.9, cle/2-2*gap]);
}
module corners()
{
    _lrg=sph*2;
	union(){
		difference()
        {
			sphere(r=sph, center = true, $fn=30);
			translate([-_lrg/2+cle/4, 0, 0]) cube([_lrg,_lrg,_lrg], center = true);
			translate([0, -_lrg/2+cle/4, 0]) cube([_lrg,_lrg,_lrg], center = true);
			translate([0, 0, -_lrg/2+cle/4]) cube([_lrg,_lrg,_lrg], center = true);
            //some sloped edges to make it turn better
            translate([5,cle/2+4,5.7])rotate([0,45,0])cube([cle,cle,1.5],center=true);
            translate([12,cle/2,.7])rotate([-45,0,0])cube([cle,cle,1.5],center=true);
            translate([1,cle/2,12])rotate([-45,90,0])cube([cle,cle,1.5],center=true);
		}
		// this is the actual cube
        translate([ cle+gap, cle+gap, cle+gap ]) cube([cle,cle,cle], center = true);
	}

}
module corner_strength()
{
    translate([ cle-cle/3, cle-cle/3, cle-cle/3 ]) cube([cle/3,cle/3,cle/3], center = true);
}

//this is what to remove from the cube to make a G
module letterG()
{
	rotate([90,0,0]){
	  union(){
		translate([2.1, 2.1, 20]) cylinder(r=1.25, h=21, center = true, $fn = 15);
		translate([-2.1, 2.1, 20]) cylinder(r=1.25, h=21, center = true, $fn = 15);
		translate([-2.1, -2.1, 20]) cylinder(r=1.25, h=21, center = true, $fn = 15);
		translate([0, 1.05, 20]) cube([6.7,2.1,21], center = true);
		translate([0, 2.1, 20]) cube([4.2,2.5,21], center = true);
		translate([-1.05, 0, 20]) cube([2.1,6.7,21], center = true);
		translate([-2.1, 0, 20]) cube([2.5,4.2,21], center = true);
        // make a little support for the overhang
		translate([2.5, 0.325, 20 ]) cube([6, 0.75 ,21], center = true);
        translate([8.5, 0.325, 20 ]) cube([4, 0.75 ,21], center = true);
        translate([5, 0.325, 10.5 ]) cube([11, 0.75 ,2], center = true);
        translate([5, 0.325, cle+10.5 ]) cube([11, 0.75 ,2], center = true);
	  }

	  difference(){
		translate([8.7, 8.7, 20]) cube([rad,rad,21], center = true);
		translate([7.35, 7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
	  }

	  difference(){
		translate([-8.7, 8.7, 20]) cube([rad,rad,21], center = true);
		translate([-7.35, 7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
	  }

	  difference(){
		translate([-8.7,-8.7, 20]) cube([rad,rad,21], center = true);
		translate([-7.35,-7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
      }
    }
}

//this is what to remove from the cube to make a E
module letterE()
{
	translate([cle/4+.01,4,-20])  cube([cle/2,3.4,cle+0.02], center=true);
	translate([cle/4+.01,-4,-20])  cube([cle/2,3.4,cle+0.02], center=true);
}

//this is what to remove from the cube to make a B
module letterB()
{
	rotate([0,90,0]){
		difference(){
		translate([-8.7, -8.7, 20]) cube([rad,rad,21], center = true);
		translate([-7.35, -7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
		}

		difference(){
		translate([-8.7, 8.7, 20]) cube([rad,rad,21], center = true);
		translate([-7.35, 7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
		}

		difference(){
		translate([-8.7, 0 , 20]) cube([rad,5,21], center = true);
		translate([-7.35, -rad, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
		translate([-7.35, rad, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
		}

		translate([-rad,4,20])   cylinder(r=1.67,h=21, center=true, $fn=15);
		translate([-rad,-4,20])  cylinder(r=1.67, h = 21, center=true, $fn = 15);
		translate([0,4,20])   cube([5,3.4,21], center = true);
		translate([0,-4,20])  cube([5,3.4,21], center = true);
	}
}

hideAxels=0;
hideX=0; // which layer to hide in the X direction. should be 0 for normal print
hideY=0;
hideZ=0;

if(hideAxels!=1)
centerBeams();

// Center caps
if(hideX!=2 && hideY!=2 && hideZ!=3)
difference(){
    rotate(180, [0, 1, 0]) caps();
    letterE();
}
if(hideX!=2 && hideY!=2 && hideZ!=1)
difference(){
    rotate(90, [0, 0, 1]) caps();
    translate([0,0,40]) letterE();
    // now part of the B so the cap looks the same as those around it
    translate([-cle,0,cle+gap])rotate([0,90,0])
    {
        difference(){
            translate([-8.7, 0 , 20]) cube([rad,5,21], center = true);
            translate([-7.35, -rad, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
            translate([-7.35, rad, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
        }
        difference(){
            translate([-8.7, -8.7, 20]) cube([rad,rad,21], center = true);
            translate([-7.35, -7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
		}

		difference(){
            translate([-8.7, 8.7, 20]) cube([rad,rad,21], center = true);
            translate([-7.35, 7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
		}
    }
}
if(hideX!=2 && hideY!=3 && hideZ!=2)
difference(){
    rotate(90, [1, 0, 0]) caps();
    letterG();
    translate([-cle,-cle-gap,0])rotate([180,-90,0])
    {
        difference(){
            translate([8.7, 8.7, 20]) cube([rad,rad,21], center = true);
            translate([7.35, 7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
        }
    }
}
if(hideX!=2 && hideY!=1 && hideZ!=2)
difference(){
    rotate(90, [-1, 0, 0]) caps();
    translate([0,40,0]) letterG();
    translate([cle,cle+gap,0])rotate([0,-90,0])
    {
        difference(){
            translate([8.7, 8.7, 20]) cube([rad,rad,21], center = true);
            translate([7.35, 7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
        }
    }
}

if(hideX!=1 && hideY!=2 && hideZ!=2)
difference(){
    rotate(90, [0, 1, 0]) caps();
    letterB();
    translate([cle+gap,-cle,0])rotate([180,-90,90])
    {
        difference(){
            translate([8.7, 8.7, 20]) cube([rad,rad,21], center = true);
            translate([7.35, 7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
        }
    }
}
if(hideX!=3 && hideY!=2 && hideZ!=2)
difference(){
    rotate(90, [0, -1, 0]) caps();
    translate([-40,0,0])letterB();
    translate([-cle-gap,cle,0])rotate([180,-90,-90])
    {
        difference(){
            translate([8.7, 8.7, 20]) cube([rad,rad,21], center = true);
            translate([7.35, 7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
        }
    }
    translate([-cle-gap,-cle,0])rotate([180,90,-90])
    {
        difference(){
            translate([8.7, 8.7, 20]) cube([rad,rad,21], center = true);
            translate([7.35, 7.35, 20]) cylinder(r=rad, h=21, center = true, $fn = 15);
        }
    }
}

// Corners
if(hideX!=1 && hideY!=1 && hideZ!=1)
union(){
    difference(){
        rotate(90, [0, 0, 1]) rotate(90, [1, 0, 0]) corners();
        translate([20, 40, 20]) letterG();
        translate([20, 20, 40]) letterE();
        translate([0, 20, 20]) letterB();
    }
    rotate(90, [0, 0, 1]) rotate(90, [1, 0, 0]) corner_strength();
}
if(hideX!=3 && hideY!=1 && hideZ!=1)
union(){
    difference(){
        rotate(180, [0, 0, 1]) rotate(90, [1, 0, 0]) corners();
        translate([-20, 40, 20]) letterG();
        translate([-20, 20, 40]) letterE();
        translate([-40, 20, 20]) letterB();
    }
    rotate(180, [0, 0, 1]) rotate(90, [1, 0, 0]) corner_strength();
}
if(hideX!=3 && hideY!=3 && hideZ!=1)
union(){
    difference(){
        rotate(270, [0, 0, 1]) rotate(90, [1, 0, 0]) corners();
        translate([-20, 0, 20]) letterG();
        translate([-20, -20, 40]) letterE();
        translate([-40, -20, 20]) letterB();
    }
    rotate(270, [0, 0, 1]) rotate(90, [1, 0, 0]) corner_strength();
}

if(hideX!=1 && hideY!=3 && hideZ!=1)
union(){
    difference(){
        rotate(360, [0, 0, 1]) rotate(90, [1, 0, 0]) corners();
        translate([20, 0, 20]) letterG();
        translate([20, -20, 40]) letterE();
        translate([0, -20, 20]) letterB();
    }
    rotate(360, [0, 0, 1]) rotate(90, [1, 0, 0]) corner_strength();
}
if(hideX!=3 && hideY!=1 && hideZ!=3)
union(){
    difference(){
        rotate(90, [0, 0, 1]) rotate(90, [0, 1, 0]) corners();
        translate([-20, 40, -20]) letterG();
        translate([-20, 20, 0]) letterE();
        translate([-40, 20, -20]) letterB();
    }
    rotate(90, [0, 0, 1]) rotate(90, [0, 1, 0])corner_strength();
}

if(hideX!=3 && hideY!=3 && hideZ!=3)
union(){
    difference(){
        rotate(180, [0, 0, 1]) rotate(90, [0, 1, 0]) corners();
        translate([-20, 0, -20]) letterG();
        translate([-20, -20, 0]) letterE();
        translate([-40, -20, -20]) letterB();
    }
    rotate(180, [0, 0, 1]) rotate(90, [0, 1, 0]) corner_strength();
}

if(hideX!=1 && hideY!=3 && hideZ!=3)
union(){
    difference(){
        rotate(270, [0, 0, 1]) rotate(90, [0, 1, 0]) corners();
        translate([20, 0, -20]) letterG();
        translate([20, -20, 0]) letterE();
        translate([0, -20, -20]) letterB();
    }
    rotate(270, [0, 0, 1]) rotate(90, [0, 1, 0]) corner_strength();
}
if(hideX!=1 && hideY!=1 && hideZ!=3)
union(){
    difference(){
        rotate(360, [0, 0, 1]) rotate(90, [0, 1, 0]) corners();
        translate([20, 40, -20]) letterG();
        translate([20, 20, 0]) letterE();
        translate([0, 20, -20]) letterB();
    }
    rotate(360, [0, 0, 1]) rotate(90, [0, 1, 0]) corner_strength();
}

// Edges
if(hideX!=2 && hideY!=1 && hideZ!=1)
union(){
    difference(){
        rotate(90, [0, 0, 1]) rotate(90, [1, 0, 0]) edges();
        translate([0, 40, 20]) letterG();
        translate([0, 20, 40]) letterE();
        translate([-20, 20, 20]) letterB();
    }
    rotate(90, [0, 0, 1]) rotate(90, [1, 0, 0]) edge_strength();
}
if(hideX!=3 && hideY!=2 && hideZ!=1)
union(){
    difference(){
        rotate(180, [0, 0, 1]) rotate(90, [1, 0, 0]) edges();
        translate([-20, 20, 20]) letterG();
        translate([-20, 0, 40]) letterE();
        translate([-40, 0, 20]) letterB();
    }
    rotate(180, [0, 0, 1]) rotate(90, [1, 0, 0]) edge_strength();
}

if(hideX!=2 && hideY!=3 && hideZ!=1)
union(){
    difference(){
        rotate(270, [0, 0, 1]) rotate(90, [1, 0, 0]) edges();
        translate([0, 0, 20]) letterG();
        translate([0, -20, 40]) letterE();
        translate([-20, -20, 20]) letterB();
    }
    rotate(270, [0, 0, 1]) rotate(90, [1, 0, 0]) edge_strength();
}
if(hideX!=1 && hideY!=2 && hideZ!=1)
union(){
    difference(){
        rotate(360, [0, 0, 1]) rotate(90, [1, 0, 0]) edges();
        translate([20, 20, 20]) letterG();
        translate([20, 0, 40]) letterE();
        translate([0, 0, 20]) letterB();
    }
    rotate(360, [0, 0, 1]) rotate(90, [1, 0, 0]) edge_strength();
}
if(hideX!=3 && hideY!=2 && hideZ!=3)
union(){
    difference(){
        rotate(90, [0, 0, 1]) rotate(90, [0, 1, 0]) edges();
        translate([-20, 20, -20]) letterG();
        translate([-20, 0, 0]) letterE();
        translate([-40, 0, -20]) letterB();
    }
    rotate(90, [0, 0, 1]) rotate(90, [0, 1, 0]) edge_strength();
}
if(hideX!=2 && hideY!=3 && hideZ!=3)
union(){
    difference(){
        rotate(180, [0, 0, 1]) rotate(90, [0, 1, 0]) edges();
        translate([0, 0, -20]) letterG();
        translate([0,-20, 0]) letterE();
        translate([-20, -20, -20]) letterB();
    }
    rotate(180, [0, 0, 1]) rotate(90, [0, 1, 0]) edge_strength();
}

if(hideX!=1 && hideY!=2 && hideZ!=3)
union(){
    difference(){
        rotate(270, [0, 0, 1]) rotate(90, [0, 1, 0]) edges();
        translate([20, 20, -20]) letterG();
        translate([20, 0, 0]) letterE();
        translate([0, 0, -20]) letterB();
    }
    rotate(270, [0, 0, 1]) rotate(90, [0, 1, 0]) edge_strength();
}

if(hideX!=2 && hideY!=1 && hideZ!=3)
union(){
    difference(){
        rotate(360, [0, 0, 1]) rotate(90, [0, 1, 0]) edges();
        translate([0, 40, -20]) letterG();
        translate([0,20, 0]) letterE();
        translate([-20, 20, -20]) letterB();
    }
    rotate(360, [0, 0, 1]) rotate(90, [0, 1, 0]) edge_strength();
}

if(hideX!=3 && hideY!=1 && hideZ!=2)
union(){
    difference(){
        rotate(90, [0, 0, 1]) edges();
        translate([-20, 40, 0]) letterG();
        translate([-20, 20, 20]) letterE();
        translate([-40, 20, 0]) letterB();
    }
    rotate(90, [0, 0, 1]) edge_strength();
}

if(hideX!=3 && hideY!=3 && hideZ!=2)
union(){
    difference(){
        rotate(180, [0, 0, 1]) edges();
        translate([-20, 0, 0]) letterG();
        translate([-20,-20, 20]) letterE();
        translate([-40, -20, 0]) letterB();
    }
    rotate(180, [0, 0, 1]) edge_strength();
}

if(hideX!=1 && hideY!=3 && hideZ!=2)
union(){
    difference(){
        rotate(270, [0, 0, 1]) edges();
        translate([20, 0, 0]) letterG();
        translate([20,-20, 20]) letterE();
        translate([0, -20, 0]) letterB();
    }
    rotate(270, [0, 0, 1]) edge_strength();
}

if(hideX!=1 && hideY!=1 && hideZ!=2)
union(){
    difference(){
        rotate(360, [0, 0, 1]) edges();
        translate([20, 40, 0]) letterG();
        translate([20,20, 20]) letterE();
        translate([0, 20, 0]) letterB();
    }
    rotate(360, [0, 0, 1]) edge_strength();
}

// finally some thin supports to hold up the cube, these will have to break away
translate([0,0,-cle/2-gap+gap*.95/2])difference()
{
    cube([cle*3+2*gap-rad*2,cle*3+2*gap-rad*2,gap*0.95],center=true);
    cube([cle*3+2*gap-rad*2-1,cle*3+2*gap-rad*2-1,gap],center=true);
}
translate([0,0,cle/2+gap/2])difference()
{
    cube([cle*3+2*gap-rad*2,cle*3+2*gap-rad*2,gap*0.95],center=true);
    cube([cle*3+2*gap-rad*2-1,cle*3+2*gap-rad*2-1,gap],center=true);
}

```


[User]
Hi, I'm looking to create a parametric fidget cube design in OpenSCAD, kind of like the one I've been using (attached for reference). It's a 2x4 cube arrangement, meaning eight total cubes connected by hinges, and I need it to be fully parametric so I can customize the size, gaps, and text.

Here are the main features I need:

*   **Cube Dimensions:**  The size of the individual cubes should be adjustable via a `cubeSize` parameter.
*   **Hinges:**  The design should include functional hinges connecting the cubes. The hinge pin diameter (`hingeDiameter`) and the gap around the hinge for movement (`hingeGap`) should also be parametric. I'd also like the option to show or hide the hinge geometry with a `hinges` boolean parameter.
*   **Spacing:** There needs to be a parametric gap (`spacing`) between the cubes.
*   **Text Engraving:**  I'd like to engrave text onto the faces of the cube. The text should be configurable for different lines and sizes.  I need parameters for:
    *   `text1`: Top surface, line 1, with `text1_size`
    *   `text2`: Top surface, line 2, with `text2_size`
    *   `text3`: Bottom surface, line 1, left, with `text3_size`
    *   `text4`: Bottom surface, line 1, right, with `text4_size`
    *   `text5`: Bottom surface, line 2, left, with `text5_size`
    *   `text6`: Bottom surface, line 2, right, with `text6_size`
     *  `textDepth`: How deep the text is engraved
*   **Text Fill Modes**: I need a `textFill` parameter with three modes:
    *   `0`: Create an indent for the text.
    *   `1`: Show only the text (essentially text as a solid, hiding the cubes).
    *   `2`: No text.
*   **Rounded Corners**: The cubes should have rounded corners, controlled by a `chamfer` parameter.
*   **Dual Extruder**: I'd like to be able to use this for dual extruders; when `textFill = 1`, the text should be a separate object, for a second color.

I'm picturing the overall structure as a main file that includes a `solidCube` module (a single cube with rounded corners), a `spaceForHingeConnector` module to create the gaps for hinges, a `spaceForOneHinge` module that puts those gaps in the correct places within a single cube, a `completeCube` that has both the solid part and those gaps, a `hinge` module to create a hinge object, a `pair` module to join two cubes together with a hinge, a `cubeSet` module to create the full 2x4 structure and the hinges to join them, and a `textSet` module to create the text geometry. The final cube will be based on `textFill`.

Please generate the necessary OpenSCAD files, and use comments to explain the different parts of the code. I don't have a preference on libraries, so choose ones you're comfortable with if needed. I don't need any specific modules from any libraries, though.

[Assistant]
Fidget_Cube_V12.scad:
```
// for dual extruders what features to show for each color
// show the hinges
//[Multiple Extruders]
hinges=1; // [0:Hide, 1:Show]
//show the cubes
cubes=1;  // [0:Hide, 1:Show]
//if showing text then the rest of it is not shown (hinges and cubes are hidden)
textFill=0;  //  [0:leave indent for text, 1:Show only text, 2:noText]

//[main configurable parameters]
// the size in mm of one of the cubes
cubeSize=20; // [10:1:50]
// how big around the hinge pins are
hingeDiameter=5.0; //[3:0.1:10]
// the gap around the hinges to allow movement, technically half this amount as this is a diameter
hingeGap=0.6; //[0:0.05:2]
// the gap between cubes
spacing=0.5;  //[0.1:0.05:3]
//how deep the text is engraved into the cube
textDepth=1.5; //[0.1:0.5:3]

//[Text]
//top surface line1 (size is height in mm)
text1="THINGIVERSE";
text1_size=8;
//top surface line2
text2="TheFisherOne";
text2_size=8;
//bottom surface line1 left
text3="OPEN";
text3_size=6.5;
//bottom surface line1 right
text4="SCAD";
text4_size=6.5;
//bottom surface line2 left
text5="FIDGET";
text5_size=5.5;
//bottom surface line2 right
text6="CUBE";
text6_size=5.5;

// [Hidden]
//calculated values
holeDiameter=hingeDiameter+hingeGap;
//want to space the 2 hinge connectors evenly across the cube face
// so there are 3 gaps to fill
hingeOffset=(cubeSize-2*holeDiameter)/3;

// does not show up in the final, just enough space for an overlap when removing parts
xtra=0.01;
chamfer=hingeDiameter; // the size of the curved corners of the cubes
$fn=8;

// this makes a rounded corner
module chamferTakeAway(radius,length)
{
    //line it up with the edge that needs to be chamfered
    rotate([0,0,180])translate([-radius,-radius,length/2])
    intersection()
    {
        // take only one quarter of the circle
        translate([radius,radius,0])cube([radius*2,radius*2,length*2],center=true);
        difference()
        {
            // remove a cylinder from a cube, to get the part that needs to be taken away
            cube([radius*2+2*xtra,radius*2+2*xtra,length+2*xtra],center=true);
            cylinder(r=radius, h=length+4*xtra,center=true);

        }
    }
}

// this makes a single cube with 3 chamfered corners but no allocation for the hinges
module solidCube()
{
    difference()
    {
        cube(cubeSize);
        chamferTakeAway(chamfer,cubeSize);
        translate([cubeSize,cubeSize,0])rotate([90,-90,0])chamferTakeAway(chamfer,cubeSize);
        translate([cubeSize,cubeSize,cubeSize])rotate([-90,90,90])chamferTakeAway(chamfer,cubeSize);
    }
}

// this makes a gap in the side of a cube for a single hinge connector to rotate in
// it is not the space for the pin itself
module spaceForHingeConnector()
{
    union()
    {
        translate([-xtra,-xtra,0])cube([holeDiameter+xtra,holeDiameter+xtra,holeDiameter]);
        translate([-xtra,holeDiameter/2,holeDiameter]) rotate([0,90,0]) cube([holeDiameter,holeDiameter,holeDiameter*1.5+xtra]);
        //cylinder(d=holeDiameter, h=holeDiameter+xtra);
        translate([holeDiameter/2,1.5*holeDiameter,0]) rotate([90,0,0]) cube([holeDiameter,holeDiameter,holeDiameter*1.5+xtra]);
        //cylinder(d=holeDiameter, h=holeDiameter+xtra);
    }
}

// this makes space for the hinge pin and the two connectors
module spaceForOneHinge()
{

    union()
    {
        translate([holeDiameter,holeDiameter,-xtra])cylinder(d=holeDiameter, h=cubeSize+2*xtra);
        translate([0,0,hingeOffset]) spaceForHingeConnector();
        translate([0,0,cubeSize-hingeOffset-holeDiameter]) spaceForHingeConnector();
    }
}
//this makes a single cube, with the space removed for the 2 hinges
module completeCube()
{
    if (cubes){
        rotate([90,0,0])difference(){
            solidCube();
            spaceForOneHinge();
            translate([cubeSize,cubeSize,0])rotate([90,-90,0])spaceForOneHinge();
        }
    }
}

//this makes a hinge, with 2 pins and 2 connectors
module hinge()
{
    if(hinges){
        cylinder(d=hingeDiameter, h=cubeSize); //pin1
        translate([2*holeDiameter+spacing,0,0])cylinder(d=hingeDiameter, h=cubeSize); //pin2
        translate([0,0,hingeOffset+holeDiameter/2])rotate([0,90,0])cylinder(d=hingeDiameter, h=holeDiameter+holeDiameter); //connector1
        translate([0,0,cubeSize-hingeOffset-holeDiameter/2])rotate([0,90,0])cylinder(d=hingeDiameter, h=holeDiameter+holeDiameter); //connector2
    };
}
//this makes a pair of cubes joined by a hinge
module pair()
{
    translate([cubeSize+spacing,0,0])completeCube();
    translate([cubeSize-holeDiameter,0,holeDiameter])rotate([90,0,0])hinge();
    translate([cubeSize,0,0])mirror()completeCube();
}
module cubeSet()
{
    // first make 4 pairs of cubes to make 8 cubes in total
    pair();
    translate([0,spacing*3+cubeSize*2,0])mirror([0,1,0]) pair();
    translate([0,2*cubeSize+2*spacing,cubeSize])rotate([0,180,90])pair();
    translate([2*cubeSize+spacing,0,0])mirror()translate([0,2*cubeSize+2*spacing,cubeSize])rotate([0,180,90])pair();
    // then add the 4 hinges that join the 4 pairs
    translate([holeDiameter, -holeDiameter,0])rotate([0,0,90])hinge();
    translate([2*cubeSize+spacing-holeDiameter, -holeDiameter,0])rotate([0,0,90])hinge();
    translate([holeDiameter, -holeDiameter+2*cubeSize+2*spacing,0])rotate([0,0,90])hinge();
    translate([2*cubeSize+spacing-holeDiameter, -holeDiameter+2*cubeSize+2*spacing,0])rotate([0,0,90])hinge();
}
module textSet()
{
    translate([cubeSize*1.6+spacing,cubeSize,textDepth])rotate([180,0,90])linear_extrude(height=textDepth+1) text(text=text1, size=text1_size, halign="center", valign="center");

    translate([cubeSize*0.4,cubeSize,textDepth])rotate([180,0,90])linear_extrude(height=textDepth+1) text(text=text2, size=text2_size, halign="center", valign="center");

    translate([cubeSize*1.4+spacing,2*cubeSize+2*spacing+hingeDiameter/2,cubeSize-textDepth])rotate([180,180,90])linear_extrude(height=textDepth+1) text(text=text3, size=text3_size, halign="center", valign="center");

    translate([cubeSize*1.4+spacing,-hingeDiameter/2,cubeSize-textDepth])rotate([180,180,90])linear_extrude(height=textDepth+1) text(text=text4, size=text4_size, halign="center", valign="center");

    translate([cubeSize*0.6+spacing,2*cubeSize+2*spacing+hingeDiameter/2,cubeSize-textDepth])rotate([180,180,90])linear_extrude(height=textDepth+1) text(text=text5, size=text5_size, halign="center", valign="center");

    translate([cubeSize*0.6+spacing,-hingeDiameter/2,cubeSize-textDepth])rotate([180,180,90])linear_extrude(height=textDepth+1) text(text=text6, size=text6_size, halign="center", valign="center");
}

if (textFill==1)
{
    translate([0,cubeSize,cubeSize])rotate([0,180,0])intersection()
    {
        textSet();
        cubeSet();
    }
    //this post is there just to make sure that every layer has some of each color
    // had problems printing without this
    //translate([5,5,0])cylinder(r=2.5, h=cubeSize);
} else if(textFill==0) {
    translate([0,cubeSize,cubeSize])rotate([0,180,0])difference()
    {
        cubeSet();
        textSet();
    }
} else {
    cubeSet();
}

```


[User]
Hey, I'm looking to design an endstop mount for my delta 3D printer, and I'd like to use OpenSCAD to make it fully parametric. I've got a pretty clear idea of what I want, so let me describe it.

The mount needs to have a base, with a space to attach a standard microswitch. It’s going to be triggered by a wheel on the carriage passing by. The base should have a rectangular shape with rounded corners, and I'll need to be able to define the width, length, and thickness of this base. I'd also like a fillet radius for those rounded corners.

The switch mounts onto two arms that extend from the base, and these arms should have some offset from the edges of the base to give some clearance from bolt heads on the carriage. These arms need to support the switch body and have nubs to help align the switch mounting holes. The switch itself will have its own dimensions that will need to be parameterized - things like length, width, and thickness, plus the bolt hole spacing, and diameter of the holes.

To hold the switch securely, I'd like to include a channel for a cable tie, going around the switch body. This channel should be curved on one side and straight on the other, and I will need to define the thickness and width of the channel. Also, I'd like the cable tie channel to end before the switch body.

I'm planning on bolting the mount to the carriage, so I'll need to define the bolt hole and counterbore diameters and thicknesses for the base.

Finally, the nubs that align the switch should be spheres that extend a defined amount, and the sphere should be determined by the bump angle, with a way to set the diameter of the switch bolt holes for the bump position calculations.

I'd also like to be able to generate a mirrored version of the part for the other side of the carriage. I've got some example code I can share if you'd like, but I think this is a fairly straightforward design. It makes use of the `drawPath.scad` library, as well as the `rotate_extrude.scad` library - both from my local file structure. Thanks!

[Assistant]
endstop_mount_final.scad:
```
//use <../Libraries/drawPath.scad>

// Width of base
wBase = 20;
// Length of base
lBase = 25;
// Thickness of base
thBase = 5;
// Fillet radius
rFillet = 1;
// Thickness under bolt head
thBoltBase = 3;

// Length of switch body
lSwitch = 20;
// Width of switch body
wSwitch = 10;
// Thickness of switch body
thSwitch = 6;
// Distance between centers of holes on switch
boltSpacing = 9.57;
// Diameter of bolt holes on switch
dSwitchBolt = 2.5;

// Switch holding arms are offset from edge of the base to give better clearance from potential bolt heads on carriage
clearanceOffset = 2;

// Diameter of counterbore for bolt head
dBoltHead = 10;
// Diameter of bolt hole
dBolt = 5.3;

// Thickness of cable tie channel
thCableTie = 1.5;
// Width of cable tie channel
wCableTie = 5;

// Angle in degreess between bump edge and surface, around the edge of the bump
bumpAngle = 45;

// Width across flats of nut
//wSwitchNut = 4;

/* [Hidden] */
$fa=2;
$fs=0.1;
err = 0.002;

// calculated
h0 = 10-thSwitch/2;
y0 = wSwitch+rFillet*2;

// distance from bolt counterbore to edge
wBoltPadding = (lBase-wSwitch-dBoltHead)/2;
// width of "pad" profile that is extruded to form the switch-holding arms
wPad = (wBase-thSwitch)/2-clearanceOffset;

// diameter of sphere used to make bump for the given bumpAngle
dSphere = dSwitchBolt/cos(bumpAngle);
// distance to set sphere into the arms for the given bumpAngle
sphereInset = dSphere*sin(bumpAngle)/2;

// Max width across points of nut
//dSwitchNut = 2*(wSwitchNut)/sqrt(3); // M2

endstopMount();

// display switch body mockup
//translate([(wBase+thSwitch-err)/2,0,thBase]) rotate([0,-90,0]) microswitch();

module endstopMount() {
  difference() {
    union() {
      linear_extrude(thBase, convexity=2) {
        baseProfile();
      }

      // right brace
      translate([wBase-wPad-clearanceOffset,0,thBase-err]) {
        linear_extrude(lSwitch-wPad+thCableTie, convexity=2) padProfile();
        translate([0,wSwitch,lSwitch-wPad+thCableTie]) rotate([90,0]) rotate_extrude2(angle=90, convexity=2)
          padProfile();
      }

      // left brace
      translate([clearanceOffset,0,thBase-err]) {
        linear_extrude(lSwitch-wPad+thCableTie, convexity=2) mirror([1,0]) translate([-wPad,0]) padProfile();
        translate([wPad,wSwitch,lSwitch-wPad+thCableTie]) rotate([90,0]) rotate(180) rotate_extrude2(angle=-90) padProfile();
      }

      // nubs for lining up holes
      for (i=[-1,1], j=[-1,1])
        translate([wBase/2+j*(thSwitch/2+sphereInset),wSwitch-(1.25+dSwitchBolt/2),thBase+lSwitch/2+i*boltSpacing/2])
          sphere(d=dSphere);
    }
    translate([wBase/2,lBase-wBoltPadding-dBoltHead/2])
      mountBolt();

    // radius'd cable tie channel
    r1 = wPad-thCableTie;
    translate([wPad+clearanceOffset+err,wCableTie/2+wSwitch/2,r1+thBase])rotate([90,0])
      rotate(180) rotate_extrude2(angle=90, convexity=2) translate([r1,0]) square([thCableTie,wCableTie]);
    // straight through cable tie channel
    translate([wBase-wPad-clearanceOffset,wCableTie/2+wSwitch/2,thBase])
      rotate([0,90,0]) translate([0,-wCableTie,-err]) linear_extrude(wPad+2*err, convexity=2)
        square([thCableTie,wCableTie]);
  }
}

module baseProfile() {
  path = concat(
    arcPath(r=rFillet,angle=90,c=[wBase-rFillet,lBase-rFillet]),
    arcPath(r=rFillet,angle=90,offsetAngle=90,c=[rFillet,lBase-rFillet]),
    arcPath(r=rFillet,angle=90,offsetAngle=180,c=[rFillet,wSwitch+rFillet]),
    arcPath(r=rFillet,angle=-90,offsetAngle=90,c=[clearanceOffset-rFillet,wSwitch-rFillet]),
    arcPath(r=rFillet,angle=90,offsetAngle=180,c=[clearanceOffset+rFillet,rFillet]),
    arcPath(r=rFillet,angle=90,offsetAngle=270,c=[clearanceOffset+wPad-rFillet,rFillet]),
    arcPath(r=rFillet,angle=-90,offsetAngle=180,c=[clearanceOffset+wPad+rFillet,wSwitch-rFillet]),
    arcPath(r=rFillet,angle=-90,offsetAngle=90,c=[wBase-wPad-clearanceOffset-rFillet,wSwitch-rFillet]),
    arcPath(r=rFillet,angle=90,offsetAngle=180,c=[wBase-wPad-clearanceOffset+rFillet,rFillet]),
    arcPath(r=rFillet,angle=90,offsetAngle=270,c=[wBase-clearanceOffset-rFillet,rFillet]),
    arcPath(r=rFillet,angle=-90,offsetAngle=180,c=[wBase-clearanceOffset+rFillet,wSwitch-rFillet]),
    arcPath(r=rFillet,angle=90,offsetAngle=270,c=[wBase-rFillet,wSwitch+rFillet])

  );
  polygon(path);
}

module padProfile() {
  path = square([wPad,wSwitch],r=rFillet);
  difference() {
    polygon(path);
    translate([wPad-thCableTie,wSwitch/2-wCableTie/2]) square([thCableTie+err,wCableTie]);
  }
}

module microswitch() {
  color([0.3,0.3,0.3,0.5+err]) linear_extrude(thSwitch-2*err) difference() {
    square([lSwitch,wSwitch]);
    bolts2d();
  }
}

module mountBolt() {
  translate([0,0,thBase-thBoltBase])
    cylinder(d=dBoltHead,h=thBase);
  translate([0,0,-1])
    cylinder(d=dBolt,h=thBase);
}

module bolts2d() {
  for (i = [-1,1]) {
    translate([lSwitch/2+i*boltSpacing/2,wSwitch-(1.25+dSwitchBolt/2)])
      circle(d=dSwitchBolt);
  }
}

module nuts2d() {
  for (i = [0,1]) {
    translate([5.15+i*boltSpacing,wSwitch-(1.25+dSwitchBolt/2)])
      rotate(0) circle(d=dSwitchNut,$fn=6);
  }
}

// use <../Libraries/rotate_extrude.scad>;

// older versions of OpenSCAD do not support "angle" parameter for rotate_extrude
// this module provides that capability even when using older versions (such as thingiverse customizer)
module rotate_extrude2(angle=360, convexity=2, size=max(wBase+2,lBase+2)/2) {

  module angle_cut(angle=90,size=1000) {
    x = size*cos(angle/2);
    y = size*sin(angle/2);
    translate([0,0,-size])
      linear_extrude(2*size) polygon([[0,0],[x,y],[x,size],[-size,size],[-size,-size],[x,-size],[x,-y]]);
  }

  // support for angle parameter in rotate_extrude was added after release 2015.03
  // Thingiverse customizer is still on 2015.03
  angleSupport = (version_num() > 20150399) ? true : false; // Next openscad releases after 2015.03.xx will have support angle parameter
  // Using angle parameter when possible provides huge speed boost, avoids a difference operation

  if (angleSupport) {
    rotate_extrude(angle=angle,convexity=convexity)
      children();
  } else {
    rotate([0,0,angle/2]) difference() {
      rotate_extrude(convexity=convexity) children();
      angle_cut(angle, size);
    }
  }
}

// use <../Libraries/drawPath.scad>

// based on get_fragments_from_r documented on wiki
// https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language
function fragments(r=1) = ($fn > 0) ?
  ($fn >= 3 ? $fn : 3) :
  ceil(max(min(360.0 / $fa, r*2*PI / $fs), 5));

// Draw a circular arc with center c, radius r, etc.
// "center" parameter centers the sweep of the arc about the offsetAngle (half to each side of it)
// "internal" parameter does enables polyhole radius correction
function arcPath(r=1, angle=360, offsetAngle=0, c=[0,0], center=false, internal=false) =
  let (
    fragments = ceil((abs(angle) / 360) * fragments(r,$fn)),
    step = angle / fragments,
    a = offsetAngle-(center ? angle/2 : 0),
    R = internal ? r / cos (180 / fragments) : r,
    last = (angle == 360 ? 1 : 0)
  )
  [ for (i = [0:fragments-last] ) let(a2=i*step+a) c+R*[cos(a2), sin(a2)] ];

function circle(r=1, c=[0,0], internal=false, d) =
  let(r1 = d==undef ? r : d/2)
  arcPath(r=r1,c=c,angle=-360,internal=internal);

function square(size=1, center=false,r=0) =
  let(
    x = len(size) ? size.x : size,
    y = len(size) ? size.y : size,
    o = center ? [-x/2,-y/2] : [0,0],
    d = r*2
  )
  //assert(d <= x && d <= y)
  translate(o,
    (r > 0 ?
      concat(
        arcPath(r=r, angle=-90, offsetAngle=0,   c=[x-r,  r]),
        arcPath(r=r, angle=-90, offsetAngle=270, c=[  r,  r]),
        arcPath(r=r, angle=-90, offsetAngle=180, c=[  r,y-r]),
        arcPath(r=r, angle=-90, offsetAngle=90, c=[x-r,y-r])
      ) :
      [[0,0],[0,y],[x,y],[x,0]]
    )
  );

function translate(v, points) = [for (p = points) p+v];

function scale(v=1, points) = let(s = len(v) ? v : [v,v,v])
  len(points) && len(points[0]) == 3 ?
    [for (p = points) [s.x*p.x,s.y*p.y,s.z*p.z]] :
    [for (p = points) [s.x*p.x,s.y*p.y]];

// rotate a list of 3d points by angle vector v
// vector v = [pitch,roll,yaw] in degrees
function rotate(v, points) =
  let(
    lv = len(v),
    V = (lv == undef ? [0,0,v] :
      (lv > 2 ? v :
        (lv > 1 ? [v.x,v.y,0] :
          (lv > 0 ? [v.x,0,0] : [0,0,0])
        )
      )
    ),
    cosa = cos(V.z), sina = sin(V.z),
    cosb = cos(V.y), sinb = sin(V.y),
    cosc = cos(V.x), sinc = sin(V.x),
    Axx = cosa*cosb,
    Axy = cosa*sinb*sinc - sina*cosc,
    Axz = cosa*sinb*cosc + sina*sinc,
    Ayx = sina*cosb,
    Ayy = sina*sinb*sinc + cosa*cosc,
    Ayz = sina*sinb*cosc - cosa*sinc,
    Azx = -sinb,
    Azy = cosb*sinc,
    Azz = cosb*cosc
  )
  [for (p = points)
    let( pz = (p.z == undef) ? 0 : p.z )
    [Axx*p.x + Axy*p.y + Axz*pz,
     Ayx*p.x + Ayy*p.y + Ayz*pz,
     Azx*p.x + Azy*p.y + Azz*pz]
  ];

// rotate about z axis without adding 3rd dimension to points
function rotate2d(a=0, points) =
  let(
    cosa = cos(a),
    sina = sin(a)
  )
  [for (p = points) [p.x * cosa - p.y * sina,p.x * sina + p.y * cosa]];

```