[3d] Make end piece snap hole slightly undersized for a tighter fit

// Total number of probes supported (0 for just an endpiece)
count = 4; // [0,1,2,3,4,5]

// Wall thickness, set to multiple of perimeter width
wall = 2.0;
// Flat thickness
wall_t = 2;

// Overall width of flat
oa_w = 60;
// Overall depth of flat
oa_d = 95;
// Interior height between flats
height = 7.5;
// Center hole ratio
center_ratio = 3;

// Diameter of probe-holding hole
probe_d = 4.75;
// Length of probe holder
probe_l = 25;
// Distance of probe holder offset
probe_off = 7;
// Reduce thickness of probe holder (allow wire to pass more easily)
probe_cut = 1.25;

// Include holes around the perimeter of the flat?
flat_has_holes = true;
// Include the probe holder flat stub on endpiece as well? (can help retain wire)
end_has_stub = true;

/* [Hidden] */
// Center hole width
center_w = oa_w / center_ratio;
// Center hole depth
center_d = (oa_d - oa_w) + center_w;
// Diameter of holes in flat
flat_hole_d = (oa_w - center_w) / 4 - 0.5;

e=0.01;

do_render();
//fit_test(true);

module do_render() {
  if (count == 0)
    half(true);
  else if (count == 1)
    single();
  else if (count == 2)
    complete_2();
  else if (count == 3)
    complete_3();
  else if (count == 4)
    complete_4();
  else if (count == 5)
    complete_5();
}

module single() {
  half(false);
  translate([-oa_w - 1,0,0]) rotate(180) half(true);
}

module complete_2() {
  half(false);
  translate([oa_w + probe_off + probe_d + 1, 0, 0]) rotate(180) half(false);
  translate([0, oa_d + 1,0]) rotate(180) half(true);
}

module complete_3() {
  complete_2();
  translate([oa_w + probe_off + probe_d + 1, oa_d + 1, 0]) rotate(180) half(false);
}

module complete_4() {
  single();
  translate([oa_w + probe_off + probe_d + 1, 0, 0]) rotate(180) half(false);
  translate([0, oa_d + 1, 0]) half(false);
  translate([oa_w + probe_off + probe_d + 1, oa_d + 1, 0]) rotate(180) half(false);
}

module complete_5() {
  complete_4();
  translate([-oa_w - 1, oa_d + 1, 0]) rotate(180) half(false);
}

module rextrude_180(diameter, h) {
  intersection() {
    translate([-diameter/2-e, -e, -e])
      cube([diameter+2*e, diameter/2+e, h+2*e]);
    rotate_extrude()
      children();
  }
}

module fit_test(is_end) {
  if (is_end)
    mirror([0,1,0]) translate([0,-20,0]) scale([10,10,10]) lip_poly_stack_end();
  else
    translate([0,0,0]) scale([10,10,10]) lip_poly_stack(height);
  translate([0,-96.5,0]) scale([10,10,10]) lip_poly_stack(height);
}

module half(is_end) {
  probe_x=oa_w/2 + probe_off;
  probe_y=-probe_l - 5; // -oa_w/2 - 0;

  difference() {
    union() {
      flat();

      if (!is_end) {
        // probe holster
        intersection() {
          translate([probe_x, probe_y, probe_d/2+wall]) rotate([-90]) {
            difference() {
              union() {
                cylinder(probe_l, d=probe_d+2*wall, $fn=24);
                translate([-probe_d/2-wall,0,0])
                  cube([probe_d+2*wall, probe_d/2+wall, probe_l]);
              }
              translate([0,0,-e]) cylinder(probe_l+2*e, d=probe_d, $fn=16);
            }
          }
          // Keep the height of the cylinder less than the height of the layer
          translate([probe_x-probe_d/2-wall, probe_y, 0])
            cube([probe_d+2*wall, probe_l, height+wall_t-probe_cut]);
        } // interection
      } // if !is_end

      if (!is_end)
        // connect to holster
        translate([center_w/2+wall, probe_y, 0])
          cube([probe_x-center_w/2+probe_d/2, probe_l, wall_t]);
      else if (end_has_stub)
        // connect to holster
        mirror([0,1,0])
          translate([center_w/2+wall, probe_y, 0])
            cube([probe_x-center_w/2+probe_d/2, probe_l, wall_t]);

      // lip round
      translate([0,-(center_d-center_w)/2,0]) rotate(180)
        rextrude_180(oa_w, height*4, $fn=46)
        translate([center_w/2,0,0]) lip_poly(is_end, height);

      translate([0,(center_d-center_w)/2,0])
        rextrude_180(oa_w, height*4, $fn=46)
        translate([center_w/2,0,0]) lip_poly(is_end, height);
      // lip flat
      translate([center_w/2,(center_d-center_w)/2,0]) rotate([90])
        linear_extrude(center_d-center_w) lip_poly(is_end, height);
      translate([-center_w/2,-(center_d-center_w)/2,0]) rotate([90,0,180])
        linear_extrude(center_d-center_w) lip_poly(is_end, height);
    }  // end union

    if (is_end)
      mirror([0,1,0]) flat_holes();
    else
       flat_holes();

    // chip away a bit of snap in mating area
    translate([-4/2, -center_d/2-wall-e, wall_t+height-wall])
      cube([4, center_d+2*(wall+e), height*2]);
  } // end diff
}

module flat_hole() {
  rotate(20) {
    cylinder(wall_t+2*e, d=flat_hole_d);
    cube([flat_hole_d/2,flat_hole_d/2,wall_t+2*e]);
  }
}

module flat_holes() {
  flat_hole_offset = (oa_w - center_w)/4 + center_w/2;

  if (flat_has_holes) {
    translate([0, -(oa_d-oa_w)/2, 0]) for (rot=[0:180/4:180])
      rotate(-rot) translate([flat_hole_offset,0,-e]) flat_hole();
    translate([0, (oa_d-oa_w)/2, 0]) for (rot=[0:180/4:180])
      rotate(rot) translate([flat_hole_offset,0,-e]) flat_hole();
    translate([flat_hole_offset,0,-e]) flat_hole();
    translate([-flat_hole_offset,0,-e]) rotate(180) flat_hole();
  }
}

module flat() {
  difference() {
    hull() {
      translate([0, -(oa_d-oa_w)/2, 0]) cylinder(wall_t, d=oa_w, $fn=60);
      translate([0, (oa_d-oa_w)/2, 0]) cylinder(wall_t, d=oa_w, $fn=60);
    }

    // Center hole
    hull() {
      translate([0, -(center_d-center_w)/2, -e]) cylinder(wall_t+2*e, d=center_w+2, $fn=34);
      translate([0, (center_d-center_w)/2, -e]) cylinder(wall_t+2*e, d=center_w+2, $fn=34);
    }
  }
}

module lip_poly(is_end, interior_h) {
  if (is_end)
    lip_poly_stack_end();
  else
    lip_poly_stack(interior_h);
}

module lip_poly_stack(interior_h) {
  h=interior_h+wall_t;
  t_sock=wall;
  t_plug=t_sock;
  // Back the top snap portion away from the mating area by this amount
  // The lip is only 0.6mm so err needs to be less than this for any engagement to occur
  err_h=0.20;
  // Raise the snap lip this extra amount to account for a non-smooth surface
  // or layer rounding error, 1/2 a layer height is good
  err_v=0.15;

  polygon([
    // origin is bottom center, moving counterclockwise
    [0.5, 0], [t_sock,0], [t_sock, h], [-err_h, h],
    [-err_h, h+1+err_v],
      [0.6-err_h, h+1+0.6+err_v], [0.6-err_h, h+1+0.6+0.4+err_v],
    [-err_h, h+1+0.8+0.8+err_v],
    [-t_plug-err_h, h+1+0.8+0.8+err_v], [-t_plug-err_h, h], [0, h-t_plug-err_h],
    [0, 1+0.8+0.8], [0.8, 1+0.8], [0, 1], [0, 0.5]
  ]);
}

module lip_poly_stack_end() {
  t_sock = wall;
  stub = wall_t - 1.8;
  extra_tight = 0.1; // extend more under the snap lip since there is only one point of contact for this piece

  polygon([
    [0, 0], [t_sock, 0],
    // these points define the height, should be 1+0.8+0.8=2.6
    // but cut down to wall_t to be flush with flat(), this means wall_t
    // needs to be at least 1.8 to form a proper-sided socket
    [t_sock, wall_t], [0.5-extra_tight, wall_t], [-extra_tight, wall_t-0.5], [-extra_tight, wall_t-1],
    [0.8-extra_tight, wall_t-1-0.8], [0.8-extra_tight-stub, wall_t-1-0.8-stub]
  ]);
}

// Total number of probes supported (0 for just an endpiece)
count = 4; // [0,1,2,3,4,5]

// Wall thickness, set to multiple of perimeter width
wall = 2.0;
// Flat thickness
wall_t = 2;

// Overall width of flat
oa_w = 60;
// Overall depth of flat
oa_d = 95;
// Interior height between flats
height = 7.5;
// Center hole ratio
center_ratio = 3;

// Diameter of probe-holding hole
probe_d = 4.75;
// Length of probe holder
probe_l = 25;
// Distance of probe holder offset
probe_off = 7;
// Reduce thickness of probe holder (allow wire to pass more easily)
probe_cut = 1.25;

// Include holes around the perimeter of the flat?
flat_has_holes = true;
// Include the probe holder flat stub on endpiece as well? (can help retain wire)
end_has_stub = true;

/* [Hidden] */
// Center hole width
center_w = oa_w / center_ratio;
// Center hole depth
center_d = (oa_d - oa_w) + center_w;
// Diameter of holes in flat
flat_hole_d = (oa_w - center_w) / 4 - 0.5;

e=0.01;

do_render();

module do_render() {
  if (count == 0)
    half(true);
  else if (count == 1)
    single();
  else if (count == 2)
    complete_2();
  else if (count == 3)
    complete_3();
  else if (count == 4)
    complete_4();
  else if (count == 5)
    complete_5();
}

module single() {
  half(false);
  translate([-oa_w - 1,0,0]) rotate(180) half(true);
}

module complete_2() {
  half(false);
  translate([oa_w + probe_off + probe_d + 1, 0, 0]) rotate(180) half(false);
  translate([0, oa_d + 1,0]) rotate(180) half(true);
}

module complete_3() {
  complete_2();
  translate([oa_w + probe_off + probe_d + 1, oa_d + 1, 0]) rotate(180) half(false);
}

module complete_4() {
  single();
  translate([oa_w + probe_off + probe_d + 1, 0, 0]) rotate(180) half(false);
  translate([0, oa_d + 1, 0]) half(false);
  translate([oa_w + probe_off + probe_d + 1, oa_d + 1, 0]) rotate(180) half(false);
}

module complete_5() {
  complete_4();
  translate([-oa_w - 1, oa_d + 1, 0]) rotate(180) half(false);
}

module rextrude_180(diameter, h) {
  intersection() {
    translate([-diameter/2-e, -e, -e])
      cube([diameter+2*e, diameter/2+e, h+2*e]);
    rotate_extrude()
      children();
  }
}

module half(is_end) {
  probe_x=oa_w/2 + probe_off;
  probe_y=-probe_l - 5; // -oa_w/2 - 0;

  difference() {
    union() {
      flat();

      if (!is_end) {
        // probe holster
        intersection() {
          translate([probe_x, probe_y, probe_d/2+wall]) rotate([-90]) {
            difference() {
              union() {
                cylinder(probe_l, d=probe_d+2*wall, $fn=24);
                translate([-probe_d/2-wall,0,0])
                  cube([probe_d+2*wall, probe_d/2+wall, probe_l]);
              }
              translate([0,0,-e]) cylinder(probe_l+2*e, d=probe_d, $fn=16);
            }
          }
          // Keep the height of the cylinder less than the height of the layer
          translate([probe_x-probe_d/2-wall, probe_y, 0])
            cube([probe_d+2*wall, probe_l, height+wall_t-probe_cut]);
        } // interection
      } // if !is_end

      if (!is_end)
        // connect to holster
        translate([center_w/2+wall, probe_y, 0])
          cube([probe_x-center_w/2+probe_d/2, probe_l, wall_t]);
      else if (end_has_stub)
        // connect to holster
        mirror([0,1,0])
          translate([center_w/2+wall, probe_y, 0])
            cube([probe_x-center_w/2+probe_d/2, probe_l, wall_t]);

      // lip round
      translate([0,-(center_d-center_w)/2,0]) rotate(180)
        rextrude_180(oa_w, height*4, $fn=46)
        translate([center_w/2,0,0]) lip_poly(is_end, height);

      translate([0,(center_d-center_w)/2,0])
        rextrude_180(oa_w, height*4, $fn=46)
        translate([center_w/2,0,0]) lip_poly(is_end, height);
      // lip flat
      translate([center_w/2,(center_d-center_w)/2,0]) rotate([90])
        linear_extrude(center_d-center_w) lip_poly(is_end, height);
      translate([-center_w/2,-(center_d-center_w)/2,0]) rotate([90,0,180])
        linear_extrude(center_d-center_w) lip_poly(is_end, height);
    }  // end union

    if (is_end)
      mirror([0,1,0]) flat_holes();
    else
       flat_holes();

    // chip away a bit of snap in mating area
    translate([-4/2, -center_d/2-wall-e, wall_t+height-wall])
      cube([4, center_d+2*(wall+e), height*2]);
  } // end diff
}

module flat_hole() {
  rotate(20) {
    cylinder(wall_t+2*e, d=flat_hole_d);
    cube([flat_hole_d/2,flat_hole_d/2,wall_t+2*e]);
  }
}

module flat_holes() {
  flat_hole_offset = (oa_w - center_w)/4 + center_w/2;

  if (flat_has_holes) {
    translate([0, -(oa_d-oa_w)/2, 0]) for (rot=[0:180/4:180])
      rotate(-rot) translate([flat_hole_offset,0,-e]) flat_hole();
    translate([0, (oa_d-oa_w)/2, 0]) for (rot=[0:180/4:180])
      rotate(rot) translate([flat_hole_offset,0,-e]) flat_hole();
    translate([flat_hole_offset,0,-e]) flat_hole();
    translate([-flat_hole_offset,0,-e]) rotate(180) flat_hole();
  }
}

module flat() {
  difference() {
    hull() {
      translate([0, -(oa_d-oa_w)/2, 0]) cylinder(wall_t, d=oa_w, $fn=60);
      translate([0, (oa_d-oa_w)/2, 0]) cylinder(wall_t, d=oa_w, $fn=60);
    }

    // Center hole
    hull() {
      translate([0, -(center_d-center_w)/2, -e]) cylinder(wall_t+2*e, d=center_w+2, $fn=34);
      translate([0, (center_d-center_w)/2, -e]) cylinder(wall_t+2*e, d=center_w+2, $fn=34);
    }
  }
}

module lip_poly(is_end, interior_h) {
  if (is_end)
    lip_poly_stack_end();
  else
    lip_poly_stack(interior_h);
}

module lip_poly_stack(interior_h) {
  h=interior_h+wall_t;
  t_sock=wall;
  t_plug=t_sock;
  // Back the top snap portion away from the mating area by this amount
  // The lip is only 0.6mm so err needs to be less than this for any engagement to occur
  err_h=0.20;
  // Raise the snap lip this extra amount to account for a non-smooth surface
  // or layer rounding error, 1/2 a layer height is good
  err_v=0.15;

  polygon([
    // origin is bottom center, moving counterclockwise
    [0.5, 0], [t_sock,0], [t_sock, h], [-err_h, h],
    [-err_h, h+1+err_v],
      [0.6-err_h, h+1+0.6+err_v], [0.6-err_h, h+1+0.6+0.4+err_v],
    [-err_h, h+1+0.8+0.8+err_v],
    [-t_plug-err_h, h+1+0.8+0.8+err_v], [-t_plug-err_h, h], [0, h-t_plug-err_h],
    [0, 1+0.8+0.8], [0.8, 1+0.8], [0, 1], [0, 0.5]
  ]);
}

module lip_poly_stack_end() {
  t_sock = wall;
  stub = wall_t - 1.8;

  polygon([
    [0, 0], [t_sock, 0],
    // these points define the height, should be 1+0.8+0.8=2.6
    // but cut down to wall_t to be flush with flat(), this means wall_t
    // needs to be at least 1.8 to form a proper-sided socket
    [t_sock, wall_t], [0.5, wall_t], [0, wall_t-0.5], [0, wall_t-1],
    [0.8, wall_t-1-0.8], [0.8-stub, wall_t-1-0.8-stub]
  ]);
}


added template for checking if holes are aligned properly

use<m3d/fn.scad>

d_a = 7;
d_b = 8;
a_b_distance = 10.5;
b_top_distance = 18;

module screw_holes(h, extra_d=0.5)
{
  translate([0, d_a/2+a_b_distance+d_b/2, 0])
    cylinder(d=d_b+extra_d, h=h, $fn=fn(60));
  cylinder(d=d_a+extra_d, h=h, $fn=fn(60));
}

translate([-6/2, 0, 0])
  cube([6, a_b_distance+d_a/2+d_b+b_top_distance, 0.4]);
screw_holes(0.8, extra_d=0);


Oops.  Put the mask panel on the correct side of the tank.

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

// 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 = 190 + thickness; // z = 19cm to accomodate 16cm tank height plus cover
                            // (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;

// 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 = 78;
view_opening_bottom = 28;
view_opening_width = 190;
view_opening_height = 91;

// 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();
} 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();
    projection() translate([(tank_width+thickness)*2,0,0]) tank_support_layer3();
} else if (DXF_CAMERA_SUPPORT) {
    projection() rotate(a=[90,0,0]) camera_support(justone=true);
} else if (DXF_RPI_SUPPORT) {
    projection() rotate(a=[90,0,0]) rpi_support();
}
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();
    camera_support(x=width, y=depth/2, z=height/2);
    rpi_support(x=width, y=rpi_ypos-10, z=height/2);
    mock_rpi(x=width, y=rpi_ypos, z=height/2);
    top_cover();
}

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

module mask(x) {
    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]);
        // opening for wires
        translate([0, depth-thickness/2, base_height+thickness])
        rotate(a=[0,90,0])
            cylinder(r=10, h=width);
        // tabs for rigidity w/ bottom panel
        cutouts(4,depth-thickness,outset*2,rot=[-90,0,90],trans=[x+thickness/2,(depth-thickness)/2,base_height+thickness/2]);
    }
}

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
        camera_support(x=width+thickness*2, y=depth/2, z=height/2+support_drop, round=false);
        rpi_support(x=width+thickness*2, y=rpi_ypos-10, z=height/2+support_drop, round=false);
        wire_opening();
        // 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, 3/4*height])
                rounded_truncation(width=overhang-thickness/2, up=true);
            translate([0, 0, 3/4*height])
                rounded_truncation(width=overhang-thickness/2, up=true);
        }
    }
}

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])
        cube([thickness*2,10,10], center=true);
}

module camera_support(x, y, z, justone=false, round=true) {
    spacing = 20;
    out = 11;
    up = 36;
    hanging_support(x, y + spacing/2, z, out, up, round=round);
    if (!justone) {
        hanging_support(x, y - spacing/2, z, out, up, round=round);
    }
}

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) {
    difference() {
        cube([x,y,z], center);

        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() {
    color("Grey", alpha=0.5)
    translate([-outset,-outset,height-thickness])
    difference() {
        cube([width+outset*2,depth+outset*2,thickness]);
        cutouts(5,width-thickness,outset,rot=0,trans=[width/2+outset,0,0]);
        cutouts(5,width-thickness,outset,rot=180,trans=[width/2+outset,depth+outset*2,0]);
        cutouts(5,depth-thickness,outset,rot=-90,trans=[0,depth/2+outset,0]);
        cutouts(5,depth-thickness,outset,rot=90,trans=[width+outset*2,depth/2+outset,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);
    }
}

module tank_support() {
    translate([thickness/2, thickness/2, base_height+thickness])
    union() {
        tank_support_layer1();
        color("Grey", alpha=0.5)
            tank_support_layer2();
        color("Grey", alpha=0.5)
            tank_support_layer3();
    }
}
// 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])
        cube([tank_width, depth-thickness, thickness]);
}
// Clear acrylic end blocks to prevent tank shifting / seat it in correct location
module tank_support_layer3() {
    translate([0, 0, thickness*2])
        cube([tank_width, tank_foot_offset_right, thickness]);
    translate([0, depth-thickness-tank_foot_offset_left, thickness*2])
        cube([tank_width, tank_foot_offset_left, thickness]);
}

module cutouts(num, width, outset, rot, trans) {
    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, -thickness/2, -thickness/2])
            cube([w, outset+thickness, thickness*2]);
    }
}

module wire_opening() {
    translate([width, rpi_ypos-35, overhang+20])
    scale([1,2,1])
    rotate(a=[0,90,0])
        cylinder(d=12, 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);
        scale([1,1,0.5])
            vert_face_base(0, window_thickness);
        scale([1,1,0.5])
            vert_face_base(width);
        top_cover();
    }
}

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

module mock_rpi(x, y, z) {
    color("Blue", alpha=0.5)
    translate([x+thickness, 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.1;
    // 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

// 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 = 190 + thickness; // z = 19cm to accomodate 16cm tank height plus cover
                            // (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;

// 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_offset = tank_width + thickness;  // center-to-center
mask_loc = width - mask_offset;
// dimensions for mask view opening
view_opening_right = 80;
view_opening_bottom = 28;
view_opening_width = 190;
view_opening_height = 91;

// 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();
} 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();
    projection() translate([(tank_width+thickness)*2,0,0]) tank_support_layer3();
} else if (DXF_CAMERA_SUPPORT) {
    projection() rotate(a=[90,0,0]) camera_support(justone=true);
} else if (DXF_RPI_SUPPORT) {
    projection() rotate(a=[90,0,0]) rpi_support();
}
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();
    camera_support(x=width, y=depth/2, z=height/2);
    rpi_support(x=width, y=rpi_ypos-10, z=height/2);
    mock_rpi(x=width, y=rpi_ypos, z=height/2);
    top_cover();
}

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

module mask(x) {
    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_right+thickness/2, view_opening_bottom+base_height+thickness/2])
            cube([width, view_opening_width, view_opening_height]);
        // opening for wires
        translate([0, thickness, base_height+thickness])
        rotate(a=[0,90,0])
            cylinder(r=10, h=width);
        // tabs for rigidity w/ bottom panel
        cutouts(4,depth-thickness,outset*2,rot=[-90,0,90],trans=[x+thickness/2,(depth-thickness)/2,base_height+thickness/2]);
    }
}

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
        camera_support(x=width+thickness*2, y=depth/2, z=height/2+support_drop, round=false);
        rpi_support(x=width+thickness*2, y=rpi_ypos-10, z=height/2+support_drop, round=false);
        wire_opening();
        // 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, 3/4*height])
                rounded_truncation(width=overhang-thickness/2, up=true);
            translate([0, 0, 3/4*height])
                rounded_truncation(width=overhang-thickness/2, up=true);
        }
    }
}

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])
        cube([thickness*2,10,10], center=true);
}

module camera_support(x, y, z, justone=false, round=true) {
    spacing = 20;
    out = 11;
    up = 36;
    hanging_support(x, y + spacing/2, z, out, up, round=round);
    if (!justone) {
        hanging_support(x, y - spacing/2, z, out, up, round=round);
    }
}

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) {
    difference() {
        cube([x,y,z], center);

        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() {
    color("Grey", alpha=0.5)
    translate([-outset,-outset,height-thickness])
    difference() {
        cube([width+outset*2,depth+outset*2,thickness]);
        cutouts(5,width-thickness,outset,rot=0,trans=[width/2+outset,0,0]);
        cutouts(5,width-thickness,outset,rot=180,trans=[width/2+outset,depth+outset*2,0]);
        cutouts(5,depth-thickness,outset,rot=-90,trans=[0,depth/2+outset,0]);
        cutouts(5,depth-thickness,outset,rot=90,trans=[width+outset*2,depth/2+outset,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);
    }
}

module tank_support() {
    translate([mask_loc+thickness/2, thickness/2, base_height+thickness])
    union() {
        tank_support_layer1();
        color("Grey", alpha=0.5)
            tank_support_layer2();
        color("Grey", alpha=0.5)
            tank_support_layer3();
    }
}
// Two strips of hardboard on either side of LED strip
module tank_support_layer1() {
    // front bottom strip, w/ space for wires
    translate([0, 20, 0])
        cube([tank_width/3, depth-thickness-20, thickness]);
    // back bottom strip, full-depth
    translate([2*tank_width/3, 0, 0])
        cube([tank_width/3, depth-thickness, thickness]);
}
// Clear acrylic over LED strip
module tank_support_layer2() {
    translate([0, 0, thickness])
        cube([tank_width, depth-thickness, thickness]);
}
// Clear acrylic end blocks to prevent tank shifting / seat it in correct location
module tank_support_layer3() {
    translate([0, 0, thickness*2])
        cube([tank_width, tank_foot_offset_right, thickness]);
    translate([0, depth-thickness-tank_foot_offset_left, thickness*2])
        cube([tank_width, tank_foot_offset_left, thickness]);
}

module cutouts(num, width, outset, rot, trans) {
    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, -thickness/2, -thickness/2])
            cube([w, outset+thickness, thickness*2]);
    }
}

module wire_opening() {
    translate([width, rpi_ypos-35, overhang+20])
    scale([1,2,1])
    rotate(a=[0,90,0])
        cylinder(d=12, 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);
        scale([1,1,0.5])
            vert_face_base(0, window_thickness);
        scale([1,1,0.5])
            vert_face_base(width);
        top_cover();
    }
}

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

module mock_rpi(x, y, z) {
    color("Blue", alpha=0.5)
    translate([x+thickness, 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]);
                }
}


2D -> 3D take on the print

2D -> 3D take on the print

eps = 0.01;
wall = 0.65;
length = 59;
width = 21.6;
cut_in_length = 15;

module element()
{
  module profile_shell()
  {
    module exterior()
    {
      module profile()
      {
        r=7;
        hull()
        {
          translate(r*[1,1,0] + [0,4,0])
            circle(r=r, $fn=50);
          square([width, 6]);
        }
      }
      intersection()
      {
        minkowski()
        {
          difference()
          {
            translate(-5*[1,1,0])
              square([30,30]);
            profile();
          }
          circle(r=wall, $fn=50);
        }
        profile();
      }
    }
    difference()
    {
      exterior();
      translate(wall*[1,0] - eps*[0,1])
        square([width-2*wall, wall+2*eps]);
    }
  }

  module surround()
  {
    linear_extrude(length)
      profile_shell();
  }
  module side()
  {
    linear_extrude(wall)
      hull()
        profile_shell();
  }

  module core()
  {
    surround();
    for(dz=[0, length-wall])
      translate([0,0,dz])
        side();
  }

  rotate([90, 0, 0])
    difference()
    {
      core();
      translate(length/2*[0,0,1] - [6,0,0])
        translate(-cut_in_length/2*[0,0,1])
          cube([width, 30, cut_in_length]);
    }
}

element();

side_h = 17;
base_size = [21.6, 59, 6];
top_cylinder_start_h = 8;
top_r = side_h - top_cylinder_start_h;

module element()
{

//  cube(base_size);
//  translate([top_r, 0, top])
//    rotate([-90, 0, 0])
//      cylinder(r=top_r, h=base_size[1], $fn=100);
}

element();