[User]
Hey, I'm looking to design a customizable wall plate system using OpenSCAD. I've seen some existing designs, but I'd like to have one that's easy to extend and modify with new port types. I need it to be parametric so I can easily adjust the number of ports on the plate, the size of the plate itself, and which port types are included.

Here's what I'm thinking:

**Core Requirements:**

*   **Plate Size:** I need to be able to choose between a standard size plate, and two larger sizes (junior jumbo and jumbo).
*   **Number of Ports:** The plate should support between 1 and 5 ports, configurable by the user.
*   **Port Types:** I need a variety of port types, including:
    *   Blank port (just an empty space)
    *   Full Plate Toggle Switch
    *   Full Plate Long Toggle Switch
    *   Full Plate Duplex Outlet
    *   Full Plate Single Round Outlet
    *   Full Plate Rocker/Designer Plate
    *   Keystone Jack
    *   VGA Port
    *   HDMI Port
    *   DVI-I Port
    *   DisplayPort
    *   Cat5e/Cat6 Port
    *   USB-A Port
    *   USB-B Port
    *   Firewire IEEE 1394 Port
    *   DB-09 Port
    *   PS2 Port
    *    F-Type/Coaxial Port
    *   S-Video Port
    *   Stereo Headphone Jack
*   **Top/Bottom Options:** For each port location (1-5) on the plate, some ports have a "top" and "bottom" part that can be assigned independently from the other. For example, a port can have a keystone jack on the top part and be blank on the bottom, or vice-versa. Ports that take up a "full plate" (e.g., duplex outlet) would not need the top/bottom option.
*   **Screw Holes:** The plate needs to have appropriate screw holes based on the port type. For example, a toggle switch would need two holes spaced a specific distance, while a standard box would need different holes. All of these must scale correctly with plate size.
*   **Beveling:** The plate should have a beveled edge for a more finished look.

**Parametric Design:**

I need to be able to set parameters for:

*   `plate_width`: To control the number of ports (1-5).
*    `plate_size`: To choose between standard, junior-jumbo, and jumbo plate sizes.
*   `1st_plate` through `5th_plate`: To specify the main port type for each location.
*   `1st_plate_bottom_hole` through `5th_plate_bottom_hole`: To specify the bottom port type for each location, or "none" if there isn't one.

**Modularity & Code Style**

*   I'd like the code to be relatively straightforward to modify so I can add new port types myself in the future, with well-defined modules for each port type and for the plate itself.
*   I don't need comments on every line of the code, but it should be reasonably commented and organized to make the structure clear.

Essentially, I need a system that's flexible enough to accommodate a wide array of connectors and easily adjustable to various wall plate configurations. I'd like to think of it as a "wall plate construction kit." Can you help me create this in OpenSCAD? I don't want to use any libraries if possible, but I'm open to it if you think it makes a big difference for the overall design.

[Assistant]
WALLY_Customizer_v2.scad:
```
// WALLY - Wall Plate Customizer v1.3
// by TheNewHobbyist 2013 (http://thenewhobbyist.com)
// http://www.thingiverse.com/thing:47956
//
// Most of the connectors used with this thing are panel mount connectors from
// www.DataPro.net I can't vouch for their quality but they did have easy to
// find dimensions for their products so I used them.
//
// This could be tightened up considerably but I thought I'd get it out there for people
// to experiment with before I lose my mind staring at it too long. Also this is my
// first OpenSCAD model so cut me some slack ;)
//
//   Notes:
//
// * The plug types "Duplex Outlet", "Long_Toggle Switch", "Single Outlet", "Toggle Switch", and "Rocker"
//   will all use the entire plate when selected in top or bottom configuration. The
//   other selection (if not "none" will be selected.
//
// * "None" and "Blank should not be considered equal. if "None" is selected for one of
//   the two holes on a plate the other selection will be centered on that plate. If
//   "Blank" is selected that plug location will remain blank but the other selection
//   will NOT move into a centered position.
//
//	Change Log:
//
//	v1.1
//	UPDATED: Screw hole locations for designer and toggle plate types.
//	UPDATED: Hole size for designer plate type.
//	UPDATED: Outlet hole spacing
//
//	v1.2
//	ADDED: Long Toggle Switch for laird
//
//	v1.3
//	ADDED: 3 new sizes for plate export: Standard, Junior-Jumbo, and Jumbo.
//

//////////////////////////
// Customizer Settings: //
//////////////////////////

//How big are we talkin' here?
plate_width = 1; //	[1:5]

// Bigger hole in your wall? Try this
plate_size = 0; // [0:Standard, 1:Junior-Jumbo, 2:Jumbo]

//Pick a plug or full plate type
1st_plate = "none"; // ["none":None, "blank":Blank Port, "toggle":Full Plate - Toggle Switch, "long_toggle":Full Plate - Long Toggle Switch, "outlet":Full Plate - Duplex Outlet, "single":Full Plate - Single Round Outlet, "rocker":Full Plate - Rocker/Designer Plate, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a bottom plug type or leave as none
1st_plate_bottom_hole = "none";  //	["none":None, "blank":Blank Port, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a plug or full plate type
2nd_plate = "none";  // ["none":None, "blank":Blank Port, "toggle":Full Plate - Toggle Switch, "long_toggle":Full Plate - Long Toggle Switch, "outlet":Full Plate - Duplex Outlet, "single":Full Plate - Single Round Outlet, "rocker":Full Plate - Rocker/Designer Plate, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a bottom plug type or leave as none
2nd_plate_bottom_hole = "none";  //	["none":None, "blank":Blank Port, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a plug or full plate type
3rd_plate = "none";  // ["none":None, "blank":Blank Port, "toggle":Full Plate - Toggle Switch, "long_toggle":Full Plate - Long Toggle Switch, "outlet":Full Plate - Duplex Outlet, "single":Full Plate - Single Round Outlet, "rocker":Full Plate - Rocker/Designer Plate, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a bottom plug type or leave as none
3rd_plate_bottom_hole = "none";  //	["none":None, "blank":Blank Port, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a plug or full plate type
4th_plate = "none";  // ["none":None, "blank":Blank Port, "toggle":Full Plate - Toggle Switch, "long_toggle":Full Plate - Long Toggle Switch, "outlet":Full Plate - Duplex Outlet, "single":Full Plate - Single Round Outlet, "rocker":Full Plate - Rocker/Designer Plate, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a bottom plug type or leave as none
4th_plate_bottom_hole = "none";  //	["none":None, "blank":Blank Port, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a plug or full plate type
5th_plate = "none";  // ["none":None, "blank":Blank Port, "toggle":Full Plate - Toggle Switch, "long_toggle":Full Plate - Long Toggle Switch, "outlet":Full Plate - Duplex Outlet, "single":Full Plate - Single Round Outlet, "rocker":Full Plate - Rocker/Designer Plate, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]
//Pick a bottom plug type or leave as none
5th_plate_bottom_hole = "none";  //	["none":None, "blank":Blank Port, "keystone1":Keystone Jack, "vga":VGA Port, "hdmi":HDMI Port, "dvi":DVI-I Port, "displayport":Displayport, "cat5e":Cat5e/Cat6 Port, "usb-a":USB-A Port, "usb-b":USB-B Port, "firewire":Firewire IEEE 1394 Port, "db09":DB-09 Port, "ps2":PS2 Port, "f-type": F-Type/Coaxial Port,"svideo":S-Video Port, "stereo":Stereo Headphone Jack]

//////////////////////
// Static Settings: //
//////////////////////

module GoAwayCustomizer() {
// This module is here to stop Customizer from picking up the variables below
}

// Move the customizer settings into an array so we can process them
plate = [1st_plate, 2nd_plate, 3rd_plate, 4th_plate, 5th_plate];
plate_bottom_hole = [1st_plate_bottom_hole, 2nd_plate_bottom_hole, 3rd_plate_bottom_hole, 4th_plate_bottom_hole, 5th_plate_bottom_hole];

l_offset = [34.925,39.6875,44.45];
r_offset = [34.925,39.6875,44.45];
spacer = [0,0,46.0375,92.075,138.1125,184.15];
solid_plate_width = l_offset[plate_size] + spacer[plate_width] + r_offset[plate_size];

height_sizes = [114.3,123.825,133.35];

height = 114.3; //plate height (static)
gang_width=[0,69.86,115.824,161.925,206.375,254]; // Plate widths (standard)

edgewidth = solid_plate_width + 10; // Bevel setting for top and bottom
rightbevel = solid_plate_width - 4; // Bevel for right side (scales)

left_offset = 34.925; // Offset first hole position
switch_offset = 46.0375; // Offset all additional holes
thinner_offset=[0,0.92,0.95,0.96,0.97,0.973]; // Manual fix for right side wackiness

positions=[height_sizes[plate_size]/2,height_sizes[plate_size]/2 - 14.25,height_sizes[plate_size]/2 + 14.25];

// preview[view:north, tilt:bottom]

///////////////////
// Hole Control: //
///////////////////

module plate_section(p) {
    p=p-1;
	if (plate[p] == "toggle" || plate_bottom_hole[p] == "toggle") {
		translate([0,l_offset[plate_size] + (switch_offset * p),0]) toggle_screws();
		translate([0,l_offset[plate_size] + (switch_offset * p),0]) hole("toggle");
	}
    	else if (plate[p] == "long_toggle" || plate_bottom_hole[p] == "long_toggle") {
		translate([0,l_offset[plate_size] + (switch_offset * p),0]) toggle_screws();
		translate([0,l_offset[plate_size] + (switch_offset * p),0]) hole("long_toggle");
    }
	else if (plate[p] == "single" || plate_bottom_hole[p] == "single") {
		translate([0,l_offset[plate_size] + (switch_offset * p),0]) toggle_screws();
        translate([0,l_offset[plate_size] + (switch_offset * p),0]) hole("single");
    }
    else if (plate[p] == "rocker" || plate_bottom_hole[p] == "rocker") {
		translate([0,l_offset[plate_size] + (switch_offset * p),0]) rocker_screws();
		translate([0,l_offset[plate_size] + (switch_offset * p),0]) hole("rocker");
    }
	else if (plate[p] == "outlet" || plate_bottom_hole[p] == "outlet") {
        translate([0,l_offset[plate_size] + (switch_offset * p),0]) hole("outlet");
	}
	else if (plate_bottom_hole[p] == "none") {
		translate([0,l_offset[plate_size] + (switch_offset * p),0]) box_screws();
		translate([positions[0],l_offset[plate_size] + (switch_offset * p),0]) hole(plate[p]);
    }
	else if (plate[p] == "none") {
		translate([0,l_offset[plate_size] + (switch_offset * p),0]) box_screws();
		translate([positions[0],l_offset[plate_size] + (switch_offset * p),0]) hole(plate_bottom_hole[p]);
    }
	else {
        translate([0,l_offset[plate_size] + (switch_offset * p),0]) box_screws();
        translate([positions[1],l_offset[plate_size] + (switch_offset * p),0]) hole(plate[p]);
        translate([positions[2],l_offset[plate_size] + (switch_offset * p),0]) hole(plate_bottom_hole[p]);
	}
}

/////////////////
// SolidWorks: //
/////////////////

module plate_solid(p) {
    p=p-1;
    if (plate[p] == "keystone1" && plate_bottom_hole[p] == "none") {
        translate([height_sizes[plate_size]/2 + 14.3,l_offset[plate_size] - 11.5 + (switch_offset * p),-3.9]) hole("keystone_solid");
	}
    else if (plate[p] == "keystone1" && plate_bottom_hole[p] != "outlet" && plate_bottom_hole[p] != "single" &&     plate_bottom_hole[p] != "toggle" && plate_bottom_hole[p] != "rocker") {
        translate([height_sizes[plate_size]/2,l_offset[plate_size] - 11.5 + (switch_offset * p),-3.9]) hole("keystone_solid");
	}
    if (plate_bottom_hole[p] == "keystone1" && plate[p] == "none") {
        translate([height_sizes[plate_size]/2 + 14.3,l_offset[plate_size] - 11.5 + (switch_offset * p),-3.9]) hole("keystone_solid");
	}
    else if (plate_bottom_hole[p] == "keystone1" && plate[p] != "outlet" && plate_bottom_hole[p] != "single" && plate[p] != "toggle" && plate[p] != "rocker") {
        translate([height_sizes[plate_size]/2 + 28.5,l_offset[plate_size] - 11.5 + (switch_offset * p),-3.9]) hole("keystone_solid");
	}
}

///////////////////
// PlateStation: //
///////////////////

//Plate size and bevel
module plate() {
	difference() {
		cube([height_sizes[plate_size],solid_plate_width,6]);
		translate([-4.3,-5,6.2]) rotate([0,45,0]) cube([6,edgewidth,6]); //Top Bevel
		translate([height_sizes[plate_size]-4.2,-5,6.25]) rotate([0,45,0]) cube([6,edgewidth,6]); //Bottom Bevel
		translate([height_sizes[plate_size]+10,-4.4,6.1]) rotate([0,45,90]) cube([6,height_sizes[plate_size]+20,6]); //Left Bevel (doesn't change)
		translate([height_sizes[plate_size]+10,rightbevel,6]) rotate([0,45,90]) cube([6,height_sizes[plate_size]+10,6]); //Right Bevel (scales right)
	}
}

// Thinning Plate
module plate_inner() {
	scale([0.95,thinner_offset[plate_width],1]) {
        translate([3,3,0]) {
            difference() {
                cube([height_sizes[plate_size],solid_plate_width,6]);
                translate([-4.3,-5,6.2]) rotate([0,45,0]) cube([6,edgewidth,6]); //Top Bevel
                translate([height_sizes[plate_size]-4.2,-5,6.25]) rotate([0,45,0]) cube([6,edgewidth,6]);           //Bottom Bevel
                translate([height_sizes[plate_size]+10,-4.4,6.1]) rotate([0,45,90]) cube([6,height_sizes[plate_size]+20,6]); //Left Bevel (doesn't change)
                translate([height_sizes[plate_size]+10,rightbevel,6]) rotate([0,45,90]) cube([6,height_sizes[plate_size]+10,6]); //Right Bevel (scales right)
            }
		}
	}
}

// Box screw holes
module box_screws() {
	 translate([height_sizes[plate_size]/2 + 41.67125,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 + 41.67125,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
	 translate([height_sizes[plate_size]/2 - 41.67125,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 - 41.67125,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
}

// Rocker/Designer screw holes
module rocker_screws() {
	 translate([height_sizes[plate_size]/2 + 48.41875,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 + 48.41875,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
	 translate([height_sizes[plate_size]/2 - 48.41875,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 - 48.41875,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
}

// Toggle screw holes
module toggle_screws() {
	 translate([height_sizes[plate_size]/2 + 30.1625,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 + 30.1625,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
	 translate([height_sizes[plate_size]/2 - 30.1625,0,-1]) cylinder(r=2, h=10, $fn=12);
	 translate([height_sizes[plate_size]/2 - 30.1625,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
}

///////////////
// Portland: //
///////////////

// Hole Cutout definitions
module hole(hole_type) {

    // Toggle switch hole
	if (hole_type == "toggle") {
		translate([height_sizes[plate_size]/2,0,0]) cube([23.8125,10.3188,15], center = true);
	}

    // Toggle switch hole and screw holes
	if (hole_type == "long_toggle") {
		translate([height_sizes[plate_size]/2,0,0]) cube([43.6563,11.9063,15], center = true);
	}

    // Rocker switch plate
	if (hole_type == "rocker") {
		translate([height_sizes[plate_size]/2,0,0]) cube([67.1,33.3,15], center = true);
	}

    // Duplex power outlet plate or dual side toggles
	if (hole_type == "outlet" || hole_type == "dualsidetoggle") {
		translate([height_sizes[plate_size]/2 + 19.3915,0,0]) {
			difference() {
				cylinder(r=17.4625, h=15, center = true);
				translate([-24.2875,-15,-2]) cube([10,37,15], center = false);
				translate([14.2875,-15,-2]) cube([10,37,15], center = false);
			}
		}
		translate([height_sizes[plate_size]/2 - 19.3915,0,0]) {
			difference() {
				cylinder(r=17.4625, h=15, center = true);
				translate([-24.2875,-15,-2]) cube([10,37,15], center = false);
				translate([14.2875,-15,-2]) cube([10,37,15], center = false);
			}
		}
		translate([height_sizes[plate_size]/2,0,-1]) cylinder(r=2, h=10);
		translate([height_sizes[plate_size]/2,0,3.5]) cylinder(r1=2, r2=3.3, h=3);
	}

    // Blank plate
	if (hole_type == "blank") { }

    // VGA & DB09 plate
    // VGA Fits http://www.datapro.net/products/vga-dual-panel-mount-f-f-cable.html
    // DB09 Fits http://www.datapro.net/products/db9-serial-panel-mount-male-extension.html
	if (hole_type == "vga" || hole_type == "db09") {
        translate([0,-12.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,12.5,3]) cylinder(r=1.75, h=10, center = true); {
            difference() {
                cube([10,19,13], center=true);
                translate([-5,-9.2,1]) rotate([0,0,-35.6]) cube([4.4,2.4,15], center=true);
                translate([.9,-11.2,0]) rotate([0,0,9.6]) cube([10,4.8,15], center=true);
                translate([4.6,-8.5,0]) rotate([0,0,37.2]) cube([4.4,2.4,15], center=true);
                translate([-5,9.2,1]) rotate([0,0,35.6]) cube([4.4,2.4,15], center=true);
                translate([0.9,11.2,0]) rotate([0,0,-9.6]) cube([10,4.8,15], center=true);
                translate([4.6,8.5,0]) rotate([0,0,-37.2]) cube([4.4,2.4,15], center=true);
            }
		}
	}

    // HDMI plate
    // Fits http://www.datapro.net/products/hdmi-panel-mount-extension-cable.html
	if (hole_type == "hdmi") {
		translate([0,-13,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,13,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([6,16,10], center=true);
	}

    // DVI-I plate
    // Fits http://www.datapro.net/products/dvi-i-panel-mount-extension-cable.html
	if (hole_type == "dvi") {
		translate([0,-16,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,16,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([10,26,10], center=true);
	}

    // DisplayPort plate
    // Fits http://www.datapro.net/products/dvi-i-panel-mount-extension-cable.html
	if (hole_type == "displayport") {
		translate([0,-13.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,13.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,0]) {
			difference() {
				translate([0,0,3]) cube([7,19,10], center=true);
				translate([2.47,-9.37,3]) rotate([0,0,-54.6]) cube([3,5,14], center=true);
			}
		}
	}

    // USB-A Plate
    // Fits http://www.datapro.net/products/usb-panel-mount-type-a-cable.html
	if (hole_type == "usb-a") {
		translate([0,-15,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,15,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([8,16,10], center=true);
	}

    // USB-B Plate
    // Fits http://www.datapro.net/products/usb-panel-mount-type-b-cable.html
	if (hole_type == "usb-b") {
		translate([0,-13,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,13,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([11,12,10], center=true);
	}

    // 1394 Firewire Plate
    // Fits http://www.datapro.net/products/firewire-panel-mount-extension.html
	if (hole_type == "firewire") {
		translate([0,-13.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,13.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([7,12,10], center=true);
	}

    // F-Type / Cable TV Plate
    // Fits http://www.datapro.net/products/f-type-panel-mounting-coupler.html
	if (hole_type == "f-type") {
		translate([0,0,3]) cylinder(r=4.7625, h=10, center=true);
	}

    // Cat5e & Gat6 plate
    // Cat5e Fits http://www.datapro.net/products/cat-5e-panel-mount-ethernet.html
    // Cat6 Fits hhttp://www.datapro.net/products/cat-6-panel-mount-ethernet.html
	if (hole_type == "cat5e" || hole_type == "cat6") {
		translate([0,-12.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,12.5,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cube([15,15,10], center=true);
	}

    // S-Video & PS2 plate
    // S-Video Fits hhttp://www.datapro.net/products/cat-6-panel-mount-ethernet.html
    // PS2 http://www.datapro.net/products/ps2-panel-mount-extension-cable.html
	if (hole_type == "svideo" || hole_type == "ps2") {
		translate([0,-10,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,10,3]) cylinder(r=1.75, h=10, center = true);
		translate([0,0,3]) cylinder(r=5, h=10, center=true);
	}

    // Stereo / 1/4" headphone jack coupler
    // Stereo coupler Fits http://www.datapro.net/products/stereo-panel-mount-coupler.html
	if (hole_type == "stereo") {
		translate([0,0,3]) cylinder(r=2.985, h=10, center=true);
	}

    // Single round outlet
	if (hole_type == "single") {
		translate([height_sizes[plate_size]/2,0,0]) cylinder(r=18, h=12, center=true);
	}

    //Keystone1 hole
    //Hole for 1 Keystone Jack
	if (hole_type == "keystone1") {
		translate([0,0,5]) cube([16.5,15,10], center = true);
	}

    //Keystone Solid
	if (hole_type == "keystone_solid") {
		rotate([0,0,90]) {
			difference() {
				translate([0,0,.1]) cube([23,30.5,9.8]);
				translate([4,4,0]) {
					difference() {
						cube([15,22.5,10]);
						translate([-1,-0.001,3.501]) cube([17,2,6.5]);
						translate([-1,2.5,-3.40970]) rotate([45,0,0]) cube([17,2,6.5]);
						translate([-1,18.501,6.001]) cube([17,4,4]);
						translate([-1,20.5,0]) rotate([-45,0,0]) cube([17,2,6.5]);
					}
				}
			}
		}
    }
} //End of module "hole"

////////////////////////
// Number One ENGAGE: //
////////////////////////

// Rotate so it sits correctly on plate (whoops) and make upside down
rotate([0,180,90]) {

    // put plate at 0,0,0 for easier printing
    translate([-height_sizes[plate_size]/2,-solid_plate_width/2,-6]) {

        difference() {
            plate();
            translate([0,0,-3]) plate_inner();
            for (i = [1:plate_width]) {
                plate_section(i);
            }
        }
        union() {
            for (i = [1:plate_width]) {
                plate_solid(i);
            }
        }
    }
}

```


[User]
I'd like to create a random crystal cluster generator in OpenSCAD. I want to be able to specify a few parameters to control the overall look.

Specifically, I need a base cylinder, and then a number of randomly placed and oriented "crystal points" coming off of it.

Here's a more detailed breakdown of what I'm looking for:

**Parameters:**

*   `point_num`: The number of crystal points to generate.
*   `tilt`:  The maximum tilt angle (in degrees) for the crystal points. The orientation of each crystal point will be randomly tilted within this range in all axes.
*   `minsize`: The minimum size for the crystal points (both diameter and height are scaled by this).
*   `maxsize`: The maximum size for the crystal points.
* The base cylinder will be centered and should have a diameter equal to `maxsize` * 2 at the bottom, `maxsize` at the top, and height of `maxsize`. It will have 6 sides.

**Crystal Points:**

*   Each crystal point will be composed of two cylinders.
    *   The first cylinder's diameter will be scaled by a random size between `minsize` and `maxsize` and the height will be 2.5 times that size. It has 6 sides.
    *   The second cylinder will have the same center as the first cylinder's top. It has a diameter 1.5 times the size of the first and will have height scaled to .35 of the overall size with a top diameter of 0. It has 6 sides.
*   The random size for each crystal point's diameter should be independently generated.
*   The position of each point should be random within the range -1 to 1 in the x,y, and z direction.
*   The orientation of each point should be random within the range of `-tilt` to `tilt` in all three axes.

I think it would be helpful if the generation of the random values for the location and orientation were seeded. The positions should be generated with seed values i-1 and the rotations with seed values of i where i is between 1 and 100 stepping by 100/point_num. For example, if `point_num` is 10, the steps for i would be 1, 11, 21, 31 ... 91.

The entire thing will be in a single `.scad` file. I don't need any extra libraries or anything.

[Assistant]
CRCCG.scad:
```
point_num=14;
tilt=15;
minsize=10;
maxsize=30;

cylinder(d1=maxsize*2,d2=maxsize,h=maxsize,$fn=6,center=true);

for(i=[1:100/point_num:100]){
seed=i-1;
 seed2=i;
 single_rand = rands(minsize,maxsize,1)[0];
echo(single_rand);

random_vect=rands(-1,1,3,seed);
 random_vect2=rands(-tilt,tilt,3,seed2);
 echo(random_vect2);
translate(random_vect)
 rotate(random_vect2)
point(single_rand,single_rand*2.5);
}

module point(DI,HI){
cylinder(d1=DI,d2=DI*1.5,h=HI*.65,$fn=6);
translate([0,0,HI*.65])
cylinder(d1=DI*1.5,d2=0,h=HI*.35,$fn=6);
}
```


[User]
Alright, I'm looking to get a respirator mask designed in OpenSCAD, something that I can customize with parameters for different face sizes. I've got a basic idea of how I want it to look, inspired by some of the DIY mask designs out there, but I need help putting it together in a way that's easy to adjust.

Here’s what I’m thinking:

**Overall Structure:**

I want the design to have three main components that can be toggled – the mask itself, a filter port, and the option to combine both. So, a variable `part` with the options "mask", "vent", and "both" would control which pieces are rendered.

**Mask Shape:**

The mask should have a rounded, organic shape that fits comfortably around the face. I'd like to control the following dimensions:

*   `depth`: The overall depth or protrusion of the mask.
*   `nose_height`:  The height of the nose area.
*   `cheek_height`: The height of the cheek area.
*   `cheek_spread`: How wide the mask is at the cheeks.
*   `chin_spread`: How wide the mask is at the chin.
*  `nose_radius`, `pinch_radius`, `cheek_radius`, `chin_radius`: Radii to control the roundness of the main features
*   `pinch_depth`: The depth of the "pinched" area between the cheek and nose, basically creating a bridge for the nose.
*  `skew`: how far the chin is shifted towards the back of the mask
*  `taper`: controls the overall taper of the mask

**Filter Port:**

The filter port needs to be designed to attach to the mask, and should have a lip for fitting it to the mask. I also want to add grill cuts for airflow, and straps for securing it to the mask.

*   The filter port's dimensions should be derived relative to the dimensions of the mask.
*   `port_depth`: The depth of the filter port.
* `lip_thickness`: The width of the lip on the filter port
* The grill should be a series of horizontal cuts and be controlled by `wall_thickness` so that each cut is an appropriate distance apart
* Add straps with a length related to the filter area

**Additional Technical Details:**

*   Use a resolution variable (`res`) to control the smoothness of the curves, which should default to something reasonable like 64.
*   `wall_thickness` variable should allow the user to change the thickness of the parts
* `port_fit` variable should control the amount of overlap between the mask and port

**Libraries**
I'm not very familiar with the OpenSCAD libraries, but I'd like you to use the BOSL2 library for any complex shape or boolean operations that can benefit from its functionality.

Could you design this, please, and make it parametric so that I can easily tweak all these values? I really appreciate the help!

[Assistant]
respirator_mask_v1.0.scad:
```

/* [Basic Settings] */
part = "both"; // [mask:Mask Only,vent:Filter Only,both:Mask And Filter]

depth = 40;          //[25:100]
nose_height = 55;    //[40:100]
cheek_height = 100;  //[70:150]
cheek_spread = 65;   //[45:100]
chin_spread = 40;    //[10:75]

/* [More Settings] */
nose_radius = 15;   //[10:30]
pinch_radius = 20;  //[20:60]
pinch_depth = 7;    //[0:10]
cheek_radius = 25;  //[20:50]
chin_radius = 15;   //[10:50]
skew = 20;          //[0:50]
taper = .7;         //[.5:.1:1]

{
/****************************************************************************************************************/

    res = 64;
    wall_thickness = 2;
    port_fit = 0.2;
    port_depth = wall_thickness*4;
    lip_thickness = wall_thickness*5;

    // Mask
    chin_x = max(0,(chin_spread-chin_radius)/2);
    chin_y = -skew;
    cheek_x = max(0, (cheek_spread-cheek_radius)/2);
    cheek_y = chin_y+max(0,cheek_height-chin_radius-cheek_radius);
    chin_cut_a = 11;
    chin_cut_l = cheek_height*2;
    chin_cut_w = max(cheek_spread+cheek_radius*2,chin_spread+chin_radius*2);
    nose_x = 0;
    nose_y = cheek_y+max(0,nose_height-nose_radius);
    nose_cut_a = 22.5;
    nose_cut_l = cheek_radius+nose_height+nose_radius;
    nose_cut_w = cheek_spread+cheek_radius;
    measure_a = atan2(nose_y-cheek_y,nose_x-cheek_x);
    measure_x1 = cheek_x+cos(measure_a-90)*cheek_radius;
    measure_y1 = cheek_y+sin(measure_a-90)*cheek_radius;
    measure_x2 = nose_x+cos(measure_a-90)*nose_radius;
    measure_y2 = nose_y+sin(measure_a-90)*nose_radius;
    edge_a = atan2(measure_y2-measure_y1,measure_x2-measure_x1);
    edge_x1 = cheek_x+cos(edge_a-90)*(cheek_radius+pinch_radius);
    edge_y1 = cheek_y+sin(edge_a-90)*(cheek_radius+pinch_radius);
    edge_x2 = nose_x+cos(edge_a-90)*(nose_radius+pinch_radius);
    edge_y2 = nose_y+sin(edge_a-90)*(nose_radius+pinch_radius);
    edge_ctx = edge_x1+(edge_x2-edge_x1)/2+cos(edge_a+90)*pinch_depth;
    edge_cty = edge_y1+(edge_y2-edge_y1)/2+sin(edge_a+90)*pinch_depth;

    // Port
    port_space_needed = wall_thickness*4+port_fit*2;
    port_x1 = (chin_x+cos(-45)*chin_radius)*taper+cos(135)*port_space_needed;
    port_y1 = (chin_y+sin(-45)*chin_radius/2)*taper+sin(135)*port_space_needed;
    port_x2 = (cheek_x+cos(45)*cheek_radius)*taper+cos(-135)*port_space_needed;
    port_y2 = (cheek_y+sin(45)*cheek_radius/2)*taper+sin(-135)*port_space_needed;

    // Vent Cuts: Grill & Strap
    grill_height = abs(port_y2-port_y1)-wall_thickness*2;
    cuts = floor(grill_height/wall_thickness);
    strap_a = atan2(port_y1-port_y2, port_x1-port_x2);
    strap_length = port_y2-port_y1;
    strap_thickness = lip_thickness/3.5;
    strap_x = min(port_x1,port_x2)+abs(port_x1-port_x2)/2+(wall_thickness+port_fit)*2+strap_thickness/2;
    strap_y = port_y1+strap_length/2;

/****************************************************************************************************************/

    if (part != "vent") { // Renders Mask
        rotate([180,0,0]) {
            translate(v = [0,-cheek_x/2,-depth]) {
                union() {
                    difference() {
                        extrude_mask();
                        extrude_nose_cut();
                        extrude_chin_cut();
                    }
                    extrude_socket();
                }
            }
        }
    }

    if (part != "mask") { // Renders Vent
        translate(v = [max(chin_spread, cheek_spread, nose_radius) + max(port_x1,port_x2) +lip_thickness, 0, 0]) {
            difference() {
                union() {
                    extrude_lip();
                    extrude_port();
                }
                linear_extrude(height = wall_thickness) {
                    outline_grill();
                    outline_straps();
                }
            }
        }
    }

/****************************************************************************************************************/

    module outline_mask() {
        difference() {
            hull() {
                for (side = [-1,1]) {
                    translate(v = [chin_x*side,chin_y,0]) {
                        circle(r = chin_radius, $fn = res);
                    }
                    translate(v = [cheek_x*side,cheek_y,0]) {
                        circle(r = cheek_radius, $fn = res);
                    }
                }
                translate(v = [nose_x,nose_y,0]) {
                    circle(r = nose_radius, $fn = res);
                }
            }

            for (side = [-1,1]) {
                hull() { // Nose Pinch
                    translate(v = [edge_x1*side,edge_y1,0]) {
                        circle(r = pinch_radius, $fn = res);
                    }
                    translate(v = [edge_x2*side,edge_y2,0]) {
                        circle(r = pinch_radius, $fn = res);
                    }
                    translate(v = [edge_ctx*side,edge_cty,0]) {
                        circle(r = pinch_radius, $fn = res);
                    }
                }
            }

        }
    }

    module extrude_mask() {
        difference() {
            linear_extrude(height = depth, scale = taper) {
                minkowski() {
                    outline_mask();
                    circle(r = wall_thickness, $fn = res);
                }
            }
            linear_extrude(height = depth-wall_thickness, scale = taper) {
                outline_mask();
            }
            linear_extrude(height = depth+wall_thickness) {
                minkowski() {
                    outline_vent();
                    circle(r = (wall_thickness+port_fit)*2, $fn = res);
                }
            }
        }
    }

    module extrude_nose_cut() {
        translate(v = [0,cheek_y+cheek_radius,0]) {
            rotate([nose_cut_a,0,0]) {
                translate(v = [0,nose_cut_l/2,-depth]) {
                    linear_extrude(height = depth) {
                        square(
                            size = [
                                nose_cut_w,
                                nose_cut_l
                            ],
                            center=true
                        );
                    }
                }
            }
        }
    }

    module extrude_chin_cut() {
        translate(v = [0,cheek_y/2,0]) {
            rotate([-chin_cut_a,0,0]) {
                translate(v = [0,-cheek_height/2,-depth]) {
                    linear_extrude(height = depth) {
                        square(
                            size = [
                                chin_cut_w,
                                chin_cut_l
                            ],
                            center=true
                        );
                    }
                }
            }
        }
    }

    module outline_vent() {
        polygon([
            [-port_x1,port_y1],
            [-port_x2,port_y2],
            [port_x2,port_y2],
            [port_x1,port_y1]
        ]);
    }

    module extrude_lip() {
        linear_extrude(height = wall_thickness) {
            minkowski() {
                outline_vent();
                circle(r = lip_thickness+wall_thickness, $fn = res);
            }
        }
    }

    module extrude_port(
        depth = port_depth,
        outer = wall_thickness*2+port_fit,
        inner = wall_thickness+port_fit
    ) {
        difference() {
            linear_extrude(height = depth) {
                minkowski() {
                    outline_vent();
                    circle(r = outer, $fn = res);
                }
            }
            linear_extrude(height = depth) {
                minkowski() {
                    outline_vent();
                    circle(r = inner, $fn = res);
                }
            }
        }
    }

    module extrude_socket() {
        translate(v = [0,0,depth-wall_thickness-port_depth]) {
            difference() {
                extrude_port(
                    depth = port_depth + wall_thickness,
                    outer = wall_thickness*3+port_fit*2,
                    inner = .1
                );
                translate(v = [0,0,wall_thickness]) {
                    extrude_port(
                        depth = port_depth,
                        outer = (wall_thickness*2+port_fit*2),
                        inner = wall_thickness
                    );
                }
            }
        }
    }

    module outline_grill() {
        for (y = [0:wall_thickness*2:grill_height]) {
            grill_width = (abs(port_x1-port_x2)*2*y/grill_height+min(port_x1,port_x2)*2)-wall_thickness*2;
            translate(v = [-grill_width/2, port_y1+wall_thickness/2+y, 0]) {
                square(size = [grill_width, wall_thickness], center=false);
            }
        }
    }

    module outline_straps() {
        for (side = [-1,1]) {
            translate(v = [(strap_x+wall_thickness/2)*side, strap_y, 0]) {
                rotate([0,0,strap_a*side]) {
                    square(size = [strap_length*.75, lip_thickness/2], center=true);
                }
            }
        }
    }
}
```


[User]
I'd like to create a customizable board game tile grid, specifically for Carcassonne tiles. I've been playing around with a design I found, but I want a fresh start with something that's fully parametric and more robust.

Here's what I'm looking for:

**Core Functionality:**

*   **Tile Grid:** The main thing is a grid that holds the tiles. It should be designed to hold 3x3 tiles, but easily modified for other sizes.
*   **Tile Stops:** I need raised walls/stops around each tile space to prevent the tiles from sliding. These walls should be a separate height from the base, and also be easily modifiable for height and width.
*   **Dovetail Connections:** The grid needs dovetail joints along its edges so I can connect multiple grids together to make larger boards. These dovetails should have adjustable width and depth, and ideally, I'd like to be able to specify different widths for horizontal and vertical dovetails for asymmetrical connections. These will prevent boards from being connected incorrectly as they will not fit with misaligned orientations.
*   **Rounded Corners:** The inside corners of each tile space should be rounded. The radius of these corners should be configurable.
*   **Base Plate:** The entire grid should have a base plate of adjustable thickness, and beams to connect the walls. The floorbeams should have an adjustable width.
*   **Center Crosses:** I want cross-shaped supports in the center of each tile space. The length of these cross pieces should be adjustable.
*   **Corner Reinforcements:** The corners should have a reinforcement structure with adjustable width.

**Parameters (that need to be changeable):**

*   `Base_Height`: Thickness of the base plate.
*   `Wall_Height`: Height of the tile stops above the base plate.
*   `Wall_Width`: Thickness of the tile stops.
*   `Tile_Side`: Size of the game tiles (single dimension - assume tiles are square).
*   `Cross_Length`: Length of the cross-shaped stops.
*   `Floor_Width`: Width of the baseplate beams
*   `Horizontal_Dovetail_Width`: Width of the thin part of the horizontal dovetails.
*  `Vertical_Dovetail_Width`: Width of the thin part of the vertical dovetails.
*   `Dovetail_Width_Tolerance`: Widthwise dovetail tolerance.
*   `Dovetail_Depth`: Depth of the dovetails.
*   `Dovetail_Depth_Tolerance`: Depthwise dovetail tolerance.
*   `Inner_Corner_Radius`: Radius of the corners in the baseplate square holes

**Key Design Elements:**

*   The dovetails should be designed to interlock, creating a secure connection between grids.
*   The asymmetrical dovetail design is important so that all the pieces fit together only one way. The dovetails should prevent tiles from being incorrectly oriented, which could leave unfilled gaps.
* The tile stops, center crosses, and beams need to be robust enough to hold tiles in place.

**Libraries:**

I'm not looking for you to reinvent any wheels, and I'd like to leverage the following libraries where appropriate:

*   `BOSL` (for general functions)
*   `BOSL2` (for more advanced functions, if needed)

I'm aiming for a clean, modular OpenSCAD design. Please make sure it's easy to modify and customize.

[Assistant]
Carcassonne_tile_grid_customizer_v2asym.scad:
```
//Thickness of the baseplate
Base_Height = 1.4;

//Height of the tile stops above the baseplate
Wall_Height = 1.5;

//Thickness of the tile stops
Wall_Width = 1;

//Size of the game tiles (add some tolerance)
Tile_Side = 45.8;

//Size of the cross-shaped stops
Cross_Length = 20;

//Width of the baseplate beams
Floor_Width = 7;

//Width of the thin part of the horizontal dovetails
Horizontal_Dovetail_Width = 18;

//Width of the thin part of vertical the dovetails
Vertical_Dovetail_Width = 10;

//Widthwise dovetail tolerance
Dovetail_Width_Tolerance = 0.8;

//Depth of the dovetais
Dovetail_Depth=4;

//Depthwise dovetail tolerance
Dovetail_Depth_Tolerance=0.25;

//Radius of the corners in the baseplate square holes
Inner_Corner_Radius = 4;

/* [Hidden] */
wallheight = Base_Height+Wall_Height;
totalwidth = 3*Tile_Side+2*Wall_Width;
firstdividecenter = Tile_Side+Wall_Width/2;
seconddividecenter = Tile_Side*2+Wall_Width+Wall_Width/2;
centerfirsttile = Tile_Side/2;
centersecondtile = Tile_Side+Wall_Width+Tile_Side/2;
centerthirdtile = Tile_Side*2+Wall_Width*2+Tile_Side/2;
h_dovewidth = Horizontal_Dovetail_Width;
h_negdovewidth = Horizontal_Dovetail_Width+Dovetail_Width_Tolerance;
v_dovewidth = Vertical_Dovetail_Width;
v_negdovewidth = Vertical_Dovetail_Width+Dovetail_Width_Tolerance;
dovedepth=Dovetail_Depth;
negdovedepth=Dovetail_Depth+Dovetail_Depth_Tolerance;
doveangleoffset=(2/(2*Wall_Width))*dovedepth;
negdoveangleoffset=(2/(2*Wall_Width))*negdovedepth;

module centercross (xpos, ypos, zpos) {
	translate( [xpos, ypos, zpos])
		translate ([-Cross_Length/2,-Wall_Width/2, 0]) cube ([Cross_Length, Wall_Width, wallheight]);
	translate ( [xpos, ypos, zpos])
		translate ([-Wall_Width/2,-Cross_Length/2, 0]) cube ([Wall_Width, Cross_Length, wallheight]);
} // module

module corner (xpos, ypos, zpos) {
	translate ( [xpos, ypos, zpos])
	difference () {
		cube ([Floor_Width+Inner_Corner_Radius, Floor_Width+Inner_Corner_Radius, Base_Height]);
		translate ([Floor_Width+Inner_Corner_Radius, Floor_Width+Inner_Corner_Radius, 0]) cylinder (3, r=Inner_Corner_Radius, $fn=20);
	} // difference
} // module corner

// y-direction floorbeams - translate to near edge of beam, then draw
cube ([Floor_Width, totalwidth, Base_Height]);
translate ([firstdividecenter-Floor_Width/2, 0, 0]) cube ([Floor_Width, totalwidth, Base_Height]);
translate ([Tile_Side+Wall_Width+Tile_Side+Wall_Width/2-Floor_Width/2, 0, 0]) cube ([Floor_Width, totalwidth, Base_Height]);

difference () {
//wall fill for dovetail
translate ([totalwidth-Floor_Width, 0, 0]) cube ([Floor_Width, totalwidth, Base_Height]);

//negative dovetail far y axis
translate([totalwidth+Wall_Width, Tile_Side+Wall_Width+Tile_Side/2, 0])
	polyhedron  (
	points = [[0, -v_negdovewidth/2 ,0], [0, -v_negdovewidth/2, wallheight], [0,v_negdovewidth/2,wallheight], [0,v_negdovewidth/2,0],
		[-negdovedepth, v_negdovewidth/2+negdoveangleoffset, 0], [-negdovedepth, v_negdovewidth/2+negdoveangleoffset, wallheight],
		[-negdovedepth, -v_negdovewidth/2-negdoveangleoffset, wallheight], [-negdovedepth, -v_negdovewidth/2-negdoveangleoffset, 0]],
	triangles = [[0,1,2], [2,3,0], [4,5,6], [6,7,4],
		 [0,7,6], [6,1,0], [3,2,5], [5,4,3],
		[1,6,5], [5,2,1], [0,3,4], [4,7,0],  ]);
} // difference

// x-direction floorbeams - translate to near edge of beam, then draw

difference () {
cube([totalwidth,Floor_Width,Base_Height]);

// negative dovetail near x axis
translate ([Tile_Side+Wall_Width+Tile_Side/2, -Wall_Width,0])
	polyhedron  (
	points = [[-h_negdovewidth/2, 0, 0], [-h_negdovewidth/2, 0, wallheight],
		[h_negdovewidth/2,0, wallheight], [h_negdovewidth/2, 0, 0],
		[h_negdovewidth/2+negdoveangleoffset, negdovedepth,0], [h_negdovewidth/2+negdoveangleoffset, negdovedepth, wallheight],
		[-h_negdovewidth/2-negdoveangleoffset, negdovedepth, wallheight], [-h_negdovewidth/2-negdoveangleoffset, negdovedepth, 0]],
	triangles = [[0, 1, 2], [2, 3, 0], [4, 5, 6], [6, 7, 4],
		 [0, 7, 6], [6, 1, 0], [3, 2, 5], [5, 4, 3],
		[1, 6, 5], [5, 2, 1], [0, 3, 4], [4, 7, 0],  ]);
} // difference

translate ([0, firstdividecenter-Floor_Width/2, 0]) cube ([totalwidth, Floor_Width, Base_Height]);
translate ([0, Tile_Side+Wall_Width+Tile_Side+Wall_Width/2-Floor_Width/2, 0]) cube ([totalwidth, Floor_Width, Base_Height]);
translate ([0, totalwidth-Floor_Width,0]) cube ([totalwidth, Floor_Width, Base_Height]);

// four center crosses - translate to center of cross, then translate back and draw
centercross (firstdividecenter, firstdividecenter, 0);
centercross (firstdividecenter, seconddividecenter, 0);
centercross (seconddividecenter, firstdividecenter, 0);
centercross (seconddividecenter, seconddividecenter, 0);

//walls on x-axis
translate ([0, -Wall_Width, 0]) cube ([Cross_Length/2, Wall_Width, wallheight]);
translate ([Tile_Side+Wall_Width/2-Cross_Length/2, -Wall_Width,0]) cube ([Cross_Length, Wall_Width, wallheight]);
translate ([Tile_Side*2+Wall_Width+Wall_Width/2-Cross_Length/2, -Wall_Width, 0]) cube ([Cross_Length, Wall_Width, wallheight]);

difference () {
translate ([totalwidth-Cross_Length/2, -Wall_Width, 0]) cube ([Cross_Length/2, Wall_Width, wallheight]);
translate ([totalwidth+2*Wall_Width, -2.3*Wall_Width-Floor_Width/2, 0]) rotate (45) cube (Floor_Width, Floor_Width, wallheight);
} // difference

// walls on y-axis, far side

difference () {
translate ([totalwidth, -Wall_Width,0]) cube ([Wall_Width, Cross_Length/2+Wall_Width, wallheight]);
translate ([totalwidth+2*Wall_Width, -2.3*Wall_Width-Floor_Width/2, 0]) rotate (45) cube (Floor_Width, Floor_Width, wallheight);
} // difference

translate ([totalwidth, Tile_Side+Wall_Width/2-Cross_Length/2,0]) cube([Wall_Width,Cross_Length,wallheight]);
translate ([totalwidth, Tile_Side*2+Wall_Width+Wall_Width/2-Cross_Length/2,0]) cube ([Wall_Width, Cross_Length, wallheight]);
translate ([totalwidth, totalwidth-Cross_Length/2, 0]) cube ([Wall_Width, Cross_Length/2, wallheight]);

// walls on x-axis, far side
translate ([centerfirsttile - Cross_Length/4, totalwidth, 0]) cube ([Cross_Length/2, Wall_Width, wallheight]);
// translate ([centersecondtile - Cross_Length/4, totalwidth, 0]) cube ([Cross_Length/2, Wall_Width, wallheight]);
translate ([centerthirdtile - Cross_Length/4, totalwidth, 0]) cube ([Cross_Length/2, Wall_Width, wallheight]);

// walls on y-axis, near side
translate ([-Wall_Width, centerfirsttile - Cross_Length/4, 0]) cube ([Wall_Width, Cross_Length/2, wallheight]);
//translate ([-Wall_Width, centersecondtile - Cross_Length/4, 0]) cube ([Wall_Width, Cross_Length/2, wallheight]);
translate ([-Wall_Width, centerthirdtile - Cross_Length/4, 0]) cube ([Wall_Width, Cross_Length/2, wallheight]);

// "t's" off of walls, x-axis near
translate ([firstdividecenter - Wall_Width/2, 0, 0]) cube ([Wall_Width, Cross_Length/2, wallheight]);
translate ([seconddividecenter - Wall_Width/2, 0, 0]) cube ([Wall_Width, Cross_Length/2, wallheight]);
// "t's" off of walls, x-axis far
translate ([firstdividecenter - Wall_Width/2,  totalwidth - Cross_Length/2, 0]) cube ([Wall_Width, Cross_Length/2, wallheight]);
translate ([seconddividecenter - Wall_Width/2,  totalwidth - Cross_Length/2, 0]) cube ([Wall_Width, Cross_Length/2, wallheight]);
// "t's" off of walls, y-axis near
translate ([0, firstdividecenter - Wall_Width/2, 0]) cube ([Cross_Length/2,  Wall_Width,  wallheight]);
translate ([0, seconddividecenter - Wall_Width/2, 0]) cube ([Cross_Length/2,  Wall_Width,  wallheight]);
// "t's" off of walls, y-axis far
translate ([totalwidth - Cross_Length/2,  firstdividecenter - Wall_Width/2, 0]) cube ([Cross_Length/2, Wall_Width, wallheight]);
translate ([totalwidth - Cross_Length/2,  seconddividecenter - Wall_Width/2, 0]) cube ([Cross_Length/2, Wall_Width, wallheight]);

// solid divider sections for dovetail
// translate ([totalwidth/2 - Tile_Side/2,  -Wall_Width, 0]) cube ([Tile_Side, Wall_Width, wallheight]);
// translate ([totalwidth, totalwidth/2 - Tile_Side/2, 0]) cube ([Wall_Width, Tile_Side, wallheight]);

//dovetail near y axis
difference () {
translate ([0, Tile_Side + Wall_Width + Tile_Side/2, 0])  {
	polyhedron  (
	points = [[0, -v_dovewidth/2, 0], [0, -v_dovewidth/2, wallheight], [0, v_dovewidth/2, wallheight],
		[0, v_dovewidth/2,  0],  [-Wall_Width, v_dovewidth/2 + 1, 0], [-Wall_Width, v_dovewidth/2 + 1, wallheight],
		[-Wall_Width, -v_dovewidth/2-1, wallheight],  [-Wall_Width, -v_dovewidth/2-1,  0]],
	triangles = [[0, 1, 2], [2, 3, 0], [4, 5, 6], [6, 7, 4],
		 [0, 7, 6], [6, 1,  0], [3, 2, 5], [5, 4, 3],
		[1, 6, 5], [5, 2, 1], [0, 3, 4], [4, 7, 0],  ]);
	polyhedron  (
	points = [[0,  -v_dovewidth/2,  0],  [0,  -v_dovewidth/2,  Base_Height],  [0,  v_dovewidth/2,  Base_Height],
		[0,  v_dovewidth/2,  0],  [-dovedepth,  v_dovewidth/2 + doveangleoffset,  0],
		[-dovedepth,  v_dovewidth/2 + doveangleoffset,  Base_Height],  [-dovedepth,  -v_dovewidth/2 - doveangleoffset,  Base_Height],
		[-dovedepth,  -v_dovewidth/2 - doveangleoffset,  0]],
	triangles = [[0, 1, 2], [2, 3, 0], [4, 5, 6], [6, 7, 4],
		 [0, 7, 6], [6, 1, 0], [3, 2, 5],  [5, 4, 3],
		[1, 6, 5], [5, 2, 1], [0, 3, 4], [4, 7, 0], ]);
}  //translate
//translate ([-.5, totalwidth/2, 1]) cylinder (h = 5, r=1, $fn = 30);
}

//dovetail far x axis
difference () {
translate ([Tile_Side + Wall_Width + Tile_Side/2, totalwidth, 0]) {
	polyhedron  (
	points = [[-h_dovewidth/2, 0, 0],  [-h_dovewidth/2, 0, wallheight],  [h_dovewidth/2, 0, wallheight],
		[h_dovewidth/2, 0, 0],  [h_dovewidth/2 + 1, Wall_Width, 0],  [h_dovewidth/2 + 1, Wall_Width, wallheight],
		[-h_dovewidth/2 - 1, Wall_Width, wallheight],  	[-h_dovewidth/2 - 1, Wall_Width, 0]],
	triangles = [[0, 1, 2],  [2, 3, 0],  [4, 5, 6],  [6, 7, 4],
		 [0, 7, 6],  [6, 1, 0],  [3, 2, 5],  [5, 4, 3],
		[1, 6, 5],  [5, 2, 1],  [0, 3, 4],  [4, 7, 0],   ]);
	polyhedron  (
	points = [[-h_dovewidth/2, 0, 0],  [-h_dovewidth/2, 0, Base_Height],  [h_dovewidth/2, 0, Base_Height],
		[h_dovewidth/2, 0, 0],  [h_dovewidth/2 + doveangleoffset, dovedepth, 0],  [h_dovewidth/2 + doveangleoffset, dovedepth, Base_Height],
		[-h_dovewidth/2 - doveangleoffset, dovedepth, Base_Height],  [-h_dovewidth/2 - doveangleoffset, dovedepth, 0]],
	triangles = [[0, 1, 2],  [2, 3, 0],  [4, 5, 6],  [6, 7, 4],
		 [0, 7, 6],  [6, 1, 0],  [3, 2, 5],  [5, 4, 3],
		[1, 6, 5],  [5, 2, 1],  [0, 3, 4],  [4, 7, 0],   ]);
} // translate
//translate ([totalwidth/2, totalwidth+.6, 1]) cylinder (h = 5, r=1, $fn = 30);
}

//added base width near dovetail cutouts
translate ([firstdividecenter, Floor_Width, 0]) cube([Tile_Side + Wall_Width*2,  Wall_Width, Base_Height]);
translate ([totalwidth - Floor_Width - Wall_Width, firstdividecenter, 0]) cube([Wall_Width, Tile_Side + Wall_Width*2, Base_Height]);

difference() {
//wall fill for dovetail
translate ([totalwidth/2 - Tile_Side/2 , -Wall_Width, 0]) cube([Tile_Side, Wall_Width, wallheight]);

//negative dovetail near x axis
translate ([Tile_Side + Wall_Width + Tile_Side/2, -Wall_Width, 0])
	polyhedron  (
	points = [[-h_negdovewidth/2, 0, 0],  [-h_negdovewidth/2, 0, wallheight],
		[h_negdovewidth/2, 0, wallheight],  [h_negdovewidth/2, 0, 0],
		[h_negdovewidth/2 + 2, Wall_Width*2, 0],  [h_negdovewidth/2 + 2, Wall_Width*2, wallheight],
		[-h_negdovewidth/2 - 2, Wall_Width*2, wallheight],  [-h_negdovewidth/2-2, Wall_Width*2, 0]],
	triangles = [[0, 1, 2],  [2, 3, 0],  [4, 5, 6],  [6, 7, 4],
		 [0, 7, 6],  [6, 1, 0],  [3, 2, 5],  [5, 4, 3],
		[1, 6, 5],  [5, 2, 1],  [0, 3, 4],  [4, 7, 0],   ]);
	} // polyhedron

difference() {
//wall fill for dovetail
translate ([totalwidth, totalwidth/2-Tile_Side/2, 0]) cube([Wall_Width, Tile_Side, wallheight]);

//negative dovetail far y axis
translate ([totalwidth + Wall_Width, Tile_Side + Wall_Width + Tile_Side/2, 0])
	polyhedron  (
	points = [[0, -v_negdovewidth/2, 0],  [0, -v_negdovewidth/2, wallheight],  [0, v_negdovewidth/2, wallheight],
		[0, v_negdovewidth/2, 0],  [-Wall_Width*2, v_negdovewidth/2 + 2, 0],
		[-Wall_Width*2, v_negdovewidth/2 + 2, wallheight],  [-Wall_Width*2, -v_negdovewidth/2-2, wallheight],
		[-Wall_Width*2, -v_negdovewidth/2 - 2, 0]],
	triangles = [[0, 1, 2],  [2, 3, 0],  [4, 5, 6],  [6, 7, 4],
		 [0, 7, 6],  [6, 1, 0],  [3, 2, 5],  [5, 4, 3],
		[1, 6, 5],  [5, 2, 1],  [0, 3, 4],  [4, 7, 0],   ]);
} // difference

corner (0, 0, 0);
corner (firstdividecenter + Floor_Width/2 - Floor_Width, Wall_Width, 0);
corner (seconddividecenter + Floor_Width/2 - Floor_Width, 0, 0);
corner (0, firstdividecenter + Floor_Width/2 - Floor_Width, 0);
corner (firstdividecenter + Floor_Width/2 - Floor_Width, firstdividecenter + Floor_Width/2 - Floor_Width, 0);
corner (seconddividecenter + Floor_Width/2 - Floor_Width, firstdividecenter + Floor_Width/2 - Floor_Width, 0);
corner (0, seconddividecenter + Floor_Width/2 - Floor_Width, 0);
corner (firstdividecenter + Floor_Width/2 - Floor_Width, seconddividecenter + Floor_Width/2 - Floor_Width, 0);
corner (seconddividecenter + Floor_Width/2 - Floor_Width, seconddividecenter + Floor_Width/2 - Floor_Width, 0);

translate ([firstdividecenter + Floor_Width/2, 0, 0])  { rotate (a = 90, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([seconddividecenter + Floor_Width/2, Wall_Width, 0])  { rotate (a = 90, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([totalwidth, 0, 0])  { rotate (a = 90, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([firstdividecenter + Floor_Width/2, firstdividecenter - Floor_Width/2, 0])  { rotate (a = 90, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([seconddividecenter + Floor_Width/2, firstdividecenter - Floor_Width/2, 0])  { rotate (a = 90, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([totalwidth-Wall_Width, firstdividecenter - Floor_Width/2, 0])  { rotate (a = 90, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([firstdividecenter + Floor_Width/2, seconddividecenter - Floor_Width/2, 0])  { rotate (a = 90, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([seconddividecenter + Floor_Width/2, seconddividecenter - Floor_Width/2, 0])  { rotate (a = 90, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([totalwidth, seconddividecenter - Floor_Width/2, 0])  { rotate (a = 90, v = [0, 0, 1]) { corner (0, 0, 0); }}

translate ([firstdividecenter + Floor_Width/2, firstdividecenter + Floor_Width/2, 0])  { rotate (a = 180, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([seconddividecenter + Floor_Width/2, firstdividecenter + Floor_Width/2, 0])  { rotate (a = 180, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([totalwidth, firstdividecenter + Floor_Width/2, 0])  { rotate (a = 180, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([firstdividecenter + Floor_Width/2, seconddividecenter + Floor_Width/2, 0])  { rotate (a = 180, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([seconddividecenter + Floor_Width/2, seconddividecenter + Floor_Width/2, 0])  { rotate (a = 180, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([totalwidth-Wall_Width, seconddividecenter + Floor_Width/2, 0])  { rotate (a = 180, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([firstdividecenter + Floor_Width/2, totalwidth, 0])  { rotate (a = 180, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([seconddividecenter + Floor_Width/2, totalwidth, 0])  { rotate (a = 180, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([totalwidth, totalwidth, 0])  { rotate (a = 180, v = [0, 0, 1]) { corner (0, 0, 0); }}

translate ([0, firstdividecenter + Floor_Width/2, 0])  { rotate (a = 270, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([firstdividecenter - Floor_Width/2, firstdividecenter + Floor_Width/2, 0])  { rotate (a = 270, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([seconddividecenter - Floor_Width/2, firstdividecenter + Floor_Width/2, 0])  { rotate (a = 270, v = [0, 0, 1]) { corner (0, 0, 0); }}

translate ([0, seconddividecenter + Floor_Width/2, 0])  { rotate (a = 270, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([firstdividecenter - Floor_Width/2, seconddividecenter + Floor_Width/2, 0])  { rotate (a = 270, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([seconddividecenter - Floor_Width/2, seconddividecenter + Floor_Width/2, 0])  { rotate (a = 270, v = [0, 0, 1]) { corner (0, 0, 0); }}

translate ([0, totalwidth, 0])  { rotate (a = 270, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([firstdividecenter - Floor_Width/2, totalwidth, 0])  { rotate (a = 270, v = [0, 0, 1]) { corner (0, 0, 0); }}
translate ([seconddividecenter - Floor_Width/2, totalwidth, 0])  { rotate (a = 270, v = [0, 0, 1]) { corner (0, 0, 0); }}

```


[User]
I need a hydroponic bottle cap designed for a standard "Weck Rundglas 60" bottle, but I want it to be easily adjustable for other bottle sizes. I'd like the cap to have a few key features: a flat base that sits on the bottle opening, a lip that fits inside the bottle neck for a snug fit, a cone-shaped section rising from the lip to hold the hydroponic net pot, and drainage gaps.

Specifically, could you create a parametric OpenSCAD design where I can easily adjust:

*   `outer_diameter`: The overall diameter of the base of the cap. This should fit on top of the bottle opening.
*   `inner_diameter`: The diameter of the lip that fits inside the bottle's neck.
*   `hole_size`: The diameter of the hole at the top of the cone where the net pot sits.
*   `cone_height`: The height of the cone-shaped section.
*   `lip_height`: The height of the lip that fits inside the bottle neck.
*   `base_height`: The thickness of the flat base section of the cap.
*   `gap_width`: The width of the drainage gaps.

I'd like three drainage gaps evenly spaced around the circumference. I don't need any complex shapes or features beyond this. Could you generate an OpenSCAD file or files using these parameters? I'm not super familiar with OpenSCAD, so having a clear and well-commented code would be fantastic. I'm not using any external libraries.

[Assistant]
hydroponicholder.scad:
```
$fn=67;
outer_diameter=69;
inner_diameter=54;
hole_size=8;
cone_height=23;
lip_height=2;
base_height=2;
gap_width=2;

difference(){
union(){
cylinder(d=outer_diameter,h=base_height);
translate([0,0,base_height]) cylinder (d=inner_diameter,h=lip_height);
translate([0,0,base_height+lip_height]) cylinder(d1=inner_diameter,d2=hole_size+6,h=cone_height-3);
}

union(){
 cylinder(d=inner_diameter-6,h=base_height);
 translate([0,0,base_height]) cylinder(d1=inner_diameter-6,d2=hole_size,h=cone_height);
    }

for(a=[0,120,240]) {
rotate([0,0,a]) translate([0,0,(cone_height/2)+6]) cube([inner_diameter,gap_width,cone_height+2],center=true);}

}

```


[User]
Hey, I'm looking for some OpenSCAD code to make a customizable magnetic hook. I've seen a design that’s close to what I want, and I'd like to be able to tweak the parameters to get it just right for my needs.

Basically, I want a hook with a threaded base that can hold a cylindrical magnet. The magnet will be inserted after printing, not embedded within.

Here are the key things I'd like to control:

*   **Magnet Dimensions**: I need to be able to specify the diameter and height of the cylindrical magnet I'll be using.
*   **Hook Shape**:
    *   Overall height of just the hook part (excluding the base and threads)
    *   Overall width of the hook.
    *   Thickness of the hook itself.
    *   The hook should have a taper towards the base – I'd like to control how much it tapers as a proportion (0 for no taper, 1 for maximum taper to the base).
    *   The starting point for the hook curve needs to be adjustable – I’ll need to control the vertical drop from the base before the curve starts.
*   **Threaded Base**:
    *   The threaded portion needs to be customizable. I'd need to be able to set both its height and the overall diameter (which needs to account for the magnet diameter and wall thickness).
    *   I want to use the "fff" style threads, so it should calculate the inner thread radius using `calculate_inner_thread_radius_fff`.
*   **Wall Thicknesses**:
    *   I need to be able to set a regular wall thickness.
    *   I also need a "thin wall" thickness for certain parts, like between the threads and the magnet cavity.
*   **Tessellation Tolerance**: I want to be able to set the tessellation tolerance.
*   **Cap**:
    *   I’d also like a separate cap with internal threads, like a lid for the magnet holder.
    *   The cap needs to have grip studs around the outer edge for easy turning. These need to be a customizable depth.
    *   The cap also needs to have a variable taper height to more closely align with the base.

I’d like the model to be built using `polystrut_3d` for the hook shape. I saw an example use `concat`, `cumulative_path_length`, `taper_hook`, and `ease_out` to build the hook shape. I would like a similar system.

I need this to be well-commented so I can easily understand and modify it later. Let me know if you need more details. I'm looking forward to seeing the code!

I'd like this to use the `MCAD` library for the thread generation and I'm happy with the rest being built from scratch.

[Assistant]
magnetic_hook_customizer.scad:
```
magnet_diameter = 8;
magnet_height   = 4.8;

// the overall height of just the hook (excludes base/threads)
hook_height           = 18;
hook_width            = 12;
// main width of the hook, excluding any taper
hook_thickness        = 5;
// by how much should the hook taper towards the base, values should be between zero (no taper) and one
hook_taper_proportion = 0.8;
// distance between the start of the hook and the threaded section, hooks with higher thickness may need a higher initial drop
hook_initial_drop     = 1;

/* [Hidden] */

wall_thickness        = 2;
thin_wall_thickness   = 0.6;
tessalation_tolerance = 0.25;

grip_stud_depth = 1;
min_thread_height = 5;

thread_height     = max(min_thread_height, magnet_height + tessalation_tolerance * 2);
thread_depth      = 10 - calculate_inner_thread_radius_fff(10);
thread_diameter   = magnet_diameter + tessalation_tolerance * 2 + thin_wall_thickness * 2 + thread_depth * 2;

$fa = 3;
$fn = 360 / $fa;

function circumference(d) = d * PI;

cap();
translate([max(hook_width, magnet_diameter + tessalation_tolerance * 2 + wall_thickness * 2) + 10, 0, 0])
    hook();

module hook()
{
    difference()
    {
        union()
        {
            threads_fff(thread_diameter, thread_height, countersink = "both");
            translate([0, 0, magnet_height + tessalation_tolerance * 2])
                cylinder(d = thread_diameter - thread_depth * 2, h = wall_thickness);
            difference()
            {
                translate([0, 0, max(magnet_height + tessalation_tolerance * 2 + wall_thickness, thread_height)])
                    assign
                    (
                        points =
                        concat([
                            [0, 0, 0],
                            [0, 0, hook_initial_drop]],
                            [ for(a = [180 : 10 : 270]) hook_curve_coords(a, (hook_width / 2) + hook_initial_drop) ],
                            [[ (hook_curve_coords(270, (hook_width / 2) + hook_initial_drop))[0], (hook_curve_coords(270, (hook_width / 2) + hook_initial_drop))[1], (hook_curve_coords(270, (hook_width / 2) + hook_initial_drop))[2] + hook_height - hook_width]],
                            [ for(a = [270: 10 : 450]) hook_curve_coords(a, (hook_curve_coords(270, (hook_width / 2) + hook_initial_drop))[2] + hook_height - hook_width) ]
                        ),
                        cumulative_hook_length = cumulative_path_length(concat([
                            [0, 0, 0],
                            [0, 0, hook_initial_drop]],
                            [ for(a = [180 : 10 : 270]) hook_curve_coords(a, (hook_width / 2) + hook_initial_drop) ],
                            [[ (hook_curve_coords(270, (hook_width / 2) + hook_initial_drop))[0], (hook_curve_coords(270, (hook_width / 2) + hook_initial_drop))[1], (hook_curve_coords(270, (hook_width / 2) + hook_initial_drop))[2] + hook_height - hook_width]],
                            [ for(a = [270: 10 : 450]) hook_curve_coords(a, (hook_curve_coords(270, (hook_width / 2) + hook_initial_drop))[2] + hook_height - hook_width) ]
                        ))
                    )
                    difference()
                    {
                        polystrut_3d(points = hook_taper_proportion == 0 ? points : taper_hook(points, cumulative_hook_length), d = hook_thickness, ends = "flat", $fn = 5);
                        translate(points[len(points) - 1])
                            assign(size = hook_thickness)
                            translate([-size / 2, -size / 2, -size / 2])
                            cube([size, size, size / 2]);
                    }
                cylinder(d = 999, h = thread_height);
            }
        }
        translate([0,0, -1])
            cylinder(d = magnet_diameter + tessalation_tolerance * 2, h = magnet_height + tessalation_tolerance * 2 + 1);
    }

    function hook_curve_coords(a, offset) =
        [sin(a) * hook_width / 2, 0, cos(a) * hook_width / 2 + offset];

    function cumulative_path_length(path) = concat(0, path_length_accumulator(path));

    function path_length_accumulator(path, eval_idx = 1, length = 0) =
        eval_idx == len(path) - 1 ?
            length + point_distance_3d(path[eval_idx - 1], path[eval_idx]) :
            concat(length + point_distance_3d(path[eval_idx - 1], path[eval_idx]), path_length_accumulator(path, eval_idx + 1, length + point_distance_3d(path[eval_idx - 1], path[eval_idx])));

    function taper_hook(path, cumulative_length, eval_idx = 0) =
        eval_idx == len(path) - 1 ?
            [ [ path[eval_idx][0], path[eval_idx][1], path[eval_idx][2], ease_out((thread_diameter - thread_depth * 2), hook_thickness, (clamp(cumulative_length[eval_idx] / cumulative_length[len(cumulative_length) - 1] / hook_taper_proportion, 0, 1)), 3) ] ] :
            concat([ [ path[eval_idx][0], path[eval_idx][1], path[eval_idx][2], ease_out((thread_diameter - thread_depth * 2), hook_thickness, (clamp(cumulative_length[eval_idx] / cumulative_length[len(cumulative_length) - 1] / hook_taper_proportion, 0, 1)), 3) ] ], taper_hook(path, cumulative_length, eval_idx + 1));
}

module cap()
{
    d = thread_diameter + tessalation_tolerance * 2 + wall_thickness * 2;

    difference()
    {
        cylinder(d = d - grip_stud_depth * 2, h = thread_height + thin_wall_thickness);
        translate([0, 0, thin_wall_thickness])
            threads_fff(thread_diameter + tessalation_tolerance * 2, thread_height + 1);
    }
    grip_studs(ceil(circumference(d) / 3), grip_stud_depth, d - grip_stud_depth * 2, thread_height + thin_wall_thickness, 2);
}

module polystrut_3d(d, r, points, ends = "round")
{
    d = d == undef ? r * 2 : d;

    difference()
    {
        union()
        {
            for(idx = [1 : len(points) - 1])
            {
                p1 = [points[idx - 1][0], points[idx - 1][1], points[idx - 1][2]];
                p2 = [points[idx][0], points[idx][1], points[idx][2]];
                d1 = points[idx - 1][3] == undef ? d : points[idx - 1][3];
                d2 = points[idx][3] == undef ? d : points[idx][3];

                hull()
                {
                    translate(p1) sphere(d = d1);

                    if(idx == len(points) - 1 && ends == "flat")
                    {
                        translate(p2) intersection()
                        {
                            sphere(d = d2);
                            rotate([0, 0, angle_a(p1, p2)]) rotate([0, angle_b(p1, p2), 0]) translate([-d2 / 2, 0, 0]) cube(d2, center = true);
                        }
                    }
                    translate(p2) sphere(d = d2);
                }
            }
        }
    }

    function angle_a(p1, p2) =
        (((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0])) == 0 ? 0.00001 : ((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0]))) < 0 ? 180 + atan((p2[1] == undef ? 0 : p2[1]) - (p1[1] == undef ? 0 : p1[1]) / (((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0])) == 0 ? 0.00001 : ((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0])))) : atan((p2[1] == undef ? 0 : p2[1]) - (p1[1] == undef ? 0 : p1[1]) / (((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0])) == 0 ? 0.00001 : ((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0]))));

    function angle_b(p1, p2) =
        -(((((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0])) == 0 ? 0.00001 : ((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0]))) / cos(angle_a(p1, p2))) < 0 ? 180 + atan((p2[2] == undef ? 0 : p2[2]) - (p1[2] == undef ? 0 : p1[2]) / ((((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0])) == 0 ? 0.00001 : ((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0]))) / cos(angle_a(p1, p2)))) : atan((p2[2] == undef ? 0 : p2[2]) - (p1[2] == undef ? 0 : p1[2]) / ((((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0])) == 0 ? 0.00001 : ((p2[0] == undef ? 0 : p2[0]) - (p1[0] == undef ? 0 : p1[0]))) / cos(angle_a(p1, p2)))));
}

module grip_studs(num_studs, stud_depth, inner_diameter, height, taper_height = 0)
{
    a_step        = 360 / num_studs;
    stud_width    = inner_diameter * 3.141592654 / num_studs;
    stud_diameter = stud_width / cos(45);
    num_rows      = ceil(height / (stud_diameter / 2 * cos(45))) + 1;
    fill_height   = (1 - cos(stud_diameter / inner_diameter * 90)) * inner_diameter / 2;

    intersection()
    {
        union()
        {
            for(idx = [0 : num_rows - 1])
            {
                translate([0, 0, idx * stud_diameter / 2 * cos(45)]) rotate([0, 0, idx * a_step / 2])
                {
                    for(idx2 = [0 : num_studs - 1])
                    {
                        rotate([90, 0, idx2 * a_step]) translate([0, 0, inner_diameter / 2])
                        {
                            translate([0, 0, -fill_height]) cylinder(d = stud_width, h = fill_height, $fn = 4);
                            cylinder(d1 = stud_width, d2 = 0, h = stud_depth, $fn = 4);
                        }
                    }
                }
            }
        }

        union()
        {
            cylinder(d1 = inner_diameter, d2 = inner_diameter + stud_depth * 2, h = taper_height);
            translate([0, 0, taper_height])
                cylinder(d = inner_diameter + stud_depth * 2, h = height - taper_height * 2);
            translate([0, 0, height - taper_height])
                cylinder(d1 = inner_diameter + stud_depth * 2, d2 = inner_diameter, h = taper_height);
        }
    }
}

module threads_fff(outer_diameter, length, countersink = "none", outer_diameter_1, outer_diameter_2)
{
    threads(outer_diameter, length, 2, 40, countersink, outer_diameter_1, outer_diameter_2);
}

module threads(outer_diameter, length, pitch, pitch_angle, countersink = "none", outer_diameter_1, outer_diameter_2)
{
    $fn = $fn == 0 ? 360 / $fa : $fn;

    outer_diameter_1 = outer_diameter_1 != undef ? outer_diameter_1 : outer_diameter;
    outer_diameter_2 = outer_diameter_2 != undef ? outer_diameter_2 : outer_diameter;

    outer_radius_1 = outer_diameter_1 / 2;
    outer_radius_2 = outer_diameter_2 / 2;
    inner_radius_1 = calculate_inner_thread_radius(outer_radius_1, pitch, pitch_angle);
    inner_radius_2 = calculate_inner_thread_radius(outer_radius_2, pitch, pitch_angle);
    num_steps      = max(floor($fn / 3), 3);
    step_angle     = 360 / num_steps;
    num_turns      = ceil(length / pitch + 1);
    step_height    = pitch / num_steps;

    intersection()
    {
        thread_shape();
        full_thread();
    }

    module thread_shape()
    {
        taper_height_bottom = countersink == "both" || countersink == "bottom" ? pitch / 2 : 0;
        taper_height_top    = countersink == "both" || countersink == "top" ? pitch / 2 : 0;

        union()
        {
            cylinder(r1 = inner_radius_1, r2 = outer_radius_1, h = taper_height_bottom);
            translate([0, 0, taper_height_bottom])
                cylinder(d = max(outer_diameter_1, outer_diameter_2), h = length - taper_height_bottom - taper_height_top);
            translate([0, 0, length - taper_height_top])
                cylinder(r1 = outer_radius_2, r2 = inner_radius_2, h = taper_height_top);
        }
    }

    module full_thread()
    {
        if(inner_radius_1 >= 0.2 && inner_radius_2 >= 0.2)
        {
            for(turn_idx = [0 : num_turns - 1])
                for(step_idx = [0 : num_steps - 1])
                    polyhedron
                    (
                        points = calculateStepVertices(turn_idx, step_idx),
                        faces  =
                        [
                            [1, 0, 3],
                            [1, 3, 6],
                            [6, 3, 8],
                            [1, 6, 4],
                            [0, 1, 2],
                            [1, 4, 2],
                            [2, 4, 5],
                            [5, 4, 6],
                            [5, 6, 7],
                            [7, 6, 8],
                            [7, 8, 3],
                            [0, 2, 3],
                            [3, 2, 7],
                            [7, 2, 5]
                        ]
                    );
      }
      else
      {
        echo("pitch Degrees too aggresive, the thread will not be made!!");
        echo("Try to increase de value for the degrees and/or...");
        echo(" decrease the pitch value and/or...");
        echo(" increase the outer diameter value.");
      }
    }

    function calculateStepVertices(turn_idx, step_idx) =
        [
            [
                0,
                0,
                turn_idx * pitch - pitch
            ],
            [
                (cos(step_idx * step_angle)) * lookup((turn_idx * pitch + step_idx * step_height) - pitch, ([[0, inner_radius_1], [length, inner_radius_2]])),
                (sin(step_idx * step_angle)) * lookup((turn_idx * pitch + step_idx * step_height) - pitch, ([[0, inner_radius_1], [length, inner_radius_2]])),
                (turn_idx * pitch + step_idx * step_height) - pitch
            ],
            [
                (cos((step_idx + 1) * step_angle)) * lookup((turn_idx * pitch + (step_idx + 1) * step_height) - pitch, ([[0, inner_radius_1], [length, inner_radius_2]])),
                (sin((step_idx + 1) * step_angle)) * lookup((turn_idx * pitch + (step_idx + 1) * step_height) - pitch, ([[0, inner_radius_1], [length, inner_radius_2]])),
                (turn_idx * pitch + (step_idx + 1) * step_height) - pitch
            ],
            [
                0,
                0,
                turn_idx * pitch
            ],
            [
                (cos(step_idx * step_angle)) * lookup((turn_idx * pitch + step_idx * step_height) - pitch / 2, ([[0, outer_radius_1], [length, outer_radius_2]])),
                (sin(step_idx * step_angle)) * lookup((turn_idx * pitch + step_idx * step_height) - pitch / 2, ([[0, outer_radius_1], [length, outer_radius_2]])),
                (turn_idx * pitch + step_idx * step_height) - pitch / 2
            ],
            [
                (cos((step_idx + 1) * step_angle)) * lookup((turn_idx * pitch + (step_idx + 1) * step_height) - pitch / 2, ([[0, outer_radius_1], [length, outer_radius_2]])),
                (sin((step_idx + 1) * step_angle)) * lookup((turn_idx * pitch + (step_idx + 1) * step_height) - pitch / 2, ([[0, outer_radius_1], [length, outer_radius_2]])),
                (turn_idx * pitch + (step_idx + 1) * step_height) - pitch / 2
            ],
            [
                (cos(step_idx * step_angle)) * lookup((turn_idx * pitch + step_idx * step_height), ([[0, inner_radius_1], [length, inner_radius_2]])),
                (sin(step_idx * step_angle)) * lookup((turn_idx * pitch + step_idx * step_height), ([[0, inner_radius_1], [length, inner_radius_2]])),
                (turn_idx * pitch + step_idx * step_height)
            ],
            [
                (cos((step_idx + 1) * step_angle)) * lookup((turn_idx * pitch + (step_idx + 1) * step_height), ([[0, inner_radius_1], [length, inner_radius_2]])),
                (sin((step_idx + 1) * step_angle)) * lookup((turn_idx * pitch + (step_idx + 1) * step_height), ([[0, inner_radius_1], [length, inner_radius_2]])),
                (turn_idx * pitch + (step_idx + 1) * step_height)
            ],
            [
                0,
                0,
                turn_idx * pitch + pitch
            ]
        ];
}

function calculate_inner_thread_radius(outer_radius, pitch, pitch_angle) = outer_radius - pitch / 2 * cos(pitch_angle) / sin(pitch_angle);

function calculate_inner_thread_radius_fff(outer_radius) = outer_radius - 2 / 2 * cos(40) / sin(40);

PI                = 3.141592654;
E                 = 2.718281828459045;
GOLDEN_RATIO      = 1.61803398875;
GOLDEN_MEAN       = GOLDEN_RATIO;

BIG_NUMBER        = 1e10;
SMALL_NUMBER      = 1e-10;
LOW_NUMBER        = -BIG_NUMBER;

POSITIVE_INFINITY = 1 / 0;
NEGATIVE_INFINITY = -1 / 0;

MM_PER_INCH = 25.4;

function clamp(value, minimum, maximum) = max(min(value, maximum), minimum);

EASE_IN     = 0;
EASE_OUT    = 1;
EASE_IN_OUT = 2;
EASE_LINEAR = 3;
EASE_OUT_IN = 4;

function ease_in(a, b, proportion, exponent = 2) = (b - a) * pow(clamp(proportion, 0, 1), exponent) + a;
function ease_out(a, b, proportion, exponent = 2) = (b - a) * (1 - pow(1 - clamp(proportion, 0, 1), exponent)) + a;

function ease_in_out(a, b, proportion, exponent = 2) =
    proportion < 0.5 ?
        ease_in(a, ((b - a) / 2 + a), proportion * 2, exponent) :
        ease_out(((b - a) / 2 + a), b, proportion * 2 - 1, exponent);

function ease_out_in(a, b, proportion, exponent = 2) =
    proportion >= 0.5 ?
        ease_in(((b - a) / 2 + a), b, proportion * 2 - 1, exponent) :
        ease_out(a, ((b - a) / 2 + a), proportion * 2, exponent);

function ease_linear(a, b, proportion) = lerp(a, b, proportion);
function lerp(start, end, amount) = start + (end - start) * amount;
function point_distance_3d(p1, p2) = sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]) + (p1[2] - p2[2]) * (p1[2] - p2[2]));

```


[User]
Hey, I'm trying to make some containers, and I've got a specific shape in mind. It's basically two truncated cones stacked on top of each other. I'd like an OpenSCAD file that lets me control the diameters of the bottom, middle, and top of the container, as well as the height of each conical section.

I'd like the parameters to be called `d_bottom`, `d_middle`, `d_top`, `h_bottom`, and `h_top` respectively. So, d_bottom is the diameter of the bottom, where it sits on the table, d_middle is the diameter at the midpoint and where the two cones join, d_top is the diameter of the top, h_bottom is the height from the bottom up to the middle diameter, and h_top is the height from the middle diameter up to the top.

The code should define a module, maybe called `container`, that takes these five parameters and creates the container. I'm pretty new to this, but I'm picturing two `cylinder()` calls with the appropriate diameters and heights.  The second cylinder would need a `translate()` to put it above the first one. I'd also appreciate if you could add some default values for all of the parameters in the module definition. Also, I would prefer the defaults for $fa and $fs to be 1.

The design is intended to be printed in vase mode or with zero infill, so we don't need to worry about wall thickness or anything like that.

Thanks in advance!

[Assistant]
conetainers.scad:
```

// Bottom Diameter
d_bottom = 49;

// Middle Diameter
d_middle = 32.67;

// Top Diameter
d_top    = 49;

// Height of lower part
h_bottom = 35;

// Height of upper part
h_top    = 35;

/* [scad] */
$fs=1;
$fa=1;

module container(
  d_bottom = d_bottom,
  d_middle = d_middle,
  d_top    = d_top,
  h_bottom = h_bottom,
  h_top    = h_top,
) {
  cylinder(d1=d_bottom, d2=d_middle, h=h_bottom);
  translate([0,0,h_bottom])
    cylinder(d1=d_middle, d2=d_top, h=h_top);
}

//container(66,60,60,16,34);
//container(49,32.7,49,35,35);
//container(60,68,60,10,60);
container();

```


[User]
Okay, I'm looking to generate some V-slot profiles in OpenSCAD, and I'd like them to be configurable. I'm thinking I need a way to choose between different V-slot sizes (like 2020, 2525, and 3030), and also to specify how many of those profiles should be next to each other (single, double, triple, or quadruple). Finally, I need to set the length of the extrusion.

Basically, I want a module that takes the V-slot type, a number for how many side-by-side, and a length, and generates the geometry. This would be similar to the `UVslot.scad` example I have, but I'd rather start fresh to learn more about how it all works. So can you provide me with a new OpenSCAD file (or files, if you think it would make sense) that can do all this? It doesn't need to be super polished or fancy, but it should be parametric and easy to modify. I'd also like the code to be well-commented so I can understand what each part does.

[Assistant]
UVslot.scad:
```
//// File: UVslot.scad
//// Version: OpenSCAD 2015.03-2
//// Author: Kijja Chaovanakij
//// Date: 2020.3.14

//// Fix to be compatible with the new customizer.
//// Date: 2020.05.31

//parts to generate
part=0; //[0:demo, 1:vslot]
//vslot size
vSelect=1; //[1:vSlot2020, 2:vSlot2525, 3:vSlot3030]
//Vslot set
vSet=1; //[1:single, 2:double, 3:triple, 4:quadruple]
//vslot length
vLen=20; //[10:5:200]

//// demo
if (part==0){
  color("Aqua")
  translate([0, -35, 0])
    singleProfile(1, 30);
  color("Chartreuse")
    multiProfile(2, 40, 2);
  color("HotPink")
  translate([0, 40, 0])
    multiProfile(3, 50, 3);
}//i
//// end demo

//// main
if (part==1 && vSet==1)
  color("Aqua")
  singleProfile(vSelect, vLen);
if (part==1 && vSet>=2)
  color("Chartreuse")
  multiProfile(vSelect, vLen, vSet);
////  end main

//singleProfile();
module singleProfile(size=vSelect, hi=vLen){
  sv=vSlotParams[size];
  $fn=24;
  if (size>=1 && size<=3)
  linear_extrude(height=hi)
  difference(){
    minkowski(){
      circle(r=sv[7]);
      square(sv[1]-sv[7]*2, true);
    }//m
    circle(d=sv[6]);
    for (i=[0:90:270])
      rotate([0, 0, i])
        vProfile(size);
  }//d
}//m
//

//multiProfile();
module multiProfile(size=vSelect, hi=vLen, pile=vSet){
  sv=vSlotParams[size];
  $fn=24;
  if (size>=1 && size<=3)
  if (pile>=2)
  linear_extrude(height=hi)
  difference(){
    minkowski(){
      circle(r=sv[7]);
      square([sv[1]*pile-sv[7]*2, sv[1]-sv[7]*2], true);
    }//m
    for (i=[-(pile-1)/2+1/2:1:(pile-1)/2])
      translate([i*sv[1], 0])
        innerProfile(size=size);
    for (i=[-pile/2+1/2:1:pile/2]){
      translate([i*sv[1], 0])
        circle(d=sv[6]);
      for (j=[0,180])
        translate([i*sv[1], 0])
          rotate([0, 0, j])
            vProfile(size=size);
    }//f
    translate([(pile-1)*sv[1]/2, 0])
      rotate([0, 0, 270])
        vProfile(size=size);
    mirror([1, 0, 0])
    translate([(pile-1)*sv[1]/2, 0])
      rotate([0, 0, 270])
        vProfile(size=size);
  }//d
}//m
//

//vProfile();
module vProfile(size=vSelect){
  sv=vSlotParams[size];
  $fn=24;   ep=0.002;     fill_rad=sv[3]/4;
  dep=(sv[5]-sv[4])/2;
  innerRad=(sv[1]/2-sv[2]/2-sv[3]-dep)/3;
  vCutRad=sv[3]/8;
  if (size>=1 && size<=3)
  union(){
    minkowski(){
      circle(r=innerRad);
      hull(){
        translate([0, sv[2]/2+innerRad])
          square([sv[4]-innerRad*2, ep], true);
        translate([0, sv[2]/2+dep+innerRad])
          square([sv[5]-innerRad*2, ep], true);
        translate([0, sv[1]/2-sv[3]-innerRad])
          square([sv[5]-innerRad*2, ep], true);
      }//h
    }//m
    difference(){
      translate([-sv[4]/2-sv[3]-vCutRad, sv[1]/2-sv[3]])
        square([sv[4]+sv[3]*2+vCutRad*2, sv[3]+ep]);
      vCut();
      mirror([1, 0, 0])
        vCut();
    }//d
  }//u
//sub module
  module vCut(){
    hull(){
      translate([-sv[4]/2-vCutRad, sv[1]/2-sv[3]+vCutRad])
        circle(r=vCutRad+ep);
      translate([-sv[4]/2-sv[3]-vCutRad, sv[1]/2-vCutRad])
        circle(r=vCutRad+ep);
      translate([-sv[4]/2-sv[3]-vCutRad*2-ep, sv[1]/2-sv[3]])
        square([ep, sv[3]]);
    }//h
  }//end sub
}//m
//

//innerProfile();
module innerProfile(size=vSelect){
  sv=vSlotParams[size];
  $fn=24;   ep=0.002;     fill_rad=sv[3]/4;
  innerRad=(sv[1]/2-sv[2]/2-sv[3]-(sv[5]-sv[4])/2)/3;
  innerDep=(sv[1]-sv[2])/2-sv[3];
  if (size>=1 && size<=3)
  union(){
    minkowski(){
      circle(r=innerRad);
      hull(){
        translate([sv[1]/2-sv[2]/2-innerRad, 0])
          square([ep, sv[4]-innerRad*2], true);
        mirror([1, 0, 0])
        translate([sv[1]/2-sv[2]/2-innerRad, 0])
          square([ep, sv[4]-innerRad*2], true);
        translate([sv[3]-innerRad, 0])
          square([ep, sv[4]+innerDep*2-innerRad*2], true);
        mirror([1, 0, 0])
        translate([sv[3]-innerRad, 0])
          square([ep, sv[4]+innerDep*2-innerRad*2], true);
      }//h
    }//m
  }//u
}//m
//

//// Do not change these parameters unless necessary.
vSlotParams =
 [[ "name", //0
    "frame_size", //1
    "inner_size", //2
    "wall_thi", //3
    "slot_wid", //4
    "max_inner_wid", //5
    "hole_dia", //6
    "corner_rad"], //7
  [ "vslot2020", 20, 7.8,  1.8, 6.2, 11.0, 5.0, 1.5],
  [ "vslot2525", 25, 9.8,  2.0, 6.2, 15.0, 5.0, 1.5],
  [ "vslot3030", 30, 11.6, 2.2, 8.2, 16.5, 6.8, 2.5],
 ]; //end

```


[User]
Hi! I'm looking to create a parametric design for surgical mask retainers, similar to the one described on Thingiverse by 'KeithFromCanada' Olson. I'd like to make a design that's fully customizable using OpenSCAD.

Here's what I'm imagining:

*   **Overall Shape:** The retainer will consist of a strap with multiple arms/catches on each end. It should be symmetrical and have the option to mirror it along the X and Y axes.

*   **Strap:**
    *   I need to define the `Strap_Length`, `Strap_Width`, `Strap_Gully` (the space between strap sections), and `Strap_Thickness`.
    *   The strap should have a rectangular cross-section.

*   **Catch:**
    *   Each arm should have a "catch" with a defined `Catch_Length`, `Catch_Width`, `Catch_Area`, and `Catch_Thickness`.
    *   The `Catch_Area2` should be calculated as `Catch_Area + Strap_Width + half(Strap_Gully) + half(Catch_Width)`.
    *   The `Catch_Offset` should be half of the `Catch_Length`.
    *   The catches should be cylindrical and rotated into place.

*   **Arm:**
    *   There should be a specified `Number_Of_Arms`.
    *   The arms should be spaced by `Arm_Spacing`, with a specified `Arm_Width` and `Arm_Thickness`.

*   **Text:**
    *   I want to add customizable text, `Strap_Text`, which should have a `Text_Height` and `Text_Thickness`.
    *   The `Text_Gully` should be equal to `Text_Height - Strap_Gully`.
    *   The text should be centered along the length of the strap, and it should be extruded.
    *  The text must be uppercase for proper anchoring.

*   **Print Quality:** I want to be able to set the `Draft_Quality` and `Final_Quality` for the `Smoothness` variable which sets `$fn`. The `$preview` variable should also be respected.

*   **Output:** The final output should be a single union of all the parts, mirrored as specified.

I want all of these parameters to be easily customizable in OpenSCAD, with appropriate ranges set as comments so they can be adjusted in the Customizer. I don't need any special libraries right now.

Can you please generate the necessary OpenSCAD file(s) for this?

[Assistant]
sms_v01-06.scad:
```
//--------------------------------
// Parametric Surgical Mask Retainer (Remix of https://www.thingiverse.com/thing:4255721)
// 'KeithFromCanada' Olson
// Licence: CC 2020
// OpenSCAD 2019.05
//--------------------------------

// OpenSCAD Customizer Docs: https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Customizer
// Thingiverse Customizer Docs: http://customizer.makerbot.com/docs

function twice(x = 1)  = x *    2.00000;
function half(x = 1)   = x *    0.50000;

/* [Print Quality] */

Draft_Quality = 24; // [12:6:360]
Final_Quality = 72; // [12:6:360]
Smoothness = $preview ? Draft_Quality : Final_Quality;
$fn  = Smoothness;

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

/*
$vpr = ([30,0,335]);
$vpt = ([-8,  0, -3]);
$vpd = 225;
//*/

/* [Strap] */

Strap_Length     = 180.00;  // [50:5:400]
Strap_Width      =   4.00;  // [2:0.5:10]
// Space between strap sections.
Strap_Gully      =   2.00;  // [0:0.5:10]
Strap_Thickness  =   1.00;  // [0.5:0.5:5]

/* [Catch] */
Catch_Length    =   8.00;  // [1:1:10]
Catch_Width     =   4.00;  // [1:1:10]
Catch_Area      =   2.00;  // [1:1:10]
Catch_Area2     = Catch_Area + Strap_Width + half(Strap_Gully) + half(Catch_Width);
// Should be set high enough to ensure that catches won't bend in use. If set higher than strap thickness, multiple colours can be used by swapping filament mid-print.
Catch_Thickness =   1.50;  // [0.5:0.5:10]
Catch_Offset    = half(Catch_Length);

/* [Arm] */
Arm_Spacing    =  10.00;  // [5:0.5:20]
Arm_Width      =   4.50;  // [3:0.5:10]
Arm_Thickness  =   1.50;  // [0.5:0.5:10]
Number_Of_Arms =   4;     // [1:1:8]

/* [Text] */
Strap_Text   = "HERO AT WORK";
Text_Height  =  8.00;  // [8:1:80]
Text_Gully   = Text_Height - Strap_Gully;
// If set lower or higher than strap thickness, multiple colours can be used by swapping filament mid-print.
Text_Thickness   =  0.50;  // [0.5:0.5:10]

/* [Hidden] */

union()
{
    {
                    Draw_Strap();
    mirror([0,1,0]) Draw_Strap();
    }

    mirror([1,0,0])
    {
                    Draw_Strap();
    mirror([0,1,0]) Draw_Strap();
    }

    {
        linear_extrude(Text_Thickness)
          text( Strap_Text,
                font = "Arial:style=Bold",
                halign = "center",
                valign = "center",
                size = Text_Height * 1.05
               );
    }
}

module Draw_Strap()
{
    translate([-half(Strap_Length),0,0])
    {
        translate([0, half(Strap_Gully), 0])
          cube([Arm_Spacing * (Number_Of_Arms - 1) + Catch_Length, Strap_Width, Strap_Thickness]);

        for (ii=[0:Number_Of_Arms-1])
        {
            translate([Arm_Spacing * ii, 0, 0])
              cube([Arm_Width, Catch_Area2, Arm_Thickness]);
            translate([Arm_Spacing * ii + Catch_Offset, Catch_Area2, 0])
              scale([1, Catch_Width/Catch_Length, 1])
              cylinder(d = Catch_Length, h = Catch_Thickness);
        }

        translate ([Arm_Spacing * (Number_Of_Arms - 1) + Catch_Length, 0, 0])
            {

                // Please don't judge me for the following travesty.  I'm so exhausted that
                // my brain has turned into Patrick Star.

                aa = half(Text_Gully) / cos(45);
                bb = (half(Text_Gully) + Strap_Width) / cos(45);
                cc = (bb - (half(Text_Gully) + Strap_Width)) / cos(45);
                dd = (half(Text_Gully) + Strap_Width) - cc;
                ee = aa + dd;

                ff = half(Text_Gully)+Strap_Width;
                gg = ff * sin(45);
                hh = (half(Text_Gully + Strap_Gully) + Strap_Width) - ff * cos(45);

                jj = half(Text_Gully);
                kk = ee - jj * sin(45);
                ll = half(Strap_Gully) + jj * cos(45);

                nn = kk-gg;
                oo = ll - hh;
                pp = sqrt(nn*nn + oo*oo);

                rotate([0,0,-90])
                  translate([-(half(Text_Gully + Strap_Gully) + Strap_Width), 0, 0])
                  rotate_extrude(angle = 45, convexity = 10)
                  translate([half(Text_Gully), 0, 0])
                  square([Strap_Width, Strap_Thickness]);

                translate([ee*1, -(half(Text_Gully) + Strap_Width)*0+half(Strap_Gully), 0])
                  rotate([0,0,90])
                  rotate_extrude(angle = 45, convexity = 10)
                  translate([half(Text_Gully), 0, 0])
                  square([Strap_Width, Strap_Thickness]);

                translate([gg,hh, 0])
                  rotate([0,0,45])
                  cube([pp,Strap_Width,Strap_Thickness]);

                translate([ee,half(Text_Gully + Strap_Gully),0])
                  cube([half(Strap_Length)-(Arm_Spacing * (Number_Of_Arms - 1) + Catch_Length + ee),Strap_Width, Strap_Thickness]);
            }

    }

}

;
```


[User]
I need an OpenSCAD design for a filter frame, intended for use in a facemask. I’ve got a few specific requirements for the design.

The overall shape of the frame should be a rounded square, with a specified side length and corner radius. It needs to have two layers (top and bottom) with a rim along the outside holding them together. The two layers will have a zigzagging grill structure to support a filter medium that will be woven through them. The grill should have a specified bar width, with spacing based on the other parameters I give you. Finally, there needs to be an option to add support enforcers on the main body of the frame, which will be placed at specific distances.

Here are the parameters I'd like to be able to adjust:

*   `side_length`: The overall length of one side of the frame (it’s a square).
*   `corner_radius`: The radius of the rounded corners.
*   `height`: The total height of the assembled filter frame.
*   `thickness`: The thickness of the top and bottom horizontal parts of the frame.
*   `rim_width`: The width of the outer rim that holds the top and bottom of the frame together.
*   `bar_width`: The width of the individual bars in the zigzag grill.
*   `filter_thickness`: The thickness of the material we plan to use for filtering between the two layers.
*   `support_enforcers`: A boolean-like option (e.g. "yes" or "no") to include support enforcers or not.

The grill spacing has a complex calculation associated with it, but I think the easiest way to describe it is that the number of bars along the length of the frame should be automatically calculated so that the spacing is as even as possible given all of the above parameters. You can use `n = round(((sqrt2 * (a - 2 * w) - 2 * (r - w) * (sqrt2 - 1) - 2 * ft) / (2 * ft + gt) - 1) / 2) * 2 + 1;` to calculate the amount, where `a` is `side_length`, `w` is `rim_width`, `r` is `corner_radius`, `ft` is `filter_thickness`, and `gt` is `bar_width`. The spacing of the bars should be calculated using `sp = (sqrt2 * (a - 2 * w) - 2 * (r - w) * (sqrt2 - 1) - n * gt) / (n + 1);`. The actual bars should be `gt` wide. I've already provided these equations to ensure they're consistent with what I need.

The support enforcers should be attached to the frame with the same rounded corners, and placed at a distance from the outermost bars such that the gap between is `1.8`.

Essentially, there will be two identical halves with the zigzag grill, and then a separate set of enforcers. I would like to use the `hull` and `difference` modules as much as possible.

Please generate an OpenSCAD file that implements this design. I don't need any external libraries for this.

[Assistant]
filter.scad:
```
// Outside size of the filter
side_length = 70.0;

// Outside corner radius
corner_radius = 16.0;

// Height of the completed filter
height = 9.0;

// Thickness of the horizontal parts
thickness = 1.25;

// Width of the rim holding the filter together
rim_width = 5.0;

// Width of individual filter bars
bar_width = 0.8;

// Thickness of the filtering material
filter_thickness = 0.26;

// Include support enforcers
support_enforcers = "no"; // [yes,no]

module filter(a, r, h, t, w, gt, ft, e) {
  sqrt2 = sqrt(2);
  n = round(((sqrt2 * (a - 2 * w) - 2 * (r - w) * (sqrt2 - 1) - 2 * ft) / (2 * ft + gt) - 1) / 2) * 2 + 1;

  module rounded (a, r, t) {
    hull() {
      translate([r, r, 0]) cylinder(r = r, h = t, $fn = 180);
      translate([a - r, r, 0]) cylinder(r = r, h = t, $fn = 180);
      translate([a - r, a - r, 0]) cylinder(r = r, h = t, $fn = 180);
      translate([r, a - r, 0]) cylinder(r = r, h = t, $fn = 180);
    }
  }

  module grill (n, ofs = 0) {
    sp = (sqrt2 * (a - 2 * w) - 2 * (r - w) * (sqrt2 - 1) - n * gt) / (n + 1);
    echo("grill spacing:", sp);

    for (x = [ofs : 2 : n - 1]) {
      translate([sp + x * (gt + sp), 0, 0]) cube([gt, sqrt2 * a - 2 * r * (sqrt2 - 1), h - t]);
    }
  }

  module filter_grill (base = false) {
    difference() {
      rounded(a, r, t);
      translate([w, w, -0.1]) rounded(a - 2 * w, r - w, t + 0.2);
    }
    intersection() {
      union() {
        difference() {
          translate([0, 0, 0]) rotate([0, 0, -45]) translate([sqrt2 * (a - 2 * w) / -2 + (r - w) * (sqrt2 - 1), r * (sqrt2 - 1), 0]) grill(n, base ? 1 : 0);
          translate([w, w, -0.1]) rounded(a - 2 * w, r - w, h - 2 * t + 0.1);
        }
        if (base) {
          rotate([0, 0, -45]) {
            translate([sqrt2 * (a - 2 * w) / -2 - w + (r - w) * (sqrt2 - 1), 0, 0]) cube([w, a, h - t]);
            translate([sqrt2 * (a - 2 * w) / 2 - (r - w) * (sqrt2 - 1), 0, 0]) cube([w, a, h - t]);
          }
        }
      }
      rounded(a, r, h - t);
    }
  }

  module enforcers () {
    hdist = 1.8;
    intersection() {
      rotate([0, 0, -45]) {
        translate([sqrt2 * (a - 2 * w) / -2 - w + (r - w) * (sqrt2 - 1) - hdist, 0, 0]) cube([w, a, h - t]);
        translate([sqrt2 * (a - 2 * w) / 2 - (r - w) * (sqrt2 - 1) + hdist, 0, 0]) cube([w, a, h - t]);
      }
      rounded(a, r, h - 2 * t - 0.01);
    }
  }

  translate([0, 0, h - t]) mirror([0, 0, 1]) {
    filter_grill(true);
    translate([a + 5, 0, 0]) filter_grill();
  }

  if (e) {
    translate([a + 5, 0, 0]) enforcers();
  }
}

filter(
  side_length,
  corner_radius,
  height,
  thickness,
  rim_width,
  bar_width,
  filter_thickness,
  support_enforcers == "yes"
);

```