fix: prevent dividing by 0 when count is 1

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2017-2022 Jean-Sebastien CONAN
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Part of the camelSCAD library.
 *
 * Operators that repeat children modules with respect to particular rules.
 *
 * @package operator/repeat
 * @author jsconan
 */

/**
 * Repeats and rotates the children modules `count` times with the provided `interval`.
 *
 * @param Number [count] - The number of times the children must be repeated.
 * @param Number [angle] - The angle interval in which rotate the children.
 * @param Vector [axis] - The rotation axis around which rotate the children.
 * @param Vector [interval] - The interval between each repeated children.
 * @param Vector [origin] - The rotate origin.
 * @param Number [intervalX] - The X interval between each repeated children
 *                             (will overwrite the X coordinate in the `interval` vector).
 * @param Number [intervalY] - The Y interval between each repeated children
 *                             (will overwrite the Y coordinate in the `interval` vector).
 * @param Number [intervalZ] - The Z interval between each repeated children
 *                             (will overwrite the Z coordinate in the `interval` vector).
 * @param Number [axisX] - The X axis factor (will overwrite the X coordinate in the `axis` vector).
 * @param Number [axisY] - The Y axis factor (will overwrite the Y coordinate in the `axis` vector).
 * @param Number [axisZ] - The Z axis factor (will overwrite the Z coordinate in the `axis` vector).
 * @param Number [originX] - The X origin (will overwrite the X coordinate in the `origin` vector).
 * @param Number [originY] - The Y origin (will overwrite the Y coordinate in the `origin` vector).
 * @param Number [originZ] - The Z origin (will overwrite the Z coordinate in the `origin` vector).
 */
module repeatRotate(count    = 2,
                    angle    = DEGREES,
                    axis     = zAxis3D(),
                    interval = 0,
                    origin   = 0,
                    center   = false,
                    intervalX, intervalY, intervalZ,
                    axisX, axisY, axisZ,
                    originX, originY, originZ) {

    interval = apply3D(interval, intervalX, intervalY, intervalZ);
    origin = apply3D(origin, originX, originY, originZ);
    angle = deg(angle);
    partAngle = angle / divisor(angle % DEGREES ? count - 1 : count);
    axis = apply3D(axis, axisX, axisY, axisZ) * partAngle;
    offset = center ? -interval * (count - 1) / 2 : ORIGIN_3D;

    for (i = [0 : count - 1]) {
        translate(offset + interval * i) {
            rotateOrigin(a=axis * i, o=origin) {
                children();
            }
        }
    }
}

/**
 * Repeats and rotates the children modules in two directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Number [angleX] - The angle interval in which rotate the children along the X axis.
 * @param Number [angleY] - The angle interval in which rotate the children along the Y axis.
 * @param Vector [axisX] - The rotation axis around which rotate the children along the X axis.
 * @param Vector [axisY] - The rotation axis around which rotate the children along the Y axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Vector [originX] - The rotate origin along the X axis.
 * @param Vector [originY] - The rotate origin along the Y axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeatRotate2D(countX    = 2,
                      countY    = 2,
                      angleX    = DEGREES,
                      angleY    = DEGREES,
                      axisX     = zAxis3D(),
                      axisY     = yAxis3D(),
                      intervalX = 0,
                      intervalY = 0,
                      originX   = 0,
                      originY   = 0,
                      center    = false) {

    repeatRotate(count=countY, interval=vector3D(intervalY), angle=angleY, axis=vector3D(axisY), origin=originY, center=center) {
        repeatRotate(count=countX, interval=vector3D(intervalX), angle=angleX, axis=vector3D(axisX), origin=originX, center=center) {
            children();
        }
    }
}

/**
 * Repeats and rotates the children modules in three directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Number [countZ] - The number of times the children must be repeated along the Z axis.
 * @param Number [angleX] - The angle interval in which rotate the children along the X axis.
 * @param Number [angleY] - The angle interval in which rotate the children along the Y axis.
 * @param Number [angleZ] - The angle interval in which rotate the children along the Z axis.
 * @param Vector [axisX] - The rotation axis around which rotate the children along the X axis.
 * @param Vector [axisY] - The rotation axis around which rotate the children along the Y axis.
 * @param Vector [axisZ] - The rotation axis around which rotate the children along the Z axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Vector [intervalZ] - The interval between each repeated children along the Z axis.
 * @param Vector [originX] - The rotate origin along the X axis.
 * @param Vector [originY] - The rotate origin along the Y axis.
 * @param Vector [originZ] - The rotate origin along the Z axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeatRotate3D(countX    = 2,
                      countY    = 2,
                      countZ    = 2,
                      angleX    = DEGREES,
                      angleY    = DEGREES,
                      angleZ    = DEGREES,
                      axisX     = zAxis3D(),
                      axisY     = yAxis3D(),
                      axisZ     = xAxis3D(),
                      intervalX = 0,
                      intervalY = 0,
                      intervalZ = 0,
                      originX   = 0,
                      originY   = 0,
                      originZ   = 0,
                      center    = false) {

    repeatRotate(count=countZ, interval=vector3D(intervalZ), angle=angleZ, axis=vector3D(axisZ), origin=originZ, center=center) {
        repeatRotate(count=countY, interval=vector3D(intervalY), angle=angleY, axis=vector3D(axisY), origin=originY, center=center) {
            repeatRotate(count=countX, interval=vector3D(intervalX), angle=angleX, axis=vector3D(axisX), origin=originX, center=center) {
                children();
            }
        }
    }
}

/**
 * Repeats the children modules on every angle given in the `map`.
 *
 * @param Vector[] map - The list of angles at which place the children.
 * @param Vector [offset] - An offset to add before the rotation is applied.
 * @param Number [x] - The X-coordinate to apply on the offset.
 * @param Number [y] - The Y-coordinate to apply on the offset.
 * @param Number [z] - The Z-coordinate to apply on the offset.
 */
module repeatRotateMap(map, offset, x, y, z) {
    offset = apply3D(offset, x, y, z);

    for (at = map) {
        rotate(vector3D(at)) {
            translate(offset) {
                children();
            }
        }
    }
}

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2017-2022 Jean-Sebastien CONAN
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Part of the camelSCAD library.
 *
 * Operators that repeat children modules with respect to particular rules.
 *
 * @package operator/repeat
 * @author jsconan
 */

/**
 * Repeats and rotates the children modules `count` times with the provided `interval`.
 *
 * @param Number [count] - The number of times the children must be repeated.
 * @param Number [angle] - The angle interval in which rotate the children.
 * @param Vector [axis] - The rotation axis around which rotate the children.
 * @param Vector [interval] - The interval between each repeated children.
 * @param Vector [origin] - The rotate origin.
 * @param Number [intervalX] - The X interval between each repeated children
 *                             (will overwrite the X coordinate in the `interval` vector).
 * @param Number [intervalY] - The Y interval between each repeated children
 *                             (will overwrite the Y coordinate in the `interval` vector).
 * @param Number [intervalZ] - The Z interval between each repeated children
 *                             (will overwrite the Z coordinate in the `interval` vector).
 * @param Number [axisX] - The X axis factor (will overwrite the X coordinate in the `axis` vector).
 * @param Number [axisY] - The Y axis factor (will overwrite the Y coordinate in the `axis` vector).
 * @param Number [axisZ] - The Z axis factor (will overwrite the Z coordinate in the `axis` vector).
 * @param Number [originX] - The X origin (will overwrite the X coordinate in the `origin` vector).
 * @param Number [originY] - The Y origin (will overwrite the Y coordinate in the `origin` vector).
 * @param Number [originZ] - The Z origin (will overwrite the Z coordinate in the `origin` vector).
 */
module repeatRotate(count    = 2,
                    angle    = DEGREES,
                    axis     = zAxis3D(),
                    interval = 0,
                    origin   = 0,
                    center   = false,
                    intervalX, intervalY, intervalZ,
                    axisX, axisY, axisZ,
                    originX, originY, originZ) {

    interval = apply3D(interval, intervalX, intervalY, intervalZ);
    origin = apply3D(origin, originX, originY, originZ);
    angle = deg(angle);
    partAngle = angle / (angle % DEGREES ? count - 1 : count);
    axis = apply3D(axis, axisX, axisY, axisZ) * partAngle;
    offset = center ? -interval * (count - 1) / 2 : ORIGIN_3D;

    for (i = [0 : count - 1]) {
        translate(offset + interval * i) {
            rotateOrigin(a=axis * i, o=origin) {
                children();
            }
        }
    }
}

/**
 * Repeats and rotates the children modules in two directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Number [angleX] - The angle interval in which rotate the children along the X axis.
 * @param Number [angleY] - The angle interval in which rotate the children along the Y axis.
 * @param Vector [axisX] - The rotation axis around which rotate the children along the X axis.
 * @param Vector [axisY] - The rotation axis around which rotate the children along the Y axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Vector [originX] - The rotate origin along the X axis.
 * @param Vector [originY] - The rotate origin along the Y axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeatRotate2D(countX    = 2,
                      countY    = 2,
                      angleX    = DEGREES,
                      angleY    = DEGREES,
                      axisX     = zAxis3D(),
                      axisY     = yAxis3D(),
                      intervalX = 0,
                      intervalY = 0,
                      originX   = 0,
                      originY   = 0,
                      center    = false) {

    repeatRotate(count=countY, interval=vector3D(intervalY), angle=angleY, axis=vector3D(axisY), origin=originY, center=center) {
        repeatRotate(count=countX, interval=vector3D(intervalX), angle=angleX, axis=vector3D(axisX), origin=originX, center=center) {
            children();
        }
    }
}

/**
 * Repeats and rotates the children modules in three directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Number [countZ] - The number of times the children must be repeated along the Z axis.
 * @param Number [angleX] - The angle interval in which rotate the children along the X axis.
 * @param Number [angleY] - The angle interval in which rotate the children along the Y axis.
 * @param Number [angleZ] - The angle interval in which rotate the children along the Z axis.
 * @param Vector [axisX] - The rotation axis around which rotate the children along the X axis.
 * @param Vector [axisY] - The rotation axis around which rotate the children along the Y axis.
 * @param Vector [axisZ] - The rotation axis around which rotate the children along the Z axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Vector [intervalZ] - The interval between each repeated children along the Z axis.
 * @param Vector [originX] - The rotate origin along the X axis.
 * @param Vector [originY] - The rotate origin along the Y axis.
 * @param Vector [originZ] - The rotate origin along the Z axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeatRotate3D(countX    = 2,
                      countY    = 2,
                      countZ    = 2,
                      angleX    = DEGREES,
                      angleY    = DEGREES,
                      angleZ    = DEGREES,
                      axisX     = zAxis3D(),
                      axisY     = yAxis3D(),
                      axisZ     = xAxis3D(),
                      intervalX = 0,
                      intervalY = 0,
                      intervalZ = 0,
                      originX   = 0,
                      originY   = 0,
                      originZ   = 0,
                      center    = false) {

    repeatRotate(count=countZ, interval=vector3D(intervalZ), angle=angleZ, axis=vector3D(axisZ), origin=originZ, center=center) {
        repeatRotate(count=countY, interval=vector3D(intervalY), angle=angleY, axis=vector3D(axisY), origin=originY, center=center) {
            repeatRotate(count=countX, interval=vector3D(intervalX), angle=angleX, axis=vector3D(axisX), origin=originX, center=center) {
                children();
            }
        }
    }
}

/**
 * Repeats the children modules on every angle given in the `map`.
 *
 * @param Vector[] map - The list of angles at which place the children.
 * @param Vector [offset] - An offset to add before the rotation is applied.
 * @param Number [x] - The X-coordinate to apply on the offset.
 * @param Number [y] - The Y-coordinate to apply on the offset.
 * @param Number [z] - The Z-coordinate to apply on the offset.
 */
module repeatRotateMap(map, offset, x, y, z) {
    offset = apply3D(offset, x, y, z);

    for (at = map) {
        rotate(vector3D(at)) {
            translate(offset) {
                children();
            }
        }
    }
}


doc: fix typo

doc: fix typo

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2017-2022 Jean-Sebastien CONAN
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Part of the camelSCAD library.
 *
 * Operators that repeat children modules with respect to particular rules.
 *
 * @package operator/repeat
 * @author jsconan
 */

/**
 * Repeats and rotates the children modules `count` times with the provided `interval`.
 *
 * @param Number [count] - The number of times the children must be repeated.
 * @param Number [angle] - The angle interval in which rotate the children.
 * @param Vector [axis] - The rotation axis around which rotate the children.
 * @param Vector [interval] - The interval between each repeated children.
 * @param Vector [origin] - The rotate origin.
 * @param Number [intervalX] - The X interval between each repeated children
 *                             (will overwrite the X coordinate in the `interval` vector).
 * @param Number [intervalY] - The Y interval between each repeated children
 *                             (will overwrite the Y coordinate in the `interval` vector).
 * @param Number [intervalZ] - The Z interval between each repeated children
 *                             (will overwrite the Z coordinate in the `interval` vector).
 * @param Number [axisX] - The X axis factor (will overwrite the X coordinate in the `axis` vector).
 * @param Number [axisY] - The Y axis factor (will overwrite the Y coordinate in the `axis` vector).
 * @param Number [axisZ] - The Z axis factor (will overwrite the Z coordinate in the `axis` vector).
 * @param Number [originX] - The X origin (will overwrite the X coordinate in the `origin` vector).
 * @param Number [originY] - The Y origin (will overwrite the Y coordinate in the `origin` vector).
 * @param Number [originZ] - The Z origin (will overwrite the Z coordinate in the `origin` vector).
 */
module repeatRotate(count    = 2,
                    angle    = DEGREES,
                    axis     = zAxis3D(),
                    interval = 0,
                    origin   = 0,
                    center   = false,
                    intervalX, intervalY, intervalZ,
                    axisX, axisY, axisZ,
                    originX, originY, originZ) {

    interval = apply3D(interval, intervalX, intervalY, intervalZ);
    origin = apply3D(origin, originX, originY, originZ);
    angle = deg(angle);
    partAngle = angle / (angle % DEGREES ? count - 1 : count);
    axis = apply3D(axis, axisX, axisY, axisZ) * partAngle;
    offset = center ? -interval * (count - 1) / 2 : ORIGIN_3D;

    for (i = [0 : count - 1]) {
        translate(offset + interval * i) {
            rotateOrigin(a=axis * i, o=origin) {
                children();
            }
        }
    }
}

/**
 * Repeats and rotates the children modules in two directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Number [angleX] - The angle interval in which rotate the children along the X axis.
 * @param Number [angleY] - The angle interval in which rotate the children along the Y axis.
 * @param Vector [axisX] - The rotation axis around which rotate the children along the X axis.
 * @param Vector [axisY] - The rotation axis around which rotate the children along the Y axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Vector [originX] - The rotate origin along the X axis.
 * @param Vector [originY] - The rotate origin along the Y axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeatRotate2D(countX    = 2,
                      countY    = 2,
                      angleX    = DEGREES,
                      angleY    = DEGREES,
                      axisX     = zAxis3D(),
                      axisY     = yAxis3D(),
                      intervalX = 0,
                      intervalY = 0,
                      originX   = 0,
                      originY   = 0,
                      center    = false) {

    repeatRotate(count=countY, interval=vector3D(intervalY), angle=angleY, axis=vector3D(axisY), origin=originY, center=center) {
        repeatRotate(count=countX, interval=vector3D(intervalX), angle=angleX, axis=vector3D(axisX), origin=originX, center=center) {
            children();
        }
    }
}

/**
 * Repeats and rotates the children modules in three directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Number [countZ] - The number of times the children must be repeated along the Z axis.
 * @param Number [angleX] - The angle interval in which rotate the children along the X axis.
 * @param Number [angleY] - The angle interval in which rotate the children along the Y axis.
 * @param Number [angleZ] - The angle interval in which rotate the children along the Z axis.
 * @param Vector [axisX] - The rotation axis around which rotate the children along the X axis.
 * @param Vector [axisY] - The rotation axis around which rotate the children along the Y axis.
 * @param Vector [axisZ] - The rotation axis around which rotate the children along the Z axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Vector [intervalZ] - The interval between each repeated children along the Z axis.
 * @param Vector [originX] - The rotate origin along the X axis.
 * @param Vector [originY] - The rotate origin along the Y axis.
 * @param Vector [originZ] - The rotate origin along the Z axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeatRotate3D(countX    = 2,
                      countY    = 2,
                      countZ    = 2,
                      angleX    = DEGREES,
                      angleY    = DEGREES,
                      angleZ    = DEGREES,
                      axisX     = zAxis3D(),
                      axisY     = yAxis3D(),
                      axisZ     = xAxis3D(),
                      intervalX = 0,
                      intervalY = 0,
                      intervalZ = 0,
                      originX   = 0,
                      originY   = 0,
                      originZ   = 0,
                      center    = false) {

    repeatRotate(count=countZ, interval=vector3D(intervalZ), angle=angleZ, axis=vector3D(axisZ), origin=originZ, center=center) {
        repeatRotate(count=countY, interval=vector3D(intervalY), angle=angleY, axis=vector3D(axisY), origin=originY, center=center) {
            repeatRotate(count=countX, interval=vector3D(intervalX), angle=angleX, axis=vector3D(axisX), origin=originX, center=center) {
                children();
            }
        }
    }
}

/**
 * Repeats the children modules on every angle given in the `map`.
 *
 * @param Vector[] map - The list of angles at which place the children.
 * @param Vector [offset] - An offset to add before the rotation is applied.
 * @param Number [x] - The X-coordinate to apply on the offset.
 * @param Number [y] - The Y-coordinate to apply on the offset.
 * @param Number [z] - The Z-coordinate to apply on the offset.
 */
module repeatRotateMap(map, offset, x, y, z) {
    offset = apply3D(offset, x, y, z);

    for (at = map) {
        rotate(vector3D(at)) {
            translate(offset) {
                children();
            }
        }
    }
}

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2017-2022 Jean-Sebastien CONAN
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Part of the camelSCAD library.
 *
 * Operators that repeat children modules with respect to particular rules.
 *
 * @package operator/repeat
 * @author jsconan
 */

/**
 * Repeats and rotates the children modules `count` times with the provided `interval`.
 *
 * @param Number [count] - The number of times the children must be repeated.
 * @param Number [angle] - The angle interval in which rotate the children.
 * @param Vector [axis] - The rotation axis around which rotate the children.
 * @param Vector [interval] - The interval between each repeated children.
 * @param Vector [origin] - The rotate origin.
 * @param Number [intervalX] - The X interval between each repeated children
 *                             (will overwrite the X coordinate in the `interval` vector).
 * @param Number [intervalY] - The Y interval between each repeated children
 *                             (will overwrite the Y coordinate in the `interval` vector).
 * @param Number [intervalZ] - The Z interval between each repeated children
 *                             (will overwrite the Z coordinate in the `interval` vector).
 * @param Number [axisX] - The X axis factor (will overwrite the X coordinate in the `axis` vector).
 * @param Number [axisY] - The Y axis factor (will overwrite the Y coordinate in the `axis` vector).
 * @param Number [axisZ] - The Z axis factor (will overwrite the Z coordinate in the `axis` vector).
 * @param Number [originX] - The X origin (will overwrite the X coordinate in the `origin` vector).
 * @param Number [originY] - The Y origin (will overwrite the Y coordinate in the `origin` vector).
 * @param Number [originZ] - The Z origin (will overwrite the Z coordinate in the `origin` vector).
 */
module repeatRotate(count    = 2,
                    angle    = DEGREES,
                    axis     = zAxis3D(),
                    interval = 0,
                    origin   = 0,
                    center   = false,
                    intervalX, intervalY, intervalZ,
                    axisX, axisY, axisZ,
                    originX, originY, originZ) {

    interval = apply3D(interval, intervalX, intervalY, intervalZ);
    origin = apply3D(origin, originX, originY, originZ);
    angle = deg(angle);
    partAngle = angle / (angle % DEGREES ? count - 1 : count);
    axis = apply3D(axis, axisX, axisY, axisZ) * partAngle;
    offset = center ? -interval * (count - 1) / 2 : ORIGIN_3D;

    for (i = [0 : count - 1]) {
        translate(offset + interval * i) {
            rotateOrigin(a=axis * i, o=origin) {
                children();
            }
        }
    }
}

/**
 * Repeats and rotates the children modules in two directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Number [angleX] - The angle interval in which rotate the children along the X axis.
 * @param Number [angleY] - The angle interval in which rotate the children along the Y axis.
 * @param Vector [axisX] - The rotation axis around which rotate the children along the X axis.
 * @param Vector [axisY] - The rotation axis around which rotate the children along the Y axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Vector [originX] - The rotate origin along the X axis.
 * @param Vector [originY] - The rotate origin along the Y axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeatRotate2D(countX    = 2,
                      countY    = 2,
                      angleX    = DEGREES,
                      angleY    = DEGREES,
                      axisX     = zAxis3D(),
                      axisY     = yAxis3D(),
                      intervalX = 0,
                      intervalY = 0,
                      originX   = 0,
                      originY   = 0,
                      center    = false) {

    repeatRotate(count=countY, interval=vector3D(intervalY), angle=angleY, axis=vector3D(axisY), origin=originY, center=center) {
        repeatRotate(count=countX, interval=vector3D(intervalX), angle=angleX, axis=vector3D(axisX), origin=originX, center=center) {
            children();
        }
    }
}

/**
 * Repeats and rotates the children modules in three directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Number [countZ] - The number of times the children must be repeated along the Z axis.
 * @param Number [angleX] - The angle interval in which rotate the children along the X axis.
 * @param Number [angleY] - The angle interval in which rotate the children along the Y axis.
 * @param Number [angleZ] - The angle interval in which rotate the children along the Z axis.
 * @param Vector [axisX] - The rotation axis around which rotate the children along the X axis.
 * @param Vector [axisY] - The rotation axis around which rotate the children along the Y axis.
 * @param Vector [axisZ] - The rotation axis around which rotate the children along the Z axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Vector [intervalZ] - The interval between each repeated children along the Z axis.
 * @param Vector [originX] - The rotate origin along the X axis.
 * @param Vector [originY] - The rotate origin along the Y axis.
 * @param Vector [originZ] - The rotate origin along the Z axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeatRotate3D(countX    = 2,
                      countY    = 2,
                      countZ    = 2,
                      angleX    = DEGREES,
                      angleY    = DEGREES,
                      angleZ    = DEGREES,
                      axisX     = zAxis3D(),
                      axisY     = yAxis3D(),
                      axisZ     = xAxis3D(),
                      intervalX = 0,
                      intervalY = 0,
                      intervalZ = 0,
                      originX   = 0,
                      originY   = 0,
                      originZ   = 0,
                      center    = false) {

    repeatRotate(count=countZ, interval=vector3D(intervalZ), angle=angleZ, axis=vector3D(axisZ), origin=originZ, center=center) {
        repeatRotate(count=countY, interval=vector3D(intervalY), angle=angleY, axis=vector3D(axisY), origin=originY, center=center) {
            repeatRotate(count=countX, interval=vector3D(intervalX), angle=angleX, axis=vector3D(axisX), origin=originX, center=center) {
                children();
            }
        }
    }
}

/**
 * Repeats the children modules on every angle given in the `map`.
 *
 * @param Vector[] map - The list of angles at which place the children.
 * @param Vector [offset] - An offset to add before the ratation is applied.
 * @param Number [x] - The X-coordinate to apply on the offset.
 * @param Number [y] - The Y-coordinate to apply on the offset.
 * @param Number [z] - The Z-coordinate to apply on the offset.
 */
module repeatRotateMap(map, offset, x, y, z) {
    offset = apply3D(offset, x, y, z);

    for (at = map) {
        rotate(vector3D(at)) {
            translate(offset) {
                children();
            }
        }
    }
}


screws/screw_head: Cleanup and fix drive cut

include <system.scad>
include <thread-data.scad>
include <nut-data.scad>
include <materials.scad>

use <shapes.scad>
use <transforms.scad>
use <misc.scad>

// for proper threads
use <naca_sweep.scad>

// naive, assume head height is same as thread size (generally true for cap heads)
function get_screw_head_h(head, thread) = get(ThreadSize, thread);
function get_screw_head_d(head, thread) = 2 * get(ThreadSize, thread);

module screw(part, nut, thread, head="socket", h=10, tolerance=1.05, head_embed=false, with_nut=true, with_head=true, nut_offset=0, orient=Z, align=N)
{
    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "screw: No nut or thread given");
    assert(head != U, "screw: head == U");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "screw: Mismatched nut and thread");*/

    head_h = get_screw_head_h(head=head, thread=thread_);
    nut_h = get(NutThickness,nut)*tolerance;
    threadsize = get(ThreadSize, thread_);

    s = threadsize*tolerance;
    total_h = h;

    if(part==U)
    {
        difference()
        {
            screw(part="pos", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
            screw(part="neg", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
        }
        if($show_vit)
        %screw(part="vit", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
    }
    else if(part=="pos")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    tz(head_embed?-head_h:0)
    {
        if(with_head)
        {
            tz(h/2+.01)
            screw_head(part=part, head=head, thread=thread_, orient=Z, align=Z);
        }

        tz(-h/2+.01)
        screw_thread(thread=thread_, head=head, h=h+.1, tolerance=tolerance, orient=Z, align=N);

        if(with_nut && nut != U)
        {
            tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
            screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);
        }
    }
    else if(part=="neg")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    tz(head_embed?-head_h:0)
    {
        if(with_head)
        tz(h/2+.01)
        screw_head(part=part, head=head, thread=thread_, orient=Z, align=Z);
    }
    else if(part=="vit")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        if(with_nut && nut != U)
        {
            tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
            %screw_nut(nut=nut, align=-Z);
        }
    }
}

module screw_cut(nut, thread, head="socket", h=10, tolerance=1.05, head_embed=false, with_nut=true, with_nut_access=true, nut_cut_h=1000, with_head=true, head_cut_h=1000, nut_offset=0, cut_access=true, orient=Z, align=N)
{
    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "screw_cut: No nut or thread given");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "screw_cut: Mismatched nut and thread");*/

    nut_h = get(NutThickness,nut)*tolerance;
    assert(head != U, "screw_cut: head == U");

    head_h = get_screw_head_h(head=head, thread=thread_);
    assert(head_h != U, "screw_cut: head_h is U!");

    threadsize = get(ThreadSize, thread_);
    assert(threadsize != U, "screw_cut: threadsize is U!");

    s = threadsize*tolerance;
    total_h = h;
    assert(s != U, "screw_cut: s is U!");
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        tz(head_embed?-head_h:0)
        {
            if(with_head)
            {
                tz(h/2+.01)
                screw_head_cut(head=head, thread=thread_, tolerance=tolerance, override_h=head_cut_h, align=Z);
            }

            screw_thread_cut(thread=thread_, h=h+.1, tolerance=tolerance, orient=Z, align=N);

            if(with_nut && nut != U)
            {
                tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
                screw_nut_cut(nut=nut, tolerance=tolerance, h=nut_cut_h, with_access=with_nut_access, align=-Z);
            }

            if($show_vit)
            {
                %screw(nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=-Z, align=N);
            }
        }
    }
}

module screw_thread(thread, h=10, tolerance=1.00, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread: thread is undef");
    threadsize = get(ThreadSize, thread);
    d = get(ThreadSize, thread);
    pitch = get(ThreadPitchMm, thread);
    windings = ceil(h/pitch);

    size_align(size=[d,d,h], orient=orient, align=align)
    material(Mat_Steel)
    {
        if($preview_mode)
        {
            cylindera(d=d, h=h, align=Z);
        }
        else
        {
            intersection()
            {
                cylindera(d=d, h=h, align=Z);
                tz(-pitch)
                threading(angle=60, pitch=pitch, d=d, windings=windings, full=true);
            }
            /*inner_d = (get(ThreadInternalMinorDiaMin, thread) + get(ThreadInternalMinorDiaMin, thread))/2;*/
            /*cylindera(d=inner_d, h=h, align=Z);*/
        }
    }
}

module screw_thread_cut(thread, h=10, tolerance=1.05, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread_cut: thread is undef");
    threadsize = get(ThreadSize, thread);
    cylindera(d=threadsize*tolerance, h=h, orient=orient, align=align);
}

module screw_head(part, head="socket", drive="hex", thread, tolerance=1.00, override_h=U, orient=Z, align=N)
{
    assert(head != U, "screw_head: head == U");
    assert(thread!=U, "screw_head: thread == undef");

    threadsize = get(ThreadSize, thread);
    head_h = fallback(override_h, get_screw_head_h(head=head, thread=thread));
    head_d = get_screw_head_d(head=head, thread=thread);

    if(part==U)
    {
        difference()
        {
            screw_head(part="pos", head=head, drive=drive, thread=thread, tolerance=tolerance, override_h=override_h, orient=orient, align=align);
            screw_head(part="neg", head=head, drive=drive, thread=thread, tolerance=tolerance, override_h=override_h, orient=orient, align=align);
        }
    }
    else if(part=="pos")
    material(Mat_Steel)
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        if(head=="socket")
        {
            tz(-head_h/2)
            cylindera(d=head_d, h = head_h, align=Z);
        }
        else if(head=="button")
        {
            c = head_d;
            f = head_h*1.25;
            r = ( pow(c,2)/4 + pow(f,2) )/(2*f);

            d = r - f; // displacement to move sphere

            intersection()
            {
                tz(-head_h/2)
                scale([1,1,.5])
                spherea(r = head_h, align=N);

                tz(-head_h/2)
                cylindera(d=head_d, h = head_h/2, align=Z);
            }
        }
    }
    else if(part=="neg")
    material(Mat_Steel)
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        tz(-head_h/2)
        if(drive=="hex")
        {
            threadsize_cut = threadsize-head_drive_threadsize_minus(head);
            inset = head_drive_inset(head, head_h);
            tz(inset)
            cylindera(d = fn_radius(threadsize_cut, 6), h = head_h, $fn = 6, align=Z);
        }
    }
}

function head_drive_threadsize_minus(head) =
head=="socket"?  0:
head=="button"?  0.5:
head=="set"?     1.5:
0;

function head_drive_inset(head, head_h) =
head=="socket"?  1*mm:
head=="button"?  0.5*mm:
head=="set"?     -head_h/2:
0;

module screw_head_cut(head="socket", thread, tolerance=1.05, override_h=U, orient=Z, align=N)
{
    assert(head != U, "screw_head: head == U");
    assert(thread!=U, "screw_head_cut: thread is undef");

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(head=head, thread=thread);
    head_d = get_screw_head_d(head=head, thread=thread)*tolerance;
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        if(head=="socket" || head=="button")
        {
            tz(-head_h/2)
            cylindera(d=head_d, h=fallback(override_h, head_h), align=Z);
        }
        else if(head=="set")
        {
            tz(-head_h/2)
            cylindera(d=threadsize, h=fallback(override_h, head_h), align=Z);
        }
        else
        {
            assert("head: not implemented");
        }
    }
}

module screw_nut(nut, thread, tolerance=1.00, override_h=U, orient=Z, align=N)
{
    assert(nut!=U, "screw_nut: nut is undef");

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut);
    nut_thread = fallback(thread, get(NutThread, nut));
    h_ = fallback(override_h, nut_thick);
    material(Mat_Steel)
    size_align([nut_dia, nut_dia, h_], orient=orient, align=align)
    {
        difference()
        {
            cylindera($fn=nut_facets, d=nut_dia, h=h_);
            tz(-h_/2-.2)
            screw_thread(thread=nut_thread, h=h_+.1);
        }
    }
}

module screw_nut_cut(nut, tolerance=1.05, h=1000, with_access=true, orient=Z, align=N)
{
    assert(nut!=U, "screw_nut_cut: nut is undef");

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_dia = nut_dia(nut);
    size_align(size=[nut_dia, nut_dia, nut_thick], orient=orient, align=align)
    {
        tz(nut_thick/2)
        {
            tz(-.1)
            screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);

            if(with_access)
            tz(-nut_thick-.01)
            {
                cylindera(d=nut_dia+2*mm, h=h, orient=Z, align=-Z);
            }
        }
    }
}

function nut_radius(nut) = outradius(get(NutWidthMin, nut)/2, get(NutFacets, nut));
function nut_dia(nut) = 2*nut_radius(nut);

module nut_trap_cut(nut, thread, head, trap_offset=10, screw_l=10*mm, screw_offset=0*mm, cut_screw=false, trap_h=10, trap_axis=-Y, orient=Z, align=N)
{
    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "nut_trap_cut: No nut or thread given");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "nut_trap_cut: Mismatched nut and thread");*/

    threadsize = get(ThreadSize, thread_);
    head_h = get_screw_head_h(head=head, thread=thread);
    nut_h = get(NutThickness, nut) +.5*mm;

    tolerance = -.2*mm;
    nut_width_min = get(NutWidthMin, nut)+tolerance;
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut)+tolerance;
    total_h = nut_h;
    size_align(size=[nut_dia, nut_dia, total_h], orient=orient, orient_ref=Z, align=align)
    {
        if(cut_screw)
        {
            tz(-nut_h/2)
            tz(screw_offset)
            screw_cut($show_vit=false, nut=nut, thread=thread_, with_nut=false, tolerance=1.1, h=screw_l, orient=-Z, align=Z);
        }
        else
        {
            tz(nut_h/2+screw_offset)
            screw_thread_cut($show_vit=false, thread=thread_, tolerance=1.1, h=screw_l, orient=-Z, align=Z);
        }

        hull()
        {
            orient(axis=Z, axis_ref=orient)
            translate(-.15*mm*trap_axis)
            stack(dist=trap_h, axis=trap_axis)
            {
                orient(axis=orient, axis_ref=Z)
                {
                    rotate([0,0,30])
                    cylindera($fn=nut_facets, d=nut_dia, h=nut_h, align=N);
                }

                orient(axis=orient, axis_ref=Z)
                cubea(size=[nut_width_min,nut_width_min,nut_h], align=N);
            }
        }

        if($show_vit)
        {
            if(cut_screw)
            {
                tz(-nut_h/2)
                tz(screw_offset)
                %screw(nut=nut, thread=thread_, with_nut=false, tolerance=1, h=screw_l, orient=-Z, align=Z);
            }

            rotate([0,0,30])
            %screw_nut(nut=nut, thread=thread, tolerance=1, orient=-Z, align=N);
        }

    }
}

if(false)
{
    all_axes()
    translate($axis*5*mm)
    color($color)
    screw(nut=NutHexM3, h=10*mm, orient=-$axis, align=$axis);
}

// all nuts
if(false)
{
    for(nuti=[0:1:len(AllNut)-1])
    {
        nut = AllNut[nuti];
        v_threadsize = v_get(AllNut,ThreadSize);
        v_nutwidthmin = v_get(AllNut,NutWidthMin);
        dist = v_cumsum(v_nutwidthmin, 0, nuti)[nuti]*1.2;
        translate(X*dist)
        rotate([0,0,30])
        screw(nut=nut, h=get(NutWidthMax, nut)*5, head_embed=false, orient=-Z, align=Z);
    }
}

// From ParkinBots threading library
// https://www.thingiverse.com/thing:1659079
/*module Threading(D = 0, pitch = 1, d = 12, windings = 10, helices = 1, angle = 60, steps=40)*/
/*{*/
    /*R = D==0?d/2+2*pitch/PI:D/2; */
    /*translate([0,0,-pitch])*/
    /*difference()*/
    /*{*/
        /*translate([0,0,pitch])*/
        /*cylinder (r=R, h =pitch*(windings-helices));*/
        /*threading(pitch, d, windings, helices, angle, steps, true); */
    /*}*/
/*}*/

// From ParkinBots threading library
// https://www.thingiverse.com/thing:1659079
module threading(pitch = 1, d = 12, windings = 10, helices = 1, angle = 60, steps=40, full = false)
{
    // tricky: glue two 180° pieces together to get a proper manifold
    r = d/2;
    Steps = steps/2;
    Pitch = pitch*helices;
    if(full)
    {
        cylinder(r = r-.5-pitch/PI, h=pitch*(windings+helices), $fn=steps);
    }

    sweep(gen_dat(), planar_caps=true);   // half screw
    rz(180)
    tz(Pitch/2)
    sweep(gen_dat(), planar_caps=true);   // half screw

    function gen_dat() =
        let(ang = 180, bar = R_(180, -90, 0, Ty_(-r+.5, vec3D(pitch/PI*Rack(windings, angle)))))
        [for (i=[0:Steps]) Tz_(i/2/Steps*Pitch, -Rz_(i*ang/Steps, bar))];

    function Rack(w, angle) =
        concat([[0, 2]],
            [for (i=[0:w-1], j=[0:3])
            let(t = [ [0, 1], [2*tan(angle/2), -1], [PI/2, -1], [2*tan(angle/2)+PI/2, 1]])
            [t[j][0]+i*PI, t[j][1]]], [[w*PI, 1], [w*PI, 2]
            ]);
}

if(false)
{
    $fn=32;
    all_heads=
    [
        "socket",
        "button",
        "set"
    ];
    for(i=[0:len(all_heads)-1])
    {
        head=all_heads[i];
        tx(i*8*mm)
        screw(nut=NutHexM3, head=head, h=25, orient=-Z);
    }

}

if(false)
{
    $fn=128;
    nut1 = NutHexM3;
    stack(axis=X, dist = 20)
    {
        screw(nut=NutHexM8, head="socket", h=25, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, with_head=1, with_nut=0, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, with_head=0, with_nut=0, orient=-Z);
        screw(nut=NutHexM8, head="set", h=25, with_nut=0, orient=-Z);

        threading(pitch = 1.25, d=8, windings=10, full=true);
    }
}

if(false)
{
    box_w = 150*mm;
    box_d = 10*mm;
    box_h = 10*mm;
    o = 10*mm;

    $show_vit = true;
    /*$fs= 0.2;*/
    /*$fa = 4;*/

    nut1 = NutHexM3;
    nut2 = NutHexM5;
    nut3 = NutKnurlM3_3_42;
    nut4 = NutKnurlM3_5_42;

    /*all_axes()*/
    /*for($axis=[X])*/
    /*color($color)*/
    /*translate($axis*15)*/
    /*echo($axis)*/

    /*orient(axis=$axis, axis_ref=-X)*/
    for(y=[-1,1])
    translate(-y*Y*20*mm)
    difference()
    {
        cubea([box_w,box_d,box_h], align=X+Z);
        test_cuts(orient=y*Z);
        /*%test_cuts(orient=y, axis_ref=-Z);*/
    }

}

module test_cuts(orient)
{
    union()
    {
        /*offset = 0;*/
        offset = -5;
        /*translate([0,offset,0])*/
        /*translate([5,0,-5])*/
        translate([5,0,0])
        stack(dist=10,axis=X)
        {
            screw_cut(nut=nut1, head="button", h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut1, head="socket", h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(thread=ThreadM5, h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut1, h=box_h, head_embed=false, orient=orient, align=Z);

            screw_cut(nut=nut1, h=box_h, nut_offset=0*mm, head_embed=true, orient=orient, align=Z);
            screw_cut(nut=nut2, h=box_h, nut_offset=3*mm, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut3, h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut4, h=box_h, head_embed=false, orient=orient, align=Z);

            translate([0,0,-5*mm])
            screw_cut(nut=nut3, h=box_h, head_embed=false, orient=orient, align=Z);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=5, head_embed=false, trap_h=10, trap_axis=-Y, orient=orient, align=N);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=5, head_embed=false, trap_h=10, trap_axis=Y, orient=orient, align=N);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=box_d, head_embed=false, trap_h=10, screw_offset=2*mm, trap_axis=Z, orient=orient, align=N);
            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=box_d, head_embed=false, trap_h=10, screw_offset=2*mm, trap_axis=-Z, orient=orient, align=N);
        }
    }
}

include <system.scad>
include <thread-data.scad>
include <nut-data.scad>
include <materials.scad>

use <shapes.scad>
use <transforms.scad>
use <misc.scad>

// for proper threads
use <naca_sweep.scad>

// naive, assume head height is same as thread size (generally true for cap heads)
function get_screw_head_h(head, thread) = get(ThreadSize, thread);
function get_screw_head_d(head, thread) = 2 * get(ThreadSize, thread);

module screw(part, nut, thread, head="socket", h=10, tolerance=1.05, head_embed=false, with_nut=true, with_head=true, nut_offset=0, orient=Z, align=N)
{
    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "screw: No nut or thread given");
    assert(head != U, "screw: head == U");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "screw: Mismatched nut and thread");*/

    head_h = get_screw_head_h(head=head, thread=thread_);
    nut_h = get(NutThickness,nut)*tolerance;
    threadsize = get(ThreadSize, thread_);

    s = threadsize*tolerance;
    total_h = h;

    if(part==U)
    {
        difference()
        {
            screw(part="pos", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
            screw(part="neg", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
        }
        if($show_vit)
        %screw(part="vit", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
    }
    else if(part=="pos")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    tz(head_embed?-head_h:0)
    {
        if(with_head)
        {
            tz(h/2+.01)
            screw_head(part=part, head=head, thread=thread_, orient=Z, align=Z);
        }

        tz(-h/2+.01)
        screw_thread(thread=thread_, head=head, h=h+.1, tolerance=tolerance, orient=Z, align=N);

        if(with_nut && nut != U)
        {
            tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
            screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);
        }
    }
    else if(part=="neg")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    tz(head_embed?-head_h:0)
    {
        if(with_head)
        tz(h/2+.01)
        screw_head(part=part, head=head, thread=thread_, orient=Z, align=Z);
    }
    else if(part=="vit")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        if(with_nut && nut != U)
        {
            tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
            %screw_nut(nut=nut, align=-Z);
        }
    }
}

module screw_cut(nut, thread, head="socket", h=10, tolerance=1.05, head_embed=false, with_nut=true, with_nut_access=true, nut_cut_h=1000, with_head=true, head_cut_h=1000, nut_offset=0, cut_access=true, orient=Z, align=N)
{
    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "screw_cut: No nut or thread given");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "screw_cut: Mismatched nut and thread");*/

    nut_h = get(NutThickness,nut)*tolerance;
    assert(head != U, "screw_cut: head == U");

    head_h = get_screw_head_h(head=head, thread=thread_);
    assert(head_h != U, "screw_cut: head_h is U!");

    threadsize = get(ThreadSize, thread_);
    assert(threadsize != U, "screw_cut: threadsize is U!");

    s = threadsize*tolerance;
    total_h = h;
    assert(s != U, "screw_cut: s is U!");
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        tz(head_embed?-head_h:0)
        {
            if(with_head)
            {
                tz(h/2+.01)
                screw_head_cut(head=head, thread=thread_, tolerance=tolerance, override_h=head_cut_h, align=Z);
            }

            screw_thread_cut(thread=thread_, h=h+.1, tolerance=tolerance, orient=Z, align=N);

            if(with_nut && nut != U)
            {
                tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
                screw_nut_cut(nut=nut, tolerance=tolerance, h=nut_cut_h, with_access=with_nut_access, align=-Z);
            }

            if($show_vit)
            {
                %screw(nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=-Z, align=N);
            }
        }
    }
}

module screw_thread(thread, h=10, tolerance=1.00, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread: thread is undef");
    threadsize = get(ThreadSize, thread);
    d = get(ThreadSize, thread);
    pitch = get(ThreadPitchMm, thread);
    windings = ceil(h/pitch);

    size_align(size=[d,d,h], orient=orient, align=align)
    material(Mat_Steel)
    {
        if($preview_mode)
        {
            cylindera(d=d, h=h, align=Z);
        }
        else
        {
            intersection()
            {
                cylindera(d=d, h=h, align=Z);
                tz(-pitch)
                threading(angle=60, pitch=pitch, d=d, windings=windings, full=true);
            }
            /*inner_d = (get(ThreadInternalMinorDiaMin, thread) + get(ThreadInternalMinorDiaMin, thread))/2;*/
            /*cylindera(d=inner_d, h=h, align=Z);*/
        }
    }
}

module screw_thread_cut(thread, h=10, tolerance=1.05, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread_cut: thread is undef");
    threadsize = get(ThreadSize, thread);
    cylindera(d=threadsize*tolerance, h=h, orient=orient, align=align);
}

module screw_head(part, head="socket", drive="hex", thread, tolerance=1.00, override_h=U, orient=Z, align=N)
{
    assert(head != U, "screw_head: head == U");
    assert(thread!=U, "screw_head: thread == undef");

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(head=head, thread=thread);
    head_d = get_screw_head_d(head=head, thread=thread);

    if(part==U)
    {
        difference()
        {
            screw_head(part="pos", head=head, drive=drive, thread=thread, tolerance=tolerance, override_h=override_h, orient=orient, align=align);
            screw_head(part="neg", head=head, drive=drive, thread=thread, tolerance=tolerance, override_h=override_h, orient=orient, align=align);
        }
    }
    else if(part=="pos")
    material(Mat_Steel)
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        if(head=="socket")
        {
            tz(-head_h/2)
            cylindera(d=head_d, h = fallback(override_h,head_h), align=Z);
        }
        else if(head=="button")
        {
            c = head_d;
            f = head_h*1.25;
            r = ( pow(c,2)/4 + pow(f,2) )/(2*f);

            d = r - f; // displacement to move sphere

            intersection()
            {
                tz(-head_h/2)
                scale([1,1,.5])
                spherea(r = head_h, align=N);

                tz(-head_h/2)
                cylindera(d=head_d, h = fallback(override_h,head_h)/2, align=Z);
            }
        }
    }
    else if(part=="neg")
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        if(head=="socket")
        {
            if(drive == "hex")
            {
                cylindera(d = fn_radius(threadsize, 6), h = 1000, $fn = 6, align=Z);
            }
        }
        else if(head=="button")
        {
            if(drive == "hex")
            {
                // button head uses -0.5*mm smaller hex
                tz(-head_h/2)
                cylindera(d = fn_radius(threadsize-.5, 6), h = 1000, $fn = 6, align=Z);
            }
        }
        else if(head=="set")
        {
            if(drive == "hex")
            {
                // set head uses -1*mm smaller hex
                tz(-head_h)
                cylindera(d = fn_radius(threadsize-1, 6), h = 1000, $fn = 6, align=Z);
            }

        }
    }
}

module screw_head_cut(head="socket", thread, tolerance=1.05, override_h=U, orient=Z, align=N)
{
    assert(head != U, "screw_head: head == U");
    assert(thread!=U, "screw_head_cut: thread is undef");

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(head=head, thread=thread);
    head_d = get_screw_head_d(head=head, thread=thread)*tolerance;
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        if(head=="socket" || head=="button")
        {
            tz(-head_h/2)
            cylindera(d=head_d, h=fallback(override_h, head_h), align=Z);
        }
        else if(head=="set")
        {
            tz(-head_h/2)
            cylindera(d=threadsize, h=fallback(override_h, head_h), align=Z);
        }
        else
        {
            assert("head: not implemented");
        }
    }
}

module screw_nut(nut, thread, tolerance=1.00, override_h=U, orient=Z, align=N)
{
    assert(nut!=U, "screw_nut: nut is undef");

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut);
    nut_thread = fallback(thread, get(NutThread, nut));
    h_ = fallback(override_h, nut_thick);
    material(Mat_Steel)
    size_align([nut_dia, nut_dia, h_], orient=orient, align=align)
    {
        difference()
        {
            cylindera($fn=nut_facets, d=nut_dia, h=h_);
            tz(-h_/2-.2)
            screw_thread(thread=nut_thread, h=h_+.1);
        }
    }
}

module screw_nut_cut(nut, tolerance=1.05, h=1000, with_access=true, orient=Z, align=N)
{
    assert(nut!=U, "screw_nut_cut: nut is undef");

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_dia = nut_dia(nut);
    size_align(size=[nut_dia, nut_dia, nut_thick], orient=orient, align=align)
    {
        tz(nut_thick/2)
        {
            tz(-.1)
            screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);

            if(with_access)
            tz(-nut_thick-.01)
            {
                cylindera(d=nut_dia+2*mm, h=h, orient=Z, align=-Z);
            }
        }
    }
}

function nut_radius(nut) = outradius(get(NutWidthMin, nut)/2, get(NutFacets, nut));
function nut_dia(nut) = 2*nut_radius(nut);

module nut_trap_cut(nut, thread, head, trap_offset=10, screw_l=10*mm, screw_offset=0*mm, cut_screw=false, trap_h=10, trap_axis=-Y, orient=Z, align=N)
{
    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "nut_trap_cut: No nut or thread given");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "nut_trap_cut: Mismatched nut and thread");*/

    threadsize = get(ThreadSize, thread_);
    head_h = get_screw_head_h(head=head, thread=thread);
    nut_h = get(NutThickness, nut) +.5*mm;

    tolerance = -.2*mm;
    nut_width_min = get(NutWidthMin, nut)+tolerance;
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut)+tolerance;
    total_h = nut_h;
    size_align(size=[nut_dia, nut_dia, total_h], orient=orient, orient_ref=Z, align=align)
    {
        if(cut_screw)
        {
            tz(-nut_h/2)
            tz(screw_offset)
            screw_cut($show_vit=false, nut=nut, thread=thread_, with_nut=false, tolerance=1.1, h=screw_l, orient=-Z, align=Z);
        }
        else
        {
            tz(nut_h/2+screw_offset)
            screw_thread_cut($show_vit=false, thread=thread_, tolerance=1.1, h=screw_l, orient=-Z, align=Z);
        }

        hull()
        {
            orient(axis=Z, axis_ref=orient)
            translate(-.15*mm*trap_axis)
            stack(dist=trap_h, axis=trap_axis)
            {
                orient(axis=orient, axis_ref=Z)
                {
                    rotate([0,0,30])
                    cylindera($fn=nut_facets, d=nut_dia, h=nut_h, align=N);
                }

                orient(axis=orient, axis_ref=Z)
                cubea(size=[nut_width_min,nut_width_min,nut_h], align=N);
            }
        }

        if($show_vit)
        {
            if(cut_screw)
            {
                tz(-nut_h/2)
                tz(screw_offset)
                %screw(nut=nut, thread=thread_, with_nut=false, tolerance=1, h=screw_l, orient=-Z, align=Z);
            }

            rotate([0,0,30])
            %screw_nut(nut=nut, thread=thread, tolerance=1, orient=-Z, align=N);
        }

    }
}

if(false)
{
    all_axes()
    translate($axis*5*mm)
    color($color)
    screw(nut=NutHexM3, h=10*mm, orient=-$axis, align=$axis);
}

// all nuts
if(false)
{
    for(nuti=[0:1:len(AllNut)-1])
    {
        nut = AllNut[nuti];
        v_threadsize = v_get(AllNut,ThreadSize);
        v_nutwidthmin = v_get(AllNut,NutWidthMin);
        dist = v_cumsum(v_nutwidthmin, 0, nuti)[nuti]*1.2;
        translate(X*dist)
        rotate([0,0,30])
        screw(nut=nut, h=get(NutWidthMax, nut)*5, head_embed=false, orient=-Z, align=Z);
    }
}

// From ParkinBots threading library
// https://www.thingiverse.com/thing:1659079
/*module Threading(D = 0, pitch = 1, d = 12, windings = 10, helices = 1, angle = 60, steps=40)*/
/*{*/
    /*R = D==0?d/2+2*pitch/PI:D/2; */
    /*translate([0,0,-pitch])*/
    /*difference()*/
    /*{*/
        /*translate([0,0,pitch])*/
        /*cylinder (r=R, h =pitch*(windings-helices));*/
        /*threading(pitch, d, windings, helices, angle, steps, true); */
    /*}*/
/*}*/

// From ParkinBots threading library
// https://www.thingiverse.com/thing:1659079
module threading(pitch = 1, d = 12, windings = 10, helices = 1, angle = 60, steps=40, full = false)
{
    // tricky: glue two 180° pieces together to get a proper manifold
    r = d/2;
    Steps = steps/2;
    Pitch = pitch*helices;
    if(full)
    {
        cylinder(r = r-.5-pitch/PI, h=pitch*(windings+helices), $fn=steps);
    }

    sweep(gen_dat(), planar_caps=true);   // half screw
    rz(180)
    tz(Pitch/2)
    sweep(gen_dat(), planar_caps=true);   // half screw

    function gen_dat() =
        let(ang = 180, bar = R_(180, -90, 0, Ty_(-r+.5, vec3D(pitch/PI*Rack(windings, angle)))))
        [for (i=[0:Steps]) Tz_(i/2/Steps*Pitch, -Rz_(i*ang/Steps, bar))];

    function Rack(w, angle) =
        concat([[0, 2]],
            [for (i=[0:w-1], j=[0:3])
            let(t = [ [0, 1], [2*tan(angle/2), -1], [PI/2, -1], [2*tan(angle/2)+PI/2, 1]])
            [t[j][0]+i*PI, t[j][1]]], [[w*PI, 1], [w*PI, 2]
            ]);
}

if(false)
{
    $fn=128;
    nut1 = NutHexM3;
    stack(axis=X, dist = 20)
    {
        screw(nut=NutHexM8, head="socket", h=25, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, with_head=1, with_nut=0, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, with_head=0, with_nut=0, orient=-Z);
        screw(nut=NutHexM8, head="set", h=25, with_nut=0, orient=-Z);

        threading(pitch = 1.25, d=8, windings=10, full=true);
    }
}

if(false)
{
    box_w = 150*mm;
    box_d = 10*mm;
    box_h = 10*mm;
    o = 10*mm;

    $show_vit = true;
    /*$fs= 0.2;*/
    /*$fa = 4;*/

    nut1 = NutHexM3;
    nut2 = NutHexM5;
    nut3 = NutKnurlM3_3_42;
    nut4 = NutKnurlM3_5_42;

    /*all_axes()*/
    /*for($axis=[X])*/
    /*color($color)*/
    /*translate($axis*15)*/
    /*echo($axis)*/

    /*orient(axis=$axis, axis_ref=-X)*/
    for(y=[-1,1])
    translate(-y*Y*20*mm)
    difference()
    {
        cubea([box_w,box_d,box_h], align=X+Z);
        test_cuts(orient=y*Z);
        /*%test_cuts(orient=y, axis_ref=-Z);*/
    }

}

module test_cuts(orient)
{
    union()
    {
        /*offset = 0;*/
        offset = -5;
        /*translate([0,offset,0])*/
        /*translate([5,0,-5])*/
        translate([5,0,0])
        stack(dist=10,axis=X)
        {
            screw_cut(nut=nut1, head="button", h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut1, head="socket", h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(thread=ThreadM5, h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut1, h=box_h, head_embed=false, orient=orient, align=Z);

            screw_cut(nut=nut1, h=box_h, nut_offset=0*mm, head_embed=true, orient=orient, align=Z);
            screw_cut(nut=nut2, h=box_h, nut_offset=3*mm, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut3, h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut4, h=box_h, head_embed=false, orient=orient, align=Z);

            translate([0,0,-5*mm])
            screw_cut(nut=nut3, h=box_h, head_embed=false, orient=orient, align=Z);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=5, head_embed=false, trap_h=10, trap_axis=-Y, orient=orient, align=N);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=5, head_embed=false, trap_h=10, trap_axis=Y, orient=orient, align=N);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=box_d, head_embed=false, trap_h=10, screw_offset=2*mm, trap_axis=Z, orient=orient, align=N);
            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=box_d, head_embed=false, trap_h=10, screw_offset=2*mm, trap_axis=-Z, orient=orient, align=N);
        }
    }
}


[3d] Loosen mating tolerances on probewrap

-- Increase space between stacks 0.75->1.25, increase height same amount
to compensate
-- Fix lip_poly_stack() interior top point being sloped by err_v amount
-- Increase err_h to make the parts snap to gether easier
-- Increase slop between stacks by 0.03mm (likely no change due to layer
rounding)

// Total number of probes supported (0 for just an endpiece)
count = 4; // [0,1,2,3,4]

// Wall thickness, set to multiple of perimeter width
wall = 2.5;
// Flat thickness
wall_t = 2;

// Overall width of flat
oa_w = 60;
// Overall depth of flat
oa_d = 95;
// Interior height between flats
height = 7.5;
// Center hole ratio
center_ratio = 3;

// Diameter of probe-holding hole
probe_d = 4.75;
// Length of probe holder
probe_l = 25;
// Distance of probe holder offset
probe_off = 7;
// Reduce thickness of probe holder (allow wire to pass more easily)
probe_cut = 1.25;

// Include holes around the perimeter of the flat?
flat_has_holes = true;
// Include the probe holder flat stub on endpiece as well? (can help retain wire)
end_has_stub = true;

/* [Hidden] */
// Center hole width
center_w = oa_w / center_ratio;
// Center hole depth
center_d = (oa_d - oa_w) + center_w;
// Diameter of holes in flat
flat_hole_d = (oa_w - center_w) / 4 - 0.5;

e=0.01;

do_render();

module do_render() {
  if (count == 0)
    half(true);
  else if (count == 1)
    single();
  else if (count == 2)
    complete_2();
  else if (count == 3)
    complete_3();
  else if (count == 4)
    complete_4();
}

module single() {
  half(false);
  translate([-oa_w - 1,0,0]) rotate(180) half(true);
}

module complete_2() {
  half(false);
  translate([oa_w + probe_off + probe_d + 1, 0, 0]) rotate(180) half(false);
  translate([0, oa_d + 1,0]) rotate(180) half(true);
}

module complete_3() {
  complete_2();
  translate([oa_w + probe_off + probe_d + 1, oa_d + 1, 0]) rotate(180) half(false);
}

module complete_4() {
  single();
  translate([oa_w + probe_off + probe_d + 1, 0, 0]) rotate(180) half(false);
  translate([0, oa_d + 1, 0]) half(false);
  translate([oa_w + probe_off + probe_d + 1, oa_d + 1, 0]) rotate(180) half(false);
}

module rextrude_180(diameter, h) {
  intersection() {
    translate([-diameter/2-e, -e, -e])
      cube([diameter+2*e, diameter/2+e, h+2*e]);
    rotate_extrude()
      children();
  }
}

module half(is_end) {
  probe_x=oa_w/2 + probe_off;
  probe_y=-probe_l - 5; // -oa_w/2 - 0;

  difference() {
    union() {
      flat();

      if (!is_end) {
        // probe holster
        intersection() {
          translate([probe_x, probe_y, probe_d/2+wall]) rotate([-90]) {
            difference() {
              union() {
                cylinder(probe_l, d=probe_d+2*wall, $fn=24);
                translate([-probe_d/2-wall,0,0])
                  cube([probe_d+2*wall, probe_d/2+wall, probe_l]);
              }
              translate([0,0,-e]) cylinder(probe_l+2*e, d=probe_d, $fn=16);
            }
          }
          // Keep the height of the cylinder less than the height of the layer
          translate([probe_x-probe_d/2-wall, probe_y, 0])
            cube([probe_d+2*wall, probe_l, height+wall_t-probe_cut]);
        } // interection
      } // if !is_end

      if (!is_end)
        // connect to holster
        translate([center_w/2+wall, probe_y, 0])
          cube([probe_x-center_w/2+probe_d/2, probe_l, wall_t]);
      else if (end_has_stub)
        // connect to holster
        mirror([0,1,0])
          translate([center_w/2+wall, probe_y, 0])
            cube([probe_x-center_w/2+probe_d/2, probe_l, wall_t]);

      // lip round
      translate([0,-(center_d-center_w)/2,0]) rotate(180)
        rextrude_180(oa_w, height*4, $fn=46)
        translate([center_w/2,0,0]) lip_poly(is_end, height);

      translate([0,(center_d-center_w)/2,0])
        rextrude_180(oa_w, height*4, $fn=46)
        translate([center_w/2,0,0]) lip_poly(is_end, height);
      // lip flat
      translate([center_w/2,(center_d-center_w)/2,0]) rotate([90])
        linear_extrude(center_d-center_w) lip_poly(is_end, height);
      translate([-center_w/2,-(center_d-center_w)/2,0]) rotate([90,0,180])
        linear_extrude(center_d-center_w) lip_poly(is_end, height);
    }  // end union

    if (is_end)
      mirror([0,1,0]) flat_holes();
    else
       flat_holes();

    // chip away a bit of snap in mating area
    translate([-4/2, -center_d/2-wall-e, wall_t+height-wall])
      cube([4, center_d+2*(wall+e), height*2]);
  } // end diff
}

module flat_hole() {
  rotate(20) {
    cylinder(wall_t+2*e, d=flat_hole_d);
    cube([flat_hole_d/2,flat_hole_d/2,wall_t+2*e]);
  }
}

module flat_holes() {
  flat_hole_offset = (oa_w - center_w)/4 + center_w/2;

  if (flat_has_holes) {
    translate([0, -(oa_d-oa_w)/2, 0]) for (rot=[0:180/4:180])
      rotate(-rot) translate([flat_hole_offset,0,-e]) flat_hole();
    translate([0, (oa_d-oa_w)/2, 0]) for (rot=[0:180/4:180])
      rotate(rot) translate([flat_hole_offset,0,-e]) flat_hole();
    translate([flat_hole_offset,0,-e]) flat_hole();
    translate([-flat_hole_offset,0,-e]) rotate(180) flat_hole();
  }
}

module flat() {
  difference() {
    hull() {
      translate([0, -(oa_d-oa_w)/2, 0]) cylinder(wall_t, d=oa_w, $fn=60);
      translate([0, (oa_d-oa_w)/2, 0]) cylinder(wall_t, d=oa_w, $fn=60);
    }

    // Center hole
    hull() {
      translate([0, -(center_d-center_w)/2, -e]) cylinder(wall_t+2*e, d=center_w+2, $fn=34);
      translate([0, (center_d-center_w)/2, -e]) cylinder(wall_t+2*e, d=center_w+2, $fn=34);
    }
  }
}

module lip_poly(is_end, interior_h) {
  if (is_end)
    lip_poly_stack_end();
  else
    lip_poly_stack(interior_h);
}

module lip_poly_stack(interior_h) {
  h=interior_h+wall_t;
  t_sock=wall;
  t_plug=t_sock;
  // Back the top snap portion away from the mating area by this amount
  // The lip is only 0.6mm so err needs to be less than this for any engagement to occur
  err_h=0.20;
  // Raise the snap lip this extra amount to account for a non-smooth surface
  // or layer rounding error, 1/2 a layer height is good
  err_v=0.15;

  polygon([
    // origin is bottom center, moving counterclockwise
    [0.5, 0], [t_sock,0], [t_sock, h], [-err_h, h],
    [-err_h, h+1+err_v],
      [0.6-err_h, h+1+0.6+err_v], [0.6-err_h, h+1+0.6+0.4+err_v],
    [-err_h, h+1+0.8+0.8+err_v],
    [-t_plug-err_h, h+1+0.8+0.8+err_v], [-t_plug-err_h, h], [0, h-t_plug-err_h],
    [0, 1+0.8+0.8], [0.8, 1+0.8], [0, 1], [0, 0.5]
  ]);
}

module lip_poly_stack_end() {
  t_sock = wall;
  stub = wall_t - 1.8;

  polygon([
    [0, 0], [t_sock, 0],
    // these points define the height, should be 1+0.8+0.8=2.6
    // but cut down to wall_t to be flush with flat(), this means wall_t
    // needs to be at least 1.8 to form a proper-sided socket
    [t_sock, wall_t], [0.5, wall_t], [0, wall_t-0.5], [0, wall_t-1],
    [0.8, wall_t-1-0.8], [0.8-stub, wall_t-1-0.8-stub]
  ]);
}

// Total number of probes supported (0 for just an endpiece)
count = 4; // [0,1,2,3,4]

// Wall thickness, set to multiple of perimeter width
wall = 2.5;
// Flat thickness
wall_t = 2;

// Overall width of flat
oa_w = 60;
// Overall depth of flat
oa_d = 95;
// Interior height between flats
height = 7;
// Center hole ratio
center_ratio = 3;

// Diameter of probe-holding hole
probe_d = 4.75;
// Length of probe holder
probe_l = 25;
// Distance of probe holder offset
probe_off = 7;
// Reduce thickness of probe holder (allow wire to pass more easily)
probe_cut = 0.75;

// Include holes around the perimeter of the flat?
flat_has_holes = true;
// Include the probe holder flat stub on endpiece as well? (can help retain wire)
end_has_stub = true;

/* [Hidden] */
// Center hole width
center_w = oa_w / center_ratio;
// Center hole depth
center_d = (oa_d - oa_w) + center_w;
// Diameter of holes in flat
flat_hole_d = (oa_w - center_w) / 4 - 0.5;

e=0.01;

do_render();

module do_render() {
  if (count == 0)
    half(true);
  else if (count == 1)
    single();
  else if (count == 2)
    complete_2();
  else if (count == 3)
    complete_3();
  else if (count == 4)
    complete_4();
}

module single() {
  half(false);
  translate([-oa_w - 1,0,0]) rotate(180) half(true);
}

module complete_2() {
  half(false);
  translate([oa_w + probe_off + probe_d + 1, 0, 0]) rotate(180) half(false);
  translate([0, oa_d + 1,0]) rotate(180) half(true);
}

module complete_3() {
  complete_2();
  translate([oa_w + probe_off + probe_d + 1, oa_d + 1, 0]) rotate(180) half(false);
}

module complete_4() {
  single();
  translate([oa_w + probe_off + probe_d + 1, 0, 0]) rotate(180) half(false);
  translate([0, oa_d + 1, 0]) half(false);
  translate([oa_w + probe_off + probe_d + 1, oa_d + 1, 0]) rotate(180) half(false);
}

module rextrude_180(diameter, h) {
  intersection() {
    translate([-diameter/2-e, -e, -e])
      cube([diameter+2*e, diameter/2+e, h+2*e]);
    rotate_extrude()
      children();
  }
}

module half(is_end) {
  probe_x=oa_w/2 + probe_off;
  probe_y=-probe_l - 5; // -oa_w/2 - 0;

  difference() {
    union() {
      flat();

      if (!is_end) {
        // probe holster
        intersection() {
          translate([probe_x, probe_y, probe_d/2+wall]) rotate([-90]) {
            difference() {
              union() {
                cylinder(probe_l, d=probe_d+2*wall, $fn=24);
                translate([-probe_d/2-wall,0,0])
                  cube([probe_d+2*wall, probe_d/2+wall, probe_l]);
              }
              translate([0,0,-e]) cylinder(probe_l+2*e, d=probe_d, $fn=16);
            }
          }
          // Keep the height of the cylinder less than the height of the layer
          translate([probe_x-probe_d/2-wall, probe_y, 0])
            cube([probe_d+2*wall, probe_l, height+wall_t-probe_cut]);
        } // interection
      } // if !is_end

      if (!is_end)
        // connect to holster
        translate([center_w/2+wall, probe_y, 0])
          cube([probe_x-center_w/2+probe_d/2, probe_l, wall_t]);
      else if (end_has_stub)
        // connect to holster
        mirror([0,1,0])
          translate([center_w/2+wall, probe_y, 0])
            cube([probe_x-center_w/2+probe_d/2, probe_l, wall_t]);

      // lip round
      translate([0,-(center_d-center_w)/2,0]) rotate(180)
        rextrude_180(oa_w, height*4, $fn=46)
        translate([center_w/2,0,0]) lip_poly(is_end, height);

      translate([0,(center_d-center_w)/2,0])
        rextrude_180(oa_w, height*4, $fn=46)
        translate([center_w/2,0,0]) lip_poly(is_end, height);
      // lip flat
      translate([center_w/2,(center_d-center_w)/2,0]) rotate([90])
        linear_extrude(center_d-center_w) lip_poly(is_end, height);
      translate([-center_w/2,-(center_d-center_w)/2,0]) rotate([90,0,180])
        linear_extrude(center_d-center_w) lip_poly(is_end, height);
    }  // end union

    if (is_end)
      mirror([0,1,0]) flat_holes();
    else
       flat_holes();

    // chip away a bit of snap in mating area
    translate([-4/2, -center_d/2-wall-e, wall_t+height-wall])
      cube([4, center_d+2*(wall+e), height*2]);
  } // end diff
}

module flat_hole() {
  rotate(20) {
    cylinder(wall_t+2*e, d=flat_hole_d);
    cube([flat_hole_d/2,flat_hole_d/2,wall_t+2*e]);
  }
}

module flat_holes() {
  flat_hole_offset = (oa_w - center_w)/4 + center_w/2;

  if (flat_has_holes) {
    translate([0, -(oa_d-oa_w)/2, 0]) for (rot=[0:180/4:180])
      rotate(-rot) translate([flat_hole_offset,0,-e]) flat_hole();
    translate([0, (oa_d-oa_w)/2, 0]) for (rot=[0:180/4:180])
      rotate(rot) translate([flat_hole_offset,0,-e]) flat_hole();
    translate([flat_hole_offset,0,-e]) flat_hole();
    translate([-flat_hole_offset,0,-e]) rotate(180) flat_hole();
  }
}

module flat() {
  difference() {
    hull() {
      translate([0, -(oa_d-oa_w)/2, 0]) cylinder(wall_t, d=oa_w, $fn=60);
      translate([0, (oa_d-oa_w)/2, 0]) cylinder(wall_t, d=oa_w, $fn=60);
    }

    // Center hole
    hull() {
      translate([0, -(center_d-center_w)/2, -e]) cylinder(wall_t+2*e, d=center_w+2, $fn=34);
      translate([0, (center_d-center_w)/2, -e]) cylinder(wall_t+2*e, d=center_w+2, $fn=34);
    }
  }
}

module lip_poly(is_end, interior_h) {
  if (is_end)
    lip_poly_stack_end();
  else
    lip_poly_stack(interior_h);
}

module lip_poly_stack(interior_h) {
  h=interior_h+wall_t;
  t_sock=wall;
  t_plug=t_sock;
  // Back the top snap portion away from the mating area by this amount
  // The lip is only 0.6mm so err needs to be less than this for any engagement to occur
  err_h=0.15;
  // Raise the snap lip this extra amount to account for a non-smooth surface
  // or layer rounding error, 1/2 a layer height is good
  err_v=0.12;

  polygon([
    // origin is bottom center, moving counterclockwise
    [0.5, 0], [t_sock,0], [t_sock, h], [-err_h, h],
    [-err_h, h+1+err_v],
      [0.6-err_h, h+1+0.6+err_v], [0.6-err_h, h+1+0.6+0.4+err_v],
    [-err_h, h+1+0.8+0.8+err_v],
    [-t_plug-err_h, h+1+0.8+0.8], [-t_plug-err_h, h], [0, h-t_plug-err_h],
    [0, 1+0.8+0.8], [0.8, 1+0.8], [0, 1], [0, 0.5]
  ]);
}

module lip_poly_stack_end() {
  t_sock = wall;
  stub = wall_t - 1.8;

  polygon([
    [0, 0], [t_sock, 0],
    // these points define the height, should be 1+0.8+0.8=2.6
    // but cut down to wall_t to be flush with flat(), this means wall_t
    // needs to be at least 1.8 to form a proper-sided socket
    [t_sock, wall_t], [0.5, wall_t], [0, wall_t-0.5], [0, wall_t-1],
    [0.8, wall_t-1-0.8], [0.8-stub, wall_t-1-0.8-stub]
  ]);
}


Change the shape of kite Change line length

lineLength=35;
bearing =0 + sin($t*360)*45;
elevation = 30+sin(-$t*360*2)*15;
yaw = bearing-sin($t*360)*30;
drift=5;
roll = sin(360*$t+90+drift)*120;
angleOfAttack=15;
pitch=elevation + angleOfAttack;
useNED=-1;
rotate(a=bearing, v=[0,0,1*useNED]) rotate(a=elevation, v=[0,1*useNED,0]){
	translate(v=[lineLength/2,0,0]) cube(size=[lineLength,0.1,0.1], center=true);
	translate(v=[lineLength,0,0])
 	  rotate(a=-1*elevation, v=[0,1*useNED,0]) rotate(a=-1*bearing, v=[0,0,1*useNED])
	  rotate(a=yaw ,v=[0,0,1*useNED]) rotate(a=pitch, v=[0,1*useNED,0]) rotate(a=roll, v=[1,0,0])
       color("red")
//cube(size=[0.1,,3,1],center=true); }
polygon(points=[[2,0],[2,1], [1.5,2],[0,3],[1.3,2],[1.8,1], [1.8,0],[1.8,-1],[1.3,-2],[0,-3],[1.5,-2],[2,-1]], paths=[[0,1,2,3,4,5,6,7,8,9,10,11]]);}
//import("skpfile.stl");

lineLength=20;
bearing =0 + sin($t*360)*45;
elevation = 30+sin(-$t*360*2)*15;
yaw = bearing-sin($t*360)*30;
drift=5;
roll = sin(360*$t+90+drift)*120;
angleOfAttack=15;
pitch=elevation + angleOfAttack;
useNED=-1;
rotate(a=bearing, v=[0,0,1*useNED]) rotate(a=elevation, v=[0,1*useNED,0]){
	translate(v=[lineLength/2,0,0]) cube(size=[lineLength,0.1,0.1], center=true);
	translate(v=[lineLength,0,0])
 	  rotate(a=-1*elevation, v=[0,1*useNED,0]) rotate(a=-1*bearing, v=[0,0,1*useNED])
	  rotate(a=yaw ,v=[0,0,1*useNED]) rotate(a=pitch, v=[0,1*useNED,0]) rotate(a=roll, v=[1,0,0])
       color("red") cube(size=[0.1,,3,1],center=true); }


Detab.

Detab.

// fishbox.scad
//  - Enclosure design for zebrafish Skinner box
//
//  Author: Mark Liffiton
//  Date: Oct, 2014
//
//  Units are mm

// Set thickness to account for thickness of material
// PLUS needed clearance for cuts into which material should fit.
thickness = 3;

// Interior box dimensions (from centerpoints of walls, so actual dimension is minus material thickness)
width = 400;     // x
depth = 300;     // y
i_height = 180;  // z  (i_height because 'height' is used for height of entire box)

// amount faces extend past each other at corners
overhang = 20;

// amount horizontal support bars extend past side supports
outset = overhang/2;

// tank base plate
base_width = width-thickness*2;
base_height = overhang;

// create height var to account for raised base
height = i_height + base_height;

// light bar
light_bar_width = 30;
light_pos_x = 50;
light_height = height-10;

// for slight offsets/tweaks
epsilon = 1;

// vars to control projection for DXF export (see makefile)
if (DXF_TOP) {
    projection() top_cover();
} else if (DXF_BOTTOM) {
    projection() tank_base();
} else if (DXF_SIDE1) {
    projection() rotate(a=[90,0,0]) side(y=0);
} else if (DXF_SIDE2) {
    projection() rotate(a=[90,0,0]) side(y=depth);
} else if (DXF_END1) {
    projection() rotate(a=[0,90,0]) vert_face(x=0);
} else if (DXF_END2) {
    projection() rotate(a=[0,90,0]) vert_face(x=width);
} else if (DXF_LIGHT_BAR) {
    projection() light_bar();
}
else {
    // 3D model
    // components to include (comment out unwanted)
    vert_face(x=0);
    vert_face(x=width);
    side(y=0);
    side(y=depth);
    tank_base();
    light_bar();
    // camera supports
    hanging_supports(x=width, y=depth/2, z=height/2, out=20, up=30, spacing=20);
    // rpi support
    hanging_support(x=width, y=depth/4+9, z=height/2, out=3, up=85);
    mock_rpi(x=width, y=depth/4, z=height/2);
    top_cover();
}

//////////////////////////////////////////////////////////////////
// Module definitions
//

module vert_face(x=0) {
    difference() {
        vert_face_base(x);
        camera_opening();
        // camera supports
        hanging_supports(x=width+thickness*2, y=depth/2, z=height/2+10, out=20, up=30, spacing=20);
        // rpi supports
        hanging_support(x=width+thickness*2, y=depth/4+9, z=height/2+10, out=3, up=85);
        scale([1,1,0.5])
            side_base(y=0);
        scale([1,1,0.5])
            side_base(y=depth);
    }
}

module vert_face_base(x) {
    translate([x-thickness/2,-overhang,overhang])
        cube([thickness,depth+overhang*2,height-overhang]);
}

module camera_opening() {
    translate([width, depth/2, height/2])
        cube([thickness*2,10,10], center=true);
}

module hanging_supports(x, y, z, out, up, spacing) {
    hanging_support(x, y + spacing/2, z, out, up);
    hanging_support(x, y - spacing/2, z, out, up);
}

module hanging_support(x, y, z, out, up) {
    inner_w = out+thickness;
    inner_h = up;
    inner_offset = 5;
    translate([x,y,z])
    // shift down to center on camera opening
    translate([0,0,-inner_offset])
    // push inner opening up against vert face
    translate([inner_w/2,0,0])
    difference() {
        cube([inner_w+20, thickness, inner_h+30], center=true);
        // cutout for camera
        translate([0,0,inner_offset])
            cube([inner_w, thickness*2, inner_h], center=true);
        // cutout for passing through vertface
        translate([-10-epsilon,0,0])
            cube([inner_w, thickness*2, inner_h-10], center=true);
        // bottom cutout
        translate([-inner_w/2,0,-25])
            cube([thickness, thickness*2, 10], center=true);
    }
}

module top_cover() {
    color("Grey", alpha=0.5)
    translate([-outset,-outset,height-thickness])
    difference() {
        cube([width+outset*2,depth+outset*2,thickness]);
        cutouts(5,width-thickness,outset,rot=0,trans=[width/2+outset,0,0]);
        cutouts(5,width-thickness,outset,rot=180,trans=[width/2+outset,depth+outset*2,0]);
        cutouts(5,depth-thickness,outset,rot=-90,trans=[0,depth/2+outset,0]);
        cutouts(5,depth-thickness,outset,rot=90,trans=[width+outset*2,depth/2+outset,0]);
    }
}

module tank_base() {
    translate([thickness,-outset,base_height])
    difference() {
        cube([base_width, depth+outset*2, thickness]);
        cutouts(5,base_width,outset,rot=0,trans=[base_width/2,0,0]);
        cutouts(5,base_width,outset,rot=180,trans=[base_width/2,depth+outset*2,0]);
    }
}

module light_bar() {
    translate([light_pos_x-light_bar_width/2, -outset, light_height])
    difference() {
        cube([light_bar_width, depth+outset*2, thickness]);
        cutouts(2,light_bar_width,outset,rot=0,trans=[light_bar_width/2,0,0]);
        cutouts(2,light_bar_width,outset,rot=180,trans=[light_bar_width/2,depth+outset*2,0]);
    }
}

module cutouts(num, width, outset, rot, trans) {
    w = width / ((num-1) * 2);
    translate(trans)
    rotate([0,0,1], a=rot)
    for (i = [0 : num-1]) {
        translate([-width/2 - w/2 + i*2*w, -thickness/2, -epsilon])
            cube([w, outset+thickness, thickness+epsilon*2]);
    }
}

module light_wire_opening() {
    translate([light_pos_x, 0, light_height-10])
    scale([2,1,1])
    rotate(v=[1,0,0], a=90)
        cylinder(d=10, h=thickness+epsilon, center=true);
}

module side(y=0) {
    difference() {
        side_base(y);
        vert_face(x=0);
        vert_face(x=width);
        tank_base();
        light_bar();
        light_wire_opening();
        top_cover();
    }
}

module side_base(y) {
    translate([-overhang,y-thickness/2,0])
        cube([width+overhang*2, thickness, height]);
}

module mock_rpi(x, y, z) {
    translate([x+thickness, y, z])
        rotate(v=[0,1,0], a=90)
            translate([-85/2, -56/2, 0])
                union() {
                    cube([85, 56, 2]);
                    // USB1
                    translate([67, 41, 2])
                        cube([20, 12, 12]);
                    // USB2
                    translate([67, 23, 2])
                        cube([20, 12, 12]);
                }
}


/ ‘Impression 3D/2016 - Trocar/trocar-test1.scad’


bearing: fix ziptie/clip distance etc

bearing: fix ziptie/clip distance etc

use <shapes.scad>
include <bearing_data.scad>
include <units.scad>

module bearing(bearing_type, override_h=undef, orient=[0,0,1], align=[0,0,0])
{
    h = override_h==undef ? bearing_type[2] : override_h;
    size_align(size=[bearing_type[1],bearing_type[1],bearing_type[2]], align=align ,orient=orient)
    difference()
    {
        // outer
        fncylindera(h=h, d=bearing_type[1], align=[0,0,0]);
        // inner
        fncylindera(h=h+.1, d=bearing_type[0], align=[0,0,0]);
        // clips
        if(len(bearing_type) > 3)
        {
            clip_depth=.5;
            for(z=[-1,1])
            translate([0,0,z*bearing_type[3]/2])
            difference()
            {
                fncylindera(h=1*mm, d=bearing_type[2]+1, align=[0,0,-z]);
                fncylindera(h=1*mm+.1, d=bearing_type[4]-clip_depth, align=[0,0,-z]);
            }

        }
    }
}

module bearing_mount_holes(bearing_type, ziptie_type=[2*mm, 3*mm], ziptie_bearing_distance=3*mm, tolerance=1.01, orient=[1,0,0], ziptie_dist=undef, show_zips=false)
{
    ziptie_thickness = ziptie_type[0];
    ziptie_width = ziptie_type[1]+0.6*mm;

    ziptie_dist_ = (ziptie_dist==undef?bearing_type[3]/2:ziptie_dist);
    ziptie_thickness_cut = bearing_type[1]+ziptie_bearing_distance+ziptie_thickness*3;

    orient(orient)
    {
        // Main bearing cut
        fncylindera(h=bearing_type[2], d=bearing_type[1]*tolerance, orient=[0,0,1]);

        for(z=[-1,1])
        translate([0,0,z*ziptie_dist_ - z*1/2])
        hollow_cylinder(
                d=bearing_type[1]+ziptie_bearing_distance+ziptie_thickness,
                thickness = ziptie_thickness*3,
                h = ziptie_width,
                taper=false,
                orient=[0,0,1],
                align=[0,0,0]
                );

        // bearing
        fncylindera(h=bearing_type[2], d=bearing_type[1], orient=[0,0,1]);

        // for linear rod
        fncylindera(d=bearing_type[0]*1.5, h=100, orient=[0,0,1]);

        if(show_zips)
        {
            for(z=[-1,1])
            translate([0,0,z*ziptie_dist_])
            {
                %hollow_cylinder(
                        d=bearing_type[1]+ziptie_bearing_distance+ziptie_thickness,
                        thickness = ziptie_thickness,
                        h = ziptie_width,
                        orient=[0,0,1],
                        align=[0,0,-z]
                        );
            }
        }
    }
}

if(false)
{
    b=bearing_igus_rj4jp_01_12;
    bearing_mount_holes(b, orient=[0,0,1]);
    translate([30,0,0])
    bearing(b, orient=[0,0,1]);
}

use <shapes.scad>
include <bearing_data.scad>

module bearing(bearing_type, orient=[0,0,1], align=[0,0,0])
{
    size_align(size=[bearing_type[1],bearing_type[1],bearing_type[2]], align=align ,orient=orient)
    difference()
    {
        // outer
        fncylindera(h=bearing_type[2], d=bearing_type[1], align=[0,0,0]);
        // inner
        fncylindera(h=bearing_type[2]+1, d=bearing_type[0], align=[0,0,0]);
        // clips
        if(len(bearing_type) > 3)
        {
            clip_depth=.5;
            for(j=[-1,1])
            translate([0,0,j*bearing_type[3]/2])
            difference()
            {
                fncylindera(h=1, d=bearing_type[2]+1, align=[0,0,0]);
                fncylindera(h=2, d=bearing_type[4]-clip_depth, align=[0,0,0]);
            }

        }
    }
}

module bearing_mount_holes(bearing_type, ziptie_type=[2*mm, 3*mm], ziptie_bearing_distance=2*mm, orient=[1,0,0], ziptie_dist=undef, show_zips=false)
{
    ziptie_thickness = ziptie_type[0];
    ziptie_width = ziptie_type[1]+0.6*mm;

    ziptie_dist_ = ziptie_dist==undef?bearing_type[3]/2:ziptie_dist;
    ziptie_thickness_cut = bearing_type[1]+ziptie_bearing_distance+ziptie_thickness*3;

    orient(orient)
    {
        // Main bearing cut
        fncylindera(h=bearing_type[2], d=bearing_type[1]*rod_fit_tolerance, orient=[0,0,1]);

        for(i=[-1,1])
        translate([0,0,i*ziptie_dist_])
        hollow_cylinder(
                d=bearing_type[1]+ziptie_bearing_distance+ziptie_thickness,
                thickness = ziptie_thickness*3,
                h = ziptie_width,
                taper=true,
                orient=[0,0,1]
                );

        // bearing
        fncylindera(h=bearing_type[2], d=bearing_type[1], orient=[0,0,1]);

        // for linear rod
        fncylindera(d=bearing_type[0]*1.5, h=100, orient=[0,0,1]);

        if(show_zips)
        {
            for(i=[-1,1])
            translate([0,0,i*ziptie_dist_])
            {
                %hollow_cylinder(
                        d=bearing_type[1]+ziptie_bearing_distance+ziptie_thickness,
                        thickness = ziptie_thickness,
                        h = ziptie_width,
                        orient=[0,0,1]
                        );
            }
        }
    }
}

/*bearing_mount_holes(zaxis_bearing, orient=[0,0,1]);*/