bearing: support align param

bearing: support align param

use <transforms.scad>;
use <shapes.scad>
include <bearing_data.scad>
include <units.scad>
include <misc.scad>

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

        }
    }
}

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

    ziptie_dist_ = fallback(ziptie_dist, bearing_type[3]/2);

    h = fallback(override_h, bearing_type[2]) + extra_h;
    size_align(size=[bearing_type[1],bearing_type[1],h], align=align ,orient=orient)
    {
        // Main bearing cut
        cylindera(h=bearing_type[2]*tolerance, d=bearing_type[1]*tolerance, orient=[0,0,1]);

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

        // for linear rod
        cylindera(d=bearing_type[0]+2*mm, h=100, orient=[0,0,1]);

        if($show_vit)
        {
            %bearing(bearing_type=bearing_type);

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

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

use <transforms.scad>;
use <shapes.scad>
include <bearing_data.scad>
include <units.scad>
include <misc.scad>

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

        }
    }
}

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

    ziptie_dist_ = fallback(ziptie_dist, bearing_type[3]/2);

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

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

        // for linear rod
        cylindera(d=bearing_type[0]+2*mm, h=100, orient=[0,0,1]);

        if($show_vit)
        {
            %bearing(bearing_type=bearing_type);

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

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


=> rules coding

=> rules coding

//Азотистые основания:
x=60;
y=26;
z=7;
t=0;
edge=0.71*y;
dx=edge*1.41;
//Тимин
translate ([dx/2,t,0]) {
  color("Lime") translate ([0,0,0]) cube ([x-dx/2,y,z]);
  color("Lime") rotate ([0,0,45]) cube ([edge,edge,z]);
}

//Азотистые основания:
x=60;
y=26;
z=7;
t=0;
edge=0.71*y;
dx=edge*1.41;
//Тимин
translate ([dx/2,t,0]){
  color("Lime") translate ([0,0,0]) cube ([x-dx/2,y,z]);
  color("Lime") rotate ([0,0,45]) cube ([edge,edge,z]);
}


re-enable the cube and a customizer option for cube or cylinder


added hub feature

added hub feature

//===================================================================================
// Parameteric Spur Gear SCAD script
//
// Based on Boston Gear design rules https://www.bostongear.com/pdf/gear_theory.pdf
//
// Copyright (c) 2016 Mark David Seminatore
//
// Refer to included LICENSE.txt for usage rights and restrictions
//===================================================================================
//

$fs = 0.01;

// Example 24 tooth gear with default options
mdsSpurGear(teeth = 24);

//
//
//
module mdsSpurGear(teeth = 24, diametral_pitch = 24, pressure_angle = 20, shaft_dia = 0.125, thickness = 0.125, major_holes = true, minor_holes = true, num_holes = 4, hub_thickness = 1.25, debug_echo = false) {

    // calculate basic gear parameters
    pitch_dia   = teeth / diametral_pitch;
    pitch_radius = pitch_dia / 2;

    addendum = 1 / diametral_pitch;

    outer_dia = pitch_dia + 2 * addendum;
    outer_radius = outer_dia / 2;

    whole_depth = 2.2 / diametral_pitch + 0.002;
    dedendum = whole_depth - addendum;

    root_dia = pitch_dia - 2 * dedendum;
    root_radius = root_dia / 2;

    base_dia = pitch_dia * cos(pressure_angle);
    base_radius = base_dia / 2;

    r_mid_point = root_radius / 2 + shaft_dia / 4;

    if (debug_echo) {
        echo(root_dia=root_dia,base_dia=base_dia,pitch_dia=pitch_dia,outer_dia=outer_dia,whole_depth=whole_depth, addendum=addendum,dedendum=dedendum);
        echo(r_mid_point = r_mid_point);
    }

    // draw rim if present
    linear_extrude(height = hub_thickness * thickness)
    union() {
        difference() {
            circle($fn = teeth*3, d = root_dia);
            circle($fn = teeth*3, d = 0.9 * root_dia);
        }

        pitch_to_base_angle = involuteIntersectionAngle( base_radius, pitch_radius );
        outer_to_base_angle = involuteIntersectionAngle( base_radius, outer_radius );

        half_angle = 360 / (4 * teeth);

        base_angle = pitch_to_base_angle + half_angle;

        if (debug_echo)
            echo(half_angle=half_angle,pitch_to_base_angle=pitch_to_base_angle,base_angle=base_angle);

        // draw teeth
        for (i = [1 : teeth]) {

            // pitch_angle =

            // coordinates of tooth base
            b1 = fromPolar(root_radius, base_angle);
            b2 = fromPolar(root_radius, -base_angle);

            // coordinates of tooth at pitch diameter
            p1 = b1 + fromPolar(dedendum, pitch_to_base_angle);
            p2 = b2 + fromPolar(dedendum, pitch_to_base_angle);

            // coordinates of tooth at outer diameter
            o1 = p1 + fromPolar(addendum, -pressure_angle);
            o2 = p2 + fromPolar(addendum, pressure_angle);

            rotate([0, 0, i * 360 / teeth])
            polygon([b1, p1, o1, o2, p2, b2]);
        }
    }

    // draw the main disc
    translate([0, 0, (hub_thickness * thickness - thickness)/2])
    linear_extrude(height = thickness)
    union() {

        difference() {
            // draw root circle
            circle($fn = teeth*3, d = root_dia);

            // subtract shaft
            circle(d = shaft_dia);

            // draw major holes
            if (major_holes && r_mid_point >= 0.125) {
                echo("Draw major holes");
                angle_inc = 360 / num_holes;

                for (i = [1 : num_holes]) {
                    rotate([0, 0, i * angle_inc])
                    translate([r_mid_point, 0, 0])
                    circle(d = 0.85 * r_mid_point);
                }
            }

            // draw minor hollows
            if (minor_holes && r_mid_point >= 0.25) {
                echo("Draw minor holes");
                angle_inc = 360 / num_holes;

                inner_minor_dia = r_mid_point / 4;
                inner_minor_ctr = r_mid_point / 3;
                outer_minor_dia = r_mid_point / 2;
                outer_minor_ctr = r_mid_point * 1.2;

                for (i = [1 : num_holes]) {
                    rotate([0, 0, i * angle_inc + angle_inc / 2]) {
                        // selectively draw inner minor holes
                        if (inner_minor_ctr - inner_minor_dia / 2 > shaft_dia / 2 + 0.125)
                        {
                            translate([inner_minor_ctr, 0, 0])
                            circle(d = inner_minor_dia);
                        }

                        // selectively draw outer minor holes
                        translate([outer_minor_ctr, 0, 0])
                        circle(d = outer_minor_dia);
                    }
                }
            }
        }
    }
}

//
function involuteIntersectionAngle(base_radius, radius) = sqrt( pow(radius / base_radius,2) - 1);

//
function fromPolar(r, theta) = [r * cos(theta), r * sin(theta)];

//===================================================================================
// Parameteric Spur Gear SCAD script
//
// Based on Boston Gear design rules https://www.bostongear.com/pdf/gear_theory.pdf
//
// Copyright (c) 2016 Mark David Seminatore
//
// Refer to included LICENSE.txt for usage rights and restrictions
//===================================================================================
//

$fs = 0.01;

// Example 24 tooth gear with default options
mdsSpurGear(teeth = 24);

//
//
//
module mdsSpurGear(teeth = 24, diametral_pitch = 24, pressure_angle = 20, shaft_dia = 0.125, thickness = 0.125, major_holes = true, minor_holes = true, num_holes = 4, debug_echo = false) {

    // calculate basic gear parameters
    pitch_dia   = teeth / diametral_pitch;
    pitch_radius = pitch_dia / 2;

    addendum = 1 / diametral_pitch;

    outer_dia = pitch_dia + 2 * addendum;
    outer_radius = outer_dia / 2;

    whole_depth = 2.2 / diametral_pitch + 0.002;
    dedendum = whole_depth - addendum;

    root_dia = pitch_dia - 2 * dedendum;
    root_radius = root_dia / 2;

    base_dia = pitch_dia * cos(pressure_angle);
    base_radius = base_dia / 2;

    r_mid_point = root_radius / 2 + shaft_dia / 4;

    if (debug_echo) {
        echo(root_dia=root_dia,base_dia=base_dia,pitch_dia=pitch_dia,outer_dia=outer_dia,whole_depth=whole_depth, addendum=addendum,dedendum=dedendum);
        echo(r_mid_point = r_mid_point);
    }

    linear_extrude(height = thickness)
    union() {
        pitch_to_base_angle = involuteIntersectionAngle( base_radius, pitch_radius );
        outer_to_base_angle = involuteIntersectionAngle( base_radius, outer_radius );

        half_angle = 360 / (4 * teeth);

        base_angle = pitch_to_base_angle + half_angle;

        echo(half_angle=half_angle,pitch_to_base_angle=pitch_to_base_angle,base_angle=base_angle);

        // draw teeth
        for (i = [1 : teeth]) {

            // pitch_angle =

            // coordinates of tooth base
            b1 = fromPolar(root_radius, base_angle);
            b2 = fromPolar(root_radius, -base_angle);

            // coordinates of tooth at pitch diameter
            p1 = b1 + fromPolar(dedendum, pitch_to_base_angle);
            p2 = b2 + fromPolar(dedendum, pitch_to_base_angle);

            // coordinates of tooth at outer diameter
            o1 = p1 + fromPolar(addendum, -pressure_angle);
            o2 = p2 + fromPolar(addendum, pressure_angle);

            rotate([0, 0, i * 360 / teeth])
            polygon([b1, p1, o1, o2, p2, b2]);
        }

        difference() {
            // draw root circle
            circle($fn = teeth*3, d = root_dia);

            // subtract shaft
            circle(d = shaft_dia);

            // draw major holes
            if (major_holes && r_mid_point >= 0.125) {
                echo("Draw major holes");
                angle_inc = 360 / num_holes;

                for (i = [1 : num_holes]) {
                    rotate([0, 0, i * angle_inc])
                    translate([r_mid_point, 0, 0])
                    circle(d = r_mid_point);
                }
            }

            // draw minor hollows
            if (minor_holes && r_mid_point >= 0.25) {
                echo("Draw minor holes");
                angle_inc = 360 / num_holes;

                inner_minor_dia = r_mid_point / 4;
                inner_minor_ctr = r_mid_point / 3;
                outer_minor_dia = r_mid_point / 2;
                outer_minor_ctr = 4 * r_mid_point / 3;

                for (i = [1 : num_holes]) {
                    rotate([0, 0, i * angle_inc + angle_inc / 2]) {
                        // selectively draw inner minor holes
                        if (inner_minor_ctr - inner_minor_dia / 2 > shaft_dia / 2 + 0.125)
                        {
                            translate([inner_minor_ctr, 0, 0])
                            circle(d = inner_minor_dia);
                        }

                        // selectively draw outer minor holes
                        translate([outer_minor_ctr, 0, 0])
                        circle(d = outer_minor_dia);
                    }
                }
            }
        }
    }
}

//
function involuteIntersectionAngle(base_radius, radius) = sqrt( pow(radius / base_radius,2) - 1);

//
function fromPolar(r, theta) = [r * cos(theta), r * sin(theta)];


Removed finger and thumb pinhole columns, which proved unnecessary (with the hard uPrint plastic) and took up valuable space. Also made divider walls thinner.

/*
OpenSCAD design for Typeatron Mark 1
Copyright 2013 by Joshua Shinavier

Note: 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.

Some details to double-check before printing:
  * channel for index finger's pressure sensor does not intersect thumb button structure
    Ideally, let it be entirely below, so that the sensor can lie flat on the case floor

*/

function sq(x) = x*x;

////////////////////////////////////////////////////////////////////////////////
// rendering settings

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

// hollow out buttons to save plastic/money (possible only with thick buttons)
hollowButtons = false;

// put pinholes in strategic locations where there is insuficient solid material for a hole;
// this requires a column to be built around the hole.  The columns take up valuable space,
// but better coverage with pinholes makes it possible to secure the lid more firmly to the case
floatingPinHoles = false;

simpleEdges = true;

/*
cornerRoundingRes = 100;
ledDomeRes = 100;
thumbCurveRes = 50;
//*/
//*
cornerRoundingRes = 10;
ledDomeRes = 10;
thumbCurveRes = 10;
//*/

ledRimRes = 20;
lightSensorWellRes = 20;
pinHoleRes = 10;

////////////////////////////////////////////////////////////////////////////////
// printer parameters

// the following values are for Shapeways' "Strong & Flexible Plastics" material
shapewaysAccuracy = 0.15;  // then 0.15% of longest axis

//accuracy = shapewaysAccuracy;
//accuracy = 0.178;  // for Stratasys Dimension Elite
accuracy = 0.254;  // for Stratasys uPrint

accuracyRatio = accuracy / shapewaysAccuracy;

// Additional figures from Shapeways, but adjusted for the resolution of the particular printer
// Assumed to be more or less analogous.
clearance = 0.5 * accuracyRatio;
minWallSupported = 0.7 * accuracyRatio;
minWallFree = 0.7 * accuracyRatio;
minWireSupported = 0.9 * accuracyRatio;
minWireFree = 1.0 * accuracyRatio;
minEmbossedDetail = 0.2 * accuracyRatio;
minEmbossedText = 0.5 * accuracyRatio;
minEngravedDetail = 0.2 * accuracyRatio;
minEngravedText = 0.5 * accuracyRatio;
//Min Bounding Box: x+y+z ≥ 7.5mm
//Max Bounding Box: 650x350x550mm (White) · 230x180x320mm (Black) · 150x150x150mm (Polished & Dyed)

accuracy2 = accuracy * 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 = accuracy + 0.1;

////////////////////////////////////////////////////////////////////////////////
// dimensions of foreign parts

batteryWidth = 25;
batteryLength = 36;
batteryHeight = 5.4;

chargerHeaderLength = 5.1;
chargerHeaderWidth = 2.6;

laserWidth = 10.3;
laserThick = 3.3;

ledDomeRadius = 5.0 / 2;
ledRimRadius = 5.9 / 2;
ledRimThick = 1.0;

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

modemWidth = 16.8;
modemLength = 42.8;
modemHeight = 4.1;

motionSensorWidth = 15.4;
motionSensorLength = 25.8;
motionSensorHeight = 2.5;

powerSwitchLength = 11.7;
powerSwitchWidth = 4.0;
powerSwitchDepth = 7.7;

pressureSensorWidth = 7.8;  // by direct measurement of the wide "head". The data sheet says 7.2mm
pressureSensorThick = 0.40;  // by direct measurement, without removing the adhesive backing. The data sheet says 0.35mm

tactileSwitchWellWidth = 7;
tactileSwitchHeight = 5.0;
tactileSwitchLegLength = 3.4;
tactileSwitchPressDepth = 0.25;
tactileSwitchWellDepth = 4.0;  // this is the height of the body of the tactile switch (without the push button)
tactileSwitchBaseThick = 1.5;

nanoLength = 43.2;
nanoWidth = 18.0;
nanoHeightWithoutUsb = 5.4;  // used only for visualization
nanoHeightToUsbBase = 3.4;
nanoUsbWidth = 7.7;
nanoUsbHeight = 3.8;
nanoUsbCableWidth = 9.6;
nanoUsbCableHeight = 6.6;
nanoUsbCableOffsetFromPerfboard = 3.3;

transducerWidth = 14.5;
transducerLength = 21.5;
transducerHeight = 7.9;

////////////////////////////////////////////////////////////////////////////////
// parameters of the printed part

// measurements taken from the clay model
thumbCurveRadius = 39.0;
offsetFromTopEdgeToFirstFinger = 15.0;
fingerWidth = 84 / 4;  // 21 -- 82mm was measured, but this seemed a little cramped
thumbWidth = 25;
thumbUpperRightX = 25.0;
fingerNotchDepth = 5;  // curve in which fingers rest
thumbNotchDepth = 5;   // curve in which the thumb would rest, in the absence of a button

// reasonably chosen values
caseWidth = 70;
caseLength = 120;
lidThick = 1.5;
floorThick = 1.5;

dividerThick = minWallSupported;

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

thickestComponent = transducerHeight;
cavityHeight = thickestComponent + foreignPartClearance;
caseHeight = lidThick + floorThick + cavityHeight;

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

topYOfFingers = caseLength - offsetFromTopEdgeToFirstFinger + dividerThick/2;
bottomYOfFingers = caseLength - offsetFromTopEdgeToFirstFinger - 4*fingerWidth - dividerThick/2;

alignmentPegWidth = 2;
alignmentPegHeight = 2;

buttonHeight = 5;
buttonWidth = tactileSwitchWellWidth;
fingerButtonLength = fingerWidth - dividerThick;
thumbButtonLength = thumbWidth - dividerThick;
// clearance between button and walls (on all four sides).  We take the risk of a low tolerance for the sake of a snug fit
buttonClearance = accuracy / 2;
buttonLip = 2.5;
buttonSlack = tactileSwitchPressDepth * 2;  // minimum vertical clearance between button and floor/retainers
buttonStabilizerThick = 1.75; // vertical bars on buttons and case
buttonRetainerThick = 1.0; // horizontal bars on buttons and case
buttonRetainerGap = 0.75;
buttonRetainerHookHeight = tactileSwitchHeight - (tactileSwitchPressDepth + buttonSlack + accuracy2);

// buttons are inset by 2/3 of the depth of the finger or thumb notch, making most of the contact of the finger
// with the case rather than the button.  This should make the Typeatron easy to grip while typing.  Direct inward
// pressure must be exerted on the button in order to close the switch.
fingerButtonInsetDepth = fingerNotchDepth * 2/3;
thumbButtonInsetDepth = thumbNotchDepth * 2/3;

totalFingerWellDepth = fingerButtonInsetDepth + buttonHeight + tactileSwitchHeight;
totalThumbWellDepth = thumbButtonInsetDepth + buttonHeight + tactileSwitchHeight;

// this keeps the edges of the thumb container from protruding out of the case
thumbWellDepression = 2;

thumbUpperRightY = caseLength - thumbCurveRadius + sqrt(sq(thumbCurveRadius) - sq(thumbCurveRadius - thumbUpperRightX)) - thumbWellDepression;

pinHoleRadius = 0.7 * sqrt(accuracyRatio);
pinHoleBlockWidth = 2 * (pinHoleRadius + clearance*1.5);

// just to be on the safe side, we overestimate the necessary clearance
pressureSensorChannelHeight = clearance * 1.5;

cavityWidth = caseWidth - rimThick - totalFingerWellDepth - tactileSwitchBaseThick;
cavityLength = caseLength - 2*rimThick;

buttonLidHeight = (caseHeight-buttonWidth)/2;

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

////////////////////////////////////////////////////////////////////////////////
// modules

// creates a button of the given length.  Clearance on all sides of the button is subtracted.
// the long axis of the button is x.  y is side-to-side.  z is up and down (as you press the button).
module button(length) {
    innerRetainerWellWidth = (length - tactileSwitchWellWidth - foreignPartClearance)/2 - buttonStabilizerThick;
    retainerLength = innerRetainerWellWidth - buttonClearance - buttonRetainerGap;

    // this measurement is crucial
    stabilizerHeight
        = 2 * buttonRetainerThick
        + (tactileSwitchHeight - tactileSwitchWellDepth + foreignPartClearance)
        + pressureSensorThick
        + 0.15;

    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,buttonHeight-buttonWidth/2]);
            }

            // stabilizer bars
            translate([buttonClearance,buttonClearance,buttonHeight]) {
                cube([buttonStabilizerThick, buttonWidth-2*buttonClearance, stabilizerHeight]);
            }
            translate([length-buttonStabilizerThick-buttonClearance,buttonClearance,buttonHeight]) {
                cube([buttonStabilizerThick, buttonWidth-2*buttonClearance, stabilizerHeight]);
            }

            // retainer bars
            translate([buttonClearance,buttonClearance,buttonHeight+stabilizerHeight-buttonRetainerThick]) {
                cube([retainerLength,buttonWidth-2*buttonClearance, buttonRetainerThick]);
            }
            translate([length-buttonClearance-retainerLength,buttonClearance,buttonHeight+stabilizerHeight-buttonRetainerThick]) {
                cube([retainerLength,buttonWidth-2*buttonClearance, buttonRetainerThick]);
            }
        }

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

// a complex well which houses a finger button and prevents it from detaching from the Typeatron
module fingerButtonWell(length) {
    outerRetainerWellWidth = buttonStabilizerThick + buttonRetainerGap;
    innerRetainerWellWidth = (length - tactileSwitchWellWidth - foreignPartClearance)/2 - buttonStabilizerThick;

    translate([-length/2,-buttonWidth/2,tactileSwitchWellDepth]) {
        // rectangular well for button body
        cube([length,buttonWidth,buttonHeight+10]);  // clearance of 10 is more than enough

        // retainer wells
        translate([0,0,-tactileSwitchWellDepth]) {
            cube([outerRetainerWellWidth, buttonWidth, tactileSwitchWellDepth]);

            cube([innerRetainerWellWidth, buttonWidth, tactileSwitchWellDepth - buttonRetainerThick]);

            translate([length-outerRetainerWellWidth,0,0]) {
                cube([outerRetainerWellWidth, buttonWidth, tactileSwitchWellDepth]);
            }

            translate([length-innerRetainerWellWidth,0,0]) {
                cube([innerRetainerWellWidth, buttonWidth, tactileSwitchWellDepth - buttonRetainerThick]);
            }
		}
    }
}

// well for four-legged through-hole tactile push button switch with origin at the center of the base of the switch
// legs of the switch run along the x axis
module tactileSwitchWell(length, depth) {
    w = tactileSwitchWellWidth + foreignPartClearance;
    l = 1.5; // width of leg holes

    // the pressure sensor channels extend slightly past the well for the tactile switch
    fsrChannelDepth = tactileSwitchLegLength + tactileSwitchHeight;

    translate([-w/2, -w/2, 0]) {

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

        translate([0,0,-tactileSwitchLegLength]) {
            // channels for legs/wires of switch
            translate([0,0,0]) {
                cube([l,l,depth+tactileSwitchLegLength]);
            }
            translate([w-l,0, 0]) {
                cube([l,l,depth+tactileSwitchLegLength]);
            }
            translate([0,w-l, 0]) {
                cube([l,l,depth+tactileSwitchLegLength]);
            }
            translate([w-l,w-l, 0]) {
                cube([l,l,depth+tactileSwitchLegLength]);
            }
        }
    }

    // channels for pressure sensor
    // note: the slight 0.001 overlap prevents a confusing "film" in the STL
    translate([-(pressureSensorWidth+foreignPartClearance)/2,-w/2,-tactileSwitchLegLength]) {
        translate([0, -pressureSensorChannelHeight, 0]) {
            cube([pressureSensorWidth+foreignPartClearance, pressureSensorChannelHeight+0.001, fsrChannelDepth]);
        }
        translate([0, w-0.001, 0]) {
            cube([pressureSensorWidth+foreignPartClearance, pressureSensorChannelHeight, fsrChannelDepth]);
        }
    }

    // rim to allow the pressure sensor to wrap around
    translate([-(pressureSensorWidth+foreignPartClearance)/2, -w/2-pressureSensorChannelHeight, depth]) {
        cube([pressureSensorWidth+foreignPartClearance, w+2*pressureSensorChannelHeight, tactileSwitchHeight - tactileSwitchWellDepth]);
    }
}

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

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

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

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

    // well and channels for finger button, tactile switch, wires, and pressure sensor
    translate([-totalWellDepth,0,caseHeight/2]) {
        rotate([0,90,0]) { rotate([0,0,90]) {
            fingerButtonWell(buttonLength);
            tactileSwitchWell(buttonLength, tactileSwitchWellDepth);
        }}
    }
}

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

module buttons() {
    fingerButtonsPrintable = [caseWidth + 15,bottomYOfFingers+dividerThick,10];
    fingerButtonsInSitu = [caseWidth - fingerButtonInsetDepth,bottomYOfFingers + dividerThick,buttonLidHeight + buttonWidth];
    thumbButtonPrintable = [thumbUpperRightX+thumbButtonLength+dividerThick,caseLength+12,10];
    thumbButtonInSitu = [thumbUpperRightX + thumbWidth - dividerThick/2,thumbUpperRightY - thumbButtonInsetDepth, buttonLidHeight + buttonWidth];

    // TODO: simplify this ridiculous if..then
    if (buttonsInSitu) {
        // Note: the buttons are oriented so as to make the longest surfaces the smoothest
        translate(fingerButtonsInSitu) {
            for (i = [0:3]) {
                translate([0,i*fingerWidth,0]) { rotate([90,180,-90]) {
                    button(fingerButtonLength);
                }}
            }
        }
        translate(thumbButtonInSitu) {
            rotate([90,180,0]) {
                button(thumbButtonLength);
            }
        }
    } else {
        // Note: the buttons are oriented so as to make the longest surfaces the smoothest
        translate(fingerButtonsPrintable) {
            for (i = [0:3]) {
                translate([0,i*fingerWidth,0]) { rotate([90,180,-90]) {
                    button(fingerButtonLength);
                }}
            }
        }
        translate(thumbButtonPrintable) {
            rotate([90,180,0]) {
                button(thumbButtonLength);
            }
        }
    }
}

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

// for the rounded flat edges of the case
module roundedEdge(length, height, cornerRadius) {
    rem = height - 2*cornerRadius;

    if (simpleEdges) {
        cylinder(r=cornerRadius, h=length, $fn=cornerRoundingRes);
    } else {
        intersection() {
            translate([-height/2, 0, 0]) {
                cube([height, cornerRadius, length]);
            }
            union() {
                translate([-rem/2.0, 0, 0]) {
                    cylinder(h=length, r=cornerRadius, $fn=cornerRoundingRes);
                }
                translate([rem/2.0, 0, 0]) {
                    cylinder(h=length, r=cornerRadius, $fn=cornerRoundingRes);
                }
                translate([-rem/2.0, -cornerRadius, 0]) {
                    cube([rem, 2 * cornerRadius, length]);
                }
            }
        }
    }
}

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

module pinHole() {
    translate([0,0,-1]) {
        cylinder(h=caseHeight+2,r=pinHoleRadius, $fn=pinHoleRes);
    }
}

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

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

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

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

    if (floatingPinHoles) {
        // finger pinholes
        translate([rimThick + cavityWidth + pinHoleBlockWidth/2,caseLength - offsetFromTopEdgeToFirstFinger + dividerThick/2,0]) {
            pinHole();
        }
        for (i = [1:3]) {
            translate([
                rimThick + cavityWidth,
                caseLength - offsetFromTopEdgeToFirstFinger - fingerWidth * i,
                0]) {
                pinHole();
            }
        }
        translate([rimThick+cavityWidth, caseLength-offsetFromTopEdgeToFirstFinger - fingerWidth*4 + pinHoleBlockWidth/2, 0]) {
            pinHole();
        }

        // thumb pinholes
        translate([thumbUpperRightX+1.5, thumbUpperRightY-totalFingerWellDepth-pinHoleBlockWidth/2, 0]) {
            pinHole();
        }
        translate([thumbUpperRightX+thumbWidth-1.5, thumbUpperRightY-totalFingerWellDepth-pinHoleBlockWidth/2, 0]) {
            pinHole();
        }
    }
}

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

module statusLEDHole() {
    translate([caseWidth - rimThick - 5.5, caseLength, caseHeight/2]) {
        rotate(a=[90,0,0]) {
            cylinder(h=rimThick,r=ledDomeRadius+foreignPartClearance, $fn=ledDomeRes);

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

module caseConvexHull() {
    // box with cutout for thumb cylinder
    difference() {
        translate([rimThick, rimThick,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=cavityHeight);
                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);
        }
    }
}

module thumbButtonContainer() {
    translate([thumbUpperRightX, thumbUpperRightY, 0]) {
        rotate([0,0,90]) {
            translate([0,-(thumbButtonLength+dividerThick)/2],0){
                fingerContainer(totalFingerWellDepth, thumbButtonLength);
            }
        }
    }
}

module thumbButtonWell() {
    translate([thumbUpperRightX, thumbUpperRightY, 0]) {
        rotate([0,0,90]) {
            translate([0,-(thumbButtonLength+dividerThick)/2],0){
                fingerWell(thumbNotchDepth, thumbWidth, totalFingerWellDepth, thumbButtonLength);
            }
        }
    }

    // take an extra "bite" out of the cusp to the right of the thumb button, which would otherwise get in the way
    translate([thumbUpperRightX - 10, thumbUpperRightY + thumbWellDepression -1, 0]) {
        cube([12, 5, caseHeight]);
    }
}

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

                // inner cavity
                translate([0,0,lidThick]) {
                    translate([rimThick,rimThick,0]) {
                        cube([cavityWidth,caseLength-thumbCurveRadius-rimThick+0.001,cavityHeight]);
                    }
                    translate([thumbCurveRadius,rimThick,0]) {
                        cube([cavityWidth+rimThick-thumbCurveRadius,cavityLength,cavityHeight]);
                    }
                    translate([rimThick+cavityWidth - 0.001,topYOfFingers,0]) {
                        cube([caseWidth - 2*rimThick - cavityWidth, caseLength - rimThick - topYOfFingers, cavityHeight]);
                    }
                    translate([rimThick+cavityWidth - 0.001,rimThick,0]) {
                        cube([caseWidth - 2*rimThick - cavityWidth, bottomYOfFingers - rimThick, cavityHeight]);
                    }
                }
            }

            thumbButtonContainer();

            if (floatingPinHoles) {
                // columns for finger pinholes
                translate([rimThick + cavityWidth,caseLength - offsetFromTopEdgeToFirstFinger + dividerThick/2 - pinHoleBlockWidth/2,0]) {
                    cube([pinHoleBlockWidth,pinHoleBlockWidth,caseHeight]);
                }
                for (i = [1:3]) {
                    translate([
                        rimThick + cavityWidth - pinHoleBlockWidth/2,
                        caseLength - offsetFromTopEdgeToFirstFinger - fingerWidth * i - pinHoleBlockWidth/2,
                        0]) {
                        cube([pinHoleBlockWidth,pinHoleBlockWidth,caseHeight]);
                    }
                }
                translate([rimThick+cavityWidth - pinHoleBlockWidth/2, caseLength-offsetFromTopEdgeToFirstFinger - fingerWidth*4, 0]) {
                    cube([pinHoleBlockWidth,pinHoleBlockWidth,caseHeight]);
                }

                // columns for thumb pinholes
                translate([thumbUpperRightX, thumbUpperRightY-totalFingerWellDepth-pinHoleBlockWidth, 0]) {
                    cube([pinHoleBlockWidth,pinHoleBlockWidth,caseHeight]);
                }
                translate([thumbUpperRightX+thumbWidth-pinHoleBlockWidth, thumbUpperRightY-totalFingerWellDepth-pinHoleBlockWidth, 0]) {
                    cube([pinHoleBlockWidth,pinHoleBlockWidth,caseHeight]);
                }
            }
        }

        pinHoles();

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

        thumbButtonWell();

        lightSensorWell();

        statusLEDHole();

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

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

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

            // hole for SPDT Mini Power Switch
            translate([powerSwitchOffset+dividerThick,0,(caseHeight-powerSwitchWidth-foreignPartClearance)/2]) {
                cube([powerSwitchLength+foreignPartClearance,rimThick+1,powerSwitchWidth+foreignPartClearance]);
            }
        }

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

module alignmentPeg() {
    cube([alignmentPegWidth,alignmentPegWidth,buttonLidHeight - lidThick]);
    translate([accuracy, accuracy, 0]) {
        cube([alignmentPegWidth-accuracy2,alignmentPegWidth-accuracy2,alignmentPegHeight]);
    }
}

////////////////////////////////////////////////////////////////////////////////
// main

echo("accuracy: ", accuracy);
echo("clearance: ", clearance);
echo("foreignPartClearance ", foreignPartClearance);
echo("caseWidth: ", caseWidth);
echo("caseLength: ", caseLength);
echo("caseHeight: ", caseHeight);
echo("rimThick: ", rimThick);
echo("cavityWidth: ", cavityWidth);
echo("cavityLength: ", cavityLength);
echo("cavityHeight: ", cavityHeight);
echo("pinHoleRadius: ", pinHoleRadius);
echo("pinHoleBlockWidth: ", pinHoleBlockWidth);

// body
difference() {
    basicCase();

    translate([0,0,-1]) {
        cube([caseWidth, caseLength, buttonLidHeight+1.0001]);
    }

    // screwdriver/leverage slot(s)
    translate([rimThick+cavityWidth,rimThick/2,buttonLidHeight]) {
        rotate([150,0,0]) { cube([5,10,10]); }
    }
}

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

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

buttons();

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

    // container for Arduino Nano v3.0
    translate([nanoOffset,0,caseHeight-floorThick-nanoHeightToUsbBase]) {
        // vertical wall
        translate([nanoWidth+foreignPartClearance,0,0]) {
            cube([dividerThick,nanoLength+foreignPartClearance+dividerThick,nanoHeightToUsbBase]);
        }
        // horizontal wall
        translate([0,nanoLength+foreignPartClearance,0]) {
            cube([nanoWidth+foreignPartClearance+dividerThick,dividerThick,nanoHeightToUsbBase]);
        }
    }

    // container for power switch
    translate([powerSwitchOffset,-0.1,(caseHeight-powerSwitchWidth-foreignPartClearance)/2]) {
        // right vertical wall
        cube([dividerThick,powerSwitchDepth-rimThick+foreignPartClearance+dividerThick+0.1,(caseHeight+powerSwitchWidth+foreignPartClearance)/2-floorThick]);
        // left vertical wall
        translate([dividerThick+powerSwitchLength+foreignPartClearance,0,0]) {
            cube([dividerThick,powerSwitchDepth-rimThick+foreignPartClearance+dividerThick+0.1,(caseHeight+powerSwitchWidth+foreignPartClearance)/2-floorThick]);
        }
        // base
        translate([0,0,powerSwitchWidth+foreignPartClearance]) {
            cube([powerSwitchLength+foreignPartClearance+2*dividerThick,powerSwitchDepth-rimThick+foreignPartClearance+dividerThick+0.1,(caseHeight-powerSwitchWidth-foreignPartClearance)/2-floorThick]);
        }
        // rear support
        translate([0,powerSwitchDepth-rimThick+foreignPartClearance+0.1,powerSwitchWidth+foreignPartClearance-1]) {
            cube([powerSwitchLength+foreignPartClearance+2*dividerThick,dividerThick,1]);
        }
    }

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

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

        // Surface Transducer - Small
        translate([transducerVizOffsetX,transducerVizOffsetY,-10]) {
           cube([transducerLength,transducerWidth,transducerHeight]);
        }
        // Bluetooth Modem - BlueSMiRF Silver
        translate([modemVizOffsetX,modemVizOffsetY,-10]) { cube([modemWidth,modemLength,modemHeight]); }
        // Polymer Lithium Ion Battery - 110mAh
        //translate([-40,0,-10]) { cube([12,28,5.7]); }
        // Polymer Lithium Ion Battery - 400mAh
	    translate([batteryVizOffsetX,batteryVizOffsetY,-10]) { cube([batteryWidth,batteryLength,batteryHeight]); }
        // Triple Axis Accelerometer & Gyro Breakout - MPU-6050
        translate([motionSensorVizOffsetX,motionSensorVizOffsetY,-10]) { cube([motionSensorWidth, motionSensorLength, motionSensorHeight]); }
    }
}

/*
OpenSCAD design for Typeatron Mark 1
Copyright 2013 by Joshua Shinavier

Note: 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.

Some details to double-check before printing:
  * channel for index finger's pressure sensor does not intersect thumb button structure
    Ideally, let it be entirely below, so that the sensor can lie flat on the case floor

*/

function sq(x) = x*x;

////////////////////////////////////////////////////////////////////////////////
// rendering settings

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

// hollow out buttons to save plastic/money (possible only with thick buttons)
hollowButtons = false;

simpleEdges = true;

/*
cornerRoundingRes = 100;
ledDomeRes = 100;
thumbCurveRes = 50;
//*/
//*
cornerRoundingRes = 10;
ledDomeRes = 10;
thumbCurveRes = 10;
//*/

ledRimRes = 20;
lightSensorWellRes = 20;
pinHoleRes = 10;

////////////////////////////////////////////////////////////////////////////////
// printer parameters

// the following values are for Shapeways' "Strong & Flexible Plastics" material
shapewaysAccuracy = 0.15;  // then 0.15% of longest axis

//accuracy = shapewaysAccuracy;
//accuracy = 0.178;  // for Stratasys Dimension Elite
accuracy = 0.254;  // for Stratasys uPrint

accuracyRatio = accuracy / shapewaysAccuracy;

// Additional figures from Shapeways, but adjusted for the resolution of the particular printer
// Assumed to be more or less analogous.
clearance = 0.5 * accuracyRatio;
minWallSupported = 0.7 * accuracyRatio;
minWallFree = 0.7 * accuracyRatio;
minWireSupported = 0.9 * accuracyRatio;
minWireFree = 1.0 * accuracyRatio;
minEmbossedDetail = 0.2 * accuracyRatio;
minEmbossedText = 0.5 * accuracyRatio;
minEngravedDetail = 0.2 * accuracyRatio;
minEngravedText = 0.5 * accuracyRatio;
//Min Bounding Box: x+y+z ≥ 7.5mm
//Max Bounding Box: 650x350x550mm (White) · 230x180x320mm (Black) · 150x150x150mm (Polished & Dyed)

accuracy2 = accuracy * 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 = accuracy + 0.1;

////////////////////////////////////////////////////////////////////////////////
// dimensions of foreign parts

batteryWidth = 25;
batteryLength = 36;
batteryHeight = 5.4;

chargerHeaderLength = 5.1;
chargerHeaderWidth = 2.6;

laserWidth = 10.3;
laserThick = 3.3;

ledDomeRadius = 5.0 / 2;
ledRimRadius = 5.9 / 2;
ledRimThick = 1.0;

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

modemWidth = 16.8;
modemLength = 42.8;
modemHeight = 4.1;

motionSensorWidth = 15.4;
motionSensorLength = 25.8;
motionSensorHeight = 2.5;

powerSwitchLength = 11.7;
powerSwitchWidth = 4.0;
powerSwitchDepth = 7.7;

pressureSensorWidth = 7.8;  // by direct measurement of the wide "head". The data sheet says 7.2mm
pressureSensorThick = 0.40;  // by direct measurement, without removing the adhesive backing. The data sheet says 0.35mm

tactileSwitchWellWidth = 7;
tactileSwitchHeight = 5.0;
tactileSwitchLegLength = 3.4;
tactileSwitchPressDepth = 0.25;
tactileSwitchWellDepth = 4.0;  // this is the height of the body of the tactile switch (without the push button)
tactileSwitchBaseThick = 1.5;

nanoLength = 43.2;
nanoWidth = 18.0;
nanoHeightWithoutUsb = 5.4;  // used only for visualization
nanoHeightToUsbBase = 3.4;
nanoUsbWidth = 7.7;
nanoUsbHeight = 3.8;
nanoUsbCableWidth = 9.6;
nanoUsbCableHeight = 6.6;
nanoUsbCableOffsetFromPerfboard = 3.3;

transducerWidth = 14.5;
transducerLength = 21.5;
transducerHeight = 7.9;

////////////////////////////////////////////////////////////////////////////////
// parameters of the printed part

// measurements taken from the clay model
thumbCurveRadius = 39.0;
offsetFromTopEdgeToFirstFinger = 15.0;
fingerWidth = 84 / 4;  // 21 -- 82mm was measured, but this seemed a little cramped
thumbWidth = 25;
thumbUpperRightX = 25.0;
fingerNotchDepth = 5;  // curve in which fingers rest
thumbNotchDepth = 5;   // curve in which the thumb would rest, in the absence of a button

// reasonably chosen values
caseWidth = 70;
caseLength = 120;
lidThick = 1.5;
floorThick = 1.5;

dividerThick = minWallSupported * 1.5;

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

thickestComponent = transducerHeight;
cavityHeight = thickestComponent + foreignPartClearance;
caseHeight = lidThick + floorThick + cavityHeight;

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

topYOfFingers = caseLength - offsetFromTopEdgeToFirstFinger + dividerThick/2;
bottomYOfFingers = caseLength - offsetFromTopEdgeToFirstFinger - 4*fingerWidth - dividerThick/2;

alignmentPegWidth = 2;
alignmentPegHeight = 2;

buttonHeight = 5;
buttonWidth = tactileSwitchWellWidth;
fingerButtonLength = fingerWidth - dividerThick;
thumbButtonLength = thumbWidth - dividerThick;
// clearance between button and walls (on all four sides).  We take the risk of a low tolerance for the sake of a snug fit
buttonClearance = accuracy / 2;
buttonLip = 2.5;
buttonSlack = tactileSwitchPressDepth * 2;  // minimum vertical clearance between button and floor/retainers
buttonStabilizerThick = 1.75; // vertical bars on buttons and case
buttonRetainerThick = 1.0; // horizontal bars on buttons and case
buttonRetainerGap = 0.75;
buttonRetainerHookHeight = tactileSwitchHeight - (tactileSwitchPressDepth + buttonSlack + accuracy2);

// buttons are inset by 2/3 of the depth of the finger or thumb notch, making most of the contact of the finger
// with the case rather than the button.  This should make the Typeatron easy to grip while typing.  Direct inward
// pressure must be exerted on the button in order to close the switch.
fingerButtonInsetDepth = fingerNotchDepth * 2/3;
thumbButtonInsetDepth = thumbNotchDepth * 2/3;

totalFingerWellDepth = fingerButtonInsetDepth + buttonHeight + tactileSwitchHeight;
totalThumbWellDepth = thumbButtonInsetDepth + buttonHeight + tactileSwitchHeight;

// this keeps the edges of the thumb container from protruding out of the case
thumbWellDepression = 2;

thumbUpperRightY = caseLength - thumbCurveRadius + sqrt(sq(thumbCurveRadius) - sq(thumbCurveRadius - thumbUpperRightX)) - thumbWellDepression;

pinHoleRadius = 0.7 * sqrt(accuracyRatio);
pinHoleBlockWidth = 2 * (pinHoleRadius + clearance*1.5);

// just to be on the safe side, we overestimate the necessary clearance
pressureSensorChannelHeight = clearance * 1.5;

cavityWidth = caseWidth - rimThick - totalFingerWellDepth - tactileSwitchBaseThick;
cavityLength = caseLength - 2*rimThick;

buttonLidHeight = (caseHeight-buttonWidth)/2;

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

////////////////////////////////////////////////////////////////////////////////
// modules

// creates a button of the given length.  Clearance on all sides of the button is subtracted.
// the long axis of the button is x.  y is side-to-side.  z is up and down (as you press the button).
module button(length) {
    innerRetainerWellWidth = (length - tactileSwitchWellWidth - foreignPartClearance)/2 - buttonStabilizerThick;
    retainerLength = innerRetainerWellWidth - buttonClearance - buttonRetainerGap;

    // this measurement is crucial
    stabilizerHeight
        = 2 * buttonRetainerThick
        + (tactileSwitchHeight - tactileSwitchWellDepth + foreignPartClearance)
        + pressureSensorThick
        + 0.15;

    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,buttonHeight-buttonWidth/2]);
            }

            // stabilizer bars
            translate([buttonClearance,buttonClearance,buttonHeight]) {
                cube([buttonStabilizerThick, buttonWidth-2*buttonClearance, stabilizerHeight]);
            }
            translate([length-buttonStabilizerThick-buttonClearance,buttonClearance,buttonHeight]) {
                cube([buttonStabilizerThick, buttonWidth-2*buttonClearance, stabilizerHeight]);
            }

            // retainer bars
            translate([buttonClearance,buttonClearance,buttonHeight+stabilizerHeight-buttonRetainerThick]) {
                cube([retainerLength,buttonWidth-2*buttonClearance, buttonRetainerThick]);
            }
            translate([length-buttonClearance-retainerLength,buttonClearance,buttonHeight+stabilizerHeight-buttonRetainerThick]) {
                cube([retainerLength,buttonWidth-2*buttonClearance, buttonRetainerThick]);
            }
        }

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

// a complex well which houses a finger button and prevents it from detaching from the Typeatron
module fingerButtonWell(length) {
    outerRetainerWellWidth = buttonStabilizerThick + buttonRetainerGap;
    innerRetainerWellWidth = (length - tactileSwitchWellWidth - foreignPartClearance)/2 - buttonStabilizerThick;

    translate([-length/2,-buttonWidth/2,tactileSwitchWellDepth]) {
        // rectangular well for button body
        cube([length,buttonWidth,buttonHeight+10]);  // clearance of 10 is more than enough

        // retainer wells
        translate([0,0,-tactileSwitchWellDepth]) {
            cube([outerRetainerWellWidth, buttonWidth, tactileSwitchWellDepth]);

            cube([innerRetainerWellWidth, buttonWidth, tactileSwitchWellDepth - buttonRetainerThick]);

            translate([length-outerRetainerWellWidth,0,0]) {
                cube([outerRetainerWellWidth, buttonWidth, tactileSwitchWellDepth]);
            }

            translate([length-innerRetainerWellWidth,0,0]) {
                cube([innerRetainerWellWidth, buttonWidth, tactileSwitchWellDepth - buttonRetainerThick]);
            }
		}
    }
}

// well for four-legged through-hole tactile push button switch with origin at the center of the base of the switch
// legs of the switch run along the x axis
module tactileSwitchWell(length, depth) {
    w = tactileSwitchWellWidth + foreignPartClearance;
    l = 1.5; // width of leg holes

    // the pressure sensor channels extend slightly past the well for the tactile switch
    fsrChannelDepth = tactileSwitchLegLength + tactileSwitchHeight;

    translate([-w/2, -w/2, 0]) {

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

        translate([0,0,-tactileSwitchLegLength]) {
            // channels for legs/wires of switch
            translate([0,0,0]) {
                cube([l,l,depth+tactileSwitchLegLength]);
            }
            translate([w-l,0, 0]) {
                cube([l,l,depth+tactileSwitchLegLength]);
            }
            translate([0,w-l, 0]) {
                cube([l,l,depth+tactileSwitchLegLength]);
            }
            translate([w-l,w-l, 0]) {
                cube([l,l,depth+tactileSwitchLegLength]);
            }
        }
    }

    // channels for pressure sensor
    // note: the slight 0.001 overlap prevents a confusing "film" in the STL
    translate([-(pressureSensorWidth+foreignPartClearance)/2,-w/2,-tactileSwitchLegLength]) {
        translate([0, -pressureSensorChannelHeight, 0]) {
            cube([pressureSensorWidth+foreignPartClearance, pressureSensorChannelHeight+0.001, fsrChannelDepth]);
        }
        translate([0, w-0.001, 0]) {
            cube([pressureSensorWidth+foreignPartClearance, pressureSensorChannelHeight, fsrChannelDepth]);
        }
    }

    // rim to allow the pressure sensor to wrap around
    translate([-(pressureSensorWidth+foreignPartClearance)/2, -w/2-pressureSensorChannelHeight, depth]) {
        cube([pressureSensorWidth+foreignPartClearance, w+2*pressureSensorChannelHeight, tactileSwitchHeight - tactileSwitchWellDepth]);
    }
}

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

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

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

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

    // well and channels for finger button, tactile switch, wires, and pressure sensor
    translate([-totalWellDepth,0,caseHeight/2]) {
        rotate([0,90,0]) { rotate([0,0,90]) {
            fingerButtonWell(buttonLength);
            tactileSwitchWell(buttonLength, tactileSwitchWellDepth);
        }}
    }
}

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

module buttons() {
    fingerButtonsPrintable = [caseWidth + 15,bottomYOfFingers+dividerThick,10];
    fingerButtonsInSitu = [caseWidth - fingerButtonInsetDepth,bottomYOfFingers + dividerThick,buttonLidHeight + buttonWidth];
    thumbButtonPrintable = [thumbUpperRightX+thumbButtonLength+dividerThick,caseLength+12,10];
    thumbButtonInSitu = [thumbUpperRightX + thumbWidth - dividerThick/2,thumbUpperRightY - thumbButtonInsetDepth, buttonLidHeight + buttonWidth];

    // TODO: simplify this ridiculous if..then
    if (buttonsInSitu) {
        // Note: the buttons are oriented so as to make the longest surfaces the smoothest
        translate(fingerButtonsInSitu) {
            for (i = [0:3]) {
                translate([0,i*fingerWidth,0]) { rotate([90,180,-90]) {
                    button(fingerButtonLength);
                }}
            }
        }
        translate(thumbButtonInSitu) {
            rotate([90,180,0]) {
                button(thumbButtonLength);
            }
        }
    } else {
        // Note: the buttons are oriented so as to make the longest surfaces the smoothest
        translate(fingerButtonsPrintable) {
            for (i = [0:3]) {
                translate([0,i*fingerWidth,0]) { rotate([90,180,-90]) {
                    button(fingerButtonLength);
                }}
            }
        }
        translate(thumbButtonPrintable) {
            rotate([90,180,0]) {
                button(thumbButtonLength);
            }
        }
    }
}

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

// for the rounded flat edges of the case
module roundedEdge(length, height, cornerRadius) {
    rem = height - 2*cornerRadius;

    if (simpleEdges) {
        cylinder(r=cornerRadius, h=length, $fn=cornerRoundingRes);
    } else {
        intersection() {
            translate([-height/2, 0, 0]) {
                cube([height, cornerRadius, length]);
            }
            union() {
                translate([-rem/2.0, 0, 0]) {
                    cylinder(h=length, r=cornerRadius, $fn=cornerRoundingRes);
                }
                translate([rem/2.0, 0, 0]) {
                    cylinder(h=length, r=cornerRadius, $fn=cornerRoundingRes);
                }
                translate([-rem/2.0, -cornerRadius, 0]) {
                    cube([rem, 2 * cornerRadius, length]);
                }
            }
        }
    }
}

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

module pinHole() {
    translate([0,0,-1]) {
        cylinder(h=caseHeight+2,r=pinHoleRadius, $fn=pinHoleRes);
    }
}

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

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

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

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

    // finger pinholes
    translate([rimThick + cavityWidth + pinHoleBlockWidth/2,caseLength - offsetFromTopEdgeToFirstFinger + dividerThick/2,0]) {
        pinHole();
    }
    for (i = [1:3]) {
        translate([
            rimThick + cavityWidth,
            caseLength - offsetFromTopEdgeToFirstFinger - fingerWidth * i,
            0]) {
            pinHole();
        }
    }
    translate([rimThick+cavityWidth, caseLength-offsetFromTopEdgeToFirstFinger - fingerWidth*4 + pinHoleBlockWidth/2, 0]) {
        pinHole();
    }

    // thumb pinholes
    translate([thumbUpperRightX+1.5, thumbUpperRightY-totalFingerWellDepth-pinHoleBlockWidth/2, 0]) {
        pinHole();
    }
    translate([thumbUpperRightX+thumbWidth-1.5, thumbUpperRightY-totalFingerWellDepth-pinHoleBlockWidth/2, 0]) {
        pinHole();
    }
}

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

module statusLEDHole() {
    translate([caseWidth - rimThick - 5.5, caseLength, caseHeight/2]) {
        rotate(a=[90,0,0]) {
            cylinder(h=rimThick,r=ledDomeRadius+foreignPartClearance, $fn=ledDomeRes);

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

module caseConvexHull() {
    // box with cutout for thumb cylinder
    difference() {
        translate([rimThick, rimThick,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=cavityHeight);
                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);
        }
    }
}

module thumbButtonContainer() {
    translate([thumbUpperRightX, thumbUpperRightY, 0]) {
        rotate([0,0,90]) {
            translate([0,-(thumbButtonLength+dividerThick)/2],0){
                fingerContainer(totalFingerWellDepth, thumbButtonLength);
            }
        }
    }
}

module thumbButtonWell() {
    translate([thumbUpperRightX, thumbUpperRightY, 0]) {
        rotate([0,0,90]) {
            translate([0,-(thumbButtonLength+dividerThick)/2],0){
                fingerWell(thumbNotchDepth, thumbWidth, totalFingerWellDepth, thumbButtonLength);
            }
        }
    }

    // take an extra "bite" out of the cusp to the right of the thumb button, which would otherwise get in the way
    translate([thumbUpperRightX - 10, thumbUpperRightY + thumbWellDepression -1, 0]) {
        cube([12, 5, caseHeight]);
    }
}

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

                // inner cavity
                translate([0,0,lidThick]) {
                    translate([rimThick,rimThick,0]) {
                        cube([cavityWidth,caseLength-thumbCurveRadius-rimThick+0.001,cavityHeight]);
                    }
                    translate([thumbCurveRadius,rimThick,0]) {
                        cube([cavityWidth+rimThick-thumbCurveRadius,cavityLength,cavityHeight]);
                    }
                    translate([rimThick+cavityWidth - 0.001,topYOfFingers,0]) {
                        cube([caseWidth - 2*rimThick - cavityWidth, caseLength - rimThick - topYOfFingers, cavityHeight]);
                    }
                    translate([rimThick+cavityWidth - 0.001,rimThick,0]) {
                        cube([caseWidth - 2*rimThick - cavityWidth, bottomYOfFingers - rimThick, cavityHeight]);
                    }
                }
            }

            thumbButtonContainer();

            // blocks for finger pinholes
            translate([rimThick + cavityWidth,caseLength - offsetFromTopEdgeToFirstFinger + dividerThick/2 - pinHoleBlockWidth/2,0]) {
                cube([pinHoleBlockWidth,pinHoleBlockWidth,caseHeight]);
            }
            for (i = [1:3]) {
                translate([
                    rimThick + cavityWidth - pinHoleBlockWidth/2,
                    caseLength - offsetFromTopEdgeToFirstFinger - fingerWidth * i - pinHoleBlockWidth/2,
                    0]) {
                    cube([pinHoleBlockWidth,pinHoleBlockWidth,caseHeight]);
                }
            }
            translate([rimThick+cavityWidth - pinHoleBlockWidth/2, caseLength-offsetFromTopEdgeToFirstFinger - fingerWidth*4, 0]) {
                cube([pinHoleBlockWidth,pinHoleBlockWidth,caseHeight]);
            }

            // blocks for thumb pinholes
            translate([thumbUpperRightX, thumbUpperRightY-totalFingerWellDepth-pinHoleBlockWidth, 0]) {
                cube([pinHoleBlockWidth,pinHoleBlockWidth,caseHeight]);
            }
            translate([thumbUpperRightX+thumbWidth-pinHoleBlockWidth, thumbUpperRightY-totalFingerWellDepth-pinHoleBlockWidth, 0]) {
                cube([pinHoleBlockWidth,pinHoleBlockWidth,caseHeight]);
            }
        }

        pinHoles();

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

        thumbButtonWell();

        lightSensorWell();

        statusLEDHole();

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

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

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

            // hole for SPDT Mini Power Switch
            translate([powerSwitchOffset+dividerThick,0,(caseHeight-powerSwitchWidth-foreignPartClearance)/2]) {
                cube([powerSwitchLength+foreignPartClearance,rimThick+1,powerSwitchWidth+foreignPartClearance]);
            }
        }

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

module alignmentPeg() {
    cube([alignmentPegWidth,alignmentPegWidth,buttonLidHeight - lidThick]);
    translate([accuracy, accuracy, 0]) {
        cube([alignmentPegWidth-accuracy2,alignmentPegWidth-accuracy2,alignmentPegHeight]);
    }
}

////////////////////////////////////////////////////////////////////////////////
// main

echo("accuracy: ", accuracy);
echo("clearance: ", clearance);
echo("foreignPartClearance ", foreignPartClearance);
echo("caseWidth: ", caseWidth);
echo("caseLength: ", caseLength);
echo("caseHeight: ", caseHeight);
echo("rimThick: ", rimThick);
echo("cavityWidth: ", cavityWidth);
echo("cavityLength: ", cavityLength);
echo("cavityHeight: ", cavityHeight);
echo("pinHoleRadius: ", pinHoleRadius);
echo("pinHoleBlockWidth: ", pinHoleBlockWidth);

// body
difference() {
    basicCase();

    translate([0,0,-1]) {
        cube([caseWidth, caseLength, buttonLidHeight+1.0001]);
    }

    // screwdriver/leverage slot(s)
    translate([rimThick+cavityWidth,rimThick/2,buttonLidHeight]) {
        rotate([150,0,0]) { cube([5,10,10]); }
    }
}

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

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

buttons();

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

    // container for Arduino Nano v3.0
    translate([nanoOffset,0,caseHeight-floorThick-nanoHeightToUsbBase]) {
        // vertical wall
        translate([nanoWidth+foreignPartClearance,0,0]) {
            cube([dividerThick,nanoLength+foreignPartClearance+dividerThick,nanoHeightToUsbBase]);
        }
        // horizontal wall
        translate([0,nanoLength+foreignPartClearance,0]) {
            cube([nanoWidth+foreignPartClearance+dividerThick,dividerThick,nanoHeightToUsbBase]);
        }
    }

    // container for power switch
    translate([powerSwitchOffset,-0.1,(caseHeight-powerSwitchWidth-foreignPartClearance)/2]) {
        // right vertical wall
        cube([dividerThick,powerSwitchDepth-rimThick+foreignPartClearance+dividerThick+0.1,(caseHeight+powerSwitchWidth+foreignPartClearance)/2-floorThick]);
        // left vertical wall
        translate([dividerThick+powerSwitchLength+foreignPartClearance,0,0]) {
            cube([dividerThick,powerSwitchDepth-rimThick+foreignPartClearance+dividerThick+0.1,(caseHeight+powerSwitchWidth+foreignPartClearance)/2-floorThick]);
        }
        // base
        translate([0,0,powerSwitchWidth+foreignPartClearance]) {
            cube([powerSwitchLength+foreignPartClearance+2*dividerThick,powerSwitchDepth-rimThick+foreignPartClearance+dividerThick+0.1,(caseHeight-powerSwitchWidth-foreignPartClearance)/2-floorThick]);
        }
        // rear support
        translate([0,powerSwitchDepth-rimThick+foreignPartClearance+0.1,powerSwitchWidth+foreignPartClearance-1]) {
            cube([powerSwitchLength+foreignPartClearance+2*dividerThick,dividerThick,1]);
        }
    }

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

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

        // Surface Transducer - Small
        translate([transducerVizOffsetX,transducerVizOffsetY,-10]) {
           cube([transducerLength,transducerWidth,transducerHeight]);
        }
        // Bluetooth Modem - BlueSMiRF Silver
        translate([modemVizOffsetX,modemVizOffsetY,-10]) { cube([modemWidth,modemLength,modemHeight]); }
        // Polymer Lithium Ion Battery - 110mAh
        //translate([-40,0,-10]) { cube([12,28,5.7]); }
        // Polymer Lithium Ion Battery - 400mAh
	    translate([batteryVizOffsetX,batteryVizOffsetY,-10]) { cube([batteryWidth,batteryLength,batteryHeight]); }
        // Triple Axis Accelerometer & Gyro Breakout - MPU-6050
        translate([motionSensorVizOffsetX,motionSensorVizOffsetY,-10]) { cube([motionSensorWidth, motionSensorLength, motionSensorHeight]); }
    }
}


cut-in beneath side mounting wing increased

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

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

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

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

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

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

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

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

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

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

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

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


Use values for slide switch FH35Q

Use values for slide switch FH35Q

/*
    Vitamin: SlideSwitch
    Model of various Slide Switches

    Derived from: http://www.maplin.co.uk/p/double-pole-sub-miniature-fh35q
        see also: http://www.edutronic.co.uk/examples/catalog.htm

    Authors:
        Jamie Osborne (@jmeosbn)

    Local Frame:
        Point of origin is through the centre of the bottom hole

    Parameters: none

    Returns:
        Model of a slide switch with mounting holes
*/

module SlideSwitch()
{

    plate_w =  7.0;
    plate_l = 23.0;
    plate_h =  1.0;         // FIXME: ASSUMED VALUE
    plate_r =  1.0;         // FIXME: ASSUMED VALUE
    centres = 19.0;         // distance between mounting holes
    holedia =  2.5;         // clearance for M2

    tang_w  =  4.0;         // FIXME: ASSUMED VALUE
    tang_d  =  4.0;         // FIXME: ASSUMED VALUE
    tang_h  =  7.5;
    tthrow  =  3.5;         // amount tang moves to actuate switch

    body_w  = 15.0;
    body_d  =  7.0;
    body_h  =  7.5;

    tags_w  =  1.5;
    tags_h  =  3.0;
    tags_d  =  1.0;         // FIXME: ASSUMED VALUE
    tags_x  =  4.0;         // FIXME: ASSUMED VALUE
    tags_y  =  4.0;         // FIXME: ASSUMED VALUE

    tags_a  =    2;         // amount of tags across
    tags_b  =    3;         // amount of tags down

    // Mounting Plate

    color(Grey60)
    translate([0, centres/2, 0])    // move origin to bottom hole
    linear_extrude(plate_h)
    {
        difference()
        {
            // mounting plate
            // TODO: Show rounded corners?
            roundedSquare(size=[plate_w, plate_l], radius=plate_r, center=true);

            // mounting holes
            translate([0, centres/2, 0])
                circle(d=holedia);
            translate([0, -centres/2, 0])
                circle(d=holedia);

            // tang hole
            square(size=[tang_w, tang_d + tthrow], center=true);
        }
    }

    // Switch Tang

    color(Grey50)
    translate([0, centres/2 + tthrow/2, 0])
    linear_extrude(tang_h)
    {
        square(size=[tang_w, tang_d], center=true);
    }

    // Switch Body

    color(Grey50)
    translate([0, centres/2, -body_h])
    linear_extrude(body_h)
    {
        square(size=[body_d, body_w], center=true);
    }

    // Switch Tags

    color("silver")
    translate([-tags_x * (tags_a-1)/2, centres/2-tags_y*(tags_b-1)/2, -body_h - tags_h])
    linear_extrude(tags_h)
    {
        for (x=[0 : tags_a-1], y=[0 : tags_b-1]) {
            translate([x * tags_x, y * tags_y, 0]) {
                square(size=[tags_d, tags_w], center=true);
            }
        }
    }
}

// Example Usage
// SlideSwitch();

/*
    Vitamin: SlideSwitch
    Model of various Slide Switches

    Derived from: http://www.maplin.co.uk/p/double-pole-miniature-fh36p

    Authors:
        Jamie Osborne (@jmeosbn)

    Local Frame:
        Point of origin is through the centre of the bottom hole

    Parameters: none

    Returns:
        Model of a slide switch with mounting holes
*/

module SlideSwitch()
{

    plate_w = 12.5;
    plate_l = 35.0;
    plate_h =  1.0;         // FIXME: ASSUMED VALUE
    centres = 28.0;         // distance between mounting holes

    tang_w  =  5.0;         // FIXME: ASSUMED VALUE
    tang_d  =  5.0;         // FIXME: ASSUMED VALUE
    tang_h  = 10.0;
    tthrow  =  5.5;         // amount tang moves to actuate switch

    body_w  = 24.0;         // FIXME: ASSUMED VALUE
    body_d  = 12.5;         // FIXME: ASSUMED VALUE
    body_h  =  8.0;         // FIXME: ASSUMED VALUE

    tags_w  =  2.5;
    tags_h  =  5.0;
    tags_d  =  1.0;         // FIXME: ASSUMED VALUE
    tags_x  =  8.0;         // FIXME: ASSUMED VALUE
    tags_y  =  8.0;         // FIXME: ASSUMED VALUE

    tags_a  =    2;         // switch poles
    tags_b  =    3;         // switch throw

    // Mounting Plate

    color(Grey60)
    translate([0, centres/2, 0])    // move origin to bottom hole
    linear_extrude(plate_h)
    {
        difference()
        {
            // mounting plate
            // TODO: Show rounded corners?
            roundedSquare(size=[plate_w, plate_l], radius=2, center=true);

            // mounting holes (M3 tapped)
            translate([0, centres/2, 0])
                circle(d=3);
            translate([0, -centres/2, 0])
                circle(d=3);

            // tang hole
            square(size=[tang_w, tang_d + tthrow], center=true);
        }
    }

    // Switch Tang

    color(Grey50)
    translate([0, centres/2 + tthrow/2, 0])
    linear_extrude(tang_h)
    {
        square(size=[tang_w, tang_d], center=true);
    }

    // Switch Body

    color(Grey50)
    translate([0, centres/2, -body_h])
    linear_extrude(body_h)
    {
        square(size=[body_d, body_w], center=true);
    }

    // Switch Tags

    color("silver")
    translate([-tags_x * (tags_a-1)/2, centres/2-tags_y*(tags_b-1)/2, -body_h - tags_h])
    linear_extrude(tags_h)
    {
        for (x=[0 : tags_a-1], y=[0 : tags_b-1]) {
            translate([x * tags_x, y * tags_y, 0]) {
                square(size=[tags_d, tags_w], center=true);
            }
        }
    }
}

// Example Usage
// SlideSwitch();