Add a visual indicator when the actual number of assertions does not match the expected number

/**
 * @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.
 */

/**
 * Part of the camelSCAD library.
 *
 * Unit testing API.
 *
 * @package util/test
 * @author jsconan
 */

/**
 * Declares the target package of the test suite.
 *
 * @param String title
 * @param Number [expected]
 */
module testPackage(title, expected) {
    $testPackage = string(title);

    displayTestTitle(title, "package", true);
    children();
    displayExpectedAsserts($children, expected, "module");
}

/**
 * Declares the function or the feature that is tested.
 *
 * @param String title
 * @param Number [expected]
 */
module testModule(title, expected) {
    $testModule = string(title);

    displayTestTitle(title, "module");
    children();
    displayExpectedAsserts($children, expected, "test");
}

/**
 * Declares a unit test.
 *
 * @param String title
 * @param Number [expected]
 */
module testUnit(title, expected) {
    $testUnit = string(title);

    children();
    displayExpectedAsserts($children, expected, "assertion");
}

/**
 * Asserts the actual value is equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertEqual(actual, expected, message) {
    result = actual == expected;
    message = or(message, "Should be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotEqual(actual, expected, message) {
    result = actual != expected;
    message = or(message, "Should not be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertApproxEqual(actual, expected, message) {
    result = approx(actual, expected);
    message = or(message, "Should be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotApproxEqual(actual, expected, message) {
    result = !approx(actual, expected);
    message = or(message, "Should not be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is equivalent to true.
 *
 * @param * value
 * @param String [message]
 */
module assertTrue(value, message) {
    result = !!value;
    message = or(message, "Should be true");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is falsy.
 *
 * @param * value
 * @param String [message]
 */
module assertFalse(value, message) {
    result = !value;
    message = or(message, "Should be false");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Failed assert.
 *
 * @param String [message]
 */
module failed(message) {
    message = or(message, "Failed!");

    displayAssertResult(false, message);
}

/**
 * Asserts the value is undefined.
 *
 * @param * value
 * @param String [message]
 */
module assertUndef(value, message) {
    result = isUndef(value);
    message = or(message, "Should be an undefined value");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a number.
 *
 * @param * value
 * @param String [message]
 */
module assertNumber(value, message) {
    result = isNumber(value);
    message = or(message, "Should be a number");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an integer.
 *
 * @param * value
 * @param String [message]
 */
module assertInteger(value, message) {
    result = isInteger(value);
    message = or(message, "Should be an integer");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a boolean.
 *
 * @param * value
 * @param String [message]
 */
module assertBoolean(value, message) {
    result = isBoolean(value);
    message = or(message, "Should be a boolean");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a vector.
 *
 * @param * value
 * @param String [message]
 */
module assertVector(value, message) {
    result = isVector(value);
    message = or(message, "Should be a vector");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an array.
 *
 * @param * value
 * @param String [message]
 */
module assertArray(value, message) {
    result = isArray(value);
    message = or(message, "Should be an array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyArray(value, message) {
    result = value == [];
    message = or(message, "Should be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is not an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyArray(value, message) {
    result = isArray(value) && value != [];
    message = or(message, "Should not be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a string.
 *
 * @param * value
 * @param String [message]
 */
module assertString(value, message) {
    result = isString(value);
    message = or(message, "Should be a string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyString(value, message) {
    result = value == "";
    message = or(message, "Should be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a not empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyString(value, message) {
    result = isString(value) && value != "";
    message = or(message, "Should not be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Displays a title.
 *
 * @private
 * @param String title
 * @param String type
 * @param Boolean [big]
 */
module displayTestTitle(title, type, big) {
    echo(formatTestTitle(title, type, big));
}

/**
 * Displays the number of expected asserts.
 *
 * @private
 * @param Number actual
 * @param Number [expected]
 * @param String [type]
 */
module displayExpectedAsserts(actual, expected, type) {
    echo(formatExpectedAsserts(actual, expected, type));
    if (!checkExpectedAsserts(actual, expected)) {
        color([1, 0.4, 0.2]) {
            cube(15, center=true);
        }
    }
}

/**
 * Displays an assert result.
 *
 * @private
 * @param Boolean result
 * @param String message
 * @param Array [details]
 */
module displayAssertResult(result, message, details) {
    echo(formatAssertResult(result, message, details));
    if (!result) {
        color([1, 0, 0]) {
            sphere(10);
        }
    }
}

/**
 * @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.
 */

/**
 * Part of the camelSCAD library.
 *
 * Unit testing API.
 *
 * @package util/test
 * @author jsconan
 */

/**
 * Declares the target package of the test suite.
 *
 * @param String title
 * @param Number [expected]
 */
module testPackage(title, expected) {
    $testPackage = string(title);

    displayTestTitle(title, "package", true);
    children();
    displayExpectedAsserts($children, expected, "module");
}

/**
 * Declares the function or the feature that is tested.
 *
 * @param String title
 * @param Number [expected]
 */
module testModule(title, expected) {
    $testModule = string(title);

    displayTestTitle(title, "module");
    children();
    displayExpectedAsserts($children, expected, "test");
}

/**
 * Declares a unit test.
 *
 * @param String title
 * @param Number [expected]
 */
module testUnit(title, expected) {
    $testUnit = string(title);

    children();
    displayExpectedAsserts($children, expected, "assertion");
}

/**
 * Asserts the actual value is equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertEqual(actual, expected, message) {
    result = actual == expected;
    message = or(message, "Should be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotEqual(actual, expected, message) {
    result = actual != expected;
    message = or(message, "Should not be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertApproxEqual(actual, expected, message) {
    result = approx(actual, expected);
    message = or(message, "Should be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotApproxEqual(actual, expected, message) {
    result = !approx(actual, expected);
    message = or(message, "Should not be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is equivalent to true.
 *
 * @param * value
 * @param String [message]
 */
module assertTrue(value, message) {
    result = !!value;
    message = or(message, "Should be true");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is falsy.
 *
 * @param * value
 * @param String [message]
 */
module assertFalse(value, message) {
    result = !value;
    message = or(message, "Should be false");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Failed assert.
 *
 * @param String [message]
 */
module failed(message) {
    message = or(message, "Failed!");

    displayAssertResult(false, message);
}

/**
 * Asserts the value is undefined.
 *
 * @param * value
 * @param String [message]
 */
module assertUndef(value, message) {
    result = isUndef(value);
    message = or(message, "Should be an undefined value");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a number.
 *
 * @param * value
 * @param String [message]
 */
module assertNumber(value, message) {
    result = isNumber(value);
    message = or(message, "Should be a number");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an integer.
 *
 * @param * value
 * @param String [message]
 */
module assertInteger(value, message) {
    result = isInteger(value);
    message = or(message, "Should be an integer");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a boolean.
 *
 * @param * value
 * @param String [message]
 */
module assertBoolean(value, message) {
    result = isBoolean(value);
    message = or(message, "Should be a boolean");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a vector.
 *
 * @param * value
 * @param String [message]
 */
module assertVector(value, message) {
    result = isVector(value);
    message = or(message, "Should be a vector");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an array.
 *
 * @param * value
 * @param String [message]
 */
module assertArray(value, message) {
    result = isArray(value);
    message = or(message, "Should be an array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyArray(value, message) {
    result = value == [];
    message = or(message, "Should be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is not an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyArray(value, message) {
    result = isArray(value) && value != [];
    message = or(message, "Should not be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a string.
 *
 * @param * value
 * @param String [message]
 */
module assertString(value, message) {
    result = isString(value);
    message = or(message, "Should be a string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyString(value, message) {
    result = value == "";
    message = or(message, "Should be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a not empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyString(value, message) {
    result = isString(value) && value != "";
    message = or(message, "Should not be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Displays a title.
 *
 * @private
 * @param String title
 * @param String type
 * @param Boolean [big]
 */
module displayTestTitle(title, type, big) {
    echo(formatTestTitle(title, type, big));
}

/**
 * Displays the number of expected asserts.
 *
 * @private
 * @param Number actual
 * @param Number [expected]
 * @param String [type]
 */
module displayExpectedAsserts(actual, expected, type) {
    echo(formatExpectedAsserts(actual, expected, type));
}

/**
 * Displays an assert result.
 *
 * @private
 * @param Boolean result
 * @param String message
 * @param Array [details]
 */
module displayAssertResult(result, message, details) {
    echo(formatAssertResult(result, message, details));
    if (!result) {
        color([1, 0, 0]) {
            sphere(10);
        }
    }
}


holes made more visible on a preview

holes made more visible on a preview

use <lcd.scad>
use <menu_knob.scad>
use <menu_knob_manipulator.scad>
use <reset_button.scad>
use <sdcard_reader.scad>
use <cable_guide.scad>

boxSize = [143, 148, 6+7];
lcdOffset = boxSize - [98+5, 60+5, 12];
encoderOffset = [20-14/2, boxSize[1]-30/2-15/2-3, boxSize[2]-(6+7)];
sdCardOffset = [35, 80, boxSize[2]-6];
resetButtonOffset = [encoderOffset[0]+14/2, 68, 1];

//rotate([180, 0, 0])
{
  difference()
  {
    union()
    {
      // main mounting legs
      for(offset = [ [boxSize[0]-16, 0, boxSize[2]-7], [boxSize[0]-70, 0, boxSize[2]-7] ])
        translate(offset)
          cube([16, 40+13, 7]);
      // side mounting wing
      translate([15, 0, 6])
        cube([18, 20, 7]);

      difference()
      {
        // main working area
        cube(boxSize);
        // prusa's framework  corner
        translate([15, 0, 0])
          cube([143-15, 53, boxSize[2]]);
        // LCD
        translate(lcdOffset)
          lcdBoard();
        // encoder
        translate(encoderOffset)
          menuKnob();
        // SD card mount
        translate(sdCardOffset)
          rotate([0,180,0])
            sdCardReader();
        // reset button
        translate(resetButtonOffset)
          resetButton();
        // cable guide drill
        translate([boxSize[0]-75-5, 40+16+10, 0])
          cableGuide();
        // side mounting wing cut-in, so that screw will hold tight
        cube([15, 30, 6]);
      }
    }

    // mounting drills
    for(offsetOY = [ 13/2+5/2, 40+13+5])
      for(offsetOX = [15/2, boxSize[0]-70+16/2, boxSize[0]-16/2])
        translate([offsetOX, offsetOY, -1])
          cylinder(r=5/2, h=6+7+2, $fs=1);
  }

  // LCD stub
  %translate(lcdOffset)
    lcdBoard();
  // encoder stub
  %translate(encoderOffset)
  {
    menuKnob();
    translate([7, 6, 23+4])
      rotate([180,0,0])
        menuKnobManipulator();
  }
  // SD card mount stub
  %translate(sdCardOffset)
    rotate([0,180,0])
      sdCardReader();
  // reset button stub
  %translate(resetButtonOffset)
    resetButton();
}

use <lcd.scad>
use <menu_knob.scad>
use <menu_knob_manipulator.scad>
use <reset_button.scad>
use <sdcard_reader.scad>
use <cable_guide.scad>

boxSize = [143, 148, 6+7];
lcdOffset = boxSize - [98+5, 60+5, 12];
encoderOffset = [20-14/2, boxSize[1]-30/2-15/2-3, boxSize[2]-(6+7)];
sdCardOffset = [35, 80, boxSize[2]-6];
resetButtonOffset = [encoderOffset[0]+14/2, 68, 1];

//rotate([180, 0, 0])
{
  difference()
  {
    union()
    {
      // main mounting legs
      for(offset = [ [boxSize[0]-16, 0, boxSize[2]-7], [boxSize[0]-70, 0, boxSize[2]-7] ])
        translate(offset)
          cube([16, 40+13, 7]);
      // side mounting wing
      translate([15, 0, 6])
        cube([18, 20, 7]);

      difference()
      {
        // main working area
        cube(boxSize);
        // prusa's framework  corner
        translate([15, 0, 0])
          cube([143-15, 53, boxSize[2]]);
        // LCD
        translate(lcdOffset)
          lcdBoard();
        // encoder
        translate(encoderOffset)
          menuKnob();
        // SD card mount
        translate(sdCardOffset)
          rotate([0,180,0])
            sdCardReader();
        // reset button
        translate(resetButtonOffset)
          resetButton();
        // cable guide drill
        translate([boxSize[0]-75-5, 40+16+10, 0])
          cableGuide();
        // side mounting wing cut-in, so that screw will hold tight
          cube([15, 30, 6]);
      }
    }

    // mounting drills
    for(offsetOY = [ 13/2+5/2, 40+13+5])
      for(offsetOX = [15/2, boxSize[0]-70+16/2, boxSize[0]-16/2])
        translate([offsetOX, offsetOY, 0])
          cylinder(r=5/2, h=6+7, $fs=1);
  }

  // LCD stub
  %translate(lcdOffset)
    lcdBoard();
  // encoder stub
  %translate(encoderOffset)
  {
    menuKnob();
    translate([7, 6, 23+4])
      rotate([180,0,0])
        menuKnobManipulator();
  }
  // SD card mount stub
  %translate(sdCardOffset)
    rotate([0,180,0])
      sdCardReader();
  // reset button stub
  %translate(resetButtonOffset)
    resetButton();
}


Tweak placement of cover.

Tweak placement of cover.

// Modified from: https://github.com/luisibanez/ShapesFor3DPrinting/tree/master/OpenSCA

module piCameraEnclosure()
{

	module piCameraBackCoverBevel(clearance) {
		c = clearance;
		hw = ( 25 / 2 ) + c;
		he = hw + 1.5 + c;
		polyhedron(
			points = [
				[ hw, -15-c, -1-c ],
				[ hw,  15+c, -1-c ],
				[ hw,  15+c,  1 ],
				[ hw, -15-c,  1 ],
				[ he, -15-c, -1-c ],
				[ he,  15+c, -1-c ]
				],
			faces = [
				[ 5, 1, 0, 4],
				[ 4, 0, 3 ],
				[ 2, 1, 5 ],
				[ 5, 4, 3, 2],
				[ 0, 1, 2, 3],
			]
		);
	}

	module piCameraBackCoverBevels(clearance) {
		union() {
			piCameraBackCoverBevel(clearance);
			mirror([1,0,0])
				piCameraBackCoverBevel(clearance);
		}
	}

	module piCameraBackCover(clearance=0, indent=true) {
		c = 2*clearance;
		translate([0,-5.5,9])
			difference() {
				union() {
					piCameraBackCoverBevels(clearance);
					cube(size=[25+c,30+c,2+c],center=true);
				}
				translate([0,12,0])
					cube(size=[20.5-c,7,3],center=true);
				if (indent)
					translate([0,-12,1.5])
					rotate([0,90,0])
						cylinder(r=1, h=40, center=true, $fn=20);
			}
	}

	module cableOpening() {
		translate([0,14,8.1])
			cube(size=[17,12,4],center=true);
	}

	module chipOpening() {
		clearance = 0.5;
		c = 2 * clearance;
		translate([0,-9.5,5])
			cube(size=[8+c,10+c,4],center=true);
	}

	module lensOpening() {
		clearance = 0.5;
		c = 2 * clearance;
		translate([0,0,2.5])
			cube(size=[8+c,8+c,5+c], center=true);
	}

	module boardOpening() {
		clearance = 0.2;
		c = 2 * clearance;
		translate([0,-3,7.5])
			cube(size=[25+c,24+c,5+clearance], center=true);
	}

	module cameraFrame() {
		translate([0,-0.5,5])
			cube(size=[40,40,10], center=true);
	}

	module roundedCorner(radius) {
		translate([-radius, -radius, 0])
		difference() {
			translate([0,0,-50])
				cube(size=[radius+0.1,radius+0.1,100]);
			cylinder(h=100, r=radius, $fn=50, center=true);
		}
   }

   module cornerRounding(radius) {
		// vertical corners
		translate([20,19.5,0])
			roundedCorner(radius);
		translate([-20,19.5,0])
		rotate([0,0,90])
			roundedCorner(radius);
		translate([-20,-20.5,0])
		rotate([0,0,180])
			roundedCorner(radius);
		translate([20,-20.5,0])
		rotate([0,0,-90])
			roundedCorner(radius);

		// top corners
		translate([-20,0,10])
		rotate([0,-90,0])
		rotate([90,0,0])
			roundedCorner(radius/2);
		translate([20,0,10])
		rotate([90,0,0])
			roundedCorner(radius/2);
   }

	module piCameraAdapter() {
		difference() {
			cameraFrame();
			boardOpening();
			chipOpening();
			cableOpening();
			lensOpening();
			cornerRounding(radius=8);
			piCameraBackCover(clearance=0.3, indent=false);
		}
	}

	union() {
		piCameraAdapter();
		// Comment out translate to see
		// back cover in place.
		translate([40,5,-8])
			piCameraBackCover();
	}
}

piCameraEnclosure();

// Modified from: https://github.com/luisibanez/ShapesFor3DPrinting/tree/master/OpenSCA

module piCameraEnclosure()
{

	module piCameraBackCoverBevel(clearance) {
		c = clearance;
		hw = ( 25 / 2 ) + c;
		he = hw + 1.5 + c;
		polyhedron(
			points = [
				[ hw, -15-c, -1-c ],
				[ hw,  15+c, -1-c ],
				[ hw,  15+c,  1 ],
				[ hw, -15-c,  1 ],
				[ he, -15-c, -1-c ],
				[ he,  15+c, -1-c ]
				],
			faces = [
				[ 5, 1, 0, 4],
				[ 4, 0, 3 ],
				[ 2, 1, 5 ],
				[ 5, 4, 3, 2],
				[ 0, 1, 2, 3],
			]
		);
	}

	module piCameraBackCoverBevels(clearance) {
		union() {
			piCameraBackCoverBevel(clearance);
			mirror([1,0,0])
				piCameraBackCoverBevel(clearance);
		}
	}

	module piCameraBackCover(clearance=0, indent=true) {
		c = 2*clearance;
		translate([0,-5.5,9])
			difference() {
				union() {
					piCameraBackCoverBevels(clearance);
					cube(size=[25+c,30+c,2+c],center=true);
				}
				translate([0,12,0])
					cube(size=[20.5-c,7,3],center=true);
				if (indent)
					translate([0,-12,1.5])
					rotate([0,90,0])
						cylinder(r=1, h=40, center=true, $fn=20);
			}
	}

	module cableOpening() {
		translate([0,14,8.1])
			cube(size=[17,12,4],center=true);
	}

	module chipOpening() {
		clearance = 0.5;
		c = 2 * clearance;
		translate([0,-9.5,5])
			cube(size=[8+c,10+c,4],center=true);
	}

	module lensOpening() {
		clearance = 0.5;
		c = 2 * clearance;
		translate([0,0,2.5])
			cube(size=[8+c,8+c,5+c], center=true);
	}

	module boardOpening() {
		clearance = 0.2;
		c = 2 * clearance;
		translate([0,-3,7.5])
			cube(size=[25+c,24+c,5+clearance], center=true);
	}

	module cameraFrame() {
		translate([0,-0.5,5])
			cube(size=[40,40,10], center=true);
	}

	module roundedCorner(radius) {
		translate([-radius, -radius, 0])
		difference() {
			translate([0,0,-50])
				cube(size=[radius+0.1,radius+0.1,100]);
			cylinder(h=100, r=radius, $fn=50, center=true);
		}
   }

   module cornerRounding(radius) {
		// vertical corners
		translate([20,19.5,0])
			roundedCorner(radius);
		translate([-20,19.5,0])
		rotate([0,0,90])
			roundedCorner(radius);
		translate([-20,-20.5,0])
		rotate([0,0,180])
			roundedCorner(radius);
		translate([20,-20.5,0])
		rotate([0,0,-90])
			roundedCorner(radius);

		// top corners
		translate([-20,0,10])
		rotate([0,-90,0])
		rotate([90,0,0])
			roundedCorner(radius/2);
		translate([20,0,10])
		rotate([90,0,0])
			roundedCorner(radius/2);
   }

	module piCameraAdapter() {
		difference() {
			cameraFrame();
			boardOpening();
			chipOpening();
			cableOpening();
			lensOpening();
			cornerRounding(radius=8);
			piCameraBackCover(clearance=0.3, indent=false);
		}
	}

	union() {
		piCameraAdapter();
		// Comment out translate to see
		// back cover in place.
		translate([40,0,-8])
			piCameraBackCover();
	}
}

piCameraEnclosure();


xaxis/ends: more work, add idler holder

xaxis/ends: more work, add idler holder

include <MCAD/motors.scad>
include <MCAD/stepper.scad>

include <config.scad>
include <thing_libutils/misc.scad>
include <thing_libutils/bearing.scad>

motor_mount_wall_thick = xaxis_pulley[1]+1*mm-xaxis_pulley[0]/2;
xaxis_end_motorsize = lookup(NemaSideSize,xaxis_motor);
xaxis_end_motor_offset=[xaxis_end_motorsize/2+9*mm,motor_mount_wall_thick,0];

    xaxis_end_wz = xaxis_rod_distance+xaxis_bearing[2]+5*mm;
module xaxis_end_body(with_motor, nut_top=false)
{
    x_side = with_motor?1:-1;
    nut_h = zaxis_nut[4];
    wx = zaxis_bearing[1]/2+zaxis_nut[1];
    wx_ = with_motor? xaxis_end_motorsize+xaxis_end_motor_offset[0] - xaxis_end_motorsize/2 : wx;
    wy = xaxis_bearing[1]*2+xaxis_zaxis_distance_y;

    /*%cuberounda([wx_, xaxis_rod_d*2, xaxis_end_wz], rounding_radius=3, align=[with_motor?1:-1,0,0]);*/

    if(with_motor)
    translate(xaxis_end_motor_offset)
    translate([0,1*mm,0])
    cuberounda([xaxis_end_motorsize, motor_mount_wall_thick, xaxis_end_motorsize], rounding_radius=3, align=[0,-1,0]);

    // nut mount
    mirror([0,0,nut_top?1:0])
    translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -xaxis_end_wz/2])
    {
        union()
        {
            fncylindera(h=zaxis_nut[4], d=zaxis_nut[1], align=[0,0,1]);

            // lead screw
            // ensure some support for the leadscrew cutout all the way to the top
            fncylindera(h=xaxis_end_wz, d=zaxis_nut[2]*2, align=[0,0,1]);
        }
    }

    // x axis rod holders
    for(z=[-1,1])
    translate([0,0,z*(xaxis_rod_distance/2)])
        fncylindera(h=wx_,d=xaxis_rod_d*2, orient=[1,0,0], align=[x_side,0,0]);

    // support around z axis bearings
    translate([0, -xaxis_zaxis_distance_y, 0])
    {
        difference()
        {
            sizey= zaxis_bearing[1]*2;
            fncylindera(h=xaxis_end_wz,d=sizey, orient=[0,0,1], align=[0,0,0]);
            translate([0,0,.1])
            cubea([sizey/2+.1, sizey+.2, xaxis_end_wz+.4], orient=[0,0,1], align=[-x_side,0,0]);
        }
    }
}

module xaxis_end(with_motor=false, show_motor=false, nut_top=false, show_nut=false, show_rods=false)
{
    x_side = with_motor?1:-1;
    nut_h = zaxis_nut[4];
    wx = zaxis_bearing[1]/2+zaxis_nut[1];
    wx_ = with_motor? xaxis_end_motorsize+xaxis_end_motor_offset[0] - xaxis_end_motorsize/2 : wx;
    wy = xaxis_bearing[1]*2+xaxis_zaxis_distance_y;
    xaxis_end_wz = xaxis_rod_distance+xaxis_bearing[2]+5*mm;
    difference()
    {
        extrasize = with_motor?0*mm:0*mm;
        extrasize_align = with_motor?-1:1;

        hull()
        {
            xaxis_end_body(with_motor, nut_top);

            // projection against z, ensure easy print
            translate([0,0,-xaxis_end_wz/2])
                linear_extrude(1)
                projection(cut=false)
                xaxis_end_body(with_motor);
        }

        xaxis_end_beltpath();

        // z smooth bearing mounts
        for(z=[-1,1])
            translate([0,0,z*xaxis_rod_distance/2])
                translate([0, -xaxis_zaxis_distance_y, 0])
                bearing_mount_holes(
                        zaxis_bearing,
                        ziptie_type,
                        ziptie_bearing_distance,
                        orient=[0,0,1],
                        show_zips=false
                );

        // x smooth rods
        for(z=[-1,1])
            translate([0,0,z*(xaxis_rod_distance/2)])
                fncylindera(h=xaxis_rod_l,d=xaxis_rod_d, orient=[1,0,0]);

        if(with_motor)
        {
            screw_dist = lookup(NemaDistanceBetweenMountingHoles, xaxis_motor);

            // axle
            translate(xaxis_end_motor_offset)
            {
                translate([0, .1, 0])
                translate([0,1,0])
                fncylindera(d=1.1*lookup(NemaRoundExtrusionDiameter, xaxis_motor), h=motor_mount_wall_thick, orient=[0,1,0], align=[0,1,0]);

                // motor axle
                translate([0, .1, 0])
                fncylindera(d=1.1*lookup(NemaAxleDiameter, xaxis_motor), h=lookup(NemaFrontAxleLength, xaxis_motor), orient=[0,1,0], align=[0,1,0]);

                // bearing for offloading force on motor shaft
                translate([0, -xaxis_pulley[1]-1*mm+.1, 0])
                scale(1.03)
                bearing(bearing_MR105, orient=[0,1,0], align=[0,-1,0]);

                xaxis_motor_thread=ThreadM3;
                for(x=[-1,1])
                for(z=[-1,1])
                translate([x*screw_dist/2, .1, z*screw_dist/2])
                {
                    // screw
                    translate([0,1,0])
                    fncylindera(d=lookup(ThreadSize, xaxis_motor_thread), h=wy+motor_mount_wall_thick, orient=[0,1,0], align=[0,1,0]);

                    // screw head
                    translate([0,-motor_mount_wall_thick,0])
                        fncylindera(d=lookup(ThreadSize, xaxis_motor_thread)*1.9, h=wy+motor_mount_wall_thick, orient=[0,1,0], align=[0,1,0]);
                }
            }

        }

        // nut mount
        mirror([0,0,nut_top?1:0])
        translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -xaxis_end_wz/2])
        {
            union()
            {
                translate([0,0,-.1])
                {
                    // nut
                    fncylindera(h=nut_h+1, d=zaxis_nut[0]*1.05, align=[0,0,1]);

                    // lead screw
                    translate([0,0,-.1])
                    fncylindera(h=xaxis_end_wz+1, d=zaxis_nut[2]*1.5, align=[0,0,1]);

                    for(i=[-1,1])
                        translate([i*13.5, 0, 0])
                            fncylindera(h=5, r=1.6, align=[0,0,1]);
                }
            }
        }
    }

    if(with_motor && show_motor)
    {
        translate(xaxis_end_motor_offset)
        {
            /*translate([0,-motor_mount_wall_thick,0])*/
            /*translate([0,-5,0])*/
            translate([0,-1,0])
            /*translate([0,-xaxis_pulley[0]/2,0])*/
            pulley(xaxis_pulley, flip=false, orient=[0,1,0], align=[0,-1,0]);

            motor(xaxis_motor, NemaMedium, dualAxis=false, orientation=[-90,0,0]);
        }
    }

    %if(show_nut)
    {
        mirror([0,0,nut_top?1:0])
        translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -xaxis_end_wz/2-zaxis_nut[3]])
        xaxis_end_znut();
    }

    %if(show_rods)
    {
        for(z=[-1,1])
            translate([0,0,z*xaxis_rod_distance/2])
            translate([0, -xaxis_zaxis_distance_y, 0])
            fncylindera(d=zaxis_rod_d, h=zaxis_rod_l, orient=[0,0,1]);
    }
}

xaxis_end_beltpath_height = xaxis_pulley_inner_d+5*mm;
xaxis_end_beltpath_width = max(belt_width+3*mm, xaxis_pulley[0]+3*mm);

module xaxis_end_beltpath(xaxis_end_beltpath_length=1000)
{
    diag = pythag_hyp(xaxis_end_beltpath_width,xaxis_end_beltpath_width)/2;

    hull()
    for(z=[-1,1])
    translate([0,0,z*xaxis_end_beltpath_height/2])
    translate([-diag/2,0,0])
    translate([diag/2,0,0])
    rotate([z*-45,0,0])
    cubea([xaxis_end_beltpath_length, diag, diag]);
}

module xaxis_end_idlerholder(xaxis_end_beltpath_length=10)
{
    diag = pythag_hyp(xaxis_end_beltpath_width,xaxis_end_beltpath_width)/2;

    difference()
    {
        union()
        {
            cubea([10*mm, xaxis_end_beltpath_width/sqrt(2), xaxis_rod_distance], align=[1,0,0]);

            translate([10*mm, 0,0])
                cubea([15*mm, xaxis_idler_pulley[0]+8*mm, xaxis_rod_distance+xaxis_rod_d*2], align=[1,0,0]);
        }

        // x smooth rods
        for(z=[-1,1])
            translate([0,0,z*(xaxis_rod_distance/2)])
                fncylindera(h=xaxis_rod_l,d=xaxis_rod_d, orient=[1,0,0]);

        translate([20,0,0])
            pulley(xaxis_idler_pulley, orient=[0,1,0]);

        translate([10*mm+15*mm/2,0,0])
        fncylindera(d=lookup(ThreadSize, ThreadM5), h=100, orient=[0,1,0]);

        xaxis_end_beltpath();
    }
}

module xaxis_end_znut()
{
    difference()
    {
        union()
        {
            fncylindera(d=zaxis_nut[1], h=zaxis_nut[3], orient=[0,0,1], align=[0,0,1]);
            fncylindera(d=zaxis_nut[0], h=zaxis_nut[4], orient=[0,0,1], align=[0,0,1]);
        }
        translate([0,0,-.1])
            fncylindera(d=zaxis_nut[2], h=zaxis_nut[4]+.2, orient=[0,0,1], align=[0,0,1]);
    }
}

/*debug=false;*/
/*[>debug=true;<]*/
/*if(debug)*/
/*{*/
    /*// x smooth rods*/
    /*for(z=[-1,1])*/
        /*translate([0,0,z*(xaxis_rod_distance/2)])*/
            /*%fncylindera(h=xaxis_rod_l,d=xaxis_rod_d, orient=[1,0,0]);*/

    /*xaxis_end(with_motor=true, show_motor=true, nut_top=false, show_nut=true, show_rods=true);*/

    /*translate([150,0,0])*/
    /*{*/
        /*xaxis_end(with_motor=false, show_motor=true, nut_top=false, show_nut=true, show_rods=true);*/
        /*xaxis_end_idlerholder();*/
    /*}*/
/*}*/

/*print=false;*/
/*if(print)*/
/*{*/
    /*translate([0,0,xaxis_end_wz/2])*/
    /*xaxis_end(with_motor=true, show_motor=false, show_nut=false);*/

    /*translate([100,0,xaxis_end_wz/2])*/
    /*xaxis_end(with_motor=false, show_motor=false, show_nut=false);*/

    /*translate([50,30,25])*/
        /*rotate([0,90,0])*/
        /*xaxis_end_idlerholder();*/
/*}*/

include <MCAD/motors.scad>
include <MCAD/stepper.scad>

include <config.scad>
include <thing_libutils/misc.scad>
include <thing_libutils/bearing.scad>

motor_mount_wall_thick = xaxis_pulley[1]+1*mm-xaxis_pulley[0]/2;
xaxis_end_motorsize = lookup(NemaSideSize,xaxis_motor);
xaxis_end_motor_offset=[xaxis_end_motorsize/2+9*mm,motor_mount_wall_thick,0];

module xaxis_end_body(with_motor, nut_top=false)
{
    x_side = with_motor?1:-1;
    nut_h = zaxis_nut[4];
    wx = zaxis_bearing[1]/2+zaxis_nut[1];
    wx_ = with_motor? xaxis_end_motorsize+xaxis_end_motor_offset[0] - xaxis_end_motorsize/2 : wx;
    wy = xaxis_bearing[1]*2+xaxis_zaxis_distance_y;
    wz = xaxis_rod_distance+xaxis_bearing[2]+5*mm;

    /*%cuberounda([wx_, xaxis_rod_d*2, wz], rounding_radius=3, align=[with_motor?1:-1,0,0]);*/

    if(with_motor)
    translate(xaxis_end_motor_offset)
    translate([0,1*mm,0])
    cuberounda([xaxis_end_motorsize, motor_mount_wall_thick, xaxis_end_motorsize], rounding_radius=3, align=[0,-1,0]);

    // nut mount
    mirror([0,0,nut_top?1:0])
    translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -wz/2])
    {
        union()
        {
            fncylindera(h=zaxis_nut[4], d=zaxis_nut[1], align=[0,0,1]);

            // lead screw
            // ensure some support for the leadscrew cutout all the way to the top
            fncylindera(h=wz, d=zaxis_nut[2]*2, align=[0,0,1]);
        }
    }

    // x axis rod holders
    for(z=[-1,1])
    translate([0,0,z*(xaxis_rod_distance/2)])
        fncylindera(h=wx_,d=xaxis_rod_d*2, orient=[1,0,0], align=[x_side,0,0]);

    // support around z axis bearings
    translate([0, -xaxis_zaxis_distance_y, 0])
    {
        difference()
        {
            sizey= zaxis_bearing[1]*2;
            fncylindera(h=wz,d=sizey, orient=[0,0,1], align=[0,0,0]);
            translate([0,0,.1])
            cubea([sizey/2+.1, sizey+.2, wz+.4], orient=[0,0,1], align=[-x_side,0,0]);
        }
    }
}

module xaxis_end(with_motor=false, show_motor=false, nut_top=false, show_nut=false, show_rods=false)
{
    x_side = with_motor?1:-1;
    nut_h = zaxis_nut[4];
    wx = zaxis_bearing[1]/2+zaxis_nut[1];
    wx_ = with_motor? xaxis_end_motorsize+xaxis_end_motor_offset[0] - xaxis_end_motorsize/2 : wx;
    wy = xaxis_bearing[1]*2+xaxis_zaxis_distance_y;
    wz = xaxis_rod_distance+xaxis_bearing[2]+5*mm;
    difference()
    {
        extrasize = with_motor?0*mm:0*mm;
        extrasize_align = with_motor?-1:1;

        hull()
        {
            xaxis_end_body(with_motor, nut_top);

            // projection against z, ensure easy print
            translate([0,0,-wz/2])
                linear_extrude(1)
                projection(cut=false)
                xaxis_end_body(with_motor);
        }

        beltpath_height = xaxis_pulley_inner_d+5*mm;
        beltpath_width = max(belt_width+3*mm, xaxis_pulley[0]+3*mm);

        // belt path
        beltpath_length = 1000;
        cubea([beltpath_length,beltpath_width, beltpath_height]);

        diag = pythag_hyp(beltpath_width,beltpath_width)/2;

        for(z=[-1,1])
        translate([0,0,z*beltpath_height/2])
        translate([-diag/2,0,0])
        translate([diag/2,0,0])
        rotate([z*-45,0,0])
        cubea([beltpath_length, diag, diag]);

        // z smooth bearing mounts
        for(z=[-1,1])
            translate([0,0,z*xaxis_rod_distance/2])
                translate([0, -xaxis_zaxis_distance_y, 0])
                bearing_mount_holes(
                        zaxis_bearing,
                        ziptie_type,
                        ziptie_bearing_distance,
                        orient=[0,0,1],
                        show_zips=false
                );

        // x smooth rods
        for(z=[-1,1])
            translate([0,0,z*(xaxis_rod_distance/2)])
                fncylindera(h=xaxis_rod_l,d=xaxis_rod_d, orient=[1,0,0]);

        if(with_motor)
        {
            screw_dist = lookup(NemaDistanceBetweenMountingHoles, xaxis_motor);

            // axle
            translate(xaxis_end_motor_offset)
            {
                translate([0, .1, 0])
                translate([0,1,0])
                fncylindera(d=1.1*lookup(NemaRoundExtrusionDiameter, xaxis_motor), h=motor_mount_wall_thick, orient=[0,1,0], align=[0,1,0]);

                // motor axle
                translate([0, .1, 0])
                fncylindera(d=1.1*lookup(NemaAxleDiameter, xaxis_motor), h=lookup(NemaFrontAxleLength, xaxis_motor), orient=[0,1,0], align=[0,1,0]);

                // bearing for offloading force on motor shaft
                translate([0, -xaxis_pulley[1]-1*mm+.1, 0])
                scale(1.03)
                bearing(bearing_MR105, orient=[0,1,0], align=[0,-1,0]);

                xaxis_motor_thread=ThreadM3;
                for(x=[-1,1])
                for(z=[-1,1])
                translate([x*screw_dist/2, .1, z*screw_dist/2])
                {
                    // screw
                    translate([0,1,0])
                    fncylindera(d=lookup(ThreadSize, xaxis_motor_thread), h=wy+motor_mount_wall_thick, orient=[0,1,0], align=[0,1,0]);

                    // screw head
                    translate([0,-motor_mount_wall_thick,0])
                        fncylindera(d=lookup(ThreadSize, xaxis_motor_thread)*1.9, h=wy+motor_mount_wall_thick, orient=[0,1,0], align=[0,1,0]);
                }
            }

        }

        // nut mount
        mirror([0,0,nut_top?1:0])
        translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -wz/2])
        {
            union()
            {
                translate([0,0,-.1])
                {
                    // nut
                    fncylindera(h=nut_h+1, d=zaxis_nut[0]*1.05, align=[0,0,1]);

                    // lead screw
                    translate([0,0,-.1])
                    fncylindera(h=wz+1, d=zaxis_nut[2]*1.5, align=[0,0,1]);

                    for(i=[-1,1])
                        translate([i*13.5, 0, 0])
                            fncylindera(h=5, r=1.6, align=[0,0,1]);
                }
            }
        }
    }

    if(with_motor && show_motor)
    {
        translate(xaxis_end_motor_offset)
        {
            /*translate([0,-motor_mount_wall_thick,0])*/
            /*translate([0,-5,0])*/
            translate([0,-1,0])
            /*translate([0,-xaxis_pulley[0]/2,0])*/
            pulley(xaxis_pulley, flip=false, orient=[0,1,0], align=[0,-1,0]);

            motor(xaxis_motor, NemaMedium, dualAxis=false, orientation=[-90,0,0]);
        }
    }

    %if(show_nut)
    {
        mirror([0,0,nut_top?1:0])
        translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -wz/2-zaxis_nut[3]])
        xaxis_end_znut();
    }

    %if(show_rods)
    {
        for(z=[-1,1])
            translate([0,0,z*xaxis_rod_distance/2])
            translate([0, -xaxis_zaxis_distance_y, 0])
            fncylindera(d=zaxis_rod_d, h=zaxis_rod_l, orient=[0,0,1]);
    }
}

module xaxis_end_znut()
{
    difference()
    {
        union()
        {
            fncylindera(d=zaxis_nut[1], h=zaxis_nut[3], orient=[0,0,1], align=[0,0,1]);
            fncylindera(d=zaxis_nut[0], h=zaxis_nut[4], orient=[0,0,1], align=[0,0,1]);
        }
        translate([0,0,-.1])
            fncylindera(d=zaxis_nut[2], h=zaxis_nut[4]+.2, orient=[0,0,1], align=[0,0,1]);
    }
}

debug=false;
/*debug=true;*/
if(debug)
{
    // x smooth rods
    for(z=[-1,1])
        translate([0,0,z*(xaxis_rod_distance/2)])
            %fncylindera(h=xaxis_rod_l,d=xaxis_rod_d, orient=[1,0,0]);

    xaxis_end(with_motor=true, show_motor=true, nut_top=false, show_nut=true, show_rods=true);

    translate([150,0,0])
        xaxis_end(with_motor=false, show_motor=true, nut_top=false, show_nut=true, show_rods=true);
}

print=true;
if(print)
{
    xaxis_end(with_motor=true, show_motor=false, show_nut=false);
    translate([100,0,0])
    xaxis_end(with_motor=false, show_motor=false, show_nut=false);
}


xaxis/ends: round stuff

xaxis/ends: round stuff

use <thing_libutils/shapes.scad>;
use <thing_libutils/misc.scad>;
use <thing_libutils/attach.scad>;
use <thing_libutils/triangles.scad>
use <thing_libutils/linear-extrusion.scad>;
use <thing_libutils/bearing.scad>

include <config.scad>

xaxis_carriage_bearing_distance = 8;
xaxis_carriage_padding = 2;
xaxis_carriage_mount_distance = 23;
xaxis_carriage_mount_offset_z = 0;
xaxis_carriage_teeth_height=xaxis_belt_width*1.5;
xaxis_carriage_mount_screws = ThreadM4;

xaxis_carriage_conn = [[0, -xaxis_bearing[1]/2 - xaxis_carriage_bearing_offset_z,0], [0,0,0]];

module x_carriage_base()
{
    bottom_width = xaxis_bearing[2] + 2*xaxis_carriage_padding;
    top_width = xaxis_bearing[2]*2 + xaxis_carriage_bearing_distance + 2*xaxis_carriage_padding;
    thickness = xaxis_bearing[1]/2 + xaxis_carriage_bearing_offset_z;

    difference()
    {
        union()
        {
            hull()
            {
                // top bearings
                translate([0,0,xaxis_rod_distance/2])
                    cuberounda([top_width, thickness, xaxis_bearing[1]+xaxis_carriage_padding+ziptie_bearing_distance*2], align=[0,1,0]);

                /*#cubea([top_width,xaxis_carriage_bearing_offset_z,xaxis_rod_distance/2], align=[0,1,0]);*/

                // bottom bearing
                translate([0,0,-xaxis_rod_distance/2])
                    cuberounda([bottom_width, thickness, xaxis_bearing[1]+xaxis_carriage_padding+ziptie_bearing_distance*2], align=[0,1,0]);

            }

            translate([0,xaxis_carriage_beltpath_offset,0])
            {
                difference()
                {
                    translate([0,0,xaxis_pulley_inner_d/2])
                        cuberounda([50, xaxis_carriage_teeth_height, 10], align=[0,0,-1]);
                }
            }
        }

        translate([0,xaxis_carriage_beltpath_offset+.1,0])
        {
            // Cut clearing space for the belt
            translate([0,0,-xaxis_pulley_inner_d/2])
                cubea([500,xaxis_carriage_teeth_height,5], align=[0,0,0], extrasize=[0,0,4], extrasize_align=[0,0,1]);

            belt_thickness=1.5;
            for(i=[-1,1])
                translate([i*30,0,0])
                    cubea([15,xaxis_carriage_teeth_height,15-belt_thickness], align=[0,0,0]);

            // Cut in the middle for belt
            cubea([7,xaxis_carriage_teeth_height+.1,15], align=[0,0,0]);

            translate([0,0,xaxis_pulley_inner_d/2])
            {
                // Belt slit
                cubea([500,xaxis_carriage_teeth_height,belt_thickness], align=[0,0,0]);

                // Teeth cuts
                teeth = 50;
                teeth_height = 2;
                translate([-belt_tooth_distance/2*teeth-.5,0,0])
                for(i=[0:teeth])
                {
                    translate([i*belt_tooth_distance,0,0])
                        cubea([belt_tooth_distance*belt_tooth_ratio,xaxis_carriage_teeth_height,teeth_height], align=[0,0,-1]);
                }
            }

        }

    }
}

module x_carriage_holes()
{
    for(i=[-1,1])
    translate([i*(xaxis_bearing[2]/2+xaxis_carriage_bearing_distance/2),0,0])
    translate([0, xaxis_bearing[1]/2+xaxis_carriage_bearing_offset_z, xaxis_rod_distance/2])
    bearing_mount_holes(xaxis_bearing, orient=[1,0,0]);

    translate([0, xaxis_bearing[1]/2+xaxis_carriage_bearing_offset_z, -xaxis_rod_distance/2])
    bearing_mount_holes(xaxis_bearing, orient=[1,0,0]);

    // Extruder mounting holes
    translate([0,0,xaxis_carriage_mount_offset_z])
    for(j=[-1,1])
    for(i=[-1,1])
    {
        translate([i*xaxis_carriage_mount_distance/2,0,j*xaxis_carriage_mount_distance/2])
        {
            screw_dia = lookup(ThreadSize, xaxis_carriage_mount_screws);
            fncylindera(d=screw_dia, h=100, orient=[0,1,0]);
        }
    }
}

// Final part
module x_carriage(show_bearings=true)
{
    difference()
    {
        x_carriage_base();
        x_carriage_holes();
    }

    if(show_bearings)
    {
        for(j=[-1,1])
        translate([0,xaxis_bearing[1]/2 + xaxis_carriage_bearing_offset_z,0])
        translate([j*(xaxis_carriage_bearing_distance+xaxis_bearing[2])/2,0,0])
        translate([0, 0, xaxis_rod_distance/2])
                bearing(xaxis_bearing, orient=[1,0,0]);

        translate([0,xaxis_bearing[1]/2 + xaxis_carriage_bearing_offset_z,0])
        translate([0, 0, -xaxis_rod_distance/2])
            bearing(xaxis_bearing, orient=[1,0,0]);

        translate([0,xaxis_carriage_beltpath_offset,0])
        for(i=[-1,1])
        translate([i*50,0,0])
        pulley(xaxis_idler_pulley, orient=[0,1,0]);
    }
}

if(false)
{
    x_carriage();
}

use <thing_libutils/shapes.scad>;
use <thing_libutils/misc.scad>;
use <thing_libutils/attach.scad>;
use <thing_libutils/triangles.scad>
use <thing_libutils/linear-extrusion.scad>;
use <thing_libutils/bearing.scad>

include <config.scad>

xaxis_carriage_bearing_distance = 8;
xaxis_carriage_padding = 2;
xaxis_carriage_mount_distance = 23;
xaxis_carriage_mount_offset_z = 0;
xaxis_carriage_teeth_height=xaxis_belt_width*1.5;
xaxis_carriage_mount_screws = ThreadM4;

xaxis_carriage_conn = [[0, -xaxis_bearing[1]/2 - xaxis_carriage_bearing_offset_z,0], [0,0,0]];

module x_carriage_base()
{
    bottom_width = xaxis_bearing[2] + 2*xaxis_carriage_padding;
    top_width = xaxis_bearing[2]*2 + xaxis_carriage_bearing_distance + 2*xaxis_carriage_padding;
    thickness = xaxis_bearing[1]/2 + xaxis_carriage_bearing_offset_z;

    difference()
    {
        union()
        {
            hull()
            {
                // top bearings
                translate([0,0,xaxis_rod_distance/2])
                    cubea([top_width, thickness, xaxis_bearing[1]+xaxis_carriage_padding+ziptie_bearing_distance*2], align=[0,1,0]);

                /*#cubea([top_width,xaxis_carriage_bearing_offset_z,xaxis_rod_distance/2], align=[0,1,0]);*/

                // bottom bearing
                translate([0,0,-xaxis_rod_distance/2])
                    cubea([bottom_width, thickness, xaxis_bearing[1]+xaxis_carriage_padding+ziptie_bearing_distance*2], align=[0,1,0]);

            }

            translate([0,xaxis_carriage_beltpath_offset,0])
            {
                difference()
                {
                    translate([0,0,xaxis_pulley_inner_d/2])
                        cubea([50, xaxis_carriage_teeth_height, 10], align=[0,0,-1]);
                }
            }
        }

        translate([0,xaxis_carriage_beltpath_offset+.1,0])
        {
            // Cut clearing space for the belt
            translate([0,0,-xaxis_pulley_inner_d/2])
                cubea([500,xaxis_carriage_teeth_height,5], align=[0,0,0], extrasize=[0,0,4], extrasize_align=[0,0,1]);

            belt_thickness=1.5;
            for(i=[-1,1])
                translate([i*30,0,0])
                    cubea([15,xaxis_carriage_teeth_height,15-belt_thickness], align=[0,0,0]);

            // Cut in the middle for belt
            cubea([7,xaxis_carriage_teeth_height+.1,15], align=[0,0,0]);

            translate([0,0,xaxis_pulley_inner_d/2])
            {
                // Belt slit
                cubea([500,xaxis_carriage_teeth_height,belt_thickness], align=[0,0,0]);

                // Teeth cuts
                teeth = 50;
                teeth_height = 2;
                translate([-belt_tooth_distance/2*teeth-.5,0,0])
                for(i=[0:teeth])
                {
                    translate([i*belt_tooth_distance,0,0])
                        cubea([belt_tooth_distance*belt_tooth_ratio,xaxis_carriage_teeth_height,teeth_height], align=[0,0,-1]);
                }
            }

        }

    }
}

module x_carriage_holes()
{
    for(i=[-1,1])
    translate([i*(xaxis_bearing[2]/2+xaxis_carriage_bearing_distance/2),0,0])
    translate([0, xaxis_bearing[1]/2+xaxis_carriage_bearing_offset_z, xaxis_rod_distance/2])
    bearing_mount_holes(xaxis_bearing, orient=[1,0,0]);

    translate([0, xaxis_bearing[1]/2+xaxis_carriage_bearing_offset_z, -xaxis_rod_distance/2])
    bearing_mount_holes(xaxis_bearing, orient=[1,0,0]);

    // Extruder mounting holes
    translate([0,0,xaxis_carriage_mount_offset_z])
    for(j=[-1,1])
    for(i=[-1,1])
    {
        translate([i*xaxis_carriage_mount_distance/2,0,j*xaxis_carriage_mount_distance/2])
        {
            screw_dia = lookup(ThreadSize, xaxis_carriage_mount_screws);
            fncylindera(d=screw_dia, h=100, orient=[0,1,0]);
        }
    }
}

// Final part
module x_carriage(show_bearings=true)
{
    difference()
    {
        x_carriage_base();
        x_carriage_holes();
    }

    if(show_bearings)
    {
        for(j=[-1,1])
        translate([0,xaxis_bearing[1]/2 + xaxis_carriage_bearing_offset_z,0])
        translate([j*(xaxis_carriage_bearing_distance+xaxis_bearing[2])/2,0,0])
        translate([0, 0, xaxis_rod_distance/2])
                bearing(xaxis_bearing, orient=[1,0,0]);

        translate([0,xaxis_bearing[1]/2 + xaxis_carriage_bearing_offset_z,0])
        translate([0, 0, -xaxis_rod_distance/2])
            bearing(xaxis_bearing, orient=[1,0,0]);

        translate([0,xaxis_carriage_beltpath_offset,0])
        for(i=[-1,1])
        translate([i*50,0,0])
        pulley(xaxis_idler_pulley, orient=[0,1,0]);
    }
}

if(false)
{
    x_carriage();
}


Add test unit operator: testUnitContext()

/**
 * @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.
 */

/**
 * Part of the camelSCAD library.
 *
 * Unit testing API.
 *
 * @package util/test
 * @author jsconan
 */

/**
 * Declares the target package of the test suite.
 *
 * @param String title
 * @param Number [expected]
 */
module testPackage(title, expected) {
    $testPackage = string(title);

    displayTestTitle(title, "package", true);
    children();
    displayExpectedAsserts($children, expected, "module");
}

/**
 * Declares the function or the feature that is tested.
 *
 * @param String title
 * @param Number [expected]
 */
module testModule(title, expected) {
    $testModule = string(title);

    displayTestTitle(title, "module");
    children();
    displayExpectedAsserts($children, expected, "test");
}

/**
 * Declares a unit test.
 *
 * @param String title
 * @param Number [expected]
 */
module testUnit(title, expected) {
    $testUnit = string(title);

    children();
    displayExpectedAsserts($children, expected, "assertion");
}

/**
 * Declares a unit test context. That allows to isolate context data from other unit tests.
 *
 * @param String title
 * @param Number [expected]
 */
module testUnitContext(title, expected) {
    $testUnit = str($testUnit, " - ", string(title));

    children();
    displayExpectedAsserts($children, expected, "assertion");
}

/**
 * Asserts the actual value is equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertEqual(actual, expected, message) {
    result = actual == expected;
    message = or(message, "Should be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotEqual(actual, expected, message) {
    result = actual != expected;
    message = or(message, "Should not be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertApproxEqual(actual, expected, message) {
    result = approx(actual, expected);
    message = or(message, "Should be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotApproxEqual(actual, expected, message) {
    result = !approx(actual, expected);
    message = or(message, "Should not be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is equivalent to true.
 *
 * @param * value
 * @param String [message]
 */
module assertTrue(value, message) {
    result = !!value;
    message = or(message, "Should be true");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is falsy.
 *
 * @param * value
 * @param String [message]
 */
module assertFalse(value, message) {
    result = !value;
    message = or(message, "Should be false");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Failed assert.
 *
 * @param String [message]
 */
module failed(message) {
    message = or(message, "Failed!");

    displayAssertResult(false, message);
}

/**
 * Asserts the value is undefined.
 *
 * @param * value
 * @param String [message]
 */
module assertUndef(value, message) {
    result = isUndef(value);
    message = or(message, "Should be an undefined value");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a number.
 *
 * @param * value
 * @param String [message]
 */
module assertNumber(value, message) {
    result = isNumber(value);
    message = or(message, "Should be a number");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an integer.
 *
 * @param * value
 * @param String [message]
 */
module assertInteger(value, message) {
    result = isInteger(value);
    message = or(message, "Should be an integer");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a boolean.
 *
 * @param * value
 * @param String [message]
 */
module assertBoolean(value, message) {
    result = isBoolean(value);
    message = or(message, "Should be a boolean");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a vector.
 *
 * @param * value
 * @param String [message]
 */
module assertVector(value, message) {
    result = isVector(value);
    message = or(message, "Should be a vector");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an array.
 *
 * @param * value
 * @param String [message]
 */
module assertArray(value, message) {
    result = isArray(value);
    message = or(message, "Should be an array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyArray(value, message) {
    result = value == [];
    message = or(message, "Should be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is not an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyArray(value, message) {
    result = isArray(value) && value != [];
    message = or(message, "Should not be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a string.
 *
 * @param * value
 * @param String [message]
 */
module assertString(value, message) {
    result = isString(value);
    message = or(message, "Should be a string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyString(value, message) {
    result = value == "";
    message = or(message, "Should be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a not empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyString(value, message) {
    result = isString(value) && value != "";
    message = or(message, "Should not be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Displays a title.
 *
 * @private
 * @param String title
 * @param String type
 * @param Boolean [big]
 */
module displayTestTitle(title, type, big) {
    echo(formatTestTitle(title, type, big));
}

/**
 * Displays the number of expected asserts.
 *
 * @private
 * @param Number actual
 * @param Number [expected]
 * @param String [type]
 */
module displayExpectedAsserts(actual, expected, type) {
    echo(formatExpectedAsserts(actual, expected, type));
    if (!checkExpectedAsserts(actual, expected)) {
        color([1, 0.4, 0.2]) {
            cube(15, center=true);
        }
    }
}

/**
 * Displays an assert result.
 *
 * @private
 * @param Boolean result
 * @param String message
 * @param Array [details]
 */
module displayAssertResult(result, message, details) {
    echo(formatAssertResult(result, message, details));
    if (!result) {
        color([1, 0, 0]) {
            sphere(10);
        }
    }
}

/**
 * @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.
 */

/**
 * Part of the camelSCAD library.
 *
 * Unit testing API.
 *
 * @package util/test
 * @author jsconan
 */

/**
 * Declares the target package of the test suite.
 *
 * @param String title
 * @param Number [expected]
 */
module testPackage(title, expected) {
    $testPackage = string(title);

    displayTestTitle(title, "package", true);
    children();
    displayExpectedAsserts($children, expected, "module");
}

/**
 * Declares the function or the feature that is tested.
 *
 * @param String title
 * @param Number [expected]
 */
module testModule(title, expected) {
    $testModule = string(title);

    displayTestTitle(title, "module");
    children();
    displayExpectedAsserts($children, expected, "test");
}

/**
 * Declares a unit test.
 *
 * @param String title
 * @param Number [expected]
 */
module testUnit(title, expected) {
    $testUnit = string(title);

    children();
    displayExpectedAsserts($children, expected, "assertion");
}

/**
 * Asserts the actual value is equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertEqual(actual, expected, message) {
    result = actual == expected;
    message = or(message, "Should be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotEqual(actual, expected, message) {
    result = actual != expected;
    message = or(message, "Should not be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertApproxEqual(actual, expected, message) {
    result = approx(actual, expected);
    message = or(message, "Should be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotApproxEqual(actual, expected, message) {
    result = !approx(actual, expected);
    message = or(message, "Should not be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is equivalent to true.
 *
 * @param * value
 * @param String [message]
 */
module assertTrue(value, message) {
    result = !!value;
    message = or(message, "Should be true");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is falsy.
 *
 * @param * value
 * @param String [message]
 */
module assertFalse(value, message) {
    result = !value;
    message = or(message, "Should be false");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Failed assert.
 *
 * @param String [message]
 */
module failed(message) {
    message = or(message, "Failed!");

    displayAssertResult(false, message);
}

/**
 * Asserts the value is undefined.
 *
 * @param * value
 * @param String [message]
 */
module assertUndef(value, message) {
    result = isUndef(value);
    message = or(message, "Should be an undefined value");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a number.
 *
 * @param * value
 * @param String [message]
 */
module assertNumber(value, message) {
    result = isNumber(value);
    message = or(message, "Should be a number");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an integer.
 *
 * @param * value
 * @param String [message]
 */
module assertInteger(value, message) {
    result = isInteger(value);
    message = or(message, "Should be an integer");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a boolean.
 *
 * @param * value
 * @param String [message]
 */
module assertBoolean(value, message) {
    result = isBoolean(value);
    message = or(message, "Should be a boolean");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a vector.
 *
 * @param * value
 * @param String [message]
 */
module assertVector(value, message) {
    result = isVector(value);
    message = or(message, "Should be a vector");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an array.
 *
 * @param * value
 * @param String [message]
 */
module assertArray(value, message) {
    result = isArray(value);
    message = or(message, "Should be an array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyArray(value, message) {
    result = value == [];
    message = or(message, "Should be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is not an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyArray(value, message) {
    result = isArray(value) && value != [];
    message = or(message, "Should not be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a string.
 *
 * @param * value
 * @param String [message]
 */
module assertString(value, message) {
    result = isString(value);
    message = or(message, "Should be a string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyString(value, message) {
    result = value == "";
    message = or(message, "Should be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a not empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyString(value, message) {
    result = isString(value) && value != "";
    message = or(message, "Should not be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Displays a title.
 *
 * @private
 * @param String title
 * @param String type
 * @param Boolean [big]
 */
module displayTestTitle(title, type, big) {
    echo(formatTestTitle(title, type, big));
}

/**
 * Displays the number of expected asserts.
 *
 * @private
 * @param Number actual
 * @param Number [expected]
 * @param String [type]
 */
module displayExpectedAsserts(actual, expected, type) {
    echo(formatExpectedAsserts(actual, expected, type));
    if (!checkExpectedAsserts(actual, expected)) {
        color([1, 0.4, 0.2]) {
            cube(15, center=true);
        }
    }
}

/**
 * Displays an assert result.
 *
 * @private
 * @param Boolean result
 * @param String message
 * @param Array [details]
 */
module displayAssertResult(result, message, details) {
    echo(formatAssertResult(result, message, details));
    if (!result) {
        color([1, 0, 0]) {
            sphere(10);
        }
    }
}


Allow to center repeated shapes

Allow to center repeated shapes

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

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

/**
 * Repeats the children modules `count` times with the provided `interval`.
 *
 * @param Number [count] - The number of times the children must be repeated.
 * @param Vector [interval] - The interval between each repeated children.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 * @param Number [intervalX] - The X interval between each repeated children
 *                             (will overwrite the X coordinate in the `interval` vector).
 * @param Number [intervalY] - The Y interval between each repeated children
 *                             (will overwrite the Y coordinate in the `interval` vector).
 * @param Number [intervalZ] - The Z interval between each repeated children
 *                             (will overwrite the Z coordinate in the `interval` vector).
 */
module repeat(count    = 2,
              interval = [0, 0, 0],
              center   = false,
              intervalX, intervalY, intervalZ) {

    interval = apply3D(interval, intervalX, intervalY, intervalZ);
    offset = center ? -interval * (count - 1) / 2 : 0;

    for (i = [0 : count - 1]) {
        translate(offset + interval * i) {
            children();
        }
    }
}

/**
 * Repeats the children modules in two directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeat2D(countX    = 2,
                countY    = 2,
                intervalX = [0, 0, 0],
                intervalY = [0, 0, 0],
                center    = false) {

    repeat(count=countY, interval=vector3D(intervalY), center=center) {
        repeat(count=countX, interval=vector3D(intervalX), center=center) {
            children();
        }
    }
}

/**
 * Repeats the children modules in three directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Number [countZ] - The number of times the children must be repeated along the Z axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Vector [intervalZ] - The interval between each repeated children along the Z axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeat3D(countX    = 2,
                countY    = 2,
                countZ    = 2,
                intervalX = [0, 0, 0],
                intervalY = [0, 0, 0],
                intervalZ = [0, 0, 0],
                center    = false) {

    repeat(count=countZ, interval=vector3D(intervalZ), center=center) {
        repeat(count=countY, interval=vector3D(intervalY), center=center) {
            repeat(count=countX, interval=vector3D(intervalX), center=center) {
                children();
            }
        }
    }
}

/**
 * Repeats horizontally a shape in two directions, the interval is set by the size of the shape.
 * @param Vector size - The size of the shape.
 * @param Vector [count] - The number of shapes on each axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeatShape2D(size, count = 1, center) {
    size = vector2D(size);
    count = vector2D(count);

    repeat2D(
        countX = count[0],
        countY = count[1],
        intervalX = [size[0], 0, 0],
        intervalY = [0, size[1], 0],
        center = center
    ) {
        children();
    }
}

/**
 * Repeats a shape in three directions, the interval is set by the size of the shape.
 * @param Vector size - The size of the shape.
 * @param Vector [count] - The number of shapes on each axis.
 * @param Boolean [center] - Whether or not center the repeated shapes.
 */
module repeatShape3D(size, count = 1, center) {
    size = vector3D(size);
    count = vector3D(count);

    repeat3D(
        countX = count[0],
        countY = count[1],
        countZ = count[2],
        intervalX = [size[0], 0, 0],
        intervalY = [0, size[1], 0],
        intervalZ = [0, 0, size[2]],
        center = center
    ) {
        children();
    }
}

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

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

/**
 * Repeats the children modules `count` times with the provided `interval`.
 *
 * @param Number [count] - The number of times the children must be repeated.
 * @param Vector [interval] - The interval between each repeated children.
 * @param Number [intervalX] - The X interval between each repeated children
 *                             (will overwrite the X coordinate in the `interval` vector).
 * @param Number [intervalY] - The Y interval between each repeated children
 *                             (will overwrite the Y coordinate in the `interval` vector).
 * @param Number [intervalZ] - The Z interval between each repeated children
 *                             (will overwrite the Z coordinate in the `interval` vector).
 */
module repeat(count    = 2,
              interval = [0, 0, 0],
              intervalX, intervalY, intervalZ) {

    interval = apply3D(interval, intervalX, intervalY, intervalZ);

    for (i = [0 : count - 1]) {
        translate(interval * i) {
            children();
        }
    }
}

/**
 * Repeats the children modules in two directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 */
module repeat2D(countX    = 2,
                countY    = 2,
                intervalX = [0, 0, 0],
                intervalY = [0, 0, 0]) {

    repeat(count=countY, interval=vector3D(intervalY)) {
        repeat(count=countX, interval=vector3D(intervalX)) {
            children();
        }
    }
}

/**
 * Repeats the children modules in three directions `count` times with the provided `interval`.
 *
 * @param Number [countX] - The number of times the children must be repeated along the X axis.
 * @param Number [countY] - The number of times the children must be repeated along the Y axis.
 * @param Number [countZ] - The number of times the children must be repeated along the Z axis.
 * @param Vector [intervalX] - The interval between each repeated children along the X axis.
 * @param Vector [intervalY] - The interval between each repeated children along the Y axis.
 * @param Vector [intervalZ] - The interval between each repeated children along the Z axis.
 */
module repeat3D(countX    = 2,
                countY    = 2,
                countZ    = 2,
                intervalX = [0, 0, 0],
                intervalY = [0, 0, 0],
                intervalZ = [0, 0, 0]) {

    repeat(count=countZ, interval=vector3D(intervalZ)) {
        repeat(count=countY, interval=vector3D(intervalY)) {
            repeat(count=countX, interval=vector3D(intervalX)) {
                children();
            }
        }
    }
}

/**
 * Repeats horizontally a shape in two directions, the interval is set by the size of the shape.
 * @param Vector size - The size of the shape
 * @param Vector [count] - The number of shapes on each axis
 * @param Boolean [center] - Whether or not center the repeated shapes
 */
module repeatShape2D(size, count = 1, center) {
    size = vector2D(size);
    count = vector2D(count);

    translate(center ? -vmul(size, count - [1, 1]) / 2 : 0) {
        repeat2D(
            countX = count[0],
            countY = count[1],
            intervalX = [size[0], 0, 0],
            intervalY = [0, size[1], 0]
        ) {
            children();
        }
    }
}

/**
 * Repeats a shape in three directions, the interval is set by the size of the shape.
 * @param Vector size - The size of the shape
 * @param Vector [count] - The number of shapes on each axis
 * @param Boolean [center] - Whether or not center the repeated shapes
 */
module repeatShape3D(size, count = 1, center) {
    size = vector3D(size);
    count = vector3D(count);

    translate(center ? -vmul(size, count - [1, 1, 1]) / 2 : 0) {
        repeat3D(
            countX = count[0],
            countY = count[1],
            countZ = count[2],
            intervalX = [size[0], 0, 0],
            intervalY = [0, size[1], 0],
            intervalZ = [0, 0, size[2]]
        ) {
            children();
        }
    }
}


3d printed spool

3d printed spool

// Alphabets New Spool
$fn=(50);

// Parameters
ncards=64; // number of cards
sdiam=85; // diameter of the spool mm
e=2; // 3d printed part thickness mm

// Variables
srad=sdiam/2;
angle=360/ncards; // angle of rotation

// define copy_mirror function
module copy_mirror(vec=[0,1,0])
{
    children();
    mirror(vec) children();
}

// Define axis motor
module axismotor() {
intersection() {square(size = [3, 6], center = true); circle(2.5);}
}

// Define axis support
module axis() {
circle(1.5);
}

// define aro
module aro() {
difference(){
    difference() { circle(srad); circle(srad-5);}
    for (a=[0:angle:360]){
    rotate(a)
    translate([srad-2.5,0,0]) circle(1.5);
    }
}
}

// CODE STARTS HERE

linear_extrude(height = e) {
union(){
aro();
    difference() {
        intersection() {
            union() {
                translate ([srad,0,0]) aro();
                translate ([-srad,0,0])  aro();
                circle (6.5);
                }
            circle(srad-5);
        }

    axismotor();
    }
}
}

translate ([sdiam+5,0,0])
        union() {
        linear_extrude(height = 5) {circle(1.5);}
        linear_extrude(height = e) {
        union(){
        aro();
                intersection() {
                    union() {
                        translate ([srad,0,0]) aro();
                        translate ([-srad,0,0])  aro();
                        circle (6.5);
                        }
                    circle(srad-5);
                }

        }
        }
    }

error: pathspec 'V2/Hardware/structure/spool-3dp.scad' did not match any file(s) known to git