Final form of the Monomanual Type-o-tastic, or whatever it will be called.  All that remains is to double/triple check measurements and arithmetic before printing

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

visualizeInternalComponents = false;

// 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;

//cornerRoundingRes = 50;
cornerRoundingRes = 10;
pinHoleRes = 10;
//connectorPinRes = 50;
connectorPinRes = 10;
//ledHoleRes = 50;
ledHoleRes = 10;

containmentWallThick = 1;

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

chargerHeaderLength = 5.10;
chargerHeaderWidth = 2.56;

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

ledDomeRadius = 2.5;
ledRimRadius = 2.9;

caseWidth = 70;
caseLength = 130;
caseHeight = 12;

wallThick = caseHeight/2;
lidThick = 1.5;
floorThick = 1.5;

pushButtonWellWidth = 7;
pushButtonHeight = 5.0;
pushButtonLegLength = 4;
pushButtonPressDepth = 0.3;
pushButtonWellDepth = 4;
pushButtonBaseThick = 1.5;

fingerHeightOffset = 5;
fingerWellDepth = 10;
fingerRad = 15;
thumbWellDepth = wallThick;
thumbWellRadius = 30;
buttonThick = 8;
buttonWidth = pushButtonWellWidth;
buttonLength = 25;
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 + error);
totalFingerWellDepth = fingerWellDepth + buttonThick/2 + pushButtonPressDepth + pushButtonWellDepth;
totalThumbWellDepth = thumbWellDepth + buttonThick/2 + pushButtonPressDepth + pushButtonWellDepth;

pinHoleRadius = 0.8;

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

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

thumbKeyOffset = 23;

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

module button() {
    difference() {
        union() {
                    // rounded cap
                    translate([buttonClearance,buttonWidth/2,buttonWidth/2]) {
                        rotate([0,90,0]) { cylinder(h=buttonLength-2*buttonClearance,r=buttonWidth/2-buttonClearance, $fn=cornerRoundingRes); }
                    }

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

                    // stabilizer rods
                    translate([buttonClearance,buttonClearance,buttonThick]) {
                        cube([buttonStabilizerWellWidth-2*buttonClearance, buttonWidth-2*buttonClearance, buttonStabilizerRodHeight]);
		            }
                    translate([buttonLength-buttonStabilizerWellWidth+buttonClearance,buttonClearance,buttonThick]) {
                        cube([buttonStabilizerWellWidth-2*buttonClearance, buttonWidth-2*buttonClearance, buttonStabilizerRodHeight]);
		            }
                    // retainer rods
                    translate([buttonClearance,buttonClearance,buttonThick+pushButtonHeight-pushButtonWellDepth+buttonRetainerThick+buttonSlack+error]) {
                        cube([buttonStabilizerWellWidth+buttonLip-2*buttonClearance,buttonWidth-2*buttonClearance, 1]);
                    }
                    translate([buttonLength-buttonStabilizerWellWidth-buttonLip+buttonClearance,buttonClearance,buttonThick+pushButtonHeight-pushButtonWellDepth+buttonRetainerThick+buttonSlack+error]) {
                        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=buttonLength+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(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([2,2,depth+pushButtonLegLength]);
                }
                translate([pushButtonWellWidth-2,0, 0]) {
                    cube([2,2,depth+pushButtonLegLength]);
                }
                translate([0,pushButtonWellWidth-2, 0]) {
                    cube([2,2,depth+pushButtonLegLength]);
                }
                translate([pushButtonWellWidth-2,pushButtonWellWidth-2, 0]) {
                    cube([2,2,depth+pushButtonLegLength]);
                }
        }
    }

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

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

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

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

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

module fingerWell(depth, radius) {
    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);
    }
}

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

module pinHoles() {
    translate([0,0,-1]) {

        for (i = [0:4]) {
            translate([wallThick+cavityWidth+3,caseLength-fingerHeightOffset-fingerRad*i*2,0]) { pinHole(); }
            //translate([wallThick+cavityWidth+10,caseLength-fingerHeightOffset-fingerRad*i*2,0]) { pinHole(); }

        }

        for (i = [1:5]) {
            translate([wallThick*2/3,wallThick*2/3 + i*(caseLength-thumbBevelLength - wallThick*2/3)/5,0]) {
                pinHole();
            }
        }
        translate([15,caseLength-thumbBevelLength+3,0]) {
            pinHole();
        }

        translate([thumbBevelWidth, caseLength-wallThick*2/3,0]) {
            pinHole();
        }
        translate([thumbBevelWidth, caseLength-15, 0]) {
            pinHole();
        }
    }
}

module caseConvexHull() {
		// box with thumb bevel
        difference() {
            translate([wallThick, wallThick,0]) {
                cube([caseWidth-caseHeight,caseLength-caseHeight,caseHeight]);
            }
            translate([thumbBevelWidth,caseLength-wallThick,-1]) {
                rotate([0,0,thumbBevelAngle]) {
                    rotate([0,-90,0]) {
                        cube([caseHeight+1, 100, thumbBevelStretch]);
                    }
                }
            }
        }

        translate([wallThick,wallThick,wallThick]) {
            sphere(wallThick, $fn=cornerRoundingRes);
            rotate([-90,0,0]) {
                cylinder(h=caseLength-thumbBevelLength-wallThick, r=wallThick, $fn=cornerRoundingRes);
            }
            rotate([0,90,0]) {
                cylinder(h=caseWidth-caseHeight, r=wallThick, $fn=cornerRoundingRes);
            }
        }
        translate([caseWidth-wallThick,wallThick,wallThick]) {
            sphere(wallThick, $fn=cornerRoundingRes);
            rotate([-90,0,0]) {
                cylinder(h=caseLength-caseHeight, r=wallThick, $fn=cornerRoundingRes);
            }
        }
        translate([caseWidth-wallThick,caseLength-wallThick,wallThick]) {
            sphere(wallThick, $fn=cornerRoundingRes);
        }

		// thumb bevel
		translate([wallThick,caseLength-thumbBevelLength,wallThick]) {
            sphere(wallThick, $fn=cornerRoundingRes);
        }
        translate([thumbBevelWidth,caseLength-wallThick,wallThick]) {
            sphere(wallThick, $fn=cornerRoundingRes);
            rotate([0,90,0]) {
                cylinder(h=caseWidth-thumbBevelWidth-wallThick, r=wallThick, $fn=cornerRoundingRes);
            }
            rotate([0,0,180+thumbBevelAngle]) {
                rotate([0,90,0]) {
                    cylinder(h=thumbBevelStretch, r=wallThick, $fn=cornerRoundingRes);
                }
            }
        }
}

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

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

    pinHoles();

    // finger button wells
    for (i = [0:3]) {
        translate([
            caseWidth,
            caseLength - fingerHeightOffset - fingerRad * (1 + 2 * i),
        0]) {

            fingerWell(fingerWellDepth, fingerRad);

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

    // thumb button well
    translate([thumbBevelWidth,caseLength-wallThick,caseHeight]) {
        rotate([0,0,180+thumbBevelAngle]) {
            rotate([0,90,0]) {
                translate([0,0,thumbKeyOffset]) {
                    rotate([0,90,0]) { rotate([0,0,-90]) {
                        //cube([50,100,200]);

                        translate([wallThick,0,0]) {
                            fingerWell(thumbWellDepth, thumbWellRadius);
                        }
                    }}

                    rotate([0,90,-90]) {
                        translate([0,wallThick,wallThick-totalThumbWellDepth]) {
                            pushButtonWell(pushButtonWellDepth+100);

                            rotate([180,0,0]) {
                                translate([-27,-wallThick+lidThick,pushButtonBaseThick]) {
                                    cube([39,caseHeight-floorThick-lidThick,15]);
                                    cube([22,caseHeight-floorThick-lidThick,20]);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // hole for status LED
    translate([cavityWidth+wallThick-ledRimRadius-3,caseLength+1,wallThick]) {
    	    rotate(a=[90,0,0]) {
            cylinder(h=wallThick+2,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,wallThick]) {
                cylinder(h=10+1,r=ledRimRadius+clearance, $fn=20);
            }
        }
    }

    // hole for battery charger headers
	translate([wallThick+cavityWidth-chargerHeaderWidth-2*error,0,caseHeight-floorThick-chargerHeaderLength-2*error]) {
        cube([chargerHeaderWidth+2*error,wallThick,chargerHeaderLength+2*error]);
    }

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

    // hole for USB port
    translate([wallThick+cavityWidth-nanoLeftMargin-nanoWidth/2-nanoUsbWidth/2-clearance,0,caseHeight-floorThick-nanoHeightToUsbBase-nanoUsbHeight-clearance]) {
        cube([nanoUsbWidth+2*clearance,wallThick,nanoUsbHeight+clearance]);
    }
  }
}

// body
difference() {
    union() {
        basicCase();
        translate([wallThick, wallThick, 0]) {
            cylinder(h=caseHeight-floorThick+0.001, r=3);
        }
    }
    cube([caseWidth, caseLength, (caseHeight-buttonWidth)/2+0.0001]);

    for (i = [0:4]) {
        translate([wallThick+cavityWidth+10,caseLength-fingerHeightOffset-fingerRad*i*2,0]) {
            cylinder(h=caseHeight/2+error2, r=1.5+error2, $fn=connectorPinRes);
        }
    }
    translate([thumbBevelWidth-3, caseLength-wallThick-6, 0]) {
        cylinder(h=caseHeight/2+error2, r=1.5+error2, $fn=connectorPinRes);
    }
    translate([wallThick+7, caseLength-thumbBevelLength+7, 0]) {
        cylinder(h=caseHeight/2+error2, r=1.5+error2, $fn=connectorPinRes);
    }
    translate([wallThick, wallThick, 0]) {
        cylinder(h=caseHeight/2+error2, r=1.5+error2, $fn=connectorPinRes);
    }
}

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

    for (i = [0:4]) {
        translate([wallThick+cavityWidth+10,caseLength-fingerHeightOffset-fingerRad*i*2,0]) {
            cylinder(h=caseHeight/2, r=1.5, $fn=connectorPinRes);
        }
    }
    translate([thumbBevelWidth-3, caseLength-wallThick-6, 0]) {
        cylinder(h=caseHeight/2, r=1.5, $fn=connectorPinRes);
    }
    translate([wallThick+7, caseLength-thumbBevelLength+7, 0]) {
        cylinder(h=caseHeight/2, r=1.5, $fn=connectorPinRes);
    }
    translate([wallThick, wallThick, 0]) {
        cylinder(h=caseHeight/2, r=1.5, $fn=connectorPinRes);
    }
}

// buttons
// Note: the buttons are oriented so as to make the longest surfaces the smoothest
translate([-5,0,10]) {
    for (i = [0:4]) {
        translate([0,i*(buttonLength+2),0]) { rotate([90,180,-90]) {
            //cube([10,20,40]);

            button();
        }}
    }
}

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

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

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

        // Surface Transducer - Small
        translate([thumbBevelWidth-wallThick+4,caseLength-2*wallThick-21.4-4,-10]) {
           cube([13.8,21.4,7.9]);
        }
        // Bluetooth Modem - BlueSMiRF Silver -- 42.0 x 16.0 x 3.9
        translate([0,31,-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([18,47,-10]) { cube([25,35,5]); }
        // Triple Axis Accelerometer & Gyro Breakout - MPU-6050
        translate([0,3,-10]) { cube([15.5, 25.7, 2.5]); }
    }
}

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

visualizeInternalComponents = false;

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

// 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;

containmentWallThick = 1;

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

chargerHeaderLength = 5.10;
chargerHeaderWidth = 2.56;

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

ledDomeRadius = 2.5;
ledRimRadius = 2.9;

caseWidth = 70;
caseLength = 130;
caseHeight = 11;

wallThick = caseHeight/2;
lidThick = 1.5;
floorThick = 1.5;
lidMargin = 1;

fingerHeightOffset = 5;
fingerWellDepth = 10;
fingerRad = 15;
thumbWellDepth = wallThick;
thumbWellRadius = 30;
pushButtonWellWidth = 7;
pushButtonWellDepth = 4;
pushButtonBaseThick = 1;
pushButtonLegLength = 4;
pushButtonHeight = 5.0;
pushButtonPressDepth = 0.3;
//pushButtonRadius = 3.5/2;
pushButtonLegProtrusion = pushButtonLegLength - pushButtonBaseThick;
buttonThick = 8;
buttonWidth = pushButtonWellWidth;
buttonLength = 25;
buttonStabilizerWellWidth = 3;
buttonStabilizerRodHeight = pushButtonHeight - (pushButtonPressDepth + 0.5 + error);
totalFingerWellDepth = fingerWellDepth + buttonThick/2 + pushButtonPressDepth + pushButtonWellDepth;
totalThumbWellDepth = thumbWellDepth + buttonThick/2 + pushButtonPressDepth + pushButtonWellDepth;

buttonClearance = 0.3;

thumbBevelLength = 48;
thumbBevelWidth = 31;

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

thumbKeyOffset = 23;

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

lidWidth = caseWidth - wallThick + lidMargin - totalFingerWellDepth + 3;
lidLength = caseLength - thumbBevelLength - wallThick + 2*lidMargin;

// button well with origin at the center of the base of the switch
// legs of the switch run along the x axis
module pushButtonWell(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([-1,0,0]) {
                    cube([2,2,depth+pushButtonLegLength]);
                }
                translate([pushButtonWellWidth-1,0, 0]) {
                    cube([2,2,depth+pushButtonLegLength]);
                }
                translate([-1,pushButtonWellWidth-2, 0]) {
                    cube([2,2,depth+pushButtonLegLength]);
                }
                translate([pushButtonWellWidth-1,pushButtonWellWidth-2, 0]) {
                    cube([2,2,depth+pushButtonLegLength]);
                }
        }
    }

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

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

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

module fingerWell(depth, radius) {
    translate([radius - depth,0,0]) {
        cylinder(h=caseHeight,r=radius);
    }

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

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

// create the lid
// buffer: hyper-extends the lid, for better rendering
// shrinkage: clearance around the edges of the lid
module lid(buffer, shrinkage) {
    translate([0,0,-buffer]) {
        translate([wallThick-lidMargin+shrinkage,wallThick-lidMargin+shrinkage,0]) {
            cube([lidWidth,lidLength,lidThick+buffer]);
        }
        translate([thumbBevelWidth,wallThick-lidMargin+shrinkage,0]) {
            cube([lidWidth-thumbBevelWidth+wallThick,cavityLength+(lidMargin-shrinkage)*2,lidThick+buffer]);
        }

        // note: currently no "shrinkage" on this edge
        translate([thumbBevelWidth,caseLength-wallThick,caseHeight]) {
            rotate([0,0,180+thumbBevelAngle]) {
                rotate([0,90,0]) {
                    translate([0,0,thumbKeyOffset]) {
                        rotate([0,90,-90]) {
                            translate([0,wallThick,wallThick-totalThumbWellDepth]) {
                                rotate([180,0,0]) {
                                    translate([-27,-wallThick,pushButtonBaseThick]) {
                                        translate([0,0,-6]) {
                                            cube([47,lidThick+buffer,20]);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

module button() {
    difference() {
        union() {
                    translate([buttonClearance,buttonWidth/2,buttonWidth/2]) {
                        rotate([0,90,0]) { cylinder(h=buttonLength-2*buttonClearance,r=buttonWidth/2-buttonClearance); }
                    }

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

                    translate([buttonClearance,buttonClearance,buttonThick]) {
                        cube([buttonStabilizerWellWidth-2*buttonClearance, buttonWidth-2*buttonClearance, buttonStabilizerRodHeight]);
		            }

                    translate([buttonLength-buttonStabilizerWellWidth+buttonClearance,buttonClearance,buttonThick]) {
                        cube([buttonStabilizerWellWidth-2*buttonClearance, buttonWidth-2*buttonClearance, buttonStabilizerRodHeight]);
		            }
        }

        translate([buttonClearance+1,0,0]) { cube([1,buttonWidth,2]); }
        translate([buttonLength-buttonClearance-2,0,0]) { cube([1,buttonWidth,2]); }

        //translate([0,buttonWidth/2,buttonWidth/2]) {
        //    rotate([0,90,0]) { cylinder(h=buttonLength+1,r=buttonWidth/3); }
        //}

        //translate([buttonLength/2,buttonWidth/2,buttonThick-0.5]) {
        //    cylinder(h=1,r=pushButtonRadius);
        //}
    }
}

module screwHoles() {
    translate([0,0,-1]) {
        translate([15,caseLength-thumbBevelLength-wallThick+9,0]) { cylinder(h=caseHeight+2,r=1.5); }

        translate([wallThick+cavityWidth+2.5,caseLength-fingerHeightOffset-fingerRad*2,0]) { cylinder(h=caseHeight+2,r=1.5); }
        translate([wallThick+cavityWidth+2.5,caseLength-fingerHeightOffset-fingerRad*4,0]) { cylinder(h=caseHeight+2,r=1.5); }
        translate([wallThick+cavityWidth+2.5,caseLength-fingerHeightOffset-fingerRad*6,0]) { cylinder(h=caseHeight+2,r=1.5); }
    }
}

module caseConvexHull() {
		// box with thumb bevel
        difference() {
            translate([wallThick, wallThick,0]) {
                cube([caseWidth-caseHeight,caseLength-caseHeight,caseHeight]);
            }
            translate([thumbBevelWidth,caseLength-wallThick,-1]) {
                rotate([0,0,thumbBevelAngle]) {
                    rotate([0,-90,0]) {
                        cube([caseHeight+1, 100, thumbBevelStretch]);
                    }
                }
            }
        }

        translate([wallThick,wallThick,wallThick]) {
            sphere(wallThick);
            rotate([-90,0,0]) {
                cylinder(h=caseLength-thumbBevelLength-wallThick, r=wallThick);
            }
            rotate([0,90,0]) {
                cylinder(h=caseWidth-caseHeight, r=wallThick);
            }
        }
        translate([caseWidth-wallThick,wallThick,wallThick]) {
            sphere(wallThick);
            rotate([-90,0,0]) {
                cylinder(h=caseLength-caseHeight, r=wallThick);
            }
        }
        translate([caseWidth-wallThick,caseLength-wallThick,wallThick]) {
            sphere(wallThick);
        }

		// thumb bevel
		translate([wallThick,caseLength-thumbBevelLength,wallThick]) {
            sphere(wallThick);
        }
        translate([thumbBevelWidth,caseLength-wallThick,wallThick]) {
            sphere(wallThick);
            rotate([0,90,0]) {
                cylinder(h=caseWidth-thumbBevelWidth-wallThick, r=wallThick);
            }
            rotate([0,0,180+thumbBevelAngle]) {
                rotate([0,90,0]) {
                    cylinder(h=thumbBevelStretch, r=wallThick);
                }
            }
        }
}

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

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

    /*
    // depression for lid
    lid(1,0);
    */

    screwHoles();

    // finger button wells
    for (i = [0:3]) {
        translate([
            caseWidth,
            caseLength - fingerHeightOffset - fingerRad * (1 + 2 * i),
        0]) {

            fingerWell(fingerWellDepth, fingerRad);

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

    // thumb button well
    translate([thumbBevelWidth,caseLength-wallThick,caseHeight]) {
        rotate([0,0,180+thumbBevelAngle]) {
            rotate([0,90,0]) {
                translate([0,0,thumbKeyOffset]) {
                    rotate([0,90,0]) { rotate([0,0,-90]) {
                        //cube([50,100,200]);

                        translate([wallThick,0,0]) {
                            fingerWell(thumbWellDepth, thumbWellRadius);
                        }
                    }}

                    rotate([0,90,-90]) {
                        translate([0,wallThick,wallThick-totalThumbWellDepth]) {
                            pushButtonWell(pushButtonWellDepth+100);

                            rotate([180,0,0]) {
                                translate([-27,-wallThick+lidThick,pushButtonBaseThick]) {
                                    cube([39,caseHeight-floorThick-lidThick,15]);
                                    cube([22,caseHeight-floorThick-lidThick,20]);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    // hole for status LED
    translate([cavityWidth+wallThick-ledRimRadius-3,caseLength+1,wallThick]) {
    	    rotate(a=[90,0,0]) {
            cylinder(h=wallThick+2,r=ledDomeRadius+error);

            // 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,wallThick]) {
                cylinder(h=10+1,r=ledRimRadius+clearance);
            }
        }
    }

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

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

    // hole for USB port
    translate([wallThick+cavityWidth-nanoLeftMargin-nanoWidth/2-nanoUsbWidth/2-clearance,0,caseHeight-floorThick-nanoHeightToUsbBase-nanoUsbHeight-clearance]) {
        cube([nanoUsbWidth+2*clearance,wallThick,nanoUsbHeight+clearance]);
    }
    // trim off the thin remainder above the USB port which would be problematic to print,
    // and make it harder to get the Nano into its socket anyway
    translate([wallThick+cavityWidth-nanoLeftMargin-nanoWidth/2-nanoUsbWidth/2-clearance,wallThick-lidMargin,0]) {
        cube([nanoUsbWidth+2*clearance,lidMargin,2]);
    }
  }
}

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

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

/*
// lid
translate([0,0,caseHeight + 2]) {
    difference() {
       // the gap of 0.4 was determined by taking into account the 0.15mm accuracy of
       // the device and the 0.15% error by longest axis of the material, keeping in mind
	   // that there are always a pair of gaps across the lid from each other.
       // 0.4mm is actually larger than necessary in the horizontal (narrower) dimension.
       lid(0,0.4);

       screwHoles();
    }
}
*/

// buttons
// Note: the buttons are oriented so as to make the longest surfaces the most smooth
translate([-5,0,10]) {
    for (i = [0:4]) {
        translate([0,i*(buttonLength+2),0]) { rotate([90,180,-90]) {
            //cube([10,20,40]);

            button();
        }}
    }
}

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

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

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

        // Surface Transducer - Small
        translate([thumbBevelWidth-wallThick+4,caseLength-2*wallThick-21.4-4,-10]) {
           cube([13.8,21.4,7.9]);
        }
        // Bluetooth Modem - BlueSMiRF Silver -- 42.0 x 16.0 x 3.9
        translate([1,31,-10]) { cube([16,42,3.9]); }
        // Polymer Lithium Ion Battery - 110mAh
        translate([-40,3,-10]) { cube([12,28,5.7]); }
        // Polymer Lithium Ion Battery - 400mAh
	    translate([18,47,-10]) { cube([25,35,5]); }
        // Triple Axis Accelerometer & Gyro Breakout - MPU-6050
        translate([1,4,-10]) { cube([15.5, 25.7, 2.5]); }
    }
}


fix passing of fatten value

fix passing of fatten value

echo("a box with holes in it for Simon");
// todo: get rid of hard-coded '28' in walls_of_box

bigbox = 80;						// old box was 70?
bigboxht = 55;						// up from 50
fatten_for_lid_cut=0;				// test fit lids: if more than .6 needed, bad geometry or print
									// also, should be zero when printing center section
sides = 2;							// skinny, assumes some further light-stopping material/paint
ledhole=15;
holeside=ledhole + sides*2 + 10;	// accomodate lense assembly
cornerad=4;						// used with minkowski(), cornerad*2 + 4-flat-sides = perimeter
small_chamber = 1/3;				// total of chambers' ratios should == 1
big_chamber = 2/3;				// chambers can be swapped
slit_width = 5;						// mm
cuvtt_inside = 12;					// put cuvette into a lid, slide detector around it, put other lid on.
lid_lad = 7;						// basic lid thickness
lid_puff = 4;						// rounded-ness for lid

module walls_of_box(
			fattener=fatten_for_lid_cut,
			longside=bigbox,
			bigboxht=bigboxht,
			wallthick=sides,
			ledhole=ledhole,
			cornerad=cornerad,
			holeside=holeside,
			insiders_x=bigbox - sides*3 - cornerad*2,
			insiders_y=ledhole - cornerad,		// inner-wall corner radius is hard-coded at 1/2 of outer
			){
	echo("longside = ", longside);
	echo("insiders_x = ", insiders_x);
	echo("insiders_y = ", insiders_y);
	difference(){
		minkowski(){
			square([longside - cornerad*2, holeside - cornerad*2], center=true);
			circle(r=cornerad+fattener, $fn=36);
		}
		for(block=[[small_chamber*insiders_x - fattener*2, fattener/2],
					[big_chamber*insiders_x - fattener*2, 28-fattener/2]]){
			echo("block[0] = ", block[0]);
			translate([-longside/2+(wallthick+fattener/2)*2+block[0]/2 + block[1], 0, 0]){
				minkowski(){
					square([block[0], holeside - cornerad - sides*2 - fattener*2], center=true);
					circle(r=(cornerad-fattener)/2, $fn=36);
				}
				// this square is just here to show the light-path
				#square([block[0], slit_width], center=true);
			}
		}
	}
}

module growbox(to_height=bigboxht, slit_width=slit_width, fatboy=0){
	difference(){
		linear_extrude(height = to_height, center = true, convexity = 10, twist = 0, slices = to_height*2, scale = 1){
			walls_of_box(fattener=fatboy+fatten_for_lid_cut);
		}
		cube([bigbox-sides*2-cornerad, slit_width, to_height - 10], center=true);
		for (i=[-bigbox/2, bigbox/2]){
			translate([i,0,0]) rotate([0,90,0])
			#cylinder(r=ledhole/2, h=sides*3, center=true, $fn=128);
		}
	}
}

module lids_on_it (overhang=lid_puff, basethick=lid_lad, with_cuvt=1){
	difference(){
		minkowski(){
			cube([bigbox, holeside, basethick], center=true);
			sphere(r=overhang/2, center=true, $fn=64);
		}
		// imprint the base of the walls and cuvette on a lid-like-structure
		translate([0,0,bigboxht/2])
			growbox(fatboy=.2);
		translate([0,0,bigboxht/2])
			cube([cuvtt_inside+.5, cuvtt_inside+.5, bigboxht], center=true);
		// an extra cut to ensure the lid doesn't obscure the cuvett:
		translate([0,0,bigboxht/2])
			cube([bigbox-sides*4, slit_width-.1, bigboxht], center=true);
	}
}

translate([0,holeside+lid_puff+2,(lid_lad+lid_puff)/2])
	lids_on_it(with_cuvt=1);
translate([0,-(holeside+lid_puff+2),(lid_lad+lid_puff)/2])
	lids_on_it(with_cuvt=0);
translate([0,0,bigboxht/2])
	growbox();

echo("a box with holes in it for Simon");
// todo: get rid of hard-coded '28' in walls_of_box

bigbox = 80;						// old box was 70?
bigboxht = 55;						// up from 50
fatten_for_lid_cut=.0;				// test fit lids: if more than .6 needed, bad geometry or print
sides = 2;							// skinny, assumes some further light-stopping material/paint
ledhole=15;
holeside=ledhole + sides*2 + 10;	// accomodate lense assembly
cornerad=4;						// used with minkowski(), cornerad*2 + 4-flat-sides = perimeter
small_chamber = 1/3;				// total of chambers' ratios should == 1
big_chamber = 2/3;				// chambers can be swapped
slit_width = 5;						// mm
cuvtt_inside = 12;					// put cuvette into a lid, slide detector around it, put other lid on.
lid_lad = 7;						// basic lid thickness
lid_puff = 4;						// rounded-ness for lid

module walls_of_box(
			fattener=fatten_for_lid_cut,
			longside=bigbox,
			bigboxht=bigboxht,
			wallthick=sides,
			ledhole=ledhole,
			cornerad=cornerad,
			holeside=holeside,
			insiders_x=bigbox - sides*3 - cornerad*2,
			insiders_y=ledhole - cornerad,		// inner-wall corner radius is hard-coded at 1/2 of outer
			){
	echo("longside = ", longside);
	echo("insiders_x = ", insiders_x);
	echo("insiders_y = ", insiders_y);
	difference(){
		minkowski(){
			square([longside - cornerad*2, holeside - cornerad*2], center=true);
			circle(r=cornerad-fattener, $fn=36);
		}
		for(block=[[small_chamber*insiders_x - fattener*2, fattener/2],
					[big_chamber*insiders_x - fattener*2, 28-fattener/2]]){
			echo("block[0] = ", block[0]);
			translate([-longside/2+(wallthick+fattener/2)*2+block[0]/2 + block[1], 0, 0]){
				minkowski(){
					square([block[0], holeside - cornerad - sides*2 - fattener*2], center=true);
					circle(r=cornerad/2, $fn=36);
				}
				// this square is just here to show the light-path
				#square([block[0], slit_width], center=true);
			}
		}
	}
}

module growbox(to_height=bigboxht, slit_width=slit_width, fatboy=0){
	difference(){
		linear_extrude(height = to_height, center = true, convexity = 10, twist = 0, slices = to_height*2, scale = 1){
			walls_of_box(fattener=fatboy);
		}
		cube([bigbox-sides*2-cornerad, slit_width, to_height - 10], center=true);
		for (i=[-bigbox/2, bigbox/2]){
			translate([i,0,0]) rotate([0,90,0])
			#cylinder(r=ledhole/2, h=sides*3, center=true, $fn=128);
		}
	}
}

module lids_on_it (overhang=lid_puff, basethick=lid_lad, with_cuvt=1){
	difference(){
		minkowski(){
			cube([bigbox, holeside, basethick], center=true);
			sphere(r=overhang/2, center=true, $fn=64);
		}
		// imprint the base of the walls and cuvette on a lid-like-structure
		translate([0,0,bigboxht/2])
			growbox(fatboy=.2);
		translate([0,0,bigboxht/2])
			cube([cuvtt_inside+.5, cuvtt_inside+.5, bigboxht], center=true);
		// an extra cut to ensure the lid doesn't obscure the cuvett:
		translate([0,0,bigboxht/2])
			cube([bigbox-sides*4, slit_width-.1, bigboxht], center=true);
	}
}

translate([0,holeside+lid_puff+2,(lid_lad+lid_puff)/2])
	lids_on_it(with_cuvt=1);
translate([0,-(holeside+lid_puff+2),(lid_lad+lid_puff)/2])
	lids_on_it(with_cuvt=0);
translate([0,0,bigboxht/2])
	growbox();


system: Add XY,XZ,YZ

system: Add XY,XZ,YZ

XAXIS = [1,0,0];
YAXIS = [0,1,0];
ZAXIS = [0,0,1];
NAXIS = [0,0,0];

X = XAXIS;
Y = YAXIS;
Z = ZAXIS;
N = NAXIS;

XY = X+Y;
XZ = X+Z;
YZ = Y+Z;

AXES = [X,Y,Z];

U = undef;

function get(key, dict) =
    let(x = search(key, dict))
    let(kv = dict[x[0]])
    kv[1];

XAXIS = [1,0,0];
YAXIS = [0,1,0];
ZAXIS = [0,0,1];
NAXIS = [0,0,0];

X = XAXIS;
Y = YAXIS;
Z = ZAXIS;
N = NAXIS;

AXES = [X,Y,Z];

U = undef;

function get(key, dict) =
    let(x = search(key, dict))
    let(kv = dict[x[0]])
    kv[1];


screws/cut: add cut_screw, screw_offset params

include <system.scad>
include <shapes.scad>
include <transforms.scad>
include <misc.scad>

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

use <scad-utils/transformations.scad>

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

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

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

    s = threadsize*tolerance;
    total_h = h;
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        translate([0,0,head_embed?-head_h:0])
        {
            if(with_head)
            {
                translate([0,0,h/2+.01])
                screw_head(thread=thread_, orient=Z, align=Z);
            }

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

            if(with_nut && nut != U)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);
            }
        }

        if($show_vit)
        {
            translate([0,0,head_embed?-head_h:0])
            if(with_nut && nut != U)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                %screw_nut(nut=nut, align=-Z);
            }
        }
    }
}

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

    nut_h = get(NutThickness,nut)*tolerance;
    head_h = get_screw_head_h(thread_);
    assert(head_h != U, "screw_cut: head_h is U!");

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

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

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

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

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

module screw_thread(thread, h=10, tolerance=1.00, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread: thread is undef");
    // TODO render thread
    threadsize = get(ThreadSize, thread);
    cylindera(d=threadsize, h=h, orient=orient, align=align);
}

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

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

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread);
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    difference()
    {
        cylindera(d=head_d, h = fallback(override_h,head_h));
        translate([0,0,head_h/2])
        cylindera(d=threadsize, h = head_d/2);
    }
}

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

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread)*tolerance;
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    translate([0,0,-head_h/2])
    cylindera(d=head_d, h=fallback(override_h, head_h), align=Z);
}

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

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut);
    cylindera($fn=nut_facets, d=nut_dia, h=fallback(override_h, nut_thick), orient=orient, align=align);
}

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

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_dia = nut_dia(nut);
    size_align(size=[nut_dia, nut_dia, nut_thick], orient=orient, align=align)
    {
        translate([0,0,nut_thick/2])
        {
            screw_nut(nut, tolerance, orient=Z, align=-Z);
            translate([0,0,-nut_thick+.01])
            {
                cylindera(d=nut_dia+2*mm, h=h, orient=Z, align=-Z);
            }
        }
    }
}

// distance from center to flat on a polygon (taking in the diagonal distance)
function apothem(radius, facets) = radius/cos(180/facets);

function nut_radius(nut) = apothem(get(NutWidthMax, nut)/2, get(NutFacets, nut));
function nut_dia(nut) = 2*nut_radius(nut);

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

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

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

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

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

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

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

    }
}

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

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

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

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

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

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

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

}

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

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

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

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

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

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

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

include <system.scad>
include <shapes.scad>
include <transforms.scad>
include <misc.scad>

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

use <scad-utils/transformations.scad>

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

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

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

    s = threadsize*tolerance;
    total_h = h;
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        translate([0,0,head_embed?-head_h:0])
        {
            if(with_head)
            {
                translate([0,0,h/2+.01])
                screw_head(thread=thread_, orient=Z, align=Z);
            }

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

            if(with_nut && nut != U)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);
            }
        }

        if($show_vit)
        {
            translate([0,0,head_embed?-head_h:0])
            if(with_nut && nut != U)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                %screw_nut(nut=nut, align=-Z);
            }
        }
    }
}

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

    nut_h = get(NutThickness,nut)*tolerance;
    head_h = get_screw_head_h(thread_);
    assert(head_h != U, "screw_cut: head_h is U!");

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

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

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

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

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

module screw_thread(thread, h=10, tolerance=1.00, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread: thread is undef");
    // TODO render thread
    threadsize = get(ThreadSize, thread);
    cylindera(d=threadsize, h=h, orient=orient, align=align);
}

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

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

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread);
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    difference()
    {
        cylindera(d=head_d, h = fallback(override_h,head_h));
        translate([0,0,head_h/2])
        cylindera(d=threadsize, h = head_d/2);
    }
}

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

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread)*tolerance;
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    translate([0,0,-head_h/2])
    cylindera(d=head_d, h=fallback(override_h, head_h), align=Z);
}

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

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut);
    cylindera($fn=nut_facets, d=nut_dia, h=fallback(override_h, nut_thick), orient=orient, align=align);
}

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

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_dia = nut_dia(nut);
    size_align(size=[nut_dia, nut_dia, nut_thick], orient=orient, align=align)
    {
        translate([0,0,nut_thick/2])
        {
            screw_nut(nut, tolerance, orient=Z, align=-Z);
            translate([0,0,-nut_thick+.01])
            {
                cylindera(d=nut_dia+2*mm, h=h, orient=Z, align=-Z);
            }
        }
    }
}

// distance from center to flat on a polygon (taking in the diagonal distance)
function apothem(radius, facets) = radius/cos(180/facets);

function nut_radius(nut) = apothem(get(NutWidthMax, nut)/2, get(NutFacets, nut));
function nut_dia(nut) = 2*nut_radius(nut);

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

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

    nut_width_min = get(NutWidthMin, nut);
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut);
    total_h = nut_h;
    size_align(size=[nut_dia, nut_dia, total_h], orient=orient, orient_ref=Z, align=align)
    {
        translate([0,0,nut_h/2-.1])
        screw_thread_cut(thread=thread_, tolerance=1.1, h=screw_l, orient=-Z, align=Z);

        if(screw_l_extra>0)
        translate([0,0,-nut_h/2+.1])
        screw_thread_cut(thread=thread_, tolerance=1.1, h=screw_l_extra+.1, orient=Z, align=-Z);

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

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

        if($show_vit)
        {
            translate([0,0, screw_l])
            translate([0,0, -nut_h/2])
            %screw(nut=nut, thread=thread, tolerance=1, with_nut=false, orient=-Z, align=-Z);

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

    }
}

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

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

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

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

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

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

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

}

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

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

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

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

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

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

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


Better base for milwaukee battery

Better base for milwaukee battery

use <BezierScad.scad>;

// dimension
// heights:
// - base = 0mm
// - slope highest point = 26.5mm
// - slope mid point = 14mm
// - hook lowest edge = 19mm

// Guide lines
//translate([20,-5,60]) cube([1,29,1],true);
//%translate([0,5.5,100])color("gray") cube([1,52,1],true);
//%translate([0,0,100])color("gray") cube([46,1,1],true);
//%translate([50,-20,26.5]) rotate([-19,0,0]) translate([0,0,0.5])color("gray") cube([1,100,1],true);
//%translate([50,22,0]) rotate([-65,0,0]) translate([0,0,0.5])color("gray") cube([1,100,1],true);

module outer_horizontal_boundary(dim, x0, y0, x1, y1, y2) {
    module half() {
        BezLine([
          [0, 0],
          [dim*y0, dim*x0],
          [dim*y1, dim*x1],
          [dim*y2, dim],
          [0, dim],
          ], width = [0.1, 0.1], resolution = 6);
    }
    half();
    mirror([1,0,0]) half();
}

/*
#color("green") difference() {
translate([0,-19.5, 3]) hull() linear_extrude(height=10) outer_horizontal_boundary(52, 0.06, 0.72, 0.66, 0.41, 0.30);
  translate([0,0.5,0]) battery_tower();
}
*/

/*
#color("green") difference() {
      translate([0,-19.5, -55]) hull() linear_extrude(height=10)    outer_horizontal_boundary(52, 0.04, 0.68, 0.66, 0.45, 0.30);
  translate([0,0.5,0]) battery_tower();
}//*/

//translate([0,-15,-16]) hull() outer_horizontal_boundary(46.4, 0.9, 0.58, 0.37, 0.26);
module outline(height=10, type=1) {
  translate([0,-20.5, -height/2]) hull() linear_extrude(height=height) {
    if (type == 1) {
      outer_horizontal_boundary(52, 0.04, 0.68, 0.66, 0.45, 0.30);
    } else if (type == 2) {
      outer_horizontal_boundary(52, 0.04, 0.73, 0.81, 0.43, 0.2);
    } else if (type == 3) {
      outer_horizontal_boundary(52, 0.03, 0.73, 0.86, 0.44, 0.2);
    }
  }
}

module thin_wall() {
  type = 2;
  intersection() {
//*
  minkowski() {
    difference() {
      cube([100,100,100], true);
      outline(110,type);
    }
    sphere(r=1);
  }
//    */
    outline(110,type);
  }
}
//translate([0,0,40])#outline(1,2);

module hooks2() {
  module hook() {
    radius=40;
    intersection() {
      translate([0,radius,0]) {
        translate([0,0,19]) cylinder(r=radius+1.5,h=4,$fn=120);
        cylinder(r=radius,h=20,$fn=120);
      };
      cube([10,10,50],true);
    }
    intersection() {
      translate([0,radius,0]) {
        cylinder(r=radius-0.5,h=23,$fn=120);
      };
      translate([2,0,0]) cube([14,10,50],true);
    }
  }

  for(a=[0:1:1]) {
    mirror([a,0,0]) translate([17.5,13.5,0]) color("green")rotate([0,0,112]) hook();
  }

}

module hooks() {
  module hook() {
    radius=40;
    intersection() {
      union() {
        translate([0,radius,0]) {
          translate([0,0,19]) cylinder(r=radius+2,h=4,$fn=120);
          cylinder(r=radius,h=20,$fn=120);
          translate([0,0,2]) rotate([-4,0,0]) translate([0,-1,-5]) cylinder(r=radius,h=20,$fn=120);
        };
      }
      rotate([0,0,-30])cube([10,10,50],true);
    }
    intersection() {
      translate([0,radius,0]) {
        cylinder(r=radius-0.5,h=23,$fn=120);
      };
      translate([2,0,0]) cube([14,10,50],true);
    }
  }

  for(a=[0:1:1]) {
    mirror([a,0,0]) translate([17.5,13.5,0]) color("green")rotate([0,0,112]) hook();
  }

}

module battery_tower_base_negative_mold() {
  hull() {
    intersection() {
      translate([0,-165,-452]) rotate([0,90,0]) cylinder(d=1000,h=100,center=true,$fn=360);
      translate([0,-11,0])cube([100,50,100],true);
    }
    translate([0,13.13,11.91]) rotate([0,90,0]) cylinder(d=6,h=100,center=true,$fn=36);
    translate([0,22,0])rotate([-65,0,0])translate([0,0,-1])cube([100,20,2],true);
  }
  translate([0,0,-48.9]) rotate([-2,0,0]) cube([100,100,100],true);
}
//color("red") battery_tower_base_negative_mold(2);

module handle() {
  hull() {
    translate([0,0,1]) outline(2,2);
    translate([0,0,29]) outline(22,3);
  }
  hull() {
//    translate([0,0,38]) outline(10,3);
//    translate([0,5,70]) cylinder(d=50,h=1);
  }
}

difference() {
  union() {
//    intersection() {
//      thin_wall();
//      translate([0,0,15]) cube([1000,1000,30], true);
//    }
    color("blue")difference() {
//      translate([0,0,26+5]) outline(16,2);
      handle();
      translate([0,0,-0.01]) battery_tower();
    }
  }
  battery_tower_base_negative_mold();
  hooks();
}
//battery_tower();

//translate([0,0,50]) hooks();

//translate([0,0,120])color("green")cube([46,1,1],true);
//translate([0,-18, 50]) hull() outer_horizontal_boundary(50, 0.83, 0.62, 0.37, 0.26);

module battery_tower() {
    clearance = 0.0;
    battery_r = 11 + clearance;
    battery_distance = 9.4;
    cell_h = 60;
    big_tower_h = cell_h + 10;
    battery_d = battery_r * 2;
    big_tower_w = battery_d * 1.1;
    triangle_h = battery_distance * sqrt(3);
    big_tower_offset = triangle_h - battery_r;

    module trunk(height) {
        hull() {
            translate([-battery_distance, 0, 0])
                cylinder(height, r=battery_r,$fn=60);
            translate([battery_distance, 0, 0])
                cylinder(height, r=battery_r,$fn=60);
            translate([0, triangle_h, 0])
                cylinder(height, r=battery_r+0.0,$fn=60);
        }
    }

    trunk(cell_h);

    module edge(height) {
      hull() {
      translate([12.8,21.5])cylinder(d=1.5+ clearance*2,h=height, $fn=36);
      translate([12.8,19.4])cylinder(d=1.5+ clearance*2,h=height, $fn=36);
      translate([10,19.2])cylinder(d=4+ clearance*2,h=height, $fn=36);
      }
    }
    color("red") edge(big_tower_h);
    color("red") mirror([1,0,0]) edge(big_tower_h);
    intersection() {
        trunk(big_tower_h);
        translate([-big_tower_w / 2, big_tower_offset, 0])
            cube([big_tower_w, big_tower_w, big_tower_h]);
    }

    color("blue") translate([6.5, 15, big_tower_h]) cube([0.2, 7, 10], center = true);
    color("blue") translate([-6.5, 15, big_tower_h]) cube([0.2, 7, 10], center = true);
}

use <BezierScad.scad>;

// dimension
// heights:
// - base = 0mm
// - slope highest point = 26.5mm
// - slope mid point = 14mm
// - hook lowest edge = 19mm

module outer_horizontal_boundary(dim, x0, y0, x1, y1, y2) {
    module half() {
        BezLine([
          [0, 0],
          [dim*y0, dim*x0],
          [dim*y1, dim*x1],
          [dim*y2, dim],
          [0, dim],
          ], width = [0.1, 0.1], resolution = 6);
    }
    half();
    mirror([1,0,0]) half();
}

/*
#color("green") difference() {
translate([0,-19.5, 3]) hull() linear_extrude(height=10) outer_horizontal_boundary(52, 0.06, 0.72, 0.66, 0.41, 0.30);
  translate([0,0.5,0]) battery_tower2();
}
*/

/*
#color("green") difference() {
      translate([0,-19.5, -55]) hull() linear_extrude(height=10)    outer_horizontal_boundary(52, 0.04, 0.68, 0.66, 0.45, 0.30);
  translate([0,0.5,0]) battery_tower();
}//*/

//translate([0,-15,-16]) hull() outer_horizontal_boundary(46.4, 0.9, 0.58, 0.37, 0.26);
module outline(height=10, type=1) {
  translate([0,-20.5, -height/2]) hull() linear_extrude(height=height) {
    if (type == 1) {
      outer_horizontal_boundary(52, 0.04, 0.68, 0.66, 0.45, 0.30);
    } else if (type == 2) {
      outer_horizontal_boundary(52, 0.04, 0.73, 0.81, 0.43, 0.2);
    } else if (type == 3) {
      outer_horizontal_boundary(52, 0.03, 0.73, 0.86, 0.44, 0.2);
    }
  }
}

module thin_wall() {
  type = 2;
  intersection() {
//*
  minkowski() {
    difference() {
      cube([100,100,100], true);
      outline(110,type);
    }
    sphere(r=1);
  }
//    */
    outline(110,type);
  }
}
//translate([0,0,40])#outline(1,3);
module hooks2() {
  module hook() {
    radius=40;
    intersection() {
      translate([0,radius,0]) {
        translate([0,0,19]) cylinder(r=radius+1.5,h=4,$fn=120);
        cylinder(r=radius,h=20,$fn=120);
      };
      cube([10,10,50],true);
    }
    intersection() {
      translate([0,radius,0]) {
        cylinder(r=radius-0.5,h=23,$fn=120);
      };
      translate([2,0,0]) cube([14,10,50],true);
    }
  }

  for(a=[0:1:1]) {
    mirror([a,0,0]) translate([17.5,13.5,0]) color("green")rotate([0,0,112]) hook();
  }

}

module hooks() {
  module hook() {
    radius=40;
    intersection() {
      union() {
        translate([0,radius,0]) {
          translate([0,0,19]) cylinder(r=radius+2,h=4,$fn=120);
          cylinder(r=radius,h=20,$fn=120);
          translate([0,0,2]) rotate([-4,0,0]) translate([0,-1,-5]) cylinder(r=radius,h=20,$fn=120);
        };
      }
      rotate([0,0,-30])cube([10,10,50],true);
    }
    intersection() {
      translate([0,radius,0]) {
        cylinder(r=radius-0.5,h=23,$fn=120);
      };
      translate([2,0,0]) cube([14,10,50],true);
    }
  }

  for(a=[0:1:1]) {
    mirror([a,0,0]) translate([17.5,13.5,0]) color("green")rotate([0,0,112]) hook();
  }

}

//translate([20,-5,60]) cube([1,29,1],true);
%translate([50,-20,26.5]) rotate([-19,0,0]) translate([0,0,0.5])color("gray") cube([1,100,1],true);
%translate([50,22,0]) rotate([-65,0,0]) translate([0,0,0.5])color("gray") cube([1,100,1],true);

module mold() {
  hull()
  {
  translate([0,13.13,11.91]) rotate([0,90,0]) cylinder(d=6,h=100,center=true,$fn=36);
//  translate([0,11,10.1]) rotate([0,90,0]) cylinder(d=10,h=100,center=true,$fn=36);
  translate([0,-20,26.5])rotate([-19,0,0])translate([0,0,-20])cube([100,2,40],true);
  translate([0,22,0])rotate([-65,0,0])translate([0,0,-1])cube([100,20,2],true);
  }
}
//color("red")mold();
module handle() {
  hull() {
  translate([0,0,1]) outline(2,2);
  translate([0,0,29]) outline(22,3);
  }
}
difference() {
  union() {
//    intersection() {
//      thin_wall();
//      translate([0,0,15]) cube([1000,1000,30], true);
//    }
    color("blue")difference() {
//      translate([0,0,26+5]) outline(16,2);
      handle();
      translate([0,0,-0.01]) battery_tower();
    }
  }
  mold();
  hooks();
}
//battery_tower();

//translate([0,0,50]) hooks();

//translate([0,0,120])color("green")cube([46,1,1],true);
//translate([0,-18, 50]) hull() outer_horizontal_boundary(50, 0.83, 0.62, 0.37, 0.26);

module battery_tower() {
    clearance = 0.0;
    battery_r = 11 + clearance;
    battery_distance = 9.4;
    cell_h = 60;
    big_tower_h = cell_h + 10;
    battery_d = battery_r * 2;
    big_tower_w = battery_d * 1.1;
    triangle_h = battery_distance * sqrt(3);
    big_tower_offset = triangle_h - battery_r;

    module trunk(height) {
        hull() {
            translate([-battery_distance, 0, 0])
                cylinder(height, r=battery_r,$fn=60);
            translate([battery_distance, 0, 0])
                cylinder(height, r=battery_r,$fn=60);
            translate([0, triangle_h, 0])
                cylinder(height, r=battery_r+0.0,$fn=60);
        }
    }

    trunk(cell_h);

    module edge(height) {
      hull() {
      translate([12.8,21.5])cylinder(d=1.5+ clearance*2,h=height, $fn=36);
      translate([12.8,19.4])cylinder(d=1.5+ clearance*2,h=height, $fn=36);
      translate([10,19.2])cylinder(d=4+ clearance*2,h=height, $fn=36);
      }
    }
    color("red") edge(big_tower_h);
    color("red") mirror([1,0,0]) edge(big_tower_h);
    intersection() {
        trunk(big_tower_h);
        translate([-big_tower_w / 2, big_tower_offset, 0])
            cube([big_tower_w, big_tower_w, big_tower_h]);
    }

    color("blue") translate([6.5, 15, big_tower_h]) cube([0.2, 7, 10], center = true);
    color("blue") translate([-6.5, 15, big_tower_h]) cube([0.2, 7, 10], center = true);
}


feat: allow selecting a single visual test

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

/**
 * Part of the camelSCAD library.
 *
 * Testing helpers for visual tests.
 *
 * @package shape/context
 * @author jsconan
 */

/**
 * Places the children at the proper test area.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param Number [index] - The index number of the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not the children are centered and need to be moved.
 */
module placeVisualTest(length, width, index=0, margin=0, cols=0, center=false) {
    margin = vector2D(margin);
    x = cols ? index % cols : index;
    y = cols ? floor(index / cols) : 0;
    offset = center ? [length, width, 0] / 2 : ORIGIN_3D;

    translate(offset + [(length + margin.x) * x, (width + margin.y) * y, 0]) {
        children();
    }
}

/**
 * Colorizes a test element.
 * @param Number alpha - The alpha level for the testbed color.
 */
module testElement(alpha) {
    %color(TESTBED_COLOR, alpha) {
        children();
    }
}

/**
 * Extrudes and colorizes a test element.
 * @param Number alpha - The alpha level for the testbed color.
 */
module testbed(alpha) {
    testElement(alpha) {
        linear_extrude(height=TESTBED_THICKNESS, convexity=10, center=true) {
            children();
        }
    }
}

/**
 * Renders a test area, moving the tested shapes in it.
 * @param Number index - The index number of the test.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param String [title] - The title for the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not the children are centered and need to be moved.
 */
module visualTest(index, length, width, title="test", margin=1, cols=0, center=false) {
    title = str(title, " ", index);

    placeVisualTest(
        length = length,
        width = width,
        index = index,
        margin = margin,
        cols = cols,
        center = center
    ) {
        // test area
        testbed(.1) {
            square([length, width], center=center);
        }
        translate(center ? ORIGIN_3D : [length, width, 0] / 2) {
            testbed(1) {
                text(title, size=min(length, width) / len(title), font="Liberation Sans", valign="center", halign="center");
            }
        }

        // tested shapes
        children();
    }
}

/**
 * Renders a test area for each child module, moving the tested shapes in it.
 * A single test can be selected by its index thanks to the global variable `selectTest`.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param String [title] - The title for the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not center the whole suite.
 * @param Boolean [centerEach] - Whether or not center each test area.
 */
module visualTestSuite(length, width, title="test", margin=1, cols=0, center=false, centerEach=false) {
    margin = vector2D(margin);
    count = $children;
    selectTest = is_undef(selectTest) ? undef : min(max(0, selectTest), count - 1);
    start = is_undef(selectTest) ? 0 : selectTest;
    end = is_undef(selectTest) ? count - 1 : selectTest;
    lines = cols ? ceil(count / cols) : 1;
    offset = center ? -([margin.x * (cols - 1), margin.y * (lines - 1), 0] + [length * cols, width * lines, 0]) / 2 : ORIGIN_3D;

    translate(offset) {
        for (i = [start : end]) {
            visualTest(
                index = i,
                length = length,
                width = width,
                title = title,
                margin = margin,
                cols = cols,
                center = centerEach
            ) {
                children(i);
            }
        }
    }
}

// Select a single test to show
selectTest = undef;

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

/**
 * Part of the camelSCAD library.
 *
 * Testing helpers for visual tests.
 *
 * @package shape/context
 * @author jsconan
 */

/**
 * Places the children at the proper test area.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param Number [index] - The index number of the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not the children are centered and need to be moved.
 */
module placeVisualTest(length, width, index=0, margin=0, cols=0, center=false) {
    margin = vector2D(margin);
    x = cols ? index % cols : index;
    y = cols ? floor(index / cols) : 0;
    offset = center ? [length, width, 0] / 2 : ORIGIN_3D;

    translate(offset + [(length + margin.x) * x, (width + margin.y) * y, 0]) {
        children();
    }
}

/**
 * Colorizes a test element.
 * @param Number alpha - The alpha level for the testbed color.
 */
module testElement(alpha) {
    %color(TESTBED_COLOR, alpha) {
        children();
    }
}

/**
 * Extrudes and colorizes a test element.
 * @param Number alpha - The alpha level for the testbed color.
 */
module testbed(alpha) {
    testElement(alpha) {
        linear_extrude(height=TESTBED_THICKNESS, convexity=10, center=true) {
            children();
        }
    }
}

/**
 * Renders a test area, moving the tested shapes in it.
 * @param Number index - The index number of the test.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param String [title] - The title for the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not the children are centered and need to be moved.
 */
module visualTest(index, length, width, title="test", margin=1, cols=0, center=false) {
    title = str(title, " ", index);

    placeVisualTest(
        length = length,
        width = width,
        index = index,
        margin = margin,
        cols = cols,
        center = center
    ) {
        // test area
        testbed(.1) {
            square([length, width], center=center);
        }
        translate(center ? ORIGIN_3D : [length, width, 0] / 2) {
            testbed(1) {
                text(title, size=min(length, width) / len(title), font="Liberation Sans", valign="center", halign="center");
            }
        }

        // tested shapes
        children();
    }
}

/**
 * Renders a test area for each child module, moving the tested shapes in it.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param String [title] - The title for the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not center the whole suite.
 * @param Boolean [centerEach] - Whether or not center each test area.
 */
module visualTestSuite(length, width, title="test", margin=1, cols=0, center=false, centerEach=false) {
    margin = vector2D(margin);
    lines = cols ? ceil($children / cols) : 1;
    offset = center ? -([margin.x * (cols - 1), margin.y * (lines - 1), 0] + [length * cols, width * lines, 0]) / 2 : ORIGIN_3D;

    translate(offset) {
        for (i = [0 : $children - 1]) {
            visualTest(
                index = i,
                length = length,
                width = width,
                title = title,
                margin = margin,
                cols = cols,
                center = centerEach
            ) {
                children(i);
            }
        }
    }
}