[User]
I'm looking to design a set of 3D printed drift skates, and I'd like to use OpenSCAD for this. I have a pretty clear idea of what I want, based on an existing design I found, but I need help parameterizing it and getting the OpenSCAD code right.

The core of the design involves three main parts: a deck, a truck that holds the wheels, and bearing adaptors. I need to be able to generate each of these independently.

First off, the wheels. I'm aiming for compatibility with two different wheel brands: JMK Performance and TwoLions. The key difference is that JMK wheels have a bearing-to-bearing distance of 26.5mm, while TwoLions are 24.5mm. The wheels themselves are 73mm in outer diameter, 20mm inner diameter, with a 25mm edge radius offset by 8mm and 43mm width. I need to be able to adjust the axle distance as well - let's call that `modifyAxleDistance`, and it should be able to take values between -20 and 20.

The trucks should be able to be printed in either a solid or "light" mode. Light mode means there will be a hollowed-out interior. The truck design includes a well for the wheel that has a 3mm wheel gap, 10mm wheel shell, and is attached to the deck. The height of the truck is dependent on the wheel diameter. It also has a flying buttress design to mount the truck to the deck. I'll need to specify a bolt pattern for mounting the truck, using M4 or M5 bolts.

The deck has an overall width of 135mm and height of 160mm. It has a 15 degree angle, edge radius of 40mm, edge thickness of 5mm, and a main thickness which is dependent on the size of the deck bolt head (using either M4 or M5 bolts). The deck needs to be mountable to the trucks using either a 4-bolt pattern. Bolt distance is 42mm, but there is a second 4-bolt pattern using 51mm distance and 78.2mm width. The deck should be able to be generated independently of the truck, and there should be a laser cut version that doesn't have the mounting holes.

Finally, the bearing adaptors will need to fit within the wheels using the bearing to bearing distance based on the wheel brand. They should have an outer diameter of 22mm on the large end and 13mm on the small end, with the inner diameter matching that of the bearing shell, 8.4mm. The width of the bearing adaptor should depend on the brand.

I'm planning on including several variables that allow for adjustment of size and print quality.  A `qualitySelection` parameter (1-100) to control the model's polygon count, as well as variables like bolt head radius, wall thicknesses, and ratios that determine how much the truck extends around the wheel.

I think that covers the basics.  I need to be able to produce .stl files for each part, with the ability to easily adjust parameters like wheel brand, bolt size, and the amount of material used in the truck (i.e. light vs solid).  Could you generate the OpenSCAD code for this? Please use the MCAD library.

[Assistant]
openfreeskate.scad:
```
// Lower quality gives faster compile times and a more polygonal shape. Higher qualit adds more smoothly rounded wheel wells.
qualitySelection = 20;  // [1:100]
// Increase or decrease the axle to axle distance between your wheels.
modifyAxleDistance = 0; // [-20:20]
buildTarget = "printed_deck"; // [truck, printed_deck, lazer_cut_deck, bearing_adaptor]
// Trucks/Decks in general are compatible with any brand. Bearing adaptors change size with brand.
wheelBrand = "JMKPerformance"; // [JMKPerformance, TwoLions]
deckBoltSize = "M5"; // [M4, M5]
// Using the "light" option hollows out the interior of the truck. Using this option will use less filament, but only if your printer can print the midsection wihout supports.
light = true;
module build(target){
    rotate([0,-90,0]) {
        if (target == "truck") {
            truck(light);
        } else if (target == "printed_deck") {
            deck(true);
        }
    }
    if (target == "bearing_adaptor") {
        bearing_adaptor();
    } else if (target == "lazer_cut_deck") {
            lazercutdeck(false);
    }
}

build(buildTarget);

 $quality = qualitySelection/100; // Quality scale 0 to 1. 1 Takes a lot of CPU/RAM to render for now.
 $fn = round(100*$quality);
    //wheel info here
    od=73;
    id=20;
    edge_r=25;
    edge_r_offset=8;
    w=43;
    bearing_to_bearing = ( wheelBrand == "JMKPerformance" ) ? 26.5 : 24.5; // Change this to 26.5 for JMK Performance wheels. Twolions seem to be 24.5.

    //non-wheel
    bolt_head_radius=8;
    wall_thickness=21;
    bolt_wall_thickness=8;
    wheel_gap=3;
    whell_shell_width=10;
    added_wheel_distance=20+modifyAxleDistance;
    wheel_coverage_ratio=.9;
    wheel_coverage_offset=(od/pow(wheel_coverage_ratio,1.55))/8;
    truck_height=wheel_coverage_ratio*od;

   wheel_hull_shrink_ratio=.8;

    bearing_shell_od=22;
    bearing_shell_id=8.4;
    bearing_shell_w=8;

    deck_bolt_size = deckBoltSize; // M4 vs M5 for now.

    use_m4 = deck_bolt_size == "M4";
    use_m5 = deck_bolt_size == "M5";

    assert(use_m4 || use_m5);

    deck_bolt_head_bore_h = use_m4 ? 4 : use_m5 ? 5 : 0;
    deck_bolt_head_bore_r= use_m4 ? 3.5 : use_m5 ? 4.3 : 0; //4.5
    deck_bolt_total_h= use_m4 ? 19.7 : use_m5 ? 20.8 : 0;
    deck_bolt_total_r= use_m4 ? 2 : use_m5 ? 2.5 : 0; //3
    deck_bolt_nut_bore_r= use_m4 ? 4 : use_m5 ? 4.6 : 0; //5.5
    deck_bolt_nut_bore_h= use_m4 ? 3 : use_m5 ? 3.8 : 0;

    deck_bolt_nut_bore_translation=deck_bolt_head_bore_h+10;

    deck_bolt_nut_bore_extra_h=40; //actual h is 3 or 3.8, but this is to go through the whole truck

    deck_w=135;
    deck_h=160;
    deck_angle=15;
    deck_edge_r=40;
    deck_thickness_edge=5;
    deck_thickness=deck_bolt_head_bore_h+5;

    bolt_distance=42;
    bolt_distance_from_center=bolt_distance/2; // JMK bolt distance is 63.7/2 based off https://www.thingiverse.com/thing:4152124.
    // 4 bolt measurements from https://www.thingiverse.com/thing:4152124. bolt distance is 51/2. distance in the horizontal is 78.2 total.

    four_bolt_distance = 51;
    four_bolt_distance_from_center = four_bolt_distance/2;
    four_bolt_width = 78.2;
    four_bolt_width_from_center = four_bolt_width/2;

    platform_interface_size=[w+2*wall_thickness+2*wheel_gap,od+added_wheel_distance+1.7,deck_thickness];
        truck_interface_size=[w+2*wall_thickness+2*wheel_gap,od+added_wheel_distance+40,20];

    truck_interface_thicknes=10;

    bearing_adaptor_od_big = 22;
    bearing_adaptor_od_small = 13;
    bearing_adaptor_width = (w + 2*wheel_gap -  bearing_to_bearing)/2;

module bearing_mock (od = 22, id=8, w=7) {
    difference(){
        cylinder(r=od/2, h=w);
        cylinder(r=id/2, h=w);
    }
}

module bearing_adaptor(){
    difference(){
        cylinder(r1=bearing_adaptor_od_big/2,r2=bearing_adaptor_od_small/2,h=bearing_adaptor_width);
        cylinder(r=bearing_shell_id/2, h=bearing_adaptor_width);
    }
}

module bearing_washer(width){
    difference(){
        cylinder(r=bearing_adaptor_od_big/2,h=width);
        cylinder(r=bearing_shell_id/2, h=width);
    }
}

module inwheel_spacer(width){
    difference(){
        union(){
            cylinder(r1=bearing_shell_id/2 + 2,r2=bearing_shell_id/2 + 3,h=width/2);
            translate([0,0,width/2])cylinder(r1=bearing_shell_id/2 + 3,r2=bearing_shell_id/2 + 2,h=width/2);
        }
        cylinder(r=(bearing_shell_id/2), h=width);
    }
}

module edge(od,edge_r,low_poly=false){
    translate ([0,0,edge_r])
    rotate_extrude(convexity = 10, $fn = low_poly?13:round(500*$quality))
    intersection(){
        translate([0,-(od)/2])
            square([od/2,od]);
        translate([od/2-edge_r, 0, 0])
            circle(r = edge_r);
    }
}

module wheel_shell(od,w,edge_r,edge_r_offset,low_poly=false){
    intersection(){
        cylinder(r=od/2, h=w);
        hull(){
            translate ([0,0,-edge_r_offset])edge(od,edge_r,low_poly);
            translate ([0,0,edge_r_offset+w-2*edge_r]) edge(od,edge_r,low_poly);
        }
    }
}

module wheel(){
    difference(){
        wheel_shell(od,w,edge_r,edge_r_offset);
        bearing_mock (od = bearing_shell_od, id=bearing_shell_id, w=bearing_shell_w) ;
        translate ([0,0,w-bearing_shell_w])
            bearing_mock (od = bearing_shell_od, id=bearing_shell_id, w=bearing_shell_w) ;
        cylinder(r=id/2, h=w);
    }
}

module wheels_for_hulling(){
    translate([0,-(od+wheel_gap*2+added_wheel_distance)/2])
    translate([0,0,wall_thickness+wheel_gap])
        wheel_shell(od*wheel_hull_shrink_ratio,w,edge_r,edge_r_offset,true);

    translate([0,(od+wheel_gap*2+added_wheel_distance)/2])
    translate([0,0,wall_thickness+wheel_gap])
        wheel_shell(od*wheel_hull_shrink_ratio,w,edge_r,edge_r_offset,true);
}

module tire(){

    difference(){
        wheel_shell(od,w,edge_r,edge_r_offset);
        bearing_mock (od = bearing_shell_od, id=bearing_shell_id, w=bearing_shell_w);
        translate ([0,0,w-bearing_shell_w])
            bearing_mock (od = bearing_shell_od, id=bearing_shell_id, w=bearing_shell_w);
        cylinder(r=(od*3/4)/2, h=w);
    }
}

module truck_half() {
    translate([-wheel_coverage_offset,wheel_coverage_offset,0])
        cylinder(r=(truck_height/2), h=w+2*wall_thickness+2*wheel_gap, $fn=6);
}

module well(){
        translate([0,0,wall_thickness])
            wheel_shell(od+wheel_gap*2,w+2*wheel_gap,10,0);
        cylinder(r=bearing_shell_id/2, h=w+2*wheel_gap+2*wall_thickness);
        cylinder(r=bolt_head_radius, h=wall_thickness-bolt_wall_thickness);
        translate([0,0,w+2*wheel_gap+2*wall_thickness-(wall_thickness-bolt_wall_thickness)])
            cylinder(r=bolt_head_radius, h=wall_thickness-bolt_wall_thickness);
}

module well_shell(){
    wheel_shell(od+wheel_gap*2+whell_shell_width,w+2*wheel_gap+2*wall_thickness,edge_r,edge_r_offset,true);
}

module flying_butrous(hole_scale_z,hole_scale_y=1.3, side_hole_scale=1.5){
    module quarter_flying_butrous(){
        hull(){
            translate([0,0])
                cube([truck_interface_size[0]/2,truck_interface_size[1]*0.25,truck_interface_size[2]*0.5],center=false);
            translate([0,bolt_distance_from_center-10])
                cube([truck_interface_size[0]*0.5,truck_interface_size[1]*0.5-bolt_distance_from_center,truck_interface_size[2]],center=false);
        }
    }

    difference(){
        union(){
            mirror([0,0,0])
                quarter_flying_butrous();
            mirror([0,1,0])
                quarter_flying_butrous();
            mirror([1,0,0])
            mirror([0,1,0])
                quarter_flying_butrous();
            mirror([1,0,0])
                quarter_flying_butrous();
        }
        sides=12;

        scale([hole_scale_z,hole_scale_y,1])
        rotate([0,0,180/sides])
            cylinder(r=bolt_distance_from_center-9,h=100,$fn=sides);

        scale([1,side_hole_scale,1])
        translate([0,0,(truck_interface_size[2]/2+bolt_distance_from_center)])
        rotate([0,90,0])
            cylinder(r=bolt_distance_from_center,h=100,$fn=14,center=true);

    }
}

module truck(lightweight = true){
    module outline(){
        hull(){
            translate([0,-(od+wheel_gap*2+added_wheel_distance)/2])
                truck_half();
            mirror([0,1,0])
            translate([0,-(od+wheel_gap*2+added_wheel_distance)/2])
                truck_half();
        }
    }

    intersection(){
        difference(){
            union(){
                intersection(){
                outline();
                    union(){
                        translate([0,-(od+wheel_gap*2+added_wheel_distance)/2])
                            well_shell();
                        mirror([0,1,0])
                        translate([0,-(od+wheel_gap*2+added_wheel_distance)/2])
                            well_shell();

                        translate([-40,0,truck_interface_size[0]/2])
                        rotate([0,-90,0])
                        mirror([0,0,1])
                            flying_butrous(3.4,1.8);

                        translate([23,0,truck_interface_size[0]/2])
                        rotate([0,-90,0]) {
                            flying_butrous(1.7,1.3,0.3);
                    //        //this adds a plate on the bottom for grinding
                    //        translate(-[truck_interface_size[0]/2,truck_interface_size[1]/2])cube([truck_interface_size[0],truck_interface_size[1],truck_interface_size[2]/2]);
                        }
                    }
                }
            }

            if (lightweight) {
                hull_od = 45;
                hull_translate = -od/2 -wheel_gap + hull_od/2+truck_interface_thicknes;
                hull_edge_r = edge_r;
                hull_edge_offset = edge_r_offset/2;
                union(){
                    difference(){
                        hull(){
                            translate([hull_translate,-(od+wheel_gap*2+added_wheel_distance)/2,wall_thickness])
                                wheel_shell(hull_od,w+wheel_gap*2,hull_edge_r,hull_edge_offset);
                            mirror([0,1,0])
                            translate([hull_translate,-(od+wheel_gap*2+added_wheel_distance)/2,wall_thickness])
                                wheel_shell(hull_od,w+wheel_gap*2,hull_edge_r,hull_edge_offset);
                        }
                        // HACKITY HACK DON'T TALK BACK
                        // Clean up these magic numbers.
                        translate([-15,-100,0])
                            cube([100,200,400]);
                    }
                    difference(){
                        {
                            intersect_amount=18;
                            translate([-15,0,wall_thickness+wheel_gap+w/2])
                            scale([intersect_amount/9.0,1,(w/2+wheel_gap)/(w/2+wheel_gap-intersect_amount)])
                            intersection(){
                                translate([0,0,intersect_amount])
                                rotate([90,0,0])
                                    cylinder(h=200, r=(w/2+wheel_gap),center=true,$fn=40);

                                translate([0,0,-intersect_amount])
                                rotate([90,0,0])
                                    cylinder(h=200, r=(w/2+wheel_gap),center=true,$fn=40);
                            }
                        }
                        mirror([1,0,0])
                        translate([16,-100,0])
                            cube([100,200,400]);

                        translate([18,-100,0])
                            cube([100,200,400]);
                    }
                }
            }

            translate([0,-(od+wheel_gap*2+added_wheel_distance)/2])
                well();
            mirror([0,1,0])
            translate([0,-(od+wheel_gap*2+added_wheel_distance)/2])
                well();

            deck(false);
            four_deckbolts();
            run = 40 + bolt_head_radius;
            rise = wall_thickness-bolt_wall_thickness;
            slope = rise/run;
            slope1 = slope/1.6;
            slope2 = 2*slope;
            length = 100;
            anglelength = 20;

            height_offset=5+deck_bolt_head_bore_h/3;
            shift_to_deck= 40;

            translate([0,100])
            rotate([90])
            linear_extrude(height=200)
                polygon( points=[[-shift_to_deck+height_offset,0],[length-shift_to_deck,0],[length-shift_to_deck,length*slope1],[anglelength-shift_to_deck,anglelength*slope2]] );

            mirror([0,0,1])
            translate([0,100,-(w+2*wheel_gap+2*wall_thickness)])
            rotate([90])
            linear_extrude(height=200)
                polygon( points=[[-shift_to_deck+height_offset,0],[length-shift_to_deck,0],[length-shift_to_deck,length*slope1],[anglelength-shift_to_deck,anglelength*slope2]] );
        }

        hull(){
            deck(false,false,true);
            wheels_for_hulling();
        }
        minimum_necessary_area_for_strength();
    }
}

module unmirrored_deck(shrunk = false){
    trans_x=deck_h/2-deck_edge_r;
    trans_y=shrunk ? deck_w/3-deck_edge_r : deck_w/2-deck_edge_r;
    hull(){
        translate([0,0,deck_thickness/2])
        rotate([0,0,deck_angle])
            cube(platform_interface_size,center=true);
        translate([trans_x,trans_y])
            cylinder(r=deck_edge_r,h=deck_thickness_edge);
        translate([-trans_x,trans_y])
            cylinder(r=deck_edge_r,h=deck_thickness_edge);
        translate([-trans_x,-trans_y])
            cylinder(r=deck_edge_r,h=deck_thickness_edge);
        translate([trans_x,-trans_y])
            cylinder(r=deck_edge_r,h=deck_thickness_edge);
        }
    }

module deck(add_bore=true,add_angle=true,shrunk = false){

    difference(){
        translate([-od/2-wheel_gap-deck_thickness,0,wall_thickness+wheel_gap+w/2])
        rotate([0,90,0])
                rotate([0,0,add_angle?deck_angle:0])
        union(){
            unmirrored_deck(shrunk);
            mirror([0,1,0])
                unmirrored_deck(shrunk);
        }

        if(add_bore) {
            // deckbolts(true);
            four_deckbolts(true);
        }
    }
}

module deckbolt(just_head=false, additional_r=0){
    color("green")
    rotate([0,90,0])
    union(){
        cylinder(r=deck_bolt_head_bore_r+additional_r,h=deck_bolt_head_bore_h);
        if (!just_head){
            cylinder(r=deck_bolt_total_r+additional_r,h=deck_bolt_total_h);
            translate([0,0,deck_bolt_nut_bore_translation])
                cylinder(r=deck_bolt_nut_bore_r+additional_r,h=deck_bolt_nut_bore_h+deck_bolt_nut_bore_extra_h,$fn=6);
        }
    }
}

module deckbolts(add_mirror=false){
    translate([-od/2-wheel_gap-deck_thickness,bolt_distance_from_center,wall_thickness+wheel_gap+w/2])
        deckbolt();

    translate([-od/2-wheel_gap-deck_thickness,-bolt_distance_from_center,wall_thickness+wheel_gap+w/2])
        deckbolt();
    if (add_mirror){
        translate([0,0,wall_thickness+wheel_gap+w/2])
        rotate([deck_angle*2]){
            translate([-od/2-wheel_gap-deck_thickness,bolt_distance_from_center])
                deckbolt();
            translate([-od/2-wheel_gap-deck_thickness,-bolt_distance_from_center])
                deckbolt();
        }
    }
}

module four_deckbolts(add_mirror=false, additional_r=0, just_head=false, width_offset=0){
    module twobolts(){
        translate([-od/2-wheel_gap-deck_thickness,four_bolt_distance_from_center])
            deckbolt(just_head,additional_r);

        translate([-od/2-wheel_gap-deck_thickness,-four_bolt_distance_from_center])
            deckbolt(just_head,additional_r);
    }

    translate([0,0,wall_thickness+wheel_gap+w/2]){
    translate ([0,0,four_bolt_width_from_center+width_offset])
        twobolts();
    translate ([0,0,-four_bolt_width_from_center-width_offset])
        twobolts();

        if (add_mirror){
            rotate([deck_angle*2]){
                translate ([0,0,four_bolt_width_from_center+width_offset])
                    twobolts();
                translate ([0,0,-four_bolt_width_from_center-width_offset])
                    twobolts();
            }
        }
    }
}

module minimum_necessary_area_for_strength(){
    difference(){
        hull(){
            radius=8;
            four_deckbolts(false, radius, false, radius-4);
            wheels_for_hulling();
        }
        deck(false,false,true);
    }
}

module lazercutdeck(include_bolt_head_bore=false){
    difference(){

    projection(cut = false)
    rotate([0,-90,0])
    {
        deck(!include_bolt_head_bore);
        // truck(true);
        // four_deckbolts();
    }
    if(include_bolt_head_bore){
        projection(cut = false)
        rotate([0,-90,0])
        four_deckbolts(true,0,include_bolt_head_bore);
    }

    }
}
```


[User]
Okay, I'd like to have an OpenSCAD design for a filament estimator, something that can be 3D printed and attached to a filament spool to give a rough estimate of how much filament is left. I want it to be parametric so I can adjust it for different spools and filaments later.

Here's what I'm thinking, based on something I saw someone else do:

*   **Basic Shape**: The core part will be a flat "stick" that extends radially outward from the spool's center. The inner end should have a hole that fits snugly over the spool holder rod. There also needs to be an optional clamp that can grip the spool holder rod for added stability if the user wants it. This is optional and should be enabled/disabled by a boolean.
*   **Spool Holder Clamp:** If the clamp is enabled it should be cylindrical, with a section cut out to allow it to be attached to a spool holder rod. The clamp needs parameters for the inner radius (to fit the rod) and the width of the cut out.
*   **Filament Measurement:** The estimator will have a scale on the top of the stick part. The scale will be based on the relationship between the radius of the filament and the mass left on the spool.
    *   The user should be able to define the radius of the center hole of the spool, the radius where the filament starts on a full spool, and the radius at a sample point with a corresponding filament weight.
    *   Based on these inputs, the scale will be drawn. The scale should display lines and numerical labels showing estimated weight remaining in grams. I want to be able to set how much each line represents (e.g. each line is 50 grams) and how often to show a label (e.g., every 100 grams). The maximum weight displayed should be configurable, which will also determine the length of the scale.
    *   I need to be able to set a text label that describes the type of filament being measured, and that should appear near the top of the scale.
*   **Units/Sizes:** The units should be millimeters and grams. I'd like to be able to control the height of the scale lines, how long they are, the depth of the scale engraving, and the font sizes.
*   **Libraries:** I don't think any specific libraries are necessary, but maybe we can use the `text()` module that is native to OpenSCAD.

I think that covers the main points. Let me know if you have any questions. I can clarify further as needed.

[Assistant]
filament_estimator.scad:
```
/*

Filament Estimator

Copyright Thomas Axelsson 2020.

License: CC BY-SA 4.0

*/

/* [Fastening] */
// Should the measure have a spool holder clamp?
have_clamp = 1; // [0:without clamp, 1:with clamp]

// Inner radius of the clamp. Make it approx 0.2 mm smaller than the spool holder rod.
clamp_radius = 11.5; // 11.7 - 0.2

// Width of the opening in the clamp.
clamp_opening = 30;

/* [Filament Calculations] */
// Spool distances
// Radius of the hole in the center of the spool
hole_radius = 26.5;
// Radius from the spool center to where the filament starts
empty_radius = 53;
// Radius from the spool center to sample point on filament
sample_radius = 89;
// Weight of the filament at sample point.
sample_weight = 750; // g

/* [Scale] */
// Filament type label
filament_type = "PLA";

// Maximum weight to show on the scale. Determines the length of the scale.
scale_max_weight = 850;

// How much weight each line on the scale represents.
scale_step = 50;

// How often too label the lines, in weight.
scale_label_step = 100;

/* [Hidden] */

// Not used in current calculations. Just for rendering.
// The width of the spool
spool_inner_width = 45.5;

$fn = 50;

// Font size
FONT_MM = 0.85;

// Overlap for cuts, so OpenSCAD does not intersect surfaces
intersect_margin = 0.01;

*filament();
measure();
if (have_clamp) {
    clamp();
}

module filament() {
    color("blue", alpha=0.2)
    translate([0, 0, -spool_inner_width - 0.1])
    difference() {
        cylinder(h=spool_inner_width, r=sample_radius);
        translate([0, 0, -spool_inner_width/2])
        cylinder(h=2*spool_inner_width, r=empty_radius);
    }
}

function mass_to_radius(m) =
    // Find the area of filament for radius r, then divide it
    // by the area of max filament. Use this ratio to scale
    // the weight m. Then solve for the radius r.
    sqrt((m*(pow(sample_radius,2)-pow(empty_radius, 2))) / sample_weight + pow(empty_radius, 2));

// Using filament center as origin
module measure() {
    w = 10;
    d = 1.5;
    line_height = 0.5;
    line_short_length = 2;
    line_long_length = 3;
    line_depth = 0.4;
    font_height_mm = 3;
    label_font_height_mm = 4;
    scale_max_radius = mass_to_radius(scale_max_weight + 50) + 5;

    difference() {
        // "Stick"
        translate([hole_radius, -w/2, 0])
        cube([scale_max_radius - hole_radius, w, d]);

        // Inset text and markers
        translate([0, 0, d - line_depth + intersect_margin]) {
            // Top label
            translate([scale_max_radius - 2, 0, 0])
            linear_extrude(line_depth)
            rotate([0, 0, -90])
            text(filament_type, label_font_height_mm * FONT_MM,
                 "Liberation Sans:style=Bold", halign="center",
                 valign="top");

            // Measure markers
            for (m = [0:scale_step:scale_max_weight]) {
                r = mass_to_radius(m);
                has_text = (m % scale_label_step == 0);
                line_length = has_text ? line_long_length : line_short_length;
                echo(m=m, r=r);
                // Position vertically
                translate([r, 0, 0]) {
                    // Line - left of origin. Position on edge.
                    translate([-line_height/2, w/2 - line_length, 0])
                    cube([line_height, line_length, line_depth]);

                    // Text - right of origin
                    if (has_text) {
                        // Spacing
                        linear_extrude(height = line_depth)
                        // Move sideways
                        translate([0, w/2 - line_length - 0.5, 0])
                        // Vertical align
                        translate([-font_height_mm/2, 0, 0])
                        rotate([0, 0, -90])
                        text(str(m), font_height_mm * FONT_MM,
                             "Liberation Sans");
                    }
                }
            }
        }
    }
}

module clamp() {
    d = 3;
    w = 3;
    translate([hole_radius - clamp_radius, 0, 0])
    difference() {
        cylinder(h=d, r=clamp_radius + w);
        // Hole
        translate([0, 0, -d/2])
        cylinder(h=2*d, r=clamp_radius);
        // Opening
        translate([0, 0, -intersect_margin])
        linear_extrude(2*d)
        // Make walls *2 to cut through the ring completely
        polygon([[0, 0], [-clamp_radius*2, clamp_opening],
                 [-clamp_radius*2, -clamp_opening]]);
    }
}
```


[User]
Hey, I need a custom shim for my Samsung smart door lock, and I'm hoping you can help me design it. I've got some measurements here, and I'd like to be able to adjust some of them easily. I'm thinking this should be an OpenSCAD design, to make it parametric.

Basically, I need a flat plate with some cutouts and some "pins" that stick out. The plate itself needs to have an adjustable thickness and an overall height and width. I need to also put some holes and slots into it. Then there are two sets of notches on opposite sides, which have a fixed size and placement relative to each other.

Here are the specific dimensions I have in mind initially, but remember that I need to be able to change these:

*   **Shim Thickness:** I'm starting with 18mm.
*   **Plate Height:** 124mm
*   **Plate Width:** 29mm
*   **Hole Diameter:** 4.5mm
*   **Hole Edge Distance:** 15mm from the top and bottom edges
*  **Slot Diameter:** 4.5mm
*  **Slot Width:** 15mm
*  **Slot Edge Distance:** 4 + slot diameter/2 (so 4 + 2.25 = 6.25mm) from the top and bottom edges
*   **Notch Height:** 1.5mm
*   **Notch Width:** 3.5mm
*   **Notch Thickness:** 1mm
*   **Notch Vertical Distance:** 66.5mm (center to center)
*   **Notch Lateral Distance:** 20.7mm (center to center)

The holes and slots are centered on the x axis, but should be on the y axis with their respective edge distances. The notches should be on the z axis (extending above the plate), with 2 of them mirrored on the x axis. We also need the notches to have a corresponding cutout which will extend through the plate.

I'm planning to 3D print this, so the tolerances don't need to be super precise, but I would like to be able to adjust the dimensions and re-generate the model if needed. I'm not familiar with external libraries, so I think I'm only requesting one OpenSCAD file at this point.

Could you help me with this design?

[Assistant]
Samsung_Smart_Door_Lock_Shim.scad:
```
/* [Global] */
//mm
ShimThickness = 18;

/* [Hidden] */
ht = 124;
wd = 29;
tk = ShimThickness;
holeD = 4.5;
holeEdgeDist = 15;
slotD = 4.5;
slotWd = 15;
slotEdgeDist = 4+slotD/2;
notchHt = 1.5;
notchWd = 3.5;
notchTk = 1;
notchVertDist = 66.5; //centers
notchLatDist = 20.7; //centers

$fn=20;

difference() {
    union() {
        //plate
        linear_extrude(tk) minkowski() {
                square([wd-4, ht-4], center=true);
                circle(r=2);
        }
        //pins
        Pins();
        rotate(180) Pins();
    }

    //deduct slot and hole
    linear_extrude(tk*3, center=true) {
        SlotAndHole();
        rotate(180) SlotAndHole();
    }

    //deduct notches
    Sockets();
    rotate(180) Sockets();
}

module SlotAndHole() {
    //slot
    translate([0, ht/2-slotEdgeDist]) {
        square([slotWd, slotD], center=true);
        translate([-slotWd/2, 0])
            circle(d=slotD);
        translate([+slotWd/2, 0])
            circle(d=slotD);
    }

    //hole
    translate([0, ht/2-holeEdgeDist])
        circle(d=holeD);
}

module Pins() {
    translate([notchLatDist/2, notchVertDist/2, tk+notchTk/2])
        cube([notchWd, notchHt, notchTk], center=true);
    translate([-notchLatDist/2, notchVertDist/2, tk+notchTk/2])
        cube([notchWd, notchHt, notchTk], center=true);
}

module Sockets() {
    translate([notchLatDist/2, notchVertDist/2])
        cube([notchWd+0.25, notchHt+0.25, notchTk*3], center=true);
    translate([-notchLatDist/2, notchVertDist/2])
        cube([notchWd+0.25, notchHt+0.25, notchTk*3], center=true);
}
```


[User]
I'm trying to create a compound planetary gearbox, and I'm having trouble getting the customizer working correctly. I need an OpenSCAD design that lets me adjust the key parameters easily.

Specifically, I need a design that incorporates two planetary stages. The first stage should have a sun gear, planet gears, and a ring gear, and then the output of those planet gears will drive a second planetary stage with another set of planet and ring gears. It's important that the whole thing can be easily customized with variables for things like number of teeth in each gear, module, layer height, etc. I'm including the code from a previous design to show the kind of functionality I need, but I'm not asking for it to be improved - I need a new implementation that is parametric.

Here's a breakdown of the parameters I need to be able to control:

*   **Modules:** Number of planetary layers (either 2 or 3 for now).
*   **gear_h:** Height of the gears in the first planetary stage.
*   **gear_h2:** Height of the gears in the second planetary stage.
*   **cp:** Circular pitch (distance from one tooth to the next).
*   **planet_t:** Number of teeth in the planet gears of the first stage.
*   **planet_t2:** Number of teeth in the planet gears of the second stage.
*   **ring_t1:** Number of teeth in the ring gear of the first stage.
*   **ring_t2:** Number of teeth in the ring gear of the second stage.
*   **sgm:** Sun gear multiplier (to fuse the sun gear to a multiple of planet gear teeth).
*   **shaft_d:** Diameter of the shafts.
*   **shafts:** Number of secondary shafts (for larger sun gears).
*   **outer_d:** Outer diameter of the assembly (for the ring gear).
*   **outer_t:** Number of teeth around the outer edge (optional).
*   **outer_w:** Width of the outer teeth (if outer_t is greater than zero).
*   **wall:** Wall thickness.
*    **slot_w** Slot width (not currently being used in the code, but would be good to include).
*    **slot_l** Slot length (not currently being used in the code, but would be good to include).
*   **planets:** Number of planet gears in each stage.
*   **nTwist:** Number of teeth to twist across the gear height, for creating herringbone gears.
*    **depth_ratio**: A value that describes how far the teeth extend from the pitch diameter.
*   **tol:** Gear clearance.
*   **P:** Pressure angle for the gear teeth.
*   **$fa:** Curve resolution settings, minimum angle.
*   **$fs:** Curve resolution settings, minimum size.
*   **layer_h:** Layer height (for a horizontal split of the ring gear).

Additionally, it would be awesome if the sun gears could be fused together and/or to additional shafts. The planet gears should also be able to rotate around their own centers and each planetary stage should be offset vertically. Also, the design should include herringbone gears.

I would need this to use the `MCAD` library, specifically the `involute_gears.scad` file, for the involute gear generation. The code I have uses a modified herringbone gear, but using MCAD would be easier.

Let me know if you need anything else. Thanks!

[Assistant]
Compound10.scad:
```
// OpenSCAD Compound Planetary System
// (c) 2019, tmackay
//
// Licensed under a Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license, http://creativecommons.org/licenses/by-sa/4.0.
//include <MCAD/involute_gears.scad>

// Modules, planetary layers
modules = 2; //[2:1:3]
// Height of planetary layers (drive stage)
gear_h = 7.5;
// Height of planetary layers (output stage)
gear_h2 = 7.5;
// Circular pitch: Length of the arc from one tooth to the next
cp = 245;
// Number of teeth in inner planet gears
planet_t = 13; //[10:1:20]
planet_t2 = 15; //[10:1:20]
// Number of teeth in ring gears
ring_t1 = 38; //[30:1:90]
ring_t2 = 45; //[30:1:90]
// Sun gear multiplier
sgm = 1; //[1:1:5]
// Shaft diameter
shaft_d = 10;
// secondary shafts (for larger sun gears)
shafts = 6;
// Outer diameter
outer_d = 60;
// Outer teeth
outer_t = 0;
// Width of outer teeth
outer_w=3;
// Wall thickness
wall = 0;
// Slot width
slot_w = 0;
// Slot length
slot_l = 0;
// Number of planet gears in inner circle
planets = 3; //[3:1:21]
// number of teeth to twist across
nTwist=1;
// Gear depth ratio
depth_ratio=1;
// Gear clearance
tol=0.2;
// pressure angle
P=45; //[30:60]
// Curve resolution settings, minimum angle
$fa = 5;
// Curve resolution settings, minimum size
$fs = 1;
// Layer height (for ring horizontal split)
layer_h = 0.2;

// For fused sun gears, we require them to be a multiple of the planet teeth
drive_t = planet_t*sgm;
drive_t2 = planet_t2*sgm;

if ((drive_t+ring_t1)%planets)
{
    echo(str("Warning: For even spacing, planets must divide ", drive_t+ring_t1));
}
if ((drive_t2+ring_t2)%planets)
{
    echo(str("Warning: For even spacing (2), planets must divide ", drive_t2+ring_t2));
}

if (drive_t + 2*planet_t != ring_t1) {
    echo(str("Teeth fewer than ideal (ring1): ", drive_t+2*planet_t-ring_t1));
}
if (drive_t2 + 2*planet_t2 != ring_t2) {
    echo(str("Teeth fewer than ideal (ring2): ", drive_t2+2*planet_t2-ring_t2));
}

// Planetary gear ratio for fixed ring: 1:1+R/S
echo(str("Gear ratio of first planetary stage: 1:", 1+ring_t1/drive_t)); // eg. 1:3.1818...

// (Planet/Ring interaction: Nr*wr-Np*wp=(Nr-Np)*wc)
// one revolution of carrier (wc=1) turns planets on their axis
// wp = (Np-Nr)/Np = eg. (10-31)/10=-2.1 turns
// Secondary Planet/Ring interaction
// wr = ((Nr-Np)+Np*wp)/Nr = eg. ((34-11)-11*2.1)/34 = 1/340
// or Nr2/((Nr2-Np2)+Np2*(Np1-Nr1)/Np1)
echo(str("Gear ratio of planet/ring stage: 1:", abs(ring_t2/((ring_t2-planet_t2)+planet_t2*(planet_t-ring_t1)/planet_t)))); // eg. 3.8181..

// Final gear ratio is product of above, eg. 1:1298.18..
echo(str("Input/Output gear ratio: 1:",abs((1+ring_t1/drive_t)*ring_t2/((ring_t2-planet_t2)+planet_t2*(planet_t-ring_t1)/planet_t))));

// what ring should be for teeth to mesh without additional scaling
ring_t1_n = drive_t+2*planet_t;
ring_t2_n = drive_t2+2*planet_t2;

// Coincident planet offsets
cp2=(drive_t+planet_t)*cp/(drive_t2+planet_t2);

// Tolerances for geometry connections.
AT=0.02;
ST=AT*2;
TT=AT/2;

helix_angle=atan(PI*nTwist*cp/90/gear_h);
helix_angle2=atan(PI*nTwist*cp2/90/gear_h);

// center gears
rotate([0,0,180/drive_t*(1-planet_t%2)])
    drivegear(t1=drive_t,bore=shaft_d,rot=180/drive_t*(1-planet_t%2));
translate([0,0,gear_h-TT])intersection(){
    rotate([0,0,180/drive_t2*(1-planet_t2%2)])
        drivegear(t1=drive_t2,bore=shaft_d,cp=cp2,helix_angle=helix_angle2,gear_h=gear_h2,rot=180/drive_t2*(1-planet_t2%2));
    rotate([0,0,180/drive_t*(1-planet_t%2)])
        linear_extrude(height=gear_h2,scale=1+gear_h2/(drive_t*cp/360)*sqrt(3),slices=1)
            gear2D(drive_t,cp*PI/180,P,depth_ratio,tol);
}
if(modules==3)translate([0,0,gear_h+gear_h2-AT])intersection(){
    rotate([0,0,180/drive_t*(1-planet_t%2)])
        drivegear(t1=drive_t,bore=shaft_d,rot=180/drive_t*(1-planet_t%2));
    rotate([0,0,180/drive_t2*(1-planet_t2%2)])
        linear_extrude(height=gear_h,scale=1+gear_h/(drive_t2*cp2/360)*sqrt(3),slices=1)
            gear2D(drive_t2,cp2*PI/180,P,depth_ratio,tol);
}

// planets
planets(t1=planet_t, t2=drive_t,offset=(drive_t+planet_t)*cp/360,n=planets,t=ring_t1+drive_t)
    planetgear(t1 = planet_t, bore=shaft_d);

translate([0,0,gear_h-TT])
    planets(t1=planet_t2, t2=drive_t2,offset=(drive_t2+planet_t2)*cp2/360,n=planets,t=ring_t2+drive_t2)
        intersection(){
            planetgear(t1 = planet_t2, bore=shaft_d,cp=cp2,helix_angle=helix_angle2,gear_h=gear_h2);
            linear_extrude(height=gear_h2,scale=1+gear_h2/(planet_t*cp/360)*sqrt(3),slices=1)
                    gear2D(planet_t,cp*PI/180,P,depth_ratio,tol);
        }
if(modules==3)translate([0,0,gear_h+gear_h2-AT])
    planets(t1=planet_t, t2=drive_t,offset=(drive_t+planet_t)*cp/360,n=planets,t=ring_t1+drive_t)
        intersection(){
            planetgear(t1 = planet_t, bore=shaft_d);
            linear_extrude(height=gear_h,scale=1+gear_h/(planet_t2*cp2/360)*sqrt(3),slices=1)
                    gear2D(planet_t2,cp2*PI/180,P,depth_ratio,tol);
        }

// rings
s1=ring_t1_n/ring_t1;
s2=ring_t2_n/ring_t2;
ha1=atan(PI*nTwist*s1*cp/90/gear_h); // not sure about this, seems close enough.
ha2=atan(PI*nTwist*s2*cp2/90/gear_h);
gearcase(t1=ring_t1,bore=outer_d/2,cp=s1*cp,helix_angle=ha1);
translate([0,0,gear_h-TT])
    difference(){
        gearcase(t1=ring_t2,bore=outer_d/2,cp=s2*cp2,helix_angle=ha2,gear_h=gear_h2);
        linear_extrude(height=(ring_t1*s1*cp/360)/sqrt(3),scale=0,slices=1)
            hull()gear2D(ring_t1,s1*cp*PI/180,P,depth_ratio,-tol);
        translate([-outer_d,-outer_d,-TT])cube([2*outer_d,2*outer_d,layer_h+AT]);
    }
if(modules==3)translate([0,0,gear_h+gear_h2-AT])
    difference(){
        gearcase(t1=ring_t1,bore=outer_d/2,cp=s1*cp,helix_angle=ha1);
        linear_extrude(height=(ring_t2*s2*cp2/360)/sqrt(3),scale=0,slices=1)
            hull()gear2D(ring_t2,s2*cp2*PI/180,P,depth_ratio,-tol);
        translate([-outer_d,-outer_d,-TT])cube([2*outer_d,2*outer_d,layer_h+AT]);
    }

// Drive gear with slot
module drivegear(t1=13,reverse=false,bore=2*cos(P)*13*cp/360-wall,rot=0)
{
    planetgear(t1,!reverse,bore,cp=cp,helix_angle=helix_angle,gear_h=gear_h,rot=rot);
}

// reversible herringbone gear with bore hole
module planetgear(t1=13,reverse=false,bore=0,rot=0)
{
    difference()
    {
        translate([0,0,gear_h/2])
        if (reverse) {
            mirror([0,1,0])
                herringbone(t1,PI*cp/180,P,depth_ratio,tol,helix_angle,gear_h);
        } else {
            herringbone(t1,PI*cp/180,P,depth_ratio,tol,helix_angle,gear_h);
        }

        translate([0,0,-TT]){
            rotate([0,0,-rot])
                cylinder(d=bore, h=2*gear_h+AT);
            // Extra speed holes, for strength
            if(bore>0 && bore/4+(t1-2*tan(P))*cp/720>bore)
                for(i = [0:360/shafts:360-360/shafts])rotate([0,0,i-rot])
                    translate([bore/4+(t1-2*tan(P))*cp/720,0,-AT])
                        cylinder(d=bore,h=2*gear_h+AT);
        }
    }
}

// Space out planet gears approximately equally
module planets()
{
    for(i = [0:n-1])
    rotate([0,0,round(i*t/n)*360/t])
        translate([offset,0,0]) rotate([0,0,round(i*t/n)*360/t*t2/t1])
            children();
}

module gearcase(t1=13,teethtwist=1,bore=18*cp/360)
{
    difference() {
        cylinder(r=bore,h=gear_h);
        translate([0,0,-TT])planetgear(t1=t1,gear_h=gear_h+AT,tol=-tol,helix_angle=helix_angle,cp=cp,depth_ratio=depth_ratio,P=P);
	}
            // outer notches
        if(outer_t>0)for(i = [0:360/outer_t:360-360/outer_t])rotate([0,0,i])
            translate([outer_d/2,0,-AT])rotate([0,0,45])
                cylinder(d=outer_w,h=gear_h+AT,$fn=4);

}

// Herringbone gear code, taken from:
// Planetary gear bearing (customizable)
// https://www.thingiverse.com/thing:138222
// Captive Planetary Gear Set: parametric. by terrym is licensed under the Creative Commons - Attribution - Share Alike license.
module herringbone(
	number_of_teeth=15,
	circular_pitch=10,
	pressure_angle=28,
	depth_ratio=1,
	clearance=0,
	helix_angle=0,
	gear_thickness=5){
union(){
    translate([0,0,-TT])
	gear(number_of_teeth,
		circular_pitch,
		pressure_angle,
		depth_ratio,
		clearance,
		helix_angle,
		gear_thickness/2+TT);
	mirror([0,0,1]) translate([0,0,-TT])
		gear(number_of_teeth,
			circular_pitch,
			pressure_angle,
			depth_ratio,
			clearance,
			helix_angle,
			gear_thickness/2+TT);
}}

module gear (
	number_of_teeth=15,
	circular_pitch=10,
	pressure_angle=28,
	depth_ratio=1,
	clearance=0,
	helix_angle=0,
	gear_thickness=5,
	flat=false){
pitch_radius = number_of_teeth*circular_pitch/(2*PI);
twist=tan(helix_angle)*gear_thickness/pitch_radius*180/PI;

flat_extrude(h=gear_thickness,twist=twist,flat=flat)
	gear2D (
		number_of_teeth,
		circular_pitch,
		pressure_angle,
		depth_ratio,
		clearance);
}

module flat_extrude(h,twist,flat){
	if(flat==false)
		linear_extrude(height=h,twist=twist,slices=6)children(0);
	else
		children(0);
}

module gear2D (
	number_of_teeth,
	circular_pitch,
	pressure_angle,
	depth_ratio,
	clearance){
pitch_radius = number_of_teeth*circular_pitch/(2*PI);
base_radius = pitch_radius*cos(pressure_angle);
depth=circular_pitch/(2*tan(pressure_angle));
outer_radius = clearance<0 ? pitch_radius+depth/2-clearance : pitch_radius+depth/2;
root_radius1 = pitch_radius-depth/2-clearance/2;
root_radius = (clearance<0 && root_radius1<base_radius) ? base_radius : root_radius1;
backlash_angle = clearance/(pitch_radius*cos(pressure_angle)) * 180 / PI;
half_thick_angle = 90/number_of_teeth - backlash_angle/2;
pitch_point = involute (base_radius, involute_intersect_angle (base_radius, pitch_radius));
pitch_angle = atan2 (pitch_point[1], pitch_point[0]);
min_radius = max (base_radius,root_radius);

intersection(){
	rotate(90/number_of_teeth)
		circle($fn=number_of_teeth*3,r=pitch_radius+depth_ratio*circular_pitch/2-clearance/2);
	union(){
		rotate(90/number_of_teeth)
			circle($fn=number_of_teeth*2,r=max(root_radius,pitch_radius-depth_ratio*circular_pitch/2-clearance/2));
		for (i = [1:number_of_teeth])rotate(i*360/number_of_teeth){
			halftooth (
				pitch_angle,
				base_radius,
				min_radius,
				outer_radius,
				half_thick_angle);
			mirror([0,1])halftooth (
				pitch_angle,
				base_radius,
				min_radius,
				outer_radius,
				half_thick_angle);
		}
	}
}}

module halftooth (
	pitch_angle,
	base_radius,
	min_radius,
	outer_radius,
	half_thick_angle){
index=[0,1,2,3,4,5];
start_angle = max(involute_intersect_angle (base_radius, min_radius)-5,0);
stop_angle = involute_intersect_angle (base_radius, outer_radius);
angle=index*(stop_angle-start_angle)/index[len(index)-1];
p=[[0,0], // The more of these the smoother the involute shape of the teeth.
	involute(base_radius,angle[0]+start_angle),
	involute(base_radius,angle[1]+start_angle),
	involute(base_radius,angle[2]+start_angle),
	involute(base_radius,angle[3]+start_angle),
	involute(base_radius,angle[4]+start_angle),
	involute(base_radius,angle[5]+start_angle)];

difference(){
	rotate(-pitch_angle-half_thick_angle)polygon(points=p);
	square(2*outer_radius);
}}

// Mathematical Functions
//===============

// Finds the angle of the involute about the base radius at the given distance (radius) from it's center.
//source: http://www.mathhelpforum.com/math-help/geometry/136011-circle-involute-solving-y-any-given-x.html

function involute_intersect_angle (base_radius, radius) = sqrt (pow (radius/base_radius, 2) - 1) * 180 / PI;

// Calculate the involute position for a given base radius and involute angle.

function involute (base_radius, involute_angle) =
[
	base_radius*(cos (involute_angle) + involute_angle*PI/180*sin (involute_angle)),
	base_radius*(sin (involute_angle) - involute_angle*PI/180*cos (involute_angle))
];
```


[User]
I'm looking to generate some V-groove pulleys for round polyurethane belts, and I need the design to be parametric so I can easily adjust the dimensions. I've seen some designs using a rotate_extrude to create the groove profile, and that seems like a good approach. I need to specify the effective diameter (where the belt center sits), the belt diameter itself, the bore diameter for the shaft, the groove angle (60 degrees is common), and the overall thickness.

I'd like the ability to create both single and double pulleys, so if that's possible please include it! For the double pulley I need to be able to specify each pulley diameter and the thickness for each section, and also include the bore diameter and groove angle.

So, to summarize, I'm after an OpenSCAD design, or a set of OpenSCAD files, that can generate:

*   A single V-groove pulley with:
    *   Effective diameter
    *   Belt diameter
    *   Bore diameter
    *   Groove angle
    *   Thickness
*   A double V-groove pulley with:
    *   Two effective diameters (one for each groove)
    *   Belt diameter
    *   Bore diameter
    *   Groove angle
    *   Thickness for each section

I'm not looking to use any particular libraries (i.e., I don't have any dependencies), so I'd like to see all the relevant code. I need the code to be well-commented so it's easy to understand what each part does. I think that's everything, thanks!

[Assistant]
v_pulley.scad:
```
// Copyright <senorjp@gmail.com> 2020
//
// Customizer to create V-profile pulleys
// suitable for round belts

// Effective diameter (diameter at center of belt) (mm)
effective_diameter = 30;

// Belt diameter (mm)
belt_diameter = 2;

// Bore diameter (mm)
bore_diameter = 4;

// Angle of the groove (60 degrees standard) (degrees)
groove_angle = 60;

// Thickness (mm)
thickness = 8;

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

belt_pulley(d=effective_diameter, belt_d=belt_diameter, axle_d = bore_diameter, t=thickness, angle=groove_angle);

module v_groove(
    d = 20,
    belt_d=2,
    angle=60,
    ){
    // a toroidal shape which is a v-shaped groove that
    // holds a belt centered at the diameter (d)
    // that is to say: the d is measured at the center
    // of the belt.  If you want d at the inside diamater
    // or outside diameter, adjust d accordingly

        rotate_extrude()
        translate([d/2,0])
        union() {
            translate([-(belt_d/2)/cos(90-angle/2),0])
        polygon(points=[
            [0,0],
            [(belt_d*2)/tan(angle/2),belt_d*2],
            [(belt_d*2)/tan(angle/2),-belt_d*2],
        ]);
            // show the belt
            color("black") circle(r=belt_d/2);
        }
}

module belt_pulley(
    d = 20,
    shaft_d=4,
    belt_d=2,
    angle=60,
    t=5.5,
    wedge_factor=.2
    ){
    difference() {

        union() {
    difference() {
        cylinder(r=d/2+belt_d,h=t, center=true);

        v_groove(
            d = d,
            belt_d=belt_d,
            angle=angle
        );
    }
        //cylinder(r=d/2-belt_d/2-belt_d*wedge_factor,h=t, center=true);
    }
        cylinder(r=shaft_d/2,h=t+1, center=true);

}
}

//belt_pulley();

module double_pulley(
    d1=15,
    d2=10,
    t=6,
    shaft_d=4,
    belt_d=2,
    angle=60
    ) {

    translate([0,0,t/2])
    belt_pulley(
        d=d1,
        t=t,
        shaft_d=shaft_d,
        belt_d=belt_d,
        angle=angle
    );
    translate([0,0,t+t/2])
    belt_pulley(
        d=d2,
        t=t,
        shaft_d=shaft_d,
        belt_d=belt_d,
        angle=angle
    );

}

//belt_pulley();
//dia_1=80;
//double_pulley(d1=dia_1,d2=dia_1/4,belt_width=25.4/8,t=7);

//double_pulley(d1=15,d2=30,t=6);
//double_pulley(d1=20,d2=8);

```


[User]
Hey, I'm looking to create a customizable, semi-circular pen holder that can be wall-mounted. I've been playing with OpenSCAD, and I'd like to have a design that allows me to adjust a few key parameters.

Specifically, I need a holder with a semi-circular or arc-shaped profile (not quite full cylinder, maybe around 270 degrees by default, but customizable) and it should have a few vertical support pillars and a slatted or sliced design for the main body.

Here's the breakdown of what I'd like to be able to control:

*   **Overall Size:**
    *   **`height`:** The total height of the pen holder.
    *   **`radius`:** The radius of the main semi-circular shape.
    *   **`thickness`:** The wall thickness of the main body.
*   **Base and Top:**
    *   **`bottom_height`:** The thickness of a solid bottom plate.
    *   **`bottom_part`:** The height of a small base plate at the bottom of the back wall.
    *   **`top_part`:** The height of a top plate on the top of the back wall.
*   **Slices:**
    *   **`slice_height`:** The height of each individual slice.
    *   **`slice_count`:** The number of slices that make up the main body.
*   **Pillars:**
    *   **`pillar_count`:** The number of vertical pillars supporting the structure.
    *   **`pillar_width`:** The width of the pillars as a percentage of the available space.  So if it was 50%, the pillars would take up 50% of the available angle of the arc, evenly spaced.
*   **Shape:**
    *   **`angle`:** The arc angle, in degrees, of the semi-circular shape. It should be less than or equal to 360 degrees.
*   **Other:**
    *   **`remove_back`**: A boolean to enable or disable removal of material from the back of the sliced sections.
   *    **`$fn`**: This value should be customizable for all applicable curves in the design.

The design should feature:

*   A main semi-circular shell with the specified radius and thickness.
*   Vertical pillars along the curve.
*   Sliced segments along the arc with a gap between each slice.
*   A solid bottom plate.
*   A small solid plate at the bottom of the back wall
*    A small solid plate at the top of the back wall
*   The ability to remove or not remove material from the back side.

I think that's everything. I'm not sure if you can generate multiple .scad files, but if you need to break this into a few files (like a main file and maybe a file for helper functions or something), that would be fine. I'm not using any libraries so I guess that means everything has to be written from scratch, right?

[Assistant]
CPH.scad:
```
//include <common.scad>

module semi_cylinder(r, h, ang, $fn=24, center = true) {
   a = ang/$fn;
   linear_extrude(height=h, convexity=8)
   translate(center ? [0, 0] : [-r * cos(a*$fn/2), 0])
   polygon([for(i=[-$fn/2:$fn/2])
      [r * cos(i*a), r * sin(i*a)]
   ], convexity=8);
}

module semi_cylinder_shell(r, thickness, h, ang, fn = 24) {
   difference() {
      semi_cylinder(r+thickness, h, ang, fn);
      translate([0,0,-0.05])
      semi_cylinder(r, h+0.1, ang, fn);
   }
}

//$fn is per segment for on sections
module cylinder_shell_parts(r, thickness, h, on = 15, off=45, $fn = 4) {
   segments = ceil(360 / (on+off));

   aps = on / $fn;

   for(s = [0:segments-1])
      linear_extrude(height=h, convexity=8)
      polygon(concat([
            for(a = [0:$fn])
               let (ang = s*(on+off)+aps *a)
               [(r+thickness) * cos(ang), (r+thickness) * sin(ang)]
      ],
      [
            for(a = [0:$fn])
               let (ang = s*(on+off)+on-aps*a)
               [r * cos(ang), r * sin(ang)]
      ]), convexity=8);
}

//Total height including bottom plate
height = 80;

//Radius
radius = 25;

//Wall thickness
thickness = 2;

//Bottom plate thickness
bottom_height = 1.5;

//Height of each slice
slice_height = 1;

//Number of slices
slice_count = 8;

//Number of vertical pillars
pillar_count = 8;

//Width of pillars as a percentage
pillar_width = 40; //[0:100]

//Height of the backing plate, bottom part
bottom_part = 5;

//Height of the backing plate, top part
top_part = 20;

//Remove slices from back side
remove_back = true;

//Completeness of the circle (360 will be full)
angle = 270;//[180:360]

translate([-cos(angle/2) * (radius+thickness), 0]) {
   rotate([0, 0, -angle/2])
   intersection() {
      rotate([0, 0, angle/2])
      semi_cylinder_shell(radius, thickness, height, angle, 52);

      ang = 100*angle / (pillar_width + 100 * pillar_count);

      cylinder_shell_parts(
         radius, thickness, height,
         ang*pillar_width/100, ang*(100-pillar_width)/100, 4
      );
   }

   difference() {
      for(i = [0:slice_count])
         translate([0, 0, (height-slice_height) * i / slice_count])
         semi_cylinder_shell(radius, thickness, slice_height, angle, 64);

      if(remove_back)
         translate([
            cos(angle/2) * (radius+thickness)-0.1,
            -sin(angle/2) * (radius+thickness), -0.1
         ])
         cube([thickness+1, sin(angle/2) * (radius+thickness) * 2, height+0.2]);
   }

   semi_cylinder(radius + thickness, bottom_height, angle, 64);
}

translate([0, -sin(angle/2) * (radius+thickness)])
cube([thickness, sin(angle/2) * (radius+thickness)*2, bottom_part]);

translate([0, -sin(angle/2) * (radius+thickness), height - top_part])
cube([thickness, sin(angle/2) * (radius+thickness) * 2, top_part]);

```


[User]
Hey, I'm looking to get an OpenSCAD model made for a Bahtinov mask, which is a tool used for focusing telescopes. I've got the basic design parameters, and I'd like the model to be parametric so I can adjust it for different scopes in the future.

Here are the details:

*   **Overall Shape:** The mask is essentially a flat disk with cutouts that create the Bahtinov pattern.
*   **Outer Diameter:** I need a parameter to specify the outer diameter of the disk. Let's call it `outerDiameter` and set the default to 215 mm, with a range between 80 and 500 mm.
*   **Aperture:** There's an inner circle inside the disk representing the area where light passes through, its diameter is a parameter called `aperture`. The default value should be 203 mm, with a range of 80 to 495 mm. It should always be less than the outer diameter.
*   **Center Hole:** I need an optional circular hole in the center of the mask. Its diameter should be a parameter called `centerHoleDiameter`, with default value of 71 mm, and a range from 0 to 120 mm. A value of zero means no center hole. The center hole should also always be less than `outerDiameter`.
*   **Gap and Bar Width:** The gaps and bars in the Bahtinov pattern should have a uniform width controlled by a parameter named `gap`, with a default of 6mm and a range between 4 and 10mm.

*   **Bahtinov Pattern:** The Bahtinov pattern consists of three sets of parallel bars, each set angled relative to the others. One set should be aligned horizontally. The other two should be rotated by +30 and -30 degrees relative to the horizontal.

*   **Bars:** The three sets of bars should be created using a sub-module named `bars` that will create parallel bars for a specific width and gap, and number of bars. The `bahtinovBars` should create the full pattern.
*   **Mask Creation:** The final mask should be generated using a module called `bahtinov2D` that should create the necessary circle to get the mask outline.
*   **Structural Bars:** A vertical and a horizontal bar are needed to make the mask stronger. They should be added within `bahtinov2D` using the `square` module.
*   **Extrusion and Handle:** Finally, I need the design to be extruded to 2mm thick, and a small handle on the edge, cylindrical in shape, 18mm high and with a radius equal to `gap` divided by 2. This should all be done in the root.

I'd like to keep everything in one `.scad` file if possible. Do you need any more details?

[Assistant]
bahtinov.scad:
```
/*                                                 -*- c++ -*-
 * A Bahtinov mask generator.
 * Units in mm, default values are for
 * a Celestron NexStar GPS 8.
 *
 * Copyright 2013-2020, Brent Burton
 * License: CC-BY-SA
 *
 * Updated: 2014-06-23
 * Updated: 2019-11-09 - increased center diameter limit to 120.
 * Updated: 2020-11-18 - increased outer and aperture diameters to 500.
 */

// This is the diameter of the mask.
outerDiameter = 215; // [80:500]

// The telescope light's path diameter.
aperture = 203; // [80:495]

// Diameter of secondary mirror holder. If no secondary, set to 0.
centerHoleDiameter = 71; // [0:120]

// Width of the gaps and the bars.
gap = 6; // [4:10]

/* create a series of bars covering roughly one half of the
 * scope aperture. Located in the +X side.
 */
module bars(gap, width, num=5) {
    num = round(num);
    for (i=[-num:num]) {
        translate([width/2,i*2*gap]) square([width,gap], center=true);
    }
}

module bahtinovBars(gap,width) {
    numBars = aperture/2 / gap / 2 -1;
    // +X +Y bars
    intersection() {
        rotate([0,0,30])
            bars(gap, width, numBars);
        square([outerDiameter, outerDiameter], center=false);
    }
    // +X -Y bars
    intersection() {
        rotate([0,0,-30])
            bars(gap, width, numBars);
        translate([0,-outerDiameter])
        square([outerDiameter, outerDiameter], center=false);
    }
    // -X bars
    rotate([0,0,180])
        bars(gap, width, numBars);
}

$fn=180;
module bahtinov2D() {
    R = aperture/2;
    difference() {
        union() {
            difference() {            // makes circular mask bars
                circle(r=R);
                bahtinovBars(gap,R);
            }

            difference() {                  // makes the outer margin
                circle(r=outerDiameter/2);
                circle(r=R);
            }

            if (centerHoleDiameter > 0 && centerHoleDiameter < outerDiameter) {
                circle(r=centerHoleDiameter/2+3);
            }

            // Add horizontal and vertical structural bars:
            square([gap, 2*(R-1)], center=true);
            translate([R/2, 0])
                square([R-1, gap], center=true);
        }

        // subtract center hole if needed:
        if (centerHoleDiameter > 0 && centerHoleDiameter < outerDiameter) {
            circle(r=centerHoleDiameter/2);
        }
    }
}

union() {
    linear_extrude(height=2) bahtinov2D();
    // add a little handle
    translate([outerDiameter/2-gap,0,0]) cylinder(r=gap/2, h=18);
}

```


[User]
Okay, I need a parametric OpenSCAD design for a universal LCD mounting mask. It should be highly configurable, allowing for different LCD module sizes, thicknesses, and mounting hole positions. I'd also like the option to include a protective glass layer, and have the design be easily customized for different screw hole shapes.

Here's a breakdown of the features I'm looking for and their corresponding parameters:

**General Model Visibility:**

*   **`show_mask`**: (Boolean, 0/1) Whether to display the mask. Default: 1 (yes)
*   **`show_display_module`**: (Boolean, 0/1) Whether to display the LCD module. Default: 1 (yes)
*   **`show_protective_glass`**: (Boolean, 0/1) Whether to display the protective glass. Default: 1 (yes)
*   **`show_mounting_plate`**: (Boolean, 0/1) Whether to display the mounting plate. Default: 1 (yes)
*   **`show_disasembled`**: (Boolean, 0/1) Whether to show the components disassembled. Default: 1 (yes)
*   **`slide_display_module`**: (Float) Vertical slide distance for the LCD module when disassembled.
*   **`slide_protective_glass`**: (Float) Vertical slide distance for the protective glass when disassembled.
*   **`slide_mounting_plate_module`**: (Float) Vertical slide distance for the mounting plate when disassembled.

**LCD Display Parameters:**

*   **`lcd_module_model_id`**: (String) A text identifier for the LCD module, to be printed on the mask itself.
*   **`lcd_pcb_width`**: (Float) Width of the LCD module's PCB.
*   **`lcd_pcb_height`**: (Float) Height of the LCD module's PCB.
*  **`lcd_pcb_thickness`**: (Float) Thickness of the LCD module's PCB.
*   **`display_width`**: (Float) Width of the LCD display area.
*   **`display_height`**: (Float) Height of the LCD display area.
*   **`display_thickness`**: (Float) Thickness of the LCD display.
*   **`lcd_display_glass_width`**: (Float) Width of the LCD display glass.
*   **`lcd_display_glass_height`**: (Float) Height of the LCD display glass.
*   **`display_pcb_vertical_distance`**: (Float) Vertical distance of the display from the PCB's bottom-left corner.
*   **`display_pcb_horizontal_distance`**: (Float) Horizontal distance of the display from the PCB's bottom-left corner.
*   **`screw_hole_distance_to_pcb`**: (Float) Distance of mounting screw holes from the PCB edges.

**Mounting Plate Parameters:**

*   **`mounting_plate_thickness`**: (Float) Thickness of the mounting plate.
*   **`mounting_plate_overlap`**: (Float) Overlap of the mounting plate beyond the mask.

**Protective Glass Parameters:**

*   **`protective_glass_width`**: (Float) Width of the protective glass.
*   **`protective_glass_height`**: (Float) Height of the protective glass.
*   **`protective_glass_thickness`**: (Float) Thickness of the protective glass.

**Mask Parameters:**

*   **`front_plate_overlap`**: (Float) How much the front plate overlaps the mounting hole.
*   **`front_plate_thickness`**: (Float) Thickness of the front plate.
*   **`wall_clearance_to_pcb_param`**: (Float) Clearance between mask wall and PCB
*   **`vertical_wall_thickness`**: (Float) Thickness of the vertical walls.
*   **`vertical_side_wall_height_param`**: (Float) Height of the side vertical walls.
*   **`vertical_bracket_wall_height_param`**: (Float) Height of the bracket vertical walls
*   **`side_vertical_walls`**: (Boolean, 0/1) Whether to include side vertical walls. Default: 1 (yes)
*   **`display_hole_match`**: (Integer, 0/1) Should the display hole match the LCD module or the LCD glass size? 0 = LCD Module, 1 = LCD Glass. Default: 1 (LCD Glass)
*   **`display_hole_with_protective_glass`**: (Boolean, 0/1) Should the display hole include a notch for protective glass? Default: 1 (yes)
*   **`bracket_thickness`**: (Float) Thickness of the mounting brackets.
*   **`bracket_length`**: (Float) Length of the mounting brackets.
*   **`screw_distancer_profile`**: (Integer, 0/1/2) Shape of the screw distancers: 0 = circle, 1 = square, 2 = hexagon. Default: 0 (circle).
*   **`screw_distancer_width_param`**: (Float) Width/diameter of the screw distancer.
*   **`screw_distancer_hole_diameter`**: (Float) Diameter of the screw hole in the distancer.
*   **`aligment_tabs`**: (Boolean, 0/1) Whether to include PCB alignment tabs. Default: 1 (yes)
*  **`mask_hole_rounding_param`**: (Integer) Round mounting plate hole dimensions to nearest multiple of value. Default 5
*  **`display_clearance_to_mask`**: (Float) Clearance between display and mask
*  **`mask_clearance_to_mounting_hole`**: (Float) Clearance between mask and mounting hole
*  **`inscript_model_id`**: (Boolean, 0/1) Should the model ID be inscribed on the mask? Default: 1 (yes)
*  **`inscription_font_size`**: (Float) The font size of the inscription. Default: 4

**Key Design Considerations:**

*   The design should use a modular approach for readability.
*   The model should include the option to show all components or just the mask
*   The script should perform basic error checking, such as ensuring front plate isn't too thick for the display
*   The text for the model ID should be added by linear extrude to an inner face so the text is on the "inside" of the finished part.
*   If `display_hole_match` is set to 1 (LCD Glass), the hole should have a deeper cutout to accommodate the entire display unit. if `display_hole_with_protective_glass` is true the cutout should also include a smaller pocket for the protective glass.

I believe that covers everything. I'd like to use the standard OpenSCAD built-in libraries, so there shouldn't be any special external libraries required. I'm looking forward to seeing the generated code!

[Assistant]
universal-lcd-mask.scad:
```
/*

UNIVERSAL LCD MASK

Predrag Supurovic
https://pedja.supurovic.net

Version 1.7

https://pedja.supurovic.net/3d-model-universal-mounting-mask-for-lcd-modules/

https://www.thingiverse.com/thing:4615029
https://www.prusaprinters.org/prints/42320-universal-mounting-mask-for-lcd-modules
https://www.myminifactory.com/object/3d-print-universal-mounting-mask-for-lcd-modules-137183

UPDATE 2020-10-22

Added parameter ofr LCD identification that is printed on model
Mask model ID is inscripted on inner side of mask
Added distancer tabs to prevent flexing mask
Added PCB alignment tabs
Improved LCD visibility (shalower hole for LCD display)
Fixed protective glass notch position

UPDATE 2020-10-20

Added parameters to set LCD module display glass dimensions
Added option to have protection glass between mask and display module
Added some new parameters
Fixed minor calculation bugs

UPDATE 2020-10-16

Found out OpenSCAD bug: if parameter has default value as integer it is not possible to enter decimal values in customizer. So I had to set decimal values for all parameters that needed this fixed.

UPDATE 2020-10-12

Added option to change shape of screw distancers. now user can select circle, square and hexagon base shape.

UPDATE 2020-10-05

I found out that Thingverse Customizer does not support boolean parameter (they should be presented checkboxes) so I had to update file to use numeric values presented as yes/no in customizer. It is ugly but at least it works.

RELEASED 2020-10-05

The first public version

*/

/* PARAMETERS */

/* [Hidden] */
//$vpt = [0, 0, 0];
//$vpr = [-45, 120, 0];
//$vpr = [-60, 0, 0];

/* [Models Visibility] */

//Show mask
show_mask = 1; // [0:no, 1:yes]

//Show display module
show_display_module = 1; // [0:no, 1:yes]

//Show protective glass
show_protective_glass = 1; // [0:no, 1:yes]

//Show mounting plate
show_mounting_plate = 1; // [0:no, 1:yes]

//Show disasembled
show_disasembled = 1; // [0:no, 1:yes]

// Slide display module verticaly
slide_display_module = 60;

// Slide protective glass verticaly
slide_protective_glass = 30;

// Slide mounting plate module verticaly
slide_mounting_plate_module = 80;

/* [LCD Display Parameters] */

//LCD Module Model ID
lcd_module_model_id = "C1627A";

// Width of display PCB
lcd_pcb_width = 80.01;
// Height of display PCB
lcd_pcb_height = 36.01;
// Thickness of display PCB
lcd_pcb_thickness = 1.60;

// Width of LCD display part
display_width = 71.30;
// Height of LCD display part
display_height = 26.30;
// Thickness of LCD display part
display_thickness = 8.61;

// Width of LCD display glass
lcd_display_glass_width = 64.50;
// Height of LCD display glass
lcd_display_glass_height = 15.50;

// Display vertical distance according to bottom-left corner of PCB
display_pcb_vertical_distance = 4.80;
// Display horizontal distance according to bottom-left corner of PCB
display_pcb_horizontal_distance = 4.30;
// Distance of screw hole center to PCB edge
screw_hole_distance_to_pcb = 2.5;

/* [Mounting Plate Parameters] */

// Mounting plate thickness
mounting_plate_thickness = 1.21;

// Mounting plate overlap
mounting_plate_overlap = 50.01;

/* [Protective Glass Parameters] */

// Protective Glass Width
protective_glass_width = 70.01;

// Protective Glass Height
protective_glass_height = 20.01;

// Protective Glass Thickness
protective_glass_thickness = 2.01;

/* [Mask Parameters] */

// How much front plate overlaps mounting hole
front_plate_overlap = 2.01;
// Front plate thickness
front_plate_thickness = 1.21;
// Verticall walls thickness

// Mask wall clearance to PCB
wall_clearance_to_pcb_param = 2.01;

vertical_wall_thickness = 2.01;
// Verticall side wall height
vertical_side_wall_height_param = 0.01;
// Verticall bracket wall height
vertical_bracket_wall_height_param = 0.01;

// Should mask have side vertical walls?
side_vertical_walls = 1; // [0:no, 1:yes]

// Display hole matches LCD module or just LCD glass size
display_hole_match = 1; // [0:LCD module, 1: LCD glass]

// Should dislay hole have notch for protective glass
display_hole_with_protective_glass = 1; //[0:without glass, 1:with glass]

// Bracket thickness
bracket_thickness = 2.01;
// Bracket length
bracket_length = 5.01;

screw_distancer_profile = 0; //[0:circle, 1:square, 2:hexagon]
// Screw distancer width / diameter
screw_distancer_width_param = 6.10;
// Screw distancer hole diameter
screw_distancer_hole_diameter = 2.01;

// Create PCB aligment tabs
aligment_tabs = 1; //[0:no, 1:yes]

// Round mounting plate hole dimensions to:
mask_hole_rounding_param = 5;

// Clearance between display and mask plate
display_clearance_to_mask = 0.20;

// Clearance between mask and mounting hole
mask_clearance_to_mounting_hole = 0.20;

// Inscript mask model ID
inscript_model_id = 1; //[0:no, 1:yes]

// Inscription font size
inscription_font_size = 4; //[2:0.1:6]

/*
--------- do not edit further in code ------------
*/

/* [Hidden] */

$fa = 1;
$fs = 0.4;
$t = 0;

assert (front_plate_thickness < (display_thickness - 1), "ERROR: Front plate is too thick for this display size!");

pcb_color = "green";

display_color = "DarkSlateGray";

lcd_display_color = "DarkSeaGreen";

minimum_front_plate_thickness = 0.6;

protective_glass_color = "grey";
protective_glass_opacity = 0.1;
protective_glass_notch_thickness = 0.6;
protective_glass_notch_clearance = 0.4;

mask_color = "black";

// normalize mask hole rounding
mask_hole_rounding = (mask_hole_rounding_param < 1) ? 1 : floor (mask_hole_rounding_param);

// calculate display hole width
display_hole_width =
  display_hole_match ?
    lcd_display_glass_width + (display_clearance_to_mask * 2) :
    display_width + (display_clearance_to_mask * 2);

// calculate display hole height
display_hole_height =
    display_hole_match ?
    lcd_display_glass_height + (display_clearance_to_mask * 2) :
    display_height + (display_clearance_to_mask * 2);

// calculate mask hole width
calc_mask_hole_width =
   (mask_clearance_to_mounting_hole*2) +
   lcd_pcb_width +
   (vertical_wall_thickness + wall_clearance_to_pcb_param) * 2;

mask_hole_width = ceilx (calc_mask_hole_width, mask_hole_rounding);

// calculate width wall clearance
width_wall_clearance_to_pcb = ((mask_hole_width - calc_mask_hole_width)/2)+wall_clearance_to_pcb_param;

// calculate mask hole width
calc_mask_hole_height =
   mask_clearance_to_mounting_hole*2 +
   lcd_pcb_height +
   (vertical_wall_thickness + wall_clearance_to_pcb_param) * 2;

mask_hole_height = ceilx (calc_mask_hole_height, mask_hole_rounding);

// calculate width wall clearance
height_wall_clearance_to_pcb = ((mask_hole_height - calc_mask_hole_height)/2)+wall_clearance_to_pcb_param;

front_plate_width = front_plate_overlap*2 + mask_hole_width;

front_plate_height = front_plate_overlap*2 + mask_hole_height;

// Screw distancer height
/*
screw_distancer_height = display_thickness - front_plate_thickness +
    (display_hole_with_protective_glass ?
       protective_glass_notch_thickness + protective_glass_thickness :
       0);
*/
screw_distancer_height = display_thickness - front_plate_thickness +
  (display_hole_match == 0 ? 0 :
            (display_hole_with_protective_glass ?
               protective_glass_notch_thickness + protective_glass_thickness :
               protective_glass_notch_thickness)
        );

screw_distancer_diameter_to_display = sqrt (pow (display_pcb_vertical_distance - screw_hole_distance_to_pcb,2) + pow (display_pcb_horizontal_distance - screw_hole_distance_to_pcb,2)) * 2;
//echo ("screw_distancer_diameter_to_display:", screw_distancer_diameter_to_display);

screw_distancer_width_to_display = max (display_pcb_vertical_distance - screw_hole_distance_to_pcb, display_pcb_horizontal_distance- screw_hole_distance_to_pcb) * 2;
//echo ("screw_distancer_width_to_display:", screw_distancer_width_to_display);

screw_distancer_hexagon_diameter_to_display = screw_distancer_diameter_to_display;
//echo ("screw_distancer_hexagon_diameter_to_display:", screw_distancer_hexagon_diameter_to_display);

// left wall position
wall_x1 = (front_plate_overlap);
wall_y1 = (front_plate_overlap);

// display module position
display_module_x = wall_x1 + vertical_wall_thickness + width_wall_clearance_to_pcb;
display_module_y = wall_y1 + vertical_wall_thickness + height_wall_clearance_to_pcb;

// Display module distance from front plate
display_module_z = show_disasembled ?
        slide_display_module :
        (display_hole_match == 0 ? display_thickness :
            display_thickness + (display_hole_with_protective_glass ?
               protective_glass_notch_thickness + protective_glass_thickness :
               protective_glass_notch_thickness)
        );

// Protective glass disance from front plate
protective_glass_z = show_disasembled ? slide_protective_glass : protective_glass_notch_thickness+0.1;

// Mounting plate dsitance from front plate
mounting_plate_module_z = show_disasembled ? slide_mounting_plate_module : 0;

bracket_height = bracket_thickness * (sqrt(2));
minimal_bracket_wall_height = bracket_height + 2 + mounting_plate_thickness;

minimal_side_wall_height =  2;

vertical_side_wall_height = (vertical_side_wall_height_param > minimal_side_wall_height) ? vertical_side_wall_height_param : minimal_side_wall_height;

vertical_bracket_wall_height = (vertical_bracket_wall_height_param > minimal_bracket_wall_height) ? vertical_bracket_wall_height_param : minimal_bracket_wall_height;

translate([-front_plate_width/2,-front_plate_height/2,0]) {

  if (show_protective_glass && (display_hole_match == 1) && display_hole_with_protective_glass) {
    protective_glass (display_module_x,display_module_y,protective_glass_z);
  }

    if (show_display_module) {
        lcd_module (display_module_x,display_module_y,display_module_z);
    }

    if (show_mounting_plate) {
        mounting_plate(0,0,mounting_plate_module_z);
    }

    if (show_mask) {
        mask();
    }

}

echo();
echo ("Mounting hole width: ", mask_hole_width);
echo ("Mounting hole height: ", mask_hole_height);
echo();

/* MASK */
module mask (translate_x = 0, translate_y = 0, translate_z = 0) {
    translate ([translate_x,translate_y,translate_z]) {
        union() {
            color (mask_color) {

                // front plate
                front_plate();

                // side vertical walls

                if (side_vertical_walls) {

                    translate ([wall_x1,wall_y1,front_plate_thickness])
                      cube ([vertical_wall_thickness,mask_hole_height,vertical_side_wall_height]);

                    translate ([wall_x1+mask_hole_width-vertical_wall_thickness,wall_y1,front_plate_thickness])
                      cube ([vertical_wall_thickness,mask_hole_height,vertical_side_wall_height]);

                }

                // front vertical wall
                translate ([wall_x1,wall_y1,front_plate_thickness])
                  cube ([mask_hole_width,vertical_wall_thickness,vertical_bracket_wall_height]);

                // back vertical wall
                translate ([wall_x1,wall_y1+mask_hole_height-vertical_wall_thickness,front_plate_thickness])
                  cube ([mask_hole_width,vertical_wall_thickness,vertical_bracket_wall_height]);

                // screw holders
                translate ([display_module_x,display_module_y,front_plate_thickness])
                all_lcd_screw_holders();

                //brackets
                bracket_position = mask_hole_width/3-bracket_length/2;

                translate ([wall_x1+bracket_position+bracket_length/2,wall_y1,front_plate_thickness + mounting_plate_thickness  - 0.2 ])
                    bracket(bracket_thickness, bracket_length);

                translate ([wall_x1+bracket_position*2+bracket_length/2,wall_y1,front_plate_thickness + mounting_plate_thickness - 0.2 ])
                    bracket(bracket_thickness, bracket_length);

                translate ([wall_x1+bracket_position+bracket_length/2,wall_y1+mask_hole_height,front_plate_thickness + bracket_thickness*2 + mounting_plate_thickness - 0.2 ])
                rotate ([180,0,0])
                    bracket(bracket_thickness, bracket_length);

                translate ([wall_x1+bracket_position*2+bracket_length/2,wall_y1+mask_hole_height,front_plate_thickness + bracket_thickness*2 + mounting_plate_thickness  - 0.2 ])
                rotate ([180,0,0])
                    bracket(bracket_thickness, bracket_length);
            }

        }
    }
}

module front_plate (translate_x = 0, translate_y = 0, translate_z = 0) {
    translate ([translate_x,translate_y,translate_z]) {
        difference() {
            cube([front_plate_width, front_plate_height, front_plate_thickness]);

            lcd_hole (0,0,0);

            if (display_hole_match) {
              if (display_hole_with_protective_glass){
                protective_glass_notch (display_module_x,display_module_y,0);
                lcd_display_notch(display_module_x,display_module_y,protective_glass_notch_thickness+protective_glass_thickness);
              } else {
                lcd_display_notch(display_module_x,display_module_y,protective_glass_notch_thickness);
              }
            }

        }

        if (inscript_model_id) {
          // text
          text_hole_type = display_hole_match ? "G" : "M";
          text_protective_glass_type = display_hole_match && display_hole_with_protective_glass ? "G" : "";
          engraving_text = str (lcd_module_model_id, "  ", text_hole_type, " ", text_protective_glass_type);

          translate ([front_plate_width/2,wall_y1+vertical_wall_thickness+1.5,front_plate_thickness])
            linear_extrude(0.9)
              text (engraving_text, size=inscription_font_size, font="Liberation Sans:style=Bold", halign="center", spacing=1.2);
        }

    }
}

module lcd_hole (translate_x = 0, translate_y = 0, translate_z = 0) {
    translate ([translate_x,translate_y,translate_z]) {
         x_diff = display_hole_match ? (display_width-lcd_display_glass_width)/2 : 0;
         y_diff = display_hole_match ? (display_height-lcd_display_glass_height)/2: 0;

         translate ([display_module_x+display_pcb_horizontal_distance+x_diff-display_clearance_to_mask, display_module_y+display_pcb_vertical_distance+y_diff-display_clearance_to_mask,-0.1]) {
            color (lcd_display_color) {
              //if ((display_hole_match == 0) || (display_hole_with_protective_glass == 0)) {
              //  cube([display_hole_width, display_hole_height, front_plate_thickness+0.2]);
              //  pyramid(display_hole_width, display_hole_height,front_plate_thickness+0.2);
              //} else {
                cube([display_hole_width, display_hole_height, front_plate_thickness+0.2]);

              //}
            }
          }
        }

}

module pyramid (width, length, height) {

  scale_width = (width+ (2 * height)) / width;
  scale_length = (length + (2 * height)) / length;

//echo ("scale_width:", scale_width);
//echo ("scale_length:", scale_length);
  rotate ([180,0,0])
    translate ([width/2,-length/2,-height])
      linear_extrude(height, center=false, scale=[scale_width, scale_length]) {
        translate ([-width/2,-length/2])
          polygon (points=[[0,0],[0,length], [width,length], [width,0]]);
      }
}

module bracket (thickness = 4, length = 40) {

  a = thickness / (sqrt(2) / 2);
  translate ([0,0,thickness*2])
     rotate ([45+180,0,0])
        prism(length, a, a);

}

/* LCD MODULE */
module lcd_module (translate_x = 0, translate_y = 0, translate_z = display_thickness) {
    translate ([translate_x,translate_y,translate_z]) {
        union() {
            color (pcb_color) {

                difference() {
                    cube ([lcd_pcb_width, lcd_pcb_height, lcd_pcb_thickness]);

                            //all_lcd_screw_holders (0,0,-display_thickness);

                     lcd_screw_hole (screw_hole_distance_to_pcb, screw_hole_distance_to_pcb, 0);
                    lcd_screw_hole (lcd_pcb_width-screw_hole_distance_to_pcb, screw_hole_distance_to_pcb, 0);
                    lcd_screw_hole (lcd_pcb_width-screw_hole_distance_to_pcb, lcd_pcb_height-screw_hole_distance_to_pcb, 0);
                    lcd_screw_hole (screw_hole_distance_to_pcb, lcd_pcb_height-screw_hole_distance_to_pcb, 0);
                }
            }

            lcd_display(display_pcb_horizontal_distance,display_pcb_vertical_distance,-display_thickness);
        }

    }
}

module lcd_display (translate_x = 0, translate_y = 0, translate_z = 0) {
    translate ([translate_x,translate_y,translate_z-0.1])

        union () {
          color (display_color) {
            cube([display_width, display_height, display_thickness+0.1]);
          }
          translate ([(display_width-lcd_display_glass_width)/2,(display_height-lcd_display_glass_height)/2,-0.1]) {
            color (lcd_display_color) {
              cube([lcd_display_glass_width, lcd_display_glass_height, 0.2]);
            }
          }
        }

}

module lcd_display_notch (translate_x = 0, translate_y = 0, translate_z = 0) {
  translate ([translate_x,translate_y,translate_z]) {

    display_notch_width = display_width + 0.4;
    display_notch_height = display_height + 0.4;

    translate ([
      display_pcb_horizontal_distance - (display_notch_width-display_width)/2,
      display_pcb_vertical_distance - (display_notch_height-display_height)/2,
      0
    ]) {

      cube([display_notch_width, display_notch_height, display_thickness+0.2]);

    }

  }

}

module protective_glass (translate_x = 0, translate_y = 0, translate_z = 0) {

  translate ([translate_x,translate_y,translate_z]) {

    //translate ([((lcd_pcb_width - protective_glass_width)/2),((display_width - protective_glass_w)/2),-0.1]) {
    translate ([display_pcb_horizontal_distance + ((display_width - protective_glass_width)/2), display_pcb_vertical_distance+((display_height - protective_glass_height)/2),-0.1]) {

      color (protective_glass_color, protective_glass_opacity) {
        cube([protective_glass_width, protective_glass_height, protective_glass_thickness+0.1]);
      }
    }

  }
}

module protective_glass_notch (translate_x = 0, translate_y = 0, translate_z = 0) {

  translate ([translate_x,translate_y,translate_z]) {

//(front_plate_thickness-minimum_front_plate_thickness)

    translate ([
      display_pcb_horizontal_distance + ((display_width - protective_glass_width - protective_glass_notch_clearance)/2),
      display_pcb_vertical_distance + ((display_height - protective_glass_height - protective_glass_notch_clearance)/2),
      protective_glass_notch_thickness
    ]) {

      color (protective_glass_color) {
        cube([protective_glass_width+protective_glass_notch_clearance, protective_glass_height+protective_glass_notch_clearance, front_plate_thickness]);
        //cube([protective_glass_width, protective_glass_height, front_plate_thickness]);
      }
    }
  }

  echo ("Protective glass width: ", round (protective_glass_width));
  echo ("Protective glass height: ", round (protective_glass_height));
  echo ("Protective glass thickness: ", round (protective_glass_thickness));
  echo();
}

module all_lcd_screw_holders (translate_x = 0, translate_y = 0, translate_z = 0) {
      translate ([translate_x,translate_y,translate_z]) {

            lcd_screw_holder(screw_hole_distance_to_pcb,screw_hole_distance_to_pcb);
            lcd_screw_holder(lcd_pcb_width-screw_hole_distance_to_pcb,screw_hole_distance_to_pcb);
            lcd_screw_holder(lcd_pcb_width-screw_hole_distance_to_pcb,lcd_pcb_height-screw_hole_distance_to_pcb);
            lcd_screw_holder(screw_hole_distance_to_pcb,lcd_pcb_height-screw_hole_distance_to_pcb);

            // center distancers
            if (display_hole_match == 0) {

                distancer_thickness = 0.5;
                distancer_width = 5;

                translate ([(lcd_pcb_width-distancer_width)/2,display_pcb_vertical_distance-distancer_thickness-0.3,0])
                  cube ([distancer_width, distancer_thickness,screw_distancer_height]);

                translate ([(lcd_pcb_width-distancer_width)/2,display_pcb_vertical_distance+display_hole_height-0.1,0])
                  cube ([distancer_width, distancer_thickness,screw_distancer_height]);

            }

            if (aligment_tabs) {
              lcd_alignment_tab (screw_hole_distance_to_pcb,screw_hole_distance_to_pcb,0);
              lcd_alignment_tab (-lcd_pcb_width+screw_hole_distance_to_pcb,-lcd_pcb_height+screw_hole_distance_to_pcb,180);
            }

    }
}

module lcd_alignment_tab (x = 0, y = 0, rotation = 0) {
  translate ([screw_hole_distance_to_pcb-x,screw_hole_distance_to_pcb-y]) {
            align_tab_width = 2;
            align_tab_height = screw_distancer_height + 2;
            align_tab_thickness = 1.5;
            rotate (rotation) {
              translate ([screw_hole_distance_to_pcb/2,-align_tab_thickness-0.2,0]) {
                cube ([align_tab_width, screw_hole_distance_to_pcb+align_tab_thickness-screw_distancer_hole_diameter/2,screw_distancer_height]);
                cube ([align_tab_width, align_tab_thickness,align_tab_height]);
              }

              translate ([-align_tab_thickness-0.2,screw_hole_distance_to_pcb+align_tab_width/2,0]) {
                rotate (-90) {
                  cube ([align_tab_width, screw_hole_distance_to_pcb+align_tab_thickness-screw_distancer_hole_diameter/2,screw_distancer_height]);
                  cube ([align_tab_width, align_tab_thickness,align_tab_height]);
                }
              }
            }
  }
}

module lcd_screw_holder (translate_x = 0, translate_y = 0, translate_z = 0) {
    translate ([translate_x,translate_y,translate_z]) {

        difference() {
            // circle
            if (screw_distancer_profile == 0) {
                screw_distancer_diameter = min (screw_distancer_diameter_to_display, screw_distancer_width_param);
                cylinder(d=screw_distancer_diameter, h=screw_distancer_height);
            }
            // square
            if (screw_distancer_profile == 1) {
                screw_distancer_width = min (screw_distancer_width_to_display, screw_distancer_width_param);
                translate ([-screw_distancer_width/2, -screw_distancer_width/2, 0]) {
                    cube([screw_distancer_width, screw_distancer_width, screw_distancer_height]);
                }
            }
            // hexagon
            if (screw_distancer_profile == 2) {
                screw_distancer_hexagon_diameter = min (screw_distancer_hexagon_diameter_to_display, screw_distancer_width_param);
                cylinder(d=screw_distancer_hexagon_diameter, h=screw_distancer_height, $fn=6);
            }
            lcd_screw_hole();
        }
    }
}

module lcd_screw_hole (translate_x = 0, translate_y = 0, translate_z = 0) {
    translate ([translate_x,translate_y,translate_z-0.1]) {
      cylinder(d=screw_distancer_hole_diameter, h=screw_distancer_height+0.2);
    }
}

module mounting_plate(translate_x = 0, translate_y = 0, translate_z = 0) {

    translate ([translate_x,translate_y,translate_z-0.1]) {

          difference() {
              translate ([-mounting_plate_overlap/2,-mounting_plate_overlap/2,front_plate_thickness])
                cube ([front_plate_width+mounting_plate_overlap, front_plate_height+mounting_plate_overlap, front_plate_thickness]);

              translate ([wall_x1, wall_y1, (front_plate_thickness)-0.1])
                cube ([mask_hole_width, mask_hole_height, front_plate_thickness+0.2]);

          }
      }

}

module prism(length, width, height){
       polyhedron(
               points=[[0,0,0], [length,0,0], [length,width,0], [0,width,0], [0, width, height], [length, width, height]],
               faces=[[0,1,2,3],[5,4,3,2],[0,4,5,1],[0,3,4],[5,2,1]]
               );
   }

function ceilx(value, x) = ceil(value/x)*x;
```


[User]
I need a parametric OpenSCAD design for a replacement router base plate. I've included a description and an example SCAD file to help you understand what I'm looking for.

The base plate is essentially a flat disk with several features cut out: a central hole, countersunk mounting screw holes arranged in a circle, and a series of circular cutouts also arranged in a circle. The key parameters are the outer diameter of the base, the diameter of the central hole, the thickness of the plate, the width of a border or mounting surface, the radius of the mounting screw circle, the number of mounting screws, the diameters of the screw hole and its countersunk portion, and finally, the minimum gap between the inner circle and outer cutouts. The circular cutouts are determined by their radius and a pattern circle radius. They are arrayed with the minimum gap between them.

The goal is to create a flexible design that can be customized for different routers. I’m not looking for an exact copy of the example, but something that can be modified with the provided parameters. I would prefer it if you used the built-in OpenSCAD primitives, rather than external libraries, unless there's a really compelling reason to use one for something like generating an array.

Could you provide me with one or more .scad files that implement this design? Please also include some comments in the code so that it's easy to read.

[Assistant]
router_base.scad:
```
 // Customizable Router Base Plate
 // The default values are for a Porter-Cable Model 690.
 // David O'Connor         david@lizardstation.com
 //  CC-BY-3.0 Creative Commons Attribution license applies
 // 6 October 2020

// The diameter of the central hole
ID =25;
// Minimum gap between holes
minimumHoleGap = 4;
//  Outside diameter of base
 OD = 145.25;
// Base plate thickness
thickness = 6;
// The width of base mounting surface
borderWidth = 25;
// The radius of the pattern of mounting screws
screwHolePatternRadius = 59.71;
// Number of screw holes
numScrewHoles = 3;
// Diameter of the screw hole countersunk portion
screwHoleCsDia = 10.8;
// Diameter of the screw holes
screwHoleDia = 6.2;

cutoutHoleRadius = (OD/2 - borderWidth - ID/2 - minimumHoleGap) / 2;
cutoutPatternRadius = (ID/2 +minimumHoleGap+ (OD/2 - borderWidth))/2;
dth = asin((0.5*minimumHoleGap +cutoutHoleRadius) / cutoutPatternRadius) * 2 ;
numCutouts = floor(360 / dth);

difference() {

    cylinder($fa = 2, r = OD / 2, h = thickness, center = true);
    {
        // The hole in the center
        cylinder(r = ID / 2, h = 2 * thickness, center = true);

        // The countersunk mounting holes
        for (i = [1:numScrewHoles]) {
            th = i * 360 / numScrewHoles;
            x = cos(th) * screwHolePatternRadius;
            y = sin(th) * screwHolePatternRadius;
            translate([x, y, 0]) {
                cylinder($fs = 0.2 , r = screwHoleDia/2, h = 2 * thickness, center = true);
                r1 = screwHoleDia / 2;
                r2 = screwHoleCsDia/2;
                translate([0, 0, -r2 + thickness/2])
                cylinder(r1 = 0, r2 = 2*r2, h = 2*r2, center = false);
            }
        }

        // The circular pattern of holes
        for (j = [1:numCutouts]) {
            thc = j * 360 / numCutouts;
            x2 = cos(thc) * cutoutPatternRadius;
            y2 = sin(thc) * cutoutPatternRadius;
            translate([x2, y2, 0]) {
                cylinder($fn = 40, r = cutoutHoleRadius, h = 2 * thickness, center = true);
            }
        }
    }
}

```


[User]
I'd like to design a modular wall shelf using OpenSCAD, and I'm hoping you can help me create a parametric model. The idea is to have pieces that interlock with each other, almost like a puzzle, so you can combine them to create larger shelves.

Here's what I'm envisioning:

*   **Basic Structure:** The shelf will be composed of interlocking, rectangular pieces. Each piece will have a specific depth, and the width and height will be adjustable.
*   **Interlocking Mechanism:** The edges of each piece will have a "puzzle-like" pattern, with male and female connectors that allow them to slot together. These should be designed with a small tolerance to ensure they fit snugly. Think of a standard jigsaw-style connector, but with a smaller, more refined design.
*   **Parametric Dimensions:** I want to be able to control the overall width and height of a single basic piece, as well as the depth. The overall width and height values should refer to the outside dimensions. I'd also like to define a tolerance value that will affect the connectors to provide a snug fit.
*   **Modular Design:** The shelf system should allow to be expanded with multiple of the same piece. For example, I want a 3x2 shelf structure (3 pieces wide and 2 pieces high) to be relatively easily defined using OpenSCAD primitives and modules.
*   **Mounting:** A single piece should have a hole on the back, for mounting to a wall. These "hole pieces" should be at least one of the basic rectangular pieces that is attached to at least two other pieces, to ensure that they hold together tightly when mounted.
*   **Orientation:** The pieces cannot be rotated, meaning that their orientation should be fixed. The male/female connectors should be on the right places to allow the shelf to interlock without rotations.

I think I'd need modules for the following:
    *  `top_left_corner`: Creates a top left corner piece with male and female connectors.
    *  `bottom_left_corner`: Creates a bottom left corner piece with male and female connectors.
    * `top_right_corner`: Creates a top right corner piece with male and female connectors.
    * `bottom_right_corner`: Creates a bottom right corner piece with male and female connectors.
    *  `top_edge`: Creates a top edge piece with male and female connectors on either side.
    * `bottom_edge`: Creates a bottom edge piece with male and female connectors on either side.
    * `left_edge`: Creates a left edge piece with male and female connectors on either side.
    * `right_edge`: Creates a right edge piece with male and female connectors on either side.
   * `piece`: Combines the corner and edge modules to form a single rectangular puzzle piece.

I imagine I could use a loop, or multiple loops, to arrange multiple of these pieces, as well. So, having a clear way to call this modular piece multiple times to form a larger structure is necessary.

Could you help me generate the OpenSCAD code for this? I'm not sure which libraries you might want to use, but I'm happy to use them if they make this design easier.

[Assistant]
puzzle_shelf.scad:
```
width = 80; // [0:20:200]
height = 60; // [0:20:200]
depth = 20;

tolerance = 0.4;

h_tol = tolerance / 2;
units_horizontal = floor(width / 20) - 1;
units_vertical = floor(height / 20) - 1;

module top_left_corner() {
    translate([-7.5 + h_tol, -1 + h_tol, 0])
        cube([7.5 - h_tol, 3.5 - tolerance, depth]);
    translate([-7.5 + h_tol, -4 + h_tol, 0])
        cube([2.5 - tolerance, 6.5 - tolerance, depth]);
    translate([-2.5 + h_tol, -10, 0])
        cube([2.5 - h_tol, 10, depth]);
}

module bottom_left_corner() {
    translate([-2.5 + h_tol, -2.5 + h_tol, 0])
        cube([12.5 - h_tol, 2.5 - h_tol, depth]);
    translate([-2.5 + h_tol, -7.5 + h_tol, 0])
        cube([3.5 - tolerance, 7.5 - h_tol, depth]);
    translate([0, -7.5 + h_tol , 0])
        cube([4 - h_tol, 2.5 - tolerance, depth]);
    translate([-7.5 + h_tol, -7.5 + h_tol , 0])
        cube([7.5 - h_tol, 5 - tolerance, depth]);
}

module top_right_corner() {
    translate([-10, 0, 0])
        cube([12.5 - h_tol, 2.5 - h_tol, depth]);
    translate([-6 + h_tol, 0, 0])
        cube([8.5 - tolerance, 7.5 - h_tol, depth]);
    translate([-9 + h_tol, 5 + h_tol, 0])
        cube([9 - h_tol, 2.5 - tolerance, depth]);
}

module bottom_right_corner() {
    translate([0, -2.5 + h_tol, 0])
        cube([2.5 - h_tol, 12.5 - h_tol, depth]);
    translate([0, -2.5 + h_tol, 0])
        cube([7.5 - h_tol, 8.5 - tolerance, depth]);
    translate([5 + h_tol, 0, 0])
        cube([2.5 - tolerance, 9 - h_tol, depth]);
}

module top_edge() {
    translate([-10, 0, 0])
        cube([20, 2.5 - h_tol, depth]);
    translate([-6 + h_tol, 0, 0])
        cube([8.5 - tolerance, 7.5 - h_tol, depth]);
    translate([-9 + h_tol, 5 + h_tol, 0])
        cube([9 - h_tol, 2.5 - tolerance, depth]);
}

module right_edge() {
    translate([0, -10, 0])
        cube([2.5 - h_tol, 20, depth]);
    translate([0, -2.5 + h_tol, 0])
        cube([7.5 - h_tol, 8.5 - tolerance, depth]);
    translate([5 + h_tol, 0, 0])
        cube([2.5 - tolerance, 9 - h_tol, depth]);
}

module bottom_edge() {
    translate([-10, -2.5 + h_tol, 0])
        cube([20, 2.5 - h_tol, depth]);
    translate([0, -7.5 + h_tol , 0])
        cube([4 - h_tol, 2.5 - tolerance, depth]);
    translate([-7.5 + h_tol, -7.5 + h_tol , 0])
        cube([8.5 - tolerance, 7.5 - h_tol, depth]);
}

module left_edge() {
    translate([-7.5 + h_tol, -1 + h_tol, 0])
        cube([7.5 - h_tol, 8.5 - tolerance, depth]);
    translate([-7.5 + h_tol, -4 + h_tol, 0])
        cube([2.5 - tolerance, 9 - h_tol, depth]);
    translate([-2.5 + h_tol, -10, 0])
        cube([2.5 - h_tol, 20, depth]);
}

module piece() {
    top_left_corner();
    translate([0, -30, 0])
        bottom_left_corner();
    translate([30, -30, 0])
        bottom_right_corner();
    translate([30, 0, 0])
        top_right_corner();
    translate([10, 0, 0])
        top_edge();
    translate([0, -20, 0])
        left_edge();
    translate([20, -30, 0])
        bottom_edge();
    translate([30, -10, 0])
        right_edge();
}

top_left_corner();
translate([0, -10 - 20 * units_vertical, 0])
    bottom_left_corner();
translate([10 + 20 * units_horizontal, -10 - 20 * units_vertical, 0])
    bottom_right_corner();
translate([10 + 20 * units_horizontal, 0, 0])
    top_right_corner();

for (dx = [10:20:10 + 20 * units_horizontal - 1]) {
    translate([dx, 0, 0])
        top_edge();
    translate([dx + 10, -10 - 20 * units_vertical, 0])
        bottom_edge();
}

for (dy = [10:20:10 + 20 * units_vertical - 1]) {
    translate([0, -dy - 10, 0])
        left_edge();
    translate([10 + 20 * units_horizontal, -dy, 0])
        right_edge();
}
```