config: Increase X rods distance to 60mm

include <bearings.scad>
include <screws.scad>
include <thing_libutils/metric-thread.scad>

e=0.01;

// resolution of any round object (segment length)
fnr                         = 0.4;

extrusion_height            = 0.4*mm;

// width of a single wall, should match your slicer settings
extrusion_width             = 0.4*mm;

// how many lines should a normal wall have (i.e. 0.6(extrusion_width)*6(normal_wall_factor)= 3.6mm wall width)
normal_wall_factor          = 3;

thick_wall_factor           = 4;

// diameter of your filament.
filament_d                  = 1.75*mm;
filament_r                  = filament_d/2;

main_width = 340*mm;
main_depth = 420*mm;
main_height = 400*mm;
main_upper_width = 420*mm;

extrusion_color = [0.5,0.5,0.5];
extrusion_size = 20*mm;
extrusion_thread = ThreadM5;

main_lower_dist_z= 80*mm;
main_upper_dist_y= 160*mm;

xaxis_rod_distance = 60*mm;
xaxis_rod_d = 9.975*mm;
xaxis_rod_l = 500*mm;
xaxis_bearing=bearing_igus_rj4jp_01_10;

yaxis_rod_distance = 170*mm;
yaxis_rod_d = 11.975*mm;
yaxis_rod_l = 500*mm;
yaxis_bearing=bearing_igus_rj4jp_01_12;
ycarriage_size=[220*mm,220*mm,5*mm];

zaxis_rod_d = 11.975*mm;
zaxis_rod_l = 500*mm;
zaxis_bearing=bearing_igus_rj4jp_01_12;

// NEMA17 motor w/340mm leadscrew
zaxis_motor = [
                [NemaModel, 17],
                [NemaLengthShort, 33*mm],
                [NemaLengthMedium, 39*mm],
                [NemaLengthLong, 47*mm],
                [NemaSideSize, 42.20*mm],
                [NemaDistanceBetweenMountingHoles, 31.04*mm],
                [NemaMountingHoleDiameter, 4*mm],
                [NemaMountingHoleDepth, 4.5*mm],
                [NemaMountingHoleLip, -1*mm],
                [NemaMountingHoleCutoutRadius, 0*mm],
                [NemaEdgeRoundingRadius, 7*mm],
                [NemaRoundExtrusionDiameter, 22*mm],
                [NemaRoundExtrusionHeight, 1.9*mm],
                [NemaAxleDiameter, 8*mm],
                // custom front axle length
                [NemaFrontAxleLength, 340*mm],
                [NemaBackAxleLength, 15*mm],
                [NemaAxleFlatDepth, 0.5*mm],
                [NemaAxleFlatLengthFront, 15*mm],
                [NemaAxleFlatLengthBack, 14*mm]
         ];

zaxis_motor_offset_z = 10*mm;

// inner_d, outer_d, thread
zaxis_nut = [20*mm, 36.5*mm, 8*mm];

zaxis_nut_mount_outer = zaxis_nut[1]/2 + zaxis_bearing[1]/2 + 3;

// place z rod on edge of motor
/*zaxis_rod_screw_distance_x = max(zaxis_nut_mount_outer, zaxis_rod_d/2 + lookup(NemaSideSize,zaxis_motor)/2);*/
zaxis_rod_screw_distance_x = zaxis_nut_mount_outer;

xaxis_zaxis_distance_y = xaxis_rod_d/2 + zaxis_bearing[1]/2;

include <bearings.scad>
include <screws.scad>
include <thing_libutils/metric-thread.scad>

e=0.01;

// resolution of any round object (segment length)
fnr                         = 0.4;

extrusion_height            = 0.4*mm;

// width of a single wall, should match your slicer settings
extrusion_width             = 0.4*mm;

// how many lines should a normal wall have (i.e. 0.6(extrusion_width)*6(normal_wall_factor)= 3.6mm wall width)
normal_wall_factor          = 3;

thick_wall_factor           = 4;

// diameter of your filament.
filament_d                  = 1.75*mm;
filament_r                  = filament_d/2;

main_width = 340*mm;
main_depth = 420*mm;
main_height = 400*mm;
main_upper_width = 420*mm;

extrusion_color = [0.5,0.5,0.5];
extrusion_size = 20*mm;
extrusion_thread = ThreadM5;

main_lower_dist_z= 80*mm;
main_upper_dist_y= 160*mm;

xaxis_rod_distance = 45*mm;
xaxis_rod_d = 9.975*mm;
xaxis_rod_l = 500*mm;
xaxis_bearing=bearing_igus_rj4jp_01_10;

yaxis_rod_distance = 170*mm;
yaxis_rod_d = 11.975*mm;
yaxis_rod_l = 500*mm;
yaxis_bearing=bearing_igus_rj4jp_01_12;
ycarriage_size=[220*mm,220*mm,5*mm];

zaxis_rod_d = 11.975*mm;
zaxis_rod_l = 500*mm;
zaxis_bearing=bearing_igus_rj4jp_01_12;

// NEMA17 motor w/340mm leadscrew
zaxis_motor = [
                [NemaModel, 17],
                [NemaLengthShort, 33*mm],
                [NemaLengthMedium, 39*mm],
                [NemaLengthLong, 47*mm],
                [NemaSideSize, 42.20*mm],
                [NemaDistanceBetweenMountingHoles, 31.04*mm],
                [NemaMountingHoleDiameter, 4*mm],
                [NemaMountingHoleDepth, 4.5*mm],
                [NemaMountingHoleLip, -1*mm],
                [NemaMountingHoleCutoutRadius, 0*mm],
                [NemaEdgeRoundingRadius, 7*mm],
                [NemaRoundExtrusionDiameter, 22*mm],
                [NemaRoundExtrusionHeight, 1.9*mm],
                [NemaAxleDiameter, 8*mm],
                // custom front axle length
                [NemaFrontAxleLength, 340*mm],
                [NemaBackAxleLength, 15*mm],
                [NemaAxleFlatDepth, 0.5*mm],
                [NemaAxleFlatLengthFront, 15*mm],
                [NemaAxleFlatLengthBack, 14*mm]
         ];

zaxis_motor_offset_z = 10*mm;

// inner_d, outer_d, thread
zaxis_nut = [20*mm, 36.5*mm, 8*mm];

zaxis_nut_mount_outer = zaxis_nut[1]/2 + zaxis_bearing[1]/2 + 3;

// place z rod on edge of motor
/*zaxis_rod_screw_distance_x = max(zaxis_nut_mount_outer, zaxis_rod_d/2 + lookup(NemaSideSize,zaxis_motor)/2);*/
zaxis_rod_screw_distance_x = zaxis_nut_mount_outer;

xaxis_zaxis_distance_y = xaxis_rod_d/2 + zaxis_bearing[1]/2;


pale with a disk on the bottom

pale with a disk on the bottom

count = 2; // number of pales needed

module element()
{
  for(i = [0:9])
    translate([0, 3*i, 0])
      rotate([-90, 0, 0])
        cylinder(h=5, r1=5/2, r2=0, $fs=0.01);
  translate([0, -2, 0])
    rotate([-90, 0, 0])
      cylinder(h=2, r=20/2, $fn=100);
}

module halfElement()
{
  difference()
  {
    element();
    translate([-20, -3, -12])
      cube([50,50,12]);
  }
}

for(i = [0:count-1])
  translate([i*22, 0, 0])
  {
    translate([0, 5, 0])
      halfElement();
    rotate([0, 0, 180])
      halfElement();
  }


added matrix NxM elements on the print

added matrix NxM elements on the print

eps=0.01;
wall=4;
h=wall+8+5;
$fn=60;
N=4;
M=2;

module support_()
{
  translate(-wall/2*[0, 1, 0])
  {
    dist=8+wall+(8+2)/2;
    cube([dist, wall, wall]);
    translate([dist-wall, 0, 0])
      cube([wall, wall, wall+8]);
  }
}

module screw_space_()
{
  cylinder(r=(3.5+0.4)/2, h=h+eps);
  translate(-eps*[0,0,1])
    cylinder(r1=(8+0.5)/2, r2=(3.5+0.5)/2, h=4.5+eps);
}

module element()
{
  difference()
  {
    union()
    {
      cylinder(r=(8+2)/2, h=h);
      support_();
    }
    screw_space_();
  }
}

for(i=[1:N])
  for(j=[1:M])
    translate([(j-1)*25, (i-1)*12, 0])
      element();

eps=0.01;
wall=4;
h=wall+8+5;
$fn=60;
N=4;

module support_()
{
  translate(-wall/2*[0, 1, 0])
  {
    dist=8+wall+(8+2)/2;
    cube([dist, wall, wall]);
    translate([dist-wall, 0, 0])
      cube([wall, wall, wall+8]);
  }
}

module screw_space_()
{
  cylinder(r=(3.5+0.4)/2, h=h+eps);
  translate(-eps*[0,0,1])
    cylinder(r1=(8+0.5)/2, r2=(3.5+0.5)/2, h=4.5+eps);
}

module element()
{
  difference()
  {
    union()
    {
      cylinder(r=(8+2)/2, h=h);
      support_();
    }
    screw_space_();
  }
}

for(i=[1:N])
  translate([0, (i-1)*12, 0])
    element();


bearings/linear: fix clip groove depth

bearings/linear: fix clip groove depth


bottom holder part

bottom holder part

module holder()
{
  cylinder(h=5, r=50);
  d=30;
  e=d+4;
  h=5+40;
  difference()
  {
    translate([-e/2, -e/2, 0])
      cube([e, e, h]);
    translate([-d/2, -d/2, 0])
      cube([d, d, h]);
    translate([-h/2, 0, h-10])
      rotate([0, 90, 0])
        cylinder(r=5+1, h=h);
  }
}

holder();

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


lcd2004: fix oops

lcd2004: fix oops

include <thing_libutils/system.scad>
include <thing_libutils/units.scad>

include <config.scad>

use <thing_libutils/linear-extrusion.scad>
use <thing_libutils/screws.scad>
use <thing_libutils/transforms.scad>

// wall thickness
lcd2004_wall_thick=4;
lcd2004_width=151+2*lcd2004_wall_thick;
lcd2004_depth=63.5+2*lcd2004_wall_thick;
mount_lcd2004_mount_angle=45;

module mount_lcd2004_parts(
    eps=[0.1,0.1,0], // gap for cavities
    part=undef,
    height=16*mm,
    align, orient, roll, extra_roll, extra_roll_orient
    )
{

    size_align(size=[lcd2004_width,lcd2004_depth,height], align=align, orient=orient, orient_ref=Z, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
    if(part==undef)
    {
        difference()
        {
            mount_lcd2004_parts(lcd2004_wall_thick=lcd2004_wall_thick, eps=eps, part="pos", height=height);
            mount_lcd2004_parts(lcd2004_wall_thick=lcd2004_wall_thick, eps=eps, part="neg", height=height);
        }
    }
    else if(part=="pos")
    {
        translate([-lcd2004_width/2,-lcd2004_depth/2,-height/2])
        rcubea(size=[lcd2004_width,lcd2004_depth,height], rounding_radius=3, align=[1,1,1]);
    }
    else if(part=="neg")
    mirror(Y)
    translate([-lcd2004_width/2,-lcd2004_depth/2,-height/2])
    translate([lcd2004_wall_thick,lcd2004_wall_thick,0])
    {
        // LCD cutout
        translate([14.5,10,-2])
        cubea([97,40,19], align=[1,1,1]);

        // LCD PCB
        translate([14,-0.25,4])
        cubea([98.5,60.5,9+10], align=[1,1,1]);

        // Main PCB
        translate([0,7.75,8])
        cubea([151,56,5+22], align=[1,1,1]);

        // Switch axis
        translate([137,35,-2])
        cylindera(r=8/2, h=19, align=Z);

        // buzzer
        translate([130+13.5/2,8+39+12.5/2,-2])
        cylindera(r=12.5/2, h=19, align=Z);

        // reset switch
        translate([137,16,-2])
        cylindera(r=2, h=19, align=Z);

        // buzzer/switch/reset
        translate([116,8,2])
        cubea([29,55.5,10], align=[1,1,1]);

        // buzzer/switch/reset
        translate([118,18,2])
        cubea([33,35.5,10], align=[1,1,1]);

        // buzzer/switch/reset
        translate([0,18,2])
        cubea([11,35.5,10], align=[1,1,1]);

        // SD slot
        translate([-lcd2004_wall_thick-1,17,9])
        cubea([lcd2004_wall_thick+2,27,10], align=[1,1,1]);

        // Screw hole for PCB
        translate([0+2.9,8+2.5,3])
        cylindera(r=2.5/2, h=6, align=Z);

        // Screw hole for PCB
        translate([151-2.7,8+2.5,3])
        cylindera(r=2.5/2, h=6, align=Z);

        // Screw hole for PCB
        translate([0+2.9,8+55.5-2.5,3])
        cylindera(r=2.5/2, h=6, align=Z);

        // Screw hole for PCB
        translate([151-2.7,8+55.5-2.5,3])
        cylindera(r=2.5/2, h=6, align=Z);

        // LCD pin header
        translate([14.5+8,60-5,2])
        cubea([42,8.5,9], align=[1,1,1]);

        // Anti-Warping
        translate([50,-lcd2004_wall_thick/2,2])
        cubea([2,55.5+8+lcd2004_wall_thick,11], align=[1,1,1]);

        // Anti-Warping
        translate([90,-lcd2004_wall_thick/2,2])
        cubea([2,55.5+8+lcd2004_wall_thick,11], align=[1,1,1]);
    }
}

module gantry_lower()
{
    color(color_extrusion)
    for(z=[-1,1])
    {
        translate([0,0,z*-main_lower_dist_z/2])
        {
            for(y=[-1,1])
            translate([0, y*(main_depth/2), 0])
            {
                if($preview_mode)
                {
                    cubea([main_width, extrusion_size, extrusion_size], align=[0,y,-1]);
                }
                else
                {
                    linear_extrusion(h=main_width, align=[0,y,-1], orient=X);
                }
            }

            for(x=[-1,1])
            translate([x*(main_width/2), 0, 0])
            {
                if($preview_mode)
                {
                    cubea([extrusion_size, main_depth, extrusion_size], align=[-x,0,-1]);
                }
                else
                {
                    linear_extrusion(h=main_depth, align=[-x,0,-1], orient=Y);
                }
            }
        }
    }
}

lcd2004_mount_thickness = 5*mm;
module lcd2004_extrusion_conn(part, orient=Z, align=N)
{
    s = [extrusion_size, extrusion_size, lcd2004_mount_thickness];
    size_align(size=s, orient=orient, orient_ref=Z, align=align)
    if(part==undef)
    {
        difference()
        {
            lcd2004_extrusion_conn(part="pos");
            lcd2004_extrusion_conn(part="neg");
        }
    }
    else if(part=="pos")
    {
        rcubea(size=s, rounding_radius=3);
    }
    else if(part=="neg")
    {
        translate([0,0,-lcd2004_mount_thickness/2])
        screw_cut(nut=extrusion_nut, with_nut=true, nut_cut_h=30, with_head=true, head_cut_h=30, length=12*mm, align=Z, orient=Z);
    }
}

lcd2004_offset = [0, -34.2*mm,-5*mm];

module mount_lcd2004(part, show_gantry=false)
{
    if(part==undef)
    {
        difference()
        {
            mount_lcd2004(part="pos", show_gantry=show_gantry);
            mount_lcd2004(part="neg", show_gantry=show_gantry);
        }
    }
    else if(part == "pos")
    {
        hull()
        {
            for(x=[-1,1])
            translate([x*(lcd2004_width/2+extrusion_size),0,0])
            {
                translate([0,0,main_lower_dist_z/2-extrusion_size])
                lcd2004_extrusion_conn(part=part, orient=Z, align=[-x,0,-1]);
            }

            for(x=[-1,1])
            translate([x*(lcd2004_width/2+extrusion_size),0,0])
            {
                translate([0,0,-main_lower_dist_z/2])
                lcd2004_extrusion_conn(part=part, orient=[0,0,-1], align=[-x,0,1]);
            }

            translate(lcd2004_offset)
            rotate([-mount_lcd2004_mount_angle,0,0])
            mount_lcd2004_parts(part=part, orient=Y, align=Y);

        }
        if(show_gantry)
        {
            translate([0,main_depth/2,0])
            translate([0,extrusion_size/2,0])
            gantry_lower();
        }

    }
    else if(part == "neg")
    {
        for(x=[-1,1])
        translate([x*(lcd2004_width/2+extrusion_size),0,0])
        {
            translate([0,0,main_lower_dist_z/2-extrusion_size])
            lcd2004_extrusion_conn(part=part, orient=Z, align=[-x,0,-1]);

            translate([0,0,-main_lower_dist_z/2])
            {
                lcd2004_extrusion_conn(part=part, orient=[0,0,-1], align=[-x,0,1]);
            }
        }

        translate([0,10*mm,-extrusion_size/2])
        /*#rcubea([lcd2004_width+30*mm, extrusion_size+.2, main_lower_dist_z-extrusion_size-lcd2004_mount_thickness*2]);*/
        rcubea([1000, extrusion_size+.2, main_lower_dist_z-extrusion_size-lcd2004_mount_thickness*2]);

        /*translate([0,0,-extrusion_size/2])*/
        /*rcubea([lcd2004_width, extrusion_size+.2, main_lower_dist_z-extrusion_size-lcd2004_mount_thickness*1]);*/

        translate(lcd2004_offset)
        rotate([-mount_lcd2004_mount_angle,0,0])
        {
            mount_lcd2004_parts(part=part, orient=Y, align=Y);

            translate([0,1000+5,0])
            rotate([90,0,0])
            linear_extrude(height=1000)
            projection(cut=false)
            rotate([-90,0,0])
            mount_lcd2004_parts(part=part, orient=Y, align=Y);
        }

        translate(lcd2004_offset)
        translate([0,27,-13])
        translate([0,1000,0])
        rotate([90,0,0])
        linear_extrude(height=1000)
        projection(cut=false)
        rotate([-90,0,0])
        mount_lcd2004_parts(part=part, orient=Y, align=Y);

        translate([0,main_depth/2,0])
        translate([0,extrusion_size/2,0])
        gantry_lower();
    }

}

module part_lcd2004_mount()
{
    rotate([90+mount_lcd2004_mount_angle,0,0])
    translate(-lcd2004_offset)
    mount_lcd2004();
}

if(false)
{
    mount_lcd2004(show_gantry=true);
}

include <thing_libutils/system.scad>
include <thing_libutils/units.scad>

include <config.scad>

use <thing_libutils/linear-extrusion.scad>
use <thing_libutils/screws.scad>
use <thing_libutils/transforms.scad>

// wall thickness
lcd2004_wall_thick=4;
lcd2004_width=151+2*lcd2004_wall_thick;
lcd2004_depth=63.5+2*lcd2004_wall_thick;
mount_lcd2004_mount_angle=45;

module mount_lcd2004_parts(
    eps=[0.1,0.1,0], // gap for cavities
    part=undef,
    height=16*mm,
    align, orient, roll, extra_roll, extra_roll_orient
    )
{

    size_align(size=[lcd2004_width,lcd2004_depth,height], align=align, orient=orient, orient_ref=Z, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
    if(part==undef)
    {
        difference()
        {
            mount_lcd2004_parts(lcd2004_wall_thick=lcd2004_wall_thick, eps=eps, part="pos", height=height);
            mount_lcd2004_parts(lcd2004_wall_thick=lcd2004_wall_thick, eps=eps, part="neg", height=height);
        }
    }
    else if(part=="pos")
    {
        translate([-lcd2004_width/2,-lcd2004_depth/2,-height/2])
        rcubea(size=[lcd2004_width,lcd2004_depth,height], rounding_radius=3, align=[1,1,1]);
    }
    else if(part=="neg")
    mirror(Y)
    translate([-lcd2004_width/2,-lcd2004_depth/2,-height/2])
    translate([lcd2004_wall_thick,lcd2004_wall_thick,0])
    {
        // LCD cutout
        translate([14.5,10,-2])
        cubea([97,40,19], align=[1,1,1]);

        // LCD PCB
        translate([14,-0.25,4])
        cubea([98.5,60.5,9+10], align=[1,1,1]);

        // Main PCB
        translate([0,7.75,8])
        cubea([151,56,5+22], align=[1,1,1]);

        // Switch axis
        translate([137,35,-2])
        cylindera(r=8/2, h=19, align=Z);

        // buzzer
        translate([130+13.5/2,8+39+12.5/2,-2])
        cylindera(r=12.5/2, h=19, align=Z);

        // reset switch
        translate([137,16,-2])
        cylindera(r=2, h=19, align=Z);

        // buzzer/switch/reset
        translate([116,8,2])
        cubea([29,55.5,10], align=[1,1,1]);

        // buzzer/switch/reset
        translate([118,18,2])
        cubea([33,35.5,10], align=[1,1,1]);

        // buzzer/switch/reset
        translate([0,18,2])
        cubea([11,35.5,10], align=[1,1,1]);

        // SD slot
        translate([-lcd2004_wall_thick-1,17,9])
        cubea([lcd2004_wall_thick+2,27,10], align=[1,1,1]);

        // Screw hole for PCB
        translate([0+2.9,8+2.5,3])
        cylindera(r=2.5/2, h=6, align=Z);

        // Screw hole for PCB
        translate([151-2.7,8+2.5,3])
        cylindera(r=2.5/2, h=6, align=Z);

        // Screw hole for PCB
        translate([0+2.9,8+55.5-2.5,3])
        cylindera(r=2.5/2, h=6, align=Z);

        // Screw hole for PCB
        translate([151-2.7,8+55.5-2.5,3])
        cylindera(r=2.5/2, h=6, align=Z);

        // LCD pin header
        translate([14.5+8,60-5,2])
        cubea([42,8.5,9], align=[1,1,1]);

        // Anti-Warping
        translate([50,-lcd2004_wall_thick/2,2])
        cubea([2,55.5+8+lcd2004_wall_thick,11], align=[1,1,1]);

        // Anti-Warping
        translate([90,-lcd2004_wall_thick/2,2])
        cubea([2,55.5+8+lcd2004_wall_thick,11], align=[1,1,1]);
    }
}

module gantry_lower()
{
    color(color_extrusion)
    for(z=[-1,1])
    {
        translate([0,0,z*-main_lower_dist_z/2])
        {
            for(y=[-1,1])
            translate([0, y*(main_depth/2), 0])
            {
                if($preview_mode)
                {
                    cubea([main_width, extrusion_size, extrusion_size], align=[0,y,-1]);
                }
                else
                {
                    linear_extrusion(h=main_width, align=[0,y,-1], orient=X);
                }
            }

            for(x=[-1,1])
            translate([x*(main_width/2), 0, 0])
            {
                if($preview_mode)
                {
                    cubea([extrusion_size, main_depth, extrusion_size], align=[-x,0,-1]);
                }
                else
                {
                    linear_extrusion(h=main_depth, align=[-x,0,-1], orient=Y);
                }
            }
        }
    }
}

lcd2004_mount_thickness = 5*mm;
module lcd2004_extrusion_conn(part, orient=Z, align=N)
{
    s = [extrusion_size, extrusion_size, lcd2004_mount_thickness];
    size_align(size=s, orient=orient, orient_ref=Z, align=align)
    if(part==undef)
    {
        difference()
        {
            lcd2004_extrusion_conn(part="pos");
            lcd2004_extrusion_conn(part="neg");
        }
    }
    else if(part=="pos")
    {
        rcubea(size=s, rounding_radius=3);
    }
    else if(part=="neg")
    {
        translate([0,0,-lcd2004_mount_thickness/2])
        screw_cut(nut=extrusion_nut, with_nut=true, nut_cut_h=30, with_head=true, head_cut_h=30, length=12*mm, align=Z, orient=Z);
    }
}

lcd2004_offset = [0, -34.2*mm,-5*mm];

module mount_lcd2004(part, show_gantry=false)
{
    if(part==undef)
    {
        difference()
        {
            mount_lcd2004(part="pos", show_gantry=show_gantry);
            mount_lcd2004(part="neg", show_gantry=show_gantry);
        }
    }
    else if(part == "pos")
    {
        hull()
        {
            for(x=[-1,1])
            translate([x*(lcd2004_width/2+extrusion_size),0,0])
            {
                translate([0,0,main_lower_dist_z/2-extrusion_size])
                lcd2004_extrusion_conn(part=part, orient=Z, align=[-x,0,-1]);
            }

            for(x=[-1,1])
            translate([x*(lcd2004_width/2+extrusion_size),0,0])
            {
                translate([0,0,-main_lower_dist_z/2])
                lcd2004_extrusion_conn(part=part, orient=[0,0,-1], align=[-x,0,1]);
            }

            translate(lcd2004_offset)
            rotate([-mount_lcd2004_mount_angle,0,0])
            mount_lcd2004_parts(part=part, orient=Y, align=Y);

        }
        if(show_gantry)
        {
            translate([0,main_depth/2,0])
            translate([0,extrusion_size/2,0])
            gantry_lower();
        }

    }
    else if(part == "neg")
    {
        for(x=[-1,1])
        translate([x*(lcd2004_width/2+extrusion_size),0,0])
        {
            translate([0,0,main_lower_dist_z/2-extrusion_size])
            lcd2004_extrusion_conn(part=part, orient=Z, align=[-x,0,-1]);

            translate([0,0,-main_lower_dist_z/2])
            {
                lcd2004_extrusion_conn(part=part, orient=[0,0,-1], align=[-x,0,1]);
            }
        }

        translate([0,10*mm,-extrusion_size/2])
        /*#rcubea([lcd2004_width+30*mm, extrusion_size+.2, main_lower_dist_z-extrusion_size-lcd2004_mount_thickness*2]);*/
        rcubea([1000, extrusion_size+.2, main_lower_dist_z-extrusion_size-lcd2004_mount_thickness*2]);

        /*translate([0,0,-extrusion_size/2])*/
        /*rcubea([lcd2004_width, extrusion_size+.2, main_lower_dist_z-extrusion_size-lcd2004_mount_thickness*1]);*/

        translate(lcd2004_offset)
        rotate([-mount_lcd2004_mount_angle,0,0])
        {
            mount_lcd2004_parts(part=part, orient=Y, align=Y);

            translate([0,1000+5,0])
            rotate([90,0,0])
            linear_extrude(height=1000)
            projection(cut=false)
            rotate([-90,0,0])
            mount_lcd2004_parts(part=part, orient=Y, align=Y);
        }

        translate(lcd2004_offset)
        translate([0,27,-13])
        translate([0,1000,0])
        rotate([90,0,0])
        linear_extrude(height=1000)
        projection(cut=false)
        rotate([-90,0,0])
        mount_lcd2004_parts(part=part, orient=Y, align=Y);

        translate([0,main_depth/2,0])
        translate([0,extrusion_size/2,0])
        gantry_lower();
    }

}

module part_lcd2004_mount()
{
    rotate([90+mount_lcd2004_mount_angle,0,0])
    translate(-lcd2004_offset)
    mount_lcd2004();
}

/*if(false)*/
{
    mount_lcd2004(show_gantry=true);
}


Init context of test modules and test units in test package

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2017 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.
 *
 * Unit testing API.
 *
 * @package util/test
 * @author jsconan
 */

/**
 * Declares the target package of the test suite.
 *
 * @param String title
 * @param Number [expected]
 */
module testPackage(title, expected) {
    $testPackage = string(title);
    $testModule = "";
    $testUnit = "";

    displayTestTitle(title, "package", true);
    children();
    displayExpectedAsserts($children, expected, "module");
}

/**
 * Declares the function or the feature that is tested.
 *
 * @param String title
 * @param Number [expected]
 */
module testModule(title, expected) {
    $testModule = string(title);

    displayTestTitle(title, "module");
    children();
    displayExpectedAsserts($children, expected, "test");
}

/**
 * Declares a unit test.
 *
 * @param String title
 * @param Number [expected]
 */
module testUnit(title, expected) {
    $testUnit = string(title);

    children();
    displayExpectedAsserts($children, expected, "assertion");
}

/**
 * Declares a unit test context. That allows to isolate context data from other unit tests.
 *
 * @param String title
 * @param Number [expected]
 */
module testUnitContext(title, expected) {
    $testUnit = str($testUnit, " - ", string(title));

    children();
    displayExpectedAsserts($children, expected, "assertion");
}

/**
 * Asserts the actual value is equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertEqual(actual, expected, message) {
    result = actual == expected;
    message = or(message, "Should be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotEqual(actual, expected, message) {
    result = actual != expected;
    message = or(message, "Should not be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertApproxEqual(actual, expected, message) {
    result = approx(actual, expected);
    message = or(message, "Should be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotApproxEqual(actual, expected, message) {
    result = !approx(actual, expected);
    message = or(message, "Should not be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is equivalent to true.
 *
 * @param * value
 * @param String [message]
 */
module assertTrue(value, message) {
    result = !!value;
    message = or(message, "Should be true");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is falsy.
 *
 * @param * value
 * @param String [message]
 */
module assertFalse(value, message) {
    result = !value;
    message = or(message, "Should be false");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Failed assert.
 *
 * @param String [message]
 */
module failed(message) {
    message = or(message, "Failed!");

    displayAssertResult(false, message);
}

/**
 * Asserts the value is undefined.
 *
 * @param * value
 * @param String [message]
 */
module assertUndef(value, message) {
    result = isUndef(value);
    message = or(message, "Should be an undefined value");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a number.
 *
 * @param * value
 * @param String [message]
 */
module assertNumber(value, message) {
    result = isNumber(value);
    message = or(message, "Should be a number");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an integer.
 *
 * @param * value
 * @param String [message]
 */
module assertInteger(value, message) {
    result = isInteger(value);
    message = or(message, "Should be an integer");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a boolean.
 *
 * @param * value
 * @param String [message]
 */
module assertBoolean(value, message) {
    result = isBoolean(value);
    message = or(message, "Should be a boolean");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a vector.
 *
 * @param * value
 * @param String [message]
 */
module assertVector(value, message) {
    result = isVector(value);
    message = or(message, "Should be a vector");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an array.
 *
 * @param * value
 * @param String [message]
 */
module assertArray(value, message) {
    result = isArray(value);
    message = or(message, "Should be an array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyArray(value, message) {
    result = value == [];
    message = or(message, "Should be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is not an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyArray(value, message) {
    result = isArray(value) && value != [];
    message = or(message, "Should not be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a string.
 *
 * @param * value
 * @param String [message]
 */
module assertString(value, message) {
    result = isString(value);
    message = or(message, "Should be a string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyString(value, message) {
    result = value == "";
    message = or(message, "Should be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a not empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyString(value, message) {
    result = isString(value) && value != "";
    message = or(message, "Should not be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Displays a title.
 *
 * @private
 * @param String title
 * @param String type
 * @param Boolean [big]
 */
module displayTestTitle(title, type, big) {
    echo(formatTestTitle(title, type, big));
}

/**
 * Displays the number of expected asserts.
 *
 * @private
 * @param Number actual
 * @param Number [expected]
 * @param String [type]
 */
module displayExpectedAsserts(actual, expected, type) {
    echo(formatExpectedAsserts(actual, expected, type));
    if (!checkExpectedAsserts(actual, expected)) {
        color([1, 0.4, 0.2]) {
            cube(15, center=true);
        }
    }
}

/**
 * Displays an assert result.
 *
 * @private
 * @param Boolean result
 * @param String message
 * @param Array [details]
 */
module displayAssertResult(result, message, details) {
    echo(formatAssertResult(result, message, details));
    if (!result) {
        color([1, 0, 0]) {
            sphere(10);
        }
    }
}

/**
 * @license
 * MIT License
 *
 * Copyright (c) 2017 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.
 *
 * Unit testing API.
 *
 * @package util/test
 * @author jsconan
 */

/**
 * Declares the target package of the test suite.
 *
 * @param String title
 * @param Number [expected]
 */
module testPackage(title, expected) {
    $testPackage = string(title);

    displayTestTitle(title, "package", true);
    children();
    displayExpectedAsserts($children, expected, "module");
}

/**
 * Declares the function or the feature that is tested.
 *
 * @param String title
 * @param Number [expected]
 */
module testModule(title, expected) {
    $testModule = string(title);

    displayTestTitle(title, "module");
    children();
    displayExpectedAsserts($children, expected, "test");
}

/**
 * Declares a unit test.
 *
 * @param String title
 * @param Number [expected]
 */
module testUnit(title, expected) {
    $testUnit = string(title);

    children();
    displayExpectedAsserts($children, expected, "assertion");
}

/**
 * Declares a unit test context. That allows to isolate context data from other unit tests.
 *
 * @param String title
 * @param Number [expected]
 */
module testUnitContext(title, expected) {
    $testUnit = str($testUnit, " - ", string(title));

    children();
    displayExpectedAsserts($children, expected, "assertion");
}

/**
 * Asserts the actual value is equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertEqual(actual, expected, message) {
    result = actual == expected;
    message = or(message, "Should be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotEqual(actual, expected, message) {
    result = actual != expected;
    message = or(message, "Should not be equal");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertApproxEqual(actual, expected, message) {
    result = approx(actual, expected);
    message = or(message, "Should be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the actual value is not approximately equal to the expected value.
 *
 * @param * actual
 * @param * expected
 * @param String [message]
 */
module assertNotApproxEqual(actual, expected, message) {
    result = !approx(actual, expected);
    message = or(message, "Should not be approximately equal ");
    details = [actual, expected];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is equivalent to true.
 *
 * @param * value
 * @param String [message]
 */
module assertTrue(value, message) {
    result = !!value;
    message = or(message, "Should be true");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is falsy.
 *
 * @param * value
 * @param String [message]
 */
module assertFalse(value, message) {
    result = !value;
    message = or(message, "Should be false");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Failed assert.
 *
 * @param String [message]
 */
module failed(message) {
    message = or(message, "Failed!");

    displayAssertResult(false, message);
}

/**
 * Asserts the value is undefined.
 *
 * @param * value
 * @param String [message]
 */
module assertUndef(value, message) {
    result = isUndef(value);
    message = or(message, "Should be an undefined value");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a number.
 *
 * @param * value
 * @param String [message]
 */
module assertNumber(value, message) {
    result = isNumber(value);
    message = or(message, "Should be a number");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an integer.
 *
 * @param * value
 * @param String [message]
 */
module assertInteger(value, message) {
    result = isInteger(value);
    message = or(message, "Should be an integer");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a boolean.
 *
 * @param * value
 * @param String [message]
 */
module assertBoolean(value, message) {
    result = isBoolean(value);
    message = or(message, "Should be a boolean");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a vector.
 *
 * @param * value
 * @param String [message]
 */
module assertVector(value, message) {
    result = isVector(value);
    message = or(message, "Should be a vector");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an array.
 *
 * @param * value
 * @param String [message]
 */
module assertArray(value, message) {
    result = isArray(value);
    message = or(message, "Should be an array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyArray(value, message) {
    result = value == [];
    message = or(message, "Should be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is not an empty array.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyArray(value, message) {
    result = isArray(value) && value != [];
    message = or(message, "Should not be an empty array");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a string.
 *
 * @param * value
 * @param String [message]
 */
module assertString(value, message) {
    result = isString(value);
    message = or(message, "Should be a string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is an empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertEmptyString(value, message) {
    result = value == "";
    message = or(message, "Should be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Asserts the value is a not empty string.
 *
 * @param * value
 * @param String [message]
 */
module assertNotEmptyString(value, message) {
    result = isString(value) && value != "";
    message = or(message, "Should not be an empty string");
    details = [value];

    displayAssertResult(result, message, details);
}

/**
 * Displays a title.
 *
 * @private
 * @param String title
 * @param String type
 * @param Boolean [big]
 */
module displayTestTitle(title, type, big) {
    echo(formatTestTitle(title, type, big));
}

/**
 * Displays the number of expected asserts.
 *
 * @private
 * @param Number actual
 * @param Number [expected]
 * @param String [type]
 */
module displayExpectedAsserts(actual, expected, type) {
    echo(formatExpectedAsserts(actual, expected, type));
    if (!checkExpectedAsserts(actual, expected)) {
        color([1, 0.4, 0.2]) {
            cube(15, center=true);
        }
    }
}

/**
 * Displays an assert result.
 *
 * @private
 * @param Boolean result
 * @param String message
 * @param Array [details]
 */
module displayAssertResult(result, message, details) {
    echo(formatAssertResult(result, message, details));
    if (!result) {
        color([1, 0, 0]) {
            sphere(10);
        }
    }
}


feat: add parameter to center the simple test elements

/**
 * @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.
 * @param String|Vector [c] - The color of the element.
 * @param Number|Vector [size] - The size of the element.
 * @param Boolean [center] - Whether or not the element is centered.
 */
module testElement(c="red", size=1, center=false) {
    color(c) {
        cube(size=size, center=center);
    }
}

/**
 * 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.
 * @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
        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 `selectTest`.
 * @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;
    selectTest = is_undef(selectTest) ? undef : min(max(0, selectTest), count - 1);
    start = is_undef(selectTest) ? 0 : selectTest;
    end = is_undef(selectTest) ? count - 1 : selectTest;
    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);
            }
        }
    }
}

// Select a single test to show
selectTest = undef;

/**
 * @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.
 * @param String|Vector [c] - The color of the element.
 * @param Number|Vector [size] - The size of the element.
 */
module testElement(c="red", size=1) {
    color(c) {
        cube(size);
    }
}

/**
 * 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.
 * @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
        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 `selectTest`.
 * @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;
    selectTest = is_undef(selectTest) ? undef : min(max(0, selectTest), count - 1);
    start = is_undef(selectTest) ? 0 : selectTest;
    end = is_undef(selectTest) ? count - 1 : selectTest;
    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);
            }
        }
    }
}

// Select a single test to show
selectTest = undef;