[User]
I'm trying to create a 3D model of a sort of flattened, wavy tube. I have a set of points that define the shape, and I want to connect these points to form the tube. The points are defined in a `vertlist`, and the faces that connect these points are defined in `facets`. The basic shape is a single surface that represents the side of the tube, plus two end surfaces to seal it off. I'm using a `polyhedron` to create the shape, and I want to use a customizer variable called `bug` to switch between a few different combinations of `facets`, so I can see if I'm getting all the faces correct.

Specifically:
- If `bug` is 0, I want to use the first four elements of `facets` to create a properly formed tube: the first face from `facets` is the top surface, the second face is the bottom surface, and the third and fourth faces are the sides.
- If `bug` is 1, I want the same as 0, but include the fifth face as well to see what it looks like with an extra (incorrect) face
- If `bug` is 2, I only want to use the third, fourth and fifth faces.
- If `bug` is 3, I want the third through seventh faces.

The `vertlist` is a long list of 3D coordinates. The `facets` list is a list of index lists, where each index refers to an element in the `vertlist`.

Could you generate the OpenSCAD file for this model? Please make sure the customizer variable works correctly for each of the cases that I outlined, and use the exact variable names I included.

[Assistant]
fs5.scad:
```
// set to 0 for good case, 1 etc for bug case
bug = 0;

/* [hidden] */

if (bug==0)
	polyhedron(
        points = vertlist,
        faces  = [facets[0],facets[1],facets[2],facets[3]]
    );
else if (bug==1)
	polyhedron(
        points = vertlist,
        faces  = [facets[0],facets[1],facets[2],facets[3],facets[4]]
    );
else if (bug==2)
	polyhedron(
        points = vertlist,
        faces  = [facets[2],facets[3],facets[4]]
    );
else if (bug==3)
	polyhedron(
        points = vertlist,
        faces  = [facets[2],facets[3],facets[4],facets[5],facets[6]]
    );

vertlist = [
[19.5, -9.52628, 7],
[20.8793, -10.199, 7],
[22.3389, -10.6733, 7],
[23.8502, -10.9397, 7],
[25.3839, -10.9933, 7],
[26.9101, -10.8329, 7],
[28.3992, -10.4616, 7],
[29.8221, -9.88673, 7],
[31.1511, -9.11941, 7],
[32.3604, -8.17459, 7],
[33.4265, -7.07066, 7],
[34.3285, -5.82911, 7],
[35.049, -4.4741, 7],
[35.5739, -3.03201, 7],
[35.8929, -1.5309, 7],
[36, 0, 7],
[35.8929, 1.5309, 7],
[35.5739, 3.03201, 7],
[35.049, 4.4741, 7],
[34.3285, 5.82911, 7],
[33.4265, 7.07066, 7],
[32.3604, 8.17459, 7],
[31.1511, 9.11941, 7],
[29.8221, 9.88673, 7],
[28.3992, 10.4616, 7],
[26.9101, 10.8329, 7],
[25.3839, 10.9933, 7],
[23.8502, 10.9397, 7],
[22.3389, 10.6733, 7],
[20.8793, 10.199, 7],
[19.5, 9.52628, 7],
[18.1237, 8.82981, 7],
[16.6792, 8.28897, 7],
[15.1839, 7.91031, 7],
[13.6561, 7.69845, 7],
[12.1142, 7.65595, 7],
[10.5771, 7.78333, 7],
[9.0632, 8.07903, 7],
[7.59107, 8.53948, 7],
[6.17853, 9.15908, 7],
[4.84273, 9.9303, 7],
[3.59987, 10.8438, 7],
[2.46504, 11.8885, 7],
[1.45203, 13.0517, 7],
[0.573128, 14.3192, 7],
[-0.161, 15.6758, 7],
[-0.741441, 17.1048, 7],
[-1.16115, 18.5891, 7],
[-1.5, 21.6506, 7],
[-1.60705, 23.1815, 7],
[-1.92612, 24.6826, 7],
[-2.451, 26.1247, 7],
[-3.17147, 27.4797, 7],
[-4.07351, 28.7213, 7],
[-5.13956, 29.8252, 7],
[-6.34888, 30.77, 7],
[-7.67792, 31.5374, 7],
[-9.10081, 32.1123, 7],
[-10.5899, 32.4835, 7],
[-12.1161, 32.6439, 7],
[-13.6498, 32.5904, 7],
[-15.1611, 32.3239, 7],
[-16.6207, 31.8497, 7],
[-18, 31.1769, 7],
[-19.2723, 30.3188, 7],
[-20.4127, 29.2919, 7],
[-21.3992, 28.1163, 7],
[-22.2124, 26.8148, 7],
[-22.8366, 25.4129, 7],
[-23.2596, 23.9377, 7],
[-23.4732, 22.418, 7],
[-23.4732, 20.8833, 7],
[-23.2596, 19.3636, 7],
[-22.8366, 17.8884, 7],
[-22.2124, 16.4864, 7],
[-21.3992, 15.185, 7],
[-20.4127, 14.0094, 7],
[-19.2723, 12.9825, 7],
[-18, 12.1244, 7],
[-16.7087, 11.2807, 7],
[-15.5181, 10.3001, 7],
[-14.4425, 9.19453, 7],
[-13.4951, 7.97731, 7],
[-12.6874, 6.66326, 7],
[-12.0291, 5.26833, 7],
[-11.5282, 3.80945, 7],
[-11.1909, 2.30432, 7],
[-11.0213, 0.771229, 7],
[-11.0213, -0.771229, 7],
[-11.1909, -2.30432, 7],
[-11.5282, -3.80945, 7],
[-12.0291, -5.26833, 7],
[-12.6874, -6.66326, 7],
[-13.4951, -7.97731, 7],
[-14.4425, -9.19453, 7],
[-15.5181, -10.3001, 7],
[-18, -12.1244, 7],
[-19.2723, -12.9825, 7],
[-20.4127, -14.0094, 7],
[-21.3992, -15.185, 7],
[-22.2124, -16.4864, 7],
[-22.8366, -17.8884, 7],
[-23.2596, -19.3636, 7],
[-23.4732, -20.8833, 7],
[-23.4732, -22.418, 7],
[-23.2596, -23.9377, 7],
[-22.8366, -25.4129, 7],
[-22.2124, -26.8148, 7],
[-21.3992, -28.1163, 7],
[-20.4127, -29.2919, 7],
[-19.2723, -30.3188, 7],
[-18, -31.1769, 7],
[-16.6207, -31.8497, 7],
[-15.1611, -32.3239, 7],
[-13.6498, -32.5904, 7],
[-12.1161, -32.6439, 7],
[-10.5899, -32.4835, 7],
[-9.10081, -32.1123, 7],
[-7.67792, -31.5374, 7],
[-6.34888, -30.77, 7],
[-5.13956, -29.8252, 7],
[-4.07351, -28.7213, 7],
[-3.17147, -27.4797, 7],
[-2.451, -26.1247, 7],
[-1.92612, -24.6826, 7],
[-1.60705, -23.1815, 7],
[-1.5, -21.6506, 7],
[-1.41503, -20.1105, 7],
[-1.16115, -18.5891, 7],
[-0.741441, -17.1048, 7],
[-0.161, -15.6758, 7],
[0.573128, -14.3192, 7],
[1.45203, -13.0517, 7],
[2.46504, -11.8885, 7],
[3.59987, -10.8438, 7],
[4.84273, -9.9303, 7],
[6.17853, -9.15908, 7],
[7.59107, -8.53948, 7],
[9.0632, -8.07903, 7],
[10.5771, -7.78333, 7],
[12.1142, -7.65595, 7],
[13.6561, -7.69845, 7],
[15.1839, -7.91031, 7],
[16.6792, -8.28897, 7],
[19.5, -9.52628, 0],
[20.8793, -10.199, 0],
[22.3389, -10.6733, 0],
[23.8502, -10.9397, 0],
[25.3839, -10.9933, 0],
[26.9101, -10.8329, 0],
[28.3992, -10.4616, 0],
[29.8221, -9.88673, 0],
[31.1511, -9.11941, 0],
[32.3604, -8.17459, 0],
[33.4265, -7.07066, 0],
[34.3285, -5.82911, 0],
[35.049, -4.4741, 0],
[35.5739, -3.03201, 0],
[35.8929, -1.5309, 0],
[36, 0, 0],
[35.8929, 1.5309, 0],
[35.5739, 3.03201, 0],
[35.049, 4.4741, 0],
[34.3285, 5.82911, 0],
[33.4265, 7.07066, 0],
[32.3604, 8.17459, 0],
[31.1511, 9.11941, 0],
[29.8221, 9.88673, 0],
[28.3992, 10.4616, 0],
[26.9101, 10.8329, 0],
[25.3839, 10.9933, 0],
[23.8502, 10.9397, 0],
[22.3389, 10.6733, 0],
[20.8793, 10.199, 0],
[19.5, 9.52628, 0],
[18.1237, 8.82981, 0],
[16.6792, 8.28897, 0],
[15.1839, 7.91031, 0],
[13.6561, 7.69845, 0],
[12.1142, 7.65595, 0],
[10.5771, 7.78333, 0],
[9.0632, 8.07903, 0],
[7.59107, 8.53948, 0],
[6.17853, 9.15908, 0],
[4.84273, 9.9303, 0],
[3.59987, 10.8438, 0],
[2.46504, 11.8885, 0],
[1.45203, 13.0517, 0],
[0.573128, 14.3192, 0],
[-0.161, 15.6758, 0],
[-0.741441, 17.1048, 0],
[-1.16115, 18.5891, 0],
[-1.5, 21.6506, 0],
[-1.60705, 23.1815, 0],
[-1.92612, 24.6826, 0],
[-2.451, 26.1247, 0],
[-3.17147, 27.4797, 0],
[-4.07351, 28.7213, 0],
[-5.13956, 29.8252, 0],
[-6.34888, 30.77, 0],
[-7.67792, 31.5374, 0],
[-9.10081, 32.1123, 0],
[-10.5899, 32.4835, 0],
[-12.1161, 32.6439, 0],
[-13.6498, 32.5904, 0],
[-15.1611, 32.3239, 0],
[-16.6207, 31.8497, 0],
[-18, 31.1769, 0],
[-19.2723, 30.3188, 0],
[-20.4127, 29.2919, 0],
[-21.3992, 28.1163, 0],
[-22.2124, 26.8148, 0],
[-22.8366, 25.4129, 0],
[-23.2596, 23.9377, 0],
[-23.4732, 22.418, 0],
[-23.4732, 20.8833, 0],
[-23.2596, 19.3636, 0],
[-22.8366, 17.8884, 0],
[-22.2124, 16.4864, 0],
[-21.3992, 15.185, 0],
[-20.4127, 14.0094, 0],
[-19.2723, 12.9825, 0],
[-18, 12.1244, 0],
[-16.7087, 11.2807, 0],
[-15.5181, 10.3001, 0],
[-14.4425, 9.19453, 0],
[-13.4951, 7.97731, 0],
[-12.6874, 6.66326, 0],
[-12.0291, 5.26833, 0],
[-11.5282, 3.80945, 0],
[-11.1909, 2.30432, 0],
[-11.0213, 0.771229, 0],
[-11.0213, -0.771229, 0],
[-11.1909, -2.30432, 0],
[-11.5282, -3.80945, 0],
[-12.0291, -5.26833, 0],
[-12.6874, -6.66326, 0],
[-13.4951, -7.97731, 0],
[-14.4425, -9.19453, 0],
[-15.5181, -10.3001, 0],
[-18, -12.1244, 0],
[-19.2723, -12.9825, 0],
[-20.4127, -14.0094, 0],
[-21.3992, -15.185, 0],
[-22.2124, -16.4864, 0],
[-22.8366, -17.8884, 0],
[-23.2596, -19.3636, 0],
[-23.4732, -20.8833, 0],
[-23.4732, -22.418, 0],
[-23.2596, -23.9377, 0],
[-22.8366, -25.4129, 0],
[-22.2124, -26.8148, 0],
[-21.3992, -28.1163, 0],
[-20.4127, -29.2919, 0],
[-19.2723, -30.3188, 0],
[-18, -31.1769, 0],
[-16.6207, -31.8497, 0],
[-15.1611, -32.3239, 0],
[-13.6498, -32.5904, 0],
[-12.1161, -32.6439, 0],
[-10.5899, -32.4835, 0],
[-9.10081, -32.1123, 0],
[-7.67792, -31.5374, 0],
[-6.34888, -30.77, 0],
[-5.13956, -29.8252, 0],
[-4.07351, -28.7213, 0],
[-3.17147, -27.4797, 0],
[-2.451, -26.1247, 0],
[-1.92612, -24.6826, 0],
[-1.60705, -23.1815, 0],
[-1.5, -21.6506, 0],
[-1.41503, -20.1105, 0],
[-1.16115, -18.5891, 0],
[-0.741441, -17.1048, 0],
[-0.161, -15.6758, 0],
[0.573128, -14.3192, 0],
[1.45203, -13.0517, 0],
[2.46504, -11.8885, 0],
[3.59987, -10.8438, 0],
[4.84273, -9.9303, 0],
[6.17853, -9.15908, 0],
[7.59107, -8.53948, 0],
[9.0632, -8.07903, 0],
[10.5771, -7.78333, 0],
[12.1142, -7.65595, 0],
[13.6561, -7.69845, 0],
[15.1839, -7.91031, 0],
[16.6792, -8.28897, 0],
[19.5, -9.52628, 7],
[19, -10.3923, 7],
[19, -10.3923, 7],
[18.7061, -10.9013, 6.6029],
[18.4519, -11.3416, 6.10822],
[18.2454, -11.6993, 5.53151],
[18.0931, -11.9632, 4.89091],
[17.9997, -12.1249, 4.20656],
[17.9682, -12.1794, 3.5],
[17.9997, -12.1249, 2.79344],
[18.0931, -11.9632, 2.10909],
[18.2454, -11.6993, 1.46849],
[18.4519, -11.3416, 0.891784],
[18.7061, -10.9013, 0.3971],
[19, -10.3923, 0],
[19, -10.3923, 0],
[19.5, -9.52628, 0],
[20.8793, -10.199, 7],
[20.5047, -11.1262, 7],
[20.5047, -11.1262, 7],
[20.2845, -11.6712, 6.6029],
[20.0941, -12.1426, 6.10822],
[19.9394, -12.5255, 5.53151],
[19.8252, -12.808, 4.89091],
[19.7553, -12.9811, 4.20656],
[19.7317, -13.0395, 3.5],
[19.7553, -12.9811, 2.79344],
[19.8252, -12.808, 2.10909],
[19.9394, -12.5255, 1.46849],
[20.0941, -12.1426, 0.891784],
[20.2845, -11.6712, 0.3971],
[20.5047, -11.1262, 0],
[20.5047, -11.1262, 0],
[20.8793, -10.199, 0],
[22.3389, -10.6733, 7],
[22.0969, -11.6435, 7],
[22.0969, -11.6435, 7],
[21.9547, -12.2139, 6.6029],
[21.8317, -12.7072, 6.10822],
[21.7318, -13.1079, 5.53151],
[21.6581, -13.4036, 4.89091],
[21.6129, -13.5847, 4.20656],
[21.5977, -13.6458, 3.5],
[21.6129, -13.5847, 2.79344],
[21.6581, -13.4036, 2.10909],
[21.7318, -13.1079, 1.46849],
[21.8317, -12.7072, 0.891784],
[21.9547, -12.2139, 0.3971],
[22.0969, -11.6435, 0],
[22.0969, -11.6435, 0],
[22.3389, -10.6733, 0],
[23.8502, -10.9397, 7],
[23.7457, -11.9343, 7],
[23.7457, -11.9343, 7],
[23.6842, -12.5188, 6.6029],
[23.6311, -13.0245, 6.10822],
[23.5879, -13.4352, 5.53151],
[23.5561, -13.7382, 4.89091],
[23.5365, -13.9239, 4.20656],
[23.53, -13.9865, 3.5],
[23.5365, -13.9239, 2.79344],
[23.5561, -13.7382, 2.10909],
[23.5879, -13.4352, 1.46849],
[23.6311, -13.0245, 0.891784],
[23.6842, -12.5188, 0.3971],
[23.7457, -11.9343, 0],
[23.7457, -11.9343, 0],
[23.8502, -10.9397, 0],
[25.3839, -10.9933, 7],
[25.4188, -11.9927, 7],
[25.4188, -11.9927, 7],
[25.4393, -12.5801, 6.6029],
[25.4571, -13.0882, 6.10822],
[25.4715, -13.501, 5.53151],
[25.4821, -13.8055, 4.89091],
[25.4886, -13.9921, 4.20656],
[25.4908, -14.0549, 3.5],
[25.4886, -13.9921, 2.79344]];

facets = [[143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, 131, 130, 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287], [288, 305, 306, 289], [289, 306, 307, 290], [290, 307, 308, 291], [291, 308, 309, 292], [292, 309, 310, 293], [293, 310, 311, 294], [294, 311, 312, 295], [295, 312, 313, 296], [296, 313, 314, 297], [297, 314, 315, 298], [298, 315, 316, 299], [299, 316, 317, 300], [300, 317, 318, 301], [301, 318, 319, 302], [302, 319, 320, 303], [303, 320, 321, 304], [305, 322, 323, 306], [306, 323, 324, 307], [307, 324, 325, 308], [308, 325, 326, 309], [309, 326, 327, 310], [310, 327, 328, 311], [311, 328, 329, 312], [312, 329, 330, 313], [313, 330, 331, 314], [314, 331, 332, 315], [315, 332, 333, 316], [316, 333, 334, 317], [317, 334, 335, 318], [318, 335, 336, 319]];

```


[User]
I'd like to create a parametric stand for my Apple devices, specifically my iPhone, Apple Watch, and AirPods. I want to be able to adjust various dimensions to accommodate different phone cases and charging cables. I've seen a similar design online that I like, but it didn't quite work for me due to the larger size of my charging cables.

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

**Overall Structure:**

*   The stand should have a base with a cylindrical shape.
*   There should be a central post or cylinder that the Apple Watch sits around.
*   There should be an angled platform for the iPhone to rest on, with a cable hole.
*   An area to hold the Airpods case, with its own cable hole.

**Phone Holder:**

*   **Cable Plug Dimensions:** Parameters for the length and width of the phone charging cable plug. I need to be able to specify this precisely so my Anker Powerline cables fit.
*   **Platform Depth:** Adjustable depth for the phone platform.
*   **Platform Angle:** Adjustable angle for the phone platform.
*   **Platform Charger Position:**  A parameter to control the distance of the charging plug hole from the back of the platform - this is important for cases.
*   **Charging Port Cutout:** An option to enable a cutout in the support wall behind the phone for cases with a charging port cover that flips behind the phone. This should include width and height parameters for the cutout.
*   **Support Thickness:** Parameter for the thickness of the support wall behind the phone.

**Apple Watch Holder:**

*   **Center Diameter:** Parameter for the diameter of the central cylinder/post where the Apple Watch sits.
*   **Strap Gap:** Parameter to adjust the width of the channel for the watch straps.
*   **Strap Wall Width:** Parameter for the width of the wall to the left and right of the watch strap channel.
*   **Strap Wall Height:** Parameter to adjust the height of the wall to the left and right of the watch strap channel.
*   **Puck Diameter:**  Parameter for the diameter of the Apple Watch charging puck.
*   **Puck Bottom Gap:** Parameter for how far the charging puck sits above the base.
*   There should be a tunnel for the charging cable to exit.

**AirPods Holder:**

*   **Cable Plug Dimensions:** Parameters for the length and width of the AirPods charging cable plug.
*   **AirPods Case Dimensions:** Parameters for the width, depth, and height of the AirPods case.
*   A lip or surround to hold the AirPods case in place.
*   There should be a cutout/tunnel for the charging cable.
*   The design of the Airpods case holder should match the aesthetic of the rest of the stand.

**General Parameters:**

*   **Base Height:** Parameter for the height of the base.
*   **Center Height:** Parameter for the total height of the center post.
*   **Smoothness:** Parameter to control the smoothness of the curves (using `$fn`).

**Additional Requirements:**

*   I'd like to be able to generate different parts of the model, like just the phone stand part, or just the Airpods holder.
*   I'd also like to include some test parts so I can test the fit of my cable holes and the positioning of the phone and watch charging puck, before committing to printing the full stand. These testers should be separate models/modules.

I'm imagining these test parts being:
    *   A cable tester (to test fit of charging cables)
    *   A phone positioning tester (to test placement of a phone and charging cable, including if the port cover cutout is being used)
    *   A puck tester (to test watch puck size and positioning)

For libraries, I don't have any preference if it's all from scratch, otherwise, I'm open to suggestions.

I think that covers everything. Could you please generate the OpenSCAD code for this?

[Assistant]
Apple_Devices_Stand.scad:
```
// Which one would you like to see?
part = "full_model"; // [full_model:iPhone+Apple Watch+Airpods,no_airpods:iPhone+Apple Watch only,airpods_only:Airpods only,cable_tester:Cable size tester,phone_tester:Phone positioning tester,puck_tester:Apple Watch charging puck tester]

/* [Phone Holder] */

// Phone charging cable plug size in the long dimension (Stock Apple cable = 8mm / Anker Powerline = 10.5mm)
phone_plug_size_long=8;

// Phone charging cable plug size in the short dimension (Stock Apple cable = 5.6mm / Anker Powerline = 6mm)
phone_plug_size_short=5.6;

// Distance from the back of the phone platform to the middle of the charging plug hole (Stock iPhone 6 Plus: 4.8mm, also works with small case)
platform_charger_from_back=4.8;

// Some cases (e.g. Otterbox) have a charging port cover which flips behind the phone. Enable this to make a cutout in the back plate for the flap. Note: I would recommend that you also increase Phone Support Thickness in the Advanced Parameters tab to add more support
enable_phone_support_cutout="no"; // [yes,no]
phone_support_cutout_width=35;
phone_support_cutout_height=20;

// Thickness of the support wall behind the phone
phone_support_thickness=10; // [5:50]

/* [Airpods Holder] */

// Airpods charging cable plug size in the long dimension (Stock Apple cable = 8mm / Anker Powerline = 10.5mm)
airpods_plug_size_long=8;

// Phone charging cable plug size in the short dimension (Stock Apple cable = 5.6mm / Anker Powerline = 6mm)
airpods_plug_size_short=5.6;

/* [Advanced Parameters - Modify if you dare] */

// Height of the base
base_height=15; // [10:20]

// Diameter of the center potion that the watch goes around
center_outside_diameter=55; // [45:65]

// Total height of the model - changes size of wall behind the phone
center_height=80; // [60:100]

// Angle of the phone platform (in degrees)
phone_platform_angle=10; // [0:20]

// Depth of the phone platform
phone_platform_depth=20; // [20:30]

// Gap above the puck/below the phone platform - measured from the height of the corner of the phone platform
phone_platform_gap=2; // [-2:10]

// How far the upper watch strap platform is raised above the lower platform
watch_strap_raised=5; // [5:8]

// Width of the channel for watch straps - increase if you have fat watch straps
watch_strap_gap=8; // [8:16]

// Width of the wall to the left and right of the watch strap - works best if this matches the Airpods wall width
watch_strap_wall_width=4; // [0:8]

// Height of the wall to the left and right of the watch strap
watch_strap_wall_height=10; // [0:20]

// Diameter for the watch charging puck
puck_diameter=28.4;

// The gap below the watch charging puck (how far the puck sits above the base)
puck_bottom_gap=5; // [5:20]

// Width of the Airpods case
airpod_case_x=44.3;

// Depth of the Airpods case
airpod_case_y=21.3;

// Height of the Airpods case. The surround will be 1/2 this height
airpod_case_z=53.5;

//
airpods_holder_base=35;

// Smoothness of curves
smoothness=100; // [30:150]

/* [Hidden] */

$fn=smoothness;

watch_strap_raised_corner_diameter=watch_strap_raised+watch_strap_wall_height;

base_outside=center_outside_diameter+watch_strap_gap*2+watch_strap_wall_width*2;

puck_hole_depth=8;
puck_cable_diameter=6;
puck_cable_gap=1; // Distance of the puck from the front of the hole
puck_cable_tunnel_width=puck_cable_diameter-2;
puck_cable_center=-center_outside_diameter/2+puck_cable_diameter/2+puck_cable_gap;

puck_small_diameter=20; // Small hole within the larger puck hole

platform_height=base_height+puck_bottom_gap+puck_diameter+phone_platform_gap;

main_cable_tunnel_x=25;
main_cable_tunnel_y=base_outside/2+2;
main_cable_tunnel_gap=3;
main_cable_tunnel_z=base_height-main_cable_tunnel_gap*2+watch_strap_raised;

// Airpods
airpods_cable_tunnel_x=15;

surround_gap=4;
surround_x=airpod_case_x+surround_gap*2;
surround_y=airpod_case_y+surround_gap*2;
surround_z=airpod_case_z/2+airpods_holder_base;

grabber_slot_width=20;
grabber_slot_depth=20;
grabber_slot_corner_radius=4;

if (part == "puck_tester") {
    puck_tester();
} else if (part == "cable_tester") {
    cable_tester();
} else if (part == "phone_tester") {
    phone_tester();
} else if (part == "no_airpods") {
    draw_stand();
} else if (part == "airpods_only") {
    draw_airpods();
} else {
    union() {
        draw_airpods_for_full();
        draw_stand();
    }
}

// Puck tester
// Used to test the fit and positioning of the Apple Watch charging puck
module puck_tester() {
    intersection() {
        translate([-puck_diameter/2-5,-center_outside_diameter/2,0]) cube([puck_diameter+10,puck_hole_depth+5,puck_diameter+10]);
        difference() {
            // Main cylinder
            cylinder(d=center_outside_diameter,h=puck_diameter+10);

            //  Puck hole
            translate([0,-center_outside_diameter/2+puck_hole_depth,puck_diameter/2+5]) rotate([90,0,0]) cylinder(d=puck_diameter,h=puck_hole_depth);

            // Puck cable hole
            translate([0,puck_cable_center,0]) cylinder(d=puck_cable_diameter,h=puck_diameter);

            // Watch charger small cutout
            translate([0,0,puck_small_diameter/2+5]) rotate([90,0,0]) cylinder(d=puck_small_diameter,h=base_outside);

            // Tunnel to watch charger cable
            translate([-puck_cable_tunnel_width/2,puck_cable_center,0]) cube([puck_cable_tunnel_width,-puck_cable_center,base_height+puck_bottom_gap]);
        }
    }
}

// Cable tester
// Used to test the cable hole size
module cable_tester() {
    plug_tester_width=phone_plug_size_long+10;
    plug_tester_back_z=20;
    plug_tester_base_z=10;
    difference() {
        translate([-plug_tester_width/2,0,0]) cube([plug_tester_width,phone_plug_size_short+10,plug_tester_base_z]);

        // Cable hole
        translate([(phone_plug_size_short-phone_plug_size_long)/2,phone_plug_size_short/2+5,0]) hull() {
            cylinder(d=phone_plug_size_short, h=plug_tester_base_z);
            translate([phone_plug_size_long-phone_plug_size_short,0,0]) cylinder(d=phone_plug_size_short, h=plug_tester_base_z);
        }
    }
}

// Phone tester
// Used to test the positioning of the phone on the platform
module phone_tester() {
    // If we have a cutout, need to accomodate
    phone_tester_width = (enable_phone_support_cutout == "yes")? max(phone_plug_size_long,phone_support_cutout_width)+10 : phone_plug_size_long+10;
    phone_tester_back_z = (enable_phone_support_cutout == "yes")? max(phone_support_cutout_height,10)+10 : 20;

    phone_tester_base_z=10;
    difference() {
        translate([-phone_tester_width/2,0,0]) cube([phone_tester_width,5+phone_plug_size_short/2+platform_charger_from_back+10,phone_tester_base_z+phone_tester_back_z]);
        translate([-phone_tester_width/2,0,phone_tester_base_z]) cube([phone_tester_width,5+phone_plug_size_short/2+platform_charger_from_back,phone_tester_back_z]);

        // Cable hole
        translate([(phone_plug_size_short-phone_plug_size_long)/2,5+phone_plug_size_short/2,0]) hull() {
            cylinder(d=phone_plug_size_short, h=phone_tester_base_z);
            translate([phone_plug_size_long-phone_plug_size_short,0,0]) cylinder(d=phone_plug_size_short, h=phone_tester_base_z);
        }

        // Cutout
        if (enable_phone_support_cutout == "yes") {
              translate([-phone_support_cutout_width/2,5+phone_plug_size_short/2+platform_charger_from_back,phone_tester_base_z]) cube([phone_support_cutout_width,phone_support_thickness,phone_support_cutout_height]);
        }
    }
}

// Main stand
module draw_stand() {
    difference() {
        union() {
            // Center and base
            difference() {
                union() {
                    // Base
                    cylinder(d=base_outside,h=base_height);
                    // Center
                    cylinder(d=center_outside_diameter,h=center_height+base_height);
                }

                // Phone platform
                translate([0,-center_outside_diameter/2+phone_platform_depth,platform_height]) rotate([-phone_platform_angle,0,0]) {
                    // Area where phone goes
                    translate([-center_outside_diameter/2,-center_outside_diameter,0]) cube([center_outside_diameter,center_outside_diameter,center_height]);

                    // Area behind phone
                    translate([-center_outside_diameter/2,phone_support_thickness,0]) cube([center_outside_diameter,center_outside_diameter,center_height]);

                    // Phone support cutout
                    if (enable_phone_support_cutout == "yes") {
                        translate([-phone_support_cutout_width/2,0,0]) cube([phone_support_cutout_width,phone_support_thickness,phone_support_cutout_height]);
                    }

                    // Cable hole
                    translate([(phone_plug_size_short-phone_plug_size_long)/2,-platform_charger_from_back,-phone_platform_gap-puck_diameter/2]) hull() {
                        cylinder(d=phone_plug_size_short, h=phone_platform_gap+puck_diameter/2);
                        translate([phone_plug_size_long-phone_plug_size_short,0,0]) cylinder(d=phone_plug_size_short, h=phone_platform_gap+puck_diameter/2);
                    }
                }

            }

            // Strap wall and raised
            difference() {
                union() {
                    // Strap Wall
                    difference() {
                        cylinder(d=base_outside,h=base_height+watch_strap_raised+watch_strap_wall_height);
                        cylinder(d=base_outside-watch_strap_wall_width*2,h=base_height+watch_strap_raised+watch_strap_wall_height+1);
                    }
                    // Raised
                    cylinder(d=base_outside,h=base_height+watch_strap_raised);
                }

                // Front right wall curve
                translate([0,0,watch_strap_raised_corner_diameter/2+base_height]) rotate([90,0,45]) small_s(watch_strap_raised_corner_diameter);

                // Front left wall curve
                translate([0,0,watch_strap_raised_corner_diameter/2+base_height]) rotate([90,0,-45]) mirror([1,0,0]) small_s(watch_strap_raised_corner_diameter);

                // Front cutout
                translate([0,0,base_height]) rotate([0,0,-135]) cube([base_outside,base_outside,watch_strap_raised+watch_strap_wall_height+1]);

                // Back cutout
                translate([0,0,base_height+watch_strap_raised]) rotate([0,0,45]) cube([base_outside,base_outside,watch_strap_wall_height+1]);

                // Back left wall curve
                translate([0,0,watch_strap_wall_height/2+base_height+watch_strap_raised]) rotate([90,0,-135]) small_s(watch_strap_wall_height);

                // Back right wall curve
                translate([0,0,watch_strap_wall_height/2+base_height+watch_strap_raised]) rotate([90,0,135]) mirror([1,0,0]) small_s(watch_strap_wall_height);

            }
        }

        // Watch charger large cutout
        translate([0,-center_outside_diameter/2+puck_hole_depth,puck_diameter/2+base_height+puck_bottom_gap]) rotate([90,0,0]) cylinder(d=puck_diameter,h=base_outside);

        // Watch charger small cutout
        translate([0,0,puck_small_diameter/2+base_height+puck_bottom_gap]) rotate([90,0,0]) cylinder(d=puck_small_diameter,h=base_outside);

        // Watch charger big cable
        translate([0,puck_cable_center,main_cable_tunnel_gap]) cylinder(d=puck_cable_diameter,h=base_height+puck_bottom_gap+puck_diameter/2);

        // Tunnel to watch charger cable
        //translate([-puck_cable_tunnel_width/2,puck_cable_center,main_cable_tunnel_gap]) cube([puck_cable_tunnel_width,-puck_cable_center,base_height+puck_bottom_gap]);

        // Inner main cutout
        inner_space_x=center_outside_diameter-puck_hole_depth*2+4;//main_cable_tunnel_x*1.5;
        inner_space_y=center_outside_diameter/2-puck_hole_depth;
        inner_space_z=base_height+puck_bottom_gap+puck_small_diameter;
        translate([0,0,-1]) cylinder(d=inner_space_x,h=inner_space_z+1);
        //translate([-inner_space_x/2,-inner_space_y,2]) cube([inner_space_x,inner_space_y,inner_space_z+1]);

        // Main cable tunnel
        translate([-main_cable_tunnel_x/2,-1,main_cable_tunnel_gap]) cube([main_cable_tunnel_x,main_cable_tunnel_y,main_cable_tunnel_z]);

        // Right cable tunnel to Airpods
        rotate([0,0,-65]) translate([-airpods_cable_tunnel_x/2,-1,main_cable_tunnel_gap]) cube([airpods_cable_tunnel_x,main_cable_tunnel_y,main_cable_tunnel_z]);
    }
}

module draw_airpods_for_full() {
    rotate([0,0,25]) translate([airpod_case_x/2+base_outside/2,0,0]) draw_airpods();
}
module draw_airpods() {
    difference() {
        // Surround
        union() {
            // Square part
            //translate([-surround_y+surround_gap,-surround_y/2,0]) cube([surround_y,surround_y,base_height+watch_strap_raised+watch_strap_wall_height]);

            // Double cylinders
            translate([(surround_y-surround_x)/2,0,0]) hull() {
                cylinder(d=surround_y, h=surround_z);
                translate([surround_x-surround_y,0,0]) cylinder(d=surround_y, h=surround_z);
            }
        }

        // Airpod case size
        translate([-(airpod_case_x-airpod_case_y)/2,0,airpod_case_y/2+airpods_holder_base]) hull() {
            hull() {
                sphere(d=airpod_case_y);
                translate([airpod_case_x-airpod_case_y,0,0]) sphere(d=airpod_case_y);
            }
            hull() {
                translate([0,0,airpod_case_z-airpod_case_y]) sphere(d=airpod_case_y);
                translate([airpod_case_x-airpod_case_y,0,airpod_case_z-airpod_case_y]) sphere(d=airpod_case_y);
            }
        }

        // Grabber slot
        difference() {
            translate([-grabber_slot_width/2,-surround_y/2+-1,surround_z-grabber_slot_depth]) cube([grabber_slot_width,surround_y+2,grabber_slot_depth+1]);

            // Grabber slot bottom-left corner
            translate([-grabber_slot_width/2+grabber_slot_corner_radius,-surround_y/2-1,surround_z+grabber_slot_corner_radius-grabber_slot_depth]) rotate([-90,90,0]) difference() {
                cube([grabber_slot_corner_radius,grabber_slot_corner_radius,surround_y+2]);
                cylinder(d=grabber_slot_corner_radius*2,h=surround_y+2);
            }

            // Grabber slot bottom-right corner
            translate([+grabber_slot_width/2-grabber_slot_corner_radius,-surround_y/2-1,surround_z+grabber_slot_corner_radius-grabber_slot_depth]) rotate([-90,0,0]) difference() {
                cube([grabber_slot_corner_radius,grabber_slot_corner_radius,surround_y+2]);
                cylinder(d=grabber_slot_corner_radius*2,h=surround_y+2);
            }
        }

        // Grabber slot top-left corner
        translate([-grabber_slot_width/2-grabber_slot_corner_radius,surround_y/2+1,surround_z-grabber_slot_corner_radius]) rotate([90,0,0]) difference() {
            cube([grabber_slot_corner_radius+1,grabber_slot_corner_radius+1,surround_y+2]);
            cylinder(d=grabber_slot_corner_radius*2,h=surround_y+2);
        }

        // Grabber slot rop-right corner
        translate([+grabber_slot_width/2+grabber_slot_corner_radius,surround_y/2+1,surround_z-grabber_slot_corner_radius]) rotate([90,-90,0]) difference() {
            cube([grabber_slot_corner_radius+1,grabber_slot_corner_radius+1,surround_y+2]);
            cylinder(d=grabber_slot_corner_radius*2,h=surround_y+2);
        }

        // Cable hole
        translate([(airpods_plug_size_short-airpods_plug_size_long)/2,0,-1]) hull() {
            cylinder(d=airpods_plug_size_short, h=airpods_holder_base+20);
            translate([airpods_plug_size_long-airpods_plug_size_short,0,0]) cylinder(d=airpods_plug_size_short, h=airpods_holder_base+20);
        }

        // Cable tunnel to main body
        if (part == "airpods_only") {
            // Airpods only, cable goes out the back
            rotate([0,0,-90]) translate([-(surround_x/2+1),-airpods_cable_tunnel_x/2,main_cable_tunnel_gap]) cube([surround_x/2+1,airpods_cable_tunnel_x,main_cable_tunnel_z]);
        } else {
            // Full model, cable goes out the side
            translate([-(surround_x/2+1),-airpods_cable_tunnel_x/2,main_cable_tunnel_gap]) cube([surround_x/2+1,airpods_cable_tunnel_x,main_cable_tunnel_z]);
        }

        // Center cutout
        hull() {
            cylinder(d=airpod_case_y,h=main_cable_tunnel_z+main_cable_tunnel_gap);
            if (part != "airpods_only") {
                translate([-airpod_case_x/2+airpod_case_y/2,0,0]) cylinder(d=airpod_case_y,h=main_cable_tunnel_z+main_cable_tunnel_gap);
            }
        }
    }
}

// Helper to create curves
module small_s(size=5) {
    difference() {
        union() {
            cylinder(d=size,h=base_outside);
            cube([size,size,base_outside]);
        }
        translate([size,0,-1]) cylinder(d=size,h=base_outside+2);
    }
}

```


[User]
I need an OpenSCAD design for a parasol adapter. I have two existing files that describe what I'm trying to do (attached), but I'd like to have the code written from scratch, hopefully more clearly and with better parameterization.

Here's the basic idea: I need an adapter that fits inside a parasol base and holds a parasol pole. The adapter is essentially a cylinder with a rim at the top, and ribs on the outer diameter for a better grip on the base. There's also a vertical slot on the side of the adapter to accommodate a retaining mechanism in the base, but this is optional.

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

*   **`innerdia`**: The inner diameter of the adapter, which corresponds to the outer diameter of the parasol tube.
*   **`outerdia`**: The outer diameter of the adapter, which corresponds to the inner diameter of the parasol base.
*   **`rim`**: The height of the rim at the top of the adapter, measured relative to the `outerdia`.
*   **`height`**: The height of the adapter below the rim.
*   **`wall`**: The general wall thickness of the adapter (and ribs), assuming 100% infill for printing.
*  **`rimwall`**: The height of the rim itself
*   **`slot`**: The width of the vertical slot on the side of the adapter (0 if not needed).
*   **`tol`**: A tolerance value to adjust for fit, probably a small value like 0.2mm.
*  **`crimp_percent`**: A percentage value to account for material shrinkage when printing, should be applied to the whole model. This is usually something like 0.2% for PLA.

I'd like the rim to have a chamfer, for aesthetics and ease of insertion.

The ribs should extend from the bottom of the rim to the bottom of the adapter, should be evenly spaced around the circumference, and should be as many as possible given a minimum spacing (e.g., ~4x `wall`). It's important to have them stop before they touch each other, and also that there should be rib-like structures on the left and right sides of the slot if a slot is needed.

I need it to use a fairly high `$fn`, at least 96, for nice curves.

I would like the design to be parametric, but without using any libraries (MCAD, BOSL, BOSL2, dotSCAD, NopSCADlib, BOLTS) and should output a single .scad file.

Could you generate this OpenSCAD code for me?

[Assistant]
parasol_adapter.scad:
```
//
//  adapter to fit various size parasol tubes to various size
//  parasol bases
//
//  design by:
//      egil kvaleberg
//      28 may 2017
//
//  v1: original
//  v2: modified to provide more support if slot is wide
//

// crimp factor for material, approx 0.2% may work for PLA, ABS needs more, maybe as high as 1.5%
crimp_percent = 0.2;

// inner diameter of adapter (which is outer dia of parasol tube)
innerdia = 38.0;

// outer diameter of adapter (which is inner dia of base tube)
outerdia = 56.5;

// top rim relative to outerdia
rim = 2.7;

// adapter height below rim
height = 65.0;

// general wall (assume 100% infill)
wall = 2.0;

// height of rim
rimwall = 5.0;

// width of slot in side, 0 if none
slot = 22.0;

// tolerance of fit
tol = 0.2;

fn=1*96;
d=1*0.1;
chamfer = wall*0.4;

a0 = asin((slot/2) / (innerdia/2));
echo(a0);
a1 = 360-2*a0;
nrib = max(5,ceil(a1*outerdia*3.14 / (360*4*wall)));
echo(nrib);

module add() {
    // rim
    cylinder(r1=outerdia/2+rim-chamfer, r2=outerdia/2+rim, h=chamfer, $fn=fn);
    translate([0,0,chamfer])
      cylinder(r=outerdia/2+rim, h=rimwall-chamfer, $fn=fn);
    // inner core
    cylinder(r=innerdia/2+wall, h=rimwall+height, $fn=fn);
    // ribs
    intersection () {
        union () {
            translate([0,0,-d])
              cylinder(r=outerdia/2-tol, h=d+rimwall+height-chamfer, $fn=fn);
            translate([0,0,rimwall+height-chamfer])
              cylinder(r1=outerdia/2-tol, r2=outerdia/2-tol-chamfer, h=chamfer+d, $fn=fn);
        }
        union () {
            for (a = [-1.5:1.0:nrib+0.5])
              rotate([0,0,a0 + a*a1/(nrib-1)])
                translate([0,-wall/2,0])
                  cube([outerdia/2,wall,rimwall+height]);
            for (s = [1,-1])
              translate([0,s*(slot/2+wall/2)-wall/2,0])
                cube([outerdia/2,wall,rimwall+height]);
        }
    }
}

module sub() {
    // inner core
    translate([0,0,-d])
      cylinder(r=innerdia/2+tol, h=d+rimwall+height+d, $fn=fn);
    translate([0,0,-d])
      cylinder(r1=innerdia/2+tol+chamfer, r2=innerdia/2+tol, h=d+chamfer, $fn=fn);
    // slot
    if (slot > 0) {
        translate([outerdia/2-2*rim,-slot/2,rimwall * 0.5])
          cube([(1+2)*rim,slot,rimwall+height+d]);
        translate([0,-slot/2,rimwall])
          cube([outerdia/2+rim,slot,height+d]);
    }
}

s0 = 1.0 + crimp_percent/100;
scale([s0,s0,s0])
difference () {
    add();
    sub();
}
```
parasol_adapter_v1.scad:
```
//
//  adapter to fit various size parasol tubes to various size
//  parasol bases
//
//  design by:
//      egil kvaleberg
//      28 may 2017
//

//outer 55.4  55.3
//10%   50
// 1%   55
//0.1   55.35
//0.2%

// crimp factor for material, approx 0.2% may work for PLA, ABS needs more
crimp_percent = 0.2;

// inner diameter of adapter (which is outer dia of parasol tube)
innerdia = 38.0;

// outer diameter of adapter (which is inner dia of base tube)
outerdia = 56.5;

// top rim relative to outerdia
rim = 2.7;

// adapter height below rim
height = 65.0;

// general wall (assume 100% infill)
wall = 2.0;

// height of rim
rimwall = 5.0;

// width of slot in side, 0 if none
slot = 22.0;

// tolerance of fit
tol = 0.2;

fn=1*96;
d=1*0.1;
chamfer = wall*0.4;

a0 = asin((slot/2) / (innerdia/2));
echo(a0);
a1 = 360-2*a0;
nrib = max(5,ceil(a1*outerdia*3.14 / (360*4*wall)));
echo(nrib);

module add() {
    // rim
    cylinder(r1=outerdia/2+rim-chamfer, r2=outerdia/2+rim, h=chamfer, $fn=fn);
    translate([0,0,chamfer])
      cylinder(r=outerdia/2+rim, h=rimwall-chamfer, $fn=fn);
    // inner core
    cylinder(r=innerdia/2+wall, h=rimwall+height, $fn=fn);
    // ribs
    intersection () {
        union () {
            translate([0,0,-d])
              cylinder(r=outerdia/2-tol, h=d+rimwall+height-chamfer, $fn=fn);
            translate([0,0,rimwall+height-chamfer])
              cylinder(r1=outerdia/2-tol, r2=outerdia/2-tol-chamfer, h=chamfer+d, $fn=fn);
        }
        union () {
            for (a = [0.5:1.0:nrib-1.5])
              rotate([0,0,a0 + a*a1/(nrib-1)])
                translate([0,-wall/2,0])
                  cube([outerdia/2,wall,rimwall+height]);
            for (s = [1,-1])
              translate([0,s*(slot/2+wall/2)-wall/2,0])
                cube([outerdia/2,wall,rimwall+height]);
        }
    }
}

module sub() {
    // inner core
    translate([0,0,-d])
      cylinder(r=innerdia/2+tol, h=d+rimwall+height+d, $fn=fn);
    translate([0,0,-d])
      cylinder(r1=innerdia/2+tol+chamfer, r2=innerdia/2+tol, h=d+chamfer, $fn=fn);
    // slot
    if (slot > 0) {
        translate([outerdia/2-2*rim,-slot/2,rimwall * 0.5])
          cube([(1+2)*rim,slot,rimwall+height+d]);
        translate([0,-slot/2,rimwall])
          cube([outerdia/2+rim,slot,height+d]);
    }
}

s0 = 1.0 + crimp_percent/100;
scale([s0,s0,s0])
difference () {
    add();
    sub();
}
```


[User]
Hey, I'm looking to create a parametric adapter for connecting a saw to a vacuum cleaner. I've got some specific measurements in mind, but I'd like to be able to easily adjust them for different tools.

Basically, I need a cylindrical adapter with a larger diameter at one end, transitioning to a smaller diameter at the other. Here are the key features:

*   **Hole Diameters:** I need two hole diameters, `d1` for the larger end and `d2` for the smaller end. I would like to be able to customize the hole diameters with a tolerance value so I can change the fit.
*   **Lengths:** There are three length parameters: `l1` for the length of the larger diameter section, `l2` for the transition length, and `l3` for the length of the smaller diameter section.
*   **Wall Thickness:** I need to be able to set the wall thickness `wt` of the adapter.
*   **Smoothness:** I want to be able to adjust the smoothness of the curves for printing purposes (or by other means).
*   **Units:** All measurements are in millimeters (mm).

I'd like to be able to tweak all of these variables (`d1`, `d2`, `l1`, `l2`, `l3`, `wt`, `tol`, `$fn`) in an OpenSCAD file to customize the adapter.

Can you generate an OpenSCAD file or files for this? It should be easy to understand and modify, and have good comments. I think that should be all I need.

[Assistant]
pipeAdapter.scad:
```
// all measurements are in millimeters (mm)

// tolerance added to every hole diameter (mm)
tol = 0.0;

// wall thickness (mm)
wt 		= 1.2;

// bigger hole diameter (mm)
d1 = 42.0;
d1t = d1 + tol;

// smaller hole diameter (mm)
d2 = 34.9;
d2t = d2 + tol;

// bigger diameter part length (mm)
l1 = 18.0;

// transition part length (mm)
l2 = 15.0;

// smaller diameter part length (mm)
l3 = 30.0;

// difference in holes and solids to prevent (visual) glitches (mm)
e = 0.02*1;

// smoothness
$fn = 256;

module solid()
{
	cylinder(d=d1t+2*wt,h=l1);
	translate([0,0,l1			]) cylinder(d1=d1t+2*wt,d2=d2t+2*wt	,h=l2);
	translate([0,0,l1+l2		]) cylinder(d=d2t+2*wt					,h=l3);
}

module hole()
{
	translate([0,0,-e			]) cylinder(d=d1t,h=l1+2*e);
	translate([0,0,l1			]) cylinder(d1=d1t,d2=d2t,h=l2+e);
	translate([0,0,l1+l2		]) cylinder(d=d2t,h=l3+e);
}

difference()
{
	solid();
	hole();
}
```


[User]
I'm looking to create a customizable clip, similar to the one described as a "universal clamp" that's used to hold a spool on an axis. I'd like to be able to adjust various parameters of this clamp using OpenSCAD.

Specifically, I need to be able to control the following:

*   **Rod Diameter:** The inner diameter of the clamp that fits around the rod/axis.
*   **Spring Diameter:** The outer diameter of the flexible spring portion of the clamp.
*   **Spring Width:** The width of the spring, ideally as a multiple of the extrusion width of my printer.
*   **Spring Height:** The height/thickness of the spring portion.
*   **Rim Height:** The height of the raised rim or lip around the clamp openings, if it has one.
*   **Front Opening Angle:** The angle of the gap at the front of the clamp.
*   **Back Opening Angle:** The angle of the gap at the back of the clamp.
*   **Pretension:** The amount that the clamp is pre-stressed to grip the rod. This should be adjustable as a percentage of the rod diameter.
*    **Fillet Radius:** The radius of the rounded fillets connecting the spring to the main clamp body.
*   **Y-Offset:** The position of the contact point between the bracket and spring along the Y-axis, relative to the center of the rod. This should be adjustable as a percentage of the rod diameter and should allow for the spring to be centered (or offset up or down).
* **Extrusion Width:** The width of the extrusion used to make the model.
*   **Layer Height:** The targeted layer height that the model is designed to be printed at.
*   **Resolution:** The circular resolution in segments per 360 Degrees, allowing for adjustment of the "smoothness" of circles.

I'd like the design to consist of two halves, mirrored along one of the main axes. There should also be a rim that could have the same inner and outer shape as the clamp, just slightly taller in the Z axis.

I'd like for the design to include the inner shape, the outer shape of the spring, and also include a difference operation to "cut away" some of the material.

I'm not sure how to best achieve this in OpenSCAD so I'm hoping you can help by providing the necessary `.scad` file(s) to create this clamp with all of the customizable parameters I've mentioned.

[Assistant]
universal-clamp.scad:
```
// by Martin Muehlhaeuser Twitter: @mmone

/* [main parameters] */
// Inner diameter of the clamp bracket
rodDiameter = 8;
// Outer diameter of the spring ring in units
springDiameter = 27;
// Spring width. As x times extrusion width.
springWidth = 8;
// Spring height in units
springHeight = 5;
// Rim height
rimHeight = 2;
// Width of the brackets front opening in degrees.
frontOpeningAngle = 60; //[1:90]
// Width of the brackets back opening in degrees.
backOpeningAngle = 50; //[1:90]
// Pretension in percentage of rod diameter. Reduces the distance between the two bracket halfs.
pretension = 30; //[-50:50]
// Radius of the fillet connecting spring and bracket.
filletRadius=3;
// Offset of the contact point between bracket and spring along the y-Axis in percent of rod-diameter. Zero means centered.
yOffset = 22; //[-50:50]

/* [printer Parameters] */
// Extrusion width in units.
extrusionWidth = 0.4;
// The targeted layer height for this model.
layerHeight = 0.3;
// circular resolution in segments per 360 Degrees.
resolution = 360;

/* [Hidden] */
_springWidth = extrusionWidth * springWidth;
_yOffset = rodDiameter * yOffset/100;
_pretension = rodDiameter*(pretension/100)/ 2;

module mirrorCopy(vec=[0,1,0])
{
    children();
    mirror(vec) children();
}

// calculate a point on a circle
function pointOnCircle(radius=1, center=[0,0], angle) = (
     [(sin(angle) * radius) + center[0], (cos(angle) * radius) + center[1]]
);

function springProfile(
    springRadius=1,
    rodRadius=1,
    width=2,
    frontOpeningAngle = 20,
    backOpeningAngle = 20,
    yOffset = 0,
    filletRadius = 1,
    pretension = 1,
    step=1) = (

    let(halfFrontOpening = frontOpeningAngle/2) // frontal opening of the bracket in degrees
    let(halfBackOpening = backOpeningAngle/2)  // back opening of the bracket in degrees
    let(offsetY = sqrt(pow(springRadius,2) - pow((rodRadius + width - pretension),2)) - yOffset)  // offset of the spring circle (rod center is at world[0,0] )
    let(a = sqrt(pow(pretension,2) + pow(offsetY,2)))
    let(b = rodRadius + width + filletRadius)
    let(c1 = springRadius + width/2 + filletRadius)
    let(c2 = springRadius - width/2 - filletRadius)
    let(angleUp = acos( (pow(a,2) + pow(b,2) - pow(c1,2)) / (2*a*b)) + atan(pretension/offsetY) - 90)
    let(angleDown = 90 - (acos( (pow(a,2) + pow(b,2) - pow(c2,2)) / (2*a*b)) + atan(pretension/offsetY)))
    let(yUp = sin(angleUp) * b)
    let(xUp = cos(angleUp) * b)
    let(yDown = sin(angleDown) * b)
    let(xDown = cos(angleDown) * b)
    let(halfOpeningAngleInner = acos( (offsetY - yDown) / c2) )
    let(halfOpeningAngleOuter = acos( (offsetY + yUp) / c1))

    concat(
        [concat( //  main body
            [for (a=[180+halfBackOpening:step:360-halfFrontOpening]) pointOnCircle(rodRadius, [pretension,0], a) ], // rod inner
            [for (a=[360-halfFrontOpening:-step:270+angleUp]) pointOnCircle(rodRadius+width, [pretension,0], a) ], // rod outer top

            [for (a=[90+angleUp:step:180-halfOpeningAngleOuter]) pointOnCircle(filletRadius, [-(xUp-pretension),yUp], a) ], //fillet upper left

            [for (a=[360-halfOpeningAngleOuter:-step:170]) pointOnCircle(springRadius+(width/2), [0,-offsetY], a) ],
            [pointOnCircle(springRadius+(width/2), [0,-offsetY], 170)], // outer ring

            [for (a=[170:step:360-halfOpeningAngleInner]) pointOnCircle(springRadius-(width/2), [0,-offsetY], a) ], //inner ring

            [for (a=[-halfOpeningAngleInner:step:90-angleDown]) pointOnCircle(filletRadius, [-(xDown-pretension),-yDown], a) ],//fillet lower left

            [for (a=[270-angleDown:-step:180+halfBackOpening]) pointOnCircle(rodRadius+width, [pretension,0], a) ],
            [pointOnCircle(rodRadius, [pretension,0], 180+halfBackOpening)] // rod outer bottom
        )],
        [concat( // cutout
            [for (a=[360-halfOpeningAngleInner:step:360-halfOpeningAngleOuter]) pointOnCircle(springRadius, [0,-offsetY], a) ], // ring segment
            [for (a=[180-halfOpeningAngleOuter:-step:90+angleUp]) pointOnCircle(filletRadius + width/2, [-(xUp-pretension),yUp], a) ], //fillet upper left
            [for (a=[270+angleUp:-step:270-angleDown]) pointOnCircle(rodRadius+width/2, [pretension,0], a) ],// rod outer top

            [for (a=[90-angleDown:-step:-halfOpeningAngleInner]) pointOnCircle(filletRadius+width/2, [-(xDown-pretension),-yDown], a) ],
            [ pointOnCircle(springRadius, [0,-offsetY], 360-halfOpeningAngleInner) ] // fillet lower left
        )],
        [concat( // rim
            [for (a=[180+halfBackOpening:step:360-halfFrontOpening]) pointOnCircle(rodRadius, [pretension,0], a) ], // rim inner
            [for (a=[360-halfFrontOpening:-step:180+halfBackOpening]) pointOnCircle(rodRadius + width/2, [pretension,0], a) ] // rod outer
        )]
    )
);

module clamp() {
    roundoverRadius = _springWidth * 0.24;
    coutoutFilletRadius = _springWidth * 0.15;
    convexity = 10;

    points = springProfile(
                 springRadius=(springDiameter-_springWidth)/2,
                 rodRadius = rodDiameter/2,
                 width=_springWidth,
                 frontOpeningAngle=frontOpeningAngle,
                 backOpeningAngle=backOpeningAngle,
                 yOffset = _yOffset,
                 filletRadius=filletRadius,
                 pretension=_pretension,
                 step=360/resolution
            );

    union() {
        mirrorCopy([1,0,0]) {
            difference() {
            linear_extrude(height=springHeight, center=false, $fn = resolution, convexity = convexity)
                offset(r=roundoverRadius) offset(delta=-roundoverRadius)
                    polygon(points = points[0]);

            linear_extrude(height=springHeight*4, center=true, $fn = resolution, convexity = convexity)
                offset(r=coutoutFilletRadius) offset(delta=-coutoutFilletRadius)
                    polygon(points = points[1]);

            }
            translate([0,0,springHeight])
                linear_extrude(height=rimHeight, center=false, $fn = resolution, convexity = convexity)
                    offset(r=roundoverRadius) offset(delta=-roundoverRadius)
                        polygon(points = points[2]);
        }
    }
}

clamp();

```


[User]
Okay, I'm looking to design a customizable essential oil diffuser necklace charm and I'd like OpenSCAD code to do this. I want a main body that's essentially a round container with a snap-on lid. The lid should have a decorative cut-out pattern and be interchangeable so that I can use different designs.

Here's what I need:

**Core Structure:**

*   **Main Body:** A round container with a flat bottom and a cavity inside for the oil-soaked felt pad.
    *   I want to be able to specify the inside diameter (`inside_dia`), the height of the inside cavity (`inside_h`), the thickness of the walls and base (`thick`), and the thickness of the top part of the lid (`top_thick`).
    *   I want to make sure that the inside corners of the container get beveled (`bevel_size`).
*   **Lid:** A separate, round, flat piece that snaps onto the main body.
    *   It should have a decorative cut-out pattern in the center.
    *   I want to be able to rotate this cutout (`im_rot`).
    *   It should be sized slightly larger than the inside diameter to make it fit snug, with an additional modifier for resizing the image (`im_oversize`).
    *   I also want to be able to invert the cut-out if I need to (`im_invert`), and trim the top and bottom of the image (`im_trim_bottom_percent`, `im_trim_top_percent`).
*   **Snap Mechanism:** The lid should attach to the main body with a snap-fit mechanism.
    *   I need to be able to specify the width of the clips (`clip_width`). I will also need the ability to adjust the diameter of the snap connection (`snap_adj`), as well as attempt to correct for material shrinkage (`attempt_size_correction`).
*   **Hanging Ring:** The main body will need a small hole and ring for the necklace chain.
    *   I need to be able to specify the diameter of the hanging hole (`hole_dia`) and the diameter of the ring around the hole (`ring_dia`). Also, where the hole is placed in the side (`ring_inset`).

**Customization:**

*   **Cut-out Pattern:** I need a way to either:
    *   Import an image file to use as the cut-out pattern (`im`).
    *   Use a polygon specified by a list of points and paths (`center_shape`)
    *   Import an STL file (`importStl`).

**Parameters:**

I'd like all of these values to be adjustable parameters within the OpenSCAD code. I'd also like to have the ability to select between generating the top, the bottom, the full assembly, or only the center cut out for debugging purposes.

I'm thinking I'll need something that functions like a `loadShape` module that can handle an stl, an image, or a polygon, and a `cutout` module that takes the output of that to create the design cut-out. Additionally, I'll need a `roundContainerShape` module, `roundclip` module, a `top` module, a `bottom` module, and a `lid_center` module. I'm happy with the approach the existing design takes in that it just uses an intersection of extrusions and cubes to create the shapes.

I don't need anything overly fancy here; I'm just looking for a functional and customizable design. I don't have any specific requests about the libraries to be used, so please feel free to use any libraries that will help you accomplish this most effectively. I think I'm ready to have the code generated for me!

[Assistant]
medallion.scad:
```
// Creates an essential oil diffuser necklace charm
//
// Licence is Creative Commons - Attribution - Non-Commercial
// For attribution, simply link to:
//    https://www.thingiverse.com/thing:2357083

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

/* [main] */

// which part to generate
part=3; // [1:top STL,2:bottom STL,3:assembled view,4:just the center design]

// your design for the holes
center_shape=[[[-10,10],[10,10],[10,-10],[-10,-10]],[[0,1,2,3]]];  //[draw_polygon:100x100]

// invert the image (set to true if cut-out area of image is black)
im_invert=0; // [0:false,1:true]

// rotate the image
im_rot=0;

// scale up/down the image slightly
im_oversize=0.25;

/* [upload image] */

// the image to cut out WARNING: this feature can be SLOW!
im=""; // [image_surface:100x100]

// trim the bottom (can cause slowness and certain values may cause customizer to time out)
im_trim_bottom_percent=1; // [0:100]

// trim the top (can cause slowness and certain values may cause customizer to time out)
im_trim_top_percent=-1; // [0:100]

/* [geometry] */

// wall thickness (bottom and sides)
thick=1.5;

// top thickness
top_thick=2;

// how big the inside is
inside_dia=30;

// how high the inside is
inside_h=2.5;

// how wide the fastener clips are
clip_width=7.5;

// don't overdo it or the inside corner could cut through
bevel_size=1;

// spread snaps a little for better fit -- related to material shrinkage (this is a dia)
snap_adj=0.45;

// EXPERIMENTAL! attempt to correct for material changes and get exact size
attempt_size_correction=0; // [0:false,1:true]

/* [holes for hanging] */

// how big the mounting hole is
hole_dia=1.25;

// how big around the mounting ring is
ring_dia=7;

/* [hidden] */

// use this to manually load an stl
importStl=""; // Auryn.stl celtic.stl (can be blank)

// clean up some variable interpretations
ring_gauge=hole_dia;
id=inside_dia;
im_w=99; // does nothing
od=id+thick*2;
im_h=inside_h+thick*2;
ring_inset=thick+ring_gauge/2; // how deep into the body (from center) -- default is just enough so entire ring breaks through to the inside

if(attempt_size_correction==1){
	id=id+0.4; // correction factor
	thick=thick-0.07; // correction factor
	top_thick=top_thick-0.17; //correction factor
}

module roundContainerShape(id,od,h,bevel,thick,top_thick){
	difference(){
		union(){ // outside
			translate([0,0,h-bevel]) cylinder(r2=od/2-bevel,r1=od/2,h=bevel);
			translate([0,0,bevel]) cylinder(r=od/2,h=h-bevel*2);
			cylinder(r1=od/2-bevel,r2=od/2,h=bevel);
		}
		translate([0,0,thick]) cylinder(r=id/2,h=h-thick-top_thick+0.02);
	}
}

module roundclip(dia,thick,h,w,numclips,isPositive){
	margin=0.2; // extra space on the sides
	extra=thick/2; // extra size for cut-out (avoid same value error and allow room for mechanics to work)
	if(isPositive){
		intersection(){
			rotate_extrude() translate([dia/2-thick/2,thick/2]) {
				square([thick/2,h-thick/2]);
				circle(r=thick/2,$fn=20);
			}
			for(a=[0:360/numclips:359.99]){
				rotate([0,0,a]) translate([-(w-margin*2)/2,0,-0.5]) cube([w-margin*2,dia+1,h+1]);
			}
		}
	}else{
		intersection(){
			rotate_extrude() translate([dia/2-thick/2,thick/2]) {
				square([thick/2+extra,h-thick/2]);
				circle(r=thick/2,$fn=20);
				translate([-thick/2,-thick/2-margin]) square([thick+extra,thick/2+margin]);
			}
			for(a=[0:360/numclips:359.99]){
				rotate([0,0,a]) translate([-w/2,0,-0.5]) cube([w,dia+extra+1,h+1]);
			}
		}
	}
}

// usePolygon is a [[points],[paths]]
module loadShape(resultSize,importImage="",importStl="",usePolygon=undef){
	resize(resultSize){
		if(importStl!=""){
			import(importStl);
		}else if(importImage!=""){
			surface(file=importImage,center=true);
		}else if(usePolygon!=undef&&usePolygon[1]!=undef&&usePolygon[1][0]!=undef){
			echo(usePolygon[0][0]);
			linear_extrude(1) polygon(points=usePolygon[0],paths=usePolygon[1]);
		}else{
			echo("No shape!");
			cylinder(r=resultSize[0],h=resultSize[1]);
		}
	}
}

// you can import an stl or image and make sure it comes out a given size
// clipping top and bottom takes some time
module cutout(resultSize,importImage="",importStl="",usePolygon=undef,trim_bottom_percent=-1,trim_top_percent=-1,invert_cyl=false){
	trim_bottom_percent=trim_bottom_percent<0?(importImage!=""?0.1:0.0):trim_bottom_percent;
	trim_top_percent=0.1;//trim_top_percent<0?0.0:trim_top_percent;
	trim_thickness=resultSize[2]-(resultSize[2]*(trim_bottom_percent+trim_top_percent));
	trim_offs=resultSize[2]*trim_bottom_percent;
	resize([resultSize[0],resultSize[1],invert_cyl?resultSize[2]+1:resultSize[2]])difference(){
		// if we want to cut a shape out of a cylinder, then start with a cylinder
		if(invert_cyl){
			resize(resultSize) cylinder(r=max(resultSize[0],resultSize[1]),h=resultSize[2]);
		}
		// the shape
		// determine whether it is faster to just load, intersect, or difference
		if(trim_bottom_percent<=0&&trim_top_percent<=0){ // just load it
			loadShape(resultSize,importImage,importStl,usePolygon);
		}else if(false){ // intersect is the best way
			translate([0,0,(invert_cyl?-0.5:0)-trim_offs]) intersection(){
				loadShape(resultSize,importImage,importStl,usePolygon);
				translate([-(resultSize[0]+1)/2,-(resultSize[1]+1)/2,trim_offs]) cube([resultSize[0]+1,resultSize[1]+1,trim_thickness]);
			}
		}else{ // cheaper to just cut off the top/bottom
			translate([0,0,(invert_cyl?-0.5:0)-trim_offs]) difference(){
				loadShape(resultSize,importImage,importStl,usePolygon);
				if(trim_bottom_percent>0){
					translate([-(resultSize[0]+1)/2,-(resultSize[1]+1)/2,trim_offs-resultSize[2]]) cube([resultSize[0]+1,resultSize[1]+1,resultSize[2]]);
				}
				if(trim_top_percent>0){
					translate([-(resultSize[0]+1)/2,-(resultSize[1]+1)/2,trim_offs+trim_thickness]) cube([resultSize[0]+1,resultSize[1]+1,resultSize[2]]);
				}
			}
		}
		// square with a hole in it
		if(invert_cyl==false){
			difference(){
				translate([-(resultSize[0]+1)/2,-(resultSize[1]+1)/2,-0.5]) cube([resultSize[0]+1,resultSize[1]+1,resultSize[2]+1]);
				translate([0,0,-1]) resize([resultSize[0],resultSize[1],resultSize[2]+2]) cylinder(r=max(resultSize[0],resultSize[1]),h=resultSize[2]);
			}
		}
	}
}

module lid_center(){
	rotate([0,0,im_rot]) cutout([id+im_oversize,id+im_oversize,top_thick],importImage=im,importStl=importStl,usePolygon=center_shape,trim_bottom_percent=im_trim_bottom_percent/100.0,trim_bottom_percent=im_trim_bottom_percent/100.0,invert_cyl=im_invert==1);
}

module top(flip=false){
	// the decorative center
	translate([0,0,flip?0:inside_h+thick]) lid_center();
	translate([0,0,flip?inside_h+thick+top_thick:0]) rotate([flip?180:0,0,0]){
		// the clips
		roundclip(od+snap_adj,thick,inside_h+thick+top_thick-bevel_size,clip_width,2,true,$fn=64);
		// the top
		intersection(){
			roundContainerShape(id,od,inside_h+thick+top_thick,bevel_size,thick,top_thick,$fn=64);
			translate([0,0,inside_h+thick]) difference(){
				cylinder(r=od/2,h=top_thick+1); // cut off top
				translate([0,0,-1]) cylinder(r=id/2,h=top_thick+2,$fn=64); // leave a center hole
			}
		}
	}
}

module bottom(){
	difference(){
	roundContainerShape(id,od,inside_h+thick+top_thick,bevel_size,thick,top_thick,$fn=64);
		translate([0,0,inside_h+thick]) cylinder(r=id,h=top_thick*2); // cut off top
		roundclip(od,thick,inside_h+thick,clip_width,2,false,$fn=64); // clip hole	translate([od/2+ring_dia/2-ring_inset,0,inside_h/2+thick]) rotate_extrude($fn=64) translate([ring_dia/2,0]) circle(r=ring_gauge/2,$fn=20);
		// hole for the ring
		translate([od/2+ring_dia/2-ring_inset,0,inside_h/2+thick]) rotate_extrude($fn=64) translate([ring_dia/2,0]) circle(r=ring_gauge/2,$fn=20);
	}
}

// ---- main

if(part==1){ // top
	top(flip=true);
}else if(part==2){// base
	bottom();
}else if(part==3){// entire assembly
	difference(){
		union(){
			bottom();
			top(flip=false);
		}
		//cube([100,100,100]); // cutaway
	}
} else if(part==4) {
	lid_center();
}
```


[User]
I'm looking to design a mount for an Eachine AIO FPV camera, specifically for an E011 quadcopter, but I want it to be easily adaptable for other similar micro quads. The design I'm envisioning should be parametric so I can adjust it for different camera sizes and mounting configurations. I'd like the mount to have a protective box around the camera, with cutouts for the lens, wires, an LED, and potentially a switch. I also need a way to mount it to the quadcopter frame.

Here are some key features and dimensions I'm considering:

**Camera Box:**

*   The camera itself is roughly 15.5mm wide, 8mm deep, and 12mm tall, but it'd be good to be able to adjust these dimensions parametrically. The box should account for a wall thickness of around 0.8mm.
*   The lens protrudes, so I'll need a cutout for that. It's about 10.2mm wide and offset about 5mm from the side of the camera and 1mm from the bottom of the camera. It also extends about 8mm from the camera.
*   Cutouts for the wires are needed, roughly 3mm wide and 3.5mm tall, offset by -1.5mm on the side and 7mm from the front and 2.5mm from the bottom.
*   There's also an LED cutout, about 8mm wide and 4mm tall, offset by 3.3mm to the right and 5mm from the bottom. A switch cutout might be nice too, maybe 5mm x 3.2mm offset 9.8mm to the right and 9mm from the bottom, but this can be optional for now, it can always be added later.
*   I'd like to have some lens protectors that extend outward a bit to shield the lens during crashes.

**Mounting to Quadcopter:**

*   The quadcopter's mounting holes are about 36mm apart - 18mm from the center on both sides, and I need a third mount in the front at around -14mm from the center with a slightly smaller diameter. The mounting posts are cylinders with a diameter of 2.8mm, except for the front one which is smaller at 2.4mm.
*   There also are supporting beams on the quad, so it needs to fit around those.
*   The base height needs to be low, maybe around 0.6mm thick.
*   I'd like to angle the camera downward slightly, maybe around 20 degrees.
*   I want a mount for the antenna on the left side as well, since I'll be changing the stock antenna out for a simple wire dipole. The mount needs to have a hole for the antenna which has about a 2mm diameter, and be about 5mm high.

I would like all of the dimensions to be parametric so that I can change them as needed. Please generate an OpenSCAD file or files using the BOSL2 library that meets these requirements. It'd be great if the code was well-commented for easy modifications. Thanks!

[Assistant]
e011_AIO_FPV_mount_3_Point_V1.1.scad:
```

// Eachine AIO FPV mount
// E011 quadcopter

camWidth = 15.5; // wall thickness .8mm = 17.1 mm outer box size
camDepth = 8;
camHeight = 12;

camLensWidth = 10.2;
camLensOffsetRight = 5; // distance from right side of camera to right side of lens
camLensOffsetBottom = 1;
lensDepth = 8;

LED_CutoutWidth = 8;
LED_CutoutHeight = 4;
LED_OffsetRight = 3.3;
LED_OffsetBottom = 5;

//switchCutoutWidth = 5;
//switchCutoutHeight = 3.2;
//switchOffsetRight = 9.8;
//switchOffsetBottom = 9;

wireCutoutWidth = 3;
wireCutoutHeight = 3.5;
wireOffsetRight = -1.5;
wireOffsetFront = 7;
wireOffsetBottom = 2.5;

wallThickness = 0.8; // 2x .4mm extrusion width

baseHeight = 0.6;

cameraAngle = -20;

antennaDiameter = 2;

module tx01_mount() {

    difference() {
        union() {
            e010_bottom_plate();
            translate ([-((camLensWidth/2)+camLensOffsetRight),-4.5,-1.5]) rotate ([cameraAngle,0,0]) tx01_box();
        }
        // xtal cutout
        translate ([-5,-1,0]) rotate([90,0,0]) cylinder(r=1.8, h=9, center=true, $fn=64);
        // cap cutout
        //translate ([-5,3,-0.2]) cube([3,7,1]);
        // bottom cutoff
        translate ([-18,-18,-36]) cube(36);
    }
}

module tx01_box() {
    difference() {
        // camera box outer size
        cube([camWidth+(wallThickness*2),camDepth+(wallThickness*2),camHeight+6], center=false);

        // main camera cutout
        translate ([wallThickness,wallThickness,-1]) cube([camWidth,camDepth,32]);

        // lens cutout
        translate ([camLensOffsetRight, -.2,camLensOffsetBottom+5.1]) cube([camLensWidth,3,14]);
        // wire cutout
        translate ([wireOffsetRight,wireOffsetFront,wireOffsetBottom+5]) cube ([3,wireCutoutWidth,wireCutoutHeight]);
        // LED cutout
        translate ([LED_OffsetRight+wallThickness,camDepth,LED_OffsetBottom+6]) cube ([LED_CutoutWidth,LED_CutoutHeight,4]);
        // switch cutout
        //translate ([switchOffsetRight+wallThickness,camDepth,switchOffsetBottom+6]) cube ([switchCutoutWidth,switchCutoutHeight,4]);
    }

    // right lens protector
    difference()
    {
        // main baffle
        translate ([camLensOffsetRight-wallThickness*2,-lensDepth-1,-6]) cube([wallThickness,lensDepth+1,camHeight+12]);
        // bottom chamfer
        translate ([camLensOffsetRight-wallThickness*2-.1,-lensDepth-1,-3]) rotate ([25,0,0]) cube([55,4.8,55]);
        // top chamfer
        translate ([camLensOffsetRight-wallThickness*2-.1,-lensDepth-1,17]) rotate ([45,0,0]) cube([55,4.8,55]);
    }
    // left lens protector
    difference()
    {
        // main baffle
        translate ([camLensOffsetRight+camLensWidth+wallThickness,-lensDepth-1,-6]) cube([wallThickness,lensDepth+1,camHeight+12]);
        // bottom chamfer
        translate ([camLensOffsetRight+camLensWidth+wallThickness-.2,-lensDepth-1,-3]) rotate ([25,0,0])  cube([55,4.6,55]);
        // top chamfer
        translate ([camLensOffsetRight+camLensWidth+wallThickness-.2,-lensDepth-1,17]) rotate ([45,0,0])  cube([55,4.6,55]);
        // hole through baffle
        translate ([camLensOffsetRight+camLensWidth+wallThickness-1,-2,4])rotate ([0,35,0]) cylinder(r=antennaDiameter/1.5,h=8,$fn=32);
    }

    // Antenna Mount
    difference() {
        translate ([camWidth+(wallThickness*3)+.2,-2,10]) cylinder(r=(antennaDiameter/2)+wallThickness,h=5,$fn=32);
        translate ([camWidth+(wallThickness*3)+.2,-2,9]) cylinder(r=(antennaDiameter/2),h=7,$fn=32);
        translate ([camWidth,-4,9]) rotate ([0,45,0]) cube ([8,8,8]);
    }
}

module e010_bottom_plate() {

    // mount left
    difference() {
        translate([-18,4,0]) cylinder(r=2.8,h=baseHeight,$fn=64);
        translate([-18,4,-0.5]) cylinder(r=0.8, h=2,$fn=32);
    }
    //mount right
    difference() {
        translate([18,4,0]) cylinder(r=2.8,h=baseHeight,$fn=64);
        translate([18,4,-0.5]) cylinder(r=0.8, h=2,$fn=32);
    }
    //mount front
    difference() {
        translate([0,-14,0]) cylinder(r=2.4,h=baseHeight,$fn=64);
        translate([0,-14,-0.5]) cylinder(r=0.8, h=2,$fn=32);
    }
    // beam front
    translate ([-11.9,-4.1,0]) cube ([23.9,2,baseHeight]);
    // right beam
    translate ([-17.5,1.5,0]) rotate ([0,0,-45]) cube ([22,2.8,baseHeight]);
    // left beam
    translate ([16.7,4,0]) rotate ([0,0,225]) cube ([23.5,2.8,baseHeight]);

    /*
    // back rounded corner right
    difference() {
        translate ([-5.5,13,0]) cylinder(r=2,h=baseHeight,$fn=64);
        translate ([-4,9.5,-0.5]) cylinder(r=3.55,h=2,$fn=64);
    }
    // back rounded corner left
    difference () {
        translate ([5.5,13,0]) cylinder(r=2,h=baseHeight,$fn=64);
        translate ([4,9.5,-0.5]) cylinder(r=3.55,h=2,$fn=64);
    }
    translate ([-5.5,13,0]) cube ([11,2,baseHeight]);
    // back angle beam Right
    translate([-17,1.0,0]) rotate ([0,0,46.15]) cube([17, 2,baseHeight]);
    // back gusset right
    translate([-16.3,4.2,0]) rotate ([0,0,24]) cube([3,1.5,baseHeight]);

    // back angle beam Left
    translate([5.5,13.0,0]) rotate ([0,0,-46]) cube([16.7, 2,baseHeight]);
    // back gusset left
    translate([17,5.4,0]) rotate ([0,0,156]) cube([3,1.5,baseHeight]);

    */
    // middle beam
    translate([-17,3.8,0]) cube([34,2.8,baseHeight]);
}

tx01_mount();
```


[User] I'm trying to make a highly customizable sphere, but I'm having trouble getting it to do what I want. I need to be able to stretch it in the X, Y, and Z dimensions, control the radius, have a different radius for the bottom (like an egg shape), and control the number of sides that make up the sphere. I've attached my attempt, but it doesn't quite get the egg-shape right, and I'd really like to have the bottom radius be more independently controllable. Could you help me create this using OpenSCAD? Also, I'd like to use the BOSL2 library for any functions that might be helpful.

[Assistant]
Customizible_Sphere.scad:
```
x_step = 1;//[-500:500]

y_step = 1;//[-500:500]

z_step = 1;//[-500:500]

Radius = 5;//[1:500]

Radius_of_Botom = 5;//[1:500]

Sides = 30;//[3:500]

scale(x_step, y_step, z_step) sphere(Radius, $fn = Sides);
```


[User]
I'm looking to create a customizable coin weight box, similar to the one described in the provided text, but I'd like it to be more modular and have a few additional features.

Essentially, I need a parametric OpenSCAD design that can create a box with multiple cylindrical chambers for stacking coins, along with a lid. The original description provides a good starting point, but I want to be able to control the following:

**Core Parameters:**

*   **`coin_diameter`**:  The diameter of the coins being used (e.g. 18.75mm for 2 Euro cents).
*   **`chamber_height`**: The height of each cylindrical chamber, which would allow for a stack of coins.
*   **`number_of_rows`**:  The number of rows of coin chambers.
*   **`number_of_columns`**: The number of columns of coin chambers.

**Box Customization:**

*   **`box_shell_thickness`**: The thickness of the box walls and base, and the lid height.
*   **`coin_diameter_clearance`**:  An extra amount added to the coin diameter to prevent coins from sticking.
*   **`open_slot`**: A boolean to specify whether to include an opening slot to remove the lid with a flathead screwdriver, and if so, to provide the size parameters for the slot.
*   **`lid_fit_tolerance`**: Specify how tight the lid should fit into the box.
*   **`base_chamfer`:** Add a chamfer to the bottom edge of the box so it sits flat on a surface without a hard corner.

**Lid Customization:**

*   **`lid_type`**: Options for different lid styles (e.g., a simple flat lid, or a lid with a lip that fits into the box, or one with a slot). The original lid style with a lip is acceptable to begin with, but being able to control the fit is a priority.

**Additional Feature Requests:**

*   **Individual Chamber Control**: Instead of a grid pattern, would it be possible to specify which individual chamber locations should be included? This is less important than the features above, but would be great to have.
*   **Support for Custom Shapes**: Could this be designed such that it would be easy to adjust the chamber shape to be other polygons, rather than a cylinder? The base design with cylinder chambers is acceptable to begin with, but I want the option of having shapes besides a circle.

**Output and Modularity:**
*   I'd like the output to be configurable such that I can export the box and the lid separately, or both as one output.
*   The design should be modular enough so that it's straightforward to expand it later with more options, ideally using custom modules.

I would be very grateful if you could generate the necessary OpenSCAD file(s) for this. I don't need any additional libraries beyond standard OpenSCAD features, but do feel free to use built-in modules that make sense for this. Thanks!

[Assistant]
customizable_coin_weight.scad:
```
 /*
 * Customizable Coin Weights - https://www.thingiverse.com/thing:2358931
 * by Dennis Hofmann - https://www.thingiverse.com/mightynozzle/about
 * created 2017-06-02
 * updated 2017-07-19
 *
 * Changelog
 * v1.1 - 2017-07-19:
 *     - added an option to choose which parts needed (box, lid or both)
 *     - added opening slot to remove the lid with a flathead screwdriver
 *
 * This work is licensed under the Creative Commons - Attribution - Non-Commercial license.
 * https://creativecommons.org/licenses/by-nc/3.0/
 */

 // Parameter Section //
//-------------------//

/* [General Settings] */

// Choose, which part you want to see!
part = "all_parts__"; //[all_parts__:All Parts,box__:Box,lid__:Lid]

// Here you can set the coin diameter. A list of common coins (Europe first, sorry :-P): 1 Euro Cent: 16.25, 2 Cent: 18.75, 5 Cent: 21.25mm, 10 Cent: 19.75, 20 Cent: 22.25, 50 Cent: 24.25, 1 Euro : 23.25, 2 Euro: 25.75. 1 US Cent: 19.05, 5 US Cent: 21.209, 10 US Cent: 17.907, 25 US Cent: 24.257, 50 US Cent: 30.607, 1 US Dollar: 38.1/26.5 or 26.492. Default: 1 Euro Cent with 16.25mm
coin_diameter_in_millimeter = 18.75;

// Set the height of the coin chamber(s). This is the height without the bottom shell and without the lid height. E.g. 1/2/5 Euro Cent have a height of 1.67mm each. 20 Coins have a height of 33.4mm. 20x 5 Cent have a weight of 78.4g!
chamber_height_in_millimeter = 26.0;

// The number of coin chamber rows. The higher the number, the longer it takes to create the .stl-File. A healthy number will be between 1 and 4.
number_of_coin_chamber_rows = 2; //[1:15]

// The number of coin chamber columns. The higher the number, the longer it takes to create the .stl-File. A healthy number will be between 1 and 4.
number_of_coin_chamber_columns = 2; //[1:15]

// A single slot on top of the box to remove the lid.
open_slot = "yes"; //[yes,no]

/* [Optional Settings] */
// Set an extra of Diameter of the coin chamber. Otherwise the coins may stuck. Default: 1.0
coin_diameter_extra_in_millimeter = 1.0;

// The shell thickness of the bottom, between coin chamber and box, and the lid height. Default: 2.0
shell_thickness_in_millimeter = 2.0;

/* [Hidden] */
shell_th = shell_thickness_in_millimeter;
coin_d = coin_diameter_in_millimeter;
coin_d_extra = coin_diameter_extra_in_millimeter;
chamber_h = chamber_height_in_millimeter;
chamber_rows = number_of_coin_chamber_rows;
chamber_columns = number_of_coin_chamber_columns;

cube_x = coin_d * number_of_coin_chamber_rows + coin_d_extra + shell_th * 2;
cube_y = coin_d * number_of_coin_chamber_columns + coin_d_extra + shell_th * 2;
cube_z = chamber_h + shell_th * 2 + 2.0;
cylinder_d = coin_d + coin_d_extra;
cylinder_h = chamber_h + shell_th * 2;
coin_hole_0_pos = -(coin_d + coin_d_extra) / 2 - shell_th;

 // Program Section //
//-----------------//

if(part == "box__") {
    box_part();
} else if(part == "lid__") {
    lid_part();
} else if(part == "all_parts__") {
    all_parts();
} else {
    all_parts();
}

 // Module Section //
//----------------//

module all_parts() {
    box_part();
    if(chamber_rows < chamber_columns) {
        translate([cube_x + 5.0, 0, 0]) {
            lid_part();
        }
    } else {
        translate([0, cube_y + 5.0, 0]) {
            lid_part();
        }
    }
}

module box_part() {
    color("DeepSkyBlue") box();
}

module lid_part() {
    color("HotPink") lid();

}

module box() {
    difference() {
        translate([coin_hole_0_pos, coin_hole_0_pos, 0]) {
            cube([cube_x, cube_y, cube_z]);
        }
        if(open_slot == "yes") {
            translate([-2, -cylinder_d / 2 - shell_th - 0.5, cylinder_h]) {
                cube([4.0, shell_th + 1, 2.1]);
            }
        }
        translate([0, 0, shell_th]) {
            for(row = [0 : chamber_rows - 1]) {
                for(column = [0 : chamber_columns - 1]) {
                    translate([row * coin_d, column * coin_d, 0]) {
                        cylinder(d = cylinder_d, h = cylinder_h + 0.1, $fn = 48);
                    }
                }
            }
        }
    }
}

module lid(){
    difference() {
        for(row = [0 : chamber_rows - 1]) {
            for(column = [0 : chamber_columns - 1]) {
                translate([row * coin_d, column * coin_d, 0]) {
                    cylinder(d = cylinder_d + shell_th * 2, h = shell_th, $fn = 50);
                    translate([0, 0, shell_th]) {
                        difference() {
                            cylinder(d = cylinder_d, h = 2, $fn = 48);
                            cylinder(d = cylinder_d - shell_th * 2, h = 2.1, $fn = 16);
                        }
                    }
                }
            }
        }
        translate([0, 0, shell_th]) {
            cube([(chamber_rows - 1) * coin_d, (chamber_columns - 1) * coin_d, 3]);
        }
    }
}

```


[User]
I'm looking to design a customizable fidget spinner, and I'd like the design to be parametric in OpenSCAD. I want it to be able to handle standard 608 bearings in the center, and then have three weights around the perimeter. The design should allow the user to specify either cylindrical or spherical weights. I'd also like to be able to control the wall thickness between the central bearing and the perimeter weights.

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

*   `bearing_od`: The outer diameter of the central bearing. Standard 608 bearings are 22mm.
*   `bearing_height`: The height of the central bearing.
*  `weight_od`: The outer diameter of the weights.
*   `weight_type`: A variable to select either "cylinder" or "sphere" as the weight shape.
*   `wall_thickness`: The thickness of the wall between the center bearing and the weights.
*  I also need to have some quality and speed parameters: `$fa`, `$fs`.

For the spherical weights, it needs to be designed such that it can be easily printed in two halves and then glued together after the sphere weight is inserted. I'm imagining the easiest way to do this would be to make the split in the Z-plane, with some overlap to ensure a good bond. It'd be great if there was a comment that specifies to translate the model down by half the print height before printing to print only half. The user should also be able to add a small amount of clearance (e.g. 0.3mm) to the diameter of sphere weights in order to make this process easier.

I'm also hoping that you can provide the code for a function that solves the Apollonius problem of finding a circle tangent to three other circles. I'd like this included as `tangent_circle(x1, y1, r1, x2, y2, r2, x3, y3, r3)`, which will return the vector `[x, y, r]` of the solution.

I want the spinner to use this function to create the rounded edges between the lobes, similar to the design in the attached file.

I'd like to avoid using the `minkowski()` method to create the rounded edges, if possible, as I've found it can be quite slow. I prefer the method using `rotate_extrude()` with `hull()`.

I think that covers everything. Let me know if you have any questions.

[Assistant]
fidget.scad:
```
// Center bearing outer diameter. 608 bearing is 22 mm.
bearing_od = 22;

// Height of center bearing.
bearing_height = 7;

// Weight outer diameter.
weight_od = 22;

// Shape used as weight. "cylinder" for bearings, "sphere" for balls.
weight_type = "cylinder"; // [cylinder, sphere]

// Thickness of wall between bearing and weights.
wall_thickness = 5;

/* [Hidden] */
$fa = 5;
$fs = 0.5;

bearing_to_weight_centers_dist = (bearing_od + weight_od)/2 + wall_thickness;
lobe_r = weight_od/2 + wall_thickness;

// Calculate the tangent circle between the top, right, and center circles.
tc = tangent_circle(0, 0, bearing_od/2 + wall_thickness,
		    0, bearing_to_weight_centers_dist, lobe_r,
		    cos(30)*bearing_to_weight_centers_dist, -sin(30)*bearing_to_weight_centers_dist, lobe_r);

spinner();

module spinner() {
     // Take the solid spinner body, cut the bearing and weight holes.
     difference() {
	  spinner_solid();
	  cylinder(d=bearing_od, h=100, center=true);
	  weights();
     }
}

module torus(R, r) {
     rotate_extrude() translate([R, 0]) circle(r);
}

module filled_torus(R, r) {
     hull() torus(R, r);
}

// Credit to "Ronaldo" from the OpenSCAD mailing list for this faster method
// based on rotate_extrude() for generating the solid model with rounded edges.
module spinner_solid() {
     r  = bearing_height/2;
     x1 = bearing_to_weight_centers_dist;
     R  = lobe_r;
     x2 = norm([tc[0], tc[1]]);

     // the three lobes
     rotate( 90) translate([x1, 0, 0]) filled_torus(R - r, r);
     rotate(-30) translate([x1, 0, 0]) filled_torus(R - r, r);
     rotate(210) translate([x1, 0, 0]) filled_torus(R - r, r);

     // the concave section
     intersection() {
	  translate([0, 0, -r])
	       linear_extrude(height=2*r) polygon(filler_poly_pts);
	  union() {
	       rotate( 30) translate([x2, 0, 0]) torus(tc[2] + r, r);
	       rotate(-90) translate([x2, 0, 0]) torus(tc[2] + r, r);
	       rotate(150) translate([x2, 0, 0]) torus(tc[2] + r, r);
	  }
     }

     // the center filling

     translate([0, 0, -r]) {
	  linear_extrude(height=2*r) {
	       difference() {
		    polygon(filler_poly_pts);
		    offset(delta=r) cutter();
		    rotate(120) offset(delta=r) cutter();
		    rotate(240) offset(delta=r) cutter();
	       }
	  }
     }
}

// Old, slow method for creating rounded spinner profile.
module spinner_solid_minkowski() {
     // Take the spinner profile, generate a 3D solid body with rounded edges.
     minkowski() {
	  linear_extrude(height = 0.1) offset(r=-bearing_height/2) profile();
	  sphere(d=bearing_height);
     }
}

module weight() {
     if (weight_type == "cylinder") {
	  translate([0, bearing_to_weight_centers_dist]) cylinder(d=weight_od, h=100, center=true);
     } else {
	  translate([0, bearing_to_weight_centers_dist]) sphere(d=weight_od);
     }
}

module weights() {
     weight();
     rotate([0, 0, 120]) weight();
     rotate([0, 0, 240]) weight();
}

// Creates a 2D circle at a position. Takes a vector, [x, y, r]
module circ(v) {
     translate([v[0], v[1]]) circle(v[2]);
}

module lobe() {
     circ([0, bearing_to_weight_centers_dist, lobe_r]);
}

module lobes() {
     lobe();
     rotate([0, 0, 120]) lobe();
     rotate([0, 0, 240]) lobe();
}

// Points for polygon to fill up the center. Six sided polygon from
// weight center to tangent circle centers; cuts exactly through the
// tangent points.
filler_poly_pts = [[0, bearing_to_weight_centers_dist],
		   [tc[0], tc[1]],
		   [cos(30)*bearing_to_weight_centers_dist, -sin(30)*bearing_to_weight_centers_dist],
		   [0, -norm([tc[0], tc[1]])],
		   [-cos(30)*bearing_to_weight_centers_dist, -sin(30)*bearing_to_weight_centers_dist],
		   [-tc[0], tc[1]]];

module body() {
     polygon(filler_poly_pts);
     lobes();
}

module cutter() {
     circ(tc);
}

// Solves the Apollonius problem of a circle externally tangent to three other circles.
// Returns a vector, [x, y, r] of the solution circle.
function tangent_circle(x1, y1, r1, x2, y2, r2, x3, y3, r3) =
     let (a2 = 2*(x1 - x2),
	  a3 = 2*(x1 - x3),
	  b2 = 2*(y1 - y2),
	  b3 = 2*(y1 - y3),
	  c2 = 2*(r1 - r2),
	  c3 = 2*(r1 - r3),
	  d2 = (x1*x1 + y1*y1 - r1*r1) - (x2*x2 + y2*y2 - r2*r2),
	  d3 = (x1*x1 + y1*y1 - r1*r1) - (x3*x3 + y3*y3 - r3*r3),
	  N = (b2*d3 - b3*d2) / (a3*b2 - a2*b3),
	  M = (b3*c2 - b2*c3) / (a3*b2 - a2*b3),
	  P = (a2*d3 - a3*d2) / (a2*b3 - a3*b2),
	  Q = (a3*c2 - a2*c3) / (a2*b3 - a3*b2),
	  S = N - x1,
	  T = P - y1,
	  a = M*M + Q*Q - 1,
	  b = 2*(S*M + T*Q - r1),
	  c = S*S + T*T - r1*r1,
	  r = max((-b + sqrt(b*b - 4*a*c)) / (2*a), (-b - sqrt(b*b - 4*a*c)) / (2*a)),
	  x = N + M*r,
	  y = P + Q*r)
     [x, y, r];

// 2D shape of the spinner outline.
module profile() {
     difference() {
	  body();
	  cutter();
	  rotate([0, 0, 120]) cutter();
	  rotate([0, 0, 240]) cutter();
     }
}

```


[User]
I'd like to design a fidget spinner that can accommodate different weights. It should have a central bearing and three arms, each holding a weight. I want it to be parametric so I can easily change the weight type and other dimensions.

Specifically, I need the design to work with several different types of weights: 608 bearings, 5/16", 3/8", and 1/2" hex nuts (SAE standard); M8, M10 (both 16mm and 17mm flat-to-flat), and M12 (both 18mm and 19mm flat-to-flat) hex nuts (ISO and DIN standards); M4 square nuts, and then also weights using multiple US coins: 4 pennies, 3 nickels, 5 dimes, or 4 quarters. I'd also like to be able to use 5/16", 3/8", 1/2", and 5/8" ball bearings.

I'd like to have a parameter to expand the diameter of the arms, probably in 0.1% increments, so that the fit of the nuts can be adjusted. I want to control the roundness of the edges of the spinner, and also control the number of arms. I want the number of arms to default to 3, but I want the option to choose 2, 3, 4, 5 or 6. The default roundness should be 2mm.

The design should consider the dimensions of all these weight types for proper fit. The nuts should probably protrude from the arms but that's okay. The design should work with a standard 608 bearing.

For the hex nuts I'd like for the model to use 6 segments per round, for square nuts I'd like 4. I would like to keep the segments of the rest of the model as a default of $fn=60.

Can you generate an OpenSCAD file for this? I don't expect it to be using any particular external library.

[Assistant]
trirazor-2017-10-04.scad:
```
// lucina 2017-10-04
// preview[view:south, tilt:top]

// the type of weight
whichWeight = "3/8 hex nut";   //[608 bearing, 5/16 hex nut, 3/8 hex nut, 1/2 hex nut, M8 hex nut, M10 16 hex nut, M10 17 hex nut, M12 18 hex nut, M12 19 hex nut, M4 square nut, 4 pennies, 3 nickels, 5 dimes, 4 quarters, 5/16 ball bearing, 3/8 ball bearing, 1/2 ball bearing, 5/8 ball bearing]
// expand diameter 1=.1%
expand = 0; // [0:1:25]

/* [Hidden] */
$fn = 60;
// the number of blades
blades = 3; //[2, 3, 4, 5, 6]
// mm edge roundness
radius = 2;  //[0:.5:3]

x = 0; y = 1; z = 2;
inch = 25.4;

bearing = [22, 22, 7]; //608 bearing
sae = [.577*inch, .5 * inch, 7]; // 5/16 hex nut
sae38 = [.650*inch,.562*inch, 7];	// 3/8
sae12 = [.866*inch, .75*inch, 7];	// 1/2
metric8 = [15, 13, 6.5];   // M8 hex nut
metric10_16 = [18.48, 16, 8.4];   // M10 16 hex nut
metric10_17 = [19.63, 17, 8];   // M10 17 hex nut
metric12_18 = [20.78, 18, 10.8];   // M12 18 hex nut
metric12_19 = [21.94, 19, 10];   // M12 19 hex nut
metric4sq = [7, 7, 3.2];
penny = [19.05, 19.05, 4*1.55];  //x4
nickel = [21.21, 21.21, 3*1.95]; //x3
dime = [17.91, 17.91, 5*1.35]; //x5
quarter = [24.26, 24.26, 4*1.75];  // x4
ball516 = [5/16*inch, 5/16*inch, 5/16*inch];
ball38 = [3/8*inch, 3/8*inch, 3/8*inch];
ball12 = [1/2*inch, 1/2*inch, 1/2*inch];
ball58 = [5/8*inch, 5/8*inch, 5/8*inch];

weight = (whichWeight == "608 bearing") ? bearing :
   (whichWeight == "5/16 hex nut") ? sae :
   (whichWeight == "3/8 hex nut") ? sae38 :
   (whichWeight == "1/2 hex nut") ? sae12 :
   (whichWeight == "M8 hex nut") ? metric8 :
   (whichWeight == "M10 16 hex nut") ? metric10_16 :
   (whichWeight == "M10 17 hex nut") ? metric10_17 :
   (whichWeight == "M12 18 hex nut") ? metric12_18 :
   (whichWeight == "M12 19 hex nut") ? metric12_19 :
   (whichWeight == "M4 square nut") ? metric4sq :
   (whichWeight == "4 pennies") ? penny :
   (whichWeight == "3 nickels") ? nickel :
   (whichWeight == "5 dimes") ? dime :
   (whichWeight == "4 quarters") ? quarter :
   (whichWeight == "5/16 ball bearing") ? ball516 :
   (whichWeight == "3/8 ball bearing") ? ball38 :
   (whichWeight == "1/2 ball bearing") ? ball12 : ball58;
echo(whichWeight, weight[x], weight[y], weight[z]);

resolution = (whichWeight == "5/16 hex nut") ? 6 :
   (whichWeight == "3/8 hex nut") ? 6 :
   (whichWeight == "1/2 hex nut") ? 6 :
   (whichWeight == "M8 hex nut") ? 6 :
   (whichWeight == "M10 16 hex nut") ? 6 :
   (whichWeight == "M10 17 hex nut") ? 6 :
   (whichWeight == "M12 18 hex nut") ? 6 :
   (whichWeight == "M12 19 hex nut") ? 6 :
   (whichWeight == "M4 square nut") ? 4 : 60;
echo("resolution=", resolution);

thick = 3;
//space = max(12, 1.4*weight[x]);
space =1.4*weight[x];
factor = 1.62 * sae38[x]/weight[x] + expand/10;
distance = -.15*weight[x]/sae38[x];

base = [factor*weight[x], factor*weight[x], bearing[z]*2];
outer = [blades/2*base[x]+space, blades/2*base[x]+space, bearing[z]];
step = 360 / blades;
stop = (blades-1) * step;
module tri(small, big) {
   difference() {
      hull() {
         cylinder(h=.25, d=big[x]+.125*big[x], center=true);
         cylinder(h=big[z], d=big[x], center=true);
      }
      union() {
         for (angle=[0:step:stop])
            rotate([0, 0, angle])
               translate([0, small[y]/2+space/2, 0])
                  cylinder(h=small[z], d=small[x], center=true);
      }
   }
}
module fcylinder(z, x, rad) {
   minkowski() {
      cylinder(h=z-2*rad, d=x-2*rad, center=true);
         sphere(rad, center=true);
   }
}
difference() {
union() {
   tri(base, outer);
   fcylinder(bearing[z], bearing[x]+2*thick, radius);
}
union() {
cylinder(h=bearing[x]*2, d=bearing[x], center=true);
   for(angle=[0:step:stop])
      rotate([0, 0, angle+step/2])
         translate([0, base[y]/2+space/2+distance*weight[x], 0])
            rotate([0, 0, 30])
               cylinder(h=bearing[z]*2, d=weight[x], center=true,
          $fn=resolution);
}
}
```


[User]
Hey, I'm looking to create a customizable monogrammed coaster using OpenSCAD, and I'd like your help designing the code. I want to be able to adjust the size, position, and font of up to three initials, and also control things like the coaster's thickness, the letter thickness, an optional outer ring, and a decorative pattern.

Specifically, I envision a coaster with the following adjustable parameters:

*   **Extrusion Distance (thick):** This will be the overall thickness of the coaster.
*   **Letter Thickness (letter_thick):** This should control the thickness of the letters, likely achieved by repeating the letter at slightly different scales.
*  **Font Name (font_name):** I'd like to be able to specify the font used for the letters, taking advantage of fonts like the ones available on Google Fonts.
*   **Outer Ring Width (outer_ring_width):** This should add an optional outer ring around the coaster. A value of 0 should mean no ring is present.
*   **Include Pattern (include_pattern):** I'd like to be able to include or exclude a decorative pattern on the coaster. It'll probably consist of a bunch of lines, maybe some horizontal, vertical, and angled.
*   **Left Initial:**
    *   **Letter (left_letter):** The actual letter to be displayed.
    *   **Size (left_size):** The size of the letter.
    *   **Horizontal Shift (left_shift_h):** Adjust the horizontal position of the letter.
    *   **Vertical Shift (left_shift_v):** Adjust the vertical position of the letter.
*   **Center Initial:**
    *   **Letter (center_letter):** The actual letter to be displayed.
    *   **Size (center_size):** The size of the letter.
    *   **Horizontal Shift (center_shift_h):** Adjust the horizontal position of the letter.
    *   **Vertical Shift (center_shift_v):** Adjust the vertical position of the letter.
*   **Right Initial:**
    *   **Letter (right_letter):** The actual letter to be displayed.
    *   **Size (right_size):** The size of the letter.
    *   **Horizontal Shift (right_shift_h):** Adjust the horizontal position of the letter.
    *   **Vertical Shift (right_shift_v):** Adjust the vertical position of the letter.
*   **Smoothness (smoothness):** This would control the `$fn` variable for the circles and other curved shapes, allowing me to adjust the overall smoothness.

The coaster should be circular, and the design should be parametric enough that I can tweak the letter positions to have them intersect and remain connected. I'd also like the code to be easily adaptable for laser cutting by commenting out the extrusion and generating a 2D shape.

Do you think you could generate the necessary OpenSCAD code for me, without relying on any external libraries?

[Assistant]
Monogram_Coaster.scad:
```
// Customizable Monogrammed Coaster
// Will Schleter (wschleter@gmail.com)
// June 2, 2017

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

/* [General Settings] */
// Extrusion distance
thick=10;

// Letter thickness
letter_thick=1.05; // [1:0.02:1.2]

// name:style (see http://google.com/fonts)
font_name="Alex Brush";

// (use 0 for no outer ring)
outer_ring_width = 10;

// Include top/bottom pattern
include_pattern = "yes"; // [yes,no]

// Smoothness factor
smoothness=90;

/* [Left Initial] */
left_letter="A";
left_size=85;
left_shift_h=0; // [-50:50]
left_shift_v=0; // [-50:50]

/* [Center Initial] */
center_letter="E";
center_size=150;
center_shift_h=0; // [-50:50]
center_shift_v=0; // [-50:50]

/* [Right Initial] */
right_letter="L";
right_size=85;
right_shift_h=0; // [-50:50]
right_shift_v=0; // [-50:50]

coaster();

// To create a 2D dxf file for laser cutter
// 1. Comment out the coster() line above
// 2. Uncomment the projection line below
// 3. Press F6 to rebuild the object
// 4. Use File/Export menu to create a dxf file

//projection(cut=false) coaster();

module coaster() {

$fn=smoothness;

// this scale takes the arbitray sizes used in the geometry to an outer radius of 3.5 inches
scale(0.232) linear_extrude(thick) {

// outer circle
difference() {
  circle(190);
  circle(190-outer_ring_width);
}

// letters
// this loop is a kludge to make thicker letters
// each letter is repeated several times at a slightly larger scale
for (i=[1:0.02:letter_thick]) {
    translate([-120+left_shift_h,left_shift_v,0])
        text(left_letter,left_size*i,font_name,halign="center",valign="center");
    translate([center_shift_h,center_shift_v,0])
        text(center_letter,center_size*i,font_name,halign="center",valign="center");
    translate([120+right_shift_h,right_shift_v,0])
        text(right_letter,right_size*i,font_name,halign="center",valign="center");
}

if (include_pattern=="yes") {
// horizontal lines
translate([0,90,0]) square([315,7],center=true);
translate([0,-90,0]) square([315,7],center=true);

// angled lines at 20 deg
translate([60,-112,0]) rotate([0,0,160]) square([132,7],center=true);
translate([-60,-112,0]) rotate([0,0,20]) square([132,7],center=true);
translate([60,112,0]) rotate([0,0,20]) square([132,7],center=true);
translate([-60,112,0]) rotate([0,0,160]) square([132,7],center=true);

// angled lines at 50 deg
translate([32,-130,0]) rotate([0,0,130]) square([105,7],center=true);
translate([32,130,0]) rotate([0,0,50]) square([105,7],center=true);
translate([-32,130,0]) rotate([0,0,130]) square([105,7],center=true);
translate([-32,-130,0]) rotate([0,0,50]) square([105,7],center=true);

// vertical lines
translate([0,140,0])  square([7,95],center=true);
translate([0,-140,0])  square([7,95],center=true);
} // end of if pattern

} // end of linear_extrude

} // end of module
```