shapes/torus: add d parameter

shapes/torus: add d parameter

/*use <../scad-utils/transformations.scad>*/
include <system.scad>
include <units.scad>
use <misc.scad>
use <transforms.scad>

module cubea(size=[10,10,10], align=N, extra_size=N, extra_align=N, orient=Z, orient_ref=Z, roll=0, extra_roll, extra_roll_orient)
{
    size_align(size=size, extra_size=extra_size, align=align, extra_align=extra_align, orient=orient, orient_ref=orient_ref, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
    cube(size+extra_size, center=true);
}

module rcubea(size=[10,10,10], round_r=1, align=N, extra_size=N, extra_align=N, orient=Z, orient_ref=Z, roll=0, extra_roll, extra_roll_orient)
{
    assert(size.x > round_r);
    assert(size.y > round_r);
    assert(size.z > round_r);

    size_align(size=size, extra_size=extra_size, align=align, extra_align=extra_align, orient=orient, orient_ref=orient_ref, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
    rcube(size=size+extra_size, round_r=round_r);
}

module rcube(size=[20,20,20], round_r=1)
{
    if($preview_mode || round_r == 0)
    {
        cubea(size);
    }
    else
    {
        hull()
        for(x=[-(size[0]/2-round_r),(size[0]/2-round_r)])
        for(y=[-(size[1]/2-round_r),(size[1]/2-round_r)])
        for(z=[-(size[2]/2-round_r),(size[2]/2-round_r)])
        translate([x,y,z])
        {
            sphere(r=round_r);
        }
    }
}

module pyramid(size=[10,10,10], orient, align)
{
    let(s=size)
    size_align(size=s, align=align, orient=orient)
    polyhedron(
        points=[
            // the four points at base
            [s.x/2,s.y/2,-s.z/2],[s.x/2,-s.y/2,-s.z/2],[-s.x/2,-s.y/2,-s.z/2],[-s.x/2,s.y/2,-s.z/2],
            // the apex point
            [0,0,s.z/2]
        ],
        faces=[
            // each triangle side
            [0,1,4],[1,2,4],[2,3,4],[3,0,4],
            // two triangles for square base
            [1,0,3],[2,1,3]
        ]
        );
}

module spherea(r, d, align=N, extra_r, extra_d, extra_align, orient=Z, orient_ref=Z, roll=0, extra_roll, extra_roll_orient)
{
    d_ = v_fallback(d, [r*2]);
    assert(d_ != U);

    extra_d_ = v_fallback(extra_d, [extra_r*2, 0]);

    size=[d_,d_,d_];
    extra_size=[extra_d_,extra_d_,extra_d_];
    size_align(size=size, extra_size=extra_size, align=align, extra_align=extra_align, orient=orient, orient_ref=orient_ref, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
    sphere(d=d_+extra_d_);
}

module cylindera(
        h=10,
        r=U,
        r1=U,
        r2=U,
        d=U,
        d1=U,
        d2=U,
        align=N,
        orient=Z,
        extra_h=0,
        extra_r=U,
        extra_d=U,
        extra_align=N,
        round_r=0,
        debug=false
        )
{
    pi=3.1415926536;

    d1_ = v_fallback(d1, [r*2, r1*2]);
    d2_ = v_fallback(d2, [r*2, r2*2]);

    r1_ = v_fallback(r1, [d1_/2, d/2, r]);
    r2_ = v_fallback(r2, [d2_/2, d/2, r]);

    r_max = v_fallback(r, [max(r1_,r2_)]);

    extra_r_ = v_fallback(extra_r, [extra_d/2, 0]);

    if(debug)
    {
        echo(useDia, h, r_, r1_, r2_, extra_r_, align);
    }

    size_align(size=[r_max*2,r_max*2,h], extra_size=[extra_r_*2, extra_r_*2, extra_h], orient=orient, orient_ref=Z, align=align, extra_align=extra_align)
    {
        if(round_r>0)
        {
            rcylindera(h=h+extra_h, r1=r1_+extra_r_, r2=r2_+extra_r_, round_r=round_r);
        }
        else
        {
            cylinder(h=h+extra_h, r1=r1_+extra_r_, r2=r2_+extra_r_, center=true);
        }
    }
}

module torus(d, r, radius, radial_width, align=N, orient=Z)
{
    r_ = v_fallback(r,[d/2, radius, 5]);
    size_align(size=[r_*2+radial_width*2, r_*2+radial_width*2, radial_width*2], align=align, orient=orient)
    rotate_extrude()
    translate(r_*X)
    circle(radial_width);
}

module rcylindera(
        h=10,
        r=U,
        r1=U,
        r2=U,
        d=U,
        d1=U,
        d2=U,
        align=N,
        orient=Z,
        extra_h=0,
        extra_r=U,
        extra_d=U,
        extra_align=N,
        round_r=1,
        debug=false
        )
{
    d1_ = v_fallback(d1, [r*2, r1*2]);
    d2_ = v_fallback(d2, [r*2, r2*2]);

    r1_ = v_fallback(r1, [d1_/2, d/2, r]);
    r2_ = v_fallback(r2, [d2_/2, d/2, r]);

    r_max = v_fallback(r, [max(r1_,r2_)]);

    h_ = h+extra_h;

    extra_r_ = v_fallback(extra_r, [extra_d/2, 0]);

    assert(r1_ != U);
    assert(r2_ != U);
    assert(extra_r_ != U);

    assert(h >= 2*round_r);

    if(debug)
    {
        echo(useDia, h, r_, r1_, r2_, extra_r_, align);
    }

    size_align(size=[r_max*2,r_max*2,h], extra_size=[extra_r_*2, extra_r_*2, extra_h], orient=orient, orient_ref=Z, align=align, extra_align=extra_align)
    {
        if($preview_mode || round_r == 0)
        {
            cylindera(h=h_, r1=r1_+extra_r_, r2=r2_+extra_r_);
        }
        else
        {
            r_ = [r1_+extra_r_/2,r2_+extra_r_/2];

            hull()
            {
                for(z=[-1,1])
                translate([0, 0, z*(-h_/2)])
                {
                    r__=z!=-1?r_[0]:r_[1];
                    torus(radius=r__-round_r, radial_width=round_r, align=z*Z);
                }
            }
        }
    }
}

function circle_profile(r=10, fn=$fn) = [for (i=[0:fn-1]) [r*sin(i*360/fn), r*cos(i*360/fn)]];

function rounded_rectangle_profile(size=[10,10],r=[1,1],fn=32) = [
let(r_ = is_array(r) ? r : [r,r])
for (index = [0:fn-1])
let(a = index/fn*360)
[cos(a)*r.x, sin(a)*r.y]
+ sign_x(index, fn) * X * (size.x/2-r.x)
+ sign_y(index, fn) * Y * (size.y/2-r.y)
];

function sign_x(i,n) =
i < n/4 || i > n-n/4  ?  1 :
i > n/4 && i < n-n/4  ? -1 :
    0;

function sign_y(i,n) =
i > 0 && i < n/2  ?  1 :
i > n/2 ? -1 :
    0;

// From Obiscad
// Draw a point in the position given by the vector p
module point(p, r=0.7, fn)
{
    translate(p)
    sphere(r=r);
}

// Draw a vector poiting to the z axis
// This is an auxiliary module for implementing the vector module
//
// Parameters:
//  l: total vector length (line + arrow)
//  l_arrow: Vector arrow length
//  mark: If true, a mark is draw in the vector head, for having
// a visual reference of the rolling angle
module vectorz(l=10, l_arrow=4, mark=true)
{
    // vector body length (not including the arrow)
    lb = l - l_arrow;

    // The vector is located at 0,0,0
    union()
    {
        // Draw the arrow
        tz(lb)
        cylindera(r1=2/2, r2=0.2, h=l_arrow);

        // Draw the mark
        if(mark)
        tz(lb)
        tx(X)
        cubea([2,0.3,l_arrow*0.8], align=X);

        // Draw the body
        cylindera(r=1/2, h=lb, align=Z);
    }

    // Draw a sphere in the vector base
    spherea(r=1/2, align=-Z);
}

// From Obiscad,
// Draw a vector
//
// There are two modes of drawing the vector
// * Mode 1: Given by a cartesian point(x,y,z). A vector from the origin
//           to the end (x,y,z) is drawn. The l parameter (length) must
//           be 0  (l=0)
// * Mode 2: Give by direction and length
//           A vector of length l pointing to the direction given by
//           v is drawn
// Parameters:
//  v: Vector cartesian coordinates
//  l: total vector length (line + arrow)
//  l_arrow: Vector arrow length
//  mark: If true, a mark is draw in the vector head, for having
//    a visual reference of the rolling angle
module vector(v, l=4, l_arrow=2, mark=false)
{
  //-- Get the vector length from the coordinates
  mod = v_mod(v);

  //-- The vector is very easy implemented by means of the orientate
  //-- operator:
  //--  orientate(v) vectorz(l=mod, l_arrow=l_arrow)
  //--  BUT... in OPENSCAD 2012.02.22 the recursion does not
  //--    not work, so that if the user use the orientate operator
  //--    on a vector, openscad will ignore it..
  //-- The solution at the moment (I hope the openscad developers
  //--  implement the recursion in the near future...)
  //--  is to repite the orientate operation in this module

  //---- SAME CALCULATIONS THAN THE ORIENTATE OPERATOR!
  //-- Calculate the rotation axis

  vref = Z;
  raxis = v_cross(vref,v);

  //-- Calculate the angle between the vectors
  ang = v_anglev(vref,v);

  raxis_=ang==180&&raxis==N?X:raxis;

  //-- orientate the vector
  //-- Draw the vector. The vector length is given either
  //--- by the mod variable (when l=0) or by l (when l!=0)
  if (l==0)
  {
      rotate(a=ang, v=raxis_)
      vectorz(l=mod, l_arrow=l_arrow, mark=mark);
  }
  else
  {
      rotate(a=ang, v=raxis_)
      vectorz(l=l, l_arrow=l_arrow, mark=mark);
  }

}

// From Obiscad,
//--------------------------------------------------------------------
//-- Draw a connector
//-- A connector is defined a 3-tuple that consist of a point
//--- (the attachment point), and axis (the attachment axis) and
//--- an angle the connected part should be rotate around the
//--  attachment axis
//--
//--- Input parameters:
//--
//--  Connector c = [p , n, ang] where:
//--
//--     p : The attachment point
//--     v : The attachment axis
//--   ang : the angle
//--------------------------------------------------------------------
module connector(c)
{
    //-- Get the three components from the connector
    p = c[0];
    v = c[1];
    ang = c[2];

    //-- Draw the attachment point
    color(v)
    point(p);

    //-- Draw the attachment axis vector (with a mark)
    translate(p)
    rotate(a=ang, v=v)
    color(v)
    vector($fn=16, v=v_unitv(v), l=6, l_arrow=2, mark=true);
}

// positive angles go from start to end counterclockwise
// negative angles are allowed
module pie_slice_shape(r, start_angle, end_angle) {
    R = r * 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;
    intersection() {
        circle(r);
        polygon([
                [0,0],
                [R * cos(a0), R * sin(a0)],
                [R * cos(a1), R * sin(a1)],
                [R * cos(a2), R * sin(a2)],
                [R * cos(a3), R * sin(a3)],
                [R * cos(a4), R * sin(a4)],
                [0,0]
        ]);
    }
}

// positive angles go from start to end counterclockwise
// negative angles are allowed
module pie_slice(r, start_angle, end_angle, h, orient=Z, align=N)
{
    size_align(size=[r*2, r*2, h], orient=orient, orient_ref=Z, align=align)
    linear_extrude(h)
    {
        pie_slice_shape(r, start_angle, end_angle);
    }
}

module hollow_cylinder(d=10, thickness=1, h=10, taper=false, taper_h=U, orient=Z, align=N)
{
    outer_d = d+thickness/2;
    inner_d = d-thickness/2;
    taper_h = taper_h == U ? min(h/4, (outer_d-inner_d)/2) : max(taper_h,h/2);
    taper_ = taper && taper_h > 0;
    size_align(size=[outer_d, outer_d, h], orient=orient, align=align);
    difference()
    {
        union()
        {
            cylindera(h=h-(taper_?taper_h*2:0), d=outer_d, orient=Z, align=N);
            if(taper_)
            {
                for(z=[-1,1])
                translate([0,0,z*(h/2-taper_h)])
                mirror([0,0,z==-1?1:0])
                cylindera(d1=outer_d, d2=outer_d-inner_d/4, h=taper_h, align=Z);
            }
        }
        if(taper_)
        {
            for(z=[-1,1])
            translate([0,0,z*(h/2-taper_h)])
            mirror([0,0,z==-1?1:0])
            cylindera(d1=inner_d, d2=outer_d, h=taper_h+.1, align=Z, extra_h=.2);

            // override fn for inner cylinder cut, to ensure same fragments as taper
            // this ensures cleaner mesh
            fn = fn_from_d(d=outer_d);
            cylindera(h=h+.2+taper_h, d=inner_d, orient=Z, align=N, $fn=fn);
        }
        else
        {
            cylindera(h=h, d=inner_d, orient=Z, align=N, extra_h=.4);
        }
    }
}

/**
 * Standard right-angled triangle
 *
 * @param number o_len Lenght of the opposite side
 * @param number a_len Lenght of the adjacent side
 * @param number depth How wide/deep the triangle is in the 3rd dimension
 * @todo a better way ?
 */
module triangle(o_len, a_len, depth, align=N, orient=Z)
{
    size_align(size=[a_len, depth, o_len], align=align, orient=orient)
    rotate([90,0,0])
    translate([-a_len/2, -o_len/2, -depth/2])
    linear_extrude(height=depth)
    {
        polygon(points=[[0,0],[a_len,0],[0,o_len]], paths=[[0,1,2]]);
    }
}

/**
 * Rounded Standard right-angled triangle
 *
 * @param number o_len Lenght of the opposite side
 * @param number a_len Lenght of the adjacent side
 * @param number depth How wide/deep the triangle is in the 3rd dimension
 * @todo a better way ?
 */
module rtriangle(o_len, a_len, depth, round_r=2, align=N, orient=Z)
{
    size = [a_len-round_r, depth-round_r, o_len-round_r];
    r_x = round_r;
    r_y = round_r;
    r_z = -round_r;

    hyp = pythag_hyp(o_len,a_len);
    a_z = atan(a_len/o_len);

    translate([0,0,size[2]+2])
    color(X)
    rotate([90,90,0])
    pie_slice(r=2, start_angle=0, end_angle=a_z, h=depth);

    /*translate([size[0]+4,0,0])*/
    /*color(X)*/
    /*rotate([90,90,0])*/
    /*pie_slice(r=2, start_angle=90, end_angle=90+a_z, h=depth);*/

    size_align(size=size, align=align, orient=orient)
    /*hull()*/
    for(x=[-(size[0]/2)+round_r,(size[0]/2)-r_x])
    for(y=[-(size[1]/2)+round_r,(size[1]/2)-r_y])
    for(z=[-(size[2]/2)+round_r,(size[2]/2)-r_z])
    translate([x,y,z])
    if(x<=1 || z<=1)
    sphere(r=round_r);
}

module teardrop(r, d, h=10, truncate=1, align=N, orient=Y, roll=0)
{
    r_= v_fallback(r, [d/2]);

    sx1 = r_ * sin(-45);
    sx2 = r_ * -sin(-45);
    sy = r_ * -cos(-45);
    ex = 0;
    ey = (sin(-135) + cos(-135)) * r_;

    dx= ex-sx1;
    dy = ey-sy;

    eys = lerp(-r_,ey,1-truncate);

    dys = eys-sy;
    ex1 = sy+dys*dx/dy;
    ex2 = -ex1;

    size_align(size=[d,d,h], align=align, orient=orient, orient_ref=Z, roll=roll)
    union()
    {
        linear_extrude(height = h, center = true, convexity = r_, twist = 0)
        circle(r = r_, center = true);

        linear_extrude(height = h, center = true, convexity = r_, twist = 0)
        polygon(points = [
                [sy, sx1],
                [sy, sx2],
                [eys, ex2],
                [eys, ex1]],
                paths = [[0, 1, 2, 3]]);
    }
}

// test teardrop
if(false)
{
    r=5*mm;
    all_axes()
    translate($axis*r*2)
    color($color)
    teardrop(r=r, h=20, orient=$axis, align=$axis);
}

// test cubea
if(false)
{
    w=5*mm;
    h=2*mm;
    all_axes()
    color($color)
    cubea([w,w,h], orient=$axis, align=$axis*3);
}
/**
/*debug();*/

module debug()
{
    $fs = 0.1;
    $fa = 1;

    /*triangle_a(45, 5, 5);*/
    /*triangle(15, 10, 5, align=[1,1,1], orient=-Z);*/
    /*triangle(15, 10, 5, align=[1,1,1], orient=Z);*/

    triangle(10, 30, 5, align=[1,1,1], orient=Z);
    translate([0,-10,0])
    rtriangle(10, 30, 5, align=[1,1,1], orient=Z);
}

/**
 * Standard right-angled triangle (tangent version)
 *
 * @param number angle of adjacent to hypotenuse (ie tangent)
 * @param number a_len Lenght of the adjacent side
 * @param number depth How wide/deep the triangle is in the 3rd dimension
 */
module triangle_a(tan_angle, a_len, depth)
{
    linear_extrude(height=depth)
    {
        polygon(points=[[0,0],[a_len,0],[0,tan(tan_angle) * a_len]], paths=[[0,1,2]]);
    }
}

// Tests:
module test_triangles()
{
    // Generate a bunch of triangles by sizes
    for (i = [1:10])
    {
        translate([i*7, -30, i*7])
        {
            triangle(i*5, sqrt(i*5+pow(i,2)), 5);
        }
    }

    // Generate a bunch of triangles by angle
    for (i = [1:85/5])
    {
        translate([i*7, 22, i*7])
        {
            triangle_a(i*5, 10, 5);
        }
    }
}

if(false)
{
    stack(axis=X, dist=15)
    {
        hollow_cylinder(thickness=5, h=10, taper=false, orient=Z, align=Z);
        hollow_cylinder(thickness=5, h=10, taper=true, orient=Z, align=Z);
    }

    /*cylindera(d=10, h=10, orient=Z, align=Z);*/
    /*translate([10,0,0])*/
    /*{*/
    /*hollow_cylinder(d=10, thickness=4, h=10, taper=true, taper_h=.5, orient=Z, align=Z);*/
    /*cylindera(d=8, h=10, orient=Z, align=Z);*/
    /*}*/

    /*%pie_slice(r=10, start_angle=-30, end_angle=270, h=10);*/

    /*cubea([10,10,10],X);*/
    /*%cubea([10,10,10],X,[5,5,5],[1,1,1]);*/

    /*%cubea([10,10,10],[-1,0,1]);*/
    /*%cubea([10,10,10],[-1,0,1],[1,1,1],[-1,0,1]);*/
    /*%cylindera(h=10, r=10/2, align=[-1,1,1], extra_r=5/2, extra_h=2, extra_align=[-1,1,1], $fn=100);*/

    /*%cylindera(h=10, d=10, align=[-1,1,1], extra_d=5, extra_h=2, extra_align=[-1,1,1], $fn=100);*/
    /*%cylindera(h=10, d=10, align=[1,1,1], extra_d=15, extra_h=2, extra_align=[1,1,1], $fn=100);*/

    /*%cylindera(h=10, d=10, align=[1,-1,1], extra_d=5, extra_h=2, extra_align=[-1,-1,1], $fn=100);*/
    /*%cylindera(h=10, d=10, align=[1,1,1], extra_d=5, extra_h=2, extra_align=[1,1,1], $fn=100);*/

    /*%cylindera(h=10, d=10, align=X, extra_d=10);*/

    /*hull_pairwise()*/
    /*sphere(30/2);*/

    /*translate([30,0,0])*/
    /*rcubea(s=[35,35,35], round_r=10);*/
    /*rcubea(s=[35,35,35], align=-Y);*/

    /*translate([60,0,0])*/
    /*sphere(30/2);*/

    cylindera(h=20, r=5*mm, align=Y, orient=Z, $fn=100);

/*size_align(size=[5,5,20], orient=Y, orient_ref=Z)*/
/*size_align(size=[5,5,20], orient=Y, orient_ref=Y, align=Y)*/
/*size_align(size=[5,20,5], orient=Z, orient_ref=Y, align=N)*/
/*rotate([90,0,0])*/
/*cylinder(d=5, h=20, center=true);*/

/*cylindera(d=10, h=10, orient=Y, align=Z);*/
}

if(false)
{
    /*cubea();*/
    /*rcubea(size=[10,10,10]);*/

    is_build = true;
    $fs = is_build ? 0.5 : 2;
    $fa = is_build ? 4 : 12;

    stack(axis=X, dist=15)
    {
        cylindera(h=10,r=5);

        rcylindera(h=10,r=5);

        rcubea([10,10,10]);
    }
}

if(false)
{
    r=5*mm;
    all_axes()
    translate($axis*r*2)
    color($color)
    /*orient(axis=$axis, axis_ref=Z)*/
    /*translate(Z*10)*/
    /*rcylindera(r1=5, r2=3, h=10);*/
    {
        rcylindera(h=20, extra_d = 1*mm, r1=r, r2=r/2, align=$axis, orient=$axis, extra_h=r, extra_align=-$axis, $fn=16); /*cylindera(h=20, round_r=1, extra_r = 2*mm, r1=r, r2=r/2, align=$axis, orient=$axis, extra_h=r, extra_align=-$axis, $fn=16);*/
        cylindera(h=20, extra_d = 1*mm, r1=r, r2=r/2, align=$axis, orient=$axis, extra_h=r, extra_align=-$axis, $fn=16); /*cylindera(h=20, round_r=1, extra_r = 2*mm, r1=r, r2=r/2, align=$axis, orient=$axis, extra_h=r, extra_align=-$axis, $fn=16);*/
    }

}

if(false)
{
    all_axes()
    translate($axis*r*2)
    color($color)
    {
        orient(axis=$axis, axis_ref=Z)
        hull()
        {
            cubea(size=[5,8,7]);
            translate(5*Z)
            cubea(size=[2,2,2]);
        }
    }
}

if(false)
{
    stack(dist=50, axis=Y)
    {
        stack(dist=10, axis=-Z)
        {
            /*cylindera(orient=-Z, align=-Z);*/
            pie_slice(r=30, h=5, start_angle=0, end_angle=120, orient=Z, align=-Z);
            pie_slice(r=30, h=5, start_angle=0, end_angle=120, orient=-Z, align=Z);
            /*pie_slice(r=30, h=5, start_angle=-90, end_angle=180, align=-Z);*/
            /*pie_slice(r=30, h=5, start_angle=90, end_angle=270, align=-Z);*/
            /*pie_slice(r=30, h=5, start_angle=0, end_angle=270, align=-Z);*/
        }

        stack(dist=10, axis=Y)
        {
            /*cylindera(orient=Y, align=Y);*/
            pie_slice(r=30, h=5, start_angle=0, end_angle=120, orient=-Y, align=Y);
            pie_slice(r=30, h=5, start_angle=0, end_angle=120, orient=Y, align=Y);
            pie_slice(r=30, h=5, start_angle=-90, end_angle=180, orient=Y, align=Y);
            pie_slice(r=30, h=5, start_angle=90, end_angle=270, orient=Y, align=Y);
            pie_slice(r=30, h=5, start_angle=0, end_angle=270, orient=Y, align=Y);
        }
    }
}

if(false)
{
    /*$fs = 0.5;*/
    /*$fa = 4;*/

    r=5;
    hull()
    {
        stack(dist=10, axis=-Z)
        {
            rcylindera(r=r, h=r*2, orient=Y, align=Y);
            rcylindera(r=r, h=r*2, orient=Y, align=Y);
        }

        rcylindera(r=5, h=r, orient=X, align=Y+X+Z);
    }
}

/*use <../scad-utils/transformations.scad>*/
include <system.scad>
include <units.scad>
use <misc.scad>
use <transforms.scad>

module cubea(size=[10,10,10], align=N, extra_size=N, extra_align=N, orient=Z, orient_ref=Z, roll=0, extra_roll, extra_roll_orient)
{
    size_align(size=size, extra_size=extra_size, align=align, extra_align=extra_align, orient=orient, orient_ref=orient_ref, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
    cube(size+extra_size, center=true);
}

module rcubea(size=[10,10,10], round_r=1, align=N, extra_size=N, extra_align=N, orient=Z, orient_ref=Z, roll=0, extra_roll, extra_roll_orient)
{
    assert(size.x > round_r);
    assert(size.y > round_r);
    assert(size.z > round_r);

    size_align(size=size, extra_size=extra_size, align=align, extra_align=extra_align, orient=orient, orient_ref=orient_ref, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
    rcube(size=size+extra_size, round_r=round_r);
}

module rcube(size=[20,20,20], round_r=1)
{
    if($preview_mode || round_r == 0)
    {
        cubea(size);
    }
    else
    {
        hull()
        for(x=[-(size[0]/2-round_r),(size[0]/2-round_r)])
        for(y=[-(size[1]/2-round_r),(size[1]/2-round_r)])
        for(z=[-(size[2]/2-round_r),(size[2]/2-round_r)])
        translate([x,y,z])
        {
            sphere(r=round_r);
        }
    }
}

module pyramid(size=[10,10,10], orient, align)
{
    let(s=size)
    size_align(size=s, align=align, orient=orient)
    polyhedron(
        points=[
            // the four points at base
            [s.x/2,s.y/2,-s.z/2],[s.x/2,-s.y/2,-s.z/2],[-s.x/2,-s.y/2,-s.z/2],[-s.x/2,s.y/2,-s.z/2],
            // the apex point
            [0,0,s.z/2]
        ],
        faces=[
            // each triangle side
            [0,1,4],[1,2,4],[2,3,4],[3,0,4],
            // two triangles for square base
            [1,0,3],[2,1,3]
        ]
        );
}

module spherea(r, d, align=N, extra_r, extra_d, extra_align, orient=Z, orient_ref=Z, roll=0, extra_roll, extra_roll_orient)
{
    d_ = v_fallback(d, [r*2]);
    assert(d_ != U);

    extra_d_ = v_fallback(extra_d, [extra_r*2, 0]);

    size=[d_,d_,d_];
    extra_size=[extra_d_,extra_d_,extra_d_];
    size_align(size=size, extra_size=extra_size, align=align, extra_align=extra_align, orient=orient, orient_ref=orient_ref, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
    sphere(d=d_+extra_d_);
}

module cylindera(
        h=10,
        r=U,
        r1=U,
        r2=U,
        d=U,
        d1=U,
        d2=U,
        align=N,
        orient=Z,
        extra_h=0,
        extra_r=U,
        extra_d=U,
        extra_align=N,
        round_r=0,
        debug=false
        )
{
    pi=3.1415926536;

    d1_ = v_fallback(d1, [r*2, r1*2]);
    d2_ = v_fallback(d2, [r*2, r2*2]);

    r1_ = v_fallback(r1, [d1_/2, d/2, r]);
    r2_ = v_fallback(r2, [d2_/2, d/2, r]);

    r_max = v_fallback(r, [max(r1_,r2_)]);

    extra_r_ = v_fallback(extra_r, [extra_d/2, 0]);

    if(debug)
    {
        echo(useDia, h, r_, r1_, r2_, extra_r_, align);
    }

    size_align(size=[r_max*2,r_max*2,h], extra_size=[extra_r_*2, extra_r_*2, extra_h], orient=orient, orient_ref=Z, align=align, extra_align=extra_align)
    {
        if(round_r>0)
        {
            rcylindera(h=h+extra_h, r1=r1_+extra_r_, r2=r2_+extra_r_, round_r=round_r);
        }
        else
        {
            cylinder(h=h+extra_h, r1=r1_+extra_r_, r2=r2_+extra_r_, center=true);
        }
    }
}

module torus(r=U, radius=5, radial_width, align=N, orient=Z)
{
    r_ = fallback(r, radius);
    size_align(size=[r_*2+radial_width*2, r_*2+radial_width*2, radial_width*2], align=align, orient=orient)
    rotate_extrude()
    translate(r_*X)
    circle(radial_width);
}

module rcylindera(
        h=10,
        r=U,
        r1=U,
        r2=U,
        d=U,
        d1=U,
        d2=U,
        align=N,
        orient=Z,
        extra_h=0,
        extra_r=U,
        extra_d=U,
        extra_align=N,
        round_r=1,
        debug=false
        )
{
    d1_ = v_fallback(d1, [r*2, r1*2]);
    d2_ = v_fallback(d2, [r*2, r2*2]);

    r1_ = v_fallback(r1, [d1_/2, d/2, r]);
    r2_ = v_fallback(r2, [d2_/2, d/2, r]);

    r_max = v_fallback(r, [max(r1_,r2_)]);

    h_ = h+extra_h;

    extra_r_ = v_fallback(extra_r, [extra_d/2, 0]);

    assert(r1_ != U);
    assert(r2_ != U);
    assert(extra_r_ != U);

    assert(h >= 2*round_r);

    if(debug)
    {
        echo(useDia, h, r_, r1_, r2_, extra_r_, align);
    }

    size_align(size=[r_max*2,r_max*2,h], extra_size=[extra_r_*2, extra_r_*2, extra_h], orient=orient, orient_ref=Z, align=align, extra_align=extra_align)
    {
        if($preview_mode || round_r == 0)
        {
            cylindera(h=h_, r1=r1_+extra_r_, r2=r2_+extra_r_);
        }
        else
        {
            r_ = [r1_+extra_r_/2,r2_+extra_r_/2];

            hull()
            {
                for(z=[-1,1])
                translate([0, 0, z*(-h_/2)])
                {
                    r__=z!=-1?r_[0]:r_[1];
                    torus(radius=r__-round_r, radial_width=round_r, align=z*Z);
                }
            }
        }
    }
}

function circle_profile(r=10, fn=$fn) = [for (i=[0:fn-1]) [r*sin(i*360/fn), r*cos(i*360/fn)]];

function rounded_rectangle_profile(size=[10,10],r=[1,1],fn=32) = [
let(r_ = is_array(r) ? r : [r,r])
for (index = [0:fn-1])
let(a = index/fn*360)
[cos(a)*r.x, sin(a)*r.y]
+ sign_x(index, fn) * X * (size.x/2-r.x)
+ sign_y(index, fn) * Y * (size.y/2-r.y)
];

function sign_x(i,n) =
i < n/4 || i > n-n/4  ?  1 :
i > n/4 && i < n-n/4  ? -1 :
    0;

function sign_y(i,n) =
i > 0 && i < n/2  ?  1 :
i > n/2 ? -1 :
    0;

// From Obiscad
// Draw a point in the position given by the vector p
module point(p, r=0.7, fn)
{
    translate(p)
    sphere(r=r);
}

// Draw a vector poiting to the z axis
// This is an auxiliary module for implementing the vector module
//
// Parameters:
//  l: total vector length (line + arrow)
//  l_arrow: Vector arrow length
//  mark: If true, a mark is draw in the vector head, for having
// a visual reference of the rolling angle
module vectorz(l=10, l_arrow=4, mark=true)
{
    // vector body length (not including the arrow)
    lb = l - l_arrow;

    // The vector is located at 0,0,0
    union()
    {
        // Draw the arrow
        tz(lb)
        cylindera(r1=2/2, r2=0.2, h=l_arrow);

        // Draw the mark
        if(mark)
        tz(lb)
        tx(X)
        cubea([2,0.3,l_arrow*0.8], align=X);

        // Draw the body
        cylindera(r=1/2, h=lb, align=Z);
    }

    // Draw a sphere in the vector base
    spherea(r=1/2, align=-Z);
}

// From Obiscad,
// Draw a vector
//
// There are two modes of drawing the vector
// * Mode 1: Given by a cartesian point(x,y,z). A vector from the origin
//           to the end (x,y,z) is drawn. The l parameter (length) must
//           be 0  (l=0)
// * Mode 2: Give by direction and length
//           A vector of length l pointing to the direction given by
//           v is drawn
// Parameters:
//  v: Vector cartesian coordinates
//  l: total vector length (line + arrow)
//  l_arrow: Vector arrow length
//  mark: If true, a mark is draw in the vector head, for having
//    a visual reference of the rolling angle
module vector(v, l=4, l_arrow=2, mark=false)
{
  //-- Get the vector length from the coordinates
  mod = v_mod(v);

  //-- The vector is very easy implemented by means of the orientate
  //-- operator:
  //--  orientate(v) vectorz(l=mod, l_arrow=l_arrow)
  //--  BUT... in OPENSCAD 2012.02.22 the recursion does not
  //--    not work, so that if the user use the orientate operator
  //--    on a vector, openscad will ignore it..
  //-- The solution at the moment (I hope the openscad developers
  //--  implement the recursion in the near future...)
  //--  is to repite the orientate operation in this module

  //---- SAME CALCULATIONS THAN THE ORIENTATE OPERATOR!
  //-- Calculate the rotation axis

  vref = Z;
  raxis = v_cross(vref,v);

  //-- Calculate the angle between the vectors
  ang = v_anglev(vref,v);

  raxis_=ang==180&&raxis==N?X:raxis;

  //-- orientate the vector
  //-- Draw the vector. The vector length is given either
  //--- by the mod variable (when l=0) or by l (when l!=0)
  if (l==0)
  {
      rotate(a=ang, v=raxis_)
      vectorz(l=mod, l_arrow=l_arrow, mark=mark);
  }
  else
  {
      rotate(a=ang, v=raxis_)
      vectorz(l=l, l_arrow=l_arrow, mark=mark);
  }

}

// From Obiscad,
//--------------------------------------------------------------------
//-- Draw a connector
//-- A connector is defined a 3-tuple that consist of a point
//--- (the attachment point), and axis (the attachment axis) and
//--- an angle the connected part should be rotate around the
//--  attachment axis
//--
//--- Input parameters:
//--
//--  Connector c = [p , n, ang] where:
//--
//--     p : The attachment point
//--     v : The attachment axis
//--   ang : the angle
//--------------------------------------------------------------------
module connector(c)
{
    //-- Get the three components from the connector
    p = c[0];
    v = c[1];
    ang = c[2];

    //-- Draw the attachment point
    color(v)
    point(p);

    //-- Draw the attachment axis vector (with a mark)
    translate(p)
    rotate(a=ang, v=v)
    color(v)
    vector($fn=16, v=v_unitv(v), l=6, l_arrow=2, mark=true);
}

// positive angles go from start to end counterclockwise
// negative angles are allowed
module pie_slice_shape(r, start_angle, end_angle) {
    R = r * 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;
    intersection() {
        circle(r);
        polygon([
                [0,0],
                [R * cos(a0), R * sin(a0)],
                [R * cos(a1), R * sin(a1)],
                [R * cos(a2), R * sin(a2)],
                [R * cos(a3), R * sin(a3)],
                [R * cos(a4), R * sin(a4)],
                [0,0]
        ]);
    }
}

// positive angles go from start to end counterclockwise
// negative angles are allowed
module pie_slice(r, start_angle, end_angle, h, orient=Z, align=N)
{
    size_align(size=[r*2, r*2, h], orient=orient, orient_ref=Z, align=align)
    linear_extrude(h)
    {
        pie_slice_shape(r, start_angle, end_angle);
    }
}

module hollow_cylinder(d=10, thickness=1, h=10, taper=false, taper_h=U, orient=Z, align=N)
{
    outer_d = d+thickness/2;
    inner_d = d-thickness/2;
    taper_h = taper_h == U ? min(h/4, (outer_d-inner_d)/2) : max(taper_h,h/2);
    taper_ = taper && taper_h > 0;
    size_align(size=[outer_d, outer_d, h], orient=orient, align=align);
    difference()
    {
        union()
        {
            cylindera(h=h-(taper_?taper_h*2:0), d=outer_d, orient=Z, align=N);
            if(taper_)
            {
                for(z=[-1,1])
                translate([0,0,z*(h/2-taper_h)])
                mirror([0,0,z==-1?1:0])
                cylindera(d1=outer_d, d2=outer_d-inner_d/4, h=taper_h, align=Z);
            }
        }
        if(taper_)
        {
            for(z=[-1,1])
            translate([0,0,z*(h/2-taper_h)])
            mirror([0,0,z==-1?1:0])
            cylindera(d1=inner_d, d2=outer_d, h=taper_h+.1, align=Z, extra_h=.2);

            // override fn for inner cylinder cut, to ensure same fragments as taper
            // this ensures cleaner mesh
            fn = fn_from_d(d=outer_d);
            cylindera(h=h+.2+taper_h, d=inner_d, orient=Z, align=N, $fn=fn);
        }
        else
        {
            cylindera(h=h, d=inner_d, orient=Z, align=N, extra_h=.4);
        }
    }
}

/**
 * Standard right-angled triangle
 *
 * @param number o_len Lenght of the opposite side
 * @param number a_len Lenght of the adjacent side
 * @param number depth How wide/deep the triangle is in the 3rd dimension
 * @todo a better way ?
 */
module triangle(o_len, a_len, depth, align=N, orient=Z)
{
    size_align(size=[a_len, depth, o_len], align=align, orient=orient)
    rotate([90,0,0])
    translate([-a_len/2, -o_len/2, -depth/2])
    linear_extrude(height=depth)
    {
        polygon(points=[[0,0],[a_len,0],[0,o_len]], paths=[[0,1,2]]);
    }
}

/**
 * Rounded Standard right-angled triangle
 *
 * @param number o_len Lenght of the opposite side
 * @param number a_len Lenght of the adjacent side
 * @param number depth How wide/deep the triangle is in the 3rd dimension
 * @todo a better way ?
 */
module rtriangle(o_len, a_len, depth, round_r=2, align=N, orient=Z)
{
    size = [a_len-round_r, depth-round_r, o_len-round_r];
    r_x = round_r;
    r_y = round_r;
    r_z = -round_r;

    hyp = pythag_hyp(o_len,a_len);
    a_z = atan(a_len/o_len);

    translate([0,0,size[2]+2])
    color(X)
    rotate([90,90,0])
    pie_slice(r=2, start_angle=0, end_angle=a_z, h=depth);

    /*translate([size[0]+4,0,0])*/
    /*color(X)*/
    /*rotate([90,90,0])*/
    /*pie_slice(r=2, start_angle=90, end_angle=90+a_z, h=depth);*/

    size_align(size=size, align=align, orient=orient)
    /*hull()*/
    for(x=[-(size[0]/2)+round_r,(size[0]/2)-r_x])
    for(y=[-(size[1]/2)+round_r,(size[1]/2)-r_y])
    for(z=[-(size[2]/2)+round_r,(size[2]/2)-r_z])
    translate([x,y,z])
    if(x<=1 || z<=1)
    sphere(r=round_r);
}

module teardrop(r, d, h=10, truncate=1, align=N, orient=Y, roll=0)
{
    r_= v_fallback(r, [d/2]);

    sx1 = r_ * sin(-45);
    sx2 = r_ * -sin(-45);
    sy = r_ * -cos(-45);
    ex = 0;
    ey = (sin(-135) + cos(-135)) * r_;

    dx= ex-sx1;
    dy = ey-sy;

    eys = lerp(-r_,ey,1-truncate);

    dys = eys-sy;
    ex1 = sy+dys*dx/dy;
    ex2 = -ex1;

    size_align(size=[d,d,h], align=align, orient=orient, orient_ref=Z, roll=roll)
    union()
    {
        linear_extrude(height = h, center = true, convexity = r_, twist = 0)
        circle(r = r_, center = true);

        linear_extrude(height = h, center = true, convexity = r_, twist = 0)
        polygon(points = [
                [sy, sx1],
                [sy, sx2],
                [eys, ex2],
                [eys, ex1]],
                paths = [[0, 1, 2, 3]]);
    }
}

// test teardrop
if(false)
{
    r=5*mm;
    all_axes()
    translate($axis*r*2)
    color($color)
    teardrop(r=r, h=20, orient=$axis, align=$axis);
}

// test cubea
if(false)
{
    w=5*mm;
    h=2*mm;
    all_axes()
    color($color)
    cubea([w,w,h], orient=$axis, align=$axis*3);
}
/**
/*debug();*/

module debug()
{
    $fs = 0.1;
    $fa = 1;

    /*triangle_a(45, 5, 5);*/
    /*triangle(15, 10, 5, align=[1,1,1], orient=-Z);*/
    /*triangle(15, 10, 5, align=[1,1,1], orient=Z);*/

    triangle(10, 30, 5, align=[1,1,1], orient=Z);
    translate([0,-10,0])
    rtriangle(10, 30, 5, align=[1,1,1], orient=Z);
}

/**
 * Standard right-angled triangle (tangent version)
 *
 * @param number angle of adjacent to hypotenuse (ie tangent)
 * @param number a_len Lenght of the adjacent side
 * @param number depth How wide/deep the triangle is in the 3rd dimension
 */
module triangle_a(tan_angle, a_len, depth)
{
    linear_extrude(height=depth)
    {
        polygon(points=[[0,0],[a_len,0],[0,tan(tan_angle) * a_len]], paths=[[0,1,2]]);
    }
}

// Tests:
module test_triangles()
{
    // Generate a bunch of triangles by sizes
    for (i = [1:10])
    {
        translate([i*7, -30, i*7])
        {
            triangle(i*5, sqrt(i*5+pow(i,2)), 5);
        }
    }

    // Generate a bunch of triangles by angle
    for (i = [1:85/5])
    {
        translate([i*7, 22, i*7])
        {
            triangle_a(i*5, 10, 5);
        }
    }
}

if(false)
{
    stack(axis=X, dist=15)
    {
        hollow_cylinder(thickness=5, h=10, taper=false, orient=Z, align=Z);
        hollow_cylinder(thickness=5, h=10, taper=true, orient=Z, align=Z);
    }

    /*cylindera(d=10, h=10, orient=Z, align=Z);*/
    /*translate([10,0,0])*/
    /*{*/
    /*hollow_cylinder(d=10, thickness=4, h=10, taper=true, taper_h=.5, orient=Z, align=Z);*/
    /*cylindera(d=8, h=10, orient=Z, align=Z);*/
    /*}*/

    /*%pie_slice(r=10, start_angle=-30, end_angle=270, h=10);*/

    /*cubea([10,10,10],X);*/
    /*%cubea([10,10,10],X,[5,5,5],[1,1,1]);*/

    /*%cubea([10,10,10],[-1,0,1]);*/
    /*%cubea([10,10,10],[-1,0,1],[1,1,1],[-1,0,1]);*/
    /*%cylindera(h=10, r=10/2, align=[-1,1,1], extra_r=5/2, extra_h=2, extra_align=[-1,1,1], $fn=100);*/

    /*%cylindera(h=10, d=10, align=[-1,1,1], extra_d=5, extra_h=2, extra_align=[-1,1,1], $fn=100);*/
    /*%cylindera(h=10, d=10, align=[1,1,1], extra_d=15, extra_h=2, extra_align=[1,1,1], $fn=100);*/

    /*%cylindera(h=10, d=10, align=[1,-1,1], extra_d=5, extra_h=2, extra_align=[-1,-1,1], $fn=100);*/
    /*%cylindera(h=10, d=10, align=[1,1,1], extra_d=5, extra_h=2, extra_align=[1,1,1], $fn=100);*/

    /*%cylindera(h=10, d=10, align=X, extra_d=10);*/

    /*hull_pairwise()*/
    /*sphere(30/2);*/

    /*translate([30,0,0])*/
    /*rcubea(s=[35,35,35], round_r=10);*/
    /*rcubea(s=[35,35,35], align=-Y);*/

    /*translate([60,0,0])*/
    /*sphere(30/2);*/

    cylindera(h=20, r=5*mm, align=Y, orient=Z, $fn=100);

/*size_align(size=[5,5,20], orient=Y, orient_ref=Z)*/
/*size_align(size=[5,5,20], orient=Y, orient_ref=Y, align=Y)*/
/*size_align(size=[5,20,5], orient=Z, orient_ref=Y, align=N)*/
/*rotate([90,0,0])*/
/*cylinder(d=5, h=20, center=true);*/

/*cylindera(d=10, h=10, orient=Y, align=Z);*/
}

if(false)
{
    /*cubea();*/
    /*rcubea(size=[10,10,10]);*/

    is_build = true;
    $fs = is_build ? 0.5 : 2;
    $fa = is_build ? 4 : 12;

    stack(axis=X, dist=15)
    {
        cylindera(h=10,r=5);

        rcylindera(h=10,r=5);

        rcubea([10,10,10]);
    }
}

if(false)
{
    r=5*mm;
    all_axes()
    translate($axis*r*2)
    color($color)
    /*orient(axis=$axis, axis_ref=Z)*/
    /*translate(Z*10)*/
    /*rcylindera(r1=5, r2=3, h=10);*/
    {
        rcylindera(h=20, extra_d = 1*mm, r1=r, r2=r/2, align=$axis, orient=$axis, extra_h=r, extra_align=-$axis, $fn=16); /*cylindera(h=20, round_r=1, extra_r = 2*mm, r1=r, r2=r/2, align=$axis, orient=$axis, extra_h=r, extra_align=-$axis, $fn=16);*/
        cylindera(h=20, extra_d = 1*mm, r1=r, r2=r/2, align=$axis, orient=$axis, extra_h=r, extra_align=-$axis, $fn=16); /*cylindera(h=20, round_r=1, extra_r = 2*mm, r1=r, r2=r/2, align=$axis, orient=$axis, extra_h=r, extra_align=-$axis, $fn=16);*/
    }

}

if(false)
{
    all_axes()
    translate($axis*r*2)
    color($color)
    {
        orient(axis=$axis, axis_ref=Z)
        hull()
        {
            cubea(size=[5,8,7]);
            translate(5*Z)
            cubea(size=[2,2,2]);
        }
    }
}

if(false)
{
    stack(dist=50, axis=Y)
    {
        stack(dist=10, axis=-Z)
        {
            /*cylindera(orient=-Z, align=-Z);*/
            pie_slice(r=30, h=5, start_angle=0, end_angle=120, orient=Z, align=-Z);
            pie_slice(r=30, h=5, start_angle=0, end_angle=120, orient=-Z, align=Z);
            /*pie_slice(r=30, h=5, start_angle=-90, end_angle=180, align=-Z);*/
            /*pie_slice(r=30, h=5, start_angle=90, end_angle=270, align=-Z);*/
            /*pie_slice(r=30, h=5, start_angle=0, end_angle=270, align=-Z);*/
        }

        stack(dist=10, axis=Y)
        {
            /*cylindera(orient=Y, align=Y);*/
            pie_slice(r=30, h=5, start_angle=0, end_angle=120, orient=-Y, align=Y);
            pie_slice(r=30, h=5, start_angle=0, end_angle=120, orient=Y, align=Y);
            pie_slice(r=30, h=5, start_angle=-90, end_angle=180, orient=Y, align=Y);
            pie_slice(r=30, h=5, start_angle=90, end_angle=270, orient=Y, align=Y);
            pie_slice(r=30, h=5, start_angle=0, end_angle=270, orient=Y, align=Y);
        }
    }
}

if(false)
{
    /*$fs = 0.5;*/
    /*$fa = 4;*/

    r=5;
    hull()
    {
        stack(dist=10, axis=-Z)
        {
            rcylindera(r=r, h=r*2, orient=Y, align=Y);
            rcylindera(r=r, h=r*2, orient=Y, align=Y);
        }

        rcylindera(r=5, h=r, orient=X, align=Y+X+Z);
    }
}


Update thickness based on measurement / cut test.  Improve base.

// 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.
thickness = 1.8;  // 1.8mm = 0.07" (looks good based on cut test piece)

// Interior box dimensions (from centerpoints of walls, so actual dimension is minus material thickness)
width = 290;    // x = 29cm wall-to-wall
depth = 350;    // y = 35cm to accomodate 34.5cm tank length
i_height = 180+thickness;
                // z = 18cm 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 = 20;

// amount horizontal support bars extend past side supports
outset = overhang/2;

// tank base plate
base_depth = depth-thickness*2;
base_height = overhang;

// 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*3;

// 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 {
    // 3D model
    // components to include (comment out unwanted)
    vert_face(x=0);
    vert_face(x=width);
    side(y=0);
    side(y=depth);
    tank_base();
    light_bar();
    // camera supports
    hanging_supports(x=width, y=depth/2, z=height/2, out=20, up=30, spacing=20);
    // rpi support
    hanging_support(x=width, y=depth/4+9, z=height/2, out=3, up=85);
    mock_rpi(x=width, y=depth/4, z=height/2);
    top_cover();
}

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

module vert_face(x=0) {
    difference() {
        vert_face_base(x);
        camera_opening();
        // camera supports
        hanging_supports(x=width+thickness, y=depth/2, z=height/2+10, out=20, up=30, spacing=20);
        // rpi supports
        hanging_support(x=width+thickness, y=depth/4+9, z=height/2+10, out=3, up=85);
        side(y=0);
        side(y=depth);
        tank_base();
    }
}

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

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

module hanging_supports(x, y, z, out, up, spacing) {
    hanging_support(x, y + spacing/2, z, out, up);
    hanging_support(x, y - spacing/2, z, out, up);
}

module hanging_support(x, y, z, out, up) {
    inner_w = out+thickness;
    inner_h = up;
    inner_offset = 5;
    translate([x,y,z])
    // shift down to center on camera opening
    translate([0,0,-inner_offset])
    // push inner opening up against vert face
    translate([inner_w/2,0,0])
    difference() {
        cube([inner_w+20, thickness, inner_h+30], center=true);
        // cutout for camera
        translate([0,0,inner_offset])
            cube([inner_w, thickness*2, inner_h], center=true);
        // cutout for passing through vertface
        translate([-10-epsilon,0,0])
            cube([inner_w, thickness*2, inner_h-10], center=true);
        // bottom cutout
        translate([-inner_w/2,0,-25])
            cube([thickness, thickness*2, 10], center=true);
    }
}

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,base_height])
    difference() {
        cube([width+outset*2, base_depth, thickness]);
        cutouts(5,depth,outset,rot=-90,trans=[0,base_depth/2,0]);
        cutouts(5,depth,outset,rot=90,trans=[width+outset*2,base_depth/2,0]);
    }
}

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, -epsilon])
            cube([w, outset+thickness, thickness+epsilon*2]);
    }
}

module light_wire_opening() {
    translate([light_pos_x, 0, light_height-10])
    scale([2,1,1])
    rotate(v=[1,0,0], a=90)
        cylinder(d=10, h=thickness+epsilon, center=true);
}

module side(y=0) {
    difference() {
        side_base(y);
        scale([1,1,0.5])
            vert_face_base(x=0);
        scale([1,1,0.5])
            vert_face_base(x=width);
        light_bar();
        light_wire_opening();
        top_cover();
    }
}

module side_base(y) {
    translate([-overhang,y-thickness/2,overhang/2])
        cube([width+overhang*2, thickness, height-overhang/2]);
}

module mock_rpi(x, y, z) {
    translate([x+thickness, y, z])
        rotate(v=[0,1,0], a=90)
            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
//
//  Units are mm

// Set thickness to account for thickness of material
// PLUS needed clearance for cuts into which material should fit.
thickness = 6.35;  // 6.35mm = 1/4"

// Interior box dimensions (from centerpoints of walls, so actual dimension is minus material thickness)
width = 290;    // x = 29cm wall-to-wall
depth = 350;    // y = 35cm to accomodate 34.5cm tank length
i_height = 180+thickness;
                // z = 18cm 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 = 20;

// amount horizontal support bars extend past side supports
outset = overhang/2;

// tank base plate
base_width = width-thickness*2;
base_height = overhang;

// 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*3;

// 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 {
    // 3D model
    // components to include (comment out unwanted)
    vert_face(x=0);
    vert_face(x=width);
    side(y=0);
    side(y=depth);
    tank_base();
    light_bar();
    // camera supports
    hanging_supports(x=width, y=depth/2, z=height/2, out=20, up=30, spacing=20);
    // rpi support
    hanging_support(x=width, y=depth/4+9, z=height/2, out=3, up=85);
    mock_rpi(x=width, y=depth/4, z=height/2);
    top_cover();
}

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

module vert_face(x=0) {
    difference() {
        vert_face_base(x);
        camera_opening();
        // camera supports
        hanging_supports(x=width+thickness, y=depth/2, z=height/2+10, out=20, up=30, spacing=20);
        // rpi supports
        hanging_support(x=width+thickness, y=depth/4+9, z=height/2+10, out=3, up=85);
        scale([1,1,0.5])
            side_base(y=0);
        scale([1,1,0.5])
            side_base(y=depth);
    }
}

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

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

module hanging_supports(x, y, z, out, up, spacing) {
    hanging_support(x, y + spacing/2, z, out, up);
    hanging_support(x, y - spacing/2, z, out, up);
}

module hanging_support(x, y, z, out, up) {
    inner_w = out+thickness;
    inner_h = up;
    inner_offset = 5;
    translate([x,y,z])
    // shift down to center on camera opening
    translate([0,0,-inner_offset])
    // push inner opening up against vert face
    translate([inner_w/2,0,0])
    difference() {
        cube([inner_w+20, thickness, inner_h+30], center=true);
        // cutout for camera
        translate([0,0,inner_offset])
            cube([inner_w, thickness*2, inner_h], center=true);
        // cutout for passing through vertface
        translate([-10-epsilon,0,0])
            cube([inner_w, thickness*2, inner_h-10], center=true);
        // bottom cutout
        translate([-inner_w/2,0,-25])
            cube([thickness, thickness*2, 10], center=true);
    }
}

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([thickness,-outset,base_height])
    difference() {
        cube([base_width, depth+outset*2, thickness]);
        cutouts(5,base_width,outset,rot=0,trans=[base_width/2,0,0]);
        cutouts(5,base_width,outset,rot=180,trans=[base_width/2,depth+outset*2,0]);
    }
}

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, -epsilon])
            cube([w, outset+thickness, thickness+epsilon*2]);
    }
}

module light_wire_opening() {
    translate([light_pos_x, 0, light_height-10])
    scale([2,1,1])
    rotate(v=[1,0,0], a=90)
        cylinder(d=10, h=thickness+epsilon, center=true);
}

module side(y=0) {
    difference() {
        side_base(y);
        vert_face(x=0);
        vert_face(x=width);
        tank_base();
        light_bar();
        light_wire_opening();
        top_cover();
    }
}

module side_base(y) {
    translate([-overhang,y-thickness/2,0])
        cube([width+overhang*2, thickness, height]);
}

module mock_rpi(x, y, z) {
    translate([x+thickness, y, z])
        rotate(v=[0,1,0], a=90)
            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]);
                }
}


Minor comment

Minor comment

// Bathroom Sensor 3D Model with methane sensor, 2 motion sensors and Arduino Yun, http://vor.space
// ©Futurice Oy, paul.houghton@futurice.com, CC-attribution-sharealike license, http://creativecommons.org/licenses/by-sa/4.0/
// Be vary patient, this model may take an hour or more to render

$fn = 8;

cube_side = 140;
cube_tip_clip = 10;
wall_width = 29;  // Bathroom divider wall tunnel size
side_clip_ratio = 2;
left_wing_clip_height = 35;
right_wing_clip_height = 35;

skin = 10;
inner_skin = 8;
thin_skin = 0.1;
motion_skin = 0.2;
yun_skin = 0.4;

sensor_x=94;
sensor_y=40;

yun_x = 0;
yun_y = -22.9/2;
yun_z = -3;

logo_spread=67;

text_x=27;
text_y=12;
text_spread=32;

spike_z1 = 23;
spike_z2 = 51;
dx=1;

//bathroom_sensor_shell();
//bathroom_sensor_shell_left();
bathroom_sensor_shell_right();
//bathroom_sensors_shown();

module bathroom_sensor_shell_left() {
    difference() {
        bathroom_sensor_shell();
        union() {
            translate([-50,0,-cube_side]) cube([cube_side*2, cube_side*2, cube_side*2]);
            unspike(z=spike_z1,skin=.2);
            unspike(z=spike_z2,skin=.2);
        }
    }
    spike(z=spike_z1);
    spike(z=spike_z2);
}

module bathroom_sensor_shell_right() {
    difference() {
        bathroom_sensor_shell();
        union() {
            translate([-50,-cube_side*2,-cube_side]) cube([cube_side*2, cube_side*2, cube_side*2]);
            spike(z=spike_z1,skin=.2);
            spike(z=spike_z2,skin=.2);
        }
    }
    unspike(z=spike_z1);
    unspike(z=spike_z2);
}

module spike(z=0, skin=0) {
    translate([-5+dx,-4.3,z+skin/2]) rotate([0,0,45]) {
        minkowski() {
            cube([8,4,3]);
            sphere(r=skin);
        }
    }
}

module unspike(z=0, skin=0) {
    translate([1+dx,-4.3,z+skin/2]) rotate([0,0,45]) {
        minkowski() {
            cube([8,4,3]);
            sphere(r=skin);
        }
    }
}

module bathroom_sensor_shell() {
    linear_extrude(height=2) {
        text_imprint(theta=0,x=-text_x,y=text_y+text_spread);
        text_imprint(theta=180,x=text_x,y=-text_y-text_spread);
    }
    translate([0,0,36]) union() {
        difference() {
            union() {
                color("red") box();
                color("blue") yun_holder();
                color("pink") translate([123,0,0]) rotate([0,90,0]) {
                    cylinder(r=14.2,h=8);
                }
                logo_moved(theta=0, y=-logo_spread);
                logo_moved(theta=180, y=logo_spread);
            }
            union() {
                methane_space_moved_skin();
                motion_moved_skin(y=-sensor_y, theta=0, rot=120);
                motion_moved_skin(y=sensor_y, theta=90, rot=120+180);
                yun_moved_skin();
                translate([129,-7,14]) rotate([90,0,0]) m3();
                translate([129,7,14]) rotate([-90,0,0]) m3();
            }
        }
        color("green") yun_airgap_feet();
    }
}

module bathroom_sensors_shown() {
    translate([0,0,36]) union() {
        methane_moved();
        motion_moved(y=-sensor_y, theta=0, rot=120);
        motion_moved(y=sensor_y, theta=90, rot=120+180);
    }
}

module motion_moved(y=0, theta=0, rot=0) {
    translate([sensor_x, y, -20])
        rotate([36, 137, theta]) {
            motion_angle(rot);
        }
}

module motion_moved_skin(y=0, theta=0, rot=0) {
    minkowski() {
        motion_moved(y, theta, rot);
        sphere(r=motion_skin);
    }
}

module motion_angle(angle = 0) {
    rotate([0, 0, angle]) {
        motion();
    }
}

module yun_moved_skin() {
    minkowski() {
        yun_moved();
        sphere(r=yun_skin);
    }
}

module yun_moved() {
    translate([yun_x, yun_y, yun_z])
        yun();
}

module yun_holder() {
    translate([-7.5, -wall_width/2, -13])
        cube([81, wall_width, 5]);
}

module yun_airgap_feet() {
    translate([-10, -wall_width/2, -17])
        cube([130, 6, 3]);
    translate([-10, wall_width/2 - 6, -17])
        cube([130, 6, 3]);
}

module methane_moved_skin() {
    minkowski() {
        methane_moved();
        sphere(r=thin_skin);
    }
}

module methane_space_moved_skin() {
    minkowski() {
        methane_space_moved();
        sphere(r=thin_skin);
    }
}

module methane_space_moved() {
    translate([cube_side + skin - cube_tip_clip*2 - 5, 0, 0])
        rotate([0, 90, 0]) {
            methane_space();
        }
}

module methane_moved() {
    translate([cube_side + skin - cube_tip_clip*2 - 5, 0, 0])
        rotate([0, 90, 0]) {
            methane();
        }
}

module poly(clip=0, shorten=0) {
    difference() {
        polyhedron(
            points=[[shorten,cube_side,0],
                [shorten,0,-cube_side],
                [shorten,-cube_side,0],
                [shorten,0,cube_side],
                [cube_side,0,0]],
            faces=[[0,1,4],[1,2,4],[2,3,4],[3,0,4],[1,0,3],[2,1,3]]);
        union() {
            translate([-2*cube_side, -2*cube_side, clip/.85])
                cube([cube_side*4, cube_side*4, cube_side]);
            translate([cube_side - clip/2, 0, 0])
                cube([clip*2, clip*3, clip*3], center = true);
            side_block(wall_width*side_clip_ratio, 180);
            side_block(wall_width*side_clip_ratio, 0);
        }
    }
}

module wall_tunnel() {
    translate([-2*cube_side, -wall_width/2, -2*cube_side - cube_tip_clip/.7])
        cube([4*cube_side, wall_width, 2*cube_side]);
    left_wing_clip();     // Comment this line to make wing full size
    right_wing_clip();     // Comment this line to make wing full size
}

module left_wing_clip() {
    translate([-cube_side, 0, -left_wing_clip_height - cube_side])
        cube([cube_side*2, cube_side, cube_side]);
}

module right_wing_clip() {
    translate([-cube_side, -cube_side, -right_wing_clip_height - cube_side])
        cube([cube_side*2, cube_side, cube_side]);
}

module box() {
    difference() {
        minkowski() {
            difference() {
                union() {
                    poly(clip=cube_tip_clip, shorten=0);
                    intersection() {
                        hull() {
                            poly(clip=cube_tip_clip, shorten=0);
                        }
                        minkowski() {
                            wall_tunnel();
                            sphere(r=skin);
                        }
                    }
                }
            }
            sphere(r=skin);
        }
        union() {
            methane_moved_skin();
            difference() {
                minkowski() {
                    translate([-skin, 0, 0])
                        poly(clip=cube_tip_clip/2, shorten=10);
                    sphere(r=inner_skin);
                }
                minkowski() {
                    wall_tunnel();
                    sphere(r=skin-inner_skin);
                }
            }
            wall_tunnel();
        }
    }
}

module side_block(y=0, theta=0) {
    rotate([theta, 0, 0]) {
        translate([-2*cube_side, y, -2*cube_side])
            cube([4*cube_side, 3*wall_width, 4*cube_side]);
    }
}

module methane_space() {
    minkowski() {
        methane();
        sphere(r=thin_skin);
    }
}

module text_imprint(theta=0,x=0,y=0) {
    translate([x+40,y,0]) rotate([180,0,theta]) {
        translate([0,9,0]) scale([.6,.6,1]) text("http://vor.space");
        scale(.6,.6,1) text("by futurice");
    }
}

module logo_moved(theta=0, y=0) {
    s=.2;
    translate([41, y, -9]) rotate([90,0,theta]) {
        scale([s,s,s]) logo();
    }
}

module logo() {
    import("vor-logo-negative.stl", convexity=10);
}

module yun() {
    include <../3d-iot-component-models/arduino-yun-mini.scad>
}

module methane() {
    include <../3d-iot-component-models/methane-sensor.scad>
}

module motion() {
    include <../3d-iot-component-models/pir-motion-sensor.scad>
}

module m3() {
    include <../3d-iot-component-models/m3.scad>
}

// Bathroom Sensor 3D Model with methane sensor, 2 motion sensors and Arduino Yun, http://vor.space
// ©Futurice Oy, paul.houghton@futurice.com, CC-attribution-sharealike license, http://creativecommons.org/licenses/by-sa/4.0/

$fn = 8;

cube_side = 140;
cube_tip_clip = 10;
wall_width = 29;  // Bathroom divider wall tunnel size
side_clip_ratio = 2;
left_wing_clip_height = 35;
right_wing_clip_height = 35;

skin = 10;
inner_skin = 8;
thin_skin = 0.1;
motion_skin = 0.2;
yun_skin = 0.4;

sensor_x=94;
sensor_y=40;

yun_x = 0;
yun_y = -22.9/2;
yun_z = -3;

logo_spread=67;

text_x=27;
text_y=12;
text_spread=32;

spike_z1 = 23;
spike_z2 = 51;
dx=1;

//bathroom_sensor_shell();
//bathroom_sensor_shell_left();
bathroom_sensor_shell_right();
//bathroom_sensors_shown();

module bathroom_sensor_shell_left() {
    difference() {
        bathroom_sensor_shell();
        union() {
            translate([-50,0,-cube_side]) cube([cube_side*2, cube_side*2, cube_side*2]);
            unspike(z=spike_z1,skin=.2);
            unspike(z=spike_z2,skin=.2);
        }
    }
    spike(z=spike_z1);
    spike(z=spike_z2);
}

module bathroom_sensor_shell_right() {
    difference() {
        bathroom_sensor_shell();
        union() {
            translate([-50,-cube_side*2,-cube_side]) cube([cube_side*2, cube_side*2, cube_side*2]);
            spike(z=spike_z1,skin=.2);
            spike(z=spike_z2,skin=.2);
        }
    }
    unspike(z=spike_z1);
    unspike(z=spike_z2);
}

module spike(z=0, skin=0) {
    translate([-5+dx,-4.3,z+skin/2]) rotate([0,0,45]) {
        minkowski() {
            cube([8,4,3]);
            sphere(r=skin);
        }
    }
}

module unspike(z=0, skin=0) {
    translate([1+dx,-4.3,z+skin/2]) rotate([0,0,45]) {
        minkowski() {
            cube([8,4,3]);
            sphere(r=skin);
        }
    }
}

module bathroom_sensor_shell() {
    linear_extrude(height=2) {
        text_imprint(theta=0,x=-text_x,y=text_y+text_spread);
        text_imprint(theta=180,x=text_x,y=-text_y-text_spread);
    }
    translate([0,0,36]) union() {
        difference() {
            union() {
                color("red") box();
                color("blue") yun_holder();
                color("pink") translate([123,0,0]) rotate([0,90,0]) {
                    cylinder(r=14.2,h=8);
                }
                logo_moved(theta=0, y=-logo_spread);
                logo_moved(theta=180, y=logo_spread);
            }
            union() {
                methane_space_moved_skin();
                motion_moved_skin(y=-sensor_y, theta=0, rot=120);
                motion_moved_skin(y=sensor_y, theta=90, rot=120+180);
                yun_moved_skin();
                translate([129,-7,14]) rotate([90,0,0]) m3();
                translate([129,7,14]) rotate([-90,0,0]) m3();
            }
        }
        color("green") yun_airgap_feet();
    }
}

module bathroom_sensors_shown() {
    translate([0,0,36]) union() {
        methane_moved();
        motion_moved(y=-sensor_y, theta=0, rot=120);
        motion_moved(y=sensor_y, theta=90, rot=120+180);
    }
}

module motion_moved(y=0, theta=0, rot=0) {
    translate([sensor_x, y, -20])
        rotate([36, 137, theta]) {
            motion_angle(rot);
        }
}

module motion_moved_skin(y=0, theta=0, rot=0) {
    minkowski() {
        motion_moved(y, theta, rot);
        sphere(r=motion_skin);
    }
}

module motion_angle(angle = 0) {
    rotate([0, 0, angle]) {
        motion();
    }
}

module yun_moved_skin() {
    minkowski() {
        yun_moved();
        sphere(r=yun_skin);
    }
}

module yun_moved() {
    translate([yun_x, yun_y, yun_z])
        yun();
}

module yun_holder() {
    translate([-7.5, -wall_width/2, -13])
        cube([81, wall_width, 5]);
}

module yun_airgap_feet() {
    translate([-10, -wall_width/2, -17])
        cube([130, 6, 3]);
    translate([-10, wall_width/2 - 6, -17])
        cube([130, 6, 3]);
}

module methane_moved_skin() {
    minkowski() {
        methane_moved();
        sphere(r=thin_skin);
    }
}

module methane_space_moved_skin() {
    minkowski() {
        methane_space_moved();
        sphere(r=thin_skin);
    }
}

module methane_space_moved() {
    translate([cube_side + skin - cube_tip_clip*2 - 5, 0, 0])
        rotate([0, 90, 0]) {
            methane_space();
        }
}

module methane_moved() {
    translate([cube_side + skin - cube_tip_clip*2 - 5, 0, 0])
        rotate([0, 90, 0]) {
            methane();
        }
}

module poly(clip=0, shorten=0) {
    difference() {
        polyhedron(
            points=[[shorten,cube_side,0],
                [shorten,0,-cube_side],
                [shorten,-cube_side,0],
                [shorten,0,cube_side],
                [cube_side,0,0]],
            faces=[[0,1,4],[1,2,4],[2,3,4],[3,0,4],[1,0,3],[2,1,3]]);
        union() {
            translate([-2*cube_side, -2*cube_side, clip/.85])
                cube([cube_side*4, cube_side*4, cube_side]);
            translate([cube_side - clip/2, 0, 0])
                cube([clip*2, clip*3, clip*3], center = true);
            side_block(wall_width*side_clip_ratio, 180);
            side_block(wall_width*side_clip_ratio, 0);
        }
    }
}

module wall_tunnel() {
    translate([-2*cube_side, -wall_width/2, -2*cube_side - cube_tip_clip/.7])
        cube([4*cube_side, wall_width, 2*cube_side]);
    left_wing_clip();     // Comment this line to make wing full size
    right_wing_clip();     // Comment this line to make wing full size
}

module left_wing_clip() {
    translate([-cube_side, 0, -left_wing_clip_height - cube_side])
        cube([cube_side*2, cube_side, cube_side]);
}

module right_wing_clip() {
    translate([-cube_side, -cube_side, -right_wing_clip_height - cube_side])
        cube([cube_side*2, cube_side, cube_side]);
}

module box() {
    difference() {
        minkowski() {
            difference() {
                union() {
                    poly(clip=cube_tip_clip, shorten=0);
                    intersection() {
                        hull() {
                            poly(clip=cube_tip_clip, shorten=0);
                        }
                        minkowski() {
                            wall_tunnel();
                            sphere(r=skin);
                        }
                    }
                }
            }
            sphere(r=skin);
        }
        union() {
            methane_moved_skin();
            difference() {
                minkowski() {
                    translate([-skin, 0, 0])
                        poly(clip=cube_tip_clip/2, shorten=10);
                    sphere(r=inner_skin);
                }
                minkowski() {
                    wall_tunnel();
                    sphere(r=skin-inner_skin);
                }
            }
            wall_tunnel();
        }
    }
}

module side_block(y=0, theta=0) {
    rotate([theta, 0, 0]) {
        translate([-2*cube_side, y, -2*cube_side])
            cube([4*cube_side, 3*wall_width, 4*cube_side]);
    }
}

module methane_space() {
    minkowski() {
        methane();
        sphere(r=thin_skin);
    }
}

module text_imprint(theta=0,x=0,y=0) {
    translate([x+40,y,0]) rotate([180,0,theta]) {
        translate([0,9,0]) scale([.6,.6,1]) text("http://vor.space");
        scale(.6,.6,1) text("by futurice");
    }
}

module logo_moved(theta=0, y=0) {
    s=.2;
    translate([41, y, -9]) rotate([90,0,theta]) {
        scale([s,s,s]) logo();
    }
}

module logo() {
    import("vor-logo-negative.stl", convexity=10);
}

module yun() {
    include <../3d-iot-component-models/arduino-yun-mini.scad>
}

module methane() {
    include <../3d-iot-component-models/methane-sensor.scad>
}

module motion() {
    include <../3d-iot-component-models/pir-motion-sensor.scad>
}

module m3() {
    include <../3d-iot-component-models/m3.scad>
}


One Ommatidium can be drawn.

One Ommatidium can be drawn.

Modules on OpenSCAD are awesome!

// Mockup of OmmatiDiag
/*
 * The mockup was done after the first crude drawing with
 * [Tinkrecad](https://tinkercad.com/things/3utMiKsZhx9) proved to be too
 * inflexible.
 * The basic unit of the detecotor will be the scintillator size, which we
 * chose to be 17" x 17" translating to 430 x 430 mm in SI units.
*/

// Basic length variables [mm]
length = 430;
padding = 20;
height = 100;

// Housing
module Housing()
  color ("gray", 0.618) {
    // box bottom
    cube([length + padding, length + padding,1]);
    // box back walls
    cube([length + padding, 1, height]);
    cube([1, length + padding, height]);
    }

    // box front walls, can (and should be turned off for increased visibility)
    /*
    color ("gray", 0.6) {
      translate(v = [0, length + padding, 0]) {
        cube([length + padding, 1, height]);
      }
      translate(v = [length + padding, 0, 0]) {
        cube([1, length + padding, height]); //
      }
    }
    */

//Housing();

// Scintillator
module Scintillator()
  translate(v = [padding/2, padding/2, 1]) {
    color ("green", 0.25) {
      cube([length,length,1]);
      }
    }
//~ Scintillator();

// Ommatidium
module Ommatidium() {
  // FOV of one Ommatidium
  FOVSize = ([430/4, 430/3]);
  module FOV()
    color("pink",0.5) cube([FOVSize[0], FOVSize[1], 1], center=true);

  // CMOS (Aptina AR0130)
  CMOSSize = ([1280,960]);
  pixelsize = 3.75 / 1000; // [um]
  module CMOS()
    color ("blue", 0.618) cube([CMOSSize[0] * pixelsize ,CMOSSize[1] * pixelsize, 0.5], center=true);
  translate ([0,0,0]) CMOS();

  // FOV CMOS
  d_cmos_lens = 5;
  d_lens_scintillator = 20;
  module CMOSCone()
      color("green",0.5) cylinder(h = d_cmos_lens, r1 = CMOSSize[0] * pixelsize / 2 , r2 = lensdiameter * 2, center=true);
  module CMOSCross() {
    color("red", 1) polyhedron(
      points=[ [0, 0, 0], //origin
        [-CMOSSize[0]*pixelsize/2, 0, d_cmos_lens],                 // horizontal_1
        [CMOSSize[0]*pixelsize/2, 0,d_cmos_lens], // horizontal_2
        [0, -CMOSSize[1] * pixelsize / 2,d_cmos_lens],                    // vertical_1
        [0,  CMOSSize[1] * pixelsize / 2,d_cmos_lens]],  // vertical_2
      triangles=[[0,1,2], [0,3,4]]);
    }

  mirror([0,0,1]) translate([0,0,-d_cmos_lens])CMOSCross();
  translate([0,0,d_cmos_lens/2])CMOSCone();

  // Lens (http://is.gd/4H9sZf)
  lensdiameter = 2;
  module Lens()
    // the lens is a squashed sphere which we translate to the middle of the CMOS
      scale([2,2,0.5]) sphere(lensdiameter, $fn=50, center=true);
  translate([0, 0, d_cmos_lens]) Lens();

  // FOV Lens
  module LensCone()
    color("green",0.5) cylinder(h = d_lens_scintillator, r1 = lensdiameter * 2 , r2 = FOVSize[1]/2);
  module LensCross() {
    color("red", 1) polyhedron(
      points=[ [0, 0, 0], //origin
        [-FOVSize[0]/2, 0, d_lens_scintillator],   // horizontal_1
        [FOVSize[0]/2, 0,d_lens_scintillator],     // horizontal_2
        [0, -FOVSize[1]/2 ,d_lens_scintillator],   // vertical_1
        [0,  FOVSize[1]/2 ,d_lens_scintillator]],  // vertical_2
      triangles=[[0,1,2], [0,3,4]]);
    }
  translate([0,0,d_cmos_lens]) LensCross();
  translate([0,0,d_cmos_lens]) LensCone();
  translate([0,0,d_cmos_lens + d_lens_scintillator]) FOV();
}

Ommatidium();


fixup! misc: Rename sum_vec to v_sum

fixup! misc: Rename sum_vec to v_sum

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);

function vec_add(vec,v) = [for(vv=vec) vv+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];

// sum a vector up to position i
function v_sum(v, i) = i >= 0 ? v[i] + v_sum(v, i - 1) : 0;

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

// filter/remove a val from a vec
function filter(vec,val=undef) = [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);

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);

function vec_add(vec,v) = [for(vv=vec) vv+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];

// sum a vector up to position i
function v_sum(v, i) = i >= 0 ? v[i] + sum_vec(v, i - 1) : 0;

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

// filter/remove a val from a vec
function filter(vec,val=undef) = [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);