[User]
Okay, I'd like to design a customizable filament swatch for 3D printing. I have a few ideas about the features I want, and I think it'll be pretty similar to the one I've been using but I want to make sure all features I want are included.

Here are the core things:
1.  **Basic Shape:** The swatch should be a rounded rectangle. I'll want to control the width, height, and thickness, and also the corner radius. I also need to define how thick the walls are surrounding the text fields and the steps area.
2.  **Text:** I need to add a few lines of text.
    *   There should be an upper text line.
    *   There should also be two lower text lines, one on the left, and one on the right, I will want to control the spacing between the lines.
    *  I'd like a top text that's printed along the very top edge, not on the face. It will help quickly identify the material when in a storage box. I should have a configurable drop-down selection for that, with commonly-used filament types. Also I want to be able to overwrite that with my own text if needed.
    *   I'll need to set the font size for the upper text and the lower text independently.
    *   I'll need to pick the font, using a google font selector. I've had issues with the customizer in the past so it would be good to just have them all available, rather than manually setting them all using the "google fonts" link.
    *  I need to choose between having the text raised or embossed.
3.  **Thickness Steps:** I'd like to include a series of steps with increasing thickness, similar to a test card for layer heights. I should have an array of thickness values for these steps, including the option to add negative numbers for bridge structures. These thicknesses should be adjustable
     * I need the steps to be generated as a series of 'layers' along the length of the card
     * I should be able to have text indicating the thickness on the steps, with a configurable font size, text height, and format for leading and trailing zeroes (or lack thereof). I would also like to rotate these labels if required.
4. **Test elements**: I need several test shapes to check overhangs, bridges, etc. These need to be parametric so I can turn on or off some of the features as needed.
    *   I need a few circles with increasing distances, that are cut out as holes. One of these circles needs to have a filament holder (a cylindrical cut-out), but only if the test shapes are activated.
    *   I also need a sphere test.
    *   I'd like some cylindrical and overhang tests.
    *   I'd like a pyramid test as well.
5.  **Hole:** Add a hole for hanging the swatch, with configurable radius.
6.  **Indents:** I want to optionally add indents on the sides to make removing the swatch easier from a box. I want to set the radius/size of these.

I think that covers it. The specific values for these would be configurable through variables at the top of the file(s). I don't need any external libraries as long as all of these features can be included. This should include variables, modules, and functions.

[Assistant]
Configurable_Filament_Swatch_VZA.scad:
```
//upper text line
textstring1="Filam.-PM PLA+";

//Set font size in mm
textsize_upper=5;

//Left lower text line, e.g. for layer thickness, print temperature
textstring2=".2  215C";

//Right lower text line, e.g. for the color of the filament
textstring3="Banana Yellow";

//Set font size in mm
textsize_lower=5;

//Top text (on the top edge, to quickly find the material type).
texttop_predefined=" P L A +";//[-,P L A, A B S, P E T G, N y l o n, P V B, H I P S, P E E K, T P U, P O M, P V A, P P, A S A, P C, U L T E M, R e s i n, P A, n G E N, H T, P H A, A M 1 8 0 0,A M 3 3 0 0,N G E N _ F L E X,P U R,F L 6 0 0 0]

//Own top text: Use text here if you want to override the drop-down right above. Note that you can increase readability by using all capital letters and by spacing the letters out with a space each: "P L A"
texttop_configurable="";

//Font name, e.g. "Pathway Gothic One,", "Voltaire", "Oswald", depends on what is available on the platform. In the customizer,choose from about 700 Google Fonts. For an overview please refer: http://www.google.com/fonts. I like the Aldo font and use it with OpenSCAD on my PC.
fontname="Aldo";//[Pathway Gothic One,Voltaire,Oswald,"Oswald:style=Bold","PT Sans Narrow:style=Bold",Archivo,ABeeZee,Abel,Abril Fatface,Aclonica,Acme,Actor,Adamina,Advent Pro,Aguafina Script,Akronim,Aladin,Aldrich,Alef,Alegreya,Alegreya Sans,Alegreya Sans SC,Alegreya SC,Alex Brush,Alfa Slab One,Alice,Alike,Alike Angular,Allan,Allerta,Allerta Stencil,Allura,Almendra,Almendra Display,Almendra SC,Amarante,Amaranth,Amatic SC,Amethysta,Amiri,Anaheim,Andada,Andika,Angkor,Annie Use Your Telescope,Anonymous Pro,Antic,Antic Didone,Antic Slab,Anton,Arapey,Arbutus,Arbutus Slab,Architects Daughter,Archivo Black,Archivo Narrow,Arimo,Arizonia,Armata,Artifika,Arvo,Asap,Asset,Astloch,Asul,Atomic Age,Aubrey,Audiowide,Autour One,Average,Average Sans,Averia Gruesa Libre,Averia Libre,Averia Sans Libre,Averia Serif Libre,Bad Script,Balthazar,Bangers,Basic,Battambang,Baumans,Bayon,Belgrano,Belleza,BenchNine,Bentham,Berkshire Swash,Bevan,Bigelow Rules,Bigshot One,Bilbo,Bilbo Swash Caps,Bitter,Black Ops One,Bokor,Bonbon,Boogaloo,Bowlby One,Bowlby One SC,Brawler,Bree Serif,Bubblegum Sans,Bubbler One,Buda,Buenard,Butcherman,Butterfly Kids,Cabin,Cabin Condensed,Cabin Sketch,Caesar Dressing,Cagliostro,Calligraffitti,Cambay,Cambo,Candal,Cantarell,Cantata One,Cantora One,Capriola,Cardo,Carme,Carrois Gothic,Carrois Gothic SC,Carter One,Caudex,Cedarville Cursive,Ceviche One,Changa One,Chango,Chau Philomene One,Chela One,Chelsea Market,Chenla,Cherry Cream Soda,Cherry Swash,Chewy,Chicle,Chivo,Cinzel,Cinzel Decorative,Clicker Script,Coda,Coda Caption,Codystar,Combo,Comfortaa,Coming Soon,Concert One,Condiment,Content,Contrail One,Convergence,Cookie,Copse,Corben,Courgette,Cousine,Coustard,Covered By Your Grace,Crafty Girls,Creepster,Crete Round,Crimson Text,Croissant One,Crushed,Cuprum,Cutive,Cutive Mono,Damion,Dancing Script,Dangrek,Dawning of a New Day,Days One,Dekko,Delius,Delius Swash Caps,Delius Unicase,Della Respira,Denk One,Devonshire,Dhurjati,Didact Gothic,Diplomata,Diplomata SC,Domine,Donegal One,Doppio One,Dorsa,Dosis,Dr Sugiyama,Droid Sans,Droid Sans Mono,Droid Serif,Duru Sans,Dynalight,Eagle Lake,Eater,EB Garamond,Economica,Ek Mukta,Electrolize,Elsie,Elsie Swash Caps,Emblema One,Emilys Candy,Engagement,Englebert,Enriqueta,Erica One,Esteban,Euphoria Script,Ewert,Exo,Exo 2,Expletus Sans,Fanwood Text,Fascinate,Fascinate Inline,Faster One,Fasthand,Fauna One,Federant,Federo,Felipa,Fenix,Finger Paint,Fira Mono,Fira Sans,Fjalla One,Fjord One,Flamenco,Flavors,Fondamento,Fontdiner Swanky,Forum,Francois One,Freckle Face,Fredericka the Great,Fredoka One,Freehand,Fresca,Frijole,Fruktur,Fugaz One,Gabriela,Gafata,Galdeano,Galindo,Gentium Basic,Gentium Book Basic,Geo,Geostar,Geostar Fill,Germania One,GFS Didot,GFS Neohellenic,Gidugu,Gilda Display,Give You Glory,Glass Antiqua,Glegoo,Gloria Hallelujah,Goblin One,Gochi Hand,Gorditas,Goudy Bookletter 1911,Graduate,Grand Hotel,Gravitas One,Great Vibes,Griffy,Gruppo,Gudea,Gurajada,Habibi,Halant,Hammersmith One,Hanalei,Hanalei Fill,Handlee,Hanuman,Happy Monkey,Headland One,Henny Penny,Herr Von Muellerhoff,Hind,Holtwood One SC,Homemade Apple,Homenaje,Iceberg,Iceland,IM Fell Double Pica,IM Fell Double Pica SC,IM Fell DW Pica,IM Fell DW Pica SC,IM Fell English,IM Fell English SC,IM Fell French Canon,IM Fell French Canon SC,IM Fell Great Primer,IM Fell Great Primer SC,Imprima,Inconsolata,Inder,Indie Flower,Inika,Irish Grover,Istok Web,Italiana,Italianno,Jacques Francois,Jacques Francois Shadow,Jim Nightshade,Jockey One,Jolly Lodger,Josefin Sans,Josefin Slab,Joti One,Judson,Julee,Julius Sans One,Junge,Jura,Just Another Hand,Just Me Again Down Here,Kalam,Kameron,Kantumruy,Karla,Karma,Kaushan Script,Kavoon,Kdam Thmor,Keania One,Kelly Slab,Kenia,Khand,Khmer,Khula,Kite One,Knewave,Kotta One,Koulen,Kranky,Kreon,Kristi,Krona One,La Belle Aurore,Laila,Lakki Reddy,Lancelot,Lateef,Lato,League Script,Leckerli One,Ledger,Lekton,Lemon,Liberation Sans,Libre Baskerville,Life Savers,Lilita One,Lily Script One,Limelight,Linden Hill,Lobster,Lobster Two,Londrina Outline,Londrina Shadow,Londrina Sketch,Londrina Solid,Lora,Love Ya Like A Sister,Loved by the King,Lovers Quarrel,Luckiest Guy,Lusitana,Lustria,Macondo,Macondo Swash Caps,Magra,Maiden Orange,Mako,Mallanna,Mandali,Marcellus,Marcellus SC,Marck Script,Margarine,Marko One,Marmelad,Martel Sans,Marvel,Mate,Mate SC,Maven Pro,McLaren,Meddon,MedievalSharp,Medula One,Megrim,Meie Script,Merienda,Merienda One,Merriweather,Merriweather Sans,Metal,Metal Mania,Metamorphous,Metrophobic,Michroma,Milonga,Miltonian,Miltonian Tattoo,Miniver,Miss Fajardose,Modak,Modern Antiqua,Molengo,Molle,Monda,Monofett,Monoton,Monsieur La Doulaise,Montaga,Montez,Montserrat,Montserrat Alternates,Montserrat Subrayada,Moul,Moulpali,Mountains of Christmas,Mouse Memoirs,Mr Bedfort,Mr Dafoe,Mr De Haviland,Mrs Saint Delafield,Mrs Sheppards,Muli,Mystery Quest,Neucha,Neuton,New Rocker,News Cycle,Niconne,Nixie One,Nobile,Nokora,Norican,Nosifer,Nothing You Could Do,Noticia Text,Noto Sans,Noto Serif,Nova Cut,Nova Flat,Nova Mono,Nova Oval,Nova Round,Nova Script,Nova Slim,Nova Square,NTR,Numans,Nunito,Odor Mean Chey,Offside,Old Standard TT,Oldenburg,Oleo Script,Oleo Script Swash Caps,Open Sans,Open Sans Condensed,Oranienbaum,Orbitron,Oregano,Orienta,Original Surfer,Over the Rainbow,Overlock,Overlock SC,Ovo,Oxygen,Oxygen Mono,Pacifico,Paprika,Parisienne,Passero One,Passion One,Patrick Hand,Patrick Hand SC,Patua One,Paytone One,Peddana,Peralta,Permanent Marker,Petit Formal Script,Petrona,Philosopher,Piedra,Pinyon Script,Pirata One,Plaster,Play,Playball,Playfair Display,Playfair Display SC,Podkova,Poiret One,Poller One,Poly,Pompiere,Pontano Sans,Port Lligat Sans,Port Lligat Slab,Prata,Preahvihear,Press Start 2P,Princess Sofia,Prociono,Prosto One,PT Mono,PT Sans,PT Sans Caption,PT Sans Narrow,PT Serif,PT Serif Caption,Puritan,Purple Purse,Quando,Quantico,Quattrocento,Quattrocento Sans,Questrial,Quicksand,Quintessential,Qwigley,Racing Sans One,Radley,Rajdhani,Raleway,Raleway Dots,Ramabhadra,Ramaraja,Rambla,Rammetto One,Ranchers,Rancho,Ranga,Rationale,Ravi Prakash,Redressed,Reenie Beanie,Revalia,Ribeye,Ribeye Marrow,Righteous,Risque,Roboto,Roboto Condensed,Roboto Slab,Rochester,Rock Salt,Rokkitt,Romanesco,Ropa Sans,Rosario,Rosarivo,Rouge Script,Rozha One,Rubik Mono One,Rubik One,Ruda,Rufina,Ruge Boogie,Ruluko,Rum Raisin,Ruslan Display,Russo One,Ruthie,Rye,Sacramento,Sail,Salsa,Sanchez,Sancreek,Sansita One,Sarina,Sarpanch,Satisfy,Scada,Scheherazade,Schoolbell,Seaweed Script,Sevillana,Seymour One,Shadows Into Light,Shadows Into Light Two,Shanti,Share,Share Tech,Share Tech Mono,Shojumaru,Short Stack,Siemreap,Sigmar One,Signika,Signika Negative,Simonetta,Sintony,Sirin Stencil,Six Caps,Skranji,Slabo 13px,Slabo 27px,Slackey,Smokum,Smythe,Sniglet,Snippet,Snowburst One,Sofadi One,Sofia,Sonsie One,Sorts Mill Goudy,Source Code Pro,Source Sans Pro,Source Serif Pro,Special Elite,Spicy Rice,Spinnaker,Spirax,Squada One,Sree Krushnadevaraya,Stalemate,Stalinist One,Stardos Stencil,Stint Ultra Condensed,Stint Ultra Expanded,Stoke,Strait,Sue Ellen Francisco,Sunshiney,Supermercado One,Suranna,Suravaram,Suwannaphum,Swanky and Moo Moo,Syncopate,Tangerine,Taprom,Tauri,Teko,Telex,Tenali Ramakrishna,Tenor Sans,Text Me One,The Girl Next Door,Tienne,Timmana,Tinos,Titan One,Titillium Web,Trade Winds,Trocchi,Trochut,Trykker,Tulpen One,Ubuntu,Ubuntu Condensed,Ubuntu Mono,Ultra,Uncial Antiqua,Underdog,Unica One,UnifrakturCook,UnifrakturMaguntia,Unkempt,Unlock,Unna,Vampiro One,Varela,Varela Round,Vast Shadow,Vesper Libre,Vibur,Vidaloka,Viga,Voces,Volkhov,Vollkorn,VT323,Waiting for the Sunrise,Wallpoet,Walter Turncoat,Warnes,Wellfleet,Wendy One,Wire One,Yanone Kaffeesatz,Yellowtail,Yeseva One,Yesteryear,Zeyada]

//Text raised or embossed
text_type="embossed";//[embossed,raised]

//Different top edges for testing enabled? If you want a simple rounded swatch, set this to disabled
edge_tests="enabled";//[enabled,disabled]

//Number of advanced test circles?
test_circles=6;//[0:6]

// Thickness progression of the 'layers' is taken from https://www.thingiverse.com/thing:3067940 : 0.2, 0.4, 0.6, 0.8, 1.0, 1.6, without the 2.25 mm. If you put a negative number here, it will generate a "bridge" structure of the respective thickness.
steps_thickness=[-.6, 0.2, 0.4, 0.6, 0.8, 1.0, 1.6];

//Switch on or off the text to indicate the thickness on the steps
steps_text="enabled";//[enabled,disabled]

//Set font size on the thickness steps in mm. These show the step size below (0.2, 0.4, ...).
steps_textsize=5;

//textheight on thickness steps
steps_textheight=.4;

//Set wether the leading zeroes should be visible on the thickness steps.
steps_text_format="enforce leading zero 0._";//[enforce leading zero 0._,enforce leading and trailing zero 0.0,no enforcing _._]

//rotate the text on the steps by 90 degree?
steps_text_rotate="no";//[yes,no]

//Swatch size. This is compatible to Jaxels box. Other sizes are e.g. 75,40,3. First is swatch Width
w=79.5;

//Swatch Height, should be >=26mm. If you go below 26mm, you will have to reduce the values wall=1 and round=2
h=30;

//Swatch Thickness, should be >=.18mm.
th=2.2;

//Swatch roundness radius
round=5;

//Border size around the text
wall=2;

//Text line separation
linesep=1.4;

//hole radius. Set to 0 to have no hole
r_hole=2;

//side indents to take out the swatch from the box. set to 0 to have no indents
r_indent=2;

/* [Hidden] */
//-------------------------------------------------------------------------------

/*
Configurable Filament Swatch
Version ZA, January 2022
Written by MC Geisler (mcgenki at gmail dot com)

This is a swatch with configurable text.

Have fun!

License: Attribution 4.0 International (CC BY 4.0)

You are free to:
    Share - copy and redistribute the material in any medium or format
    Adapt - remix, transform, and build upon the material
    for any purpose, even commercially.
*/

texttop=(texttop_configurable==""?texttop_predefined:texttop_configurable);

$fn=48;

font_recessed=1.5;

//***USE THIS ONLY IF YOU KNOW WHAT YOU ARE DOING***: In case your steps are not in the right heigt, add here some little bit of thickness to your step height. This might help e.g. if you use cura to slice. Its not needed for Simplify3D or Slic3r.
step_thickness_correction=0;

module rounded_square(x,y,z,r)
{
    delta=r;

    hull()
    {
        translate([r,r,0]) cylinder(h=z,r=r);
        translate([-r+x,r,0]) cylinder(h=z,r=r);
        translate([r,-r+y,0]) cylinder(h=z,r=r);
        translate([-r+x,-r+y,0])cylinder(h=z,r=r);
    }
}

module rounded_biteoff_square(x,y,z,r)
{
    delta=r;

    difference()
    {
        translate([-delta,0,0])
            cube([x+2*r,y,z]);

        translate([-2*r,0,0])
        hull()
        {
            translate([r,r,-.1]) cylinder(h=z+1,r=r);
            translate([r,-r+y,-.1]) cylinder(h=z+1,r=r);
        }

        translate([2*r,0,0])
        hull()
        {
            translate([-r+x,-r+y,-.1])cylinder(h=z+1,r=r);
            translate([-r+x,r,-.1]) cylinder(h=z+1,r=r);
        }
    }
}

module write_text_on_top(x,y,z,dist)
{
            //text on top
        fontdepth=.6;
        translate([x-dist,y-fontdepth,z-.2+.1*0])
        //translate([ w-2*wall,h-wall-textsize*(1+linesep),height ])
            rotate([-90,0,0])
                linear_extrude(height=fontdepth*2,convexity = 10)
                    text(texttop,size=z-.2,font="Open Sans:style=Bold",halign="right",valign="bottom");
}

module rounded_square_test_pattern(x,y,z,r)
{
    delta=r;
    r3=z/2;
    r2=r;

    difference()
    {
        hull()
        {
            //bottom left cylindrical/round
            translate([r,r,0]) cylinder(h=z,r=r);

            //bottom right test flattened/round
//            translate([-r2+x-delta,r2,0]) cylinder(h=z,r=r2);
//            translate([-r2+x,r2+delta,0]) cylinder(h=z,r=r2);
//            translate([-r2+x,r2,z/2])
//                scale([r2,r2,z/2])
//                    sphere(r=1);
            translate([-r+x,r,0]) cylinder(h=z,r=r);

            //upper left ball/rounded
            /// translate([r,-r+y,0]) cylinder(h=z,r=r);
            translate([r3,-r3+y,r3]) scale([r3,r3,z/2]) sphere(r=1);

            //upper right corner
            r_corner=.1;
            translate([-r_corner+x,-r_corner+y,0]) cylinder(h=z,r=r_corner);
        }

        for (i = [0:test_circles])
        {
            //circle top left with round test pattern
            translate([wall+r_hole+r_hole*2.5*i,h-wall-r_hole,th/2-.1])
                cylinder(r=r_hole, h=th+.2, center=true);

            if (i==6) //filament holder
            {
                //filament container
                filament_r=1.8/2;
                translate([wall+r_hole+r_hole*2.5*i, h+.1, th/2])
                    rotate([90,0,0])
                        cylinder(r=filament_r,h=r_hole*2+wall+2,center=false);
            }
        }

        //cuts
        cutin=wall*1.2;
        cutwide=1;
        wallwide_from=.5;
        wallwide_step=.1;
        wallwide_number=5;
        for(i=[0:wallwide_number-1])
        {
            wallwide=wallwide_from+wallwide_step*i;
            translate([x-r-r2-(cutwide+wallwide)*i,-.1,-.1])
                cube([cutwide,cutin+.1,z+.2]);
        }

        //overhang
        i=wallwide_number-1;
        short_x=(x-r-r2-(cutwide+wallwide_from+wallwide_step*i)*i);
        translate([0,-.01,-z])
            rotate([0,-asin(2*z/short_x),0])
                cube([short_x,wall*1.2,z]);

        //write_text_on_top(x,y,z);
    }
}

module rounded_square_test_pattern_raised(x,y,z,r,text_type, edge_tests)
{
    //sphere test
    if(test_circles>=1)
        translate([wall+r_hole+r_hole*2.5,h-wall-r_hole,th/2])
            intersection()
            {
                cylinder(r=r_hole, h=th, center=true);
                translate([0,0,-r_hole+th/2])
                    sphere(r=r_hole);
            }

    //cylinder/hole test
    r_testhole=.5;
    r_testcylinder=r_testhole+.5;
    if(test_circles>=2)
    translate([wall+r_hole+r_hole*5,h-wall-r_hole,th/2])
        difference()
        {
            union()
            {
                cylinder(r=r_testcylinder, h=th, center=true);
                translate([-r_testhole,-r_hole,-th/2])
                    cube([r_testhole*2,r_hole*2,.2],center=false);
                if (text_type=="embossed")
                    translate([-r_hole,-r_testhole,th/2-.2])
                        cube([r_hole*2,r_testhole*2,.2],center=false);
            }
            cylinder(r=r_testhole, h=th+.2, center=true);
        }

    //overhang test
    bridge_h=.2;
    bridge_pos=(text_type=="raised"?th-font_recessed-bridge_h:th/2) ;
    if(test_circles>=3)
        translate([wall+r_hole+r_hole*7.5,h-wall-r_hole,bridge_pos])
            translate([-r_hole/2,-r_hole,0])
                cube([r_hole,r_hole*2,bridge_h],center=false);

    //hanging cylinder test
    testradius=(z-.3)/2;
    if(test_circles>=4)
        translate([wall+r_hole+r_hole*10, h-wall-r_hole*.5, th-testradius])
                rotate([90,0,0])
                    cylinder(r=testradius,h=r_hole*1.7,center=false);

     //pyramid test
    testpyramid=z-.3;
    biggerpyramid=1;
    if(test_circles>=5)
    {
        translate([wall+r_hole+r_hole*12.5, h-wall-r_hole*2, th-z/2])
            rotate([-90,45,0])
                cylinder(d1=z*sqrt(2), d2=0, h=testpyramid, $fn=4);
        translate([wall+r_hole+r_hole*12.5, h-wall, th-z/2])
            intersection()
            {
                rotate([90,0,0])
                    cylinder(d1=z*sqrt(2), d2=0, h=testpyramid*1, $fn=4);
                cube([3*z,3*z,z],center=true);
            }
    }
    //6
//    testwidth=z-.5;
//    translate([wall+r_hole+r_hole*15, h-wall-r_hole*1.4, th-testwidth/2])
//        cube([testwidth,r_hole*2,testwidth],center=true);

}

module textlines(height)
{
    translate([ w-wall,h-wall-textsize_upper,height ])
        linear_extrude(height=th,convexity = 10)
            text(textstring1,size=textsize_upper,font=fontname,halign="right",valign="baseline");

    translate([ wall,h-wall-(textsize_upper+textsize_lower*linesep),height ])
        linear_extrude(height=th,convexity = 10)
                text(textstring2,size=textsize_lower,font=fontname,halign="left",valign="baseline");

    translate([ w-wall,h-wall-(textsize_upper+textsize_lower*linesep),height ])
        linear_extrude(height=th,convexity = 10)
                text(textstring3,size=textsize_lower,font=fontname,halign="right",valign="baseline");
}

/**** substr ****

[str] substr(`str`, `[pos]`, `[len]`)
Returns a substring of a string.

Arguments:
- [str] `str`: The original string.
- [int] `pos` (optional): The substring position (0 by default).
- [int] `len` (optional): The substring length (string length by default).

Usage:
str = "OpenScad is a free CAD software.";
echo(substr(str, 12)); // "a free CAD software."
echo(substr(str, 12, 10)); // "a free CAD"
echo(substr(str, len=8)); // or substr(str, 0, 8); // "OpenScad"
*/

function substr(str, pos=0, len=-1, substr="") =
	len == 0 ? substr :
	len == -1 ? substr(str, pos, len(str)-pos, substr) :
	substr(str, pos+1, len-1, str(substr, str[pos]));

function no_leading_zero(strng) =
	strng == undef?
		undef
	: strng == ""?
		""
	: strng[0] == "0"?
        substr(strng, pos=1, len=-1, substr="")
    : strng;

function str_configurable_zero(number) =
	steps_text_format=="enforce leading zero 0._"?
		str(abs(number))
	: steps_text_format=="enforce leading and trailing zero 0.0"?
		 (round(number)==number? substr(str(abs(number+.01)), pos=0, len=len(str(abs(number+.01)))-1, substr=""):str(abs(number)))
	: no_leading_zero(str(abs(number)));
module swatch(text_type, edge_tests)
{
    //wall thickness around step area
    stepareadistance=(edge_tests=="enabled"?1.5:1);
    //height of the text field -> needs more space if raised text
    font_h= (text_type=="raised"?textsize_lower*.2 : 1)+textsize_upper + linesep*textsize_lower;
    //step area height
    steparea_h=h-(stepareadistance+1)*wall-font_h-wall;
    //if raised text, what is the area to recess so the raised text goes on top
    fontarea_h=font_h+wall;

    difference()
    {
        if(edge_tests=="enabled")
            rounded_square_test_pattern(w,h,th,round);
        else
            rounded_square(w,h,th,round);

        write_text_on_top(w,h,th,(edge_tests=="enabled"?wall:round));

        //hole to hang
        translate([wall+r_hole,h-wall-r_hole,th/2])
            cylinder(r=r_hole, h=th*2, center=true);

        for (i = [0:len(steps_thickness)-1])
        {
            steparea_w=(w-(2*stepareadistance)*wall)/len(steps_thickness)*(i+1);

            translate([ wall*stepareadistance,wall*stepareadistance,(steps_thickness[i]<0?-.1:steps_thickness[i]+step_thickness_correction) ])
            {
                rounded_square(steparea_w,steparea_h,th*2,round-wall);
            }
        }

        //handles
        handle_groove_out=r_indent/2;
        translate([-handle_groove_out,h-wall-r_indent,th/2])
            cylinder(r=r_indent, h=th*2, center=true);
        translate([w+handle_groove_out,h-wall-r_indent,th/2])
            cylinder(r=r_indent, h=th*2, center=true);

        if (text_type=="embossed")
        {
            //text
            textlines(th-font_recessed);
        }
        else
        {//xxx     if(text_type!="embossed")
            translate([ wall/2,h-wall/2-fontarea_h,th-font_recessed ])
                rounded_square(w-wall,fontarea_h,th,round-wall/2);
        }
    }

    //thickness numbers & bridges
    textheight=steps_textheight;//th-steps_thickness[len(steps_thickness)-1];
    textheight_bridge=steps_textheight;

    one_steparea_w=(w-2*stepareadistance*wall)/len(steps_thickness);
    for (i = [0:len(steps_thickness)-1])
    {
        steparea_w=one_steparea_w*(i+1);
        translate([ wall*stepareadistance+ steparea_w,0,0 ])
        {
            if (steps_text=="enabled")
            {
                rotate([0,0,(steps_text_rotate=="yes"?90:0)])
                translate([ (steps_text_rotate=="yes"?one_steparea_w/3:-one_steparea_w/10)
                            ,(steps_text_rotate=="yes"?wall*.5:wall*2)
                            ,(steps_thickness[i]<0?th-textheight_bridge:steps_thickness[i]+step_thickness_correction)])
                    linear_extrude(height=(steps_thickness[i]<0?textheight_bridge+step_thickness_correction:textheight),convexity = 10)
                            text(str_configurable_zero(steps_thickness[i]),size=steps_textsize,font=fontname,
                                halign=(steps_text_rotate=="yes"?"left":"right"),valign="baseline");
            }

            if (steps_thickness[i]<0) // bridge
            {
                gap=round/3;
                translate([ -one_steparea_w+gap,wall*stepareadistance,th-(-steps_thickness[i])-textheight_bridge ])
                    cube([one_steparea_w-gap, steparea_h, -steps_thickness[i]+step_thickness_correction]);
                    //rounded_biteoff_square(one_steparea_w-gap, steparea_h, -steps_thickness[i],round-wall);
            }
        }
    }

    if(text_type!="embossed")
    {
        textlines(0);
    }

    if(edge_tests=="enabled")
    {
        rounded_square_test_pattern_raised(w,h,th,round,text_type, edge_tests);
    }

}

Debug="false";
if (Debug!="true")
{
    swatch(text_type, edge_tests);
}
else
{
    swatch("raised", "disabled");
    translate([w+3,0,0]) swatch("raised", "enabled");
    translate([0,h+3,0]) swatch("embossed", "disabled");
    translate([w+3,h+3,0]) swatch("embossed", "enabled");
}

```


[User]
I'd like to create a parametric door lock mechanism, similar to a bolt or latch. The design should be customizable in terms of overall length and width.

The core of the design is a pill-shaped solid with mounting points on the sides. These mounting points need to have a threaded hole to attach to the door or frame. The pill shape itself should be formed by a cylinder with hemispherical ends. I would like to be able to specify the length and width of this 'pill', with the width controlling the diameter of the cylinder and spheres, and the length controlling the overall length of the pill.

I'm thinking the pill will be oriented along the x-axis initially, but this should be easy to rotate so that it operates along the z-axis for 3D printing purposes, and I can do that rotation myself.

The main body of the mechanism needs to have a cutout that allows it to move freely, kind of a U shape or inverted bracket that provides both top and bottom constraint. I want this cutout to have a slight clearance; maybe 0.25 of the width of the pill? This U needs to be wider than the width of the main body so that it doesn't bind on the sides. The length of the main body is going to need to be longer than the main bolt to allow for the travel of the latch.

On the sides of the pill-shaped main body, I need mounting cylinders with a hole. The mounting cylinders should have a screw-thread cavity for attaching with a screw. The hole for these mounting cylinders should go all the way through. I don't care exactly what size of the screw thread is used, just that it should be consistent across the design and that the hole in the cylinder is threaded.

I'd also like the latch itself to have a 'catch' or lip on the top, which helps to restrain it from sliding further than it should, but should allow it to slide freely.

I'm looking for the design to be parametric, so I can adjust the Length and Width, with Width generally being about half of the Length. So Length = 100 and Width = 50 would be reasonable. Can you generate the OpenSCAD code for this? I'd like to ensure all the main components can be printed in place without requiring any additional support material. Please make sure the clearance is adequate for that. I don't need to have the mounting screws included, I'm happy to add them myself.

I'd also like the code to include a module that generates the threaded mounting points.

[Assistant]
lock.scad:
```
// lock.scad
// library for parametric door lock, latch, bolt, detent
// Author: Tony Z. Tonchev, Michigan (USA)
// last update: Jaqnuary 2019

// (in mm)
Length = 100;

// (in mm)
Width = 50;

/* [Hidden] */

//$fn=100;
TZT_Bolt ([Length,Width/2]);

module TZT_Bolt (TZT_Size) {
    difference () {
        rotate ([90,90,0]) difference () {
            TZT_Pill (TZT_Size);
            translate ([TZT_Size[1]/2,0,0])
                cube ([TZT_Size[1],TZT_Size[1]*2,TZT_Size[1]*2+TZT_Size[0]],true);
            TZT_Pill ([TZT_Size[0], TZT_Size[1]/2]);
        }
        for (i=[-1:2:1]) for (j=[-1:1:1]) {
            translate ([i*(TZT_Size[1]/2+5),j*TZT_Size[0]*0.40+TZT_Size[0]*0.05,0])
                cylinder (TZT_Size[1]*2,5,5,true);
        }
        translate ([0,+TZT_Size[0]*0.25,0])
            cube ([TZT_Size[1]*2,1,TZT_Size[1]*2],true);

        hull ()
            for (i=[0.1,0.5])
                translate ([0,-i*TZT_Size[0],0])
                cylinder (TZT_Size[1],TZT_Size[1]/3,TZT_Size[1]/3);
    }
    for (i=[-1:2:1]) for (j=[-1:1:1]) {
        translate ([i*(TZT_Size[1]/2+5),j*TZT_Size[0]*0.40+TZT_Size[0]*0.05,0]) TZT_Mount ();
    }
    rotate ([90,90,0]) difference () {
        translate ([0,0,TZT_Size[0]*0.15+TZT_Size[1]/4])
            union () {
                TZT_Pill ([TZT_Size[0]*0.7-TZT_Size[1]/2, TZT_Size[1]/2-1]);
                rotate ([0,90,0])
                    translate ([-TZT_Size[0]/2+TZT_Size[0]*0.15+TZT_Size[1]/4,0,-TZT_Size[1]/2])
                    TZT_Pill ([TZT_Size[1], TZT_Size[1]/3-1]);
            }
        translate ([TZT_Size[1]/2,0,0])
            cube ([TZT_Size[1],TZT_Size[1]*2,TZT_Size[1]*2+TZT_Size[0]],true);
    }
}
module TZT_Mount () {
    translate ([0,0,5]) difference () {
        cylinder (10,5,5,true);
        cylinder (11,2,2,true);
        translate ([0,0,4]) cylinder (3,2,5,true);
    }
}
module TZT_Pill (TZT_Size) {
    cylinder (TZT_Size[0],TZT_Size[1],TZT_Size[1],true);
    for (i=[-1:2:1]) translate ([0,0,i*TZT_Size[0]/2]) sphere (TZT_Size[1]);
}
```


[User]
I need a parametric laptop stand designed for laser cutting. I'd like it to be customizable in several ways, allowing me to adjust it to different laptop sizes and desired viewing angles. The basic structure should involve side pieces that interlock with cross members, creating a stable frame. I want it to be a press-fit design, so no glue or screws are required.

Here are the specific parameters I need to control:

*   **Material Thickness:** I need to be able to specify the thickness of the material I'm using, and the design should automatically adjust for that. Also, the design should account for kerf.
*   **Stand Width:** I need to set the total width of the stand, excluding any overhang.
*   **Cross Member Overhang:** I need an option for cross members to extend past the sides to improve the press fit.
*   **Stand Height:** I need to set the height of the back of the stand.
*  **Lower Deck Depth:** I need to set the depth of the bottom support legs.
*   **Upper Deck Depth:** The depth of the upper deck needs to be adjustable, so my laptop doesn't fall off.
*   **Upper Deck Angle:** I need to control the angle of the upper deck for different viewing angles.
*   **Side Spar Thickness:** The width of the side supports needs to be adjustable for strength.
*   **Spar Spacing:** I'd like to be able to specify the spacing between the side supports and have them automatically arrange themselves.
*   **Cross Member Spacing:** Same as the side supports, I need to specify the spacing between the cross members and have them automatically arrange themselves.
*   **Internal Spar Adjustment:** An adjustment to move internal support spars, in case there are conflicts with the crossmembers.
*   **Edge Support Layers:** I need to control how many end layers are used to increase strength with thinner materials.
*   **Curve Radius:** I need to be able to control the radius of corners of the stand.
*   **Upper Deck Lip Width:** I want to adjust the width of the lip at the front of the upper deck.
*   **Upper Deck Lip Height:** I need to set the height of the lip to stop the laptop from sliding.
*  **Upper Deck Lip Depth:** I need an option to make the laptop extend beyond the front of the top deck.
*   **Use Bottom Mid Crossmember:** A boolean to control whether to add a middle cross member for added strength.
*   **Resolution:** I need to control the resolution of curves, so I can increase it for a smoother design.
*   **Projection:** An option to show a 2D projection of all the parts, to be used for laser cutting, with a scale square.

I don't have any libraries I need to use. Everything should be coded from scratch. Can you generate the appropriate OpenSCAD files to handle this?

[Assistant]
Notebook_stand_2.scad:
```
/*[ Stand properties.  Size to your laptop]*/

//Thickness of material used (subtract as needed to account for kerf)
material_thickness = 4.0; //[1 : 0.1 : 20]

//The size of the kerf (4mm MDF = 0.23) (size to materiel and machine)
kerf = 0.23; //[0 : 0.01 : 0.5]

//the total width of the stand (not including any overhang)
stand_width = 280; //[200 : 5 : 500]

//Optional crossmember overhang for pressfit side support
cross_member_overhang = 10; //[0 : 1 :50]

//Height of the back of the stand
stand_height = 180; //[100 : 5 : 300]

//Depth bottom edge support legs (shorter than upper deck but too short and stand will be unstable)
lower_deck_depth = 190; //[150 : 5 : 300]

//Upper deck depth (too short and laptop will fall off the back, too long and stand will be unstable)
upper_deck_depth = 250; //[200 : 5 : 300]

//The angle of the upper deck. Generally between 20-30 degress, increase to make steeper
upper_deck_angle = 25; //[0 : 1 : 40]

/*[ Support structure properties]*/

//The width of the side spars, set too thin will make for a fragile stand
side_spar_thickness = 30; //[20 : 1 : 40]

//The suggested spacing between supports. Automatically adjusts support numbers for optimal placement
spar_spacing = 70; //[10 : 1 : 100]

//The suggested spacing between cross members. Automatically adjusts support numbers for optimal placement
cross_member_spacing = 60;//[10 : 1 : 100]

//Adjustment to move internal supports when there are conflicts with crossmembers
internal_spar_upper_deck_depth_divisor = 0.25; //[0.1 : 0.05 : 1]

//increase the number of end layers for increased stand strength with thinner materiels
edge_supports_layers = 1; //[1 : 1 : 10]

//The radius of corners, Smaller numbers make for sharper corners
curve_radius = 22; //[15 : 1 : 35]

/*[ Laptop Lip properties. Size appropriately to your laptop]*/

//The center width of supports with lips. For better stability > 90 is reccomended
upper_deck_front_lip_width = 94; //[50 : 5 : 400]

//The height the lip needed to catch the front edge of your laptop
upper_deck_front_lip_height = 12; //[0 : 5 : 50]

//The length the laptop can extends past the front of the top deck.
upper_deck_front_lip_depth = 0; //[-20 : 5 : 30]

//Cuts holes for a middle crossmember for extra support with thinner or deeper stands
use_bottom_mid_crossmember = "false";//[true, false]

/*[ Model properties.]*/

//The resolution of the curves. Higher values give smoother curves but increases render time.
resolution = 30; //[10 : 10 : 120]

//show 2d projection of individual parts with a 100mm square for scale
projection = "false";//[true, false]

$fn = resolution;

actualStandWidth = stand_width - material_thickness;
acutalLipWidth = upper_deck_front_lip_width - material_thickness;
upper_deck_rear_height = stand_height - 2 * curve_radius;
actual_lower_deck_depth = lower_deck_depth - curve_radius;
actual_upper_deck_depth = upper_deck_depth - curve_radius;

if (projection == "true") {
    //end support
    projection() side(true, false);
    //inner support
    projection() translate([0,upper_deck_rear_height * 1.5,0] )side(true, true);
    //inner support with lip
    projection() translate([actual_lower_deck_depth * 1.5,0 ,0] )side(false, true);
    //cross member
    projection()  translate ([-10-curve_radius-(side_spar_thickness * 2),0,0]) crossMember(true);
    projection()  translate ([-10-curve_radius-(side_spar_thickness * 4),0,0]) crossMember(false);
    projection() translate ([-10-curve_radius-(side_spar_thickness * 6),0,0]) bottomRightCrossMember();

    //scale
    translate([actual_lower_deck_depth * 1.5, upper_deck_rear_height,0] ) square([100,100]);

} else {
    supportStructure();
    translate ([-10-curve_radius-(side_spar_thickness * 2),0,0]) crossMember(true);
    translate ([-10-curve_radius-(side_spar_thickness * 4),0,0]) crossMember(false);
    translate ([-10-curve_radius-(side_spar_thickness * 6),0,0]) bottomRightCrossMember();
}

module supportStructure() {
    normal_spar_count = floor(actualStandWidth / spar_spacing);
    normal_spar_spacing = actualStandWidth  / normal_spar_count;

    lipspaceLeft  = actualStandWidth / 2.0 - acutalLipWidth / 2.0;
    lipspaceRight = actualStandWidth / 2.0 + acutalLipWidth / 2.0;

    for(i=[0:normal_spar_count]) {
        if (upper_deck_front_lip_height == 0 || i * normal_spar_spacing < lipspaceLeft || i * normal_spar_spacing > lipspaceRight) {
            if ( i==0 || i == normal_spar_count) {
                    translate([0,0,i*normal_spar_spacing]) color("red") side(true, false);
            } else {
                if (abs(i*normal_spar_spacing - lipspaceLeft) > 5 && abs(i*normal_spar_spacing - lipspaceRight) > 5) {
                    translate([0,0,i*normal_spar_spacing]) side(true, true);
                }
            }
        }
    }

    //double up end supports?
    if (edge_supports_layers > 1) {
        for(i=[2:edge_supports_layers]) {
            diff = i - 1;
            translate([0,0,diff*material_thickness]) color("red") side(true, false);
            translate([0,0,actualStandWidth - diff*material_thickness]) color("red") side(true, false);
        }
   }

    //add 2 lipped spars
    if (upper_deck_front_lip_height > 0) {
       normal_lip_count = floor(acutalLipWidth / spar_spacing);
       if (normal_lip_count < 2) {
            translate([0,0,lipspaceLeft]) color("green") side(false, true);
            translate([0,0,lipspaceRight]) color("green") side(false, true);
      } else {
           normal_lip_spacing = acutalLipWidth / normal_lip_count;
           for(i=[0:normal_lip_count]) {
                translate([0,0,lipspaceLeft + i*normal_lip_spacing]) color("blue") side(false, true);
            }
        }
    }

}

module side(hideLip, isInternal) {
    linear_extrude(height=material_thickness) {
        difference() { //remove crossmember holes
            union() {
                if (!isInternal) {
                    difference() {
                        edgeSidePieces();
                        offset(r = -side_spar_thickness) edgeSidePieces();
                    }
                   difference() {
                        edgeSupportPieces();
                        offset(r = -side_spar_thickness) edgeSupportPieces();
                    }
                } else {
                    difference() {
                        internalSidePieces();
                        offset(r = -side_spar_thickness) internalSidePieces();
                    }
                }

                if (!hideLip) {
                    topDeckLip();
                }
            }
            //subtract cross members
            crossMemberHoles(isInternal);
        }
    }
}

module edgeSupportPieces() {
         hull () {
        //top left
        translate([0, 0, 0]) circle(r=curve_radius);
        //top right
        rotate([0,0,-upper_deck_angle]) translate([(actual_upper_deck_depth - curve_radius) * internal_spar_upper_deck_depth_divisor , 0, 0]) circle(r=curve_radius);
        //bottom left
        translate([0, -upper_deck_rear_height, 0]) circle(r=curve_radius);

     }
 }

module edgeSidePieces() {
    hull () {
        //top left
        translate([0, 0, 0]) circle(r=curve_radius);
        //top right
        rotate([0,0,-upper_deck_angle]) translate([actual_upper_deck_depth - curve_radius, 0]) circle(r=curve_radius);
        //bottom left
        translate([0, -upper_deck_rear_height, 0]) circle(r=curve_radius);
        //bottom right
         translate([actual_lower_deck_depth - curve_radius, -upper_deck_rear_height, 0]) circle(r=curve_radius);
    }
}

module internalSidePieces() {
    union() {
                   rotate([0,0,-upper_deck_angle]) hull () {
                       //make sure spar extends out to lip
        translate([0, curve_radius - side_spar_thickness/2.0, 0]) circle(d=side_spar_thickness);
         translate([actual_upper_deck_depth - curve_radius, curve_radius - side_spar_thickness/2.0, 0]) circle(d=side_spar_thickness);

                   }

    difference() {
    hull () {
        //top left
        translate([0, 0, 0]) circle(r=curve_radius);
        //top right
        rotate([0,0,-upper_deck_angle]) translate([(actual_upper_deck_depth - curve_radius) * internal_spar_upper_deck_depth_divisor , 0, 0]) circle(r=curve_radius);
        //bottom left
        translate([0, -upper_deck_rear_height, 0]) circle(r=curve_radius);

     }
     }
    }

}

module topDeckLip() {
    //top right
    rotate([0,0,-upper_deck_angle])
    union() {
            hull () {
                     translate([actual_upper_deck_depth - curve_radius, curve_radius - side_spar_thickness, 0]) square([side_spar_thickness/2.0,side_spar_thickness]);
                     translate([actual_upper_deck_depth + upper_deck_front_lip_depth, curve_radius - side_spar_thickness/2.0, 0]) circle(d=side_spar_thickness);
            }
             hull () {
                     translate([actual_upper_deck_depth + upper_deck_front_lip_depth, curve_radius - side_spar_thickness/2.0, 0]) circle(d=side_spar_thickness);
                     translate([actual_upper_deck_depth + upper_deck_front_lip_depth - side_spar_thickness/4.0, curve_radius + upper_deck_front_lip_height, 0]) circle(d=side_spar_thickness/2.0);

        }
    }
}

module crossMemberHoles(isInternal) {

    //back members
    actual_back_height = upper_deck_rear_height - 2 *curve_radius;
    back_crossMember_count = floor(actual_back_height / cross_member_spacing);
    back_crossMember_spacing = actual_back_height  / back_crossMember_count;

    //do not stagger back, makes assembly hard
    for(i=[0:back_crossMember_count]) {
        translate([-curve_radius,-i * back_crossMember_spacing - curve_radius,0])
            square([side_spar_thickness / 2.0, material_thickness - kerf]);

    }

    //top members
    actual_top_depth = actual_upper_deck_depth - 3 * curve_radius;
    top_crossMember_count = floor(actual_top_depth / cross_member_spacing);
    top_crossMember_spacing = actual_top_depth  / top_crossMember_count;

    for(i=[0:top_crossMember_count]) {
            if (isInternal) { //stagger holes
              rotate([0,0,-upper_deck_angle])
                translate([i * top_crossMember_spacing + curve_radius ,curve_radius - side_spar_thickness-0.1,0])
                    square([material_thickness - kerf,side_spar_thickness / 2.0]);
            } else {
               rotate([0,0,-upper_deck_angle])
                translate([i * top_crossMember_spacing + curve_radius ,curve_radius - side_spar_thickness / 2.0 +0.1,0])
                    square([material_thickness - kerf,side_spar_thickness / 2.0]);
         }
    }

    //bottom right
    translate([actual_lower_deck_depth - (curve_radius + side_spar_thickness / 2.0) ,-upper_deck_rear_height - curve_radius,0])
                    square([side_spar_thickness / 2.0 - kerf, material_thickness - kerf]);

    if (use_bottom_mid_crossmember == "true") {
     //bottom mid
        translate([actual_lower_deck_depth/2.0 - (curve_radius/2.0) ,-upper_deck_rear_height - curve_radius,0])
                    square([side_spar_thickness / 2.0 - kerf, material_thickness - kerf]);
    }

}

module basicCrossMember() {
           hull() {
            square([side_spar_thickness, stand_width]);
            if (cross_member_overhang > 0) {
                if (cross_member_overhang < side_spar_thickness) {
                    translate([side_spar_thickness/2.0,-cross_member_overhang,0])
                        resize([side_spar_thickness, cross_member_overhang, 0])
                            circle(d=side_spar_thickness);
                    translate([side_spar_thickness/2.0,stand_width+cross_member_overhang,0])
                           resize([side_spar_thickness, cross_member_overhang, 0])
                                circle(d=side_spar_thickness);
                    }else {
                    translate([side_spar_thickness/2.0,-cross_member_overhang,0])
                                circle(d=side_spar_thickness);
                    translate([side_spar_thickness/2.0,stand_width+cross_member_overhang,0])
                                circle(d=side_spar_thickness);
                }
            }
        }
    }

module bottomRightCrossMember() {
        linear_extrude(height=material_thickness) {

    difference() {

       //material
        basicCrossMember();

      //doubled up end supports?
        for(i=[1:edge_supports_layers]) {
            diff = i - 1;
            translate([0,diff*material_thickness,0]) square([side_spar_thickness/4.0, material_thickness-kerf]);
            translate([3*side_spar_thickness/4.0,diff*material_thickness,0]) square([side_spar_thickness/4.0, material_thickness-kerf]);
            translate([0,actualStandWidth - diff*material_thickness,0]) square([side_spar_thickness/4.0, material_thickness-kerf]);
            translate([3*side_spar_thickness/4.0,actualStandWidth - diff*material_thickness,0]) square([side_spar_thickness/4.0, material_thickness-kerf]);
          }
      }
  }

}

module crossMember(isReversed) {

        linear_extrude(height=material_thickness) {

    difference() {

        //material
        basicCrossMember();
        //cutouts

        normal_spar_count = floor(actualStandWidth / spar_spacing);
        normal_spar_spacing = actualStandWidth  / normal_spar_count;

        lipspaceLeft  = actualStandWidth / 2.0 - acutalLipWidth / 2.0;
        lipspaceRight = actualStandWidth / 2.0 + acutalLipWidth / 2.0;

        for(i=[0:normal_spar_count]) {
            if (upper_deck_front_lip_height == 0 ||  (abs(i*normal_spar_spacing - lipspaceLeft) > 5 && abs(i*normal_spar_spacing - lipspaceRight) > 5)) {
                if ( i==0 || i == normal_spar_count || !isReversed) { //edge
                    translate([side_spar_thickness/2.0,i*normal_spar_spacing,0]) square([side_spar_thickness/2.0, material_thickness-kerf]);
                } else { //internal
                    translate([0,i*normal_spar_spacing,0]) square([side_spar_thickness/2.0, material_thickness-kerf]);
                }
            }
        }

        //doubled up end supports?
        if (edge_supports_layers > 1) {
            for(i=[2:edge_supports_layers]) {
                diff = i - 1;
                translate([side_spar_thickness/2.0,diff*material_thickness,0]) square([side_spar_thickness/2.0, material_thickness-kerf]);
                translate([side_spar_thickness/2.0,actualStandWidth - diff*material_thickness,0]) square([side_spar_thickness/2.0, material_thickness-kerf]);
              }
       }

        //add 2 lipped spars

        if (upper_deck_front_lip_height > 0) {
           normal_lip_count = floor(acutalLipWidth / spar_spacing);
           if (normal_lip_count < 2) {
               if (!isReversed) {
                 translate([side_spar_thickness/2.0,lipspaceLeft,0]) square([side_spar_thickness/2.0, material_thickness-kerf]);
                 translate([side_spar_thickness/2.0,lipspaceRight,0]) square([side_spar_thickness/2.0, material_thickness-kerf]);
               } else {
                translate([0,lipspaceLeft,0]) square([side_spar_thickness/2.0, material_thickness-kerf]);
                translate([0,lipspaceRight,0]) square([side_spar_thickness/2.0, material_thickness-kerf]);
               }
          } else {
               normal_lip_spacing = acutalLipWidth / normal_lip_count;
               for(i=[0:normal_lip_count]) {
                                  if (!isReversed) {
                    translate([side_spar_thickness/2.0,lipspaceLeft + i*normal_lip_spacing,0]) square([side_spar_thickness/2.0, material_thickness-kerf]);
                                  } else {
                      translate([0,lipspaceLeft + i*normal_lip_spacing,0]) square([side_spar_thickness/2.0, material_thickness-kerf]);
                                }
                }
            }
        }
    }
    }
}

```


[User]
I need to create a grid of washers, and I'd like a parametric OpenSCAD design for that. Here's what I'm thinking:

Each individual washer should be a simple cylinder with a hole in the middle, so basically a hollow cylinder. I need to be able to adjust the inner diameter, the border thickness (the difference between the inner and outer diameter), and the overall thickness of each washer.

Then, I want to arrange these washers in a grid, specifying the number of rows and columns. Crucially, I need to be able to set the spacing between the washers as well. I'd also like to be able to modify the color of the washers using the `color()` command.

The units should all be in millimeters.

I'm looking for the design to be easy to modify, so please make sure I can adjust these parameters:

*   `inner`: Inner diameter of the washer.
*   `border`: Thickness of the washer material (outer - inner diameter).
*   `thickness`: Height/thickness of the washer.
*  `rows`: Number of rows in the grid.
*  `cols`: Number of columns in the grid.
*  `spacing`: Distance between washers in the grid.
* `washer_color`: Color of the washers

I would like to use the basic OpenSCAD library rather than any community libraries.

[Assistant]
washer_x_y.scad:
```
// Multiple Customizable Washer
// NIS 12/01/2019
// Version 1.1

// inner diameter in mm
inner = 26; // [0.4:0.1:540]

// border in mm
border = 6; // [0.4:0.1:30]

// thickness in mm
thickness = 2.6;  // [0.2:0.1:40]

// rows
rows = 5;

// cols
cols = 3;

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

// #######################################################
// based on 953338
// info customizer.makerbot.com/docs

outer = inner + border;
$fn=120;

module myWasher (inner, outer, thickness) {

    difference() {
        cylinder(d=outer, h=thickness);
        translate([0,0,-thickness]) # cylinder(d=inner, h=thickness*3);
    };
}

// dimensions in mm

for(runY = [0 : rows-1])
    for(runX = [0 : cols-1])
    {
      translate([runX*(2+outer), runY*(2+outer), 0])
      color("blue") myWasher (inner, outer, thickness);
    }

```


[User]
Okay, I'd like to design a customizable dodecahedron that can be 3D printed. I want to be able to specify the length of the edges, the height of the faces, the size of the text on each face, and the indentation depth of that text. It should also be possible to define the text on each of the 12 faces. It also needs to have a slight clearance between faces to make assembly easier.

The faces will each be a pentagon, and I want to be able to print the faces and the text separately, so I can do dual extrusion. I want to specify an optional variable to output the faces or the text, or both. I plan on printing the faces with the text already indented, which should turn out correctly when the faces are assembled.

I'm picturing a design where the faces are printed flat, and then they can be bent (using heat if needed) and glued together. The hinge will only be 1 layer thick (when printing at 0.2mm layer height).

I'd like to use the standard OpenSCAD library for this.

[Assistant]
Dodecahedron.scad:
```
/* [Dimensions] */
// Length of the edges[mm]
edge_length = 20;

// Height of the outer surfaces [mm]
height = 1.2;

// Height of the Text
textsize = 8;

// Depth of indentation of the text [mm]
textheight = 0.2;

// Extra clearance between faces [mm]
clearance  = 0.1;

/* [Labels] */
// text1 is opposite to text7, text2 to text8 ...
text1 = "1";
text2 = "5";
text3 = "11";
text4 = "3";
text5 = "9.";
text6 = "7";
text7 = "12";
text8 = "8";
text9 = "2";
text10 = "10";
text11 = "4";
text12 = "6.";

size = edge_length;

part = "Base"; // ["Base":Create only Cube, "Text": Create only the text, "Both":Create both]

/* [Hidden] */
numbers = [str(text1), str(text2), str(text3), str(text4), str(text5), str(text6), str(text7), str(text8), str(text9), str(text10), str(text11), str(text12)];

// creates a multiline Text from array
module textlines(text, textsize=10, lineheight=1.2, halign="left", valign="baseline") {
    text(text, size = textsize, halign=halign, valign=valign);
}

module pentagon_extruded_base(size, h, fillet=0, baseheight=0.2,num=1, i=0, textsize=10, textheight=0.2, clearance=0.1)
    {
    scale = 1 - fillet / size;

    difference(){
        union()
            {
            translate([0,0,baseheight])linear_extrude(h, scale=scale)circle(r=size*0.8507-clearance, $fn=5);
            linear_extrude(baseheight)
                circle(r=size*0.8507, $fn=5);
            }

        linear_extrude(textheight)
            rotate([0,0,54+72*i])scale([-1, 1, 1])
                textlines(numbers[num-1], halign="center", valign="center", textsize=textsize);
        }
    }

module pentagon_extruded_inner(size, h, fillet=0, textheight=0.2, num=1, i=0, textsize=10, clearance=0.1)
    {
    linear_extrude(textheight)
    rotate([0,0,54+72*i])scale([-1, 1, 1])
        textlines(numbers[num-1], halign="center", valign="center", textsize=textsize);
    }

module pentagon_6(side, h, fillet=0, num=1, textsize=10, textheight=0.2, clearance=0.1)
    {
    innerr = 0.6881 * side;
    innerd = 2 * innerr;
    pentagon_extruded_base(side, h, fillet=fillet, num=num*6-5, textsize=textsize, textheight=textheight, clearance=clearance);

    for (i=[0:4])
        {
        alpha = 36+72*i;
        translate([cos(alpha)*innerd, -sin(alpha)*innerd,0])rotate([0,0,180])
            pentagon_extruded_base(side, h, fillet=fillet, num=num*6-4+i, i=i, textsize=textsize, textheight=textheight, clearance=clearance);
        }
    }

module pentagon_6_inner(side, h, fillet=0, num=1, textsize=10, textheight=0.2, clearance=0.1)
    {
    innerr = 0.6881 * side;
    innerd = 2 * innerr;
    pentagon_extruded_inner(side, h, fillet=fillet, num=num*6-5, textheight=textheight, textsize=textsize, clearance=clearance);

    for (i=[0:4])
        {
        alpha = 36+72*i;
        translate([cos(alpha)*innerd, -sin(alpha)*innerd,0])
            rotate([0,0,180])pentagon_extruded_inner(side, h, fillet=fillet, num=num*6-4+i, i=i, textheight=textheight, textsize=textsize, clearance=clearance);
        }
    }

angle = 2 * asin(sqrt((5+sqrt(5))/10)); // dihedral angle

fillet = height * tan(90 - angle/2);

// base
if (part == "Base" || part == "Both")
    {
    pentagon_6(size, height, fillet=fillet, num=1, textsize=textsize, textheight=textheight, clearance=clearance);
    translate([-0.425181*size, -3.926752*size, 0])rotate([0,0,180])     pentagon_6(size, height, fillet=fillet, num=2, textsize=textsize, textheight=textheight, clearance=clearance);
    }

// numbers
if (part == "Text" || part == "Both")
    {
    pentagon_6_inner(size, height, fillet=fillet, num=1, textsize=textsize, textheight=textheight, clearance=clearance);
    translate([-0.425181*size, -3.926752*size, 0])rotate([0,0,180]) pentagon_6_inner(size, height, fillet=fillet, num=2, textsize=textsize, textheight=textheight, clearance=clearance);
    }

```


[User]
Okay, I need an OpenSCAD design for a security camera mount that will be used under a roof with a 22.5-degree slope. I want the design to be parametric so I can easily change the angle and other dimensions.

Here's what I'm thinking:

1.  **Base:** The base will be a kind of angled block that matches the roof slope. It will have a flat top surface for mounting the camera. I envision the base as a cylinder that has been cut off and scaled to give that angled appearance.

2.  **Camera Mount:** On top of the base, there should be a camera mount. I'd like to include some screw holes for attaching my generic security camera, which has a roughly square mounting pattern. Maybe four holes on a 50x50mm square, or something easily adjustable.

3.  **Cable Management:** I need a small, separate plate that can be attached to the base with a screw. This plate will have a slot or hole to guide the camera cable and keep insects out. It also needs to be able to close up the screw holes for the main mount to the roof so they don't become insect habitats. This plate can just be a simple disc-like shape with a protrusion for the hole for wire, and a hole for mounting.

4.  **Support Structure:** The main base should have some support ribs or pillars to give it strength, maybe around the perimeter, but not obstructing the camera mounting. They should be perpendicular to the angled top surface.

5.  **Holes and Cutouts:** The design needs to have holes to attach to the roof or mounting surface. I envision a grid of countersunk holes on the base, maybe 50mm apart, so that I can screw it securely onto the roof. A central hole for camera wiring would also be needed.

6.  **Parametric Design:**
    *   **Angle:** Make the roof angle (22.5 degrees in this case) a parameter.
    *   **Base Height:** Let me adjust the height of the base.
    *   **Camera Mounting Hole Spacing:** Allow me to control the distance between the camera mounting holes.
    *   **Hole Diameters:** All hole diameters should be parameters.
    *   **Support Size:** The radius/diameter of the perimeter supports.
    *   **Cable Management Plate Size:** Let me adjust the size of the plate and position of cable hole.
    *   **Plate mount offset:** Let me adjust the distance of plate from center of the base.
    *   **Support count:** The number of supports around the perimeter of the base.

I'm not sure how to best implement all of this, but I think I can use standard OpenSCAD functions for cylinders, cubes, and transformations. I do not want to use BOSL libraries; please just use the core functionality of OpenSCAD.

I'd like to be able to easily customize this mount for different angles, camera sizes, and mounting surfaces.

[Assistant]
support.scad:
```
$fn=100;

angle=22.5;
height=170*tan(angle)+5;
echo(str("Calculated height: ",height));

module holes_supports() {
     for(x=[-25:50:25]) for(y=[-25:50:25]) translate([x,y,height-10]) cylinder(10,3,3);
}

module holes() {
     for(x=[-25:50:25]) for(y=[-25:50:25]) translate([x,y,0]) cylinder(200,1,1);

}

module pads_holes() {
         for(angle=[0:90:359]) rotate(angle,[0,0,1]) translate([40,0,0]) cylinder(height+5,2.5,2.5);
         for(angle=[0:90:359]) rotate(angle,[0,0,1]) translate([40,0,height/3*2+10]) cylinder(height/3,5,5);
}

module base() {
    scale([1,1,height]) union() {
        intersection() {
            cylinder(1,50,50);
            translate([0,-100,0]) {
                    cube([200,200,200]);
            }
        }

        intersection() {
            scale([6/5,1,1]) cylinder(1,50,50);
            translate([-200,-100,0]) {
                    cube([200,200,200]);
            }
        }
    }
}

module central_hole(r=42,l=height-5) {
        cylinder(l,r,r);
}

module pads() {
    for(angle=[0:90:359]) rotate(angle,[0,0,1]) translate([40,0,0]) cylinder(height,7,7);
}

module whole() {
    difference() {
        union() {
            difference() {
                base();
                central_hole();
                central_hole(r=25,l=2*height);
            }
            holes_supports();
            pads();

        }
        holes();
        pads_holes();
    }
}

module plan() {
    rotate(-angle,[0,1,0]) cube([1000,1000,height],center=true);
}

//%plan();
//%pads_holes();
//%pads();
//%holes_supports();

module assemble() {
    difference() {
        whole();
        plan();
    }
}

//color([0,0,1,0.3]) translate([0,0,height]) rotate(angle,[0,1,0]) assemble();

module plate() {
    difference() {
        union() {
            difference() {
                cylinder(1,41,41);
                translate([0,0,-height+10]) assemble();
                pads();
                holes();
            }
            difference() {
                central_hole(r=24,l=7);
                central_hole(r=22,l=8);
            }
        }
        translate([34,0,0]) cube([20,5,20],center=true);
        translate([24,0,0]) cylinder(20,2.5,2.5,center=true);
        for(x=[-25:50:25]) for(y=[-25:50:25]) translate([x,y,-5]) cylinder(10,3.5,3.5);
    }
}

color([1,0,0,0.3]) plate();

%for(angle=[0:90:359]) rotate(angle,[0,0,1]) translate([40,0,-5]) cylinder(height,8,8);
```
support.scad:
```
$fn=100;

angle=22.5;
height=170*tan(angle)+5;
echo(str("Calculated height: ",height));

module holes_supports() {
     for(x=[-25:50:25]) for(y=[-25:50:25]) translate([x,y,height-10]) cylinder(10,3,3);
}

module holes() {
     for(x=[-25:50:25]) for(y=[-25:50:25]) translate([x,y,0]) cylinder(200,1,1);

}

module pads_holes() {
         for(angle=[0:90:359]) rotate(angle,[0,0,1]) translate([40,0,0]) cylinder(height+5,2.5,2.5);
         for(angle=[0:90:359]) rotate(angle,[0,0,1]) translate([40,0,height/3*2+10]) cylinder(height/3,5,5);
}

module base() {
    scale([1,1,height]) union() {
        intersection() {
            cylinder(1,50,50);
            translate([0,-100,0]) {
                    cube([200,200,200]);
            }
        }

        intersection() {
            scale([6/5,1,1]) cylinder(1,50,50);
            translate([-200,-100,0]) {
                    cube([200,200,200]);
            }
        }
    }
}

module central_hole(r=42,l=height-5) {
        cylinder(l,r,r);
}

module pads() {
    for(angle=[0:90:359]) rotate(angle,[0,0,1]) translate([40,0,0]) cylinder(height,7,7);
}

module whole() {
    difference() {
        union() {
            difference() {
                base();
                central_hole();
                central_hole(r=25,l=2*height);
            }
            holes_supports();
            pads();

        }
        holes();
        pads_holes();
    }
}

module plan() {
    rotate(-angle,[0,1,0]) cube([1000,1000,height],center=true);
}

//%plan();
//%pads_holes();
//%pads();
//%holes_supports();

module assemble() {
    difference() {
        whole();
        plan();
    }
}

//color([0,0,1,0.3]) translate([0,0,height]) rotate(angle,[0,1,0]) assemble();

module plate() {
    difference() {
        union() {
            difference() {
                cylinder(1,41,41);
                translate([0,0,-height+10]) assemble();
                pads();
                holes();
            }
            difference() {
                central_hole(r=24,l=7);
                central_hole(r=22,l=8);
            }
        }
        translate([34,0,0]) cube([20,5,20],center=true);
        translate([24,0,0]) cylinder(20,2.5,2.5,center=true);
        for(x=[-25:50:25]) for(y=[-25:50:25]) translate([x,y,-5]) cylinder(10,3.5,3.5);
    }
}

color([1,0,0,0.3]) plate();

%for(angle=[0:90:359]) rotate(angle,[0,0,1]) translate([40,0,-5]) cylinder(height,8,8);
```


[User]
Hey, I'm looking to create a parametric design for a Schlage SC1 key in OpenSCAD. I'd like to be able to customize the bitting depths for all five pins, and also add a custom text label to the handle.

Specifically, I need the following parameters:

*   **Pin Depths:** Individual numeric values representing the depths of each of the 5 pins. I'll be using standard Schlage bitting depths, but these should be changeable.
*   **Label Text:** A string for the text embossed on the key handle.
*   **Text Depth:** A numeric value for how deeply the label text is embossed.
*   **Text Size:** A numeric value for the size of the font.
*   **Font Choice:** Allow a selection of a few standard font options (like Times, Arial, Helvetica, Courier, Futura).

The key should have a standard SC1 profile, including:

*   The overall key dimensions (length, width, thickness, and handle dimensions).
*   The proper pin spacing.
*   The angle of the cut channels.
*  The correct chamfer at the tip.
*   A small hole in the handle for a keyring.

I would like the ability to flip the key for printing. The actual bitting profile cut in the key should be designed to be printable with FDM (3D printing).

I'd appreciate if the design is robust, meaning that I can change all these values and it will still create a valid key design. The design shouldn't rely on hard-coded values as much as possible, and should use the parameters whenever possible.

I'm expecting this to include one main .scad file, with function and module definitions, and the main model creation in the last few lines. Please don't worry about using external libraries in this instance. Let me know if you need any more information.

[Assistant]
SC1_5_pin.scad:
```
// Schlage SC1 5 pin with label - by Chris Myers
// Based on Physical Keygen - by Nirav Patel <http://eclecti.cc>
//
// Generate a duplicate of a Schlage SC1 key by editing the last line of the file
// and entering in the key code of the lock.  If you don't know the key code,
// you can measure the key and compare the numbers at:
// http://web.archive.org/web/20050217020917fw_/http://dlaco.com/spacing/tips.htm
//
// This work is licensed under a Creative Commons Attribution 3.0 Unported License.

//1st number of key code (Handle)
a = 0;
//2nd number of key code
b = 0;
//3rd number of key code
c = 0;
//4th number of key code
d = 0;
//5th number of key code (Tip)
e = 0;
function mm(i) = i*25.4;

/* [Label] */
// will be embossed into the key
label = "Home";
//depth of the lettering
textDepth = 1;
// size of the font
textSize = 6;
// font to use
textFont = "Times"; // [Arial,Times,Helvetica,Courier,Futura]

module rounded(size, r) {
    union() {
        translate([r, 0, 0]) cube([size[0]-2*r, size[1], size[2]]);
        translate([0, r, 0]) cube([size[0], size[1]-2*r, size[2]]);
        translate([r, r, 0]) cylinder(h=size[2], r=r);
        translate([size[0]-r, r, 0]) cylinder(h=size[2], r=r);
        translate([r, size[1]-r, 0]) cylinder(h=size[2], r=r);
        translate([size[0]-r, size[1]-r, 0]) cylinder(h=size[2], r=r);
    }
}

module bit() {
    w = mm(1/4);
    difference() {
        translate([-w/2, 0, 0]) cube([w, mm(1), w]);
        translate([-mm(7/256), 0, 0]) rotate([0, 0, 135]) cube([w, w, w]);
         translate([mm(7/256), 0, 0]) rotate([0, 0, -45]) cube([w, w, w]);
    }
}

// Schlage SC1 5 pin key.  The measurements are mostly guesses based on reverse
// engineering some keys I have and some publicly available information.
module sc1(bits) {
    // You may need to adjust these to fit your specific printer settings
    thickness = mm(0.080);
    length = mm(17/16);
    width = mm(.335);

    shoulder = mm(.231);
    pin_spacing = mm(.156);
    depth_inc = mm(.015);

    // A fudge factor.  Printing with your average RepRap, the bottom layer is
    // going to be squeezed larger than you want. You can make the pins
    // go slighly deeper by a fudge amount to make up for it if you aren't
    // adjusting for it elsewhere like Skeinforge or in your firmware.
    // 0.5mm works well for me.
    fudge = 0.5;

    // Handle size
    h_l = mm(1);
    h_w = mm(1);
    h_d = mm(1/16);
    difference() {
        // blade and key handle
        union() {
            translate([-h_l, -h_w/2 + width/2, 0]) rounded([h_l, h_w, thickness], mm(1/4));
            intersection() {
                // cut a little off the tip to avoid going too long
                cube([length - mm(1/64), width, thickness]);
                translate([0, mm(1/4), thickness*3/4]) rotate([0, 90, 0]) cylinder(h=length, r=mm(1/4), $fn=64);
            }
        }

        // chamfer the tip
        translate([length, mm(1/8), 0]) {
            rotate([0, 0, 45]) cube([10, 10, thickness]);
            rotate([0, 0, 225]) cube([10, 10, thickness]);
        }

        // put in a hole for keychain use
        translate([-h_l + mm(3/16), width/2, 0]) cylinder(h=thickness, r=mm(1/8));

        // write text on Key
        translate([-h_l / 2 + mm(3/16)/2, width/2, 1-fudge])
        rotate([180,0,-90])
        linear_extrude(height = textDepth)
        text(label, size = textSize, font = textFont, valign="center", halign="center");

        // cut the channels in the key.  designed more for printability than accuracy
        union() {
            translate([-h_d, mm(9/64), thickness/2]) rotate([62, 0, 0]) cube([length + h_d, width, width]);
            translate([-h_d, mm(7/64), thickness/2]) rotate([55, 0, 0]) cube([length + h_d, width, width]);
            translate([-h_d, mm(7/64), thickness/2]) cube([length + h_d, mm(1/32), width]);
        }

        translate([-h_d, width - mm(9/64), 0]) cube([length + h_d, width - (width - mm(10/64)), thickness/2]);
        translate([-h_d, width - mm(9/64), thickness/2]) rotate([-110, 0, 0]) cube([length + h_d, width, thickness/2]);

        intersection() {
            translate([-h_d, mm(1/32), thickness/2]) rotate([-118, 0, 0]) cube([length + h_d, width, width]);
            translate([-h_d, mm(1/32), thickness/2]) rotate([-110, 0, 0]) cube([length + h_d, width, width]);
        }

        // Do the actual bitting
        for (b = [0:4]) {
            translate([shoulder + b*pin_spacing, width - bits[b]*depth_inc - fudge, 0]) bit();
        }
    }
}

// Flip the key over for easy printing

rotate([180, 0, 0]) sc1([a,b,c,d,e]);

```


[User]
I'm looking to create a parametric brace for mounting to either 2020 or 3030 aluminum extrusion profiles, specifically for use with a filler spool holder. I need the design to be flexible enough to accommodate different mounting angles and screw types.

Here’s what I need:

*   **Profile Type:** The brace should work with both 2020 and 3030 aluminum profiles. This will determine the slot width and depth, as well as the nut width used in the design. I'd like to be able to specify this via a variable.

*   **Profile Position:** The brace can be mounted either vertically or horizontally on the profile. This will affect the position of the screw hole and the orientation of the mounting studs. This also should be controlled via a variable.

*   **Rotation:** I'd need the brace to be rotatable around the mounting point. This angle should be variable and I need to be able to specify both positive and negative degrees, within a reasonable range like -60 to 60 degrees.

*   **Screw Type:** The design needs to support both flat-head and round-head screws. This should also be a parameter that I can control. The screw diameter should also be controllable, and the design should adjust the screw hole size and depth to accommodate each type, and make sure there's enough material around the screw head. For round-head screws, I need to ensure the head height is also taken into consideration. The screw head diameter should be a parameter, or if it's zero it should be automatically calculated. Same with round-head screw head height. The screw length also needs to be a parameter, and should be used to calculate if there is enough wall for the screw.

*   **Studs:** The brace should include studs that fit into the profile slots for secure mounting. The depth of these studs should be a parameter. Also, there should be some clearance between the studs and the profile slot. And also clearance for the nut.

*   **Clearance:** I need to be able to control the clearances around the screws and profile studs.

*   **Hook:** The brace design will have a hook that interfaces with a filler holder and will have the following measurements:
    *  hook_width=26;
    *  hook_neck_width=20;
    *  hook_thickness=4;
    *  hook_height=24+26/2;
    *   hook_wall_clearence=4;
    *   hook_clearence=0.3;

*   **Wall Thickness:** There needs to be a minimum wall thickness for structural integrity around the screw holes and in general. This should also be configurable.

*   **Chamfers:** Chamfers should be used on edges for easy printing.

I'm thinking the design will consist of one main module, and I want the output to be a single .scad file with all of the modules. I don't need to use any specialized libraries for this - just the standard OpenSCAD functions should be sufficient. The output should include comments.

I'd like to define the following parameters:

*   `hook_clearence` (float): Clearance for the hook interface. Default 0.3.
*   `brace_wall` (float): Wall thickness for the brace. Default 4.
*   `brace_angle` (int): Rotation angle of the brace in degrees. Default 0.
*   `profile_position` (string): Mounting orientation - "vertical" or "horizontal". Default "vertical".
*   `profile_type` (string): Profile type - "2020" or "3030". Default "2020".
*   `profile_stud_depth` (float): Depth of the profile mounting studs. Default 2.
*   `profile_clearence` (float): Clearance between studs and profile slot. Default 0.0.
*   `profile_nut_clearence` (float): Clearance around profile nuts. Default 0.3.
*   `screw_dia` (float): Screw diameter. Default 3.
*   `screw_head` (string): Screw head type - "flat" or "round". Default "flat".
*   `screw_head_dia` (float): Screw head diameter. Default 0 (auto).
*    `round_head_screw_head_height` (float): Round head screw head height. Default 0 (auto).
*   `screw_length` (float): Screw length. Default 8.
*   `screw_min_wall` (float): Minimum wall thickness around screw. Default 2.
*   `screw_clearence` (float): Clearance around screw hole. Default 0.2.
*    `$chamfer` (float): Default chamfer value. Default 0.6.

Can you generate that for me?

[Assistant]
filler-profile-brace.scad:
```
// Original - 0.4
hook_clearence=0.3;

brace_wall=4;
brace_angle=0; // [-60:60]
profile_position = "vertical"; // [vertical,horizontal]
profile_type = "2020"; // [2020,3030]
profile_stud_depth=2;
profile_clearence=0.0;
profile_nut_clearence=0.3;

screw_dia = 3;
screw_head = "flat"; // [flat,round]
// Screw head diameter, 0 for auto
screw_head_dia = 0;
// Round head screw head height, 0 for auto
round_head_screw_head_height = 0;
screw_length=8;
screw_min_wall = 2;
screw_clearence=0.2;

$chamfer=0.6;

/* [Hidden] */

// Filler holder hook measurements
hook_width=26;
hook_neck_width=20;
hook_thickness=4;
hook_height=24+26/2;
hook_wall_clearence=4;

$fn=32;

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

module chamfered_cube(size=10,chamfer=999999,center=false)
{
    sz = size[0]?size:[size,size,size];
    chamfer = chamfer!=999999?chamfer:($chamfer?$chamfer:0);
    translate(center?[0:0:0]:sz/2) {
        hull() {
            cube([sz.x,sz.y-2*chamfer,sz.z-2*chamfer],center=true);
            cube([sz.x-2*chamfer,sz.y-2*chamfer,sz.z],center=true);
            cube([sz.x-2*chamfer,sz.y,sz.z-2*chamfer],center=true);
        }
    }
}

module screw_hole_flathead(thickness,screw_dia,cap_dia,deep)
{
    translate([0,0,-1])
        cylinder(d=screw_dia,h=thickness+2);
    translate([0,0,deep])
        cylinder(d1=screw_dia,d2=cap_dia,h=(cap_dia-screw_dia)/2);
    translate([0,0,deep+(cap_dia-screw_dia)/2-0.001])
        cylinder(d=cap_dia,h=thickness-deep-(cap_dia-screw_dia)/2+0.001+1);
}

module brace(
    profile_type=profile_type,
    brace_angle=brace_angle,
    profile_position=profile_position,
    screw_dia=screw_dia)
{
    profile_size = profile_type == "2020" ? 20 : 30;
    profile_slot_width = profile_type == "2020" ? 6 : 8;
    profile_slot_depth = profile_type == "2020" ? 6 : 9;
    // T-Nut width
    profile_nut_width = profile_type == "2020" ? 10 : 16;
    min_base_width = 20;
    min_stud_length = 3;

    // Head height of a round head screw, usually equal to the thread diameter
    _round_head_screw_head_height = round_head_screw_head_height ?
        round_head_screw_head_height :
        screw_dia;
    // Screw head diameter, usually a bit less than twice the thread diameter
    _screw_head_dia = screw_head_dia ?
        screw_head_dia :
        2 * screw_dia;
    round_head_screw_wall = max(screw_min_wall,screw_length - profile_slot_depth + 0.5);
    flat_head_screw_head_height=(_screw_head_dia-screw_dia)/2+0.5;
    flat_head_screw_wall = max(screw_min_wall+flat_head_screw_head_height/2,flat_head_screw_head_height,screw_length-profile_slot_depth+0.5);
    back_wall = screw_head=="round" ?
        max(brace_wall,_round_head_screw_head_height+round_head_screw_wall) :
        max(brace_wall,flat_head_screw_wall);

    hook_wall=hook_wall_clearence-hook_clearence;
    _brace_angle=abs(brace_angle);
    vertical=profile_position=="vertical"?1:0;
    screw_vpos = vertical ?
        hook_clearence+hook_height-screw_dia-2*screw_clearence :
        brace_wall+hook_clearence+hook_height-profile_size/2;

    mirror([brace_angle<0?1:0,0,0]) rotate(180) difference() {
        xcenter=hook_width/2+hook_clearence+brace_wall;
        xtrans=2*xcenter*(1-cos(_brace_angle));
        basewidth=brace_angle==0 ?
            hook_width+2*hook_clearence+2*brace_wall :
            vertical ?
                min_base_width :
                max(profile_nut_width+2*min_stud_length,min_base_width);

        union() {
            translate([xtrans,back_wall-brace_wall,0])
                rotate(_brace_angle)
                    chamfered_cube([hook_width+2*hook_clearence+2*brace_wall,brace_wall+2*hook_clearence+hook_thickness+hook_wall,brace_wall+hook_clearence+hook_height]);
            hull() {
                translate([xtrans,back_wall-brace_wall,0])
                    rotate(_brace_angle)
                        chamfered_cube([hook_width+2*hook_clearence+2*brace_wall,2*$chamfer+0.001,brace_wall+hook_clearence+hook_height]);
                translate([xcenter-basewidth/2,0,0])
                    chamfered_cube([basewidth,2*$chamfer+0.001,brace_wall+hook_clearence+hook_height]);
            }
            hull() {
                translate([xtrans,back_wall-brace_wall,0])
                    rotate(_brace_angle)
                        translate([0,brace_wall+2*hook_clearence+hook_thickness+hook_wall-2*$chamfer,0])
                            chamfered_cube([hook_width+2*hook_clearence+2*brace_wall,2*$chamfer+0.001,brace_wall+hook_clearence+hook_height]);
                translate([xcenter-basewidth/2,0,0])
                    chamfered_cube([basewidth,2*$chamfer+0.001,brace_wall+hook_clearence+hook_height]);
            }

            // Profile studs
            if (vertical) {
                profile_stud_height=screw_vpos-profile_nut_width/2-profile_nut_clearence;
                translate([xcenter-profile_slot_width/2+profile_clearence,-profile_stud_depth,0])
                    chamfered_cube([profile_slot_width-2*profile_clearence,profile_stud_depth+2*$chamfer,profile_stud_height]);
            } else {
                translate([xcenter,0,0])
                    reflect([1,0,0])
                        translate([-basewidth/2,-profile_stud_depth,screw_vpos-(profile_slot_width-2*profile_clearence)/2])
                            chamfered_cube([(basewidth-profile_nut_width)/2-profile_nut_clearence,profile_stud_depth+2*$chamfer,profile_slot_width-2*profile_clearence]);
            }
        }

        translate([xtrans,back_wall-brace_wall,0]) {
            rotate(_brace_angle) {
                translate([brace_wall,brace_wall,brace_wall]) {
                    translate([hook_width/2+hook_clearence,0,hook_width/2])
                        rotate([-90,0,0])
                            cylinder(d=hook_width+2*hook_clearence,h=hook_thickness+2*hook_clearence);
                    translate([0,0,hook_width/2])
                        cube([hook_width+2*hook_clearence,hook_thickness+2*hook_clearence,hook_height-hook_width/2+1]);
                }

                translate([brace_wall+(hook_width-hook_neck_width)/2,brace_wall+2*hook_clearence+hook_thickness-1,brace_wall+hook_width/2])
                    cube([hook_neck_width+2*hook_clearence,hook_wall+2,hook_height-hook_width/2+hook_clearence+1]);
            }
        }

        // Screw hole
        if (screw_head == "round") {
            translate([xcenter,0,screw_vpos]) {
                rotate([-90,0,0]) {
                    translate([0,0,-1])
                        cylinder(d=screw_dia+2*screw_clearence,h=(back_wall+hook_thickness+2*hook_clearence+hook_wall)*cos(_brace_angle)+(2*brace_wall+hook_width+2*hook_clearence)*sin(_brace_angle)+1);
                    translate([0,0,round_head_screw_wall])
                        cylinder(d=_screw_head_dia+2*screw_clearence,h=(back_wall+hook_thickness+2*hook_clearence+hook_wall)*cos(_brace_angle)+(2*brace_wall+hook_width+2*hook_clearence)*sin(_brace_angle)-round_head_screw_wall);
                }
            }
        } else {
            translate([xcenter,0,screw_vpos])
                rotate([-90,0,0])
                    screw_hole_flathead((back_wall+hook_thickness+2*hook_clearence+hook_wall)*cos(_brace_angle)+(2*brace_wall+hook_width+2*hook_clearence)*sin(_brace_angle)+1,screw_dia+2*screw_clearence,_screw_head_dia+2*screw_clearence,flat_head_screw_wall-flat_head_screw_head_height/*3-screw_dia/2-screw_clearence*/);
        }
    }
}

brace();

```


[User]
Hi, I'm looking for an OpenSCAD design for a dice box, and I'd like it to be parametric so I can customize it for different dice sets. I'm envisioning a box with a base and a lid, and the base should have hexagonal pockets to hold the dice. I'd like to be able to specify the number of pockets in both the X and Y directions. Also, I want the option to include holes for magnets to keep the lid closed, and I need to be able to specify the magnet diameter and thickness.

I was thinking the layout should be offset - like the example code I've seen, rows with an odd number of pockets and rows with an even number of pockets should be offset from each other. It would be great to specify the size of each hexagon, the spacing between hexagons, the depth of the pockets, and the overall box wall thickness, base thickness, and lid thickness.

To make it easier to open, could the lid have a thumb cutout? I would like to control the clearance between the lid and base with another parameter. I'd also like a toggle to control whether there are also holes in the lid, corresponding to the holes in the base.

I do not have any existing code to start from. I want to be able to adjust the size of everything easily, and I want to make sure it has a decent clearance and tolerance, as well as to be easy to print on my FDM printer. And I need to be able to generate both the lid and base separately.

I'm also hoping that the design will have rounded corners for both aesthetic and structural reasons. I'd like to specify a radius for those.

Could you please make sure to use the BOSL2 library?

[Assistant]
dice_box.scad:
```
part = 0; // [0:base,1:lid]

// Bigger is smoother
corner_smoothness = 30;
// Holes for magnets?
magnets = 1; // [0:No,1:Yes]
// Magnet hole diameter (mm)
magnet_diameter = 10.11;
// Magnet hole thicknes (mm)
magnet_thickness = 3;
// Size across the flat sides of the hole (mm)
hole_diameter = 22;
// Depth of hole in base (mm)
hole_depth = 7;
// Holes in lid?
holes_in_lid = 1; // [0:No,1:Yes]
// Clearance between lid and base (mm)
lid_clearance = 0.75;
// Add thumb cutout to lid
thumb_cutout = 1; // [0:No,1:Yes]
// Number of holes in X direction
x_num = 3; //[1, 3, 5, 7, 9]
// Number of holes in Y direction
y_num = 5; //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

/* [Hidden] */
$fn = corner_smoothness;
hex_flats_diameter = hole_diameter;
hex_points_diameter = hex_flats_diameter * (2 / sqrt(3));
hex_side_length = hex_flats_diameter / sqrt(3);
hex_spacing = 2;
hex_corner_radius = 3;
base_hex_height = hole_depth;
base_wall_thickness = 2;
base_bottom_thickness = 3;
base_corner_radius = 2;
base_height = base_bottom_thickness + base_hex_height;
lid_hex_height = hex_flats_diameter - base_hex_height;
lid_wall_thickness = base_wall_thickness;
lid_top_thickness = base_bottom_thickness;
lid_corner_radius = base_corner_radius;
lid_height = lid_top_thickness + lid_hex_height;
magnet_offset = base_wall_thickness + hex_side_length / 2 - hex_spacing / 2;
thumb_grip_width = 20;

x_long_num = ceil(x_num / 2);
x_short_num = floor(x_num / 2);

base_width = (hex_points_diameter * x_long_num) + (hex_side_length * x_short_num)  + (hex_spacing * (x_num - 1)) + (base_wall_thickness * 2);
base_length = (hex_flats_diameter * y_num) + (hex_spacing * (y_num - 1)) + (base_wall_thickness * 2);
lid_width = base_width + lid_clearance * 2 + (base_wall_thickness * 2);
lid_length = base_length + lid_clearance * 2 + (base_wall_thickness * 2);

echo("hex flats diameter", hex_flats_diameter);
echo("hex points diameter", hex_points_diameter);
echo("hex side length", hex_side_length);
echo("base width", base_width);
echo("base length", base_length);
echo("base height", base_height);
echo("lid width", lid_width);
echo("lid length", lid_length);
echo("lid height", lid_height);
echo("magnet offset", magnet_offset);
echo("x long", x_long_num);
echo("x short", x_short_num);

if (part == 0)
    {
    make_base();
    }
else
    {
    make_lid();
    }

module make_base()
    {
    difference ()
        {
        // Reposition back to [0, 0, 0]
        translate([((hex_points_diameter / 2) + base_wall_thickness), ((hex_flats_diameter / 2) + base_wall_thickness), 0])
            {
            difference()
                {
                // Combine holes with base
                translate([((hex_points_diameter / 2) + base_wall_thickness) * -1, ((hex_flats_diameter / 2) + base_wall_thickness) * -1, 0]) base(base_width, base_length, base_bottom_thickness + base_hex_height);

                for (x = [0:x_num - 1])
                    {
                    echo("x", x);
                    echo("rem", rem);
                    rem = x % 2;
                    if (rem == 0)
                        // long row of hex holes
                        for (y = [0:y_num - 1] )
                            {
                            translate([(hex_side_length + hex_points_diameter) * (x/2) + (hex_spacing * x), y * (hex_flats_diameter + hex_spacing), base_bottom_thickness]) hex(base_hex_height);
                            }
                    else
                        // short row of hex holes
                        for (y = [0:(y_num - 2)] )
                            {
                            translate([((hex_points_diameter + hex_side_length) * (x/2)) + (hex_spacing * x), (y * (hex_flats_diameter + hex_spacing)) + (hex_flats_diameter / 2) + hex_spacing / 2, base_bottom_thickness]) hex(base_hex_height);
                            }
                    }
                }
            }

        // Magnet holes
        if (magnets)
            {
            for (x = [0:x_num - 1])
                {
                echo("x", x);
                echo("rem", rem);
                rem = x % 2;
                if (rem != 0)
                    // magnets in short row
                    for (y = [0:(y_num - 2)] )
                        {
                        translate([(hex_points_diameter + hex_side_length) * (x/2) + (hex_spacing * (x + 1)) + hex_side_length, magnet_offset, base_height - magnet_thickness]) cylinder(r=magnet_diameter / 2, h=magnet_thickness + 10);
                        translate([(hex_points_diameter + hex_side_length) * (x/2) + (hex_spacing * (x + 1)) + hex_side_length, base_length - magnet_offset , base_height - magnet_thickness]) cylinder(r=magnet_diameter / 2, h=magnet_thickness + 10);
                        }
                }
            }
        }
    }

module make_lid()
    {
    difference ()
        {
        // Reposition back to [0, 0, 0]
        translate([((hex_points_diameter / 2) + lid_wall_thickness + lid_clearance  + base_wall_thickness), ((hex_flats_diameter / 2) + lid_wall_thickness + lid_clearance  + base_wall_thickness), 0])
            {
            difference()
                {
                // Combine holes with base
                translate([((hex_points_diameter / 2) + lid_wall_thickness + lid_clearance + base_wall_thickness) * -1, ((hex_flats_diameter / 2) + lid_wall_thickness + lid_clearance  + base_wall_thickness) * -1, 0]) lid(lid_width, lid_length, lid_height + base_height - base_bottom_thickness);
                if (holes_in_lid)
                    {
                    for (x = [0:x_num - 1])
                        {
                        rem = x % 2;
                        if (rem == 0)
                            // long row of hex holes
                            for (y = [0:(y_num -1)] )
                                {
                                //translate([0, x * (hex_flats_diameter + hex_spacing), lid_top_thickness]) hex(lid_hex_height);
                                translate([(hex_side_length + hex_points_diameter) * (x/2) + (hex_spacing * x), y * (hex_flats_diameter + hex_spacing), lid_top_thickness]) hex(lid_hex_height);
                                }
                        else
                            // short row of hex holes
                            for (y = [0:(y_num - 2)] )
                                {
                                translate([((hex_points_diameter + hex_side_length) * (x/2)) + (hex_spacing * x), (y * (hex_flats_diameter + hex_spacing)) + (hex_flats_diameter / 2) + hex_spacing / 2, lid_top_thickness]) hex(lid_hex_height);
                               }
                            }
                        }
                    }
                }

            // Magnet holes
            if (magnets)
                {
                for (x = [0:x_num - 1])
                    {
                    echo("x", x);
                    echo("rem", rem);
                    rem = x % 2;
                    if (rem != 0)
                        // magnets in short row
                        for (y = [0:(y_num - 2)] )
                            {
                            translate([lid_clearance + base_wall_thickness + (hex_points_diameter + hex_side_length) * (x/2) + (hex_spacing * (x + 1)) + hex_side_length, + lid_wall_thickness + lid_clearance + magnet_offset, lid_height - magnet_thickness]) cylinder(r=magnet_diameter / 2, h=magnet_thickness + 1);
                            translate([lid_clearance + base_wall_thickness + (hex_points_diameter + hex_side_length) * (x/2) + (hex_spacing * (x + 1)) + hex_side_length, lid_length - lid_wall_thickness - lid_clearance - magnet_offset , lid_height - magnet_thickness]) cylinder(r=magnet_diameter / 2, h=magnet_thickness + 1);
                            }
                        }
                    }

 //           // Magnet holes
 //           if (magnets)
 //               {
//                translate([lid_width / 2, lid_wall_thickness + lid_clearance + magnet_offset, lid_height - magnet_thickness]) cylinder(r=magnet_diameter / 2, h=magnet_thickness + 1);
//
//                translate([lid_width / 2, lid_length - lid_wall_thickness - lid_clearance - magnet_offset, lid_height - magnet_thickness]) cylinder(r=magnet_diameter / 2, h=magnet_thickness + 1);
//                }

             // Subtrct base
             translate([lid_wall_thickness, lid_wall_thickness, lid_hex_height + lid_top_thickness]) base_cutout(base_width + lid_clearance * 2, base_length + lid_clearance * 2, base_bottom_thickness + base_hex_height + 1);

             // Thumb cutout
 //            translate([-1, lid_length / 2, lid_height + base_height + 1]) thumb_cutout();
            if (thumb_cutout)
                {
                translate([0, lid_length / 2, lid_height + base_height]) thumb_grip();
                translate([lid_width, lid_length / 2, lid_height + base_height]) thumb_grip();
                }
            }
    }

// Hex shape with rounded corners
module hex(h) {
    resize([hex_points_diameter, hex_flats_diameter, h + 1]) hull() {
        for ( i = [0:5] ) {
            rotate( i*360/6, [0, 0, 1])
            translate( [(hex_corner_radius * 2) + (hex_points_diameter /2 ), 0, 0] ) cylinder(r=hex_corner_radius,h=1);;
        }
    }
}

// base with rounded corners
module base(x, y, z)
    {
    union();
        {
        resize([x, y, z])
            {
            hull()
                {
                translate([ base_corner_radius, base_corner_radius, 0]) cylinder(r = base_corner_radius, h = z);

                translate([ x - base_corner_radius, base_corner_radius, 0]) cylinder(r = base_corner_radius, h = z);

                translate([base_corner_radius, y - base_corner_radius, 0]) cylinder(r = base_corner_radius, h = z);

                translate([ x - base_corner_radius, y -base_corner_radius, 0]) cylinder(r = base_corner_radius, h = z);
                }
            }
        resize([lid_width, lid_length, base_bottom_thickness])
            {
            difference()
                {
                hull()
                    {
                    translate([ base_corner_radius - lid_wall_thickness - lid_clearance, base_corner_radius  - lid_wall_thickness - lid_clearance, base_corner_radius])
                        union()
                            {
                            sphere(r = base_corner_radius);
                            cylinder(r = base_corner_radius, h = base_corner_radius);
                            }
                    translate([ x - base_corner_radius  + lid_wall_thickness + lid_clearance, base_corner_radius - lid_wall_thickness - lid_clearance, base_corner_radius])
                        union()
                            {
                            sphere(r = base_corner_radius);
                            cylinder(r = base_corner_radius, h = base_corner_radius);
                            }
                    translate([base_corner_radius - lid_wall_thickness - lid_clearance, y - base_corner_radius + lid_wall_thickness + lid_clearance, base_corner_radius])
                        union()
                            {
                            sphere(r = base_corner_radius);
                            cylinder(r = base_corner_radius, h = base_corner_radius);
                            }
                    translate([ x - base_corner_radius + lid_wall_thickness + lid_clearance, y - base_corner_radius + lid_wall_thickness + lid_clearance, base_corner_radius])
                        union()
                            {
                            sphere(r = base_corner_radius);
                            cylinder(r = base_corner_radius, h =  base_corner_radius);
                            }
                    }
                    translate([(lid_wall_thickness + lid_clearance + 1) * -1, (lid_wall_thickness + lid_clearance + 1) * -1, base_bottom_thickness]) cube([base_width + (lid_wall_thickness * 2) + (lid_clearance  * 2) + 2, base_length + (lid_wall_thickness * 2) + (lid_clearance * 2) + 2, base_bottom_thickness]);
                }
            }
        }
    }

module base_cutout(x, y, z)
    {
    resize([x, y, z])
        {
        hull()
            {
            translate([ base_corner_radius, base_corner_radius, 0]) cylinder(r = base_corner_radius, h = z);

            translate([ x - base_corner_radius, base_corner_radius, 0]) cylinder(r = base_corner_radius, h = z);

            translate([base_corner_radius, y - base_corner_radius, 0]) cylinder(r = base_corner_radius, h = z);

            translate([ x - base_corner_radius, y -base_corner_radius, 0]) cylinder(r = base_corner_radius, h = z);
            }
        }
    }

// lid with rounded corners
module lid(x, y, z)
    {
    resize([x, y, z])
        {
        hull()
            {
            translate([ lid_corner_radius, lid_corner_radius, lid_corner_radius]) corner(lid_corner_radius, z);

            translate([ x - lid_corner_radius, lid_corner_radius, lid_corner_radius]) corner(lid_corner_radius, z);

            translate([lid_corner_radius, y - lid_corner_radius, lid_corner_radius]) corner(lid_corner_radius, z);

            translate([ x - lid_corner_radius, y - lid_corner_radius, lid_corner_radius]) corner(lid_corner_radius, z);
            }
        }
    }

module corner(radius, height)
    {
    union()
        {
        sphere(r = radius);
        cylinder(r = radius, h = height);
        }
    }

module thumb_grip()
    {
    resize([lid_wall_thickness, thumb_grip_width, lid_height * 2]) sphere(r = thumb_grip_width / 2);
    }

```


[User]
I'd like to design a towel hook that I can customize. It should have a base that mounts to the wall, and then a hook that extends out. The main feature I need is to be able to control the width of the hook, so I can match it to different widths of double-sided tape I might use to attach it.

Specifically, I envision a design with:

*   A rectangular base, say 45mm long and 5mm thick.
*   The hook portion should start 50mm away from one end of the base, along the long dimension.
*   The hook should initially have a small vertical section, say 5mm tall.
*   Then, the main hook arm should extend outward and upward at a 135-degree angle from horizontal, for 30mm. This section will also have a thickness of 5mm.
*   At the end of the angled section should be a rounded end that's a half-cylinder that extends in the same direction as the base to act as a stopper on the hook.
*   At the start of the hook arm, where it meets the base, there should be a supporting section for strength. It should start with a 5mm diameter cylinder, connected to the hook base, that extends in the opposite direction of the main hook arm.
*   This supporting section should include a cutout to provide a gap at the base of the hook. This cutout should be like a rectangle that is 3.8mm x 8mm in the horizontal and vertical, and should start 2mm away from the center of the cylinder.
*   There should also be a cylinder cutout on the supporting section to make the shape look and feel more organic. This cutout should be 5mm in diameter, be 7.5mm away from the first cylinder.

The key parameter I'm looking for is the `width`, which will apply to all the rectangular parts and also the height of the cylinders. It should be adjustable between 5mm and 100mm.

Could you generate the OpenSCAD code for this? I'm not using any specific libraries, so just base OpenSCAD is fine.

[Assistant]
towel_hook_1.scad:
```
// Simple Towel Hook, customizable width
//
// Thought it would be nice if the hook could made
// as wide as the 2 sided tape used to attach it
// Default width, 19mm
//

// Desired width in millimeters
width=19;  // [5:100]

cube ([45, 5 , width]);
translate([50,0,0])
{
    translate ([0,5,0])
    {
        rotate([0,0,135])
        {
            cube ([30, 5 , width]);
            translate([30,5 ,0])
            {
                cylinder(h=width,r=5);
            }
        }
        translate([-5,0,0])
        {
            cylinder(h=width,r=5);
            rotate([0,0,163.5])
            {
                difference()
                {
                    translate([2 ,-4,])
                    {
                        cube([3.8,8,width]);
                    };
                    translate([7.5,0,0])
                    {
                        $fn=24;
                        cylinder(h=width,d=5);
                    }

                }

            }

        }
    }
}

```