Print-ready form of Typeatron Mark 1. Will double-check all measurements and clean up the code a bit before printing.

// The origin is at the "bottom right" of the case (in the empty space where the lid goes),
// with the thumb to the north (the y axis), the fingers of the left hand to the left
// (the x axis) and pointing away, and the bottom of the case pointing down (the z axis)
// away from the palm.

function pythag(a, b) = sqrt(a*a + b*b);
function angle(a, b) = atan(a/b);

// toggle this variable to see the ICs in position and make sure they fit inside the case
visualizeInternalComponents = false;

// this is the accuracy value for Shapeways' "Strong & Flexible Plastics" option
error = 0.15;

error2 = error * 2;

// A safe gap between the printed part and each side of a foreign object such as a circuit board.
// We assume these have been directly measured with digital calipers (as opposed to relying on product specifications),
// with an accuracy of no less than 0.1mm.
foreignPartClearance = error + 0.1;

pinHoleRes = 10;
/*
cornerRoundingRes = 100;
connectorPinRes = 100;
ledHoleRes = 100;
//*/
//*
cornerRoundingRes = 10;
connectorPinRes = 10;
ledHoleRes = 10;
//*/
containmentWallThick = 1;
containmentShelfThick = 1;

thumbCurveRadius = 39.0;
offsetFromTopEdgeToFirstFingerWell = 15.0;
fingerWidth = 84 / 4;  // 21 -- 82mm was measured, but this seemed a little cramped
thumbWidth = 25;
horizontalOffsetBeforeThumbWell = 25.0;
buttonBodySeparation = 1.5;

nanoLength = 43.2;
nanoWidth = 18.0;
nanoHeightWithoutUsb = 5.4;
nanoHeightToUsbBase = 3.3;
nanoUsbWidth = 7.6;
nanoUsbHeight = 3.9;
nanoUsbCableWidth = 9.7;
nanoUsbCableHeight = 6.7;
nanoUsbCableOffsetFromPerfboard = 3.4;

chargerHeaderLength = 5.1;
chargerHeaderWidth = 2.6;

powerSwitchLength = 11.6;
powerSwitchWidth = 4.0;
powerSwitchDepth = 7.5;

lightSensorRadius = 5.0 / 2;
lightSensorThick = 2.0;
lightSensorWireThick = 0.6;

// horizontal offset of components with ports/controls on the bottom edge
nanoOffset = 0;
nanoOuterWallOffset = nanoOffset + nanoWidth + 2*foreignPartClearance;
chargerHeaderOffset = nanoOuterWallOffset + containmentWallThick;
// place the power switch a reasonable distance past the charger header
powerSwitchOffset = chargerHeaderOffset + chargerHeaderWidth + error2 + 5;

ledDomeRadius = 2.50;
ledRimRadius = 2.95;
ledRimThick = 1.10;

laserWidth = 10.3;
laserThick = 3.3;

lidThick = 1.5;
floorThick = 1.5;

caseWidth = 70;
caseLength = 120;
thickestComponent = 7.9;
caseHeight = lidThick + floorThick + thickestComponent + 1;

wallThick = caseHeight/2;
caseCornerRadius = wallThick;  // TODO: give the case a square profile by making caseCornerRadius less than half of the case height

topYOfFingerWells = caseLength - offsetFromTopEdgeToFirstFingerWell + buttonBodySeparation/2;
bottomYOfFingerWells = caseLength - offsetFromTopEdgeToFirstFingerWell - 4*fingerWidth - buttonBodySeparation/2;

alignmentPegWidth = 2;
alignmentPegHeight = 2;

pushButtonWellWidth = 7;
pushButtonHeight = 5.0;
pushButtonLegLength = 3.4;
pushButtonPressDepth = 0.25;
pushButtonWellDepth = 4.0;
pushButtonBaseThick = 1.5;

fingerWellDepth = 5;
thumbWellDepth = 5;
buttonThick = 8;
buttonWidth = pushButtonWellWidth;
fingerButtonLength = fingerWidth - buttonBodySeparation;
thumbButtonLength = thumbWidth - buttonBodySeparation;
buttonStabilizerWellWidth = 3;
buttonClearance = error;  // horizontal clearance between button and walls
buttonLip = 2.5;
buttonSlack = 0.5;  // minimum vertical clearance between button and floor/retainers
buttonRetainerThick = 1.0;
buttonStabilizerRodHeight = pushButtonHeight - (pushButtonPressDepth + buttonSlack + error2);

// buttons are inset by only 1/3 of the depth of the finger or thumb well
totalFingerWellDepth = fingerWellDepth/3 + buttonThick + pushButtonHeight;
totalThumbWellDepth = thumbWellDepth/3 + buttonThick + pushButtonHeight;

pinHoleRadius = 0.8;

thumbBevelLength = 48;
thumbBevelWidth = 31;
thumbBevelBuffer = 2;

thumbBevelAngle = angle(thumbBevelLength-wallThick,thumbBevelWidth-wallThick);
thumbBevelStretch = pythag(thumbBevelLength-wallThick, thumbBevelWidth-wallThick);

cavityWidth = caseWidth - wallThick - totalFingerWellDepth - pushButtonBaseThick;
cavityLength = caseLength - (2 * wallThick);
cavityHeight = caseHeight-floorThick-lidThick;

echo("caseWidth: ", caseWidth);
echo("caseLength: ", caseLength);
echo("caseHeight: ", caseHeight);
echo("wallThick: ", wallThick);
echo("cavityWidth: ", cavityWidth);
echo("cavityLength: ", cavityLength);
echo("cavityHeight: ", cavityHeight);

// for visualization only
nanoVizOffsetX = 1;
nanoVizOffsetY = 1;
modemVizOffsetX = cavityWidth - 2 - 16;
modemVizOffsetY = nanoLength + containmentWallThick + 2;
motionSensorVizOffsetX = nanoOffset + nanoWidth + containmentWallThick + 3;
motionSensorVizOffsetY = 3;
batteryVizOffsetX = modemVizOffsetX - 1 - 25;
batteryVizOffsetY = modemVizOffsetY;
transducerVizOffsetX = motionSensorVizOffsetX;
transducerVizOffsetY = motionSensorVizOffsetY + 1 + 26;

// creates a button of the given length.  Clearance on all sides of the button is subtracted.
module button(length) {
    difference() {
        union() {
            // rounded cap
            translate([buttonClearance,buttonWidth/2,buttonWidth/2]) {
                rotate([0,90,0]) { cylinder(h=length-2*buttonClearance,r=buttonWidth/2-buttonClearance, $fn=cornerRoundingRes); }
            }

            // button body
            translate([buttonClearance,buttonClearance,buttonWidth/2]) {
                cube([length-2*buttonClearance,buttonWidth-2*buttonClearance,buttonThick-buttonWidth/2]);
            }

            // stabilizer rods
            translate([buttonClearance,buttonClearance,buttonThick]) {
                cube([buttonStabilizerWellWidth-2*buttonClearance, buttonWidth-2*buttonClearance, buttonStabilizerRodHeight]);
            }
            translate([length-buttonStabilizerWellWidth+buttonClearance,buttonClearance,buttonThick]) {
                cube([buttonStabilizerWellWidth-2*buttonClearance, buttonWidth-2*buttonClearance, buttonStabilizerRodHeight]);
            }
            // retainer rods
            translate([buttonClearance,buttonClearance,buttonThick+pushButtonHeight-pushButtonWellDepth+buttonRetainerThick+buttonSlack+error2]) {
                cube([buttonStabilizerWellWidth+buttonLip-2*buttonClearance,buttonWidth-2*buttonClearance, 1]);
            }
            translate([length-buttonStabilizerWellWidth-buttonLip+buttonClearance,buttonClearance,buttonThick+pushButtonHeight-pushButtonWellDepth+buttonRetainerThick+buttonSlack+error2]) {
                cube([buttonStabilizerWellWidth+buttonLip-2*buttonClearance,buttonWidth-2*buttonClearance, 1]);
            }
        }

	    // hollow out the button to save plastic/money
        translate([0,buttonWidth/2,buttonWidth/2]) {
            rotate([0,90,0]) { cylinder(h=length+1,r=buttonWidth/3); }
        }
    }
}

// button well with origin at the center of the base of the switch
// legs of the switch run along the x axis
module pushButtonWell(length, depth) {
    translate([-pushButtonWellWidth/2, -pushButtonWellWidth/2, 0]) {

		// square well for body of switch
        cube([pushButtonWellWidth,pushButtonWellWidth, depth]);

		// channels for legs/wires of switch
        translate([0,0,-pushButtonLegLength]) {
                translate([0,0,0]) {
                    cube([1.5,1.5,depth+pushButtonLegLength]);
                }
                translate([pushButtonWellWidth-1.5,0, 0]) {
                    cube([1.5,1.5,depth+pushButtonLegLength]);
                }
                translate([0,pushButtonWellWidth-1.5, 0]) {
                    cube([1.5,1.5,depth+pushButtonLegLength]);
                }
                translate([pushButtonWellWidth-1.5,pushButtonWellWidth-1.5, 0]) {
                    cube([1.5,1.5,depth+pushButtonLegLength]);
                }
        }
    }

    // rectangular well for button body, and stabilizer wells
    translate([-length/2,-buttonWidth/2,pushButtonWellDepth]) {
        cube([length,buttonWidth,buttonThick+10]);

        translate([0,0,-pushButtonWellDepth]) {
            cube([buttonStabilizerWellWidth, buttonWidth, pushButtonWellDepth]);

            cube([buttonStabilizerWellWidth+buttonLip, buttonWidth, pushButtonWellDepth-buttonRetainerThick]);
		}

        translate([length-buttonStabilizerWellWidth,0,-pushButtonWellDepth]) {
            cube([buttonStabilizerWellWidth, buttonWidth, pushButtonWellDepth]);

            translate([-buttonLip,0,0]) {
                cube([buttonStabilizerWellWidth+buttonLip, buttonWidth, pushButtonWellDepth-buttonRetainerThick]);
            }
		}
    }
}

// depth: depth of circular well for the finger
// width: width of the entire construction
// totalWellDepth: depth to base of push button switch
// buttonLength: length of the moving part
module fingerWell(depth, width, totalWellDepth, buttonLength) {
    x = width / 2;
    y = depth;
    radius = (x*x + y*y) / (2*y);

    translate([radius - depth,0,0]) {
        cylinder(h=caseHeight,r=radius, $fn=cornerRoundingRes);
    }

    translate([radius - depth, 0, wallThick]) {
        cylinder(h=wallThick+.001, r1=radius-wallThick/2, r2=radius+wallThick/2, $fn=cornerRoundingRes);
    }

    translate([radius - depth,0,0]) {
        cylinder(h=wallThick+.001, r2=radius-wallThick/2, r1=radius+wallThick/2, $fn=cornerRoundingRes);
    }

    // well and channels for push button switch and wires
    translate([-totalWellDepth,0,caseHeight/2]) {
        rotate([0,90,0]) { rotate([0,0,90]) {
            pushButtonWell(buttonLength, pushButtonWellDepth);
        }}
    }
}

module fingerContainer(totalWellDepth, buttonLength) {
//    translate([-totalWellDepth-pushButtonBaseThick, -buttonLength-buttonBodySeparation, lidThick]) {
//    translate([-totalWellDepth-pushButtonBaseThick, 0, lidThick]) {
    translate([-totalWellDepth-pushButtonBaseThick, -buttonLength/2-buttonBodySeparation, lidThick]) {
        cube([totalWellDepth+pushButtonBaseThick, buttonLength + 2*buttonBodySeparation, caseHeight-lidThick]);
    }
}

// for the thumb rest
module roundedCylinder(height, radius, cornerRadius) {
    cylinder(r=(thumbCurveRadius - caseCornerRadius), h=caseHeight);
        translate([0, 0, caseCornerRadius]) {
            rotate_extrude(convexity = 10) {
                translate([thumbCurveRadius - caseCornerRadius, 0, 0]) {
                    circle(r = caseCornerRadius);
                }
            }
        cylinder(r=thumbCurveRadius, h=(caseHeight - 2*caseCornerRadius));
    }
    translate([0, 0, caseHeight - caseCornerRadius]) {
        rotate_extrude(convexity = 10) {
            translate([thumbCurveRadius - caseCornerRadius, 0, 0]) {
                circle(r = caseCornerRadius);
            }
        }
    }
}

// for the rounded flat edges of the case
module roundedEdge(length, height, cornerRadius) {
    rem = height - 2*cornerRadius;
    intersection() {
        translate([-height/2, 0, 0]) {
            cube([height, cornerRadius, length]);
        }
        union() {
            translate([-rem/2.0, 0, 0]) {
                cylinder(h=length, r=cornerRadius);
            }
            translate([rem/2.0, 0, 0]) {
                cylinder(h=length, r=cornerRadius);
            }
            translate([-rem/2.0, -cornerRadius, 0]) {
                cube([rem, 2 * cornerRadius, length]);
            }
        }
    }
}

// for the intersections between rounded edges
module roundedCorner(height, cornerRadius) {
    difference() {
        union() {
            translate([0, 0, -height/2.0 + cornerRadius]) {
                sphere(r=cornerRadius);
                cylinder(r=cornerRadius, h=(height - 2*cornerRadius));
            }
            translate([0, 0, height/2.0 - cornerRadius]) {
                sphere(r=cornerRadius);
            }
        }
        translate([0, 0, -height/2]) {
            cube([cornerRadius, cornerRadius, height]);
        }
    }
}

module pinHole() {
    cylinder(h=caseHeight+2,r=pinHoleRadius, $fn=pinHoleRes);
}

module pinHoles() {
    translate([0,0,-1]) {
        // pinholes on the straight portion of the right edge
        for (i = [1:4]) {
            translate([wallThick*2/3,wallThick*2/3 + i*(caseLength-thumbCurveRadius - wallThick*2/3)/4,0]) {
                pinHole();
            }
        }

        // pinholes close to the corners
        translate([wallThick*2/3,wallThick+pinHoleRadius,0]) {
            pinHole();
        }
        translate([caseWidth-wallThick*2/3,wallThick+pinHoleRadius,0]) {
            pinHole();
        }
        translate([caseWidth-wallThick-pinHoleRadius,caseLength-wallThick*2/3,0]) {
            pinHole();
        }
        translate([caseWidth-wallThick*2/3,caseLength-wallThick-pinHoleRadius,0]) {
            pinHole();
        }
        translate([caseWidth-wallThick-pinHoleRadius,wallThick*2/3,0]) {
            pinHole();
        }

        // pinholes on the bottom edge which avoid interfering with the battery charger port, power switch, and USB port
        translate([wallThick,0,0]) {
            translate([nanoOffset+pinHoleRadius,wallThick*2/3,0]) {
                pinHole();
            }
            translate([nanoOffset+nanoWidth-pinHoleRadius,wallThick*2/3,0]) {
                pinHole();
            }
            translate([powerSwitchOffset-pinHoleRadius,wallThick*2/3,0]) {
                pinHole();
            }
            translate([powerSwitchOffset+powerSwitchLength+2*containmentWallThick+2*foreignPartClearance+pinHoleRadius,wallThick*2/3,0]) {
                pinHole();
            }
        }

        // pinholes on curved surface of thumb rest
        translate([thumbCurveRadius, caseLength-thumbCurveRadius, 0]) {
            rotate([0,0,-20]) {
                translate([-thumbCurveRadius+wallThick*2/3,0,0]) {
                    pinHole();
                }
            }
            rotate([0,0,-40]) {
                translate([-thumbCurveRadius+wallThick*2/3,0,0]) {
                    pinHole();
                }
            }
            rotate([0,0,-60]) {
                translate([-thumbCurveRadius+wallThick*2/3,0,0]) {
                    pinHole();
                }
            }
        }
    }
}

module lightSensorWell() {
    translate([caseWidth + 1, ((caseLength - wallThick) + topYOfFingerWells) / 2, caseHeight / 2]) {
        rotate([0,-90,0]) { rotate([0,0,90]) {
            cylinder(r=(lightSensorRadius+foreignPartClearance), h=(lightSensorThick+foreignPartClearance + 1));
            translate([-lightSensorRadius, -(lightSensorWireThick + error)/2, 0]) {
                cube([lightSensorRadius*2, lightSensorWireThick + error, wallThick + 2]);
            }
        }}
    }
}

module caseConvexHull() {
    // box with cutout for thumb cylinder
    difference() {
        translate([wallThick, wallThick,0]) {
            cube([caseWidth-caseHeight,caseLength-caseHeight,caseHeight]);
        }
        translate([0, caseLength - thumbCurveRadius, -1]) {
            cube([thumbCurveRadius, thumbCurveRadius, caseHeight + 2]);
        }
    }

    // thumb rest
    difference() {
        translate([thumbCurveRadius, caseLength - thumbCurveRadius, 0]) {
            intersection() {
                 roundedCylinder(caseHeight, thumbCurveRadius, caseCornerRadius);
                 translate([-thumbCurveRadius, 0, 0]) {
                    cube([thumbCurveRadius, thumbCurveRadius, caseHeight]);
                }
            }
        }
        translate([thumbCurveRadius, caseLength - thumbCurveRadius, lidThick]) {
            intersection() {
                cylinder(r=(thumbCurveRadius - caseCornerRadius), h=(caseHeight - lidThick - floorThick));
                translate([-thumbCurveRadius, 0, 0]) { cube([thumbCurveRadius, thumbCurveRadius, caseHeight]); }
            }
        }
    }

    // rounded edges and corners
    ccr = caseCornerRadius;
    shortEdge = caseWidth - 2*ccr;
    mediumEdge = caseLength - thumbCurveRadius - ccr;
    longEdge = caseLength - 2 * ccr;
    tinyEdge = caseWidth - thumbCurveRadius - ccr;
    translate([ccr, ccr, caseHeight/2.0]) {
        roundedCorner(caseHeight, ccr);
        rotate([-90, 0, 0]) { rotate([0, 0, 90]) {
            roundedEdge(mediumEdge, caseHeight, ccr);
        }}
        rotate([0, 90, 0]) { rotate([0, 0, 180]) {
            roundedEdge(shortEdge, caseHeight, ccr);
        }}
    }
    translate([caseWidth-ccr, ccr, caseHeight/2.0]) {
        rotate([0, 0, 90]) { roundedCorner(caseHeight, ccr); }
        rotate([-90, 0, 0]) { rotate([0, 0, -90]) {
            roundedEdge(longEdge, caseHeight, ccr);
        }}
    }
    translate([caseWidth-ccr, caseLength-ccr, caseHeight/2.0]) {
        rotate([0, 0, 180]) { roundedCorner(caseHeight, ccr); }
        rotate([0, -90, 0]) {
            roundedEdge(tinyEdge, caseHeight, ccr);
        }
    }
}

thumbWellDepression = 2;

module thumbButtonContainer() {
    r = thumbCurveRadius;
    x = r - horizontalOffsetBeforeThumbWell;
    y = sqrt(r*r - x*x);
    translate([horizontalOffsetBeforeThumbWell, caseLength - r + y - thumbWellDepression, 0]) {
        rotate([0,0,90]) {
            translate([0,-(thumbButtonLength+buttonBodySeparation)/2],0){
                fingerContainer(totalFingerWellDepth, thumbButtonLength);
            }
        }
    }
}

module thumbButtonWell() {
    r = thumbCurveRadius;
    x = r - horizontalOffsetBeforeThumbWell;
    y = sqrt(r*r - x*x);
    translate([horizontalOffsetBeforeThumbWell, caseLength - r + y - thumbWellDepression, 0]) {
        rotate([0,0,90]) {
            translate([0,-(thumbButtonLength+buttonBodySeparation)/2],0){
                fingerWell(thumbWellDepth, thumbWidth, totalFingerWellDepth, thumbButtonLength);
            }
        }
    }
}

module basicCase() {
    difference() {
        union() {
            difference() {
                caseConvexHull();

                // inner cavity
                translate([0,0,lidThick]) {
                    translate([wallThick,wallThick,0]) {
                        cube([cavityWidth,caseLength-thumbCurveRadius-wallThick+0.001,cavityHeight]);
                    }
                    translate([thumbCurveRadius,wallThick,0]) {
                        cube([cavityWidth+wallThick-thumbCurveRadius,cavityLength,cavityHeight]);
                    }
                    translate([wallThick+cavityWidth - 0.001,topYOfFingerWells,0]) {
                        cube([caseWidth - 2*wallThick - cavityWidth, offsetFromTopEdgeToFirstFingerWell - wallThick - buttonBodySeparation/2, cavityHeight]);
                    }
                    translate([wallThick+cavityWidth - 0.001,wallThick,0]) {
                        cube([caseWidth - 2*wallThick - cavityWidth, bottomYOfFingerWells - wallThick, cavityHeight]);
                    }
                }
            }

            thumbButtonContainer();
        }

        pinHoles();

        // finger button wells
        for (i = [0:3]) {
            translate([
                caseWidth,
                caseLength - offsetFromTopEdgeToFirstFingerWell - fingerWidth * (i + 0.5),
                0]) {
                    fingerWell(fingerWellDepth, fingerWidth, totalFingerWellDepth, fingerButtonLength);
            }
        }

        thumbButtonWell();

        lightSensorWell();

        // hole for status LED
        translate([caseWidth - wallThick - 5.5, caseLength, caseHeight/2]) {
            rotate(a=[90,0,0]) {
                cylinder(h=wallThick,r=ledDomeRadius+foreignPartClearance, $fn=ledHoleRes);

                // note: the rim depression uses a larger error/clearance, since it doesn't need
                // to fit tightly and can't be filed down
                translate([0,0,-2+wallThick]) {
                    cylinder(h=10,r=ledRimRadius+foreignPartClearance, $fn=20);
                }
            }
        }

        // holes for ports/controls on bottom edge
        translate([wallThick, 0, 0]) {
            // hole for Arduino USB port
            translate([nanoOffset+foreignPartClearance+nanoWidth/2-nanoUsbWidth/2,0,caseHeight-floorThick-nanoHeightToUsbBase-nanoUsbHeight-foreignPartClearance]) {
                cube([nanoUsbWidth+2*foreignPartClearance,wallThick+1,nanoUsbHeight+foreignPartClearance]);
            }

            translate([nanoOffset+foreignPartClearance+nanoWidth/2-nanoUsbCableWidth/2,0,caseHeight-floorThick-nanoHeightToUsbBase-nanoUsbHeight/2-nanoUsbCableHeight/2-foreignPartClearance]) {
                cube([nanoUsbCableWidth+2*foreignPartClearance,wallThick-nanoUsbCableOffsetFromPerfboard,nanoUsbCableHeight+foreignPartClearance]);
            }

            // hole for battery charger headers
            translate([chargerHeaderOffset,0,(caseHeight-chargerHeaderLength-error2)/2]) {
                cube([chargerHeaderWidth+error2,wallThick+1,chargerHeaderLength+error2]);
            }

            // hole for SPDT Mini Power Switch
            translate([powerSwitchOffset+containmentWallThick,0,wallThick-powerSwitchWidth/2-error]) {
                cube([powerSwitchLength+error2,wallThick+1,powerSwitchWidth+error2]);
            }
        }

        // cutaway for laser shelf
        translate([caseWidth - wallThick - 1, bottomYOfFingerWells - laserWidth - foreignPartClearance*2, lidThick]) {
            cube([wallThick + 2, laserWidth + foreignPartClearance*2, laserThick + foreignPartClearance*2]);
        }
    }
}

module alignmentPeg() {
    cube([alignmentPegWidth,alignmentPegWidth,(caseHeight-buttonWidth)/2 - lidThick]);
    translate([error, error, 0]) {
        cube([alignmentPegWidth-error2,alignmentPegWidth-error2,alignmentPegHeight]);
    }
}

// body
difference() {
    basicCase();

    cube([caseWidth, caseLength, (caseHeight-buttonWidth)/2+0.0001]);

    // screwdriver/leverage slot(s)
    translate([wallThick+cavityWidth,wallThick/2,(caseHeight-buttonWidth)/2]) {
        rotate([150,0,0]) { cube([5,10,10]); }
    }
}

// lid
translate([-5, 0, 8]) {
    rotate([0,180,0]) {
        difference() {
            basicCase();
            translate([0,0,(caseHeight-buttonWidth)/2]) {
                cube([caseWidth, caseLength, caseHeight]);
            }
        }

        // alignment pegs
        translate([wallThick, wallThick, lidThick]) { alignmentPeg(); }
        translate([caseWidth-wallThick-alignmentPegWidth, wallThick, lidThick]) { alignmentPeg(); }
        translate([caseWidth-wallThick-alignmentPegWidth, caseLength-wallThick-alignmentPegWidth, lidThick]) { alignmentPeg(); }
    }
}

// buttons
// Note: the buttons are oriented so as to make the longest surfaces the smoothest
translate([caseWidth + 15,bottomYOfFingerWells+buttonBodySeparation,10]) {
    for (i = [0:3]) {
        translate([0,i*(fingerButtonLength+buttonBodySeparation),0]) { rotate([90,180,-90]) {
            button(fingerButtonLength);
        }}
    }
}
translate([horizontalOffsetBeforeThumbWell+thumbButtonLength+buttonBodySeparation,caseLength+12,10]) {
    rotate([90,180,0]) {
        button(thumbButtonLength);
    }
}

// electronic components
translate([wallThick,wallThick,0]) {

    // container for Arduino Nano v3.0
    translate([nanoOffset,0,0]) {
        translate([0,0,caseHeight-floorThick-nanoHeightToUsbBase-error]) {
            translate([nanoWidth+2*foreignPartClearance,0,0]) {
                cube([containmentWallThick,nanoLength+2*foreignPartClearance+containmentWallThick,nanoHeightToUsbBase+error]);
            }
            translate([0,nanoLength+2*foreignPartClearance,0]) {
                cube([nanoWidth+2*foreignPartClearance+containmentWallThick,containmentWallThick,nanoHeightToUsbBase+error]);
            }
        }
    }

    // container for power switch
    translate([powerSwitchOffset,0,wallThick-powerSwitchWidth/2-error]) {
        cube([containmentWallThick,powerSwitchDepth-wallThick+error+containmentWallThick,caseHeight-floorThick-wallThick+powerSwitchWidth/2+error]);
        translate([containmentWallThick+powerSwitchLength+error2,0,0]) {
            cube([containmentWallThick,powerSwitchDepth-wallThick+error+containmentWallThick,caseHeight-floorThick-wallThick+powerSwitchWidth/2+error]);
        }
        translate([0,0,powerSwitchWidth+2*error]) {
            cube([powerSwitchLength+2*(containmentWallThick+error),powerSwitchDepth-wallThick+error+containmentWallThick,caseHeight-floorThick-wallThick-powerSwitchWidth/2-error]);
        }
        translate([0,powerSwitchDepth-wallThick+error,powerSwitchWidth+2*error-1]) {
            cube([powerSwitchLength+2*(containmentWallThick+error),containmentWallThick,1]);
        }
    }

    // shelf for the laser
    translate([cavityWidth, bottomYOfFingerWells - laserWidth - 2*foreignPartClearance - wallThick, lidThick + laserThick + 2 * foreignPartClearance]) {
        cube([caseWidth-2*wallThick-cavityWidth, laserWidth + 2*foreignPartClearance, containmentShelfThick]);
    }

    if (visualizeInternalComponents) {
        // Arduino Nano v3.0
        translate([nanoVizOffsetX,nanoVizOffsetY,-10]) {
            cube([nanoWidth,nanoLength,nanoHeightWithoutUsb]);
        }

        // Surface Transducer - Small
        translate([transducerVizOffsetX,transducerVizOffsetY,-10]) {
           cube([21.4,13.8,7.9]);
        }
        // Bluetooth Modem - BlueSMiRF Silver -- 42.0 x 16.0 x 3.9
        translate([modemVizOffsetX,modemVizOffsetY,-10]) { cube([16,42,3.9]); }
        // Polymer Lithium Ion Battery - 110mAh
        //translate([-40,0,-10]) { cube([12,28,5.7]); }
        // Polymer Lithium Ion Battery - 400mAh
	    translate([batteryVizOffsetX,batteryVizOffsetY,-10]) { cube([25,35,5]); }
        // Triple Axis Accelerometer & Gyro Breakout - MPU-6050
        translate([motionSensorVizOffsetX,motionSensorVizOffsetY,-10]) { cube([15.5, 25.7, 2.5]); }
    }
}

// The origin is at the "bottom right" of the case (in the empty space where the lid goes),
// with the thumb to the north (the y axis), the fingers of the left hand to the left
// (the x axis) and pointing away, and the bottom of the case pointing down (the z axis)
// away from the palm.

function pythag(a, b) = sqrt(a*a + b*b);
function angle(a, b) = atan(a/b);

// toggle this variable to see the ICs in position and make sure they fit inside the case
visualizeInternalComponents = true;

// this is the accuracy value for Shapeways' "Strong & Flexible Plastics" option
error = 0.15;

error2 = error * 2;

// note: AFAIK, "clearance" is a minimum gap in printed parts.  Here, however, it is used
// as a lesser/safer accuracy for gaps between the printed part and foreign objects such as
// circuit boards, which may not have been cut or measured with the same degree of precision.
clearance = 0.5;

pinHoleRes = 10;
/*
cornerRoundingRes = 100;
connectorPinRes = 100;
ledHoleRes = 100;
//*/
//*
cornerRoundingRes = 10;
connectorPinRes = 10;
ledHoleRes = 10;
//*/
containmentWallThick = 1;

thumbCurveRadius = 39.0;
offsetFromTopEdgeToFirstFingerWell = 15.0;
fingerWidth = 84 / 4;  // 21 -- 82mm was measured, but this seemed a little cramped
thumbWidth = 25;
horizontalOffsetBeforeThumbWell = 25.0;
buttonBodySeparation = 1.5;
thumbButtonTiltDegrees = 5;

nanoLength = 43.2;
nanoWidth = 18.0;
nanoHeightWithoutUsb = 5.4;
nanoHeightToUsbBase = 3.3;
nanoUsbWidth = 7.6;
nanoUsbHeight = 3.9;

chargerHeaderLength = 5.1;
chargerHeaderWidth = 2.6;

powerSwitchLength = 11.6;
powerSwitchWidth = 4.0;
powerSwitchDepth = 7.5;

// horizontal offset of components with ports/controls on the bottom edge
nanoOffset = 0;
nanoOuterWallOffset = nanoOffset + nanoWidth + 2*clearance;
chargerHeaderOffset = nanoOuterWallOffset + containmentWallThick;
powerSwitchOffset = chargerHeaderOffset + chargerHeaderWidth + error2;

ledDomeRadius = 2.5;
ledRimRadius = 2.95;
ledRimThick = 1.0;

lidThick = 1.5;
floorThick = 1.5;

caseWidth = 70;
caseLength = 130;
thickestComponent = 7.9;
caseHeight = lidThick + floorThick + thickestComponent + 1;

wallThick = caseHeight/2;
caseCornerRadius = wallThick;  // TODO: give the case a square profile by making caseCornerRadius less than half of the case height

echo("caseHeight: ", caseHeight);
echo("wallThick: ", wallThick);

pushButtonWellWidth = 7;
pushButtonHeight = 5.0;
pushButtonLegLength = 3.4;
pushButtonPressDepth = 0.25;
pushButtonWellDepth = 4.0;
pushButtonBaseThick = 1.5;

fingerWellDepth = 5;
thumbWellDepth = 5;
buttonThick = 8;
buttonWidth = pushButtonWellWidth;
fingerButtonLength = fingerWidth - buttonBodySeparation;
thumbButtonLength = thumbWidth - buttonBodySeparation;
buttonStabilizerWellWidth = 3;
buttonClearance = 0.3;  // horizontal clearance between button and walls
buttonLip = 2.5;
buttonSlack = 0.5;  // minimum vertical clearance between button and floor/retainers
buttonRetainerThick = 1.0;
buttonStabilizerRodHeight = pushButtonHeight - (pushButtonPressDepth + buttonSlack + error2);

// buttons are inset by only 1/3 of the depth of the finger or thumb well
totalFingerWellDepth = fingerWellDepth/3 + buttonThick + pushButtonHeight;
totalThumbWellDepth = thumbWellDepth/3 + buttonThick + pushButtonHeight;

pinHoleRadius = 0.8;

thumbBevelLength = 48;
thumbBevelWidth = 31;
thumbBevelBuffer = 2;

thumbBevelAngle = angle(thumbBevelLength-wallThick,thumbBevelWidth-wallThick);
thumbBevelStretch = pythag(thumbBevelLength-wallThick, thumbBevelWidth-wallThick);

cavityWidth = caseWidth - wallThick - totalFingerWellDepth - pushButtonBaseThick;
cavityLength = caseLength - (2 * wallThick);

// for visualization only
nanoVizOffsetX = 1;
nanoVizOffsetY = 1;
modemVizOffsetX = nanoOffset + nanoWidth + containmentWallThick + 1;
modemVizOffsetY = 1;
motionSensorVizOffsetX = 1;
motionSensorVizOffsetY = nanoLength + containmentWallThick + 1;
batteryVizOffsetX = modemVizOffsetX;
batteryVizOffsetY = motionSensorVizOffsetY;
transducerVizOffsetX = cavityWidth - 15;
transducerVizOffsetY = batteryVizOffsetY + 47 + 1;

// creates a button of the given length.  Clearance on all sides of the button is subtracted.
module button(length) {
    difference() {
        union() {
            // rounded cap
            translate([buttonClearance,buttonWidth/2,buttonWidth/2]) {
                rotate([0,90,0]) { cylinder(h=length-2*buttonClearance,r=buttonWidth/2-buttonClearance, $fn=cornerRoundingRes); }
            }

            // button body
            translate([buttonClearance,buttonClearance,buttonWidth/2]) {
                cube([length-2*buttonClearance,buttonWidth-2*buttonClearance,buttonThick-buttonWidth/2]);
            }

            // stabilizer rods
            translate([buttonClearance,buttonClearance,buttonThick]) {
                cube([buttonStabilizerWellWidth-2*buttonClearance, buttonWidth-2*buttonClearance, buttonStabilizerRodHeight]);
            }
            translate([length-buttonStabilizerWellWidth+buttonClearance,buttonClearance,buttonThick]) {
                cube([buttonStabilizerWellWidth-2*buttonClearance, buttonWidth-2*buttonClearance, buttonStabilizerRodHeight]);
            }
            // retainer rods
            translate([buttonClearance,buttonClearance,buttonThick+pushButtonHeight-pushButtonWellDepth+buttonRetainerThick+buttonSlack+error2]) {
                cube([buttonStabilizerWellWidth+buttonLip-2*buttonClearance,buttonWidth-2*buttonClearance, 1]);
            }
            translate([length-buttonStabilizerWellWidth-buttonLip+buttonClearance,buttonClearance,buttonThick+pushButtonHeight-pushButtonWellDepth+buttonRetainerThick+buttonSlack+error2]) {
                cube([buttonStabilizerWellWidth+buttonLip-2*buttonClearance,buttonWidth-2*buttonClearance, 1]);
            }
        }

	    // hollow out the button to save plastic/money
        translate([0,buttonWidth/2,buttonWidth/2]) {
            rotate([0,90,0]) { cylinder(h=length+1,r=buttonWidth/3); }
        }
    }
}

// button well with origin at the center of the base of the switch
// legs of the switch run along the x axis
module pushButtonWell(length, depth) {
    translate([-pushButtonWellWidth/2, -pushButtonWellWidth/2, 0]) {

		// square well for body of switch
        cube([pushButtonWellWidth,pushButtonWellWidth, depth]);

		// channels for legs/wires of switch
        translate([0,0,-pushButtonLegLength]) {
                translate([0,0,0]) {
                    cube([1.5,1.5,depth+pushButtonLegLength]);
                }
                translate([pushButtonWellWidth-1.5,0, 0]) {
                    cube([1.5,1.5,depth+pushButtonLegLength]);
                }
                translate([0,pushButtonWellWidth-1.5, 0]) {
                    cube([1.5,1.5,depth+pushButtonLegLength]);
                }
                translate([pushButtonWellWidth-1.5,pushButtonWellWidth-1.5, 0]) {
                    cube([1.5,1.5,depth+pushButtonLegLength]);
                }
        }
    }

    // rectangular well for button body, and stabilizer wells
    translate([-length/2,-buttonWidth/2,pushButtonWellDepth]) {
        cube([length,buttonWidth,buttonThick+10]);

        translate([0,0,-pushButtonWellDepth]) {
            cube([buttonStabilizerWellWidth, buttonWidth, pushButtonWellDepth]);

            cube([buttonStabilizerWellWidth+buttonLip, buttonWidth, pushButtonWellDepth-buttonRetainerThick]);
		}

        translate([length-buttonStabilizerWellWidth,0,-pushButtonWellDepth]) {
            cube([buttonStabilizerWellWidth, buttonWidth, pushButtonWellDepth]);

            translate([-buttonLip,0,0]) {
                cube([buttonStabilizerWellWidth+buttonLip, buttonWidth, pushButtonWellDepth-buttonRetainerThick]);
            }
		}
    }
}

// depth: depth of circular well for the finger
// width: width of the entire construction
// totalWellDepth: depth to base of push button switch
// buttonLength: length of the moving part
module fingerWell(depth, width, totalWellDepth, buttonLength) {
    x = width / 2;
    y = depth;
    radius = (x*x + y*y) / (2*y);

    translate([radius - depth,0,0]) {
        cylinder(h=caseHeight,r=radius, $fn=cornerRoundingRes);
    }

    translate([radius - depth, 0, wallThick]) {
        cylinder(h=wallThick+.001, r1=radius-wallThick/2, r2=radius+wallThick/2, $fn=cornerRoundingRes);
    }

    translate([radius - depth,0,0]) {
        cylinder(h=wallThick+.001, r2=radius-wallThick/2, r1=radius+wallThick/2, $fn=cornerRoundingRes);
    }

    // well and channels for push button switch and wires
    translate([-totalWellDepth,0,caseHeight/2]) {
        rotate([0,90,0]) { rotate([0,0,90]) {
            pushButtonWell(buttonLength, pushButtonWellDepth);
        }}
    }
}

module fingerContainer(totalWellDepth, buttonLength) {
//    translate([-totalWellDepth-pushButtonBaseThick, -buttonLength-buttonBodySeparation, lidThick]) {
//    translate([-totalWellDepth-pushButtonBaseThick, 0, lidThick]) {
    translate([-totalWellDepth-pushButtonBaseThick, -buttonLength/2-buttonBodySeparation, lidThick]) {
        cube([totalWellDepth+pushButtonBaseThick, buttonLength + 2*buttonBodySeparation, caseHeight-lidThick]);
    }
}

// for the thumb rest
module roundedCylinder(height, radius, cornerRadius) {
    cylinder(r=(thumbCurveRadius - caseCornerRadius), h=caseHeight);
        translate([0, 0, caseCornerRadius]) {
            rotate_extrude(convexity = 10) {
                translate([thumbCurveRadius - caseCornerRadius, 0, 0]) {
                    circle(r = caseCornerRadius);
                }
            }
        cylinder(r=thumbCurveRadius, h=(caseHeight - 2*caseCornerRadius));
    }
    translate([0, 0, caseHeight - caseCornerRadius]) {
        rotate_extrude(convexity = 10) {
            translate([thumbCurveRadius - caseCornerRadius, 0, 0]) {
                circle(r = caseCornerRadius);
            }
        }
    }
}

// for the rounded flat edges of the case
module roundedEdge(length, height, cornerRadius) {
    rem = height - 2*cornerRadius;
    intersection() {
        translate([-height/2, 0, 0]) {
            cube([height, cornerRadius, length]);
        }
        union() {
            translate([-rem/2.0, 0, 0]) {
                cylinder(h=length, r=cornerRadius);
            }
            translate([rem/2.0, 0, 0]) {
                cylinder(h=length, r=cornerRadius);
            }
            translate([-rem/2.0, -cornerRadius, 0]) {
                cube([rem, 2 * cornerRadius, length]);
            }
        }
    }
}

// for the intersections between rounded edges
module roundedCorner(height, cornerRadius) {
    difference() {
        union() {
            translate([0, 0, -height/2.0 + cornerRadius]) {
                sphere(r=cornerRadius);
                cylinder(r=cornerRadius, h=(height - 2*cornerRadius));
            }
            translate([0, 0, height/2.0 - cornerRadius]) {
                sphere(r=cornerRadius);
            }
        }
        translate([0, 0, -height/2]) {
            cube([cornerRadius, cornerRadius, height]);
        }
    }
}

module pinHole() {
    cylinder(h=caseHeight+2,r=pinHoleRadius, $fn=pinHoleRes);
}

module pinHoles() {
    translate([0,0,-1]) {
        for (i = [1:4]) {
            translate([wallThick*2/3,wallThick*2/3 + i*(caseLength-thumbCurveRadius - wallThick*2/3)/4,0]) {
                pinHole();
            }
        }
        // pinhole on right edge close to the lower right corner
        translate([wallThick*2/3,10,0]) {
            pinHole();
        }

        // pinholes on the bottom edge which avoid interfering with the battery charger port, power switch, and USB port
        translate([10,wallThick*2/3,0]) {
            pinHole();
        }
        translate([wallThick+cavityWidth-nanoLeftMargin-nanoWidth/2-8,wallThick*2/3,0]) {
            pinHole();
        }
        translate([wallThick+cavityWidth-nanoLeftMargin-nanoWidth/2+8,wallThick*2/3,0]) {
            pinHole();
        }
    }
}

module caseConvexHull() {
    // box with cutout for thumb cylinder
    difference() {
        translate([wallThick, wallThick,0]) {
            cube([caseWidth-caseHeight,caseLength-caseHeight,caseHeight]);
        }
        translate([0, caseLength - thumbCurveRadius, -1]) {
            cube([thumbCurveRadius, thumbCurveRadius, caseHeight + 2]);
        }
    }

    // thumb rest
    difference() {
        translate([thumbCurveRadius, caseLength - thumbCurveRadius, 0]) {
            intersection() {
                 roundedCylinder(caseHeight, thumbCurveRadius, caseCornerRadius);
                 translate([-thumbCurveRadius, 0, 0]) {
                    cube([thumbCurveRadius, thumbCurveRadius, caseHeight]);
                }
            }
        }
        translate([thumbCurveRadius, caseLength - thumbCurveRadius, lidThick]) {
            intersection() {
                cylinder(r=(thumbCurveRadius - caseCornerRadius), h=(caseHeight - lidThick - floorThick));
                translate([-thumbCurveRadius, 0, 0]) { cube([thumbCurveRadius, thumbCurveRadius, caseHeight]); }
            }
        }
    }

    // rounded edges and corners
    ccr = caseCornerRadius;
    shortEdge = caseWidth - 2*ccr;
    mediumEdge = caseLength - thumbCurveRadius - ccr;
    longEdge = caseLength - 2 * ccr;
    tinyEdge = caseWidth - thumbCurveRadius - ccr;
    translate([ccr, ccr, caseHeight/2.0]) {
        roundedCorner(caseHeight, ccr);
        rotate([-90, 0, 0]) { rotate([0, 0, 90]) {
            roundedEdge(mediumEdge, caseHeight, ccr);
        }}
        rotate([0, 90, 0]) { rotate([0, 0, 180]) {
            roundedEdge(shortEdge, caseHeight, ccr);
        }}
    }
    translate([caseWidth-ccr, ccr, caseHeight/2.0]) {
        rotate([0, 0, 90]) { roundedCorner(caseHeight, ccr); }
        rotate([-90, 0, 0]) { rotate([0, 0, -90]) {
            roundedEdge(longEdge, caseHeight, ccr);
        }}
    }
    translate([caseWidth-ccr, caseLength-ccr, caseHeight/2.0]) {
        rotate([0, 0, 180]) { roundedCorner(caseHeight, ccr); }
        rotate([0, -90, 0]) {
            roundedEdge(tinyEdge, caseHeight, ccr);
        }
    }
}

thumbWellDepression = 2;

module thumbButtonContainer() {
    r = thumbCurveRadius;
    x = r - horizontalOffsetBeforeThumbWell;
    y = sqrt(r*r - x*x);
    translate([horizontalOffsetBeforeThumbWell, caseLength - r + y - thumbWellDepression, 0]) {
        rotate([0,0,90]) {
            translate([0,-(thumbButtonLength+buttonBodySeparation)/2],0){
                fingerContainer(totalFingerWellDepth, thumbButtonLength);
            }
        }
    }
}

module thumbButtonWell() {
    r = thumbCurveRadius;
    x = r - horizontalOffsetBeforeThumbWell;
    y = sqrt(r*r - x*x);
    translate([horizontalOffsetBeforeThumbWell, caseLength - r + y - thumbWellDepression, 0]) {
        rotate([0,0,90]) {
            translate([0,-(thumbButtonLength+buttonBodySeparation)/2],0){
                fingerWell(thumbWellDepth, thumbWidth, totalFingerWellDepth, thumbButtonLength);
            }
        }
    }
}

module basicCase() {
  difference() {
    union() {
        difference() {
            caseConvexHull();

            // inner compartment
            translate([wallThick,wallThick,lidThick]) {
                cube([cavityWidth,caseLength-thumbCurveRadius-wallThick+0.001,caseHeight-floorThick-lidThick]);
            }
            translate([thumbCurveRadius,wallThick,lidThick]) {
                cube([cavityWidth+wallThick-thumbCurveRadius,cavityLength,caseHeight-floorThick-lidThick]);
            }
        }

        thumbButtonContainer();
    }

    pinHoles();

    // finger button wells
    for (i = [0:3]) {
        translate([
            caseWidth,
            caseLength - offsetFromTopEdgeToFirstFingerWell - fingerWidth * (i + 0.5),
            0]) {
                fingerWell(fingerWellDepth, fingerWidth, totalFingerWellDepth, fingerButtonLength);
        }
    }

    thumbButtonWell();

    // hole for status LED
    translate([0,40,caseHeight/2]) {
    	    rotate(a=[0,90,0]) {
            cylinder(h=wallThick,r=ledDomeRadius+error, $fn=ledHoleRes);

            // note: the rim depression uses a larger error/clearance, since it doesn't need
            // to fit tightly and can't be filed down
            translate([0,0,-2+wallThick]) {
//            translate([0,0,-1+wallThick+ledRimThick]) {
                cylinder(h=10,r=ledRimRadius+clearance, $fn=20);
            }
        }
    }

    // holes for ports/controls on bottom edge
    translate([wallThick, 0, 0]) {
        // hole for Arduino USB port
        translate([nanoOffset+clearance+nanoWidth/2-nanoUsbWidth/2,0,caseHeight-floorThick-nanoHeightToUsbBase-nanoUsbHeight-clearance]) {
            cube([nanoUsbWidth+2*clearance,wallThick+1,nanoUsbHeight+clearance]);
        }

        // hole for battery charger headers
        translate([chargerHeaderOffset,0,(caseHeight-chargerHeaderLength-error2)/2]) {
            cube([chargerHeaderWidth+error2,wallThick+1,chargerHeaderLength+error2]);
        }

        // hole for SPDT Mini Power Switch
        translate([powerSwitchOffset+containmentWallThick,0,wallThick-powerSwitchWidth/2-error]) {
            cube([powerSwitchLength+error2,wallThick+1,powerSwitchWidth+error2]);
        }
    }
  }
}

// body
difference() {
    basicCase();

    cube([caseWidth, caseLength, (caseHeight-buttonWidth)/2+0.0001]);

    // matching holes for alignment pegs
    translate([caseWidth-wallThick, wallThick, 0]) {
        cylinder(h=caseHeight*2/3, r=1.5+error, $fn=connectorPinRes);
    }
    translate([caseWidth-wallThick, caseLength-wallThick, 0]) {
        cylinder(h=caseHeight*2/3, r=1.5+error, $fn=connectorPinRes);
    }

    // screwdriver/leverage slots
    translate([wallThick,wallThick/2,(caseHeight-buttonWidth)/2]) {
        rotate([150,0,0]) { cube([5,10,10]); }
    }
    translate([wallThick+cavityWidth+3,wallThick/2,(caseHeight-buttonWidth)/2]) {
        rotate([150,0,0]) { cube([5,10,10]); }
    }
    translate([wallThick+cavityWidth+3,caseLength-wallThick/2,(caseHeight-buttonWidth)/2]) {
        rotate([-60,0,0]) { cube([5,10,10]); }
    }
}

// lid
translate([caseWidth + 10, 0, 0]) {
    difference() {
        basicCase();
        translate([0,0,(caseHeight-buttonWidth)/2]) {
            cube([caseWidth, caseLength, caseHeight]);
        }
    }

    // alignment pegs
    translate([caseWidth-wallThick, wallThick, lidThick]) {
        cylinder(h=caseHeight/3, r=1.5, $fn=connectorPinRes);
    }
    translate([caseWidth-wallThick, caseLength-wallThick, lidThick]) {
        cylinder(h=caseHeight/3, r=1.5, $fn=connectorPinRes);
    }
}

//tmp
/*
    r = thumbCurveRadius;
    x = r - horizontalOffsetBeforeThumbWell;
    y = sqrt(r*r - x*x);
    translate([horizontalOffsetBeforeThumbWell, caseLength - r + y, 0]) {
        cube([10,20,40]);
    }
*/

// buttons
// Note: the buttons are oriented so as to make the longest surfaces the smoothest
translate([-10,0,10]) {
    for (i = [0:3]) {
        translate([0,i*(fingerButtonLength+2),0]) { rotate([90,180,-90]) {
            button(fingerButtonLength);
        }}
    }
}
translate([-2,caseLength-35,10]) {
    rotate([90,180,-90]) {
        button(thumbButtonLength);
    }
}

// electronic components
translate([wallThick,wallThick,0]) {

    // container for Arduino Nano v3.0
    translate([nanoOffset,0,0]) {
        translate([0,0,caseHeight-floorThick-nanoHeightToUsbBase-error]) {
            translate([nanoWidth+2*clearance,0,0]) {
                cube([containmentWallThick,nanoLength+2*clearance+containmentWallThick,nanoHeightToUsbBase+error]);
            }
            translate([0,nanoLength+2*clearance,0]) {
                cube([nanoWidth+2*clearance+containmentWallThick,containmentWallThick,nanoHeightToUsbBase+error]);
            }
        }
    }

    // container for power switch
    translate([powerSwitchOffset,0,wallThick-powerSwitchWidth/2-error]) {
        cube([containmentWallThick,powerSwitchDepth-wallThick+error+containmentWallThick,caseHeight-floorThick-wallThick+powerSwitchWidth/2+error]);
        translate([containmentWallThick+powerSwitchLength+error2,0,0]) {
            cube([containmentWallThick,powerSwitchDepth-wallThick+error+containmentWallThick,caseHeight-floorThick-wallThick+powerSwitchWidth/2+error]);
        }
        translate([0,0,powerSwitchWidth+2*error]) {
            cube([powerSwitchLength+2*(containmentWallThick+error),powerSwitchDepth-wallThick+error+containmentWallThick,caseHeight-floorThick-wallThick-powerSwitchWidth/2-error]);
        }
        translate([0,powerSwitchDepth-wallThick+error,powerSwitchWidth+2*error-1]) {
            cube([powerSwitchLength+2*(containmentWallThick+error),containmentWallThick,1]);
        }
    }

    if (visualizeInternalComponents) {
        // Arduino Nano v3.0
        translate([nanoVizOffsetX,nanoVizOffsetY,-10]) {
            cube([nanoWidth,nanoLength,nanoHeightWithoutUsb]);
        }

        // Surface Transducer - Small
        translate([transducerVizOffsetX,transducerVizOffsetY,-10]) {
           cube([13.8,21.4,7.9]);
        }
        // Bluetooth Modem - BlueSMiRF Silver -- 42.0 x 16.0 x 3.9
        translate([modemVizOffsetX,modemVizOffsetY,-10]) { cube([16,42,3.9]); }
        // Polymer Lithium Ion Battery - 110mAh
        //translate([-40,0,-10]) { cube([12,28,5.7]); }
        // Polymer Lithium Ion Battery - 400mAh
	    translate([batteryVizOffsetX,batteryVizOffsetY,-10]) { cube([25,35,5]); }
        // Triple Axis Accelerometer & Gyro Breakout - MPU-6050
        translate([motionSensorVizOffsetX,motionSensorVizOffsetY,-10]) { cube([15.5, 25.7, 2.5]); }
    }
}


added possiblity of not specifiying given elements

use <rollup_string_fix.scad>
use <rollup_string_limiter.scad>

// required quantities. adjust according to your needs.
// (note: you need two identical elements per functional part)
fixes = 2*2;
limiters = 3*2;

// ball-size
r=(3+2)/2;

if(fixes)
  for(i = [0:fixes])
    translate([0, 10*i, 0])
      rollup_string_fix(r);

if(limiters)
  for(i = [0:limiters-1])
    translate([20, 10*i, 0])
      rollup_string_limiter(r);

use <rollup_string_fix.scad>
use <rollup_string_limiter.scad>

// required quantities. adjust according to your needs.
// (note: you need two identical elements per functional part)
fixes = 2*2;
limiters = 3*2;

// ball-size
r=(3+2)/2;

for(i = [0:fixes-1])
  translate([0, 10*i, 0])
    rollup_string_fix(r);

for(i = [0:limiters-1])
  translate([20, 10*i, 0])
    rollup_string_limiter(r);


changed BOM to be JSON parable

changed BOM to be JSON parable

module bom(code, desc, category) {
	echo(str(
    "BOM: { \"code\" : \"",
    code,
    "\", \"description\" : \"",
    desc,
    "\", \"categories\" : ",
    category,
    " }"
  ));
}

module bom(code, desc, category) {
	echo(str("BOM: ",code," - ", desc, "; ", category));
}


nut/data: add some knurl nuts (M2)

nut/data: add some knurl nuts (M2)

// THIS FILE HAS BEEN GENERATED
// DO NOT MODIFY DIRECTLY

include <units.scad>
include <thread-data.scad>

NutHoleDia = 0;
NutWidthMin = 1;
NutThickness = 2;
NutWidthMax = 3;
NutThread = 4;
NutFacets = 5;

NutHexM1_2 = [
    [NutHoleDia, 1.2*mm],
    [NutWidthMin, 3*mm],
    [NutThickness, 1*mm],
    [NutWidthMax, 3.28*mm],
    [NutThread, ThreadM1_2],
    [NutFacets, 6],
];

NutHexM1_4 = [
    [NutHoleDia, 1.4*mm],
    [NutWidthMin, 3*mm],
    [NutThickness, 1.2*mm],
    [NutWidthMax, 3.28*mm],
    [NutThread, ThreadM1_4],
    [NutFacets, 6],
];

NutHexM1_6 = [
    [NutHoleDia, 1.6*mm],
    [NutWidthMin, 3.2*mm],
    [NutThickness, 1.3*mm],
    [NutWidthMax, 3.48*mm],
    [NutThread, ThreadM1_6],
    [NutFacets, 6],
];

NutHexM1_8 = [
    [NutHoleDia, 1.8*mm],
    [NutWidthMin, 3.5*mm],
    [NutThickness, 1.4*mm],
    [NutWidthMax, 3.82*mm],
    [NutThread, ThreadM1_8],
    [NutFacets, 6],
];

NutHexM2 = [
    [NutHoleDia, 2*mm],
    [NutWidthMin, 4*mm],
    [NutThickness, 1.6*mm],
    [NutWidthMax, 4.32*mm],
    [NutThread, ThreadM2],
    [NutFacets, 6],
];

NutHexM2_5 = [
    [NutHoleDia, 2.5*mm],
    [NutWidthMin, 5*mm],
    [NutThickness, 2*mm],
    [NutWidthMax, 5.45*mm],
    [NutThread, ThreadM2_5],
    [NutFacets, 6],
];

NutHexM3 = [
    [NutHoleDia, 3*mm],
    [NutWidthMin, 5.5*mm],
    [NutThickness, 2.4*mm],
    [NutWidthMax, 6.01*mm],
    [NutThread, ThreadM3],
    [NutFacets, 6],
];

NutHexM3_5 = [
    [NutHoleDia, 3.5*mm],
    [NutWidthMin, 6*mm],
    [NutThickness, 2.8*mm],
    [NutWidthMax, 6.58*mm],
    [NutThread, ThreadM3_5],
    [NutFacets, 6],
];

NutHexM4 = [
    [NutHoleDia, 4*mm],
    [NutWidthMin, 7*mm],
    [NutThickness, 3.2*mm],
    [NutWidthMax, 7.66*mm],
    [NutThread, ThreadM4],
    [NutFacets, 6],
];

NutHexM5 = [
    [NutHoleDia, 5*mm],
    [NutWidthMin, 8*mm],
    [NutThickness, 4.7*mm],
    [NutWidthMax, 8.79*mm],
    [NutThread, ThreadM5],
    [NutFacets, 6],
];

NutHexM6 = [
    [NutHoleDia, 6*mm],
    [NutWidthMin, 10*mm],
    [NutThickness, 5.2*mm],
    [NutWidthMax, 11.05*mm],
    [NutThread, ThreadM6],
    [NutFacets, 6],
];

NutHexM7 = [
    [NutHoleDia, 7*mm],
    [NutWidthMin, 11*mm],
    [NutThickness, 5.5*mm],
    [NutWidthMax, 12.12*mm],
    [NutThread, ThreadM7],
    [NutFacets, 6],
];

NutHexM8 = [
    [NutHoleDia, 8*mm],
    [NutWidthMin, 13*mm],
    [NutThickness, 6.8*mm],
    [NutWidthMax, 14.38*mm],
    [NutThread, ThreadM8],
    [NutFacets, 6],
];

NutHexM10 = [
    [NutHoleDia, 10*mm],
    [NutWidthMin, 16*mm],
    [NutThickness, 8.4*mm],
    [NutWidthMax, 17.77*mm],
    [NutThread, ThreadM10],
    [NutFacets, 6],
];

NutHexM12 = [
    [NutHoleDia, 12*mm],
    [NutWidthMin, 18*mm],
    [NutThickness, 10.8*mm],
    [NutWidthMax, 20.03*mm],
    [NutThread, ThreadM12],
    [NutFacets, 6],
];

NutHexM14 = [
    [NutHoleDia, 14*mm],
    [NutWidthMin, 21*mm],
    [NutThickness, 12.8*mm],
    [NutWidthMax, 23.35*mm],
    [NutThread, ThreadM14],
    [NutFacets, 6],
];

NutHexM16 = [
    [NutHoleDia, 16*mm],
    [NutWidthMin, 24*mm],
    [NutThickness, 14.8*mm],
    [NutWidthMax, 26.75*mm],
    [NutThread, ThreadM16],
    [NutFacets, 6],
];

NutHexM18 = [
    [NutHoleDia, 18*mm],
    [NutWidthMin, 27*mm],
    [NutThickness, 15.8*mm],
    [NutWidthMax, 29.56*mm],
    [NutThread, ThreadM18],
    [NutFacets, 6],
];

NutHexM20 = [
    [NutHoleDia, 20*mm],
    [NutWidthMin, 30*mm],
    [NutThickness, 18*mm],
    [NutWidthMax, 32.95*mm],
    [NutThread, ThreadM20],
    [NutFacets, 6],
];

NutHexM22 = [
    [NutHoleDia, 22*mm],
    [NutWidthMin, 34*mm],
    [NutThickness, 19.4*mm],
    [NutWidthMax, 37.29*mm],
    [NutThread, ThreadM22],
    [NutFacets, 6],
];

NutHexM24 = [
    [NutHoleDia, 24*mm],
    [NutWidthMin, 36*mm],
    [NutThickness, 21.5*mm],
    [NutWidthMax, 39.55*mm],
    [NutThread, ThreadM24],
    [NutFacets, 6],
];

NutHexM27 = [
    [NutHoleDia, 27*mm],
    [NutWidthMin, 41*mm],
    [NutThickness, 23.8*mm],
    [NutWidthMax, 45.29*mm],
    [NutThread, ThreadM27],
    [NutFacets, 6],
];

NutHexM30 = [
    [NutHoleDia, 30*mm],
    [NutWidthMin, 46*mm],
    [NutThickness, 25.6*mm],
    [NutWidthMax, 50.85*mm],
    [NutThread, ThreadM30],
    [NutFacets, 6],
];

NutHexM33 = [
    [NutHoleDia, 33*mm],
    [NutWidthMin, 50*mm],
    [NutThickness, 28.7*mm],
    [NutWidthMax, 55.37*mm],
    [NutThread, ThreadM33],
    [NutFacets, 6],
];

NutHexM36 = [
    [NutHoleDia, 36*mm],
    [NutWidthMin, 55*mm],
    [NutThickness, 31*mm],
    [NutWidthMax, 60.79*mm],
    [NutThread, ThreadM36],
    [NutFacets, 6],
];

NutHexM39 = [
    [NutHoleDia, 39*mm],
    [NutWidthMin, 60*mm],
    [NutThickness, 33.4*mm],
    [NutWidthMax, 66.44*mm],
    [NutThread, ThreadM39],
    [NutFacets, 6],
];

NutHexM42 = [
    [NutHoleDia, 42*mm],
    [NutWidthMin, 65*mm],
    [NutThickness, 34*mm],
    [NutWidthMax, 71.3*mm],
    [NutThread, ThreadM42],
    [NutFacets, 6],
];

NutHexM45 = [
    [NutHoleDia, 45*mm],
    [NutWidthMin, 70*mm],
    [NutThickness, 36*mm],
    [NutWidthMax, 76.95*mm],
    [NutThread, ThreadM45],
    [NutFacets, 6],
];

NutHexM48 = [
    [NutHoleDia, 48*mm],
    [NutWidthMin, 75*mm],
    [NutThickness, 38*mm],
    [NutWidthMax, 82.6*mm],
    [NutThread, ThreadM48],
    [NutFacets, 6],
];

NutHexM52 = [
    [NutHoleDia, 52*mm],
    [NutWidthMin, 80*mm],
    [NutThickness, 42*mm],
    [NutWidthMax, 88.25*mm],
    [NutThread, ThreadM52],
    [NutFacets, 6],
];

NutHexM56 = [
    [NutHoleDia, 56*mm],
    [NutWidthMin, 85*mm],
    [NutThickness, 45*mm],
    [NutWidthMax, 93.56*mm],
    [NutThread, ThreadM56],
    [NutFacets, 6],
];

NutHexM60 = [
    [NutHoleDia, 60*mm],
    [NutWidthMin, 90*mm],
    [NutThickness, 48*mm],
    [NutWidthMax, 99.21*mm],
    [NutThread, ThreadM60],
    [NutFacets, 6],
];

NutHexM64 = [
    [NutHoleDia, 64*mm],
    [NutWidthMin, 95*mm],
    [NutThickness, 51*mm],
    [NutWidthMax, 104.86*mm],
    [NutThread, ThreadM64],
    [NutFacets, 6],
];

NutHexThinM2 = [
    [NutHoleDia, 2*mm],
    [NutWidthMin, 4*mm],
    [NutThickness, 1.2*mm],
    [NutWidthMax, 4.32*mm],
    [NutThread, ThreadM2],
    [NutFacets, 6],
];

NutHexThinM2_5 = [
    [NutHoleDia, 2.5*mm],
    [NutWidthMin, 5*mm],
    [NutThickness, 1.6*mm],
    [NutWidthMax, 5.45*mm],
    [NutThread, ThreadM2_5],
    [NutFacets, 6],
];

NutHexThinM3 = [
    [NutHoleDia, 3*mm],
    [NutWidthMin, 5.5*mm],
    [NutThickness, 1.8*mm],
    [NutWidthMax, 6.01*mm],
    [NutThread, ThreadM3],
    [NutFacets, 6],
];

NutHexThinM3_5 = [
    [NutHoleDia, 3.5*mm],
    [NutWidthMin, 6*mm],
    [NutThickness, 2*mm],
    [NutWidthMax, 6.58*mm],
    [NutThread, ThreadM3_5],
    [NutFacets, 6],
];

NutHexThinM4 = [
    [NutHoleDia, 4*mm],
    [NutWidthMin, 7*mm],
    [NutThickness, 2.2*mm],
    [NutWidthMax, 7.66*mm],
    [NutThread, ThreadM4],
    [NutFacets, 6],
];

NutHexThinM5 = [
    [NutHoleDia, 5*mm],
    [NutWidthMin, 8*mm],
    [NutThickness, 2.7*mm],
    [NutWidthMax, 8.79*mm],
    [NutThread, ThreadM5],
    [NutFacets, 6],
];

NutHexThinM6 = [
    [NutHoleDia, 6*mm],
    [NutWidthMin, 10*mm],
    [NutThickness, 3.2*mm],
    [NutWidthMax, 11.05*mm],
    [NutThread, ThreadM6],
    [NutFacets, 6],
];

NutHexThinM8 = [
    [NutHoleDia, 8*mm],
    [NutWidthMin, 13*mm],
    [NutThickness, 4*mm],
    [NutWidthMax, 14.38*mm],
    [NutThread, ThreadM8],
    [NutFacets, 6],
];

NutHexThinM10 = [
    [NutHoleDia, 10*mm],
    [NutWidthMin, 16*mm],
    [NutThickness, 5*mm],
    [NutWidthMax, 17.77*mm],
    [NutThread, ThreadM10],
    [NutFacets, 6],
];

NutHexThinM12 = [
    [NutHoleDia, 12*mm],
    [NutWidthMin, 18*mm],
    [NutThickness, 6*mm],
    [NutWidthMax, 20.03*mm],
    [NutThread, ThreadM12],
    [NutFacets, 6],
];

NutHexThinM14 = [
    [NutHoleDia, 14*mm],
    [NutWidthMin, 21*mm],
    [NutThickness, 7*mm],
    [NutWidthMax, 23.35*mm],
    [NutThread, ThreadM14],
    [NutFacets, 6],
];

NutHexThinM16 = [
    [NutHoleDia, 16*mm],
    [NutWidthMin, 24*mm],
    [NutThickness, 8*mm],
    [NutWidthMax, 26.75*mm],
    [NutThread, ThreadM16],
    [NutFacets, 6],
];

NutHexThinM18 = [
    [NutHoleDia, 18*mm],
    [NutWidthMin, 27*mm],
    [NutThickness, 9*mm],
    [NutWidthMax, 29.56*mm],
    [NutThread, ThreadM18],
    [NutFacets, 6],
];

NutHexThinM20 = [
    [NutHoleDia, 20*mm],
    [NutWidthMin, 30*mm],
    [NutThickness, 10*mm],
    [NutWidthMax, 32.95*mm],
    [NutThread, ThreadM20],
    [NutFacets, 6],
];

NutHexThinM22 = [
    [NutHoleDia, 22*mm],
    [NutWidthMin, 34*mm],
    [NutThickness, 11*mm],
    [NutWidthMax, 37.29*mm],
    [NutThread, ThreadM22],
    [NutFacets, 6],
];

NutHexThinM24 = [
    [NutHoleDia, 24*mm],
    [NutWidthMin, 36*mm],
    [NutThickness, 12*mm],
    [NutWidthMax, 39.55*mm],
    [NutThread, ThreadM24],
    [NutFacets, 6],
];

NutHexThinM27 = [
    [NutHoleDia, 27*mm],
    [NutWidthMin, 41*mm],
    [NutThickness, 13.5*mm],
    [NutWidthMax, 45.2*mm],
    [NutThread, ThreadM27],
    [NutFacets, 6],
];

NutHexThinM30 = [
    [NutHoleDia, 20*mm],
    [NutWidthMin, 46*mm],
    [NutThickness, 15*mm],
    [NutWidthMax, 50.85*mm],
    [NutThread, ThreadM30],
    [NutFacets, 6],
];

NutHexThinM33 = [
    [NutHoleDia, 33*mm],
    [NutWidthMin, 50*mm],
    [NutThickness, 16.5*mm],
    [NutWidthMax, 55.37*mm],
    [NutThread, ThreadM33],
    [NutFacets, 6],
];

NutHexThinM36 = [
    [NutHoleDia, 36*mm],
    [NutWidthMin, 55*mm],
    [NutThickness, 18*mm],
    [NutWidthMax, 60.79*mm],
    [NutThread, ThreadM36],
    [NutFacets, 6],
];

NutHexThinM39 = [
    [NutHoleDia, 39*mm],
    [NutWidthMin, 60*mm],
    [NutThickness, 19.5*mm],
    [NutWidthMax, 66.44*mm],
    [NutThread, ThreadM39],
    [NutFacets, 6],
];

NutHexThinM42 = [
    [NutHoleDia, 42*mm],
    [NutWidthMin, 65*mm],
    [NutThickness, 21*mm],
    [NutWidthMax, 71.3*mm],
    [NutThread, ThreadM42],
    [NutFacets, 6],
];

NutHexThinM45 = [
    [NutHoleDia, 45*mm],
    [NutWidthMin, 70*mm],
    [NutThickness, 22.5*mm],
    [NutWidthMax, 76.95*mm],
    [NutThread, ThreadM45],
    [NutFacets, 6],
];

NutHexThinM48 = [
    [NutHoleDia, 48*mm],
    [NutWidthMin, 75*mm],
    [NutThickness, 24*mm],
    [NutWidthMax, 82.6*mm],
    [NutThread, ThreadM48],
    [NutFacets, 6],
];

NutHexThinM52 = [
    [NutHoleDia, 42*mm],
    [NutWidthMin, 80*mm],
    [NutThickness, 26*mm],
    [NutWidthMax, 88.25*mm],
    [NutThread, ThreadM52],
    [NutFacets, 6],
];

NutHexThinM56 = [
    [NutHoleDia, 56*mm],
    [NutWidthMin, 85*mm],
    [NutThickness, 28*mm],
    [NutWidthMax, 93.56*mm],
    [NutThread, ThreadM56],
    [NutFacets, 6],
];

NutHexThinM60 = [
    [NutHoleDia, 60*mm],
    [NutWidthMin, 90*mm],
    [NutThickness, 30*mm],
    [NutWidthMax, 99.21*mm],
    [NutThread, ThreadM60],
    [NutFacets, 6],
];

NutHexThinM64 = [
    [NutHoleDia, 64*mm],
    [NutWidthMin, 95*mm],
    [NutThickness, 32*mm],
    [NutWidthMax, 104.86*mm],
    [NutThread, ThreadM64],
    [NutFacets, 6],
];

NutKnurlM2_4_42 = [
    [NutHoleDia, 2*mm],
    [NutWidthMin, 3.25*mm],
    [NutThickness, 4*mm],
    [NutWidthMax, 3.25*mm],
    [NutThread, ThreadM2],
    [NutFacets, 20],
];

NutKnurlM2_6_42 = [
    [NutHoleDia, 2*mm],
    [NutWidthMin, 3.25*mm],
    [NutThickness, 6*mm],
    [NutWidthMax, 3.25*mm],
    [NutThread, ThreadM2],
    [NutFacets, 20],
];

NutKnurlM3_8_42 = [
    [NutHoleDia, 3*mm],
    [NutWidthMin, 4.1*mm],
    [NutThickness, 8*mm],
    [NutWidthMax, 4.1*mm],
    [NutThread, ThreadM3],
    [NutFacets, 20],
];

NutKnurlM3_5_42 = [
    [NutHoleDia, 3*mm],
    [NutWidthMin, 4.1*mm],
    [NutThickness, 5*mm],
    [NutWidthMax, 4.1*mm],
    [NutThread, ThreadM3],
    [NutFacets, 20],
];

NutKnurlM3_3_42 = [
    [NutHoleDia, 3*mm],
    [NutWidthMin, 4.1*mm],
    [NutThickness, 3*mm],
    [NutWidthMax, 4.1*mm],
    [NutThread, ThreadM3],
    [NutFacets, 20],
];

AllNut = [NutHexM1_2,
NutHexM1_4,
NutHexM1_6,
NutHexM1_8,
NutHexM2,
NutHexM2_5,
NutHexM3,
NutHexM3_5,
NutHexM4,
NutHexM5,
NutHexM6,
NutHexM7,
NutHexM8,
NutHexM10,
NutHexM12,
NutHexM14,
NutHexM16,
NutHexM18,
NutHexM20,
NutHexM22,
NutHexM24,
NutHexM27,
NutHexM30,
NutHexM33,
NutHexM36,
NutHexM39,
NutHexM42,
NutHexM45,
NutHexM48,
NutHexM52,
NutHexM56,
NutHexM60,
NutHexM64,
NutHexThinM2,
NutHexThinM2_5,
NutHexThinM3,
NutHexThinM3_5,
NutHexThinM4,
NutHexThinM5,
NutHexThinM6,
NutHexThinM8,
NutHexThinM10,
NutHexThinM12,
NutHexThinM14,
NutHexThinM16,
NutHexThinM18,
NutHexThinM20,
NutHexThinM22,
NutHexThinM24,
NutHexThinM27,
NutHexThinM30,
NutHexThinM33,
NutHexThinM36,
NutHexThinM39,
NutHexThinM42,
NutHexThinM45,
NutHexThinM48,
NutHexThinM52,
NutHexThinM56,
NutHexThinM60,
NutHexThinM64,
NutKnurlM2_4_42,
NutKnurlM2_6_42,
NutKnurlM3_5_42,
NutKnurlM3_3_42,
];

// THIS FILE HAS BEEN GENERATED
// DO NOT MODIFY DIRECTLY

include <units.scad>
include <thread-data.scad>

NutHoleDia = 0;
NutWidthMin = 1;
NutThickness = 2;
NutWidthMax = 3;
NutThread = 4;
NutFacets = 5;

NutHexM1_2 = [
    [NutHoleDia, 1.2*mm],
    [NutWidthMin, 3*mm],
    [NutThickness, 1*mm],
    [NutWidthMax, 3.28*mm],
    [NutThread, ThreadM1_2],
    [NutFacets, 6],
];

NutHexM1_4 = [
    [NutHoleDia, 1.4*mm],
    [NutWidthMin, 3*mm],
    [NutThickness, 1.2*mm],
    [NutWidthMax, 3.28*mm],
    [NutThread, ThreadM1_4],
    [NutFacets, 6],
];

NutHexM1_6 = [
    [NutHoleDia, 1.6*mm],
    [NutWidthMin, 3.2*mm],
    [NutThickness, 1.3*mm],
    [NutWidthMax, 3.48*mm],
    [NutThread, ThreadM1_6],
    [NutFacets, 6],
];

NutHexM1_8 = [
    [NutHoleDia, 1.8*mm],
    [NutWidthMin, 3.5*mm],
    [NutThickness, 1.4*mm],
    [NutWidthMax, 3.82*mm],
    [NutThread, ThreadM1_8],
    [NutFacets, 6],
];

NutHexM2 = [
    [NutHoleDia, 2*mm],
    [NutWidthMin, 4*mm],
    [NutThickness, 1.6*mm],
    [NutWidthMax, 4.32*mm],
    [NutThread, ThreadM2],
    [NutFacets, 6],
];

NutHexM2_5 = [
    [NutHoleDia, 2.5*mm],
    [NutWidthMin, 5*mm],
    [NutThickness, 2*mm],
    [NutWidthMax, 5.45*mm],
    [NutThread, ThreadM2_5],
    [NutFacets, 6],
];

NutHexM3 = [
    [NutHoleDia, 3*mm],
    [NutWidthMin, 5.5*mm],
    [NutThickness, 2.4*mm],
    [NutWidthMax, 6.01*mm],
    [NutThread, ThreadM3],
    [NutFacets, 6],
];

NutHexM3_5 = [
    [NutHoleDia, 3.5*mm],
    [NutWidthMin, 6*mm],
    [NutThickness, 2.8*mm],
    [NutWidthMax, 6.58*mm],
    [NutThread, ThreadM3_5],
    [NutFacets, 6],
];

NutHexM4 = [
    [NutHoleDia, 4*mm],
    [NutWidthMin, 7*mm],
    [NutThickness, 3.2*mm],
    [NutWidthMax, 7.66*mm],
    [NutThread, ThreadM4],
    [NutFacets, 6],
];

NutHexM5 = [
    [NutHoleDia, 5*mm],
    [NutWidthMin, 8*mm],
    [NutThickness, 4.7*mm],
    [NutWidthMax, 8.79*mm],
    [NutThread, ThreadM5],
    [NutFacets, 6],
];

NutHexM6 = [
    [NutHoleDia, 6*mm],
    [NutWidthMin, 10*mm],
    [NutThickness, 5.2*mm],
    [NutWidthMax, 11.05*mm],
    [NutThread, ThreadM6],
    [NutFacets, 6],
];

NutHexM7 = [
    [NutHoleDia, 7*mm],
    [NutWidthMin, 11*mm],
    [NutThickness, 5.5*mm],
    [NutWidthMax, 12.12*mm],
    [NutThread, ThreadM7],
    [NutFacets, 6],
];

NutHexM8 = [
    [NutHoleDia, 8*mm],
    [NutWidthMin, 13*mm],
    [NutThickness, 6.8*mm],
    [NutWidthMax, 14.38*mm],
    [NutThread, ThreadM8],
    [NutFacets, 6],
];

NutHexM10 = [
    [NutHoleDia, 10*mm],
    [NutWidthMin, 16*mm],
    [NutThickness, 8.4*mm],
    [NutWidthMax, 17.77*mm],
    [NutThread, ThreadM10],
    [NutFacets, 6],
];

NutHexM12 = [
    [NutHoleDia, 12*mm],
    [NutWidthMin, 18*mm],
    [NutThickness, 10.8*mm],
    [NutWidthMax, 20.03*mm],
    [NutThread, ThreadM12],
    [NutFacets, 6],
];

NutHexM14 = [
    [NutHoleDia, 14*mm],
    [NutWidthMin, 21*mm],
    [NutThickness, 12.8*mm],
    [NutWidthMax, 23.35*mm],
    [NutThread, ThreadM14],
    [NutFacets, 6],
];

NutHexM16 = [
    [NutHoleDia, 16*mm],
    [NutWidthMin, 24*mm],
    [NutThickness, 14.8*mm],
    [NutWidthMax, 26.75*mm],
    [NutThread, ThreadM16],
    [NutFacets, 6],
];

NutHexM18 = [
    [NutHoleDia, 18*mm],
    [NutWidthMin, 27*mm],
    [NutThickness, 15.8*mm],
    [NutWidthMax, 29.56*mm],
    [NutThread, ThreadM18],
    [NutFacets, 6],
];

NutHexM20 = [
    [NutHoleDia, 20*mm],
    [NutWidthMin, 30*mm],
    [NutThickness, 18*mm],
    [NutWidthMax, 32.95*mm],
    [NutThread, ThreadM20],
    [NutFacets, 6],
];

NutHexM22 = [
    [NutHoleDia, 22*mm],
    [NutWidthMin, 34*mm],
    [NutThickness, 19.4*mm],
    [NutWidthMax, 37.29*mm],
    [NutThread, ThreadM22],
    [NutFacets, 6],
];

NutHexM24 = [
    [NutHoleDia, 24*mm],
    [NutWidthMin, 36*mm],
    [NutThickness, 21.5*mm],
    [NutWidthMax, 39.55*mm],
    [NutThread, ThreadM24],
    [NutFacets, 6],
];

NutHexM27 = [
    [NutHoleDia, 27*mm],
    [NutWidthMin, 41*mm],
    [NutThickness, 23.8*mm],
    [NutWidthMax, 45.29*mm],
    [NutThread, ThreadM27],
    [NutFacets, 6],
];

NutHexM30 = [
    [NutHoleDia, 30*mm],
    [NutWidthMin, 46*mm],
    [NutThickness, 25.6*mm],
    [NutWidthMax, 50.85*mm],
    [NutThread, ThreadM30],
    [NutFacets, 6],
];

NutHexM33 = [
    [NutHoleDia, 33*mm],
    [NutWidthMin, 50*mm],
    [NutThickness, 28.7*mm],
    [NutWidthMax, 55.37*mm],
    [NutThread, ThreadM33],
    [NutFacets, 6],
];

NutHexM36 = [
    [NutHoleDia, 36*mm],
    [NutWidthMin, 55*mm],
    [NutThickness, 31*mm],
    [NutWidthMax, 60.79*mm],
    [NutThread, ThreadM36],
    [NutFacets, 6],
];

NutHexM39 = [
    [NutHoleDia, 39*mm],
    [NutWidthMin, 60*mm],
    [NutThickness, 33.4*mm],
    [NutWidthMax, 66.44*mm],
    [NutThread, ThreadM39],
    [NutFacets, 6],
];

NutHexM42 = [
    [NutHoleDia, 42*mm],
    [NutWidthMin, 65*mm],
    [NutThickness, 34*mm],
    [NutWidthMax, 71.3*mm],
    [NutThread, ThreadM42],
    [NutFacets, 6],
];

NutHexM45 = [
    [NutHoleDia, 45*mm],
    [NutWidthMin, 70*mm],
    [NutThickness, 36*mm],
    [NutWidthMax, 76.95*mm],
    [NutThread, ThreadM45],
    [NutFacets, 6],
];

NutHexM48 = [
    [NutHoleDia, 48*mm],
    [NutWidthMin, 75*mm],
    [NutThickness, 38*mm],
    [NutWidthMax, 82.6*mm],
    [NutThread, ThreadM48],
    [NutFacets, 6],
];

NutHexM52 = [
    [NutHoleDia, 52*mm],
    [NutWidthMin, 80*mm],
    [NutThickness, 42*mm],
    [NutWidthMax, 88.25*mm],
    [NutThread, ThreadM52],
    [NutFacets, 6],
];

NutHexM56 = [
    [NutHoleDia, 56*mm],
    [NutWidthMin, 85*mm],
    [NutThickness, 45*mm],
    [NutWidthMax, 93.56*mm],
    [NutThread, ThreadM56],
    [NutFacets, 6],
];

NutHexM60 = [
    [NutHoleDia, 60*mm],
    [NutWidthMin, 90*mm],
    [NutThickness, 48*mm],
    [NutWidthMax, 99.21*mm],
    [NutThread, ThreadM60],
    [NutFacets, 6],
];

NutHexM64 = [
    [NutHoleDia, 64*mm],
    [NutWidthMin, 95*mm],
    [NutThickness, 51*mm],
    [NutWidthMax, 104.86*mm],
    [NutThread, ThreadM64],
    [NutFacets, 6],
];

NutHexThinM2 = [
    [NutHoleDia, 2*mm],
    [NutWidthMin, 4*mm],
    [NutThickness, 1.2*mm],
    [NutWidthMax, 4.32*mm],
    [NutThread, ThreadM2],
    [NutFacets, 6],
];

NutHexThinM2_5 = [
    [NutHoleDia, 2.5*mm],
    [NutWidthMin, 5*mm],
    [NutThickness, 1.6*mm],
    [NutWidthMax, 5.45*mm],
    [NutThread, ThreadM2_5],
    [NutFacets, 6],
];

NutHexThinM3 = [
    [NutHoleDia, 3*mm],
    [NutWidthMin, 5.5*mm],
    [NutThickness, 1.8*mm],
    [NutWidthMax, 6.01*mm],
    [NutThread, ThreadM3],
    [NutFacets, 6],
];

NutHexThinM3_5 = [
    [NutHoleDia, 3.5*mm],
    [NutWidthMin, 6*mm],
    [NutThickness, 2*mm],
    [NutWidthMax, 6.58*mm],
    [NutThread, ThreadM3_5],
    [NutFacets, 6],
];

NutHexThinM4 = [
    [NutHoleDia, 4*mm],
    [NutWidthMin, 7*mm],
    [NutThickness, 2.2*mm],
    [NutWidthMax, 7.66*mm],
    [NutThread, ThreadM4],
    [NutFacets, 6],
];

NutHexThinM5 = [
    [NutHoleDia, 5*mm],
    [NutWidthMin, 8*mm],
    [NutThickness, 2.7*mm],
    [NutWidthMax, 8.79*mm],
    [NutThread, ThreadM5],
    [NutFacets, 6],
];

NutHexThinM6 = [
    [NutHoleDia, 6*mm],
    [NutWidthMin, 10*mm],
    [NutThickness, 3.2*mm],
    [NutWidthMax, 11.05*mm],
    [NutThread, ThreadM6],
    [NutFacets, 6],
];

NutHexThinM8 = [
    [NutHoleDia, 8*mm],
    [NutWidthMin, 13*mm],
    [NutThickness, 4*mm],
    [NutWidthMax, 14.38*mm],
    [NutThread, ThreadM8],
    [NutFacets, 6],
];

NutHexThinM10 = [
    [NutHoleDia, 10*mm],
    [NutWidthMin, 16*mm],
    [NutThickness, 5*mm],
    [NutWidthMax, 17.77*mm],
    [NutThread, ThreadM10],
    [NutFacets, 6],
];

NutHexThinM12 = [
    [NutHoleDia, 12*mm],
    [NutWidthMin, 18*mm],
    [NutThickness, 6*mm],
    [NutWidthMax, 20.03*mm],
    [NutThread, ThreadM12],
    [NutFacets, 6],
];

NutHexThinM14 = [
    [NutHoleDia, 14*mm],
    [NutWidthMin, 21*mm],
    [NutThickness, 7*mm],
    [NutWidthMax, 23.35*mm],
    [NutThread, ThreadM14],
    [NutFacets, 6],
];

NutHexThinM16 = [
    [NutHoleDia, 16*mm],
    [NutWidthMin, 24*mm],
    [NutThickness, 8*mm],
    [NutWidthMax, 26.75*mm],
    [NutThread, ThreadM16],
    [NutFacets, 6],
];

NutHexThinM18 = [
    [NutHoleDia, 18*mm],
    [NutWidthMin, 27*mm],
    [NutThickness, 9*mm],
    [NutWidthMax, 29.56*mm],
    [NutThread, ThreadM18],
    [NutFacets, 6],
];

NutHexThinM20 = [
    [NutHoleDia, 20*mm],
    [NutWidthMin, 30*mm],
    [NutThickness, 10*mm],
    [NutWidthMax, 32.95*mm],
    [NutThread, ThreadM20],
    [NutFacets, 6],
];

NutHexThinM22 = [
    [NutHoleDia, 22*mm],
    [NutWidthMin, 34*mm],
    [NutThickness, 11*mm],
    [NutWidthMax, 37.29*mm],
    [NutThread, ThreadM22],
    [NutFacets, 6],
];

NutHexThinM24 = [
    [NutHoleDia, 24*mm],
    [NutWidthMin, 36*mm],
    [NutThickness, 12*mm],
    [NutWidthMax, 39.55*mm],
    [NutThread, ThreadM24],
    [NutFacets, 6],
];

NutHexThinM27 = [
    [NutHoleDia, 27*mm],
    [NutWidthMin, 41*mm],
    [NutThickness, 13.5*mm],
    [NutWidthMax, 45.2*mm],
    [NutThread, ThreadM27],
    [NutFacets, 6],
];

NutHexThinM30 = [
    [NutHoleDia, 20*mm],
    [NutWidthMin, 46*mm],
    [NutThickness, 15*mm],
    [NutWidthMax, 50.85*mm],
    [NutThread, ThreadM30],
    [NutFacets, 6],
];

NutHexThinM33 = [
    [NutHoleDia, 33*mm],
    [NutWidthMin, 50*mm],
    [NutThickness, 16.5*mm],
    [NutWidthMax, 55.37*mm],
    [NutThread, ThreadM33],
    [NutFacets, 6],
];

NutHexThinM36 = [
    [NutHoleDia, 36*mm],
    [NutWidthMin, 55*mm],
    [NutThickness, 18*mm],
    [NutWidthMax, 60.79*mm],
    [NutThread, ThreadM36],
    [NutFacets, 6],
];

NutHexThinM39 = [
    [NutHoleDia, 39*mm],
    [NutWidthMin, 60*mm],
    [NutThickness, 19.5*mm],
    [NutWidthMax, 66.44*mm],
    [NutThread, ThreadM39],
    [NutFacets, 6],
];

NutHexThinM42 = [
    [NutHoleDia, 42*mm],
    [NutWidthMin, 65*mm],
    [NutThickness, 21*mm],
    [NutWidthMax, 71.3*mm],
    [NutThread, ThreadM42],
    [NutFacets, 6],
];

NutHexThinM45 = [
    [NutHoleDia, 45*mm],
    [NutWidthMin, 70*mm],
    [NutThickness, 22.5*mm],
    [NutWidthMax, 76.95*mm],
    [NutThread, ThreadM45],
    [NutFacets, 6],
];

NutHexThinM48 = [
    [NutHoleDia, 48*mm],
    [NutWidthMin, 75*mm],
    [NutThickness, 24*mm],
    [NutWidthMax, 82.6*mm],
    [NutThread, ThreadM48],
    [NutFacets, 6],
];

NutHexThinM52 = [
    [NutHoleDia, 42*mm],
    [NutWidthMin, 80*mm],
    [NutThickness, 26*mm],
    [NutWidthMax, 88.25*mm],
    [NutThread, ThreadM52],
    [NutFacets, 6],
];

NutHexThinM56 = [
    [NutHoleDia, 56*mm],
    [NutWidthMin, 85*mm],
    [NutThickness, 28*mm],
    [NutWidthMax, 93.56*mm],
    [NutThread, ThreadM56],
    [NutFacets, 6],
];

NutHexThinM60 = [
    [NutHoleDia, 60*mm],
    [NutWidthMin, 90*mm],
    [NutThickness, 30*mm],
    [NutWidthMax, 99.21*mm],
    [NutThread, ThreadM60],
    [NutFacets, 6],
];

NutHexThinM64 = [
    [NutHoleDia, 64*mm],
    [NutWidthMin, 95*mm],
    [NutThickness, 32*mm],
    [NutWidthMax, 104.86*mm],
    [NutThread, ThreadM64],
    [NutFacets, 6],
];

NutKnurlM3_8_42 = [
    [NutHoleDia, 3*mm],
    [NutWidthMin, 4.1*mm],
    [NutThickness, 8*mm],
    [NutWidthMax, 4.1*mm],
    [NutThread, ThreadM3],
    [NutFacets, 20],
];

NutKnurlM3_5_42 = [
    [NutHoleDia, 3*mm],
    [NutWidthMin, 4.1*mm],
    [NutThickness, 5*mm],
    [NutWidthMax, 4.1*mm],
    [NutThread, ThreadM3],
    [NutFacets, 20],
];

NutKnurlM3_3_42 = [
    [NutHoleDia, 3*mm],
    [NutWidthMin, 4.1*mm],
    [NutThickness, 3*mm],
    [NutWidthMax, 4.1*mm],
    [NutThread, ThreadM3],
    [NutFacets, 20],
];

AllNut = [NutHexM1_2,
NutHexM1_4,
NutHexM1_6,
NutHexM1_8,
NutHexM2,
NutHexM2_5,
NutHexM3,
NutHexM3_5,
NutHexM4,
NutHexM5,
NutHexM6,
NutHexM7,
NutHexM8,
NutHexM10,
NutHexM12,
NutHexM14,
NutHexM16,
NutHexM18,
NutHexM20,
NutHexM22,
NutHexM24,
NutHexM27,
NutHexM30,
NutHexM33,
NutHexM36,
NutHexM39,
NutHexM42,
NutHexM45,
NutHexM48,
NutHexM52,
NutHexM56,
NutHexM60,
NutHexM64,
NutHexThinM2,
NutHexThinM2_5,
NutHexThinM3,
NutHexThinM3_5,
NutHexThinM4,
NutHexThinM5,
NutHexThinM6,
NutHexThinM8,
NutHexThinM10,
NutHexThinM12,
NutHexThinM14,
NutHexThinM16,
NutHexThinM18,
NutHexThinM20,
NutHexThinM22,
NutHexThinM24,
NutHexThinM27,
NutHexThinM30,
NutHexThinM33,
NutHexThinM36,
NutHexThinM39,
NutHexThinM42,
NutHexThinM45,
NutHexThinM48,
NutHexThinM52,
NutHexThinM56,
NutHexThinM60,
NutHexThinM64,
NutKnurlM3_5_42,
NutKnurlM3_3_42,
];


model is now simple (manifold)

model is now simple (manifold)

/*
 * configuration
 */

brushes_count = 2;

/*
 * modules
 */

module lower_tub()
{
  translate([0, 20+4, 0])
    difference()
    {
      cylinder(h=10+3, r=20);
      translate([0,0,3])
        cylinder(h=10+3, r2=20, r1=16);
    }
}

module upper_ring()
{
  translate([0, 24+5, 0])
    difference()
    {
      cylinder(h=16, r=24+5);
      cylinder(h=16, r=24+0);
    }
}

module back_wall()
{
  translate([0,0,0])
  {
    // lower part
    translate([-70/2, 0, 0])
      cube([70, 3, 20]);
    // upper part
    translate([-70/2, 0, 100-16+13])
      cube([70, 3, 20]);
    // connector
    translate([-20/2, 0, 0])
      cube([20, 3, 100]);
  }
}

module brush_holder()
{
  lower_tub();
  translate([0,0,100])
    upper_ring();
  back_wall();
  // lower mounting
  difference()
  {
    translate([-30/2, 3, 0])
      cube([30, 10, 10+3]);
    translate([0, 0.1, 0]) // needed to make model simple (aka: manifold)
      hull()
        lower_tub();
  }
  // upper mounting
  translate([0, 0, 100])
  {
    difference()
    {
      translate([-40/2, 3, 0])
        cube([40, 10, 16]);
      translate([0, 0.1, 0]) // needed to make model simple (aka: manifold)
        hull()
          upper_ring();
    }
  }
}

/*
 * main program
 */

for(offset = [0:brushes_count-1])
{
  translate(offset * [60, 0, 0])
    brush_holder();
}

/*
 * configuration
 */
brushes_count = 2;

/*
 * modules
 */

module lower_tub()
{
  translate([0, 20+4, 0])
    difference()
    {
      cylinder(h=10+3, r=20);
      translate([0,0,3])
        cylinder(h=10+3, r2=20, r1=16);
    }
}

module upper_ring()
{
  translate([0, 24+5, 0])
    difference()
    {
      cylinder(h=16, r=24+5);
      cylinder(h=16, r=24+0);
    }
}

module back_wall()
{
  translate([0,0,0])
  {
    // lower part
    translate([-70/2, 0, 0])
      cube([70, 3, 20]);
    // upper part
    translate([-70/2, 0, 100-16+13])
      cube([70, 3, 20]);
    // connector
    translate([-20/2, 0, 0])
      cube([20, 3, 100]);
  }
}

module brush_holder()
{
  lower_tub();
  translate([0,0,100])
    upper_ring();
  back_wall();
  // lower mounting
  difference()
  {
    translate([-30/2, 3, 0])
      cube([30, 10, 10+3]);
    hull()
      lower_tub();
  }
  // upper mounting
  translate([0, 0, 100])
  {
    difference()
    {
      translate([-40/2, 3, 0])
        cube([40, 10, 16]);
      hull()
        upper_ring();
    }
  }
}

/*
 * main program
 */
for(offset = [0:brushes_count-1])
{
  translate(offset * [60, 0, 0])
    brush_holder();
}


updated plunger spring holder

updated plunger spring holder