[User]
I'm looking to create a customizable box using OpenSCAD, and I want to make sure I can easily adjust different parameters. The box should have a base and a lid, and I'd like the ability to choose whether to generate just the base, just the lid, or both.

Here are the specific parameters I need:

*   **`auswahl`:** An integer choice for which parts to generate. 1 should generate both the base and lid, 2 should generate only the base, and 3 should generate only the lid.
*   **`breite`:** The width of the box, in millimeters. This should be adjustable.
*   **`tiefe`:** The depth of the box, in millimeters. Also adjustable.
*   **`hoehe`:** The height of the base, in millimeters, which I need to be able to modify.
*  **`wand_dicke`:** The wall thickness of both the base and the lid. I'd like to set this in millimeters.
*   **`boden_dicke`:** The thickness of the base floor, in millimeters.
*   **`text`:** A string of text to emboss on both the base and the lid.

The lid height should be related to the base floor thickness, specifically, the lid height (`deckel_hoehe`) should be four times the `boden_dicke`. Both the base and lid should have the specified text embossed on them.

Could you generate an OpenSCAD file that takes these parameters and generates the base and/or lid as requested? I don't need any external libraries for this design.

[Assistant]
box.scad:
```
//    einfache Box (konfigurierbar)
//   Henry Piller 2017
// variable description
// Auswahl
auswahl = 1; // [1:Beides ,2:Unterteil ,3:Deckel]
//Beite in mm
breite= 90;//[10:100]
//Tiefe  in mm
tiefe = 60;//[10:100]
//Hoehe in mm
hoehe = 35;//[10:200]
// Wanddicke
wand_dicke = 1.2; // [1.2,1.6,2,2.4,2.8]
// Bodenstaerke (=1/3 Deckelhoehe)
boden_dicke = 1.6; // [1.2,1.6,2,2.4,2.8]
// Deckel und Bodenbeschriftung
text = "BOX";
//   /* [Hidden] */
deckel_hoehe = boden_dicke*4;
text_laenge=len(text);
if (auswahl==2) {unterteil();}
if (auswahl==3) {deckel();}
if (auswahl==1) {unterteil();deckel();}
// unterteil
module unterteil() {
rotate([0,0,0]){
translate([5, 0, 0]) {
difference()
    {
      cube([breite,tiefe,hoehe], center=false);
 translate([breite/2-text_laenge*4,tiefe/2-5,boden_dicke-1]){
     linear_extrude(boden_dicke+2) {
       text(text);
    }
}
    translate([wand_dicke,wand_dicke,boden_dicke])
      cube([breite-2*wand_dicke,tiefe-2*wand_dicke,hoehe+0.1], center=false);
}}}}
 //deckel
module deckel() {
rotate([0,0,0]){
translate([-breite-15, 0, 0]) {
difference()
    {
      cube([breite+2*wand_dicke,tiefe+2*wand_dicke,deckel_hoehe], center=false);
 translate([breite/2-text_laenge*4,tiefe/2-5,boden_dicke-1]){
     linear_extrude(boden_dicke+2) {
       text(text);
    }
}
    translate([wand_dicke,wand_dicke,boden_dicke])
      cube([breite,tiefe,deckel_hoehe+0.1], center=false);
}}}}
```


[User]
I'd like a customizable OpenSCAD file for a drinking cup, please. I need to be able to set the top diameter, the bottom diameter, the height, and the wall thickness. I'd also like to be able to add a name or word to the bottom of the cup. The name should be embossed, not engraved - so it sticks out. For the basic shape of the cup, I'd like a cylinder for the bottom that smoothly transitions to another cylinder at the top using the provided diameters. The cup will have a flat bottom.

I'm not sure of all of the parameters, but here are some specific values that I think would work as a starting point: a top diameter of 60mm, a bottom diameter of 40mm, a height of 85mm, a wall thickness of 3mm. The text I'd like to put on the bottom of the cup is "Henry". And finally, I'd like a 'rounding' factor to be adjustable, maybe a value from 6 to 100. I'm not sure what that means, but the provided example code seemed to use that value to control the cylinder's smoothness. I'd like the text to sit 5mm below the bottom edge of the cup and extend out by 1mm from the bottom.

[Assistant]
becher.scad:
```
//  Becher (konfigurierbar)
//   Henry Piller 2017
// variable description
//Durchmesser oben in mm
durchm2 = 60; //[40:100]
//Durchmesser unten in mm
durchm1 = 40; //[30:100]
//Hoehe in mm
    hoehe = 85; //[10:200]
// Bodenbeschriftung
text = "Henry";

// /* [Hidden] /*

boden_dicke = 5; // [3,4,5,6]
rund_stufe = 15; // [6,8,15,100]
// Wanddicke
wand_dicke = 3; // [2,3,4,5]
text_laenge=len(text);
$fn = rund_stufe;
// beginn
    difference()
  {
      cylinder(hoehe+boden_dicke, r1 = durchm1/2+2*wand_dicke, r2 = durchm2/2+2*wand_dicke, center = false);
 translate([-text_laenge*4,-5,1]){     linear_extrude(hoehe+boden_dicke) {
       text(text);     }
   }
   translate([0,0,boden_dicke]){
    cylinder(h = hoehe+boden_dicke, r1 = durchm1/2+wand_dicke, r2 = durchm2/2+wand_dicke, center = false);
}}
```


[User]
I'd like to design a custom nameplate for my Pelican 1510 case, and I'd like to do it parametrically in OpenSCAD. I need a base plate that fits the recessed area on the top of the case, and then I want to be able to put two lines of text on it. The first line is usually a name, and the second a phone number or email.

I want to be able to control the font size for both lines of text independently, and also be able to align it to the left, right or center. Also, I need to be able to shift the text around in X and Y individually for each line, so I can position it exactly where I want it. Finally, I'd like an option to extrude the text, or create an inverted version. If I choose to invert the text, the base plate should be extruded and the text should be cut out from it. If I don't invert it, the text is extruded and sits on top of the base. I think that is all that is needed.

[Assistant]
twolineswinvert.scad:
```
//text to print
textline1 = "Your Name";
textline2 = "youremailhere@gmail.com";

//font size
fontsize1 = 8 ;
fontsize2 = 6.5 ;
//alignment
halign = "center"; // [left, right, center]

//extrude amount
//Set to 2 for inverted text
extrude = 3 ;

//invert text
invert = "no" ; // [yes, no]

/* [Move Text] */
//X shift
x1 = 18 ;
x2 = 1 ;
//Y shift
y1 = 7 ;
y2 = -3 ;

module object1() {polyhedron(
 points=[[115.99429842792378,25.64023577185051,5.009266748153425e-30],[0.49429842792365203,0.6402357718505005,5.009266748153425e-30],[0.49429842792365203,25.64023577185051,5.009266748153425e-30],[0.49429842792365203,0.6402357718505005,5.009266748153425e-30],[115.99429842792378,25.64023577185051,5.009266748153425e-30],[115.99429842792378,0.6402357718505005,5.009266748153425e-30],[113.9942984279238,0.6402357718505005,3.2499999999999996],[2.244079131527713,23.640235771850513,3.2499999999999996],[2.244079131527713,0.6402357718505005,3.2499999999999996],[2.244079131527713,23.640235771850513,3.2499999999999996],[113.9942984279238,0.6402357718505005,3.2499999999999996],[113.9942984279238,23.640235771850513,3.2499999999999996],[115.99429842792378,25.64023577185051,5.009266748153425e-30],[113.9942984279238,0.6402357718505005,3.2499999999999996],[115.99429842792378,0.6402357718505005,5.009266748153425e-30],[113.9942984279238,0.6402357718505005,3.2499999999999996],[115.99429842792378,25.64023577185051,5.009266748153425e-30],[113.9942984279238,23.640235771850513,3.2499999999999996],[2.244079131527713,23.640235771850513,3.2499999999999996],[115.99429842792378,25.64023577185051,5.009266748153425e-30],[0.49429842792365203,25.64023577185051,5.009266748153425e-30],[115.99429842792378,25.64023577185051,5.009266748153425e-30],[2.244079131527713,23.640235771850513,3.2499999999999996],[113.9942984279238,23.640235771850513,3.2499999999999996],[113.9942984279238,0.6402357718505005,3.2499999999999996],[0.49429842792365203,0.6402357718505005,5.009266748153425e-30],[115.99429842792378,0.6402357718505005,5.009266748153425e-30],[0.49429842792365203,0.6402357718505005,5.009266748153425e-30],[113.9942984279238,0.6402357718505005,3.2499999999999996],[2.244079131527713,0.6402357718505005,3.2499999999999996],[2.244079131527713,23.640235771850513,3.2499999999999996],[0.49429842792365203,0.6402357718505005,5.009266748153425e-30],[2.244079131527713,0.6402357718505005,3.2499999999999996],[0.49429842792365203,0.6402357718505005,5.009266748153425e-30],[2.244079131527713,23.640235771850513,3.2499999999999996],[0.49429842792365203,25.64023577185051,5.009266748153425e-30] ],
faces=[[0,1,2],[3,4,5],[6,7,8],[9,10,11],[12,13,14],[15,16,17],[18,19,20],[21,22,23],[24,25,26],[27,28,29],[30,31,32],[33,34,35]]);}

module textArea(){
translate( [20,5,1.8])
    rotate([0,0,180])
    translate([-92,-13,0])
    linear_extrude(extrude)
    union(){
  translate([x1,y1,0])
text(textline1, fontsize1, halign) ;
  translate([x2, y2, 0])
text(textline2, fontsize2, halign) ;
}
}

module finished(){
    scale([1.03,1, 1])
    if (invert == "yes") {
    difference(){
        object1();
        textArea();
}
}   else union() {
        textArea();
        object1();
}
}

finished();

```


[User]
I'm looking to design a custom vaping drip tip and would like your help generating the OpenSCAD code for it. The design should be parametric, allowing me to easily adjust various dimensions. I need three main components: the drip tip itself, a cap for the drip tip, and a 90-degree adapter for horizontal vaping.

Here are the specific details for each component, and some general requirements. I'd like to use the BOSL2 library, if that's alright.

**Drip Tip:**

*   **Outer Diameter:** The outer diameter of the main body of the drip tip should be adjustable via a `DripOuterDiameter` variable.
*   **Height:**  The total height of the drip tip should be controlled by a `DripHeight` parameter.
*   **Inner Hole Diameter:** There should be an adjustable inner hole, specified by a `InnerHoleDripDiameter` parameter.
*   **Fillet:** A rounded fillet should be applied at the top edge of the drip tip, and its radius should be determined by a `Fillet` variable.
*   **Insertion Rings:** The drip tip needs an insertion section with multiple rings.
    *   The number of rings is controlled by a `Rings` parameter.
    *   The height of each ring is controlled by `RingHeight`.
    *   The inner diameter of the rings is controlled by `InnerMINDiameter` and `InnerMAXDiameter` which may be different, creating a slight taper.
*   **General Inner Hole:** The general inner hole through the whole component is controlled by `InnerHoleDiameter` - this might be smaller than `InnerHoleDripDiameter`.
*   **Facets:** The number of facets for curves should be controlled by a `Facets` parameter.

**Cap:**

*   **Height:** The cap's height is adjustable with a `CapHeight` parameter.
*   **Radius:** The cap should fit over the drip tip, and so its radius should be calculated automatically based on `DripOuterDiameter` and `InnerHoleDripDiameter` and perhaps a bit of clearance, but I'd like a `Tolerance` variable added that could be set to a typical value like 0.2mm.

**90-Degree Adapter:**

*   This adapter should allow the drip tip to connect at a 90-degree angle, making it suitable for "horizontal" vaping.
*   The internal dimensions of the adapter should match those of the drip tip, specifically `InnerHoleDripDiameter` and it should accommodate the `Tolerance` variable.
*   The adapter should have two openings, one for the drip tip and one that will attach to a vape.
*   I'm expecting to need to use some kind of `intersection` method to create the 90-degree turn, and there should be a smooth transition.

**General Parameters:**

*   **Tolerance:** As mentioned, a `Tolerance` parameter should be used in calculations for fitting parts together, with a default value around 0.2 mm.
*   **Booleans:** I'd like a boolean option to generate each of the three components or none, using `MakeDrip`, `MakeCap`, and `MakeAdapter90`, set to 1 to generate the part and 0 not to.

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

[Assistant]
Vaping_drip.scad:
```
/*[Vaping drip generator]*/
// By MagoNegro JUN-2017

// Drip outer diameter:
DripOuterDiameter=17;
// Drip height:
DripHeight=13;
// Drip internal hole diameter:
InnerHoleDripDiameter=12;
// Fillet radius:
Fillet=1;
// Cap height:
CapHeight=10;
// Inner hole diameter:
InnerHoleDiameter=8;
// Number of rings (for insert):
Rings=5;
// Ring height (InsertHeigh=Rings*RingHeight):
RingHeight=1;
// Max inner diameter for the rings:
InnerMAXDiameter = 12.7;
// Min inner diameter for the rings:
InnerMINDiameter = 12.7;
// Facets:
Facets=60; // [4:100]
// Tolerance (Typ. 0.2mm)
Tolerance = 0.2;
// Make Drip or not (0..1)
MakeDrip=1;
// Make Cap or not (0..1)
MakeCap=1;
// Make Adapter 90 or not (0..1)
MakeAdapter90=1;

/* [Hidden] */
CapRadius=DripOuterDiameter/2+(DripOuterDiameter-InnerHoleDripDiameter)/2;
W=(DripOuterDiameter-InnerHoleDripDiameter)/2;

module MakeDrip()
{
    difference()
    {
        union()
        {
            // Rings
            for(i=[0:Rings-1])
            {
                translate([0,0,i*RingHeight])
                    cylinder(h = RingHeight,
                        r1 = InnerMINDiameter/2,
                        r2 = InnerMAXDiameter/2,
                        center = false,$fn=Facets);
            }
            // Drip
            translate([0,0,RingHeight*Rings])
                cylinder(h = DripHeight-Fillet,
                    r = DripOuterDiameter/2,
                    center = false,$fn=Facets);
            translate([0,0,RingHeight*Rings+DripHeight-Fillet])
                cylinder(h = Fillet,
                    r = DripOuterDiameter/2-Fillet,
                    center = false,$fn=Facets);
            rotate([0,0,360/2*Facets])
            translate([0, 0, RingHeight*Rings+DripHeight-Fillet])
                rotate_extrude(convexity = 10,$fn = Facets)
                    translate([DripOuterDiameter/2-Fillet, 0, 0])
                        intersection()
                            {
                            circle(r = Fillet, $fn = Facets);
                            square(Fillet);
                            }
        }
    // Hole
    cylinder(h = DripHeight+(Rings*RingHeight)+1,
                r = InnerHoleDiameter/2,
                center = false,$fn=Facets);
    translate([0, 0, RingHeight*Rings+DripHeight/4])
        cylinder(h = DripHeight*0.75+1,
                r = InnerHoleDripDiameter/2,
                center = false,$fn=Facets);

    }
}

module MakeCap()
{
    difference()
    {
                cylinder(h = CapHeight,
                    r = CapRadius,
                    center = false,$fn=Facets);
    // Hole
    translate([0,0,((DripOuterDiameter-InnerHoleDripDiameter)/2)+1])
    cylinder(h = CapHeight,
                r = (DripOuterDiameter/2)+Tolerance,
                center = false,$fn=Facets);
    }
}

module MakeAdapter()
{
rotate([90,0,0])
union()
    {
    difference()
        {
        intersection()
            {
            rotate_extrude(convexity = 10, $fn = Facets)
                translate([InnerHoleDripDiameter, 0, 0])
                    circle(r = (InnerHoleDripDiameter/2)-Tolerance, $fn = Facets);
            translate([0, 0, -InnerHoleDripDiameter])
                cube([InnerHoleDripDiameter*2,InnerHoleDripDiameter*2,InnerHoleDripDiameter*2]);
            }
        intersection()
            {
            rotate_extrude(convexity = 10, $fn = Facets)
                translate([InnerHoleDripDiameter, 0, 0])
                    circle(r = (InnerHoleDripDiameter/2)-W, $fn = Facets);
            translate([0, 0, -InnerHoleDripDiameter])
                cube([InnerHoleDripDiameter*2,InnerHoleDripDiameter*2,InnerHoleDripDiameter*2]);
            }
        }

    translate([InnerHoleDripDiameter,0,0])
    rotate([90,0,0])
    difference()
        {
        cylinder(h = DripHeight,
                r = (InnerHoleDripDiameter/2)-Tolerance,
                center = false,$fn=Facets);
        cylinder(h = DripHeight,
                r = (InnerHoleDripDiameter/2)-W,
                center = false,$fn=Facets);
        }
    translate([-DripHeight,InnerHoleDripDiameter,0])
    rotate([90,0,90])
    difference()
        {
        cylinder(h = DripHeight,
                r = (InnerHoleDripDiameter/2)-Tolerance,
                center = false,$fn=Facets);
        cylinder(h = DripHeight,
                r = (InnerHoleDripDiameter/2)-W,
                center = false,$fn=Facets);
        }

    }
}

// Main
if (MakeDrip!=0) MakeDrip();
if (MakeCap!=0) translate([CapRadius*2,0,0]) MakeCap();
if (MakeAdapter90!=0) translate([-DripOuterDiameter*2,0,DripHeight]) MakeAdapter();

```


[User]
I'd like to design a customizable can cover. I have a good idea of the shape from a Thingiverse design, but I'd like a parametric version that I can easily modify using OpenSCAD.

The basic shape should be a rotational extrusion, essentially a hollow cylinder with an inner and outer wall. I need to specify the outer diameter of the can, the inner diameter of the can, the height of the outer part of the cover (how much it goes over the top edge of the can), the height of the inner part of the cover (how much it goes inside the can), and the thickness of the cover walls.

I'd like the OpenSCAD code to accept the following parameters:

*   `outer_diameter_in_millimeter`: The outer diameter of the can, including a little extra tolerance.
*   `inner_diameter_in_millimeter`: The inner diameter of the can, including a little less tolerance.
*   `outer_cover_height_in_millimeter`: The height of the outer cover.
*   `inner_cover_height_in_millimeter`: The height of the inner cover.
*   `cover_thickness_in_millimeter`: The thickness of the cover's walls.

The final cover should be a single solid object created by the rotational extrusion. The points for the polygon should form the cross-section of the can cover, so that the following points are in order of extrusion:

1.  Inner diameter / 2, minus `cover_thickness_in_millimeter` at a z-height of 0.
2.  Outer diameter / 2, plus `cover_thickness_in_millimeter` at a z-height of 0.
3.  Outer diameter / 2, plus `cover_thickness_in_millimeter` at a z-height of `outer_cover_height_in_millimeter + cover_thickness_in_millimeter`.
4.  Outer diameter / 2 at a z-height of `outer_cover_height_in_millimeter + cover_thickness_in_millimeter`.
5.  Outer diameter / 2 at a z-height of `cover_thickness_in_millimeter`.
6.  Inner diameter / 2 at a z-height of `cover_thickness_in_millimeter`.
7.  Inner diameter / 2 at a z-height of `inner_cover_height_in_millimeter + cover_thickness_in_millimeter`.
8.  Inner diameter / 2, minus `cover_thickness_in_millimeter` at a z-height of `inner_cover_height_in_millimeter + cover_thickness_in_millimeter`.

I don't need any additional libraries, just basic OpenSCAD commands.

[Assistant]
customizable_can_cover.scad:
```
/*
 * Customizable Can Cover - https://www.thingiverse.com/thing:2366517
 * by Dennis Hofmann - https://www.thingiverse.com/mightynozzle/about
 * created 2017-06-05
 * version v1.0
 *
 * This work is licensed under the Creative Commons - Attribution - Non-Commercial license.
 * https://creativecommons.org/licenses/by-nc/3.0/
*/

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

/* [General Settings] */

// Use the outer diameter of the Can. You should add a litte extra of 1-2mm tolerance. E.g. outer diameter of 100mmm -> use 101mm
outer_diameter_in_millimeter = 101.0;

// Use the inner diameter of the Can. You should substract a little extra of 1-2mm tolerance. E.g. inner diameter of 95mm -> use 94mm
inner_diameter_in_millimeter = 94.0;

// Your preferred height of the outer shell. If you set e.g. 10mm it will overhang by 10mm from the top edge of the Can
outer_cover_height_in_millimeter = 24.0;

// Your preferred height of the inner shell. This also hides the sharp edges of the Can. If you set e.g. 10mm it will overhang by 10mm from the top edge of the Can
inner_cover_height_in_millimeter = 12.0;

// Shell thickness of the cover
cover_thickness_in_millimeter = 1.0;

/* [Hidden] */
outer_dia = outer_diameter_in_millimeter;
inner_dia = inner_diameter_in_millimeter;
outer_cover_h = outer_cover_height_in_millimeter;
inner_cover_h = inner_cover_height_in_millimeter;
cover_th = cover_thickness_in_millimeter;

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

color("DeepSkyBlue") rotate_extrude($fn = 200) {
    polygon(
        points=[
            [inner_dia / 2 - cover_th,    0],
            [outer_dia / 2 + cover_th,    0],
            [outer_dia / 2 + cover_th,    outer_cover_h + cover_th],
            [outer_dia / 2,               outer_cover_h + cover_th],
            [outer_dia / 2,               cover_th],
            [inner_dia / 2,               cover_th],
            [inner_dia / 2,               inner_cover_h + cover_th],
            [inner_dia / 2 - cover_th,    inner_cover_h + cover_th]
        ]
    );
}

```


[User]
Okay, I'm looking to create a parametric marble spinner toy, sort of like the "Atom Marble Spinner" I saw on Thingiverse (thing:2166485). I want to be able to control all the key dimensions. I'd like it to have a central bearing, and then multiple "orbits" that hold marbles as weights.

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

*   **Marble Holder:** The key thing here is a way to grip the marbles securely. I need to be able to specify the marble diameter. The holders should be like little 'cages' that are open at the top and bottom, maybe using a sphere intersection with cylinders. I want to be able to adjust the "grip" of the holder by controlling how much smaller than the marble the opening is.

*   **Orbits:** I need to define the orbits as ellipses around the central bearing.  I want to be able to adjust the orbit width and length. I also want to specify the thickness of the orbit itself and chamfer its edges. The orbits will have holes to mount the marble holders. I want to make copies of the orbit for multiple electrons, rotated around the center.

*   **Central Bearing:**  This is a simple cylinder with a specific outer diameter and height, and chamfered edges.

*   **Parametric Controls:** I need to be able to adjust all these values using parameters, such as:

    *   `marble_diameter`: Diameter of the marble.
    *   `bearing_outer_diameter`: Diameter of the outer edge of the central bearing.
    *   `height`: Height of the spinner.
    *   `thickness`: Thickness of the orbits.
    *   `orbit_width`: Width of the elliptical orbit.
    *   `orbit_length`: Length of the elliptical orbit.
    *   `electron_angle`: Angle of the marble holder on the orbit.
    *   `marble_pressure`: How much smaller the holder is than the marble.
    *   `chamfer`: Size of the chamfer edges.

I also want to be able to specify the number of marbles, which are evenly distributed around the spinner (i.e., I expect it to be 3 in the model).

Essentially, it's a spinner with a central bearing, several orbiting marble holders, and everything is parametric. I'm not sure if this can be done in one .scad file, but please make it as organized as possible. If there is any OpenSCAD library you recommend for the task, please do specify. I'm not sure if I should use `hull()` for this, for example, so please include that sort of thing in your consideration. I don't have any strong opinions about whether to use any other specific shapes for the marble holders.

[Assistant]
marble-atom-spinner.scad:
```
height=7;
bearing_outer_diameter=22.2;
marble_diameter=15.5;
thickness=4;
orbit_width=20; //[10:100]
orbit_length=70; //[10:100]
// This controls the position of the electron on the orbit
electron_angle=55; //[0:180]
marble_pressure=0.3; // [0:0.1:5]
chamfer=0.3; // [0:0.1:5]

/* [Hidden] */

$fn=64;

electron_holder=[(marble_diameter-marble_pressure)*1.2,(marble_diameter-marble_pressure)*0.9,(marble_diameter-marble_pressure)*0.7];

// Calculated parameters
orbit_a = (orbit_length+thickness)/2;
orbit_b = (orbit_width+thickness)/2;
electron_position = [sin(electron_angle)*orbit_a,cos(electron_angle)*orbit_b];
electron_tangent_angle = atan(-(electron_position.x*orbit_b*orbit_b)/(electron_position.y*orbit_a*orbit_a));

module orbit_shape()
{
    resize([2*orbit_a,2*orbit_b])
        circle(orbit_length);
}

module orbit()
{
    module inner_chamfer()
    {
        lscale=(orbit_length-2*thickness)/(orbit_length-2*thickness+2*chamfer);
        wscale=(orbit_width-2*thickness)/(orbit_width-2*thickness+2*chamfer);
        linear_extrude(height=chamfer,scale=[lscale,wscale])
            offset(-thickness/2+chamfer)
                orbit_shape();
    }

    difference() {
        hull() {
            translate([0,0,chamfer])
                linear_extrude(height=height-2*chamfer)
                    offset(thickness/2)
                        orbit_shape();
            linear_extrude(height=height)
                offset(thickness/2-chamfer)
                    orbit_shape();
        }

        translate([0,0,-.001])
            inner_chamfer();
        linear_extrude(height=height)
            offset(-thickness/2)
                orbit_shape();
        translate([0,0,height+.001])
            mirror([0,0,1])
                inner_chamfer();
    }
}

module electron_cutout()
{
    translate([electron_position.x,electron_position.y,height/2])
        sphere(d=marble_diameter-marble_pressure);
}

module electron()
{
    translate([electron_position.x,electron_position.y,height/2]) {
        rotate(electron_tangent_angle) {
            intersection() {
                resize([electron_holder[0],electron_holder[1],marble_diameter-marble_pressure])
                    sphere(d=marble_diameter);
                intersection() {
                    cylinder(d=2*(marble_diameter-marble_pressure),h=height,center=true);
                    rotate([0,90,0])
                        cylinder(d=electron_holder[2],h=2*(marble_diameter-marble_pressure),center=true);
                }
            }
        }
    }
}

module bearing()
{
    module chamfer()
    {
        cylinder(d1=bearing_outer_diameter+2*chamfer,d2=0,h=bearing_outer_diameter/2+chamfer);
    }

    translate([0,0,-.001])
        chamfer();
    cylinder(d=bearing_outer_diameter,h=height);
    translate([0,0,height+.001])
        mirror([0,0,1])
            chamfer();
}

module spinner()
{
    difference() {
        union() {
            for (angle=[0:120:360-1]) {
                rotate(angle) {
                    orbit();
                    electron();
                }
            }
            cylinder(d=2*(orbit_width+thickness)/sqrt(3)+.001,h=height,$fn=6);
        }

        for (angle=[0:120:360-1])
            rotate(angle)
                #electron_cutout();
        bearing();
    }
}

module demo()
{
    spinner();
    for (angle=[0:120:360-1])
        rotate(angle)
            color("red")
                electron_cutout();
}

spinner();
//demo();

```


[User]
Hey, I'm looking to generate some custom stands for terrain maps I'm exporting from Terrain2STL. I've got a basic idea in mind, but I need a parametric OpenSCAD design that I can tweak for different sized maps.

Essentially, I want to create a stand that has a sloped back with rounded edges. The back needs to have a flat area cut into it, where the terrain map will be glued. Also, I'd like to engrave some text on the back of the stand, with two lines, one above the other.

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

*   **`width`**: Overall width of the stand.
*   **`length`**:  Overall length of the stand along the sloped face.
*   **`thickness`**: The thickness of the stand, along the back.
*   **`base`**: Height of the flat bottom part of the stand
*  **`slant`**:  The angle of the slope on the back, in degrees. (Note that the example used `angle = 90-slant` so that might be helpful.)
*   **`corner`**: The radius for rounding the edges of the stand.
*   **`border`**:  The inset distance for the flat cut-out.
*   **`textOne`**:  The text for the first line of the engraved text.
*   **`textTwo`**:  The text for the second line of the engraved text.
*   **`textSize`**:  The size of the engraved text.

The design should be generated with `hull()` to round the edges, and `linear_extrude()` for the text. The text should be centered horizontally and placed appropriately for engraving. The flat cut-out should remove a rectangle from the back of the stand. The overall stand should be generated using `difference()` of a background/base shape, an `inset` and the text using the `text()` primitive.

Could you generate the necessary OpenSCAD file(s) for this? Thanks!

[Assistant]
hburg_test.scad:
```
// mathgrrl porcelain terrain

////////////////////////////////////////////////////////////////////////////////////
// parameters

width = 46.35;
length = 59.8;
thickness = 4;
base = 8;
slant = 10;
angle = 90-slant;
corner = 0.1;
border = 3;
textOne = "Harrisonburg";
textTwo = "Virginia";
textSize = 4.5;

////////////////////////////////////////////////////////////////////////////////////
// renders

difference(){
    union(){
        //map();
        back();
    }
    inset();
    location();
}

////////////////////////////////////////////////////////////////////////////////////
// create back base

module back(){
    hull(){
        // left side ccz from origin
        translate([0,0,0]) sphere(corner);
        translate([0,length*cos(angle)+thickness,0]) sphere(corner);
        translate([0,length*cos(angle)+thickness,length*sin(angle)]) sphere(corner);
        translate([0,length*cos(angle),length*sin(angle)]) sphere(corner);
        // right side ccz from translated origin
        translate([width,0,0]) sphere(corner);
        translate([width,length*cos(angle)+thickness,0]) sphere(corner);
        translate([width,length*cos(angle)+thickness,length*sin(angle)]) sphere(corner);
        translate([width,length*cos(angle),length*sin(angle)]) sphere(corner);
    }
}

////////////////////////////////////////////////////////////////////////////////////
// engraved location text

module location(){
    // line 1
    translate([23,length*cos(angle)+thickness-2+0.1,border+14])
    rotate(-90,[1,0,0])
    rotate(180,[0,0,1])
    linear_extrude(height=2)
    text(textOne,font="Antenna:style=Medium",size=textSize,halign="center",$fn=36);
    // line 2
    translate([23,length*cos(angle)+thickness-2+0.1,border+7])
    rotate(-90,[1,0,0])
    rotate(180,[0,0,1])
    linear_extrude(height=2)
    text(textTwo,font="Antenna:style=Medium",size=textSize,halign="center",$fn=36);
}

////////////////////////////////////////////////////////////////////////////////////
// glaze inset

module inset(){
    translate([border,border,-0.1])
        cube([width-2*border,length*cos(angle)+thickness-2*border,border]);
}

////////////////////////////////////////////////////////////////////////////////////
// import map from Terrain2STL

module map(){
    rotate(angle,[1,0,0])
    translate([0,0,-.1])
    scale(.1)
    import("rawmodel-12469.stl");
}

```


[User]
Okay, I'm looking to design a parametric rear sensor mount for my Anet A8 3D printer. I've got an inductive sensor that I want to mount, and I need to be able to adjust the position. I've looked at some existing designs, but they don't quite fit my setup, particularly the horizontal Y-sensor on the newer Anet A8 model.

Here's a breakdown of what I need:

**General Mounting:**

*   The mount needs a backplate that attaches to the linear bearing housing on the Y-axis. I need to be able to specify the width, height, and thickness of this plate. By default, the width should be 30mm, the height 14mm and the thickness 5mm.
*   The mounting holes for the linear bearing housing should be adjustable in terms of their vertical offset from the top edge (3.7mm default) and horizontal distance from each other (18mm default).
*   The screw holes used to mount this to the linear bearing housing should be able to have a dowel design for strength.  I need to specify the diameter for the hole in the linear bearing (3.4mm default), the diameter for the screw (2.8mm default) that goes into that hole and a smaller diameter for the end of the dowel for effective dowel effect(2.0mm default).  I should be able to control the length of the dowels (10mm default). There should also be an option to bevel the dowels for easier insertion, with this option defaulting to true. The dowels should be centered on the linear bearing mounting holes.
*   I need to specify a sensor offset in the printer's Y-direction, 7mm by default,  to adjust the sensor location in front of the linear bearing.
*   There should be an option to rotate the mount in the Z direction. It should default to 180 degrees.

**Sensor Mounting:**

*   I need to be able to choose between a clip mount and a screw mount for the sensor. These should be controlled by a variable called `mount_type` that takes on either a value of 0 (clip) or 1 (screw). The mount should adapt to the chosen type.

**Clip Mount Specifics:**

*   For the clip mount (when `mount_type` is 0), I need to specify the wall thickness of the clip (3mm default).
*   I need to be able to specify the width of the gap in the front of the clip (4mm default).
*   I need to be able to specify the height of the wall surrounding the sensor (8mm default).

**Screw Mount Specifics:**

*   For the screw mount (when `mount_type` is 1), I need to specify the thickness of the material surrounding the sensor (7mm default), since screws exert more force than a clip.
*   The screw mount should have holes for M3 screws, with countersunk or flat heads. These mounting holes should be mirrored on both sides of the sensor. They should be placed as far out as is practical while still retaining strength.

**General Sensor Mount:**

*   I need to specify the diameter of the sensor.  I am using an M18 inductive sensor, so this diameter would default to 18mm.
*   There should be an option to add an inner thread structure for the sensor clamp which should default to true.
*   If the thread structure is included, I need to be able to specify the thread pitch. This commonly 1mm for M18 sensors and should default to that.

**Screws:**

*   I need to specify the diameter of the screw heads used for the mounting to the linear bearing housing (5.5mm default).
*   The mounting screws should be for M3 bolts and should default to 2.85mm diameter. There should be an option to have a wider bore on the screw mounts for ease of assembly as well.

I'd like the design to be parametric so I can easily adjust these settings.  I think it would be best to split the sensor mount into separate printable parts to increase printability (especially for the screw mount).

I also need the code to be well-commented, well structured, and easy to understand.

Could you generate the OpenSCAD code for this?

[Assistant]
Rear_Sensor_Mount_v1.3_NewAnet.scad:
```
ver = 1.3;
eps = 0.01*1;
$fn = 50*1;

// Increase this if your slicer or printer make holes too tight (default 0.1)
extra_radius = 0.05*1;

// Major diameter of metric 3mm thread
m3_major = 2.85*1;
m3_radius = m3_major / 2 + extra_radius;
m3_wide_radius = m3_major / 2 + extra_radius + 0.2;

// Select your desired type of sensor mount
mount_type = 0;  // [0:Clip, 1:Screw]

// Diameter of mounting hole for sensor (if you wish tolerances +/-, add them here!)
sensor_diameter = 18; //[6:0.1:25]

/* [Parameters Sensor Mount CLIP] */

// CLIP MOUNT ONLY - Wall thickness of the clip holding the sensor (default: 3)
sensor_thickness_wall = 3; //[2:0.1:8]

// CLIP MOUNT ONLY - Width of the gap in the front of the clip (default: 4)
sensor_gap = 4; //[0:0.1:8]

/* [Parameters Sensor Mount SCREW] */

// SCREW MOUNT ONLY - How thick is the material (square) surrounding the sensor - should be thick enough to withstand the screw forces (default: 7)
sensor_thickness_wall_s = 7; //[5:0.1:10]

/* [General Parameters Sensor Mount] */

// Add inner thread structure to sensor clamp
threaded = 1; // [0:No, 1:Yes]

// Thread pitch of sensor - see data sheet, for M18 sensor commonly '1' (default: 1)
thread_pitch = 1; //[0.5:0.1:2.5]

/* [General Mounting Options] */

// Width of back plate - should match the width of the linear bearing housing (for stock Anet A8: 30)
mount_width = 30;  //[20:0.1:60]

// Height of back plate (default: 12.7)
mount_height = 14; //[10:0.1:50]

// Thickness of back plate (default: 2.5)
mount_thickness = 5; //[2:0.1:10]

//1/2 width of support neck section (is one half of total width)
mount_neck_width =5; //[4:0.1:12]
// Give an offset of the sensor to the linear bearing in printers y-direction (default: 7)
sensor_offset_mount = 7; //[-4:0.1:12]

//  Height of the clip holding the sensor (default: 8)
sensor_height_wall = 8;  //[2:0.1:12]

// Diameter of screw head(s) (default: 5.5)
dia_screw_head = 5.5; //[4:0.1:7]

// How far should the mount holes be away from the top bezel - should match the geometry of linear bearing housing (for stock Anet A8: 3.7)
mount_hole_vert_offset = 3.7; //[2.5:0.1:5]
mount_height_holes = mount_height - mount_hole_vert_offset;

// Horizontal distance of linear bearing mounting holes in mm (default: 18)
mount_hole_dist = 18; //[10:0.1:30]

// Diameter of screw hole for mount to linear bearing - should be smaller than screw for effective dowel and MUST BE SMALLER by an amount of around twice the nozzle width than dia mounting hole! (recommended: 2.8)
dia_screw_hole = 2.8; //[2:0.1:4]
// Nonconical: 2.6

// Diameter of screw hole at the end of the dowel - conical for best dowel effect, i.e. smaller than value above (recommended: 2)
dia_screw_hole2 = 2;  //[1:0.1:4]

// Diameter of the hole of linear bearing - must be larger then diameter of screw hole (default: 3.4)
dia_mounting_hole = 3.4; //[1:0.1:5]

// Length of dowels (default: 10)
dowel_length = 10; //[6:0.1:20]

// Add bevel to dowels for easier insertion (reduces effective dowel length slightly)
add_bevels = 1;  // [0:No, 1:Yes]

rotate([0,0,180])
difference(){
    union(){
        backwall(mount_width, mount_height, mount_thickness);
        if(mount_type == 0){
            sensor_clip(sensor_diameter, sensor_thickness_wall,sensor_height_wall, sensor_offset_mount);
        }
        else{
            sensor_screw_splitted(sensor_diameter, sensor_thickness_wall_s,  sensor_height_wall, sensor_offset_mount);
        }
    }

    // Ensure screws are accessible
    mirror2([1,0,0])
            translate([mount_hole_dist/2,-eps,mount_height_holes])
                rotate([-90,0,0])
                    cylinder(r1=dia_screw_head/2, r2=0, h = 20);
}

module backwall(w, h, th){
    thickness_slices = 1;
    angle_bevel = 35;

    difference(){
        union(){
            translate([0, -th/2, h/2])
                cube([w, th, h], center=true);

            //Cylinders for dowels
             mirror2([1,0,0])
                translate([mount_hole_dist/2,-th+eps,mount_height_holes])
                    rotate([90,0,0])
                        cylinder(r=dia_mounting_hole/2, h = dowel_length);

            //Dowel support
             mirror2([1,0,0])
                translate([mount_hole_dist/2,-th+eps,mount_height_holes])
                    rotate([90,0,0])
                        cylinder(r1=dia_mounting_hole/2*1.3, r2=dia_mounting_hole/2, h = 0.5);

        }

        // Holes for screws
        mirror2([1,0,0])
            translate([mount_hole_dist/2,eps,mount_height_holes])
                rotate([90,0,0])
                    cylinder(r1=dia_screw_hole/2,r2=dia_screw_hole2/2, h = th+dowel_length+3*eps);

        // Holes for screwheads
        mirror2([1,0,0])
            translate([mount_hole_dist/2,eps,mount_height_holes])
                rotate([90,0,0])
                    cylinder(r=dia_screw_head/2, h = 1);

        // Slices for dowels
        mirror2([1,0,0])
            translate([mount_hole_dist/2-thickness_slices/2,-th-0.5-dowel_length,mount_height_holes-dia_mounting_hole/2])
                cube([thickness_slices,dowel_length+3*eps,dia_mounting_hole+2*eps]);

        // Bevel
        if(add_bevels)
             mirror2([1,0,0])
                translate([mount_hole_dist/2-dia_mounting_hole/2,-th-dowel_length-dia_mounting_hole,mount_height_holes-dia_mounting_hole/2+(sin(angle_bevel)*dia_mounting_hole)])
                    rotate([-angle_bevel,0,0])
                        cube([dia_mounting_hole+2*eps,dia_mounting_hole,dia_mounting_hole*2]);
    }
}

module sensor_clip(dia, th_wall, h_wall, y_offset) {

    difference(){
        union(){
            difference(){
                union(){
                    translate([0,dia/2+5+y_offset,0])
                        cylinder(r=dia/2+th_wall, h = h_wall);

                     mirror2([1,0,0])

                            cube([mount_neck_width,y_offset+dia/2,h_wall]);

                }

                translate([0,dia/2+5+y_offset,-eps])
                        cylinder(r=dia/2, h = h_wall+2*eps);
            }

            // Threaded
            if(threaded)
                translate([0,dia/2+5+y_offset,0])
                     thread_in(dia, h_wall, thread_pitch);

        }
    // Gap
    translate([0, dia+5+y_offset , h_wall/2])
        cube([sensor_gap, dia, h_wall+2*eps], center=true);

 }

}

module sensor_screw(dia, th_wall, h_wall, y_offset) {

    // Generate points for curvature
    points= concat([[0,dia/2+5+y_offset]],cos_line_i(+dia/2+th_wall,mount_width/2,dia/2+5+y_offset,0,30), [[0,0]]);

    difference(){
        union(){
            difference(){
                union(){
                    translate([0,dia/2+5+y_offset,0])
                        cylinder(r=dia/2+th_wall, h = h_wall);

                     mirror2([1,0,0])

                            cube([mount_neck_width,y_offset+dia/2-4,h_wall]);

                }

                translate([0,dia/2+5+y_offset,-eps])
                        cylinder(r=dia/2, h = h_wall+2*eps);

                translate([-mount_width/2-eps, -10+eps , -eps])
                       cube([mount_width+2*eps, 10, mount_height+2*eps]);

              // Holes for screws
                mirror2([1,0,0])
                    translate([(dia+th_wall)/2, dia/2+y_offset+5+2*eps, h_wall/2])
                        rotate([90,0,0])
                            cylinder(r=m3_radius, h = dia/2+y_offset);

                // Holes for screw wide
                mirror2([1,0,0])
                    translate([(dia+th_wall)/2,dia/2+y_offset+5-eps,h_wall/2])
                        rotate([-90,0,0])
                            cylinder(r=m3_wide_radius, h = dia/2+th_wall);

              // Holes for screw heads
                mirror2([1,0,0])
                    translate([(dia+th_wall)/2,dia/2+y_offset+5+eps+5,h_wall/2])
                        rotate([-90,0,0])
                            cylinder(r=dia_screw_head/2, h = dia/2+th_wall);
            }

            // Threaded
            if(threaded)
                translate([0,dia/2+5+y_offset,0])
                     thread_in(dia, h_wall, thread_pitch);

        }

}

}

// sensor screw splitted in printable parts
module sensor_screw_splitted(dia, th_wall, h_wall, y_offset)
{
    h_wall=h_wall>6?h_wall:7;
     echo(h_wall);
	intersection()
	{
		sensor_screw(dia, th_wall, h_wall, y_offset);
        translate([-max(dia+2*th_wall+eps,mount_width)/2-eps,-(mount_thickness+dowel_length+2*eps)-eps,-eps])
            cube([max(dia+2*th_wall+eps,mount_width)+2*eps, (dia/2+y_offset+5)+(mount_thickness+dowel_length+2*eps)+2*eps, mount_height+2*eps]);
	}

	translate([0, 5,0])
			difference()
			{
				sensor_screw(dia, th_wall, h_wall, y_offset);
                translate([-max(dia+2*th_wall+eps,mount_width)/2-eps,-(mount_thickness+dowel_length+2*eps)-eps,-eps])
            cube([max(dia+2*th_wall+eps,mount_width)+2*eps, (dia/2+y_offset+5)+(mount_thickness+dowel_length+2*eps)+2*eps, mount_height+2*eps]);
			}

}

module thread_in(dia,hi,p=1,thr=36)
{
	h = (cos(30)*p)/8;
	Rmin = (dia/2) - (5*h);	// as wiki Dmin
	s = 360/thr;
	t = (hi-p)/p;			// number of turns
	n = t*thr;				// number of segments
	echo(str("dia=",dia," hi=",hi," p=",p," h=",h," Rmin=",Rmin," s=",s));
	difference()
	{
		cylinder(r = (dia/2)+1.5,h = hi);
		translate([0,0,-1]) cylinder(r = (dia/2)+0.1, h = hi+2);
	}
	for(sg=[0:n])
		th_in_pt(Rmin+0.1,p,s,sg,thr,h,(hi-p)/n);
}

module th_in_pt(rt,p,s,sg,thr,h,sh)
// rt = radius of thread (nearest centre)
// p = pitch
// s = segment length (degrees)
// sg = segment number
// thr = segments in circumference
// h = ISO h of thread / 8
// sh = segment height (z)
{
//	as = 360 - (((sg % thr) * s) - 180);	// angle to start of seg
//	ae = as - s + (s/100);		// angle to end of seg (with overlap)
	as = ((sg % thr) * s - 180);	// angle to start of seg
	ae = as + s -(s/100);		// angle to end of seg (with overlap)
	z = sh*sg;
	pp = p/2;
	//         2,5
	//          /|
	//  1,4 /  |
 	//        \  |
	//          \|
	//         0,3
	//  view from front (x & z) extruded in y by sg
	//
	polyhedron(
		points = [
			[cos(as)*(rt+(5*h)),sin(as)*(rt+(5*h)),z],				//0
			[cos(as)*rt,sin(as)*rt,z+(3/8*p)],						//1
			[cos(as)*(rt+(5*h)),sin(as)*(rt+(5*h)),z+(3/4*p)],		//2
			[cos(ae)*(rt+(5*h)),sin(ae)*(rt+(5*h)),z+sh],			//3
			[cos(ae)*rt,sin(ae)*rt,z+(3/8*p)+sh],					//4
			[cos(ae)*(rt+(5*h)),sin(ae)*(rt+(5*h)),z+(3/4*p)+sh]],	//5
		faces = [
			[0,1,2],			// near face
			[3,5,4],			// far face
			[0,3,4],[0,4,1],	// left face
			[0,5,3],[0,2,5],	// bottom face
			[1,4,5],[1,5,2]]);	// top face
}

function cos_line(x_min,x_max,y_min,y_max,fn=180)=
    [for(i=[180:180/fn:360])
    [x_min+((i-180)/180)*(x_max-x_min),y_min+(cos(i)+1)/2*(y_max-y_min)]
];
function cos_line_i(x_min,x_max,y_min,y_max,fn=180)=
    [for(i=[180:180/fn:360])
    [x_min+(cos(i)+1)/2*(x_max-x_min),y_min+((i-180)/180)*(y_max-y_min)]
];

module mirror2(v) {
    children();
    mirror(v) children();
}

```


[User]
Hey, I'm looking to create a customizable pipe adapter in OpenSCAD, and I need some help getting started. I'd like to be able to specify the inner diameters of both ends, as well as the lengths of the collars and the connecting section. Additionally, I'd like control over the thickness of each collar.

Here are some specific parameters I'd like to control:

*   **`innerDiameter1`**: The inner diameter of the first collar (e.g., 40mm).
*   **`innerDiameter2`**: The inner diameter of the second collar (e.g., 57mm).
*   **`collar1Length`**: The length of the first collar (e.g., 20mm).
*   **`collar2Length`**: The length of the second collar (e.g., 20mm).
*   **`collar1Thickness`**: The thickness of the first collar (e.g., 3mm).
*   **`collar2Thickness`**: The thickness of the second collar (e.g., 3mm).
*  **`distanceLength`**: The length of the connecting section between the two collars (e.g., 30mm).

I'd also like to include optional tightening gaps in the collars. These should be rectangular cutouts that allow a bit of flex when fitting pipes. For these gaps, I'd like to be able to control:

*   **`collar1TighteningGap`**: A boolean (0 or 1) to enable or disable the gap on the first collar.
*   **`collar1TighteningGapWidth`**: The width of the gap on the first collar (e.g., 2mm).
*   **`collar1TighteningGapLength`**: The length of the gap on the first collar (e.g., 16mm).
*   **`collar2TighteningGap`**: A boolean (0 or 1) to enable or disable the gap on the second collar.
*   **`collar2TighteningGapWidth`**: The width of the gap on the second collar (e.g., 2mm).
*   **`collar2TighteningGapLength`**: The length of the gap on the second collar (e.g., 16mm).
*   **`tighteningGapsPosition`**: An integer (0, 1, or 2) to specify the relative positioning of the two gaps:
    *   0: Both gaps on the same side of the adapter
    *   1: Gaps positioned 90 degrees from each other.
    *   2: Gaps positioned 180 degrees from each other (opposite sides).

Could you generate the OpenSCAD code for this?  I don't need any external libraries for this.

[Assistant]
pipe_adapter.scad:
```
/******************************************************************************
 *
 *  GLOBALS
 *
 ******************************************************************************/

/* [General] */
innerDiameter1 = 40;
innerDiameter2 = 57;
collar1Length = 20;
collar2Length = 20;
collar1Thickness = 3;
collar2Thickness = 3;
distanceLength = 30;

/* [Tightening Gaps] */
collar1TighteningGap = 1; // [0: No, 1: Yes]
collar1TighteningGapWidth = 2;
collar1TighteningGapLength = 16;
collar2TighteningGap = 1; // [0: No, 1: Yes]
collar2TighteningGapWidth = 2;
collar2TighteningGapLength = 16;
tighteningGapsPosition = 0; // [0: Same side, 1: 90 degrees, 2: Opposite sides]

/* [Hidden] */
$fa = 1;
$fs = 0.5;

/******************************************************************************
 *
 *  EXECUTION
 *
 ******************************************************************************/

union()
{
    collar1();
    translate([0, 0, collar1Length + distanceLength])
    {
        if(tighteningGapsPosition > 0)
        {
            if(tighteningGapsPosition == 1)
            {
                rotate([0, 0, 90])
                    collar2();
            }
            else
                rotate([0, 0, 180])
                    collar2();
        }
        else
            collar2();
    }

    translate([0, 0, collar1Length])
        distance(innerDiameter1, innerDiameter1 + 2 * collar1Thickness,
                 innerDiameter2, innerDiameter2 + 2 * collar2Thickness, distanceLength);
}

/***************************************************************************
 *
 *  DIRECT MODULES
 *
 ******************************************************************************/

module collar1()
{
    render(convexity = 2)
        difference()
        {
            collar(innerDiameter1, collar1Length, collar1Thickness);
            gap1();
        }
}

module collar2()
{
    render(convexity = 2)
        difference()
        {
            collar(innerDiameter2, collar2Length, collar2Thickness);
            gap2();
        }
}

module gap1()
{
    if(collar1TighteningGap == 1)
    {
        translate([-collar1TighteningGapWidth / 2, innerDiameter1 / 2 - 1, -1])
            cube([collar1TighteningGapWidth, collar1Thickness + 2, collar1TighteningGapLength + 1]);
    }
}

module gap2()
{
    if(collar2TighteningGap == 1)
    {
        translate([-collar2TighteningGapWidth / 2, innerDiameter2 / 2 - 1, collar2Length - collar2TighteningGapLength])
            cube([collar2TighteningGapWidth, collar2Thickness + 2, collar2TighteningGapLength + 1]);
    }
}

 module distance(bottomInnerDiameter, bottomOuterDiameter, topInnerDiameter, topOuterDiameter, length)
 {
     render(convexity=1)
         difference()
         {
            _cutCone(bottomOuterDiameter, topOuterDiameter, length);
            _cutCone(bottomInnerDiameter, topInnerDiameter, length);
            translate([0, 0, -0.1])
                linear_extrude(height = 0.2)
                    circle(r = bottomInnerDiameter);
            translate([0, 0, length - 0.1])
                linear_extrude(height = 0.2)
                    circle(r = topInnerDiameter);
         }
 }

/******************************************************************************
 *
 *  HELPER AND COMPONENT MODULES
 *
 ******************************************************************************/

module collar(diameter, length, thickness)
{
    linear_extrude(height = length)
        difference()
        {
            circle(r = diameter/2 + thickness, center = true);
            circle(r = diameter/2, center = true);
        }
 }

module _cutCone(bottomDiameter, topDiameter, height)
{
     linear_extrude(height = height, scale = topDiameter / bottomDiameter)
        circle(r = bottomDiameter / 2, center = true);
}
```


[User]
I want to create a customizable fidget spinner, and I'd like your help generating the OpenSCAD code. The spinner should have the following adjustable parameters:

*   **Outer bearing diameter:**  This should be adjustable to accommodate different bearing sizes, with a default of 22.08mm (for a 608 bearing).
*   **Number of arms:** The spinner should have a variable number of arms, defaulting to 3, and I'd like to be able to set this to anywhere between 1 and 10.
*   **Arm length:** This is the distance from the center of the spinner to the outer edge of the arm. The default should be 35mm, I think.
*   **Spinner height:**  This will be the overall height of the spinner, and will often match the bearing height. It should default to 7.0mm (again, for a 608 bearing).
*   **Satellite shape:**  I want to be able to choose if I want to use nuts or circular objects like bearings or coins for the ends of the arms. This should be a toggle option defaulting to "nut".
*   **Satellite width:** This will be the width of the nut (flat side to flat side) or the diameter of the circle used at the end of the arms. For a M8 nut, a good default would be 12.8mm.
*   **Shell Thickness:** I'd like to be able to adjust the shell thickness of the spinner itself.  A default of 3.0mm would be great.

The design should have a central hole for the bearing, and smaller holes at the end of each arm for the chosen "satellite" object. It should be a single, solid piece.

Could you generate the necessary OpenSCAD code for this? I don't need any special libraries.

[Assistant]
customizable_fidget_spinner.scad:
```
/*
 * Customizable Fidget Spinner - https://www.thingiverse.com/thing:2369135
 * by Dennis Hofmann - https://www.thingiverse.com/mightynozzle/about
 * created 2017-06-06
 * version v1.0
 *
 * This work is licensed under the Creative Commons - Attribution - Non-Commercial license.
 * https://creativecommons.org/licenses/by-nc/3.0/
*/

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

// The outer diameter of your bearing + a little extra for tolerance. Default value is 22.08mm for a 608 bearing.
outer_bearing_diameter_in_millimeter = 22.08;

// The number of arms. Default value is 2.
number_of_arms = 3; //[1:10]

// The length from the center to the edge of the spinner. Default value is 35mm.
arm_length_in_millimeter = 35.0;

// The height of the spinner. You could use the height of the bearing. Default value is 7.0mm for a 608 bearing
spinner_height_in_millimeter = 7.0;

// You can choose between nuts and round objects like bearings, coins, etc..
satellite_option = "nut"; //[nut,circle]

// The width of the nut from one flat side to the opposite or the diameter of the round object to push in + a little extra for tolerance. Default value is in my case 12.8mm (12.72 measured + 0.08 extra) for a M8 Nut.
satellite_width_in_millimeter = 12.8;

// Shell thickness of the Fidget Spinner. Default value is 2.0mm.
shell_thickness_in_millimeter = 3.0;

/* [Hidden] */
spinner_h = spinner_height_in_millimeter;
arms = number_of_arms;
satellite_w = satellite_width_in_millimeter;
outer_bearing_d = outer_bearing_diameter_in_millimeter;
shell_th = shell_thickness_in_millimeter;
arm_len = arm_length_in_millimeter;

satellite_pos = arm_len - satellite_w / 2 - shell_th;
satellite_shell_d = satellite_w + shell_th * 2;
bearing_shell_d = outer_bearing_d + shell_th * 2;
$fn = 100;

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

color("DeepSkyBlue") linear_extrude(height = spinner_h)	{
	difference(){
		for(arm=[1 : 1 : arms]) {
			rotate([0, 0, arm * 360 / arms]) {
				hull() {
					translate([0, satellite_pos, 0]) {
						circle(d = satellite_shell_d);
					}
					circle(d = bearing_shell_d);
				}
			}
		}
		for(arm=[1 : 1 : arms]) {
			rotate([0, 0, arm * 360 / arms]) {
				translate([0, satellite_pos, 0]) {
					if(satellite_option == "nut") {
						nut2d(satellite_w);
					}
					if(satellite_option == "circle") {
						circle(d = satellite_w);
					}
				}
				circle(d = outer_bearing_d);
			}
		}
	}
}

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

module nut2d(width) {
    union() {
    	for(r=[0:60:120]) rotate([0,0,r]) square([width, width / tan(60)], center=true);
    }
}
```


[User]
Hey, I've got this idea for a cool 3D printed thing. I want to make a double helix, but instead of just having the helixes, I want each one to have text wrapped around it, kind of like the DNA double helix model you see, but with words!

So, picture this: two spiral lines wrapping around a central axis, and connected by cross-lines so they stay together. That part is clear, and I have some of the code that does it. But what I'm struggling with is how to add text onto the helixes. I was thinking the text could be carved into the surface of a sphere or some other shape, which is then aligned and intersected with the helix. The cross lines should still pass through these spherical shapes, but not through the text.

Here are some specifics:

*   **Basic Structure:** A double helix, like a DNA strand.
*   **Text:** I want to be able to specify any text. Each character will have its own set of double helixes.
*   **Sphere:** The text should appear as if it's carved into a sphere-like shape. This shape is aligned to the center of the double helix.
*   **Helix Parameters:** I want to control the radius of the helix, the number of levels (how many times it spirals), the vertical distance between levels, and the thickness of the helix lines.
*   **Cross Lines:** I want to control the thickness of the cross lines connecting the two helixes.
*   **Font:** The text font and size need to be configurable.
*   **Text Depth:** I need to control how deeply the text is carved into the sphere.
*   **Multiple characters:** If I provide a string of more than 1 character, each character will be printed on its own structure (with its own double helix).
*   **Libraries:** It is important that you use the following existing libraries:
    *   line3d.scad
    *   helix.scad
    *   rotate_p.scad
    *   polysections.scad
    *   path_extrude.scad
    *   circle_path.scad

I think with those parameters I'll have something really unique! Could you create a parametric OpenSCAD file for this? Let me know if you have questions.

[Assistant]
text_helix.scad:
```
/* [BASIC] */

text = "";
font_size = 16;
font_name = "Arial Black";
text_depth = 1;

/* [ADVANCED] */

sphere_radius = 14;
helix_line_thickness = 6;
cross_line_thickness = 4;
levels = 1;
level_dist = 70;
fn = 24;

// Commonly-used private API

function __frags(radius) = $fn > 0 ?
            ($fn >= 3 ? $fn : 3) :
            max(min(360 / $fa, radius * 6.28318 / $fs), 5);

function __nearest_multiple_of_4(n) =
    let(
        remain = n % 4
    )
    (remain / 4) > 0.5 ? n - remain + 4 : n - remain;

function __is_vector(value) = !(value >= "") && len(value) != undef;

function __to2d(p) = [p[0], p[1]];

function __to3d(p) = [p[0], p[1], 0];

function __reverse(vt) =
    let(leng = len(vt))
    [
        for(i = [0:leng - 1])
            vt[leng - 1 - i]
    ];

function __angy_angz(p1, p2) =
    let(
        dx = p2[0] - p1[0],
        dy = p2[1] - p1[1],
        dz = p2[2] - p1[2],
        ya = atan2(dz, sqrt(pow(dx, 2) + pow(dy, 2))),
        za = atan2(dy, dx)
    ) [ya, za];

function __length_between(p1, p2) =
    let(
        dx = p2[0] - p1[0],
        dy = p2[1] - p1[1],
        dz = p2[2] - p1[2]
    ) sqrt(pow(dx, 2) + pow(dy, 2) + pow(dz, 2));

/**
* line3d.scad
*
* Creates a 3D line from two points.
*
* @copyright Justin Lin, 2017
* @license https://opensource.org/licenses/lgpl-3.0.html
*
* @see https://openhome.cc/eGossip/OpenSCAD/lib-line3d.html
*
**/

module line3d(p1, p2, thickness, p1Style = "CAP_CIRCLE", p2Style = "CAP_CIRCLE") {
    r = thickness / 2;

    frags = __nearest_multiple_of_4(__frags(r));
    half_fa = 180 / frags;

    dx = p2[0] - p1[0];
    dy = p2[1] - p1[1];
    dz = p2[2] - p1[2];

    length = sqrt(pow(dx, 2) + pow(dy, 2) + pow(dz, 2));
    ay = 90 - atan2(dz, sqrt(pow(dx, 2) + pow(dy, 2)));
    az = atan2(dy, dx);

    angles = [0, ay, az];

    module cap_with(p) {
        translate(p)
            rotate(angles)
                children();
    }

    module cap_butt() {
        cap_with(p1)
            linear_extrude(length)
                circle(r, $fn = frags);

        // hook for testing
        test_line3d_butt(p1, r, frags, length, angles);
    }

    module cap(p, style) {
        if(style == "CAP_CIRCLE") {
            cap_leng = r / 1.414;
            cap_with(p)
                linear_extrude(cap_leng * 2, center = true)
                    circle(r, $fn = frags);

            // hook for testing
            test_line3d_cap(p, r, frags, cap_leng, angles);
        } else if(style == "CAP_SPHERE") {
            cap_leng = r / cos(half_fa);
            cap_with(p)
                sphere(cap_leng, $fn = frags);

            // hook for testing
            test_line3d_cap(p, r, frags, cap_leng, angles);
        }
    }

    cap_butt();
    cap(p1, p1Style);
    cap(p2, p2Style);
}

// Override them to test
module test_line3d_butt(p, r, frags, length, angles) {

}

module test_line3d_cap(p, r, frags, cap_leng, angles) {

}

/**
* helix.scad
*
* Gets all points on the path of a spiral around a cylinder.
* Its $fa, $fs and $fn parameters are consistent with the cylinder module.
* It depends on the circle_path module so you have to include circle_path.scad.
*
* @copyright Justin Lin, 2017
* @license https://opensource.org/licenses/lgpl-3.0.html
*
* @see https://openhome.cc/eGossip/OpenSCAD/lib-helix.html
*
**/

function helix(radius, levels, level_dist, vt_dir = "SPI_DOWN", rt_dir = "CT_CLK") =
    let(
        is_vt = __is_vector(radius),
        r1 = is_vt ? radius[0] : radius,
        r2 = is_vt ? radius[1] : radius,
        init_r = vt_dir == "SPI_DOWN" ? r2 : r1,
        _frags = __frags(init_r),
        h = level_dist * levels,
        vt_d = vt_dir == "SPI_DOWN" ? 1 : -1,
        rt_d = rt_dir == "CT_CLK" ? 1 : -1,
        r_diff = (r1 - r2) * vt_d,
        h_step = level_dist / _frags * vt_d,
        r_step = r_diff / (levels * _frags),
        a_step = 360 / _frags * rt_d,
        begin_r = vt_dir == "SPI_DOWN" ? r2 : r1,
        begin_h = vt_dir == "SPI_DOWN" ? h : 0
    )
    [
        for(i = [0:_frags * levels])
            let(r = begin_r + r_step * i, a = a_step * i)
                [r * cos(a), r * sin(a), begin_h - h_step * i]
    ];

/**
* rotate_p.scad
*
* Rotates a point 'a' degrees around an arbitrary axis.
* The rotation is applied in the following order: x, y, z.
*
* @copyright Justin Lin, 2017
* @license https://opensource.org/licenses/lgpl-3.0.html
*
* @see https://openhome.cc/eGossip/OpenSCAD/lib-rotate_p.html
*
**/

function _rotx(pt, a) =
    let(cosa = cos(a), sina = sin(a))
    [
        pt[0],
        pt[1] * cosa - pt[2] * sina,
        pt[1] * sina + pt[2] * cosa
    ];

function _roty(pt, a) =
    let(cosa = cos(a), sina = sin(a))
    [
        pt[0] * cosa + pt[2] * sina,
        pt[1],
        -pt[0] * sina + pt[2] * cosa,
    ];

function _rotz(pt, a) =
    let(cosa = cos(a), sina = sin(a))
    [
        pt[0] * cosa - pt[1] * sina,
        pt[0] * sina + pt[1] * cosa,
        pt[2]
    ];

function _rotate_p_3d(point, a) =
    _rotz(_roty(_rotx(point, a[0]), a[1]), a[2]);

function to_avect(a) =
     len(a) == 3 ? a : (
         len(a) == 2 ? [a[0], a[1], 0] : (
             len(a) == 1 ? [a[0], 0, 0] : [0, 0, a]
         )
     );

function rotate_p(point, a) =
    let(angle = to_avect(a))
    len(point) == 3 ?
        _rotate_p_3d(point, angle) :
        __to2d(
            _rotate_p_3d(__to3d(point), angle)
        );

/**
* polysections.scad
*
* Crosscutting a tube-like shape at different points gets several cross-sections.
* This module can operate reversely. It uses cross-sections to construct a tube-like shape.
*
* @copyright Justin Lin, 2017
* @license https://opensource.org/licenses/lgpl-3.0.html
*
* @see https://openhome.cc/eGossip/OpenSCAD/lib-polysections.html
*
**/

module polysections(sections, triangles = "SOLID") {

    function side_indexes(sects, begin_idx = 0) =
        let(
            leng_sects = len(sects),
            leng_pts_sect = len(sects[0])
        )
        concat(
            [
                for(j = [begin_idx:leng_pts_sect:begin_idx + (leng_sects - 2) * leng_pts_sect])
                    for(i = [0:leng_pts_sect - 1])
                        [
                            j + i,
                            j + (i + 1) % leng_pts_sect,
                            j + (i + 1) % leng_pts_sect + leng_pts_sect
                        ]
            ],
            [
                for(j = [begin_idx:leng_pts_sect:begin_idx + (leng_sects - 2) * leng_pts_sect])
                    for(i = [0:leng_pts_sect - 1])
                        [
                            j + i,
                            j + (i + 1) % leng_pts_sect + leng_pts_sect ,
                            j + i + leng_pts_sect
                        ]
            ]
        );

    function search_at(f_sect, p, leng_pts_sect, i = 0) =
        i < leng_pts_sect ?
            (p == f_sect[i] ? i : search_at(f_sect, p, leng_pts_sect, i + 1)) : -1;

    function the_same_after_twisting(f_sect, l_sect, leng_pts_sect) =
        let(
            found_at_i = search_at(f_sect, l_sect[0], leng_pts_sect)
        )
        found_at_i <= 0 ? false :
            l_sect == concat(
                [for(i = [found_at_i:leng_pts_sect-1]) f_sect[i]],
                [for(i = [0:found_at_i - 1]) f_sect[i]]
            );

    module solid_sections(sects) {

        leng_sects = len(sects);
        leng_pts_sect = len(sects[0]);
        first_sect = sects[0];
        last_sect = sects[leng_sects - 1];

        v_pts = [
            for(sect = sects)
                for(pt = sect)
                    pt
        ];

        function begin_end_the_same() =
            first_sect == last_sect ||
            the_same_after_twisting(first_sect, last_sect, leng_pts_sect);

        if(begin_end_the_same()) {
            polyhedron(
                v_pts,
                side_indexes(sects)
            );
        } else {
            first_idxes = [for(i = [0:leng_pts_sect - 1]) leng_pts_sect - 1 - i];
            last_idxes = [
                for(i = [0:leng_pts_sect - 1])
                    i + leng_pts_sect * (leng_sects - 1)
            ];

            polyhedron(
                v_pts,
                concat([first_idxes], side_indexes(sects), [last_idxes])
            );
        }
    }

    module hollow_sections(sects) {
        leng_sects = len(sects);
        leng_sect = len(sects[0]);
        half_leng_sect = leng_sect / 2;
        half_leng_v_pts = leng_sects * half_leng_sect;

        function strip_sects(begin_idx, end_idx) =
            [
                for(i = [0:leng_sects - 1])
                    [
                        for(j = [begin_idx:end_idx])
                            sects[i][j]
                    ]
            ];

        function first_idxes() =
            [
                for(i =  [0:half_leng_sect - 1])
                    [
                       i,
                       i + half_leng_v_pts,
                       (i + 1) % half_leng_sect + half_leng_v_pts,
                       (i + 1) % half_leng_sect
                    ]
            ];

        function last_idxes(begin_idx) =
            [
                for(i =  [0:half_leng_sect - 1])
                    [
                       begin_idx + i,
                       begin_idx + (i + 1) % half_leng_sect,
                       begin_idx + (i + 1) % half_leng_sect + half_leng_v_pts,
                       begin_idx + i + half_leng_v_pts
                    ]
            ];

        outer_sects = strip_sects(0, half_leng_sect - 1);
        inner_sects = strip_sects(half_leng_sect, leng_sect - 1);

        function to_v_pts(sects) =
             [
                for(sect = sects)
                    for(pt = sect)
                        pt
             ];

        outer_v_pts =  to_v_pts(outer_sects);
        inner_v_pts = to_v_pts(inner_sects);

        outer_idxes = side_indexes(outer_sects);
        inner_idxes = [
            for(idxes = side_indexes(inner_sects, half_leng_v_pts))
                __reverse(idxes)
        ];

        first_outer_sect = outer_sects[0];
        last_outer_sect = outer_sects[leng_sects - 1];
        first_inner_sect = inner_sects[0];
        last_inner_sect = inner_sects[leng_sects - 1];

        leng_pts_sect = len(first_outer_sect);

        function begin_end_the_same() =
           (first_outer_sect == last_outer_sect && first_inner_sect == last_inner_sect) ||
           (
               the_same_after_twisting(first_outer_sect, last_outer_sect, leng_pts_sect) &&
               the_same_after_twisting(first_inner_sect, last_inner_sect, leng_pts_sect)
           );

        if(begin_end_the_same()) {
            polyhedron(
                concat(outer_v_pts, inner_v_pts),
                concat(outer_idxes, inner_idxes)
            );
        } else {
            first_idxes = first_idxes();
            last_idxes = last_idxes(half_leng_v_pts - half_leng_sect);

            polyhedron(
                concat(outer_v_pts, inner_v_pts),
                concat(first_idxes, outer_idxes, inner_idxes, last_idxes)
            );
        }
    }

    module triangles_defined_sections() {
        module tri_sections(tri1, tri2) {
            polyhedron(
                points = concat(tri1, tri2),
                faces = [
                    [0, 1, 2],
                    [3, 5, 4],
                    [0, 4, 1], [1, 5, 2], [2, 3, 0],
                    [0, 3, 4], [1, 4, 5], [2, 5, 3]
                ]
            );
        }

        module two_sections(section1, section2) {
            for(idx = triangles) {
                // hull is for preventing from WARNING: Object may not be a valid 2-manifold
                hull() tri_sections(
                    [
                        section1[idx[0]],
                        section1[idx[1]],
                        section1[idx[2]]
                    ],
                    [
                        section2[idx[0]],
                        section2[idx[1]],
                        section2[idx[2]]
                    ]
                );
            }
        }

        for(i = [0:len(sections) - 2]) {
             two_sections(
                 sections[i],
                 sections[i + 1]
             );
        }
    }

    if(triangles == "SOLID") {
        solid_sections(sections);
    } else if(triangles == "HOLLOW") {
        hollow_sections(sections);
    }
    else {
        triangles_defined_sections();
    }
}

/**
* path_extrude.scad
*
* It extrudes a 2D shape along a path.
* This module is suitable for a path created by a continuous function.
* It depends on the rotate_p function and the polysections module.
* Remember to include "rotate_p.scad" and "polysections.scad".
*
* @copyright Justin Lin, 2017
* @license https://opensource.org/licenses/lgpl-3.0.html
*
* @see https://openhome.cc/eGossip/OpenSCAD/lib-path_extrude.html
*
**/

module path_extrude(shape_pts, path_pts, triangles = "SOLID", twist = 0, scale = 1.0, closed = false) {
    sh_pts = len(shape_pts[0]) == 3 ? shape_pts : [for(p = shape_pts) __to3d(p)];
    pth_pts = len(path_pts[0]) == 3 ? path_pts : [for(p = path_pts) __to3d(p)];

    len_path_pts = len(pth_pts);
    len_path_pts_minus_one = len_path_pts - 1;

    scale_step_vt = __is_vector(scale) ?
        [(scale[0] - 1) / len_path_pts_minus_one, (scale[1] - 1) / len_path_pts_minus_one] :
        [(scale - 1) / len_path_pts_minus_one, (scale - 1) / len_path_pts_minus_one];

    scale_step_x = scale_step_vt[0];
    scale_step_y = scale_step_vt[1];
    twist_step = twist / len_path_pts_minus_one;

    function first_section() =
        let(
            p1 = pth_pts[0],
            p2 = pth_pts[1],
            angy_angz = __angy_angz(p1, p2),
            ay = -angy_angz[0],
            az = angy_angz[1]
        )
        [
            for(p = sh_pts)
                rotate_p(
                    rotate_p(p, [90, 0, -90]),
                    [0, ay, az]
                ) + p1
        ];

    function section(p1, p2, i) =
        let(
            length = __length_between(p1, p2),
            angy_angz = __angy_angz(p1, p2),
            ay = -angy_angz[0],
            az = angy_angz[1]
        )
        [
            for(p = sh_pts)
                let(scaled_p = [p[0] * (1 + scale_step_x * i), p[1] * (1 + scale_step_y * i), p[2]])
                rotate_p(
                     rotate_p(
                         rotate_p(scaled_p, twist_step * i), [90, 0, -90]
                     ) + [length, 0, 0],
                     [0, ay, az]
                ) + p1
        ];

    function path_extrude_inner(index) =
       index == len_path_pts ? [] :
           concat(
               [section(pth_pts[index - 1], pth_pts[index],  index)],
               path_extrude_inner(index + 1)
           );

    if(closed && pth_pts[0] == pth_pts[len_path_pts_minus_one]) {
        // round-robin
        sections = path_extrude_inner(1);
        polysections(
            concat(sections, [sections[0]]),
            triangles = triangles
        );
    } else {
        polysections(
            concat([first_section()], path_extrude_inner(1)),
            triangles = triangles
        );
    }
}

/**
* circle_path.scad
*
* Sometimes you need all points on the path of a circle. Here's
* the function. Its $fa, $fs and $fn parameters are consistent
* with the circle module.
*
* @copyright Justin Lin, 2017
* @license https://opensource.org/licenses/lgpl-3.0.html
*
* @see https://openhome.cc/eGossip/OpenSCAD/lib-circle_path.html
*
**/

function circle_path(radius, n) =
    let(
        _frags = __frags(radius),
        step_a = 360 / _frags,
        end_a = 360 - step_a * ((n == undef || n > _frags) ? 1 : _frags - n + 1)
    )
    [
        for(a = [0 : step_a : end_a])
            [radius * cos(a), radius * sin(a)]
    ];

module text_helix() {
    $fn = fn;

    circle_pts = circle_path(helix_line_thickness / 2);

    points1 = helix(
        radius = sphere_radius,
        levels = levels ,
        level_dist = level_dist
    );

    points2 = [
        for(p = points1) rotate_p(p, 180)
    ];

    angle = 180 * levels;

    module connected_helixs() {
        path_extrude(circle_pts, points1);
        path_extrude(circle_pts, points2);

        for(i = [1:len(points1) - 2]) {
            line3d(points1[i], points2[i], cross_line_thickness);
        }
    }

    module sphere_chr(c) {
        rotate(angle)
            translate([0, -sphere_radius, 0])
                rotate([90, 0, 0])
                    linear_extrude(sphere_radius, center = true)
                        text(c,
                             font = font_name ,
                             size = font_size,
                             valign = "center",
                             halign = "center"
                        );
    }

    if(len(text) == 0) {
        connected_helixs();
            translate([0, 0, level_dist  * levels  / 2])  sphere(sphere_radius);
            linear_extrude(helix_line_thickness, center = true)
                circle(sphere_radius + helix_line_thickness);
    } else {
        for(i = [0:len(text) - 1]) {
            translate([(sphere_radius + helix_line_thickness + 1) * 2 * i, 0, 0])
            union() {
                connected_helixs();

                translate([0, 0, level_dist  * levels  / 2]) union() {
                    difference() {
                        sphere(sphere_radius);
                        sphere_chr(text[i]);
                        rotate(180) sphere_chr(text[i]);
                    }

                    color("white")
                        sphere(sphere_radius - text_depth);
                }

                // base
                linear_extrude(helix_line_thickness, center = true)
                    circle(sphere_radius + helix_line_thickness);
            }
        }
    }
}

text_helix();

```


[User]
I'm looking to create a customizable case for different 3D printer controller boards, based on the Prusa i3 MK2 RAMBo case, but I need it to be parametric.  I want to be able to easily adjust the dimensions to fit different boards, including MKS SBASE, MKS BASE, and the original RAMBo mini.

Here's the general idea:

The design should include:
*   A main body with side, upper, and rear panels.
*   Reinforcements for mounting, especially for zipties for the heated bed cable.
*   Mounting posts and screw blocks.
*   Mounting holes for the controller board itself.
*   Corner reinforcements.
*   Frame mounting screw blocks.
*   A heatbed cable entry opening with a ziptie reinforcement.
*   Door closing mechanisms.
*   Holes for ventilation.

I need the following parameters to be customizable:
*   `board_length`: Length of the controller PCB in mm.
*   `board_width`: Width of the controller PCB in mm.
*   `hole_offset`: Offset from the corner of the PCB to the mounting hole center.
*   `top_gap`: Padding at the top of the case for board clearance.
*   `hbp_offset`:  Vertical shift of the HBP cable entry.

The design should also consider the following existing elements from the Prusa design:
*   Heatbed cable filament holder.
*   X-axis cable entry hole.
*   RAMBo M3 mounting screw holes
*   Frame mounting screw cutouts
*   USB connector hole cutout (although I'm fine with this being omitted, so I'd like it to be parameterized out)
*   Reset button cutout (also optional).
*   Door closing screw cutout.
*   Heated bed cable ziptie holes.
*   Nut traps for mounting screws.
*   Inner edges and large corner cutouts.

Additionally I want to include an extruder cable holder and some associated cable-routing features that will work with the rest of the design.

I'm thinking it would be best if you generate multiple OpenSCAD files:
1. A main file that defines the overall `rambo()` module which is the overall case construction, and that contains all parameters and includes the other modules.
2. A separate file or files for each of the following modules:
    * `main_body()`: Generates the base case structure.
    * `ventilation_holes()`: Adds ventilation holes.
    * `cutouts()`: Generates the cutouts.
    * `ext_cable_holder()`: Creates the extruder cable holder.
    * `filament_holes()`: Creates the filament routing features
    * `sbase()`: generates a "test shape" for the MKS SBASE
    * `mks_base()`: generates a "test shape" for the MKS BASE
   *   `usb_cutout()`: (optional and should be able to be disabled)

Please use the standard OpenSCAD libraries in your design. No extra ones are needed. The parameters should all be at the beginning of the main file so that they are easily changed.

I'm looking for a clean, parameterized design that is easy to customize for different boards.  Thanks!

[Assistant]
RAMBo-cover-base.scad:
```
// PRUSA iteration3
// RAMBo base
// GNU GPL v3
// Josef Průša <iam@josefprusa.cz> and contributors
// http://www.reprap.org/wiki/Prusa_Mendel
// http://prusamendel.org

// Length of controller PCB (mm)
board_length=103;

// Width of controller PCB
board_width=69;

// offset from the corner of the pcb to the mount hole centre
hole_offset=4;

// Padding at the top of the case - needed for some boards
top_gap=0;

// shift the HBP cable entry up (negative values) or down
hbp_offset = 0;

// // MKS SBASE
// board_length=146.5;
// hole_offset=4;
// board_width=95;
// top_gap=6;

// MKS BASE
//board_length=110.0;
//hole_offset=5;
//board_width=90;
//top_gap=6;

// RAMBO mini
// board_length=103;
// hole_offset=4;
// board_width=69;
// top_gap=0;

// general parameters that are computed
case_length=board_length+15.5+top_gap; // original 118.5
case_width=board_width+23; // original 92
case_height=35 * 1; // original 35. this isn't really adequately parameterized

// main body
module main_body()
{
    // side panel
    cube( [ case_length , case_width , 2 ] );

    // upper panel
    cube( [ 1.5 , case_width , case_height ] );
    // rear panel
    translate( [ 0 , case_width-2 , 0 ] ) cube( [ case_length-13 , 2 , case_height ] );

    // heatbed ziptie reinforcement
    translate( [ case_length-55.5 +hbp_offset, case_width-3.5 , 0.5 ] ) cube( [ 6.5 , 2 , case_height-0.5 ] );

    // upper panel frame reinforcement
    translate( [ 0 , 0 , 1 ] ) cube( [ 5.5 , 7 , case_height-1 ] );

    // side panel reinforcement
    translate( [ 0 , 0 , 1 ] ) cube( [ 9 , case_width , 3 ] );
    translate( [board_length-hole_offset-1 , 0 , 0 ] ) cube( [ 9 , case_width , 4 ] );

    // post at bottom
    difference()
    {
        // bottom side reinforcement
        translate( [case_length-7.5-13 , case_width-6 , 0 ] ) cube( [ 7.5 , 6 , 35 ] );
        translate( [ case_length-7.5-13-2 , case_width-13 , 0 ] ) rotate([0,0,45]) cube( [ 7.5 , 6 , 36 ] );
    }
    //post at top
    translate( [ 0 , case_width-7 , 0 ] ) cube( [ 5.5 , 7 , 35 ] );

    // screw mounting block
    translate( [ case_length/2 - 0.75 - 4.5 , case_width-8 , 0 ] ) cube( [ 9.5 , 7 , 35 ] );

    // frame side panel
    translate( [ 20 , 0 , 0 ] ) cube( [ case_length-38.5 , 3 , 5 ] );

    // RAMBo mounting holes
    translate( [ 3.5+board_length-hole_offset  +top_gap, 10+hole_offset , 1 ] ) rotate([0,0,90]) cylinder( h = 7, r = 5.5, $fn=6);
    translate( [ 3.5+board_length-hole_offset  +top_gap, 10+board_width-hole_offset , 1 ] ) rotate([0,0,90]) cylinder( h = 7, r = 5.5, $fn=6);
    #translate( [ 3.5+hole_offset  +top_gap, 10+hole_offset , 1 ] ) cylinder( h = 7, r = 5.5, $fn=6);
    translate( [ 3.5+hole_offset  +top_gap, 10+board_width-hole_offset , 1 ] ) cylinder( h = 7, r = 5.5, $fn=6);

    // corners reinforcement
    translate( [ 0 , 10+board_width-hole_offset , 1 ] ) cube( [ 13 +top_gap, case_width-10-board_width+hole_offset , 6 ] );
    translate( [ 0 , 10+board_width-hole_offset -(sqrt(3) * 5.5)/2 , 1 ] ) cube( [ 7.5 +top_gap, sqrt(3) * 5.5+2 , 6 ] );
    translate( [ 0 , 7.75 , 1 ] ) cube( [ 7.5+top_gap , 2.25 + hole_offset + sqrt(3)*5.5/2 , 6 ] );
    translate( [ 0 , 0 , 1 ] ) cube( [ 13 +top_gap , 10+hole_offset , 6 ] );

    // frame mounting screw blocks
    // these are referenced from the bottom - so that the case grows upwards when dimensions enlarged
    //upper
    translate( [ 1 , 0 , 0 ] ) cube( [ case_length-90.5, 4 , 10 ] );

    // this does nothing
    // translate( [ 18.45 , 0 , 0 ] ) cube( [ 3 , 4 , 5.5] );

    //lower
    translate( [ case_length-20.5 , 0 , 0 ] ) cube( [ 20.5 , 4 , 10 ] );

    // heatbed filament holder body
    translate( [ case_length-36.9 + hbp_offset,case_width-1.65 , 24 ] ) rotate([0,90,0]) cylinder( h = 18 - hbp_offset, r = 5, $fn=6);

    // door closing corner
    translate( [ case_length-16.5 , case_width-3.5 , case_height ] ) cylinder( h = 3, r1=2.5, r2=1, $fn=30);

    // doors pin upper
    translate( [ 2 , case_width-7 , case_height-4 ] ) cube( [ 6 , 7 , 4] );
    translate( [ 4 , case_width-3.5 ,case_height ] ) cylinder( h = 3, r1=2.5, r2=1, $fn=30);

    // x-axis cables entry hole body
    translate( [ case_length-29.5 , 2 , 0 ] ) cylinder( h = 5, r = 6, $fn=6);
}

module ventilation_holes()
{
    // stock is 14 blocks of 5.5 width = 77mm
    // refactored to fit as many as we can in the equivalent length

    // width of ribs was 69 (19 + gap(6) + 19 + gap(6) + 19)

    width = (case_width-23-12) / 3;

     for ( i = [0 : 5.5: case_length-41.5-top_gap] ){
         // fill width ribs underneath
        translate([13 +top_gap+ i,10.5,-1]) cube([3.65,case_width-29,1.2]);

        // row 1
        translate([13+top_gap + i,10.5,-1]) cube([3.65,width,10]);

        // middle row
        translate([13 +top_gap+ i,10.5+6 + width,-1]) cube([3.65,width,10]);

        // row 3
        translate([13 +top_gap+ i,10.5+12 + width*2,-1]) cube([3.65,width,10]);
    }
}

module cutouts(){
    // side
    translate( [ 12 , 19 , 1 ] ) cube( [ case_length-33 , case_width-41 , 3 ] );

    // RAMBo M3 screws
    translate( [ 3.5+board_length-hole_offset +top_gap, 10+hole_offset  , -1 ] ) cylinder( h = 10, r = 1.9, $fn=30);
    translate( [ 3.5+board_length-hole_offset +top_gap, 10+board_width-hole_offset , -1 ] ) cylinder( h = 10, r = 1.9, $fn=30);
    translate( [ 3.5+hole_offset +top_gap, 10+hole_offset , -1 ] ) cylinder( h = 10, r = 1.9, $fn=30);
    translate( [ 3.5+hole_offset +top_gap, 10+board_width-hole_offset , -1 ] ) cylinder( h = 10, r = 1.9, $fn=30);

    translate( [ 3.5+board_length-hole_offset +top_gap, 10+hole_offset  , 6 ] ) cylinder( h = 3, r1 = 1.9, r2=2.4, $fn=30);
    translate( [ 3.5+board_length-hole_offset +top_gap, 10+board_width-hole_offset, 6 ] ) cylinder( h = 3, r1 = 1.9, r2=2.4, $fn=30);
    translate( [ 3.5+hole_offset+top_gap , 10+hole_offset , 6 ] ) cylinder( h = 3, r1 = 1.9, r2=2.4, $fn=30);
    translate( [ 3.5+hole_offset +top_gap, 10+board_width-hole_offset , 6 ] ) cylinder( h = 3, r1 = 1.9, r2=2.4, $fn=30);

    translate( [ 2 , 0 , 0 ] ) ventilation_holes();

    // frame mounting screws
    //upper
    translate( [ case_length-100, -2 , 15.6 ] ) rotate([0,45,0]) cube( [ 15 , 10 , 10] );
    translate( [ case_length-102 , 8 , 5 ] ) rotate([90,0,0]) cylinder( h = 10, r = 1.6, $fn=30);
    translate( [ case_length-102 , 9 , 5 ] ) rotate([90,0,0]) cylinder( h = 5, r = 3, $fn=30);
    translate( [ case_length-103.35 , -2 , 5 ] ) cube( [ 2.7 , 15 , 10] );
    translate( [ case_length-103.4 , -2 , 7 ] ) rotate([0,5,0]) cube( [ 2.8 , 15 , 10] );
    translate( [ case_length-103.4 , -2 , 7 ] ) rotate([0,-5,0]) cube( [ 2.8 , 15 , 10] );

    //lower
    translate( [ case_length-8 , 9 , 5 ] ) rotate([90,0,0]) cylinder( h = 10, r = 1.6, $fn=30);
    translate( [ case_length-8, 9 , 5 ] ) rotate([90,0,0]) cylinder( h = 5, r = 3, $fn=30);
    translate( [ case_length, -2 , 6 ] ) rotate([0,-45,0]) cube( [ 5 , 15 , 15] );
    translate( [ case_length-24 , -2 , 8.5 ] ) rotate([0,45,0]) cube( [ 5 , 8 , 15] );
    translate( [ case_length-9.35 , -2 , 5 ] ) cube( [ 2.7 , 15 , 10] );
    translate( [ case_length-9.2 , -2 , 6.5 ] ) rotate([0,5,0]) cube( [ 2.5 , 15 , 10] );
    translate( [ case_length-9.3 , -2 , 6.5 ] ) rotate([0,-5,0]) cube( [ 2.5 , 15 , 10] );

    // flat bottom under the frame screws
    translate( [ -1 , -2 , -5 ] ) cube( [ case_length+2 , 15 , 5] );

    // USB connector hole - i don't want that
    //translate( [ -1 , 41.5 , 11 ] ) cube( [ 5.5 , 13 , 11 ] );

    // reset button - i don't want that
    //translate( [ -2 , 65 , 14 ] ) rotate([0,90,0]) cylinder( h = 10, r = 2, $fn=30);

    // door closing screw
    translate( [ case_length/2 - 0.75 , case_width-4 , -1 ] ) cylinder( h = 43, r = 1.9, $fn=30);

    // heatbed cable opening hole
    difference()
    {
        translate( [ case_length-43 +hbp_offset, case_width+2 , 24 ] ) rotate([90,90,0]) cylinder( h = 5, r = 7, $fn=6);
        translate( [ case_length-47.5 +hbp_offset, case_width-12 , 13.5 ] ) cube( [ 8 , 15 , 5] );
    }
    translate( [ case_length-45.5+hbp_offset, case_width-12 , 20.5 ] ) cube( [ 5 , 15 , 15] );

    // heatbed cable ziptie holes
    translate( [ case_length-54.5 +hbp_offset, case_width-5 , 30 ] ) cube( [ 4 , 10 , 2 ] );
    translate( [ case_length-54.5 +hbp_offset, case_width-5 , 16 ] ) cube( [ 4 , 10 , 2 ] );

    // heatbed filament holder hole
    translate( [ case_length-37.5 + hbp_offset, case_width-3 , 24 ] ) rotate([0,90,0]) cylinder( h = 17, r = 1.8, $fn=30);
    translate( [ case_length-37.5 +hbp_offset , case_width-3 , 24 ] ) rotate([0,90,0]) cylinder( h = 2, r = 2, r2=1.8, $fn=30);
    translate( [ case_length-48.5 +hbp_offset, case_width , 15 ] ) cube( [ 30 - hbp_offset, 10 , 15] );

    // nut traps HEX
    translate( [ 3.5+hole_offset +top_gap, 10+hole_offset  , -1 ] ){
    cylinder( h = 4, r = 3.2, $fn=6);
    rotate([0,0,0]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    rotate([0,0,60]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    rotate([0,0,120]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    }

    translate( [ 3.5+board_length-hole_offset +top_gap, 10+hole_offset  , -1 ] ){
    cylinder( h = 4, r = 3.2, $fn=6);
    rotate([0,0,0]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    rotate([0,0,60]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    rotate([0,0,120]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    }

    translate( [ 3.5+board_length-hole_offset+top_gap, 10+board_width-hole_offset , -1 ] ){
    cylinder( h = 4, r = 3.2, $fn=6);
    rotate([0,0,0]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    rotate([0,0,60]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    rotate([0,0,120]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    }

    translate( [ 3.5+hole_offset +top_gap, 10+board_width-hole_offset , -1 ] ){
    cylinder( h = 4, r = 3.2, $fn=6);
    rotate([0,0,0]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    rotate([0,0,60]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    rotate([0,0,120]) resize([0,2,0]) cylinder( h = 4, r = 3.5, $fn=6);
    }

    translate( [ 3.5+hole_offset +top_gap, 10+hole_offset , -1 ] ) cylinder( h = 1.5, r1 = 5, r2=3.2, $fn=6);
    translate( [ 3.5+board_length-hole_offset +top_gap, 10+hole_offset , -1 ] ) cylinder( h = 1.5, r1 = 5, r2=3.2, $fn=6);
    translate( [ 3.5+board_length-hole_offset +top_gap, 10+board_width-hole_offset  , -1 ] ) cylinder( h = 1.5, r1 = 5, r2=3.2, $fn=6);
    translate( [ 3.5+hole_offset +top_gap, 10+board_width-hole_offset  , -1 ] ) cylinder( h = 1.5, r1 = 5, r2=3.2, $fn=6);

    translate( [ case_length/2 - 0.75, case_width-4 , -1 ] ) cylinder( h = 4, r = 3.2, $fn=30);

    // door closing corners
    translate( [ 8 , case_width-12 , 28 ] ) rotate([0,0,45]) cube( [ 5 , 5 , 10] );
    translate( [ case_length-12.5 , case_width-11 , 4 ] ) rotate([0,0,45]) cube( [ 5 , 5 , 40] );

    // inner edges cutout
    translate( [ case_length/2 - 0.75 - 4.5 , case_width-12.5 , 2 ] ) rotate([0,0,45]) cube( [ 5 , 5 , 50] );
    translate( [ case_length-48.5 , case_width-9.5 , 2 ] ) rotate([0,0,45]) cube( [ 5 , 5 , 50] );
    translate( [ 6 , case_width-12 , 7 ] ) rotate([0,0,45]) cube( [ 5 , 5 , 24] );
    translate( [ 5.5 , 4 , 7 ] ) rotate([0,0,45]) cube( [ 3 , 5 , 29] );

    // x axis cable hole
    translate( [ case_length-29.5 , 2 , -1 ] ) cylinder( h = 10, r = 4, $fn=6);
    translate( [ case_length-38.5 , -5 , -1 ] ) cube( [ 15, 5, 10] );

    // large corner coutout
    translate( [ -27 , case_width-12 , -54 ] ) rotate([50,0,45]) cube( [ 50, 50, 50] );
    translate( [ case_length+19 , case_width-32 , -10 ] ) rotate([0,0,45]) cube( [ 50, 50, 50] );

    //usb
    //usb_cutout();

}

// extruder cable holder
module ext_cable_holder()
{
difference()
{
    // body
    union(){
        rotate([90,0,0]) translate([-8, 26, -28]) rotate([0,90,0]) cylinder( h = 4, r1 = 8, r2=11, $fn=6);
        rotate([90,0,0]) translate([-4, 26, -28]) rotate([0,90,0]) cylinder( h = 4, r1 = 11, r2=11, $fn=6);
        rotate([90,0,0]) translate( [ 1.5 , 26 , -28 ] ) rotate([0,90,0]) cylinder( h = 10, r1 = 13, r2=7.5, $fn=6);
        translate( [-4, 28, 16.5 ] ) rotate([0,45,0]) cube( [ 6 , 5.5 , 7 ] );
    }

    // upper cut
    translate( [ -15 , 13 , 15] ) cube( [ 15 , 15 , 25 ] );
    // lower cut
    translate( [ 1.5 , 26 , 10] ) cube( [ 15 , 15 , 26 ] );

    // ziptie holder
    difference(){
        rotate([90,0,0]) translate([-4, 26, -28]) rotate([0,90,0]) cylinder( h = 3.5, r = 8.5,  $fn=30);
        rotate([90,0,0]) translate([-5, 26, -28]) rotate([0,90,0]) cylinder( h = 5.5, r = 6.2,  $fn=30);
        }
}
}

module rambo_cover()
{

}

module filament_holes()
{
    difference()
    {
        union()
        {
            // extruder cable filament holder body
            translate( [ 4.5 , 25.5 , 26 ] ) rotate([0,90,0]) cylinder( h = 7, r = 3.5, $fn=6);
            translate( [ 4.5 , 22.5 , 22.5] ) cube( [ 7 , 3 , 7 ] );
            translate( [ 4.5 , 23.5 , 20.8] ) cube( [ 7 , 2.5 , 7 ] );
            translate( [ 4.5 , 24.7 , 26 ] ) rotate([0,90,0]) cylinder( h = 7, r = 3, $fn=30);
        }
        // extruder filament inner hole
        translate( [ -5 , 25 , 26 ] ) rotate([0,90,0]) cylinder( h = 20, r = 1.75, $fn=30);
        translate( [ 4, 25 , 26 ] ) rotate([0,90,0]) cylinder( h =3, r1 = 1.9, r2=1.75, $fn=30);
    }
}

module rambo()
{
    difference()
    {

        union()
        {
            ext_cable_holder();
            difference()
            {
                main_body();
                cutouts();
            }
        }

        // upper extruder cable opening
        translate( [ -5 , 28 , 26 ] ) rotate([0,90,-15]) cylinder( h = 20, r = 1.4, $fn=30);

        // main hole
        translate( [ -10 , 28 , 26 ] ) rotate([0,90,0]) cylinder( h = 24, r = 5.5, $fn=30);
        // cable opening slot
        translate( [ -15 , 26.5 , 30 ] ) cube( [ 30 , 3 , 10 ] );

        // flatten on door side
        translate( [ -15 , 20 , 35 ] ) cube( [ 30 , 20 , 20 ] );

        // opening slot cuts
        translate( [ -1.5 , 26 , 33] ) rotate([45,0,45]) cube( [ 3 , 3 , 3 ] );
        translate( [ 1 , 28 , 33] ) rotate([45,0,45]) cube( [ 3 , 3 , 3 ] );

        // screw body edge
        translate( [ case_length/2 +5.75 ,case_width-17.8 , 2] ) rotate([0,0,45]) cube( [ 10 , 10 , 50 ] );

        // lightening slots
        translate( [ case_length-18.5 ,21 , 2] ) cube( [ 5 , case_width-44 , 5 ] );
        translate( [ 3 ,21 , 2] ) cube( [ 4.5, case_width-44 , 5 ] );

        // bottom holes print supports
        translate( [ 3.5+hole_offset +top_gap, 10+hole_offset ,0] ){
        translate( [ 0 , 0 , 2.5 ] ) cube([3.2,5.6,2], center=true);
        translate( [ 0 , 0 , 3 ] ) cube([3.2,3.8,2], center=true);
        }

        translate( [ 3.5+board_length-hole_offset +top_gap, 10+hole_offset ,0] ){
        translate( [ 0 , 0 , 2.5 ] ) cube([3.2,5.6,2], center=true);
        translate( [ 0 , 0 , 3 ] ) cube([3.2,3.8,2], center=true);
        }

        translate( [ 3.5+board_length-hole_offset +top_gap, 10+board_width-hole_offset ,0] ){
        translate( [ 0 , 0 , 2.5 ] ) cube([3.2,5.6,2], center=true);
        translate( [ 0 , 0 , 3 ] ) cube([3.2,3.8,2], center=true);
        }

        translate( [ 3.5+hole_offset +top_gap, 10+board_width-hole_offset ,0] ){
        translate( [ 0 , 0 , 2.5 ] ) cube([3.2,5.6,2], center=true);
        translate( [ 0 , 0 , 3 ] ) cube([3.2,3.8,2], center=true);
        }

        translate( [ case_length/2 - 0.75 , case_width-4 ,0] ){
           intersection(){cylinder(r=3.2, h=10, $fn=30);
        translate( [ 0 , 0 , 2.5 ] ) cube([3.8,8,2], center=true);}
        translate( [ 0 , 0 , 3 ] ) cube([3.8,3.8,2], center=true);
        }

    }

    filament_holes();
}

module sbase()
{

sbase_width = 146.5;
sbase_depth = 95;
sbase_hole_offset = 4;

    difference()
    {
        union()
        {
             // pcb
            cube([sbase_width,sbase_depth,1.5]);

            // ethernet
            translate([sbase_width - 21, 29 ,1.5])cube([21,16,13.5]);

            // usb
            translate([sbase_width - 16.5, 50.8 ,1.5])cube([16.5,12.2,11]);

            // sd holder
            translate([sbase_width - 14.6, 9.2 ,1.5])cube([14.6,14.85,2]);
            #translate([sbase_width - 15 + 2.6, 10 ,2.5])cube([15,11,1]);

            // big caps
            translate([sbase_width -7.3, sbase_depth-23, 1.5]) cylinder(r=13/2,h=20);
            translate([sbase_width -22, sbase_depth-23, 1.5]) cylinder(r=13/2,h=20);

            // power connectors
            translate([sbase_width -26.5, sbase_depth-10.7, 1.5])cube([10.6,10.7,18.2]);
             translate([sbase_width -43.2, sbase_depth-10.7, 1.5])cube([10.6,10.7,18.2]);

             // stepper connectors
             for(x = [0:78.8/4:78.8])
             {
                translate([11.7+x,0,1.5]) cube([12.5,5.75,7]);
             }

             // stepper driver heatsink
             translate([12.3,15.2,1.5]) cube ([90,6,15.5]);

             // pins near the drivers
             translate([1,8.5,1.5]) cube ([2.5,15,8.4]);

        }

        // holes
        translate([sbase_hole_offset,sbase_hole_offset,-1]) cylinder(r=1.8, h = 6);
        translate([sbase_width-sbase_hole_offset,sbase_depth-4,-1]) cylinder(r=1.8, h = 6);
        translate([sbase_width-sbase_hole_offset,sbase_hole_offset,-1]) cylinder(r=1.8, h = 6);
        translate([4,sbase_depth-sbase_hole_offset,-1]) cylinder(r=1.8, h = 6);
    }

}

module mks_base()
{
    width = 110; depth =90; thickness=1.7;
    hole_offset=5;

    difference()
    {
        union()
        {
            // pcb
            cube([width,depth,thickness]);

            // usb

            translate([width-15.7-12.3,depth-12,thickness]) cube([12.3,16.4,10.8]);
        }

        // holes
        translate([hole_offset,hole_offset,-1]) cylinder(r=3/2,h=10);
        translate([width-hole_offset,hole_offset,-1]) cylinder(r=3/2,h=10);
        translate([hole_offset,depth-hole_offset,-1]) cylinder(r=3/2,h=10);
        translate([width-hole_offset,depth-hole_offset,-1]) cylinder(r=3/2,h=10);

    }

}

module usb_cutout()
{
    // this is very specific to each board, so you're on your own
    translate([3.5 + top_gap,10,1+7])  translate([110-15.7-12.3,90-12,thickness]) cube([12.3,50.4,10.8]);
}

rambo();

```