Update case lid

Update case lid

// Generic case and lid - Improved
// http://www.thingiverse.com/thing:643264
// Printbus, November 2016

// Originally remixed from dandmpye's Generic case (box and lid) http://www.thingiverse.com/thing:28608
//   Revised dimensioning for improved scalability
//   Reformatted and labels revised for readability
//   Added provision for mounting tabs
// Rev A incorporates several improvements in the design and with openSCAD utilization
//   Reduced interdependency between parameters through individual parameters that can be tailored
//     Box wall thickness, base thickness, mounting tab thickness, lid thickness, lid lip height
//   Added table of reference data for hardware dimensions
//   Improved mesh overlap to reduce "ghosting" in rendering view
//   Added informative echo outputs to openSCAD console
//   Revised path for custom function library
//   Added sample code for sidewall teardrop and round holes
//   Added sample code for board mounting standoffs
//
// To keep the design manageable...
//   The diameter of the lid corner posts is factored from the screw hole in the post
//   The radius of the box corners is set to match the lid corner posts
//   The inside edge of the straight portions of lid lip is aligned with center of corner screw holes
//     This means the lip width will reduce as the box side wall thickness increases
//
// Hint: To minimize infill artifacts on box sidewalls, try to adjust box_wt and slicer number of
//   solid perimeters so that no sidewall infill is required for your extrusion width

////////// REVISION HISTORY ///////////////////////////////////////////////////
// YYMMDD date code
// 150119 - initial publish
// 161104 - revA
//

////////// INCLUDE AND USE  ///////////////////////////////////////////////////
// NOTE: openSCAD version 2014.03 or newer required for MCAD library

// EXTERNAL FUNCTIONS USED
use <roundCornersCube.scad>
//  Get roundCornersCube.scad from http://www.thingiverse.com/thing:8812
//  Revise path location to the custom openSCAD function if not in standard custom library location
//  roundCornersCube (x,y,z,r) gives cube of size xyz with corner radius r
//  Note that cube center parameter is fixed at true, and the corner $fn is fixed at 25
//  MCAD roundedBox is not used - those results seem imperfect and doesn't set corner $fn
use <MCAD/polyholes.scad>
//  Requires openSCAD MCAD library to be installed (openSCAD 2014.03 or later)
//  polyhole(h,d) provides improved size control on small holes
//  cylinder center parameter defaults to false; $fn intentionally varies with size of hole
use <MCAD/teardrop.scad>
//  Requires openSCAD MCAD library to be installed (openSCAD 2014.03 or later)
//  teardrop(radius, length, angle) provides a sidewall hole more printable than round hole
//  Angle parameter of 90 degrees gives teardrop shape; unclear when another value would be used
//  Consists of a round lobe and a right angle at the teardrop point
//  radius refers to the radius of the round lobe
//  $fn on round lobe is forced to 30
//  length is really the thickness of the teardrop formed in the x-axis
//  overall size of the teardrop is radius * (1 + 1/sin(45)), or radius*2.414
//  For angle=90, round lobe will center at x,y,z=0. Teardrop builds on x-axis for length parameter

//////////////// PARAMETERS  ///////////////////////////////////////////////////
// All dimensions are millimeter
// Case parameters for tailoring
box_sx = 104;       // box outside size in X axis, not including mounting tabs
box_sy = 70;       // box outside size in Y axis
box_sz = 27;       // box outside size in Z axis, including the lid thickness
box_wt = 2.0;      // box wall thickness (keep less than box_cp_hid)
box_bt = 2.0;      // box base or bottom thickness (should be a multiple of printed layer height)

// Lid parameters for tailoring
lid_sz = 2.0;      // lid size in Z axis or lid thickness (should be a multiple of printed layer height)
lip_h = 1.60;      // lid lip height (should be a multiple of layer height)
fit_tol = 0.40;    // fit tolerance or clearance between box and lid

// Mounting tab parameters for tailoring
tab_use = true;     // true or false for mounting tab on x-axis yes/no
tab_sz = box_bt;    // tab size in z-axis or height (typically set same as box base thickness)
tab_sx = 8;         // mounting tab size in x-axis (the amount tab extends from box)
tab_hoy = 7;        // tab hole offset in y axis from edge of tab

// Fastener and hardware component hole sizing
/*====================================================
Standard hardware dimension reference data
These are a reference point only; adjustment may be necessary due to nozzle size, etc.
Also note that MCAD polyhole does better at actual diameters than circle/cylinder

SIZE         THREAD  CLEAR   NUT AFD  NUT OD   NUT     HEAD     HEAD    WASHER  FLAT
             HOLE    HOLE    WRENCH   @fn=6    HEIGHT  OD       HEIGHT  HEIGHT  OD
-----------  ------  ------  -------  -------  ------  ------   ------  ------  -----
M2 x 0.4     1.75mm  2.20mm  4.0mm    4.62mm   1.6mm   4.0mm    2.0mm   0.3mm   5.5mm
M2.5 x 0.45  2.20mm  2.75mm  5.0mm    5.77mm   2.0mm   5.0mm    2.5mm   0.3mm   6.0mm
M3 x 0.5     2.70mm  3.30mm  5.5mm    6.35mm   2.4mm   6.0mm    3.0mm   0.5mm   7.0mm
M4 x 0.7     3.50mm  4.40mm  7.0mm    8.08mm   3.2mm   8.0mm    4.0mm   0.8mm   9.0mm
M5 x 0.8     4.50mm  5.50mm  8.0mm    9.24mm   4.7mm   10.0mm   5.0mm   1.0mm   10.0mm
M6 x 1.0     5.50mm  6.60mm  10.0mm   11.55mm  5.2mm   12.0mm   6.0mm   1.6mm   12.0mm
M8 x 1.25    7.20mm  8.80mm  13.0mm   15.01mm  6.8mm   16.0mm   8.0mm   2.0mm   17.0mm
#2-56        1.85mm  2.44mm  4.76mm   5.50mm   1.59mm  4.60mm   2.18mm  0.91mm  6.35mm
#3-56        2.26mm  2.79mm  4.76mm   5.50mm   1.59mm  5.28mm   2.51mm  0.91mm  7.94mm
#4-40        2.44mm  3.26mm  6.35mm   7.33mm   2.38mm  5.97mm   2.85mm  1.14mm  9.53mm
#6-32        2.95mm  3.80mm  7.94mm   9.17mm   2.78mm  7.37mm   3.51mm  1.14mm  11.11mm
#8-32        3.66mm  4.50mm  8.73mm   10.08mm  3.18mm  8.74mm   4.17mm  1.14mm  12.7mm
#10-24       4.09mm  5.11mm  9.53mm   11.00mm  3.18mm  10.13mm  4.83mm  1.14mm  14.29mm
#10-32       4.31mm  5.11mm  9.53mm   11.00mm  3.18mm  10.13mm  4.83mm  1.14mm  14.29mm
1/4-20       5.56MM  6.76MM  11.11mm  12.83mm  4.76mm  13.03mm  6.35mm  1.80mm  18.65
Notes:       1,2     2,3     4        5        6       7,8      8,9     8,10    8,11
----------------------------------------------------------------------------------------
Note  1: Thread hole is for tap or self thread of machine screw in soft material
Note  2: Hole dimensions are from littlemachineshop.com Tap Drill - 50% Thread column data
Note  3: Clearance hole data is littlemachineshop.com Clearance Drill - Standard Fit column data
Note  4: Nut Across Flat Diameter (AFD) metric data is ISO, inch is boltdepot.com US Nut Size table
Note  5: Nut round OD is the openSCAD circle diameter required to achieve a nut size at $fn=6
         Nut OD is calculated as = (NUT AFD)/cos(30)
Note  6: Nut height is for standard hex nut; jam nuts are less, lock nuts are more
         Metric data is ISO 4032, SAE data from boltdepot.com US Nut Size tables
Note  7: Head diameter varies with the head style; value shown is max across all except truss head
Note  8: Data from http://www.numberfactory.com/nf_metric.html or http://www.numberfactory.com/nf_inch.html
Note  9: Head height varies with head style; value shown is max across all styles
Note 10: Standard washer height or thickness
Note 11: Flat washer outer diameter
****************************************************************************************/
// Tailor fastener sizing and fit adjustments here
// All screw holes are implemented with MCAD polyhole - more accurate than hole by cylinder
// Dimensions shown were determined with Simplify3D as slicer, inside perimeters printed first
// Tolerance added to nominal corrects for extrusion width and other printer variations

// Box and lid screw hole parameters for tailoring
box_cp_hid = 2.44 + 0.1; // corner post hole inside diameter for screw threading (2.44 nominal for #4)
box_cp_hd = 10.0;        // corner post hole depth must be less than (box_sz - lid_sz)
lid_hid = 3.26 + 0.1;    // lid screw clearance hole inside diameter (3.26 nominal for #4)
lid_rid = 6.0 + 0.1;     // lid screw head recess inside diameter (6.0 nominal for #4)
lid_rd = 0.0;            // depth of lid head recess (lid_sz must be able to accomodate the recess)
tab_hid = 3.26 +0.1;     // inside diameter of the mounting tab clearance holes (3.26 nominal for #4)

////////// NON-USER PARAMETERS AND CALCULATIONS /////////////////////////////////
// Parameters beyond this point are normally not altered in basic tailoring
// Modify at your own risk

// Geometry mesh factors
MF = 0.01;       // Mesh Factor is the amount of overlap on geometries for proper mesh
MSA = MF;        // Mesh Single Adjustment factor (translate ends; size adjustment on single ended mesh)
MDA = 2*MF;      // Mesh Double Adjustment (size adjustment on double ended mesh like boring holes)

// calculated parameters
box_cp_od = box_cp_hid * 3 ;     // set outer diameter of the corner standoff post to 3*hole diameter
box_cp_or = box_cp_od / 2 ;      // outer radius of the corner standoff post
lip_w = box_cp_or - box_wt - fit_tol; // lid lip width set so inside of lip aligns lid hole centers
lip_arc_or = box_cp_od - box_wt; // outside radius of the corner arc on the lid lip
lip_arc_od = 2 * lip_arc_or;     // outside diameter of the corner arc on the lid lip
box_r = (3 * box_cp_hid) / 2;    // radius of the box corners automatically based on screw size

// Define matrix of lid hole locations
// Matrix starts with hole closest to x,y=0 and goes CCW around box
lid_hole_centers = [[           box_cp_or ,          box_cp_or , 0 ],
					[  box_sx - box_cp_or ,          box_cp_or , 0 ],
					[  box_sx - box_cp_or , box_sy - box_cp_or , 0 ],
					[           box_cp_or , box_sy - box_cp_or , 0 ]];

// Define matrix of mounting tab hole locations
// Matrix starts with hold closest to x,y=0 and goes CCW around box
tab_hole_centers = [[          -tab_sx/2 ,          tab_hoy , 0 ],
                    [  box_sx + tab_sx/2 ,          tab_hoy , 0 ],
                    [  box_sx + tab_sx/2 , box_sy - tab_hoy , 0 ],
                    [          -tab_sx/2 , box_sy - tab_hoy , 0 ]];

//////////////////////////////////////////////////////////////////////////////////////
// Top level geometry
// Edit true or false for obtaining box and lid respectively
difference() {
    rounded_cube_case(generate_box=true, generate_lid=true);
}

//----------------------------------------------------------
module standoff( post_od , post_id , post_h , hole_depth) {
  // Generates a standoff for mounting lid or circuit board
  // post_od: outer diamter of the standoff post
  // post_id: diameter of the inner hole in the standoff post
  // post_h: height of the standoff post
  // hole_depth: depth of the inner hole in the standoff post
  difference() {
    // start with a solid post
    // set $fn=25 to match what roundCornersCube does
    cylinder( d = post_od , h = post_h , center = false, $fn = 25);

    // remove the hole for the screw
    translate([ 0, 0, post_h - hole_depth ])
      polyhole( hole_depth + MSA, post_id );
  }  // end difference
}  // end module standoff

//----------------------------------------------------------
module cylindrical_lip( l_od , l_h , l_w ) {
  // Generate a quarter circle arc for use as a lip
  // l_od: outer diameter of the lip arc
  // l_h: height of the lip arc
  // l_w: width of the lip arc
  l_r = l_od / 2;   // calculate radius of the lip arc
  difference() {
    // start with a solid cylinder
    cylinder( r = l_r, h = l_h, center=false, $fs=0.01);

    // hollow it out to form a ring
    translate([ 0, 0, -MSA ])                    // -MSA to offset oversized cylinder being removed
      cylinder( r = l_r - l_w, h = l_h + MDA, center=false, $fs=0.01 ); // +MDA on h to ensure removal

    // remove all but a quarter of the ring
    translate([ -l_r, 0, -MSA ])                 // -MSA to offset oversized cube being removed
      cube([ l_od + MSA , l_r, l_h + MDA ]);     // oversize by +MSA or +MDA to ensure complete removal
    translate([ 0, -l_r, -MSA ])                 // -MSA to offset oversized cube being removed
      cube([ l_od + MSA, l_r + MSA, l_h + MDA ]);// oversize by +MSA or +MDA to ensure complete removal

  }  // end difference
}  // end module cylindrical_lip

//----------------------------------------------------------
module rounded_cube_case (generate_box, generate_lid) {
  // generate_box: true or false flag
  // generate_lid: true or false flag

  // Design rendering of box starts at x=0, y=0, z=0
  // When present, mounting tabs are always added as extensions on X axis
  // left-right-forward-rear references are with respect to axis in default view (+x to right, +y to rear)
  // If both box and lid are being generated, lid will be offset in Y to place it rearward of box

  if (generate_box == true) {    // we need to create the box part
    echo(str("Box outer size is x:",box_sx,"mm, y:",box_sy,"mm, z:",box_sz,"mm (with lid)"));
    echo(str("Available clearance between corner posts is x:",box_sx - 2*box_cp_od,"mm, y:",box_sy - 2*box_cp_od));
    echo(str("Available height inside closed box is ",box_sz - box_bt - lid_sz,"mm"));

    union() {
      difference() {
        union() {
          // Start with a solid cube with rounded corners
          // Z-axis height of box is reduced by lid thickness
          translate([ box_sx/2, box_sy/2, (box_sz - lid_sz)/2 ])
            roundCornersCube( box_sx, box_sy, box_sz - lid_sz, box_r );
          // Extend base for the mounting tabs if present
          if ( tab_use == true ) {
            echo(str("Mounting tabs of ",tab_sx,"mm increase x-axis size to ",box_sx + 2*tab_sx,"mm"));
            echo(str("Tab mounting hole spacing is x:",box_sx+tab_sx,"mm, y:",box_sy-2*tab_hoy,"mm"));
            difference () {
              // start with the plate for the mounting tabs
              translate ([ box_sx/2, box_sy/2, tab_sz/2 ])
                roundCornersCube ( box_sx + (tab_sx*2), box_sy, tab_sz - MSA, box_r );

              // now remove the mounting holes in the mounting tabs
              for(j = tab_hole_centers) {        // for each hole in the mounting tab
                translate([ 0, 0, -MSA ])        // shift Z to ensure complete removal of hole
                  translate(j)
                    polyhole ( tab_sz + MDA, tab_hid ); // oversize height to ensure complete removal
              }  // end for loop on mounting tab holes
            }  // end difference
          }  // end if tab_use
        }  // end union

        // Hollow out the box volume, leaving box_bt at bottom and box_wt at sides
        translate([ box_sx/2, box_sy/2, (box_sz - lid_sz)/2 + box_bt ])
          roundCornersCube( box_sx - (box_wt*2), box_sy - (box_wt*2), box_sz, box_r - box_wt);

        // Define any holes in the box walls here
        // Sample teardrop holes with bottom of round lobe at floor of box - uncomment and tailor as desired

        // hole for USB power plug
        translate([ -MDA, box_sy/2 - 5,  9 + box_bt ])  // left sidewall
          cube([box_bt + 2 * MDA, 11, 6]);

//*/

        // Sample round holes centered on available wall height - uncomment and tailor as desired
/*
        hole_r = 2;
        translate([ box_sx*2/3, box_wt/2, (box_sz-lid_sz-box_bt)/2 + box_bt ])  // forward sidewall
          rotate([90, 0, 0])
            cylinder( h=box_wt + MDA, r=hole_r, center=true, $fn=30 );
        translate([ box_sx - box_wt/2, box_sy*2/3, (box_sz-lid_sz-box_bt)/2 + box_bt ])  // right sidewall
          rotate([0, 90, 0])
            cylinder( h=box_wt + MDA, r=hole_r, center=true, $fn=30 );
        translate([ box_sx*2/3, box_sy - box_wt/2, (box_sz-lid_sz-box_bt)/2 + box_bt ])  // forward sidewall
          rotate([90, 0, 0])
            cylinder( h=box_wt + MDA, r=hole_r, center=true, $fn=30 );
        translate([ box_wt/2, box_sy*2/3, (box_sz-lid_sz-box_bt)/2 + box_bt ])  // left sidewall
          rotate([0, 90, 0])
            cylinder( h=box_wt + MDA, r=hole_r, center=true, $fn=30 );
//*/

      }  // end difference

      // Add the corner standoff posts for the lid screws
      for(i = lid_hole_centers) {
        translate([ 0, 0, box_bt - MSA ])  // raise up to the inside of the box
          translate(i)                     // locate a corner
            standoff( box_cp_od, box_cp_hid, box_sz - box_bt - lid_sz + MSA, box_cp_hd );
      }  // end for loop on standoff posts

      // Add any mounting standoff posts on the box bottom here
      // Sample mounting posts for small board centered in box - uncomment and tailor as desired

      bd_hsp_x = 91;  // board hole spacing in x axis
      bd_hsp_y = 45;    // board hole spacing in y axis
      bd_post_od = 6;   // diameter of the board mounting post
      bd_post_id = 2.7; // hole diameter in the mounting post (2.7 for threading M3)
      bd_post_h = 4;    // height of the board mounting posts
      echo(str("Available height above board posts is ",box_sz - box_bt - lid_sz - bd_post_h,"mm"));
      translate([ box_sx/2 - bd_hsp_x/2, box_sy/2 - bd_hsp_y/2, box_wt - MSA ])  // forward left corner
        standoff( post_od = bd_post_od, post_id=bd_post_id , post_h=bd_post_h , hole_depth=4); // for M3
      translate([ box_sx/2 + bd_hsp_x/2, box_sy/2 - bd_hsp_y/2, box_wt - MSA ])  // forward right
        standoff( post_od = bd_post_od, post_id=bd_post_id , post_h=bd_post_h , hole_depth=4); // for M3
      translate([ box_sx/2 + bd_hsp_x/2, box_sy/2 + bd_hsp_y/2, box_wt - MSA ])  // right rear
        standoff( post_od = bd_post_od, post_id=bd_post_id , post_h=bd_post_h , hole_depth=4); // for M3
      translate([ box_sx/2 - bd_hsp_x/2, box_sy/2 + bd_hsp_y/2, box_wt - MSA ])  // left rear
        standoff( post_od = bd_post_od, post_id=bd_post_id , post_h=bd_post_h , hole_depth=4); // for M3
//*/
    }  // End union

  }  // end if generate_box

  y_offset = generate_box ? box_sy+10 : 0; // offset lid Y by box_sy+10 if we are also doing box
  if ( generate_lid == true ) {        // we need to create the lid part
    translate([ 0, y_offset, 0]) {
      difference() {
        // create the base solids for the lid
        union() {
          // Start with a solid plate with rounded corners to match the box part
          translate([ box_sx/2, box_sy/2, lid_sz/2 ])
            roundCornersCube( box_sx, box_sy, lid_sz, box_r );

          // Add a reinforcement lip to the lid, starting with the straight portions
          // increase lip width by MSA to ensure mesh with quarter circle lip added below
          translate([ box_cp_od + fit_tol, box_wt + fit_tol, lid_sz ])
            cube([box_sx - (box_cp_od*2) - (fit_tol*2), lip_w + MSA, lip_h ]);
          translate([box_wt + fit_tol, box_cp_od + fit_tol, lid_sz])
            cube([ lip_w + MSA, box_sy - (box_cp_od*2) - (fit_tol*2), lip_h ]);
          translate([box_sx - box_cp_or - fit_tol, box_cp_od + fit_tol, lid_sz ])
            cube([lip_w + MSA, box_sy - (box_cp_od*2) - (fit_tol*2), lip_h ]);
          translate([box_cp_od + fit_tol, box_sy - box_cp_or - fit_tol, lid_sz])
            cube([box_sx - (box_cp_od*2) - (fit_tol*2), lip_w + MSA, lip_h]);

          // Fit a quarter circle lip around the corner mounting posts
          translate([ 0, 0, lid_sz])
            translate( lid_hole_centers[0] )
              rotate(180)
                cylindrical_lip( lip_arc_od, lip_h, lip_w );
          translate([ 0, 0, lid_sz ])
            translate( lid_hole_centers [1] )
              rotate(270)
                cylindrical_lip( lip_arc_od, lip_h, lip_w );
          translate([ 0, 0, lid_sz ])
            translate( lid_hole_centers [2] )
              cylindrical_lip( lip_arc_od, lip_h, lip_w );
          translate([ 0, 0, lid_sz ])
            translate( lid_hole_centers [3] )
              rotate(90)
                cylindrical_lip( lip_arc_od, lip_h, lip_w );
        }  // end union of the base solids for the lid

        for (i = lid_hole_centers) {               // for each corner in the lid
          // remove the material for the corner screw hole
          translate([ 0, 0, -MSA ])                // shift Z to ensure complete removal of hole
            translate(i)
              polyhole( lid_sz + MDA, lid_hid );   // height is +MDA to ensure complete removal
          if ( lid_rd > 0 ) {                      // we need to countersink the screw head
            // remove the material for the screw head recess
            translate([ 0, 0, -MSA ])              // -MSA to offset oversize hole being removed
              translate(i)
                polyhole( lid_rd + MSA, lid_rid ); // oversize +MSA to ensure complete removal
          }  // end if lid_rd
        }  // end for loop on lid holes

        // Add removal of any holes in the lid here
        // Sample square hole centered in lid - uncomment and tailor as desired

        // holes for buttons
        translate([ 20, box_sy*0.3, lid_sz/2 ])
          cube([ 19, 19, lid_sz + MDA], center=true );
        translate([ 20, box_sy*0.7, lid_sz/2 ])
          cube([ 19, 19, lid_sz + MDA], center=true );

        // hole for display
        translate([ 55, box_sy*0.5, lid_sz/2 ])
          cube([ 24, 15, lid_sz + MDA], center=true );

//*/
      }  // end difference for lid
    }  // end translate to move away from box if it is also being made
  }  // end if generate_lid

  echo(str("Maximum lid screw length is ", box_cp_hd + lid_sz - lid_rd,"mm"));

}  // end module rounded_cube_case

// Generic case and lid - Improved
// http://www.thingiverse.com/thing:643264
// Printbus, November 2016

// Originally remixed from dandmpye's Generic case (box and lid) http://www.thingiverse.com/thing:28608
//   Revised dimensioning for improved scalability
//   Reformatted and labels revised for readability
//   Added provision for mounting tabs
// Rev A incorporates several improvements in the design and with openSCAD utilization
//   Reduced interdependency between parameters through individual parameters that can be tailored
//     Box wall thickness, base thickness, mounting tab thickness, lid thickness, lid lip height
//   Added table of reference data for hardware dimensions
//   Improved mesh overlap to reduce "ghosting" in rendering view
//   Added informative echo outputs to openSCAD console
//   Revised path for custom function library
//   Added sample code for sidewall teardrop and round holes
//   Added sample code for board mounting standoffs
//
// To keep the design manageable...
//   The diameter of the lid corner posts is factored from the screw hole in the post
//   The radius of the box corners is set to match the lid corner posts
//   The inside edge of the straight portions of lid lip is aligned with center of corner screw holes
//     This means the lip width will reduce as the box side wall thickness increases
//
// Hint: To minimize infill artifacts on box sidewalls, try to adjust box_wt and slicer number of
//   solid perimeters so that no sidewall infill is required for your extrusion width

////////// REVISION HISTORY ///////////////////////////////////////////////////
// YYMMDD date code
// 150119 - initial publish
// 161104 - revA
//

////////// INCLUDE AND USE  ///////////////////////////////////////////////////
// NOTE: openSCAD version 2014.03 or newer required for MCAD library

// EXTERNAL FUNCTIONS USED
use <roundCornersCube.scad>
//  Get roundCornersCube.scad from http://www.thingiverse.com/thing:8812
//  Revise path location to the custom openSCAD function if not in standard custom library location
//  roundCornersCube (x,y,z,r) gives cube of size xyz with corner radius r
//  Note that cube center parameter is fixed at true, and the corner $fn is fixed at 25
//  MCAD roundedBox is not used - those results seem imperfect and doesn't set corner $fn
use <MCAD/polyholes.scad>
//  Requires openSCAD MCAD library to be installed (openSCAD 2014.03 or later)
//  polyhole(h,d) provides improved size control on small holes
//  cylinder center parameter defaults to false; $fn intentionally varies with size of hole
use <MCAD/teardrop.scad>
//  Requires openSCAD MCAD library to be installed (openSCAD 2014.03 or later)
//  teardrop(radius, length, angle) provides a sidewall hole more printable than round hole
//  Angle parameter of 90 degrees gives teardrop shape; unclear when another value would be used
//  Consists of a round lobe and a right angle at the teardrop point
//  radius refers to the radius of the round lobe
//  $fn on round lobe is forced to 30
//  length is really the thickness of the teardrop formed in the x-axis
//  overall size of the teardrop is radius * (1 + 1/sin(45)), or radius*2.414
//  For angle=90, round lobe will center at x,y,z=0. Teardrop builds on x-axis for length parameter

//////////////// PARAMETERS  ///////////////////////////////////////////////////
// All dimensions are millimeter
// Case parameters for tailoring
box_sx = 104;       // box outside size in X axis, not including mounting tabs
box_sy = 70;       // box outside size in Y axis
box_sz = 27;       // box outside size in Z axis, including the lid thickness
box_wt = 2.0;      // box wall thickness (keep less than box_cp_hid)
box_bt = 2.0;      // box base or bottom thickness (should be a multiple of printed layer height)

// Lid parameters for tailoring
lid_sz = 2.0;      // lid size in Z axis or lid thickness (should be a multiple of printed layer height)
lip_h = 1.60;      // lid lip height (should be a multiple of layer height)
fit_tol = 0.40;    // fit tolerance or clearance between box and lid

// Mounting tab parameters for tailoring
tab_use = true;     // true or false for mounting tab on x-axis yes/no
tab_sz = box_bt;    // tab size in z-axis or height (typically set same as box base thickness)
tab_sx = 8;         // mounting tab size in x-axis (the amount tab extends from box)
tab_hoy = 7;        // tab hole offset in y axis from edge of tab

// Fastener and hardware component hole sizing
/*====================================================
Standard hardware dimension reference data
These are a reference point only; adjustment may be necessary due to nozzle size, etc.
Also note that MCAD polyhole does better at actual diameters than circle/cylinder

SIZE         THREAD  CLEAR   NUT AFD  NUT OD   NUT     HEAD     HEAD    WASHER  FLAT
             HOLE    HOLE    WRENCH   @fn=6    HEIGHT  OD       HEIGHT  HEIGHT  OD
-----------  ------  ------  -------  -------  ------  ------   ------  ------  -----
M2 x 0.4     1.75mm  2.20mm  4.0mm    4.62mm   1.6mm   4.0mm    2.0mm   0.3mm   5.5mm
M2.5 x 0.45  2.20mm  2.75mm  5.0mm    5.77mm   2.0mm   5.0mm    2.5mm   0.3mm   6.0mm
M3 x 0.5     2.70mm  3.30mm  5.5mm    6.35mm   2.4mm   6.0mm    3.0mm   0.5mm   7.0mm
M4 x 0.7     3.50mm  4.40mm  7.0mm    8.08mm   3.2mm   8.0mm    4.0mm   0.8mm   9.0mm
M5 x 0.8     4.50mm  5.50mm  8.0mm    9.24mm   4.7mm   10.0mm   5.0mm   1.0mm   10.0mm
M6 x 1.0     5.50mm  6.60mm  10.0mm   11.55mm  5.2mm   12.0mm   6.0mm   1.6mm   12.0mm
M8 x 1.25    7.20mm  8.80mm  13.0mm   15.01mm  6.8mm   16.0mm   8.0mm   2.0mm   17.0mm
#2-56        1.85mm  2.44mm  4.76mm   5.50mm   1.59mm  4.60mm   2.18mm  0.91mm  6.35mm
#3-56        2.26mm  2.79mm  4.76mm   5.50mm   1.59mm  5.28mm   2.51mm  0.91mm  7.94mm
#4-40        2.44mm  3.26mm  6.35mm   7.33mm   2.38mm  5.97mm   2.85mm  1.14mm  9.53mm
#6-32        2.95mm  3.80mm  7.94mm   9.17mm   2.78mm  7.37mm   3.51mm  1.14mm  11.11mm
#8-32        3.66mm  4.50mm  8.73mm   10.08mm  3.18mm  8.74mm   4.17mm  1.14mm  12.7mm
#10-24       4.09mm  5.11mm  9.53mm   11.00mm  3.18mm  10.13mm  4.83mm  1.14mm  14.29mm
#10-32       4.31mm  5.11mm  9.53mm   11.00mm  3.18mm  10.13mm  4.83mm  1.14mm  14.29mm
1/4-20       5.56MM  6.76MM  11.11mm  12.83mm  4.76mm  13.03mm  6.35mm  1.80mm  18.65
Notes:       1,2     2,3     4        5        6       7,8      8,9     8,10    8,11
----------------------------------------------------------------------------------------
Note  1: Thread hole is for tap or self thread of machine screw in soft material
Note  2: Hole dimensions are from littlemachineshop.com Tap Drill - 50% Thread column data
Note  3: Clearance hole data is littlemachineshop.com Clearance Drill - Standard Fit column data
Note  4: Nut Across Flat Diameter (AFD) metric data is ISO, inch is boltdepot.com US Nut Size table
Note  5: Nut round OD is the openSCAD circle diameter required to achieve a nut size at $fn=6
         Nut OD is calculated as = (NUT AFD)/cos(30)
Note  6: Nut height is for standard hex nut; jam nuts are less, lock nuts are more
         Metric data is ISO 4032, SAE data from boltdepot.com US Nut Size tables
Note  7: Head diameter varies with the head style; value shown is max across all except truss head
Note  8: Data from http://www.numberfactory.com/nf_metric.html or http://www.numberfactory.com/nf_inch.html
Note  9: Head height varies with head style; value shown is max across all styles
Note 10: Standard washer height or thickness
Note 11: Flat washer outer diameter
****************************************************************************************/
// Tailor fastener sizing and fit adjustments here
// All screw holes are implemented with MCAD polyhole - more accurate than hole by cylinder
// Dimensions shown were determined with Simplify3D as slicer, inside perimeters printed first
// Tolerance added to nominal corrects for extrusion width and other printer variations

// Box and lid screw hole parameters for tailoring
box_cp_hid = 2.44 + 0.1; // corner post hole inside diameter for screw threading (2.44 nominal for #4)
box_cp_hd = 10.0;        // corner post hole depth must be less than (box_sz - lid_sz)
lid_hid = 3.26 + 0.1;    // lid screw clearance hole inside diameter (3.26 nominal for #4)
lid_rid = 6.0 + 0.1;     // lid screw head recess inside diameter (6.0 nominal for #4)
lid_rd = 0.0;            // depth of lid head recess (lid_sz must be able to accomodate the recess)
tab_hid = 3.26 +0.1;     // inside diameter of the mounting tab clearance holes (3.26 nominal for #4)

////////// NON-USER PARAMETERS AND CALCULATIONS /////////////////////////////////
// Parameters beyond this point are normally not altered in basic tailoring
// Modify at your own risk

// Geometry mesh factors
MF = 0.01;       // Mesh Factor is the amount of overlap on geometries for proper mesh
MSA = MF;        // Mesh Single Adjustment factor (translate ends; size adjustment on single ended mesh)
MDA = 2*MF;      // Mesh Double Adjustment (size adjustment on double ended mesh like boring holes)

// calculated parameters
box_cp_od = box_cp_hid * 3 ;     // set outer diameter of the corner standoff post to 3*hole diameter
box_cp_or = box_cp_od / 2 ;      // outer radius of the corner standoff post
lip_w = box_cp_or - box_wt - fit_tol; // lid lip width set so inside of lip aligns lid hole centers
lip_arc_or = box_cp_od - box_wt; // outside radius of the corner arc on the lid lip
lip_arc_od = 2 * lip_arc_or;     // outside diameter of the corner arc on the lid lip
box_r = (3 * box_cp_hid) / 2;    // radius of the box corners automatically based on screw size

// Define matrix of lid hole locations
// Matrix starts with hole closest to x,y=0 and goes CCW around box
lid_hole_centers = [[           box_cp_or ,          box_cp_or , 0 ],
					[  box_sx - box_cp_or ,          box_cp_or , 0 ],
					[  box_sx - box_cp_or , box_sy - box_cp_or , 0 ],
					[           box_cp_or , box_sy - box_cp_or , 0 ]];

// Define matrix of mounting tab hole locations
// Matrix starts with hold closest to x,y=0 and goes CCW around box
tab_hole_centers = [[          -tab_sx/2 ,          tab_hoy , 0 ],
                    [  box_sx + tab_sx/2 ,          tab_hoy , 0 ],
                    [  box_sx + tab_sx/2 , box_sy - tab_hoy , 0 ],
                    [          -tab_sx/2 , box_sy - tab_hoy , 0 ]];

//////////////////////////////////////////////////////////////////////////////////////
// Top level geometry
// Edit true or false for obtaining box and lid respectively
difference() {
    rounded_cube_case(generate_box=true, generate_lid=true);
}

//----------------------------------------------------------
module standoff( post_od , post_id , post_h , hole_depth) {
  // Generates a standoff for mounting lid or circuit board
  // post_od: outer diamter of the standoff post
  // post_id: diameter of the inner hole in the standoff post
  // post_h: height of the standoff post
  // hole_depth: depth of the inner hole in the standoff post
  difference() {
    // start with a solid post
    // set $fn=25 to match what roundCornersCube does
    cylinder( d = post_od , h = post_h , center = false, $fn = 25);

    // remove the hole for the screw
    translate([ 0, 0, post_h - hole_depth ])
      polyhole( hole_depth + MSA, post_id );
  }  // end difference
}  // end module standoff

//----------------------------------------------------------
module cylindrical_lip( l_od , l_h , l_w ) {
  // Generate a quarter circle arc for use as a lip
  // l_od: outer diameter of the lip arc
  // l_h: height of the lip arc
  // l_w: width of the lip arc
  l_r = l_od / 2;   // calculate radius of the lip arc
  difference() {
    // start with a solid cylinder
    cylinder( r = l_r, h = l_h, center=false, $fs=0.01);

    // hollow it out to form a ring
    translate([ 0, 0, -MSA ])                    // -MSA to offset oversized cylinder being removed
      cylinder( r = l_r - l_w, h = l_h + MDA, center=false, $fs=0.01 ); // +MDA on h to ensure removal

    // remove all but a quarter of the ring
    translate([ -l_r, 0, -MSA ])                 // -MSA to offset oversized cube being removed
      cube([ l_od + MSA , l_r, l_h + MDA ]);     // oversize by +MSA or +MDA to ensure complete removal
    translate([ 0, -l_r, -MSA ])                 // -MSA to offset oversized cube being removed
      cube([ l_od + MSA, l_r + MSA, l_h + MDA ]);// oversize by +MSA or +MDA to ensure complete removal

  }  // end difference
}  // end module cylindrical_lip

//----------------------------------------------------------
module rounded_cube_case (generate_box, generate_lid) {
  // generate_box: true or false flag
  // generate_lid: true or false flag

  // Design rendering of box starts at x=0, y=0, z=0
  // When present, mounting tabs are always added as extensions on X axis
  // left-right-forward-rear references are with respect to axis in default view (+x to right, +y to rear)
  // If both box and lid are being generated, lid will be offset in Y to place it rearward of box

  if (generate_box == true) {    // we need to create the box part
    echo(str("Box outer size is x:",box_sx,"mm, y:",box_sy,"mm, z:",box_sz,"mm (with lid)"));
    echo(str("Available clearance between corner posts is x:",box_sx - 2*box_cp_od,"mm, y:",box_sy - 2*box_cp_od));
    echo(str("Available height inside closed box is ",box_sz - box_bt - lid_sz,"mm"));

    union() {
      difference() {
        union() {
          // Start with a solid cube with rounded corners
          // Z-axis height of box is reduced by lid thickness
          translate([ box_sx/2, box_sy/2, (box_sz - lid_sz)/2 ])
            roundCornersCube( box_sx, box_sy, box_sz - lid_sz, box_r );
          // Extend base for the mounting tabs if present
          if ( tab_use == true ) {
            echo(str("Mounting tabs of ",tab_sx,"mm increase x-axis size to ",box_sx + 2*tab_sx,"mm"));
            echo(str("Tab mounting hole spacing is x:",box_sx+tab_sx,"mm, y:",box_sy-2*tab_hoy,"mm"));
            difference () {
              // start with the plate for the mounting tabs
              translate ([ box_sx/2, box_sy/2, tab_sz/2 ])
                roundCornersCube ( box_sx + (tab_sx*2), box_sy, tab_sz - MSA, box_r );

              // now remove the mounting holes in the mounting tabs
              for(j = tab_hole_centers) {        // for each hole in the mounting tab
                translate([ 0, 0, -MSA ])        // shift Z to ensure complete removal of hole
                  translate(j)
                    polyhole ( tab_sz + MDA, tab_hid ); // oversize height to ensure complete removal
              }  // end for loop on mounting tab holes
            }  // end difference
          }  // end if tab_use
        }  // end union

        // Hollow out the box volume, leaving box_bt at bottom and box_wt at sides
        translate([ box_sx/2, box_sy/2, (box_sz - lid_sz)/2 + box_bt ])
          roundCornersCube( box_sx - (box_wt*2), box_sy - (box_wt*2), box_sz, box_r - box_wt);

        // Define any holes in the box walls here
        // Sample teardrop holes with bottom of round lobe at floor of box - uncomment and tailor as desired

        teardrop_r = 3.5;  // radius for round end of teardrop
        translate([ -MDA, box_sy/2 - 5,  9 + box_bt ])  // left sidewall
          cube([box_bt + 2 * MDA, 11, 6]);

//*/

        // Sample round holes centered on available wall height - uncomment and tailor as desired
/*
        hole_r = 2;
        translate([ box_sx*2/3, box_wt/2, (box_sz-lid_sz-box_bt)/2 + box_bt ])  // forward sidewall
          rotate([90, 0, 0])
            cylinder( h=box_wt + MDA, r=hole_r, center=true, $fn=30 );
        translate([ box_sx - box_wt/2, box_sy*2/3, (box_sz-lid_sz-box_bt)/2 + box_bt ])  // right sidewall
          rotate([0, 90, 0])
            cylinder( h=box_wt + MDA, r=hole_r, center=true, $fn=30 );
        translate([ box_sx*2/3, box_sy - box_wt/2, (box_sz-lid_sz-box_bt)/2 + box_bt ])  // forward sidewall
          rotate([90, 0, 0])
            cylinder( h=box_wt + MDA, r=hole_r, center=true, $fn=30 );
        translate([ box_wt/2, box_sy*2/3, (box_sz-lid_sz-box_bt)/2 + box_bt ])  // left sidewall
          rotate([0, 90, 0])
            cylinder( h=box_wt + MDA, r=hole_r, center=true, $fn=30 );
//*/

      }  // end difference

      // Add the corner standoff posts for the lid screws
      for(i = lid_hole_centers) {
        translate([ 0, 0, box_bt - MSA ])  // raise up to the inside of the box
          translate(i)                     // locate a corner
            standoff( box_cp_od, box_cp_hid, box_sz - box_bt - lid_sz + MSA, box_cp_hd );
      }  // end for loop on standoff posts

      // Add any mounting standoff posts on the box bottom here
      // Sample mounting posts for small board centered in box - uncomment and tailor as desired

      bd_hsp_x = 91;  // board hole spacing in x axis
      bd_hsp_y = 45;    // board hole spacing in y axis
      bd_post_od = 6;   // diameter of the board mounting post
      bd_post_id = 2.7; // hole diameter in the mounting post (2.7 for threading M3)
      bd_post_h = 4;    // height of the board mounting posts
      echo(str("Available height above board posts is ",box_sz - box_bt - lid_sz - bd_post_h,"mm"));
      translate([ box_sx/2 - bd_hsp_x/2, box_sy/2 - bd_hsp_y/2, box_wt - MSA ])  // forward left corner
        standoff( post_od = bd_post_od, post_id=bd_post_id , post_h=bd_post_h , hole_depth=4); // for M3
      translate([ box_sx/2 + bd_hsp_x/2, box_sy/2 - bd_hsp_y/2, box_wt - MSA ])  // forward right
        standoff( post_od = bd_post_od, post_id=bd_post_id , post_h=bd_post_h , hole_depth=4); // for M3
      translate([ box_sx/2 + bd_hsp_x/2, box_sy/2 + bd_hsp_y/2, box_wt - MSA ])  // right rear
        standoff( post_od = bd_post_od, post_id=bd_post_id , post_h=bd_post_h , hole_depth=4); // for M3
      translate([ box_sx/2 - bd_hsp_x/2, box_sy/2 + bd_hsp_y/2, box_wt - MSA ])  // left rear
        standoff( post_od = bd_post_od, post_id=bd_post_id , post_h=bd_post_h , hole_depth=4); // for M3
//*/
    }  // End union

  }  // end if generate_box

  y_offset = generate_box ? box_sy+10 : 0; // offset lid Y by box_sy+10 if we are also doing box
  if ( generate_lid == true ) {        // we need to create the lid part
    translate([ 0, y_offset, 0]) {
      difference() {
        // create the base solids for the lid
        union() {
          // Start with a solid plate with rounded corners to match the box part
          translate([ box_sx/2, box_sy/2, lid_sz/2 ])
            roundCornersCube( box_sx, box_sy, lid_sz, box_r );

          // Add a reinforcement lip to the lid, starting with the straight portions
          // increase lip width by MSA to ensure mesh with quarter circle lip added below
          translate([ box_cp_od + fit_tol, box_wt + fit_tol, lid_sz ])
            cube([box_sx - (box_cp_od*2) - (fit_tol*2), lip_w + MSA, lip_h ]);
          translate([box_wt + fit_tol, box_cp_od + fit_tol, lid_sz])
            cube([ lip_w + MSA, box_sy - (box_cp_od*2) - (fit_tol*2), lip_h ]);
          translate([box_sx - box_cp_or - fit_tol, box_cp_od + fit_tol, lid_sz ])
            cube([lip_w + MSA, box_sy - (box_cp_od*2) - (fit_tol*2), lip_h ]);
          translate([box_cp_od + fit_tol, box_sy - box_cp_or - fit_tol, lid_sz])
            cube([box_sx - (box_cp_od*2) - (fit_tol*2), lip_w + MSA, lip_h]);

          // Fit a quarter circle lip around the corner mounting posts
          translate([ 0, 0, lid_sz])
            translate( lid_hole_centers[0] )
              rotate(180)
                cylindrical_lip( lip_arc_od, lip_h, lip_w );
          translate([ 0, 0, lid_sz ])
            translate( lid_hole_centers [1] )
              rotate(270)
                cylindrical_lip( lip_arc_od, lip_h, lip_w );
          translate([ 0, 0, lid_sz ])
            translate( lid_hole_centers [2] )
              cylindrical_lip( lip_arc_od, lip_h, lip_w );
          translate([ 0, 0, lid_sz ])
            translate( lid_hole_centers [3] )
              rotate(90)
                cylindrical_lip( lip_arc_od, lip_h, lip_w );
        }  // end union of the base solids for the lid

        for (i = lid_hole_centers) {               // for each corner in the lid
          // remove the material for the corner screw hole
          translate([ 0, 0, -MSA ])                // shift Z to ensure complete removal of hole
            translate(i)
              polyhole( lid_sz + MDA, lid_hid );   // height is +MDA to ensure complete removal
          if ( lid_rd > 0 ) {                      // we need to countersink the screw head
            // remove the material for the screw head recess
            translate([ 0, 0, -MSA ])              // -MSA to offset oversize hole being removed
              translate(i)
                polyhole( lid_rd + MSA, lid_rid ); // oversize +MSA to ensure complete removal
          }  // end if lid_rd
        }  // end for loop on lid holes

        // Add removal of any holes in the lid here
        // Sample square hole centered in lid - uncomment and tailor as desired

        translate([ 20, box_sy*0.3, lid_sz/2 ])
         cube([ 14, 14, lid_sz + MDA], center=true );
        translate([ 20 + 14, box_sy*0.3, lid_sz/8 ])
          cube([ 6, 2, lid_sz], center=true );

        translate([ 20, box_sy*0.7, lid_sz/2 ])
          cube([ 14, 14, lid_sz + MDA], center=true );
        translate([ 20 + 14, box_sy*0.7, lid_sz/8 ])
          cube([ 6, 2, lid_sz], center=true );
        translate([ 20 + 14, box_sy*0.7, lid_sz/8 ])
          cube([ 2, 6, lid_sz], center=true );

        translate([ 60, box_sy*0.5, lid_sz/2 ])
          cube([ 24, 15, lid_sz + MDA], center=true );

      }  // end difference for lid
    }  // end translate to move away from box if it is also being made
  }  // end if generate_lid

  echo(str("Maximum lid screw length is ", box_cp_hd + lid_sz - lid_rd,"mm"));

}  // end module rounded_cube_case


increase the $fn value

increase the $fn value

// This arc module is from this thing on Thingiverse:
//		https://www.thingiverse.com/thing:1092611

module arc(radius, thick, angle)
{
	intersection()
	{
		union()
		{
			rights = floor(angle/90);
			remain = angle-rights*90;
			if(angle > 90)
			{
				for(i = [0:rights-1]){
					rotate(i*90-(rights-1)*90/2){
						polygon([[0, 0], [radius+thick, (radius+thick)*tan(90/2)], [radius+thick, -(radius+thick)*tan(90/2)]]);
					}
				}
				rotate(-(rights)*90/2)
					polygon([[0, 0], [radius+thick, 0], [radius+thick, -(radius+thick)*tan(remain/2)]]);
				rotate((rights)*90/2)
					polygon([[0, 0], [radius+thick, (radius+thick)*tan(remain/2)], [radius+thick, 0]]);
			}
			else
			{
				polygon([[0, 0], [radius+thick, (radius+thick)*tan(angle/2)], [radius+thick, -(radius+thick)*tan(angle/2)]]);
			}
		}
		difference(){
			circle(radius+thick, $fn = 40);
			circle(radius, $fn = 40);
		}
	}
}

//arc(10, 3, 300);

// This arc module is from this thing on Thingiverse:
//		https://www.thingiverse.com/thing:1092611

module arc(radius, thick, angle)
{
	intersection()
	{
		union()
		{
			rights = floor(angle/90);
			remain = angle-rights*90;
			if(angle > 90)
			{
				for(i = [0:rights-1]){
					rotate(i*90-(rights-1)*90/2){
						polygon([[0, 0], [radius+thick, (radius+thick)*tan(90/2)], [radius+thick, -(radius+thick)*tan(90/2)]]);
					}
				}
				rotate(-(rights)*90/2)
					polygon([[0, 0], [radius+thick, 0], [radius+thick, -(radius+thick)*tan(remain/2)]]);
				rotate((rights)*90/2)
					polygon([[0, 0], [radius+thick, (radius+thick)*tan(remain/2)], [radius+thick, 0]]);
			}
			else
			{
				polygon([[0, 0], [radius+thick, (radius+thick)*tan(angle/2)], [radius+thick, -(radius+thick)*tan(angle/2)]]);
			}
		}
		difference(){
			circle(radius+thick);
			circle(radius);
		}
	}
}

//arc(10, 3, 300);


Mask opening tweaks.

Mask opening tweaks.

// 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 = 75;
view_opening_right = 85;
view_opening_width = depth-thickness - view_opening_left - view_opening_right;
view_opening_bottom = 30;
view_opening_height = 90;

// 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+20, 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_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+20, 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]);
                }
}


Fix bug in rounded hanging supports.

Fix bug in rounded hanging supports.

// fishbox.scad
//  - Enclosure design for zebrafish Skinner box
//
//  Author: Mark Liffiton
//  Date: Oct, 2014 - Jan, 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:
window_thickness = 2.4;  // 2.4mm = 0.094" (looks good based on cut test piece for acrylic slotting into itself)

// 1/8" 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

// 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 light holder, 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;

// light bar
light_bar_width = 30;
light_pos_x = 50;
light_height = height-thickness*2-8;

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

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

// for slight offsets/tweaks
epsilon = 1;

// 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_LIGHT_BAR) {
    projection() light_bar();
} else if (DXF_CAMERA_SUPPORT) {
    projection() rotate(a=[90,0,0]) camera_supports(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.
    side(y=0);
    side(y=depth);
    tank_base();
    light_bar();
    camera_supports(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 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_supports(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(up=true);
            translate([0, 0, 3/4*height])
                rounded_truncation(up=true);
        }
    }
}

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

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

module camera_opening() {
    translate([width, depth/2, height/2])
        cube([thickness*2,10,10], center=true);
}

module camera_supports(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, $fn=50);
        }
        translate([-x/2+radius, 50, z/2-radius])
        rotate(a=[90,-90,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100, $fn=50);
        }
        translate([x/2-radius, 50, -z/2+radius])
        rotate(a=[90,90,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100, $fn=50);
        }
        translate([-x/2+radius, 50, -z/2+radius])
        rotate(a=[90,180,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100, $fn=50);
        }
    }
}

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() {
    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]);
    }
}

module light_bar_opening() {
    // A wider opening, so the bar can slide out of the way of placing or removing the fish tank
    translate([light_pos_x-light_bar_width/4, -outset, light_height])
        cube([light_bar_width*2, depth+outset*2, thickness]);
}

module light_bar() {
    translate([light_pos_x-light_bar_width/2, -outset, light_height])
    difference() {
        cube([light_bar_width, depth+outset*2, thickness]);
        cutouts(2,light_bar_width,outset,rot=0,trans=[light_bar_width/2,0,0]);
        cutouts(2,light_bar_width,outset,rot=180,trans=[light_bar_width/2,depth+outset*2,0]);
    }
}

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, $fn=50);
}

module side(y=0) {
    difference() {
        side_base(y);
        scale([1,1,0.5])
            vert_face_base(0, window_thickness);
        scale([1,1,0.5])
            vert_face_base(width);
        light_bar_opening();
        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(up=false, rot=[0,0,-90]);
        translate([0, 0, 0])
            rounded_truncation(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 - Jan, 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:
window_thickness = 2.4;  // 2.4mm = 0.094" (looks good based on cut test piece for acrylic slotting into itself)

// 1/8" 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

// 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 light holder, 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;

// light bar
light_bar_width = 30;
light_pos_x = 50;
light_height = height-thickness*2-8;

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

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

// for slight offsets/tweaks
epsilon = 1;

// 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_LIGHT_BAR) {
    projection() light_bar();
} else if (DXF_CAMERA_SUPPORT) {
    projection() rotate(a=[90,0,0]) camera_supports(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.
    side(y=0);
    side(y=depth);
    tank_base();
    light_bar();
    camera_supports(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 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_supports(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(up=true);
            translate([0, 0, 3/4*height])
                rounded_truncation(up=true);
        }
    }
}

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

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

module camera_opening() {
    translate([width, depth/2, height/2])
        cube([thickness*2,10,10], center=true);
}

module camera_supports(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=inner_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, $fn=50);
        }
        translate([-x/2+radius, 50, z/2-radius])
        rotate(a=[90,-90,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100, $fn=50);
        }
        translate([x/2-radius, 50, -z/2+radius])
        rotate(a=[90,90,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100, $fn=50);
        }
        translate([-x/2+radius, 50, -z/2+radius])
        rotate(a=[90,180,0])
        difference() {
            cube([radius,radius,100]);
            cylinder(r=radius, h=100, $fn=50);
        }
    }
}

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() {
    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]);
    }
}

module light_bar_opening() {
    // A wider opening, so the bar can slide out of the way of placing or removing the fish tank
    translate([light_pos_x-light_bar_width/4, -outset, light_height])
        cube([light_bar_width*2, depth+outset*2, thickness]);
}

module light_bar() {
    translate([light_pos_x-light_bar_width/2, -outset, light_height])
    difference() {
        cube([light_bar_width, depth+outset*2, thickness]);
        cutouts(2,light_bar_width,outset,rot=0,trans=[light_bar_width/2,0,0]);
        cutouts(2,light_bar_width,outset,rot=180,trans=[light_bar_width/2,depth+outset*2,0]);
    }
}

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, $fn=50);
}

module side(y=0) {
    difference() {
        side_base(y);
        scale([1,1,0.5])
            vert_face_base(0, window_thickness);
        scale([1,1,0.5])
            vert_face_base(width);
        light_bar_opening();
        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(up=false, rot=[0,0,-90]);
        translate([0, 0, 0])
            rounded_truncation(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]);
                }
}


Update Citosyne.scad

Update Citosyne.scad


widen the shade

widen the shade

bottomOuterRadius = 50;

intersection_xTranslate = -58;

outerRadius = 90;

squareLength = 59;

top_zTranslate = 1;

// this narrows the top (and bottom a little)
xScale = 0.35;  // 0.3;

// this shortens the height and widens the base
yScale = 0.7;   // 0.7;

use <../christmas-story-lamp-shade.scad>

//translate([-120, 0, 0])
translate([9, 18, 0])
%import("/home/roberto/Versioning/world/betoland/household/lamps/christmas-story-leg-lamp/jetpuf/files/LegLamp_Shade130.stl");

legLampShade(intersection_xTranslate = intersection_xTranslate,
             bottomOuterRadius = bottomOuterRadius,
             outerRadius = outerRadius,
             squareLength = squareLength,
             top_zTranslate = top_zTranslate,
             xScale = xScale,
             yScale = yScale);

bottomOuterRadius = 52;

intersection_xTranslate = -58;

outerRadius = 90;

squareLength = 59;

top_zTranslate = 1;

xScale = 0.2;

yScale = 0.7;

use <../christmas-story-lamp-shade.scad>

//translate([-120, 0, 0])
// translate([9, 18, 0])
// %import("/home/roberto/Versioning/world/betoland/household/lamps/christmas-story-leg-lamp/jetpuf/files/LegLamp_Shade130.stl");

legLampShade(intersection_xTranslate = intersection_xTranslate,
             bottomOuterRadius = bottomOuterRadius,
             outerRadius = outerRadius,
             squareLength = squareLength,
             top_zTranslate = top_zTranslate,
             xScale = xScale,
             yScale = yScale);


right wall is a mirror of the left one

right wall is a mirror of the left one

use <leftWall.scad>

module rightWall()
{
  mirror([0,1,0])
    leftWall();
}

rightWall();

error: pathspec 'navibot_sr8980_docking_station_walls/rightWall.scad' did not match any file(s) known to git


bottle adapter is no longer needed, as the 25mm hose fits perfectly

eps=0.01;
wall=1;
hose_phi=6;
bottle_screw_phi=27.4 + 0.6;
bottle_screw_h=9;
narrowing_h=10;

module adapter()
{
  // bottom ring
  difference()
  {
    $fn=200;
    cylinder(d=bottle_screw_phi+2*wall, h=bottle_screw_h);
    translate([0,0,-eps])
      cylinder(d=bottle_screw_phi, h=bottle_screw_h+2*eps);
  }
  translate([0, 0, bottle_screw_h])
  {
    // adapter
    difference()
    {
      $fn=100;
      cylinder(d2=hose_phi, d1=bottle_screw_phi+2*wall, h=10);
      translate([0,0,-eps])
        cylinder(d2=hose_phi-2*wall, d1=bottle_screw_phi, h=10+eps);
    }
    // top connector
    translate([0, 0, narrowing_h])
      difference()
      {
        $fn=100;
        cylinder(d=hose_phi, h=10);
        translate([0,0,-eps])
          cylinder(d=hose_phi-2*wall, h=10+2*eps);
      }
  }
}

adapter();