[User]
I'd like to create a parametric marble run, similar to the one I saw Ferjerez make using Processing, but implemented in OpenSCAD. I'd like the design to be based on a grid-based maze, where each cell dictates the direction of the marble's path.

Here's what I'm thinking in terms of parameters and requirements:

**Core Parameters:**

*   `marble_diameter`: The diameter of the marble to be used. This drives the size of the track.
*   `run_inner_diameter`: The inner diameter of the track itself.
*   `run_outer_diameter`: The outer diameter of the track.
*   `block_size`: The size of a single cell in the grid.
*   `gradient`: The slope of the track as it progresses through the maze.
*   `wall_size`: The thickness of the walls in the design.
* `tower_outer_diameter`: the outer diameter of the tower at the start/end of the run
* `tower_inner_diameter`: the inner diameter of the tower, where the screw is located.
* `tower_bar_width`: width of the bars inside the tower
* `tower_bar_depth`: depth of the bars inside the tower
* `tower_screw_guide_height`: the height of the guide at the bottom of the lid
*  `tolerance`: a small amount of space for better fit
* `spill`: another small space to help make sure things clear each other
* `lid_diameter`: the diameter of the lid above the tower
* `lid_height`: the height of the lid above the tower
* `dimple_size`: the size of the nipple at the bottom of the screw
* `tower_screw_cap_size`: the height of the cap at the bottom of the screw

**Maze Definition:**

*   The maze should be defined using a 2D array (or equivalent data structure) in OpenSCAD.  I'd prefer if the format can be like a list of lists, like this: `[[2,3,1], [4,12,8]]`.
*   Each number in the array will correspond to a direction, similar to Ferjerez's approach. For example, 1 could be north-to-west, 2 could be east-to-north and so on (I can give you a full mapping if needed). I'd also like to define this as an enum using variables like `NORTH`, `EAST`, `SOUTH`, and `WEST` which would make the code easier to follow.
*   The array should specify the direction of the track through each grid cell. So it should be based on entry and exit directions.

**Maze Geometry:**

*   The track should consist of curved sections between grid cells with straight track segments going into and out of each corner.
*   The height of each track segment should have a height determined by the 'gradient' parameter and the cell's location within the path of the marble.
*   The track needs to be supported, ideally with some pillars under the track at the corners of the cells.
*   The base of the maze needs to be flat and sturdy. I'd like to experiment with two styles of base. A flat slab base, and then some sort of "fancy foot" base. This should be a parameter.

**Tower Design:**

*  The marble run should start from a tower and then return into the tower.
*  The tower should have some internal guides (I'm thinking some bars) to prevent the marble from falling in the wrong place.
* The tower should have a screw in the middle.
* The screw should have a lid on top.
* The bottom of the screw should have a nipple which fits into a small indent on the bottom of the tower.

**Output:**

*   I'd like the output to be a single STL file for the whole model if possible (for testing and easy printing), but I also need to be able to generate the model as separate STL files for each of the major components. The components I'd like to be able to output separately are the tower, base, supports and the main track.

I'm not sure what libraries would be most suitable, but I am fine with using any available, provided they don't require external executables and are reasonably well-known. It would be good if the design could have parameters for the number of segments in the curves (I'd like to be able to set this from 'fine' to 'coarse'), as well. Let me know if you need anything clarified, and I look forward to the results!

[Assistant]
marble-run.scad:
```
// Marble run
// Inspired (heavily!) by Ferjerez procedurally generated mazes https://www.thingiverse.com/thing:4203859
// Licensed under Creative Commons - Attribution - Share Alike

test=true;
resolution=4.0; // [1.0:fine, 2.0:normal, 4.0:coarse]
part=1; // [0:all-in-one, 1:screw, 2:full track, 3: base, 4: tower, 5: supports, 6: track]
base_type=0; // [0:slab base, 1:fancy foot base]

marble_diamater=5.0;
run_inner_diameter=7; // 6.5; // 6
run_outer_diameter=10; // 9.0; // 8
block_size=10.0;
gradient=0.1;
wall_size=4.0;
tower_outer_diameter=18.0;
tower_inner_diameter=16.0;
tower_bar_width=2.0;
tower_bar_depth=1.0;
tower_screw_guide_height=1.0;
tolerance=0.1;
spill=0.1;
lid_diameter=24.0; // size of lid top of screw e.g. TSIZE+2
lid_height=5; // height of lid top of screw
dimple_size=4.0;
tower_screw_cap_size=3.0;

/* [Hidden] */
//track_part(channel=true,tower_part=0);
//track_part(channel=false,tower_part=0);
//track_part(channel=false,tower_part=1);
//track_part(channel=false,tower_part=2);
//track_base();
//track_tower();
//track_tower_inside();
//track_supports();
//track_full();

if (part==0) {
  translate([-lid_diameter*((part==0)?1:0),lid_diameter*((part==0)?1:0),0])
  tower_screw();
  track_full();
}
if (part==1) tower_screw();
if (part==2) track_full();
if (part==3) track_base();
if (part==4) track_tower();
if (part==5) track_supports();
if (part==6) track();

module track_full() {
  track_base();
  track_tower();
  track_supports();
  track();
}

module track() {
  difference() {
    track_part(channel=false,tower_part=0);
    track_part(channel=true,tower_part=0);
    track_tower_inside();
  }
}

function fnd(d)=(((3.1415927*d/resolution)<16)?(16):(3.1415927*d)/resolution);
fc=ceil(fnd(run_outer_diameter)/2); // count of segements in a quarter circle, rounded to a whole integer

// Enumerations
NORTH=0;
EAST=1;
SOUTH=2;
WEST=3;

OPPOSITE=[SOUTH,WEST,NORTH,EAST];

// Angle for each compass direction; index by above enumerations, from the compass direction
compass_angle=[180,90,0,270];

RIGHT=0;
LEFT=1;
STRAIGHT=2;
cd=["north", "east", "south", "west"];
sd=["right", "left", "straight"];

// Maze is a grid of 4-bit numbers, where
// bits 0, 1, 2, 3 are top left (1), top right (2), bottom right (4), bottom left (8)
// The values indicate what channel segment applies to each square. Entry and exit points:
// 0 - (invalid - no boundaries)
// 1 - north to west
// 2 - east to north
// 3 - east to west
// 4 - south to east
// 5 - (invalid - no diagonals)
// 6 - south to north
// 7 - south to west
// 8 - west to south
// 9 - north to south
// 10 - (invalid - no diagonals)
// 11 - east to south
// 12 - west to east
// 13 - north to east
// 14 - west to north
// 15 - (invalid - no exit)
// grid is indexed by [Y] then [X], so that the array definition is the same(ish) way
// around as the rendered model.
// Y order (i.e. order of rows) is front to back. Row 0 is the front (South).
// X order (i.e. order of columns) is left to right. Column 0 is the left (East)

grid=(test)?
[
  [ 2,  3,  1],
  [ 4, 12,  8]
]
:
[
  [ 2, 1, 2, 3, 3, 3, 3, 1],
  [ 6, 9, 6,13,12,12,14, 9],
  [ 6,11, 7,11, 3, 1, 6, 9],
  [ 4,12,12,12,14, 9, 6, 9],
  [ 2, 3, 3, 1, 6, 9, 6, 9],
  [ 6,13,14, 9, 4, 8, 6, 9],
  [ 6, 9, 6,11, 3, 3, 7, 9],
  [ 4, 8, 4,12,12,12,12, 8],
];

XEXT=len(grid[0]);
YEXT=len(grid);

/*
function flipstr(y, x)=(x==0)?"":str(str(grid[y][x-1], ",", flipstr(y,x-1)));
for(y=[0:1:YEXT-1])
echo (flipstr(y, XEXT));
*/
// compass direction for a square - the entry direction
entry_compass_direction=[0, NORTH, EAST,  EAST, SOUTH, 0, SOUTH, SOUTH, WEST,  NORTH, 0, EAST,  WEST, NORTH, WEST,  0];
exit_compass_direction =[0, WEST,  NORTH, WEST, EAST,  0, NORTH, WEST,  SOUTH, SOUTH, 0, SOUTH, EAST, EAST,  NORTH, 0];
function side_direction(n) = ((entry_compass_direction[n]+2)%4==exit_compass_direction[n]?STRAIGHT:
                              ((entry_compass_direction[n]+1)%4==exit_compass_direction[n]?LEFT:RIGHT));

// short-cut for following path directions
xexitdir=[for (i=[0:1:15]) (exit_compass_direction[i]==WEST)?(-1):((exit_compass_direction[i]==EAST)?1:0)];
yexitdir=[for (i=[0:1:15]) (exit_compass_direction[i]==SOUTH)?(-1):((exit_compass_direction[i]==NORTH)?1:0)];
xentrydir=[for (i=[0:1:15]) (entry_compass_direction[i]==WEST)?(-1):((entry_compass_direction[i]==EAST)?1:0)];
yentrydir=[for (i=[0:1:15]) (entry_compass_direction[i]==SOUTH)?(-1):((entry_compass_direction[i]==NORTH)?1:0)];

// Calculate path from grid
// position of the "next" square from square n - i.e. where the ball will exit to.
function next_square(n)=n+xexitdir[grid[n/XEXT][n%XEXT]]+yexitdir[grid[n/XEXT][n%XEXT]]*XEXT;
// position of the previous square
function prev_square(n)=n+xentrydir[grid[n/XEXT][n%XEXT]]+yentrydir[grid[n/XEXT][n%XEXT]]*XEXT;

function path_size(n)=(n==0)?(0):(1+path_size(prev_square(n)));

// Matrix with the path in sequence order - i.e. the value of an element of the matrix is the
// position in the path sequence
path=[for (y=[0:1:YEXT-1]) [for (x=[0:1:XEXT-1]) XEXT*YEXT-path_size(y*XEXT+x)]];

theight=gradient*block_size*XEXT*YEXT+block_size*1.5;
tdisp=run_inner_diameter/2; //(run_outer_diameter-run_inner_diameter)/2;

/*
module marble_run() {
  difference() {
    union() {
      difference() {
        union() {
          for (y=[0:1:YEXT-1])
          for (x=[0:1:XEXT-1]) {
            // superstructure
            if ((y>0) || (x>0)) {// skip the tower location
              delta=(block_size-block_size*cos(45))/2;
              // delta offset for pillar depending on whether it's a corner or not
              xd=((xexitdir[grid[y][x]]+xentrydir[grid[y][x]]))*delta;
              yd=((yexitdir[grid[y][x]]+yentrydir[grid[y][x]]))*delta;
                // width of each support is block_size-wall_size
                // angle is 60 degrees
                // height from top of support is (block_size-wall_size)/2
                // up gradient is +gradient*(block_size-wall_size)
                // down gradient is -gradient*(block_size-wall_size)
              pillar_height=path[y][x]*gradient*block_size+block_size-wall_size/2;
              pillar_support_width=block_size-wall_size;
              pillar_support_height=(block_size-wall_size)/2;
              pillar_gradient_step=gradient*(block_size-wall_size);
              pillar_support_xentrydir=xentrydir[grid[y][x]];
              pillar_support_yentrydir=yentrydir[grid[y][x]];
              pillar_support_xexitdir=xexitdir[grid[y][x]];
              pillar_support_yexitdir=yexitdir[grid[y][x]];
              pillar_rotate_angle=(side_direction(grid[y][x])==STRAIGHT)?45:0;
              translate([block_size*x+xd,block_size*y+yd,0]) {
                // pillar
                rotate(a=pillar_rotate_angle,v=[0,0,1])
                cylinder(d=wall_size,h=pillar_height,$fn=4);
                // channel supports
                translate([0,0,pillar_height-wall_size/2-pillar_support_height]) {
                  hull() {
                    rotate(a=pillar_rotate_angle,v=[0,0,1])
                    cylinder(d=wall_size,h=pillar_support_height,$fn=4);
                    translate([pillar_support_xentrydir*pillar_support_width,
                               pillar_support_yentrydir*pillar_support_width,
                               pillar_support_height+pillar_gradient_step-spill])
                    rotate(a=pillar_rotate_angle,v=[0,0,1])
                    cylinder(d=wall_size,h=spill,$fn=4);

                  }
                  hull() {
                    rotate(a=pillar_rotate_angle,v=[0,0,1])
                    cylinder(d=wall_size,h=pillar_support_height,$fn=4);
                    translate([pillar_support_xexitdir*pillar_support_width,
                               pillar_support_yexitdir*pillar_support_width,
                               pillar_support_height-pillar_gradient_step-spill])
                    rotate(a=pillar_rotate_angle,v=[0,0,1])
                    cylinder(d=wall_size,h=spill,$fn=4);

                  }
                }
                // foot
                if (base_type==1)
                rotate(a=pillar_rotate_angle,v=[0,0,1])
                for (a=[0,90])
                rotate(a=a,v=[0,0,1])
                scale([wall_size*2/block_size,1.5,0.5])
                sphere(d=block_size,$fn=8);
              }
            }
          }
          // base
          hull() {
            for (y=[0:YEXT-1:YEXT-1])
            for (x=[0:XEXT-1:XEXT-1])
            translate([x*block_size,y*block_size,0])
            sphere(d=block_size,$fn=fnd(block_size));
          }
          // tower
          translate([-tdisp,-tdisp,0]) {
            cylinder(d=tower_outer_diameter,h=theight,$fn=fnd(tower_outer_diameter));
            scale([1,1,block_size/(tower_outer_diameter+block_size)])
            sphere(d=tower_outer_diameter+block_size,$fn=fnd(tower_outer_diameter+block_size));
          }
          // Track hull
          track_part(channel=false);
        }

        // Removals from superstructure

        // flatten base
        translate([-tower_outer_diameter,-tower_outer_diameter,-block_size])
        cube([block_size*XEXT+tower_outer_diameter,block_size*YEXT+tower_outer_diameter,block_size],center=false);

        // Remove ball track
        track_part(channel=true);
      }
    }
    // Tower cutout
    translate([-tdisp,-tdisp,wall_size])
    linear_extrude(height=theight)
    difference() {
      circle(d=tower_inner_diameter,$fn=fnd(tower_inner_diameter));
      for (a=[97,225,353])
      rotate(a=a,v=[0,0,1])
      translate([tower_inner_diameter/2-tower_bar_depth+tolerance,-tower_bar_width/2,0])
      square([tower_bar_depth,tower_bar_width]);
    }
    translate([-tdisp,-tdisp,theight-tower_screw_guide_height-tolerance])
    cylinder(d=tower_inner_diameter,h=tower_screw_guide_height+tolerance+spill,$fn=fnd(tower_inner_diameter));
  }
  // Add nipple for screw
  translate([-tdisp,-tdisp,wall_size])
  cylinder(d1=dimple_size,d2=0,h=dimple_size,$fn=fnd(dimple_size));
}
*/

module track_base() {
  difference() {
    union() {
      hull() {
        for (y=[0:YEXT-1:YEXT-1])
        for (x=[0:XEXT-1:XEXT-1])
        translate([x*block_size,y*block_size,0])
        sphere(d=block_size,$fn=fnd(block_size));
      }
      // tower base
      translate([-tdisp,-tdisp,0])
      scale([1,1,block_size/(tower_outer_diameter+block_size)])
      sphere(d=tower_outer_diameter+block_size,$fn=fnd(tower_outer_diameter+block_size));
    }
    // flatten base
    translate([-tower_outer_diameter,-tower_outer_diameter,-block_size])
    cube([block_size*XEXT+tower_outer_diameter,block_size*YEXT+tower_outer_diameter,block_size],center=false);
    // remove for tower
    translate([-tdisp,-tdisp,wall_size])
    cylinder(d=tower_outer_diameter,h=theight,$fn=fnd(tower_outer_diameter));
  }
}

module track_tower_inside() {
  translate([-tdisp,-tdisp,wall_size-spill])
  linear_extrude(height=theight+spill)
  difference() {
    circle(d=tower_inner_diameter,$fn=fnd(tower_inner_diameter));
    for (a=[97,225,353])
    rotate(a=a,v=[0,0,1])
    translate([tower_inner_diameter/2-tower_bar_depth+tolerance,-tower_bar_width/2,0])
    square([tower_bar_depth,tower_bar_width]);
  }
  translate([-tdisp,-tdisp,theight-tower_screw_guide_height-tolerance])
  cylinder(d=tower_inner_diameter,h=tower_screw_guide_height+tolerance+spill,$fn=fnd(tower_inner_diameter));
}

module track_tower() {
  difference() {
    translate([-tdisp,-tdisp,wall_size])
    cylinder(d=tower_outer_diameter,h=theight-wall_size,$fn=fnd(tower_outer_diameter));

    track_part(channel=false,tower_part=1);
    track_part(channel=false,tower_part=2);
    track_part(channel=true,tower_part=1);
    track_part(channel=true,tower_part=2);
    track_tower_inside();
  }
}

module track_supports() {
  difference() {
    for (y=[0:1:YEXT-1])
    for (x=[0:1:XEXT-1]) {
      // superstructure
      if ((y>0) || (x>0)) {// skip the tower location
        delta=(block_size-block_size*cos(45))/2;
        // delta offset for pillar depending on whether it's a corner or not
        xd=((xexitdir[grid[y][x]]+xentrydir[grid[y][x]]))*delta;
        yd=((yexitdir[grid[y][x]]+yentrydir[grid[y][x]]))*delta;
          // width of each support is block_size-wall_size
          // angle is 60 degrees
          // height from top of support is (block_size-wall_size)/2
          // up gradient is +gradient*(block_size-wall_size)
          // down gradient is -gradient*(block_size-wall_size)
        pillar_height=path[y][x]*gradient*block_size+block_size-wall_size/2-wall_size;
        pillar_support_width=block_size-wall_size;
        pillar_support_height=(block_size-wall_size)/2;
        pillar_gradient_step=gradient*(block_size-wall_size);
        pillar_support_xentrydir=xentrydir[grid[y][x]];
        pillar_support_yentrydir=yentrydir[grid[y][x]];
        pillar_support_xexitdir=xexitdir[grid[y][x]];
        pillar_support_yexitdir=yexitdir[grid[y][x]];
        pillar_rotate_angle=(side_direction(grid[y][x])==STRAIGHT)?45:0;
        if(side_direction(grid[y][x])!=STRAIGHT)
        translate([block_size*x+xd,block_size*y+yd,wall_size]) {
          // pillar
          rotate(a=pillar_rotate_angle,v=[0,0,1])
          cylinder(d=wall_size,h=pillar_height,$fn=4);
          // channel supports
          translate([0,0,pillar_height-wall_size/2-pillar_support_height]) {
            hull() {
              rotate(a=pillar_rotate_angle,v=[0,0,1])
              cylinder(d=wall_size,h=pillar_support_height,$fn=4);
              translate([pillar_support_xentrydir*pillar_support_width,
                         pillar_support_yentrydir*pillar_support_width,
                         pillar_support_height+pillar_gradient_step-spill])
              rotate(a=pillar_rotate_angle,v=[0,0,1])
              cylinder(d=wall_size,h=spill,$fn=4);

            }
            hull() {
              rotate(a=pillar_rotate_angle,v=[0,0,1])
              cylinder(d=wall_size,h=pillar_support_height,$fn=4);
              translate([pillar_support_xexitdir*pillar_support_width,
                         pillar_support_yexitdir*pillar_support_width,
                         pillar_support_height-pillar_gradient_step-spill])
              rotate(a=pillar_rotate_angle,v=[0,0,1])
              cylinder(d=wall_size,h=spill,$fn=4);

            }
          }
          // foot
          if (base_type==1)
          rotate(a=pillar_rotate_angle,v=[0,0,1])
          for (a=[0,90])
          rotate(a=a,v=[0,0,1])
          scale([wall_size*2/block_size,1.5,0.5])
          sphere(d=block_size,$fn=8);
        }
      }
    }

    // Remove track
    track_part(channel=false);
    // Remove tower internal
  }
}

module tower_screw() {
  screw_inner_diameter=tower_inner_diameter-tower_bar_depth*2-tolerance*2;
  tiheight=theight-wall_size;
  // Top
  difference() {
    union() {
      cylinder(d=lid_diameter,h=lid_height,$fn=fnd(lid_diameter));
      cylinder(d=tower_inner_diameter-tolerance*2,h=lid_height+tower_screw_guide_height,$fn=fnd(lid_diameter));
      translate([0,0,lid_height+tower_screw_guide_height])
      cylinder(d1=screw_inner_diameter,d2=0,h=screw_inner_diameter/8,$fn=fnd(screw_inner_diameter));
    }
    for (a=[0:20:360])
      rotate(a=a,v=[0,0,1])
      translate([lid_diameter/2+0.5,0,-spill])
      cylinder(d=2,h=lid_height+spill*2,$fn=fnd(2));
    translate([lid_diameter/4,0,-lid_diameter/2/3])
    sphere(d=lid_diameter/2,$fn=fnd(lid_diameter/2));
  }
  translate([0,0,lid_height])
  difference() {
    intersection() {
      // trunk
      linear_extrude(height=tiheight, twist=360*tiheight/(screw_inner_diameter*4), $fn=fnd(tower_inner_diameter))
      difference() {
        circle(d=screw_inner_diameter,$fn=fnd(screw_inner_diameter));
        for (a=[0:180:180])
        rotate(a=a,v=[0,0,1])
        translate([screw_inner_diameter/2+tower_bar_depth-marble_diamater/2,0]) scale([1,sqrt(2)])
        difference() {
          union() {
            circle(d=marble_diamater,$fn=fnd(marble_diamater));
            translate([-(marble_diamater/2)*sin(30),-marble_diamater])
            square([marble_diamater,marble_diamater*2]); // was *2 on y to get the curve each side; better not on the capture side.
          }
          translate([-marble_diamater*sin(30),-marble_diamater*cos(30)]) circle(d=marble_diamater,$fn=fnd(marble_diamater));
          translate([-marble_diamater*sin(30),marble_diamater*cos(30)]) circle(d=marble_diamater,$fn=fnd(marble_diamater));
        }
      }
      union() {
        cylinder(d=screw_inner_diameter,h=tiheight-tower_screw_cap_size,$fn=fnd(screw_inner_diameter));
        // cap
        translate([0,0,tiheight-tower_screw_cap_size])
        difference() {
          sphere(d=screw_inner_diameter,$fn=fnd(screw_inner_diameter));
          // remove bottom half of sphere
          translate([-screw_inner_diameter/2,-screw_inner_diameter/2,-screw_inner_diameter/2])
          cube([screw_inner_diameter,screw_inner_diameter,screw_inner_diameter/2]);
          // remove top section to cap size
          translate([-screw_inner_diameter/2,-screw_inner_diameter/2,tower_screw_cap_size])
          cube([screw_inner_diameter,screw_inner_diameter,screw_inner_diameter/2]);
        }
      }
    }
    // dimple
    translate([0,0,tiheight-dimple_size-tolerance])
    cylinder(d1=0,d2=dimple_size+tolerance+spill,h=dimple_size+tolerance+spill,$fn=fnd(dimple_size+tolerance+spill));
  }
}

module track_part(channel=true,tower_part=0) {
  union() {
    if(tower_part==0) {
      for (y=[0:1:YEXT-1])
      for (x=[0:1:XEXT-1]) {
        // Track segment
        if ((y>0) || (x>0)) // skip the tower location
        translate([block_size*x,block_size*y,path[y][x]*gradient*block_size+block_size])
        track_piece(entry_compass_direction[grid[y][x]],side_direction(grid[y][x]),channel);
      }
    } else if (tower_part==1) {
      p=[1,0];
      translate([block_size*p[0],block_size*p[1],path[p[1]][p[0]]*gradient*block_size+block_size])
      track_piece(entry_compass_direction[grid[p[1]][p[0]]],side_direction(grid[p[1]][p[0]]),channel);
    } else if (tower_part==2) {
      p=[0,1];
      translate([block_size*p[0],block_size*p[1],path[p[1]][p[0]]*gradient*block_size+block_size])
      track_piece(entry_compass_direction[grid[p[1]][p[0]]],side_direction(grid[p[1]][p[0]]),channel);
    }
    // specific extensions into tower at both top and bottom
    if (tower_part==0 || tower_part==1)
    translate([0,0,block_size])
    intersection() {
      track_piece(entry_compass_direction[grid[0][0]],STRAIGHT,channel);
      translate([0,-block_size/2,-block_size/2-gradient*block_size]) cube([block_size/2,block_size,block_size+gradient*block_size]);
    }
    if (tower_part==0 || tower_part==2)
    translate([0,0,(XEXT*YEXT)*gradient*block_size+block_size])
    intersection() {
      track_piece(OPPOSITE[exit_compass_direction[grid[0][0]]],STRAIGHT,channel);
      translate([-block_size/2,0,-block_size/2-gradient*block_size]) cube([block_size,block_size/2,block_size+gradient*block_size]);
    }
  }
}

// Pieces

// corner piece, from [0,0] to [0,5,0.5] via [0.5,0]
// use mirror and/or rotate to get the other directions
module right_corner(channel=true) {
  fs=block_size; // full size of a block
  hs=block_size/2; // half size of a block
  translate([0,-block_size/2,0])
  if (channel) {
    for (i=[0:1:fc-1]) {
      a1=i*(90/fc);
      a2=(i+1)*(90/fc);
      hull() {
        translate([hs-hs*cos(a1),hs*sin(a1),-gradient*fs*i/fc])
        sphere(d=run_inner_diameter,$fn=fnd(run_inner_diameter));
        translate([hs-hs*cos(a2),hs*sin(a2),-gradient*fs*(i+1)/fc])
        sphere(d=run_inner_diameter,$fn=fnd(run_inner_diameter));
      }
    }
  } else {
    intersection() {
      difference() {
        for (i=[0:1:fc+1]) {
          a1=i*(90/fc);
          a2=(i+1)*(90/fc);
          intersection() {
            hull() {
              translate([hs-hs*cos(a1),hs*sin(a1),-gradient*fs*i/fc])
              sphere(d=run_outer_diameter,$fn=fnd(run_outer_diameter));
              translate([hs-hs*cos(a2),hs*sin(a2),-gradient*fs*(i+1)/fc])
              sphere(d=run_outer_diameter,$fn=fnd(run_outer_diameter));
            }
            // Wedgecut
            translate([hs,0,-hs-gradient*fs*i/fc])
            rotate(a=180-a1,v=[0,0,1])
            //translate([0,-fs/fc,0])
            cube([fs,fs*2/fc,hs]);
          }
        }
      }
      translate([-hs,-spill,-fs])
      cube([fs+spill,fs+spill,fs]);
    }
  }
}

// straight piece, from [0,0] to [0,1]
module north_straight(channel=true) {
  translate([0,-block_size/2,0])
  if (channel) {
    hull() {
      sphere(d=run_inner_diameter,$fn=fnd(run_inner_diameter));
      translate([0,block_size,-gradient*block_size])
      sphere(d=run_inner_diameter,$fn=fnd(run_inner_diameter));
    }
  } else {
    intersection() {
      difference() {
        //
        hull() {
          sphere(d=run_outer_diameter,$fn=fnd(run_outer_diameter));
          translate([0,block_size,-gradient*block_size])
          sphere(d=run_outer_diameter,$fn=fnd(run_outer_diameter));
        }
      }
      // sheared cube intersection
      hull() {
        translate([-block_size/2,-block_size/fc,-block_size])
        cube([block_size,block_size/fc,block_size]);
        translate([-block_size/2,block_size-block_size/fc,-block_size-gradient*block_size])
        cube([block_size,block_size/fc,block_size]);
      }

      translate([-block_size/2,-spill,-block_size])
      cube([block_size,block_size+spill*2,block_size]);
    }
  }
}

module track_piece(compass_direction, side_direction, channel=true) {
  if (side_direction==STRAIGHT) {
    rotate(a=compass_angle[compass_direction],v=[0,0,1])
    north_straight(channel);
  } else {
    rotate(a=compass_angle[compass_direction],v=[0,0,1])
    mirror([side_direction,0,0])
    right_corner(channel);
  }
}

```


[User]
Hey there! I'm looking for an OpenSCAD design for a custom bullet slider, something similar to the one I've been using but fully parametric. I've got a few specific requirements in mind, and I'd like to be able to adjust the dimensions for different bullet types.

Here’s what I need:

*   **Bullet Dimensions:** The design needs to accept parameters for the bullet diameter and bullet height. I’d like to specify the bullet diameter in inches and bullet height in inches as well, with an option to have everything in metric by setting a single variable (like `inch = 1`). For inches, I'd use something like `inch = 25.4`.
*   **Oversizing:** I need a parameter to specify how much larger the hole for the bullet should be than the bullet itself, expressed as a percentage.
*   **Gap Between Bullets:** The design should also have a parameter for the gap (or "reveal") between bullets, which I will be using in millimeters.
*   **Support Holes:** I would like a boolean to add or not add holes above the feet.
*   **Hollowed Center:** I'd like a boolean option to hollow out the center of the slider for weight reduction.
*   **Capture Ring:** The design should include a ring at the top of the bullet hole to capture the bullet, with a configurable height in millimeters.
*   **Slider Base:** There are some parameters for the base of the slider - a height, corner radius, base thickness, riser width, slider width, slider length, foot length, foot offset, foot return, foot width, foot gap, and foot thickness. I will use millimeters for these.

Essentially, I'd like an OpenSCAD file that uses these parameters to generate the slider body, feet, and capture ring. The main body should have a base with rounded corners, a riser section, and a cylindrical hole for the bullet. The feet should be attached to the bottom of the base. I'm picturing the design being printed with the feet facing down and some support material. I would need to have all of the parameters I listed above exposed in the top section of the file.

I'm hoping this can be done with base OpenSCAD features. Thanks a bunch!

[Assistant]
Lee_APP_Bullet_Slider_v2.scad:
```
// Bullet Diameter  inch
Bullet_Diam = 0.389;       // [0.224:0.001:0.565]
// Bullet Height  inch
Bullet_Height = .672;     // [0.626:0.001:1.285]

// Percent to increase hole diameter
Bullet_Over_Size = 5;     // percent

// Gap to next Bullet MM
Bullet_Reveil = 1.5;      // mm

//Add holes above feet for support
slider_holes =  true;   // [false,true]

// Hollow out center
hollow = true;          // [false,true]

//Capture Ring Height MM
ring = 2;                // [2:0.1:22]

/* [Hidden] */
Slide_Height = 12.4;    // Height of base slider
radius = 1.5;           // corner radius

inch = 25.4 + 0;

Base_Thickness = 2;
Riser_Width = 14;
Slider_Width = 24;
Slider_Length = 48.3 + 9;
foot_length = 6.3;
foot_offset = 5;
foot_return = 2.5;
foot_width = 2.9;
foot_gap = 2;
foot_thickness = 1.6;
$fn=240;

height = ((Bullet_Height * inch) - Slide_Height - Bullet_Reveil);
Pusher_Radius = (Bullet_Diam *(1 + Bullet_Over_Size/100) * inch)/2;

module body() {
    difference(){
        union(){
            translate([radius,radius,0]) linear_extrude(Base_Thickness) offset(r=radius)         square([Slider_Width-2*radius,Slider_Length-2*radius]);

           translate([(Slider_Width-Riser_Width)/2 + radius,radius,0]) linear_extrude(height) offset(r=radius) square([Riser_Width-2*radius,Slider_Length-2*radius]);

            translate([Slider_Width/2,0,0]) cylinder(h = height, r = Pusher_Radius+1.2);

            }

        translate([Slider_Width/2,0,-.5]) cylinder(h = height+1, r = Pusher_Radius);
        translate([0,-25,-.5]) cube([Slider_Width,25,height+1]);

        if (slider_holes == true){

            translate([2,12-foot_offset+foot_return,-.5]) cube([2.5,foot_length+foot_offset-foot_return,3]);
            translate([2,12+30-foot_offset+foot_return,-.5]) cube([2.5,foot_length+foot_offset-foot_return,3]);
            translate([Slider_Width-4.5,12-foot_offset+foot_return,-.5]) cube([2.5,foot_length+foot_offset-foot_return,3]);
            translate([Slider_Width-4.5,12+30-foot_offset+foot_return,-.5]) cube([2.5,foot_length+foot_offset-foot_return,3]);
        }
        if (hollow == true) {
            translate([(Slider_Width-12+2*radius)/2,12,-2]) linear_extrude(height) offset(r=radius) square([12-2*radius,38-2*radius]);
        }
   // add locating slot
        translate([0,Slider_Length-5,0]) rotate([0,90,0]) cylinder(h = Slider_Width, r = 0.75);

        }

}

module left_foot() {
    union(){
        foot_height = Base_Thickness + foot_thickness + foot_gap;
        translate([0,0,-(foot_thickness + foot_gap)]) cube([2,foot_length+foot_offset,foot_height]);
        translate([0,0,-(foot_thickness + foot_gap)]) cube([foot_width,foot_length+foot_offset,foot_thickness]);
        translate([0,0,-(foot_thickness + foot_gap)]) cube([foot_width,foot_offset-foot_return,foot_height]);

    }
}

module right_foot(){
    union(){

        foot_height = Base_Thickness + foot_thickness + foot_gap;

        translate([0,0,-(foot_thickness + foot_gap)]) cube([2,foot_length+foot_offset,foot_height]);
        translate([0,0,-(foot_thickness + foot_gap)]) cube([foot_width,foot_length+foot_offset,foot_thickness]);

        translate([0,foot_length+foot_return,-(foot_thickness + foot_gap)]) cube([foot_width,foot_offset-foot_return,foot_height]);

    }
}

module feet() {
    union(){
        translate([0,0-foot_offset,0]) rotate([0,0,0]) left_foot();
        translate([0,30-foot_offset,0]) rotate([0,0,0])left_foot();

        translate([Slider_Width,foot_length,0]) rotate([0,0,180]) right_foot();
        translate([Slider_Width,30+foot_length,0]) rotate([0,0,180]) right_foot();
    }
}

union(){
    difference(){
         union() {
             difference() {
             translate([Slider_Width/2,0,height-ring]) cylinder(h = ring, r = Pusher_Radius+1.2);
             translate([Slider_Width/2,0,height-ring-1]) cylinder(h = ring+2, r = Pusher_Radius);
             }

             body();
             translate([0,12,0]) feet();
             }
         translate([Slider_Width/2,0,height-0.75]) cylinder(h = 3, r1 = Pusher_Radius, r2 = Pusher_Radius+3);
         }

}

```


[User]
I'm looking to create a fully parametric double Cardan joint in OpenSCAD, similar to the one described on the Wikipedia page for constant-velocity joints, specifically the double Cardan variant.

I need a design that allows for independent control over a few key parameters:

*   **Middle Joint:** The thickness of the central joint piece and the diameter for the hole where the other components attach.
*   **Blocks:** These connect the middle joint to the sleeves. I need to control their outer diameter, the diameter of the hole that fits on the joint, the length of the block (distance between hole centers), the space between the block and the "wings" attached to the middle joint, and the space between the block and the "sleeves".
*   **Shafts & Sleeves:** I need two sets of shafts and sleeves (one set on each side of the joint) and control over the diameters of both the shaft and sleeve, as well as the length of each sleeve.
*   **Fasteners:** I need the option to choose the number of bolts (1 or 2) for each sleeve, as well as control over the bolt hole diameter and the bolt head/nut size.
*  **Rotations:**  I need to be able to set y and z axis rotations for each sleeve, relative to the middle joint.
*   **Smoothness:** I'd like to control the smoothness of curves through a `$fn` parameter.

I'd like to be able to display the entire joint, as well as the middle joint, blocks, or sleeves as individual parts.

Could you design this in OpenSCAD? I don't need any code attached for any libraries, but please specify which specific libraries you use.

[Assistant]
UDoubleCardanJoint3.scad:
```
//// File: DoubleCardanJoint3.scad
//// Version: OpenSCAD 2015.03-2
//// Author: Kijja Chaovanakij
//// Date: 2020.2.11

//smoothness[36]
$fn=36; //[36, 48, 60]

/*[part]*/
//display parts
part=0; //[0:all, 1:middle joint, 2:block, 3:sleeve]

/*[middle joint]*/
//thickness of the middle joint[8]
joint_thi=8;

/*[block]*/
//clearance diameter of the block's hole[M3:2.7+0.6, M4:3.7+0.6]
block_hole=3.3;
//outer diameter of the block[11]
block_od=11;
//length of the block(hole to hole)[12]
block_len=12; //[12:1:50]
//space between blocks and wings[0.4]
wing_sp=0.4;
//space between blocks and sleeves[1]
sleeve_sp=1;

/*[shaft1]*/
//diameter of the shaft1[8]
shaft_dia1=8;
//diameter of the sleeve1[20]
sleeve_dia1=20;
//length of the sleeve1[22]
sleeve_len1=22;

/*[shaft2]*/
//diameter of the shaft2[12]
shaft_dia2=12;
//diameter of sleeve2[24]
sleeve_dia2=24;
//length of sleeve2[26]
sleeve_len2=26;

/*[fastener]*/
//number of shaft's bolt
n_bolt=2; //[1, 2]
//clearance diameter of the shaft's bolt[M3:2.7+0.6, M4:3.7+0.6]
shaft_bolt=3.3;
//clearance diameter of the bolt's head or hex nut size[ M3:5.5+0.6, M4:7+0.6]
shaft_nut_size=6.1;

/*[rotation]*/
//y axis rotation of the sleeve1
r1y=15; //[-30:1:30]
//z axis rotation of the sleeve1
r1z=30; //[-30:1:30]
//y axis rotation of the sleeve2
r2y=-15; //[-30:1:30]
//z axis rotation of the sleeve2
r2z=-30; //[-30:1:30]

/*[Hidden]*/
//adjust the diameter of the middle joint
joint_dia=min(sleeve_dia1, sleeve_dia2);

//// main
//parts middle joint
if (part==0 || part==1){
  color("GreenYellow"){
    rotate([0, 90, 0])
    translate([0, 0, -joint_thi/2])
      round_cylinder(height=joint_thi, rad=joint_dia/2);
    translate([joint_thi/2+sleeve_sp+block_od/2, 0, 0])
      wing(wing_dia=joint_dia, angle=-90);
    mirror([1, 0, 0])
      translate([joint_thi/2+sleeve_sp+block_od/2, 0, 0])
        wing(wing_dia=joint_dia, angle=90);
  }//c
}//i
//

//parts +x side
translate([joint_thi/2+sleeve_sp+block_od/2, 0, 0])
rotate([0, 0, r1z]){
  color("Aquamarine")
  if (part==0 || part==2)
    block(outer_dia=block_od, inner_dia=block_hole);
  color("Tomato")
  translate([block_len/2, 0, 0]){
    rotate([90, r1y, 180])
    if (part==0 || part==3)
      wing(wing_dia=sleeve_dia1, angle=-90);
    translate([(block_od/2+sleeve_sp+sleeve_len1/2)*cos(r1y), 0, (block_od/2+sleeve_sp+sleeve_len1/2)*sin(r1y)])
    rotate([0, -90-r1y, 0])
    if (part==0 || part==3)
      sleeve(sleeve_dia=sleeve_dia1, sleeve_len=sleeve_len1, shaft_dia=shaft_dia1);
  }//t
}//r
//

//parts -x side
mirror([1, 0, 0])
translate([joint_thi/2+sleeve_sp+block_od/2, 0, 0])
rotate([0, 0, r2z]){
  color("Aquamarine")
  if (part==0 || part==2)
    block(outer_dia=block_od, inner_dia=block_hole);
  color("Orange")
  translate([block_len/2, 0, 0]){
    rotate([90, r2y, 180])
    if (part==0 || part==3)
      wing(wing_dia=sleeve_dia2, angle=-90);
    translate([(block_od/2+sleeve_sp+sleeve_len2/2)*cos(r2y), 0, (block_od/2+sleeve_sp+sleeve_len2/2)*sin(r2y)])
    rotate([0, -90-r2y, 0])
    if (part==0 || part==3)
      sleeve(sleeve_dia=sleeve_dia2, sleeve_len=sleeve_len2, shaft_dia=shaft_dia2);
  }//t
}//r
//

module sleeve(sleeve_dia=sleeve_dia1,
              sleeve_len=sleeve_len1,
              shaft_dia=shaft_dia1){
  ep=0.002;
  gv=1; //groove spacing
  difference(){
    union(){
      translate([0, 0, -sleeve_len/2])
        round_cylinder(height=sleeve_len, rad=sleeve_dia/2);
    }//u
    cylinder(h=sleeve_len+ep, d=shaft_dia, center=true);
    translate([sleeve_dia/4, 0, 0])
      cube([sleeve_dia*0.9, gv, sleeve_len+ep], true);
    if (n_bolt==1){
      translate([sleeve_dia/4+shaft_dia/5, 0, 0])
      rotate([90, 0, 0])
        vhole(height=sleeve_dia, dia=shaft_bolt);
      translate([sleeve_dia/4+shaft_dia/5, -(sleeve_dia-shaft_dia)*0.35, 0])
      rotate([90, 30, 0])
        cylinder(h=sleeve_dia/2, d=shaft_nut_size/cos(30), $fn=6);
      translate([sleeve_dia/4+shaft_dia/5, (sleeve_dia-shaft_dia)*0.35, 0])
      rotate([-90, 180, 0])
        vhole(height=sleeve_dia/2, dia=shaft_bolt*2, cen=false);
    }//i
    if (n_bolt==2){
      for(i=[sleeve_len/4, -sleeve_len/4])
        translate([sleeve_dia/4+shaft_dia/5, 0, i])
        rotate([90, 0, 0])
          vhole(height=sleeve_dia, dia=shaft_bolt);
      for(i=[sleeve_len/4, -sleeve_len/4])
        translate([sleeve_dia/4+shaft_dia/5, -(sleeve_dia-shaft_dia)*0.35, i])
        rotate([90, 30, 0])
          cylinder(h=sleeve_dia/2, d=shaft_nut_size/cos(30), $fn=6);
      for(i=[sleeve_len/4, -sleeve_len/4])
        translate([sleeve_dia/4+shaft_dia/5, (sleeve_dia-shaft_dia)*0.35, i])
        rotate([-90, 180, 0])
          vhole(height=sleeve_dia/2, dia=shaft_bolt*2, cen=false);
    }//i
  }//d
}//m
//

//wing();
module wing(wing_dia=joint_dia,
            length=block_od/2+1+sleeve_sp,
            base_wid=block_od*1.25,
            end_wid=block_od,
            rounding=2.4,
            space=wing_sp,
            hole_dia=block_hole,
            angle=-90){
  ep=0.002;
  difference(){
    intersection(){
      hull(){
        translate([-length, 0, 0])
          cube([0.001, base_wid, wing_dia], true);
        for (i=[end_wid/2-rounding, -end_wid/2+rounding])
          translate([end_wid/2-rounding/2, i, 0])
            cylinder(h=wing_dia, d=rounding, center=true, $fn=24);
      }//h
      translate([-length, 0, 0])
      rotate([0, 90, 0])
        cylinder(h=length+end_wid/2, d=wing_dia);
    }//i
    translate([-length/2+end_wid/4, 0, 0])
      cube([length+end_wid/2+ep, base_wid, end_wid+space*2], true);
    rotate([0, 0, angle])
      vhole(height=wing_dia, dia=hole_dia);
  }//d
}//m
//

//block();
module block(outer_dia=10, inner_dia=4){
  ep=0.002;
  difference(){
    hull(){
      translate([0, 0, -outer_dia/2])
        round_cylinder(height=outer_dia, rad=outer_dia/2);
      rotate([90, 0, 0])
      translate([block_len/2, 0, -outer_dia/2])
        round_cylinder(height=outer_dia, rad=outer_dia/2);
    }//h
    cylinder(h=outer_dia+ep, d=inner_dia, center=true, $fn=24);
    rotate([90, 0, 0])
    translate([block_len/2, 0, 0])
      vhole(height=outer_dia+ep, dia=inner_dia);
  }//d
}//m
//

//round_cylinder();
module round_cylinder(height=10, rad=6, rounding=1){
//for rounding less than height/2 and rad/2
  rotate_extrude(convexity=10)
  hull(){
    translate([rad-rounding, rounding])
      circle(r=rounding, $fn=24);
    translate([rad-rounding, height-rounding])
      circle(r=rounding, $fn=24);
    square([0.01, height], false);
  }//h
}//m
//

//vhole();
module vhole(height=4, dia=6, offset=0.6, cen=true){
  hull(){
    cylinder(h=height, d=dia, center=cen, $fn=24);
    translate([0, dia/2+offset])
      cylinder(h=height, d=0.01, center=cen, $fn=24);
  }//h
}//m
//

```


[User]
I'd like to create a customizable plant sign for my garden, and I'm thinking OpenSCAD would be the perfect tool for this. I'd like the design to have a few key features.

First, there should be a main sign body, kind of like a rounded rectangle, which will serve as the background. It needs a way to be stuck into the ground, so I'm picturing a stake that extends downwards from the sign. The stake should be a simple rectangular prism with a triangular point to help it go into the soil easily.

The main sign itself should also have a raised border around the edges, and within that border, I need to be able to add text. I need two lines of text: a title and a subtitle. The title should be slightly larger than the subtitle. I'd like both lines of text to be centered both horizontally and vertically within the border, if that makes sense.

Ideally, I would have a way to control various parameters such as the overall width, length, and height of the sign, as well as the roundness of the corners, the font size, and the text to be displayed. I would also like to control the height of the text and the height of the border from the ground.

Finally, I'd love to have an option to print only the sign (the background and stake), only the border and text, or the entire thing together. This would be super helpful if I wanted to print the text in a different color on a multi-material printer.

So, to summarize, I need:

1.  A main sign body (rounded rectangle) with a stake.
2.  A raised border on the sign.
3.  Two lines of customizable text (title and subtitle) within the border.
4.  Parametric control over dimensions, roundness, font size, and text content.
5.  An option to print the sign, text, or both.

I'm hoping you can generate a set of OpenSCAD files that accomplish this. I don't have any existing files to share; this is a fresh start. I'm not looking for any libraries at the moment, just plain vanilla OpenSCAD for now.

[Assistant]
PlantSign.scad:
```
// text
plantCategory = "Bio Tomato";
plantName = "'Tigrella'";

printOptions = "Print All"; //["Print All", "Print Sign", "Print Text"]

module extrudeText(string, fontSize, height){
    linear_extrude(height=height, convexity=10, $fn=50){
        text(
            string,
            size=fontSize,
            font="Segoe Print:Bold",
            halign="center",
            valign="center",
            spacing=0.87
        );
    }
}

module signText(title, subtitle, width, length, height){
    fontSize = 5;

    color("Cornsilk"){
        translate([width/2, 0, 0]){
            translate([0, length*2/3, 0]) {
                extrudeText(title, fontSize+1, height);
            }
            translate([0, length/3-2, 0]) {
                extrudeText(subtitle, fontSize, height);
            }
        }
    }
}

module border(width, length, height, roundness){
    color("Cornsilk"){
        difference(){
            sign(width, length, height, roundness);
            translate([roundness/2,roundness/2,-1]){
                sign(width-3, length-3, height+2, roundness);
            }
        }
    }
}

module borderAndText(title, subtitle, width, length, height, roundness){
    borderHeight = 0.6;

    translate([0,0,height]){
        border(width, length, borderHeight, roundness);
        signText(title, subtitle, width, length, borderHeight);
    }
}

module sign(width, length, height, roundness){
   minkowski(){
        cube([width, length, height]);
        cylinder(r=roundness, h=0.01);
    }
}

module triangle(stakeWidth, height){
    angle = 270;
    rotate([0,0,angle]){
        cylinder(r=sqrt(3)/3*stakeWidth, h=height, $fn=3);
    }
}

module stake(width, height){
    stakeWidth = 15;
    stakeLength = 50;

    translate([width/2-stakeWidth/2,-stakeLength,0]){
        cube([stakeWidth,stakeLength,height]);
    }
    translate([width/2, -stakeLength-(sqrt(3)/6*stakeWidth),0]){
        triangle(stakeWidth, height);
    }
}

module plantSign(width, length, height, roundness){
    color("black"){
        render(){
            union(){
                sign(width, length, height, roundness);
                stake(width, height);
            }
        }
    }
}

module printSign(printSign, printText){
    $fn = 500;
    PRINT_SIGN = "Print Sign";
    PRINT_TEXT = "Print Text";
    PRINT_ALL = "Print All";

    roundness = 3;
    signWidth = 54;
    signLength = 24;
    signHeight = 2;

    if (printOptions == PRINT_SIGN || printOptions == PRINT_ALL ){
        plantSign(signWidth, signLength, signHeight, roundness);
    }
    if (printOptions == PRINT_TEXT || printOptions == PRINT_ALL ){
        borderAndText(plantCategory, plantName, signWidth, signLength, signHeight, roundness);
    }
}

printSign(printOptions);

```


[User]
Hi, I'd like to create a parametric OpenSCAD design for a curved frame to go around a lithophane. I have a current OpenSCAD file that kind of does this but it's a bit rough and I'd like a cleaner solution with more flexibility.

Here's what I'm looking for:

1.  **Parametric Frame:** The frame should be curved, following an arc. I need to be able to control the degree of the arc (e.g., 90 degrees, 180 degrees, etc.).
2.  **Lithophane Compatibility:** The frame needs to be designed to easily align with a lithophane STL, in my workflow I prefer to have the frame start below the Z-plane so that I can import the lithophane and align it correctly. I typically use 3dp.rocks for generating my lithophanes and set the border to zero.
3.  **Aspect Ratio:** The frame needs to be able to handle both portrait and landscape aspect ratios. The frame's arc length should be tied to a maximum size dimension, such that for a landscape lithophane the arc length is that maximum size. For a portrait lithophane, the arc length should be the maximum size * aspect ratio. The height of the frame should be sized to fit.
4.  **Thickness Control:** I need to be able to set the thickness of the frame.
5.  **Radius Adjustment**: I've noticed some variation between the radius that I expect from the 3dp.rocks website and what I need for a nice fit, so I need radius adjustment parameters, different ones for landscape and portrait orientations.
6.  **Optional Support:** It would be nice if I could add an optional support structure that connects to the bottom of the frame, extending out to help prevent warping during printing, but that's also easy to break off. It would be great if the support's size and placement were also parametric.
7.  **Clean Merging:** The frame should have a small overhang on the inner edge to create a clean merge with the lithophane.
8.  **File Structure:** I would like the design to be well-organized and make good use of modules and functions. It'd be great if you could make use of a useful OpenSCAD library, maybe BOSL2 if it would help?
9.  **Slicer Alignment:** As I mentioned above, the frame should be located below the z=0 plane to help with alignment with the lithophane.
10. **Error Checking:** Let's make sure to check that the OpenSCAD version is greater than or equal to 2019 to avoid some issues.

I think that covers everything. Let me know if you have any questions!

[Assistant]
Lithophane_frame.scad:
```
//  TITLE:
//  Curved Lithophane Frame Generator
//
//  SUMMARY:
//  Use this to custom generate a nice frame for a lithophane stl. Typical lithophane stl generators add a clunky "border" by messing with the picture before generating the height map. See for example http://3dp.rocks/lithophane/
//
//  REQUIREMENTS:
//  Original Photo Aspect Ratio (width and height)
//  Some of the lithophane stl measurments
//  Merging with the lithophane stl (Many options, easy see below)
//
//  INSTRUCTIONS:
//  When using this frame, set the border in your lithophane stl generatorto to zero. See Notes below for other helpful 3dRocks settings.
//
//  There are several ways to merge the frame stl with the lithophane stl. FOR THE MOST EASE, this program sets a specific coordinate system for exact merging. To benefit from this, you need to know some tricks, like with the PrusaSlicer. This frame stl is below the z plane! This is to help align your lithophane stl file in the slicer!
//
//  EASIEST MERGE: Just bring both the frame and the lithophane stl into your slicer. For example, first import your frame into PrusaSlicer, (it will properly sit on the bed) then right click on the frame and from the popup menu "Add Part" > "Load" and select your lithophane stl, it will come in aligned properly. Done.
//
//
//  3DP ROCKS NOTES:
//
//  http://3dp.rocks/lithophane/
//
//  Image Settings:
//      Positive Image
//      Mirror Image Off
//      Flip Image Off
//
//  Model Settings:
//      Border 0
//      Thickness 3 (suggested)
//      Thinnest Layer 0.8 (suggested)
//      Vectors per pixel 4 (suggested)
//
//  Select Outer Curve on the Main Model page
//
//  Written in 2020 by Brian Dickens, please keep this reference

//Code Notes:
//3DPRocks use of Photo AspectRatio (AR)
//AR > 1 :: Arclength = MaxSize
//AR < 1 :: Arclength = MaxSize * AR

/*[Photo Parameters]*/
PhotoWidth = 828;
PhotoHeight = 960;

/*[Model Parameters]*/
MaxSize = 100; //mm
Thickness = 3; //mm
Curve = 90; //[0:10:360]

/*[Extra Parameters]*/
ExtraSupport = true;

/*[Advanced 3DPRocks FudgeFactors]*/
PortraitRadiusFF = 1.47; //percent
LandscapeRadiusFF = .2; //percent

//*HIDE FROM CUSTOMIZER
$fn = 250/1; //*
PhotoAspectRatio = PhotoWidth/PhotoHeight;

Frame(
    PhotoAspectRatio,
    Curve,
    MaxSize,
    Thickness,
    PortraitRadiusFF,
    LandscapeRadiusFF,
    ExtraSupport
);

//Broken in 2019.05 OpenSCAD for me:
//OpenSCAD render of lithophane stalls on my computer. DEAD.
//FileName = "per\\per.JPGW86H100T3V4B0A0C90NS.stl";
//union(){
//    import(FileName);
//    Frame(
//        PhotoAspectRatio,
//        Curve,
//        MaxSize,
//        Thickness,
//        PortraitRadiusFF,
//        LandscapeRadiusFF,
//        ExtraSupport
//    );
//}
//sure looks nice though, and would be a nice way to ensure location

//COMMAND LINE:
//
//@echo off
//set PhotoAspectRatio="1.5"
//set Curve=45
//set MaxSize=100
//set OLDDIR=%~dp0
//pushd "C:\Program Files\OpenSCAD"
//openscad -o %OLDDIR%frame.stl %OLDDIR%Lithophane_frame.scad -D PhotoAspectRatio=%PhotoAspectRatio% -D Curve=%Curve% -D MaxSize=%MaxSize%
//popd

module Frame(aspect, deg, mx, th, pff=0, lff=0, support=false){

    //Useless for stopping old files
    assert(
        version_num()>20190000,
        "Curved Lithophane Frame Generator requires OpenSCAD 2019 or later."
    );

    theta = deg * PI/180;
    landscape = aspect>1;

    h = landscape ?
        mx/aspect + 2*th:
        mx + 2*th;

    arclength = landscape ?
        mx:
        aspect*mx;

    radius = arclength/theta;

    //3dp.rocks Radius FudgeFactor, see notes below
    r = landscape ?
        radius + radius*lff/100:
        radius + radius*pff/100;

    //SET LOCATION:

    //rotate to neg y axis
    rot = -90-deg/2;
    //middle of bounding box, see notes below
    yoffset = GetOffset(deg,r,th);
    //frame z, push it down th, the lithophane comes in at z=0
    fz = -th;

    translate([0,yoffset,fz])
    rotate([0,0,rot])
    {
        //bottom
        rotate_extrude(angle = deg)
            translate([r,0,0])
            FrameShape(th);
        //top
        rotate_extrude(angle = deg)
            translate([r,h,0])
            rotate([0,180,180])
            FrameShape(th);
        //side
        linear_extrude(height = h, center = false)
            translate([r,0,0])
            FrameShape(th);
        //side
        linear_extrude(height = h, center = false)
            translate([r*cos(deg),r*sin(deg),0])
            rotate([0,180,-180+deg])
            FrameShape(th);

        //support
        if(support){

            /*TODO break these out into parameters */
            fw=20; //foot width
            fd=10; //foot depth
            uth=.6; //upright thickness
            tl=5; //touch frame tab length
            fh=1.8; //flat tab height
            /*END TODO */

            rp=r+th/2; //dist to end of arc

            //side
            translate([rp,0,0])
                FrameSupport(h,fw,fd,uth,tl,fh);

            //side
            translate([rp*cos(deg),rp*sin(deg),0])
                rotate([0,0,-180+deg])
                FrameSupport(h,fw,fd,uth,tl,fh);
        }
    }
}

module FrameShape(w){
    //main frame is square
    square([w,w]);
    //overhang / merge helper
    polygon([[0,w],[w,w],[0,7*w/4]]);
}

module FrameSupport(h,fw,fd,uth,tl,fh){
    translate([-fw/2,-fd-tl,0]){

        //foot
        linear_extrude(height = fh, center = false)
            square([fw,fd]);

        //perpendicular upright
        linear_extrude(height = h, center = false)
            translate([0,fd,0])
            square([fw,uth]);

        //parallel upright
        linear_extrude(height = h, center = false)
            translate([fw/2,fd,0])
            square([uth,tl]);
    }
}

function
    GetOffset(deg, r, th) =
        let(c = r*cos(deg/2), b = r-c, x = c + b/2)
        x + th/2; //see notes below

//  Notes:
//
//  GetOffset:
//  bounding box offset after rotating center line to -y axis
//  c = r*cos(deg/2) = distance from origin to middle of secant
//  b = (r-c) = thickness of arc
//  x = c+b/2 = distance from origin to arc center
//  yoffset = x + ModelThickness/2 = distance from origin to center of bounding box
//
//  3DPRocks Radius FudgeFactor
//  just experimented, don't know what 3dp.rocks is doing
//
//  Written in 2020 by Brian Dickens, please keep this reference

```


[User]
I'm looking to design a customizable nut dish that will fit on top of my popcorn bucket. I have a West Bend Medium Popcorn Bucket from Target, but I want the design to be flexible enough to fit other buckets or dishes as well.

The basic idea is a dish with a hollow, cylindrical main body. I need to be able to specify the diameter of the bottom and the top, the overall height, and the wall thickness. The bottom should also have a central hole to fit over the lip of a bucket or bowl, and I need to control the diameter of that hole at the bottom. The top of that hole needs to taper out.

Additionally, I'd like to include an optional grid of small holes in the bottom of the dish for drainage or whatever. I should be able to control the size of these holes individually, and also control the spacing between the holes in the grid. If I don't want any holes, I'd like to be able to disable them.

Ideally, the design should be parametric, meaning that all of the key dimensions (diameters, height, wall thickness, hole sizes, spacing, etc.) can be specified as variables at the top of the file.  I want to be able to change one variable and have the design update appropriately.

For libraries, could you please make sure to include the `BOSL2` library for creating parameters and `MCAD` for its useful shapes. I'm familiar with these and find them very helpful. It should be possible to set the `$fn` for the details as well.

I think that's it. Let me know if you have any questions and thanks for the help!

[Assistant]
NutDish.scad:
```
//Diameter of Bottom
DiaB=166;
RadB=DiaB/2;
//Diameter of Top
DiaT=176;
RadT=DiaT/2;
//Height
Height=50;
//Wall Thickness
WallT=2;
//Diameter of Hole at Bottom
HoleD=50;
HoleRB=HoleD/2;
HoleRT=RadT-RadB+HoleRB;
//Vertical Hole Size (Zero for None)
HoleY=5;
//Horizontal Hole Size (Zero for None)
HoleX=5;
//Space Between Holes (Zero is Invalid)
Space=1;
//Detail (low for corse details, high for fine details, Zero for Default)
$fn=360;

difference()
{
    union()
    {
        difference()
        {
            cylinder(Height,RadB,RadT);         //Outside Wall
            translate([0,0,4])
                cylinder(Height,RadB-WallT,RadT-WallT); //Inside Wall
        }
        translate([0,RadB-HoleRB,0])            //Location of Shell Hole
            cylinder(Height,HoleRB,HoleRT);     //Shell Hole
    }
    translate([0,RadB-HoleRB,-1])                        //Cut Shell Hole
        cylinder(Height+1.1,HoleRB-WallT,HoleRT-WallT);                 //Cut Shell Hole
    if(HoleY*HoleX>0){                         //Disable Holes
        difference()                           //Cut out holes exept inside
        {
            for(y=[-RadT:Space+HoleY:RadT])          //Grid of hole on Y-Axis
            {
                for(x=[-RadT:Space+HoleX:RadT])      //Grid of holes on X-Axis
                {
                    translate([x,y,2])        //Move holes into a grid
                        cube([HoleX,HoleY,6],true); //Holes
                }
            }
            union()
            {
                difference()
                {
                    cube([DiaT,DiaT,20],true);
                    translate([0,0,-5])
                        cylinder(Height+.1,RadB-WallT-1,RadT-WallT-.4);
                }
                translate([0,RadB-HoleRB,0])
                    cylinder(Height,HoleRB,HoleRT);
            }
            translate([0,RadB-HoleRB,-1])
                cylinder(Height+1.1,HoleRB-WallT,HoleRT-WallT);
        }
    }
}
```


[User]
I'm looking to create a bracket that joins two edges together, and I'd like it to be configurable with parameters for different edge thicknesses, bracket length, and screw holes. I have a basic concept in mind, and I'm hoping you can help me create the OpenSCAD file.

Here's what I need:

*   **Bracket Structure:**
    *   The bracket should have a basic "U" shape for each edge, effectively wrapping around each edge. I need to be able to specify the thickness of the material used for the bracket itself.
    *   I want to be able to specify the lengths of the two edges that are to be coupled, and they might be different sizes.
    *   The bracket should have a defined overall length.
    *   I'd like an option to have the edges be coupled either centered on the bracket or flush to one side of it.
*   **Screw Holes (Optional):**
    *   I want to be able to specify the number of screw holes to include on the bracket, should I need them.
    *   I'll need to specify the diameter of those screw holes, and these screw holes should go all the way through.
    * The screw holes should be on both sides of the bracket where the edges would be placed.
*   **Output:**
    *   I'd like a single SCAD file containing the bracket.
    *   I'd like the design to be parametric, so I can easily adjust parameters like the wall thickness, bracket length, edge thicknesses, screw count, and screw diameter.

I think that pretty much covers everything, but I am also using the MCAD library, which might make it easier to generate screw holes and other operations.

[Assistant]
edge_couple.scad:
```
// Bracket for coupling two edges together

/* [Bracket Shape] */

// The thickness of the bracket structure
wall_thickness = 2; // [1:10]

// The overall length of the bracket
bracket_length = 200; // [1:500]

// The first of two edges that will be coupled
edge_a_thickness = 50; // [1:200]

// The second of two edges that will be coupled
edge_b_thickness = 50; // [1:200]

// Set edges to be centered or flush
style = 1; // [1:centered,0:flush]

/* [Screw Holes] */

// Number of screw holes to add
screw_count = 0; // [0:100]

// Diameter of screw holes
screw_diameter = 5; // [1:10]

{
    difference() {
        centered = style==1;
        pocket = wall_thickness/2;
        min_depth = screw_count>0
            ?screw_diameter+wall_thickness*2
            :wall_thickness;

        a_edge = -pocket-max(min_depth,edge_a_thickness);
        a_inner = edge_a_thickness;
        a_outer = a_inner+wall_thickness*2;
        a_left_outer = centered?-a_outer/2:0;
        a_left_inner = centered?-a_inner/2:wall_thickness;
        a_right_outer = centered?a_outer/2:a_outer;
        a_right_inner = centered?a_inner/2:a_inner+wall_thickness;

        b_edge = pocket+max(min_depth,edge_b_thickness);
        b_inner = edge_b_thickness;
        b_outer = b_inner+wall_thickness*2;
        b_left_outer = centered?-b_outer/2:0;
        b_left_inner = centered?-b_inner/2:wall_thickness;
        b_right_outer = centered?b_outer/2:b_outer;
        b_right_inner = centered?b_inner/2:b_inner+wall_thickness;

        linear_extrude(
            height=bracket_length,
            center=false
        ) {
            polygon([
                [a_left_outer,-pocket],
                [a_left_outer,a_edge],
                [a_left_inner,a_edge],
                [a_left_inner,-pocket],
                [a_right_inner,-pocket],
                [a_right_inner,a_edge],
                [a_right_outer,a_edge],
                [a_right_outer,-pocket],

                [max(a_right_outer, b_right_outer),-pocket],
                [max(a_right_outer, b_right_outer),pocket],

                [b_right_outer,pocket],
                [b_right_outer,b_edge],
                [b_right_inner,b_edge],
                [b_right_inner,pocket],
                [b_left_inner,pocket],
                [b_left_inner,b_edge],
                [b_left_outer,b_edge],
                [b_left_outer,pocket],

                [min(a_left_outer, b_left_outer),pocket],
                [min(a_left_outer, b_left_outer),-pocket],

                [a_left_outer,-pocket]
            ]);
        }

        if (screw_count > 0) {
            a_screw = a_edge/2;
            b_screw = b_edge/2;
            screw_length = max(a_outer,b_outer)+wall_thickness*2+1;
            screw_spacing = (bracket_length-wall_thickness/2)/screw_count;
            screw_start = screw_spacing/2+wall_thickness;

            for (p = [1:screw_count]) {
                screw_placement = (p-1)*screw_spacing+screw_start;
                for (screw_side = [a_screw,b_screw]) {
                    translate(v = [0,screw_side,screw_placement]) {
                        rotate([0,90,0]) {
                            cylinder(
                                $fn = 128,
                                h = screw_length,
                                d = screw_diameter,
                                center = centered
                            );
                        }
                    }
                }
            }
        }
    }

}
```


[User]
Hey, I'm looking to design some clips for cable trunking, specifically for the kind used in air conditioning installations but also potentially for electrical trunking. I've already got some basic dimensions and a rough concept but would need a parametric OpenSCAD file that can adjust to different sizes and also allow for tweaking some details.

The basic idea is a U-shape, where the two legs of the U clip onto the edges of the trunking. Here are the things I need to control:

*   **Trunking Dimensions:** The internal length and width of the trunking. I'd need to set these parameters. Let's call them `trunking_length` and `trunking_width`.  For example, a typical trunking size would be like 80mm length x 60mm width, or 140mm length x 90mm width.
*   **Clip Thickness:** The thickness of the material used for the clip, let's say `clip_thickness`.  A good starting point would be 2mm.
*   **Clip Height:** The height of the clip, i.e., how far it extends from the trunking surface, `clip_height`.  I’m thinking around 30mm.
*   **Clip Leg Angle:** The angle at which the legs of the U bend inwards, let’s call that `clip_leg_angle`. A value like 10 degrees might work.
*   **Leg Width:** The width of each clip leg, or how far in each leg extends, `clip_leg_width`. This should be controlled relative to the width of the trunking.
*   **Spine Details:** On each leg, I'd like a few spines or ribs that add grip, let's call them `spine_width`.  Each spine should have a width, maybe 5.5mm. I’d want to control how many ribs or spines on each leg using another parameter, `number_of_spines`.  I want those spines to be rotated inward at an angle, maybe something like 40 degrees.

Also, it seems that for larger trunking sizes (e.g., the 140x90 case), the width of the clip will need to be adjusted in some way, so can we have an offset, `width_offset`, that adds some extra width depending on the value of `trunking_length`. Maybe a value of 16 for `trunking_length = 140` would make sense here.

So, to summarize the parameters:

*   `trunking_length`
*   `trunking_width`
*   `clip_thickness`
*   `clip_height`
*   `clip_leg_angle`
*   `clip_leg_width`
*   `spine_width`
*  `number_of_spines`
*   `width_offset`

I think that should cover it. I don't need any specific libraries, just the basic OpenSCAD functions should be enough. Could you generate an OpenSCAD file for this?

[Assistant]
goulotte_clips.scad:
```

//goulotte_l = 80;
//goulotte_p = 60;
goulotte_l = 140;
goulotte_p = 90;

epaisseur = 2;
hauteur = 30;

delta = goulotte_l == 140 ?16:0;

largeur_haut = 56*(goulotte_l/80)+delta;
angle = 10;
largeur_epine = 5.5;
largeur_piece = 30*(goulotte_p/60);

cube([epaisseur,largeur_haut+epaisseur*2,hauteur]);
translate([0,epaisseur,0])
    rotate([0,0,270-angle])
        cote(epaisseur,largeur_piece,hauteur,largeur_epine);
translate([0,epaisseur+largeur_haut,hauteur])
    rotate([0,180,270+angle])
        cote(epaisseur,largeur_piece,hauteur,largeur_epine);

 module cote(epaisseur, largeur, hauteur, epine)
 {
     cube([epaisseur,largeur,hauteur]);
     for (i=[0:1:4])
     {
        translate([0,(largeur*i+largeur)/5,0])
            rotate([0,0,-40])
                cube([epine,epaisseur*0.7,hauteur]);
     }

 }
```


[User]
I'm looking to generate a few different kinds of rope models using OpenSCAD, and I need some help creating a parametric design. I'd like to be able to create a straight length of rope, a rope spool, a rope ring, and a coil of rope.

Here are the things I need to be able to adjust:

*   **Rope Type:**  I need to be able to choose between a straight line, a spool, a ring, and a coil.
*   **Rope Diameter:** The overall diameter of the rope across all the strands.
*   **Z-Axis Centering:** An option to center the rope object along the Z-axis.
*   **Fragment Count:** A way to specify the number of fragments used to create the circular cross-section of the rope (the number of sides on the polygons). It should have a minimum of 6, be a multiple of 6, and should ideally go up to 48.
*   **Rope Twist:** The rotation in degrees between each layer of the rope as it winds.
*   **Line Length:** Only for the straight "line" rope, its total length.
*   **Spool Loops:** Only for the "spool" rope, how many loops are in the spool.
*   **Spool Radius:** Only for the "spool" rope, the radius of the spool itself (measured to the center of the rope).
*   **Ring Fraction:** Only for the "ring" rope, the portion of a full circle the ring occupies, in degrees.
*   **Ring Radius:** Only for the "ring" rope, the radius of the ring (measured to the center of the rope).
*   **Coil Loops:** Only for the "coil" rope, an approximate number of loops in the coil.
*   **Coil Inner Radius:** Only for the "coil" rope, the radius of the innermost loop (measured to the center of the rope).

The rope should be a triple-strand twisted rope, where each strand is round. The strand diameter and how far the strand center is from the center of the rope should be derived from the overall rope diameter. The cross-section of each strand should be approximated using a polygon, and these should be connected along the length of the rope by the given twist amount.

I understand that increasing the fragment count will increase file size, especially for spool and coil shapes. I also understand that the ring needs a full twist to line up seamlessly, and the coil uses a Theodorus Spiral to keep a consistent layer height.

I'm looking for a parametric design, so ideally, these settings would be adjustable through the OpenSCAD customizer or by editing variables directly in the .scad file. It would be great if all four shapes were implemented in a single file, using modules, or if they were in multiple files, make sure that the files use modules from the same libraries. I do not need any external libraries; I would prefer to have all the code self-contained.

[Assistant]
rope.scad:
```
//
// Rope
//
// name: rope.scad
// by:   Gumyr
// date: March 26th 2020
//
// Four configurations of triple stranded twisted rope - line, spool, ring and coil.
//
// license: Creative Commons Attribution-NonCommercial-ShareAlike 4.0
//          International Public License see:
//          https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
//

//
// Collect the rope parameters
//
// Configuration of the rope
rope_type = "spool";  // [line,spool,ring,coil]
// Diameter of the rope across three strands
rope_diameter = 20;
// Alignment of the rope across the Z-axis
center_z_axis = "no"; // [yes,no]
// The number of fragments used to render a cross section of the rope
fragment_count = 24;  // [6:6:48]
// Amount of rotatation - in degrees - between each layer of the rope
rope_twist = -20;     // [-120:120]
/* [line] */
// Linear lenght of the rope
rope_length = 500;
/* [spool] */
// Number of loops in the spool of rope
spool_loops = 10;
// Radius of the rope spool
spool_radius = 100;
/* [ring] */
// fraction of a circle in degrees
ring_fraction = 360;   // [0:360]
// Radius of the rope ring
ring_radius = 200;
/* [coil] */
// Approximate number of loops in the coil
coil_loops = 10;
// Radius of the inside of the rope coil
coil_inner_radius = 50;

//
// Instantiate the rope
rope(
  rope_type,rope_diameter,center_z_axis=="yes",fragment_count,rope_twist,     // common parameters
  rope_length,                                                                // line parameter
  spool_loops,spool_radius,                                                   // spool parameters
  ring_fraction,ring_radius,                                                  // ring parameter
  coil_loops,coil_inner_radius                                                // coil parameter
  );

//
// Rope Examples
//rope(type="line",length=400,center=true,twist=20);
//rope(type="spool",spoolLoops=10);
//rope(type="spool",spoolLoops=5,center=true,spoolR=50,fc=8*6,twist=20);
//rope(type="ring",ringFraction=360,center=true,twist=-15,diameter=50);
//rope(type="coil",innerCoilR=80,center=true);

//
// Rope definition
module rope(
  type = "spool",      // one of "line", "spool", "ring", or "coil"
  diameter = 20,       // rope diameter
  center = false,      // center on the z-axis
  fc = 4*6,            // face count along the rope proile (must be a multiple of 6)
  twist = -20,         // amount of rotatation on each layer of the rope
  length = 100,        // length of the line rope
  spoolLoops = 10,     // number of loops in the spooled rope
  spoolR = 100,        // radius of the spool (measured from the center of the rope)
  ringFraction = 360,  // fraction of a circle sweep by the rope
  ringR = 200,         // radius of the ring (measured from the center of the rope)
  coil_loops = 10,     // approximate number of loops in the coil
  innerCoilR = 50      // coil inner radius
) {

  strandR = 0.55*diameter/2;        // size of each of the three strands
  coreR   = 0.45*diameter/2;        // distance from center of rope to center of strand

  a = acos(coreR*sin(60)/strandR);
  b = (360-(2*a+60))/2;             // angle between points of contact between strands

  pc = fc+3;                        // point count per layer
  fs=(2*b)/(fc/3);                  // angle of each facit

  loopR = type=="spool" ? spoolR
        : type=="ring"  ? ringR
        : innerCoilR;
  loops = type=="spool" ? spoolLoops
        : type=="ring"  ? ringFraction/360
        : coil_loops;
  //
  // Ensure the twist allows the ring to be seamless when making a full loop
  twist = type=="ring"&&ringFraction==360 ? 15*floor(twist/15) : twist;

  //
  // Caluate the layer count and height to precisely match the given lengths
  approxLayerH = (coreR+2*strandR)*PI/fc;
  layerCount = type=="spool" ? ceil(loops*2*PI*loopR/approxLayerH)
             : type=="ring"  ? ceil(loops*2*PI*loopR/approxLayerH)
             : type=="line"  ? ceil(length/approxLayerH)
             : type=="coil"  ? ceil(loops*2*PI*loopR/approxLayerH)
             : undef;

  layerH = type=="spool" ? (loopR*2*PI/(layerCount/loops))
         : type=="ring"  ? (loopR*2*PI/(layerCount/loops))
         : type=="line"  ? length/layerCount
         : type=="coil"  ? (loopR*2*PI/(layerCount/loops))
         : undef;

  // angle made by each layer of the rope
  c = type=="spool" ? 360*(layerH/(2*PI*loopR))
    : type=="ring"  ? 360*(layerH/(2*PI*loopR))
    : type=="coil"  ? 360*(layerH/(2*PI*innerCoilR))
    : undef;

  // coils use a Theodorus Spiral not a Archimedean Spiral to ensure the layerH ..
  // .. is the same long then entire coil
  theodorusSpiralAngles = type=="coil" ? theodorusSpiralAngles(a,b,layerCount,layerH) : [];
//  theodorusSpiralRadii = type=="coil" ?  theodorusSpiralRadii(a,b,layerCount,layerH) : [];

//  echo(diameter=diameter);
//  echo(strandR=strandR);
//  echo(coreR=coreR);
//  echo(layerCount=layerCount);
//  echo(layerH=layerH);
//  echo(theodorusSpiralAngles=theodorusSpiralAngles);
//  echo(theodorusSpiralRadii=theodorusSpiralRadii);

  // Two dimensional image of the rope profile
  /*
  points = [
  for (j = [0:120:240])
    for (i=[j-b:fs:j+b])
      [coreR*sin(j)+strandR*sin(i), coreR*cos(j)+strandR*cos(i)]
  ];
  polygon(points);
  */
  //
  // Create the point cloud for the polyhedron
  points = type=="line" ? (
    //
    // A simple linear rope
    [
      for (k = [0:layerCount])
        for (j = [0:2])
          for (i=[-fc/6:fc/6])
            [coreR*sin(j*120+k*twist)+strandR*sin(j*120+k*twist+i*fs), coreR*cos(j*120+k*twist)+strandR*cos(j*120+k*twist+i*fs),k*layerH]
     ]
   ) : type=="spool" ? (
    //
    // A spool of rope
    [
      for (k = [0:layerCount])
        for (j = [0:2])
          for (i=[-fc/6:fc/6])
            let (a0=coreR*sin(j*120+k*twist)+strandR*sin(j*120+k*twist+i*fs)+loopR,b0=coreR*cos(j*120+k*twist)+strandR*cos(j*120+k*twist+i*fs),c0=0,x=a0*cos(k*c)-c0*sin(k*c),y=b0,z=a0*sin(k*c)+c0*cos(k*c))
            [x,y+(k*c/360)*(coreR+2*strandR),z]
    ]
  ) : type=="ring" ? (
    //
    // A ring of rope (loops <= 1)
    [
      for (k = [0:layerCount])
        for (j = [0:2])
          for (i=[-fc/6:fc/6])
            let (a0=coreR*sin(j*120+k*twist)+strandR*sin(j*120+k*twist+i*fs)+loopR,b0=coreR*cos(j*120+k*twist)+strandR*cos(j*120+k*twist+i*fs),c0=0,x=a0*cos(k*c)-c0*sin(k*c),y=b0,z=a0*sin(k*c)+c0*cos(k*c))
            [x,y,z]
    ]
  ) : type=="coil" ? (
    //
    // A coil of rope
    [
      for (k = [0:layerCount])
        for (j = [0:2])
          for (i=[-fc/6:fc/6])
            let (
              a0=coreR*sin(j*120+k*twist)+strandR*sin(j*120+k*twist+i*fs)+(theodorusSpiralAngles[k]/360)*(coreR+2*strandR)+innerCoilR,
              b0=coreR*cos(j*120+k*twist)+strandR*cos(j*120+k*twist+i*fs),
              c0=0,
              t=theodorusSpiralAngles[k],
              x=a0*cos(t)-c0*sin(t),
              y=b0,
              z=a0*sin(t)+c0*cos(t)
            )
            [x,y,z]
    ]
  ) : undef;

  //
  // The polyhedron's faces are the same for all the rope types
  // Note: the order of the points within each face is optimized for twist to avoid obtuse triangles
  faces = (twist>0) ?
    [
      for (p = [0:fc])        // near end cap
        [p+1,p,0],            // reverse order for manifold object
      for (p = [0:fc])        // far end cap
        [(layerCount)*pc,p+(layerCount)*pc,p+1+(layerCount)*pc],
      for (l = [0:layerCount-1])
        for (p = [0:fc+1])
          [p+l*pc,(p+1)%pc+l*pc,p+pc+l*pc],
      for (l = [0:layerCount-1])
        for (p = [0:fc+1])
          [(p+1)%pc+l*pc,(p+1)%pc+pc+l*pc,p+pc+l*pc]
    ]
  :
    [
      for (p = [0:fc])        // near end cap
        [p+1,p,0],            // reverse order for manifold object
      for (p = [0:fc])        // far end cap
        [(layerCount)*pc,p+(layerCount)*pc,p+1+(layerCount)*pc],

      for (l = [0:layerCount-1])
        for (p = [0:fc+1])
          [p+l*pc,(p+1)%pc+l*pc,(p+1)%pc+pc+l*pc],
      for (l = [0:layerCount-1])
        for (p = [0:fc+1])
          [p+l*pc,(p+1)%pc+pc+l*pc,p+pc+l*pc]
     ]
  ;

  // Translation if centered
  transRope = type=="spool" ? [0,0,-(loops+1)*(coreR+2*strandR)/2]
            : type=="ring"  ? [0,0,-(coreR+2*strandR)/2]
            : type=="coil"  ? [0,0,-(coreR+2*strandR)/2]
            : type=="line"  ? [0,-(coreR+2*strandR)/2,-length/2]
            : undef;
  translateRope = center ? transRope : [0,0,0];

  // Rotate the spool so up is on the Z axis
  rotateRope = type=="spool" ? 90
             : type=="ring"  ? 90
             : type=="coil"  ? 90
             : 0;
  translate(translateRope)
  rotate([rotateRope,0,0])
  translate([0,strandR+coreR/2,0])
    polyhedron(points,faces);
}

//
// Convert degrees to radians
function toRadians(degrees) = 2*PI*degrees/360;

//
// Calculate the angles from to the origin to equal sized segments of a spiral using the Theodorus Spiral ..
// .. as a close approximation of these angles.  Once the angles have been determined, the equation for an ..
// .. Archimedean Spiral (r = a + bθ) may be used to calculate the radius under some conditions.
//
// This is a recursive function, with several parameters used internally. User parameters are:
// - a & b from the Spiral
// - numSegments must be an positive integer
// - segmentSize is the length of each of the segments
//
function theodorusSpiralRadii(a,b,numSegments,segmentSize,i=0,radiusList=[]) =
  let (r = i==0 ? 0 : sqrt(radiusList[i-1]*radiusList[i-1]+segmentSize*segmentSize))
    i==0 ? theodorusSpiralRadii(a,b,numSegments,segmentSize,i+1,[a])
  : i==numSegments ? concat(radiusList,r)
  : theodorusSpiralRadii(a,b,numSegments,segmentSize,i+1,concat(radiusList,r));

function theodorusSpiralAngles(a,b,numSegments,segmentSize,i=0,angleSum=0,angleList=[],radiusList=[]) =
  let (
    r = i==0 ? 0 : sqrt(radiusList[i-1]*radiusList[i-1]+segmentSize*segmentSize),
    theta = i==0 ? 0 : atan(segmentSize/radiusList[i-1])
    )
    i==0 ? theodorusSpiralAngles(a,b,numSegments,segmentSize,i+1,0,[0],[a])
  : i==numSegments ? concat(angleList,angleSum+theta)
  : theodorusSpiralAngles(a,b,numSegments,segmentSize,i+1,angleSum+theta,concat(angleList,angleSum+theta),concat(radiusList,r));
```