[User]
Okay, I'm looking to create some wedding favor tokens, similar to the ones described in that OpenSCAD file I found, but with more flexibility. I really like the heart shape, but I'd like the ability to change the overall shape and text more easily, and have a bit more control over the spacing and layout of the text and other design elements.

I'd like the core of it to be a token with a customizable base shape, not just a heart. I'm thinking a circle, a rounded rectangle, or maybe even a polygon would be nice options. Inside the main shape, I want to engrave or extrude custom text, and also allow for adding small images or simple icons. I'm imagining the text and images as separate elements that can be positioned and scaled independently.

Specifically, can you generate OpenSCAD code that lets me:

1.  **Change the base shape:** I'd need to be able to select from a circle, a rounded rectangle (with adjustable corner radius), or a regular polygon (with adjustable number of sides). I would specify the size of the shape.

2.  **Add customizable text:** Allow me to specify the text content, font, font size, and positioning. I'd like to be able to add multiple lines of text, with the ability to set the vertical spacing between them. Maybe add a simple rotation parameter as well. I want to be able to set whether the text is engraved, or extruded from the surface.

3.  **Add customizable images/icons:** I'd need to be able to load simple SVG images or pre-made OpenSCAD modules, and then place and scale them on the token. I think I want to be able to set whether the images/icons are engraved, or extruded from the surface.

4.  **Control overall token thickness:** I need a parameter to adjust the total height of the token.

5.  **Control engraving/extrusion depth:** If the text or images/icons are to be engraved, I need a way to set the depth of the engraving. If they are to be extruded, I need a way to set the height of the extrusion.

6. **Border option:** Include an option to create a border around the perimeter. I want to specify both the border offset and height/depth, independent of the shape height.

7. **Preview:** It'd be ideal if the layout of elements within the token is easy to preview, either via parameters or separate code sections, so I can make sure all the elements fit well.

8. **Print layout:** Include a module that automatically lays out a grid of tokens for printing, where I can specify the row/column number and token spacing.

I'd prefer to avoid needing to install extra libraries if at all possible, but if necessary to get a nicer output, I'm willing to consider BOSL or BOSL2. But I'd like to keep it simple if possible.

[Assistant]
wedding_token.scad:
```
/*
21 grams
47 tokens

0.45g per token
1000g = £12
1g = £0.012 = 1.2p
1 token = 0.54p
*/

heartSize=14;
heartHeight=1;
$fn=50;

token();
*!forPrinting();
//sizecheck
*%translate([-3,-3])square(20,center=true);

module forPrinting()
{
  for(j=[0:1])
  {
    translate([heartSize*1.1*j*2,-heartSize*1.1*j*2])
    {
      for(i=[0:3])
      {
        translate([heartSize*1.1*i,heartSize*1.1*i])
        {
          token();
          translate([heartSize*1.1,-heartSize*1.1])
          rotate(180)
          token();
        }
      }
    }
  }
}

module token()
{
  linear_extrude(heartHeight)
  shape();
  #linear_extrude(heartHeight*1.5)
  words();
  #linear_extrude(heartHeight*1.5)
  border();
}

module border()
{
  difference()
  {
    shape();
    offset(-1)shape();
  }
}

module words()
{

rotate(45+90)
translate([0,heartSize/2.5])
text("E&N",size=heartSize/2.2,font="Gill Sans MT:style=Bold",halign="center",valign="center");

rotate(45+90)
translate([0,0.5])
text("12/08/17",size=heartSize/5,font="Gill Sans MT:style=Bold",halign="center",valign="center");

*
rotate(45+90)
translate([0,-3])
text("xxx",size=heartSize/5,font="Gill Sans MT:style=Bold",halign="center",valign="center");

  }

module shape()
{
  translate([-heartSize/2,-heartSize/2])
  {
translate([heartSize/2,0])circle(d=heartSize);
translate([0,heartSize/2])circle(d=heartSize);
square(heartSize);}
}
```


[User]
I'd like to generate a 3D model of a cellular automaton using OpenSCAD. The structure should be a vertical stack of 2D grids, each representing a step in the automaton's evolution.

Here are the parameters I'd like to control:

*   **Profondeur:** This controls the depth of each 2D grid, which affects the overall size of the grid. It effectively makes the grid wider and longer.
*   **Etages:** This specifies the number of steps in the automaton's evolution, which determines the height of the final stack.
*   **Redim:** This is a scaling factor to adjust the size of the individual cells in the grid.
*   **cell0 to cell8:** These boolean values determine the conditions for a cell to live in the next generation based on the number of living neighbors it has. For example, if `cell3` is true, a cell will live in the next generation if it has exactly 3 neighbors in the current generation.

The automaton should start with a single live cell in the center of the grid. The grid should be toroidal (cells on the edges "wrap around") when calculating neighbor counts.

The cells should be cubes, and each step (or grid) should be rendered in a slightly different color to visually represent the evolution over time. The color should progress from a starting color to a destination color.

I'd like the code to be parametric so that I can change these parameters and re-render the model. Also, I don't want to use any libraries.

[Assistant]
automatecellulaire.scad:
```
/* [Pamètres de bases] */
//Hauteur de la structure
Profondeur=7;
//Nombre d'étages
Etages=14;
// Redimensionnement
Redim = 2.01;
/* [Conditions de vie] */
// 0 cellule vivante
cell0=false;
// 1 cellule vivante
cell1=true;
// 2 cellules vivantes
cell2=false;
// 3 cellules vivantes
cell3=false;
// 4 cellules vivantes
cell4=false;
// 5 cellules vivantes
cell5=false;
// 6 cellules vivantes
cell6=false;
// 7 cellules vivantes
cell7=false;
// 8 cellules vivantes
cell8=false;

Taille=(Profondeur*2)+2; Hauteur=Profondeur+Etages; Mid=ceil(Taille/2);
points = [for (a = [0 : Taille]) [for (b = [0 : Taille]) if(b!=Mid || a!=Mid) 0 else 1 ]];

translate([-Mid-Redim/2,-Mid-Redim/2,Hauteur+1])
suite(it=1,maxit=Hauteur);

module suite()
{
translate([0,0,-it*1])
  for(i=[0:Taille])
  {
    for(j=[0:Taille])
    {
      translate([i,j])
      if(points[i][j]==1)
      {
      color([1/maxit*(it-1)/Taille*i,0.5-maxit*(it-1)/Taille*j,1-(1/maxit*(it-1))/Taille*(i+j)/2.1,1])
        scale(Redim)
        cube(1);
      }
    }
  }
  if(it<=maxit)
  {
    points2 = [
    for (a = [0 : Taille])
    [for (b = [0 : Taille])
      if(a==0 ||
      b==0 ||
      a==Taille ||
      b==Taille ||
      voisinage(x=a,y=b,p=points)==(cell0==false ? 0 : 1) ||
      voisinage(x=a,y=b,p=points)==(cell1==false ? 1 : 0) ||
      voisinage(x=a,y=b,p=points)==(cell2==false ? 2 : 0)  ||
      voisinage(x=a,y=b,p=points)==(cell3==false ? 3 : 0)  ||
      voisinage(x=a,y=b,p=points)==(cell4==false ? 4 : 0)  ||
      voisinage(x=a,y=b,p=points)==(cell5==false ? 5 : 0)  ||
      voisinage(x=a,y=b,p=points)==(cell6==false ? 6 : 0)  ||
      voisinage(x=a,y=b,p=points)==(cell7==false ? 7 : 0)  ||
      voisinage(x=a,y=b,p=points)==(cell8==false ? 8 : 0) ) 0
      else 1 ]];
    suite(it=it+1,maxit=maxit,points=points2);
  }
}

function voisinage(x,y,p) = (p[x-1][y-1]+p[x][y-1]+p[x+1][y-1]+p[x-1][y]+p[x+1][y]+p[x-1][y+1]+p[x][y+1]+p[x+1][y+1]);

```


[User]
Hey, I'm looking to create a customizable pulley wheel in OpenSCAD, and I've got a bunch of specific needs. I'd like it to be parametric so I can easily adjust things later. I've seen a design that lets you choose between different bearing styles ("Two", "One", or "None"), and I'd like mine to do the same.

For the bearings, I need to support a few standard sizes: 608 and 625 bearings, as well as a custom option where I can specify the inner diameter, outer diameter, and thickness. If I choose no bearing, I want it to generate a simple hole for a screw, with the screw size also being customizable.

The pulley wheel itself should have a thickness and outer diameter that I can set, and I need to control the height and thickness of the walls on the edges of the pulley wheel (the holders). I'd also like an option to choose between sharp-edged holders and rounded ones.

Finally, I need to specify the fillet for the bearing insertion, and have the ability to disable this, too.

I think that's everything! Let me know if you need more info. I don't have any code to start from.

[Assistant]
pulley_wheel.scad:
```
//**************Constants********************
/* [Global] */

/* [Basic Settings] */
// Bearing Style
BEARING_STYLE = "Two"; // [Two,One,None]

// Bearing Type, you can ignore the charactes after the number (i.e. "zz" or "r2s") as they have no impact on the size (Can be ignored when Bearing Style is "None")
BEARING_TYPE = "B625"; // [BCustom,B608,B625]

/* [Pulley Dimensions] */
// ... of the whole pulley wheel (mm)
THICKNESS = 12.7;
// ... of the pulley wheel without holders (mm)
OUTER_DIAMETER = 17.5;
// Height of the walls on the edges (mm)
HOLDER_HEIGHT = 2.25;
// Thickness of one wall (mm)
HOLDER_THICKNESS = 1.6;

/* [Settings for no Bearing] */

// You may type 5 for M5, 4 for M4 and so on
SCREW_SIZE = 5;

/* [Fillets and Roundings] */

// Fillet for inserting Bearings
BEARING_INSERTION_FILLET = "yes"; // [yes,no]
//
ROUND_HOLDERS = "no"; // [yes,no]

/* [Custom Bearing Settings] */

// (mm)
BEARING_THICKNESS = 5;
// (mm)
BEARING_OUTER_DIAMETER = 16;
// (mm)
BEARING_INNER_DIAMETER = 5;
/* [Hidden] */

//****************Variables (in mm)******************
BEARING_HOLDER_HEIGHT = 1.34;
// Vectors for Bearings
// Inner Diameter|Outer Diameter|Thickness
V_CUSTOM = [BEARING_INNER_DIAMETER,BEARING_OUTER_DIAMETER,BEARING_THICKNESS];
V_608 = [8,22,7];
V_625 = [5,16,5];

//****************Geometry*******************

$fa=0.5; $fs=0.5;
if (BEARING_TYPE == "BCustom")
{
  Pulley(style = BEARING_STYLE, v_bearing = V_CUSTOM, thickness = THICKNESS, o_diam = OUTER_DIAMETER, h_height = HOLDER_HEIGHT, h_thickness = HOLDER_THICKNESS, s_size = SCREW_SIZE, b_h_height = BEARING_HOLDER_HEIGHT, b_in_fillet = BEARING_INSERTION_FILLET,r_holder = ROUND_HOLDERS);
}
else if (BEARING_TYPE == "B608")
{
  Pulley(style = BEARING_STYLE, v_bearing = V_608, thickness = THICKNESS, o_diam = OUTER_DIAMETER, h_height = HOLDER_HEIGHT, h_thickness = HOLDER_THICKNESS, s_size = SCREW_SIZE, b_h_height = BEARING_HOLDER_HEIGHT, b_in_fillet = BEARING_INSERTION_FILLET,r_holder = ROUND_HOLDERS);
}
else if (BEARING_TYPE == "B625")
{
  Pulley(style = BEARING_STYLE, v_bearing = V_625, thickness = THICKNESS, o_diam = OUTER_DIAMETER, h_height = HOLDER_HEIGHT, h_thickness = HOLDER_THICKNESS, s_size = SCREW_SIZE, b_h_height = BEARING_HOLDER_HEIGHT, b_in_fillet = BEARING_INSERTION_FILLET,r_holder = ROUND_HOLDERS);
}
//WallHole(side = "Right", shape="Roundy Rectangle", off = 0, size = 33);
//Hole(xoff = 0, yoff = 0, xsize = 25, ysize = 15, radius = 7.5);
//****************Modules********************

//Main Modules
module Pulley(style = "Two", v_bearing = V_625, thickness = THICKNESS, o_diam = OUTER_DIAMETER, h_height = HOLDER_HEIGHT, h_thickness = HOLDER_THICKNESS, s_size = SCREW_SIZE, b_h_height = BEARING_HOLDER_HEIGHT, b_in_fillet = BEARING_INSERTION_FILLET,r_holder = ROUND_HOLDERS)
{

    b_i_diam = v_bearing[0];
    b_o_diam = v_bearing[1];
    b_thickn = v_bearing[2];
    wall_thickn = o_diam/2-b_o_diam/2;
    two_b_h_thickn = thickness-2*b_thickn;
    fillet_radius = 0.4;
    //TODO: ERROR DETECTION
    rotate_extrude(convexity = 10) //$fn = 100)
    translate([o_diam/2, 0]) {
        difference() {
          union() {
          //Homeplate
          if (style == "Two" || style == "One")
          {
              translate([-wall_thickn, 0, 0])
              square([wall_thickn,thickness], false);
          }
          if (r_holder=="yes")
          {
            //Holder up
            translate([0, thickness-h_thickness])
            {
              translate([h_height-h_thickness/2, h_thickness/2])
              {
                circle(r=h_thickness/2);
              }
              square([h_height-h_thickness/2,h_thickness], false);
            }
            //Holder down
            translate([0, 0])
            {
              translate([h_height-h_thickness/2, h_thickness/2])
              {
                circle(r=h_thickness/2);
              }
              square([h_height-h_thickness/2,h_thickness], false);
            }
          }
          else
          {
            //Holder up
            translate([0, thickness-h_thickness])
            {
              square([h_height,h_thickness], false);
            }
            //Holder down
            translate([0, 0])
            {
              square([h_height,h_thickness], false);
            }
          }
          if (style == "Two")
          {
            translate([-b_h_height-wall_thickn, thickness/2 - two_b_h_thickn/2])
            {
              square(size=[b_h_height, two_b_h_thickn], center=false);
            }
          }
          if (style == "One" && thickness > b_thickn)
          {
            translate([-b_h_height-wall_thickn, 0])
            {
              square(size=[b_h_height, thickness/2-b_thickn/2], center=false);
            }
          }
          if (style == "None")
          {
            translate([-(o_diam/2-s_size/2), 0])
            {
              square(size=[(o_diam/2-s_size/2), thickness], center=false);
            }
          }
        }
        if(b_in_fillet == "yes")
        {
          if (style == "Two")
          {
            polygon(points=[
            [-wall_thickn,0],
            [-wall_thickn+fillet_radius,0],
            [-wall_thickn,fillet_radius],
            ]);
            polygon(points=[
            [-wall_thickn,thickness],
            [-wall_thickn+fillet_radius,thickness],
            [-wall_thickn,-fillet_radius+thickness],
            ]);
          }
          if (style == "One" && thickness > b_thickn)
          {
            polygon(points=[
            [-wall_thickn,thickness],
            [-wall_thickn+fillet_radius,thickness],
            [-wall_thickn,-fillet_radius+thickness],
            ]);
          }
          if (style == "None")
          {
            polygon(points=[
            [-(o_diam/2-s_size/2),thickness],
            [-(o_diam/2-s_size/2)+fillet_radius,thickness],
            [-(o_diam/2-s_size/2),-fillet_radius+thickness],
            ]);
            polygon(points=[
            [-(o_diam/2-s_size/2),0],
            [-(o_diam/2-s_size/2)+fillet_radius,0],
            [-(o_diam/2-s_size/2),fillet_radius],
            ]);
          }
        }
      }
    }

}

```


[User]
Okay, I'm looking to create a customizable N scale footbridge for my model railroad, and I'd like the design to be parametric so I can adjust it to fit different areas. I've seen some examples, and I have some ideas, but I’d like to be able to generate a new bridge from scratch based on some high-level parameters.

Specifically, I'd like the bridge to have a main span, upper flights of stairs, and lower flights of stairs. The key things I'd like to control are:

*   **Main Span Width:** The length of the main, flat part of the bridge (in millimeters).
*   **Number of Posts:** The number of vertical supports along the main span.
*   **Upper Steps:** The number of steps in the stair flights that lead up to and away from the main span.
*  **Lower Steps:** The number of steps in the stair flights at the ground level.
*   **Upper Width:** The width of the main span and the upper stair flights (in millimeters).
*   **Lower Width:** The width of the lower stair flights (in millimeters).
*   **Stair Options:** I'd like to choose which side(s) of each end of the bridge have stairs. Specifically, for each end, I'd want to individually select whether to include stairs on the near side (i.e. closest to the viewer), the far side, or on the outside. Each end of the bridge has to be able to independently enable these stairs. For example, the stairs may appear only on the near side of the left end, on the outside of the right end, or all three may be enabled.

I'd also like the code to use these parameters to generate the necessary supports, railings, and other details needed for a functional-looking model. I'm not looking for a specific real-world model, but more of a generic style similar to those common in the UK or Australia.

The design should also have a few hard-coded variables:
 * wallHeight=9.0;
 * stepHeight=1.2;
 * stepLength=1.8;
 * wallThickness=0.8;
 * railHeight=5.0;
 * pillarOverhang=1.5;

I'm not planning on 3D printing anything that is too structurally challenging for my printer (FlashForge Creator Pro), so some level of structural integrity should be considered, but I also want the design to be printable without a lot of extra supports.

Could you please create the OpenSCAD files to accomplish this? I'd expect to see the use of OpenSCAD libraries for any parametric design; I'm not sure which you'll need, so just use the ones you think are best. Also, if you feel it's necessary to split this into multiple files for clarity, that would be fine with me.

[Assistant]
footbridge-var.scad:
```
// N Scale Footbridge, Modified to be customisable in Thingiverse
// MK 15/9/2018

// Thingiverse parameters
// preview[view:north west]

// Width of main span in mm (default=50)
main_span = 50.0; // [20:100]

// Number of posts on main span (default=2)
number_of_posts = 2; // [0:1:10]

// Number of steps in upper flights (default=8) (1.2mm high)
upper_steps = 8; // [1:1:30]

// Number of steps in lower flights (default=14) (1.2mm high)
lower_steps = 14; // [1:1:30]

// Width of main span and upper flights in mm (default=14)
width_upper = 14; // [4:30]

// Width of lower flights in mm (default=12)
width_lower = 12; // [4:30]

// Does the left wing have nearside stairs?
left_nearside = 1; // [1:yes,0:no]

// Does the left wing have outside stairs?
left_outside = 0; // [1:yes,0:no]

// Does the left wing have farside stairs?
left_farside = 0; // [1:yes,0:no]

// Does the right wing have nearside stairs?
right_nearside = 1; // [1:yes,0:no]

// Does the right wing have outside stairs?
right_outside = 0; // [1:yes,0:no]

// Does the right wing have farside stairs?
right_farside = 0; // [1:yes,0:no]

///////////////////////////////////////////////////////////
module dummy() {}  // end of Thingiverse parameters

wallHeight=9.0;
stepHeight=1.2;
stepLength=1.8;
wallThickness=0.8;
railHeight=5.0;
pillarOverhang=1.5;

rise1=lower_steps;
rise2=upper_steps;
span=main_span;
width1=width_lower;
width2=width_upper;

// l=horiz length of wall
module StairWall( n, offset, bottom )
{
    l = n * stepLength;
    color( "red" )
    difference()
    {
        union()
        {
        multmatrix( m=[[1,0,0,0],[0,1,0,offset],[stepHeight/stepLength,0,1,-stepHeight],[0,0,0,1]] )
            cube( size=[l,wallThickness,wallHeight+stepHeight] );

        color( "purple" )
        multmatrix( m=[[1,0,0,0],[0,1,0,offset-wallThickness*0.25],[stepHeight/stepLength,0,1,wallHeight-2*wallThickness],[0,0,0,1]] )
            cube( size=[l,1.5*wallThickness,wallThickness] );
        };

        if( bottom )
        {
            // Cut off the triangular piece below the base
            translate( [-0.5*stepLength,offset-0.1,-2*stepHeight] )
                cube( size=[2*stepLength, wallThickness+0.2,2*stepHeight] );
        }
    }
};

// l=horiz length of wall
module StairHandrail( n, offset )
{
    l = n * stepLength;
    color( "green" )
    {
        multmatrix( m=[[1,0,0,0],[0,1,0,offset],[stepHeight/stepLength,0,1,railHeight],[0,0,0,1]] )
            cube( size=[l,wallThickness,wallThickness] );
    }
};

//
module StairRamp( n, w )
{
    multmatrix( m=[[1,0,stepLength/stepHeight,0.1],[0,1,0,-wallThickness/4],[0,0,1,0],[0,0,0,1]] )
        cube( size=[stepLength,w+wallThickness/2,n*stepHeight] );
};

module Support( l, w, h, h2 )
{
    n = round(w/3);
    for( i=[0.3:(w-0.9)/n:w] )
    {
        translate( [0,i,-h2-stepHeight] )
            cube( size=[l,0.3,h2+h-0.5] );
        translate( [0.5,i,h-0.5-stepHeight] )
            cube( size=[0.7,0.3,1.6] );
        for( j = [0.5+stepLength:stepLength:l-0.0] )
        {
            translate( [j,i,h-0.5-stepHeight] )
                cube( size=[0.7,0.3,0.6] );
        }
    }

}

module Prism( l, w, h, h2 )
{
    difference()
    {
        multmatrix( m=[[1,0,0,0],[0,1,0,0],[stepHeight/stepLength,0,1,-h],[0,0,0,1]] )
        {
            Support( l, w, h, h2 );
        }
        translate( [-0.1,-0.1,-h-h2-2*stepHeight] )
        cube( size=[l+0.2,w+0.2,h+2*stepHeight] );
    };

}

module StairSupport( n, w, h )
{
    color( "blue" )
    translate( [0.5,0,0] )
    Prism( (n-1.0) * stepLength + stepHeight, w, ( n + stepHeight/stepLength ) * stepHeight, h*stepHeight );
};

// n=num steps, w=aisle width, h=num steps from ground
module StairSteps( n, w )
{
    {
        union()
        {
            for( i=[0:n-1] )
            {
                translate( [i*stepLength,0,i*stepHeight] )
                    cube( size=[stepLength+0.1,w,stepHeight+0.1] );
            }
        }
    }
};

module StairColumns( w, h )
{
    ch = wallHeight+(0.7*stepHeight);
    wt = wallThickness;

    translate( [-wt*0.25,-wt*0.25,0] )
        cube( size=[wt*1.5,wt*1.5,ch] );
    translate( [-wt*0.25,w-wt*1.25,0] )
        cube( size=[wt*1.5,wt*1.5,ch] );
}

// n=num steps, w=aisle width, h=num steps from ground, c=columns at lower end?
module Stair( n, w, h, c )
{
    translate( [0,0,h*stepHeight] )
    {
        StairSteps( n, w );
        StairRamp( n, w );
        StairSupport( n, w, h );
        StairWall( n, 0, c );
        StairWall( n, (w-wallThickness), c );
        StairHandrail( n, wallThickness/2 );
        StairHandrail( n, (w-1.5*wallThickness) );
        if( c != 0 )
            StairColumns( w, h );
    }
};

/////////////////////////////////////////////////////

// l=length of aisle, w=width of aisle, h=num steps off ground, a=angle
// w1-4=wall on side1-4
module Platform( l, w, h, sup, w1, w2, w3, w4, np )
{
    translate( [-wallThickness/4-0.001,-wallThickness/4-0.001,(h)*stepHeight-0.2] )
        cube( size=[l+wallThickness/2+0.002,w+wallThickness/2+0.002,stepHeight+0.2] );

    // columns
    ch = wallHeight+(0.7*stepHeight);
    cho = (h-0.5)*stepHeight;
    wt = wallThickness;
    translate( [-wt*0.25,-wt*0.25,cho] )
        cube( size=[wt*1.5,wt*1.5,ch] );
    translate( [l-wt*1.25,-wt*0.25,cho] )
        cube( size=[wt*1.5,wt*1.5,ch] );
    translate( [-wt*0.25,w-wt*1.25,cho] )
        cube( size=[wt*1.5,wt*1.5,ch] );
    translate( [l-wt*1.25,w-wt*1.25,cho] )
        cube( size=[wt*1.5,wt*1.5,ch] );

    if( np > 0 )
        for( i=[1:np] )
        {
            translate( [-wt*0.25+i/(np+1)*l,-wt*0.25,cho] )
                cube( size=[wt*1.5,wt*1.5,ch] );
            translate( [-wt*0.25+i/(np+1)*l,w-wt*1.25,cho] )
                cube( size=[wt*1.5,wt*1.5,ch] );
        }

    // end walls & rails
    th = (h)*stepHeight+wallHeight-2*wallThickness;
    if( w1 )
    {
        translate( [-0.01,w-wallThickness,(h)*stepHeight] )
            cube( size=[l+0.02,wallThickness,wallHeight] );

        translate( [-0.01,w-1.25*wallThickness,th] )
            cube( size=[l,1.5*wallThickness,wallThickness] );

        color("green")
        translate( [-0.01,w-1.5*wallThickness,(h)*stepHeight+railHeight] )
            cube( size=[l,wallThickness,wallThickness] );
    }
    if( w2 )
    {
        translate( [l-wallThickness,-0.01,(h)*stepHeight] )
            cube( size=[wallThickness,w+0.02,wallHeight] );

        translate( [l-1.25*wallThickness,-0.01,th] )
            cube( size=[1.5*wallThickness,w,wallThickness] );

        color("green")
        translate( [l-1.5*wallThickness,-0.01,(h)*stepHeight+railHeight] )
            cube( size=[wallThickness,w,wallThickness] );
    }
    if( w3 )
    {
        translate( [-0.01,0,(h)*stepHeight] )
            cube( size=[l+0.02,wallThickness,wallHeight] );

        translate( [-0.01,-wallThickness*0.25,th] )
            cube( size=[l,1.5*wallThickness,wallThickness] );

        color("green")
        translate( [-0.01,wallThickness/2,(h)*stepHeight+railHeight] )
            cube( size=[l,wallThickness,wallThickness] );
    }
    if( w4 )
    {
        translate( [0,-0.01,(h)*stepHeight] )
            cube( size=[wallThickness,w+0.02,wallHeight] );

        translate( [-wallThickness*0.25,-0.01,th] )
            cube( size=[1.5*wallThickness,w,wallThickness] );

        color("green")
        translate( [wallThickness/2,-0.01,(h)*stepHeight+railHeight] )
            cube( size=[wallThickness,w,wallThickness] );
    }

    // Supports
    if( sup )
    {
        translate( [stepLength/2,0,0] )
        Support( l-2, w, (h+1) * stepHeight, -stepHeight );
    }
};

function StairLength( n, w1, w2 ) = n * stepLength + w1 * width1 + w2 * width2;

module Bracing( h, l )
{
    wt = wallThickness*1.5;
    multmatrix( m=[[1,0,0,0],[0,1,0,0],[h/l,0,1,0],[0,0,0,1]] )
        cube( size=[l-wt,wt,1.5*wt] );
    multmatrix( m=[[1,0,0,0],[0,1,0,0],[-h/l,0,1,h-wt],[0,0,0,1]] )
        cube( size=[l-wt,wt,1.5*wt] );
}

// Solid pillars
// size of platform: l x w, h=num steps off ground
module Pillar( l, w, h )
{
    translate( [pillarOverhang,pillarOverhang,0] )
        cube( size=[l-2*pillarOverhang, w-2*pillarOverhang, h*stepHeight+0.01] );

    t = 1.0*wallThickness;
    po = pillarOverhang*0.8;
    // Corner columns
    o=wallThickness*0.25;
    translate( [po,po,0] )
        cube( size=[2*t, 2*t, h*stepHeight+0.01] );
    translate( [l-po-2*t,po,0] )
        cube( size=[2*t, 2*t, h*stepHeight+0.01] );
    translate( [l-po-2*t,w-po-2*t,0] )
        cube( size=[2*t, 2*t, h*stepHeight+0.01] );
    translate( [po,w-po-2*t,0] )
        cube( size=[2*t, 2*t, h*stepHeight+0.01] );

    // Supports
    translate( [t,-0.3,0] )
        Support( l-t*2.0, 1, (h+1)*stepHeight, -stepHeight );
    translate( [t,w-0.6,0] )
        Support( l-t*2.0, 1, (h+1)*stepHeight, -stepHeight );
    translate( [-0.3,w-t,0] )
        rotate( [0,0,270] )
        Support( w-t*2.0, 1, (h+1)*stepHeight, -stepHeight );
    translate( [l-0.9,w-t,0] )
        rotate( [0,0,270] )
        Support( w-t*2.0, 1, (h+1)*stepHeight, -stepHeight );
}

/////////////////////////////////////////////////////

// Basic Configurable footbridge
o1 = StairLength( rise1, 0, 0 );
o2 = StairLength( rise2, 0, 0 );

translate( [-span/2-o2-width1,31,0] )
rotate( [0,0,270] )
{
    // Left-side lower flights
    if( left_nearside )
        Stair( rise1, width1, 0, 1 );

    if( left_outside )
    {
        translate( [o1+width2, -1*o1, 0] )
        rotate( [0,0,90] )
        Stair( rise1, width2, 0, 1 );
    }

    if( left_farside )
    {
        translate( [2*o1+width2, width1, 0] )
        rotate( [0,0,180] )
        Stair( rise1, width1, 0, 1 );
    }

    // Left-side platform
    translate( [o1, 0, 0] )
    Platform( width2, width1, rise1, 0, 0, 1-left_farside, 1-left_outside, 1-left_nearside, 0 );
    translate( [o1, 0, 0] )
    Pillar( width2, width1, rise1 );

    // Central span and upper flights
    translate( [o1+width2, width1, 0] )
    rotate( [0,0,90] )
    Stair( rise2, width2, rise1, 0 );

    translate( [o1+width2, o2+width1, 0] )
    rotate( [0,0,90] )
    Platform( span, width2, rise1+rise2, 1, 1, 0, 1, 0, number_of_posts );

    translate( [o1, 2*o2+1*width1+span, 0] )
    rotate( [0,0,270] )
    Stair( rise2, width2, rise1, 0 );

    // Right-side platform
    translate( [o1, span+2*o2+width1, 0] )
    Platform( width2, width1, rise1, 0, 1-right_outside, 1-right_farside, 0, 1-right_nearside, 0, 0 );
    translate( [o1, span+2*o2+width1, 0] )
    Pillar( width2, width1, rise1 );

    // Right-side lower flights
    if( right_nearside )
    {
        translate( [0, span+2*o2+width1, 0] )
        Stair( rise1, width1, 0, 1 );
    }

    if( right_outside )
    {
        translate( [o1, span+2*o2+1*o1+2*width1, 0] )
        rotate( [0,0,270] )
        Stair( rise1, width2, 0, 1 );
    }

    if( right_farside )
    {
        translate( [2*o1+width2, span+2*o2+2*width1, 0] )
        rotate( [0,0,180] )
        Stair( rise1, width1, 0, 1 );
    }
};

```


[User]
I'd like to design a telescoping toy, like the one described above, and I'd like you to generate the OpenSCAD code for me. It should be made of multiple segments that slide into each other to extend the overall length, similar to a spyglass.

Here are the key parameters and constraints:

*   **Outer Diameter (OD):** The fully assembled, non-extended telescope should have an outer diameter that is configurable. The default value should be `42mm`.
*   **Height (H):** The height of a single, fully collapsed segment. The default height should be `42mm`.
*   **Wall Thickness (t):** The thickness of the wall of each segment. Default to `1.2mm`.
*   **Clearance (c):** The gap between segments when they're fully collapsed (nested). This prevents them from binding. Default to `1mm`.
*   **Overlap (OL):** The amount each segment overlaps the next when fully extended. The overlap helps prevent the segments from separating when extended. Default to `1mm`.
*   **Maximum Print Angle (angP):** The maximum angle allowed in the design for printing without supports. Default to `40 degrees`.
*  **Taper-Lock Angle (angT):** The angle of the taper that helps each segment interlock when extended. Default to `10 degrees`.
*   **Configuration (config):** A toggle to switch between showing the model assembled vs packed for printing. Default to `0` for packed (print version), and set to `1` to display assembled.
*   **Cross-Section (cs):** A toggle to enable or disable a cross-section view. Default to `1` to show a cross-section, set to `0` for the print version.

The design should consist of several segments, with each segment having a specific profile. The inner diameter of each segment is sized to fit over the outer diameter of the adjacent, inner segment. These segments should be designed such that when they are extended, they create a tapered interlock using the `angT` parameter and when collapsed, they have the configured `c` gap.

The segments should be automatically calculated from the parameters given, where the number of segments is derived based on the provided OD, clearance, overlap, and wall thickness.

When set to the 'packed' configuration, the segments should all be nested together as they would be when printing, and with a cross-section view, or when set to the assembled configuration, the segments should be displayed in their fully extended state. The code should calculate the total extended height. The total extended height should be calculated correctly with the segments stacked in their extended configuration, each segment adding additional length.

The code should output the total number of parts as well as the total extended height in the console using the `echo()` command.

I'd like this to be a single file. It should not require any libraries, except for built-in functions.

[Assistant]
Telescope.scad:
```
// preview[view:east, tilt:top diagonal]

// - Telescope outer diameter (mm)
OD = 42;

// - Telescope height (mm)
H = 42;

// - Wall thickness (mm)
t = 1.2;

// - Clearance between segments (collapsed) (mm)
c = 1;

// - Overlap between segments (erect) (mm)
OL = 1;

// - Maximum print angle (degrees)
angP = 40;

// - taper-lock angle (degrees)
angT = 10; //[5:30]

// - Show as packed or assembled?
config = 0; //[0:Packed (Print version), 1:Assembled]

// - Show cross-section?
cs = 1; //[1:Yes, 0:No (Print version)]

/* [hidden] */

//height of the 'kick'
hk = (OL+c)*tan(90-angP);
//height of the taper
ht = (OL+c)*tan(90-angT);
//height of grip
hg = OL/tan(angT);
//other heights
h1 = hk;
h2 = h1 +ht;
h3 = H -ht;

//radial width of a segment
unitR = t +c +OL +c;

//number of segments/parts
segments = round((OD/2 +c+OL)/unitR);
echo("parts",segments);

//height added by each segment
extension = H -hg -h1;

//total extended height
HTotal = H +extension *(segments-1);
echo("Extended height",round(HTotal));

//X coordinates for standard segment located in outermost position.
//Later segments get translated towards y-axis.
OR1 = OD/2;
OR2 = OR1 +c +OL;
IR1 = OR1 -t -c -OL;
IR2 = OR1 -t;
//first segment with cylindrical profile
p1 = [
	[OR1, 0],
	[OR1, H],
	[IR1, H],
	[IR2, h3],
	[IR2, h1],
	[IR1, 0]];
//standard segment starting with bottom-right corner CCW.
p = [
	[OR1, 0],
	[OR2, h1],
	[OR1, h2],
	[OR1, H],
	[IR1, H],
	[IR2, h3],
	[IR2, h1],
	[IR1, 0]];

//plot polygons. revolve polygons.
difference() {
    rotate_extrude(angle=cs) {
        polygon(p1);
        for(n=[1:1:segments-1]) if(IR1 -unitR *n>0) {
            translate([-unitR *n, config *(extension-c) *n,0])
                polygon(p);
        }
    }
    cube([OD/2, OD/2, cs*H*segments]);
}
```


[User]
Hi! I'm looking for help designing a parametric key holder in OpenSCAD. I've seen a few designs online, but I need something a bit more customized and flexible. I’d also like to improve the generation time.

The basic idea is that it's a two-part key holder, with a top and bottom half that are mirror images of each other (except for text). The two halves will be screwed or bolted together.

Here are the parameters that need to be adjustable:

**Overall Dimensions:**

*   `length`: The total length of the key holder.
*   `width`: The total width of the key holder.
*   `height`: The height of *one* of the two halves.

**Text Engraving/Embossing:**

*   `text_frontTop1`: Text to display at the top of the first half.
*   `text_frontTop1_size`: Text size for the above text (height in mm).
*   `text_frontTop2`: Text to display on the next line of the first half.
*  `text_frontTop2_size`: Text size for the above text (height in mm).
*   `text_frontBottom1`: Text to display at the bottom of the first half.
*   `text_frontBottom1_size`: Text size for the above text (height in mm).
*   `text_frontBottom2`: Text to display on the next line of the first half.
*  `text_frontBottom2_size`: Text size for the above text (height in mm).
*   `text_backTop1`: Text for the top of the second half.
*   `text_backTop1_size`: Text size for the above text (height in mm).
*   `text_backTop2`: Text for the next line of the top of the second half.
*  `text_backTop2_size`: Text size for the above text (height in mm).
*   `text_backBottom1`: Text for the bottom of the second half.
*   `text_backBottom1_size`: Text size for the above text (height in mm).
*   `text_backBottom2`: Text for the next line of the bottom of the second half.
*  `text_backBottom2_size`: Text size for the above text (height in mm).
*   `text_depth`: How deep the text should be (for engraving) or how high the text is (for embossing).
*   `text_type`:  A toggle to choose "embossed" or "engraved" text.
*   `text_offset`:  The distance between the edge of the text and any screw/bolt holes.

**Appearance:**

*   `design`: A toggle to choose between "sharp" or "smooth" base design. Sharp should compile quickly and be a box with the finger indents cut out. Smooth should have rounded edges and take longer to compile.
*  `offset`: How much of a slope to create for the trapezoid shape (when "smooth" is selected).
*  `smooth`: How far the rounding of the edges extends (when "smooth" is selected).
*   `round`: How rounded the corners should be (when "smooth" is selected). This should control the fragment count as well.

**Key Ring:**

*   `key_ring`:  A toggle to choose "yes" or "no" to have a key ring integrated into the design.
*   `ring_hole_diameter`: The diameter of the hole for the key ring.
*   `ring_thickness`: The thickness of the material around the key ring hole.
*   `ring_height`: The height of the key ring.

**Screws/Bolts:**

*   `nut_diameter`: The apothem (flat-to-flat) diameter of the nut.
*   `nut_height`:  The depth that the nut should sink into the base.
*   `screw_head_diameter`: The diameter of the screw head.
*   `screw_shaft_diameter`: The diameter of the screw shaft.
*   `screw_tight`:  A toggle to choose if screw should be a tight fit or a loose fit ("yes" or "no").
*   `screw_height`: How deep the screw head should be set into the base.

**Finger Indentation:**

*   `big_hole_diameter`: The diameter of the finger indentation circle.
*   `down_big_hole`:  The distance from the end of the key holder to the center of the finger indentation.
*   `in_big_hole`: How far the finger indentation should cut into the width.
*   `finger_res`: The number of fragments used for the finger indentation circle.

**Spacers:**

*   `n_spacers`: The number of spacers to generate.
*   `spacer_diameter`: The outer diameter of the spacer.
*   `spacer_height`: The thickness of the spacer.

**Extra Settings**
*   `small_hole_dist`: How far away from the edge the screw/bolt holes should be.

I think that's everything. For the text, I'd like the lines to be centered and spaced well from each other. For the smoothing, I’m hoping you can find a way to round it, bevel it, or miter it that has it compile quickly. I'm also hoping that you can provide a solution that is fast to compile with "sharp" design. I'm not sure if it is something to do with the fragment count or the minkowski function. I'd like to avoid the minkowski function as much as possible. It might be good to have an alternative to the minkowski function so I can have the "smooth" design with quick compile times. If you could keep the code modular, I think that would be easiest to read. Thank you so much!

[Assistant]
Key_Holder_mrmath3_remix.scad:
```
/*----------------------------------------------------------------------------*/
/*-------                             Info                            --------*/
/*----------------------------------------------------------------------------*/

// Key Holder Remix
// Based off of ProteanMan's "Proteus Key Holder"
// link: https://www.thingiverse.com/thing:2749745
// Author: mrmath3
// Version: 3
// Last update: 3/4/19

// Version 2 - Added engraving
// Version 3 - Added simple mode for Thingiverse Customizer

/*----------------------------------------------------------------------------*/
/*-------                          Parameters                         --------*/
/*----------------------------------------------------------------------------*/

/* [DIMENSIONS] */

// The maximum length of the main body
length = 86; // [50 : 1 : 200]

// The maximum width of the main body
width = 25; // [15 : 1 : 40]

// The height of one of the halves
height = 6; // [3 : 0.5 : 8]

/* [TEXT] */

// Text to display at the top of the first half
text_frontTop1 = "AB6";

// Text size (height in mm) of the text above
text_frontTop1_size = 5; // [2 : 0.5 : 7]

// Next line
text_frontTop2 = "Front";

// Text size (height in mm) of the text above
text_frontTop2_size = 4; // [2 : 0.5 : 7]

// Text to display at the bottom of the first half
text_frontBottom1 = "DC3";

// Text size (height in mm) of the text above
text_frontBottom1_size = 5; // [2 : 0.5 : 7]

// Next line
text_frontBottom2 = "Room";

// Text size (height in mm) of the text above
text_frontBottom2_size = 4; // [2 : 0.5 : 7]

// Text to display at the top of the second half
text_backTop1 = "DC2";

// Text size (height in mm) of the text above
text_backTop1_size = 5; // [2 : 0.5 : 7]

// Next line
text_backTop2 = "Locker";

// Text size (height in mm) of the text above
text_backTop2_size = 4; // [2 : 0.5 : 7]

// Text to display at the bottom of the second half
text_backBottom1 = "DC5";

// Text size (height in mm) of the text above
text_backBottom1_size = 5; // [2 : 0.5 : 7]

// Next line
text_backBottom2 = "Office";

// Text size (height in mm) of the text above
text_backBottom2_size = 4; // [2 : 0.5 : 7]

// How deep the text should be embossed / egraved?
text_depth = 1; // [0.5 : 0.5: 3]

// Embossed or engraved?
text_type = "engraved"; // ["embossed", "engraved"]

// How far away (in mm) the text should be from the bolt/screw holes
text_offset = 2; // [0.5 : 0.5 : 4]

/* [APPEARANCE] */

// Sharp base design (for Thingiverse Customizer) or smooth design (for OpenScad (takes ~4 min))
design = "sharp"; // ["sharp", "smooth"]

// How much of a slope to create for the trapezoid shape
offset = 2; // [0 : 0.5 : 5]

// How far (in mm) the rounding of the corners extends
smooth = 1; // [0 : 0.5 : 4]

// How rounded the corners are (high numbers take longer to render)
round = 20; // [5 : 1 : 40]

/* [KEY RING] */

// Choose whether or not to have a key ring
key_ring = "no"; // ["yes", "no"]

// The size of the hole's diameter
ring_hole_diameter = 4; // [2 : 1 : 8]

// The thickness of the perimeter of the key ring
ring_thickness = 3; // [2 : 0.5 : 5]

// The height of the key ring
ring_height = 5; // [3 : 0.5 : 8]

/* [SCREW & BOLT DIMENSIONS] */

// The length of the nut's diameter from flat part to flat part (apothem length)
nut_diameter = 5.4;

// The depth into the base that the nut should fall
nut_height = 3; // [1 : 0.5 : 4]

// The diameter of the head of the screw
screw_head_diameter = 5.8;

// The diameter of the shaft of the screw
screw_shaft_diameter = 2.85;

// Should the screw dig into the print? (else it will slide through hole)
screw_tight = "yes"; // ["yes", "no"]

// The depth into the base that the screw head should be inserted
screw_height = 3; // [1 : 0.5 : 4]

/* [FINGER INDENTATION] */

// The size of the circle that is removed to make the indentation
big_hole_diameter = 24; // [10 : 1 : 40]

// The location (from the end) of the indentation
down_big_hole = 30; // [10 : 1 : 60]

// The amount that the indentation cuts into the base shape (width-wise)
in_big_hole = 7; // [1 : 1 : 15]

// The number of fragments for the indent (the resolution)
finger_res = 50; // [10 : 5 : 80]

/* [SPACERS] */

// The number of spacers to print
n_spacers = 2; // [0 : 1 : 12]

// The outer diameter of the spacer
spacer_diameter = 10; // [3 : 1 : 15]

// The thickness of the spacer
spacer_height = 0.7; // [0.3 : 0.1 : 1.0]

/* [EXTRA SETTINGS] */

// The distance from the edge to the screw/bolt hole
small_hole_dist = 2; // [2 : 0.5 : 10]

/*----------------------------------------------------------------------------*/
/*-------                             Code                            --------*/
/*----------------------------------------------------------------------------*/

/* [HIDDEN] */

nut_wiggle = .5; // to make sure you can get the nut back out
screw_head_wiggle = .3; // smaller than nut wiggle, because easier to remove
spacer_radius_wiggle = 0.1; // to make sure no friction with screw
nut_hole_radius = (nut_diameter + nut_wiggle) / sqrt(3); // change to apothem
r_out_dia = ring_thickness * 2 + ring_hole_diameter; // outer diameter of key ring
text_overlap = (design == "sharp") ? 0 : .39 - round * 0.017; // if "round" is less than 20, then this is needed. This makes the text roughtly the right height
line_space = 1; // this is the space between the lines of text
fudge = .01; // to get diff command to work properly

/* [FUNCTIONS] */

function mod(num, div) = (num / div - floor(num / div)) * div;

/* [RENDER] */

top(key_ring);
bottom(key_ring);
spacers();

/* [MODULES] */

module top(ring = "yes") {
    if (text_type == "embossed")
        top_text(text_location = 0);
    difference() {
        if (ring == "yes" && design == "smooth") {
            union() {
                hole_base(part = 2, ring = "yes");
                key_ring();
            }
        }
        else if (ring == "no" && design == "smooth") {
            hole_base(part = 2, ring = "no");
        }
        else if (ring == "yes" && design == "sharp") {
            union() {
                simple_base(part = 2, ring == "yes");
                key_ring();
            }
        }
        else if (ring == "no" && design == "sharp") {
            simple_base(part = 2, ring = "no");
        }
        if (text_type == "engraved")
            top_text(text_location = -text_depth);
        // screw holes
        // first
        translate([offset + (nut_diameter + nut_wiggle) / 2 + small_hole_dist, 0, 0])
            screw_hole();
        // last
        translate([length - (offset + (nut_diameter + nut_wiggle) / 2 + small_hole_dist), 0, 0])
            screw_hole();
        // middle
        translate([length / 2, 0, 0])
            screw_hole();
    }
}

module top_text(text_location = 0) {
    // text_frontTop1
    translate([text_offset + offset + (nut_diameter + nut_wiggle) * 3 / 2 + small_hole_dist, width / 2, height - text_overlap + text_location + fudge * 3])
        rotate([0, 0, 90])
            linear_extrude(height = text_depth + fudge)
                text(text = text_frontTop1, size = text_frontTop1_size, halign = "center", valign = "center");
    // text_frontTop2
    translate([text_offset + offset + (nut_diameter + nut_wiggle) * 3 / 2 + small_hole_dist + text_frontTop1_size * .5 + text_frontTop2_size * .5 + line_space, width / 2, height - text_overlap + text_location + fudge * 3])
        rotate([0, 0, 90])
            linear_extrude(height = text_depth + fudge)
                text(text = text_frontTop2, size = text_frontTop2_size, halign = "center", valign = "center");
    // text_frontBottom1
    translate([length - (text_offset + offset + (nut_diameter + nut_wiggle) * 3 / 2 + small_hole_dist), width / 2, height - text_overlap + text_location + fudge * 3])
        rotate([0, 0, -90])
            linear_extrude(height = text_depth + fudge)
                text(text = text_frontBottom1, size = text_frontBottom1_size, halign = "center", valign = "center");
    // text_frontBottom2
    translate([length - (text_offset + offset + (nut_diameter + nut_wiggle) * 3 / 2 + small_hole_dist + text_frontBottom1_size * .5 + text_frontBottom2_size * .5 + line_space), width / 2, height - text_overlap + text_location + fudge * 3])
        rotate([0, 0, -90])
            linear_extrude(height = text_depth + fudge)
                text(text = text_frontBottom2, size = text_frontBottom2_size, halign = "center", valign = "center");
}

module bottom(ring = "yes") {
  translate([0, 1.1 * width, 0]) {
      if (text_type == "embossed")
          bottom_text(text_location = 0);
      difference() {
          if (ring == "yes" && design == "smooth") {
            union() {
                hole_base(part = 1, ring = "yes");
                key_ring();
            }
        }
        else if (ring == "no" && design == "smooth") {
            hole_base(part = 1, ring = "no");
        }
        else if (ring == "yes" && design == "sharp") {
            union() {
                simple_base(part = 1, ring == "yes");
                key_ring();
            }
        }
        else if (ring == "no" && design == "sharp") {
            simple_base(part = 1, ring = "no");
        }
          if (text_type == "engraved")
            bottom_text(text_location = -text_depth);
        // nut holes
        // first
        translate([offset + (nut_diameter + nut_wiggle) / 2 + small_hole_dist, 0, 0])
            nut_hole();
        // last
        translate([length - (offset + (nut_diameter + nut_wiggle) / 2 + small_hole_dist), 0, 0])
            nut_hole();
        // middle
        translate([length / 2, 0, 0])
            nut_hole();
    }
    }
}

module bottom_text(text_location = 0) {
    // text_backTop1
    translate([text_offset + offset + (nut_diameter + nut_wiggle) * 3 / 2 + small_hole_dist, width / 2, height - text_overlap + text_location + fudge * 3])
        rotate([0, 0, 90])
            linear_extrude(height = text_depth + fudge)
                text(text = text_backTop1, size = text_backTop1_size, halign = "center", valign = "center");
    // text_backTop2
    translate([text_offset + offset + (nut_diameter + nut_wiggle) * 3 / 2 + small_hole_dist + text_backTop1_size * .5 + text_backTop2_size * .5 + line_space, width / 2, height - text_overlap + text_location + fudge * 3])
        rotate([0, 0, 90])
            linear_extrude(height = text_depth + fudge)
                text(text = text_backTop2, size = text_backTop2_size, halign = "center", valign = "center");
    // text_backBottom1
    translate([length - (text_offset + offset + (nut_diameter + nut_wiggle) * 3 / 2 + small_hole_dist), width / 2, height - text_overlap + text_location + fudge * 3])
        rotate([0, 0, -90])
            linear_extrude(height = text_depth + fudge)
                text(text = text_backBottom1, size = text_backBottom1_size, halign = "center", valign = "center");
    // text_backBottom2
    translate([length - (text_offset + offset + (nut_diameter + nut_wiggle) * 3 / 2 + small_hole_dist + text_backBottom1_size * .5 + text_backBottom2_size * .5 + line_space), width / 2, height - text_overlap + text_location + fudge * 3])
        rotate([0, 0, -90])
            linear_extrude(height = text_depth + fudge)
                text(text = text_backBottom2, size = text_backBottom2_size, halign = "center", valign = "center");
}

module spacers(n = n_spacers) {
  if (n == 0) {
    // don't print any spacers
  }
  else if (n >= 0) {
    max = floor(length / (1.1 * spacer_diameter)); // maximum spacers per column
    for (j = [0 : ceil(n / max) - 1])
      for (i = [0 : min(max - 1, n - j * max - 1)]) {
        translate([i * 1.1 * spacer_diameter, j * 1.1 * spacer_diameter, 0])
          spacer();
      }
    }
}

module spacer() {
  translate([spacer_diameter / 2, spacer_diameter / 2 + 2.2 * width, 0])
    difference() {
      cylinder(h = spacer_height, d = spacer_diameter, $fn = 40);
    // hole to be cut out
      translate([0, 0, -fudge / 2])
        cylinder(h = spacer_height + fudge, d = screw_shaft_diameter + 1, $fn = 30);
    }
}

module key_ring() {
    difference() {
        translate([-ring_hole_diameter / 2, r_out_dia / 4, 0]) {
            rotate([0, 0, 20]) {
                difference() {
                    minkowski() {
                        union() {
                            cylinder(h = ring_height - smooth, d = r_out_dia - 2 * smooth, $fn = 30);
                            translate([0, -r_out_dia / 2 + smooth, 0])
                                cube([2 * (r_out_dia - smooth), r_out_dia - 2 * smooth, ring_height - smooth]);
                        }
                        sphere(r = smooth, $fn = round);
                    }
                    translate([0, 0, -fudge / 2])
                        cylinder(h = ring_height + fudge, d = ring_hole_diameter, $fn = 30);
                }
            }
        }
        translate([-r_out_dia, -r_out_dia, -smooth  - fudge])
            cube([3 * r_out_dia, 3 * r_out_dia, smooth + fudge]);
    }
}

module hole_base(part = 1, ring = "no") { // takes a while to compile
    difference() { // make bottom sharp and flat
        minkowski() { // round edges
            difference() { // cut finger holes
                // Main shape
                polyhedron(PolyPoints, PolyFaces);

                // Finger holes on the sides
                translate([length * (1 - mod(part + 1, 2)) + pow(-1, part) * down_big_hole, in_big_hole - big_hole_diameter / 2 + smooth, 0])
                    finger_hole();
                translate([length * (1 - mod(part, 2)) + pow(-1, part + 1) * down_big_hole, big_hole_diameter / 2 + width - in_big_hole - smooth, 0])
                    finger_hole();

                // hole for key ring
                if (ring == "no") {
                    // don't cut out a space for the ring
                }
                else if (ring == "yes") {
                    // cut out a space for the ring
                    translate([-ring_hole_diameter / 2, width - r_out_dia / 4, -fudge])
                        cylinder(h = height + fudge, d = (ring_hole_diameter +  ring_thickness) + 2 * smooth, $fn = 40);

                }
            }
            sphere(r = smooth, $fn = round);
        }
        translate([0, 0, -(smooth + fudge)])
            cube([length, width, smooth + fudge]);
    }
}

module simple_base(part = 1, ring = "no") { // fast compile and render
    difference() { // cut finger holes
        // Main shape
        polyhedron(SimplePoints, PolyFaces);

        // Finger holes on the sides
        translate([length * (1 - mod(part + 1, 2)) + pow(-1, part) * down_big_hole, in_big_hole - big_hole_diameter / 2 + smooth, 0])
            finger_hole();
        translate([length * (1 - mod(part, 2)) + pow(-1, part + 1) * down_big_hole, big_hole_diameter / 2 + width - in_big_hole - smooth, 0])
            finger_hole();

        // hole for key ring
        if (ring == "no") {
            // don't cut out a space for the ring
        }
        else if (ring == "yes") {
            // cut out a space for the ring
            translate([-ring_hole_diameter / 2, width - r_out_dia / 4, -fudge])
                cylinder(h = height + fudge, d = (ring_hole_diameter +  ring_thickness) + 2 * smooth, $fn = 40);

        }
    }
}

module finger_hole() {
    translate([0 , 0, -fudge / 2])
            cylinder(h = height + fudge, d = big_hole_diameter, $fn=finger_res);
}

module screw_hole() {
    translate([0, width * .5, height - screw_height ])
        union() {
            cylinder(h = height + fudge, d = screw_head_diameter + screw_head_wiggle, $fn = 40);
            translate([0, 0, nut_height - height - fudge])
                if (screw_tight == "yes") {
                    cylinder(h = height, d = screw_shaft_diameter, $fn = 30);
                }
                else if (screw_tight == "no") {
                    cylinder(h = height, d = screw_shaft_diameter + .5, $fn = 30);
                }
        }
}

module nut_hole() {
    translate([0, width / 2, height - nut_height])
        rotate([0, 0, 30])
            union() {
                cylinder(h = height, r = nut_hole_radius, $fn = 6);
                translate([0, 0, nut_height - height - fudge])
                    if (screw_tight == "yes") {
                        cylinder(h = height, d = screw_shaft_diameter, $fn = 30);
                    }
                    else if (screw_tight == "no") {
                        cylinder(h = height, d = screw_shaft_diameter + .5, $fn = 30);
                    }
            }
}

PolyPoints = [ // this subtracts the radius of the sphere used in minkowski "smooth"
  // it also allows the user to change the slant angle of the trapezoid with "offset"
  [ smooth,                   smooth,                  0 ],           //0
  [ length - smooth,          smooth,                  0 ],           //1
  [ length - smooth,          width - smooth,          0 ],           //2
  [ smooth,                   width - smooth,          0 ],           //3
  [ smooth + offset,          smooth + offset,         height - smooth ],  //4
  [ length - smooth - offset, smooth + offset,         height - smooth ],  //5
  [ length - smooth - offset, width - smooth - offset, height - smooth ],  //6
  [ smooth + offset,          width - smooth - offset, height - smooth ]]; //7

SimplePoints = [ // use this one to compile faster
  [ 0,               0,              0 ],       //0
  [ length,          0,              0 ],       //1
  [ length,          width,          0 ],       //2
  [ 0,               width,          0 ],       //3
  [ offset,          offset,         height ],  //4
  [ length - offset, offset,         height ],  //5
  [ length - offset, width - offset, height ],  //6
  [ offset,          width - offset, height ]]; //7

PolyFaces = [
  [0,1,2,3],  // bottom
  [4,5,1,0],  // front
  [7,6,5,4],  // top
  [5,6,2,1],  // right
  [6,7,3,2],  // back
  [7,4,0,3]]; // left
```


[User]
I need an OpenSCAD design for cable clips to keep my brake and shifter cables together on my Brompton folding bike. I'm using Nokon cables, which are quite flexible and need to be held in place.

I'd like to be able to customize the design, so I need the code to be parametric. I need to be able to choose between a clip for two cables or three cables. For each hole, I need to be able to change the diameter individually, and also have the option to add a stopper ring inside the hole; I'd like that stopper to have a little radius, like a rounded edge. I also want a way to add a small nook for a zip tie, to mount the whole clip to the frame, if needed, and I should be able to control the nook radius. Finally, I need to be able to change the height of the clip, the inner distance between cable holes, and the wall thickness of the clip, and the width of the gap in the clip where I'd insert the cables, and I'd like to be able to use a logo, if I choose.

In terms of specific parameters, I'd like the following to be configurable:

*   `height`: The height of the clip
*   `innerDistance`: The distance between the cable holes.
*   `wallThickness`: The thickness of the material around the holes.
*   `wallOpening`: The width of the opening where you insert cables.
*   `withLogos`: A toggle to include a Brompton logo on the clip.
*   `withFrameMount`: A toggle to include a zip tie nook for mounting the clip to the frame.
*    `frameRadius`: The radius of the frame mount (if withFrameMount is true)
*    `cableTieThickness`: The thickness of the cable tie mount.
*   `holeProfile`: A menu to select between two holes and three holes
*   `hole1Diameter`: The diameter of the first hole.
*    `hole1Stopper`: A toggle for a stopper ring in the first hole.
*   `hole2Diameter`: The diameter of the second hole.
*    `hole2Stopper`: A toggle for a stopper ring in the second hole.
*   `hole3Diameter`: The diameter of the third hole (only needed when using the triple clip).
*    `hole3Stopper`: A toggle for a stopper ring in the third hole.

I want to be able to set the stopper ring radius and the logo dimensions, but otherwise those don't need to be parameters.

I don't need to use any special libraries, just the built-in OpenSCAD functions and modules are fine.

[Assistant]
BromptonCableClip.scad:
```
height = 6; //[6:20]
innerDistance = 1; //[0:0.1:3]
wallThickness = 1.5; //[1:0.1:4]
wallOpening = 0.5; //[0.5:0.1:3]

withLogos = "no"; // [yes,no]
withFrameMount = "no"; // [yes,no]
frameRadius = 9; //[5:30]
cableTieThickness = 1.2; //[0.5:0.1:3]

// Select a hole profile
holeProfile = -1; //[-1:Double,-2:Triple, 1:Brake + Shifter, 2:Brake + Light, 3:Two Brakes + Shifter, 4:Brake + Shifter + Light]

hole1Diameter = 5.2; //[1.5:0.1:8]
hole1Stopper = "no"; // [yes,no]
hole2Diameter = 5.2; //[1.5:0.1:8]
hole2Stopper = "no"; // [yes,no]
hole3Diameter = 5.2; //[1.5:0.1:8]
hole3Stopper = "no"; // [yes,no]

/* [Hidden] */

fudge = 0.1;

module BromptonLogo(h)
{
  scale([25.4/90, -25.4/90, 1]) union()
  {
    difference()
    {
       linear_extrude(height=h)
         polygon([[-9.897130,-42.628890],[-9.965860,-42.626890],[-43.029995,-41.693620],[-44.647608,-41.165200],[-45.717685,-39.842050],[-46.069138,-38.547033],[-46.116516,-37.209190],[-45.428815,-34.775170],[-44.081090,-32.985780],[-42.455865,-31.619300],[-39.452833,-29.729601],[-36.388020,-28.093410],[-29.340906,-8.599510],[-16.778848,-13.340504],[-3.841084,-17.887800],[2.528494,-19.763631],[8.703766,-21.200821],[14.588655,-22.069298],[20.087083,-22.238990],[17.012853,-16.044020],[13.642755,-15.825261],[10.386384,-15.229080],[7.036807,-15.837975],[3.567680,-16.047120],[-1.678549,-15.567943],[-6.616638,-14.187910],[-11.160731,-11.993273],[-15.224972,-9.070280],[-19.221628,-9.574120],[-22.484072,-9.241055],[-25.530486,-8.286885],[-28.293382,-6.779147],[-30.705270,-4.785376],[-32.698663,-2.373110],[-34.206072,0.390116],[-35.160009,3.436765],[-35.492985,6.699300],[-35.265309,9.402090],[-34.607509,11.965839],[-33.557432,14.352850],[-32.152922,16.525428],[-30.431823,18.445875],[-28.431982,20.076495],[-26.191242,21.379592],[-23.747448,22.317470],[-21.961393,26.398165],[-19.592838,30.125153],[-16.700415,33.439754],[-13.342753,36.283288],[-9.578482,38.597073],[-5.466232,40.322428],[-1.064635,41.400674],[3.567680,41.773130],[7.036540,41.564215],[10.385868,40.955610],[10.386398,40.955610],[13.735451,41.564189],[17.204070,41.773130],[20.151720,41.623322],[23.016163,41.183732],[28.436392,39.494214],[33.346682,36.822587],[37.628961,33.286864],[41.165157,29.005057],[43.837197,24.095180],[45.527007,18.675243],[45.966679,15.810882],[46.116516,12.863260],[45.757356,8.314018],[44.716905,3.986069],[43.050702,-0.065125],[40.814290,-3.784102],[38.063208,-7.115402],[34.852998,-10.003563],[31.239201,-12.393123],[27.277358,-14.228620],[30.681270,-21.088670],[39.710176,-19.417970],[41.772582,-29.203420],[35.320262,-30.437450],[38.870433,-37.592050],[29.911290,-42.037260],[24.830980,-31.799640],[18.592603,-32.130890],[12.589922,-32.026992],[7.006440,-31.503055],[1.739372,-30.597870],[-3.314065,-29.350225],[-13.191188,-25.982717],[-23.447209,-21.710850],[-25.955583,-28.649450],[-21.174027,-30.281800],[-16.760685,-32.274341],[-8.323068,-37.064440],[-7.519226,-37.757832],[-7.043958,-38.602766],[-6.993769,-40.445421],[-8.001745,-41.988715],[-8.849184,-42.459450],[-9.897132,-42.628960]]);
       translate([0, 0, -fudge])
         linear_extrude(height=h+2*fudge)
           polygon([[3.567682,-7.533350],[7.902731,-7.074145],[11.911876,-5.764470],[5.440435,7.277110],[14.399578,11.722830],[19.910354,0.617050],[21.618754,3.318841],[22.892695,6.289320],[23.688841,9.485234],[23.963853,12.863330],[23.551540,16.988344],[22.368100,20.823757],[20.493780,24.289308],[18.008829,27.304736],[14.993496,29.789782],[11.528028,31.664185],[7.692674,32.847685],[3.567682,33.260020],[-0.557244,32.847685],[-4.392423,31.664185],[-7.857642,29.789782],[-10.872691,27.304736],[-13.357357,24.289308],[-15.231428,20.823757],[-16.414692,16.988344],[-16.826938,12.863330],[-16.414692,8.738316],[-15.231427,4.902905],[-13.357356,1.437356],[-10.872690,-1.578071],[-7.857642,-4.063115],[-4.392422,-5.937517],[-0.557244,-7.121015],[3.567682,-7.533350]]);
       translate([0, 0, -fudge])
         linear_extrude(height=h+2*fudge)
           polygon([[25.698631,-5.697290],[28.231050,-4.318798],[30.527968,-2.608671],[32.557901,-0.598214],[34.289361,1.681267],[35.690862,4.198468],[36.730917,6.922082],[37.378040,9.820805],[37.600745,12.863330],[37.378050,15.905771],[36.730956,18.804397],[35.690948,21.527911],[34.289509,24.045017],[32.558125,26.324420],[30.528280,28.334822],[28.231459,30.044928],[25.699147,31.423440],[28.541917,27.393696],[30.674555,22.902456],[32.014606,18.031680],[32.479613,12.863330],[32.014564,7.694806],[30.674401,2.823885],[28.541598,-1.667468],[25.698631,-5.697290]]);
    }
    linear_extrude(height=h)
      polygon([[-23.610976,30.731490],[-25.926501,31.198887],[-27.817423,32.473671],[-29.092369,34.364485],[-29.559967,36.679970],[-29.092518,38.995619],[-27.817606,40.886599],[-25.926627,42.161511],[-23.610976,42.628960],[-21.295492,42.161361],[-19.404681,40.886415],[-18.129899,38.995494],[-17.662503,36.679970],[-18.130049,34.364610],[-19.404865,32.473855],[-21.295618,31.199037],[-23.610976,30.731490]]);
  }
}

module ScaledLogo(width, height, depth) {
  if (width > height) {
    resize([0, height, 0], auto=[true,true,false]) {
      BromptonLogo(depth);
    }
  } else {
    resize([width, 0, 0], auto=[true,true,false]) {
      BromptonLogo(depth);
    }
  }
}

module StopperRing(holeRadius = 2.5, radius = 0.5) {
  rotate_extrude($fn = 40) {
    innerRadius = holeRadius;
    translate([innerRadius, 0, 0]) {
      circle(radius, center = true, $fn = 40);
    }
  }
}

module CableClipDouble(
  height = 6,
  radius = [2.1, 2.6],
  stoppers = [false, true],
  innerDistance = 1,
  wallThickness = 1.5,
  wallOpening = 0.5,
  withLogos = false,
  withFrameMount = false,
  frameRadius = 9,
  cableTieThickness = 1.2,
) {
  a = max(radius) - min(radius);
  b = radius[0] + radius[1] + innerDistance;
  c = sqrt(pow(a, 2) + pow(b, 2));
  logoOffsetY = min(radius) + (a / 2) + (wallThickness);
  logoRotation = radius[0] > radius[1] ? 1 : -1;
  alpha = acos((pow(b, 2) + pow(c, 2) - pow(a, 2)) / (2 * b * c));
  difference() {
    union() {
      difference() {
        hull() {
          translate([-radius[0] - (innerDistance / 2), 0, 0]) {
            cylinder(height, radius[0] + wallThickness, radius[0] + wallThickness, center = true, $fn = 40);
          }
          translate([radius[1] + (innerDistance / 2), 0, 0]) {
            cylinder(height, radius[1] + wallThickness, radius[1] + wallThickness, center = true, $fn = 40);
          }
        }
        translate([-radius[0] - (innerDistance / 2), 0, 0]) {
          cylinder(height + 0.1, radius[0], radius[0], center = true, $fn = 40);
        }
        translate([radius[1] + (innerDistance / 2), 0, 0]) {
          cylinder(height + 0.1, radius[1], radius[1], center = true, $fn = 40);
        }
      }
      if (stoppers[0]) {
        translate([-radius[0] - (innerDistance / 2), 0]) {
          StopperRing(radius[0], 0.3);
        }
      }
      if (stoppers[1]) {
        translate([radius[1] + (innerDistance / 2), 0]) {
          StopperRing(radius[1], 0.3);
        }
      }
      if (withLogos) {
        translate([0, -logoOffsetY+0.1, 0]) {
          rotate([90, 0, alpha * logoRotation]) {
            ScaledLogo(c, height - 1, wallThickness);
          }
        }
        if (!withFrameMount) {
          translate([0, logoOffsetY-0.1, 0]) {
            rotate([90, 0, 180 - alpha * logoRotation]) {
              ScaledLogo(c, height - 1, wallThickness);
            }
          }
        }
      }
    }
    cube([radius[0] + radius[1] + innerDistance, radius[0] * 1.5, height + 0.1], center = true);
    translate([radius[1] * 2 + (innerDistance / 2), 0, 0]) {
      cube([((radius[1] + wallThickness) * 2) + innerDistance + 0.1, wallOpening, height + 0.1], center = true);
    }

    translate([radius[1] * 2 + (innerDistance / 2) + wallThickness, 0,0]) {
      cylinder(height + 0.1, wallOpening * 2, wallOpening * 2, center = true, $fn = 40);
    }
  }

  if (withFrameMount) {
    frameMountOffset = min(radius) + (a / 2) + cableTieThickness + (wallThickness * 2);
    translate([0, frameMountOffset - 0.1, 0]) {
      rotate([0,0, 90 - alpha * logoRotation]) {
        difference() {
          cube([(cableTieThickness + wallThickness) * 2, c, height], center = true);
          translate([frameRadius, 0, 0]) {
            cylinder(height + 0.1, frameRadius, frameRadius, center = true, $fn = 100);
          }
          translate([- cableTieThickness / 2 - wallThickness, 0, 0]) {
            cube([cableTieThickness + 0.1, c + 0.1, height - (wallThickness * 2)], center = true);
          }
        }
      }
    }
  }
}

module CableClipTriple(
  height = 6,
  radius = [2.5, 2.5, 2],
  stoppers = [true, false, false],
  innerDistance = 1,
  wallThickness = 1,
  wallOpening = 0.5,
  withLogos = false,
  withFrameMount = false,
  frameRadius = 9,
  cableTieThickness = 1.2,
) {
  halfSideLength = max(radius) + (innerDistance / 2);
  sideLength = halfSideLength * 2;
  centerHeight = (sqrt(3) / 2) * sideLength;
  outerRadius = (sqrt(3) / 3) * sideLength;
  innerRadius = outerRadius / 2;
  offsetSide = halfSideLength;

  wallRadius = halfSideLength + wallThickness;

  difference() {
    union() {
      difference() {
        hull() {
          translate([-innerRadius, halfSideLength, 0]) {
            cylinder(height, wallRadius, wallRadius, center = true, $fn = 40);
          }
          translate([-innerRadius, -halfSideLength, 0]) {
            cylinder(height, wallRadius, wallRadius, center = true, $fn = 40);
          }
          translate([centerHeight-innerRadius, 0, 0]) {
            cylinder(height, wallRadius, wallRadius, center = true, $fn = 40);
          }
        }

        translate([-innerRadius, halfSideLength, 0]) {
          cylinder(height + 0.1, radius[0], radius[0], center = true, $fn = 40);
        }
        translate([-innerRadius, -halfSideLength, 0]) {
          cylinder(height + 0.1, radius[1], radius[1], center = true, $fn = 40);
        }
        translate([centerHeight-innerRadius, 0, 0]) {
          cylinder(height + 0.1, radius[2], radius[2], center = true, $fn = 40);
        }
      }

      if (stoppers[0]) {
        translate([-innerRadius, halfSideLength, 0]) {
          StopperRing(radius[0], 0.3);
        }
      }
      if (stoppers[1]) {
        translate([-innerRadius, -halfSideLength, 0]) {
          StopperRing(radius[1], 0.3);
        }
      }
      if (stoppers[2]) {
        translate([centerHeight-innerRadius, 0, 0]) {
          StopperRing(radius[2], 0.3);
        }
      }
    }
    cylinder(height + 0.1, halfSideLength - 0.5, halfSideLength - 0.5, center = true, $fn = 40);

    translate([centerHeight-innerRadius, 0, 0]) {
      cube([((radius[2] + wallThickness) * 2) + innerDistance + 0.1, wallOpening, height + 0.1], center = true);
    }

    translate([((radius[2] + wallThickness) * 2) + innerDistance, 0,0]) {
      cylinder(height + 0.1, wallThickness, wallThickness, center = true, $fn = 40);
    }
  }

  logoOffset = innerRadius + halfSideLength + wallThickness;
  if (withLogos) {
    translate([-logoOffset+0.1, 0, 0]) {
      rotate([90, 0, -90]) {
        ScaledLogo(sideLength, height - 1, wallThickness);
      }
    }
    if (!withFrameMount) {
      translate([(sin(30) * logoOffset) - 0.1, -(cos(30) * logoOffset) + 0.1, 0]) {
        rotate([90, 0, 30]) {
          ScaledLogo(sideLength, height - 1, wallThickness);
        }
      }
    }
    translate([(sin(30) * logoOffset)-0.1, (cos(30) * logoOffset)-0.1, 0]) {
      rotate([90, 0, 150]) {
        ScaledLogo(sideLength, height - 1, wallThickness);
      }
    }
  }

  if (withFrameMount) {
    frameMountOffset = logoOffset + cableTieThickness + wallThickness;
    translate([(sin(30) * frameMountOffset) - 0.1, -(cos(30) * frameMountOffset) + 0.1, 0]) {
      rotate([0,0, -60]) {
        difference() {
          cube([(cableTieThickness + wallThickness) * 2, sideLength, height], center = true);
          translate([frameRadius, 0, 0]) {
            cylinder(height + 0.1, frameRadius, frameRadius, center = true, $fn = 100);
          }
          translate([- cableTieThickness / 2 - wallThickness, 0, 0]) {
            cube([cableTieThickness + 0.1, sideLength + 0.1, height - (wallThickness * 2)], center = true);
          }
        }
      }
    }
  }
}

if (holeProfile == -2) {
  //Triple
  CableClipTriple(
    height = height,
    radius = [hole1Diameter / 2, hole2Diameter / 2, hole3Diameter / 2],
    stoppers = [hole1Stopper == "yes", hole2Stopper == "yes", hole3Stopper == "yes"],
    innerDistance = innerDistance,
    wallThickness = wallThickness,
    wallOpening = wallOpening,
    withLogos = withLogos == "yes",
    withFrameMount = withFrameMount == "yes",
    frameRadius = frameRadius,
    cableTieThickness = cableTieThickness
  );
}
if (holeProfile == -1) {
  //Double
  CableClipDouble(
    height = height,
    radius = [hole1Diameter / 2, hole2Diameter / 2],
    stoppers = [hole1Stopper == "yes", hole2Stopper == "yes"],
    innerDistance = innerDistance,
    wallThickness = wallThickness,
    wallOpening = wallOpening,
    withLogos = withLogos == "yes",
    withFrameMount = withFrameMount == "yes",
    frameRadius = frameRadius,
    cableTieThickness = cableTieThickness
  );
}
if (holeProfile == 1) {
  // Brake + Shifter
  CableClipDouble(
    height = height,
    radius = [2.1, 2.6],
    stoppers = [false, true],
    innerDistance = innerDistance,
    wallThickness = wallThickness,
    wallOpening = wallOpening,
    withLogos = withLogos == "yes",
    withFrameMount = withFrameMount == "yes",
    frameRadius = frameRadius,
    cableTieThickness = cableTieThickness
  );
}
if (holeProfile == 2) {
  // Brake + Light
  CableClipDouble(
    height = height,
    radius = [1.6, 2.6],
    stoppers = [false, true],
    innerDistance = innerDistance,
    wallThickness = wallThickness,
    wallOpening = wallOpening,
    withLogos = withLogos == "yes",
    withFrameMount = withFrameMount == "yes",
    frameRadius = frameRadius,
    cableTieThickness = cableTieThickness
  );
}
if (holeProfile == 3) {
  // Two Brakes + Shifter
  CableClipTriple(
    height = height,
    radius = [2.1, 2.6, 2.5],
    stoppers = [false, true, false],
    innerDistance = innerDistance,
    wallThickness = wallThickness,
    wallOpening = wallOpening,
    withLogos = withLogos == "yes",
    withFrameMount = withFrameMount == "yes",
    frameRadius = frameRadius,
    cableTieThickness = cableTieThickness
  );
}
if (holeProfile == 4) {
  // Brake + Shifter + Light
  CableClipTriple(
    height = height,
    radius = [1.7, 2.1, 2.5],
    stoppers = [false, false, true],
    innerDistance = innerDistance,
    wallThickness = wallThickness,
    wallOpening = wallOpening,
    withLogos = withLogos == "yes",
    withFrameMount = withFrameMount == "yes",
    frameRadius = frameRadius,
    cableTieThickness = cableTieThickness
  );
}
```


[User]
I need some OpenSCAD files designed to create USB isolator "mats" like the one described in the provided example, but I need it to be much more flexible and parametric.

Essentially, it's a thin rectangular piece that sits inside a USB-A plug and blocks certain pins, preventing electrical connection on those pins. The original concept is for isolating power or data, but I want a parametric design that lets me choose *any* pin to isolate, and for any other connector where pin spacing is regular, like micro-usb, or even some other non-usb connector.

Here are the key parameters that need to be configurable:

*   **Connector Type:** I need to be able to select from a list of common connector types like "USB-A", "USB-B", "USB-mini", "USB-micro", "generic rectangular connector". For "generic rectangular connector" I would need to provide more dimensions, but for the named USB connectors the pinout and dimensions should be pre-defined. This should be extensible in the future.
*   **Connector Dimensions:**  For generic connectors, I need to specify: overall width, overall height, overall thickness, pin count, pin width, pin spacing, and pin depth (how far back the pins go into the connector). For the USB types we can use the standard sizes, but if I want to tweak any of them I want that to be possible by overriding the default value for a given named connector.
*   **Isolate Pins:** I need a list of pin indices to isolate.  Pins should be indexed starting from 0, and this should work with any connector type.
*   **Isolator Thickness:** Thickness of the overall mat.
*  **Tolerance:** A global tolerance that is used to add a bit of slack for the fit inside the connector -  this should affect all external dimensions.
*  **Groove Width:** The width of the cutout that isolates the pin. This should probably be tied to pin width, for most cases I want it to be a multiple of the pin width.
*  **Groove Depth:** How far the cutout extends into the mat - this will probably need to be greater than the connector thickness.

I'm thinking this design should be modular, making it easy to add new connector types in the future. I'd like to use the `BOSL2` library if it's suitable, otherwise, no worries. I also need a separate module or function that would produce a visualization of the connector pinout for the selected connector, with pin indexes labeled.

For example, I would like to be able to specify something like this:

```openscad
use <connector_isolator.scad>

// USB-A isolator, isolating the VCC pin
connector_isolator(
  connector_type = "USB-A",
  isolate_pins = [ 0 ], // Isolating VCC
  isolator_thickness = 0.1,
  tolerance = 0.4,
  groove_width_multiplier = 2 // groove width is 2x the pin width
);

// Generic rectangular connector isolator
connector_isolator(
  connector_type = "generic rectangular connector",
  connector_width = 15,
  connector_height = 10,
  connector_thickness = 3,
  pin_count = 10,
  pin_width = 1,
  pin_spacing = 1.5,
  pin_depth = 8,
  isolate_pins = [1, 3, 7],
  isolator_thickness = 0.2,
  tolerance = 0.3
);

//  Visualize USB-A pinout:
connector_pinout_visualizer(
   connector_type = "USB-A",
   pin_label_size = 2,
   pin_spacing = 3, // override default pin spacing
   pin_width = 1.3,
   scale = 2 // scale up
   );
```
I need the OpenSCAD files to support these parameters and this use case. I'm looking forward to seeing what you come up with.

[Assistant]
usb_isolator.scad:
```
// usb a pinout: vcc, d-, d+, gnd
isolate_pins = [ 0, 1, 1, 0 ];
isolator_thickness = 0.1;
tolerance = 0.4;

{
    usb_a_male_outter_dimensions =  [ 12, 12, 4.5 ];
    usb_a_metal_casing_thickness = 0.3;
    usb_a_pin_offsets = [ 9, 6.5, 4.5, 2 ];
    usb_a_pin_width = 1;

    isolator_pin_depth = usb_a_male_outter_dimensions[1] - 3;
    groove_width = usb_a_pin_width * 2;

    difference() {
        translate([tolerance,0,0])
            cube([usb_a_male_outter_dimensions[0] - 2*usb_a_metal_casing_thickness - 2*tolerance, isolator_pin_depth, isolator_thickness]);
        for (pin = [0:len(isolate_pins) - 1]) {
            if (isolate_pins[pin] == 0) {
                translate([usb_a_pin_offsets[pin] - groove_width/2, -1, -isolator_thickness])
                    cube([groove_width, isolator_pin_depth * 0.8 + 1 , 3 * isolator_thickness]);
            }
        }
    }
}
```


[User]
I'm looking to create a customizable bag tag that I can 3D print, similar to the example I saw. I'd like to be able to specify the text on the tag, which should be raised. It should be something like a keyring, with a hole for attaching it. I need it to be parametric, so I can easily change the text, the size of the text, the thickness of the tag and text, and the size and position of the hole.

The tag itself should have a border, basically an outline around the text, and this outline should also be raised but not as high as the text. Also, I'd like a recessed area where the main tag outline is, so it's not just a flat plate. This should create a kind of defined border. The keyring hole should pass entirely through the tag.

I’d like to use the Roboto font family, specifically the Black style, for the text, but I need to be able to change that easily as well. Can you generate the necessary OpenSCAD code? I don't have any libraries installed, so you'll need to build it all from scratch, please.

[Assistant]
customizable_bagtag.scad:
```
// customizable bagtag - scruss, 2018-08

//CUSTOMIZER VARIABLES
// Uppercase is best
Your_Tag_Text="SCR";
//CUSTOMIZER VARIABLES END

module bbox_2d() {
    /*
    simple unified bounding box for 2D paths, inspired by  https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Commented_Example_Projects#Bounding_Box
    */
    intersection() {
        projection(cut=false)rotate([90,0,0])linear_extrude(center=true, height=1000, convexity=10)hull()children(0);
        projection(cut=false)rotate([0,90,0])linear_extrude(center=true, height=1000, convexity=10)hull()children(0);
    }
}

$fn=16;

module txt() {
    text(Your_Tag_Text, font="Roboto:style=Black", spacing=1.05, size=12, $fn=32);
}

module outl() {
    hull() {
        offset(r=4)bbox_2d()txt();
        translate([-6,6])circle(d=8, center=true);
    }
}

difference() {
    union() {
        linear_extrude(height=3)txt();
        linear_extrude(height=2)outl();
        linear_extrude(height=3) {
            difference() {
                outl();
                offset(r=-2)outl();
            }
            translate([-6,6])circle(d=8, center=true);
        }
    }
    linear_extrude(height=8, center=true)translate([-6,6])circle(d=4, center=true);
}
```


[User]
I'm looking to create a parametric fishing lure generator in OpenSCAD. I've already got a basic lure STL I imported in a quick file, but I want to be able to fully customize the dimensions and features without having to model it in a separate program.

I want the core of the lure to be a roughly bullet-shaped body that can be adjusted in length, diameter, and overall shape. I'd like to control the curvature, perhaps with a few parameters that define the "taper" of the bullet shape. I want to be able to modify the nose and tail diameter independently.

Additionally, I want the ability to add customizable attachment points for hooks and weights. These attachment points should be able to be placed anywhere along the body of the lure and rotate to a specific angle. Each hook attachment point will need a hole or eyelet. I think I'll use links from Thingiverse for the actual hooks and weights, or maybe just model them as basic primitives.

Could you generate a set of OpenSCAD files that accomplish this? I’m hoping to get something modular enough that I can add more complicated features later on. I’m happy to rely on a user-definable module or a `for` loop for adding attachments, as long as the parameters can be changed. I don't need any libraries for this, just the standard OpenSCAD features, but it has to be fully parametric and customizable.

[Assistant]
Lure_Editable.scad:
```
import("C:/Users/Engin/Desktop/Lure.stl");

```


[User]
I'm looking to create a flexible linkage system using OpenSCAD, kind of like the one in the `Slot_linkage.scad` file I provided, but I want to be able to customize it more easily. The basic idea is to have a series of links that are connected by pivots. Each link has a slot, and the pivots fit into these slots, allowing the links to rotate relative to each other.

Could you help me generate a parametric OpenSCAD file, or multiple files if that would be a better approach? Here's what I'm envisioning:

*   **Link Design:** Each link should be essentially a rounded rectangle with a slot cut out of it, and a central hole to act as the pivot point.
*   **Parameters:** I'd like to be able to control the following parameters for each link:
    *   `slot_length`: The length of the slot.
    *   `slot_width`: The width of the slot.
    *   `pivot_diameter`: The diameter of the pivot hole.
    *   `wall_thickness`: The thickness of the material around the slot and pivot.
     * `linkage_height`: The height/thickness of the link
*   **Multiple Links:** I want to define a module that can take these link parameters and create the actual linkage. It should allow me to specify how many links I want and an offset between each link. The module should handle the translation and rotation as needed. The end goal is to create a linked assembly, not just a single part.
*   **Round corners**: I would like the external and internal corners to be rounded as with `round2d` in the provided file.

Basically, I want to replace the fixed layout in the example with a parametric layout where I can easily create more complex chain-like structures with different link sizes and numbers.
I'm not sure what's best, but if you have suggestions on how to better structure the files, I'm happy to hear them. I'd prefer to use libraries if they would make this more maintainable/easier.

[Assistant]
Slot_linkage.scad:
```

slot_length=50;
slot_width=6;
pivot_diameter=12;
wall_thickness=4;
linkage_height=3;

module outter_profile (){
round2d(OR=0,IR=pivot_diameter+slot_width+wall_thickness){
translate([-(pivot_diameter+(wall_thickness*2))/2-slot_width/2,0,0]) circle((pivot_diameter+(wall_thickness*2))/2);
hull(){
translate([0,0,0]) circle((slot_width+(wall_thickness*2))/2);
translate([slot_length,0,0]) circle((slot_width+(wall_thickness*2))/2);
}
}
}

$fn=150;

module inner_profile (){
translate([-(pivot_diameter+(wall_thickness*2))/2-slot_width/2,0,0]) circle(pivot_diameter/2);
hull(){
translate([0,0,0]) circle(slot_width/2);
translate([slot_length,0,0]) circle(slot_width/2);
}
}

module round2d(OR=3,IR=1){
    offset(OR)offset(-IR-OR)offset(IR)children();
}

module all(){
difference(){
 outter_profile ();
  inner_profile ();
}
}

linear_extrude(height=linkage_height) all();

```


[User]
I'd like to create a customizable 3D-printed puzzle based on a word or phrase. The puzzle will consist of a board with slots and pegs that fit into those slots. Each peg will correspond to a letter in the text, and the pegs will have unique combinations that must match the slots on the board.

Here's a breakdown of the design:

**Overall Structure:**

*   The puzzle has a board with slots and pegs with unique combinations
*   The goal is to place the correct pegs in the correct slots to spell the desired text.

**Configuration/Parameters:**

*   **`mode`**: This parameter will determine what to generate, can be "board_with_pegs", "board", "pegs", or "letters". I expect a single file will handle all modes.
*   **`peg_sectors`**: The number of sectors on the peg for the combination. For example 12 sectors will be 3^12 combinations.
*   **`peg_size`**: A vector defining the peg size `[x, y, [z1, z2, z3]]`.
    *   x and y are the peg's width and length, in mm.
    *   `z1` is the height of the letter extrusion on the peg,
    *   `z2` is the height of the solid cube part of the peg, and
    *   `z3` is the height of the combination on the peg.
*   **`peg_spacing_x_ratio`**: A ratio to determine the x-spacing of the pegs, multiplied by the `peg_size[0]`.
*   **`board_size`**: A vector defining the board dimensions `[x, y, z]` in mm.
*   **`text`**: The text to be used for the puzzle.
*   **`font`**:  The font to use for the letters.
*  **`font_size`**:  The font size to use for the letters.
*   **`peg_vs_slot_tolerance_xy_z`**: A vector defining the tolerance between pegs and slots, `[xy, z]`.
    *   `xy` is the tolerance in the x/y dimensions of the combination pattern on each peg.
    *   `z` is the tolerance in the Z dimension (for the slot depth)
*   **`spacing_z`**: When `mode = "board_with_pegs"` this spacing (mm) is between peg bottom and board slot bottom.
*   **`board_ext_edges`**: A vector defining which edges of the board should have extension features `["left", "right", "front", "back"]`. Possible values are "m" (male), "f" (female), "" (plain edge). Example `["f", "", "", "m"]`.
*   **`ext_edge_depth`**: The depth (mm) of the extension edges.
*   **`ext_edge_tolerance`**: The tolerance (mm) for the extension edges.
*   **`preset_combinations`**: A vector of vectors containing preset combinations for the pegs. If empty, random combinations will be generated. I'd like it to support setting these via the Customizer, and echoing them as a comment, too.

**Peg Design:**

*   Each peg consists of three layers: a combination pattern, a solid layer, and an extruded letter.
*   The combination part of each peg should be a unique pattern that fits into a corresponding slot on the board. The combination patterns will be generated using the given `peg_sectors`.
*   The peg's base should be a rectangle with dimensions from `peg_size`.
*   The letter should be extruded on top of the solid cube layer. The letters should be centered on the base.

**Board Design:**

*   The board should be a cube with dimensions from `board_size`.
*   The board should have slots that exactly match the combination pattern of each letter's corresponding peg, based on the tolerance, `peg_vs_slot_tolerance_xy_z[0]`
*   The edges of the board might have extension edges (male/female) to connect other boards. These edges should be generated based on `board_ext_edges`, `ext_edge_depth` and `ext_edge_tolerance`.

**Mode Specifics:**

*   **"board_with_pegs"**: Generates both the board and pegs, positioned with a gap between them.
*   **"board"**: Generates the board with slots.
*   **"pegs"**: Generates only the pegs.
*    **"letters"**: Generates only the letters.

**Specific Features:**

*   I would like the combination of each peg to be pseudo-random. If I specify a `preset_combinations`, then it should use that, and _not_ generate the combinations from scratch.
*   Identical letters in the text should have identical combinations.
*   The peg-to-slot tolerance needs to be configurable, but should be implemented using the `offset()` function so the holes are correct (not just making the pegs smaller).
*   The extension edges should have a tongue-and-groove design, and be correctly rendered when using the 'm' and 'f' edge design on adjacent edges. I believe that is already done in the provided code.

Could you please create the OpenSCAD code for this puzzle? I don't think there's a need for any external libraries, the math should be simple enough to define inline. The `echo()` outputs should also still be used to help guide the user.

[Assistant]
name_puzzle_r2.scad:
```
/**
 ** CONFIGURATION / GLOBALS
 **/

// Generation mode. Possible modes: board_with_pegs, board, pegs, letters.
mode = "board_with_pegs"; //[board_with_pegs, board, pegs, letters]

// The number of peg sectors ("combination tags"). 12 sectors = 3^12 = 531441 possible peg combinations.
peg_sectors = 12;

// Peg size mm, [x, y, [ z1 (letter), z2 (solid), z3 (combination) ]].
peg_size = [ 40, 40, [3, 3, 6] ];

// We multiply peg_size.x with this to place the letters.
peg_spacing_x_ratio = 1.1;

// Size of the board.
board_size = [ 320, 50, 20 ];

// Text to generate
text = "TIFFANY";

// Font name and style (example "Liberation Sans:style=Bold Italic")
font = "Impact";

// Font size (inaccurate, mm).
font_size = 25;

// Tolerance between peg pattern and board slot [xy (mm), z (mm)].
peg_vs_slot_tolerance_xy_z = [0.5, 1];

// For mode board_with_pegs, this Z distance (mm) will be put between the board slot bottom and peg bottom.
spacing_z = 2;

// Extension edges. If you want to hook multiple boards together you can enable it here. Valid values are: "m" (male edge), "f" (female edge), "" (plain edge). Vector is [left, right, front, back]. For example [ "f", "", "", "m" ].
board_ext_edges = [ "", "", "", "" ];

// Extenision edge depth (mm).
ext_edge_depth = 10;

// Extension edges tolerance (mm).
ext_edge_tolerance = 0.5;

// Preset combinations (use this if you want to generate board and pegs separatly, and keep the combinations).
preset_combinations =  [];

/**
 ** MAIN LOGIC / ASSEMBLY
 **/

{
    // If combinations are not preset, create random ones.
    combinations = len(preset_combinations)==0 ?
        [ for (letter = [0:len(text)-1]) peg_combination(peg_sectors) ]
        : preset_combinations;
    echo("COMBINATIONS-DUMP: ", combinations);

    // Trick to let the combination of identical letters be the same.
    deduped_combination_indexes = [ for (letter = [0:len(text)-1]) search(text[letter], text, 1)[0] ];

    // The polygon profile per letter of the text.
    text_combination_profiles = [
        for (letter = [0:len(text)-1])
        let (combination_index = deduped_combination_indexes[letter])
        peg_profile(combinations[combination_index])
    ];

    // Some common values used across various modes.
    pegs_offset = [
        (board_size[0] - peg_size[0] * len(text) * peg_spacing_x_ratio) / 2,
        (board_size[1] - peg_size[1]) / 2,
        board_size[2] - peg_size[2][2] + 1/1000
    ];

    //
    // Assemblies
    //

    if (mode == "board_with_pegs") {
        // pegs
        translate([pegs_offset[0], pegs_offset[1],  pegs_offset[2] + spacing_z])
            pegs(text_combination_profiles, peg_vs_slot_tolerance_xy_z);

        // board (with slots)
        board(text_combination_profiles, pegs_offset);
    }

    else if (mode == "board") {
        board(text_combination_profiles, pegs_offset);
//        translate([-1 * board_size[0] + ext_edge_depth, 1, 0])
//            board(text_combination_profiles, pegs_offset);
    }

    else if (mode == "pegs") {
        pegs(text_combination_profiles, peg_vs_slot_tolerance_xy_z);
    }

    else if (mode == "letters") {
        letters();
    }
}

// Builds the board with scaled-up peg bottoms subtracted.
module board(text_combination_profiles, pegs_offset) {
    union() {
        difference() {
            cube(board_size);
            translate(pegs_offset)
                pegs(text_combination_profiles, [0, 0]);

            // extension edges (left, right, front, back)
            if (board_ext_edges[0] != "")
                cube([ext_edge_depth, board_size[1], board_size[2]]);
            if (board_ext_edges[1] != "")
                translate([board_size[0] - ext_edge_depth, 0, 0])
                    cube([ext_edge_depth, board_size[1], board_size[2]]);
            if (board_ext_edges[2] != "")
                cube([board_size[0], ext_edge_depth, board_size[2]]);
            if (board_ext_edges[3] != "")
                translate([0, board_size[1] - ext_edge_depth, 0])
                    cube([board_size[0], ext_edge_depth, board_size[2]]);

        }
        difference() {
            union() {
                // left
                translate([0, board_size[1], 0]) rotate([90,0,0])
                    ext_edge(board_ext_edges[0], [ext_edge_depth, board_size[2], board_size[1]]);
                // right
                translate([board_size[0], 0, 0]) rotate([90,0,180])
                    ext_edge(board_ext_edges[1], [ext_edge_depth, board_size[2], board_size[1]]);
                // front
                translate([0, 0, 0]) rotate([90,0,90])
                    ext_edge(board_ext_edges[2], [ext_edge_depth, board_size[2], board_size[0]]);
                // back
                translate([board_size[0], board_size[1], 0]) rotate([90,0,-90])
                    ext_edge(board_ext_edges[3], [ext_edge_depth, board_size[2], board_size[0]]);
            }
            // corner: left+front
            if (board_ext_edges[0] != "" && board_ext_edges[2] != "")
            difference() {
                cube([ext_edge_depth, ext_edge_depth, board_size[2]]);
                intersection() {
                    // left
                    translate([0, board_size[1], 0]) rotate([90,0,0])
                        ext_edge(board_ext_edges[0], [ext_edge_depth, board_size[2], board_size[1]]);
                    // front
                    translate([0, 0, 0]) rotate([90,0,90])
                        ext_edge(board_ext_edges[2], [ext_edge_depth, board_size[2], board_size[0]]);
                }
            }
            // corner: front+right
            if (board_ext_edges[2] != "" && board_ext_edges[1] != "")
            difference() {
                translate([board_size[0]-ext_edge_depth, 0, 0])
                    cube([ext_edge_depth, ext_edge_depth, board_size[2]]);
                intersection() {
                    // front
                    translate([board_size[0]/2, 0, 0]) rotate([90,0,90])
                        ext_edge(board_ext_edges[2], [ext_edge_depth, board_size[2], board_size[0]]);
                    // right
                    translate([board_size[0], board_size[1]/-2, 0]) rotate([90,0,180])
                        ext_edge(board_ext_edges[1], [ext_edge_depth, board_size[2], board_size[1]]);
                }
            }
            // corner: right+back
            if (board_ext_edges[1] != "" && board_ext_edges[3] != "")
            difference() {
                translate([board_size[0]-ext_edge_depth, board_size[1]-ext_edge_depth, 0])
                    cube([ext_edge_depth, ext_edge_depth, board_size[2]]);
                intersection() {
                    // right
                    translate([board_size[0], board_size[1]/2, 0]) rotate([90,0,180])
                        ext_edge(board_ext_edges[1], [ext_edge_depth, board_size[2], board_size[1]]);
                    // back
                    translate([board_size[0]*1.5, board_size[1], 0]) rotate([90,0,-90])
                        ext_edge(board_ext_edges[3], [ext_edge_depth, board_size[2], board_size[0]]);
                }
            }
            // corner: back+left
            if (board_ext_edges[3] != "" && board_ext_edges[0] != "")
            difference() {
                translate([0, board_size[1]-ext_edge_depth, 0])
                    cube([ext_edge_depth, ext_edge_depth, board_size[2]]);
                intersection() {
                    // back
                    translate([board_size[0]/2, board_size[1], 0]) rotate([90,0,-90])
                        ext_edge(board_ext_edges[3], [ext_edge_depth, board_size[2], board_size[0]]);
                    // left
                    translate([0, board_size[1]*1.5, 0]) rotate([90,0,0])
                        ext_edge(board_ext_edges[0], [ext_edge_depth, board_size[2], board_size[1]]);
                }
            }
        }
    }
}

module ext_edge(gender, size) {
    translate([0, size[1]/2, 0])
    union() {
        ext_edge_half(gender, size);
        mirror([0, -1, 0])
            ext_edge_half(gender, size);
    }
}

module ext_edge_half(gender, size) {
    xu = size[0]/2;
    yu = size[1]/2/10;
    if (gender == "f") {
        linear_extrude(size[2])
            union() {
                translate([0, 3*yu]) square([xu, 7*yu], false);
                translate([xu, 6*yu]) square([xu, 4*yu], false);
            }
    }
    else if (gender == "m") {
        linear_extrude(size[2])
            union() {
                translate([ext_edge_tolerance, 0])
                    square([xu-2*ext_edge_tolerance, 6*yu-ext_edge_tolerance], false);
                translate([xu-ext_edge_tolerance, 0])
                    square([xu+ext_edge_tolerance, 3*yu-ext_edge_tolerance], false);
            }
    }
}

// Builds all the pegs.
module pegs(text_combination_profiles, tolerance_xy_z = [0, 0]) {
    for (letter = [0:len(text)-1]) {
        translate([letter * peg_size[1] * peg_spacing_x_ratio, 0, 0])
            peg(text[letter], text_combination_profiles[letter], tolerance_xy_z);
    }
}

// Builds a single peg.
module peg(letter, combination_profile, tolerance_xy_z) {
    combination_height = peg_size[2][2] - tolerance_xy_z[1];
    union() {
        linear_extrude(combination_height)
            offset(delta=-tolerance_xy_z[0], chamfer=true)
            scale([peg_size[0], peg_size[1]])
            polygon(combination_profile);
        translate([0, 0, combination_height])
            difference() {
                cube([ peg_size[0], peg_size[1], peg_size[2][0] + peg_size[2][1] ]);
                translate([peg_size[0] / 2, peg_size[1] / 2, peg_size[2][1]])
                    letter(letter, peg_size[2][0] + peg_size[2][0]/1000);
            }
    }
}

// Builds all the letters.
module letters() {
    for (letter = [0:len(text)-1]) {
        translate([letter*peg_size[1]*peg_spacing_x_ratio, 0 ,0])
            letter(text[letter], peg_size[2][0]);
    }
}

// Builds single letter
module letter(letter, height) {
    linear_extrude(height)
        text(text = letter, size = font_size, font = font, halign = "center", valign = "center");
}

// Generates a random peg combination. Each sector is given a value of 0, 1 or 2.
function peg_combination(sector_count) =
    let(r = rands(0, 3, sector_count))
    [ for (sector = [0:sector_count - 1]) floor(r[sector]) ];

// Generate a polygon profile for a combination.
function peg_profile(combination, res = 8) =
    let (sector_a = 360 / len(combination))
    [
        for (a = [0:sector_a/res:359.9])
        let (sector = floor(a / sector_a))
        let (r = 0.4 + (combination[sector] - 1) * 0.1)
            [0.5 + r * cos(a), 0.5 + r * sin(a)]

    ];

// Copied from: https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Tips_and_Tricks#Add_all_values_in_a_list
function vsum(v) = [ for(p=v) 1 ] * v;

```