Tweaks for fit.

Tweaks for fit.

// fishbox.scad
//  - Enclosure design for zebrafish Skinner box
//
//  Author: Mark Liffiton
//  Date: Oct, 2014 - Nov, 2015
//
//  Units are mm

// Set thickness to account for thickness of material
// PLUS needed clearance for cuts into which material should fit.

// 0.100" Acrylic:
acrylic_thickness = 2.4;
    // 2.4mm = 0.094" (looks good based on cut test piece for acrylic slotting into itself)

// 1/8" Hardboard:
hardboard_thickness = 3.5;
    // 2.8mm = 0.110"
    //   * looked good based on cut test piece for hardboard slotting into hardboard
    //   * 2015-03-06: Hanging supports didn't fit into cutouts (or did only with lots of force)
    // 3.1mm for some breathing room
    //   * 2016-02-26: Still tighter than is ideal for sliding parts together.
    // 3.3mm for just a smidge more...
    //   * 2016-06-17: 3.3 is *still* too tight in a few connections!
    //                 Most are fine, but we need to handle the variability
    //                 in material thickness, so...
    // 3.5mm.  (Final?)

// Assign materials: window/back wall and everything else
//window_thickness = acrylic_thickness;
window_thickness = hardboard_thickness;
thickness = hardboard_thickness;

// Interior box dimensions
// The variables are used below to control placement of **centerpoints** centerpoints of walls,
// so material thickness is added to account for that.
width = 290 + thickness;    // x = 29cm wall-to-wall
depth = 348 + thickness;    // y = 34.8cm to accomodate 34.8cm tank length
i_height = 195 + thickness; // z = 19.5cm to accomodate 16cm tank height plus wires for shock
                            // (i_height because 'height' is used for height of entire box)

// amount faces extend past each other at corners
overhang = 15;

// amount horizontal support tabs extend past side supports (technically, it will go this distance minus thickness/2)
outset = thickness+2;

// tank base plate
base_height = overhang/2;

// create height var to account for raised base
height = i_height + base_height;

// height of insert for rear tank support
insert_height = height/6;

// hanging support thickness and drop (distance it "sits down" after in place)
support_w = 10;
support_drop = support_w/2;

// useful tank measurements
tank_width = 70;
tank_foot_offset_left = 105;
tank_foot_offset_right = 101;

// x-position of mask piece inside box
mask_loc = tank_width + thickness;  // center-to-center
// dimensions for mask view opening
view_opening_left = 65;
view_opening_right = 78;
view_opening_width = depth-thickness - view_opening_left - view_opening_right;
view_opening_bottom = 24;
view_opening_height = 96;

// camera mounting measurements
camera_hole_diameter = 1.7;       // bolts are m2; 2mm nominal, 1.9mm actual, and a bit less for the beam width
camera_hole_horiz_offset = 21/2;  // 21mm between mounting holes, horizontally (so half that away from the camera center
camera_hole_vert_offset = 12.5;   // 12.5mm between mounting holes, vertically (one set on the camera center, one set up that much from center)

// position of rpi and related parts
rpi_ypos = depth/2 + 60;

// for slight offsets/tweaks
epsilon = 1;

// set precision of all arcs/spheres/cylinders/etc.
$fn = 50;

// vars to control projection for DXF export (see makefile)
if (DXF_TOP) {
    projection() top_cover(spacing=1);
} else if (DXF_BOTTOM) {
    projection() tank_base();
} else if (DXF_SIDE1) {
    projection() rotate(a=[90,0,0]) side(y=0);
} else if (DXF_SIDE2) {
    projection() rotate(a=[90,0,0]) side(y=depth);
} else if (DXF_END1) {
    projection() rotate(a=[0,90,0]) vert_face(x=0);
} else if (DXF_END2) {
    projection() rotate(a=[0,90,0]) vert_face(x=width);
} else if (DXF_MASK) {
    projection() rotate(a=[0,90,0]) mask(x=mask_loc);
} else if (DXF_TANK_SUPPORT) {
    projection() tank_support_layer1();
    projection() translate([tank_width+thickness,0,0]) tank_support_layer2();
} else if (DXF_RPI_SUPPORT) {
    projection() rotate(a=[90,0,0]) rpi_support();
} else if (DXF_REAR_SUPPORT) {
    projection() rotate(a=[90,0,0]) rear_support(1);
    translate([overhang*3,0,0])
    projection() rotate(a=[90,0,0]) rear_support(2);
    translate([overhang*6,0,0])
    projection() rotate(a=[90,0,0]) rear_support(3);
}
else {
    // 3D model
    // components to include (comment out unwanted)
    vert_face(x=0);      // "near face, transparent for monitor
    vert_face(x=width);  // "far" face, with camera, rpi, etc.
    mask(x=mask_loc);
    side(y=0);
    side(y=depth);
    tank_base();
    tank_support();
    rpi_support(x=width, y=rpi_ypos-10, z=height/2);
    mock_rpi(x=width, y=rpi_ypos, z=height/2);
    top_cover();
    rear_support();
}

//////////////////////////////////////////////////////////////////
// Module definitions
//

module mask(x, tab_extra=0) {
    difference() {
        vert_face_base(x, top_offset=thickness, bottom_offset=base_height-thickness);
        side(y=0);
        side(y=depth);
        translate([x-thickness/2, -overhang, 0]) {
            translate([0, 0, 0])
                rounded_truncation(width=overhang-thickness/2, up=false);
            translate([0, depth+overhang+thickness/2, 0])
                rounded_truncation(width=overhang-thickness/2, up=false);
        }
        // opening for tank view
        translate([0,view_opening_left+thickness/2, view_opening_bottom+base_height+thickness/2])
            cube([width, view_opening_width, view_opening_height]);
        // bottom opening for wires
        translate([0, depth-thickness/2, base_height+thickness])
        rotate(a=[0,90,0])
            cylinder(r=10, h=width);
        // top openings for wires (electrical stim)
        translate([0, depth-15, height-thickness/2])
        rotate(a=[0,90,0])
            cylinder(r=5, h=width);
        translate([0, 15, height-thickness/2])
        rotate(a=[0,90,0])
            cylinder(r=5, h=width);
        // tabs for rigidity w/ bottom panel
        cutouts(4,depth-thickness,outset*2,rot=[-90,0,90],trans=[x+thickness/2,depth/2,base_height+thickness/2], extra=tab_extra);
    }
}

module vert_face(x=0) {
    difference() {
        if (x == 0) {
            vert_face_base(0, window_thickness);
        }
        else {
            vert_face_base(x);
        }
        camera_opening();
        // round=false so we get the full width of the piece in the cutout, not reduced because we hit the rounded corner
        rpi_support(x=width+thickness*2, y=rpi_ypos-10, z=height/2+support_drop, round=false);
        translate([thickness+epsilon,0,-insert_height/2])
            rear_support();
        wire_opening();
        power_jack_cutout();
        // CAUTION: OpenSCAD won't let me make a projection of vert_face(x=0)
        //   if tank_base is put after side(y=depth) here... bug.  :(
        tank_base();
        top_cover();
        side(y=0);
        side(y=depth);
        translate([x-thickness/2, -overhang, 0]) {
            translate([0, depth+overhang+thickness/2, 0])
                rounded_truncation(width=overhang-thickness/2, up=false);
            translate([0, 0, 0])
                rounded_truncation(width=overhang-thickness/2, up=false);
        }
    }
}

module vert_face_base(x, face_thickness=thickness, top_offset=0, bottom_offset=0) {
    translate([x-face_thickness/2, -overhang, bottom_offset])
        cube([face_thickness,depth+overhang*2,height-top_offset-bottom_offset]);
}

module rounded_truncation(width, up, rot=[0,0,0]) {
    truncation_height=height/4;
    rotate(a=rot)
    difference() {
        translate([-50,-epsilon,0])
            cube([100, width+2*epsilon, truncation_height]);
        if (up) {
            translate([-50, width/2, 0])
            rotate(a=[0, 90, 0])
                cylinder(d=width, h=100);
        }
        else {
            translate([-50, width/2, truncation_height])
            rotate(a=[0, 90, 0])
                cylinder(d=width, h=100);
        }
    }
}

module camera_opening() {
    translate([width, depth/2, height/2])
    union() {
        cube([thickness*2,10,10], center=true);
        translate([0, camera_hole_horiz_offset, 0])
        rotate(a=[0, 90, 0])
            cylinder(d=camera_hole_diameter, h=100, center=true);
        translate([0, -camera_hole_horiz_offset, 0])
        rotate(a=[0, 90, 0])
            cylinder(d=camera_hole_diameter, h=100, center=true);
        translate([0, camera_hole_horiz_offset, camera_hole_vert_offset])
        rotate(a=[0, 90, 0])
            cylinder(d=camera_hole_diameter, h=100, center=true);
        translate([0, -camera_hole_horiz_offset, camera_hole_vert_offset])
        rotate(a=[0, 90, 0])
            cylinder(d=camera_hole_diameter, h=100, center=true);
    }
}

module rpi_support(x, y, z, round=true) {
    hanging_support(x, y, z, out=4, up=85, round=round);
}

module hanging_support(x, y, z, out, up, round=true) {
    inner_w = out+thickness;
    inner_h = up;
    color("Red", alpha=0.5)
    translate([x,y,z])
    // push inner opening up against vert face
    translate([out/2,0,0])
    difference() {
        if (round) {
            rounded_rect(inner_w+support_w*2, thickness, inner_h+support_w*2, center=true, radius=support_w);
        }
        else {
            cube([inner_w+support_w*2, thickness, inner_h+support_w*2], center=true);
        }
        // cutout for object
        cube([inner_w, thickness*2, inner_h], center=true);
        // cutout for passing through vertface
        translate([-(inner_w/2+support_w/2),0,-support_drop/2])
            cube([support_w+2*epsilon, thickness*2, inner_h-support_drop], center=true);
        // bottom cutout
        translate([-out/2,0,-(inner_h/2+support_w-support_drop/2)])
            cube([thickness, thickness*2, support_drop], center=true);
    }
}

// rounded corners in x,z directions
module rounded_rect(x,y,z, center, radius) {
    if (center) {
        rounded_rect_centered(x,y,z, radius);
    }
    else {
        translate([x/2, y/2, z/2])
            rounded_rect_centered(x,y,z, radius);
    }
}

module rounded_rect_centered(x,y,z, radius) {
    difference() {
        cube([x,y,z], center=true);

        translate([x/2-radius, 50, z/2-radius])
        rotate(a=[90,0,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100);
        }
        translate([-x/2+radius, 50, z/2-radius])
        rotate(a=[90,-90,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100);
        }
        translate([x/2-radius, 50, -z/2+radius])
        rotate(a=[90,90,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100);
        }
        translate([-x/2+radius, 50, -z/2+radius])
        rotate(a=[90,180,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100);
        }
    }
}

module top_cover(spacing=0) {
    // width1 = main cover ; width2 = tank section cover
    width1 = width - mask_loc - thickness/2;
    width2 = mask_loc - thickness/2;
    color("Grey", alpha=0.5)
    union() {
        // back piece (over empty space)
        // spacing added to make separate pieces for laser cutting
        translate([spacing,-outset,height-thickness])
        difference() {
            translate([mask_loc,0,0])
                cube([width1+thickness+outset,depth+outset*2,thickness]);
            cutouts(4,width1,outset,rot=0,trans=[width1/2+mask_loc,0,0]);
            cutouts(4,width1,outset,rot=180,trans=[width1/2+mask_loc,depth+outset*2,0]);
            cutouts(5,depth-thickness,outset,rot=90,trans=[width+outset,depth/2+outset,0]);
        }
        // front piece (over tank)
        translate([-outset-thickness/2,-overhang,height-thickness])
        difference() {
            cube([width2+thickness+outset,depth+overhang*2,thickness]);
            cutouts(5,depth-thickness,outset,rot=90,trans=[outset+thickness/2,depth/2+overhang,0]);
        }
    }
}

module tank_base() {
    difference() {
        translate([-outset,thickness/2,base_height])
            difference() {
                cube([width+outset*2, depth-thickness, thickness]);
                cutouts(5,depth,outset,rot=-90,trans=[0,(depth-thickness)/2,0]);
                cutouts(5,depth,outset,rot=90,trans=[width+outset*2,(depth-thickness)/2,0]);
            }
        mask(x=mask_loc, tab_extra=5);
    }
}

module tank_support() {
    translate([thickness/2, thickness/2, base_height+thickness])
    union() {
        tank_support_layer1();
        color("Grey", alpha=0.5)
            tank_support_layer2();
    }
}
// Two strips of hardboard on either side of LED strip
module tank_support_layer1() {
    // front bottom strip, w/ space for wires
    translate([0, 0, 0])
        cube([tank_width/3, depth-thickness, thickness]);
    // back bottom strip, full-depth
    translate([2*tank_width/3, 0, 0])
        cube([tank_width/3, depth-thickness-20, thickness]);
}
// Clear acrylic over LED strip
module tank_support_layer2() {
    translate([0, 0, thickness])
    difference() {
        cube([tank_width, depth-thickness, thickness]);
        translate([tank_width/2, depth-tank_width/2, 0])
            cylinder(d=25, h=100, center=true);
    }
}

module cutouts(num, width, outset, rot, trans, extra=0) {
    // "extra" is *removed* from width of cubes
    // so that resulting tabs (after differencing this) are *wider*
    w = width / ((num-1) * 2);
    translate(trans)
    rotate([0,0,1], a=rot)
    for (i = [0 : num-1]) {
        translate([-width/2 - w/2 + i*2*w + extra/2, -thickness/2, -thickness/2])
            cube([w-extra, outset+thickness, thickness*2]);
    }
}

module wire_opening() {
    translate([width, rpi_ypos+20, overhang+20])
    scale([1,2,1])
    rotate(a=[0,90,0])
        cylinder(d=12, h=thickness+epsilon, center=true);
}

module power_jack_cutout() {
    translate([width, depth-40, overhang+30])
    rotate(a=[0,90,0])
        cylinder(d=8, h=thickness+epsilon, center=true);
}

module side(y=0) {
    difference() {
        side_base(y);
        translate([0,0,height/2])
        scale([1,1,0.5])
            vert_face_base(x=mask_loc);
        translate([0,0,height/2])
        scale([1,1,0.5])
            vert_face_base(0, window_thickness);
        translate([0,0,height/2])
        scale([1,1,0.5])
            vert_face_base(width);
        top_cover();
    }
}

module side_base(y) {
    translate([-overhang,y-thickness/2,0])
    difference() {
        cube([width+overhang*2, thickness, height]);
        translate([width+overhang+thickness/2, 0, 3/4*height])
            rounded_truncation(width=overhang-thickness/2, up=true, rot=[0,0,-90]);
        translate([0, 0, 3/4*height])
            rounded_truncation(width=overhang-window_thickness/2, up=true, rot=[0,0,-90]);
    }
}

module rear_support(num_high=1) {
    support_height = num_high*height;
    ypos = depth/2-thickness/2;
    color("Orange")
    translate([width+thickness/2, ypos, 0])
    union() {
        // entering-tank part
        translate([-overhang, 0, insert_height/2+overhang])
        difference() {
            rounded_rect(overhang*2, thickness, insert_height, radius=overhang/2);
            translate([overhang-thickness, -thickness/2, insert_height/2])
                cube([thickness, thickness*2, height]);
            translate([overhang-thickness, -thickness/2, insert_height/2])
                difference() {
                    translate([0,0,overhang*0.8])
                        cube([thickness+overhang, thickness*2, height]);
                    translate([overhang+thickness, 0, overhang*0.8])
                    rotate(a=[90,0,0])
                        cylinder(d=overhang*2, h=100, center=true);
                }
        }
        // connect and fill in one of the rounded corners
        cube([overhang, thickness, insert_height/2+overhang+overhang]);
        // below-tank part
        translate([0, 0, -support_height])
            cube([overhang, thickness, support_height]);
    }
}

module mock_rpi(x, y, z) {
    color("Blue", alpha=0.5)
    translate([x+thickness/2, y, z])
        rotate(a=[0,-90,180])
            translate([-85/2, -56/2, 0])
                union() {
                    cube([85, 56, 2]);
                    // USB1
                    translate([67, 41, 2])
                        cube([20, 12, 12]);
                    // USB2
                    translate([67, 23, 2])
                        cube([20, 12, 12]);
                }
}

// fishbox.scad
//  - Enclosure design for zebrafish Skinner box
//
//  Author: Mark Liffiton
//  Date: Oct, 2014 - Nov, 2015
//
//  Units are mm

// Set thickness to account for thickness of material
// PLUS needed clearance for cuts into which material should fit.

// 0.100" Acrylic:
acrylic_thickness = 2.4;
    // 2.4mm = 0.094" (looks good based on cut test piece for acrylic slotting into itself)

// 1/8" Hardboard:
hardboard_thickness = 3.3;
    // 2.8mm = 0.110"
    //   * looked good based on cut test piece for hardboard slotting into hardboard
    //   * 2015-03-06: Hanging supports didn't fit into cutouts (or did only with lots of force)
    // 3.1mm for some breathing room
    //   * 2016-02-26: Still tighter than is ideal for sliding parts together.
    // 3.3mm for just a smidge more...

// Assign materials: window/back wall and everything else
//window_thickness = acrylic_thickness;
window_thickness = hardboard_thickness;
thickness = hardboard_thickness;

// Interior box dimensions
// The variables are used below to control placement of **centerpoints** centerpoints of walls,
// so material thickness is added to account for that.
width = 290 + thickness;    // x = 29cm wall-to-wall
depth = 348 + thickness;    // y = 34.8cm to accomodate 34.8cm tank length
i_height = 195 + thickness; // z = 19.5cm to accomodate 16cm tank height plus wires for shock
                            // (i_height because 'height' is used for height of entire box)

// amount faces extend past each other at corners
overhang = 15;

// amount horizontal support tabs extend past side supports (technically, it will go this distance minus thickness/2)
outset = thickness+2;

// tank base plate
base_height = overhang/2;

// create height var to account for raised base
height = i_height + base_height;

// height of insert for rear tank support
insert_height = height/6;

// hanging support thickness and drop (distance it "sits down" after in place)
support_w = 10;
support_drop = support_w/2;

// useful tank measurements
tank_width = 70;
tank_foot_offset_left = 105;
tank_foot_offset_right = 101;

// x-position of mask piece inside box
mask_loc = tank_width + thickness;  // center-to-center
// dimensions for mask view opening
view_opening_left = 65;
view_opening_right = 78;
view_opening_width = depth-thickness - view_opening_left - view_opening_right;
view_opening_bottom = 24;
view_opening_height = 96;

// camera mounting measurements
camera_hole_diameter = 1.7;       // bolts are m2; 2mm nominal, 1.9mm actual, and a bit less for the beam width
camera_hole_horiz_offset = 21/2;  // 21mm between mounting holes, horizontally (so half that away from the camera center
camera_hole_vert_offset = 12.5;   // 12.5mm between mounting holes, vertically (one set on the camera center, one set up that much from center)

// position of rpi and related parts
rpi_ypos = depth/2 + 60;

// for slight offsets/tweaks
epsilon = 1;

// set precision of all arcs/spheres/cylinders/etc.
$fn = 50;

// vars to control projection for DXF export (see makefile)
if (DXF_TOP) {
    projection() top_cover(spacing=1);
} else if (DXF_BOTTOM) {
    projection() tank_base();
} else if (DXF_SIDE1) {
    projection() rotate(a=[90,0,0]) side(y=0);
} else if (DXF_SIDE2) {
    projection() rotate(a=[90,0,0]) side(y=depth);
} else if (DXF_END1) {
    projection() rotate(a=[0,90,0]) vert_face(x=0);
} else if (DXF_END2) {
    projection() rotate(a=[0,90,0]) vert_face(x=width);
} else if (DXF_MASK) {
    projection() rotate(a=[0,90,0]) mask(x=mask_loc);
} else if (DXF_TANK_SUPPORT) {
    projection() tank_support_layer1();
    projection() translate([tank_width+thickness,0,0]) tank_support_layer2();
} else if (DXF_RPI_SUPPORT) {
    projection() rotate(a=[90,0,0]) rpi_support();
} else if (DXF_REAR_SUPPORT) {
    projection() rotate(a=[90,0,0]) rear_support(1);
    translate([overhang*3,0,0])
    projection() rotate(a=[90,0,0]) rear_support(2);
    translate([overhang*6,0,0])
    projection() rotate(a=[90,0,0]) rear_support(3);
}
else {
    // 3D model
    // components to include (comment out unwanted)
    vert_face(x=0);      // "near face, transparent for monitor
    vert_face(x=width);  // "far" face, with camera, rpi, etc.
    mask(x=mask_loc);
    side(y=0);
    side(y=depth);
    tank_base();
    tank_support();
    rpi_support(x=width, y=rpi_ypos-10, z=height/2);
    mock_rpi(x=width, y=rpi_ypos, z=height/2);
    top_cover();
    rear_support();
}

//////////////////////////////////////////////////////////////////
// Module definitions
//

module mask(x, tab_extra=0) {
    difference() {
        vert_face_base(x, top_offset=thickness, bottom_offset=base_height-thickness);
        side(y=0);
        side(y=depth);
        translate([x-thickness/2, -overhang, 0]) {
            translate([0, 0, 0])
                rounded_truncation(width=overhang-thickness/2, up=false);
            translate([0, depth+overhang+thickness/2, 0])
                rounded_truncation(width=overhang-thickness/2, up=false);
        }
        // opening for tank view
        translate([0,view_opening_left+thickness/2, view_opening_bottom+base_height+thickness/2])
            cube([width, view_opening_width, view_opening_height]);
        // bottom opening for wires
        translate([0, depth-thickness/2, base_height+thickness])
        rotate(a=[0,90,0])
            cylinder(r=10, h=width);
        // top openings for wires (electrical stim)
        translate([0, depth-15, height-thickness/2])
        rotate(a=[0,90,0])
            cylinder(r=5, h=width);
        translate([0, 15, height-thickness/2])
        rotate(a=[0,90,0])
            cylinder(r=5, h=width);
        // tabs for rigidity w/ bottom panel
        cutouts(4,depth-thickness,outset*2,rot=[-90,0,90],trans=[x+thickness/2,depth/2,base_height+thickness/2], extra=tab_extra);
    }
}

module vert_face(x=0) {
    difference() {
        if (x == 0) {
            vert_face_base(0, window_thickness);
        }
        else {
            vert_face_base(x);
        }
        camera_opening();
        // round=false so we get the full width of the piece in the cutout, not reduced because we hit the rounded corner
        rpi_support(x=width+thickness*2, y=rpi_ypos-10, z=height/2+support_drop, round=false);
        translate([thickness+epsilon,0,-insert_height/2])
            rear_support();
        wire_opening();
        power_jack_cutout();
        // CAUTION: OpenSCAD won't let me make a projection of vert_face(x=0)
        //   if tank_base is put after side(y=depth) here... bug.  :(
        tank_base();
        top_cover();
        side(y=0);
        side(y=depth);
        translate([x-thickness/2, -overhang, 0]) {
            translate([0, depth+overhang+thickness/2, 0])
                rounded_truncation(width=overhang-thickness/2, up=false);
            translate([0, 0, 0])
                rounded_truncation(width=overhang-thickness/2, up=false);
        }
    }
}

module vert_face_base(x, face_thickness=thickness, top_offset=0, bottom_offset=0) {
    translate([x-face_thickness/2, -overhang, bottom_offset])
        cube([face_thickness,depth+overhang*2,height-top_offset-bottom_offset]);
}

module rounded_truncation(width, up, rot=[0,0,0]) {
    truncation_height=height/4;
    rotate(a=rot)
    difference() {
        translate([-50,-epsilon,0])
            cube([100, width+2*epsilon, truncation_height]);
        if (up) {
            translate([-50, width/2, 0])
            rotate(a=[0, 90, 0])
                cylinder(d=width, h=100);
        }
        else {
            translate([-50, width/2, truncation_height])
            rotate(a=[0, 90, 0])
                cylinder(d=width, h=100);
        }
    }
}

module camera_opening() {
    translate([width, depth/2, height/2])
    union() {
        cube([thickness*2,10,10], center=true);
        translate([0, camera_hole_horiz_offset, 0])
        rotate(a=[0, 90, 0])
            cylinder(d=camera_hole_diameter, h=100, center=true);
        translate([0, -camera_hole_horiz_offset, 0])
        rotate(a=[0, 90, 0])
            cylinder(d=camera_hole_diameter, h=100, center=true);
        translate([0, camera_hole_horiz_offset, camera_hole_vert_offset])
        rotate(a=[0, 90, 0])
            cylinder(d=camera_hole_diameter, h=100, center=true);
        translate([0, -camera_hole_horiz_offset, camera_hole_vert_offset])
        rotate(a=[0, 90, 0])
            cylinder(d=camera_hole_diameter, h=100, center=true);
    }
}

module rpi_support(x, y, z, round=true) {
    hanging_support(x, y, z, out=4, up=85, round=round);
}

module hanging_support(x, y, z, out, up, round=true) {
    inner_w = out+thickness;
    inner_h = up;
    color("Red", alpha=0.5)
    translate([x,y,z])
    // push inner opening up against vert face
    translate([out/2,0,0])
    difference() {
        if (round) {
            rounded_rect(inner_w+support_w*2, thickness, inner_h+support_w*2, center=true, radius=support_w);
        }
        else {
            cube([inner_w+support_w*2, thickness, inner_h+support_w*2], center=true);
        }
        // cutout for object
        cube([inner_w, thickness*2, inner_h], center=true);
        // cutout for passing through vertface
        translate([-(inner_w/2+support_w/2),0,-support_drop/2])
            cube([support_w+2*epsilon, thickness*2, inner_h-support_drop], center=true);
        // bottom cutout
        translate([-out/2,0,-(inner_h/2+support_w-support_drop/2)])
            cube([thickness, thickness*2, support_drop], center=true);
    }
}

// rounded corners in x,z directions
module rounded_rect(x,y,z, center, radius) {
    if (center) {
        rounded_rect_centered(x,y,z, radius);
    }
    else {
        translate([x/2, y/2, z/2])
            rounded_rect_centered(x,y,z, radius);
    }
}

module rounded_rect_centered(x,y,z, radius) {
    difference() {
        cube([x,y,z], center=true);

        translate([x/2-radius, 50, z/2-radius])
        rotate(a=[90,0,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100);
        }
        translate([-x/2+radius, 50, z/2-radius])
        rotate(a=[90,-90,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100);
        }
        translate([x/2-radius, 50, -z/2+radius])
        rotate(a=[90,90,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100);
        }
        translate([-x/2+radius, 50, -z/2+radius])
        rotate(a=[90,180,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100);
        }
    }
}

module top_cover(spacing=0) {
    // width1 = main cover ; width2 = tank section cover
    width1 = width - mask_loc - thickness/2;
    width2 = mask_loc - thickness/2;
    color("Grey", alpha=0.5)
    union() {
        // back piece (over empty space)
        // spacing added to make separate pieces for laser cutting
        translate([spacing,-outset,height-thickness])
        difference() {
            translate([mask_loc,0,0])
                cube([width1+thickness+outset,depth+outset*2,thickness]);
            cutouts(4,width1,outset,rot=0,trans=[width1/2+mask_loc,0,0]);
            cutouts(4,width1,outset,rot=180,trans=[width1/2+mask_loc,depth+outset*2,0]);
            cutouts(5,depth-thickness,outset,rot=90,trans=[width+outset,depth/2+outset,0]);
        }
        // front piece (over tank)
        translate([-outset-thickness/2,-overhang,height-thickness])
        difference() {
            cube([width2+thickness+outset,depth+overhang*2,thickness]);
            cutouts(5,depth-thickness,outset,rot=90,trans=[outset+thickness/2,depth/2+overhang,0]);
        }
    }
}

module tank_base() {
    difference() {
        translate([-outset,thickness/2,base_height])
            difference() {
                cube([width+outset*2, depth-thickness, thickness]);
                cutouts(5,depth,outset,rot=-90,trans=[0,(depth-thickness)/2,0]);
                cutouts(5,depth,outset,rot=90,trans=[width+outset*2,(depth-thickness)/2,0]);
            }
        mask(x=mask_loc, tab_extra=5);
    }
}

module tank_support() {
    translate([thickness/2, thickness/2, base_height+thickness])
    union() {
        tank_support_layer1();
        color("Grey", alpha=0.5)
            tank_support_layer2();
    }
}
// Two strips of hardboard on either side of LED strip
module tank_support_layer1() {
    // front bottom strip, w/ space for wires
    translate([0, 0, 0])
        cube([tank_width/3, depth-thickness, thickness]);
    // back bottom strip, full-depth
    translate([2*tank_width/3, 0, 0])
        cube([tank_width/3, depth-thickness-20, thickness]);
}
// Clear acrylic over LED strip
module tank_support_layer2() {
    translate([0, 0, thickness])
    difference() {
        cube([tank_width, depth-thickness, thickness]);
        translate([tank_width/2, depth-tank_width/2, 0])
            cylinder(d=25, h=100, center=true);
    }
}

module cutouts(num, width, outset, rot, trans, extra=0) {
    // "extra" is *removed* from width of cubes
    // so that resulting tabs (after differencing this) are *wider*
    w = width / ((num-1) * 2);
    translate(trans)
    rotate([0,0,1], a=rot)
    for (i = [0 : num-1]) {
        translate([-width/2 - w/2 + i*2*w + extra/2, -thickness/2, -thickness/2])
            cube([w-extra, outset+thickness, thickness*2]);
    }
}

module wire_opening() {
    translate([width, rpi_ypos+20, overhang+20])
    scale([1,2,1])
    rotate(a=[0,90,0])
        cylinder(d=12, h=thickness+epsilon, center=true);
}

module power_jack_cutout() {
    translate([width, depth-40, overhang+30])
    rotate(a=[0,90,0])
        cylinder(d=7, h=thickness+epsilon, center=true);
}

module side(y=0) {
    difference() {
        side_base(y);
        translate([0,0,height/2])
        scale([1,1,0.5])
            vert_face_base(x=mask_loc);
        translate([0,0,height/2])
        scale([1,1,0.5])
            vert_face_base(0, window_thickness);
        translate([0,0,height/2])
        scale([1,1,0.5])
            vert_face_base(width);
        top_cover();
    }
}

module side_base(y) {
    translate([-overhang,y-thickness/2,0])
    difference() {
        cube([width+overhang*2, thickness, height]);
        translate([width+overhang+thickness/2, 0, 3/4*height])
            rounded_truncation(width=overhang-thickness/2, up=true, rot=[0,0,-90]);
        translate([0, 0, 3/4*height])
            rounded_truncation(width=overhang-window_thickness/2, up=true, rot=[0,0,-90]);
    }
}

module rear_support(num_high=1) {
    support_height = num_high*height;
    ypos = depth/2-thickness/2;
    color("Orange")
    translate([width+thickness/2, ypos, 0])
    union() {
        // entering-tank part
        translate([-overhang, 0, insert_height/2+overhang])
        difference() {
            rounded_rect(overhang*2, thickness, insert_height, radius=overhang/2);
            translate([overhang-thickness, -thickness/2, insert_height/2])
                cube([thickness, thickness*2, height]);
            translate([overhang-thickness, -thickness/2, insert_height/2])
                difference() {
                    translate([0,0,overhang*0.8])
                        cube([thickness+overhang, thickness*2, height]);
                    translate([overhang+thickness, 0, overhang*0.8])
                    rotate(a=[90,0,0])
                        cylinder(d=overhang*2, h=100, center=true);
                }
        }
        // connect and fill in one of the rounded corners
        cube([overhang, thickness, insert_height/2+overhang+overhang]);
        // below-tank part
        translate([0, 0, -support_height])
            cube([overhang, thickness, support_height]);
    }
}

module mock_rpi(x, y, z) {
    color("Blue", alpha=0.5)
    translate([x+thickness/2, y, z])
        rotate(a=[0,-90,180])
            translate([-85/2, -56/2, 0])
                union() {
                    cube([85, 56, 2]);
                    // USB1
                    translate([67, 41, 2])
                        cube([20, 12, 12]);
                    // USB2
                    translate([67, 23, 2])
                        cube([20, 12, 12]);
                }
}


use unencrypted nunchuck

use unencrypted nunchuck

use <roundedsquare.scad>;

//<params>
includeBottom = 1; // [1:yes, 0:no]
includeTop = 1; // [1:yes, 0:no]
includeGamecubePort = 1; // [1:yes, 0:no]
includeEllipticalPort = 1; // [1:yes, 0:no]
includeNunchuckPort = 1; // [1:yes, 0:no]
includeDirectionSwitchPort = 1; // [1:yes, 0:no]
includeWashers = 0; // [1:yes, 0:no]
includeFeet = 0; // [1:yes, 0:no]
innerLength = 80;
extraWidth = 15;
sideWall = 1.5;
topWall = 1.6;
bottomWall = 2;
bottomScrewLength = 8;
topScrewLength = 5.7;
screwDiameter = 2.12;
screwHeadDiameter = 4.8;
thinPillarDiameter = 5.5;
fatPillarDiameter = 9;
stm32Width = 24.35;
stm32Length = 57.2;
tolerance = 0.2;
pcbThickness = 1.25;
bottomOverlap = 4;
topUnderlap = 4.8;
stm32ScrewYSpacing=18.8;
stm32ScrewXSpacing=52;
stm32ScrewOffset=2.4;
gcCableDiameter=3.7;
ellipticalCableMajorDiameter=5.32;
ellipticalCableMinorDiameter=2.66;
topOffset = 4.8;
topScrewX1 = 6;
topScrewXSpacing = 54.66;
buttonHoleDiameter = 5;
button1OffsetFromHole = 12.7;
button2OffsetFromHole = 5.78;
ledHoleDiameter = 4;
led1XOffsetFromButton1 = 10.16;
led1YOffsetFromButton1 = 1.27;
ledSpacing = 5.08;
pcbToPCBSpacing = 12;
usbPortWidth = 10;
usbPortHeight = 4.5;
usbPortZOffsetDown = 1;
directionSwitchNeckDiameter = 5.8;
directionSwitchOuterDiameter = 11.12;
directionSwitchNeck = 1.1;
nunchuckPortTolerance = 0.25;
nunchuckPortHeight = 7.36;
nunchuckPortWidth = 12.24;
nunchuckPortInsetDepth = 1.28;
nunchuckPortInsetWidth = 4.59;
nunchuckPortPillarTopFromCenter = 4.9;
nunchuckPortZOffset = 2;
nunchuckScrewHoleYMinusFromCenterOfPort = 6.99;
nunchuckScrewHoleXMinusFromOutsideOfWall = 12.91;
nunchuckScrewHoleYSpacing = 2.54*5;
nunchuckScrewHoleXSpacing = 2.54*6;
//</params>

module dummy() {}

includeSpacer = 0;
$fn = 32;
nudge = 0.001;

innerWidth = extraWidth+stm32Width+fatPillarDiameter*2;
bottomOffset = bottomScrewLength-pcbThickness;
bottomHeight = bottomWall+bottomOffset+pcbThickness;
bottomFatPillarLength = bottomHeight+bottomOverlap;
topHeight = topWall + topOffset + pcbThickness + max(topUnderlap,pcbToPCBSpacing);
topFatPillarLength = topHeight-topUnderlap;
nunchuckPillarLength = bottomHeight + nunchuckPortZOffset - nunchuckPortPillarTopFromCenter;

fatPillarLocations = [
    [-sideWall+fatPillarDiameter/2,-sideWall+fatPillarDiameter/2,0],
    [-sideWall+fatPillarDiameter/2,innerWidth+sideWall-fatPillarDiameter/2,0],
    [innerLength+sideWall-fatPillarDiameter/2,-sideWall+fatPillarDiameter/2,0],
    [innerLength+sideWall-fatPillarDiameter/2,innerWidth+sideWall-fatPillarDiameter/2,0]
];
stm32ScrewX1 = stm32ScrewOffset;
stm32ScrewY1 = fatPillarLocations[0][1]+fatPillarDiameter/2+thinPillarDiameter/2;

ellipticalCableY = fatPillarLocations[1][1]-fatPillarDiameter-1.5*ellipticalCableMinorDiameter;
gcPortY = stm32ScrewY1+stm32ScrewYSpacing/2;

nunchuckPortY = innerWidth+sideWall-fatPillarDiameter-nunchuckPortWidth/2-nunchuckPortTolerance*2;// (stm32ScrewY1+stm32ScrewYSpacing+thinPillarDiameter+innerWidth-fatPillarDiameter)*0.5;
nx0 = innerLength+sideWall-nunchuckScrewHoleXMinusFromOutsideOfWall;
ny0 = nunchuckPortY-nunchuckScrewHoleYMinusFromCenterOfPort;
nunchuckPillarLocations = [
    [nx0,ny0],
    [nx0-nunchuckScrewHoleXSpacing,ny0],
    [nx0-nunchuckScrewHoleXSpacing,ny0+nunchuckScrewHoleYSpacing],
    [nx0,ny0+nunchuckScrewHoleYSpacing]];
echo(nunchuckPillarLocations);

topScrewY = innerWidth-stm32ScrewY1-stm32ScrewYSpacing/2;
bottomThinPillarLocations = [
    [stm32ScrewX1,stm32ScrewY1,0],
    [stm32ScrewX1+stm32ScrewXSpacing,stm32ScrewY1,0],
    [stm32ScrewX1,stm32ScrewY1+stm32ScrewYSpacing,0],
    [stm32ScrewX1+stm32ScrewXSpacing,stm32ScrewY1+stm32ScrewYSpacing,0] ];

directionSwitchX = (topScrewX1+button1OffsetFromHole+topScrewX1+topScrewXSpacing-button2OffsetFromHole)/2;
directionSwitchY = topScrewY/2;

module base(inset=0) {
    translate([-sideWall+inset,-sideWall+inset])
    roundedSquare([innerLength+2*sideWall-2*inset,innerWidth+2*sideWall-2*inset], radius=fatPillarDiameter/2-inset);
}

module ellipticalCable() {
    rotate([0,90,0])
    translate([0,0,-nudge-sideWall])
    linear_extrude(height=3*sideWall+2*nudge)
    hull() {
        translate([-(ellipticalCableMajorDiameter-ellipticalCableMinorDiameter)/2-tolerance,0,0])
        circle(d=ellipticalCableMinorDiameter);
        translate([(ellipticalCableMajorDiameter-ellipticalCableMinorDiameter)/2+tolerance,0,0])
        circle(d=ellipticalCableMinorDiameter);
    }
}

nunchuckPortHeight = 7.36;
nunchuckPortWidth = 12.24;
nunchuckPortInsetDepth = 1.28;
nunchuckPortInsetWidth = 4.59;

module nunchuckConnector() {
    h = nunchuckPortHeight + 2*nunchuckPortTolerance;
    w = nunchuckPortWidth + 2*nunchuckPortTolerance;
    insetDepth = nunchuckPortInsetDepth;
    insetWidth = nunchuckPortInsetWidth+2*nunchuckPortTolerance;
    rotate([0,90,0])
    translate([0,0,-sideWall-nudge])
    linear_extrude(height=sideWall*2+2*nudge)
    polygon([[-h/2,-w/2],[h/2,-w/2],[h/2,w/2],[-h/2,w/2],[-h/2,insetWidth/2],[-h/2+insetDepth,insetWidth/2],[-h/2+insetDepth,-insetWidth/2],[-h/2,-insetWidth/2]]);
}

module sideWalls() {
    difference() {
        base();
        base(inset=sideWall);
    }
}

module tweakedSideWalls() {
    difference() {
        base(inset=-sideWall);
        base(inset=sideWall+tolerance);
    }
}

module fatPillar(hole=false,bottom=true) {
    fatPillarLength = bottom?bottomFatPillarLength:topFatPillarLength;
    if (!hole)
        cylinder(d=fatPillarDiameter,fatPillarLength);
    else {
        if (bottom)
            cylinder(d=screwHeadDiameter+2*tolerance,h=fatPillarLength-(bottom?bottomWall:topWall));
        cylinder(d=screwDiameter+2*tolerance,h=fatPillarLength+nudge);
    }
}

module thinPillar(hole=false,height=10) {
    if (!hole)
        cylinder(d=thinPillarDiameter,h=height);
    else {
        cylinder(d=screwDiameter+2*tolerance,h=height);
    }
}

module spacer() {
    linear_extrude(height=0.75) {
        difference() {
            hull() {
                circle(d=thinPillarDiameter);
                translate([0,stm32ScrewYSpacing]) circle(d=thinPillarDiameter);
            }
            circle(d=screwDiameter+5*tolerance);
            translate([0,stm32ScrewYSpacing]) circle(d=screwDiameter+5*tolerance);
        }
    }
}

module bottom() {
    render(convexity=2)
    difference() {
        union() {
            linear_extrude(height=bottomWall+nudge) base();
            for (p=fatPillarLocations)
                translate(p) fatPillar();
            linear_extrude(height=bottomHeight+nudge) sideWalls();
            for (p=bottomThinPillarLocations)
                translate(p) thinPillar(height=bottomWall+bottomOffset);
            if (includeNunchuckPort)
            for (p=nunchuckPillarLocations)
                translate(p) thinPillar(height=nunchuckPillarLength);
       if (includeGamecubePort)
            translate([innerLength-2*sideWall,gcPortY-gcCableDiameter*1.5,0]) cube([2*sideWall+nudge,3*gcCableDiameter,bottomHeight]);
       if (includeNunchuckPort)
            translate([innerLength-sideWall,nunchuckPortY-nunchuckPortWidth/2-2*sideWall,0])
       cube([2*sideWall,nunchuckPortWidth+4*sideWall,bottomHeight]);
       if (includeEllipticalPort)
            translate([-nudge,ellipticalCableY-ellipticalCableMinorDiameter*1.5,0]) cube([2*sideWall+nudge,3*ellipticalCableMinorDiameter,bottomHeight]);
        }
        for (p=fatPillarLocations)
            translate(p) fatPillar(hole=true);
        translate([0,0,bottomHeight]) linear_extrude(height=bottomOverlap+nudge) tweakedSideWalls();
            if (includeNunchuckPort)
            for (p=nunchuckPillarLocations)
                translate([0,0,bottomWall]) translate(p) thinPillar(height=nunchuckPillarLength,hole=true);
        for (p=bottomThinPillarLocations)
            translate([0,0,bottomWall]) translate(p) thinPillar(hole=true,height=bottomHeight+nudge);
       if (includeNunchuckPort)
           translate([innerLength,nunchuckPortY,bottomHeight+nunchuckPortZOffset]) nunchuckConnector();
        translate([-sideWall-nudge,(stm32ScrewY1+stm32ScrewYSpacing/2)-usbPortWidth/2,bottomHeight-usbPortZOffsetDown]) cube([sideWall+2*nudge,usbPortWidth,usbPortHeight+nudge]);
       if (includeGamecubePort)
       translate([innerLength-2*sideWall,gcPortY,bottomHeight]) rotate([0,90,0]) cylinder(d=gcCableDiameter,h=3*sideWall+2*nudge);
       if (includeEllipticalPort)
        translate([0,ellipticalCableY,bottomHeight]) ellipticalCable();
    }
}

module top() {
    render(convexity=2)
    difference() {
        union() {
            linear_extrude(height=topWall+nudge) base();
            for (p=fatPillarLocations)
                translate(p) fatPillar(bottom=false);
            linear_extrude(height=topHeight+nudge) sideWalls();
            translate([topScrewX1,topScrewY,0])
                thinPillar(height=topWall+topOffset);
            translate([topScrewX1+topScrewXSpacing,topScrewY,0])
                thinPillar(height=topWall+topOffset);
       if (includeNunchuckPort)
            translate([innerLength-sideWall,innerWidth-nunchuckPortY-nunchuckPortWidth/2-2*sideWall,0])
       cube([2*sideWall,nunchuckPortWidth+4*sideWall,topHeight]);
       if (includeGamecubePort)
            translate([innerLength-2*sideWall,innerWidth-gcPortY-gcCableDiameter*1.5,0]) cube([2*sideWall+nudge,3*gcCableDiameter,topHeight]);
       if (includeEllipticalPort)
            translate([-nudge,innerWidth-ellipticalCableY-ellipticalCableMinorDiameter*1.5,0]) cube([2*sideWall+nudge,3*ellipticalCableMinorDiameter,topHeight]);
        }
        for (p=fatPillarLocations)
            translate([0,0,topWall]) translate(p) fatPillar(hole=true,bottom=false);
            translate([topScrewX1,topScrewY,0.5])
                thinPillar(height=topWall+topOffset,hole=true);
            translate([topScrewX1+topScrewXSpacing,topScrewY,0.5])
                thinPillar(height=topWall+topOffset,hole=true);

    y0=innerWidth-(stm32ScrewY1+stm32ScrewYSpacing/2);
       if (includeNunchuckPort)
           translate([innerLength,innerWidth-nunchuckPortY,topHeight-nunchuckPortZOffset]) rotate([180,0,0]) nunchuckConnector();
       if (includeGamecubePort)
       translate([innerLength-2*sideWall,innerWidth-gcPortY,topHeight]) rotate([0,90,0]) cylinder(d=gcCableDiameter,h=3*sideWall+2*nudge);
       if (includeEllipticalPort)
        translate([0,innerWidth-ellipticalCableY,topHeight]) ellipticalCable();
        translate([-sideWall-nudge,y0-usbPortWidth/2,topHeight-usbPortHeight+usbPortZOffsetDown]) cube([sideWall+2*nudge,usbPortWidth,usbPortHeight+nudge]);
        translate([-sideWall*0.25-tolerance,y0-(stm32Width+2*tolerance)/2,topHeight-pcbThickness-tolerance]) cube([sideWall*0.25+tolerance+nudge,stm32Width+2*tolerance,pcbThickness+tolerance+nudge]);
        translate([topScrewX1+button1OffsetFromHole,topScrewY,-nudge]) cylinder(d=buttonHoleDiameter,h=topWall+2*nudge);
        translate([topScrewX1+topScrewXSpacing-button2OffsetFromHole,topScrewY,-nudge]) cylinder(d=buttonHoleDiameter,h=topWall+2*nudge);
        for(i=[0:3]) translate([topScrewX1+button1OffsetFromHole+led1XOffsetFromButton1+i*ledSpacing,topScrewY+led1YOffsetFromButton1,-nudge]) cylinder(d=ledHoleDiameter,h=topWall+2*nudge);
    if (includeDirectionSwitchPort)
    translate([directionSwitchX,directionSwitchY,-nudge]) { cylinder(d=directionSwitchNeckDiameter+4*tolerance,h=topWall+2*nudge);
     translate([0,0,directionSwitchNeck])
    cylinder(d=directionSwitchOuterDiameter+4*tolerance,h=topWall);
     }
    }
}

if (includeBottom)
    bottom();
if (includeTop)
    translate([0,innerWidth+sideWall+8,0])
    top();
if (includeWashers)
    render(convexity=2)
    translate([-30,0,0]) {
        for (i=[0:3])
            translate([0,i*15,0])
                scale([1.25,1.25,0])
                difference() {
                    thinPillar(hole=false,height=2);
                    translate([0,0,-nudge]) thinPillar(hole=true,height=2+2*nudge);
                }
    }
 if (includeSpacer)
    translate([-50,0,0]) spacer();
 if (includeFeet)
    translate([-70,0,0]) {
        for (i=[0:0])
            translate([0,i*15,0]) {
                cylinder(d=fatPillarDiameter,h=3);
                cylinder(d=screwHeadDiameter-1,h=7.5);
            }
    }

use <roundedsquare.scad>;

//<params>
includeBottom = 0; // [1:yes, 0:no]
includeTop = 0; // [1:yes, 0:no]
includeGamecubePort = 0; // [1:yes, 0:no]
includeEllipticalPort = 0; // [1:yes, 0:no]
includeNunchuckPort = 0; // [1:yes, 0:no]
includeDirectionSwitchPort = 1; // [1:yes, 0:no]
includeWashers = 0; // [1:yes, 0:no]
includeFeet = 1; // [1:yes, 0:no]
innerLength = 80;
extraWidth = 15;
sideWall = 1.5;
topWall = 1.6;
bottomWall = 2;
bottomScrewLength = 8;
topScrewLength = 5.7;
screwDiameter = 2.12;
screwHeadDiameter = 4.8;
thinPillarDiameter = 5.5;
fatPillarDiameter = 9;
stm32Width = 24.35;
stm32Length = 57.2;
tolerance = 0.2;
pcbThickness = 1.25;
bottomOverlap = 4;
topUnderlap = 4.8;
stm32ScrewYSpacing=18.8;
stm32ScrewXSpacing=52;
stm32ScrewOffset=2.4;
gcCableDiameter=3.7;
ellipticalCableMajorDiameter=5.32;
ellipticalCableMinorDiameter=2.66;
topOffset = 4.8;
topScrewX1 = 6;
topScrewXSpacing = 54.66;
buttonHoleDiameter = 5;
button1OffsetFromHole = 12.7;
button2OffsetFromHole = 5.78;
ledHoleDiameter = 4;
led1XOffsetFromButton1 = 10.16;
led1YOffsetFromButton1 = 1.27;
ledSpacing = 5.08;
pcbToPCBSpacing = 12;
usbPortWidth = 10;
usbPortHeight = 4.5;
usbPortZOffsetDown = 1;
directionSwitchNeckDiameter = 5.8;
directionSwitchOuterDiameter = 11.12;
directionSwitchNeck = 1.1;
nunchuckPortTolerance = 0.25;
nunchuckPortHeight = 7.36;
nunchuckPortWidth = 12.24;
nunchuckPortInsetDepth = 1.28;
nunchuckPortInsetWidth = 4.59;
nunchuckPortPillarTopFromCenter = 4.9;
nunchuckPortZOffset = 2;
nunchuckScrewHoleYMinusFromCenterOfPort = 6.99;
nunchuckScrewHoleXMinusFromOutsideOfWall = 12.91;
nunchuckScrewHoleYSpacing = 2.54*5;
nunchuckScrewHoleXSpacing = 2.54*6;
//</params>

module dummy() {}

includeSpacer = 0;
$fn = 32;
nudge = 0.001;

innerWidth = extraWidth+stm32Width+fatPillarDiameter*2;
bottomOffset = bottomScrewLength-pcbThickness;
bottomHeight = bottomWall+bottomOffset+pcbThickness;
bottomFatPillarLength = bottomHeight+bottomOverlap;
topHeight = topWall + topOffset + pcbThickness + max(topUnderlap,pcbToPCBSpacing);
topFatPillarLength = topHeight-topUnderlap;
nunchuckPillarLength = bottomHeight + nunchuckPortZOffset - nunchuckPortPillarTopFromCenter;

fatPillarLocations = [
    [-sideWall+fatPillarDiameter/2,-sideWall+fatPillarDiameter/2,0],
    [-sideWall+fatPillarDiameter/2,innerWidth+sideWall-fatPillarDiameter/2,0],
    [innerLength+sideWall-fatPillarDiameter/2,-sideWall+fatPillarDiameter/2,0],
    [innerLength+sideWall-fatPillarDiameter/2,innerWidth+sideWall-fatPillarDiameter/2,0]
];
stm32ScrewX1 = stm32ScrewOffset;
stm32ScrewY1 = fatPillarLocations[0][1]+fatPillarDiameter/2+thinPillarDiameter/2;

ellipticalCableY = fatPillarLocations[1][1]-fatPillarDiameter-1.5*ellipticalCableMinorDiameter;
gcPortY = stm32ScrewY1+stm32ScrewYSpacing/2;

nunchuckPortY = innerWidth+sideWall-fatPillarDiameter-nunchuckPortWidth/2-nunchuckPortTolerance*2;// (stm32ScrewY1+stm32ScrewYSpacing+thinPillarDiameter+innerWidth-fatPillarDiameter)*0.5;
nx0 = innerLength+sideWall-nunchuckScrewHoleXMinusFromOutsideOfWall;
ny0 = nunchuckPortY-nunchuckScrewHoleYMinusFromCenterOfPort;
nunchuckPillarLocations = [
    [nx0,ny0],
    [nx0-nunchuckScrewHoleXSpacing,ny0],
    [nx0-nunchuckScrewHoleXSpacing,ny0+nunchuckScrewHoleYSpacing],
    [nx0,ny0+nunchuckScrewHoleYSpacing]];
echo(nunchuckPillarLocations);

topScrewY = innerWidth-stm32ScrewY1-stm32ScrewYSpacing/2;
bottomThinPillarLocations = [
    [stm32ScrewX1,stm32ScrewY1,0],
    [stm32ScrewX1+stm32ScrewXSpacing,stm32ScrewY1,0],
    [stm32ScrewX1,stm32ScrewY1+stm32ScrewYSpacing,0],
    [stm32ScrewX1+stm32ScrewXSpacing,stm32ScrewY1+stm32ScrewYSpacing,0] ];

directionSwitchX = (topScrewX1+button1OffsetFromHole+topScrewX1+topScrewXSpacing-button2OffsetFromHole)/2;
directionSwitchY = topScrewY/2;

module base(inset=0) {
    translate([-sideWall+inset,-sideWall+inset])
    roundedSquare([innerLength+2*sideWall-2*inset,innerWidth+2*sideWall-2*inset], radius=fatPillarDiameter/2-inset);
}

module ellipticalCable() {
    rotate([0,90,0])
    translate([0,0,-nudge-sideWall])
    linear_extrude(height=3*sideWall+2*nudge)
    hull() {
        translate([-(ellipticalCableMajorDiameter-ellipticalCableMinorDiameter)/2-tolerance,0,0])
        circle(d=ellipticalCableMinorDiameter);
        translate([(ellipticalCableMajorDiameter-ellipticalCableMinorDiameter)/2+tolerance,0,0])
        circle(d=ellipticalCableMinorDiameter);
    }
}

nunchuckPortHeight = 7.36;
nunchuckPortWidth = 12.24;
nunchuckPortInsetDepth = 1.28;
nunchuckPortInsetWidth = 4.59;

module nunchuckConnector() {
    h = nunchuckPortHeight + 2*nunchuckPortTolerance;
    w = nunchuckPortWidth + 2*nunchuckPortTolerance;
    insetDepth = nunchuckPortInsetDepth;
    insetWidth = nunchuckPortInsetWidth+2*nunchuckPortTolerance;
    rotate([0,90,0])
    translate([0,0,-sideWall-nudge])
    linear_extrude(height=sideWall*2+2*nudge)
    polygon([[-h/2,-w/2],[h/2,-w/2],[h/2,w/2],[-h/2,w/2],[-h/2,insetWidth/2],[-h/2+insetDepth,insetWidth/2],[-h/2+insetDepth,-insetWidth/2],[-h/2,-insetWidth/2]]);
}

module sideWalls() {
    difference() {
        base();
        base(inset=sideWall);
    }
}

module tweakedSideWalls() {
    difference() {
        base(inset=-sideWall);
        base(inset=sideWall+tolerance);
    }
}

module fatPillar(hole=false,bottom=true) {
    fatPillarLength = bottom?bottomFatPillarLength:topFatPillarLength;
    if (!hole)
        cylinder(d=fatPillarDiameter,fatPillarLength);
    else {
        if (bottom)
            cylinder(d=screwHeadDiameter+2*tolerance,h=fatPillarLength-(bottom?bottomWall:topWall));
        cylinder(d=screwDiameter+2*tolerance,h=fatPillarLength+nudge);
    }
}

module thinPillar(hole=false,height=10) {
    if (!hole)
        cylinder(d=thinPillarDiameter,h=height);
    else {
        cylinder(d=screwDiameter+2*tolerance,h=height);
    }
}

module spacer() {
    linear_extrude(height=0.75) {
        difference() {
            hull() {
                circle(d=thinPillarDiameter);
                translate([0,stm32ScrewYSpacing]) circle(d=thinPillarDiameter);
            }
            circle(d=screwDiameter+5*tolerance);
            translate([0,stm32ScrewYSpacing]) circle(d=screwDiameter+5*tolerance);
        }
    }
}

module bottom() {
    render(convexity=2)
    difference() {
        union() {
            linear_extrude(height=bottomWall+nudge) base();
            for (p=fatPillarLocations)
                translate(p) fatPillar();
            linear_extrude(height=bottomHeight+nudge) sideWalls();
            for (p=bottomThinPillarLocations)
                translate(p) thinPillar(height=bottomWall+bottomOffset);
            if (includeNunchuckPort)
            for (p=nunchuckPillarLocations)
                translate(p) thinPillar(height=nunchuckPillarLength);
       if (includeGamecubePort)
            translate([innerLength-2*sideWall,gcPortY-gcCableDiameter*1.5,0]) cube([2*sideWall+nudge,3*gcCableDiameter,bottomHeight]);
       if (includeNunchuckPort)
            translate([innerLength-sideWall,nunchuckPortY-nunchuckPortWidth/2-2*sideWall,0])
       cube([2*sideWall,nunchuckPortWidth+4*sideWall,bottomHeight]);
       if (includeEllipticalPort)
            translate([-nudge,ellipticalCableY-ellipticalCableMinorDiameter*1.5,0]) cube([2*sideWall+nudge,3*ellipticalCableMinorDiameter,bottomHeight]);
        }
        for (p=fatPillarLocations)
            translate(p) fatPillar(hole=true);
        translate([0,0,bottomHeight]) linear_extrude(height=bottomOverlap+nudge) tweakedSideWalls();
            if (includeNunchuckPort)
            for (p=nunchuckPillarLocations)
                translate([0,0,bottomWall]) translate(p) thinPillar(height=nunchuckPillarLength,hole=true);
        for (p=bottomThinPillarLocations)
            translate([0,0,bottomWall]) translate(p) thinPillar(hole=true,height=bottomHeight+nudge);
       if (includeNunchuckPort)
           translate([innerLength,nunchuckPortY,bottomHeight+nunchuckPortZOffset]) nunchuckConnector();
        translate([-sideWall-nudge,(stm32ScrewY1+stm32ScrewYSpacing/2)-usbPortWidth/2,bottomHeight-usbPortZOffsetDown]) cube([sideWall+2*nudge,usbPortWidth,usbPortHeight+nudge]);
       if (includeGamecubePort)
       translate([innerLength-2*sideWall,gcPortY,bottomHeight]) rotate([0,90,0]) cylinder(d=gcCableDiameter,h=3*sideWall+2*nudge);
       if (includeEllipticalPort)
        translate([0,ellipticalCableY,bottomHeight]) ellipticalCable();
    }
}

module top() {
    render(convexity=2)
    difference() {
        union() {
            linear_extrude(height=topWall+nudge) base();
            for (p=fatPillarLocations)
                translate(p) fatPillar(bottom=false);
            linear_extrude(height=topHeight+nudge) sideWalls();
            translate([topScrewX1,topScrewY,0])
                thinPillar(height=topWall+topOffset);
            translate([topScrewX1+topScrewXSpacing,topScrewY,0])
                thinPillar(height=topWall+topOffset);
       if (includeNunchuckPort)
            translate([innerLength-sideWall,innerWidth-nunchuckPortY-nunchuckPortWidth/2-2*sideWall,0])
       cube([2*sideWall,nunchuckPortWidth+4*sideWall,topHeight]);
       if (includeGamecubePort)
            translate([innerLength-2*sideWall,innerWidth-gcPortY-gcCableDiameter*1.5,0]) cube([2*sideWall+nudge,3*gcCableDiameter,topHeight]);
       if (includeEllipticalPort)
            translate([-nudge,innerWidth-ellipticalCableY-ellipticalCableMinorDiameter*1.5,0]) cube([2*sideWall+nudge,3*ellipticalCableMinorDiameter,topHeight]);
        }
        for (p=fatPillarLocations)
            translate([0,0,topWall]) translate(p) fatPillar(hole=true,bottom=false);
            translate([topScrewX1,topScrewY,0.5])
                thinPillar(height=topWall+topOffset,hole=true);
            translate([topScrewX1+topScrewXSpacing,topScrewY,0.5])
                thinPillar(height=topWall+topOffset,hole=true);

    y0=innerWidth-(stm32ScrewY1+stm32ScrewYSpacing/2);
       if (includeNunchuckPort)
           translate([innerLength,innerWidth-nunchuckPortY,topHeight-nunchuckPortZOffset]) rotate([180,0,0]) nunchuckConnector();
       if (includeGamecubePort)
       translate([innerLength-2*sideWall,innerWidth-gcPortY,topHeight]) rotate([0,90,0]) cylinder(d=gcCableDiameter,h=3*sideWall+2*nudge);
       if (includeEllipticalPort)
        translate([0,innerWidth-ellipticalCableY,topHeight]) ellipticalCable();
        translate([-sideWall-nudge,y0-usbPortWidth/2,topHeight-usbPortHeight+usbPortZOffsetDown]) cube([sideWall+2*nudge,usbPortWidth,usbPortHeight+nudge]);
        translate([-sideWall*0.25-tolerance,y0-(stm32Width+2*tolerance)/2,topHeight-pcbThickness-tolerance]) cube([sideWall*0.25+tolerance+nudge,stm32Width+2*tolerance,pcbThickness+tolerance+nudge]);
        translate([topScrewX1+button1OffsetFromHole,topScrewY,-nudge]) cylinder(d=buttonHoleDiameter,h=topWall+2*nudge);
        translate([topScrewX1+topScrewXSpacing-button2OffsetFromHole,topScrewY,-nudge]) cylinder(d=buttonHoleDiameter,h=topWall+2*nudge);
        for(i=[0:3]) translate([topScrewX1+button1OffsetFromHole+led1XOffsetFromButton1+i*ledSpacing,topScrewY+led1YOffsetFromButton1,-nudge]) cylinder(d=ledHoleDiameter,h=topWall+2*nudge);
    if (includeDirectionSwitchPort)
    translate([directionSwitchX,directionSwitchY,-nudge]) { cylinder(d=directionSwitchNeckDiameter+4*tolerance,h=topWall+2*nudge);
     translate([0,0,directionSwitchNeck])
    cylinder(d=directionSwitchOuterDiameter+4*tolerance,h=topWall);
     }
    }
}

if (includeBottom)
    bottom();
if (includeTop)
    translate([0,innerWidth+sideWall+8,0])
    top();
if (includeWashers)
    render(convexity=2)
    translate([-30,0,0]) {
        for (i=[0:3])
            translate([0,i*15,0])
                scale([1.25,1.25,0])
                difference() {
                    thinPillar(hole=false,height=2);
                    translate([0,0,-nudge]) thinPillar(hole=true,height=2+2*nudge);
                }
    }
 if (includeSpacer)
    translate([-50,0,0]) spacer();
 if (includeFeet)
    translate([-70,0,0]) {
        for (i=[0:0])
            translate([0,i*15,0]) {
                cylinder(d=fatPillarDiameter,h=3);
                cylinder(d=screwHeadDiameter-1,h=7.5);
            }
    }


xaxis/ends: don't % vitamins

xaxis/ends: don't % vitamins

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

include <config.scad>
include <thing_libutils/misc.scad>
include <thing_libutils/bearing.scad>
use <thing_libutils/metric-screw.scad>
use <thing_libutils/timing-belts.scad>

motor_mount_wall_thick = xaxis_pulley[1] - xaxis_pulley[0]/2 + 4*mm;
xaxis_end_motorsize = lookup(NemaSideSize,xaxis_motor);
xaxis_end_motor_offset=[xaxis_end_motorsize/2+zaxis_bearing[1]/2-1*mm,motor_mount_wall_thick-2*mm,0];
xaxis_end_wz = xaxis_rod_distance+zaxis_bearing[2]+5*mm;

// overlap of the X and Z rods
xaxis_end_xz_rod_overlap = 4*mm;

// how much "stop" for the x rods
xaxis_end_rod_stop = 10*mm;

xaxis_endstop_size = [10.3*mm, 20*mm, 6.3*mm];
xaxis_endstop_screw_offset = [-1.8*mm, 0*mm, 0*mm];

module xaxis_end_body(part, with_motor, beltpath_index=0, nut_top=false)
{
    nut_h = zaxis_nut[4];
    wx = zaxis_bearing[1]/2+zaxis_nut[1];
    wx_ = with_motor? xaxis_end_motorsize+xaxis_end_motor_offset[0] - xaxis_end_motorsize/2 : wx;
    bearing_sizey= zaxis_bearing[1]+10*mm;

    if(part==undef)
    {
        difference()
        {
            xaxis_end_body(part="pos", with_motor=with_motor, beltpath_index=beltpath_index, nut_top=nut_top);
            xaxis_end_body(part="neg", with_motor=with_motor, beltpath_index=beltpath_index, nut_top=nut_top);
        }
    }
    else if(part=="pos")
    {
        if(with_motor)
        {
            screw_dist = lookup(NemaDistanceBetweenMountingHoles, xaxis_motor);
            for(x=[-1,1])
            for(z=[-1,1])
            translate([0,0,xaxis_beltpath_z_offsets[beltpath_index]])
            translate(xaxis_end_motor_offset)
            {
                translate([x*screw_dist/2, 0, z*screw_dist/2])
                cylindera(d=lookup(ThreadSize,xaxis_motor_thread)+4*mm, h=motor_mount_wall_thick, orient=[0,1,0], align=[0,-1,0], rounding_radius=2);
            }
        }

        // nut mount
        mirror([0,0,nut_top?1:0])
        translate([zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -xaxis_end_wz/2])
        {
            // main nut support
            cylindera(h=zaxis_nut[4], d=zaxis_nut[0]+5*mm, align=[0,0,1], round_radius=2);

            // nut screw holes support
            for(x=[-1,1])
            translate([x*zaxis_nut[5], 0, 0])
            cylindera(h=zaxis_nut[4], d=lookup(ThreadSize, zaxis_nut[6])+5*mm, align=[0,0,1], round_radius=2);

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

        // x axis rod holders
        xaxis_rod_d_support = xaxis_rod_d+5*mm;
        for(z=[-1,1])
        translate([-xaxis_end_xz_rod_overlap,0,z*(xaxis_rod_distance/2)])
        cylindera(h=wx_+xaxis_end_xz_rod_overlap, d=xaxis_rod_d_support, orient=[1,0,0], align=[1,0,0], round_radius=2);

        translate([wx_,0,(xaxis_rod_distance/2)+xaxis_rod_d])
        {
            rcubea(xaxis_endstop_size, align=[-1,0,-1]);
        }

        // support around z axis bearings
        translate([0, -xaxis_zaxis_distance_y, 0])
        cylindera(h=xaxis_end_wz,d=bearing_sizey, orient=[0,0,1], align=[0,0,0], round_radius=2);
    }
    else if(part=="neg")
    {
        // cut support around z axis bearings
        translate([0, -xaxis_zaxis_distance_y, 0])
        translate([0,0,.1])
        cubea([bearing_sizey/2+.1, bearing_sizey+.2, xaxis_end_wz+.4], orient=[0,0,1], align=[-1,0,0]);
    }
    else if(part=="vit")
    {
    }
}

module xaxis_end(part, with_motor=false, stop_x_rods=false, beltpath_index=0, show_motor=false, nut_top=false, show_nut=false, show_rods=false, show_bearings=false)
{
    nut_h = zaxis_nut[4];
    wx = zaxis_bearing[1]/2+zaxis_nut[1];
    wx_ = with_motor? xaxis_end_motorsize+xaxis_end_motor_offset[0] - xaxis_end_motorsize/2 : wx;
    extrasize = with_motor?0*mm:0*mm;
    extrasize_align = 1;

    if(part==undef)
    {
        difference()
        {
            xaxis_end(part="pos", with_motor=with_motor, stop_x_rods=stop_x_rods, beltpath_index=beltpath_index, show_motor=show_motor, nut_top=nut_top, show_nut=show_nut, show_rods=show_rods, show_bearings=show_bearings);
            xaxis_end(part="neg", with_motor=with_motor, stop_x_rods=stop_x_rods, beltpath_index=beltpath_index, show_motor=show_motor, nut_top=nut_top, show_nut=show_nut, show_rods=show_rods, show_bearings=show_bearings);
        }
    }
    else if(part=="pos")
    {
        hull()
        {
            xaxis_end_body(with_motor=with_motor, beltpath_index=beltpath_index, nut_top=nut_top);

            // projection against z, ensure easy print
            translate([0,0,-xaxis_end_wz/2])
                linear_extrude(1)
                projection(cut=false)
                xaxis_end_body(with_motor=with_motor, beltpath_index=beltpath_index, nut_top=nut_top);
        }
    }
    else if(part=="neg")
    {
        //endstop mount screw cuts
        translate([wx_,0,(xaxis_rod_distance/2)+xaxis_rod_d])
        translate(xaxis_endstop_screw_offset)
        for(y=[-1,1])
        translate([-5*mm,y*9.5*mm/2,xaxis_endstop_size[2]])
        {
            screw_cut(nut=MHexNutM2_5, screw_l=6*mm, screw_l_extra=0*mm, embed_head=false, with_head=true, with_nut=false, orient=[0,0,-1], align=[0,0,-1]);
            /*nut_trap_cut(nut=MHexNutM2_5, screw_l=6*mm, screw_l_extra=0*mm, trap_axis=[-1,0,0], orient=[0,0,1], align=[0,0,-1]);*/
        }

        xaxis_end_beltpath(height=xaxis_beltpath_height_body, width=xaxis_beltpath_width);

        // z smooth bearing mounts
        for(z=[-1,1])
        {
            translate([0,0,z*xaxis_rod_distance/2])
            translate([0, -xaxis_zaxis_distance_y, 0])
            {
                bearing_mount_holes(
                        bearing_type=zaxis_bearing,
                        ziptie_type=ziptie_type,
                        ziptie_bearing_distance=ziptie_bearing_distance,
                        orient=[0,0,1],
                        with_zips=true,
                        show_zips=false
                        );
                /*hull()*/
                /*{*/
                    /*cylindera(d=zaxis_bearing[1],h=xaxis_end_wz);*/

                    /*translate([-5*cm,0,0])*/
                    /*cylindera(d=zaxis_bearing[1],h=xaxis_end_wz);*/
                /*}*/
            }
        }

        // x smooth rods
        x_rod_stop = stop_x_rods ? xaxis_end_rod_stop : 0;
        for(z=[-1,1])
        translate([-xaxis_end_xz_rod_overlap-.1+x_rod_stop,0,z*(xaxis_rod_distance/2)])
        cylindera(h=wx_+xaxis_end_xz_rod_overlap-x_rod_stop+.2,d=xaxis_rod_d+.2*mm, orient=[1,0,0], align=[1,0,0]);

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

            // axle
            translate([0,0,xaxis_beltpath_z_offsets[beltpath_index]])
            translate(xaxis_end_motor_offset)
            {
                round_d=1.1*lookup(NemaRoundExtrusionDiameter, xaxis_motor);
                translate([0, .1, 0])
                translate([0,1,0])
                teardrop(d=round_d,h=motor_mount_wall_thick, tear_orient=[0,0,1], orient=[0,1,0], align=[0,-1,0], roll=90, truncate=0);

                // motor axle
                translate([0, .1, 0])
                cylindera(d=1.2*lookup(NemaAxleDiameter, xaxis_motor), h=lookup(NemaFrontAxleLength, xaxis_motor)+3*mm, orient=[0,1,0], align=[0,1,0]);

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

                for(x=[-1,1])
                for(z=[-1,1])
                translate([0,-xaxis_end_motor_offset[1],0])
                translate([x*screw_dist/2, -(xaxis_beltpath_width/2+4*mm), z*screw_dist/2])
                {
                    screw_cut(xaxis_motor_nut, h=25, with_nut=false, orient=[0,1,0], align=[0,1,0]);
                }
            }

        }

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

                    // lead screw
                    translate([0,0,-.1])
                    cylindera(h=lookup(NemaFrontAxleLength,zaxis_motor), d=zaxis_nut[2]*1.5, align=[0,0,1]);

                    for(i=[-1,1])
                    translate([i*zaxis_nut[5], 0, -zaxis_nut[3]])
                    screw_cut(thread=zaxis_nut[5], h=16*mm, with_nut=false, orient=[0,0,1], align=[0,0,1]);
                }
            }
        }
    }
    else if(part=="vit")
    {
        if(with_motor && show_motor)
        {
            translate([0,0,xaxis_beltpath_z_offsets[beltpath_index]])
            translate(xaxis_end_motor_offset)
            {
                // 1mm due to motor offset
                translate([0,-1*mm,0])
                {
                    // 1mm between pulley and motor
                    translate([0,-1*mm,0])
                        pulley(xaxis_pulley, flip=false, orient=[0,1,0], align=[0,-1,0]);
                    motor(xaxis_motor, NemaMedium, dualAxis=false, orientation=[-90,0,0]);
                }
            }
        }

        //endstop
        if($show_vit)
        {
            translate([wx_,0,(xaxis_rod_distance/2)+xaxis_rod_d])
            {
                difference()
                {
                    rcubea(xaxis_endstop_size, align=[-1,0,1]);

                    translate(xaxis_endstop_screw_offset)
                    for(y=[-1,1])
                    translate([-5*mm,y*9.5*mm/2,xaxis_endstop_size[2]])
                    screw(nut=MHexNutM2_5, h=6*mm, orient=[0,0,-1], align=[0,0,-1]);
                }
            }
        }

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

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

        if(show_bearings)
        {
            for(z=[-1,1])
            translate([0,0,z*xaxis_rod_distance/2])
            translate([0, -xaxis_zaxis_distance_y, 0])
            bearing(zaxis_bearing);
        }
    }
}

module xaxis_end_beltpath(height, width, length = 1000, align=[0,0,0], orient=[1,0,0])
{
    hull()
    for(z=[-1,1])
    translate([0,0,z*height/2])
    teardrop(h=length, d=width, orient=orient, align=align, roll=-180*min(0,z), truncate=.5);
}

module xaxis_end_idlerholder(part, width=xaxis_beltpath_width, length=10, beltpath_index=0)
{
    width = 20*mm;

    if(part==undef)
    {
        difference()
        {
            xaxis_end_idlerholder(part="pos", width=xaxis_beltpath_width, length=length, beltpath_index=beltpath_index);
            xaxis_end_idlerholder(part="neg", width=xaxis_beltpath_width, length=length, beltpath_index=beltpath_index);
        }
    }
    else if(part=="pos")
    {
        hull()
        {
            // support for x smooth rods
            for(z=[-1,1])
            translate([0,0,z*(xaxis_rod_distance/2)])
            cylindera(h=width, d=xaxis_rod_d+4*mm, orient=[1,0,0], align=[1,0,0], round_radius=2);

            // extra support for pulley
            translate([0,0,-xaxis_beltpath_z_offsets[max(0,beltpath_index)]])
            translate([width/2,0,0])
            cylindera(h=8*mm+pulley_height(xaxis_idler_pulley), d=xaxis_idler_pulley[3], orient=[0,1,0], round_radius=2);
        }
    }
    else if(part=="neg")
    {
        for(z=[-1,1])
        {
            translate([0,0,z*(xaxis_rod_distance/2)])
            {
                // x smooth rods
                translate([width/2+.1,0,0])
                cylindera(h=width/2,d=xaxis_rod_d+.2*mm, orient=[1,0,0], align=[1,0,0]);

                screw_cut(nut=MHexNutM4, h=16*mm, with_nut=false, orient=[1,0,0], align=[1,0,0]);

                translate([6*mm,0,0])
                nut_trap_cut(nut=MHexNutM4, screw_l=16*mm, trap_axis=[0,-1,0], orient=[1,0,0], $show_vit=false);
            }
        }

        translate([0,0,-xaxis_beltpath_z_offsets[max(0,beltpath_index)]])
        {
            translate([width/2,0,0])
                pulley(xaxis_idler_pulley, orient=[0,1,0]);

            translate([width/2,0,0])
                cylindera(d=lookup(ThreadSize, ThreadM5), h=100, orient=[0,1,0]);

            xaxis_end_beltpath(height=xaxis_beltpath_height_holders, width=xaxis_beltpath_width);
        }
    }
    else if(part=="vit")
    {
        translate([0,0,-xaxis_beltpath_z_offsets[max(0,beltpath_index)]])
        translate([width/2,0,0])
        pulley(xaxis_idler_pulley, orient=[0,1,0]);
    }
}

module xaxis_end_znut()
{
    difference()
    {
        union()
        {
            cylindera(d=zaxis_nut[1], h=zaxis_nut[3], orient=[0,0,1], align=[0,0,1]);
            cylindera(d=zaxis_nut[0], h=zaxis_nut[4], orient=[0,0,1], align=[0,0,1]);
        }

        translate([0,0,-.1])
        cylindera(d=zaxis_nut[2], h=zaxis_nut[4]+.2, orient=[0,0,1], align=[0,0,1]);
    }
}

include <thing_libutils/misc.scad>;
if(false)
{
    // x axis
    /*translate([0,0,axis_pos_z])*/
    {
        /*if(!preview_mode)*/
        {
            zrod_offset = zmotor_mount_rod_offset_x;
            for(z=[-1,1])
            for(z=xaxis_beltpath_z_offsets)
            translate([-z*3*mm, xaxis_zaxis_distance_y, z])
            belt_path(5*mm+main_width+2*(zrod_offset)+xaxis_end_motor_offset[0], 6, xaxis_pulley_inner_d, orient=[1,0,0], align=[0,0,0]);
        }

        /*translate([axis_pos_x,0,0])*/
        /*{*/
            // x carriage
            /*attach(xaxis_carriage_conn, [[0,-xaxis_zaxis_distance_y,0],[0,0,0]])*/
            /*{*/
                /*x_carriage_full();*/
            /*}*/
        /*}*/

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

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

                translate([x*95, xaxis_zaxis_distance_y, 0])
                mirror([0,0,max(0,x)])
                mirror([max(0,x),0,0])
                {
                    xaxis_end_idlerholder();
                    xaxis_end_idlerholder(part="vit");
                }
            }
        }
    }

}

if(false)
{
    translate([0, xaxis_zaxis_distance_y, 0])
    {
        for(x=[-1,1])
        translate([x*55,0,xaxis_end_wz/2])
        mirror([max(0,x),0,0])
        {
            xaxis_end(with_motor=true, beltpath_index=max(0,x), show_motor=false, show_nut=false, show_bearings=false);
            xaxis_end(part="vit", with_motor=true, beltpath_index=max(0,x), show_motor=false, show_nut=false, show_bearings=false);
        }

        for(x=[-1,1])
        /*translate([0,0,xaxis_end_wz/2])*/
        translate([x*45,25,0])
        translate([x*-0,0,0])
        mirror([max(0,x),0,0])
        rotate([0,-90,0])
        xaxis_end_idlerholder(beltpath_index=max(0,x));
    }
}

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

include <config.scad>
include <thing_libutils/misc.scad>
include <thing_libutils/bearing.scad>
use <thing_libutils/metric-screw.scad>
use <thing_libutils/timing-belts.scad>

motor_mount_wall_thick = xaxis_pulley[1] - xaxis_pulley[0]/2 + 4*mm;
xaxis_end_motorsize = lookup(NemaSideSize,xaxis_motor);
xaxis_end_motor_offset=[xaxis_end_motorsize/2+zaxis_bearing[1]/2-1*mm,motor_mount_wall_thick-2*mm,0];
xaxis_end_wz = xaxis_rod_distance+zaxis_bearing[2]+5*mm;

// overlap of the X and Z rods
xaxis_end_xz_rod_overlap = 4*mm;

// how much "stop" for the x rods
xaxis_end_rod_stop = 10*mm;

xaxis_endstop_size = [10.3*mm, 20*mm, 6.3*mm];
xaxis_endstop_screw_offset = [-1.8*mm, 0*mm, 0*mm];

module xaxis_end_body(part, with_motor, beltpath_index=0, nut_top=false)
{
    nut_h = zaxis_nut[4];
    wx = zaxis_bearing[1]/2+zaxis_nut[1];
    wx_ = with_motor? xaxis_end_motorsize+xaxis_end_motor_offset[0] - xaxis_end_motorsize/2 : wx;
    bearing_sizey= zaxis_bearing[1]+10*mm;

    if(part==undef)
    {
        difference()
        {
            xaxis_end_body(part="pos", with_motor=with_motor, beltpath_index=beltpath_index, nut_top=nut_top);
            xaxis_end_body(part="neg", with_motor=with_motor, beltpath_index=beltpath_index, nut_top=nut_top);
        }
    }
    else if(part=="pos")
    {
        if(with_motor)
        {
            screw_dist = lookup(NemaDistanceBetweenMountingHoles, xaxis_motor);
            for(x=[-1,1])
            for(z=[-1,1])
            translate([0,0,xaxis_beltpath_z_offsets[beltpath_index]])
            translate(xaxis_end_motor_offset)
            {
                translate([x*screw_dist/2, 0, z*screw_dist/2])
                cylindera(d=lookup(ThreadSize,xaxis_motor_thread)+4*mm, h=motor_mount_wall_thick, orient=[0,1,0], align=[0,-1,0], rounding_radius=2);
            }
        }

        // nut mount
        mirror([0,0,nut_top?1:0])
        translate([zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -xaxis_end_wz/2])
        {
            // main nut support
            cylindera(h=zaxis_nut[4], d=zaxis_nut[0]+5*mm, align=[0,0,1], round_radius=2);

            // nut screw holes support
            for(x=[-1,1])
            translate([x*zaxis_nut[5], 0, 0])
            cylindera(h=zaxis_nut[4], d=lookup(ThreadSize, zaxis_nut[6])+5*mm, align=[0,0,1], round_radius=2);

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

        // x axis rod holders
        xaxis_rod_d_support = xaxis_rod_d+5*mm;
        for(z=[-1,1])
        translate([-xaxis_end_xz_rod_overlap,0,z*(xaxis_rod_distance/2)])
        cylindera(h=wx_+xaxis_end_xz_rod_overlap, d=xaxis_rod_d_support, orient=[1,0,0], align=[1,0,0], round_radius=2);

        translate([wx_,0,(xaxis_rod_distance/2)+xaxis_rod_d])
        {
            rcubea(xaxis_endstop_size, align=[-1,0,-1]);
        }

        // support around z axis bearings
        translate([0, -xaxis_zaxis_distance_y, 0])
        cylindera(h=xaxis_end_wz,d=bearing_sizey, orient=[0,0,1], align=[0,0,0], round_radius=2);
    }
    else if(part=="neg")
    {
        // cut support around z axis bearings
        translate([0, -xaxis_zaxis_distance_y, 0])
        translate([0,0,.1])
        cubea([bearing_sizey/2+.1, bearing_sizey+.2, xaxis_end_wz+.4], orient=[0,0,1], align=[-1,0,0]);
    }
    else if(part=="vit")
    {
    }
}

module xaxis_end(part, with_motor=false, stop_x_rods=false, beltpath_index=0, show_motor=false, nut_top=false, show_nut=false, show_rods=false, show_bearings=false)
{
    nut_h = zaxis_nut[4];
    wx = zaxis_bearing[1]/2+zaxis_nut[1];
    wx_ = with_motor? xaxis_end_motorsize+xaxis_end_motor_offset[0] - xaxis_end_motorsize/2 : wx;
    extrasize = with_motor?0*mm:0*mm;
    extrasize_align = 1;

    if(part==undef)
    {
        difference()
        {
            xaxis_end(part="pos", with_motor=with_motor, stop_x_rods=stop_x_rods, beltpath_index=beltpath_index, show_motor=show_motor, nut_top=nut_top, show_nut=show_nut, show_rods=show_rods, show_bearings=show_bearings);
            xaxis_end(part="neg", with_motor=with_motor, stop_x_rods=stop_x_rods, beltpath_index=beltpath_index, show_motor=show_motor, nut_top=nut_top, show_nut=show_nut, show_rods=show_rods, show_bearings=show_bearings);
        }
    }
    else if(part=="pos")
    {
        hull()
        {
            xaxis_end_body(with_motor=with_motor, beltpath_index=beltpath_index, nut_top=nut_top);

            // projection against z, ensure easy print
            translate([0,0,-xaxis_end_wz/2])
                linear_extrude(1)
                projection(cut=false)
                xaxis_end_body(with_motor=with_motor, beltpath_index=beltpath_index, nut_top=nut_top);
        }
    }
    else if(part=="neg")
    {
        //endstop mount screw cuts
        translate([wx_,0,(xaxis_rod_distance/2)+xaxis_rod_d])
        translate(xaxis_endstop_screw_offset)
        for(y=[-1,1])
        translate([-5*mm,y*9.5*mm/2,xaxis_endstop_size[2]])
        {
            screw_cut(nut=MHexNutM2_5, screw_l=6*mm, screw_l_extra=0*mm, embed_head=false, with_head=true, with_nut=false, orient=[0,0,-1], align=[0,0,-1]);
            /*nut_trap_cut(nut=MHexNutM2_5, screw_l=6*mm, screw_l_extra=0*mm, trap_axis=[-1,0,0], orient=[0,0,1], align=[0,0,-1]);*/
        }

        xaxis_end_beltpath(height=xaxis_beltpath_height_body, width=xaxis_beltpath_width);

        // z smooth bearing mounts
        for(z=[-1,1])
        {
            translate([0,0,z*xaxis_rod_distance/2])
            translate([0, -xaxis_zaxis_distance_y, 0])
            {
                bearing_mount_holes(
                        bearing_type=zaxis_bearing,
                        ziptie_type=ziptie_type,
                        ziptie_bearing_distance=ziptie_bearing_distance,
                        orient=[0,0,1],
                        with_zips=true,
                        show_zips=false
                        );
                /*hull()*/
                /*{*/
                    /*cylindera(d=zaxis_bearing[1],h=xaxis_end_wz);*/

                    /*translate([-5*cm,0,0])*/
                    /*cylindera(d=zaxis_bearing[1],h=xaxis_end_wz);*/
                /*}*/
            }
        }

        // x smooth rods
        x_rod_stop = stop_x_rods ? xaxis_end_rod_stop : 0;
        for(z=[-1,1])
        translate([-xaxis_end_xz_rod_overlap-.1+x_rod_stop,0,z*(xaxis_rod_distance/2)])
        cylindera(h=wx_+xaxis_end_xz_rod_overlap-x_rod_stop+.2,d=xaxis_rod_d+.2*mm, orient=[1,0,0], align=[1,0,0]);

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

            // axle
            translate([0,0,xaxis_beltpath_z_offsets[beltpath_index]])
            translate(xaxis_end_motor_offset)
            {
                round_d=1.1*lookup(NemaRoundExtrusionDiameter, xaxis_motor);
                translate([0, .1, 0])
                translate([0,1,0])
                teardrop(d=round_d,h=motor_mount_wall_thick, tear_orient=[0,0,1], orient=[0,1,0], align=[0,-1,0], roll=90, truncate=0);

                // motor axle
                translate([0, .1, 0])
                cylindera(d=1.2*lookup(NemaAxleDiameter, xaxis_motor), h=lookup(NemaFrontAxleLength, xaxis_motor)+3*mm, orient=[0,1,0], align=[0,1,0]);

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

                for(x=[-1,1])
                for(z=[-1,1])
                translate([0,-xaxis_end_motor_offset[1],0])
                translate([x*screw_dist/2, -(xaxis_beltpath_width/2+4*mm), z*screw_dist/2])
                {
                    screw_cut(xaxis_motor_nut, h=25, with_nut=false, orient=[0,1,0], align=[0,1,0]);
                }
            }

        }

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

                    // lead screw
                    translate([0,0,-.1])
                    cylindera(h=lookup(NemaFrontAxleLength,zaxis_motor), d=zaxis_nut[2]*1.5, align=[0,0,1]);

                    for(i=[-1,1])
                    translate([i*zaxis_nut[5], 0, -zaxis_nut[3]])
                    screw_cut(thread=zaxis_nut[5], h=16*mm, with_nut=false, orient=[0,0,1], align=[0,0,1]);
                }
            }
        }
    }
    else if(part=="vit")
    {
        if(with_motor && show_motor)
        {
            translate([0,0,xaxis_beltpath_z_offsets[beltpath_index]])
            translate(xaxis_end_motor_offset)
            {
                // 1mm due to motor offset
                translate([0,-1*mm,0])
                {
                    // 1mm between pulley and motor
                    translate([0,-1*mm,0])
                        pulley(xaxis_pulley, flip=false, orient=[0,1,0], align=[0,-1,0]);
                    motor(xaxis_motor, NemaMedium, dualAxis=false, orientation=[-90,0,0]);
                }
            }
        }

        //endstop
        %if($show_vit)
        {
            translate([wx_,0,(xaxis_rod_distance/2)+xaxis_rod_d])
            {
                difference()
                {
                    rcubea(xaxis_endstop_size, align=[-1,0,1]);

                    translate(xaxis_endstop_screw_offset)
                    for(y=[-1,1])
                    translate([-5*mm,y*9.5*mm/2,xaxis_endstop_size[2]])
                    screw(nut=MHexNutM2_5, h=6*mm, orient=[0,0,-1], align=[0,0,-1]);
                }
            }
        }

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

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

        %if(show_bearings)
        {
            for(z=[-1,1])
            translate([0,0,z*xaxis_rod_distance/2])
            translate([0, -xaxis_zaxis_distance_y, 0])
            bearing(zaxis_bearing);
        }
    }
}

module xaxis_end_beltpath(height, width, length = 1000, align=[0,0,0], orient=[1,0,0])
{
    hull()
    for(z=[-1,1])
    translate([0,0,z*height/2])
    teardrop(h=length, d=width, orient=orient, align=align, roll=-180*min(0,z), truncate=.5);
}

module xaxis_end_idlerholder(part, width=xaxis_beltpath_width, length=10, beltpath_index=0)
{
    width = 20*mm;

    if(part==undef)
    {
        difference()
        {
            xaxis_end_idlerholder(part="pos", width=xaxis_beltpath_width, length=length, beltpath_index=beltpath_index);
            xaxis_end_idlerholder(part="neg", width=xaxis_beltpath_width, length=length, beltpath_index=beltpath_index);
        }
    }
    else if(part=="pos")
    {
        hull()
        {
            // support for x smooth rods
            for(z=[-1,1])
            translate([0,0,z*(xaxis_rod_distance/2)])
            cylindera(h=width, d=xaxis_rod_d+4*mm, orient=[1,0,0], align=[1,0,0], round_radius=2);

            // extra support for pulley
            translate([0,0,-xaxis_beltpath_z_offsets[max(0,beltpath_index)]])
            translate([width/2,0,0])
            cylindera(h=8*mm+pulley_height(xaxis_idler_pulley), d=xaxis_idler_pulley[3], orient=[0,1,0], round_radius=2);
        }
    }
    else if(part=="neg")
    {
        for(z=[-1,1])
        {
            translate([0,0,z*(xaxis_rod_distance/2)])
            {
                // x smooth rods
                translate([width/2+.1,0,0])
                cylindera(h=width/2,d=xaxis_rod_d+.2*mm, orient=[1,0,0], align=[1,0,0]);

                screw_cut(nut=MHexNutM4, h=16*mm, with_nut=false, orient=[1,0,0], align=[1,0,0]);

                translate([6*mm,0,0])
                nut_trap_cut(nut=MHexNutM4, screw_l=16*mm, trap_axis=[0,-1,0], orient=[1,0,0], $show_vit=false);
            }
        }

        translate([0,0,-xaxis_beltpath_z_offsets[max(0,beltpath_index)]])
        {
            translate([width/2,0,0])
                pulley(xaxis_idler_pulley, orient=[0,1,0]);

            translate([width/2,0,0])
                cylindera(d=lookup(ThreadSize, ThreadM5), h=100, orient=[0,1,0]);

            xaxis_end_beltpath(height=xaxis_beltpath_height_holders, width=xaxis_beltpath_width);
        }
    }
    else if(part=="vit")
    {
        %translate([0,0,-xaxis_beltpath_z_offsets[max(0,beltpath_index)]])
        translate([width/2,0,0])
        pulley(xaxis_idler_pulley, orient=[0,1,0]);
    }
}

module xaxis_end_znut()
{
    difference()
    {
        union()
        {
            cylindera(d=zaxis_nut[1], h=zaxis_nut[3], orient=[0,0,1], align=[0,0,1]);
            cylindera(d=zaxis_nut[0], h=zaxis_nut[4], orient=[0,0,1], align=[0,0,1]);
        }

        translate([0,0,-.1])
        cylindera(d=zaxis_nut[2], h=zaxis_nut[4]+.2, orient=[0,0,1], align=[0,0,1]);
    }
}

include <thing_libutils/misc.scad>;
if(false)
{
    // x axis
    /*translate([0,0,axis_pos_z])*/
    {
        /*if(!preview_mode)*/
        {
            zrod_offset = zmotor_mount_rod_offset_x;
            for(z=[-1,1])
            for(z=xaxis_beltpath_z_offsets)
            translate([-z*3*mm, xaxis_zaxis_distance_y, z])
            belt_path(5*mm+main_width+2*(zrod_offset)+xaxis_end_motor_offset[0], 6, xaxis_pulley_inner_d, orient=[1,0,0], align=[0,0,0]);
        }

        /*translate([axis_pos_x,0,0])*/
        /*{*/
            // x carriage
            /*attach(xaxis_carriage_conn, [[0,-xaxis_zaxis_distance_y,0],[0,0,0]])*/
            /*{*/
                /*x_carriage_full();*/
            /*}*/
        /*}*/

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

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

                translate([x*95, xaxis_zaxis_distance_y, 0])
                mirror([0,0,max(0,x)])
                mirror([max(0,x),0,0])
                {
                    xaxis_end_idlerholder();
                    xaxis_end_idlerholder(part="vit");
                }
            }
        }
    }

}

if(false)
{
    translate([0, xaxis_zaxis_distance_y, 0])
    {
        for(x=[-1,1])
        translate([x*55,0,xaxis_end_wz/2])
        mirror([max(0,x),0,0])
        {
            xaxis_end(with_motor=true, beltpath_index=max(0,x), show_motor=false, show_nut=false, show_bearings=false);
            xaxis_end(part="vit", with_motor=true, beltpath_index=max(0,x), show_motor=false, show_nut=false, show_bearings=false);
        }

        for(x=[-1,1])
        /*translate([0,0,xaxis_end_wz/2])*/
        translate([x*45,25,0])
        translate([x*-0,0,0])
        mirror([max(0,x),0,0])
        rotate([0,-90,0])
        xaxis_end_idlerholder(beltpath_index=max(0,x));
    }
}