screws: add debug test

screws: add debug test

include <system.scad>
include <shapes.scad>
include <transforms.scad>
include <misc.scad>

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

use <scad-utils/transformations.scad>

// naive, assume head height is same as thread size (generally true for cap heads)
function get_screw_head_h(thread) = get(ThreadSize, thread);
function get_screw_head_d(thread) = 2 * get(ThreadSize, thread);

module screw(nut, thread, 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(nut!=U && thread!=U && nut_thread != thread_, "screw: Mismatched nut and thread");*/

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

    s = threadsize*tolerance;
    total_h = h;
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        translate([0,0,head_embed?-head_h:0])
        {
            if(with_head)
            {
                translate([0,0,h/2+.01])
                screw_head(thread=thread_, orient=Z, align=Z);
            }

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

            if(with_nut && nut != U)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);
            }
        }

        if($show_vit)
        {
            translate([0,0,head_embed?-head_h:0])
            if(with_nut && nut != U)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                %screw_nut(nut=nut, align=-Z);
            }
        }
    }
}

module screw_cut(nut, thread, h=10, tolerance=1.05, head_embed=false, with_nut=true, nut_cut_h=1000, with_head=true, head_cut_h=1000, nut_offset=0, cut_access=true, orient=Z, align=N)
{
    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;
    head_h = get_screw_head_h(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)
    {
        translate([0,0,head_embed?-head_h:0])
        {
            if(with_head)
            {
                translate([0,0,h/2+.01])
                screw_head_cut(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)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                screw_nut_cut(nut=nut, tolerance=tolerance, h=nut_cut_h, align=-Z);
            }
        }
    }

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

module screw_thread(thread, h=10, tolerance=1.00, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread: thread is undef");
    // TODO render thread
    threadsize = get(ThreadSize, thread);
    cylindera(d=threadsize, h=h, orient=orient, align=align);
}

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(thread, tolerance=1.00, override_h=U, orient=Z, align=N)
{
    assert(thread!=U, "screw_head: thread is undef");

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread);
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    difference()
    {
        cylindera(d=head_d, h = fallback(override_h,head_h));
        translate([0,0,head_h/2])
        cylindera(d=threadsize, h = head_d/2);
    }
}

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

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread)*tolerance;
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    translate([0,0,-head_h/2])
    cylindera(d=head_d, h=fallback(override_h, head_h), align=Z);
}

module screw_nut(nut, 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);
    cylindera($fn=nut_facets, d=nut_dia, h=fallback(override_h, nut_thick), orient=orient, align=align);
}

module screw_nut_cut(nut, tolerance=1.05, h=1000, 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)
    {
        translate([0,0,nut_thick/2])
        {
            screw_nut(nut, tolerance, orient=Z, align=-Z);
            translate([0,0,-nut_thick+.01])
            {
                cylindera(d=nut_dia+2*mm, h=h, orient=Z, align=-Z);
            }
        }
    }
}

// distance from center to flat on a polygon (taking in the diagonal distance)
function apothem(radius, facets) = radius/cos(180/facets);

function nut_radius(nut) = apothem(get(NutWidthMax, nut)/2, get(NutFacets, nut));
function nut_dia(nut) = 2*nut_radius(nut);

module nut_trap_cut(nut, thread, trap_offset=10, screw_l=10*mm, screw_l_extra=2*mm, 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(thread);
    nut_h = get(NutThickness, nut) +.5*mm;

    nut_width_min = get(NutWidthMin, nut);
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut);
    total_h = nut_h;
    size_align(size=[nut_dia, nut_dia, total_h], orient=orient, orient_ref=Z, align=align)
    {
        translate([0,0,nut_h/2-.1])
        screw_thread_cut(thread=thread_, tolerance=1.1, h=screw_l, orient=-Z, align=Z);

        if(screw_l_extra>0)
        translate([0,0,-nut_h/2+.1])
        screw_thread_cut(thread=thread_, tolerance=1.1, h=screw_l_extra+.1, orient=Z, align=-Z);

        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)
        {
            translate([0,0, screw_l])
            translate([0,0, -nut_h/2])
            %screw(nut=nut, thread=thread, tolerance=1, with_nut=false, 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*15*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);
    }
}

if(false)
{
    box_w = 40*mm;
    box_h = 10*mm;
    box_d = 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;

    difference()
    {
        /*rcubea([box_w,box_d/2,box_h], align=[1,-1,1]);*/
        rcubea([box_w,box_d,box_h], align=[1,0,1]);

        test_cuts();
    }
}

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

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

            translate([0,0,-5*mm])
            screw_cut(nut=nut3, h=box_h, head_embed=false, orient=Z, 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=-Z, 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=Z, align=N);*/

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

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

include <system.scad>
include <shapes.scad>
include <transforms.scad>
include <misc.scad>

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

use <scad-utils/transformations.scad>

// naive, assume head height is same as thread size (generally true for cap heads)
function get_screw_head_h(thread) = get(ThreadSize, thread);
function get_screw_head_d(thread) = 2 * get(ThreadSize, thread);

module screw(nut, thread, 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(nut!=U && thread!=U && nut_thread != thread_, "screw: Mismatched nut and thread");*/

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

    s = threadsize*tolerance;
    total_h = h;
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=Z, align=align)
    {
        translate([0,0,head_embed?-head_h:0])
        {
            if(with_head)
            {
                translate([0,0,h/2+.01])
                screw_head(thread=thread_, orient=Z, align=Z);
            }

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

            if(with_nut && nut != U)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                screw_nut(nut=nut, tolerance=tolerance, orient=Z, align=-Z);
            }
        }

        if($show_vit)
        {
            translate([0,0,head_embed?-head_h:0])
            if(with_nut && nut != U)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                %screw_nut(nut=nut, align=-Z);
            }
        }
    }
}

module screw_cut(nut, thread, h=10, tolerance=1.05, head_embed=false, with_nut=true, nut_cut_h=1000, with_head=true, head_cut_h=1000, nut_offset=0, cut_access=true, orient=Z, align=N)
{
    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;
    head_h = get_screw_head_h(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)
    {
        translate([0,0,head_embed?-head_h:0])
        {
            if(with_head)
            {
                translate([0,0,h/2+.01])
                screw_head_cut(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)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                screw_nut_cut(nut=nut, tolerance=tolerance, h=nut_cut_h, align=-Z);
            }
        }
    }

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

module screw_thread(thread, h=10, tolerance=1.00, orient=Z, align=N)
{
    assert(thread!=U, "screw_thread: thread is undef");
    // TODO render thread
    threadsize = get(ThreadSize, thread);
    cylindera(d=threadsize, h=h, orient=orient, align=align);
}

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(thread, tolerance=1.00, override_h=U, orient=Z, align=N)
{
    assert(thread!=U, "screw_head: thread is undef");

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread);
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    difference()
    {
        cylindera(d=head_d, h = fallback(override_h,head_h));
        translate([0,0,head_h/2])
        cylindera(d=threadsize, h = head_d/2);
    }
}

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

    threadsize = get(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread)*tolerance;
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    translate([0,0,-head_h/2])
    cylindera(d=head_d, h=fallback(override_h, head_h), align=Z);
}

module screw_nut(nut, 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);
    cylindera($fn=nut_facets, d=nut_dia, h=fallback(override_h, nut_thick), orient=orient, align=align);
}

module screw_nut_cut(nut, tolerance=1.05, h=1000, 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)
    {
        translate([0,0,nut_thick/2])
        {
            screw_nut(nut, tolerance, orient=Z, align=-Z);
            translate([0,0,-nut_thick+.01])
            {
                cylindera(d=nut_dia+2*mm, h=h, orient=Z, align=-Z);
            }
        }
    }
}

// distance from center to flat on a polygon (taking in the diagonal distance)
function apothem(radius, facets) = radius/cos(180/facets);

function nut_radius(nut) = apothem(get(NutWidthMax, nut)/2, get(NutFacets, nut));
function nut_dia(nut) = 2*nut_radius(nut);

module nut_trap_cut(nut, thread, trap_offset=10, screw_l=10*mm, screw_l_extra=2*mm, 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(thread);
    nut_h = get(NutThickness, nut) +.5*mm;

    nut_width_min = get(NutWidthMin, nut);
    nut_facets = get(NutFacets, nut);
    nut_dia = nut_dia(nut);
    total_h = nut_h;
    size_align(size=[nut_dia, nut_dia, total_h], orient=orient, orient_ref=Z, align=align)
    {
        translate([0,0,nut_h/2-.1])
        screw_thread_cut(thread=thread_, tolerance=1.1, h=screw_l, orient=-Z, align=Z);

        if(screw_l_extra>0)
        translate([0,0,-nut_h/2+.1])
        screw_thread_cut(thread=thread_, tolerance=1.1, h=screw_l_extra+.1, orient=Z, align=-Z);

        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)
        {
            translate([0,0, screw_l])
            translate([0,0, -nut_h/2])
            %screw(nut=nut, thread=thread, tolerance=1, with_nut=false, orient=-Z, align=-Z);

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

    }
}

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

if(false)
{
    box_w = 40*mm;
    box_h = 10*mm;
    box_d = 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;

    difference()
    {
        /*rcubea([box_w,box_d/2,box_h], align=[1,-1,1]);*/
        rcubea([box_w,box_d,box_h], align=[1,0,1]);

        test_cuts();
    }
}

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

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

            translate([0,0,-5*mm])
            screw_cut(nut=nut3, h=box_h, head_embed=false, orient=Z, 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=-Z, 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=Z, align=N);*/

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

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


doc: wrong variable mentioned to show/hide testbed in visualTest

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2022 Jean-Sebastien CONAN
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Part of the camelSCAD library.
 *
 * Testing helpers for visual tests.
 *
 * @package shape/context
 * @author jsconan
 */

/**
 * Places the children at the proper test area.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param Number [index] - The index number of the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not the children are centered and need to be moved.
 */
module placeVisualTest(length, width, index=0, margin=0, cols=0, center=false) {
    margin = vector2D(margin);
    x = cols ? index % cols : index;
    y = cols ? floor(index / cols) : 0;
    offset = center ? [length, width, 0] / 2 : ORIGIN_3D;

    translate(offset + [(length + margin.x) * x, (width + margin.y) * y, 0]) {
        children();
    }
}

/**
 * Renders a test element. If no children is given, a cube with an arrow on top is rendered.
 * @param String|Vector [c] - The color of the element.
 * @param Number|Vector [angle] - The angle of the element.
 * @param Number|Vector [size] - The size of the element. It only applies if no children is given.
 * @param Boolean [center] - Whether or not the element is centered. It only applies if no children is given.
 */
module testElement(c="red", angle=0, size=1, center=false) {
    color(c) {
        rotate(angle) {
            if ($children)  {
                children();
            } else let(
                size = vector3D(size),
                half = size / 2,
                quarter = size / 4,
                offset = center ? -half : vector3D()
            ) {
                translate(offset) {
                    linear_extrude(height=size.z, convexity=10) {
                        polygon(
                            points = path([
                                ["P", half.x, size.y],
                                ["L", half.x, -half.y],
                                ["H", -quarter.x],
                                ["V", -half.y],
                                ["H", -half.x],
                                ["V", half.y],
                                ["H", -quarter.x],
                            ]),
                            convexity = 10
                        );
                    }
                    translateZ(quarter.z) {
                        cube(size=[size.x, size.y, half.z]);
                    }
                }
            }
        }
    }
}

/**
 * Colorizes a test element.
 * @param Number alpha - The alpha level for the testbed color.
 */
module testbedColor(alpha) {
    %color(TESTBED_COLOR, alpha) {
        children();
    }
}

/**
 * Extrudes and colorizes a test element.
 * @param Number alpha - The alpha level for the testbed color.
 */
module testbedExtrude(alpha) {
    testbedColor(alpha) {
        linear_extrude(height=TESTBED_THICKNESS, convexity=10, center=true) {
            children();
        }
    }
}

/**
 * Renders a test area, moving the tested shapes in it.
 * A testbed is shown, unless the global variable `SHOW_TESTBED` is set to `false`.
 * @param Number index - The index number of the test.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param String [title] - The title for the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not the children are centered and need to be moved.
 */
module visualTest(index, length, width, title="test", margin=1, cols=0, center=false) {
    title = str(title, " ", index);

    placeVisualTest(
        length = length,
        width = width,
        index = index,
        margin = margin,
        cols = cols,
        center = center
    ) {
        // test area
        if (SHOW_TESTBED) {
            testbedExtrude(.1) {
                square([length, width], center=center);
            }
            translate(center ? ORIGIN_3D : [length, width, 0] / 2) {
                testbedExtrude(1) {
                    text(title, size=min(length, width) / len(title), font="Liberation Sans", valign="center", halign="center");
                }
            }
        }

        // tested shapes
        children();
    }
}

/**
 * Renders a test area for each child module, moving the tested shapes in it.
 * A single test can be selected by its index thanks to the global variable `SELECT_TEST`.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param String [title] - The title for the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not center the whole suite.
 * @param Boolean [centerEach] - Whether or not center each test area.
 */
module visualTestSuite(length, width, title="test", margin=1, cols=0, center=false, centerEach=false) {
    margin = vector2D(margin);
    count = $children;
    SELECT_TEST = is_undef(SELECT_TEST) ? undef : min(max(0, SELECT_TEST), count - 1);
    start = is_undef(SELECT_TEST) ? 0 : SELECT_TEST;
    end = is_undef(SELECT_TEST) ? count - 1 : SELECT_TEST;
    lines = cols ? ceil(count / cols) : 1;
    offset = center ? -([margin.x * (cols - 1), margin.y * (lines - 1), 0] + [length * cols, width * lines, 0]) / 2 : ORIGIN_3D;

    translate(offset) {
        for (i = [start : end]) {
            visualTest(
                index = i,
                length = length,
                width = width,
                title = title,
                margin = margin,
                cols = cols,
                center = centerEach
            ) {
                children(i);
            }
        }
    }
}

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2022 Jean-Sebastien CONAN
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Part of the camelSCAD library.
 *
 * Testing helpers for visual tests.
 *
 * @package shape/context
 * @author jsconan
 */

/**
 * Places the children at the proper test area.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param Number [index] - The index number of the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not the children are centered and need to be moved.
 */
module placeVisualTest(length, width, index=0, margin=0, cols=0, center=false) {
    margin = vector2D(margin);
    x = cols ? index % cols : index;
    y = cols ? floor(index / cols) : 0;
    offset = center ? [length, width, 0] / 2 : ORIGIN_3D;

    translate(offset + [(length + margin.x) * x, (width + margin.y) * y, 0]) {
        children();
    }
}

/**
 * Renders a test element. If no children is given, a cube with an arrow on top is rendered.
 * @param String|Vector [c] - The color of the element.
 * @param Number|Vector [angle] - The angle of the element.
 * @param Number|Vector [size] - The size of the element. It only applies if no children is given.
 * @param Boolean [center] - Whether or not the element is centered. It only applies if no children is given.
 */
module testElement(c="red", angle=0, size=1, center=false) {
    color(c) {
        rotate(angle) {
            if ($children)  {
                children();
            } else let(
                size = vector3D(size),
                half = size / 2,
                quarter = size / 4,
                offset = center ? -half : vector3D()
            ) {
                translate(offset) {
                    linear_extrude(height=size.z, convexity=10) {
                        polygon(
                            points = path([
                                ["P", half.x, size.y],
                                ["L", half.x, -half.y],
                                ["H", -quarter.x],
                                ["V", -half.y],
                                ["H", -half.x],
                                ["V", half.y],
                                ["H", -quarter.x],
                            ]),
                            convexity = 10
                        );
                    }
                    translateZ(quarter.z) {
                        cube(size=[size.x, size.y, half.z]);
                    }
                }
            }
        }
    }
}

/**
 * Colorizes a test element.
 * @param Number alpha - The alpha level for the testbed color.
 */
module testbedColor(alpha) {
    %color(TESTBED_COLOR, alpha) {
        children();
    }
}

/**
 * Extrudes and colorizes a test element.
 * @param Number alpha - The alpha level for the testbed color.
 */
module testbedExtrude(alpha) {
    testbedColor(alpha) {
        linear_extrude(height=TESTBED_THICKNESS, convexity=10, center=true) {
            children();
        }
    }
}

/**
 * Renders a test area, moving the tested shapes in it.
 * A testbed is shown, unless the global variable `SELECT_TEST` is set to `false`.
 * @param Number index - The index number of the test.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param String [title] - The title for the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not the children are centered and need to be moved.
 */
module visualTest(index, length, width, title="test", margin=1, cols=0, center=false) {
    title = str(title, " ", index);

    placeVisualTest(
        length = length,
        width = width,
        index = index,
        margin = margin,
        cols = cols,
        center = center
    ) {
        // test area
        if (SHOW_TESTBED) {
            testbedExtrude(.1) {
                square([length, width], center=center);
            }
            translate(center ? ORIGIN_3D : [length, width, 0] / 2) {
                testbedExtrude(1) {
                    text(title, size=min(length, width) / len(title), font="Liberation Sans", valign="center", halign="center");
                }
            }
        }

        // tested shapes
        children();
    }
}

/**
 * Renders a test area for each child module, moving the tested shapes in it.
 * A single test can be selected by its index thanks to the global variable `SELECT_TEST`.
 * @param Number length - The length of the test area.
 * @param Number width - The width of the test area.
 * @param String [title] - The title for the test.
 * @param Number|Vector [margin] - A margin between each test.
 * @param Number [cols] - The number of columns per lines.
 * @param Boolean [center] - Whether or not center the whole suite.
 * @param Boolean [centerEach] - Whether or not center each test area.
 */
module visualTestSuite(length, width, title="test", margin=1, cols=0, center=false, centerEach=false) {
    margin = vector2D(margin);
    count = $children;
    SELECT_TEST = is_undef(SELECT_TEST) ? undef : min(max(0, SELECT_TEST), count - 1);
    start = is_undef(SELECT_TEST) ? 0 : SELECT_TEST;
    end = is_undef(SELECT_TEST) ? count - 1 : SELECT_TEST;
    lines = cols ? ceil(count / cols) : 1;
    offset = center ? -([margin.x * (cols - 1), margin.y * (lines - 1), 0] + [length * cols, width * lines, 0]) / 2 : ORIGIN_3D;

    translate(offset) {
        for (i = [start : end]) {
            visualTest(
                index = i,
                length = length,
                width = width,
                title = title,
                margin = margin,
                cols = cols,
                center = centerEach
            ) {
                children(i);
            }
        }
    }
}


updated plotter3.scad, was an old version, should be better now.

//todo
//hål för kontakter

include <arduino.scad>
include <spool2.scad>

draw_components=0;

plate_w = 100;
plate_h = 135;
plate_t = 1;

distances_h=6;

plate_board_dist = 20;

$fn=40;

arduino_x=30;
arduino_y=73;
arduino_rotation=180;

pen_y=plate_h/2-105;
pen_angle=-45;
pen_radius=10;

stepper_x=27;
stepper_y=-(plate_h/2-15);

driver_x=44;
driver_y=-15;

battery_x=0;
battery_y=0;

servo_x=-18;
servo_y=-10;
servo_z=20;

y_cut=53.5;

//base
difference() {
	union() {
		translate([0,-y_cut/2,0]) cube(center=true, [plate_w,plate_h-y_cut,plate_t]);
		edge();

	}

	//stepper holes
	translate([-stepper_x, stepper_y,0]) stepperholes();
	translate([stepper_x, stepper_y,0]) stepperholes();

	//uno holes
	translate([arduino_x,arduino_y,0]) {
		rotate([0,0,arduino_rotation])
			union() {
				uno_holes();
			}
	}

	//pen hole
	translate([0,pen_y,0]) rotate([pen_angle,0,0]) translate([0,0,-50]) cylinder(r=pen_radius, h=100);

	//extra holes
	translate([-34,-15,-5]) cylinder(r=12, h=10);
	translate([34,-15,-5]) cylinder(r=12, h=10);

	//stepper driver holes
	translate([-driver_x,driver_y]) rotate([0,0,90]) stepper_driver_holes(true);
	translate([driver_x,driver_y]) rotate([0,0,90]) stepper_driver_holes(false);
}

if(draw_components) {
	//spools
	color("White", alpha=0.2) {
		translate([-stepper_x,stepper_y,2]) spool(15, 8, 3.3, 1);
		translate([stepper_x,stepper_y,2]) spool(15, 8, 3.3, 1);
	}

	//steppers
	translate([-stepper_x,stepper_y,0]) stepper();
	translate([stepper_x,stepper_y,0]) stepper();

	//uno components
	translate([arduino_x,arduino_y,distances_h]) rotate([0,0,arduino_rotation]) arduino();

	//battery
	translate([battery_x,battery_y,-17*3/2]) {
		battery();
	}

	//stepper drivers
	color("Green", alpha=0.5) {
		translate([driver_x, driver_y,5]) cube(center=true, [32,35,1]);
		translate([-driver_x, driver_y,5]) cube(center=true, [32,35,1]);
	}

	//servo
	translate([servo_x, servo_y, servo_z]) rotate([-pen_angle+90,0,0]) rotate([0,90,0]) servo();

	//pen
	color("Red", alpha=0.5) {
		translate([0,pen_y,0]) rotate([pen_angle,0,0]) union() {
			translate([0,0,-10]) cylinder(r=pen_radius-1, h=100);
			translate([0,0,-30]) cylinder(r1=1, r2=pen_radius-1, h=20);
		}
	}
}

//stepper driver distances
difference() {
	union() {
		translate([-driver_x, driver_y]) rotate([0,0,90]) stepper_driver_pins(true);
		translate([driver_x,driver_y]) rotate([0,0,90]) stepper_driver_pins(false);
	}
	translate([-driver_x,driver_y]) rotate([0,0,90]) stepper_driver_holes(true);
	translate([driver_x,driver_y]) rotate([0,0,90]) stepper_driver_holes(false);
}

//wire guide
difference() {
	union() {
		translate([-6.0,-plate_h/2,0]) cylinder(r=5,h=12);
		translate([6.0,-plate_h/2,0]) cylinder(r=5,h=12);
		translate([-3.5,-plate_h/2+1.5,3]) rotate([0,90,0]) cylinder(r=1,h=7);
		translate([-3.5,-plate_h/2+1.5,7]) rotate([0,90,0]) cylinder(r=1,h=7);
		translate([-3.5,-plate_h/2+1.5,11]) rotate([0,90,0]) cylinder(r=1,h=7);
//		translate([-3.5,-plate_h/2+1.5,15]) rotate([0,90,0]) cylinder(r=1,h=7);
//		translate([-3.5,-plate_h/2+1.5,19]) rotate([0,90,0]) cylinder(r=1,h=7);
//		translate([-3.5,-plate_h/2+1.5,23]) rotate([0,90,0]) cylinder(r=1,h=7);
		translate([-0.5,-plate_h/2+1.5,0]) cube([1,29,12]);
	}
	translate([-50,-plate_h/2-100,-1]) cube([100,100,100]);
	translate([-1,-plate_h/2+5,10]) rotate([-33,0,0]) translate([0,-10,2]) cube([2,40,24]);

	translate([0,pen_y,0]) rotate([pen_angle,0,0]) {
			translate([0,0,-50]) cylinder(r=pen_radius, h=250);
	}
}

//pen guide
difference() {
    intersection() {
        translate([0,pen_y,0]) rotate([pen_angle,0,0]) {
            difference() {
                union() {
                    translate([0,0,-50]) cylinder(r1=pen_radius+2, r2=pen_radius+1, h=120);
                    translate([-2,20,0]) rotate([-pen_angle,0,0]) cube([4,20,40]);
                    translate([-25.5,8,0]) rotate([-pen_angle,0,0]) cube([15,30,40]);

                }
                translate([0,0,-50]) cylinder(r=pen_radius, h=250);
                translate([0,0,150]) rotate([-25,0,0]) cube(center=true,[100,100,1000]);
//                translate([-7.5,5,20]) cube(center=true,[3,10,6]	);
//                translate([7.5,5,20]) cube(center=true,[3,10,6]	);
            }
        }
        translate([-100,-100,0]) cube(200,200,200);
    }
    translate([servo_x, servo_y, servo_z]) rotate([-pen_angle+90,0,0]) rotate([0,90,0]) servo(cutout=1);
}

//arduino distances
translate([arduino_x,arduino_y,0])
	rotate([0,0,arduino_rotation])
		uno_holes(distances_h);

// ************ modules **************

//distances
module distance(height=17) {
	difference() {
		//translate([0,0,0]) cylinder(r=3, h=height);
		translate([-3,-7,0]) cube([6,10,height]);
		translate([0,0,-1]) cylinder(r=1.7, h=distances_h+2);
	}
}

module driver_pin() {
	rotate([0,0,45]) cylinder($fn=4, r=2.8+1.4, h=5);
}

module corner_holes() {
	translate([plate_w/2-4, plate_h/2-4, -50]) cylinder(r=2, h=100);
   translate([-plate_w/2+4, plate_h/2-4, -50]) cylinder(r=2, h=100);
}

module stepper_driver_holes(left) {
	dx=30;
	dy=26;
	union() {
        if(left) {
            translate([-dx/2,-dy/2,-5]) cylinder(r=1.7, h=11);
            translate([dx/2,-dy/2,-5]) cylinder(r=1.7, h=11);
        }
        else {
            translate([-dx/2,dy/2,-5]) cylinder(r=1.7, h=11);
            translate([dx/2,dy/2,-5]) cylinder(r=1.7, h=11);
        }
	}
}

module edge() {

	difference() {
		union() {
			difference() {
				translate([0,-y_cut/2,2.4]) cube(center=true, [plate_w,plate_h-y_cut, plate_t+3]);
				translate([0,-y_cut/2,-1]) cube(center=true, [plate_w-4,plate_h-4-y_cut, 20]);
			}
			translate([0,plate_h/2-66,1.5]) cube(center=true, [4,22, 3]);
			translate([0,3,1.5]) cube(center=true, [plate_w,3, 3]);
			translate([0,-32,1.5]) cube(center=true, [plate_w,3, 3]);
		}
		translate([-stepper_x,stepper_y,1.5]) cylinder(r=19, h=10);
		translate([stepper_x,stepper_y,1.5]) cylinder(r=19, h=10);

	}
}

module stepper_driver_pins(left) {
	dx=30;
	dy=26;
	union() {
        if(left) {
            translate([-dx/2,-dy/2,0]) driver_pin();
            translate([dx/2,-dy/2,0]) driver_pin();
        }
        else {
            translate([-dx/2,dy/2,0]) driver_pin();
            translate([dx/2,dy/2,0]) driver_pin();
        }
	}
}

//arduino
//import("Slim_Arduino_Uno_Case/arduino-case_bottom.stl");

module uno_holes(make_distances=0) {
	holes = [
//		[  2.54, 15.24,-5 ],
		[  17.78, 66.04,-5 ],
		[  45.72, 66.04,-5 ],
//		[  50.8, 13.97,-5 ]
		];
	union(){
		for(i = holes ) {
			translate(i)
				union() {
					if(make_distances > 0) {
						translate([0,0,5]) distance(make_distances);
					}
					else {
						cylinder(r=1.7,h=10);
						//translate([0,0,5.5]) cylinder(r=4,h=10);
					}
				}
		}
	}
}

module uno_connector_holes() {

}

module stepperholes() {
	translate([0,0,-50])cylinder(r=5, h=100);
	translate([35/2,  -8, -50]) cylinder(r=2, h=100);
	translate([-35/2, -8, -50]) cylinder(r=2, h=100);
}

module stepper() {
	color("Goldenrod", alpha=0.8) {
		translate([0,0,0])cylinder(r=2.5, h=8);
	}
	color("Silver", alpha=0.5) {
		translate([0,0,-1])cylinder(r=5, h=1);
		translate([0, -8, -20]) cylinder(r=14, h=19);

		difference() {
			union() {
				translate([-35/2,  -8, -2]) cylinder(r=3.5, h=1);
				translate([35/2,  -8, -2]) cylinder(r=3.5, h=1);
				translate([-35/2,  -8-3.5, -2]) cube([35,7,1]);
			}
			translate([-35/2,  -8, -4]) cylinder(r=2, h=10);
			translate([35/2,  -8, -4]) cylinder(r=2, h=10);
		}
	}
	color("Blue", alpha=0.5) {
		translate([-7.5,-8-18,-18]) cube([15,18,17]);
	}
}

module battery() {
	translate([-45/2, -26/2,17/2]) {
		color("Black", alpha=0.5) {
			cube([45,26,17]);
		}
		color("Silver", alpha=0.5) {
			translate([0,0,17/2]) rotate([0,-90,0]) {
				translate([0,13-6,0]) cylinder(r=3,h=3);
				translate([0,13+6,0]) difference() {
					cylinder(r=4,h=3,$fn=6);
					cylinder(r=3,h=4,$fn=6);
				}
			}
		}
	}
}

module servo(cutout=0) {

    if(cutout) {
        cube(center=true, [23+1,21+1,12+1]);
        translate([0,21/2-3+5,0]) cube(center=true, [32+1,1+10,12+1]);
        translate([14,20,0]) rotate([90,0,0]) cylinder(r=0.8, h=20);
        translate([14-28,20,0]) rotate([90,0,0]) cylinder(r=0.8, h=20);
    }
    else {
        color("Black", alpha=0.5) {
            cube(center=true, [23,21,12]);
            translate([0,21/2-3,0]) cube(center=true, [32,1,12]);
            translate([23/2-11.5/2,21/2,0]) rotate([-90,180,0]) {
                union() {
                    cylinder(r=11.5/2, h=5);
                    cylinder(r=2, h=10);
                    translate([-3,-3, 9]) cube([6,20,1]);
                }
            }
        }
        color("White", alpla=0.5) {

        }
    }
}

//todo
//hål för kontakter

include <arduino.scad>
include <spool2.scad>

draw_components=0;

plate_w = 100;
plate_h = 135;
plate_t = 1;

distances_h=6;

plate_board_dist = 20;

$fn=40;

arduino_x=30;
arduino_y=73;
arduino_rotation=180;

pen_y=plate_h/2-105;
pen_angle=-45;
pen_radius=10;

stepper_x=27;
stepper_y=-(plate_h/2-15);

driver_x=34;
driver_y=-15;

battery_x=0;
battery_y=0;

servo_x=0;
servo_y=-87;

y_cut=53.5;

//base
difference() {
	union() {
		translate([0,-y_cut/2,0]) cube(center=true, [plate_w,plate_h-y_cut,plate_t]);
		edge();

	}

	//stepper holes
	translate([-stepper_x, stepper_y,0]) stepperholes();
	translate([stepper_x, stepper_y,0]) stepperholes();

	//uno holes
	translate([arduino_x,arduino_y,0]) {
		rotate([0,0,arduino_rotation])
			union() {
				uno_holes();
			}
	}

	//pen hole
	translate([0,pen_y,0]) rotate([pen_angle,0,0]) translate([0,0,-50]) cylinder(r=pen_radius, h=100);

	//extra holes
	translate([-34,-15,-5]) cylinder(r=12, h=10);
	translate([34,-15,-5]) cylinder(r=12, h=10);

	//stepper driver holes
	translate([-driver_x,driver_y]) rotate([0,0,90]) stepper_driver_holes(true);
	translate([driver_x,driver_y]) rotate([0,0,90]) stepper_driver_holes(false);
}

if(draw_components) {
	//spools
	color("White", alpha=0.2) {
		translate([-stepper_x,stepper_y,2]) spool(15, 8, 3.3, 1);
		translate([stepper_x,stepper_y,2]) spool(15, 8, 3.3, 1);
	}

	//steppers
	translate([-stepper_x,stepper_y,0]) stepper();
	translate([stepper_x,stepper_y,0]) stepper();

	//uno components
	translate([arduino_x,arduino_y,distances_h]) rotate([0,0,arduino_rotation]) arduino();

	//battery
	translate([battery_x,battery_y,-17*3/2]) {
		battery();
	}

	//stepper drivers
	color("Green", alpha=0.5) {
		translate([driver_x, driver_y,5]) cube(center=true, [32,35,1]);
		translate([-driver_x, driver_y,5]) cube(center=true, [32,35,1]);
	}

	//servo
	translate([servo_x, servo_y, -12/2-plate_t/2]) servo();

	//pen
	color("Red", alpha=0.5) {
		translate([0,pen_y,0]) rotate([pen_angle,0,0]) union() {
			translate([0,0,-10]) cylinder(r=pen_radius-1, h=100);
			translate([0,0,-30]) cylinder(r1=1, r2=pen_radius-1, h=20);
		}
	}
}

//stepper driver distances
difference() {
	union() {
		translate([-driver_x, driver_y]) rotate([0,0,90]) stepper_driver_pins(true);
		translate([driver_x,driver_y]) rotate([0,0,90]) stepper_driver_pins(false);
	}
	translate([-driver_x,driver_y]) rotate([0,0,90]) stepper_driver_holes(true);
	translate([driver_x,driver_y]) rotate([0,0,90]) stepper_driver_holes(false);
}

//wire guide
difference() {
	union() {
		translate([-6.0,-plate_h/2,0]) cylinder(r=5,h=12);
		translate([6.0,-plate_h/2,0]) cylinder(r=5,h=12);
		translate([-3.5,-plate_h/2+1.5,3]) rotate([0,90,0]) cylinder(r=1,h=7);
		translate([-3.5,-plate_h/2+1.5,7]) rotate([0,90,0]) cylinder(r=1,h=7);
		translate([-3.5,-plate_h/2+1.5,11]) rotate([0,90,0]) cylinder(r=1,h=7);
//		translate([-3.5,-plate_h/2+1.5,15]) rotate([0,90,0]) cylinder(r=1,h=7);
//		translate([-3.5,-plate_h/2+1.5,19]) rotate([0,90,0]) cylinder(r=1,h=7);
//		translate([-3.5,-plate_h/2+1.5,23]) rotate([0,90,0]) cylinder(r=1,h=7);
		translate([-0.5,-plate_h/2+1.5,0]) cube([1,29,12]);
	}
	translate([-50,-plate_h/2-100,-1]) cube([100,100,100]);
	translate([-1,-plate_h/2+5,10]) rotate([-33,0,0]) translate([0,-10,2]) cube([2,40,24]);

	translate([0,pen_y,0]) rotate([pen_angle,0,0]) {
			translate([0,0,-50]) cylinder(r=pen_radius, h=250);
	}
}

//pen guide
intersection() {
	translate([0,pen_y,0]) rotate([pen_angle,0,0]) {
		difference() {
			union() {
				translate([0,0,-50]) cylinder(r1=pen_radius+2, r2=pen_radius+1, h=120);
				translate([-2,20,0]) rotate([-pen_angle,0,0]) cube([4,20,40]);
			}
			translate([0,0,-50]) cylinder(r=pen_radius, h=250);
			translate([0,0,150]) rotate([-25,0,0]) cube(center=true,[100,100,1000]);
			translate([-7.5,5,20]) cube(center=true,[3,10,6]	);
			translate([7.5,5,20]) cube(center=true,[3,10,6]	);
		}
	}
	translate([-100,-100,0]) cube(200,200,200);
}

//arduino distances
translate([arduino_x,arduino_y,0])
	rotate([0,0,arduino_rotation])
		uno_holes(distances_h);

// ************ modules **************

//distances
module distance(height=17) {
	difference() {
		//translate([0,0,0]) cylinder(r=3, h=height);
		translate([-3,-7,0]) cube([6,10,height]);
		translate([0,0,-1]) cylinder(r=1.7, h=distances_h+2);
	}
}

module driver_pin() {
	rotate([0,0,45]) cylinder($fn=4, r=2.8+1.4, h=5);
}

module corner_holes() {
	translate([plate_w/2-4, plate_h/2-4, -50]) cylinder(r=2, h=100);
   translate([-plate_w/2+4, plate_h/2-4, -50]) cylinder(r=2, h=100);
}

module stepper_driver_holes(left) {
	dx=30;
	dy=26;
	union() {
        if(left) {
            translate([-dx/2,dy/2,-5]) cylinder(r=1.7, h=11);
            translate([dx/2,-dy/2,-5]) cylinder(r=1.7, h=11);
        }
        else {
            translate([-dx/2,-dy/2,-5]) cylinder(r=1.7, h=11);
            translate([dx/2,dy/2,-5]) cylinder(r=1.7, h=11);
        }
	}
}

module edge() {

	difference() {
		union() {
			difference() {
				translate([0,-y_cut/2,2.4]) cube(center=true, [plate_w,plate_h-y_cut, plate_t+3]);
				translate([0,-y_cut/2,-1]) cube(center=true, [plate_w-4,plate_h-4-y_cut, 20]);
			}
			translate([0,plate_h/2-66,1.5]) cube(center=true, [4,22, 3]);
			translate([0,3,1.5]) cube(center=true, [plate_w,3, 3]);
			translate([0,-32,1.5]) cube(center=true, [plate_w,3, 3]);
		}
		translate([-stepper_x,stepper_y,1.5]) cylinder(r=19, h=10);
		translate([stepper_x,stepper_y,1.5]) cylinder(r=19, h=10);

	}
}

module stepper_driver_pins(left) {
	dx=30;
	dy=26;
	union() {
        if(left) {
            translate([-dx/2,dy/2,0]) driver_pin();
            translate([dx/2,-dy/2,0]) driver_pin();
        }
        else {
            translate([-dx/2,-dy/2,0]) driver_pin();
            translate([dx/2,dy/2,0]) driver_pin();
        }
	}
}

//arduino
//import("Slim_Arduino_Uno_Case/arduino-case_bottom.stl");

module uno_holes(make_distances=0) {
	holes = [
//		[  2.54, 15.24,-5 ],
		[  17.78, 66.04,-5 ],
		[  45.72, 66.04,-5 ],
//		[  50.8, 13.97,-5 ]
		];
	union(){
		for(i = holes ) {
			translate(i)
				union() {
					if(make_distances > 0) {
						translate([0,0,5]) distance(make_distances);
					}
					else {
						cylinder(r=1.7,h=10);
						//translate([0,0,5.5]) cylinder(r=4,h=10);
					}
				}
		}
	}
}

module uno_connector_holes() {

}

module stepperholes() {
	translate([0,0,-50])cylinder(r=5, h=100);
	translate([35/2,  -8, -50]) cylinder(r=2, h=100);
	translate([-35/2, -8, -50]) cylinder(r=2, h=100);
}

module stepper() {
	color("Goldenrod", alpha=0.8) {
		translate([0,0,0])cylinder(r=2.5, h=8);
	}
	color("Silver", alpha=0.5) {
		translate([0,0,-1])cylinder(r=5, h=1);
		translate([0, -8, -20]) cylinder(r=14, h=19);

		difference() {
			union() {
				translate([-35/2,  -8, -2]) cylinder(r=3.5, h=1);
				translate([35/2,  -8, -2]) cylinder(r=3.5, h=1);
				translate([-35/2,  -8-3.5, -2]) cube([35,7,1]);
			}
			translate([-35/2,  -8, -4]) cylinder(r=2, h=10);
			translate([35/2,  -8, -4]) cylinder(r=2, h=10);
		}
	}
	color("Blue", alpha=0.5) {
		translate([-7.5,-8-18,-18]) cube([15,18,17]);
	}
}

module battery() {
	translate([-45/2, -26/2,17/2]) {
		color("Black", alpha=0.5) {
			cube([45,26,17]);
		}
		color("Silver", alpha=0.5) {
			translate([0,0,17/2]) rotate([0,-90,0]) {
				translate([0,13-6,0]) cylinder(r=3,h=3);
				translate([0,13+6,0]) difference() {
					cylinder(r=4,h=3,$fn=6);
					cylinder(r=3,h=4,$fn=6);
				}
			}
		}
	}
}

module servo() {

	color("Black", alpha=0.5) {
		cube(center=true, [23,21,12]);
		translate([0,21/2-3,0]) cube(center=true, [32,1,12]);
		translate([23/2-11.5/2,21/2,0]) rotate([-90,0,0]) {
			union() {
				cylinder(r=11.5/2, h=5);
				cylinder(r=2, h=10);
				translate([-3,-3, 9]) cube([6,20,1]);
			}
		}
	}
	color("White", alpla=0.5) {

	}
}


Base complete

Base complete

Base structure is complete, though dimensions have not been verified.

//washer(15, 25, 5);
//pipeSegment(id1=15, id2=20, od1=20, od2=25, h=10);
//radialCylinders(cd=25, pd=3, h=5, angles=[0, 45,90, 180]);
baseThickness=3;
wallThickness=baseThickness; // Thickness of vertical structures.
boltsCircleDia=50;
boltsDia=3;
boltsFlangeDia=10;
boltBigFlangeDia= boltsFlangeDia+2*wallThickness; // to accomodate for cylinder
centralHoleBase=30;
baseOuterDia=boltsCircleDia+boltsDia;
baseExtensionRadius = 79; // From center (it's 29mm from bolt hole center.)
baseExtensionNarrowWidth = 10;
baseExtensionWideWidth = 15;

punchThickness = baseThickness+2; // We extend punch solids by 1 in each direction
difference() {
    union() {
        washer(centralHoleBase, baseOuterDia, baseThickness);
        radialCylinders(cd=boltsCircleDia, pd=boltsFlangeDia, h=baseThickness, angles=[120,240], $fn=60);
        radialCylinders(cd=boltsCircleDia, pd=boltBigFlangeDia, h=baseThickness, angles=[0], $fn=60);
        translate([0,-baseExtensionNarrowWidth/2, 0])
            cube([baseExtensionRadius, baseExtensionNarrowWidth, baseThickness]);
        translate([baseExtensionRadius,-baseExtensionWideWidth/2, 0])
            cube([wallThickness, baseExtensionWideWidth, baseThickness]);
    }
    union() {
    translate([0,0,-1])
        cylinder(d=centralHoleBase, h=punchThickness);
    radialCylinders(cd=boltsCircleDia, pd=boltsDia, h=punchThickness, angles=[0, 120,240], zo=-1, $fn=60);
    }
}

// A washer with base on x-y plane and axis == z-axis
module washer(id, od, h) {
    difference() {
        cylinder(h, d=od, $fn=60);
        translate([0,0,-h*0.05])
            cylinder(h*1.1, d=id, $fn=60);
    }
}

// Creates a segment of a pipe. The center of the segment is the z-axis.
// The base of the segment is the x-y plane. The base inner/outer dia are
// id1/id2 and the top inner/outer dia are id2/od2. The height of the
// segment is h.
module pipeSegment(id1, od1, id2, od2, h) {
    difference() {
        cylinder(h, d1=od1, d2=od2, $fn=60);
        translate([0,0,-h*0.05])
            cylinder(h*1.1, d1=id1, d2=id2, $fn=60);
    }
}

// Creates a union of several cylinders along a circle with diameter cd.
// One application: subtract these from a circular gasket.
// The cylinders have diameter pd and height h. The circle is on the x-y
// plane and it's center the origin. The *base* of the punches is paralel
// to the x-y plane, with z=zo. The cylinders are positioned at the
// specified vector of angles.
module radialCylinders(cd, pd, h, angles, zo) {
    //cylinder(d=pd, h);
    translate([0,0, 0]) {
        for(a = angles) {
            rotate(a)
                translate([cd/2, 0, zo])
                    cylinder(d=pd, h);
        }
    }
}

// A washer with base on x-y plane and axis == z-axis
module washer(id, od, h) {
}

// Creates several cylinders along a circle with diameter cd.
// One application: subtract these from a circular gasket.
// The cylinders have diameter pd and height h. The circle is on the x-y
// plane and it's center the origin. The *base* of the punches is on
// the x-y plane. The cylinders are positioned at the specified vector
// of angles.
module radialCylinders(cd, pd, h, angles) {
}

// Creates a segment of a pipe. The center of the segment is the z-axis.
// The base of the segment is the x-y plane. The base inner/outer dia are
// id1/id2 and the top inner/outer dia are id2/od2. The height of the
// segment is h.
module pipeSegment(id1, od1, id2, od2, h) {
}


Update shim for guitar neck

Update shim for guitar neck


bearing/data: add clip dist for thick bushings


bearing: remove show_zips, use  instead

bearing: remove show_zips, use  instead

use <transforms.scad>;
use <shapes.scad>
include <bearing_data.scad>
include <units.scad>
include <misc.scad>

module bearing(bearing_type, extra_h=0, override_h=undef, orient=[0,0,1], align=[0,0,0])
{
    h = fallback(override_h, bearing_type[2]) + extra_h;
    size_align(size=[bearing_type[1],bearing_type[1],h], align=align ,orient=orient)
    difference()
    {
        // outer
        cylindera(h=h, d=bearing_type[1], align=[0,0,0]);
        // inner
        cylindera(h=h+.1, d=bearing_type[0], align=[0,0,0]);
        // clips
        if(len(bearing_type) > 3)
        {
            clip_depth=.5;
            for(z=[-1,1])
            translate([0,0,z*bearing_type[3]/2])
            difference()
            {
                cylindera(h=1*mm, d=bearing_type[2]+1, align=[0,0,-z]);
                cylindera(h=1*mm+.1, d=bearing_type[4]-clip_depth, align=[0,0,-z]);
            }

        }
    }
}

module bearing_mount_holes(bearing_type, ziptie_type=[2*mm, 3*mm], ziptie_bearing_distance=3*mm, tolerance=1.01, orient=[1,0,0], ziptie_dist=undef, with_zips=true)
{
    ziptie_thickness = ziptie_type[0];
    ziptie_width = ziptie_type[1]+0.6*mm;

    ziptie_dist_ = fallback(ziptie_dist, bearing_type[3]/2);

    orient(orient)
    {
        // Main bearing cut
        cylindera(h=bearing_type[2]*tolerance, d=bearing_type[1]*tolerance, orient=[0,0,1]);

        if(with_zips)
        {
            for(z=[-1,1])
                translate([0,0,z*ziptie_dist_ - z*1/2])
                    hollow_cylinder(
                            d=bearing_type[1]+ziptie_bearing_distance+ziptie_thickness,
                            thickness = ziptie_thickness*2,
                            h = ziptie_width,
                            taper=false,
                            orient=[0,0,1],
                            align=[0,0,0]
                            );
        }

        // for linear rod
        cylindera(d=bearing_type[0]+1*mm, h=100, orient=[0,0,1]);

        if($show_vit)
        {
            %bearing(bearing_type=bearing_type);

            for(z=[-1,1])
            translate([0,0,z*ziptie_dist_])
            {
                %hollow_cylinder(
                        d=bearing_type[1]+ziptie_bearing_distance+ziptie_thickness,
                        thickness = ziptie_thickness,
                        h = ziptie_width,
                        taper=false,
                        orient=[0,0,1],
                        align=[0,0,-z]
                        );
            }
        }
    }
}

if(false)
{
    b=bearing_igus_rj4jp_01_12;
    bearing_mount_holes(b, orient=[0,0,1]);
    translate([30,0,0])
    bearing(b, orient=[0,0,1]);
}

use <transforms.scad>;
use <shapes.scad>
include <bearing_data.scad>
include <units.scad>
include <misc.scad>

module bearing(bearing_type, extra_h=0, override_h=undef, orient=[0,0,1], align=[0,0,0])
{
    h = fallback(override_h, bearing_type[2]) + extra_h;
    size_align(size=[bearing_type[1],bearing_type[1],h], align=align ,orient=orient)
    difference()
    {
        // outer
        cylindera(h=h, d=bearing_type[1], align=[0,0,0]);
        // inner
        cylindera(h=h+.1, d=bearing_type[0], align=[0,0,0]);
        // clips
        if(len(bearing_type) > 3)
        {
            clip_depth=.5;
            for(z=[-1,1])
            translate([0,0,z*bearing_type[3]/2])
            difference()
            {
                cylindera(h=1*mm, d=bearing_type[2]+1, align=[0,0,-z]);
                cylindera(h=1*mm+.1, d=bearing_type[4]-clip_depth, align=[0,0,-z]);
            }

        }
    }
}

module bearing_mount_holes(bearing_type, ziptie_type=[2*mm, 3*mm], ziptie_bearing_distance=3*mm, tolerance=1.01, orient=[1,0,0], ziptie_dist=undef, with_zips=true, show_zips=false)
{
    ziptie_thickness = ziptie_type[0];
    ziptie_width = ziptie_type[1]+0.6*mm;

    ziptie_dist_ = fallback(ziptie_dist, bearing_type[3]/2);

    orient(orient)
    {
        // Main bearing cut
        cylindera(h=bearing_type[2]*tolerance, d=bearing_type[1]*tolerance, orient=[0,0,1]);

        if(with_zips)
        {
            for(z=[-1,1])
                translate([0,0,z*ziptie_dist_ - z*1/2])
                    hollow_cylinder(
                            d=bearing_type[1]+ziptie_bearing_distance+ziptie_thickness,
                            thickness = ziptie_thickness*2,
                            h = ziptie_width,
                            taper=false,
                            orient=[0,0,1],
                            align=[0,0,0]
                            );
        }

        // for linear rod
        cylindera(d=bearing_type[0]+1*mm, h=100, orient=[0,0,1]);

        if(show_zips)
        {
            for(z=[-1,1])
            translate([0,0,z*ziptie_dist_])
            {
                %hollow_cylinder(
                        d=bearing_type[1]+ziptie_bearing_distance+ziptie_thickness,
                        thickness = ziptie_thickness,
                        h = ziptie_width,
                        taper=false,
                        orient=[0,0,1],
                        align=[0,0,-z]
                        );
            }
        }
    }
}

if(false)
{
    b=bearing_igus_rj4jp_01_12;
    bearing_mount_holes(b, orient=[0,0,1]);
    translate([30,0,0])
    bearing(b, orient=[0,0,1]);
}


screw/screw_cut: fix head cutout position

include <system.scad>
include <shapes.scad>
include <transforms.scad>
include <misc.scad>

include <metric-thread-data.scad>
include <metric-hexnut-data.scad>
include <metric-knurlnut-data.scad>

use <scad-utils/transformations.scad>

// naive, assume head height is same as thread size (generally true for cap heads)
function get_screw_head_h(thread) = lookup(ThreadSize, thread);
function get_screw_head_d(thread) = 2 * lookup(ThreadSize, thread);

module screw(nut, thread, h=10, tolerance=1.05, head_embed=false, with_nut=true, with_head=true, nut_offset=0, orient=[0,0,1], align=[0,0,0])
{
    nut_thread = get(MHexNutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != undef, "screw: No nut or thread given");
    /*assert(nut!=undef && thread!=undef && nut_thread != thread_, "screw: Mismatched nut and thread");*/

    head_h = get_screw_head_h(nut);
    nut_h = lookup(MHexNutThickness,nut)*tolerance;
    threadsize = lookup(ThreadSize, thread_);

    s = threadsize*tolerance;
    total_h = h;
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=[0,0,1], align=align)
    {
        translate([0,0,head_embed?-head_h:0])
        {
            if(with_head)
            {
                translate([0,0,h/2+.01])
                screw_head(thread=thread_, orient=[0,0,1], align=[0,0,1]);
            }

            screw_thread(thread=thread_, h=h+.1, tolerance=tolerance, orient=[0,0,1], align=[0,0,0]);

            if(with_nut && nut != undef)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                screw_nut(nut=nut, tolerance=tolerance, orient=[0,0,1], align=[0,0,-1]);
            }
        }
    }
}

module screw_cut(nut, thread, h=10, tolerance=1.05, head_embed=false, with_nut=true, nut_cut_h=1000, with_head=true, head_cut_h=1000, nut_offset=0, cut_access=true, orient=[0,0,1], align=[0,0,0])
{
    nut_thread = get(MHexNutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != undef, "screw_cut: No nut or thread given");
    /*assert(nut!=undef && thread!=undef && nut_thread != thread_, "screw_cut: Mismatched nut and thread");*/

    nut_h = get(MHexNutThickness,nut)*tolerance;
    head_h = get_screw_head_h(thread_);
    assert(head_h != undef, "screw_cut: head_h is undef!");

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

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

            screw_thread_cut(thread=thread_, h=h+.1, tolerance=tolerance, orient=[0,0,1], align=[0,0,0]);

            if(with_nut && nut != undef)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                screw_nut_cut(nut=nut, tolerance=tolerance, h=nut_cut_h, align=[0,0,-1]);
            }
        }
    }

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

module screw_thread(thread, h=10, tolerance=1.00, orient=[0,0,1], align=[0,0,0])
{
    assert(thread!=undef, "screw_thread: thread is undef");
    // TODO render thread
    threadsize = lookup(ThreadSize, thread);
    cylindera(d=threadsize, h=h, orient=orient, align=align);
}

module screw_thread_cut(thread, h=10, tolerance=1.05, orient=[0,0,1], align=[0,0,0])
{
    assert(thread!=undef, "screw_thread_cut: thread is undef");
    threadsize = lookup(ThreadSize, thread);
    cylindera(d=threadsize*tolerance, h=h, orient=orient, align=align);
}

module screw_head(thread, tolerance=1.00, override_h=undef, orient=[0,0,1], align=[0,0,0])
{
    assert(thread!=undef, "screw_head: thread is undef");

    threadsize = lookup(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread);
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    difference()
    {
        cylindera(d=head_d, h = fallback(override_h,head_h));
        translate([0,0,head_h/2])
        cylindera(d=threadsize, h = head_d/2);
    }
}

module screw_head_cut(thread, tolerance=1.05, override_h=undef, orient=[0,0,1], align=[0,0,0])
{
    assert(thread!=undef, "screw_head_cut: thread is undef");

    threadsize = lookup(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread)*tolerance;
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    translate([0,0,-head_h/2])
    cylindera(d=head_d, h=fallback(override_h, head_h), align=[0,0,1]);
}

module screw_nut(nut, tolerance=1.00, override_h=undef, orient=[0,0,1], align=[0,0,0])
{
    assert(nut!=undef, "screw_nut: nut is undef");

    thickness = lookup(MHexNutThickness,nut)*tolerance;
    d = lookup(MHexNutWidthMin, nut)*tolerance;
    facets = lookup(MHexNutFacets, nut);
    cylindera($fn=lookup(MHexNutFacets, nut), d=d, h=fallback(override_h, thickness), orient=orient, align=align);
}

module screw_nut_cut(nut, tolerance=1.05, h=1000, orient=[0,0,1], align=[0,0,0])
{
    assert(nut!=undef, "screw_nut_cut: nut is undef");

    thickness = lookup(MHexNutThickness,nut)*tolerance;
    d = lookup(MHexNutWidthMin, nut)*tolerance;
    size_align(size=[d,d,thickness], orient=orient, align=align)
    {
        translate([0,0,thickness/2])
        {
            screw_nut(nut, tolerance, orient=[0,0,1], align=[0,0,-1]);
            translate([0,0,-thickness+.01])
            {
                cylindera(d=d*1.1, h=h, orient=[0,0,1], align=[0,0,-1]);
            }
        }
    }
}

module nut_trap_cut(nut, thread, trap_offset=10, screw_l=10*mm, screw_l_extra=2*mm, trap_h=10, trap_axis=[0,-1,0], orient=[0,0,1], align=[0,0,0])
{
    nut_thread = get(MHexNutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != undef, "nut_trap_cut: No nut or thread given");
    /*assert(nut!=undef && thread!=undef && nut_thread != thread_, "nut_trap_cut: Mismatched nut and thread");*/

    threadsize = lookup(ThreadSize, thread_);
    head_h = get_screw_head_h(thread);
    nut_h = lookup(MHexNutThickness,nut)+.5*mm;

    nut_width_min = lookup(MHexNutWidthMin, nut)+.1*mm;
    nut_width_max = lookup(MHexNutWidthMax, nut)+.1*mm;
    s = nut_width_min;
    total_h = nut_h;

    size_align(size=[s, s, total_h], orient=orient, orient_ref=[0,0,1], align=align)
    {
        translate([0,0,nut_h/2-.1])
        screw_thread_cut(thread=thread_, tolerance=1.1, h=screw_l, orient=[0,0,-1], align=[0,0,1]);

        if(screw_l_extra>0)
        translate([0,0,-nut_h/2+.1])
        screw_thread_cut(thread=thread_, tolerance=1.1, h=screw_l_extra+.1, orient=[0,0,1], align=[0,0,-1]);

        hull()
        {
            orient(-orient)
            translate(-.15*mm*trap_axis)
            stack(dist=trap_h, axis=trap_axis)
            {
                orient(orient)
                {
                    rotate([0,0,30])
                    cylindera($fn=lookup(MHexNutFacets, nut), d=nut_width_max, h=nut_h, align=[0,0,0]);
                }

                orient(orient)
                cubea(size=[nut_width_min,nut_width_min,nut_h], align=[0,0,0]);
            }
        }

        if($show_vit)
        {
            translate([0,0, screw_l])
            translate([0,0, -nut_h/2])
            %screw(nut=nut, thread=thread, tolerance=1, with_nut=false, orient=[0,0,-1], align=[0,0,-1]);

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

    }
}

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

if(false)
{
    nut1 = MHexNutM3;
    nut2 = MHexNutM5;
    nut3 = MKnurlInsertNutM3_3_42;
    nut4 = MKnurlInsertNutM3_5_42;

    /*translate([0,20,0])*/
    /*translate([0,20,0])*/
    /*for(axis1=[0:len(AXES)-1])*/
    /*[>for(axis2=AXES)<]*/
    /*translate(20*AXES[axis1])*/
    /*{*/
        /*ta = v_rotate(90, AXES[axis1])*AXES[axis1];*/
        /*echo(AXES[axis1], ta);*/
        /*nut_trap_cut(nut=nut1, trap_axis=ta, orient=AXES[axis1]);*/
    /*}*/

    /*stack(d=30, axis=[1,0,0])*/
    /*{*/
        /*stack(d=20, axis=[0,1,0])*/
        /*{*/
            /*nut_trap_cut(nut=nut1, trap_axis=[0,0,1], orient=[1,0,0]);*/
            /*nut_trap_cut(nut=nut1, trap_axis=[0,0,1], orient=[-1,0,0]);*/
        /*}*/

        /*stack(d=20, axis=[1,0,0])*/
        /*{*/
            /*nut_trap_cut(nut=nut1, trap_axis=[0,0,1], orient=[0,1,0]);*/

            /*nut_trap_cut(nut=nut1, trap_axis=[0,0,1], orient=[0,-1,0]);*/
        /*}*/
    /*}*/

    box_w = 100;
    box_h = 10;
    box_d = 10;
    o = 10;

    $show_vit = true;
    $fs= 0.5;
    $fa = 4;

    difference()
    {
        cubea([box_w,box_d/2,box_h], align=[1,1,1]);

        test_cuts();
    }
    /*difference()*/
    /*{*/
        /*cubea([box_w,box_d/2,box_h], align=[1,-1,1]);*/

        /*test_cuts();*/
    /*}*/

}

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

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

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

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

            translate([0,0,box_h/2])
                nut_trap_cut(nut=nut1, h=box_d, head_embed=false, trap_h=10, screw_l_extra=2*mm, trap_axis=[0,0,1], orient=[0,1,0], align=[0,0,0]);

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

include <system.scad>
include <shapes.scad>
include <transforms.scad>
include <misc.scad>

include <metric-thread-data.scad>
include <metric-hexnut-data.scad>
include <metric-knurlnut-data.scad>

use <scad-utils/transformations.scad>

// naive, assume head height is same as thread size (generally true for cap heads)
function get_screw_head_h(thread) = lookup(ThreadSize, thread);
function get_screw_head_d(thread) = 2 * lookup(ThreadSize, thread);

module screw(nut, thread, h=10, tolerance=1.05, head_embed=false, with_nut=true, with_head=true, nut_offset=0, orient=[0,0,1], align=[0,0,0])
{
    nut_thread = get(MHexNutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != undef, "screw: No nut or thread given");
    /*assert(nut!=undef && thread!=undef && nut_thread != thread_, "screw: Mismatched nut and thread");*/

    head_h = get_screw_head_h(nut);
    nut_h = lookup(MHexNutThickness,nut)*tolerance;
    threadsize = lookup(ThreadSize, thread_);

    s = threadsize*tolerance;
    total_h = h;
    size_align(size=[s, s, total_h], orient=-orient, orient_ref=[0,0,1], align=align)
    {
        translate([0,0,head_embed?-head_h:0])
        {
            if(with_head)
            {
                translate([0,0,h/2+.01])
                screw_head(thread=thread_, orient=[0,0,1], align=[0,0,1]);
            }

            screw_thread(thread=thread_, h=h+.1, tolerance=tolerance, orient=[0,0,1], align=[0,0,0]);

            if(with_nut && nut != undef)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                screw_nut(nut=nut, tolerance=tolerance, orient=[0,0,1], align=[0,0,-1]);
            }
        }
    }
}

module screw_cut(nut, thread, h=10, tolerance=1.05, head_embed=false, with_nut=true, nut_cut_h=1000, with_head=true, head_cut_h=1000, nut_offset=0, cut_access=true, orient=[0,0,1], align=[0,0,0])
{
    nut_thread = get(MHexNutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != undef, "screw_cut: No nut or thread given");
    /*assert(nut!=undef && thread!=undef && nut_thread != thread_, "screw_cut: Mismatched nut and thread");*/

    nut_h = get(MHexNutThickness,nut)*tolerance;
    head_h = get_screw_head_h(thread);
    threadsize = get(ThreadSize, thread_);
    assert(threadsize != undef, "screw_cut: threadsize is undef!");

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

            screw_thread_cut(thread=thread_, h=h+.1, tolerance=tolerance, orient=[0,0,1], align=[0,0,0]);

            if(with_nut && nut != undef)
            {
                translate([0,0,-h/2+nut_h+nut_offset+(head_embed?head_h:0)])
                screw_nut_cut(nut=nut, tolerance=tolerance, h=nut_cut_h, align=[0,0,-1]);
            }
        }
    }

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

module screw_thread(thread, h=10, tolerance=1.00, orient=[0,0,1], align=[0,0,0])
{
    assert(thread!=undef, "screw_thread: thread is undef");
    // TODO render thread
    threadsize = lookup(ThreadSize, thread);
    cylindera(d=threadsize, h=h, orient=orient, align=align);
}

module screw_thread_cut(thread, h=10, tolerance=1.05, orient=[0,0,1], align=[0,0,0])
{
    assert(thread!=undef, "screw_thread_cut: thread is undef");
    threadsize = lookup(ThreadSize, thread);
    cylindera(d=threadsize*tolerance, h=h, orient=orient, align=align);
}

module screw_head(thread, tolerance=1.00, override_h=undef, orient=[0,0,1], align=[0,0,0])
{
    assert(thread!=undef, "screw_head: thread is undef");

    threadsize = lookup(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread);
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    difference()
    {
        cylindera(d=head_d, h = fallback(override_h,head_h));
        translate([0,0,head_h/2])
        cylindera(d=threadsize, h = head_d/2);
    }
}

module screw_head_cut(thread, tolerance=1.05, override_h=undef, orient=[0,0,1], align=[0,0,0])
{
    assert(thread!=undef, "screw_head_cut: thread is undef");

    threadsize = lookup(ThreadSize, thread);
    head_h = get_screw_head_h(thread);
    head_d = get_screw_head_d(thread)*tolerance;
    size_align(size=[head_d, head_d, head_h], orient=orient, align=align)
    translate([0,0,-head_h/2])
    cylindera(d=head_d, h=fallback(override_h, head_h), align=[0,0,1]);
}

module screw_nut(nut, tolerance=1.00, override_h=undef, orient=[0,0,1], align=[0,0,0])
{
    assert(nut!=undef, "screw_nut: nut is undef");

    thickness = lookup(MHexNutThickness,nut)*tolerance;
    d = lookup(MHexNutWidthMin, nut)*tolerance;
    facets = lookup(MHexNutFacets, nut);
    cylindera($fn=lookup(MHexNutFacets, nut), d=d, h=fallback(override_h, thickness), orient=orient, align=align);
}

module screw_nut_cut(nut, tolerance=1.05, h=1000, orient=[0,0,1], align=[0,0,0])
{
    assert(nut!=undef, "screw_nut_cut: nut is undef");

    thickness = lookup(MHexNutThickness,nut)*tolerance;
    d = lookup(MHexNutWidthMin, nut)*tolerance;
    size_align(size=[d,d,thickness], orient=orient, align=align)
    {
        translate([0,0,thickness/2])
        {
            screw_nut(nut, tolerance, orient=[0,0,1], align=[0,0,-1]);
            translate([0,0,-thickness+.01])
            {
                cylindera(d=d*1.1, h=h, orient=[0,0,1], align=[0,0,-1]);
            }
        }
    }
}

module nut_trap_cut(nut, thread, trap_offset=10, screw_l=10*mm, screw_l_extra=2*mm, trap_h=10, trap_axis=[0,-1,0], orient=[0,0,1], align=[0,0,0])
{
    nut_thread = get(MHexNutThread, nut);
    thread_ = fallback(thread, nut_thread);
    assert(thread_ != undef, "nut_trap_cut: No nut or thread given");
    /*assert(nut!=undef && thread!=undef && nut_thread != thread_, "nut_trap_cut: Mismatched nut and thread");*/

    threadsize = lookup(ThreadSize, thread_);
    head_h = get_screw_head_h(thread);
    nut_h = lookup(MHexNutThickness,nut)+.5*mm;

    nut_width_min = lookup(MHexNutWidthMin, nut)+.1*mm;
    nut_width_max = lookup(MHexNutWidthMax, nut)+.1*mm;
    s = nut_width_min;
    total_h = nut_h;

    size_align(size=[s, s, total_h], orient=orient, orient_ref=[0,0,1], align=align)
    {
        translate([0,0,nut_h/2-.1])
        screw_thread_cut(thread=thread_, tolerance=1.1, h=screw_l, orient=[0,0,-1], align=[0,0,1]);

        if(screw_l_extra>0)
        translate([0,0,-nut_h/2+.1])
        screw_thread_cut(thread=thread_, tolerance=1.1, h=screw_l_extra+.1, orient=[0,0,1], align=[0,0,-1]);

        hull()
        {
            orient(-orient)
            translate(-.15*mm*trap_axis)
            stack(dist=trap_h, axis=trap_axis)
            {
                orient(orient)
                {
                    rotate([0,0,30])
                    cylindera($fn=lookup(MHexNutFacets, nut), d=nut_width_max, h=nut_h, align=[0,0,0]);
                }

                orient(orient)
                cubea(size=[nut_width_min,nut_width_min,nut_h], align=[0,0,0]);
            }
        }

        if($show_vit)
        {
            translate([0,0, screw_l])
            translate([0,0, -nut_h/2])
            %screw(nut=nut, thread=thread, tolerance=1, with_nut=false, orient=[0,0,-1], align=[0,0,-1]);

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

    }
}

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

if(false)
{
    nut1 = MHexNutM3;
    nut2 = MHexNutM5;
    nut3 = MKnurlInsertNutM3_3_42;
    nut4 = MKnurlInsertNutM3_5_42;

    /*translate([0,20,0])*/
    /*translate([0,20,0])*/
    /*for(axis1=[0:len(AXES)-1])*/
    /*[>for(axis2=AXES)<]*/
    /*translate(20*AXES[axis1])*/
    /*{*/
        /*ta = v_rotate(90, AXES[axis1])*AXES[axis1];*/
        /*echo(AXES[axis1], ta);*/
        /*nut_trap_cut(nut=nut1, trap_axis=ta, orient=AXES[axis1]);*/
    /*}*/

    /*stack(d=30, axis=[1,0,0])*/
    /*{*/
        /*stack(d=20, axis=[0,1,0])*/
        /*{*/
            /*nut_trap_cut(nut=nut1, trap_axis=[0,0,1], orient=[1,0,0]);*/
            /*nut_trap_cut(nut=nut1, trap_axis=[0,0,1], orient=[-1,0,0]);*/
        /*}*/

        /*stack(d=20, axis=[1,0,0])*/
        /*{*/
            /*nut_trap_cut(nut=nut1, trap_axis=[0,0,1], orient=[0,1,0]);*/

            /*nut_trap_cut(nut=nut1, trap_axis=[0,0,1], orient=[0,-1,0]);*/
        /*}*/
    /*}*/

    box_w = 100;
    box_h = 10;
    box_d = 10;
    o = 10;

    $show_vit = true;
    $fs= 0.5;
    $fa = 4;

    difference()
    {
        cubea([box_w,box_d/2,box_h], align=[1,1,1]);

        test_cuts();
    }
    /*difference()*/
    /*{*/
        /*cubea([box_w,box_d/2,box_h], align=[1,-1,1]);*/

        /*test_cuts();*/
    /*}*/

}

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

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

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

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

            translate([0,0,box_h/2])
                nut_trap_cut(nut=nut1, h=box_d, head_embed=false, trap_h=10, screw_l_extra=2*mm, trap_axis=[0,0,1], orient=[0,1,0], align=[0,0,0]);

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


bearing_data: add some linear ball bearings