[User]
Hi! I'm trying to design some accessories for my microscope using OpenSCAD, and I could really use some help. I've been experimenting with darkfield microscopy and want to make a few custom parts.

First, I need a stand for a color filter that sits directly on the microscope's light source. The filter is a 32mm diameter circle, and I need the stand to lift it up a little so it doesn't touch the bulb, maybe 14mm high. The legs should be pretty stable, and I think three legs would work well. Maybe those legs can be 4mm thick and have a lip to help grab them? Basically, this part needs to hold a filter above a light source and be easy to pick up.

Second, I'd like a standoff to hold my mobile phone camera at the right distance from the microscope eyepiece. I'm using AMScope 20x eyepieces, which are about 28.5mm in diameter, and I think a cylindrical standoff about 16mm high would work. The wall of the cylinder should be about 2mm thick, and it would be good if there were some small "bumpers" on the outside to help keep it from sliding. Maybe four little rectangular protrusions around the outside?

Finally, I need a way to generate custom darkfield filters. These are essentially discs with an opaque central area. I'd like to make it flexible enough to adjust the overall size of the filter, the size of the obscuring area (maybe using an eclipsing-type setup with two offset circles), the thickness of the filter itself, and a frame around the filter's edge. It would also be useful to have support spokes to hold the central obscurer in place; I'd want to specify the number of spokes, their angle, thickness, and length. It should also allow for the spokes to be either solid or cut out of the filter itself. I need to specify the outside diameter of the filter, the thickness of the frame, the number of sides to the disc, the filter's thickness, the eclipse diameter, the x and y offsets of the two eclipse circles, number of spokes, spoke angle offset, spoke length, spoke width, spoke y offset, and spoke individual y offset.

I’d prefer for the OpenSCAD files to be parametric, so I can easily adjust all of these dimensions. I don’t need any specific library inclusion, just standard OpenSCAD. Let me know if you have any questions. Thanks!

[Assistant]
microscope-colorFilterHolder-V2.scad:
```
/*

Author : Sarah Cartwright
Description : A small stand to make it easy to place a colo(u)r filter on the lamp, needed when using the filter holder for the darkfield lenses.
Version : 2.00
Date: 2020/04/03

*/

$fn = 140;

standHeight = 14;
standLegThickness = 4;
filterDiameter = 32;
filterHeight = 3;

module makeWalledCylinder(diameterInner, thickness, height){
    difference(){
        cylinder(d = diameterInner + thickness, h = height);
        translate([0, 0, -0.5]) cylinder(d = diameterInner, h = height + 1);
    }
}

module makeLeg(){
    translate([0, standLegThickness / -2 + 0.3]) cylinder(d = standLegThickness, h = standHeight);
    translate([-1, 0, filterHeight]) cube([2, 4, 2]);
    translate([-1, 0, filterHeight]) cube([2, 2, 4]);

}

//cylinder(d = filterDiameter, h = standHeight);
translate([0, 0, 2]) makeWalledCylinder(diameterInner = filterDiameter + 4, thickness = standLegThickness, height = 3);
translate([0, (filterDiameter / -2) - 2, 0]) makeLeg();
rotate([0, 0, 120]) translate([0, (filterDiameter / -2) - 2, 0]) makeLeg();
rotate([0, 0, 240]) translate([0, (filterDiameter / -2) - 2, 0]) makeLeg();

```
mobilePhone-opticStandoff.scad:
```
/*

Author : Sarah Cartwright
Description : Occular standoff for obtaining a full field view using mobile phone. Using a pair makes it easier to hold the camera against the eyepieces.
Version : 1.00
Date: 2020/04/03

*/

/* [Eclipse obscurer] */
$fn = 100; // [5:1:200]
// Stand off height.
height = 16;
// Inside diameter of the tube.
diameter = 28.5;
// Thickness of the tube, added to the inside diameter.
thickness = 2;

cylinderWall(height = height, diameter = diameter, thickness = thickness);
translate([diameter / 2 + 5.1, 0, 1.5]) cube([10, 5, 3], center = true);
rotate([0, 0, 90]) translate([diameter / 2 + 5.1, 0, 1.5]) cube([10, 5, 3], center = true);
rotate([0, 0, 180]) translate([diameter / 2 + 5.1, 0, 1.5]) cube([10, 5, 3], center = true);
rotate([0, 0, 270]) translate([diameter / 2 + 5.1, 0, 1.5]) cube([10, 5, 3], center = true);

module cylinderWall(height, diameter, thickness){
    translate([0, 0, height / 2]){
        difference(){
            cylinder(h = height, d = diameter + thickness, center = true);
            cylinder(h = height + 1, d = diameter, center = true);
        }
    }
}
```
microscope-UniversalDarkFieldFilterMaker.scad:
```
/*

Author : Sarah Cartwright
Description : Customizer for a universal darkfield filter maker for microscopes.
Version : 1.00
Date: 2020/04/03

*/

/* [Eclipse obscurer] */
Use_eclipse_obscurer_shape = 1; // [0:No,1:Yes]
// Change the cutout to become the shaded area.
Inverse_shape = 1; // [0:No,1:Yes]
// The cutout's overall size.
Eclipse_circle_diameter = 12.0; // [5:0.1:100]
// The cutout's position moved from the center of the filter.
Eclipse_circle_X_offset = 0.0; // [0:0.1:100]
// The offset between the two discs that make the cutout. If this is 0, there appears to be only 1 disc.
Eclipse_circle_Y_offset = 0.0; // [0:0.1:100]

/* [Filter size and smoothness] */
// The outside diameter, changing the "outer frame width" thickness will NOT change this size.
Filter_diameter = 31.3; // [1:0.5:100]
//The number of sides to the disc. 40 is faceted, 100 is smooth, 200 is very smooth.
Disc_facets = 100; // [5:200]
// How thick (top to bottom) to make the filter in millimeters.
Filter_thickness = 1.5; // [0.05:0.01:5]
// How thick to make the outer frame - this doesn't alter the filter's overall diameter.
Outer_frame_width = 2.0; // [0.1:0.1:50]

/* [Supports] */
// The number of support spokes to put equally spaced around the filter.
Spokes = 3; // [0:1:20]
// Rotate the spoke around the filter, good for small single disk.
Spoke_angle_offset = 0; // [0.1:0.1:359.9]

/* [Supports - advanced settings (for using as obscurer parts)] */
// Turn the spokes into "spoke holes", by filling the grid and cutting out the spoke shapes.
Spoke_inverse = 0; // [0: No, 1: Yes]
// The thickness of the spoke (along X and Y), this doesn't effect the filter's thickness.
Spoke_width = 1.0; // [0.1:0.1:50]
// Between 0 and 1, 0.5 is half length, 1.5 is two thirds the way across.
Spoke_length = 1; // [0.01:0.01:2.0]
// Useful if you're using 1 or 2 wide spokes, equivalent of a straight line obscurer.
Spoke_Y_offset = 0; // [0.0:0.1:50]
// Useful for making two notches.
Spoke_individual_Y_offset = 0; // [0.0:0.1:50]

/* [Hidden] */
$fn = Disc_facets;

// --------------------------------------------------

makeDarkfieldFilter();

module trimOutsideFilter(Filter_thickness, Filter_diameter){
    difference(){
        cylinder(h = Filter_thickness + 1, d = Filter_diameter + 500, center = true);
        cylinder(h = Filter_thickness + 2, d = Filter_diameter, center = true);
    }
}

module makeOuterFilterCircle(Filter_thickness, Filter_diameter, Outer_frame_width){
    difference(){
        cylinder(h = Filter_thickness, d = Filter_diameter, center = true);
        cylinder(h = Filter_thickness + 1, d = Filter_diameter - Outer_frame_width, center = true);
    }
}

module makeTwinEclipse(Filter_thickness, Filter_diameter, Eclipse_circle_diameter, Eclipse_circle_X_offset, Eclipse_circle_Y_offset){
    difference(){
        cylinder(h = Filter_thickness, d = Filter_diameter, center = true);
        union(){
            translate([Eclipse_circle_X_offset, -Eclipse_circle_Y_offset, 0])
                cylinder(h = Filter_thickness + 1, d = Eclipse_circle_diameter, center = true);
            translate([Eclipse_circle_X_offset, Eclipse_circle_Y_offset, 0])
                cylinder(h = Filter_thickness + 1, d = Eclipse_circle_diameter, center = true);
        }
    }

}

module makeInverse_shapeTwinEclipse(Filter_thickness, Filter_diameter, Eclipse_circle_diameter, Eclipse_circle_X_offset, Eclipse_circle_Y_offset){
        union(){
            translate([Eclipse_circle_X_offset, -Eclipse_circle_Y_offset, 0])
                cylinder(h = Filter_thickness, d = Eclipse_circle_diameter, center = true);
            translate([Eclipse_circle_X_offset, Eclipse_circle_Y_offset, 0])
                cylinder(h = Filter_thickness, d = Eclipse_circle_diameter, center = true);
        }
}

module combineAllSections(Filter_thickness, Filter_diameter, Outer_frame_width, Eclipse_circle_diameter, Eclipse_circle_X_offset, Eclipse_circle_Y_offset){
    makeOuterFilterCircle(Filter_thickness, Filter_diameter, Outer_frame_width);
    if(Use_eclipse_obscurer_shape == 1){
        if(Inverse_shape == 0)
            makeTwinEclipse(Filter_thickness, Filter_diameter, Eclipse_circle_diameter, Eclipse_circle_X_offset, Eclipse_circle_Y_offset);
        else
            makeInverse_shapeTwinEclipse(Filter_thickness, Filter_diameter, Eclipse_circle_diameter, Eclipse_circle_X_offset, Eclipse_circle_Y_offset);
    }
}

module makeSupports(Spokes, Spoke_angle_offset, Spoke_length, Spoke_width, Filter_thickness, Filter_diameter, Spoke_Y_offset, Spoke_individual_Y_offset, Spoke_inverse){
    filterRadius = Filter_diameter / 2;
    spokeLength = filterRadius * Spoke_length;
    angleStart = Spoke_angle_offset;
    angleEnd = 360 + Spoke_angle_offset;
    angleStep = 360 / Spokes;
    if(Spokes > 0){
        echo(Spoke_inverse);
        if(Spoke_inverse == 0){
            translate([0, Spoke_Y_offset, 0]){
                for(a = [angleStart:angleStep:angleEnd]){
                    rotate([0, 0, a])
                        translate([filterRadius - (spokeLength / 2), Spoke_individual_Y_offset, 0])
                            cube([spokeLength, Spoke_width, Filter_thickness], center = true);
                }
            }
        }else{
            difference(){
                cylinder(h = Filter_thickness, d = Filter_diameter, center = true);
                translate([0, Spoke_Y_offset, 0]){
                    for(a = [angleStart:angleStep:angleEnd]){
                        rotate([0, 0, a])
                            translate([filterRadius - (spokeLength / 2), Spoke_individual_Y_offset, 0])
                                cube([spokeLength, Spoke_width, Filter_thickness + 1], center = true);
                    }
                }
            }
        }
    }
}

module makeDarkfieldFilter(){
    translate([0, 0, Filter_thickness / 2]){
        difference(){
            union(){
                makeSupports(Spokes, Spoke_angle_offset, Spoke_length, Spoke_width, Filter_thickness, Filter_diameter, Spoke_Y_offset, Spoke_individual_Y_offset, Spoke_inverse);
                combineAllSections(Filter_thickness, Filter_diameter, Outer_frame_width, Eclipse_circle_diameter, Eclipse_circle_X_offset, Eclipse_circle_Y_offset);
            }
            trimOutsideFilter(Filter_thickness, Filter_diameter);
        }
    }
}

```


[User]
Hi! I need to design a customizable shelf and I'm hoping you can help me generate the necessary OpenSCAD code. Here's what I have in mind:

The shelf itself should be a rectangular shape with rounded corners on the front edge. I need to be able to control the following dimensions:

*   **Width:** This will be the overall width of the shelf.
*   **Depth:** This will be how far the shelf extends out from the wall.
*   **Radius:** The radius of the rounded corners at the front.
*   **Thickness:** The thickness of the shelf material.

I would also like the ability to add some optional supports underneath the shelf. These supports will be somewhat decorative and should include:

*   Two small cube supports at the back of the shelf, one on each side.
*   A cylindrical brace running through the shelf, slightly recessed below the main shelf plane, positioned horizontally across the shelf's width.
*   Two additional cylindrical braces oriented vertically, sitting below the main shelf plane.

All of these support elements should also have their dimensions be controllable, such as the cube size and cylinder radii. I also need to be able to control the position of the supports, such as the distance from the back, the depth from the shelf, etc.

I'm also expecting to get all of these elements to work together properly, by using the `difference()` operator to remove the support structure from the shelf plane and such.

I'm hoping for a parametric design that is easy to use so I can quickly adjust the shelf's dimensions and support placement without having to change a bunch of numbers scattered throughout the code.

Can you help me with this? I think that using the `hull()` function will be important to do the rounded corners. Also I'd like the resolution of the curves to be reasonable, which I think can be done by setting a value for `$fn`. I'm thinking using a function or module might be helpful as well, for the overall shelf design at least.

[Assistant]
Customizable_Shelf.scad:
```

// Shelf width
$width=120;
// Shelf depth
$depth=70;
// Radius of corners
$radius=10;
// Thickness of shelf
$thickness=3;

$fn=90;

linear_extrude(height=$thickness){
    hull(){translate([-$width/2,-$width/4])square([$width,$depth-$radius]);
            translate([$width/2-$radius,($depth-$radius)-$width/4]) circle(r=$radius);
            translate([-$width/2+$radius,($depth-$radius)-$width/4]) circle(r=$radius);
        }
}
difference(){
union(){translate([$width/4,-$width/4,-$width/4]) cube([$width/4,$width/4,$width/4]);
translate([-$width/2,-$width/4,-$width/4]) cube([$width/4,$width/4,$width/4]);}
translate([-$width/2,0,-$width/4+$thickness]) rotate([0,90,0]) cylinder(r=$width/4-$thickness,h=$width);
translate([$width/8,-$width/4,-$width/3+$thickness]) rotate([-90,0,0]) cylinder(r=$width/3,h=$width/3);
translate([-$width/8,-$width/4,-$width/3+$thickness]) rotate([-90,0,0]) cylinder(r=$width/3,h=$width/3);
}
```


[User]
Okay, I need an OpenSCAD design for a mask strap extender, something like those ear savers everyone's printing right now, but with a few specific requirements. I've seen a few designs out there, but I need one that's optimized for printing on edge and doesn't need adjustability on both ends.

Here's the basic idea: I need a long, thin strap with rounded corners. One end should have a fixed retainer clip, basically a small tab that sticks out and angles down. The other end should have three hooks for different tightness settings. These hooks should be like small lips sticking out from the side of the band.

The whole thing should be designed so it can be easily duplicated and printed multiple times on the print bed. The goal is to print them vertically to save space and reduce print time.

Here are the details I'm looking for, and I'd like to be able to adjust all of these via variables within the file.

*   **Total Length:**  The overall length of the band. This needs to be variable.
*   **Band Thickness:** The thickness of the band, should be fairly thin.
*   **Band Width:** The width of the strap.
*   **Corner Radius:** The radius of the rounded corners of the band.
*   **Retainer Height:** The height of the fixed retainer tab.
*    **Retainer Length:** How long the fixed retainer tab sticks out
*   **Retainer Angle:** The angle of the fixed retainer tab. It needs to be greater than 90 degrees.
*   **Hook Height:** The height of the hook standoffs.
*   **Hook Thickness:** The thickness of the hook standoffs.
*   **Hook Depth:** The depth of the hook lip.
*   **Hook Lip Thickness:** The thickness of the hook lip.
*   **Hook Spacing:** The distance between each of the three hooks.
*   **Pair Spacing:** I'd like the design to include the option to generate two back-to-back versions with a small space between them for printing. This should also be a variable.

I’m expecting the code to have a roundedCube module, I think I’ve seen that in the wild before, to handle rounding those edges. And, as I'd like to print these on the edge, and then rotate the design to print a second one on the same part, the first part should start with the following transformations applied:
`rotate([0,-90,0])`

I think that's all the parameters, could you generate an OpenSCAD file or files that would do this? I do not expect this to be done via customizer but I would like to make the variables customizable.

Also, I don't expect you to use any additional external libraries in this request.

[Assistant]
Medical_Mask_coupler_versionC1.scad:
```
//Variables for all dimensions
TotalLength = 140; // Overal Length of Band
BandThick = 0.45; //Thickness of strap - set just above multiple of nozzle in use.
BandWide = 7; //Width of strap
CornerRadius = 3; //Rounding of band set to half (or less) of width for radius on end
RetainerTall = 4.5; //Length of fixed retainer angled tab
RetainerLong = 4.5; //Length of fixed retainer angled tab
RetainerAngle = 110; //Angle of retainer down (greater than 90)
HookStandTall = 5; // Height of hook
HookStandThick = 1.15; //Thickness of Standoff for hooks
HookDeep = 3; // Depth of lip on hook
HookLipThick = 1.1; // Thickness of retainer lip on hook
HookSpacing = 40; // Distance between each of the three hooks
PairSpace = .7; //Distance between two models

/*
	roundedCube() v1.0.0 by robert@cosmicrealms.com from https://github.com/Sembiance/openscad-modules
	Allows you to round any edge of a cube

	Usage
	=====
	Prototype: roundedCube(dim, r, x, y, z, xcorners, ycorners, zcorners)
	Arguments:
		-      dim = Array of x,y,z numbers representing cube size
		-        r = Radius of corners. Default: 1
		-        x = Round the corners along the X axis of the cube. Default: false
		-        y = Round the corners along the Y axis of the cube. Default: false
		-        z = Round the corners along the Z axis of the cube. Default: true
		- xcorners = Array of 4 booleans, one for each X side of the cube, if true then round that side. Default: [true, true, true, true]
		- ycorners = Array of 4 booleans, one for each Y side of the cube, if true then round that side. Default: [true, true, true, true]
		- zcorners = Array of 4 booleans, one for each Z side of the cube, if true then round that side. Default: [true, true, true, true]
		-      $fn = How smooth you want the rounding to be. Default: 128

	Change Log
	==========
	2017-01-04: v1.0.0 - Initial Release

	Thanks to Sergio Vilches for the initial code inspiration
*/
/****************************************************
/* Start of code for Surgical Mask Retainer / extender clip - Designed by S. Krietemeyer ven0msho@gmail.com - Free for non commercial use.
https://www.thingiverse.com/thing:4264787

Rev B 4/6/2020 added variables for thickness of All parts
Rev C 4/7/2020 added copied (rotated) clip and code to control spacing of the two.
Rev C1 4/10/2020 Cleaned up comments moved Variables to top of file to try and get Customizer functions on thingverse working.
*****************************************************
*/

rotate([0,-90,0]) {

Space = PairSpace/2; //Offset for one - Do not edit this line done for clarity in code below
EndHook = TotalLength-CornerRadius; //Distance for last hook - DO NOT EDIT Calculation done here for clarity in code below.

// Base strip with rounded corners
translate([-BandWide/2, 0, Space]) { roundedCube([BandWide, TotalLength, BandThick], r=CornerRadius, x=false, y=false, z=true); }
//Fixed retainer clip on origin end
//Stand
translate([0, CornerRadius, RetainerTall/2+Space])
rotate([0,90,0])
cube([RetainerTall,1.15,BandWide],center=true);
//Retainer
translate([BandWide/2, CornerRadius+.5, RetainerTall+Space])
rotate([0,RetainerAngle,90])
cube([1.15,BandWide,RetainerLong+1]);

//end hook
//Stand
translate([0, EndHook, HookStandTall/2+Space])
rotate([0,90,0])
cube([HookStandTall,HookStandThick,BandWide],center=true);
//Edge
translate([BandWide/2, EndHook-HookStandThick/2, HookStandTall-HookLipThick+Space])
rotate([0,-90,90])
cube([HookLipThick,BandWide,HookDeep]);
//Middle Hook
//Stand
translate([0, EndHook-HookSpacing, HookStandTall/2+Space])
rotate([0,90,0])
cube([HookStandTall,HookStandThick,BandWide],center=true);
//Edge
translate([BandWide/2, EndHook-HookSpacing-HookStandThick/2, HookStandTall-HookLipThick+Space])
rotate([0,-90,90])
cube([HookLipThick,BandWide,HookDeep]);

//Tight Hook
//Stand
translate([0, EndHook-HookSpacing*2, HookStandTall/2+Space])
rotate([0,90,0])
cube([HookStandTall,HookStandThick,BandWide],center=true);
//Edge
translate([BandWide/2, EndHook-HookSpacing*2-HookStandThick/2, HookStandTall-HookLipThick+Space])
rotate([0,-90,90])
cube([HookLipThick,BandWide,HookDeep]);

// Begin second copy of code above (rotated 180) to make duplicate back to back
rotate([0,180,0])
 {
// Base strip with rounded corners
translate([-BandWide/2, 0, Space]) { roundedCube([BandWide, TotalLength, BandThick], r=CornerRadius, x=false, y=false, z=true); }
//Fixed retainer clip on origin end
//Stand
translate([0, CornerRadius, RetainerTall/2+Space])
rotate([0,90,0])
cube([RetainerTall,1.15,BandWide],center=true);
//Retainer
translate([BandWide/2, CornerRadius+.5, RetainerTall+Space])
rotate([0,RetainerAngle,90])
cube([1.15,BandWide,RetainerLong+1]);

//end hook
//Stand
translate([0, EndHook, HookStandTall/2+Space])
rotate([0,90,0])
cube([HookStandTall,HookStandThick,BandWide],center=true);
//Edge
translate([BandWide/2, EndHook-HookStandThick/2, HookStandTall-HookLipThick+Space])
rotate([0,-90,90])
cube([HookLipThick,BandWide,HookDeep]);
//Middle Hook
//Stand
translate([0, EndHook-HookSpacing, HookStandTall/2+Space])
rotate([0,90,0])
cube([HookStandTall,HookStandThick,BandWide],center=true);
//Edge
translate([BandWide/2, EndHook-HookSpacing-HookStandThick/2, HookStandTall-HookLipThick+Space])
rotate([0,-90,90])
cube([HookLipThick,BandWide,HookDeep]);

//Tight Hook
//Stand
translate([0, EndHook-HookSpacing*2, HookStandTall/2+Space])
rotate([0,90,0])
cube([HookStandTall,HookStandThick,BandWide],center=true);
//Edge
translate([BandWide/2, EndHook-HookSpacing*2-HookStandThick/2, HookStandTall-HookLipThick+Space])
rotate([0,-90,90])
cube([HookLipThick,BandWide,HookDeep]);
};

}

// End of code for Mask clip begin module for Rounded Cube.

module roundedCube(dim, r=1, x=false, y=false, z=true, xcorners=[true,true,true,true], ycorners=[true,true,true,true], zcorners=[true,true,true,true], $fn=128)
{
	difference()
	{
		cube(dim);

		if(z)
		{
			translate([0, 0, -0.1])
			{
				if(zcorners[0])
					translate([0, dim[1]-r]) { rotateAround([0, 0, 90], [r/2, r/2, 0]) { meniscus(h=dim[2], r=r, fn=$fn); } }
				if(zcorners[1])
					translate([dim[0]-r, dim[1]-r]) { meniscus(h=dim[2], r=r, fn=$fn); }
				if(zcorners[2])
					translate([dim[0]-r, 0]) { rotateAround([0, 0, -90], [r/2, r/2, 0]) { meniscus(h=dim[2], r=r, fn=$fn); } }
				if(zcorners[3])
					rotateAround([0, 0, -180], [r/2, r/2, 0]) { meniscus(h=dim[2], r=r, fn=$fn); }
			}
		}

		if(y)
		{
			translate([0, -0.1, 0])
			{
				if(ycorners[0])
					translate([0, 0, dim[2]-r]) { rotateAround([0, 180, 0], [r/2, 0, r/2]) { rotateAround([-90, 0, 0], [0, r/2, r/2]) { meniscus(h=dim[1], r=r); } } }
				if(ycorners[1])
					translate([dim[0]-r, 0, dim[2]-r]) { rotateAround([0, -90, 0], [r/2, 0, r/2]) { rotateAround([-90, 0, 0], [0, r/2, r/2]) { meniscus(h=dim[1], r=r); } } }
				if(ycorners[2])
					translate([dim[0]-r, 0]) { rotateAround([-90, 0, 0], [0, r/2, r/2]) { meniscus(h=dim[1], r=r); } }
				if(ycorners[3])
					rotateAround([0, 90, 0], [r/2, 0, r/2]) { rotateAround([-90, 0, 0], [0, r/2, r/2]) { meniscus(h=dim[1], r=r); } }
			}
		}

		if(x)
		{
			translate([-0.1, 0, 0])
			{
				if(xcorners[0])
					translate([0, dim[1]-r]) { rotateAround([0, 90, 0], [r/2, 0, r/2]) { meniscus(h=dim[1], r=r); } }
				if(xcorners[1])
					translate([0, dim[1]-r, dim[2]-r]) { rotateAround([90, 0, 0], [0, r/2, r/2]) { rotateAround([0, 90, 0], [r/2, 0, r/2]) { meniscus(h=dim[1], r=r); } } }
				if(xcorners[2])
					translate([0, 0, dim[2]-r]) { rotateAround([180, 0, 0], [0, r/2, r/2]) { rotateAround([0, 90, 0], [r/2, 0, r/2]) { meniscus(h=dim[1], r=r); } } }
				if(xcorners[3])
					rotateAround([-90, 0, 0], [0, r/2, r/2]) { rotateAround([0, 90, 0], [r/2, 0, r/2]) { meniscus(h=dim[1], r=r); } }
			}
		}
	}
}

module meniscus(h, r, fn=128)
{
	$fn=fn;

	difference()
	{
		cube([r+0.2, r+0.2, h+0.2]);
		translate([0, 0, -0.1]) { cylinder(h=h+0.4, r=r); }
	}
}

module rotateAround(a, v) { translate(v) { rotate(a) { translate(-v) { children(); } } } }

```


[User]
Hi, I'm looking to design a customizable modular drawer system using OpenSCAD. I have some specific ideas in mind that I'd like to implement, and I need help putting them into code.

Essentially, I want to be able to generate both the drawer modules (the frames) and the drawers themselves, and have the drawers automatically sized to fit within the specified modules. I need parameters to control the overall dimensions of the modules, the number of drawers each module can hold, and the design of both the modules and drawers.

Here's a breakdown of the features I need:

**Drawer Module (Frame) Customization:**

*   **Outer Dimensions:** I need to be able to set the overall width, height, and depth of the drawer module. These dimensions should determine the outer size of the entire frame.
*   **Compartment Grid:** I want to specify how many drawers will fit horizontally and vertically within a module.  This will define the grid of drawer slots.
*   **Frame Thickness:** I need a parameter to control the thickness of the bars that make up the module's frame.
*   **Mesh Hole Size:** The frame should have a mesh structure with holes in it. I need to specify the size of these holes and the spacing between the bars.
*   **Stackable Design:** I'd like the modules to be designed so they can be easily stacked on top of one another, and I have the idea of threading them together using a simple tool.

**Drawer Customization:**

*   **Automatic Sizing:** The drawers need to automatically adjust their dimensions to fit the available space within a given drawer module, taking into account the frame thickness and number of compartments set for that module.
*   **Subcompartments:** I need to be able to divide each drawer into smaller compartments using subdividers. I need to control the number of subdivisions along both the width and length of the drawer, as well as how high these subdividers are as a percentage of the drawer height.
*   **Wall Thickness:** I need a parameter to set the wall thickness for the drawer (both the side walls and the floor).
*   **Handle:** The drawers need handles. I'd like to customize the handle's length, height, and the angle at which the handle's side walls converge towards the center.
*   **Rubber Bumper Accommodation:** I'd like to make sure that there's enough space in the mesh for adding rubber bumper pads to each drawer.

**Additional Features**

*   **Threading Helper:** I would also like to include a simple 3D-printable threading tool to make it easier to thread the modules together. This will be used to sew the modules together by passing a thread through the mesh of the frame.

Ideally, I'd like the design to be efficient in terms of material usage for 3D printing and easily customizable. I want to be able to specify in the top of the file whether I want to generate a drawer module, a drawer, or the threading helper. I'd like to keep all related code in one file, if possible.

Could you please design this for me using OpenSCAD? I don't think I need to be able to use any specific libraries, just the basic OpenSCAD library.

[Assistant]
drawers.scad:
```
/* [Drawer Module Settings] */
// The outer width of a drawer module
width = 183;
// The outer height of a drawer module
height = 120;
// The outer depth of a drawer module
depth = 180;
// The number of drawers that will fit into the module horizontally
compartments_hor = 3; // [1:1:10]
// The number of drawers that will fit into the module vertically
compartments_vert = 4; // [1:1:10]
// The thickness of the bars that make up the drawer module
bar_thickness = 1.6; // [1:0.2:3]
// The perpendicular distance between the bars
hole_size = 12.5; // [5:0.5:20]

/* [Drawer Settings] */
// The number of subcompartmens of the drawer across its width
drawer_compartments_x = 1; // [1:1:10]
// The number of subcompartments of the drawer across its length
drawer_compartments_y = 4; // [1:1:20]
// The height of the compartment subdividers as a percentage of the drawer height
subdivider_height = 1.0; // [0.05:0.05:1]
// The wall (and floor) thickness of the drawers
wall_thickness = 0.8; // [0.4:0.2:2]
// How far the handle protrudes from the drawer
handle_length=10; // [2:1:15]
// The height of the handle
handle_height=16; // [10:1:30]
// The angle at which the side walls of the handle converge
handle_angle=30; // [0:5:45]

/* [Component Selection] */
component = "drawer_module"; // [drawer_module:Drawer Module, drawer:Drawer, threading_helper:Threading Helper]

/* [Hidden] */
epsilon = 0.001;
$fn = 100;

if (component == "drawer_module") {
  drawer_module();
} else if (component == "drawer") {
  drawer();
} else if (component == "threading_helper") {
  threading_helper();
}

module drawer() {
  length = depth - bar_thickness;
  width = (width - (compartments_hor + 1)*bar_thickness)/compartments_hor - 2;
  height = (height - (compartments_vert + 1)*bar_thickness)/compartments_vert - 2;
  wall_coords = [(length - wall_thickness)/2, (width - wall_thickness)/2, wall_thickness/2];

  translate([0, 0, wall_coords[2]])
    cube([length, width, wall_thickness], center=true);

  copies([0], [-wall_coords[1], wall_coords[1]], [height/2])
    cube([length, wall_thickness, height], center=true);

  copies([-wall_coords[0], wall_coords[0]], [0], [height/2])
    cube([wall_thickness, width, height], center=true);

  translate([(length + handle_length)/2, 0, 0])
    drawer_handle(width=width);

  drawer_subdividers();

  module drawer_subdividers() {
    delta_y = (length - wall_thickness) / drawer_compartments_y;
    delta_x = (width - wall_thickness) / drawer_compartments_x;
    subdivs_y = [-(length - wall_thickness)/2 + delta_y : delta_y : (length - wall_thickness)/2 + epsilon - delta_y];
    subdivs_x = [-(width - wall_thickness)/2 + delta_x: delta_x : (width - wall_thickness)/2 + epsilon - delta_x];
    subdiv_height = height*subdivider_height;
    copies(subdivs_y, [0], [subdiv_height/2])
      cube([wall_thickness, width, subdiv_height], center=true);
    copies([0], subdivs_x, [subdiv_height/2])
      cube([length, wall_thickness, subdiv_height], center=true);
  }

  module drawer_handle(width=40) {
    // bottom
    translate([0, 0, wall_thickness/2]) polyhedron(
      points=[
        [-handle_length/2, -width/2, -wall_thickness/2],
        [handle_length/2, -width/2 + tan(handle_angle)*handle_length, -wall_thickness/2],
        [handle_length/2, width/2 - tan(handle_angle)*handle_length, -wall_thickness/2],
        [-handle_length/2, width/2, -wall_thickness/2],
        [-handle_length/2, -width/2, wall_thickness/2],
        [handle_length/2, -width/2 + tan(handle_angle)*handle_length, wall_thickness/2],
        [handle_length/2, width/2 - tan(handle_angle)*handle_length, wall_thickness/2],
        [-handle_length/2, width/2, wall_thickness/2]],
      faces=[
        [0,1,2,3], [7,6,5,4], [0,3,7,4], [5,6,2,1], [4,5,1,0], [6,7,3,2]]);
    // sides
    translate([-sin(handle_angle)*wall_thickness/2,
               -width/2 - 0.08 + (tan(handle_angle)*handle_length + wall_thickness)/2,
               handle_height/2])
      rotate(handle_angle)
        cube([handle_length/cos(handle_angle), wall_thickness, handle_height], center=true);
    translate([-sin(handle_angle)*wall_thickness/2,
               width/2 + 0.08 - (tan(handle_angle)*handle_length + wall_thickness)/2,
               handle_height/2])
      rotate(-handle_angle)
        cube([handle_length/cos(handle_angle), wall_thickness, handle_height], center=true);
    //front
    difference() {
      handle_front_width = width - 2*tan(handle_angle)*handle_length;
      translate([(handle_length - wall_thickness)/2, 0, handle_height/2])
        cube([wall_thickness, handle_front_width, handle_height], center=true);
      translate([(handle_length - wall_thickness)/2, 0, handle_height/2 + wall_thickness + 3])
        cube([wall_thickness*2, handle_front_width - 8, handle_height], center=true);
    }
    translate([handle_length/2 - 1.5*wall_thickness - 1, 0, handle_height/2])
      cube([wall_thickness, width - 2*tan(handle_angle)*(handle_length - wall_thickness - 1), handle_height], center=true);
  }
}

module drawer_module() {
  hole_spacing = hole_size + bar_thickness;

  mesh_sizes = [[width - 2*bar_thickness, depth - 2*bar_thickness],
                [depth - 2*bar_thickness, height - 2*bar_thickness],
                [width - 2*bar_thickness, height - 2*bar_thickness]];

  mesh_hor_y = [(-height + bar_thickness)/2:
                (height - bar_thickness)/compartments_vert:
                (height - bar_thickness)/2 + epsilon];
  mesh_vert_x = [(-width + bar_thickness)/2:
                 (width - bar_thickness)/compartments_hor:
                 (width - bar_thickness)/2 + epsilon];
  cutout_spacing = [-15*hole_spacing:hole_spacing:15*hole_spacing + epsilon];

  translate([0, 0, depth/2]) union() {
    rotate([90, 0, 90])
      copies(mesh_hor_y, [0], [0])
        mesh_wall(width=mesh_sizes[0][0], height=mesh_sizes[0][1], rotate_vector=[0, 1, 0]);

    copies(mesh_vert_x, [0], [0])
      mesh_wall(width=mesh_sizes[1][0], height=mesh_sizes[1][1], rotate_vector=[0, 1, 0]);

    translate([0, 0, (-depth + bar_thickness)/2])
      mesh_wall(width=mesh_sizes[2][0], height=mesh_sizes[2][1], rotate_vector=[0, 0, 0]);

    copies(mesh_vert_x, mesh_hor_y, [0])
      cube([bar_thickness + epsilon, bar_thickness + epsilon, depth - 2*(bar_thickness - epsilon)], center=true);

    copies(mesh_vert_x, [0], [(-depth + bar_thickness)/2, (depth - bar_thickness)/2])
      cube([bar_thickness + epsilon, height, bar_thickness + epsilon], center=true);

    copies([0], mesh_hor_y, [(-depth + bar_thickness)/2, (depth - bar_thickness)/2])
      cube([width, bar_thickness + epsilon, bar_thickness + epsilon], center=true);
  }

  module mesh_wall(width=100, height=100, rotate_vector=[0, 0, 1]) {
    rotate(90, rotate_vector) {
      rotate(-45) {
        difference() {
          rotate(45)
            cube([width, height, bar_thickness], center=true);
          copies(cutout_spacing, cutout_spacing, [0])
            cube(hole_size, center=true);
        }
      }
    }
  }
}

module threading_helper() {
  difference() {
    cylinder(r=8, h=2, center=true);
    cylinder(r=5, h=3, center=true);
    translate([5, 3, 0])
      cube([10, 12, 3], center=true);
    translate([-1.4, 6.3, 0])
      cylinder(r=0.8, h=3, center=true);
  }
}

module copies(x=[0], y=[0], z=[0], theta=[0]) {
  for (X = x)
    for (Y = y)
      for (Z = z)
        for (Theta = theta)
          rotate(Theta) translate([X, Y, Z]) children();
}

```


[User]
Hey, I need help designing a mask hook to help with ear strain. I've got some general ideas but I need a parametric OpenSCAD design.

Here's what I'm thinking:

*   **Overall Shape:** It should be a mostly flat piece, like a bar, with rounded ends. Something similar to the shape you'd get if you extruded a rounded rectangle, but I'd like to have it be a hull of two circles connected by straight lines.

*   **Width:** I want to be able to specify the total width of the bar, let's call it `TOTAL_WIDTH`, and default it to 120mm. I'd like the ability to change that as needed, so it's got to be a parameter.

*   **Height:** The height should be another parameter, `HEIGHT`, and default to 15mm. This will be the diameter of those rounded ends, and should also affect the straight sections too.

*   **Thickness:** I need a `THICKNESS` parameter, defaulting to 2mm.

*   **Hooks:** I need hooks at each end to attach the mask straps. I'm envisioning something that curves out and back inward, like a small hook. I would like a toggle using a variable called `USE_HOOK`, so that if it is set to false, it uses rubber band inserts (small rectangles) instead of the hooks.

*   **Rubber Band Insert Parameters:** These will be used if the `USE_HOOK` parameter is set to false. I need the ability to specify the `RUBBER_BAND_HEIGHT` (default 8mm) and the `RUBBER_BAND_WIDTH` (default 3mm). I need two rectangles, one following the other, to make a channel for the band.

*   **Text:** I want the option to add a text label in the middle of the bar. It needs to be a parameter (`TEXT`) that defaults to an empty string, so no text if nothing is entered. I will need to be able to change the `FONTNAME` (default to "Bitstream Vera Sans"), the `FONTSTYLE` (default to "Bold"), and `FONT_SIZE` (default to 8).

*   **Text Backing:** If text is provided, a backing should be rendered behind the text with its own thickness parameter (`FONT_BACK_THICKNESS`, default 0.5mm). The backing should be a rounded rectangle, and should have its own width parameter (`FONT_BACK_WIDTH`, default 60mm) that is relative to the bar (centered).

*   **Honeycomb Infill:** I'd like a honeycomb pattern as a kind of infill within the main body of the design. I need parameters for the `HONEYCOMB_DIA` (default 9mm) and `HONEYCOMB_WALL` (default 2mm).

*   **Border:** I'd like a border around the entire hook, with a width of `BORDER` (default 2mm).

*   **Materials:** I expect to print this with PETG, so the design should consider that it will have some flexibility.

Could you generate the necessary OpenSCAD files, including the required modules and parameters, to accomplish this design? I don't think I need any external libraries for this simple design.

[Assistant]
mask_hook.scad:
```

TOTAL_WIDTH = 120;

HEIGHT = 15;
THICKNESS = 2;

USE_HOOK = 1; // [0:false, 1:true]

FONTNAME = "Bitstream Vera Sans";
FONTSTYLE = "Bold";

FONT_SIZE = 8;

FONT_BACK_WIDTH = 60;
FONT_BACK_THICKNESS = 0.5;

TEXT = "";

HONEYCOMB_DIA = 9;
HONEYCOMB_WALL = 2;

BORDER = 2;

RUBBER_BAND_HEIGHT = 8;
RUBBER_BAND_WIDTH = 3;

FONT = str(FONTNAME,":style=",FONTSTYLE);
echo("FONT:",FONT);

module hexagon(l)  {
	circle(d=l, $fn=6);
}

module hexagon_border(inner, outer)
{
  difference()
  {
    hexagon(outer);
    hexagon(inner);

  }
}

module honeycomb(x, y, dia, wall)
{
  innerDia = dia-(2*wall);
  midDia = dia - wall;

  outerHeight = 2 * ((dia/4)/tan(30));
  innerHeight = 2* ((innerDia/4)/tan(30));
  midHeight = 2* ((midDia/4)/tan(30));

  outerEdge = outerHeight/2 / cos(30);
  innerEdge = innerHeight/2 / cos(30);
  midEdge = midHeight/2 / cos(30);

  minWall = wall * cos(30);

  xStep = midDia + midEdge;
  yStep = outerHeight-minWall;

  for (yOffset = [0:yStep:y+yStep], xOffset = [0:xStep:x+xStep]) {
			translate([xOffset, yOffset]) {
				hexagon_border(innerDia, dia);
			}
			translate([xOffset +midDia - midEdge/2 , yOffset + outerHeight/2 - wall/2]) {
				color("blue") hexagon_border(innerDia,dia);
			}
		}

}

module font_back()
{
  if (len(TEXT) > 0) {
    translate([-(FONT_BACK_WIDTH/2)+(TOTAL_WIDTH/2),0,0])
    {
      hull() {
        translate([(HEIGHT/2),0,0]) circle(d=HEIGHT);
        translate([FONT_BACK_WIDTH - (HEIGHT/2),0,0]) circle(d=HEIGHT);
      }
    }
  }
}

module rubber_band_hook() {
  //cylinder(h=2*RUBBER_BAND_HEIGHT, d=HEIGHT);

  difference(){
    hull(){
      sphere(d=HEIGHT);
      translate([0,0,RUBBER_BAND_HEIGHT])sphere(d=HEIGHT);
    }

    translate([0,0,RUBBER_BAND_HEIGHT/2+THICKNESS])
      rotate_extrude(angle=360, convexity = 10, $fn=40)
        translate([RUBBER_BAND_HEIGHT, 0, 0])
          circle(d = RUBBER_BAND_HEIGHT);

    translate([0,0,-HEIGHT/2])
    cube([4*HEIGHT, 4*HEIGHT, HEIGHT], center=true);
  }

  /*
  hull(){
  sphere(d=15);
  translate([0,0,20]) sphere(d=5);
  //translate([0,0,40]) sphere(d=15);
  }*/
}

//!rubber_band_hook();

module rubber_band_insert(){

  square([RUBBER_BAND_WIDTH, RUBBER_BAND_HEIGHT], center=true);
  translate([RUBBER_BAND_WIDTH+2,0,0])square([RUBBER_BAND_WIDTH, RUBBER_BAND_HEIGHT], center=true);
}

if (USE_HOOK) {
  translate([(HEIGHT/2),0,0]) rubber_band_hook();
  translate([TOTAL_WIDTH - (HEIGHT/2),0,0]) rubber_band_hook();

}

linear_extrude(THICKNESS) {
  difference() {
    union() {

      // infill
      difference() {
        intersection() {
          hull() {
            translate([(HEIGHT/2),0,0]) circle(d=HEIGHT);
            translate([TOTAL_WIDTH - (HEIGHT/2),0,0]) circle(d=HEIGHT);
          }
          translate([-TOTAL_WIDTH, -HEIGHT,0])
            honeycomb(TOTAL_WIDTH *4, HEIGHT*4, HONEYCOMB_DIA, HONEYCOMB_WALL);
        }
        font_back();
      }

      // border
      difference() {
        hull() {
          translate([(HEIGHT/2),0,0]) circle(d=HEIGHT);
          translate([TOTAL_WIDTH - (HEIGHT/2),0,0]) circle(d=HEIGHT);
        }
        hull() {
          translate([(HEIGHT/2),0,0]) circle(d=HEIGHT - 2*BORDER);
          translate([TOTAL_WIDTH - (HEIGHT/2),0,0]) circle(d=HEIGHT - 2*BORDER);
        }
      }

      //ends
      translate([(HEIGHT/2),0,0]) circle(d=HEIGHT);
      translate([TOTAL_WIDTH - (HEIGHT/2),0,0]) circle(d=HEIGHT);

      translate([TOTAL_WIDTH/2, ,0])
        text(text=TEXT, font=FONT, size=FONT_SIZE, halign="center", valign="center");

    }
    if (! USE_HOOK ) {
      translate([HEIGHT/3,0,0]) rubber_band_insert() ;
      translate([TOTAL_WIDTH - HEIGHT/3,0,0]) mirror([1,0,0])rubber_band_insert() ;
    }

  }

}

linear_extrude(FONT_BACK_THICKNESS)
  font_back();

```


[User]
Hi! I'm looking for an OpenSCAD design for a rack-mountable cable clip. I've seen some online, but I need one that's parametric and fits my specific rack setup. I'm thinking of a snap-in design that doesn't require screws.

Here are the basic parameters I need to be able to control:

**General Clip Dimensions:**

*   `width`: The overall width of the cable loop, should be adjustable.
*   `depth`: The depth of the cable loop.
*   `height`: The height of the main body of the clip.
*   `thickness`: The wall thickness of the clip.
*   `cablethickness`: The size of the gap in the clip for inserting and removing cables.

**Mounting Rail Parameters:**

*   `pegh`: Height of the mounting hole (peg).
*   `pegw`: Width of the mounting hole (peg).
*   `peglen`: Length of the peg.
*   `peggap`: Width of the "bending gap" on the peg that allows it to flex for a snap-in fit.
*   `sheetthickness`: The thickness of the metal sheet the rack rail is made of.

**Additional:**

*   I'd like to control the smoothness of the curves, maybe with something like `$fn` parameter.

I imagine the clip body being roughly a "U" shape with two rounded ends to capture the cables, and the mounting peg at the back. I want to be able to adjust all these dimensions. Can you generate the OpenSCAD code? I don't need any library dependencies. Thanks!

[Assistant]
rack_mountable_cable_clip.scad:
```
/* [Global] */

// Cables loop width
width=75;
// Cables loop depth
depth=45;
// Height
height=8.4;
// Wall thickness
thickness=6;
// Size of the gap for inserting/removing cables
cablethickness=4.3;

/* [Mounting rail params] */

// Height of the mounting hole
pegh=9;
// Width of the mounting hole
pegw=8.88;
// Lenght of the peg
peglen=20;
// Width of the "bending gap".
peggap=2.5;
// Thickness of the metal sheet used for making the rail.
sheetthickness=2.1;

/* [Additional] */

// Smoothness
$fn=30;

module roundcube(x,y,z) {
    if (x>y) {
        d=y;
        translate([d/2,0,0]) {
          translate([0,d/2,0])
            cylinder(d=d, h=z);
          cube([x-d,y,z]);
          translate([x-d,d/2,0])
            cylinder(d=d, h=z);
        }
    } else {
        d=x;
        translate([0,d/2,0]) {
          translate([d/2,0,0])
            cylinder(d=d, h=z);
          cube([x,y-d,z]);
          translate([d/2,y-d,0])
            cylinder(d=d, h=z);
        }
    }

}

module ushape(depth=40, width=40, thickness=7, height=10) {

  translate([-depth/2,0,0]) {

    difference() {
      cylinder(d=depth, h=height);
      translate([0,0,-1]) {
          cylinder(d=depth-2*thickness, h=height+2);
          translate([-depth,-depth/2,0])
              cube([depth, depth, height+2]);
      }
    }

    w=width-depth/2;

    translate([-w,-depth/2,0])
        roundcube(w+thickness/2, thickness, height);

    translate([-w,depth/2-thickness,0])
        roundcube(w+thickness/2, thickness, height);
  }
}

module rackpeg(peggap, pegw, peglen, pegh, thickness, sheetthickness) {

    l=peglen-thickness-sheetthickness;
    a=atan((peggap/3)/l);

    echo("Angle:", a);

    difference() {

      union() {
        roundcube(pegw, peglen+thickness, pegh);

        translate([0,thickness,0]) {
          translate([pegw-peggap,0,0])
            rotate([0,0,-a])
              cube([peggap,l,pegh+0]);
          translate([0,0,0])
            rotate([0,0,a])
              cube([peggap,l,pegh+0]);
        }
      }

      translate([(pegw-peggap)/2, thickness/2, -1])
        roundcube(peggap, peglen+thickness/2+1, pegh+2);

    }
}

module cableclip(width, depth, height, thickness, cablethickness, pegw, pegh, peglen, peggap, sheetthickness) {

  difference() {

    union() {

        w=width/2+2*cablethickness;
        dmin=depth-thickness-cablethickness;

        translate([w-cablethickness, (dmin)/2, 0])
            ushape(dmin, w, thickness, height);

        translate([-w+cablethickness, depth/2, 0])
        rotate([0,0,180])
            ushape(depth, w, thickness, height);

        translate([-pegw/2, -peglen, 0])
            rackpeg(peggap, pegw, peglen, pegh, thickness, sheetthickness);
    }

    translate([-peggap/2, -peglen+thickness/2, -1])
        roundcube(peggap, peglen+thickness/2+1, pegh+2);
  }

}

cableclip(width, depth, height, thickness,
         cablethickness, pegw, pegh, peglen, peggap,
         sheetthickness);

```


[User]
I'd like a parametric tape dispenser, please. I need it to handle standard tape rolls, so the inner diameter should be adjustable (default to 76mm), the outer diameter (default 100mm), and the tape height (default 50mm) need to be configurable.

For the dispenser itself, I need a gap between the tape and the dispenser body so the tape can move freely, this gap needs to be adjustable and I'll need to set the gap for the inner diameter (default 2mm), outer diameter (default 4mm), and tape height (default 1.5mm). The dispenser itself should have an overall thickness parameter (default 4mm). There also needs to be a notch to help hold the tape in place which should be adjustable (default 2.5mm), this should be a circular groove on the inner diameter.

I'd also like to have an option to add a stand to the dispenser. This can be a simple rectangular stand on one side. The dispenser also needs a cutting edge, so please add a simple edge on one side to tear the tape on.

Finally, the dispenser should have teeth on the outer edge for cutting. I'd like to be able to adjust the angle of the teeth, so that's a parameter too (default 7.5 degrees). The number of teeth should also be configurable (default 8).

I don't think I need any libraries beyond the basic OpenSCAD functions.

[Assistant]
tape_dispenser.scad:
```
/* =========================================================================
 * File Name: tape_dispenser.scad
 * Created By: SKaaj4

 * Creation Date: Do 09 Apr 2020 22:46:16 CEST
 * Last Modified: Do 09 Apr 2020 22:50:56 CEST
 * ========================================================================= */

/*[Resolution]*/

// Model Resolution
$fn = 160;

/*[Tape Measurements]*/

// Inner diameter of the tape
tape_inner_d = 76;

// Outer diameter of the tape (use the diameter of a full tape)
tape_outer_d = 100;

// Tape height
tape_h = 50;

/*[Dispenser Settings]*/

// Reduce the inner diameter of the dispenser by the value, so that the tape can move freely
dispenser_tape_inner_gap = 2;

// adds the value to the outer diameter of the dispenser, so that the tape can move freely
dispenser_tape_outer_gap = 4;

// adds the value two times to the overall size of the dispenser to get a gap for free movement of the tape. On the underside, it generates a circle to lift the tape a little bit.
dispenser_tape_h_gap = 1.5;

// overall thickness/strength of the dispenser
dispenser_thickness = 4;

// This Value is used for generating the notch thats hold the tape in the dispenser. Raise the value if the tape is going out of the dispenser to easy.
dispenser_holder_d = 2.5;

// Build with a stand
dispenser_stand = 1; //[1:True; 0:False]

/*[Dispenser Teeth Settings]*/
// Changes the Angle of the theeth
tape_teeth_deg = 7.5;

// Number of teeth
teeth = 8;

dispenser_h = tape_h + dispenser_tape_h_gap*2 + dispenser_thickness + dispenser_holder_d;
inner_circle = tape_inner_d - dispenser_tape_inner_gap;
inner_circle_diff = inner_circle - dispenser_thickness*2;
outer_circle = tape_outer_d + dispenser_tape_outer_gap + dispenser_thickness*2;
outer_circle_diff = outer_circle - dispenser_thickness*2;

// tape (activate when needed
//tape();

difference() {
	union() {
		// inner dispenser cylinder
		cylinder(d = inner_circle, h = dispenser_h);

		// base
		cylinder(d = outer_circle, h = dispenser_thickness);

		translate([0,0,dispenser_h-dispenser_holder_d/2])
	tape_holder_notch();
		// base tape lift
		translate([0,0, dispenser_thickness])
		cylinder(d = inner_circle + dispenser_tape_h_gap*2, h = dispenser_tape_h_gap);

	}

	cylinder(d = inner_circle_diff, h = dispenser_h + 1);

	// inner circle notches
	translate([0,0,dispenser_thickness+dispenser_h/2+dispenser_tape_h_gap]) {
	cube([inner_circle + dispenser_thickness*2, dispenser_thickness, dispenser_h], center = true);
	cube([dispenser_thickness, inner_circle + dispenser_thickness*2, dispenser_h], center = true);
	}
}

// outer circle
difference() {
	union() {
		cylinder(d = outer_circle, h = dispenser_h);

		//tape stand
		if (dispenser_stand != 0) {
			translate([-outer_circle/4, 0, dispenser_h/2])
			difference() {
				cube([outer_circle/2,outer_circle/1.4,dispenser_h], center=true);
				translate([dispenser_thickness, 0,0])
				cube([outer_circle/2,outer_circle/1.4-dispenser_thickness*2,dispenser_h], center=true);
			}
		}

		// tape cutter
		translate([0,outer_circle/2 - outer_circle/4,0])
		cube([outer_circle/2, outer_circle/4, dispenser_h]);
	}

	cylinder(d = outer_circle_diff, h = dispenser_h + 1);

	// tape access
	tape_mid = (outer_circle/2 - inner_circle/2 - dispenser_thickness) / 2;
	translate([inner_circle/2, 0, 0])
	cube([outer_circle/2 - inner_circle/2 + 1, outer_circle/2 + 1, dispenser_h + 1]);

	translate([inner_circle/3, inner_circle/2-dispenser_thickness, 0])
	cube([outer_circle/4, dispenser_thickness*2, dispenser_h + 1]);

	teeth(dispenser_h, teeth);

}
	// Round edge
	translate([outer_circle/2 - dispenser_thickness/2, 0,0])
	cylinder(d = dispenser_thickness, h = dispenser_h);

module tape() {
	color("gray")
	translate([0,0, dispenser_tape_h_gap + dispenser_thickness])
	difference() {
		cylinder(d = tape_outer_d, h = tape_h);
		translate([0,0,-0.5]) cylinder(d = tape_inner_d, h = tape_h+1);
	}
}

module tape_holder_notch() {
	rotate_extrude(convexity = 100)
	translate([inner_circle/2, 0,])
	circle(d = dispenser_holder_d);
}

module teeth(height, teeth_count = 8) {
	cube_c = height / teeth_count;
	cube_size = sqrt(pow(cube_c, 2)/2);

	translate([inner_circle/2, outer_circle/2,0])
	for( a=[0:teeth_count-1]) {
		rotate([0,0,180 - tape_teeth_deg])

		translate([-cube_size,0,a*cube_c])
			rotate([45,0,0])
		cube([cube_size*10, cube_size, cube_size]);

	}
}

```


[User]
I'd like to design a parametric hook for the Ikea Skadis pegboard, and I'm hoping you can help me generate the necessary OpenSCAD code. I have a good idea of the parameters I need, and I've even included some important considerations like smoothing edges and making the hook visually appealing.

Essentially, the hook should have a base that attaches to the Skadis board, and a customizable hook extending out from it. The base needs to have the correct dimensions to fit snugly into the Skadis holes without wobbling, something like the original file I based it on.

Here’s a breakdown of the parameters I envision:

**Base Parameters:**

*   **`BASE_HEIGHT`**: This will determine the overall height of the base section. I'd like to be able to adjust this from about 36 to 100 mm, maybe with a slider control.

**Hook Parameters:**

*   **`ENABLE_SMOOTH_EDGES`**:  A boolean to toggle smooth, rounded edges on the model. This should use a Minkowski sum technique, so please include the `minkowskiOutsideRound` module. I would like to be able to turn this off.
*   **`ENABLE_HOOK_NICE`**: Another boolean to control if the hook tip should have a small cut to make it more visually pleasing. It's a nice touch, but not needed for every print.
*   **`HOOK_SIZE_H`**: The horizontal length of the hook. This should be adjustable from 1mm to 100mm using a slider.
*   **`HOOK_SIZE_V`**: The vertical height of the hook. I'd like a range of 0 to 50mm here, also using a slider.
*   **`HOOK_ROTATION`**: To rotate the hook tip vertically, with an acceptable range of 0 to 90 degrees. I think a slider would also make sense here.

The design should also ensure it tightly fits the Skadis board, and we should aim for no wobbling when it is properly attached. To achieve this, the base should include a clip and pin mechanism similar to that used in the original designs that fit the Skadis board securely. I will need this mechanism on the base, which can be seen in the provided code.

I'd also like to ensure that the `minkowskiOutsideRound` module is used correctly to provide smooth edges when `ENABLE_SMOOTH_EDGES` is true. The rest of the modules should be written from scratch, assuming a clean OpenSCAD environment, with no other modules included.

[Assistant]
Skadis_Hook.scad:
```
/* [Hidden] */
$fn = 10;

CLIP_HEIGHT = 12;
CLIP_PIN_INNER_DISTANCE = 26;
PIN_LENGTH = 7;

BASE_WIDTH = 4.4;
BASE_DEPTH = 5;

/* [Base] */
// Height of the base.
BASE_HEIGHT = 50; // [36:100]

/* [Hook] */
// Enable Smooth Edges.
ENABLE_SMOOTH_EDGES = true;
// Make the hook a bit prettier.
ENABLE_HOOK_NICE = true;
// Length (horizontal size) of the hook.
HOOK_SIZE_H = 15; // [1:100]
// Height (vertical size) of the hook.
HOOK_SIZE_V = 10; // [0:50]
// Vertical rotation of the hook.
HOOK_ROTATION = 0; // [0:90]

minkowskiOutsideRound(1, ENABLE_SMOOTH_EDGES) {
	hook(
		base_height = BASE_HEIGHT,
		base_width = BASE_WIDTH,
		base_depth = BASE_DEPTH,
		clip_height = CLIP_HEIGHT,
		clip_pin_distance = CLIP_PIN_INNER_DISTANCE,
		pin_length = PIN_LENGTH,
		hook_size_h = HOOK_SIZE_H,
		hook_size_v = HOOK_SIZE_V,
		hook_nice = ENABLE_HOOK_NICE,
		hook_rotation = HOOK_ROTATION
	);
}

module hook(
	base_height = 50,
	base_width = 4.4,
	base_depth = 5,
	clip_height = 12,
	clip_pin_distance = 19,
	pin_length = 7,
	hook_size_h = 15,
	hook_size_v = 5,
	hook_nice = true,
	hook_rotation = 0,
) {
	// Base
	cube(size=[base_depth, base_height, base_width]);

	// Clip, Top
	translate([base_depth, base_height-base_depth, 0]) {
		cube(size=[base_depth, base_depth, base_width]);
	}

	// Clip, Back
	translate([base_depth*2, base_height-clip_height, 0]) {
		cube(size=[base_depth, clip_height, base_width]);
	}

	// Pin
	translate([base_depth, base_height-2*base_depth-clip_pin_distance, 0]) {
		cube(size=[7, base_depth, base_width]);
	}

	// Hook, Horizontal
	translate([-(hook_size_h-base_depth), 0, 0]) {
		cube(size=[hook_size_h-base_depth, base_depth, base_width]);
	}

	// Hook, Vertical
	if (hook_size_h > 0) {
		translate([-(hook_size_h-base_depth), 0, 0]) {
			rotate([0, 0, hook_rotation]) {
				difference() {
					cube(size=[base_depth, base_depth+hook_size_v, base_width]);

					if (hook_nice == true) {
						translate([0, hook_size_v+base_depth/2, -1]) {
							rotate([0, 0, 45]) {
							cube(size=[base_depth, base_depth, base_width+2]);
							}
						}
					}
				}
			}
		}
	}
}

// Library: MinkowskiRound.scad
// Version: 1.0
// Author: IrevDev
// Copyright: 2017
// License: LGPL 2.1 or later
// Url: https://github.com/Irev-Dev/Round-Anything

module minkowskiOutsideRound(r=1,enable=true,cubeSize=[500,500,500]){
    if(enable==false){//do nothing if not enabled
        children();
    } else {
        minkowski(){//expand the now positive shape
            difference(){//make the negative positive
                cube(cubeSize-[0.1,0.1,0.1],center=true);
                minkowski(){//expand the negative inwards
                    difference(){//create a negative of the children
                        cube(cubeSize,center=true);
                        children();
                    }
                    sphere(r);
                }
            }
            sphere(r);
        }
    }
}

```


[User]
I'd like to design a replacement for the optical drive bay in an older Thinkpad laptop, basically turning it into a small drawer for storing things. I have a basic idea of how I want the external shape to be and how the basic mechanism should work. I'd like it to snap into the bay just like the original optical drive.

The main part is the external frame and that is pretty fixed, but the internal storage area will have one to four holes (or compartments). I'd like to specify the width, length, and depth of each of those holes and enable or disable each hole. I also need to specify the spacing between each hole. So I need a few parameters:

*   `enablehole1`: "yes" or "no" to enable the first hole.
*   `hole1width`: Width of the first hole in mm.
*   `hole1length`: Length of the first hole in mm.
*   `hole1depth`: Depth of the first hole in mm.

And the same set of parameters for hole2, hole3 and hole4:

*   `enablehole2`: "yes" or "no" to enable the second hole.
*   `hole2width`: Width of the second hole in mm.
*   `hole2length`: Length of the second hole in mm.
*   `hole2depth`: Depth of the second hole in mm.

*   `enablehole3`: "yes" or "no" to enable the third hole.
*   `hole3width`: Width of the third hole in mm.
*   `hole3length`: Length of the third hole in mm.
*   `hole3depth`: Depth of the third hole in mm.

*   `enablehole4`: "yes" or "no" to enable the fourth hole.
*   `hole4width`: Width of the fourth hole in mm.
*   `hole4length`: Length of the fourth hole in mm.
*   `hole4depth`: Depth of the fourth hole in mm.

*   `spacing`: Distance in mm between the edges of adjacent holes

I've got the basic dimensions for the exterior here. It's a bit complex, so it might be easiest to just provide the 2D polygon coordinates and then extrude them to the proper heights. I want to use `linear_extrude` for these. Note that the z axis is the vertical axis and the polygon shapes should be specified using a right-hand coordinate system.

Here are the coordinates for the main body:
*   A base extrusion of 37mm from the polygon: `[ [0,0], [126.5,0], [126.5+5,-1], [126.5+5,6.5], [126.5+5-4,6.5+6], [0,6.5+6], [0,6.5+6-2], [10,6.5+6-2], [10,3], [0,3], ]`
*   Then, an extrusion from z=37 to z=75 from this polygon: `[[-1,0], [126.5,0], [126.5+5,-1], [126.5+5,6.5], [126.5+5-4,6.5+6], [-1,6.5+6]]`
*   Then, another extrusion from z=37 to z=128-27 from this polygon: `[ [0,0], [126.5,0], [126.5+5,-1], [126.5+5,6.5], [126.5+5-4,6.5+6], [0,6.5+6], ]`
*   Then an extrusion from z=0 to z=128-27+3 from this polygon: `[[110,0], [126.5,0], [126.5+5,-1], [126.5+5,6.5], [126.5+5-4,6.5+6], [102.5,6.5+6], [102.5+16,6.5+6], [102.5+16,6.5+6-3], [102.5+11,6.5+6-3], [102.5+11,6.5+6], [110,6.5+6], ]`
*   And lastly an extrusion from z=0 to z=128 from the polygon: `[ [126.5,0], [126.5+5,-1], [126.5+5,6.5], [126.5,6.5], [126.5,3.5], ]`

I also need a `hull()` of two parts:

*   An extrusion from z=0 to z=128 of a rectangle at `[36, 0]` with `width = 127-36` and `height = 3.5`
*   An extrusion from z=0 to z=128-27 of a rectangle at `[12,0]` with `width = 10` and `height = 3.5`

The holes can just be `cube()` shapes subtracted from the main body. I think that's it. It would be great if you could generate the scad file or files.

[Assistant]
thinkpad_cd_drawer.scad:
```
/* model thinkpad cd drawer replacement
MRAAGH
February 2020

A little drawer for users of old thinkpads who think the optical drive is dead weight
and would rather substitute it for some storage space.
This drawer snaps in just like the optical drive, it doesn't fall out on its own.
Enable 0 to 4 holes for storing specific objects.
Useful for DisplayPort adapter, usb key, sd card, pencil, screwdriver, money, phone, other adapters and cables.
Make sure to remove the screwdriver before going on a plane. I am not responsible for airport issues.
 */

// Enable first hole?
enablehole1 = "yes"; // [yes, no]
// First hole width mm
hole1width = 9;
// First hole length mm
hole1length = 86;
// First hole depth mm
hole1depth = 9;

// Enable second hole?
enablehole2 = "yes"; // [yes, no]
// Second hole width mm
hole2width = 24;
// Second hole length mm
hole2length = 51;
// Second hole depth mm
hole2depth = 9;

// Enable third hole?
enablehole3 = "yes"; // [yes, no]
// Third hole width mm
hole3width = 18;
// Third hole length mm
hole3length = 54;
// Third hole depth mm
hole3depth = 9;

// Enable fourth hole?
enablehole4 = "yes"; // [yes, no]
// Fourth hole width mm
hole4width = 18;
// Fourth hole length mm
hole4length = 54;
// Fourth hole depth mm
hole4depth = 9;

// The amount of mm between adjacent holes
spacing = 3;

difference(){union(){
    linear_extrude(37, convexity=10){
        polygon([
                [0,0],
                [126.5,0],
                [126.5+5,-1],
                [126.5+5,6.5],
                [126.5+5-4,6.5+6],
                [0,6.5+6],
                [0,6.5+6-2],
                [10,6.5+6-2],
                [10,3],
                [0,3],
        ]);
    }

    translate([0,0,37])
        linear_extrude(75-37, convexity=10){
            polygon([
                    [-1,0],
                    [126.5,0],
                    [126.5+5,-1],
                    [126.5+5,6.5],
                    [126.5+5-4,6.5+6],
                    [-1,6.5+6],
            ]);
        }

    translate([0,0,37])
    linear_extrude(128-37-27, convexity=10){
        polygon([
                [0,0],
                [126.5,0],
                [126.5+5,-1],
                [126.5+5,6.5],
                [126.5+5-4,6.5+6],
                [0,6.5+6],
        ]);
    }

    linear_extrude(128-27+3, convexity=10){
        polygon([
                [110,0],
                [126.5,0],
                [126.5+5,-1],
                [126.5+5,6.5],
                [126.5+5-4,6.5+6],
                [102.5,6.5+6],
                [102.5+16,6.5+6],
                [102.5+16,6.5+6-3],
                [102.5+11,6.5+6-3],
                [102.5+11,6.5+6],
                [110,6.5+6],
        ]);
    }

    linear_extrude(128, convexity=10){
        polygon([
                [126.5,0],
                [126.5+5,-1],
                [126.5+5,6.5],
                [126.5,6.5],
                [126.5,3.5],
        ]);
    }

    hull(){
    linear_extrude(128, convexity=10){
        translate([36,0])
        square([127-36, 3.5]);
    }
    linear_extrude(128-27, convexity=10){
        translate([12,0])
        square([10, 3.5]);
    }
    }

}

if (enablehole1 == "yes")
translate([120-hole1width,-1,0.6])
cube([hole1width,hole1depth+1,hole1length]);

if (enablehole2 == "yes")
translate([120-hole1width-spacing-hole2width,-1,0.6])
cube([hole2width,hole2depth+1,hole2length]);

if (enablehole3 == "yes")
translate([120-hole1width-spacing-hole2width-spacing-hole3width,-1,0.6])
cube([hole3width,hole3depth+1,hole3length]);

if (enablehole4 == "yes")
translate([120-hole1width-spacing-hole2width-spacing-hole3width-spacing-hole4width,-1,0.6])
cube([hole4width,hole4depth+1,hole4length]);

}

```


[User]
I'd like to design a parametric ear saver for face masks, specifically those with double elastic straps. It should be a flat piece with some text in the center, and clips at both ends to hold the mask's elastic.

Here are the specific features I'd like to be able to control with parameters:

*   **`text`**: The text to be displayed in the center of the band. Default should be "javierddn".
*   **`font`**: The font to use for the text. Default should be "Liberation Sans".
*   **`style`**: The style of the font (e.g. "bold", "italic"). Default should be "bold".
*   **`length`**: The overall length of the band. Default should be 180mm.
*   **`width`**: The overall width of the band. Default should be 30mm.
*   **`height`**: The overall height of the band. Default should be 270mm.
*    **`model_height`**: The thickness of the band. Default should be 1mm.
*   **`text_size`**: The size of the text. Default should be 9mm.
*   **`r`**: The radius of the circles that make up the clips. Default should be 3mm.
*   **`clips`**: The number of clips on each side of the band. Default should be 4.
*  **`supports_ref_x`**: This is a reference, not a parameter, this value must not be parametric, and will be used for positioning the clips. Default must be 0.6.
*  **`circle_y`**: This is the vertical offset for the circles that form the clips. Default must be 9
*  **`circle_x`**: This is the horizontal offset for the circles that form the clips. This will depend on the length parameter and the supports_ref_x parameter. Default must be length/2*0.52

The design should have the text centered horizontally and vertically. There should be 2 rectangular supports on each side of the text, as well as 2 circles on each side.  The clips should be made of two circles offset vertically with a rectangular support between them, repeated on each side of the band.  The circles should be placed at different X values.

I would like the design to make use of the `String.scad` library, and I'd prefer the file to use mm as the units.

[Assistant]
salva_orejas_custom_2.0.scad:
```
echo(version=version());

text = "javierddn";
font = "Liberation Sans";
style = "bold";
length=180;
width=30;
height=270;
model_height=1;
text_size=9;
r = 3;
clips=4;
supports_ref_x=0.6;
circle_y = 9;
circle_x = length/2*0.52;

linear_extrude(height = model_height, $fn = 100){
    //Text
    text(
        size = text_size,
        text = upper(text),
        font = is_empty(style)? font : join([font,style],":style="),
        halign = "center",
        valign= "center",
        $fn = 100
    );

    //Left
    translate ([-(length/2*0.74),3]) square([length/2*0.42,4], true);
    translate ([-(length/2*0.74),-3]) square([length/2*0.42,4], true);
    translate ([-(length/2*0.53),4]) circle(r);
    translate ([-(length/2*0.53),-4]) circle(r);

    //Right
    translate ([length/2*0.74,-3]) square([length/2*0.42,4], true);
    translate ([length/2*0.74,3]) square([length/2*0.42,4], true);
    translate ([length/2*0.53,4]) circle(r);
    translate ([length/2*0.53,-4]) circle(r);

    //Center
    translate ([0,5.5]) square([length*0.53,3],true);
    translate ([0,-5.5]) square([length*0.53,3],true);
}

for (i = [1:clips]){
    linear_extrude(height = model_height, $fn = 100) {
        x = i*10;
        translate ([-x - circle_x,circle_y])
            circle(r);
        translate ([-x - circle_x,-circle_y])
            circle(r);
        translate ([-x - circle_x -3,-9])
            square([4.5,18]);

        translate ([x + circle_x,circle_y])
            circle(r);
        translate ([x + circle_x,-circle_y])
            circle(r);
        translate ([x + circle_x-1.5,-9])
            square([4.5,18]);
    }
}

// String.scad library

_strings_version =
	[2016, 2, 2];
function strings_version() =
	_strings_version;
function strings_version_num() =
	_strings_version.x * 10000 + _strings_version.y * 100 + _strings_version.z;

_ASCII_SPACE 	= 32;
_ASCII_0 		= 48;
_ASCII_9 		= _ASCII_0 + 9;
_ASCII_UPPER_A 	= 65;
_ASCII_UPPER_Z 	= _ASCII_UPPER_A + 25;
_ASCII_LOWER_A 	= 97;
_ASCII_LOWER_Z 	= _ASCII_LOWER_A + 25;
_ASCII = "\t\n\r !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
_ASCII_HACK = "\""; // only here to work around syntax highlighter defficiencies in certain text editors
_WHITESPACE = " \t\r\n";
_ASCII_CODE = concat(9,10,13, [for(i=[_ASCII_SPACE : _ASCII_LOWER_Z+4]) i]);

function ascii_code(string) =
	!is_string(string)?
		undef
    :
        [for (result = search(string, _ASCII, 0))
            result == undef?
                undef
            :
                _ASCII_CODE[result[0]]
        ]
	;

// PEG ENGINE
_PARSED = 0;
_POS = 1;
function _match_parsed_peg( string, peg, string_pos=0, peg_op=undef,  ignore_case=false, string_code=undef ) =
	let(
		opcode = peg_op[0],
		operands = _slice(peg_op, 1)
	)

	string == undef?
		undef
    : string_pos > len(string)?
        undef

    : peg != undef && peg_op == undef?
    	_match_parsed_peg(	string, peg, string_pos, peg[1],
    						 ignore_case=ignore_case, string_code=string_code )

    : string_code == undef?
    	_match_parsed_peg( string, peg, string_pos, peg_op, ignore_case=ignore_case, string_code=ascii_code(string) )

    : opcode == "grammar"?
    	_match_parsed_peg(	string, peg_op, string_pos, peg_op[1],
							 ignore_case=ignore_case, string_code=string_code )[_PARSED][0]
	: opcode == "rule"?
		let(result = _match_parsed_peg(	string, peg, string_pos, operands[1],
										ignore_case=ignore_case, string_code=string_code ))
		result != undef?
			[[concat( [operands[0]], result[_PARSED] )], result[_POS]]
		:
			undef
	: opcode == "private_rule"?
		let(result = _match_parsed_peg(	string, peg, string_pos, operands[1],
										ignore_case=ignore_case, string_code=string_code ))
		result != undef?
			result
		:
			undef
	: opcode == "ref"?
		len(peg) > operands[0]?
			_match_parsed_peg(string, peg, string_pos, peg[operands[0]], ignore_case=ignore_case, string_code=string_code )
		:
			["ERROR: unrecognized ref id, '"+operands[0]+"'"]

	// BINARY
	: opcode == "choice"?
		let( option = _match_parsed_peg(string, peg, string_pos, operands[0], ignore_case=ignore_case, string_code=string_code ) )
		option != undef?
			option
		: len(operands) < 2?
			undef
		:
			_match_parsed_peg(string, peg, string_pos, concat(opcode, _slice(operands, 1)), ignore_case=ignore_case, string_code=string_code )
	: opcode == "sequence"?
		let( first = _match_parsed_peg(string, peg, string_pos, operands[0], ignore_case=ignore_case, string_code=string_code ) )
		first == undef?
			undef
		: len(operands) == 1?
			first
		:
			let( rest = _match_parsed_peg(string, peg, first[_POS], concat(opcode, _slice(operands, 1)), ignore_case = ignore_case) )
			rest == undef?
				undef
			: is_string(first[_PARSED][0]) && is_string(rest[_PARSED][0])?
				[[str(first[_PARSED][0], rest[_PARSED][0])], rest[_POS]]
			:
				[concat(first[_PARSED], rest[_PARSED]), rest[_POS]]

	// PREFIX
	: opcode == "positive_lookahead"?
		_match_parsed_peg(string, peg, string_pos, operands[0], ignore_case=ignore_case, string_code=string_code ) != undef?
			[[], string_pos]
		:
			undef
	: opcode == "negative_lookahead"?
		_match_parsed_peg(string, peg, string_pos, operands[0], ignore_case=ignore_case, string_code=string_code ) == undef?
			[[], string_pos]
		:
			undef

	// POSTFIX
	: opcode == "one_to_many"?
		_match_parsed_peg(string, peg, string_pos,
			["sequence", operands[0], ["zero_to_many", operands[0]]	],
			ignore_case=ignore_case, string_code=string_code
			)
	: opcode == "zero_to_many"?
		_match_parsed_peg(string, peg, string_pos,
			["choice",
				["sequence", operands[0], ["zero_to_many", operands[0]] ],
				["empty_string"]
			],
			ignore_case=ignore_case, string_code=string_code
			)
	: opcode == "many_to_many"?
		let(min = operands[1][0],
			max = operands[1][1])
		let(min = is_string(min)? parse_int(min) : min,
			max =
				max == undef?
					undef
				: is_string(max)?
					parse_int(max)
				:
					max
			)
		min == undef?
			undef
		: max == undef?
			_match_parsed_peg(string, peg, string_pos,
				concat(["sequence"],
						[for (i = [0:min-1])
							operands[0]
						],
						[["zero_to_many", operands[0]]]
					),
				ignore_case=ignore_case, string_code=string_code
				)
		: max < 0 || min > max?
			undef
		:
			_match_parsed_peg(string, peg, string_pos,
				concat(["sequence"],
						[for (i = [0:min-1])
							operands[0]
						],
						[for (i = [min:max-1])
							["zero_to_one", operands[0]]
						]
					),
				ignore_case=ignore_case, string_code=string_code
				)
	: opcode == "zero_to_one"?
		_match_parsed_peg(string, peg, string_pos,
			["choice",
				operands[0],
				["empty_string"]
			],
			ignore_case=ignore_case, string_code=string_code
			)

	// PRIMITIVES
	: opcode == "literal"?
		!starts_with(string, operands[0], string_pos, ignore_case=ignore_case) ?
			undef
		:
			[
				[between(string, string_pos, string_pos+len(operands[0]))],
				string_pos+len(operands[0])
			]
	: opcode == "positive_character_set"?
		let(matches		=
			[ for (arg = operands)
				arg == str(arg)?
					equals(string[string_pos], arg, ignore_case=ignore_case)
				:
					_match_parsed_peg(string, peg, string_pos, arg, ignore_case=ignore_case, string_code=string_code ) != undef
			])

		!any(matches)?
			undef
		:
			[ [string[string_pos]], string_pos+1 ]
	: opcode == "negative_character_set"?
		let(matches	=
			[ for (arg = operands)
				arg == str(arg)?
					equals(string[string_pos], arg, ignore_case=ignore_case)
				:
					_match_parsed_peg(string, peg, string_pos, arg, ignore_case=ignore_case, string_code=string_code ) != undef
			])

		any(matches)?
			undef
		:
			[ [string[string_pos]], string_pos+1 ]
	: opcode == "character_range"?
		!_is_in_range(string_code[string_pos], ascii_code(operands[0][0])[0], ascii_code(operands[0][1])[0])?
			undef
		:
			[ [string[string_pos]], string_pos+1 ]
	: opcode == "character_literal"?
		!equals(string[string_pos], operands[0], ignore_case=ignore_case)?
			undef
		:
			[ [string[string_pos]], string_pos+1 ]
	: opcode == "character_set_shorthand"?
		operands[0] == "s" && !_is_in_range(string_code[string_pos], 0, _ASCII_SPACE)? //whitespace
			undef
		: operands[0] == "S" && _is_in_range(string_code[string_pos], 0, _ASCII_SPACE)? //nonwhitespace
			undef

		: operands[0] == "d" && !_is_in_range(string_code[string_pos], _ASCII_0, _ASCII_9)? //digit
			undef
		: operands[0] == "D" && _is_in_range(string_code[string_pos], _ASCII_0, _ASCII_9)? //nondigit
			undef

		: operands[0] == "w" && !_is_variable_safe(string_code[string_pos])? // word character
			undef
		: operands[0] == "W" && _is_variable_safe(string_code[string_pos])? //non word character
			undef
		: !is_in(operands[0], "sSdDwW") && !equals(string[string_pos], operands[0], ignore_case=ignore_case)? // literal
			undef
		:
			[ [string[string_pos]], string_pos+1 ]
	: opcode == "wildcard"?
		string[string_pos] == undef?
			undef
		:
			[ [string[string_pos]], string_pos+1 ]
	: opcode == "start"?
		string_pos == 0?
			[[], string_pos]
		:
			undef
	: opcode == "end"?
		string_pos == len(string)?
			[[], string_pos]
		:
			undef
	: opcode == "private"?
		let( result = _match_parsed_peg(string, peg, string_pos, operands[0], ignore_case=ignore_case, string_code=string_code))
		result == undef?
			undef
		:
			[[], result[_POS]]
	: opcode == "empty_string"?
		[[], string_pos]
    :
        ["ERROR: unrecognized opcode, '"+opcode+"'"]
	;

// PEG SYMBOL LINKER
function _get_rule_indexer(peg) =
	[for (rule = _slice(peg, 1))
		rule[1]
	];
function _get_rule_index(rule, indexer) =
	[for (i = [0:len(indexer)-1]) if(indexer[i] == rule) i+1 ] [0];
function _index_peg_op_refs(peg_op, indexer) =
	let(
			opcode = peg_op[0],
			operands = _slice(peg_op, 1)
		)
	peg_op == str(peg_op)?
		peg_op
	: opcode == "ref"?
		["ref", _get_rule_index(operands[0], indexer)]
	:
		concat(opcode, [for (operand = operands) _index_peg_op_refs(operand, indexer)])
    ;
function _index_peg_refs(peg) =
	_index_peg_op_refs(peg, _get_rule_indexer(peg));
function _get_rule(peg, ref) =
	[for (rule = peg) if(rule[1] == ref) rule ] [0];

// PUBLIC FACING PEG
function peg(string, grammar) = _match_parsed_peg(string, _index_peg_refs(grammar));

_rx_peg =
_index_peg_refs
(
	["grammar",
		["private_rule", "operation",
			["sequence",
				["choice",
					["ref", "choice"],
					["ref", "sequence"],
					["ref", "postfix"],
				],
				// ["negative_lookahead", ["wildcard"]],
			]
		],
		["private_rule", "postfix",
			["choice",
				["ref", "many_to_many"],
				["ref", "one_to_many"],
				["ref", "zero_to_many"],
				["ref", "zero_to_one"],
				["ref", "primitive"]
			],
		],

		//BINARY OPERATIONS
		["rule", "choice",
			["sequence",
				["choice",
					["ref", "sequence"],
					["ref", "postfix"]
				],
				["one_to_many",
					["sequence",
						["private", ["literal", "|"]],
						["choice",
							["ref", "sequence"],
							["ref", "postfix"]
						],
					]
				]
			]
		],
		["rule", "sequence",
			["sequence",
				["ref", "postfix"],
				["one_to_many",
					["ref", "postfix"],
				 ]
			]
		],

		["rule", "positive_lookahead",
			["sequence",
				["private", ["literal", "(?="]],
				["ref", "operation"],
				["private", ["literal", ")"]],
			]
		],
		["rule", "negative_lookahead",
			["sequence",
				["private", ["literal", "(?!"]],
				["ref", "operation"],
				["private", ["literal", ")"]],
			]
		],

		//UNARY POSTFIX OPERATIONS
		["rule", "one_to_many",
			["sequence",
				["ref", "primitive"],
				["private", ["literal", "+"]]
			]
		],
		["rule", "zero_to_many",
			["sequence",
				["ref", "primitive"],
				["private", ["literal", "*"]]
			]
		],
		["rule", "zero_to_one",
			["sequence",
				["ref", "primitive"],
				["private", ["literal", "?"]]
			]
		],
		["rule", "many_to_many",
			["sequence",
				["ref", "primitive"],
				["private", ["literal", "{"]],
				["character_set_shorthand", "d"],
				["zero_to_one",
					["sequence",
						["private", ["literal", ","]],
						["character_set_shorthand", "d"],
					],
				],
				["private", ["literal", "}"]],
			]
		],

		//PRIMITIVES
		["private_rule", "primitive",
			["choice",
				["ref", "start"],
				["ref", "end"],
				["ref", "wildcard"],
				["ref", "character_set_shorthand"],
				["ref", "negative_character_set"],
				["ref", "positive_character_set"],
				["ref", "negative_lookahead"],
				["ref", "positive_lookahead"],
				["sequence",
					["private", ["literal", "("]],
					["ref", "operation"],
					["private", ["literal", ")"]],
				],
				["ref", "literal"],
			],
		],

		["rule", "start",
			["private", ["literal", "^"]],
		],
		["rule", "end",
			["private", ["literal", "$"]],
		],
		["rule", "wildcard",
			["private", ["literal", "."]],
		],
		["rule", "literal",
			["negative_character_set",
				"{","}","[","]","(",")",
				"|","*","+","?",".","\\",".",
				"^","$",
			],
		],
		["rule", "positive_character_set",
			["sequence",
				["private", ["literal", "["]],
				["one_to_many",
					["choice",
						["ref", "character_range"],
						["ref", "character_set_shorthand"],
						["ref", "character_literal"]
					]
				],
				["private", ["literal", "]"]],
			]
		],
		["rule", "negative_character_set",
			["sequence",
				["private", ["literal", "[^"]],
				["one_to_many",
					["choice",
						["ref", "character_range"],
						["ref", "character_set_shorthand"],
						["ref", "character_literal"]
					]
				],
				["private", ["literal", "]"]],
			]
		],
		["rule", "character_literal",
			["negative_character_set", "]"]
		],
		["rule", "character_range",
			["sequence",
				["character_set_shorthand", "w"],
				["private", ["literal", "-"]],
				["character_set_shorthand", "w"],
			],
		],
		["rule", "character_set_shorthand",
			["sequence",
				["private", ["literal", "\\"]],
				["positive_character_set",
					"s","S","d","D","w","W", "\\", "]", "(", ")"
				]
			]
		],
	]
);
function _parse_rx(rx) =
	_match_parsed_peg(rx, _rx_peg)[_PARSED][0];

function _match_parsed_rx(string, regex, string_pos=0, ignore_case=false) =
	_match_parsed_peg(string,
		undef,
		string_pos=string_pos,
		peg_op=regex,
		ignore_case=ignore_case)[_POS];
function _match_regex(string, pattern, pos=0, ignore_case=false) = 		//end pos
	_match_parsed_rx(string,
		regex=_parse_rx(pattern),
		string_pos=pos,
		ignore_case=ignore_case);

//returns a list representing the tokenization of an input string
//echo(tokenize("not(foo)"));
//echo(tokenize("foo bar baz  "));
_token_regex_ignore_space = _parse_rx("\\w+|\\S");
_token_regex = _parse_rx("\\w+|\\S|\\s+");
function tokenize(string, ignore_space=true) =
    _tokenize(string, ignore_space? _token_regex_ignore_space : _token_regex);
function _tokenize(string, pattern) =
    _grep(string, _index_of(string, pattern, regex=true));

function grep(string, pattern, ignore_case=false) = 		//string
    _grep(string, _index_of(string, _parse_rx(pattern), regex=true, ignore_case=ignore_case));
function _grep(string, indices) =
    [for (index = indices)
        between(string, index.x, index.y)
    ];

function replace(string, replaced, replacement, ignore_case=false, regex=false) =
	_replace(string, replacement, index_of(string, replaced, ignore_case=ignore_case, regex=regex));

function _replace(string, replacement, indices, i=0) =
    i >= len(indices)?
        after(string, indices[len(indices)-1].y-1)
    : i == 0?
        str( before(string, indices[0].x), replacement, _replace(string, replacement, indices, i+1) )
    :
        str( between(string, indices[i-1].y, indices[i].x), replacement, _replace(string, replacement, indices, i+1) )
    ;

function split(string, seperator=" ", ignore_case = false, regex=false) =
	_split(string, index_of(string, seperator, ignore_case=ignore_case, regex=regex));

function _split(string, indices, i=0) =
    len(indices) == 0?
        [string]
    : i >= len(indices)?
        _coalesce_on(after(string, indices[len(indices)-1].y-1), "", [])
    : i == 0?
        concat( _coalesce_on(before(string, indices[0].x), "", []), _split(string, indices, i+1) )
    :
        concat( between(string, indices[i-1].y, indices[i].x), _split(string, indices, i+1) )
    ;

function contains(string, substring, ignore_case=false, regex=false) =
	regex?
        _index_of_first(string, _parse_rx(substring), regex=regex, ignore_case=ignore_case) != undef
	:
		_index_of_first(string, substring, regex=regex, ignore_case=ignore_case) != undef
	;

function index_of(string, pattern, ignore_case=false, regex=false) =
	_index_of(string,
        regex? _parse_rx(pattern) : pattern,
        regex=regex,
        ignore_case=ignore_case);
function _index_of(string, pattern, pos=0, regex=false, ignore_case=false) = 		//[start,end]
	pos == undef?
        undef
	: pos >= len(string)?
		[]
	:
        _index_of_recurse(string, pattern,
            _index_of_first(string, pattern, pos=pos, regex=regex, ignore_case=ignore_case),
            pos, regex, ignore_case)
	;
function _index_of_recurse(string, pattern, index_of_first, pos, regex, ignore_case) =
    index_of_first == undef?
        []
    : concat(
        [index_of_first],
        _coalesce_on(
            _index_of(string, pattern,
                    pos = index_of_first.y,
                    regex=regex,
                    ignore_case=ignore_case),
            undef,
            [])
    );
function _index_of_first(string, pattern, pos=0, ignore_case=false, regex=false) =
	pos == undef?
        undef
    : pos >= len(string)?
		undef
	: _coalesce_on([pos, _match(string, pattern, pos, regex=regex, ignore_case=ignore_case)],
		[pos, undef],
		_index_of_first(string, pattern, pos+1, regex=regex, ignore_case=ignore_case))
    ;
function _match(string, pattern, pos, regex=false, ignore_case=false) =
    regex?
    	_match_parsed_peg(string, undef, pos, peg_op=pattern, ignore_case=ignore_case)[_POS]
    : starts_with(string, pattern, pos, ignore_case=ignore_case)?
        pos+len(pattern)
    :
        undef
    ;

function starts_with(string, start, pos=0, ignore_case=false, regex=false) =
	regex?
		_match_parsed_peg(string,
			undef,
			pos,
			_parse_rx(start),
			ignore_case=ignore_case) != undef
	:
		equals(	substring(string, pos, len(start)),
			start,
			ignore_case=ignore_case)
	;
function ends_with(string, end, ignore_case=false) =
	equals(	after(string, len(string)-len(end)-1),
		end,
		ignore_case=ignore_case)
	;

function _pop(stack, n=1) =
	n <= 1?
		len(stack) <=0? [] : stack[1]
	:
		_pop(_pop(stack), n-1)
	;
function _push(stack, char) =
	[char, stack];

function is_string(x) =
	x == str(x);

function is_empty(string) =
	string == "";

function is_null_or_empty(string) =
	string == undef || string == "";

function is_null_or_whitespace(string) =
	string == undef || len([for (char=ascii_code(string)) if(char>0) char]) < 1;

function trim(string) =
	string == undef?
		undef
	: string == ""?
		""
	:
		_null_coalesce(
			between(string, _match_set(string, _WHITESPACE, 0),
					_match_set_reverse(string, _WHITESPACE, len(string))),
			""
		)
	;

function _match_set(string, set, pos) =
	pos >= len(string)?
		len(string)
	: is_in(string[pos], set )?
		_match_set(string, set, pos+1)
	:
		pos
	;

function _match_set_reverse(string, set, pos) =
	pos <= 0?
		0
	: is_in(string[pos-1], set)?
		_match_set_reverse(string, set, pos-1)
	:
		pos
	;

function _is_in_range(code, min_char, max_char) =
	code >= min_char &&
	code <= max_char;

function _is_variable_safe(code) =
	_is_in_range(code, _ASCII_0, _ASCII_9) ||
	_is_in_range(code, _ASCII_UPPER_A, _ASCII_UPPER_Z) ||
	_is_in_range(code, _ASCII_LOWER_A, _ASCII_LOWER_Z) ||
	chr(code) == "_";

function equals(this, that, ignore_case=false) =
	ignore_case?
		lower(this) == lower(that)
	:
		this==that
	;

function upper(string) =
	let(code = ascii_code(string))
	join([for (i = [0:len(string)-1])
			code[i] >= 97 && code[i] <= 122?
                chr(code[i]-97+65)
            :
                string[i]
		]);
function lower(string) =
	let(code = ascii_code(string))
	join([for (i = [0:len(string)-1])
			code[i] >= 65 && code[i] <= 90?
                chr(code[i]+97-65)
            :
                string[i]
		]);

function reverse(string) =
	string == undef?
		undef
	: len(string) <= 0?
		""
	:
        join([for (i = [0:len(string)-1]) string[len(string)-1-i]])
    ;

function substring(string, start, length=undef) =
	length == undef?
		between(string, start, len(string))
	:
		between(string, start, length+start)
	;

//note: start is inclusive, end is exclusive
function between(string, start, end) =
	string == undef?
		undef
	: start == undef?
		undef
	: start > len(string)?
		undef
	: start < 0?
		before(string, end)
	: end == undef?
		undef
	: end < 0?
		undef
	: end > len(string)?
		after(string, start-1)
	: start > end?
		undef
	: start == end ?
		""
	:
        join([for (i=[start:end-1]) string[i]])
	;

function before(string, index=0) =
	string == undef?
		undef
	: index == undef?
		undef
	: index > len(string)?
		string
	: index <= 0?
		""
	:
        join([for (i=[0:index-1]) string[i]])
	;

function after(string, index=0) =
	string == undef?
		undef
	: index == undef?
		undef
	: index < 0?
		string
	: index >= len(string)-1?
		""
	:
        join([for (i=[index+1:len(string)-1]) string[i]])
	;

function parse_int(string, base=10, i=0, nb=0) =
	string[0] == "-" ?
		-1*_parse_int(string, base, 1)
	:
		_parse_int(string, base);

function _parse_int(string, base, i=0, nb=0) =
	i == len(string) ?
		nb
	:
		nb + _parse_int(string, base, i+1,
				search(string[i],"0123456789ABCDEF")[0]*pow(base,len(string)-i-1));

function join(strings, delimeter="") =
	strings == undef?
		undef
	: strings == []?
		""
	: _join(strings, len(strings)-1, delimeter, 0);
function _join(strings, index, delimeter) =
	index==0 ?
		strings[index]
	: str(_join(strings, index-1, delimeter), delimeter, strings[index]) ;

function is_in(string, list, ignore_case=false) =
	string == undef?
		false
    :
        any([ for (i = [0:len(list)-1]) equals(string, list[i], ignore_case=ignore_case) ])
	;

function _slice(array, start=0, end=-1) =
	array == undef?
		undef
	: start == undef?
		undef
	: start >= len(array)?
		[]
	: start < 0?
		_slice(array, len(array)+start, end)
	: end == undef?
		undef
	: end < 0?
		_slice(array, start, len(array)+end)
	: end >= len(array)?
        undef
    : start > end && start >= 0 && end >= 0?
        _slice(array, end, start)
	:
        [for (i=[start:end]) array[i]]
	;

function any(booleans, index=0) =
    index > len(booleans)?
        false
    : booleans[index]?
        true
    :
        any(booleans, index+1)
    ;
function all(booleans, index=0) =
	index >= len(booleans)?
		true
	: !booleans[index]?
		false
	:
		all(booleans, index+1)
	;

function _null_coalesce(string, replacement) =
	string == undef?
		replacement
	:
		string
	;
function _coalesce_on(value, error, fallback) =
	value == error?
		fallback
	:
		value
	;

function _unit_test(name, tests) =
	let(results =
		[for(i=[0:len(tests)/2])
			let(test = tests[i*2],
				result=tests[i*2+1])
			test==result
		])
	!all(results)?
		concat([name],
			[for(i=[0:len(tests)/2])
				let(test = tests[i*2],
					result=tests[i*2+1])
				[test, result]
			]
		)
	:
		str(name, ":\tpassed")
    ;
```


[User]
I'm looking for a way to generate a customizable box with a hinged lid and a latch, and I'd like to be able to control a number of features.  I've seen something like this before, and I want to do something similar.

Specifically, I need to be able to set the overall length, width, and height of the box halves individually. These should be outside dimensions.  I’d like a parameter for wall thickness, as well.

The box should have a hinge mechanism along one of the length edges. I'd like to be able to specify a hinge offset from the edge, and the hinge length. I'd also like to be able to set a constant width for the flat part of the hinge mechanism.  The hinge itself should be a pin type.

The latch should be on the opposite side from the hinge, and I need to be able to set its width. The latch should be a simple hook style.

I'd like an option to add a lip to the bottom half of the box, and be able to control both the lip height and width. This lip should create a tight fit when the box is closed.

Finally, I'd like an option to add engraved text to the lid, and to be able to control the text itself, the font, and potentially the depth of the engraving (though that could probably be derived from wall thickness).

Ideally, the design should be printable in place, meaning the hinge works without any assembly (other than maybe cleanup of the hinge joint). This also suggests that both halves of the box should be the same height by default. I don't care if it can also be printed in two parts, that might be a good option.

I'd like to be able to use the OpenSCAD customizer to adjust all of these parameters easily. I don't need any special OpenSCAD libraries like BOSL, but I do want it to be efficient and flexible. Please generate the necessary .scad file(s).

[Assistant]
toolbox.scad:
```
/*
 Storage box with hinge and latch

 V1.0   - First release after remix
 V1.1   - Added lip on bottom half
 V1.2   - Added text on lid and code tidy up
 V1.3   - Bug fix: bottom was 2* wall_thickness
 V1.4   - Openscad customizer (View->Customizer (unhide))
 V1.4.1 - Variable latch width
 V1.4.2 - Fix for thingiverse not supporting true/false

 Note the lid and bottom are the same height, so it can be printed
 in place flat on the bed. You may be able to print separate halves
 of different heights and push the hinge together, no guarantees.

 Remixed from:
 https://www.thingiverse.com/thing:2969508

 Tested with:
    length width   height  lip    text
    100      50     12.5   false  false  original (before remix)
    135      90     14     false  false
    135      90     18     true   true
*/

/* [Box dimensions] */
// maximums are set for a 200mm square bed
// hinge on this length
length = 135;         // [70:200]
width = 90;           // [20:95]
// per half, outside dimension. MINIMUM 12mm
height = 18;          // [12:50]
wall_width = 2;       //
latch_width = 10;

/* [Text on lid] */
// Add an engraved text on the top, 'help->font list' for available
add_text = 1;  // [0:No,1:Yes]
lid_text = "Tools";
text_font="Comic Sans MS";

/* [Lip] */
// Add a lip on bottom, note will reduce inside dimension by 2*lip_width
add_lip = 1;  // [0:No,1:Yes]
lip_height = 2.0;
lip_width = 1.0;

/* [fixed values] */
hinge_width_flat = 5;
latch_thickness = 2;
hinge_from_edge = (length>100)?20:10;  // above 100mm wide hinges are 10mm further in
hinge_length = 10;
rounding_radius = 2.5;

/* [Render resolution] */
// Set to 0.01 for higher definition curves (renders slower)
$fs = 0.01;

// derived values, does not take account of lip on bottom half
inside_length = length-2*wall_width;
inside_width = width-2*wall_width;
inside_height = height-wall_width+1;  // extra 1 to compensate for translate

/*******************/
/*                 */
/* start of design */
/*                 */
/*******************/

// lay flat on x
rotate([180,0,0])
// offset by height of each half on z
translate([0,0,-height])
// offset either side of y to allow for hinge
translate([0,hinge_width_flat,0])

// bottom with latch and optional lip
union() {
    difference() {
        roundedcube([length, width, height], false, rounding_radius, "zmax");
        // remove the inside
        translate([wall_width, wall_width, -1]) {
            roundedcube([inside_length, inside_width, inside_height],false,rounding_radius,"zmax");
        }

        // 4 finger grips on outside of bottom
        groove(((length/2)-(latch_width/2)), (width-2*wall_width), -3, g_length=latch_width-2, g_offset=-4);
        groove(((length/2)-(latch_width/2)), (width-2*wall_width), -3, g_length=latch_width-2, g_offset=-6);
        groove(((length/2)-(latch_width/2)), (width-2*wall_width), -3, g_length=latch_width-2, g_offset=-8);
        groove(((length/2)-(latch_width/2)), (width-2*wall_width), -3, g_length=latch_width-2, g_offset=-10);
    }

    // latch on inside
    translate([(length/2)-(latch_width/2), (width-2*wall_width), -3]) {
        union() {
            cube([latch_width,latch_thickness,height+1]);
            rotate([0,90,0]) {
                translate([-1,1.8,1]) {
                    cylinder(h=latch_width-2, r1=1, r2=1, $fn=100);
                }
            }
        }
    }

    // the dust lip
    if (add_lip) {
        translate([0, 0, 0]) {
            difference() {
                translate([wall_width, wall_width, -lip_height]) {
                    roundedcube([inside_length, inside_width, inside_height+lip_height], false, rounding_radius, "zmax");
                }
                translate([wall_width+lip_width, wall_width+lip_width, -lip_height-1]) {
                    roundedcube([inside_length-2*lip_width, inside_width-2*lip_width, inside_height+2], false, rounding_radius, "zmax");
                }
            }
        }
    }

    // hinges
    hingeouter(hinge_from_edge, 10);
    hingeouter((length-hinge_from_edge-hinge_length), 0);
}

// top with notch to receive the latch from the bottom
translate([0,0,height])
rotate([180,0,0])
union() {
    translate([0, -(width+hinge_width_flat), 0]) {
        difference() {
            roundedcube([length, width, height], false, rounding_radius, "zmax");

            translate([wall_width, wall_width, -1]) {
                roundedcube([inside_length, inside_width, inside_height], false, rounding_radius,"zmax");
            }

            // groove for latch in inside
            translate([(length/2)-(latch_width/2),2,2]) {
                rotate([0,90,0]) {
                    cylinder(h=latch_width-2, r1=1, r2=1, $fn=100);
                }
            }

            // text, depth of engraving is a fraction of the wall_width here
            if(add_text) {
                translate([length/2,width/2,height-wall_width/5]) {
                    linear_extrude(5, convexity=4) {
                        mirror([180,0,0]) {
                            mirror([180,0,0]) {
                                text(lid_text, font=text_font, valign="center", halign="center");
                            }
                        }
                    }
                }
            }
        }
    }

    // hinges
    hingeinner((hinge_from_edge+hinge_length+.5), 0);
    hingeinner((length-hinge_from_edge-2*hinge_length-0.5), 10);
}

module hingeouter(h_offset, mm){
    rotate([0,90,0]) {
        translate([0,-5,h_offset]) {
            difference() {
                union() {
                    cylinder(h=10, r1=4, r2=4, $fn=100);
                    difference() {
                        translate([2,3,0])
                        rotate([0,0,135])
                        cube([10,7,10]);
                        translate([0,7,-5])
                        rotate([0,0,90])
                        cube([4,10,20]);
                    }
                }
                translate([0,0,mm]) {
                    sphere(3, $fn=100);
                }
            }
        }
    }
}

module hingeinner(h_offset, mm){
    rotate([0,90,0]) {
        translate([0,0,h_offset]) {
            union() {
                cylinder(h=10, r1=4, r2=4, $fn=100);
                difference() {
                    translate([-5,-10,0])
                    rotate([0,0,45])
                    cube([9,7,10]);
                    translate([0,-10,-1])
                    rotate([0,0,90])
                    cube([3,10,20]);
                }
                translate([0,0,mm]) {
                    sphere(2.75, $fn=100);
                }
            }
        }
    }
}

module groove(x_start, y_start, z_start, g_length, g_offset) {
    translate([x_start, y_start, z_start]) {
        rotate([0, 90, 0]) {
            translate([g_offset, 4.8, 1]) {
                cylinder(h=g_length, r1=1, r2=1, $fn=100);
            }
        }
    }
}

module roundedcube(size = [1, 1, 1], center = false, radius = 0.5, apply_to = "all") {
	// If single value, convert to [x, y, z] vector
	size = (size[0] == undef) ? [size, size, size] : size;

	translate_min = radius;
	translate_xmax = size[0] - radius;
	translate_ymax = size[1] - radius;
	translate_zmax = size[2] - radius;

	diameter = radius * 2;

	obj_translate = (center == false) ?
		[0, 0, 0] : [
			-(size[0] / 2),
			-(size[1] / 2),
			-(size[2] / 2)
		];

	translate(v = obj_translate) {
		hull() {
			for (translate_x = [translate_min, translate_xmax]) {
				x_at = (translate_x == translate_min) ? "min" : "max";
				for (translate_y = [translate_min, translate_ymax]) {
					y_at = (translate_y == translate_min) ? "min" : "max";
					for (translate_z = [translate_min, translate_zmax]) {
						z_at = (translate_z == translate_min) ? "min" : "max";

						translate(v = [translate_x, translate_y, translate_z])
						if (
							(apply_to == "all") ||
							(apply_to == "xmin" && x_at == "min") || (apply_to == "xmax" && x_at == "max") ||
							(apply_to == "ymin" && y_at == "min") || (apply_to == "ymax" && y_at == "max") ||
							(apply_to == "zmin" && z_at == "min") || (apply_to == "zmax" && z_at == "max")
						) {
							sphere(r = radius);
						} else {
							rotate =
								(apply_to == "xmin" || apply_to == "xmax" || apply_to == "x") ? [0, 90, 0] : (
								(apply_to == "ymin" || apply_to == "ymax" || apply_to == "y") ? [90, 90, 0] :
								[0, 0, 0]
							);
							rotate(a = rotate)
							cylinder(h = diameter, r = radius, center = true);
						}
					}
				}
			}
		}
	}
}
```