screws/nut_trap_cut: fix orientation and add cut_screw_close

include <system.scad>
include <thread-data.scad>
include <nut-data.scad>
include <materials.scad>

use <shapes.scad>
use <transforms.scad>
use <misc.scad>

// for proper threads
use <naca_sweep.scad>

// naive, assume head height is same as thread size (generally true for cap heads)
function get_screw_head_h(head, thread) =
(head=="socket" || head == "button") ?
    get(ThreadSize, thread)
:
head == "set" ? 0
:
0;

function get_screw_head_d(head, thread) =
(head=="socket" || head == "button") ?
    2 * get(ThreadSize, thread)
:
head == "set" ? 0
:
0;

module screw(part, nut, thread, head="socket", h=10, tolerance=1.05, head_embed=false, with_nut=true, with_head=true, nut_offset=0, orient=Z, align=N)
{
    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "screw: No nut or thread given");
    assert(head != U, "screw: head == U");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "screw: Mismatched nut and thread");*/

    head_h = get_screw_head_h(head=head, thread=thread_);
    nut_h = get(NutThickness,nut)*tolerance;
    threadsize = get(ThreadSize, thread_);

    s = threadsize*tolerance;
    total_h = h;

    if(part==U)
    {
        difference()
        {
            screw(part="pos", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
            screw(part="neg", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
        }
        if($show_vit)
        %screw(part="vit", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
    }
    else if(part=="pos")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    tz(head_embed?-head_h:0)
    {
        if(with_head)
        {
            tz(h/2+.01)
            screw_head(part=part, head=head, thread=thread_, orient=Z, align=Z);
        }

        tz(-h/2+.01)
        screw_thread(thread=thread_, h=h+.1, tolerance=tolerance, orient=Z, align=N);

        if(with_nut && nut != U)
        {
            tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
            screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);
        }
    }
    else if(part=="neg")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    tz(head_embed?-head_h:0)
    {
        if(with_head)
        tz(h/2+.01)
        screw_head(part=part, head=head, thread=thread_, orient=Z, align=Z);
    }
    else if(part=="vit")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        if(with_nut && nut != U)
        {
            tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
            %screw_nut(nut=nut, align=-Z);
        }
    }
}

module screw_cut(nut, thread, head="socket", h=10, tolerance=1.05, head_embed=false, with_nut=true, with_nut_cut=true, with_nut_access=true, nut_cut_h=1000, with_head=true, head_cut_h=1000, nut_offset=0, cut_access=true, orient=Z, align=N)
{
    assert(h>0);

    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "screw_cut: No nut or thread given");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "screw_cut: Mismatched nut and thread");*/

    nut_h = get(NutThickness,nut)*tolerance;
    assert(head != U, "screw_cut: head == U");

    head_h = get_screw_head_h(head=head, thread=thread_);
    assert(head_h != U, "screw_cut: head_h is U!");

    threadsize = get(ThreadSize, thread_);
    assert(threadsize != U, "screw_cut: threadsize is U!");

    s = threadsize*tolerance;
    total_h = h;
    assert(s != U, "screw_cut: s is U!");
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        tz(head_embed?-head_h:0)
        {
            if(with_head)
            {
                tz(h/2+.01)
                screw_head_cut(head=head, thread=thread_, tolerance=tolerance, override_h=head_cut_h, align=Z);
            }

            screw_thread_cut(thread=thread_, h=h+.1, tolerance=tolerance, orient=Z, align=N);

            if(with_nut && nut != U && with_nut_cut)
            {
                tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
                screw_nut_cut(nut=nut, tolerance=tolerance, h=nut_cut_h, with_access=with_nut_access, align=-Z);
            }

            if($show_vit)
            {
                %screw(nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=-Z, align=N);
            }
        }
    }
}

module screw_thread(thread, h=10, tolerance=1.00, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread: thread is undef");
    threadsize = get(ThreadSize, thread);
    d = get(ThreadSize, thread);
    pitch = get(ThreadPitchMm, thread);
    windings = ceil(h/pitch);

    size_align(size=[d,d,h], orient=orient, align=align)
    material(Mat_Steel)
    {
        if($preview_mode)
        {
            cylindera(d=d, h=h, align=Z);
        }
        else
        {
            intersection()
            {
                cylindera(d=d, h=h, align=Z);
                tz(-pitch)
                threading(angle=60, pitch=pitch, d=d, windings=windings, full=true);
            }
            /*inner_d = (get(ThreadInternalMinorDiaMin, thread) + get(ThreadInternalMinorDiaMin, thread))/2;*/
            /*cylindera(d=inner_d, h=h, align=Z);*/
        }
    }
}

module screw_thread_cut(thread, h=10, tolerance=1.05, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread_cut: thread is undef");
    threadsize = get(ThreadSize, thread);
    cylindera(d=threadsize*tolerance, h=h, orient=orient, align=align);
}

module screw_head(part, head="socket", drive="hex", thread, tolerance=1.00, override_h=U, orient=Z, align=N)
{
    assert(head != U, "screw_head: head == U");
    assert(thread!=U, "screw_head: thread == undef");

    threadsize = get(ThreadSize, thread);
    head_h = fallback(override_h, get_screw_head_h(head=head, thread=thread));
    head_d = get_screw_head_d(head=head, thread=thread);

    if(part==U)
    {
        difference()
        {
            screw_head(part="pos", head=head, drive=drive, thread=thread, tolerance=tolerance, override_h=override_h, orient=orient, align=align);
            screw_head(part="neg", head=head, drive=drive, thread=thread, tolerance=tolerance, override_h=override_h, orient=orient, align=align);
        }
    }
    else if(part=="pos")
    material(Mat_Steel)
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        if(head=="socket")
        {
            tz(-head_h/2)
            cylindera(d=head_d, h = head_h, align=Z);
        }
        else if(head=="button")
        {
            c = head_d;
            f = head_h*1.25;
            r = ( pow(c,2)/4 + pow(f,2) )/(2*f);

            d = r - f; // displacement to move sphere

            intersection()
            {
                tz(-head_h/2)
                scale([1,1,.5])
                spherea(r = head_h, align=N);

                tz(-head_h/2)
                cylindera(d=head_d, h = head_h/2, align=Z);
            }
        }
    }
    else if(part=="neg")
    material(Mat_Steel)
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        tz(-head_h/2)
        if(drive=="hex")
        {
            threadsize_cut = threadsize-head_drive_threadsize_minus(head);
            inset = head_drive_inset(head, head_h);
            tz(inset)
            cylindera(d = fn_radius(threadsize_cut, 6), h = head_h, $fn = 6, align=Z);
        }
        else if(part=="neg")
        {
            if(head=="socket")
            {
                difference()
                {
                    tz(-head_h/2)
                    cylindera(d=head_d, h = fallback(override_h,head_h), align=Z);

                    if(drive == "hex")
                    {
                        cylindera(d = fn_radius(threadsize, 6), h = 1000, $fn = 6, align=Z);
                    }
                }
            }
            else if(head=="button")
            {
                c = head_d;
                f = head_h*1.25;
                r = ( pow(c,2)/4 + pow(f,2) )/(2*f);

                d = r - f; // displacement to move sphere

                difference()
                {
                    intersection()
                    {
                        tz(-head_h/2)
                        scale([1,1,.5])
                        spherea(r = head_h, align=N);

                        tz(-head_h/2)
                        cylindera(d=head_d, h = fallback(override_h,head_h)/2, align=Z);
                    }

                    if(drive == "hex")
                    {
                        // button head uses -0.5*mm smaller hex
                        tz(-head_h/2)
                        cylindera(d = fn_radius(threadsize-.5, 6), h = 1000, $fn = 6, align=Z);
                    }
                }
            }
            else if(head=="set")
            {
            }
        }
    }
}

function head_drive_threadsize_minus(head) =
head=="socket"?  0:
head=="button"?  0.5:
head=="set"?     1.5:
0;

function head_drive_inset(head, head_h) =
head=="socket"?  1*mm:
head=="button"?  0.5*mm:
head=="set"?     -head_h/2:
0;

module screw_head_cut(head="socket", thread, tolerance=1.05, override_h=U, orient=Z, align=N)
{
    assert(head != U, "screw_head: head == U");
    assert(thread!=U, "screw_head_cut: thread is undef");

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(head=head, thread=thread);
    head_d = get_screw_head_d(head=head, thread=thread)*tolerance;
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        if(head=="socket" || head=="button")
        {
            tz(-head_h/2)
            cylindera(d=head_d, h=fallback(override_h, head_h), align=Z);
        }
        else if(head=="set")
        {
            tz(-head_h/2)
            cylindera(d=threadsize, h=fallback(override_h, head_h), align=Z);
        }
        else
        {
            assert("head: not implemented");
        }
    }
}

module screw_nut(nut, thread, tolerance=1.00, override_h=U, orient=Z, align=N)
{
    assert(nut!=U, "screw_nut: nut is undef");

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut);
    nut_thread = fallback(thread, get(NutThread, nut));
    h_ = fallback(override_h, nut_thick);
    material(Mat_Steel)
    size_align([nut_dia, nut_dia, h_], orient=orient, align=align)
    {
        difference()
        {
            cylindera($fn=nut_facets, d=nut_dia, h=h_);
            tz(-h_/2-.2)
            screw_thread(thread=nut_thread, h=h_+.1);
        }
    }
}

module screw_nut_cut(nut, tolerance=1.05, h=1000, with_access=true, orient=Z, align=N)
{
    assert(nut!=U, "screw_nut_cut: nut is undef");

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_dia = nut_dia(nut);
    size_align(size=[nut_dia, nut_dia, nut_thick], orient=orient, align=align)
    {
        tz(nut_thick/2)
        {
            tz(-.1)
            screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);

            if(with_access)
            tz(-nut_thick-.01)
            {
                cylindera(d=nut_dia+2*mm, h=h, orient=Z, align=-Z);
            }
        }
    }
}

function nut_radius(nut) = outradius(get(NutWidthMin, nut)/2, get(NutFacets, nut));
function nut_dia(nut) = 2*nut_radius(nut);

module nut_trap_cut(nut, thread, head="socket", trap_offset=10, screw_l=10*mm, screw_offset=0*mm, cut_screw=false, cut_screw_close=false, trap_h=10, trap_axis=-Y, orient=Z, align=N)
{
    screw_min_nut_dist = 2*mm;
    assert(screw_l+screw_offset >= trap_offset, "nut_trap_cut: screw_l+screw_offset < trap_offset");
    assert(screw_offset-trap_offset <= -screw_min_nut_dist, "nut_trap_cut: screw_offset-trap_offset > -screw_min_nut_dist");

    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "nut_trap_cut: No nut or thread given");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "nut_trap_cut: Mismatched nut and thread");*/

    threadsize = get(ThreadSize, thread_);
    head_h = get_screw_head_h(head=head, thread=thread);
    nut_h = get(NutThickness, nut) +.5*mm;

    tolerance = -.2*mm;
    nut_width_min = get(NutWidthMin, nut)+tolerance;
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut)+tolerance;
    total_h = nut_h;
    size_align(size=[nut_dia, nut_dia, total_h], orient=orient, orient_ref=Z, align=align)
    {
        if(cut_screw)
        {
            if(cut_screw_close)
            {
                tz(-nut_h/2)
                tz(-screw_min_nut_dist)
                tz(trap_offset)
                screw_cut($show_vit=false, nut=nut, thread=thread_, head=head, with_nut=false, tolerance=1.1, h=screw_l, orient=Z, align=Z);
            }
            else
            {
                tz(-nut_h/2)
                tz(screw_offset)
                screw_cut($show_vit=false, nut=nut, thread=thread_, head=head, with_nut=false, tolerance=1.1, h=screw_l, orient=Z, align=Z);
            }
        }
        else
        {
            tz(nut_h/2+screw_offset)
            screw_thread_cut($show_vit=false, thread=thread_, tolerance=1.1, h=screw_l, orient=Z, align=Z);
        }

        tz(trap_offset)
        hull()
        {
            orient(axis=Z, axis_ref=orient)
            translate(-.15*mm*trap_axis)
            stack(dist=trap_h, axis=trap_axis)
            {
                orient(axis=orient, axis_ref=Z)
                {
                    rotate([0,0,30])
                    cylindera($fn=nut_facets, d=nut_dia, h=nut_h, align=N);
                }

                orient(axis=orient, axis_ref=Z)
                cubea(size=[nut_width_min,nut_width_min,nut_h], align=N);
            }
        }

        if($show_vit)
        {
            if(cut_screw)
            {
                tz(-nut_h/2)
                tz(screw_offset)
                %screw(nut=nut, thread=thread_, head=head, with_nut=false, tolerance=1, h=screw_l, orient=Z, align=Z);
            }

            tz(trap_offset)
            rotate([0,0,30])
            %screw_nut(nut=nut, thread=thread, tolerance=1, orient=Z, align=N);
        }

    }
}

if(false)
{
    all_axes()
    translate($axis*5*mm)
    color($color)
    screw(nut=NutHexM3, h=10*mm, orient=-$axis, align=$axis);
}

// all nuts
if(false)
{
    for(nuti=[0:1:len(AllNut)-1])
    {
        nut = AllNut[nuti];
        v_threadsize = v_get(AllNut,ThreadSize);
        v_nutwidthmin = v_get(AllNut,NutWidthMin);
        dist = v_cumsum(v_nutwidthmin, 0, nuti)[nuti]*1.2;
        translate(X*dist)
        rotate([0,0,30])
        screw(nut=nut, h=get(NutWidthMax, nut)*5, head_embed=false, orient=-Z, align=Z);
    }
}

// From ParkinBots threading library
// https://www.thingiverse.com/thing:1659079
/*module Threading(D = 0, pitch = 1, d = 12, windings = 10, helices = 1, angle = 60, steps=40)*/
/*{*/
    /*R = D==0?d/2+2*pitch/PI:D/2; */
    /*translate([0,0,-pitch])*/
    /*difference()*/
    /*{*/
        /*translate([0,0,pitch])*/
        /*cylinder (r=R, h =pitch*(windings-helices));*/
        /*threading(pitch, d, windings, helices, angle, steps, true); */
    /*}*/
/*}*/

// From ParkinBots threading library
// https://www.thingiverse.com/thing:1659079
module threading(pitch = 1, d = 12, windings = 10, helices = 1, angle = 60, steps=40, full = false)
{
    // tricky: glue two 180° pieces together to get a proper manifold
    r = d/2;
    Steps = steps/2;
    Pitch = pitch*helices;
    if(full)
    {
        cylinder(r = r-.5-pitch/PI, h=pitch*(windings+helices), $fn=steps);
    }

    sweep(gen_dat(), planar_caps=true);   // half screw
    rz(180)
    tz(Pitch/2)
    sweep(gen_dat(), planar_caps=true);   // half screw

    function gen_dat() =
        let(ang = 180, bar = R_(180, -90, 0, Ty_(-r+.5, vec3D(pitch/PI*Rack(windings, angle)))))
        [for (i=[0:Steps]) Tz_(i/2/Steps*Pitch, -Rz_(i*ang/Steps, bar))];

    function Rack(w, angle) =
        concat([[0, 2]],
            [for (i=[0:w-1], j=[0:3])
            let(t = [ [0, 1], [2*tan(angle/2), -1], [PI/2, -1], [2*tan(angle/2)+PI/2, 1]])
            [t[j][0]+i*PI, t[j][1]]], [[w*PI, 1], [w*PI, 2]
            ]);
}

if(false)
{
    $fn=32;
    all_heads=
    [
        "socket",
        "button",
        "set"
    ];
    for(i=[0:len(all_heads)-1])
    {
        head=all_heads[i];
        tx(i*8*mm)
        screw(nut=NutHexM3, head=head, h=25, orient=-Z);
    }

}

if(false)
{
    $fn=128;
    nut1 = NutHexM3;
    stack(axis=X, dist = 20)
    {
        screw(nut=NutHexM8, head="socket", h=25, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, with_head=1, with_nut=0, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, with_head=0, with_nut=0, orient=-Z);
        screw(nut=NutHexM8, head="set", h=25, with_nut=0, orient=-Z);

        threading(pitch = 1.25, d=8, windings=10, full=true);
    }
}

if(false)
{
    box_w = 150*mm;
    box_d = 10*mm;
    box_h = 10*mm;
    o = 10*mm;

    $show_vit = true;
    /*$fs= 0.2;*/
    /*$fa = 4;*/

    nut1 = NutHexM3;
    nut2 = NutHexM5;
    nut3 = NutKnurlM3_3_42;
    nut4 = NutKnurlM3_5_42;

    /*all_axes()*/
    /*for($axis=[X])*/
    /*color($color)*/
    /*translate($axis*15)*/
    /*echo($axis)*/

    /*orient(axis=$axis, axis_ref=-X)*/
    for(y=[-1,1])
    translate(-y*Y*20*mm)
    difference()
    {
        cubea([box_w,box_d,box_h], align=X+Z);
        test_cuts(orient=y*Z);
        /*%test_cuts(orient=y, axis_ref=-Z);*/
    }

}

if(false)
{
    nut1 = NutHexM5;
    stack(dist=10,axis=X)
    {
        $show_vit=true;
        $preview_mode=true;
        #screw(nut=nut1, h=10, orient=-Z, align=-Z);
        #screw_cut(nut=nut1, h=10, orient=-Z, align=-Z);
        #screw_cut(nut=nut1, h=10, orient=-Z, align=-Z);
        #nut_trap_cut(nut=nut1, screw_l=10, cut_screw=false, orient=-Z, align=-Z);
        #nut_trap_cut(nut=nut1, screw_l=10, cut_screw=true, orient=-Z, align=-Z);
        #screw(nut=nut1, h=20, orient=-Z, align=-Z);
        #nut_trap_cut(nut=nut1, screw_l=20, cut_screw=true, orient=-Z, align=-Z);
        #nut_trap_cut(nut=nut1, screw_l=20, trap_offset=20, cut_screw=true, orient=-Z, align=-Z);
        #nut_trap_cut(nut=nut1, screw_l=25, screw_offset=-5, trap_offset=20, cut_screw=true, orient=-Z, align=-Z);
        #nut_trap_cut(nut=nut1, screw_l=20, cut_screw=true, cut_screw_close=true, orient=-Z, align=-Z);
    }
}

module test_cuts(orient)
{
    union()
    {
        /*offset = 0;*/
        offset = -5;
        /*translate([0,offset,0])*/
        /*translate([5,0,-5])*/
        translate([5,0,0])
        stack(dist=10,axis=X)
        {
            screw_cut(nut=nut1, head="button", h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut1, head="socket", h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(thread=ThreadM5, h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut1, h=box_h, head_embed=false, orient=orient, align=Z);

            screw_cut(nut=nut1, h=box_h, nut_offset=0*mm, head_embed=true, orient=orient, align=Z);
            screw_cut(nut=nut2, h=box_h, nut_offset=3*mm, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut3, h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut4, h=box_h, head_embed=false, orient=orient, align=Z);

            translate([0,0,-5*mm])
            screw_cut(nut=nut3, h=box_h, head_embed=false, orient=orient, align=Z);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=5, head_embed=false, trap_h=10, trap_axis=-Y, orient=orient, align=N);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=5, head_embed=false, trap_h=10, trap_axis=Y, orient=orient, align=N);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=box_d, head_embed=false, trap_h=10, screw_offset=2*mm, trap_axis=Z, orient=orient, align=N);
            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=box_d, head_embed=false, trap_h=10, screw_offset=2*mm, trap_axis=-Z, orient=orient, align=N);
        }
    }
}

include <system.scad>
include <thread-data.scad>
include <nut-data.scad>
include <materials.scad>

use <shapes.scad>
use <transforms.scad>
use <misc.scad>

// for proper threads
use <naca_sweep.scad>

// naive, assume head height is same as thread size (generally true for cap heads)
function get_screw_head_h(head, thread) =
(head=="socket" || head == "button") ?
    get(ThreadSize, thread)
:
head == "set" ? 0
:
0;

function get_screw_head_d(head, thread) =
(head=="socket" || head == "button") ?
    2 * get(ThreadSize, thread)
:
head == "set" ? 0
:
0;

module screw(part, nut, thread, head="socket", h=10, tolerance=1.05, head_embed=false, with_nut=true, with_head=true, nut_offset=0, orient=Z, align=N)
{
    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "screw: No nut or thread given");
    assert(head != U, "screw: head == U");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "screw: Mismatched nut and thread");*/

    head_h = get_screw_head_h(head=head, thread=thread_);
    nut_h = get(NutThickness,nut)*tolerance;
    threadsize = get(ThreadSize, thread_);

    s = threadsize*tolerance;
    total_h = h;

    if(part==U)
    {
        difference()
        {
            screw(part="pos", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
            screw(part="neg", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
        }
        if($show_vit)
        %screw(part="vit", nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, head_embed=head_embed, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=orient, align=align);
    }
    else if(part=="pos")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    tz(head_embed?-head_h:0)
    {
        if(with_head)
        {
            tz(h/2+.01)
            screw_head(part=part, head=head, thread=thread_, orient=Z, align=Z);
        }

        tz(-h/2+.01)
        screw_thread(thread=thread_, h=h+.1, tolerance=tolerance, orient=Z, align=N);

        if(with_nut && nut != U)
        {
            tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
            screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);
        }
    }
    else if(part=="neg")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    tz(head_embed?-head_h:0)
    {
        if(with_head)
        tz(h/2+.01)
        screw_head(part=part, head=head, thread=thread_, orient=Z, align=Z);
    }
    else if(part=="vit")
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        if(with_nut && nut != U)
        {
            tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
            %screw_nut(nut=nut, align=-Z);
        }
    }
}

module screw_cut(nut, thread, head="socket", h=10, tolerance=1.05, head_embed=false, with_nut=true, with_nut_cut=true, with_nut_access=true, nut_cut_h=1000, with_head=true, head_cut_h=1000, nut_offset=0, cut_access=true, orient=Z, align=N)
{
    assert(h>0);

    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "screw_cut: No nut or thread given");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "screw_cut: Mismatched nut and thread");*/

    nut_h = get(NutThickness,nut)*tolerance;
    assert(head != U, "screw_cut: head == U");

    head_h = get_screw_head_h(head=head, thread=thread_);
    assert(head_h != U, "screw_cut: head_h is U!");

    threadsize = get(ThreadSize, thread_);
    assert(threadsize != U, "screw_cut: threadsize is U!");

    s = threadsize*tolerance;
    total_h = h;
    assert(s != U, "screw_cut: s is U!");
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        tz(head_embed?-head_h:0)
        {
            if(with_head)
            {
                tz(h/2+.01)
                screw_head_cut(head=head, thread=thread_, tolerance=tolerance, override_h=head_cut_h, align=Z);
            }

            screw_thread_cut(thread=thread_, h=h+.1, tolerance=tolerance, orient=Z, align=N);

            if(with_nut && nut != U && with_nut_cut)
            {
                tz(-h/2+nut_h+nut_offset+(head_embed?head_h:0))
                screw_nut_cut(nut=nut, tolerance=tolerance, h=nut_cut_h, with_access=with_nut_access, align=-Z);
            }

            if($show_vit)
            {
                %screw(nut=nut, thread=thread, head=head, h=h, tolerance=tolerance, with_nut=with_nut, with_head=with_head, nut_offset=nut_offset, orient=-Z, align=N);
            }
        }
    }
}

module screw_thread(thread, h=10, tolerance=1.00, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread: thread is undef");
    threadsize = get(ThreadSize, thread);
    d = get(ThreadSize, thread);
    pitch = get(ThreadPitchMm, thread);
    windings = ceil(h/pitch);

    size_align(size=[d,d,h], orient=orient, align=align)
    material(Mat_Steel)
    {
        if($preview_mode)
        {
            cylindera(d=d, h=h, align=Z);
        }
        else
        {
            intersection()
            {
                cylindera(d=d, h=h, align=Z);
                tz(-pitch)
                threading(angle=60, pitch=pitch, d=d, windings=windings, full=true);
            }
            /*inner_d = (get(ThreadInternalMinorDiaMin, thread) + get(ThreadInternalMinorDiaMin, thread))/2;*/
            /*cylindera(d=inner_d, h=h, align=Z);*/
        }
    }
}

module screw_thread_cut(thread, h=10, tolerance=1.05, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread_cut: thread is undef");
    threadsize = get(ThreadSize, thread);
    cylindera(d=threadsize*tolerance, h=h, orient=orient, align=align);
}

module screw_head(part, head="socket", drive="hex", thread, tolerance=1.00, override_h=U, orient=Z, align=N)
{
    assert(head != U, "screw_head: head == U");
    assert(thread!=U, "screw_head: thread == undef");

    threadsize = get(ThreadSize, thread);
    head_h = fallback(override_h, get_screw_head_h(head=head, thread=thread));
    head_d = get_screw_head_d(head=head, thread=thread);

    if(part==U)
    {
        difference()
        {
            screw_head(part="pos", head=head, drive=drive, thread=thread, tolerance=tolerance, override_h=override_h, orient=orient, align=align);
            screw_head(part="neg", head=head, drive=drive, thread=thread, tolerance=tolerance, override_h=override_h, orient=orient, align=align);
        }
    }
    else if(part=="pos")
    material(Mat_Steel)
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        if(head=="socket")
        {
            tz(-head_h/2)
            cylindera(d=head_d, h = head_h, align=Z);
        }
        else if(head=="button")
        {
            c = head_d;
            f = head_h*1.25;
            r = ( pow(c,2)/4 + pow(f,2) )/(2*f);

            d = r - f; // displacement to move sphere

            intersection()
            {
                tz(-head_h/2)
                scale([1,1,.5])
                spherea(r = head_h, align=N);

                tz(-head_h/2)
                cylindera(d=head_d, h = head_h/2, align=Z);
            }
        }
    }
    else if(part=="neg")
    material(Mat_Steel)
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        tz(-head_h/2)
        if(drive=="hex")
        {
            threadsize_cut = threadsize-head_drive_threadsize_minus(head);
            inset = head_drive_inset(head, head_h);
            tz(inset)
            cylindera(d = fn_radius(threadsize_cut, 6), h = head_h, $fn = 6, align=Z);
        }
        else if(part=="neg")
        {
            if(head=="socket")
            {
                difference()
                {
                    tz(-head_h/2)
                    cylindera(d=head_d, h = fallback(override_h,head_h), align=Z);

                    if(drive == "hex")
                    {
                        cylindera(d = fn_radius(threadsize, 6), h = 1000, $fn = 6, align=Z);
                    }
                }
            }
            else if(head=="button")
            {
                c = head_d;
                f = head_h*1.25;
                r = ( pow(c,2)/4 + pow(f,2) )/(2*f);

                d = r - f; // displacement to move sphere

                difference()
                {
                    intersection()
                    {
                        tz(-head_h/2)
                        scale([1,1,.5])
                        spherea(r = head_h, align=N);

                        tz(-head_h/2)
                        cylindera(d=head_d, h = fallback(override_h,head_h)/2, align=Z);
                    }

                    if(drive == "hex")
                    {
                        // button head uses -0.5*mm smaller hex
                        tz(-head_h/2)
                        cylindera(d = fn_radius(threadsize-.5, 6), h = 1000, $fn = 6, align=Z);
                    }
                }
            }
            else if(head=="set")
            {
            }
        }
    }
}

function head_drive_threadsize_minus(head) =
head=="socket"?  0:
head=="button"?  0.5:
head=="set"?     1.5:
0;

function head_drive_inset(head, head_h) =
head=="socket"?  1*mm:
head=="button"?  0.5*mm:
head=="set"?     -head_h/2:
0;

module screw_head_cut(head="socket", thread, tolerance=1.05, override_h=U, orient=Z, align=N)
{
    assert(head != U, "screw_head: head == U");
    assert(thread!=U, "screw_head_cut: thread is undef");

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(head=head, thread=thread);
    head_d = get_screw_head_d(head=head, thread=thread)*tolerance;
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    {
        if(head=="socket" || head=="button")
        {
            tz(-head_h/2)
            cylindera(d=head_d, h=fallback(override_h, head_h), align=Z);
        }
        else if(head=="set")
        {
            tz(-head_h/2)
            cylindera(d=threadsize, h=fallback(override_h, head_h), align=Z);
        }
        else
        {
            assert("head: not implemented");
        }
    }
}

module screw_nut(nut, thread, tolerance=1.00, override_h=U, orient=Z, align=N)
{
    assert(nut!=U, "screw_nut: nut is undef");

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut);
    nut_thread = fallback(thread, get(NutThread, nut));
    h_ = fallback(override_h, nut_thick);
    material(Mat_Steel)
    size_align([nut_dia, nut_dia, h_], orient=orient, align=align)
    {
        difference()
        {
            cylindera($fn=nut_facets, d=nut_dia, h=h_);
            tz(-h_/2-.2)
            screw_thread(thread=nut_thread, h=h_+.1);
        }
    }
}

module screw_nut_cut(nut, tolerance=1.05, h=1000, with_access=true, orient=Z, align=N)
{
    assert(nut!=U, "screw_nut_cut: nut is undef");

    nut_thick = get(NutThickness,nut)*tolerance;
    nut_dia = nut_dia(nut);
    size_align(size=[nut_dia, nut_dia, nut_thick], orient=orient, align=align)
    {
        tz(nut_thick/2)
        {
            tz(-.1)
            screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);

            if(with_access)
            tz(-nut_thick-.01)
            {
                cylindera(d=nut_dia+2*mm, h=h, orient=Z, align=-Z);
            }
        }
    }
}

function nut_radius(nut) = outradius(get(NutWidthMin, nut)/2, get(NutFacets, nut));
function nut_dia(nut) = 2*nut_radius(nut);

module nut_trap_cut(nut, thread, head="socket", trap_offset=10, screw_l=10*mm, screw_offset=0*mm, cut_screw=false, trap_h=10, trap_axis=-Y, orient=Z, align=N)
{
    nut_thread = get(NutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != U, "nut_trap_cut: No nut or thread given");
    /*assert(nut!=U && thread!=U && nut_thread != thread_, "nut_trap_cut: Mismatched nut and thread");*/

    threadsize = get(ThreadSize, thread_);
    head_h = get_screw_head_h(head=head, thread=thread);
    nut_h = get(NutThickness, nut) +.5*mm;

    tolerance = -.2*mm;
    nut_width_min = get(NutWidthMin, nut)+tolerance;
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut)+tolerance;
    total_h = nut_h;
    size_align(size=[nut_dia, nut_dia, total_h], orient=orient, orient_ref=Z, align=align)
    {
        if(cut_screw)
        {
            tz(-nut_h/2)
            tz(screw_offset)
            screw_cut($show_vit=false, nut=nut, thread=thread_, head=head, with_nut=false, tolerance=1.1, h=screw_l, orient=-Z, align=Z);
        }
        else
        {
            tz(nut_h/2+screw_offset)
            screw_thread_cut($show_vit=false, thread=thread_, tolerance=1.1, h=screw_l, orient=-Z, align=Z);
        }

        tz(trap_offset)
        hull()
        {
            orient(axis=Z, axis_ref=orient)
            translate(-.15*mm*trap_axis)
            stack(dist=trap_h, axis=trap_axis)
            {
                orient(axis=orient, axis_ref=Z)
                {
                    rotate([0,0,30])
                    cylindera($fn=nut_facets, d=nut_dia, h=nut_h, align=N);
                }

                orient(axis=orient, axis_ref=Z)
                cubea(size=[nut_width_min,nut_width_min,nut_h], align=N);
            }
        }

        tz(trap_offset)
        if($show_vit)
        {
            if(cut_screw)
            {
                tz(-nut_h/2)
                tz(screw_offset)
                %screw(nut=nut, thread=thread_, head=head, with_nut=false, tolerance=1, h=screw_l, orient=-Z, align=Z);
            }

            rotate([0,0,30])
            %screw_nut(nut=nut, thread=thread, tolerance=1, orient=-Z, align=N);
        }

    }
}

if(false)
{
    all_axes()
    translate($axis*5*mm)
    color($color)
    screw(nut=NutHexM3, h=10*mm, orient=-$axis, align=$axis);
}

// all nuts
if(false)
{
    for(nuti=[0:1:len(AllNut)-1])
    {
        nut = AllNut[nuti];
        v_threadsize = v_get(AllNut,ThreadSize);
        v_nutwidthmin = v_get(AllNut,NutWidthMin);
        dist = v_cumsum(v_nutwidthmin, 0, nuti)[nuti]*1.2;
        translate(X*dist)
        rotate([0,0,30])
        screw(nut=nut, h=get(NutWidthMax, nut)*5, head_embed=false, orient=-Z, align=Z);
    }
}

// From ParkinBots threading library
// https://www.thingiverse.com/thing:1659079
/*module Threading(D = 0, pitch = 1, d = 12, windings = 10, helices = 1, angle = 60, steps=40)*/
/*{*/
    /*R = D==0?d/2+2*pitch/PI:D/2; */
    /*translate([0,0,-pitch])*/
    /*difference()*/
    /*{*/
        /*translate([0,0,pitch])*/
        /*cylinder (r=R, h =pitch*(windings-helices));*/
        /*threading(pitch, d, windings, helices, angle, steps, true); */
    /*}*/
/*}*/

// From ParkinBots threading library
// https://www.thingiverse.com/thing:1659079
module threading(pitch = 1, d = 12, windings = 10, helices = 1, angle = 60, steps=40, full = false)
{
    // tricky: glue two 180° pieces together to get a proper manifold
    r = d/2;
    Steps = steps/2;
    Pitch = pitch*helices;
    if(full)
    {
        cylinder(r = r-.5-pitch/PI, h=pitch*(windings+helices), $fn=steps);
    }

    sweep(gen_dat(), planar_caps=true);   // half screw
    rz(180)
    tz(Pitch/2)
    sweep(gen_dat(), planar_caps=true);   // half screw

    function gen_dat() =
        let(ang = 180, bar = R_(180, -90, 0, Ty_(-r+.5, vec3D(pitch/PI*Rack(windings, angle)))))
        [for (i=[0:Steps]) Tz_(i/2/Steps*Pitch, -Rz_(i*ang/Steps, bar))];

    function Rack(w, angle) =
        concat([[0, 2]],
            [for (i=[0:w-1], j=[0:3])
            let(t = [ [0, 1], [2*tan(angle/2), -1], [PI/2, -1], [2*tan(angle/2)+PI/2, 1]])
            [t[j][0]+i*PI, t[j][1]]], [[w*PI, 1], [w*PI, 2]
            ]);
}

if(false)
{
    $fn=32;
    all_heads=
    [
        "socket",
        "button",
        "set"
    ];
    for(i=[0:len(all_heads)-1])
    {
        head=all_heads[i];
        tx(i*8*mm)
        screw(nut=NutHexM3, head=head, h=25, orient=-Z);
    }

}

if(false)
{
    $fn=128;
    nut1 = NutHexM3;
    stack(axis=X, dist = 20)
    {
        screw(nut=NutHexM8, head="socket", h=25, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, with_head=1, with_nut=0, orient=-Z);
        screw(nut=NutHexM8, head="button", h=25, with_head=0, with_nut=0, orient=-Z);
        screw(nut=NutHexM8, head="set", h=25, with_nut=0, orient=-Z);

        threading(pitch = 1.25, d=8, windings=10, full=true);
    }
}

if(false)
{
    box_w = 150*mm;
    box_d = 10*mm;
    box_h = 10*mm;
    o = 10*mm;

    $show_vit = true;
    /*$fs= 0.2;*/
    /*$fa = 4;*/

    nut1 = NutHexM3;
    nut2 = NutHexM5;
    nut3 = NutKnurlM3_3_42;
    nut4 = NutKnurlM3_5_42;

    /*all_axes()*/
    /*for($axis=[X])*/
    /*color($color)*/
    /*translate($axis*15)*/
    /*echo($axis)*/

    /*orient(axis=$axis, axis_ref=-X)*/
    for(y=[-1,1])
    translate(-y*Y*20*mm)
    difference()
    {
        cubea([box_w,box_d,box_h], align=X+Z);
        test_cuts(orient=y*Z);
        /*%test_cuts(orient=y, axis_ref=-Z);*/
    }

}

module test_cuts(orient)
{
    union()
    {
        /*offset = 0;*/
        offset = -5;
        /*translate([0,offset,0])*/
        /*translate([5,0,-5])*/
        translate([5,0,0])
        stack(dist=10,axis=X)
        {
            screw_cut(nut=nut1, head="button", h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut1, head="socket", h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(thread=ThreadM5, h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut1, h=box_h, head_embed=false, orient=orient, align=Z);

            screw_cut(nut=nut1, h=box_h, nut_offset=0*mm, head_embed=true, orient=orient, align=Z);
            screw_cut(nut=nut2, h=box_h, nut_offset=3*mm, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut3, h=box_h, head_embed=false, orient=orient, align=Z);
            screw_cut(nut=nut4, h=box_h, head_embed=false, orient=orient, align=Z);

            translate([0,0,-5*mm])
            screw_cut(nut=nut3, h=box_h, head_embed=false, orient=orient, align=Z);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=5, head_embed=false, trap_h=10, trap_axis=-Y, orient=orient, align=N);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=5, head_embed=false, trap_h=10, trap_axis=Y, orient=orient, align=N);

            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=box_d, head_embed=false, trap_h=10, screw_offset=2*mm, trap_axis=Z, orient=orient, align=N);
            translate([0,0,box_h/2])
            nut_trap_cut(nut=nut1, h=box_d, head_embed=false, trap_h=10, screw_offset=2*mm, trap_axis=-Z, orient=orient, align=N);
        }
    }
}


Fixing hanging supports; other minor tweaks.

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

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

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

// 1/8" Hardboard:
thickness = 3.1;
    // 2.8mm = 0.110"
    //   * looked good based on cut test piece for hardboard slotting into hardboard
    //   * 2015-03-06: Hanging supports didn't fit into cutouts (or did only with lots of force)
    // 3.1mm for some breathing room

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

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

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

// tank base plate
base_height = overhang/2;

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

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

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

// for slight offsets/tweaks
epsilon = 1;

// vars to control projection for DXF export (see makefile)
if (DXF_TOP) {
    projection() top_cover();
} else if (DXF_BOTTOM) {
    projection() tank_base();
} else if (DXF_SIDE1) {
    projection() rotate(a=[90,0,0]) side(y=0);
} else if (DXF_SIDE2) {
    projection() rotate(a=[90,0,0]) side(y=depth);
} else if (DXF_END1) {
    projection() rotate(a=[0,90,0]) vert_face(x=0);
} else if (DXF_END2) {
    projection() rotate(a=[0,90,0]) vert_face(x=width);
} else if (DXF_LIGHT_BAR) {
    projection() light_bar();
} else if (DXF_CAMERA_SUPPORT) {
    projection() rotate(a=[90,0,0]) camera_supports(justone=true);
} else if (DXF_RPI_SUPPORT) {
    projection() rotate(a=[90,0,0]) rpi_support();
}
else {
    // 3D model
    // components to include (comment out unwanted)
    vert_face(x=0);      // "near face, transparent for monitor
    vert_face(x=width);  // "far" face, with camera, rpi, etc.
    side(y=0);
    side(y=depth);
    tank_base();
    light_bar();
    camera_supports(x=width, y=depth/2, z=height/2);
    rpi_support(x=width, y=depth/2-50, z=height/2);
    mock_rpi(x=width, y=depth/2-60, z=height/2);
    top_cover();
}

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

module vert_face(x=0) {
    difference() {
        if (x == 0) {
            vert_face_base(0, window_thickness);
        }
        else {
            vert_face_base(x);
        }
        camera_opening();
        camera_supports(x=width+thickness*2, y=depth/2, z=height/2+support_drop);
        rpi_support(x=width+thickness*2, y=depth/2-50, z=height/2+support_drop);
        wire_opening();
        // CAUTION: OpenSCAD won't let me make a projection of vert_face(x=0)
        //   if tank_base is put after side(y=depth) here... bug.  :(
        tank_base();
        top_cover();
        side(y=0);
        side(y=depth);
        translate([x-thickness/2, -overhang, 0]) {
            translate([0, depth+overhang+thickness/2, 3/4*height])
                rounded_truncation(up=true);
            translate([0, 0, 3/4*height])
                rounded_truncation(up=true);
        }
    }
}

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

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

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

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

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

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

// rounded corners in x,z directions
module rounded_rect(x,y,z, center, radius) {
    difference() {
        cube([x,y,z], center);

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

module top_cover() {
    color("Grey", alpha=0.5)
    translate([-outset,-outset,height-thickness])
    difference() {
        cube([width+outset*2,depth+outset*2,thickness]);
        cutouts(5,width-thickness,outset,rot=0,trans=[width/2+outset,0,0]);
        cutouts(5,width-thickness,outset,rot=180,trans=[width/2+outset,depth+outset*2,0]);
        cutouts(5,depth-thickness,outset,rot=-90,trans=[0,depth/2+outset,0]);
        cutouts(5,depth-thickness,outset,rot=90,trans=[width+outset*2,depth/2+outset,0]);
    }
}

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

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

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

module cutouts(num, width, outset, rot, trans) {
    w = width / ((num-1) * 2);
    translate(trans)
    rotate([0,0,1], a=rot)
    for (i = [0 : num-1]) {
        translate([-width/2 - w/2 + i*2*w, -thickness/2, -thickness/2])
            cube([w, outset+thickness, thickness*2]);
    }
}

module wire_opening() {
    translate([width, depth/2-70, overhang+20])
    scale([1,2,1])
    rotate(a=[0,90,0])
        cylinder(d=12, h=thickness+epsilon, center=true, $fn=50);
}

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

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

module mock_rpi(x, y, z) {
    color("Blue", alpha=0.5)
    translate([x+thickness, y, z])
        rotate(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 - Jan, 2015
//
//  Units are mm

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

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

// 1/8" Hardboard:
thickness = 3.0;
    // 2.8mm = 0.110"
    //   * looked good based on cut test piece for hardboard slotting into hardboard
    //   * 2015-03-06: Hanging supports didn't fit into cutouts (or did only with lots of force)
    // 3.0mm for some breathing room

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

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

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

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

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

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

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

// for slight offsets/tweaks
epsilon = 1;

// vars to control projection for DXF export (see makefile)
if (DXF_TOP) {
    projection() top_cover();
} else if (DXF_BOTTOM) {
    projection() tank_base();
} else if (DXF_SIDE1) {
    projection() rotate(a=[90,0,0]) side(y=0);
} else if (DXF_SIDE2) {
    projection() rotate(a=[90,0,0]) side(y=depth);
} else if (DXF_END1) {
    projection() rotate(a=[0,90,0]) vert_face(x=0);
} else if (DXF_END2) {
    projection() rotate(a=[0,90,0]) vert_face(x=width);
} else if (DXF_LIGHT_BAR) {
    projection() light_bar();
} else if (DXF_CAMERA_SUPPORT) {
    projection() rotate(a=[90,0,0]) camera_supports(justone=true);
} else if (DXF_RPI_SUPPORT) {
    projection() rotate(a=[90,0,0]) rpi_support();
}
else {
    // 3D model
    // components to include (comment out unwanted)
    vert_face(x=0);      // "near face, transparent for monitor
    vert_face(x=width);  // "far" face, with camera, rpi, etc.
    side(y=0);
    side(y=depth);
    tank_base();
    light_bar();
    camera_supports(x=width, y=depth/2, z=height/2);
    rpi_support(x=width, y=depth/2-50, z=height/2);
    mock_rpi(x=width, y=depth/2-60, z=height/2);
    top_cover();
}

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

module vert_face(x=0) {
    difference() {
        if (x == 0) {
            vert_face_base(0, window_thickness);
        }
        else {
            vert_face_base(x);
        }
        camera_opening();
        camera_supports(x=width+thickness*2, y=depth/2, z=height/2+support_drop);
        rpi_support(x=width+thickness*2, y=depth/2-50, z=height/2+support_drop);
        wire_opening();
        // CAUTION: OpenSCAD won't let me make a projection of vert_face(x=0)
        //   if tank_base is put after side(y=depth) here... bug.  :(
        tank_base();
        top_cover();
        side(y=0);
        side(y=depth);
        translate([x-thickness/2, -overhang, 0]) {
            translate([0, depth+overhang+thickness/2, 3/4*height])
                rounded_truncation(up=true);
            translate([0, 0, 3/4*height])
                rounded_truncation(up=true);
        }
    }
}

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

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

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

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

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

module hanging_support(x, y, z, out, up) {
    inner_w = out+thickness;
    inner_h = up;
    color("Red", alpha=0.5)
    translate([x,y,z])
    // push inner opening up against vert face
    translate([inner_w/2,0,0])
    difference() {
        cube([inner_w+support_w*2, thickness, inner_h+support_w*2], center=true);
        // cutout for object
        cube([inner_w, thickness*2, inner_h], center=true);
        // cutout for passing through vertface
        translate([-(inner_w/2+support_w/2),0,-support_drop/2])
            cube([support_w+2*epsilon, thickness*2, inner_h-support_drop], center=true);
        // bottom cutout
        translate([-inner_w/2,0,-(inner_h/2+support_w-support_drop/2)])
            cube([thickness, thickness*2, support_drop], 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_opening() {
    // A wider opening, so the bar can slide out of the way of placing or removing the fish tank
    translate([light_pos_x-light_bar_width/4, -outset, light_height])
        cube([light_bar_width*2, depth+outset*2, thickness]);
}

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

module cutouts(num, width, outset, rot, trans) {
    w = width / ((num-1) * 2);
    translate(trans)
    rotate([0,0,1], a=rot)
    for (i = [0 : num-1]) {
        translate([-width/2 - w/2 + i*2*w, -thickness/2, -thickness/2])
            cube([w, outset+thickness, thickness*2]);
    }
}

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

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

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

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


xaxis/xends: more work, rounding etc

xaxis/xends: more work, rounding etc

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

include <config.scad>
include <thing_libutils/misc.scad>

motor_mount_wall_thick = 11*mm;
xaxis_end_motorsize = lookup(NemaSideSize,xaxis_motor);
xaxis_end_motor_offset=[xaxis_end_motorsize/2+9*mm,motor_mount_wall_thick,0];

module xaxis_end(with_motor=false, show_motor=false, show_nut=false)
{
    x_side = with_motor?1:-1;
    nut_h = zaxis_nut[4];
    wx = zaxis_bearing[1]/2+zaxis_nut[1];
    wx_ = with_motor? xaxis_end_motorsize+xaxis_end_motor_offset[0] - xaxis_end_motorsize/2 : wx;
    wy = xaxis_bearing[1]*2+xaxis_zaxis_distance_y;
    wz = xaxis_rod_distance+xaxis_bearing[2]+5*mm;
    difference()
    {
        extrasize = with_motor?0*mm:0*mm;
        extrasize_align = with_motor?-1:1;

        union()
        {
            /*cubea([wx_, xaxis_rod_d*2, wz], align=[with_motor?1:-1,0,0]);*/

            translate([0,-xaxis_zaxis_distance_y,0])
            cuberounda(size=[wx_, zaxis_bearing[1]*2, wz], rounding_radius=3, align=[with_motor?1:-1,0,0], extrasize=[0,motor_mount_wall_thick/2,0], extrasize_align=[0,1,0]);

            // nut mount
            translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -wz/2])
            {
                union()
                {
                    fncylindera(h=wz, d=zaxis_nut[1], align=[0,0,1]);
                    translate([-1*x_side*zaxis_nut[1]/2,0,0])
                    {
                        cubea([zaxis_nut[1]/2,zaxis_nut[1],wz],align=[x_side,0,1]);
                        cubea([zaxis_nut[1],zaxis_nut[1]/2,wz],align=[x_side,1,1]);
                    }
                }
            }
        }

        beltpath_height = xaxis_pulley_inner_d+5*mm;
        beltpath_width = max(belt_width+3*mm, xaxis_pulley[0]+3*mm);

        // belt path
        beltpath_length = 1000;
        cubea([beltpath_length,beltpath_width, beltpath_height]);

        diag = pythag_hyp(beltpath_width,beltpath_width)/2;

        for(z=[-1,1])
        translate([0,0,z*beltpath_height/2])
        translate([-diag/2,0,0])
        translate([diag/2,0,0])
        rotate([z*-45,0,0])
        cubea([beltpath_length, diag, diag]);

        for(z=[-1,1])
            translate([0,0,z*xaxis_rod_distance/2])
                translate([0, -xaxis_zaxis_distance_y, 0])
                bearing_mount_holes(zaxis_bearing, orient=[0,0,1], show_zips=false);

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

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

            // axle
            translate(xaxis_end_motor_offset)
            {
                translate([0, .1, 0])
                fncylindera(d=lookup(NemaRoundExtrusionDiameter, xaxis_motor), h=motor_mount_wall_thick+xaxis_pulley[2]+5*mm, orient=[0,1,0], align=[0,1,0]);

                for(x=[-1,1])
                for(z=[-1,1])
                translate([x*screw_dist/2, .1, z*screw_dist/2])
                {
                    // screw
                    fncylindera(d=lookup(NemaMountingHoleDiameter, xaxis_motor), h=wy+motor_mount_wall_thick, orient=[0,1,0], align=[0,1,0]);

                    // screw head
                    translate([0,-7,0])
                        fncylindera(d=lookup(NemaMountingHoleDiameter, xaxis_motor)*1.9, h=wy+motor_mount_wall_thick, orient=[0,1,0], align=[0,1,0]);
                }
            }

        }

        // nut mount
        translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -wz/2])
        {
            union()
            {
                translate([0,0,-.1])
                {
                    fncylindera(h=nut_h+1, d=zaxis_nut[0]*1.05, align=[0,0,1]);

                    fncylindera(h=wz+1, d=zaxis_nut[2]*1.2, align=[0,0,1]);

                    for(i=[-1,1])
                        translate([i*13.5, 0, 0])
                            fncylindera(h=5, r=1.6, align=[0,0,1]);
                }
            }
        }
    }

    if(with_motor && show_motor)
    {
        translate(xaxis_end_motor_offset)
        {
            translate([0,-7,0])
            pulley(pulley_2GT_20T, align, orient=[0,1,0], align=[0,-1,0]);

            motor(xaxis_motor, NemaMedium, dualAxis=false, orientation=[-90,0,0]);
        }
    }

    %if(show_nut)
    {
        translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -wz/2-zaxis_nut[3]])
        xaxis_end_znut();
    }
}

module xaxis_end_znut()
{
    difference()
    {
        union()
        {
            fncylindera(d=zaxis_nut[1], h=zaxis_nut[3], orient=[0,0,1], align=[0,0,1]);
            fncylindera(d=zaxis_nut[0], h=zaxis_nut[4], orient=[0,0,1], align=[0,0,1]);
        }
        translate([0,0,-.1])
            fncylindera(d=zaxis_nut[2], h=zaxis_nut[4]+.2, orient=[0,0,1], align=[0,0,1]);
    }
}

debug=false;
if(debug)
{
    // x smooth rods
    for(z=[-1,1])
        translate([0,0,z*(xaxis_rod_distance/2)])
            %fncylindera(h=xaxis_rod_l,d=xaxis_rod_d, orient=[1,0,0]);

    xaxis_end(with_motor=true, show_motor=true, show_nut=true);

    translate([150,0,0])
        xaxis_end(with_motor=false, show_motor=true, show_nut=true);
}

/*xaxis_end(with_motor=true, show_motor=false, show_nut=false);*/
/*translate([100,-10,0])*/
/*xaxis_end(with_motor=false, show_motor=false, show_nut=false);*/

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

include <config.scad>
include <thing_libutils/misc.scad>

motor_mount_wall_thick = 10*mm;
wz = xaxis_rod_distance+xaxis_bearing[2]+5*mm;
wy = xaxis_bearing[1]*2+xaxis_zaxis_distance_y;
xaxis_end_motorsize = lookup(NemaSideSize,xaxis_motor);
xaxis_end_motor_offset=[xaxis_end_motorsize/2+9*mm,0,0];

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

        union()
        {
            cubea([wx_, xaxis_rod_d*2, wz], align=[with_motor?1:-1,0,0]);

            translate([0,-xaxis_zaxis_distance_y,0])
            cubea([wx_, zaxis_bearing[1]*2, wz], align=[with_motor?1:-1,0,0]);

            if(with_motor)
            {
                translate(xaxis_end_motor_offset)
                cubea([xaxis_end_motorsize, motor_mount_wall_thick, wz], align=[0,1,0]);
            }
            // nut mount
            translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -wz/2])
            {
                union()
                {
                    fncylindera(h=wz, d=zaxis_nut[1], align=[0,0,1]);
                    translate([-1*x_side*zaxis_nut[1]/2,0,0])
                    {
                        cubea([zaxis_nut[1]/2,zaxis_nut[1],wz],align=[x_side,0,1]);
                        cubea([zaxis_nut[1],zaxis_nut[1]/2,wz],align=[x_side,1,1]);
                    }
                }
            }
        }

        beltpath_height = xaxis_pulley_inner_d+5*mm;
        beltpath_width = max(belt_width+3*mm, xaxis_pulley[0]+3*mm);

        // belt path
        beltpath_length = 1000;
        cubea([beltpath_length,beltpath_width, beltpath_height]);

        diag = pythag_hyp(beltpath_width,beltpath_width)/2;

        for(z=[-1,1])
        translate([0,0,z*beltpath_height/2])
        translate([-diag/2,0,0])
        translate([diag/2,0,0])
        rotate([z*-45,0,0])
        cubea([beltpath_length, diag, diag]);

        for(z=[-1,1])
            translate([0,0,z*xaxis_rod_distance/2])
                translate([0, -xaxis_zaxis_distance_y, 0])
                bearing_mount_holes(zaxis_bearing, orient=[0,0,1], show_zips=false);

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

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

            for(x=[-1,1])
            for(z=[-1,1])
            translate([x*screw_dist/2, 0, z*screw_dist/2])
            translate(xaxis_end_motor_offset)
            translate([0,motor_mount_wall_thick+.1,0])
            fncylindera(d=lookup(NemaMountingHoleDiameter, xaxis_motor), h=wy, orient=[0,1,0], align=[0,1,0]);

            translate([0,-motor_mount_wall_thick,0])
            for(x=[-1,1])
            for(z=[-1,1])
            translate([x*screw_dist/2, 0, z*screw_dist/2])
            translate(xaxis_end_motor_offset)
            translate([0,motor_mount_wall_thick+.1,0])
            fncylindera(d=lookup(NemaMountingHoleDiameter, xaxis_motor)*1.9, h=wy, orient=[0,1,0], align=[0,1,0]);
        }

        // nut mount
        translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -wz/2])
        {
            union()
            {
                translate([0,0,-.1])
                {
                    fncylindera(h=nut_h+1, d=zaxis_nut[0]*1.05, align=[0,0,1]);

                    fncylindera(h=wz+1, d=zaxis_nut[2]*1.2, align=[0,0,1]);

                    for(i=[-1,1])
                        translate([i*13.5, 0, 0])
                            fncylindera(h=5, r=1.6, align=[0,0,1]);
                }
            }
        }
    }

    if(with_motor && show_motor)
    {
        translate(xaxis_end_motor_offset)
            pulley(pulley_2GT_20T, align, orient=[0,1,0]);

        translate([0,motor_mount_wall_thick+.1,0])
        translate(xaxis_end_motor_offset)
        motor(xaxis_motor, NemaMedium, dualAxis=false, orientation=[-90,0,0]);
    }

    %if(show_nut)
    {
        translate([x_side*zaxis_rod_screw_distance_x, -xaxis_zaxis_distance_y, -wz/2-zaxis_nut[3]])
        xaxis_end_znut();
    }
}

module xaxis_end_znut()
{
    difference()
    {
        union()
        {
            fncylindera(d=zaxis_nut[1], h=zaxis_nut[3], orient=[0,0,1], align=[0,0,1]);
            fncylindera(d=zaxis_nut[0], h=zaxis_nut[4], orient=[0,0,1], align=[0,0,1]);
        }
        translate([0,0,-.1])
            fncylindera(d=zaxis_nut[2], h=zaxis_nut[4]+.2, orient=[0,0,1], align=[0,0,1]);
    }
}

debug=false;
if(debug)
{
    // x smooth rods
    for(z=[-1,1])
        translate([0,0,z*(xaxis_rod_distance/2)])
            %fncylindera(h=xaxis_rod_l,d=xaxis_rod_d, orient=[1,0,0]);

    xaxis_end(with_motor=true, show_motor=true, show_nut=true);

    translate([150,0,0])
        xaxis_end(with_motor=false, show_motor=true, show_nut=true);
}

xaxis_end(with_motor=true, show_motor=false, show_nut=false);
translate([100,-10,0])
xaxis_end(with_motor=false, show_motor=false, show_nut=false);