[3d] Remove edge stop and pi screws from Zero variant

// preview[view:northeast, tilt:topdiagonal]

/* [Options] */
// Pit probe type
Control_Probe = "Thermocouple"; // [Thermocouple,Thermistor]
// Raspberry Pi Model
Pi_Model = "3B/2B/1B+"; // [3B/2B/1B+,Connectorless,1A+,Zero]
// Which case halves
Pieces = "Both"; // [Both,Top,Bottom]
// Thickness of side walls (mm) - Set to trace width multiple
wall = 3.0;
/* [Advanced] */
// Thickness of top and bottom faces
wall_t = 2;
// External corner radius on the body
body_corner_radius = 4;
// Height of the body edge bead chamfer
body_chamfer_height = 1.5;

/* [Hidden] */
w = inch(3.75)+0.5; // overall interior case width
d = inch(3.75)-0.0; // overall interior case depth
h_b = 32; // overall interior case height

probe_centerline = 9.0; // case is split along probe centerline on the probe side
case_split = 12.4;  // and the case split on the other 3 sides

pic_ex = 2;
lcd_mount_t = 7;

body_chamfer_height_t = body_chamfer_height;
body_chamfer_height_b = body_chamfer_height;

e = 0.01;

hm43_split();

function inch(x) = x*25.4;

module cube_fillet_chamfer(size,f,c,$fn=32) {
  hull() {
    translate([f,f,c]) linear_extrude(height=size[2]-2*c) minkowski() {
      square([size[0]-2*f, size[1]-2*f]);
      circle(r=f, $fn=$fn);
    }
    translate([f+c,f+c,0]) linear_extrude(height=size[2]) minkowski() {
      square([size[0]-2*(f+c), size[1]-2*(f+c)]);
      circle(r=f, $fn=$fn);
    }
  }
}

module jhole(d, h) {
  translate([0,0,h/2]) cube([2*(wall+pic_ex),d,h], center=true);
}

module phole() {
  dia=4.5;
  // Hole for thermistor probe jack
  rotate([0,90,0]) cylinder(2*wall, d=dia, $fn=16);
  // because 3d print tends to curl up slightly between the probe holes,
  // do not go right to the edge on the final layer (stop 0.25mm short on each side)
  translate([0,-(dia+0.5)/2, -0.3]) cube([2*wall, dia+0.5, 0.6]);
}

module screwhole() {
  // Screw hole for bottom of case (including nut)
  //translate([0,0,-e]) cylinder(h_b+wall_t, d=3.4, $fn=18);
  translate([0,0,-e]) {
    cylinder(0.4, d1=7.4, d2=6.6, $fn=6);
    translate([0,0,0.4]) cylinder(3, d=6.6, $fn=6);
  }
  translate([0,0,3+0.2]) cylinder(h_b+2*wall_t, d=3.4, $fn=18);
  translate([0,0,h_b+wall_t]) cylinder(3, d=5.5, $fn=18);
}

module screwhole2() {
  translate([0,0,-e]) cylinder(3, d=6, $fn=18);
  translate([0,0,3+0.4]) cylinder(h_b-3-0.4, d=3.4, $fn=18);
}

module pic_ex_cube() {
  translate([0,33.75,2])
    cube_fillet([pic_ex+e, 59.5, 20.8],
      vertical=[0, pic_ex, pic_ex, 0],
      top=[0,pic_ex,0,0],
      bottom=[0,pic_ex,0,0]);
}

module screw_pimount() {
  // 2mm height is good for screw holes
  difference() {
    cylinder(h=2.25, d=6.4, $fn=18);
    //cylinder(h=2+e, d=2.5, $fn=16);
  }
}

module btn_rnd(dia=10) {
  cylinder(wall_t+2*e, d1=dia, d2=dia+1.5*wall_t, $fn=24);
  translate([-6.5,-6.5,0]) cube([13,13,0.75+e]);
}

module btn_square(sq=13) {
  translate([-sq/2,-sq/2,0]) cube([sq,sq,wall_t+2*e]);
}

module tc_plusminus() {
  T=0.75+e;
  H=2;
  W=6;
  // Minus
  translate([0,2,0])
    cube_fillet([T, W, H], top=[0,0,0,T]);
  translate([0,-(2+W),0]) {
    cube_fillet([T, W, H], top=[0,0,0,T]);
    translate([0,(W-H)/2,(H-W)/2])
      cube_fillet([T, H, W], top=[0,0,0,T]);
  }
}

module led_hole() {
  cylinder(wall_t+2*e, d=3.4, $fn=16);
}

module nuttrap() {
  ww_w=3;
  ww_d=wall;
  nut_h=3.0;
  nut_ingress = 5.7; //nut_d * sin(60);
  nut_d = nut_ingress / sin(60);
  nut_ingress_off = nut_ingress/sqrt(3)/2;
  oa_h=wall_t+0.4+nut_h+wall_t+6.7;

  // bottom half for M3 socket cap screw (flush)
  difference() {
    translate([-5.5/2-ww_w, -nut_ingress/2-e, 0])
      cube_fillet([5.5+2*ww_w, nut_ingress+ww_d*0.8+e, 6], vertical=[3.4,3.4], $fn=20);
    // socket cap
    translate([0,0,-e]) cylinder(3.5, d=6, $fn=18);
    // screw shaft
    translate([0,0,3.5+0.3]) cylinder(oa_h-3, d=3.4, $fn=18);
  }

  // top half M3 nut trap
  translate([0,0,h_b+wall_t-oa_h])
  difference() {
    translate([-(nut_d/2+ww_w), -nut_ingress/2, 0])
      cube_fillet([nut_d+2*ww_w, nut_ingress+ww_d, oa_h+e],
        vertical=[3.4,3.4], $fn=20);
    // M3 screw
    translate([0,0,-e]) cylinder(wall_t, d1=4, d2=3.4, $fn=16);
    // nut hole / M3 extra
    translate([0,0,wall_t+0.3]) {
      // nut 3x for an elongated trap
      translate([-0.2,0,0]) cylinder(nut_h*1.5+e, d=nut_d+e/sin(60), $fn=6);
      translate([0,0,0]) cylinder(nut_h*1.5+e, d=nut_d+e/sin(60), $fn=6);
      translate([+0.2,0,0]) cylinder(nut_h*1.5+e, d=nut_d+e/sin(60), $fn=6);
      cylinder(oa_h-wall_t-0.3, d=4, $fn=16);  // M3 with plenty of clearance
    }
    // nut ingress (sideways trapezoid with wider side at ingress)
    translate([nut_ingress_off,-nut_ingress/2,wall_t+0.3])
       linear_extrude(nut_h+e) polygon([[0,0],
         [nut_d/2+ww_w-nut_ingress_off+0.4+e, -0.8],
         [nut_d/2+ww_w-nut_ingress_off+0.4+e, nut_ingress+0.8],
         [0,nut_ingress]]);
  }
}

module locklip_p(l, l_offset=0,
  lip_insert_depth=2.0, // how deep V to insert
  lip_v_off=0.2, // extra height before starting insert
  lip_h_off=0.4, // back connector away from mating area
  lip_w=2.5,  // thickness of attachment beam
  insert_inset=[0,0] // inset the insert inside mating area
  ) {
  translate([l_offset,0,0]) rotate([90,0,0]) rotate([0,90,0])
    difference() {
      linear_extrude(height=l-2*l_offset) polygon(points=[
        [0.1, -lip_w-lip_insert_depth-lip_h_off],  // 0.1 to add depth to keep extrusion manifold
        [0.1, 0],
        [-lip_insert_depth-lip_h_off, 0],
        [-lip_insert_depth-lip_h_off, lip_v_off],
        [-lip_h_off, lip_v_off+lip_insert_depth],
        [-lip_insert_depth-lip_h_off, lip_v_off+2*lip_insert_depth,],
        [-lip_w-lip_insert_depth-lip_h_off, lip_v_off+2*lip_insert_depth],
        [-lip_w-lip_insert_depth-lip_h_off, 0]
      ]);
      translate([-lip_w-lip_insert_depth-lip_h_off-e, 0, 0]) {
        if (insert_inset[0] > 0)
          translate([0,0,-e])
            cube([lip_w+lip_insert_depth+lip_h_off+2*e,
              lip_v_off+2*lip_insert_depth+e, insert_inset[0]+e]);
        if (insert_inset[1] > 0)
          translate([0, 0, l-2*l_offset-insert_inset[1]])
            cube([lip_w+lip_insert_depth+lip_h_off+2*e,
              lip_v_off+2*lip_insert_depth+e, insert_inset[1]+e]);
      }
    }
}

module locklip_n(l, l_offset=0,
  lip_insert_depth=2.2, // how deep V to insert
  lip_tip_clip=0.3, // how much to shave off the top tip
  ) {
  translate([l-l_offset,0,0]) rotate([90,0,0]) rotate([0,-90,0])
    linear_extrude(height=l-2*l_offset) polygon(points=[
      [0.1, -3*lip_insert_depth],  // 0.1 to add depth to keep extrusion manifold
      [0.1, -lip_tip_clip],
      [-lip_insert_depth+lip_tip_clip, -lip_tip_clip],
      [-e, -lip_insert_depth],
      [-lip_insert_depth, -2*lip_insert_depth]
    ]);
}

module lcd_screw() {
  rotate([180]) difference() {
    //cylinder(lcd_mount_t, d=2.5+3.2, $fn=16);
    cylinder(lcd_mount_t+e, d=2.5, $fn=16);
  }
}

module lcd_mount() {
  // Assuming starting at bottom left screw hole
  //lcd_screw();
  //translate([75,0,0]) lcd_screw(); // bottom right hole obscured by PCB
  translate([0,31,0]) lcd_screw();
  translate([75,31,0]) lcd_screw();
}

module lcd_neg() {
  // Assuming starting at bottom left screw hole
  translate([1.0, 2.1, 0])
    cube([73.0, 26.3, lcd_mount_t+wall_t-0.8]); // black bezel inset
  translate([5.5, 7.8, 0])
    cube([64.0, 15.4, lcd_mount_t+wall_t+2*e]); // LCD active area
  translate([4.23, -2.5, 0])
    cube([16*2.54, 5, lcd_mount_t+wall_t-0.8]); // pins cutout
}

module locklip_top_n(split) {
  translate([wall, d+wall, split+wall_t+e])
    rotate([180]) {
      locklip_n(28);
      translate([w-34,0,0]) locklip_n(34);
    }
}

module hm43() {
difference() {
  union() {
    cube_bchamfer([w+2*wall, d+2*wall, h_b+2*wall_t],
      r=body_corner_radius, top=body_chamfer_height_t,
      bottom=body_chamfer_height_b, $fn=36);
    // extra thick by Pi connectors
    if (Pi_Model != "Zero")
      translate([-pic_ex,wall,wall_t])
        pic_ex_cube();
    // TC +/-
    if (Control_Probe == "Thermocouple")
      translate([w+wall*2-e,wall+10,wall_t+18]) tc_plusminus();
  }

  // Main cutout
  translate([wall, wall, wall_t])
    cube_fillet([w, d, h_b+e], bottom=[2,2,2,2],
    top=[lcd_mount_t/2,lcd_mount_t/2,lcd_mount_t/2,lcd_mount_t/2],
    vertical=[1,1]);
  if (Pi_Model != "Zero")
    translate([wall-pic_ex+e,wall,wall_t]) pic_ex_cube();

  // Probe jack side
  translate([w+wall*0.5,wall,wall_t]) {
    // Probe jacks
    translate([0,24.25,probe_centerline]) {
      if (Control_Probe == "Thermocouple")
        // TC jack
        translate([0,inch(-0.55)-16.5/2,-1.4]) cube([2*wall, 16.5, 6.5]);
      else
        translate([0,-17.25,0]) phole();
      translate([0,inch(0.37)*0,0]) phole();
      translate([0,inch(0.37)*1,0]) phole();
      translate([0,inch(0.37)*2,0]) phole();
    }
  }
  // Pi connector side
  translate([wall*-0.5,wall,wall_t]) {
    // Pi connectors
    translate([0,0,5]) {
      if (Pi_Model == "3B/2B/1B+") {
        // ethernet
        translate([0,81.5,-0.5]) jhole(15,13);
        translate([0,81.5,-1.5]) jhole(5,5);
        // USB 0+1
        translate([0,62.75,0]) jhole(13,14.8);
        translate([0,44.75,0]) jhole(13,14.8);
      }
      if (Pi_Model == "1A+")
        translate([0,44.75,0]) jhole(13,7.4);
    }
    // HeaterMeter connectors
    translate([0,0,0]) {
      // Blower/Servo output
      translate([0,25.25,1.1]) jhole(16.7,13);
      // HM power jack
      translate([0,5.25,3.4]) jhole(9.4,11);
    }
  }

  // lcd hole
  translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t-e]) lcd_neg();

  // button holes
  translate([wall+48.7, wall+29.4, h_b+wall_t-e]) {
    translate([-inch(1.1)/2,0,0]) btn_rnd(7.2);  // left
    translate([inch(1.1)/2,0,0]) btn_rnd(7.2);   // right
    translate([0,inch(0.9)/2,0]) btn_rnd(7.2);   // up
    translate([0,-inch(0.9)/2,0]) btn_rnd(7.2);  // down
    // LED holes
    translate([inch(1.3), inch(-0.05), 0]) {
      led_hole();  //red
      translate([0, inch(0.35), 0]) led_hole(); //yellow
      translate([0, inch(0.70), 0]) led_hole(); //green
    }
  }
  // close screw holes
  translate([wall+inch(0.8)+1.3,wall+2.9,0]) {
    screwhole2();
    translate([inch(2.0),0,0]) screwhole2();
  }
}  // END OF DIFFERENCE

  if (Pi_Model != "Zero") {
    // Pi mounting screws
    translate([wall+24,wall+d-7.5,wall_t]) {
      translate([58,0,0]) screw_pimount();
      translate([58,-49,0]) screw_pimount();
      translate([0,-49,0]) screw_pimount();
      translate([0,0,0]) screw_pimount();
    }

    // Pi right edge stop
    translate([wall+w-9.5, wall+d-60, wall_t])
      difference() {
        cube_fillet([9.5,60,4], vertical=[0,0,10/2], $fn=24);
        // Pi B+ microsd gap
        translate([-e,22,-e]) cube_fillet([5.5,14,4+2*e], vertical=[2,0,0,2], $fn=20);
      }
  } // if !Zero

  // close nut traps
  translate([wall+inch(0.8)+1.3,wall+2.85,0]) {
    nuttrap();
    translate([inch(2.0),0,0]) nuttrap();
  }

  // Top locklip (negative)
  if (Pi_Model == "3B/2B/1B+")
    locklip_top_n(case_split);
  else
    locklip_top_n(probe_centerline);

  // LCD mount
  difference() {
    union() {
      // Filled block above LCD hole
      translate([wall, wall+d-17, h_b+wall_t-lcd_mount_t])
        cube_fillet([w,17,lcd_mount_t+e],
          bottom=[lcd_mount_t/2,lcd_mount_t/2,0,lcd_mount_t/2]);
      // LCD grab notch
      translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t])
        translate([(77.5-20)/2,33.5-wall_t,-(1.8+wall_t)])
          // 1.8=thickness of LCD pcb
          cube_fillet([20,1.8+wall_t+wall,1.8+wall_t+e], top=[0,0,1.8+wall_t+e],
            vertical=[wall/2,wall/2]);
    }
    translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t-e]) lcd_neg();
    translate([wall+10.7, wall+52, h_b+wall_t-e]) lcd_mount();
  }

  if (Pi_Model == "3B/2B/1B+" || Pi_Model == "1A+")
    translate([wall-pic_ex, wall, wall_t]) {
      // USB pillar reinforcements
      translate([0, (44.75+62.75)/2-1, 3.5])
        cube_fillet([pic_ex+1, 2, h_b-3.5], bottom=[0,0,0,2]);
      translate([0, 81.5-15/2-3, 3.5])
        cube_fillet([pic_ex+1, 2.5, h_b-3.5], bottom=[0,0,0,2]);
      // Near pic_ex fill
      translate([0, 33.75, 0]) cube([pic_ex, 3, h_b]);
      // Far pic_ex fill (near ethernet)
      translate([0, d-5, 3.5])
       cube_fillet([pic_ex+1, 5+e, h_b-3.5], bottom=[0,0,0,2]);
    }
}

module lip_guide(l) {
  guide_offset = 0.2; // how much to recess the edge guides
  guide_h = 1.2; // how tall above the edge to extend
  linear_extrude(height=l) polygon([
    [0,-e], [wall+guide_offset,-e], [0, wall+guide_offset],
    [-guide_h, wall+guide_offset], [-guide_h, guide_offset],
    [0, guide_offset]
  ]);
}

module hm43_bottom_lips(split) {
  translate([wall, wall, split+wall_t]) {
    // bottom locklip (positive)
    translate([0,d,0]) {
      locklip_p(28, insert_inset=[2,0]);
      translate([w-34,0,0]) locklip_p(34-wall, insert_inset=[0,1]);
    }

    // front guide lip
    translate([w/2-9, 0, 0])
      rotate([0,90]) lip_guide(25);
    // left guide lip assortment
    translate([0, 5.25+9.4/2, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(25.25-5.25-9.4/2-16.7/2);
    *translate([-pic_ex, 52, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(3.5);
    *translate([-pic_ex, 70, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(3.5);
  }
  // probe side guide lip
  translate([wall, wall, probe_centerline+wall_t])
    translate([w, d-45, 0])
      rotate([-90]) rotate(90) lip_guide(40);
}

module split_volume() {
  if (Pi_Model == "3B/2B/1B+") {
    difference() {
      translate([-pic_ex-e,-e,-e])
        cube([w+pic_ex+2*wall+2*e, d+2*wall+2*e, wall_t+case_split+2*e]);
      translate([w,0,wall_t+probe_centerline])
        cube([3*wall, d+2*wall+2*e, wall_t+probe_centerline+2*e]);
    }
  }
  else
    translate([-w,-d,-e])
      cube([3*w, 3*d, wall_t+probe_centerline+2*e]);
}

module hm43_split() {
  // bottom
  if (Pieces != "Top") {
    intersection() {
      hm43();
      split_volume();
    }
    if (Pi_Model == "3B/2B/1B+")
      hm43_bottom_lips(case_split);
    else
      hm43_bottom_lips(probe_centerline);
  } // if include bottom

  // top
  if (Pieces != "Bottom") {
    translate([0,-2,h_b+2*wall_t]) rotate([180]) difference() {
      hm43();
      split_volume();
    }
  }  // if include top
}

/**********************                               **********************/
/********************** END OF CASE / LIBRARY FOLLOWS **********************/
/**********************                               **********************/

module cube_bchamfer(dim, r, top=0, bottom=0, $fn=$fn) {
  // bottom beaded area
  if (bottom != 0) hull(){
    translate([r,r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
  }
  // center
  translate([0,0,bottom]) hull(){
    translate([r,r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
  }
  // top beaded area
  if (top != 0) translate([0,0,dim[2]-top]) hull(){
    translate([r,r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
  }
}

module fillet(radius, height=100, $fn=$fn) {
  if (radius > 0) {
    //this creates acutal fillet
    translate([-radius, -radius, -height / 2 - 0.02]) difference() {
        cube([radius * 2, radius * 2, height + 0.04]);
        if ($fn == 0 && (radius == 2 || radius == 3 || radius == 4)) {
            cylinder(r=radius, h=height + 0.04, $fn=4 * radius);
        } else {
            cylinder(r=radius, h=height + 0.04, $fn=$fn);
        }

    }
  }
}

module cube_fillet(size, radius=-1, vertical=[0,0,0,0], top=[0,0,0,0], bottom=[0,0,0,0], center=false, $fn=4){
    if (center) {
        cube_fillet_inside(size, radius, vertical, top, bottom, $fn);
    } else {
        translate([size[0]/2, size[1]/2, size[2]/2])
            cube_fillet_inside(size, radius, vertical, top, bottom, $fn);
    }
}

module cube_negative_fillet(size, radius=-1, vertical=[3,3,3,3], top=[0,0,0,0], bottom=[0,0,0,0], $fn=$fn){
    j=[1,0,1,0];

    for (i=[0:3]) {
        if (radius > -1) {
            rotate([0, 0, 90*i]) translate([size[1-j[i]]/2, size[j[i]]/2, 0]) fillet(radius, size[2], $fn);
        } else {
            rotate([0, 0, 90*i]) translate([size[1-j[i]]/2, size[j[i]]/2, 0]) fillet(vertical[i], size[2], $fn);
        }
        rotate([90*i, -90, 0]) translate([size[2]/2, size[j[i]]/2, 0 ]) fillet(top[i], size[1-j[i]], $fn);
        rotate([90*(4-i), 90, 0]) translate([size[2]/2, size[j[i]]/2, 0]) fillet(bottom[i], size[1-j[i]], $fn);

    }
}

module cube_fillet_inside(size, radius=-1, vertical=[3,3,3,3], top=[0,0,0,0], bottom=[0,0,0,0], $fn=$fn){
    //makes CENTERED cube with round corners
    // if you give it radius, it will fillet vertical corners.
    //othervise use vertical, top, bottom arrays
    //when viewed from top, it starts in upper right corner (+x,+y quadrant) , goes counterclockwise
    //top/bottom fillet starts in direction of Y axis and goes CCW too

    if (radius == 0) {
        cube(size, center=true);
    } else {
        difference() {
            cube(size, center=true);
            cube_negative_fillet(size, radius, vertical, top, bottom, $fn);
        }
    }
}

//cube_fillet([10,10,10]);

// preview[view:northeast, tilt:topdiagonal]

/* [Options] */
// Pit probe type
Control_Probe = "Thermocouple"; // [Thermocouple,Thermistor]
// Raspberry Pi Model
Pi_Model = "3B/2B/1B+"; // [3B/2B/1B+,Connectorless,1A+,Zero]
// Which case halves
Pieces = "Both"; // [Both,Top,Bottom]
// Thickness of side walls (mm) - Set to trace width multiple
wall = 3.0;
/* [Advanced] */
// Thickness of top and bottom faces
wall_t = 2;
// External corner radius on the body
body_corner_radius = 4;
// Height of the body edge bead chamfer
body_chamfer_height = 1.5;

/* [Hidden] */
w = inch(3.75)+0.5; // overall interior case width
d = inch(3.75)-0.0; // overall interior case depth
h_b = 32; // overall interior case height

probe_centerline = 9.0; // case is split along probe centerline on the probe side
case_split = 12.4;  // and the case split on the other 3 sides

pic_ex = 2;
lcd_mount_t = 7;

body_chamfer_height_t = body_chamfer_height;
body_chamfer_height_b = body_chamfer_height;

e = 0.01;

hm43_split();

function inch(x) = x*25.4;

module cube_fillet_chamfer(size,f,c,$fn=32) {
  hull() {
    translate([f,f,c]) linear_extrude(height=size[2]-2*c) minkowski() {
      square([size[0]-2*f, size[1]-2*f]);
      circle(r=f, $fn=$fn);
    }
    translate([f+c,f+c,0]) linear_extrude(height=size[2]) minkowski() {
      square([size[0]-2*(f+c), size[1]-2*(f+c)]);
      circle(r=f, $fn=$fn);
    }
  }
}

module jhole(d, h) {
  translate([0,0,h/2]) cube([2*(wall+pic_ex),d,h], center=true);
}

module phole() {
  dia=4.5;
  // Hole for thermistor probe jack
  rotate([0,90,0]) cylinder(2*wall, d=dia, $fn=16);
  // because 3d print tends to curl up slightly between the probe holes,
  // do not go right to the edge on the final layer (stop 0.25mm short on each side)
  translate([0,-(dia+0.5)/2, -0.3]) cube([2*wall, dia+0.5, 0.6]);
}

module screwhole() {
  // Screw hole for bottom of case (including nut)
  //translate([0,0,-e]) cylinder(h_b+wall_t, d=3.4, $fn=18);
  translate([0,0,-e]) {
    cylinder(0.4, d1=7.4, d2=6.6, $fn=6);
    translate([0,0,0.4]) cylinder(3, d=6.6, $fn=6);
  }
  translate([0,0,3+0.2]) cylinder(h_b+2*wall_t, d=3.4, $fn=18);
  translate([0,0,h_b+wall_t]) cylinder(3, d=5.5, $fn=18);
}

module screwhole2() {
  translate([0,0,-e]) cylinder(3, d=6, $fn=18);
  translate([0,0,3+0.4]) cylinder(h_b-3-0.4, d=3.4, $fn=18);
}

module pic_ex_cube() {
  translate([0,33.75,2])
    cube_fillet([pic_ex+e, 59.5, 20.8],
      vertical=[0, pic_ex, pic_ex, 0],
      top=[0,pic_ex,0,0],
      bottom=[0,pic_ex,0,0]);
}

module screw_pimount() {
  // 2mm height is good for screw holes
  difference() {
    cylinder(h=2.25, d=6.4, $fn=18);
    //cylinder(h=2+e, d=2.5, $fn=16);
  }
}

module btn_rnd(dia=10) {
  cylinder(wall_t+2*e, d1=dia, d2=dia+1.5*wall_t, $fn=24);
  translate([-6.5,-6.5,0]) cube([13,13,0.75+e]);
}

module btn_square(sq=13) {
  translate([-sq/2,-sq/2,0]) cube([sq,sq,wall_t+2*e]);
}

module tc_plusminus() {
  T=0.75+e;
  H=2;
  W=6;
  // Minus
  translate([0,2,0])
    cube_fillet([T, W, H], top=[0,0,0,T]);
  translate([0,-(2+W),0]) {
    cube_fillet([T, W, H], top=[0,0,0,T]);
    translate([0,(W-H)/2,(H-W)/2])
      cube_fillet([T, H, W], top=[0,0,0,T]);
  }
}

module led_hole() {
  cylinder(wall_t+2*e, d=3.4, $fn=16);
}

module nuttrap() {
  ww_w=3;
  ww_d=wall;
  nut_h=3.0;
  nut_ingress = 5.7; //nut_d * sin(60);
  nut_d = nut_ingress / sin(60);
  nut_ingress_off = nut_ingress/sqrt(3)/2;
  oa_h=wall_t+0.4+nut_h+wall_t+6.7;

  // bottom half for M3 socket cap screw (flush)
  difference() {
    translate([-5.5/2-ww_w, -nut_ingress/2-e, 0])
      cube_fillet([5.5+2*ww_w, nut_ingress+ww_d*0.8+e, 6], vertical=[3.4,3.4], $fn=20);
    // socket cap
    translate([0,0,-e]) cylinder(3.5, d=6, $fn=18);
    // screw shaft
    translate([0,0,3.5+0.3]) cylinder(oa_h-3, d=3.4, $fn=18);
  }

  // top half M3 nut trap
  translate([0,0,h_b+wall_t-oa_h])
  difference() {
    translate([-(nut_d/2+ww_w), -nut_ingress/2, 0])
      cube_fillet([nut_d+2*ww_w, nut_ingress+ww_d, oa_h+e],
        vertical=[3.4,3.4], $fn=20);
    // M3 screw
    translate([0,0,-e]) cylinder(wall_t, d1=4, d2=3.4, $fn=16);
    // nut hole / M3 extra
    translate([0,0,wall_t+0.3]) {
      // nut 3x for an elongated trap
      translate([-0.2,0,0]) cylinder(nut_h*1.5+e, d=nut_d+e/sin(60), $fn=6);
      translate([0,0,0]) cylinder(nut_h*1.5+e, d=nut_d+e/sin(60), $fn=6);
      translate([+0.2,0,0]) cylinder(nut_h*1.5+e, d=nut_d+e/sin(60), $fn=6);
      cylinder(oa_h-wall_t-0.3, d=4, $fn=16);  // M3 with plenty of clearance
    }
    // nut ingress (sideways trapezoid with wider side at ingress)
    translate([nut_ingress_off,-nut_ingress/2,wall_t+0.3])
       linear_extrude(nut_h+e) polygon([[0,0],
         [nut_d/2+ww_w-nut_ingress_off+0.4+e, -0.8],
         [nut_d/2+ww_w-nut_ingress_off+0.4+e, nut_ingress+0.8],
         [0,nut_ingress]]);
  }
}

module locklip_p(l, l_offset=0,
  lip_insert_depth=2.0, // how deep V to insert
  lip_v_off=0.2, // extra height before starting insert
  lip_h_off=0.4, // back connector away from mating area
  lip_w=2.5,  // thickness of attachment beam
  insert_inset=[0,0] // inset the insert inside mating area
  ) {
  translate([l_offset,0,0]) rotate([90,0,0]) rotate([0,90,0])
    difference() {
      linear_extrude(height=l-2*l_offset) polygon(points=[
        [0.1, -lip_w-lip_insert_depth-lip_h_off],  // 0.1 to add depth to keep extrusion manifold
        [0.1, 0],
        [-lip_insert_depth-lip_h_off, 0],
        [-lip_insert_depth-lip_h_off, lip_v_off],
        [-lip_h_off, lip_v_off+lip_insert_depth],
        [-lip_insert_depth-lip_h_off, lip_v_off+2*lip_insert_depth,],
        [-lip_w-lip_insert_depth-lip_h_off, lip_v_off+2*lip_insert_depth],
        [-lip_w-lip_insert_depth-lip_h_off, 0]
      ]);
      translate([-lip_w-lip_insert_depth-lip_h_off-e, 0, 0]) {
        if (insert_inset[0] > 0)
          translate([0,0,-e])
            cube([lip_w+lip_insert_depth+lip_h_off+2*e,
              lip_v_off+2*lip_insert_depth+e, insert_inset[0]+e]);
        if (insert_inset[1] > 0)
          translate([0, 0, l-2*l_offset-insert_inset[1]])
            cube([lip_w+lip_insert_depth+lip_h_off+2*e,
              lip_v_off+2*lip_insert_depth+e, insert_inset[1]+e]);
      }
    }
}

module locklip_n(l, l_offset=0,
  lip_insert_depth=2.2, // how deep V to insert
  lip_tip_clip=0.3, // how much to shave off the top tip
  ) {
  translate([l-l_offset,0,0]) rotate([90,0,0]) rotate([0,-90,0])
    linear_extrude(height=l-2*l_offset) polygon(points=[
      [0.1, -3*lip_insert_depth],  // 0.1 to add depth to keep extrusion manifold
      [0.1, -lip_tip_clip],
      [-lip_insert_depth+lip_tip_clip, -lip_tip_clip],
      [-e, -lip_insert_depth],
      [-lip_insert_depth, -2*lip_insert_depth]
    ]);
}

module lcd_screw() {
  rotate([180]) difference() {
    //cylinder(lcd_mount_t, d=2.5+3.2, $fn=16);
    cylinder(lcd_mount_t+e, d=2.5, $fn=16);
  }
}

module lcd_mount() {
  // Assuming starting at bottom left screw hole
  //lcd_screw();
  //translate([75,0,0]) lcd_screw(); // bottom right hole obscured by PCB
  translate([0,31,0]) lcd_screw();
  translate([75,31,0]) lcd_screw();
}

module lcd_neg() {
  // Assuming starting at bottom left screw hole
  translate([1.0, 2.1, 0])
    cube([73.0, 26.3, lcd_mount_t+wall_t-0.8]); // black bezel inset
  translate([5.5, 7.8, 0])
    cube([64.0, 15.4, lcd_mount_t+wall_t+2*e]); // LCD active area
  translate([4.23, -2.5, 0])
    cube([16*2.54, 5, lcd_mount_t+wall_t-0.8]); // pins cutout
}

module locklip_top_n(split) {
  translate([wall, d+wall, split+wall_t+e])
    rotate([180]) {
      locklip_n(28);
      translate([w-34,0,0]) locklip_n(34);
    }
}

module hm43() {
difference() {
  union() {
    cube_bchamfer([w+2*wall, d+2*wall, h_b+2*wall_t],
      r=body_corner_radius, top=body_chamfer_height_t,
      bottom=body_chamfer_height_b, $fn=36);
    // extra thick by Pi connectors
    if (Pi_Model != "Zero")
      translate([-pic_ex,wall,wall_t])
        pic_ex_cube();
    // TC +/-
    if (Control_Probe == "Thermocouple")
      translate([w+wall*2-e,wall+10,wall_t+18]) tc_plusminus();
  }

  // Main cutout
  translate([wall, wall, wall_t])
    cube_fillet([w, d, h_b+e], bottom=[2,2,2,2],
    top=[lcd_mount_t/2,lcd_mount_t/2,lcd_mount_t/2,lcd_mount_t/2],
    vertical=[1,1]);
  if (Pi_Model != "Zero")
    translate([wall-pic_ex+e,wall,wall_t]) pic_ex_cube();

  // Probe jack side
  translate([w+wall*0.5,wall,wall_t]) {
    // Probe jacks
    translate([0,24.25,probe_centerline]) {
      if (Control_Probe == "Thermocouple")
        // TC jack
        translate([0,inch(-0.55)-16.5/2,-1.4]) cube([2*wall, 16.5, 6.5]);
      else
        translate([0,-17.25,0]) phole();
      translate([0,inch(0.37)*0,0]) phole();
      translate([0,inch(0.37)*1,0]) phole();
      translate([0,inch(0.37)*2,0]) phole();
    }
  }
  // Pi connector side
  translate([wall*-0.5,wall,wall_t]) {
    // Pi connectors
    translate([0,0,5]) {
      if (Pi_Model == "3B/2B/1B+") {
        // ethernet
        translate([0,81.5,-0.5]) jhole(15,13);
        translate([0,81.5,-1.5]) jhole(5,5);
        // USB 0+1
        translate([0,62.75,0]) jhole(13,14.8);
        translate([0,44.75,0]) jhole(13,14.8);
      }
      if (Pi_Model == "1A+")
        translate([0,44.75,0]) jhole(13,7.4);
    }
    // HeaterMeter connectors
    translate([0,0,0]) {
      // Blower/Servo output
      translate([0,25.25,1.1]) jhole(16.7,13);
      // HM power jack
      translate([0,5.25,3.4]) jhole(9.4,11);
    }
  }

  // lcd hole
  translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t-e]) lcd_neg();

  // button holes
  translate([wall+48.7, wall+29.4, h_b+wall_t-e]) {
    translate([-inch(1.1)/2,0,0]) btn_rnd(7.2);  // left
    translate([inch(1.1)/2,0,0]) btn_rnd(7.2);   // right
    translate([0,inch(0.9)/2,0]) btn_rnd(7.2);   // up
    translate([0,-inch(0.9)/2,0]) btn_rnd(7.2);  // down
    // LED holes
    translate([inch(1.3), inch(-0.05), 0]) {
      led_hole();  //red
      translate([0, inch(0.35), 0]) led_hole(); //yellow
      translate([0, inch(0.70), 0]) led_hole(); //green
    }
  }
  // close screw holes
  translate([wall+inch(0.8)+1.3,wall+2.9,0]) {
    screwhole2();
    translate([inch(2.0),0,0]) screwhole2();
  }
}  // END OF DIFFERENCE

  // Pi mounting screws
  translate([wall+24,wall+d-7.5,wall_t]) {
    //screw_pimount();
    translate([58,0,0]) screw_pimount();
    translate([58,-49,0]) screw_pimount();
    translate([0,-49,0]) screw_pimount();
    translate([0,0,0]) screw_pimount();
  }
  // Pi right edge stop
  translate([wall+w-9.5, wall+d-60, wall_t]) {
    difference() {
      cube_fillet([9.5,60,4], vertical=[0,0,10/2], $fn=24);
      // Pi B+ microsd gap
      translate([-e,22,-e]) cube_fillet([5.5,14,4+2*e], vertical=[2,0,0,2], $fn=20);
    }
  }

  // close nut traps
  translate([wall+inch(0.8)+1.3,wall+2.85,0]) {
    nuttrap();
    translate([inch(2.0),0,0]) nuttrap();
  }

  // Top locklip (negative)
  if (Pi_Model == "3B/2B/1B+")
    locklip_top_n(case_split);
  else
    locklip_top_n(probe_centerline);

  // LCD mount
  difference() {
    union() {
      // Filled block above LCD hole
      translate([wall, wall+d-17, h_b+wall_t-lcd_mount_t])
        cube_fillet([w,17,lcd_mount_t+e],
          bottom=[lcd_mount_t/2,lcd_mount_t/2,0,lcd_mount_t/2]);
      // LCD grab notch
      translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t])
        translate([(77.5-20)/2,33.5-wall_t,-(1.8+wall_t)])
          // 1.8=thickness of LCD pcb
          cube_fillet([20,1.8+wall_t+wall,1.8+wall_t+e], top=[0,0,1.8+wall_t+e],
            vertical=[wall/2,wall/2]);
    }
    translate([wall+10.7, wall+52, h_b+wall_t-lcd_mount_t-e]) lcd_neg();
    translate([wall+10.7, wall+52, h_b+wall_t-e]) lcd_mount();
  }

  if (Pi_Model == "3B/2B/1B+" || Pi_Model == "1A+")
    translate([wall-pic_ex, wall, wall_t]) {
      // USB pillar reinforcements
      translate([0, (44.75+62.75)/2-1, 3.5])
        cube_fillet([pic_ex+1, 2, h_b-3.5], bottom=[0,0,0,2]);
      translate([0, 81.5-15/2-3, 3.5])
        cube_fillet([pic_ex+1, 2.5, h_b-3.5], bottom=[0,0,0,2]);
      // Near pic_ex fill
      translate([0, 33.75, 0]) cube([pic_ex, 3, h_b]);
      // Far pic_ex fill (near ethernet)
      translate([0, d-5, 3.5])
       cube_fillet([pic_ex+1, 5+e, h_b-3.5], bottom=[0,0,0,2]);
    }
}

module lip_guide(l) {
  guide_offset = 0.2; // how much to recess the edge guides
  guide_h = 1.2; // how tall above the edge to extend
  linear_extrude(height=l) polygon([
    [0,-e], [wall+guide_offset,-e], [0, wall+guide_offset],
    [-guide_h, wall+guide_offset], [-guide_h, guide_offset],
    [0, guide_offset]
  ]);
}

module hm43_bottom_lips(split) {
  translate([wall, wall, split+wall_t]) {
    // bottom locklip (positive)
    translate([0,d,0]) {
      locklip_p(28, insert_inset=[2,0]);
      translate([w-34,0,0]) locklip_p(34-wall, insert_inset=[0,1]);
    }

    // front guide lip
    translate([w/2-9, 0, 0])
      rotate([0,90]) lip_guide(25);
    // left guide lip assortment
    translate([0, 5.25+9.4/2, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(25.25-5.25-9.4/2-16.7/2);
    *translate([-pic_ex, 52, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(3.5);
    *translate([-pic_ex, 70, 0])
      rotate([-90]) rotate(90) mirror([0,1,0]) lip_guide(3.5);
  }
  // probe side guide lip
  translate([wall, wall, probe_centerline+wall_t])
    translate([w, d-45, 0])
      rotate([-90]) rotate(90) lip_guide(40);
}

module split_volume() {
  if (Pi_Model == "3B/2B/1B+") {
    difference() {
      translate([-pic_ex-e,-e,-e])
        cube([w+pic_ex+2*wall+2*e, d+2*wall+2*e, wall_t+case_split+2*e]);
      translate([w,0,wall_t+probe_centerline])
        cube([3*wall, d+2*wall+2*e, wall_t+probe_centerline+2*e]);
    }
  }
  else
    translate([-w,-d,-e])
      cube([3*w, 3*d, wall_t+probe_centerline+2*e]);
}

module hm43_split() {
  // bottom
  if (Pieces != "Top") {
    intersection() {
      hm43();
      split_volume();
    }
    if (Pi_Model == "3B/2B/1B+")
      hm43_bottom_lips(case_split);
    else
      hm43_bottom_lips(probe_centerline);
  } // if include bottom

  // top
  if (Pieces != "Bottom") {
    translate([0,-2,h_b+2*wall_t]) rotate([180]) difference() {
      hm43();
      split_volume();
    }
  }  // if include top
}

/**********************                               **********************/
/********************** END OF CASE / LIBRARY FOLLOWS **********************/
/**********************                               **********************/

module cube_bchamfer(dim, r, top=0, bottom=0, $fn=$fn) {
  // bottom beaded area
  if (bottom != 0) hull(){
    translate([r,r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(bottom, r1=r-bottom, r2=r, $fn=$fn);
  }
  // center
  translate([0,0,bottom]) hull(){
    translate([r,r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(dim[2]-top-bottom, r=r, $fn=$fn);
  }
  // top beaded area
  if (top != 0) translate([0,0,dim[2]-top]) hull(){
    translate([r,r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([dim[0]-r,r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([r,dim[1]-r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
    translate([dim[0]-r,dim[1]-r,0]) cylinder(top, r2=r-top, r1=r, $fn=$fn);
  }
}

module fillet(radius, height=100, $fn=$fn) {
  if (radius > 0) {
    //this creates acutal fillet
    translate([-radius, -radius, -height / 2 - 0.02]) difference() {
        cube([radius * 2, radius * 2, height + 0.04]);
        if ($fn == 0 && (radius == 2 || radius == 3 || radius == 4)) {
            cylinder(r=radius, h=height + 0.04, $fn=4 * radius);
        } else {
            cylinder(r=radius, h=height + 0.04, $fn=$fn);
        }

    }
  }
}

module cube_fillet(size, radius=-1, vertical=[0,0,0,0], top=[0,0,0,0], bottom=[0,0,0,0], center=false, $fn=4){
    if (center) {
        cube_fillet_inside(size, radius, vertical, top, bottom, $fn);
    } else {
        translate([size[0]/2, size[1]/2, size[2]/2])
            cube_fillet_inside(size, radius, vertical, top, bottom, $fn);
    }
}

module cube_negative_fillet(size, radius=-1, vertical=[3,3,3,3], top=[0,0,0,0], bottom=[0,0,0,0], $fn=$fn){
    j=[1,0,1,0];

    for (i=[0:3]) {
        if (radius > -1) {
            rotate([0, 0, 90*i]) translate([size[1-j[i]]/2, size[j[i]]/2, 0]) fillet(radius, size[2], $fn);
        } else {
            rotate([0, 0, 90*i]) translate([size[1-j[i]]/2, size[j[i]]/2, 0]) fillet(vertical[i], size[2], $fn);
        }
        rotate([90*i, -90, 0]) translate([size[2]/2, size[j[i]]/2, 0 ]) fillet(top[i], size[1-j[i]], $fn);
        rotate([90*(4-i), 90, 0]) translate([size[2]/2, size[j[i]]/2, 0]) fillet(bottom[i], size[1-j[i]], $fn);

    }
}

module cube_fillet_inside(size, radius=-1, vertical=[3,3,3,3], top=[0,0,0,0], bottom=[0,0,0,0], $fn=$fn){
    //makes CENTERED cube with round corners
    // if you give it radius, it will fillet vertical corners.
    //othervise use vertical, top, bottom arrays
    //when viewed from top, it starts in upper right corner (+x,+y quadrant) , goes counterclockwise
    //top/bottom fillet starts in direction of Y axis and goes CCW too

    if (radius == 0) {
        cube(size, center=true);
    } else {
        difference() {
            cube(size, center=true);
            cube_negative_fillet(size, radius, vertical, top, bottom, $fn);
        }
    }
}

//cube_fillet([10,10,10]);


misc: add v_contains

misc: add v_contains

include <system.scad>
use <scad-utils/linalg.scad>
use <scad-utils/lists.scad>

function posvec(path_vec) = [for(v=path_vec) translation_part(v)];
function posvec_x(path_vec) = [for(v=path_vec) translation_part(v)[0]];
function posvec_y(path_vec) = [for(v=path_vec) translation_part(v)[1]];
function posvec_z(path_vec) = [for(v=path_vec) translation_part(v)[2]];

function vec_i(v3,i) = [for(v=v3) v[i]];
function vec_0(v3) = vec_i(v3,0);
function vec_1(v3) = vec_i(v3,1);
function vec_2(v3) = vec_i(v3,2);

// if V is array and len == 1, return single value, otherwise return V
function singlify(V) = len(V)==1?V[0]:V;

function v_itrlen(vec) = [0:1:len(vec)-1];

function range1(v1) = [min(v1), max(v1)];
function range3(v3) = [range1(vec_0(v3)), range1(vec_1(v3)), range1(vec_2(v3))];
function bbox(r) = [ [r[0][0],r[1][0],r[2][0]], [r[0][1],r[1][1],r[2][1]] ];
function transform_pp(vec_m, t_pre, t_post) = [for(m=vec_m) (t_pre*m)*t_post];
function transform_pre(vec_m, t) = [for(m=vec_m) t*m];
function transform_post(vec_m, t) = [for(m=vec_m) m*t];

function clamp(v, v1, v2) = min(max(v,v1),v2);

// from the start (or s'th element) to the e'th element - remember elements are zero based
function v_sum(v,e=U,start=0) =
let(e_= fallback(e, len(v)-1))
(e==start ? v[e] : v[e_] + v_sum(v,e_-1,start));

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

function v_i(vec,i) = [for(vv=vec) vv[i]];
function v_get(vec,key) = [for(vv=vec) get(key, vv)];
function v_add(vec,v) = [for(vv=vec) vv+v];
function v_sub(vec,v) = [for(vv=vec) vv+v];
function v_avg(v,e=U,start=0) = v_sum(v,e,start) / (len(v));
function v_abs(v, start=0) = [for(i=[start:1:len(v)-1]) abs(v[i])];
function v_sign(v, start=0) = [for(i=[start:1:len(v)-1]) sign(v[i])];
function v_max(v, m, start=0) = [for(i=[start:1:len(v)-1]) max(v[i],m)];
function v_min(v, m, start=0) = [for(i=[start:1:len(v)-1]) min(v[i],m)];
function v_clamp(v, v1, v2, start=0) = [for(i=[start:1:len(v)-1]) clamp(v[i],v1,v2)];

function v_slice(v, start, end) =
let(start_ = start==U?0:start)
let(end_ = end==U?len(v)-1:end)
[for(i=[start_:1:end_]) v[i]];

if($test_mode)
{
    v=[0,1,2];
    assert_v(v_slice(v), [0, 1,2]);
    assert_v(v_slice(v,start=1), [1,2]);
    assert_v(v_slice(v,end=1), [0, 1]);
    assert_v(v_slice(v,start=1,end=1), [1]);
}

// cumulative sum of vector [1,2,3] = [1,3,6]
function v_cumsum(v, start=0, end) = [for(i=[start:1:end==U?len(v)-1:end]) v_sum(v,i)];

// filter/remove a val from a vec
function filter(vec,val=U) = [for(v=vec) if(v!=val) v];

function vec_pair_double_transform_post(vec,t1,t2)=
    flatten(
        [for(v=vec)
            [transform_post(v,t1),transform_post(v,t2)]
        ]);

function vec_pair_double(vec)=flatten([for(v=vec) [v,v] ]);

/** Calculates length of hypotenuse according to pythagoras */
function pythag_hyp(a, b)=sqrt(a*a+b*b);
function pythag_leg(b, c)=sqrt(c*c-b*b);

function v_x(v) = [v[0],0,0];
function v_y(v) = [0,v[1],0];
function v_z(v) = [0,0,v[2]];

function v_xy(v) = [v[0],v[1],0];
function v_xyz(v) = [v[0],v[1],v[2]];
function v_yz(v) = [0,v[1],v[2]];
function v_z(v) = [0,0,v[2]];

//-- Calculate the module/magnitude of a vector
function v_mod(v) = (sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]));

//-- Calculate the cross product of two vectors
function v_cross(u,v) = [
  u[1]*v[2] - v[1]*u[2],
  u[2]*v[0] - v[2]*u[0],
  u[0]*v[1] - v[0]*u[1]
];

//-- Calculate the dot product of two vectors
function v_dot(u,v) = u[0]*v[0]+u[1]*v[1]+u[2]*v[2];

//-- Return the unit vector of a vector
function v_unitv(v) = v/v_mod(v);

//-- Return the angle between two vectores
function v_anglev(u,v) = acos( v_dot(u,v) / (v_mod(u)*v_mod(v)) );

function _orient_angles(zaxis)=
[
    -asin(zaxis.y / norm(zaxis)),
    atan2(zaxis.x, zaxis.z),
    0
];

/*function _orient_angles_(v)=*/
/*[*/
    /*-atan2(sqrt(v.x*v.x+v.y*v.y), v.z),*/
    /*0,*/
    /*-atan2(v.x, v.z)*/
/*];*/

//matrix rotation functions
function _rotate_x_matrix(a)=
[[1,0,0,0],
    [0,cos(a),-sin(a),0],
    [0,sin(a),cos(a),0],
    [0,0,0,1]];

function _rotate_y_matrix(a)=
[[cos(a),0,sin(a),0],
    [0,1,0,0],
    [-sin(a),0,cos(a),0],
    [0,0,0,1]];

function _rotate_z_matrix(a)=
[[cos(a),-sin(a),0,0],
    [sin(a),cos(a),0,0],
    [0,0,1,0],
    [0,0,0,1]];

function _rotate_matrix(a)=_rotate_z_matrix(a.z)*_rotate_y_matrix(a.y)*_rotate_x_matrix(a.x);

//hadamard product (aka "component-wise" product) for vectors
function hadamard(v1,v2) = [v1.x*v2.x, v1.y*v2.y, v1.z*v2.z];

// search in a dictionary (vector w/key-value pairs) and replace the value for a given key
function dict_replace(dict, key, newvalue)=[
        for(kv=dict)
        kv[0] == key ?
            [key, newvalue]
            :
            kv
        ];

function dict_replace_multiple(dict, newvaluesdict)=[
    for(kv_old=dict)
    let(r = search(kv_old[0], newvaluesdict, num_returns_per_match=0, index_col_num=0))
    r==[]?kv_old:newvaluesdict[r[0]]
    ];

function sinh(x) = (1 - pow(e, -2 * x)) / (2 * pow(e, -x));
function cosh(x) = (1 + pow(e, -2 * x)) / (2 * pow(e, -x));
function tanh(x) = sinh(x) / cosh(x);
function cot(x) = 1 / tan(x);

function factorial(n) = n == 0 ? 1 : factorial(n - 1) * n;

if(false)
{
    vec=[ 10, 20, 30, 40 ];
    echo("v_sum=", v_sum(vec,2,1)); // is 20+30=50
    echo("v_sum=", v_sum(vec));
    echo("v_cumsum=", v_cumsum(vec));
}

// FUNCTION: is_String(x)
//   Returns true if x is a string, false otherwise.
function is_string(x) =
	x == U || len(x) == U
		? false // if U, a boolean or a number
		: len(str(x,x)) == len(x)*2; // if an array, this is false

// FUNCTION: is_array(x)
//   Returns true if x is an array, false otherwise.
function is_array(x) = is_string(x) ? false : len(x) != U;

function identity(d)        = d == 0 ? 1 : [for(y=[1:d]) [for(x=[1:d]) x == y ? 1 : 0] ];
function unit_vector(v)     = let(x=v[0], y=v[1], z=v[2]) [x/norm(v), y/norm(v), z/norm(v)];
function skew_symmetric(v)  = let(x=v[0], y=v[1], z=v[2]) [[0, -z, y], [z, 0, -x], [-y, x, 0]];
function tensor_product1(u) = let(x=u[0], y=u[1], z=u[2]) [[x*x, x*y, x*z], [x*y, y*y, y*z], [x*z, y*z, z*z]];
function v_rotate(a, v)
    = is_array(a)
? let(rx=a[0], ry=a[1], rz=a[2])
    [[1, 0, 0],              [0, cos(rx), -sin(rx)], [0, sin(rx), cos(rx)]]
  * [[cos(ry), 0, sin(ry)],  [0, 1, 0],              [-sin(ry), 0, cos(ry)]]
  * [[cos(rz), -sin(rz), 0], [sin(rz), cos(rz), 0],  [0, 0, 1]]
    : let(uv=unit_vector(v))
    cos(a)*identity(3) + sin(a)*skew_symmetric(uv) + (1 - cos(a))*tensor_product1(uv);

 /*echo(rotate(90, X) * [1, 0, 0]);*/
 /*echo(rotate(90, X) * [0, 1, 0]);*/
 /*echo(rotate(90, X) * [0, 0, 1]);*/

function lerp(v0, v1, t) =  (1-t)*v0 + t*v1;

if($test_mode)
{
    assert(lerp(-1, 1, 0) == -1);
    assert(lerp1(-1, 1, .5) == 0);
    assert(lerp1(-1, 1, 1) == 1);
}

if($test_mode)
{
    assert_v(fallback(U,1), 1);
}

function fallback(a, b) = a==U?b:a;

// echo(fallback(U,[U, 1]));
function v_fallback(a,v,i=0) = (a!=U || i > len(v)-1) ? a : v_fallback(v[i],v,i+1);

function zip(a,b,start=0,end) = [for(i=[start:1:fallback(end,len(a)-1)]) [a[i],b[i]] ];
function zip_v(v,start=0,end) = [for(i=[start:1:fallback(end,len(v[0])-1)]) v_i(v,i) ];

if($test_mode)
{
    vec_a = [0,4];
    vec_b = [1,5];
    vec_c = [2,6];
    vec_d = [3,7];
    assert_v(zip(vec_a,vec_b),[[0,1],[4,5]]);
    assert_v(zip_v([vec_a,vec_b]),[[0,1],[4,5]]);
    assert_v(zip_v([vec_a,vec_b,vec_c,vec_d]), [[0,1,2,3],[4,5,6,7]]);
}

function vv_fallback(v,start=0,end) =
let(z = zip_v(v))
[
for(i=[start:1:fallback(end,len(v[0])-1)])
    v_fallback(v=z[i],i=0)
];

if($test_mode)
{
    vec_a=[ 10,  U,   U, 40 ];
    vec_b=[ 10,  U,  30,  U ];
    vec_c=[  U, 20,   U, 40 ];
    assert(v_fallback(a=5, v=vec_a), 5);
    assert_v(vv_fallback(v=[vec_a, vec_b,vec_c]), [10,20,30,40]);
}

function fn_from_r(r) =
                    $fn > 0.0 ?
                    ($fn >= 3 ? $fn : 3)
                    :
                    ceil(max(min(360.0 / $fa, r*2*PI / $fs), 5));

function fn_from_d(d) =
                    $fn > 0.0 ?
                    ($fn >= 3 ? $fn : 3)
                    :
                    ceil(max(min(360.0 / $fa, d*PI / $fs), 5));

module assert_v(val, expected, message)
{
    if(val != expected)
    {
        echo("assertion, unexpected value", val, expected);
        assert(val == expected, message);
    }
}

function spread(v0,v1,a) =
a>=2 ?
    [for(i=[0:1/(a-1):1]) lerp(v0,v1,i)]
:
    [lerp(v0,v1,.5)]
;

if($test_mode)
{
    assert_v(spread(-1, 1, 1), [0]);
    assert_v(spread(-1, 1, 2), [-1,1]);
    assert_v(spread(-1, 1, 3), [-1,0,1]);
    // fails because of float inaccuracy
    /*assert_v(spread(-1, 1, 4), [-1,-1/3,1/3,1]);*/
    assert_v(spread(-1, 1, 5), [-1,-.5,0,.5,1]);
}

// the apothem (inradius) is the distance from the center of a regular
// polygon to the flat side (not a vertex)
function apothem(circumradius, fn) = circumradius*cos(180/fn);
function inradius(circumradius, fn) = circumradius*cos(180/fn);;
// the circumradius (outradius) is the distance from the center of a regular
// polygon to a vertex
function circumradius(apothem, fn) = apothem / cos(180/fn);
function outradius(apothem, fn) = apothem / cos(180/fn);
function fn_radius(r, fn) = apothem(r,fn);
function hex_radius(r) = apothem(r, 6);

if($test_mode)
{
    assert_v(apothem(circumradius(5,6)), circumradius(apothem(5),6));
}

function header_col_index(v, col_keys) =
    singlify(
        [for(col_key=col_keys)
        let(result=search([col_key], v[0], index_col_number=0, num_returns_per_match=0))
        result[0][0]
        ]
    );

function geth(S, col_keys, row_index) =
    singlify(
        [for(col_key=col_keys)
            let(col_index = header_col_index(S, col_key))
            S[row_index+1][col_index]
        ]);

if($test_mode)
{
    A = [
    ["Tx", "Ty"],
    [1, 2],
    [3, 4],
    ];

    assert_v(header_col_index(A, "Tx"), 0);
    assert_v(header_col_index(A, "Ty"), 1);

    assert_v(header_col_index(A, ["Ty"]), 1);
    assert_v(header_col_index(A, ["Tx"]), 0);
    assert_v(header_col_index(A, ["Tx", "Ty"]), [0,1]);

    assert_v(geth(A, "Tx", 0), 1);
    assert_v(geth(A, "Tx", 1), 3);
    assert_v(geth(A, "Ty", 0), 2);
    assert_v(geth(A, "Ty", 1), 4);

    assert_v(geth(A, ["Tx","Ty"], 0), [1,2]);
    assert_v(geth(A, ["Tx","Ty"], 1), [3,4]);

}

function v_contains(V, val, start=0, end=U) =
    let(e=end==U?len(V):end)
    V[start]==val ? true :
    start == len(V)-1 ? false :
    v_contains(V,val,start+1,end);

if($test_mode)
{
    assert_v(v_contains([0,1], 0), true);
    assert_v(v_contains([0,1], 1), true);
    assert_v(v_contains([0,1], 2), false);
}


The module names were refactored.

The module names were refactored.

use <../../shapes/open-cylinder/open-cylinder.scad>

module lightSignal(baseHeight = 2,
	                 signalText1 = "Impalas")
{
	difference()
	{
			lightSignal_shell(baseHeight,
									showOriginal = false);

			lightSignal_cutouts(signalText1);
	}
}

/** Support functions and moduules follow. **/

module lightSignal_cutouts(signalText1)
{
	font = "Bauhaus 93:style=Regular";
	fontSize = 7.5;
	xTranslate = -18;
	translate([xTranslate, -3, -3])
	linear_extrude(height = 6)
	text(signalText1, font = font, size = fontSize);
}

module lightSignal_shell(baseHeight,
									 showOriginal = false)
{
	signalStl = "../../../../../../../../../../../../Versioning/world/beto-land-world/3d-printing/super-heroes/batman/bat-signal/customizable_phone_bat_signal_20150130-9347-hv0ikc-0.stl";
	// DO NOT CHANGE THE RADIUS; IT MACHES THE ORIGINAL MODEL
	stlBaseInnerRadius = 18.5;
	stlBaseOuterRadius = stlBaseInnerRadius + 2.5;

	union()
	{
		// this is the top hole/cutout
		zTopHoleTranslate = 35;
		color("blue")
		translate([0, 0, zTopHoleTranslate])
		openCylinder(height=1, outerRadius = 12, innerRadius=6);

		// connector for the shaft and top hole
		color("pink")
		translate([0, 0, zTopHoleTranslate-3])
		openCylinder(height=3.1, outerRadius = 19, innerRadius=8);

		// this is the bottom wide shaft
		zTranslate = -baseHeight / 2.0;
		color("orange")
		translate([0, 0, zTranslate])
		openCylinder(height=zTopHoleTranslate, outerRadius = stlBaseOuterRadius, innerRadius = stlBaseInnerRadius);

		// this thing was modeld after this original
		if(showOriginal)
		{
			color("yellow")
			translate([0, 0, zTranslate])
			import(signalStl);
		}

		// This is the bottome disk, that holds the text/icon cutout.
		color("green")
		cylinder(r=stlBaseInnerRadius+0.1, h=baseHeight, center=true);
	}
}

use <../../shapes/open-cylinder/open-cylinder.scad>

module lightSignal(baseHeight = 2,
	                 signalText1 = "Impalas")
{
	difference()
	{
			signalShell(baseHeight,
									showOriginal = false);

			plaque(signalText1);
	}
}

module plaque(signalText1)
{
	font = "Bauhaus 93:style=Regular";
	fontSize = 7.5;
	xTranslate = -18;
	translate([xTranslate, -3, -3])
	linear_extrude(height = 6)
	text(signalText1, font = font, size = fontSize);
}

module signalShell(baseHeight,
									 showOriginal = false)
{
	signalStl = "../../../../../../../../../../../../Versioning/world/beto-land-world/3d-printing/super-heroes/batman/bat-signal/customizable_phone_bat_signal_20150130-9347-hv0ikc-0.stl";
	// DO NOT CHANGE THE RADIUS; IT MACHES THE ORIGINAL MODEL
	stlBaseInnerRadius = 18.5;
	stlBaseOuterRadius = stlBaseInnerRadius + 2.5;

	union()
	{
		// this is the top hole/cutout
		zTopHoleTranslate = 35;
		color("blue")
		translate([0, 0, zTopHoleTranslate])
		openCylinder(height=1, outerRadius = 12, innerRadius=6);

		// connector for the shaft and top hole
		color("pink")
		translate([0, 0, zTopHoleTranslate-3])
		openCylinder(height=3.1, outerRadius = 19, innerRadius=8);

		// this is the bottom wide shaft
		zTranslate = -baseHeight / 2.0;
		color("orange")
		translate([0, 0, zTranslate])
		openCylinder(height=zTopHoleTranslate, outerRadius = stlBaseOuterRadius, innerRadius = stlBaseInnerRadius);

		// this thing was modeld after this original
		if(showOriginal)
		{
			color("yellow")
			translate([0, 0, zTranslate])
			import(signalStl);
		}

		// This is the bottome disk, that holds the text/icon cutout.
		color("green")
		cylinder(r=stlBaseInnerRadius+0.1, h=baseHeight, center=true);
	}
}


Added a little extra clearance for motor depth

To make press fitting the motors easier for kids

/*
    Clip to hold the stepper motor and driver board, mates to base plate using the pin system.

    Local Frame:
        TBC

*/

MotorClip_DriverOffsetX = 20;
MotorClip_DriverOffsetZ = 8;

// Connectors
// connector for where the motor attaches...
Wheel_Con_Default				= [ [0, 0 ,2], [0, 0, 1], 90, 0, 0 ];

MotorClip_Con_Fixing1 = [[20, MotorOffsetZ - dw, 14], [0,1,0], 0,0,0];
MotorClip_Con_Fixing2 = [[-20, MotorOffsetZ -dw, 14], [0,1,0], 0,0,0];

// inner fixing holes
MotorClip_Con_Fixing3 = [[10, MotorOffsetZ - dw, 14], [0,1,0], 0,0,0];
MotorClip_Con_Fixing4 = [[-10, MotorOffsetZ -dw, 14], [0,1,0], 0,0,0];

MotorClip_Con_Motor = [[0, 0, 4.5], [0,0, 1], 0,0,0];

LeftMotorClip_Con_Driver = [[-MotorClip_DriverOffsetX, 1, MotorClip_DriverOffsetZ], [1, 0, 0], 0,0,0];
RightMotorClip_Con_Driver = [[MotorClip_DriverOffsetX, 1, MotorClip_DriverOffsetZ], [-1, 0, 0], 0,0,0];

module LeftMotorClip_STL() {

    // turn off explosions inside STL!!!
    $Explode = false;

    printedPart("printedparts/MotorClip.scad", "Left Motor Clip", "LeftMotorClip_STL()") {

        // TODO: Update view
        view(t=[0,0,0], r=[58,0,225], d=681);

        // Color it as a printed plastic part
        color(PlasticColor)
            if (UseSTL) {
                import(str(STLPath, "MotorClip.stl"));
            } else {
                MotorClip_Model();
            }
    }
}

module RightMotorClip_STL() {

    // turn off explosions inside STL!!!
    $Explode = false;

    printedPart("printedparts/MotorClip.scad", "Right Motor Clip", "RightMotorClip_STL()") {

        // TODO: Update view
        view(t=[0,0,0], r=[58,0,225], d=681);

        // Color it as a printed plastic part
        color(PlasticColor)
            mirror([1,0,0])
            if (UseSTL) {
                import(str(STLPath, "MotorClip.stl"));
            } else {
                MotorClip_Model();
            }
    }
}

module MotorClip_Model() {
    if (DebugConnectors) {
        // motor shaft
        connector(DefConDown);

        connector(MotorClip_Con_Fixing1);
        connector(MotorClip_Con_Fixing2);

        connector(LeftMotorClip_Con_Driver);
    }

    dbw = ULN2003Driver_BoardWidth + 1;  // includes clearance

    difference() {
        // solid stuff
        union() {
            // plate for front of motor
            attach(MotorClip_Con_Motor, DefConDown)
                hull() {
                    // central disc
                    cylinder(r=MotorOffsetZ-dw, h=dw);

                    // fixing to base plate
                    translate([-10, MotorOffsetZ - 2*dw,0])
                        cube([20,dw, dw]);
                }

            // motor retaining clip
            attach(MotorClip_Con_Motor, DefConDown)
                attach(StepperMotor28YBJ48_Con_Body, DefConUp)
                    translate([0,0,2])
                    rotate([0,0,-10])
                    sector3D(r=StepperMotor28YBJ48_Body_OR + dw, a=200, h=StepperMotor28YBJ48_Body_Depth + 2*dw + 0.5, center=false);

            // base plate
            hull () {
                // pins
                attach(MotorClip_Con_Fixing1, DefConDown) {
                    cylinder(r=12/2, h=dw);
                }

                attach(MotorClip_Con_Fixing2, DefConDown) {
                    cylinder(r=12/2, h=dw);
                }

                // mating join to front plate
                attach(MotorClip_Con_Motor, DefConDown)
                    translate([-10, MotorOffsetZ - 2*dw,0])
                    cube([20, dw, dw]);

                // mating to driver holder?
            }

            // driver holder
            translate([-MotorClip_DriverOffsetX  -2*dw, MotorOffsetZ - 2*dw, MotorClip_DriverOffsetZ-4])
                cube([3*dw, dw, dbw]);

            // outer post
            translate([-MotorClip_DriverOffsetX  -2*dw, MotorOffsetZ - dw - 12, MotorClip_DriverOffsetZ-4])
                cube([3*dw, 12, 4]);

            // inner post
            translate([-MotorClip_DriverOffsetX  -2*dw, MotorOffsetZ - dw - 20, MotorClip_DriverOffsetZ-5.5  + dbw])
                cube([3*dw, 20, 4]);

        }

        // pin fixings
        attach(MotorClip_Con_Fixing1, DefConDown)
            cylinder(r=PinDiameter/2, h=10, center=true);

        attach(MotorClip_Con_Fixing2, DefConDown)
            cylinder(r=PinDiameter/2, h=10, center=true);

        attach(MotorClip_Con_Fixing3, DefConDown)
            cylinder(r=PinDiameter/2, h=30, center=true);

        attach(MotorClip_Con_Fixing4, DefConDown)
            cylinder(r=PinDiameter/2, h=30, center=true);

        attach(MotorClip_Con_Motor, DefConDown) {
            // hollow for motor boss
            cylinder(r=5, h=100, center=true);

            // motor boss/shaft insertion channel
            translate([-10/2, -100, -50])
                cube([10, 100, 100]);

            // hollow for motor body
            attach(MotorClip_Con_Motor, DefConDown)
                attach(StepperMotor28YBJ48_Con_Body, DefConDown)
                cylinder(r=StepperMotor28YBJ48_Body_OR, h=StepperMotor28YBJ48_Body_Depth + 1, center=false);

            // hollow for motor tabs
            translate([0,-8, -0.5])
                cube([100, 7, 1.5], center=true);
        }

        // hollow for driver board
        attach(LeftMotorClip_Con_Driver, ULN2003DriverBoard_Con_UpperLeft)
            ULN2003DriverBoard_PCB(false, ULN2003Driver_PCBThickness+0.5);

        // hollow again, but shifted slightly - cheap hack to provide some side-to-side clearance
        translate([0,0,1])
            attach(LeftMotorClip_Con_Driver, ULN2003DriverBoard_Con_UpperLeft)
            ULN2003DriverBoard_PCB(false, ULN2003Driver_PCBThickness+0.5);
    }
}

/*
    Clip to hold the stepper motor and driver board, mates to base plate using the pin system.

    Local Frame:
        TBC

*/

MotorClip_DriverOffsetX = 20;
MotorClip_DriverOffsetZ = 8;

// Connectors
// connector for where the motor attaches...
Wheel_Con_Default				= [ [0, 0 ,2], [0, 0, 1], 90, 0, 0 ];

MotorClip_Con_Fixing1 = [[20, MotorOffsetZ - dw, 14], [0,1,0], 0,0,0];
MotorClip_Con_Fixing2 = [[-20, MotorOffsetZ -dw, 14], [0,1,0], 0,0,0];

// inner fixing holes
MotorClip_Con_Fixing3 = [[10, MotorOffsetZ - dw, 14], [0,1,0], 0,0,0];
MotorClip_Con_Fixing4 = [[-10, MotorOffsetZ -dw, 14], [0,1,0], 0,0,0];

MotorClip_Con_Motor = [[0, 0, 4.5], [0,0, 1], 0,0,0];

LeftMotorClip_Con_Driver = [[-MotorClip_DriverOffsetX, 1, MotorClip_DriverOffsetZ], [1, 0, 0], 0,0,0];
RightMotorClip_Con_Driver = [[MotorClip_DriverOffsetX, 1, MotorClip_DriverOffsetZ], [-1, 0, 0], 0,0,0];

module LeftMotorClip_STL() {

    // turn off explosions inside STL!!!
    $Explode = false;

    printedPart("printedparts/MotorClip.scad", "Left Motor Clip", "LeftMotorClip_STL()") {

        // TODO: Update view
        view(t=[0,0,0], r=[58,0,225], d=681);

        // Color it as a printed plastic part
        color(PlasticColor)
            if (UseSTL) {
                import(str(STLPath, "MotorClip.stl"));
            } else {
                MotorClip_Model();
            }
    }
}

module RightMotorClip_STL() {

    // turn off explosions inside STL!!!
    $Explode = false;

    printedPart("printedparts/MotorClip.scad", "Right Motor Clip", "RightMotorClip_STL()") {

        // TODO: Update view
        view(t=[0,0,0], r=[58,0,225], d=681);

        // Color it as a printed plastic part
        color(PlasticColor)
            mirror([1,0,0])
            if (UseSTL) {
                import(str(STLPath, "MotorClip.stl"));
            } else {
                MotorClip_Model();
            }
    }
}

module MotorClip_Model() {
    if (DebugConnectors) {
        // motor shaft
        connector(DefConDown);

        connector(MotorClip_Con_Fixing1);
        connector(MotorClip_Con_Fixing2);

        connector(LeftMotorClip_Con_Driver);
    }

    dbw = ULN2003Driver_BoardWidth + 1;  // includes clearance

    difference() {
        // solid stuff
        union() {
            // plate for front of motor
            attach(MotorClip_Con_Motor, DefConDown)
                hull() {
                    // central disc
                    cylinder(r=MotorOffsetZ-dw, h=dw);

                    // fixing to base plate
                    translate([-10, MotorOffsetZ - 2*dw,0])
                        cube([20,dw, dw]);
                }

            // motor retaining clip
            attach(MotorClip_Con_Motor, DefConDown)
                attach(StepperMotor28YBJ48_Con_Body, DefConUp)
                    translate([0,0,2])
                    rotate([0,0,-10])
                    sector3D(r=StepperMotor28YBJ48_Body_OR + dw, a=200, h=StepperMotor28YBJ48_Body_Depth + 2*dw, center=false);

            // base plate
            hull () {
                // pins
                attach(MotorClip_Con_Fixing1, DefConDown) {
                    cylinder(r=12/2, h=dw);
                }

                attach(MotorClip_Con_Fixing2, DefConDown) {
                    cylinder(r=12/2, h=dw);
                }

                // mating join to front plate
                attach(MotorClip_Con_Motor, DefConDown)
                    translate([-10, MotorOffsetZ - 2*dw,0])
                    cube([20, dw, dw]);

                // mating to driver holder?
            }

            // driver holder
            translate([-MotorClip_DriverOffsetX  -2*dw, MotorOffsetZ - 2*dw, MotorClip_DriverOffsetZ-4])
                cube([3*dw, dw, dbw]);

            // outer post
            translate([-MotorClip_DriverOffsetX  -2*dw, MotorOffsetZ - dw - 12, MotorClip_DriverOffsetZ-4])
                cube([3*dw, 12, 4]);

            // inner post
            translate([-MotorClip_DriverOffsetX  -2*dw, MotorOffsetZ - dw - 20, MotorClip_DriverOffsetZ-5.5  + dbw])
                cube([3*dw, 20, 4]);

        }

        // pin fixings
        attach(MotorClip_Con_Fixing1, DefConDown)
            cylinder(r=PinDiameter/2, h=10, center=true);

        attach(MotorClip_Con_Fixing2, DefConDown)
            cylinder(r=PinDiameter/2, h=10, center=true);

        attach(MotorClip_Con_Fixing3, DefConDown)
            cylinder(r=PinDiameter/2, h=30, center=true);

        attach(MotorClip_Con_Fixing4, DefConDown)
            cylinder(r=PinDiameter/2, h=30, center=true);

        attach(MotorClip_Con_Motor, DefConDown) {
            // hollow for motor boss
            cylinder(r=5, h=100, center=true);

            // motor boss/shaft insertion channel
            translate([-10/2, -100, -50])
                cube([10, 100, 100]);

            // hollow for motor body
            attach(MotorClip_Con_Motor, DefConDown)
                attach(StepperMotor28YBJ48_Con_Body, DefConDown)
                cylinder(r=StepperMotor28YBJ48_Body_OR, h=StepperMotor28YBJ48_Body_Depth + 0.5, center=false);

            // hollow for motor tabs
            translate([0,-8, -0.5])
                cube([100, 7, 1.5], center=true);
        }

        // hollow for driver board
        attach(LeftMotorClip_Con_Driver, ULN2003DriverBoard_Con_UpperLeft)
            ULN2003DriverBoard_PCB(false, ULN2003Driver_PCBThickness+0.5);

        // hollow again, but shifted slightly - cheap hack to provide some side-to-side clearance
        translate([0,0,1])
            attach(LeftMotorClip_Con_Driver, ULN2003DriverBoard_Con_UpperLeft)
            ULN2003DriverBoard_PCB(false, ULN2003Driver_PCBThickness+0.5);
    }
}


correcting module misname

correcting module misname

// moreShapes library
// 2D and 3D utility shape functions

// some borrowed from nophead / Mendel90 utils.scad

// fudge
eta = 0.01;

module line(start, end, r) {
	hull() {
		translate(start) sphere(r=r);
		translate(end) sphere(r=r);
	}
}

module roundedSquare(size, radius, center=false, shell=0) {
    x = size[0];
	y = size[1];

	translate([center?-x/2:0, center?-y/2:0, 0])
	difference() {
		hull() {
			translate([radius, radius, 0])
			circle(r=radius);

			translate([x - radius, radius, 0])
			circle(r=radius);

			translate([x - radius, y - radius, 0])
			circle(r=radius);

			translate([radius, y - radius, 0])
			circle(r=radius);
		}

		if (shell > 0) {
			hull() {
			translate([radius + shell, radius + shell, 0])
			circle(r=radius);

			translate([x - radius - shell, radius + shell, 0])
			circle(r=radius);

			translate([x - radius - shell, y - radius - shell, 0])
			circle(r=radius);

			translate([radius + shell, y - radius - shell, 0])
			circle(r=radius);
		}
		}
	}
}

module roundedRect(size, radius, center=false, shell=0) {
	z = size[2];

	translate([0, 0, center?-z/2:0])
	    linear_extrude(height=z)
	    roundedSquare(size=size, radius=radius, center=center, shell=shell);
}

module roundedRectX(size, radius, center=false, shell=0) {
	// X-axis aligned roundedRect
	translate([0,0,center?0:size[2]]) rotate([0,90,0]) roundedRect([size[2],size[1],size[0]], radius, center, shell);
}

module roundedRectY(size, radius, center=false, shell=0) {
	// Y-axis aligned roundedRect
	translate([0,0,center?0:size[2]]) rotate([-90,0,0]) roundedRect([size[0],size[2],size[1]], radius, center, shell);
}

module allRoundedRect(size, radius, center=false) {
	// lazy implementation - must do better
	// runs VERY slow
	translate([center?-size[0]/2:0, center?-size[1]/2:0, center?-size[2]/2:0])
	hull() {
		for (x=[0,size[0]], y=[0,size[1]], z=[0,size[2]]) {
			translate([x,y,z]) sphere(r=radius);
		}
	}
}

module chamferedRect(size, chamfer, center=false) {
	hull() {
		translate([0,center?0:chamfer,0])
			square([size[0],size[1]-2*chamfer],center);

		translate([center?0:chamfer,0,0])
			square([size[0]-2*chamfer,size[1]],center);
	}
}

// Extended rotational extrude, allows control of start/end angle

// Child 2D shape is used for the rotational extrusion
// Child 2D shape should lie in xy plane, centred on z axis
// Child y axis will be aligned to z axis in the extrusion

// NB: Internal render command is necessary to correclty display
// complex child objects (e.g. differences)

// r = Radius of rotational extrusion
// childH = height of child object (approx)
// childW = width of child object (approx)

// Example usage:

//   rotate_extrude_ext(r=50, childW=20, childH=20, start_angle=0, end_angle=180) {
//			difference() {
//				square([20,20],center=true);
//				translate([10,0,0]) circle(5);
//			}
//		}

module rotate_extrude_ext(r, childW, childH, convexity) {
	or = (r + childW/2) * sqrt(2) + 1;
    a0 = (4 * start_angle + 0 * end_angle) / 4;
    a1 = (3 * start_angle + 1 * end_angle) / 4;
    a2 = (2 * start_angle + 2 * end_angle) / 4;
    a3 = (1 * start_angle + 3 * end_angle) / 4;
    a4 = (0 * start_angle + 4 * end_angle) / 4;
    if(end_angle > start_angle)
		render()
        intersection() {
			rotate_extrude(convexity=convexity) translate([r,0,0]) children(0);

			translate([0,0,-childH/2 - 1])
			linear_extrude(height=childH+2)
        		polygon([
		            [0,0],
		            [or * cos(a0), or * sin(a0)],
		            [or * cos(a1), or * sin(a1)],
		            [or * cos(a2), or * sin(a2)],
		            [or * cos(a3), or * sin(a3)],
		            [or * cos(a4), or * sin(a4)],
		            [0,0]
		       ]);
    }
}

module torusSlice(r1, r2, start_angle, end_angle, convexity=10, r3=0, $fn=64) {
	difference() {
		rotate_extrude_ext(r=r1, childH=2*r2, childW=2*r2, start_angle=start_angle, end_angle=end_angle, convexity=convexity) difference() circle(r2, $fn=$fn/4);

		rotate_extrude(convexity) translate([r1,0,0]) circle(r3, $fn=$fn/4);
	}
}

module torus(r1, r2, $fn=64) {
	rotate_extrude()
		translate([r1,0,0])
		circle(r2, $fn=$fn/4);
}

module trapezoid(a,b,h,aOffset=0,center=false) {
	// lies in x/y plane
	// edges a,b are parallel to x axis
	// h is in direction of y axis
	// b is anchored at origin, extends along positive x axis
	// a is offset along y by h, extends along positive x axis
	// a if offset along x axis, from y axis, by aOffset
	// centering is relative to edge b

	translate([center?-b/2:0, center?-h/2:0, 0])
	polygon(points=[	[0,0],
					[aOffset,h],
					[aOffset + a, h],
					[b,0]]);
}

module trapezoidPrism(a,b,h,aOffset,height,center=false) {
	translate([0,0, center?-height/2:0]) linear_extrude(height=height) trapezoid(a,b,h,aOffset,center);
}

module arrangeShapesOnAxis(axis=[1,0,0], spacing=50) {
	for (i=[0:$children-1]) {
		translate([spacing * axis[0] *i,
					spacing * axis[1]*i,
					spacing * axis[2]*i]) children(i);
	}
}

module arrangeShapesOnGrid(xSpacing=50, ySpacing=50, cols=3, showLocalAxes=false) {
	// layout is cols, rows
	for (i=[0:$children-1]) {
		translate([(i - floor(i / cols)*cols) * xSpacing, floor(i / cols) * ySpacing, 0]) {
			children(i);

			if (showLocalAxes) {
				color("red") line([0,0,0], [xSpacing/2,0,0], 0.2);
				color("green") line([0,0,0], [0, ySpacing/2,0], 0.2);
				color("blue") line([0,0,0], [0, 0,xSpacing], 0.2);
			}
		}
	}
}

module slot(h, r, l, center = true)
    linear_extrude(height = h, convexity = 6, center = center)
        hull() {
            translate([l/2,0,0])
                circle(r = r, center = true);
            translate([-l/2,0,0])
                circle(r = r, center = true);
        }

module fillet(r, h) {
	// ready to be unioned onto another part, eta fudge included
	// extends along x, y, z

    translate([r / 2, r / 2, 0])
        difference() {
            cube([r + eta, r + eta, h], center = true);
            translate([r/2, r/2, 0])
                cylinder(r = r, h = h + 1, center = true);
        }
}

module right_triangle_2d(width,height,center=true) {
    polygon(points = [[0,0], [width, 0], [0, height]]);
}

module right_triangle(width, height, h, center = true) {
    linear_extrude(height = h, center = center)
        right_triangle_2d(width,height,center=center);
}

module roundedRightTriangle(width, height, h, r=[1,1,1], center = true, $fn=12) {
    linear_extrude(height = h, center = center)
    	hull() {
        	translate([r[0],r[0],0]) circle(r[0]);
        	translate([width-r[1],r[1],0]) circle(r[1]);
        	translate([r[2],height-r[2],0]) circle(r[2]);
        }
}

module rounded_square(w, h, r, center=true)
{
	// 2D
    union() {
        translate([center?0:r,0,0])
        	square([w - 2 * r, h], center=center);
        translate([0,center?0:r,0])
        	square([w, h - 2 * r], center=center);
        for(x = [(center?-w/2:0) + r, (center?w/2:w) - r])
            for(y = [(center?-h/2:0) + r, (center?h/2:h) - r])
                translate([x, y])
                    circle(r = r);
    }
}

//
// Cylinder with rounded ends
//
module rounded_cylinder(r, h, r2, roundBothEnds=false)
{
    rotate_extrude()
        union() {
            square([r - r2, h]);
            translate([0,roundBothEnds?r2:0,0]) square([r, roundBothEnds? h-2*r2 : h - r2]);
            translate([r - r2, h - r2])
                circle(r = r2);
			if (roundBothEnds) {
				translate([r - r2, r2])
                circle(r = r2);
			}

        }
}

module sector(r, a, h, center = true) {
    linear_extrude(height = h, center = center)
        sector2D(r=r, a=a, center=center);
}

module sector2D(r, a, center = true) {
    intersection() {
            circle(r = r, center = true);
                polygon(points = [
                    [0, 0],
                    [2 * r * cos(0),  2 * r * sin(0)],
                    [2 * r * cos(a * 0.2),  2 * r * sin(a * 0.2)],
                    [2 * r * cos(a * 0.4),  2 * r * sin(a * 0.4)],
                    [2 * r * cos(a * 0.6),  2 * r * sin(a * 0.6)],
                    [2 * r * cos(a * 0.8),  2 * r * sin(a * 0.8)],
                    [2 * r * cos(a), 2 * r * sin(a)],
                ]);
        }
}

module donutSector2D(or,ir,a, center=true) {
	difference() {
		sector2D(or,a,center);

		circle(ir);
	}

}

module tube(or, ir, h, center = true) {
    linear_extrude(height = h, center = center, convexity = 5)
        difference() {
            circle(or);
            circle(ir);
        }
}

// tube that tapers
module conicalTube(or1,ir1,or2,ir2,h) {
	difference() {
		cylinder(r1=or1, r2=or2, h=h);

		translate([0,0,-eta])
			cylinder(r1=ir1, r2=ir2, h=h+2*eta);
	}
}

module moreShapesExamples() {
	arrangeShapesOnGrid(xSpacing=100, ySpacing=80, cols=4, showLocalAxes=true) {
		roundedRect([50,30,20], 5);
		roundedRectX([50,30,20], 5);
		roundedRectY([50,30,20], 5);
		//allRoundedRect([50,30,20], 5);

		roundedRect([50,30,20], 5, true);
		roundedRectX([50,30,20], 5, true);
		roundedRectY([50,30,20], 5, true);
		//allRoundedRect([50,30,20], 5, true);

		torusSlice(50, 6, 0, 120, $fn=64);
		torusSlice(r1=50, r2=6, r3=4, start_angle=0, end_angle=120, $fn=64);   // define r3 for a hollow torus

		trapezoidPrism(20,50,20,10,20);
		trapezoidPrism(20,50,20,10,20,true);

		// to be differenced
		slot(h=20, r=5, l=10);

		// to be unioned
		fillet(r=5, h=50);

		right_triangle(width=30, height=20, h=10, center = false);
		rounded_square(w=30, h=20, r=5);
		rounded_cylinder(r=10, h=50, r2=5, roundBothEnds=false);
		rounded_cylinder(r=10, h=50, r2=5, roundBothEnds=true);

		// same as extruded pieSlice
		sector(r=10, a=70, h=20, center = false);

		tube(or=10, ir=5, h=50, center = false);

		rotate_extrude_ext(r=50, childW=20, childH=20, start_angle=0, end_angle=180) {
			difference() {
				square([20,20],center=true);
				translate([10,0,0]) circle(5);
			}
		}
	}
}

*moreShapesExamples();

module overhangFreeCircle(r, ang) {
	// produces a circle with no overhang ready for extrusion
	// overhang infill is along y axis

	x1 = r*cos(ang);
	y1 = r*sin(ang);
	x2 = tan(ang) * (r-y1);

	union() {
		circle(r);

		for (i=[0,1])
			mirror([0,i,0])
			polygon(points=[[0,0],[x1,y1],[x1-x2,r],[-x1+x2,r],[-x1,y1]]);
	}
}

// supports span x axis
module minSupportBeam(size=[0,0,0], bridge=5, air=0, center=false) {
	w = size[0] > bridge ? (size[0] - bridge)/2 : 0;
	h = w > air ? air : w;

	union() {
		cube(size, center=center);

		// triangular supports
		for (i=[0,1])
			translate([i*size[0],0,eta])
			mirror([i,0,0]) {
				translate([w-h, 0, 0])
					rotate([-90,0,0])
					right_triangle(h, h, size[1], center=false);

				translate([0, 0, -h])
					cube([w-h+eta, size[1], h+eta]);

			}
	}
}

module minSupportBeamY(size=[0,0,0], bridge=5, air=0, center=false) {
	translate([size[0], 0, 0])
		rotate([0,0,90])
		minSupportBeam([size[1], size[0], size[2]], bridge, air, center=center);
}

// moreShapes library
// 2D and 3D utility shape functions

// some borrowed from nophead / Mendel90 utils.scad

// fudge
eta = 0.01;

module line(start, end, r) {
	hull() {
		translate(start) sphere(r=r);
		translate(end) sphere(r=r);
	}
}

module roundedSquare2(size, radius, center=false, shell=0) {
    x = size[0];
	y = size[1];

	translate([center?-x/2:0, center?-y/2:0, 0])
	difference() {
		hull() {
			translate([radius, radius, 0])
			circle(r=radius);

			translate([x - radius, radius, 0])
			circle(r=radius);

			translate([x - radius, y - radius, 0])
			circle(r=radius);

			translate([radius, y - radius, 0])
			circle(r=radius);
		}

		if (shell > 0) {
			hull() {
			translate([radius + shell, radius + shell, 0])
			circle(r=radius);

			translate([x - radius - shell, radius + shell, 0])
			circle(r=radius);

			translate([x - radius - shell, y - radius - shell, 0])
			circle(r=radius);

			translate([radius + shell, y - radius - shell, 0])
			circle(r=radius);
		}
		}
	}
}

module roundedRect(size, radius, center=false, shell=0) {
	z = size[2];

	translate([0, 0, center?-z/2:0])
	    linear_extrude(height=z)
	    roundedSquare2(size=size, radius=radius, center=center, shell=shell);
}

module roundedRectX(size, radius, center=false, shell=0) {
	// X-axis aligned roundedRect
	translate([0,0,center?0:size[2]]) rotate([0,90,0]) roundedRect([size[2],size[1],size[0]], radius, center, shell);
}

module roundedRectY(size, radius, center=false, shell=0) {
	// Y-axis aligned roundedRect
	translate([0,0,center?0:size[2]]) rotate([-90,0,0]) roundedRect([size[0],size[2],size[1]], radius, center, shell);
}

module allRoundedRect(size, radius, center=false) {
	// lazy implementation - must do better
	// runs VERY slow
	translate([center?-size[0]/2:0, center?-size[1]/2:0, center?-size[2]/2:0])
	hull() {
		for (x=[0,size[0]], y=[0,size[1]], z=[0,size[2]]) {
			translate([x,y,z]) sphere(r=radius);
		}
	}
}

module chamferedRect(size, chamfer, center=false) {
	hull() {
		translate([0,center?0:chamfer,0])
			square([size[0],size[1]-2*chamfer],center);

		translate([center?0:chamfer,0,0])
			square([size[0]-2*chamfer,size[1]],center);
	}
}

// Extended rotational extrude, allows control of start/end angle

// Child 2D shape is used for the rotational extrusion
// Child 2D shape should lie in xy plane, centred on z axis
// Child y axis will be aligned to z axis in the extrusion

// NB: Internal render command is necessary to correclty display
// complex child objects (e.g. differences)

// r = Radius of rotational extrusion
// childH = height of child object (approx)
// childW = width of child object (approx)

// Example usage:

//   rotate_extrude_ext(r=50, childW=20, childH=20, start_angle=0, end_angle=180) {
//			difference() {
//				square([20,20],center=true);
//				translate([10,0,0]) circle(5);
//			}
//		}

module rotate_extrude_ext(r, childW, childH, convexity) {
	or = (r + childW/2) * sqrt(2) + 1;
    a0 = (4 * start_angle + 0 * end_angle) / 4;
    a1 = (3 * start_angle + 1 * end_angle) / 4;
    a2 = (2 * start_angle + 2 * end_angle) / 4;
    a3 = (1 * start_angle + 3 * end_angle) / 4;
    a4 = (0 * start_angle + 4 * end_angle) / 4;
    if(end_angle > start_angle)
		render()
        intersection() {
			rotate_extrude(convexity=convexity) translate([r,0,0]) children(0);

			translate([0,0,-childH/2 - 1])
			linear_extrude(height=childH+2)
        		polygon([
		            [0,0],
		            [or * cos(a0), or * sin(a0)],
		            [or * cos(a1), or * sin(a1)],
		            [or * cos(a2), or * sin(a2)],
		            [or * cos(a3), or * sin(a3)],
		            [or * cos(a4), or * sin(a4)],
		            [0,0]
		       ]);
    }
}

module torusSlice(r1, r2, start_angle, end_angle, convexity=10, r3=0, $fn=64) {
	difference() {
		rotate_extrude_ext(r=r1, childH=2*r2, childW=2*r2, start_angle=start_angle, end_angle=end_angle, convexity=convexity) difference() circle(r2, $fn=$fn/4);

		rotate_extrude(convexity) translate([r1,0,0]) circle(r3, $fn=$fn/4);
	}
}

module torus(r1, r2, $fn=64) {
	rotate_extrude()
		translate([r1,0,0])
		circle(r2, $fn=$fn/4);
}

module trapezoid(a,b,h,aOffset=0,center=false) {
	// lies in x/y plane
	// edges a,b are parallel to x axis
	// h is in direction of y axis
	// b is anchored at origin, extends along positive x axis
	// a is offset along y by h, extends along positive x axis
	// a if offset along x axis, from y axis, by aOffset
	// centering is relative to edge b

	translate([center?-b/2:0, center?-h/2:0, 0])
	polygon(points=[	[0,0],
					[aOffset,h],
					[aOffset + a, h],
					[b,0]]);
}

module trapezoidPrism(a,b,h,aOffset,height,center=false) {
	translate([0,0, center?-height/2:0]) linear_extrude(height=height) trapezoid(a,b,h,aOffset,center);
}

module arrangeShapesOnAxis(axis=[1,0,0], spacing=50) {
	for (i=[0:$children-1]) {
		translate([spacing * axis[0] *i,
					spacing * axis[1]*i,
					spacing * axis[2]*i]) children(i);
	}
}

module arrangeShapesOnGrid(xSpacing=50, ySpacing=50, cols=3, showLocalAxes=false) {
	// layout is cols, rows
	for (i=[0:$children-1]) {
		translate([(i - floor(i / cols)*cols) * xSpacing, floor(i / cols) * ySpacing, 0]) {
			children(i);

			if (showLocalAxes) {
				color("red") line([0,0,0], [xSpacing/2,0,0], 0.2);
				color("green") line([0,0,0], [0, ySpacing/2,0], 0.2);
				color("blue") line([0,0,0], [0, 0,xSpacing], 0.2);
			}
		}
	}
}

module slot(h, r, l, center = true)
    linear_extrude(height = h, convexity = 6, center = center)
        hull() {
            translate([l/2,0,0])
                circle(r = r, center = true);
            translate([-l/2,0,0])
                circle(r = r, center = true);
        }

module fillet(r, h) {
	// ready to be unioned onto another part, eta fudge included
	// extends along x, y, z

    translate([r / 2, r / 2, 0])
        difference() {
            cube([r + eta, r + eta, h], center = true);
            translate([r/2, r/2, 0])
                cylinder(r = r, h = h + 1, center = true);
        }
}

module right_triangle_2d(width,height,center=true) {
    polygon(points = [[0,0], [width, 0], [0, height]]);
}

module right_triangle(width, height, h, center = true) {
    linear_extrude(height = h, center = center)
        right_triangle_2d(width,height,center=center);
}

module roundedRightTriangle(width, height, h, r=[1,1,1], center = true, $fn=12) {
    linear_extrude(height = h, center = center)
    	hull() {
        	translate([r[0],r[0],0]) circle(r[0]);
        	translate([width-r[1],r[1],0]) circle(r[1]);
        	translate([r[2],height-r[2],0]) circle(r[2]);
        }
}

module rounded_square(w, h, r, center=true)
{
	// 2D
    union() {
        translate([center?0:r,0,0])
        	square([w - 2 * r, h], center=center);
        translate([0,center?0:r,0])
        	square([w, h - 2 * r], center=center);
        for(x = [(center?-w/2:0) + r, (center?w/2:w) - r])
            for(y = [(center?-h/2:0) + r, (center?h/2:h) - r])
                translate([x, y])
                    circle(r = r);
    }
}

//
// Cylinder with rounded ends
//
module rounded_cylinder(r, h, r2, roundBothEnds=false)
{
    rotate_extrude()
        union() {
            square([r - r2, h]);
            translate([0,roundBothEnds?r2:0,0]) square([r, roundBothEnds? h-2*r2 : h - r2]);
            translate([r - r2, h - r2])
                circle(r = r2);
			if (roundBothEnds) {
				translate([r - r2, r2])
                circle(r = r2);
			}

        }
}

module sector(r, a, h, center = true) {
    linear_extrude(height = h, center = center)
        sector2D(r=r, a=a, center=center);
}

module sector2D(r, a, center = true) {
    intersection() {
            circle(r = r, center = true);
                polygon(points = [
                    [0, 0],
                    [2 * r * cos(0),  2 * r * sin(0)],
                    [2 * r * cos(a * 0.2),  2 * r * sin(a * 0.2)],
                    [2 * r * cos(a * 0.4),  2 * r * sin(a * 0.4)],
                    [2 * r * cos(a * 0.6),  2 * r * sin(a * 0.6)],
                    [2 * r * cos(a * 0.8),  2 * r * sin(a * 0.8)],
                    [2 * r * cos(a), 2 * r * sin(a)],
                ]);
        }
}

module donutSector2D(or,ir,a, center=true) {
	difference() {
		sector2D(or,a,center);

		circle(ir);
	}

}

module tube(or, ir, h, center = true) {
    linear_extrude(height = h, center = center, convexity = 5)
        difference() {
            circle(or);
            circle(ir);
        }
}

// tube that tapers
module conicalTube(or1,ir1,or2,ir2,h) {
	difference() {
		cylinder(r1=or1, r2=or2, h=h);

		translate([0,0,-eta])
			cylinder(r1=ir1, r2=ir2, h=h+2*eta);
	}
}

module moreShapesExamples() {
	arrangeShapesOnGrid(xSpacing=100, ySpacing=80, cols=4, showLocalAxes=true) {
		roundedRect([50,30,20], 5);
		roundedRectX([50,30,20], 5);
		roundedRectY([50,30,20], 5);
		//allRoundedRect([50,30,20], 5);

		roundedRect([50,30,20], 5, true);
		roundedRectX([50,30,20], 5, true);
		roundedRectY([50,30,20], 5, true);
		//allRoundedRect([50,30,20], 5, true);

		torusSlice(50, 6, 0, 120, $fn=64);
		torusSlice(r1=50, r2=6, r3=4, start_angle=0, end_angle=120, $fn=64);   // define r3 for a hollow torus

		trapezoidPrism(20,50,20,10,20);
		trapezoidPrism(20,50,20,10,20,true);

		// to be differenced
		slot(h=20, r=5, l=10);

		// to be unioned
		fillet(r=5, h=50);

		right_triangle(width=30, height=20, h=10, center = false);
		rounded_square(w=30, h=20, r=5);
		rounded_cylinder(r=10, h=50, r2=5, roundBothEnds=false);
		rounded_cylinder(r=10, h=50, r2=5, roundBothEnds=true);

		// same as extruded pieSlice
		sector(r=10, a=70, h=20, center = false);

		tube(or=10, ir=5, h=50, center = false);

		rotate_extrude_ext(r=50, childW=20, childH=20, start_angle=0, end_angle=180) {
			difference() {
				square([20,20],center=true);
				translate([10,0,0]) circle(5);
			}
		}
	}
}

*moreShapesExamples();

module overhangFreeCircle(r, ang) {
	// produces a circle with no overhang ready for extrusion
	// overhang infill is along y axis

	x1 = r*cos(ang);
	y1 = r*sin(ang);
	x2 = tan(ang) * (r-y1);

	union() {
		circle(r);

		for (i=[0,1])
			mirror([0,i,0])
			polygon(points=[[0,0],[x1,y1],[x1-x2,r],[-x1+x2,r],[-x1,y1]]);
	}
}

// supports span x axis
module minSupportBeam(size=[0,0,0], bridge=5, air=0, center=false) {
	w = size[0] > bridge ? (size[0] - bridge)/2 : 0;
	h = w > air ? air : w;

	union() {
		cube(size, center=center);

		// triangular supports
		for (i=[0,1])
			translate([i*size[0],0,eta])
			mirror([i,0,0]) {
				translate([w-h, 0, 0])
					rotate([-90,0,0])
					right_triangle(h, h, size[1], center=false);

				translate([0, 0, -h])
					cube([w-h+eta, size[1], h+eta]);

			}
	}
}

module minSupportBeamY(size=[0,0,0], bridge=5, air=0, center=false) {
	translate([size[0], 0, 0])
		rotate([0,0,90])
		minSupportBeam([size[1], size[0], size[2]], bridge, air, center=center);
}


Added steps parameter description

Added steps parameter description

/*
 * Generates a 3D model of randomized terrain.
 *
 * @param sx - the size of generated model along x axis.
 * @param sy - the size of generated model along y axis.
 * @param base_h - the minimal height of generated model.
 * @param steps - a vector of generation parameters. Each element of that vector adds additional layer of details.
 *                Single element can be a 2 element vector [s, w] or a single integer "s" (in that case "w" is
 *                assumed to be 1). "s" sets resolution of the layer, a new random number is used for the height
 *                every "s" units. "w" sets the weight of the layer.
 * @param seed - random generator seed. If unset then random value is used.
 * @return A 3D block of randomized terrain.
 */
module terrain(sx,sy,base_h=1,steps=[[1,0.2],3,5,7,11],seed=undef) {
    real_seed = seed*0 == 0 ? seed : rands(0,1000000,1)[0];
    function sumv_imp(v,i,acc) = i<0 ? acc : sumv_imp(v, i-1, acc + v[i]);
    function sumv(v) = sumv_imp(v,len(v)-1,0);
    function height(x,y) = sumv([
        for(step=steps)
        let(s=len(step)>=0 ? (len(step)>0 ? step[0] : 0) : step)
        let(w=len(step)>1 ? step[1] : 1)
        terrain_height(x,y,s,real_seed)*w]) + base_h;
    p1 = [[0,0,0],[sx,0,0],[0,sy,0],[sx,sy,0]];
    p2 = [ if (sy>=0 && sx>=0)
            for(y=[0:sy],x=[0:sx] )
                [x,y,height(x,y)] ];
    p = concat(p1,p2);
    function f_idx(x,y) = len(p1)+y*(sx+1)+x;
    f1 = [ if (sy>0 && sx>0)
            for(y=[0:sy-1],x=[0:sx-1])
                [ f_idx(x,y), f_idx(x,y+1), f_idx(x+1,y) ] ];
    f1b = [ if (sy>0 && sx>0)
            for(y=[0:sy-1],x=[0:sx-1])
                [ f_idx(x,y+1), f_idx(x+1,y+1), f_idx(x+1,y) ] ];
    f2 = [ if (sx>0)
            concat([ for(x=[sx:-1:0]) len(p1)+x+sy*(sx+1) ], [2,3]) ];
    f3 = [ if (sx>0)
            concat([ for(x=[0:sx]) len(p1)+x ], [1,0]) ];
    f4 = [ if (sy>0)
            concat([ for(y=[sy:-1:0]) len(p1)+y*(sx+1) ], [0,2]) ];
    f5 = [ if (sy>0)
            concat([ for(y=[0:sy]) len(p1)+sx+y*(sx+1) ], [3,1]) ];
    f6 = [ if (sy>0 && sx>0)
            [0,1,3,2] ];
    f = concat(f1,f1b,f2,f3,f4,f5,f6);
    polyhedron(points=p,faces=f);
}

/*
 * Generates terrain height value for given coordinates.
 *
 * @param x - x coordinate.
 * @param y - y coordinate.
 * @param steps - terrain resolution factor.
 * @param seed - random generator seed.
 * @return The height of the terain at (x,y) coordinates.
 */
function terrain_height(x,y,steps=1,seed=0) =
    let(cx=floor(x/steps),
        cy=floor(y/steps),
        sx=_terrain_rem(x/steps),
        sy=_terrain_rem(y/steps),
        h00=_terrain_cell_height(cx,cy,seed),
        h01=_terrain_cell_height(cx+1,cy,seed),
        h10=_terrain_cell_height(cx,cy+1,seed),
        h11=_terrain_cell_height(cx+1,cy+1,seed))
    _terrain_interpolate2d(h00,h01,h10,h11,sx,sy);

// http://stackoverflow.com/a/919661
function _terrain_pairing_normalize(a) = a >= 0 ? a*2 : -a*2-1;
function _terrain_pairing(i1,i2) =
    let(n1=_terrain_pairing_normalize(i1),
        n2=_terrain_pairing_normalize(i2))
    (n1 + n2)*(n1 + n2 + 1)/2 + n2;

function _terrain_rem(a) = a-floor(a);

function _terrain_interpolate2d(h00,h01,h10,h11,sx,sy) =
    (h00+(h01-h00)*sx)*(1-sy) +
    (h10+(h11-h10)*sx)*sy;

function _terrain_cell_height(x,y,seed) =
    rands(0,1,1,_terrain_pairing(x,y)+seed)[0];

/*
 * Generates a 3D model of randomized terrain.
 *
 * @param sx - the size of generated model along x axis.
 * @param sy - the size of generated model along y axis.
 * @param base_h - the minimal height of generated model.
 * @param steps - a vector of generation parameters
 * @param seed - random generator seed. If unset then random value is used.
 * @return A 3D block of randomized terrain.
 */
module terrain(sx,sy,base_h=1,steps=[[1,0.2],3,5,7,11],seed=undef) {
    real_seed = seed*0 == 0 ? seed : rands(0,1000000,1)[0];
    function sumv_imp(v,i,acc) = i<0 ? acc : sumv_imp(v, i-1, acc + v[i]);
    function sumv(v) = sumv_imp(v,len(v)-1,0);
    function height(x,y) = sumv([
        for(step=steps)
        let(s=len(step)>=0 ? (len(step)>0 ? step[0] : 0) : step)
        let(w=len(step)>1 ? step[1] : 1)
        terrain_height(x,y,s,real_seed)*w]) + base_h;
    p1 = [[0,0,0],[sx,0,0],[0,sy,0],[sx,sy,0]];
    p2 = [ if (sy>=0 && sx>=0)
            for(y=[0:sy],x=[0:sx] )
                [x,y,height(x,y)] ];
    p = concat(p1,p2);
    function f_idx(x,y) = len(p1)+y*(sx+1)+x;
    f1 = [ if (sy>0 && sx>0)
            for(y=[0:sy-1],x=[0:sx-1])
                [ f_idx(x,y), f_idx(x,y+1), f_idx(x+1,y) ] ];
    f1b = [ if (sy>0 && sx>0)
            for(y=[0:sy-1],x=[0:sx-1])
                [ f_idx(x,y+1), f_idx(x+1,y+1), f_idx(x+1,y) ] ];
    f2 = [ if (sx>0)
            concat([ for(x=[sx:-1:0]) len(p1)+x+sy*(sx+1) ], [2,3]) ];
    f3 = [ if (sx>0)
            concat([ for(x=[0:sx]) len(p1)+x ], [1,0]) ];
    f4 = [ if (sy>0)
            concat([ for(y=[sy:-1:0]) len(p1)+y*(sx+1) ], [0,2]) ];
    f5 = [ if (sy>0)
            concat([ for(y=[0:sy]) len(p1)+sx+y*(sx+1) ], [3,1]) ];
    f6 = [ if (sy>0 && sx>0)
            [0,1,3,2] ];
    f = concat(f1,f1b,f2,f3,f4,f5,f6);
    polyhedron(points=p,faces=f);
}

/*
 * Generates terrain height value for given coordinates.
 *
 * @param x - x coordinate.
 * @param y - y coordinate.
 * @param steps - terrain resolution factor.
 * @param seed - random generator seed.
 * @return The height of the terain at (x,y) coordinates.
 */
function terrain_height(x,y,steps=1,seed=0) =
    let(cx=floor(x/steps),
        cy=floor(y/steps),
        sx=_terrain_rem(x/steps),
        sy=_terrain_rem(y/steps),
        h00=_terrain_cell_height(cx,cy,seed),
        h01=_terrain_cell_height(cx+1,cy,seed),
        h10=_terrain_cell_height(cx,cy+1,seed),
        h11=_terrain_cell_height(cx+1,cy+1,seed))
    _terrain_interpolate2d(h00,h01,h10,h11,sx,sy);

// http://stackoverflow.com/a/919661
function _terrain_pairing_normalize(a) = a >= 0 ? a*2 : -a*2-1;
function _terrain_pairing(i1,i2) =
    let(n1=_terrain_pairing_normalize(i1),
        n2=_terrain_pairing_normalize(i2))
    (n1 + n2)*(n1 + n2 + 1)/2 + n2;

function _terrain_rem(a) = a-floor(a);

function _terrain_interpolate2d(h00,h01,h10,h11,sx,sy) =
    (h00+(h01-h00)*sx)*(1-sy) +
    (h10+(h11-h10)*sx)*sy;

function _terrain_cell_height(x,y,seed) =
    rands(0,1,1,_terrain_pairing(x,y)+seed)[0];