Fix the unit tests of core/line (missing  and )

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2017 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.
 */

use <../../full.scad>

/**
 * Part of the camelSCAD library.
 *
 * Unit tests: Lines handling.
 *
 * @package test/core/line
 * @author jsconan
 */
module testCoreLine() {
    testPackage("core/line.scad", 3) {
        // test core/line/arc()
        testModule("arc()", 7) {
            testUnit("no parameter", 1) {
                assertEqual(arc(), [], "Cannot build an arc without parameters, should return an empty array");
            }
            testUnit("wrong type", 3) {
                assertEqual(arc("1", "1"), [], "Cannot build an arc using strings, should return an empty array");
                assertEqual(arc(true, true), [], "Cannot build an arc using booleans, should return an empty array");
                assertEqual(arc(1, [1]), [], "Cannot build an arc using vector angle, should return an empty array");
            }
            testUnit("empty", 6) {
                assertEqual(arc(0), [], "Cannot build a circle arc with a null radius, should return an empty array");
                assertEqual(arc([0, 0]), [], "Cannot build an ellipse arc with a null radius, should return an empty array");
                assertEqual(arc([0, 1]), [], "Cannot build an ellipse arc if one of the radius is 0 (x), should return an empty array");
                assertEqual(arc([1, 0]), [], "Cannot build an ellipse arc if one of the radius is 0 (y), should return an empty array");
                assertEqual(arc(1, 0), [], "Cannot build a 0° circle arc, should return an empty array");
                assertEqual(arc([1, 1], 0), [], "Cannot build a 0° ellipse arc, should return an empty array");
            }
            testUnit("circle", 4) {
                assertEqual(arc(1, $fn=3), [ for (a = [360/3 : 360/3 : 360]) 1 * [cos(a), sin(a)] ], "Should return a list of points to draw a circle with a radius of 1 and 3 facets (triangle)");
                assertEqual(arc(1, $fn=4), [ for (a = [360/4 : 360/4 : 360]) 1 * [cos(a), sin(a)] ], "Should return a list of points to draw a circle with a radius of 1 and 4 facets (square)");
                assertEqual(arc(1, $fn=6), [ for (a = [360/6 : 360/6 : 360]) 1 * [cos(a), sin(a)] ], "Should return a list of points to draw a circle with a radius of 1 and 6 facets (hexagon)");
                assertEqual(arc(5, $fa=12, $fs=2), [ for (a = [astep(5, $fa=12, $fs=2) : astep(5, $fa=12, $fs=2) : 360]) 5 * [cos(a), sin(a)] ], "Should return a list of points to draw a circle with a radius of 1 and 16 facets");
            }
            testUnit("ellipse", 4) {
                assertEqual(arc([1, 2], $fn=3), [ for (a = [360/3 : 360/3 : 360]) [cos(a)*1, sin(a)*2] ], "Should return a list of points to draw an ellipse with a radius of [1, 2] and 3 facets (triangle)");
                assertEqual(arc([1, 2], $fn=4), [ for (a = [360/4 : 360/4 : 360]) [cos(a)*1, sin(a)*2] ], "Should return a list of points to draw an ellipse with a radius of [1, 2] and 4 facets (square)");
                assertEqual(arc([1, 2], $fn=6), [ for (a = [360/6 : 360/6 : 360]) [cos(a)*1, sin(a)*2] ], "Should return a list of points to draw an ellipse with a radius of [1, 2] and 6 facets (hexagon)");
                assertEqual(arc([5, 6], $fa=12, $fs=2), [ for (a = [0 : astep(6, $fa=12, $fs=2) : 360]) [cos(a)*5, sin(a)*6] ], "Should return a list of points to draw an ellipse with a radius of [5, 6] and 20 facets");
            }
            testUnit("circle arc", 8) {
                assertEqual(arc(1, a=30, $fn=3), [ for (a = [0 : 30 : 30]) [cos(a), sin(a)] ], "Should return a list of points to draw a 30° arc with a radius of 1 and 3 facets (triangle)");
                assertEqual(arc(1, a=140, $fn=3), concat([ for (a = [0 : 120 : 140]) [cos(a), sin(a)] ], [[cos(140), sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of 1 and 3 facets (triangle)");
                assertEqual(arc(1, a=140, $fn=4), concat([ for (a = [0 : 90 : 140]) [cos(a), sin(a)] ], [[cos(140), sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of 1 and 4 facets (square)");
                assertEqual(arc(1, a=200, $fn=4), concat([ for (a = [0 : 90 : 200]) [cos(a), sin(a)] ], [[cos(200), sin(200)]]), "Should return a list of points to draw a 200° arc with a radius of 1 and 4 facets (square)");
                assertEqual(arc(1, a=140, $fn=6), concat([ for (a = [0 : 60 : 140]) [cos(a), sin(a)] ], [[cos(140), sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of 1 and 6 facets (hexagon)");
                assertEqual(arc(1, a=200, $fn=6), concat([ for (a = [0 : 60 : 200]) [cos(a), sin(a)] ], [[cos(200), sin(200)]]), "Should return a list of points to draw a 200° arc with a radius of 1 and 6 facets (hexagon)");
                assertEqual(arc(5, a=80, $fa=12, $fs=2), concat([ for (a = [0 : astep(5, $fa=12, $fs=2) : 80]) 5 * [cos(a), sin(a)] ], [5 * [cos(80), sin(80)]]), "Should return a list of points to draw a 30° arc with a radius of 1 and 16 facets");
                assertEqual(arc(5, a=160, $fa=12, $fs=2), concat([ for (a = [0 : astep(5, $fa=12, $fs=2) : 160]) 5 * [cos(a), sin(a)] ], [5 * [cos(160), sin(160)]]), "Should return a list of points to draw a 30° arc with a radius of 1 and 16 facets");
            }
            testUnit("ellipse arc", 8) {
                assertEqual(arc([1, 2], a=30, $fn=3), [ for (a = [0 : 30 : 30]) [cos(a), 2 * sin(a)] ], "Should return a list of points to draw a 30° arc with a radius of [1, 2] and 3 facets (triangle)");
                assertEqual(arc([1, 2], a=140, $fn=3), concat([ for (a = [0 : 120 : 140]) [cos(a), 2 * sin(a)] ], [[cos(140), 2 * sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of [1, 2] and 3 facets (triangle)");
                assertEqual(arc([1, 2], a=140, $fn=4), concat([ for (a = [0 : 90 : 140]) [cos(a), 2 * sin(a)] ], [[cos(140), 2 * sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of [1, 2] and 4 facets (square)");
                assertEqual(arc([1, 2], a=200, $fn=4), concat([ for (a = [0 : 90 : 200]) [cos(a), 2 * sin(a)] ], [[cos(200), 2 * sin(200)]]), "Should return a list of points to draw a 200° arc with a radius of [1, 2] and 4 facets (square)");
                assertEqual(arc([1, 2], a=140, $fn=6), concat([ for (a = [0 : 60 : 140]) [cos(a), 2 * sin(a)] ], [[cos(140), 2 * sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of [1, 2] and 6 facets (hexagon)");
                assertEqual(arc([1, 2], a=200, $fn=6), concat([ for (a = [0 : 60 : 200]) [cos(a), 2 * sin(a)] ], [[cos(200), 2 * sin(200)]]), "Should return a list of points to draw a 200° arc with a radius of [1, 2] and 6 facets (hexagon)");
                assertEqual(arc([5, 6], a=80, $fa=12, $fs=2), concat([ for (a = [0 : astep(6, $fa=12, $fs=2) : 80]) [cos(a) * 5, sin(a) * 6] ], [[cos(80) * 5, sin(80) * 6]]), "Should return a list of points to draw a 30° arc with a radius of [5, 6] and 16 facets");
                assertEqual(arc([5, 6], a=160, $fa=12, $fs=2), concat([ for (a = [0 : astep(6, $fa=12, $fs=2) : 160]) [cos(a) * 5, sin(a) * 6] ], [[cos(160) * 5, sin(160) * 6]]), "Should return a list of points to draw a 30° arc with a radius of [5, 6] and 16 facets");
            }
        }
        // test core/line/sinusoid()
        testModule("sinusoid()", 4) {
            testUnit("no parameter", 1) {
                assertEqual(sinusoid(), [], "Cannot build a sinusoid without parameters, should return an empty array");
            }
            testUnit("wrong type", 3) {
                assertEqual(sinusoid("1", "1"), [], "Cannot build a sinusoid using strings, should return an empty array");
                assertEqual(sinusoid(true, true), [], "Cannot build a sinusoid using booleans, should return an empty array");
                assertEqual(sinusoid([1], [1]), [], "Cannot build a sinusoid using vectors, should return an empty array");
            }
            testUnit("straight", 5) {
                assertEqual(sinusoid(1, $fn=6), [ for (a = [0 : 1/6 : 1]) [a, sin(360 * a)] ], "Should build a simple sinusoid with a length of 1 and 6 segments");
                assertEqual(sinusoid(1, 2, $fn=6), [ for (a = [0 : 1/6 : 1]) [a, sin(360 * a / 2)] ], "Should build a simple sinusoid with a length of 1, a period of 2 and 6 segments");
                assertEqual(sinusoid(1, 2, 4, $fn=6), [ for (a = [0 : 1/6 : 1]) [a, 4 * sin(360 * a / 2)] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4 and 6 segments");
                assertEqual(sinusoid(1, 2, 4, 6, $fn=6), [ for (a = [0 : 1/6 : 1]) [a, 4 * sin(360 * a / 2 + 6)] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4, a delay of 6 and 6 segments");
                assertEqual(sinusoid(1, 2, 4, 6, 8, $fn=6), [ for (a = [0 : 1/6 : 1]) [a, 4 * sin(360 * a / 2 + 6) + 8] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4, a delay of 6, an offset of 8 and 6 segments");
            }
            testUnit("rotated", 5) {
                assertEqual(sinusoid(3, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([a, sin(360 * a)], 70) ], "Should build a sinusoid rotated by 70° with a length of 1 and 6 segments");
                assertEqual(sinusoid(3, 2, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([a, sin(360 * a / 2)], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2 and 6 segments");
                assertEqual(sinusoid(3, 2, 4, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([a, 4 * sin(360 * a / 2)], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4 and 6 segments");
                assertEqual(sinusoid(3, 2, 4, 6, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([a, 4 * sin(360 * a / 2 + 6)], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4, a delay of 6 and 6 segments");
                assertEqual(sinusoid(3, 2, 4, 6, 8, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([a, 4 * sin(360 * a / 2 + 6) + 8], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4, a delay of 6, an offset of 8 and 6 segments");
            }
        }
        // test core/line/cosinusoid()
        testModule("cosinusoid()", 4) {
            testUnit("no parameter", 1) {
                assertEqual(cosinusoid(), [], "Cannot build a sinusoid without parameters, should return an empty array");
            }
            testUnit("wrong type", 3) {
                assertEqual(cosinusoid("1", "1"), [], "Cannot build a sinusoid using strings, should return an empty array");
                assertEqual(cosinusoid(true, true), [], "Cannot build a sinusoid using booleans, should return an empty array");
                assertEqual(cosinusoid([1], [1]), [], "Cannot build a sinusoid using vectors, should return an empty array");
            }
            testUnit("straight", 5) {
                assertEqual(cosinusoid(1, $fn=6), [ for (a = [0 : 1/6 : 1]) [cos(360 * a), a] ], "Should build a simple sinusoid with a length of 1 and 6 segments");
                assertEqual(cosinusoid(1, 2, $fn=6), [ for (a = [0 : 1/6 : 1]) [cos(360 * a / 2), a] ], "Should build a simple sinusoid with a length of 1, a period of 2 and 6 segments");
                assertEqual(cosinusoid(1, 2, 4, $fn=6), [ for (a = [0 : 1/6 : 1]) [4 * cos(360 * a / 2), a] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4 and 6 segments");
                assertEqual(cosinusoid(1, 2, 4, 6, $fn=6), [ for (a = [0 : 1/6 : 1]) [4 * cos(360 * a / 2 + 6), a] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4, a delay of 6 and 6 segments");
                assertEqual(cosinusoid(1, 2, 4, 6, 8, $fn=6), [ for (a = [0 : 1/6 : 1]) [4 * cos(360 * a / 2 + 6) + 8, a] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4, a delay of 6, an offset of 8 and 6 segments");
            }
            testUnit("rotated", 5) {
                assertEqual(cosinusoid(3, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([cos(360 * a), a], 70) ], "Should build a sinusoid rotated by 70° with a length of 1 and 6 segments");
                assertEqual(cosinusoid(3, 2, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([cos(360 * a / 2), a], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2 and 6 segments");
                assertEqual(cosinusoid(3, 2, 4, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([4 * cos(360 * a / 2), a], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4 and 6 segments");
                assertEqual(cosinusoid(3, 2, 4, 6, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([4 * cos(360 * a / 2 + 6), a], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4, a delay of 6 and 6 segments");
                assertEqual(cosinusoid(3, 2, 4, 6, 8, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([4 * cos(360 * a / 2 + 6) + 8, a], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4, a delay of 6, an offset of 8 and 6 segments");
            }
        }

    }
    function _rot2(v, a) = [
        v[0] * cos(a) - v[1] * sin(a),
        v[1] * cos(a) + v[0] * sin(a)
    ];
}

testCoreLine();

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2017 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.
 */

use <../../full.scad>

/**
 * Part of the camelSCAD library.
 *
 * Unit tests: Lines handling.
 *
 * @package test/core/line
 * @author jsconan
 */
module testCoreLine() {
    testPackage("core/line.scad", 3) {
        // test core/line/arc()
        testModule("arc()", 7) {
            testUnit("no parameter", 1) {
                assertEqual(arc(), [], "Cannot build an arc without parameters, should return an empty array");
            }
            testUnit("wrong type", 3) {
                assertEqual(arc("1", "1"), [], "Cannot build an arc using strings, should return an empty array");
                assertEqual(arc(true, true), [], "Cannot build an arc using booleans, should return an empty array");
                assertEqual(arc(1, [1]), [], "Cannot build an arc using vector angle, should return an empty array");
            }
            testUnit("empty", 6) {
                assertEqual(arc(0), [], "Cannot build a circle arc with a null radius, should return an empty array");
                assertEqual(arc([0, 0]), [], "Cannot build an ellipse arc with a null radius, should return an empty array");
                assertEqual(arc([0, 1]), [], "Cannot build an ellipse arc if one of the radius is 0 (x), should return an empty array");
                assertEqual(arc([1, 0]), [], "Cannot build an ellipse arc if one of the radius is 0 (y), should return an empty array");
                assertEqual(arc(1, 0), [], "Cannot build a 0° circle arc, should return an empty array");
                assertEqual(arc([1, 1], 0), [], "Cannot build a 0° ellipse arc, should return an empty array");
            }
            testUnit("circle", 4) {
                assertEqual(arc(1, $fn=3), [ for (a = [360/3 : 360/3 : 360]) 1 * [cos(a), sin(a)] ], "Should return a list of points to draw a circle with a radius of 1 and 3 facets (triangle)");
                assertEqual(arc(1, $fn=4), [ for (a = [360/4 : 360/4 : 360]) 1 * [cos(a), sin(a)] ], "Should return a list of points to draw a circle with a radius of 1 and 4 facets (square)");
                assertEqual(arc(1, $fn=6), [ for (a = [360/6 : 360/6 : 360]) 1 * [cos(a), sin(a)] ], "Should return a list of points to draw a circle with a radius of 1 and 6 facets (hexagon)");
                assertEqual(arc(5, $fa=12, $fs=2), [ for (a = [astep(5) : astep(5) : 360]) 5 * [cos(a), sin(a)] ], "Should return a list of points to draw a circle with a radius of 1 and 16 facets");
            }
            testUnit("ellipse", 4) {
                assertEqual(arc([1, 2], $fn=3), [ for (a = [360/3 : 360/3 : 360]) [cos(a)*1, sin(a)*2] ], "Should return a list of points to draw an ellipse with a radius of [1, 2] and 3 facets (triangle)");
                assertEqual(arc([1, 2], $fn=4), [ for (a = [360/4 : 360/4 : 360]) [cos(a)*1, sin(a)*2] ], "Should return a list of points to draw an ellipse with a radius of [1, 2] and 4 facets (square)");
                assertEqual(arc([1, 2], $fn=6), [ for (a = [360/6 : 360/6 : 360]) [cos(a)*1, sin(a)*2] ], "Should return a list of points to draw an ellipse with a radius of [1, 2] and 6 facets (hexagon)");
                assertEqual(arc([5, 6], $fa=12, $fs=2), [ for (a = [0 : astep(6) : 360]) [cos(a)*5, sin(a)*6] ], "Should return a list of points to draw an ellipse with a radius of [5, 6] and 20 facets");
            }
            testUnit("circle arc", 8) {
                assertEqual(arc(1, a=30, $fn=3), [ for (a = [0 : 30 : 30]) [cos(a), sin(a)] ], "Should return a list of points to draw a 30° arc with a radius of 1 and 3 facets (triangle)");
                assertEqual(arc(1, a=140, $fn=3), concat([ for (a = [0 : 120 : 140]) [cos(a), sin(a)] ], [[cos(140), sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of 1 and 3 facets (triangle)");
                assertEqual(arc(1, a=140, $fn=4), concat([ for (a = [0 : 90 : 140]) [cos(a), sin(a)] ], [[cos(140), sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of 1 and 4 facets (square)");
                assertEqual(arc(1, a=200, $fn=4), concat([ for (a = [0 : 90 : 200]) [cos(a), sin(a)] ], [[cos(200), sin(200)]]), "Should return a list of points to draw a 200° arc with a radius of 1 and 4 facets (square)");
                assertEqual(arc(1, a=140, $fn=6), concat([ for (a = [0 : 60 : 140]) [cos(a), sin(a)] ], [[cos(140), sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of 1 and 6 facets (hexagon)");
                assertEqual(arc(1, a=200, $fn=6), concat([ for (a = [0 : 60 : 200]) [cos(a), sin(a)] ], [[cos(200), sin(200)]]), "Should return a list of points to draw a 200° arc with a radius of 1 and 6 facets (hexagon)");
                assertEqual(arc(5, a=80, $fa=12, $fs=2), concat([ for (a = [0 : astep(5) : 80]) 5 * [cos(a), sin(a)] ], [5 * [cos(80), sin(80)]]), "Should return a list of points to draw a 30° arc with a radius of 1 and 16 facets");
                assertEqual(arc(5, a=160, $fa=12, $fs=2), concat([ for (a = [0 : astep(5) : 160]) 5 * [cos(a), sin(a)] ], [5 * [cos(160), sin(160)]]), "Should return a list of points to draw a 30° arc with a radius of 1 and 16 facets");
            }
            testUnit("ellipse arc", 8) {
                assertEqual(arc([1, 2], a=30, $fn=3), [ for (a = [0 : 30 : 30]) [cos(a), 2 * sin(a)] ], "Should return a list of points to draw a 30° arc with a radius of [1, 2] and 3 facets (triangle)");
                assertEqual(arc([1, 2], a=140, $fn=3), concat([ for (a = [0 : 120 : 140]) [cos(a), 2 * sin(a)] ], [[cos(140), 2 * sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of [1, 2] and 3 facets (triangle)");
                assertEqual(arc([1, 2], a=140, $fn=4), concat([ for (a = [0 : 90 : 140]) [cos(a), 2 * sin(a)] ], [[cos(140), 2 * sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of [1, 2] and 4 facets (square)");
                assertEqual(arc([1, 2], a=200, $fn=4), concat([ for (a = [0 : 90 : 200]) [cos(a), 2 * sin(a)] ], [[cos(200), 2 * sin(200)]]), "Should return a list of points to draw a 200° arc with a radius of [1, 2] and 4 facets (square)");
                assertEqual(arc([1, 2], a=140, $fn=6), concat([ for (a = [0 : 60 : 140]) [cos(a), 2 * sin(a)] ], [[cos(140), 2 * sin(140)]]), "Should return a list of points to draw a 140° arc with a radius of [1, 2] and 6 facets (hexagon)");
                assertEqual(arc([1, 2], a=200, $fn=6), concat([ for (a = [0 : 60 : 200]) [cos(a), 2 * sin(a)] ], [[cos(200), 2 * sin(200)]]), "Should return a list of points to draw a 200° arc with a radius of [1, 2] and 6 facets (hexagon)");
                assertEqual(arc([5, 6], a=80, $fa=12, $fs=2), concat([ for (a = [0 : astep(6) : 80]) [cos(a) * 5, sin(a) * 6] ], [[cos(80) * 5, sin(80) * 6]]), "Should return a list of points to draw a 30° arc with a radius of [5, 6] and 16 facets");
                assertEqual(arc([5, 6], a=160, $fa=12, $fs=2), concat([ for (a = [0 : astep(6) : 160]) [cos(a) * 5, sin(a) * 6] ], [[cos(160) * 5, sin(160) * 6]]), "Should return a list of points to draw a 30° arc with a radius of [5, 6] and 16 facets");
            }
        }
        // test core/line/sinusoid()
        testModule("sinusoid()", 4) {
            testUnit("no parameter", 1) {
                assertEqual(sinusoid(), [], "Cannot build a sinusoid without parameters, should return an empty array");
            }
            testUnit("wrong type", 3) {
                assertEqual(sinusoid("1", "1"), [], "Cannot build a sinusoid using strings, should return an empty array");
                assertEqual(sinusoid(true, true), [], "Cannot build a sinusoid using booleans, should return an empty array");
                assertEqual(sinusoid([1], [1]), [], "Cannot build a sinusoid using vectors, should return an empty array");
            }
            testUnit("straight", 5) {
                assertEqual(sinusoid(1, $fn=6), [ for (a = [0 : 1/6 : 1]) [a, sin(360 * a)] ], "Should build a simple sinusoid with a length of 1 and 6 segments");
                assertEqual(sinusoid(1, 2, $fn=6), [ for (a = [0 : 1/6 : 1]) [a, sin(360 * a / 2)] ], "Should build a simple sinusoid with a length of 1, a period of 2 and 6 segments");
                assertEqual(sinusoid(1, 2, 4, $fn=6), [ for (a = [0 : 1/6 : 1]) [a, 4 * sin(360 * a / 2)] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4 and 6 segments");
                assertEqual(sinusoid(1, 2, 4, 6, $fn=6), [ for (a = [0 : 1/6 : 1]) [a, 4 * sin(360 * a / 2 + 6)] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4, a delay of 6 and 6 segments");
                assertEqual(sinusoid(1, 2, 4, 6, 8, $fn=6), [ for (a = [0 : 1/6 : 1]) [a, 4 * sin(360 * a / 2 + 6) + 8] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4, a delay of 6, an offset of 8 and 6 segments");
            }
            testUnit("rotated", 5) {
                assertEqual(sinusoid(3, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([a, sin(360 * a)], 70) ], "Should build a sinusoid rotated by 70° with a length of 1 and 6 segments");
                assertEqual(sinusoid(3, 2, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([a, sin(360 * a / 2)], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2 and 6 segments");
                assertEqual(sinusoid(3, 2, 4, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([a, 4 * sin(360 * a / 2)], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4 and 6 segments");
                assertEqual(sinusoid(3, 2, 4, 6, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([a, 4 * sin(360 * a / 2 + 6)], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4, a delay of 6 and 6 segments");
                assertEqual(sinusoid(3, 2, 4, 6, 8, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([a, 4 * sin(360 * a / 2 + 6) + 8], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4, a delay of 6, an offset of 8 and 6 segments");
            }
        }
        // test core/line/cosinusoid()
        testModule("cosinusoid()", 4) {
            testUnit("no parameter", 1) {
                assertEqual(cosinusoid(), [], "Cannot build a sinusoid without parameters, should return an empty array");
            }
            testUnit("wrong type", 3) {
                assertEqual(cosinusoid("1", "1"), [], "Cannot build a sinusoid using strings, should return an empty array");
                assertEqual(cosinusoid(true, true), [], "Cannot build a sinusoid using booleans, should return an empty array");
                assertEqual(cosinusoid([1], [1]), [], "Cannot build a sinusoid using vectors, should return an empty array");
            }
            testUnit("straight", 5) {
                assertEqual(cosinusoid(1, $fn=6), [ for (a = [0 : 1/6 : 1]) [cos(360 * a), a] ], "Should build a simple sinusoid with a length of 1 and 6 segments");
                assertEqual(cosinusoid(1, 2, $fn=6), [ for (a = [0 : 1/6 : 1]) [cos(360 * a / 2), a] ], "Should build a simple sinusoid with a length of 1, a period of 2 and 6 segments");
                assertEqual(cosinusoid(1, 2, 4, $fn=6), [ for (a = [0 : 1/6 : 1]) [4 * cos(360 * a / 2), a] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4 and 6 segments");
                assertEqual(cosinusoid(1, 2, 4, 6, $fn=6), [ for (a = [0 : 1/6 : 1]) [4 * cos(360 * a / 2 + 6), a] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4, a delay of 6 and 6 segments");
                assertEqual(cosinusoid(1, 2, 4, 6, 8, $fn=6), [ for (a = [0 : 1/6 : 1]) [4 * cos(360 * a / 2 + 6) + 8, a] ], "Should build a simple sinusoid with a length of 1, a period of 2, an amplitude of 4, a delay of 6, an offset of 8 and 6 segments");
            }
            testUnit("rotated", 5) {
                assertEqual(cosinusoid(3, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([cos(360 * a), a], 70) ], "Should build a sinusoid rotated by 70° with a length of 1 and 6 segments");
                assertEqual(cosinusoid(3, 2, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([cos(360 * a / 2), a], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2 and 6 segments");
                assertEqual(cosinusoid(3, 2, 4, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([4 * cos(360 * a / 2), a], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4 and 6 segments");
                assertEqual(cosinusoid(3, 2, 4, 6, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([4 * cos(360 * a / 2 + 6), a], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4, a delay of 6 and 6 segments");
                assertEqual(cosinusoid(3, 2, 4, 6, 8, a=70, $fn=8), [ for (a = [0 : 3/8 : 3]) _rot2([4 * cos(360 * a / 2 + 6) + 8, a], 70) ], "Should build a sinusoid rotated by 70° with a length of 1, a period of 2, an amplitude of 4, a delay of 6, an offset of 8 and 6 segments");
            }
        }

    }
    function _rot2(v, a) = [
        v[0] * cos(a) - v[1] * sin(a),
        v[1] * cos(a) + v[0] * sin(a)
    ];
}

testCoreLine();


Flip the alternate batteries

Flip the alternate batteries

// Battery packs
// battery_pack_linear - all batteries in a row
// battery_pack_double - 2 rows of batteries (can't cope with odd numbers!)

// Point of origin is the centre of the end of the end battery.

include <../config/colors.scad>

// AAs - http://en.wikipedia.org/wiki/AA_battery
// len includes the positive-end button
Battery_dia = 14.5;
Battery_len = 50.5;

// We have the total battery length, assume the positive terminal is 1/20th of the length
module battery() {
  button_h = (1/20)*Battery_len;
  top_h = (1/3)*Battery_len;
  bottom_h = Battery_len-(top_h+button_h);

  color("black")
  linear_extrude(height=bottom_h)
    circle(r=Battery_dia/2);

  color("red")
  translate([0, 0, bottom_h])
    linear_extrude(height=top_h)
      circle(r=Battery_dia/2);

  color(MetalColor)
  translate([0, 0, bottom_h+top_h])
    linear_extrude(height=button_h)
      circle(r=Battery_dia/4);

}

module battery_pack_linear(battery_sep, battery_count) {
  for(i=[0:battery_count-1]) {
    translate([i*(Battery_dia+battery_sep), 0, 0])
      if(i % 2 == 1) {
        translate([0,0,Battery_len/2])
          rotate([180, 0, 0])
            translate([0,0,-Battery_len/2])
              battery();
      } else {
        battery();
      }
  }
}

module battery_pack_double(battery_sep, battery_count) {
  for(i=[0:(battery_count/2)-1]) {
    translate([i*(Battery_dia+battery_sep), 0, 0])
      battery();
    translate([i*(Battery_dia+battery_sep), Battery_dia+battery_sep, 0])
      translate([0,0,Battery_len/2])
        rotate([180, 0, 0])
          translate([0,0,-Battery_len/2])
            battery();
  }
}

*battery_pack_linear(2,4);
*battery_pack_double(2, 4);

// Battery packs
// battery_pack_linear - all batteries in a row
// battery_pack_double - 2 rows of batteries (can't cope with odd numbers!)

// Point of origin is the centre of the end of the end battery.

include <../config/colors.scad>

// AAs - http://en.wikipedia.org/wiki/AA_battery
// len includes the positive-end button
Battery_dia = 14.5;
Battery_len = 50.5;

// We have the total battery length, assume the positive terminal is 1/20th of the length
module battery() {
  button_h = (1/20)*Battery_len;
  top_h = (1/3)*Battery_len;
  bottom_h = Battery_len-(top_h+button_h);

  color("black")
  linear_extrude(height=bottom_h)
    circle(r=Battery_dia/2);

  color("red")
  translate([0, 0, bottom_h])
    linear_extrude(height=top_h)
      circle(r=Battery_dia/2);

  color(MetalColor)
  translate([0, 0, bottom_h+top_h])
    linear_extrude(height=button_h)
      circle(r=Battery_dia/4);

}

module battery_pack_linear(battery_sep, battery_count) {
  for(i=[0:battery_count-1]) {
    translate([i*(Battery_dia+battery_sep), 0, 0])
      battery();
  }
}

module battery_pack_double(battery_sep, battery_count) {
  for(i=[0:(battery_count/2)-1]) {
    translate([i*(Battery_dia+battery_sep), 0, 0])
      battery();
    translate([i*(Battery_dia+battery_sep), Battery_dia+battery_sep, 0])
      battery();
  }
}

*battery_pack_linear(2,4);
*battery_pack_double(2, 4);


Add programming constructs tutorial

Add programming constructs tutorial

TODO: Add more functionality. e.g. matrix and vector maths, more
detail on variables and scope, lookup & search.

/* Tutorial 4 - Programming Constructs */

/* Modules are somewhat like functions, but they "return" objects */
module bit(type = "sphere", size = 10) {
	if (type == "sphere") {
		sphere(r = size / 2, center = true);
	} else if (type == "cube") {
		cube(size = size, center = true);
	} else {
		/* Echo just prints to the output window */
		echo("Unkown type: ", type);
	}
}

/* There are functions too; they return values, not objects. Values can
 * be vectors, matrices, strings... */
function quarter_circles(n) = 90 * n;

/* This will use the default arguments, type = "sphere" and size = 10 */
bit();

n_bits = 3;
arc_angle = quarter_circles(4);

/* For loops are pretty useful. Multiple conditions act like nested loops */
for (i = [0 : (arc_angle / n_bits) : arc_angle]) {
	rotate([0, 0, i]) translate([20, 0, 0]) bit("cube", 20);
}

freq = 5;
amplitude = 20;

/* Add in some trigonomic functions for fun */
for (i = [0 : 1 : arc_angle / 2]) {
	translate([0, i, amplitude * sin(freq * i)])
		bit("sphere", 5, $fn = 18);
}

/* intersection_for() is a special case, it gives the intersection of all
 * of the objects inside the loop - NB: only where *all* of them intersect */
color("red") translate([0, 0, 25]) {
	intersection_for(i = [0 : 60 : 360]) {
		rotate([0, 90, i]) cylinder(r = 10, h = 10, center = true);
	}
}

/* Notice the difference with an intersection() and a for() */
color("orange") translate([0, 0, 50]) {
	intersection() {
		for(i = [0 : 60 : 360]) {
			rotate([0, 90, i]) cylinder(r = 10, h = 10, center = true);
		}
	}
}

error: pathspec 'tut4_programming.scad' did not match any file(s) known to git


[3d] Increase effective nut ingress width, locklip_p has inset, LCD block taper

-- Nut ingress is now a sideways trapezoid so it starts wider and tapers
in.
-- Ingress width lowered 5.8->5.7mm
-- Nut recess no longer offset 0.4mm, elongated the points 0.2mm each
side
-- Locklip_p now default inset is 0, only the insert is inset now to
allow the base to extend all the way to the wall.
-- Fix microsd being slightly off-center since the case was enlarged
last rev
-- The top of the LCD block is now fileted

// preview[view:northeast, tilt:topdiagonal]

/* [Options] */
// Pit probe type
Control_Probe = "Thermocouple"; // [Thermocouple,Thermistor]
// Raspberry Pi Model
Pi_Model = "3B/2B/1B+"; // [3B/2B/1B+,Connectorless,1A+,Zero]
// Which case halves
Pieces = "Both"; // [Both,Top,Bottom]
/* [Advanced] */
// Thickness of side walls
wall = 3.0;
// Thickness of top and bottom faces
wall_t = 2;
// External corner radius on the body
body_corner_radius = 4;
// Height of the body edge bead chamfer
body_chamfer_height = 1.5;

/* [Hidden] */
w = inch(3.75)+0.5; // overall interior case width
d = inch(3.75)-0.0; // overall interior case depth
h_b = 32; // overall interior case height

probe_centerline = 9.0; // case is split along probe centerline on the probe side
case_split = 12.4;  // and the case split on the other 3 sides

pic_ex = 2;
lcd_mount_t = 7;

body_chamfer_height_t = body_chamfer_height;
body_chamfer_height_b = body_chamfer_height;

e = 0.01;

hm43_split();

function inch(x) = x*25.4;

module cube_fillet_chamfer(size,f,c,$fn=32) {
  hull() {
    translate([f,f,c]) linear_extrude(height=size[2]-2*c) minkowski() {
      square([size[0]-2*f, size[1]-2*f]);
      circle(r=f, $fn=$fn);
    }
    translate([f+c,f+c,0]) linear_extrude(height=size[2]) minkowski() {
      square([size[0]-2*(f+c), size[1]-2*(f+c)]);
      circle(r=f, $fn=$fn);
    }
  }
}

module jhole(d, h) {
  translate([0,0,h/2]) cube([2*(wall+pic_ex),d,h], center=true);
}

module phole() {
  dia=4.5;
  // Hole for thermistor probe jack
  rotate([0,90,0]) cylinder(2*wall, d=dia, $fn=16);
  // because 3d print tends to curl up slightly between the probe holes,
  // do not go right to the edge on the final layer (stop 0.25mm short on each side)
  translate([0,-(dia+0.5)/2, -0.3]) cube([2*wall, dia+0.5, 0.6]);
}

module screwhole() {
  // Screw hole for bottom of case (including nut)
  //translate([0,0,-e]) cylinder(h_b+wall_t, d=3.4, $fn=18);
  translate([0,0,-e]) {
    cylinder(0.4, d1=7.4, d2=6.6, $fn=6);
    translate([0,0,0.4]) cylinder(3, d=6.6, $fn=6);
  }
  translate([0,0,3+0.2]) cylinder(h_b+2*wall_t, d=3.4, $fn=18);
  translate([0,0,h_b+wall_t]) cylinder(3, d=5.5, $fn=18);
}

module screwhole2() {
  translate([0,0,-e]) cylinder(3, d=6, $fn=18);
  translate([0,0,3+0.4]) cylinder(h_b-3-0.4, d=3.4, $fn=18);
}

module pic_ex_cube() {
  translate([0,33.75,2])
    cube_fillet([pic_ex+e, 59.5, 20.8],
      vertical=[0, pic_ex, pic_ex, 0],
      top=[0,pic_ex,0,0],
      bottom=[0,pic_ex,0,0]);
}

module screw_pimount() {
  // 2mm height is good for screw holes
  difference() {
    cylinder(h=2.25, d=6.4, $fn=18);
    //cylinder(h=2+e, d=2.5, $fn=16);
  }
}

module btn_rnd(dia=10) {
  cylinder(wall_t+2*e, d1=dia, d2=dia+1.5*wall_t, $fn=24);
  translate([-6.5,-6.5,0]) cube([13,13,0.75+e]);
}

module btn_square(sq=13) {
  translate([-sq/2,-sq/2,0]) cube([sq,sq,wall_t+2*e]);
}

module tc_plusminus() {
  T=0.75+e;
  H=2;
  W=6;
  // Minus
  translate([0,2,0])
    cube_fillet([T, W, H], top=[0,0,0,T]);
  translate([0,-(2+W),0]) {
    cube_fillet([T, W, H], top=[0,0,0,T]);
    translate([0,(W-H)/2,(H-W)/2])
      cube_fillet([T, H, W], top=[0,0,0,T]);
  }
}

module led_hole() {
  cylinder(wall_t+2*e, d=3.4, $fn=16);
}

module nuttrap() {
  ww_w=3;
  ww_d=wall;
  nut_h=3.0;
  nut_ingress = 5.7; //nut_d * sin(60);
  nut_d = nut_ingress / sin(60);
  nut_ingress_off = nut_ingress/sqrt(3)/2;
  oa_h=wall_t+0.4+nut_h+wall_t+6.7;

  // bottom half for M3 socket cap screw (flush)
  difference() {
    translate([-5.5/2-ww_w, -nut_ingress/2-e, 0])
      cube_fillet([5.5+2*ww_w, nut_ingress+ww_d*0.8+e, 6], vertical=[3.4,3.4], $fn=20);
    // socket cap
    translate([0,0,-e]) cylinder(3.5, d=6, $fn=18);
    // screw shaft
    translate([0,0,3.5+0.3]) cylinder(oa_h-3, d=3.4, $fn=18);
  }

  // top half M3 nut trap
  translate([0,0,h_b+wall_t-oa_h])
  difference() {
    translate([-(nut_d/2+ww_w), -nut_ingress/2, 0])
      cube_fillet([nut_d+2*ww_w, nut_ingress+ww_d, oa_h],
        vertical=[3.4,3.4], $fn=20);
    // M3 screw
    translate([0,0,-e]) cylinder(wall_t, d1=4, d2=3.4, $fn=16);
    // nut hole / M3 extra
    translate([0,0,wall_t+0.3]) {
      // nut 3x for an elongated trap
      translate([-0.2,0,0]) cylinder(nut_h*1.5+e, d=nut_d+e/sin(60), $fn=6);
      translate([0,0,0]) cylinder(nut_h*1.5+e, d=nut_d+e/sin(60), $fn=6);
      translate([+0.2,0,0]) cylinder(nut_h*1.5+e, d=nut_d+e/sin(60), $fn=6);
      cylinder(oa_h-wall_t-0.3, d=4, $fn=16);  // M3 with plenty of clearance
    }
    // nut ingress (sideways trapezoid with wider side at ingress)
    translate([nut_ingress_off,-nut_ingress/2,wall_t+0.3])
       linear_extrude(nut_h+e) polygon([[0,0],
         [nut_d/2+ww_w-nut_ingress_off+0.4+e, -0.8],
         [nut_d/2+ww_w-nut_ingress_off+0.4+e, nut_ingress+0.8],
         [0,nut_ingress]]);
  }
}

module locklip_p(l, l_offset=0,
  lip_insert_depth=2.0, // how deep V to insert
  lip_v_off=0.2, // extra height before starting insert
  lip_h_off=0.4, // back connector away from mating area
  lip_w=2.5,  // thickness of attachment beam
  insert_inset=[0,0] // inset the insert inside mating area
  ) {
  translate([l_offset,0,0]) rotate([90,0,0]) rotate([0,90,0])
    difference() {
      linear_extrude(height=l-2*l_offset) polygon(points=[
        [0.1, -lip_w-lip_insert_depth-lip_h_off],  // 0.1 to add depth to keep extrusion manifold
        [0.1, 0],
        [-lip_insert_depth-lip_h_off, 0],
        [-lip_insert_depth-lip_h_off, lip_v_off],
        [-lip_h_off, lip_v_off+lip_insert_depth],
        [-lip_insert_depth-lip_h_off, lip_v_off+2*lip_insert_depth,],
        [-lip_w-lip_insert_depth-lip_h_off, lip_v_off+2*lip_insert_depth],
        [-lip_w-lip_insert_depth-lip_h_off, 0]
      ]);
      translate([-lip_w-lip_insert_depth-lip_h_off-e, 0, 0]) {
        if (insert_inset[0] > 0)
          translate([0,0,-e])
            cube([lip_w+lip_insert_depth+lip_h_off+2*e,
              lip_v_off+2*lip_insert_depth+e, insert_inset[0]+e]);
        if (insert_inset[1] > 0)
          translate([0, 0, l-2*l_offset-insert_inset[1]])
            cube([lip_w+lip_insert_depth+lip_h_off+2*e,
              lip_v_off+2*lip_insert_depth+e, insert_inset[1]+e]);
      }
    }
}

module locklip_n(l, l_offset=0,
  lip_insert_depth=2.2, // how deep V to insert
  lip_tip_clip=0.3, // how much to shave off the top tip
  ) {
  translate([l-l_offset,0,0]) rotate([90,0,0]) rotate([0,-90,0])
    linear_extrude(height=l-2*l_offset) polygon(points=[
      [0.1, -3*lip_insert_depth],  // 0.1 to add depth to keep extrusion manifold
      [0.1, -lip_tip_clip],
      [-lip_insert_depth+lip_tip_clip, -lip_tip_clip],
      [-e, -lip_insert_depth],
      [-lip_insert_depth, -2*lip_insert_depth]
    ]);
}

module lcd_screw() {
  rotate([180]) difference() {
    //cylinder(lcd_mount_t, d=2.5+3.2, $fn=16);
    cylinder(lcd_mount_t+e, d=2.5, $fn=16);
  }
}

module lcd_mount() {
  // Assuming starting at bottom left screw hole
  //lcd_screw();
  //translate([75,0,0]) lcd_screw(); // bottom right hole obscured by PCB
  translate([0,31,0]) lcd_screw();
  translate([75,31,0]) lcd_screw();
}

module lcd_neg() {
  // Assuming starting at bottom left screw hole
  translate([1.0, 2.1, 0])
    cube([73.0, 26.3, lcd_mount_t+wall_t-0.8]); // black bezel inset
  translate([5.5, 7.8, 0])
    cube([64.0, 15.4, lcd_mount_t+wall_t+2*e]); // LCD active area
  translate([4.23, -2.5, 0])
    cube([16*2.54, 5, lcd_mount_t+wall_t-0.8]); // pins cutout
}

module locklip_top_n(split) {
  translate([wall, d+wall, split+wall_t+e])
    rotate([180]) {
      locklip_n(28);
      translate([w-34,0,0]) locklip_n(34);
    }
}

module hm43() {
difference() {
  union() {
    cube_bchamfer([w+2*wall, d+2*wall, h_b+2*wall_t],
      r=body_corner_radius, top=body_chamfer_height_t,
      bottom=body_chamfer_height_b, $fn=36);
    // extra thick by Pi connectors
    if (Pi_Model != "Zero")
      translate([-pic_ex,wall,wall_t])
        pic_ex_cube();
    // TC +/-
    if (Control_Probe == "Thermocouple")
      translate([w+wall*2-e,wall+10,wall_t+18]) tc_plusminus();
  }

  // Main cutout
  translate([wall, wall, wall_t])
    cube_fillet([w, d, h_b+e], bottom=[2,2,2,2],
    top=[lcd_mount_t/2,lcd_mount_t/2,lcd_mount_t/2,lcd_mount_t/2],
    vertical=[1,1]);
  if (Pi_Model != "Zero")
    translate([wall-pic_ex+e,wall,wall_t]) pic_ex_cube();

  // Probe jack side
  translate([w+wall*0.5,wall,wall_t]) {
    // Probe jacks
    translate([0,24.25,probe_centerline]) {
      if (Control_Probe == "Thermocouple")
        // TC jack
        translate([0,inch(-0.55)-16.5/2,-1.4]) cube([2*wall, 16.5, 6.5]);
      else
        translate([0,-17.25,0]) phole();
      translate([0,inch(0.37)*0,0]) phole();
      translate([0,inch(0.37)*1,0]) phole();
      translate([0,inch(0.37)*2,0]) phole();
    }
  }
  // Pi connector side
  translate([wall*-0.5,wall,wall_t]) {
    // Pi connectors
    translate([0,0,5]) {
      if (Pi_Model == "3B/2B/1B+") {
        // ethernet
        translate([0,81.5,-0.5]) jhole(15,13);
        translate([0,81.5,-1.5]) jhole(5,5);
        // USB 0+1
        translate([0,62.75,0]) jhole(13,14.8);
        translate([0,44.75,0]) jhole(13,14.8);
      }
      if (Pi_Model == "1A+")
        translate([0,44.75,0]) jhole(13,7.4);
    }
    // HeaterMeter connectors
    translate([0,0,0]) {
      // Blower/Servo output
      translate([0,25.25,1.1]) jhole(16.7,13);
      // HM power jack
      translate([0,5.25,3.4]) jhole(9.4,11);
    }
  }

  // lcd hole
  translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t-e]) lcd_neg();

  // button holes
  translate([wall+48.7, wall+29.4, h_b+wall_t-e]) {
    translate([-inch(1.1)/2,0,0]) btn_rnd(7.2);  // left
    translate([inch(1.1)/2,0,0]) btn_rnd(7.2);   // right
    translate([0,inch(0.9)/2,0]) btn_rnd(7.2);   // up
    translate([0,-inch(0.9)/2,0]) btn_rnd(7.2);  // down
    // LED holes
    translate([inch(1.3), inch(-0.05), 0]) {
      led_hole();  //red
      translate([0, inch(0.35), 0]) led_hole(); //yellow
      translate([0, inch(0.70), 0]) led_hole(); //green
    }
  }
  // close screw holes
  translate([wall+inch(0.8)+1.3,wall+2.9,0]) {
    screwhole2();
    translate([inch(2.0),0,0]) screwhole2();
  }
}  // END OF DIFFERENCE

  // Pi mounting screws
  translate([wall+24,wall+d-7.5,wall_t]) {
    //screw_pimount();
    translate([58,0,0]) screw_pimount();
    translate([58,-49,0]) screw_pimount();
    translate([0,-49,0]) screw_pimount();
    translate([0,0,0]) screw_pimount();
  }
  // Pi right edge stop
  translate([wall+w-10, wall+d-60, wall_t]) {
    difference() {
      cube_fillet([10,60,4], vertical=[0,0,10/2], $fn=24);
      // Pi B+ microsd gap
      translate([-e,22,-e]) cube_fillet([6,14,4+2*e], vertical=[2,0,0,2], $fn=20);
    }
  }

  // close nut traps
  translate([wall+inch(0.8)+1.3,wall+2.85,0]) {
    nuttrap();
    translate([inch(2.0),0,0]) nuttrap();
  }

  // Top locklip (negative)
  if (Pi_Model == "3B/2B/1B+")
    locklip_top_n(case_split);
  else
    locklip_top_n(probe_centerline);

  // LCD mount
  difference() {
    union() {
      // Filled block above LCD hole
      translate([wall, wall+d-17, h_b+wall_t-lcd_mount_t])
        cube_fillet([w,17,lcd_mount_t+e],
          bottom=[lcd_mount_t/2,lcd_mount_t/2,0,lcd_mount_t/2]);
      // LCD grab notch
      translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t])
        translate([(77.5-20)/2,33.5-wall_t,-(1.8+wall_t)])
          // 1.8=thickness of LCD pcb
          cube_fillet([20,1.8+wall_t+wall,1.8+wall_t+e], top=[0,0,1.8+wall_t+e],
            vertical=[wall/2,wall/2]);
    }
    translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t-e]) lcd_neg();
    translate([wall+10.7, wall+52, h_b+wall_t-e]) lcd_mount();
  }

  if (Pi_Model == "3B/2B/1B+" || Pi_Model == "1A+")
    translate([wall-pic_ex, wall, wall_t]) {
      // USB pillar reinforcements
      translate([0, (44.75+62.75)/2-1, 3.5])
        cube_fillet([pic_ex+1, 2, h_b-3.5], bottom=[0,0,0,2]);
      translate([0, 81.5-15/2-3, 3.5])
        cube_fillet([pic_ex+1, 2.5, h_b-3.5], bottom=[0,0,0,2]);
      // Near pic_ex fill
      translate([0, 33.75, 0]) cube([pic_ex, 3, h_b]);
      // Far pic_ex fill (near ethernet)
      translate([0, d-5, 3.5])
       cube_fillet([pic_ex+1, 5+e, h_b-3.5], bottom=[0,0,0,2]);
    }
}

module lip_guide(l) {
  guide_offset = 0.2; // how much to recess the edge guides
  guide_h = 1.2; // how tall above the edge to extend
  linear_extrude(height=l) polygon([
    [0,-e], [wall+guide_offset,-e], [0, wall+guide_offset],
    [-guide_h, wall+guide_offset], [-guide_h, guide_offset],
    [0, guide_offset]
  ]);
}

module hm43_bottom_lips(split) {
  translate([wall, wall, split+wall_t]) {
    // bottom locklip (positive)
    translate([0,d,0]) {
      locklip_p(28, insert_inset=[2,0]);
      translate([w-34,0,0]) locklip_p(34-wall, insert_inset=[0,1]);
    }

    // front guide lip
    translate([w/2-9, 0, 0])
      rotate([0,90]) lip_guide(25);
    // left guide lip assortment
    translate([0, 5.25+9.4/2, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(25.25-5.25-9.4/2-16.7/2);
    *translate([-pic_ex, 52, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(3.5);
    *translate([-pic_ex, 70, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(3.5);
  }
  // probe side guide lip
  translate([wall, wall, probe_centerline+wall_t])
    translate([w, d-45, 0])
      rotate([-90]) rotate(90) lip_guide(40);
}

module split_volume() {
  if (Pi_Model == "3B/2B/1B+") {
    difference() {
      translate([-pic_ex-e,-e,-e])
        cube([w+pic_ex+2*wall+2*e, d+2*wall+2*e, wall_t+case_split+2*e]);
      translate([w,0,wall_t+probe_centerline])
        cube([3*wall, d+2*wall+2*e, wall_t+probe_centerline+2*e]);
    }
  }
  else
    translate([-w,-d,-e])
      cube([3*w, 3*d, wall_t+probe_centerline+2*e]);
}

module hm43_split() {
  // bottom
  if (Pieces != "Top") {
    intersection() {
      hm43();
      split_volume();
    }
    if (Pi_Model == "3B/2B/1B+")
      hm43_bottom_lips(case_split);
    else
      hm43_bottom_lips(probe_centerline);
  } // if include bottom

  // top
  if (Pieces != "Bottom") {
    translate([0,-2,h_b+2*wall_t]) rotate([180]) difference() {
      hm43();
      split_volume();
    }
  }  // if include top
}

/**********************                               **********************/
/********************** END OF CASE / LIBRARY FOLLOWS **********************/
/**********************                               **********************/

module cube_bchamfer(dim, r, top=0, bottom=0, $fn=$fn) {
  // bottom beaded area
  if (bottom != 0) hull(){
    translate([r,r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
  }
  // center
  translate([0,0,bottom]) hull(){
    translate([r,r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
  }
  // top beaded area
  if (top != 0) translate([0,0,dim[2]-top]) hull(){
    translate([r,r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
  }
}

module fillet(radius, height=100, $fn=$fn) {
  if (radius > 0) {
    //this creates acutal fillet
    translate([-radius, -radius, -height / 2 - 0.02]) difference() {
        cube([radius * 2, radius * 2, height + 0.04]);
        if ($fn == 0 && (radius == 2 || radius == 3 || radius == 4)) {
            cylinder(r=radius, h=height + 0.04, $fn=4 * radius);
        } else {
            cylinder(r=radius, h=height + 0.04, $fn=$fn);
        }

    }
  }
}

module cube_fillet(size, radius=-1, vertical=[0,0,0,0], top=[0,0,0,0], bottom=[0,0,0,0], center=false, $fn=4){
    if (center) {
        cube_fillet_inside(size, radius, vertical, top, bottom, $fn);
    } else {
        translate([size[0]/2, size[1]/2, size[2]/2])
            cube_fillet_inside(size, radius, vertical, top, bottom, $fn);
    }
}

module cube_negative_fillet(size, radius=-1, vertical=[3,3,3,3], top=[0,0,0,0], bottom=[0,0,0,0], $fn=$fn){
    j=[1,0,1,0];

    for (i=[0:3]) {
        if (radius > -1) {
            rotate([0, 0, 90*i]) translate([size[1-j[i]]/2, size[j[i]]/2, 0]) fillet(radius, size[2], $fn);
        } else {
            rotate([0, 0, 90*i]) translate([size[1-j[i]]/2, size[j[i]]/2, 0]) fillet(vertical[i], size[2], $fn);
        }
        rotate([90*i, -90, 0]) translate([size[2]/2, size[j[i]]/2, 0 ]) fillet(top[i], size[1-j[i]], $fn);
        rotate([90*(4-i), 90, 0]) translate([size[2]/2, size[j[i]]/2, 0]) fillet(bottom[i], size[1-j[i]], $fn);

    }
}

module cube_fillet_inside(size, radius=-1, vertical=[3,3,3,3], top=[0,0,0,0], bottom=[0,0,0,0], $fn=$fn){
    //makes CENTERED cube with round corners
    // if you give it radius, it will fillet vertical corners.
    //othervise use vertical, top, bottom arrays
    //when viewed from top, it starts in upper right corner (+x,+y quadrant) , goes counterclockwise
    //top/bottom fillet starts in direction of Y axis and goes CCW too

    if (radius == 0) {
        cube(size, center=true);
    } else {
        difference() {
            cube(size, center=true);
            cube_negative_fillet(size, radius, vertical, top, bottom, $fn);
        }
    }
}

//cube_fillet([10,10,10]);

// preview[view:northeast, tilt:topdiagonal]

/* [Options] */
// Pit probe type
Control_Probe = "Thermocouple"; // [Thermocouple,Thermistor]
// Raspberry Pi Model
Pi_Model = "3B/2B/1B+"; // [3B/2B/1B+,Connectorless,1A+,Zero]
// Which case halves
Pieces = "Both"; // [Both,Top,Bottom]
/* [Advanced] */
// Thickness of side walls
wall = 3;
// Thickness of top and bottom faces
wall_t = 2;
// External corner radius on the body
body_corner_radius = 4;
// Height of the body edge bead chamfer
body_chamfer_height = 1.5;

/* [Hidden] */
w = inch(3.75)+0.5; // overall interior case width
d = inch(3.75)-0.0; // overall interior case depth
h_b = 32; // overall interior case height

probe_centerline = 9.0; // case is split along probe centerline on the probe side
case_split = 12.4;  // and the case split on the other 3 sides

pic_ex = 2;
lcd_mount_t = 7;

body_chamfer_height_t = body_chamfer_height;
body_chamfer_height_b = body_chamfer_height;

e = 0.01;

hm43_split();

function inch(x) = x*25.4;

module cube_fillet_chamfer(size,f,c,$fn=32) {
  hull() {
    translate([f,f,c]) linear_extrude(height=size[2]-2*c) minkowski() {
      square([size[0]-2*f, size[1]-2*f]);
      circle(r=f, $fn=$fn);
    }
    translate([f+c,f+c,0]) linear_extrude(height=size[2]) minkowski() {
      square([size[0]-2*(f+c), size[1]-2*(f+c)]);
      circle(r=f, $fn=$fn);
    }
  }
}

module jhole(d, h) {
  translate([0,0,h/2]) cube([2*(wall+pic_ex),d,h], center=true);
}

module phole() {
  dia=4.5;
  // Hole for thermistor probe jack
  rotate([0,90,0]) cylinder(2*wall, d=dia, $fn=16);
  // because 3d print tends to curl up slightly between the probe holes,
  // do not go right to the edge on the final layer (stop 0.25mm short on each side)
  translate([0,-(dia+0.5)/2, -0.3]) cube([2*wall, dia+0.5, 0.6]);
}

module screwhole() {
  // Screw hole for bottom of case (including nut)
  //translate([0,0,-e]) cylinder(h_b+wall_t, d=3.4, $fn=18);
  translate([0,0,-e]) {
    cylinder(0.4, d1=7.4, d2=6.6, $fn=6);
    translate([0,0,0.4]) cylinder(3, d=6.6, $fn=6);
  }
  translate([0,0,3+0.2]) cylinder(h_b+2*wall_t, d=3.4, $fn=18);
  translate([0,0,h_b+wall_t]) cylinder(3, d=5.5, $fn=18);
}

module screwhole2() {
  translate([0,0,-e]) cylinder(3, d=6, $fn=18);
  translate([0,0,3+0.4]) cylinder(h_b-3-0.4, d=3.4, $fn=18);
}

module pic_ex_cube() {
  translate([0,33.75,2])
    cube_fillet([pic_ex+e, 59.5, 20.8],
      vertical=[0, pic_ex, pic_ex, 0],
      top=[0,pic_ex,0,0],
      bottom=[0,pic_ex,0,0]);
}

module screw_pimount() {
  // 2mm height is good for screw holes
  difference() {
    cylinder(h=2.25, d=6.4, $fn=18);
    //cylinder(h=2+e, d=2.5, $fn=16);
  }
}

module btn_rnd(dia=10) {
  cylinder(wall_t+2*e, d1=dia, d2=dia+1.5*wall_t, $fn=24);
  translate([-6.5,-6.5,0]) cube([13,13,0.75+e]);
}

module btn_square(sq=13) {
  translate([-sq/2,-sq/2,0]) cube([sq,sq,wall_t+2*e]);
}

module tc_plusminus() {
  T=0.75+e;
  H=2;
  W=6;
  // Minus
  translate([0,2,0])
    cube_fillet([T, W, H], top=[0,0,0,T]);
  translate([0,-(2+W),0]) {
    cube_fillet([T, W, H], top=[0,0,0,T]);
    translate([0,(W-H)/2,(H-W)/2])
      cube_fillet([T, H, W], top=[0,0,0,T]);
  }
}

module led_hole() {
  cylinder(wall_t+2*e, d=3.4, $fn=16);
}

module nuttrap() {
  ww_w=3;
  ww_d=2.7;
  nut_h=2.8;
  nut_ingress = 5.8; //nut_d * sin(60);
  nut_d = nut_ingress / sin(60);
  oa_h=wall_t+0.4+nut_h+wall_t+6.7;

  // bottom half for M3 socket cap screw (flush)
  difference() {
    translate([-5.5/2-ww_w, -nut_ingress/2-e, 0])
      cube_fillet([5.5+2*ww_w, nut_ingress+ww_d*0.8+e, 6], vertical=[3.4,3.4], $fn=20);
    // socket cap
    translate([0,0,-e]) cylinder(3.5, d=6, $fn=18);
    // screw shaft
    translate([0,0,3.5+0.3]) cylinder(oa_h-3, d=3.4, $fn=18);
  }

  // top half M3 nut trap
  translate([0,0,h_b+wall_t-oa_h])
  difference() {
    translate([-(nut_d/2+ww_w), -nut_ingress/2-e, 0])
      cube_fillet([nut_d+2*ww_w, nut_ingress+ww_d+e, oa_h],
        vertical=[3.4,3.4], $fn=20);
    // M3 screw
    translate([0,0,-e]) cylinder(wall_t, d1=4, d2=3.4, $fn=16);
    // nut hole / M3 extra
    translate([0,0,wall_t+0.3]) {
      translate([-0.4,0,0]) // nut offset slightly deeper for easier alignment
        cylinder(nut_h*1.5+e, d=nut_d, $fn=6);  // nut
      cylinder(oa_h-wall_t-0.3, d=4, $fn=16);  // M3 with plenty of clearance
    }
    // nut ingress
    translate([0,-nut_ingress/2,wall_t+0.3])
      cube([nut_d/2+ww_w+e,nut_ingress,nut_h+e]);
  }
}

module locklip_p(l, l_offset=1,
  lip_insert_depth=2.0, // how deep V to insert
  lip_v_off=0.2, // extra height before starting insert
  lip_h_off=0.4, // back connector away from mating area
  lip_w=2.5  // thickness of attachment beam
  ) {
  translate([l_offset,0,0]) rotate([90,0,0]) rotate([0,90,0])
    linear_extrude(height=l-2*l_offset) polygon(points=[
      [0, -lip_w-lip_insert_depth-lip_h_off],
      [0, 0],
      [-lip_insert_depth-lip_h_off, 0],
      [-lip_insert_depth-lip_h_off, lip_v_off],
      [-lip_h_off, lip_v_off+lip_insert_depth],
      [-lip_insert_depth-lip_h_off, lip_v_off+2*lip_insert_depth,],
      [-lip_w-lip_insert_depth-lip_h_off, lip_v_off+2*lip_insert_depth],
      [-lip_w-lip_insert_depth-lip_h_off, 0]
    ]);
}

module locklip_n(l, l_offset=1,
  lip_insert_depth=2.2, // how deep V to insert
  lip_tip_clip=0.3, // how much to shave off the top tip
  ) {
  translate([l-l_offset,0,0]) rotate([90,0,0]) rotate([0,-90,0])
    linear_extrude(height=l-2*l_offset) polygon(points=[
      [0.1, -3*lip_insert_depth],  // 0.1 to add depth to keep extrusion manifold
      [0.1, -lip_tip_clip],
      [-lip_insert_depth+lip_tip_clip, -lip_tip_clip],
      [-e, -lip_insert_depth],
      [-lip_insert_depth, -2*lip_insert_depth]
    ]);
}

module lcd_screw() {
  rotate([180]) difference() {
    //cylinder(lcd_mount_t, d=2.5+3.2, $fn=16);
    cylinder(lcd_mount_t+e, d=2.5, $fn=16);
  }
}

module lcd_mount() {
  // Assuming starting at bottom left screw hole
  //lcd_screw();
  //translate([75,0,0]) lcd_screw(); // bottom right hole obscured by PCB
  translate([0,31,0]) lcd_screw();
  translate([75,31,0]) lcd_screw();
}

module lcd_neg() {
  // Assuming starting at bottom left screw hole
  translate([1.0, 2.1, 0])
    cube([73.0, 26.3, lcd_mount_t+wall_t-0.8]); // black bezel inset
  translate([5.5, 7.8, 0])
    cube([64.0, 15.4, lcd_mount_t+wall_t+2*e]); // LCD active area
  translate([4.23, -2.5, 0])
    cube([16*2.54, 5, lcd_mount_t+wall_t-0.8]); // pins cutout
}

module locklip_top_n(split) {
  translate([wall, d+wall, split+wall_t+e])
    rotate([180]) {
      locklip_n(28);
      translate([w-34,0,0]) locklip_n(34);
    }
}

module hm43() {
difference() {
  union() {
    cube_bchamfer([w+2*wall, d+2*wall, h_b+2*wall_t],
      r=body_corner_radius, top=body_chamfer_height_t,
      bottom=body_chamfer_height_b, $fn=36);
    // extra thick by Pi connectors
    if (Pi_Model != "Zero")
      translate([-pic_ex,wall,wall_t])
        pic_ex_cube();
    // TC +/-
    if (Control_Probe == "Thermocouple")
      translate([w+wall*2-e,wall+10,wall_t+18]) tc_plusminus();
  }

  // Main cutout
  translate([wall, wall, wall_t])
    cube_fillet([w, d, h_b+e], bottom=[2,2,2,2],
    top=[lcd_mount_t/2,lcd_mount_t/2,lcd_mount_t/2,lcd_mount_t/2],
    vertical=[1,1]);
  if (Pi_Model != "Zero")
    translate([wall-pic_ex+e,wall,wall_t]) pic_ex_cube();

  // Probe jack side
  translate([w+wall*0.5,wall,wall_t]) {
    // Probe jacks
    translate([0,24.25,probe_centerline]) {
      if (Control_Probe == "Thermocouple")
        // TC jack
        translate([0,inch(-0.55)-16.5/2,-1.4]) cube([2*wall, 16.5, 6.5]);
      else
        translate([0,-17.25,0]) phole();
      translate([0,inch(0.37)*0,0]) phole();
      translate([0,inch(0.37)*1,0]) phole();
      translate([0,inch(0.37)*2,0]) phole();
    }
  }
  // Pi connector side
  translate([wall*-0.5,wall,wall_t]) {
    // Pi connectors
    translate([0,0,5]) {
      if (Pi_Model == "3B/2B/1B+") {
        // ethernet
        translate([0,81.5,-0.5]) jhole(15,13);
        translate([0,81.5,-1.5]) jhole(5,5);
        // USB 0+1
        translate([0,62.75,0]) jhole(13,14.8);
        translate([0,44.75,0]) jhole(13,14.8);
      }
      if (Pi_Model == "1A+")
        translate([0,44.75,0]) jhole(13,7.4);
    }
    // HeaterMeter connectors
    translate([0,0,0]) {
      // Blower/Servo output
      translate([0,25.25,1.1]) jhole(16.7,13);
      // HM power jack
      translate([0,5.25,3.4]) jhole(9.4,11);
    }
  }

  // lcd hole
  translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t-e]) lcd_neg();

  // button holes
  translate([wall+48.7, wall+29.4, h_b+wall_t-e]) {
    translate([-inch(1.1)/2,0,0]) btn_rnd(7.2);  // left
    translate([inch(1.1)/2,0,0]) btn_rnd(7.2);   // right
    translate([0,inch(0.9)/2,0]) btn_rnd(7.2);   // up
    translate([0,-inch(0.9)/2,0]) btn_rnd(7.2);  // down
    // LED holes
    translate([inch(1.3), inch(-0.05), 0]) {
      led_hole();  //red
      translate([0, inch(0.35), 0]) led_hole(); //yellow
      translate([0, inch(0.70), 0]) led_hole(); //green
    }
  }
  // close screw holes
  translate([wall+inch(0.8)+1.3,wall+2.9,0]) {
    screwhole2();
    translate([inch(2.0),0,0]) screwhole2();
  }
}  // END OF DIFFERENCE

  // Pi mounting screws
  translate([wall+24,wall+d-7.5,wall_t]) {
    //screw_pimount();
    translate([58,0,0]) screw_pimount();
    translate([58,-49,0]) screw_pimount();
    translate([0,-49,0]) screw_pimount();
    translate([0,0,0]) screw_pimount();
  }
  // Pi right edge stop
  translate([wall+w-10, wall+d-60, wall_t]) {
    difference() {
      cube_fillet([10,60,4], vertical=[0,0,10/2], $fn=24);
      // Pi B+ microsd gap
      translate([-e,22.5,-e]) cube_fillet([6,14,4+2*e], vertical=[2,0,0,2], $fn=20);
    }
  }

  // close nut traps
  translate([wall+inch(0.8)+1.3,wall+2.9,0]) {
    nuttrap();
    translate([inch(2.0),0,0]) nuttrap();
  }

  // Top locklip (negative)
  if (Pi_Model == "3B/2B/1B+")
    locklip_top_n(case_split);
  else
    locklip_top_n(probe_centerline);

  // LCD mount
  difference() {
    union() {
      // Filled block above LCD hole
      translate([wall, wall+d-17, h_b+wall_t-lcd_mount_t]) cube([w,17,lcd_mount_t+e]);
      // LCD grab notch
      translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t])
        translate([(77.5-20)/2,33.5-wall_t,-(1.8+wall_t)])
          // 1.8=thickness of LCD pcb
          cube_fillet([20,1.8+wall_t+wall,1.8+wall_t+e], top=[0,0,1.8+wall_t+e],
            vertical=[wall/2,wall/2]);
    }
    translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t-e]) lcd_neg();
    translate([wall+10.7, wall+52, h_b+wall_t-e]) lcd_mount();
  }

  if (Pi_Model == "3B/2B/1B+" || Pi_Model == "1A+")
    translate([wall-pic_ex, wall, wall_t]) {
      // USB pillar reinforcements
      translate([0, (44.75+62.75)/2-1, 3.5])
        cube_fillet([pic_ex+1, 2, h_b-3.5], bottom=[0,0,0,2]);
      translate([0, 81.5-15/2-3, 3.5])
        cube_fillet([pic_ex+1, 2.5, h_b-3.5], bottom=[0,0,0,2]);
      // Near pic_ex fill
      translate([0, 33.75, 0]) cube([pic_ex, 3, h_b]);
      // Far pic_ex fill (near ethernet)
      translate([0, d-5, 3.5])
       cube_fillet([pic_ex+1, 5+e, h_b-3.5], bottom=[0,0,0,2]);
    }
}

module lip_guide(l) {
  guide_offset = 0.2; // how much to recess the edge guides
  guide_h = 1.2; // how tall above the edge to extend
  linear_extrude(height=l) polygon([
    [0,-e], [wall+guide_offset,-e], [0, wall+guide_offset],
    [-guide_h, wall+guide_offset], [-guide_h, guide_offset],
    [0, guide_offset]
  ]);
}

module hm43_bottom_lips(split) {
  translate([wall, wall, split+wall_t]) {
    // bottom locklip (positive)
    translate([0,d,0]) {
      locklip_p(28);
      translate([w-34,0,0]) locklip_p(34-wall+1);
    }

    // front guide lip
    translate([w/2-9, 0, 0])
      rotate([0,90]) lip_guide(25);
    // left guide lip assortment
    translate([0, 5.25+9.4/2, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(25.25-5.25-9.4/2-16.7/2);
    *translate([-pic_ex, 52, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(3.5);
    *translate([-pic_ex, 70, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(3.5);
  }
  // probe side guide lip
  translate([wall, wall, probe_centerline+wall_t])
    translate([w, d-45, 0])
      rotate([-90]) rotate(90) lip_guide(40);
}

module split_volume() {
  if (Pi_Model == "3B/2B/1B+") {
    difference() {
      translate([-pic_ex-e,-e,-e])
        cube([w+pic_ex+2*wall+2*e, d+2*wall+2*e, wall_t+case_split+2*e]);
      translate([w,0,wall_t+probe_centerline])
        cube([3*wall, d+2*wall+2*e, wall_t+probe_centerline+2*e]);
    }
  }
  else
    translate([-w,-d,-e])
      cube([3*w, 3*d, wall_t+probe_centerline+2*e]);
}

module hm43_split() {
  // bottom
  if (Pieces != "Top") {
    intersection() {
      hm43();
      split_volume();
    }
    if (Pi_Model == "3B/2B/1B+")
      hm43_bottom_lips(case_split);
    else
      hm43_bottom_lips(probe_centerline);
  } // if include bottom

  // top
  if (Pieces != "Bottom") {
    translate([0,-2,h_b+2*wall_t]) rotate([180]) difference() {
      hm43();
      split_volume();
    }
  }  // if include top
}

/**********************                               **********************/
/********************** END OF CASE / LIBRARY FOLLOWS **********************/
/**********************                               **********************/

module cube_bchamfer(dim, r, top=0, bottom=0, $fn=$fn) {
  // bottom beaded area
  if (bottom != 0) hull(){
    translate([r,r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
  }
  // center
  translate([0,0,bottom]) hull(){
    translate([r,r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
  }
  // top beaded area
  if (top != 0) translate([0,0,dim[2]-top]) hull(){
    translate([r,r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
  }
}

module fillet(radius, height=100, $fn=$fn) {
  if (radius > 0) {
    //this creates acutal fillet
    translate([-radius, -radius, -height / 2 - 0.02]) difference() {
        cube([radius * 2, radius * 2, height + 0.04]);
        if ($fn == 0 && (radius == 2 || radius == 3 || radius == 4)) {
            cylinder(r=radius, h=height + 0.04, $fn=4 * radius);
        } else {
            cylinder(r=radius, h=height + 0.04, $fn=$fn);
        }

    }
  }
}

module cube_fillet(size, radius=-1, vertical=[0,0,0,0], top=[0,0,0,0], bottom=[0,0,0,0], center=false, $fn=4){
    if (center) {
        cube_fillet_inside(size, radius, vertical, top, bottom, $fn);
    } else {
        translate([size[0]/2, size[1]/2, size[2]/2])
            cube_fillet_inside(size, radius, vertical, top, bottom, $fn);
    }
}

module cube_negative_fillet(size, radius=-1, vertical=[3,3,3,3], top=[0,0,0,0], bottom=[0,0,0,0], $fn=$fn){
    j=[1,0,1,0];

    for (i=[0:3]) {
        if (radius > -1) {
            rotate([0, 0, 90*i]) translate([size[1-j[i]]/2, size[j[i]]/2, 0]) fillet(radius, size[2], $fn);
        } else {
            rotate([0, 0, 90*i]) translate([size[1-j[i]]/2, size[j[i]]/2, 0]) fillet(vertical[i], size[2], $fn);
        }
        rotate([90*i, -90, 0]) translate([size[2]/2, size[j[i]]/2, 0 ]) fillet(top[i], size[1-j[i]], $fn);
        rotate([90*(4-i), 90, 0]) translate([size[2]/2, size[j[i]]/2, 0]) fillet(bottom[i], size[1-j[i]], $fn);

    }
}

module cube_fillet_inside(size, radius=-1, vertical=[3,3,3,3], top=[0,0,0,0], bottom=[0,0,0,0], $fn=$fn){
    //makes CENTERED cube with round corners
    // if you give it radius, it will fillet vertical corners.
    //othervise use vertical, top, bottom arrays
    //when viewed from top, it starts in upper right corner (+x,+y quadrant) , goes counterclockwise
    //top/bottom fillet starts in direction of Y axis and goes CCW too

    if (radius == 0) {
        cube(size, center=true);
    } else {
        difference() {
            cube(size, center=true);
            cube_negative_fillet(size, radius, vertical, top, bottom, $fn);
        }
    }
}

//cube_fillet([10,10,10]);


fix error in DXF generation

fix error in DXF generation

include <../config.scad>;

module FPVCameraMount()
{
  cam_angle_rotate_2d = [
       [cos(FPV_CAMERA_ANGLE), -sin(FPV_CAMERA_ANGLE)],
       [sin(FPV_CAMERA_ANGLE),  cos(FPV_CAMERA_ANGLE)]
      ];

  fpv_cam_pos_2d = [-FPV_CAMERA_POSITON[1], FPV_CAMERA_POSITON[2]];

  bracket_points = [
    [-CAMERA_BRACKET_TAB_OFFSET - CAMERA_BRACKET_TAB_WIDTH, 0],
    [-CAMERA_BRACKET_TAB_OFFSET - CAMERA_BRACKET_TAB_WIDTH, -MATERIAL_THICKNESS],
    [-CAMERA_BRACKET_TAB_OFFSET, -MATERIAL_THICKNESS],
    [-CAMERA_BRACKET_TAB_OFFSET, 0],
    [-CAMERA_BRACKET_LENGTH * 0.5, 0],
    [-CAMERA_BRACKET_LENGTH * 0.4, CAMERA_BRACKET_BASE_HEIGHT],
    ([0, CAMERA_BRACKET_LOW_CAM_POS] * cam_angle_rotate_2d) + fpv_cam_pos_2d,
    ([0, CAMERA_BRACKET_HIGH_CAM_POS] * cam_angle_rotate_2d) + fpv_cam_pos_2d,
    [CAMERA_BRACKET_LENGTH * 0.4, CAMERA_BRACKET_BASE_HEIGHT],
    [CAMERA_BRACKET_LENGTH * 0.5, 0],
    [CAMERA_BRACKET_TAB_OFFSET, 0],
    [CAMERA_BRACKET_TAB_OFFSET, -MATERIAL_THICKNESS],
    [CAMERA_BRACKET_TAB_OFFSET + CAMERA_BRACKET_TAB_WIDTH, -MATERIAL_THICKNESS],
    [CAMERA_BRACKET_TAB_OFFSET + CAMERA_BRACKET_TAB_WIDTH, 0],
  ];

  echo("Camera bracket points", bracket_points);

  difference()
  {
    union()
    {
      translate([0, 0.01])
        polygon(points=bracket_points, paths=[[0, 1, 2, 3, 10, 11, 12, 13]]);

      minkowski()
      {
        circle(r=1, $fn=32);

        offset(r=-1)
          polygon(points=bracket_points, paths=[[4, 5, 6, 7, 8, 9]]);
      }
    }

    union()
    {
      scale(0.6)
        polygon(points=bracket_points, paths=[[4, 5, 8, 9, 10]]);

      /* Mounting solts for FPV camera back panel */
      translate(fpv_cam_pos_2d - [0, HALF_MATERIAL_THICKNESS])
        rotate([0, 0, 90 - FPV_CAMERA_ANGLE])
          translate([0, -CAMERA_BRACKET_PANEL_OFFSET])
            square([CAMERA_BRACKET_PANEL_TAB_WIDTH + MACHINE_TOLERANCE, MATERIAL_THICKNESS + MACHINE_TOLERANCE], center=true);

      /* Hole for access to the set button on the Eachine FPV camera */
      translate(fpv_cam_pos_2d)
        rotate([0, 0, -FPV_CAMERA_ANGLE])
          translate([-1, -7.5])
            circle(d=2, $fn=16);
    }
  }
}

FPVCameraMount();

include <../config.scad>;

module FPVCameraMount()
{
  cam_angle_rotate_2d = [
       [cos(FPV_CAMERA_ANGLE), -sin(FPV_CAMERA_ANGLE)],
       [sin(FPV_CAMERA_ANGLE),  cos(FPV_CAMERA_ANGLE)]
      ];

  fpv_cam_pos_2d = [-FPV_CAMERA_POSITON[1], FPV_CAMERA_POSITON[2]];

  bracket_points = [
    [-CAMERA_BRACKET_TAB_OFFSET - CAMERA_BRACKET_TAB_WIDTH, 0],
    [-CAMERA_BRACKET_TAB_OFFSET - CAMERA_BRACKET_TAB_WIDTH, -MATERIAL_THICKNESS],
    [-CAMERA_BRACKET_TAB_OFFSET, -MATERIAL_THICKNESS],
    [-CAMERA_BRACKET_TAB_OFFSET, 0],
    [-CAMERA_BRACKET_LENGTH * 0.5, 0],
    [-CAMERA_BRACKET_LENGTH * 0.4, CAMERA_BRACKET_BASE_HEIGHT],
    ([0, CAMERA_BRACKET_LOW_CAM_POS] * cam_angle_rotate_2d) + fpv_cam_pos_2d,
    ([0, CAMERA_BRACKET_HIGH_CAM_POS] * cam_angle_rotate_2d) + fpv_cam_pos_2d,
    [CAMERA_BRACKET_LENGTH * 0.4, CAMERA_BRACKET_BASE_HEIGHT],
    [CAMERA_BRACKET_LENGTH * 0.5, 0],
    [CAMERA_BRACKET_TAB_OFFSET, 0],
    [CAMERA_BRACKET_TAB_OFFSET, -MATERIAL_THICKNESS],
    [CAMERA_BRACKET_TAB_OFFSET + CAMERA_BRACKET_TAB_WIDTH, -MATERIAL_THICKNESS],
    [CAMERA_BRACKET_TAB_OFFSET + CAMERA_BRACKET_TAB_WIDTH, 0],
  ];

  echo("Camera bracket points", bracket_points);

  difference()
  {
    union()
    {
      polygon(points=bracket_points, paths=[[0, 1, 2, 3, 4, 9, 10, 11, 12, 13]]);

      minkowski()
      {
        circle(r=1, $fn=32);

        offset(r=-1)
          polygon(points=bracket_points, paths=[[4, 5, 6, 7, 8, 9]]);
      }
    }

    union()
    {
      scale(0.6)
        polygon(points=bracket_points, paths=[[4, 5, 8, 9, 10]]);

      /* Mounting solts for FPV camera back panel */
      translate(fpv_cam_pos_2d - [0, HALF_MATERIAL_THICKNESS])
        rotate([0, 0, 90 - FPV_CAMERA_ANGLE])
          translate([0, -CAMERA_BRACKET_PANEL_OFFSET])
            square([CAMERA_BRACKET_PANEL_TAB_WIDTH + MACHINE_TOLERANCE, MATERIAL_THICKNESS + MACHINE_TOLERANCE], center=true);

      /* Hole for access to the set button on the Eachine FPV camera */
      translate(fpv_cam_pos_2d)
        rotate([0, 0, -FPV_CAMERA_ANGLE])
          translate([-1, -7.5])
            circle(d=2, $fn=16);
    }
  }
}

FPVCameraMount();


main: z axis range depends on hotend height

include <config.scad>

include <thing_libutils/system.scad>
include <thing_libutils/units.scad>
include <thing_libutils/materials.scad>

use <thing_libutils/screws.scad>
use <thing_libutils/shapes.scad>;
use <thing_libutils/misc.scad>;
use <thing_libutils/transforms.scad>;
use <thing_libutils/attach.scad>;
use <thing_libutils/linear-extrusion.scad>;
use <thing_libutils/timing-belts.scad>;

use <x-end.scad>
include <x-end.h>

use <x-carriage.scad>
include <x-carriage.h>

use <y-motor-mount.scad>
include <y-motor-mount.h>

use <y-carriage-bearing-mount.scad>
include <y-carriage-bearing-mount.h>

use <y-carriage-belt-clamp.scad>
include <y-carriage-belt-clamp.h>

use <y-idler.scad>
include <y-idler.h>

use <z-motor-mount.scad>
include <z-motor-mount.h>

use <gantry-upper-connector.scad>
use <gantry-lower-connector.scad>

use <rod-clamps.scad>
include <rod-clamps.h>

use <printbed.scad>
include <printbed.h>

/*use <psu.scad>*/
/*include <psu.h>*/

/*use <lcd2004.scad>*/
/*use <power-panel-iec320.scad>*/

// x carriage
axis_range_x_ = main_width/2 + zmotor_mount_rod_offset_x - xaxis_end_width(true) - x_carriage_w/2 - .5*mm;
axis_range_x = [-1,1] *  axis_range_x_;
axis_printrange_x = [-1/2, 1/2] * printbed_size[0];
axis_x_parked = [false, true];

axis_range_y=[0*mm,200*mm];
axis_pos_y = -axis_range_y[1]/2-30*mm;
axis_range_z=[-20+26*mm+hotend_height,353*mm];
axis_pos_z = axis_range_z[0];

echo(str("Axis range X: " , axis_range_x[0], " ", axis_range_x[1]," mm"));
echo(str("Axis range Y: " , axis_range_y[0], " ", axis_range_y[1]," mm"));
echo(str("Axis range Z: " , axis_range_z[0], " ", axis_range_z[1]," mm"));

echo(str("Build area Z: " , axis_range_z[1]-axis_range_z[0] , " mm"));

home_offset_x0 = axis_range_x[0] - axis_printrange_x[0] + v_x(extruder_filapath_offset)[0];
echo(str("Home offset X0: " , home_offset_x0));
echo(str("Tool offset X: " , axis_range_x_+x_carriage_w/2));

module x_axis()
{
    // x axis
    translate([0,0,axis_pos_z])
    {
        zrod_offset = zmotor_mount_rod_offset_x;
        for(z=xaxis_beltpath_z_offsets)
        tz(z)
        ty(xaxis_zaxis_distance_y)
        tx(-sign(z)*(-main_width/2-zrod_offset+xaxis_end_motor_offset[0]))
        rotate(90*X)
        belt_path(
            len=main_width+xaxis_end_motor_offset[0],
            belt_width=xaxis_belt_width,
            belt=xaxis_belt,
            pulley_d=xaxis_pulley_inner_d,
            orient=X, align=-sign(z)*X);

        for(x=[0:len(axis_range_x)-1])
        {
            /*#cubea(size=[x_carriage_w,10,100]);*/

            pos = axis_x_parked[x] ? axis_range_x[x] : -axis_printrange_x[x];

            translate([pos,0,0])
            mirror([x==0?0:1,0,0])
            attach(xaxis_carriage_conn, [[0,-xaxis_zaxis_distance_y,0],N])
            {
                x_carriage_withmounts(show_vitamins=true, beltpath_offset=x==0?-1:1, with_sensormount=x==0);

                x_carriage_extruder(show_vitamins=true);
            }
        }

        // x smooth rods
        for(z=[-1,1])
        translate([xaxis_rod_offset_x,xaxis_zaxis_distance_y,z*(xaxis_rod_distance/2)])
        material(Mat_Chrome)
        cylindera(h=xaxis_rod_l,d=xaxis_rod_d+.1, orient=X);

        for(x=[-1,1])
        translate([x*(main_width/2), 0, 0])
        translate([0, xaxis_zaxis_distance_y, 0])
        translate([x*zmotor_mount_rod_offset_x, 0, 0])
        mirror([max(0,x),0,0])
        {
            xaxis_end(with_motor=true, beltpath_index=max(0,x), show_nut=true, show_motor=true, show_nut=true);

            xaxis_end_bucket();
        }
    }
}

module y_axis()
{
    // y axis
    attach([[yaxis_belt_path_offset_x,main_depth/2+extrusion_size,yaxis_motor_offset_z], Y], yaxis_motor_mount_conn)
    yaxis_motor_mount(show_motor=true);

    extrusion_idler_conn = [[yaxis_belt_path_offset_x, -main_depth/2-extrusion_size, -extrusion_size+yaxis_idler_offset_z], [0,-1,0]];
    attach(extrusion_idler_conn, yaxis_idler_conn)
    {
        yaxis_idler();

        attach(yaxis_idler_conn_pulleyblock, yaxis_idler_pulleyblock_conn)
        yaxis_idler_pulleyblock(show_pulley=true);
    }

    translate([yaxis_belt_path_offset_x,0,yaxis_belt_path_offset_z])
    translate([0,main_depth/2+extrusion_size+yaxis_motor_offset_x,0])
    rotate(90*Y)
    belt_path(
        len=main_depth+yaxis_motor_offset_x-yaxis_idler_pulley_offset_y+extrusion_size,
        belt_width=yaxis_belt_width,
        pulley_d=yaxis_pulley_inner_d,
        belt=yaxis_belt,
        align=-Y, orient=Y);

    translate([0,-axis_pos_y,get(LinearBearingInnerDiameter, yaxis_bearing)/2])
    attach(yaxis_carriage_bearing_mount_conn_bearing, yaxis_belt_mount_conn)
    yaxis_belt_holder();

    translate([0,0,get(LinearBearingInnerDiameter, yaxis_bearing)/2])
    {
        // y smooth rod clamps to frame
        for(x=[-1,1])
        for(y=[-1,1])
        attach([[x*(yaxis_rod_distance/2),y*(main_depth/2+extrusion_size/2),0],Z],mount_rod_clamp_conn_rod)
        mount_rod_clamp_full(rod_d=zaxis_rod_d, thick=4, width=extrusion_size, thread=extrusion_thread, orient=Y);

        for(x=[-1,1])
        translate([x*(yaxis_rod_distance/2), 0, 0])
        {
            // y smooth rods
            material(Mat_Chrome)
            cylindera(h=yaxis_rod_l,d=yaxis_rod_d, orient=Y);

            // y bearing mounts
            for(y=[-1,1])
            attach([[0,y*(yaxis_bearing_distance_y/2)-axis_pos_y,0],[0,0,-1]], yaxis_carriage_bearing_mount_conn_bearing)
            {
                yaxis_carriage_bearing_mount();
            }
        }

        attach(yaxis_carriage_bearing_mount_conn_bearing, [[0,axis_pos_y,0],Z])
        {
            yaxis_carriage_size_inner = [32*mm,32*mm,0];

            material(Mat_PrintCarriage)
            {
                // y axis carriage plate "arms"
                for(x=[-1,1])
                for(y=[-1,1])
                hull()
                {
                    translate([x*yaxis_carriage_size[0]/2, y*yaxis_carriage_size[1]/2, 0])
                    cylindera(d=10*mm, h=yaxis_carriage_size[2], align=[-x,-y,1]);

                    translate([x*(yaxis_carriage_size[0]/2-yaxis_carriage_size_inner[0]/2), y*(yaxis_carriage_size[1]/2-yaxis_carriage_size_inner[1]/2*mm), 0])
                    cylindera(d=10*mm, h=yaxis_carriage_size[2], align=[-x,-y,1]);
                }

                // y axis carriage plate
                hull()
                for(x=[-1,1])
                for(y=[-1,1])
                {
                    translate([x*(yaxis_carriage_size[0]/2-yaxis_carriage_size_inner[0]/2), y*(yaxis_carriage_size[1]/2-yaxis_carriage_size_inner[1]/2*mm), 0])
                    cylindera(d=10*mm, h=yaxis_carriage_size[2], align=[-x,-y,1]);
                }
            }

            // y axis plate
            material(Mat_PrintBed)
            translate([0,0,10*mm])
            cubea(printbed_size, align=Z);
        }
    }

}

module z_axis()
{
    // z axis
    for(x=[-1,1])
    {
        translate([x*(main_width/2),0,main_height])
        mirror([x==-1?1:0,0,0])
        translate([zmotor_mount_rod_offset_x, 0, extrusion_size/2])
        mount_rod_clamp_half(
            rod_d=zaxis_rod_d,
            screw_dist=zmotor_mount_clamp_dist,
            thick=5,
            base_thick=5,
            width=zmotor_mount_thickness_h,
            thread=zmotor_mount_clamp_thread);

        tx(x*(main_width/2))
        {
            tz(zaxis_motor_offset_z)
            mirror([x==-1?1:0,0,0])
            {
                zaxis_motor_mount(show_motor=true);

                tx(zmotor_mount_rod_offset_x)
                tz(zmotor_mount_thickness_h/2)
                mount_rod_clamp_half(
                    rod_d=zaxis_rod_d,
                    screw_dist=zmotor_mount_clamp_dist,
                    thick=5,
                    base_thick=5,
                    width=zmotor_mount_thickness_h,
                    thread=zmotor_mount_clamp_thread);
            }

            // z smooth rods
            tx(x*(zmotor_mount_rod_offset_x))
            tz(zaxis_motor_offset_z-80*mm)
            material(Mat_Chrome)
            cylindera(h=zaxis_rod_l,d=zaxis_rod_d, align=Z);
        }
    }

}

module gantry_upper()
{
    for(y=[-1,1])
    translate([0,y*(main_upper_dist_y/2),0])
    {
        for(x=[-1,1])
        translate([x*(main_width/2), 0, 0])
        linear_extrusion(h=main_height, align=[-x,0,1], orient=Z);

        translate([0, 0, main_height])
        linear_extrusion(h=main_upper_width, align=Z, orient=X);
    }

    // upper gantry connectors
    for(x=[-1,1])
    translate([x*(main_width/2),0,main_height])
    mirror([x==-1?1:0,0,0])
    gantry_upper_connector();

}

module gantry_lower()
{
    for(z=[-1,1])
    translate([0,0,z*-main_lower_dist_z/2])
    {
        for(y=[-1,1])
        translate([0, y*(main_depth/2), 0])
        linear_extrusion(h=main_width, align=[0,y,-1], orient=X);

        for(x=[-1,1])
        translate([x*(main_width/2), 0, 0])
        linear_extrusion(h=main_depth, align=-x*X-Z, orient=Y);
    }

    // lower gantry connectors
    for(x=[-1,1])
    for(y=[-1,1])
    translate([x*(main_width/2),y*(main_depth/2),-extrusion_size/2])
    mirror([x==1?0:-1,0,0])
    mirror([0,y==1?1:0,0])
    gantry_lower_connector();

}

module enclosure()
{
    // inner
    w=56*cm;
    h=56*cm;
    d=58*cm;
    wallthick=2*cm;
    backthick=0.5*cm;

    translate([0, 0, h/2+wallthick])
    {
        // left/right walls
        for(x=[-1,1])
        translate([x*w/2,0,0])
        cubea([wallthick,d,h], align=[x, 0, 0]);

        // back plate
        translate([0,d/2,0])
        cubea([w,backthick,h], align=[0,-1, 0]);

        // top/bottom plate
        for(z=[-1,1])
        translate([0,0,z*h/2])
        cubea([w+wallthick*2,d,wallthick], align=[0,0, z]);
    }
}

module main()
{
    translate([0,0,-main_lower_dist_z/2])
    gantry_lower();

    translate(-zaxis_rod_offset)
    gantry_upper();

    x_axis();
    y_axis();
    z_axis();

    translate([-75*mm,0,0])
    translate([0,main_depth/2,0])
    translate([0,extrusion_size,0])
    translate([0,0,-main_lower_dist_z/2-extrusion_size/2])
    power_panel_iec320(orient=[0,-1,0], align=Y);

    if(!$preview_mode)
    {
        translate([0,-main_depth/2,0])
        translate([0,-extrusion_size/2,0])
        translate([0,0,-main_lower_dist_z/2])
        mount_lcd2004(show_gantry=true);

        for(x=[-1,1])
        translate([x*(main_width/2-extrusion_size),main_depth/2,-main_lower_dist_z-extrusion_size])
        translate([-x*psu_a_w/2, -psu_a_d/2, psu_a_h/2])
        {
            psu_a();

            mirror([x==-1?1:0,0,0])
            translate([0, -psu_a_screw_dist_y/2, 0])
            psu_a_extrusion_bracket_side();

            translate([0, psu_a_screw_dist_y/2, 0])
            psu_a_extrusion_bracket_back();
        }

        translate([0,-main_depth/2,-main_lower_dist_z-extrusion_size])
        translate([-100,100,0])
        rotate([0,0,270])
        import("stl/RAMPS1_4.STL");
    }
}

feet_height = 8*mm;
translate([0, 0, main_lower_dist_z+extrusion_size*2+feet_height])
main();

/*psu();*/

/*%enclosure();*/

include <config.scad>

include <thing_libutils/system.scad>
include <thing_libutils/units.scad>
include <thing_libutils/materials.scad>

use <thing_libutils/screws.scad>
use <thing_libutils/shapes.scad>;
use <thing_libutils/misc.scad>;
use <thing_libutils/transforms.scad>;
use <thing_libutils/attach.scad>;
use <thing_libutils/linear-extrusion.scad>;
use <thing_libutils/timing-belts.scad>;

use <x-end.scad>
include <x-end.h>

use <x-carriage.scad>
include <x-carriage.h>

use <y-motor-mount.scad>
include <y-motor-mount.h>

use <y-carriage-bearing-mount.scad>
include <y-carriage-bearing-mount.h>

use <y-carriage-belt-clamp.scad>
include <y-carriage-belt-clamp.h>

use <y-idler.scad>
include <y-idler.h>

use <z-motor-mount.scad>
include <z-motor-mount.h>

use <gantry-upper-connector.scad>
use <gantry-lower-connector.scad>

use <rod-clamps.scad>
include <rod-clamps.h>

use <printbed.scad>
include <printbed.h>

/*use <psu.scad>*/
/*include <psu.h>*/

/*use <lcd2004.scad>*/
/*use <power-panel-iec320.scad>*/

// x carriage
axis_range_x_ = main_width/2 + zmotor_mount_rod_offset_x - xaxis_end_width(true) - x_carriage_w/2 - .5*mm;
axis_range_x = [-1,1] *  axis_range_x_;
axis_printrange_x = [-1/2, 1/2] * printbed_size[0];
axis_x_parked = [false, true];

axis_range_y=[0*mm,200*mm];
axis_pos_y = axis_range_y[0];
axis_range_z=[90*mm,353*mm];
axis_pos_z = axis_range_z[0];

echo(str("Axis range X: " , axis_range_x[0], " ", axis_range_x[1]," mm"));
echo(str("Axis range Y: " , axis_range_y[0], " ", axis_range_y[1]," mm"));
echo(str("Axis range Z: " , axis_range_z[0], " ", axis_range_z[1]," mm"));

echo(str("Build area Z: " , axis_range_z[1]-axis_range_z[0] , " mm"));

home_offset_x0 = axis_range_x[0] - axis_printrange_x[0] + v_x(extruder_filapath_offset)[0];
echo(str("Home offset X0: " , home_offset_x0));
echo(str("Tool offset X: " , axis_range_x_+x_carriage_w/2));

module x_axis()
{
    // x axis
    translate([0,0,axis_pos_z])
    {
        zrod_offset = zmotor_mount_rod_offset_x;
        for(z=xaxis_beltpath_z_offsets)
        tz(z)
        ty(xaxis_zaxis_distance_y)
        tx(-sign(z)*(-main_width/2-zrod_offset+xaxis_end_motor_offset[0]))
        rotate(90*X)
        belt_path(
            len=main_width+xaxis_end_motor_offset[0],
            belt_width=xaxis_belt_width,
            belt=xaxis_belt,
            pulley_d=xaxis_pulley_inner_d,
            orient=X, align=-sign(z)*X);

        for(x=[0:len(axis_range_x)-1])
        {
            /*#cubea(size=[x_carriage_w,10,100]);*/

            pos = axis_x_parked[x] ? axis_range_x[x] : -axis_printrange_x[x];

            translate([pos,0,0])
            mirror([x==0?0:1,0,0])
            attach(xaxis_carriage_conn, [[0,-xaxis_zaxis_distance_y,0],N])
            {
                x_carriage_withmounts(show_vitamins=true, beltpath_offset=x==0?-1:1, with_sensormount=x==0);

                x_carriage_extruder(show_vitamins=true);
            }
        }

        // x smooth rods
        for(z=[-1,1])
        translate([xaxis_rod_offset_x,xaxis_zaxis_distance_y,z*(xaxis_rod_distance/2)])
        material(Mat_Chrome)
        cylindera(h=xaxis_rod_l,d=xaxis_rod_d+.1, orient=X);

        for(x=[-1,1])
        translate([x*(main_width/2), 0, 0])
        translate([0, xaxis_zaxis_distance_y, 0])
        translate([x*zmotor_mount_rod_offset_x, 0, 0])
        mirror([max(0,x),0,0])
        {
            xaxis_end(with_motor=true, beltpath_index=max(0,x), show_nut=true, show_motor=true, show_nut=true);

            xaxis_end_bucket();
        }
    }
}

module y_axis()
{
    // y axis
    attach([[yaxis_belt_path_offset_x,main_depth/2+extrusion_size,yaxis_motor_offset_z], Y], yaxis_motor_mount_conn)
    yaxis_motor_mount(show_motor=true);

    extrusion_idler_conn = [[yaxis_belt_path_offset_x, -main_depth/2-extrusion_size, -extrusion_size+yaxis_idler_offset_z], [0,-1,0]];
    attach(extrusion_idler_conn, yaxis_idler_conn)
    {
        yaxis_idler();

        attach(yaxis_idler_conn_pulleyblock, yaxis_idler_pulleyblock_conn)
        yaxis_idler_pulleyblock(show_pulley=true);
    }

    translate([yaxis_belt_path_offset_x,0,yaxis_belt_path_offset_z])
    translate([0,main_depth/2+extrusion_size+yaxis_motor_offset_x,0])
    rotate(90*Y)
    belt_path(
        len=main_depth+yaxis_motor_offset_x-yaxis_idler_pulley_offset_y+extrusion_size,
        belt_width=yaxis_belt_width,
        pulley_d=yaxis_pulley_inner_d,
        belt=yaxis_belt,
        align=-Y, orient=Y);

    translate([0,-axis_pos_y,get(LinearBearingInnerDiameter, yaxis_bearing)/2])
    attach(yaxis_carriage_bearing_mount_conn_bearing, yaxis_belt_mount_conn)
    yaxis_belt_holder();

    translate([0,0,get(LinearBearingInnerDiameter, yaxis_bearing)/2])
    {
        // y smooth rod clamps to frame
        for(x=[-1,1])
        for(y=[-1,1])
        attach([[x*(yaxis_rod_distance/2),y*(main_depth/2+extrusion_size/2),0],Z],mount_rod_clamp_conn_rod)
        mount_rod_clamp_full(rod_d=zaxis_rod_d, thick=4, width=extrusion_size, thread=extrusion_thread, orient=Y);

        for(x=[-1,1])
        translate([x*(yaxis_rod_distance/2), 0, 0])
        {
            // y smooth rods
            material(Mat_Chrome)
            cylindera(h=yaxis_rod_l,d=yaxis_rod_d, orient=Y);

            // y bearing mounts
            for(y=[-1,1])
            attach([[0,y*(yaxis_bearing_distance_y/2)-axis_pos_y,0],[0,0,-1]], yaxis_carriage_bearing_mount_conn_bearing)
            {
                yaxis_carriage_bearing_mount();
            }
        }

        attach(yaxis_carriage_bearing_mount_conn_bearing, [[0,axis_pos_y,0],Z])
        {
            yaxis_carriage_size_inner = [32*mm,32*mm,0];

            material(Mat_PrintCarriage)
            {
                // y axis carriage plate "arms"
                for(x=[-1,1])
                for(y=[-1,1])
                hull()
                {
                    translate([x*yaxis_carriage_size[0]/2, y*yaxis_carriage_size[1]/2, 0])
                    cylindera(d=10*mm, h=yaxis_carriage_size[2], align=[-x,-y,1]);

                    translate([x*(yaxis_carriage_size[0]/2-yaxis_carriage_size_inner[0]/2), y*(yaxis_carriage_size[1]/2-yaxis_carriage_size_inner[1]/2*mm), 0])
                    cylindera(d=10*mm, h=yaxis_carriage_size[2], align=[-x,-y,1]);
                }

                // y axis carriage plate
                hull()
                for(x=[-1,1])
                for(y=[-1,1])
                {
                    translate([x*(yaxis_carriage_size[0]/2-yaxis_carriage_size_inner[0]/2), y*(yaxis_carriage_size[1]/2-yaxis_carriage_size_inner[1]/2*mm), 0])
                    cylindera(d=10*mm, h=yaxis_carriage_size[2], align=[-x,-y,1]);
                }
            }

            // y axis plate
            material(Mat_PrintBed)
            translate([0,0,10*mm])
            cubea(printbed_size, align=Z);
        }
    }

}

module z_axis()
{
    // z axis
    for(x=[-1,1])
    {
        translate([x*(main_width/2),0,main_height])
        mirror([x==-1?1:0,0,0])
        translate([zmotor_mount_rod_offset_x, 0, extrusion_size/2])
        mount_rod_clamp_half(
            rod_d=zaxis_rod_d,
            screw_dist=zmotor_mount_clamp_dist,
            thick=5,
            base_thick=5,
            width=zmotor_mount_thickness_h,
            thread=zmotor_mount_clamp_thread);

        tx(x*(main_width/2))
        {
            tz(zaxis_motor_offset_z)
            mirror([x==-1?1:0,0,0])
            {
                zaxis_motor_mount(show_motor=true);

                tx(zmotor_mount_rod_offset_x)
                tz(zmotor_mount_thickness_h/2)
                mount_rod_clamp_half(
                    rod_d=zaxis_rod_d,
                    screw_dist=zmotor_mount_clamp_dist,
                    thick=5,
                    base_thick=5,
                    width=zmotor_mount_thickness_h,
                    thread=zmotor_mount_clamp_thread);
            }

            // z smooth rods
            tx(x*(zmotor_mount_rod_offset_x))
            tz(zaxis_motor_offset_z-80*mm)
            material(Mat_Chrome)
            cylindera(h=zaxis_rod_l,d=zaxis_rod_d, align=Z);
        }
    }

}

module gantry_upper()
{
    for(y=[-1,1])
    translate([0,y*(main_upper_dist_y/2),0])
    {
        for(x=[-1,1])
        translate([x*(main_width/2), 0, 0])
        linear_extrusion(h=main_height, align=[-x,0,1], orient=Z);

        translate([0, 0, main_height])
        linear_extrusion(h=main_upper_width, align=Z, orient=X);
    }

    // upper gantry connectors
    for(x=[-1,1])
    translate([x*(main_width/2),0,main_height])
    mirror([x==-1?1:0,0,0])
    gantry_upper_connector();

}

module gantry_lower()
{
    for(z=[-1,1])
    translate([0,0,z*-main_lower_dist_z/2])
    {
        for(y=[-1,1])
        translate([0, y*(main_depth/2), 0])
        linear_extrusion(h=main_width, align=[0,y,-1], orient=X);

        for(x=[-1,1])
        translate([x*(main_width/2), 0, 0])
        linear_extrusion(h=main_depth, align=-x*X-Z, orient=Y);
    }

    // lower gantry connectors
    for(x=[-1,1])
    for(y=[-1,1])
    translate([x*(main_width/2),y*(main_depth/2),-extrusion_size/2])
    mirror([x==1?0:-1,0,0])
    mirror([0,y==1?1:0,0])
    gantry_lower_connector();

}

module enclosure()
{
    // inner
    w=56*cm;
    h=56*cm;
    d=58*cm;
    wallthick=2*cm;
    backthick=0.5*cm;

    translate([0, 0, h/2+wallthick])
    {
        // left/right walls
        for(x=[-1,1])
        translate([x*w/2,0,0])
        cubea([wallthick,d,h], align=[x, 0, 0]);

        // back plate
        translate([0,d/2,0])
        cubea([w,backthick,h], align=[0,-1, 0]);

        // top/bottom plate
        for(z=[-1,1])
        translate([0,0,z*h/2])
        cubea([w+wallthick*2,d,wallthick], align=[0,0, z]);
    }
}

module main()
{
    translate([0,0,-main_lower_dist_z/2])
    gantry_lower();

    translate(-zaxis_rod_offset)
    gantry_upper();

    x_axis();
    y_axis();
    z_axis();

    translate([-75*mm,0,0])
    translate([0,main_depth/2,0])
    translate([0,extrusion_size,0])
    translate([0,0,-main_lower_dist_z/2-extrusion_size/2])
    power_panel_iec320(orient=[0,-1,0], align=Y);

    if(!$preview_mode)
    {
        translate([0,-main_depth/2,0])
        translate([0,-extrusion_size/2,0])
        translate([0,0,-main_lower_dist_z/2])
        mount_lcd2004(show_gantry=true);

        for(x=[-1,1])
        translate([x*(main_width/2-extrusion_size),main_depth/2,-main_lower_dist_z-extrusion_size])
        translate([-x*psu_a_w/2, -psu_a_d/2, psu_a_h/2])
        {
            psu_a();

            mirror([x==-1?1:0,0,0])
            translate([0, -psu_a_screw_dist_y/2, 0])
            psu_a_extrusion_bracket_side();

            translate([0, psu_a_screw_dist_y/2, 0])
            psu_a_extrusion_bracket_back();
        }

        translate([0,-main_depth/2,-main_lower_dist_z-extrusion_size])
        translate([-100,100,0])
        rotate([0,0,270])
        import("stl/RAMPS1_4.STL");
    }
}

feet_height = 8*mm;
translate([0, 0, main_lower_dist_z+extrusion_size*2+feet_height])
main();

/*psu();*/

/*%enclosure();*/