erector set

erector set

echo("turner.scad - small gears for runners and drive of big gear, with nut-d_shaft insert");
echo(".... uses stl import of 'whiskers_small.stl'");

module gearbear() {
	difference(){
		cylinder(r=7.97/2, h=7.1, center=false, $fn=24);
		cylinder(r=1.65, h=7.1, center=false, $fn=12);
		translate([0,0,0+7.1])
			cylinder(r=6.3/2, h=5, center=true, $fn=6);
	}
	rotate([180,0,0])
		difference(){
		translate([0,-47.8,0])
			import("whiskers_small.stl");

		cylinder(r=1.65, h=18, center=true, $fn=12);
		translate([0,0,6])
			cylinder(r=5.6/2, h=2.2, center=true, $fn=16);
		translate([0,0,4.6])
			cylinder(r1=1.5, r2=5.6/2, h=1, center=true, $fn=16);
		}
}

// the little nubby insert for motor shaftage
module d_shaft(hexplugD=6.0, hexplugH=4, shaftrad=1.69, fraction_cut=5){
	difference(){
	cylinder(r=hexplugD/2, h=hexplugH, center=false, $fn=6);
		difference(){
			translate([0, 0, (hexplugH+.1)/2])
				cylinder(r=shaftrad, h=hexplugH+.1, center=true, $fn=16);
			translate([shaftrad - shaftrad / fraction_cut, 0, (hexplugH+.15)/2])
				cube([2*(shaftrad) / fraction_cut, hexplugD*.75, hexplugH+.15], center=true);
		}
	}
}

module armplate(midlen=16){
    difference(){
    linear_extrude(height=3, convexity=10){
        difference(){
            hull(){
                translate([-midlen,0,0])
                    circle(r=6);
                translate([midlen,0,0])
                    circle(r=6, $fn=16);
            }
            for (i=[2:midlen*2/3:midlen*2-1]){
                translate([i - midlen, 0, 0]){
                    circle(r=1.66, $fn=16);
                }
            }

        }
    }
    for (i=[2:midlen*2/3:midlen*2-1]){
        translate([i - midlen, 0, 1])
                cylinder(r=6.3/2, h=2.1, $fn=6);
    }
    }
}

translate([20,0,0]) rotate([0,0,90])
    armplate();

translate([0,0,6])
	gearbear();

translate([-20,0,6])
	gearbear();

translate([-12,15,6])
	gearbear();

translate([2,16,0])
	d_shaft();
translate([6,20,0])
	d_shaft();
translate([10,24,0])
	d_shaft();
translate([14,29,0])
	d_shaft();

echo("turner.scad - small gears for runners and drive of big gear, with nut-d_shaft insert");
echo(".... uses stl import of 'whiskers_small.stl'");

module gearbear() {
	difference(){
		cylinder(r=7.97/2, h=7.1, center=false, $fn=24);
		cylinder(r=1.65, h=7.1, center=false, $fn=12);
		translate([0,0,0+7.1])
			cylinder(r=6.3/2, h=5, center=true, $fn=6);
	}
	rotate([180,0,0])
		difference(){
		translate([0,-47.8,0])
			import("whiskers_small.stl");

		cylinder(r=1.65, h=18, center=true, $fn=12);
		translate([0,0,5.5])
			cylinder(r=5.5/2, h=2.67, center=true, $fn=12);
		}
}

// the little nubby insert for motor shaftage
module d_shaft(hexplugD=6.0, hexplugH=4, shaftrad=1.69, fraction_cut=5){
	difference(){
	cylinder(r=hexplugD/2, h=hexplugH, center=false, $fn=6);
		difference(){
			translate([0, 0, (hexplugH+.1)/2])
				cylinder(r=shaftrad, h=hexplugH+.1, center=true, $fn=16);
			translate([shaftrad - shaftrad / fraction_cut, 0, (hexplugH+.15)/2])
				cube([2*(shaftrad) / fraction_cut, hexplugD*.75, hexplugH+.15], center=true);
		}
	}
}

translate([0,0,6])
	gearbear();

translate([-20,0,6])
	gearbear();

translate([-12,15,6])
	gearbear();

translate([0,15,0])
	d_shaft();


cable guide is now a larger hole

cable guide is now a larger hole

module cableGuide()
{
  hull()
  {
    // upper baund
    translate([0, 20, 0])
      cube([75, 1, 6+7]);
    // lower baund
    cube([60, 1, 6+7]);
  }
}

cableGuide();

module cableGuide()
{
  hull()
  {
    // upper baund
    translate([0, 20, 0])
      cube([75, 1, 6+7]);
    // lower baund
    translate([(75-40)/2, 0, 0])
      cube([40, 1, 6+7]);
  }
}

cableGuide();


Update proMiniClip overhang

Update proMiniClip overhang

module ProMiniClip_STL()
{
	printedPart("printedparts/ProMiniClip.scad", "Pro Mini Clip", "ProMiniClip_STL()") {

		view(t=[0, -1, -1], r=[49, 0, 25], d=336);

		if (DebugCoordinateFrames) frame();
		if (DebugConnectors) connector(Con_Default);

		color(Level2PlasticColor) {
			if (UseSTL) {
				import(str(STLPath, "ProMiniClip.stl"));
			} else {
				ProMiniClipModel();
			}
		}
	}
}

module ProMiniClipModel()
{
	ProMini_Width = 18 + 0.5; // +0.5 tolerance (note this doesn't currently agree with the dimensions in the vitamin)
	ProMini_BoardHeight = 3.5; // PCB thickness including room for solder pads to stick out underneath

	clip = 1.5; // How much the clip protrudes to hold the board

	rotate([90, 0, 0])
	translate([0, 0, -2.5]) {

		pintack(side=true, h=2.25 + 3, bh=0);

		linear_extrude(5)
		translate([0, -(2Perim + ProMini_BoardHeight + 2Perim)/2, 0])
		difference() {
			square([ProMini_Width + 4Perim, 2Perim + ProMini_BoardHeight + 2Perim], center=true);

			translate([0, -(2Perim - 2Perim)/2, 0])
				square([ProMini_Width, ProMini_BoardHeight], center=true);
			translate([0, -(2Perim)/2, 0])
				square([ProMini_Width-clip, ProMini_BoardHeight+2Perim], center=true);
		}
	}
}

module ProMiniClip_STL()
{
	printedPart("printedparts/ProMiniClip.scad", "Pro Mini Clip", "ProMiniClip_STL()") {

		view(t=[0, -1, -1], r=[49, 0, 25], d=336);

		if (DebugCoordinateFrames) frame();
		if (DebugConnectors) connector(Con_Default);

		color(Level2PlasticColor) {
			if (UseSTL) {
				import(str(STLPath, "ProMiniClip.stl"));
			} else {
				ProMiniClipModel();
			}
		}
	}
}

module ProMiniClipModel()
{
	ProMini_Width = 18 + 0.5; // +0.5 tolerance (note this doesn't currently agree with the dimensions in the vitamin)
	ProMini_BoardHeight = 3.5; // PCB thickness including room for solder pads to stick out underneath

	clip = 0.8; // How much the clip protrudes to hold the board

	rotate([90, 0, 0])
	translate([0, 0, -2.5]) {

		pintack(side=true, h=2.25 + 3, bh=0);

		linear_extrude(5)
		translate([0, -(2Perim + ProMini_BoardHeight + 2Perim)/2, 0])
		difference() {
			square([ProMini_Width + 4Perim, 2Perim + ProMini_BoardHeight + 2Perim], center=true);

			translate([0, -(2Perim - 2Perim)/2, 0])
				square([ProMini_Width, ProMini_BoardHeight], center=true);
			translate([0, -(2Perim)/2, 0])
				square([ProMini_Width-clip, ProMini_BoardHeight+2Perim], center=true);
		}
	}
}


Added mock battery and mcu

Added mock battery and mcu

include <moreShapes.scad>;

use <backbone.scad>;
use <hip.scad>;
use <servo.scad>;
include <coxa.scad>;
include <femur.scad>;
include <tibia.scad>;

bb_length=90;
bb_height = 8;

servo_length = 23;
servo_width = 12.5;

h_pos = (bb_length-20)/2;

//tibia2();
//femur();
//mirror([1,0,0]) coxa();

// wheel mode
finalAssembly(0, -130, 130);

// walking mode
translate([0, 300, 28])
	finalAssembly(45, 0, -20);

module finalAssembly(coxa_rot_angle, femur_angle, tibia_angle) {

		// battery - modelled on a zippy 850mah 2s lipo
		color("yellow")
			translate([-28,-15,-20])
			cube([56,30,14]);

		// APM2.5
		color("grey")
			translate([-33,-21,6])
			cube([66, 42, 10]);

		backbone();

		for ( i = [1, -1]){
			translate([0,i*h_pos,(bb_height/2)])
				hip(servo_length,servo_width);

			for (j = [1, -1]) {
				translate([j*22.5,i*h_pos,8]) {
					rotate([0,0,j*-90]) color("blue") servo();

					translate([j*-.5,0,-2.5]) rotate([90,0,i*j*coxa_rot_angle]) {
						scale([j*1,1,i*-1]) coxa();
						translate([j*26.5,6,0]) rotate([0,90 + i*90,0]) {
							color("blue") servo();
							translate([0,0,11]) scale([j*i,1,1]) rotate([0,0,180 + femur_angle]) {
								femur();
								translate([40, 0, -11]) rotate([0, 0, 90 + tibia_angle]) {
									color("blue") servo();
									translate([0, -22, -4]) rotate([0, 0, -90]) tibia2();
								}
							}
						}
					}
				}
			}
		}
}

include <moreShapes.scad>;

use <backbone.scad>;
use <hip.scad>;
use <servo.scad>;
include <coxa.scad>;
include <femur.scad>;
include <tibia.scad>;

bb_length=90;
bb_height = 8;

servo_length = 23;
servo_width = 12.5;

wheel = true;  // true to show in wheel configuration, false for walking config

coxa_rot_ang = wheel ? 0 : 30;
femur_ang = wheel ? -130 : 0;
tibia_ang = wheel ? 130 : -20;

h_pos = (bb_length-20)/2;

//tibia2();
//femur();
//mirror([1,0,0]) coxa();

// wheel mode
finalAssembly(0, -130, 130);

// walking mode
translate([0, 300, 28])
	finalAssembly(30, 0, -20);

module finalAssembly(coxa_rot_angle, femur_angle, tibia_angle) {
		//full assembly example
		backbone();

		for ( i = [1, -1]){
			translate([0,i*h_pos,(bb_height/2)])
				hip(servo_length,servo_width);

			for (j = [1, -1]) {
				translate([j*22.5,i*h_pos,8]) {
					rotate([0,0,j*-90]) color("blue") servo();

					translate([j*-.5,0,-2.5]) rotate([90,0,i*j*coxa_rot_angle]) {
						scale([j*1,1,i*-1]) coxa();
						translate([j*26.5,6,0]) rotate([0,90 + i*90,0]) {
							color("blue") servo();
							translate([0,0,11]) scale([j*i,1,1]) rotate([0,0,180 + femur_angle]) {
								femur();
								translate([40, 0, -11]) rotate([0, 0, 90 + tibia_angle]) {
									color("blue") servo();
									translate([0, -22, -4]) rotate([0, 0, -90]) tibia2();
								}
							}
						}
					}
				}
			}
		}
}


Updated captive nut to give an overlap

Updated captive nut to give an overlap

UP = 0;
DOWN = 180;
LEFT = 90;
RIGHT = 270;
MID = 360;

generate = $generate == undef ? 0: $generate;

if (generate == 1)
{
    // openscad examples.scad -D generate=1 -o /tmp/d.csg 2>&1 >/dev/null  | sed 's/ECHO: \"\[LC\] //' | sed 's/"$//' | sed '$a;' >./generate.scad ; openscad ./generate.scad -o generate.dxf
    // (from http://forum.openscad.org/Sharing-Dump-echo-output-to-a-file-td12529.html)
    echo("[LC] use <lasercut.scad>; \nprojection(cut = false) \n\t");
}

module lasercutoutSquare(thickness=3.1, x=0, y=0,
        simple_tabs=[], simple_tab_holes=[],
        captive_nuts=[], captive_nut_holes = [],
        finger_joints = [],
        circles_add = [],
        circles_remove = [],
        slits = [],
        cutouts = [],
)
{
    points = [[0,0], [x,0], [x,y], [0,y], [0,0]];
lasercutout(thickness=thickness,
        points = points,
        simple_tabs = simple_tabs,
        simple_tab_holes = simple_tab_holes,
        captive_nuts = captive_nuts,
        captive_nut_holes = captive_nut_holes,
        finger_joints = finger_joints,
        circles_add = circles_add,
        circles_remove = circles_remove,
        slits = slits,
        cutouts = cutouts
    );
}

module lasercutout(thickness=3.1,  points= [],
        simple_tabs=[], simple_tab_holes=[],
        captive_nuts=[], captive_nut_holes = [],
        finger_joints = [],
        circles_add = [],
        circles_remove = [],
        slits = [],
        cutouts = [],
)
{
    path_total = len(points);
    path = [ for (p = [0 : 1 : path_total]) p ];

    function max_y(points) = max([for (a = [0:1:len(points)-1])  points[a][1]]);
    function min_y(points) = min([for (a = [0:1:len(points)-1])  points[a][1]]);
    function max_x(points) = max([for (a = [0:1:len(points)-1])  points[a][0]]);
    function min_x(points) = min([for (a = [0:1:len(points)-1])  points[a][0]]);

    max_y = max_y(points);
    min_y = min_y(points);
    max_x = max_x(points);
    min_x = min_x(points);

    difference()
    {
        union()
        {
            linear_extrude(height = thickness , center = false)  polygon(points=points, path=path);
            for (t = [0:1:len(simple_tabs)-1])
            {
                simpleTab(simple_tabs[t][0], simple_tabs[t][1], simple_tabs[t][2], thickness);
            }
            for (t = [0:1:len(captive_nuts)-1])
            {
                captiveNutTab(captive_nuts[t][0], captive_nuts[t][1], captive_nuts[t][2], thickness);
            }
            for (t = [0:1:len(circles_add)-1])
            {
                circlesAdd(circles_add[t][0], circles_add[t][1], circles_add[t][2], thickness);
            }
            for (t = [0:1:len(finger_joints)-1])
            {
                fingerJoint(finger_joints[t][0], finger_joints[t][1], finger_joints[t][2], thickness, max_y, min_y, max_x, min_x) ;
            }

        } // end union

        for (t = [0:1:len(simple_tab_holes)-1])
        {
               simpleTabHole(simple_tab_holes[t][0], simple_tab_holes[t][1], simple_tab_holes[t][2], thickness);
        }
        for (t = [0:1:len(captive_nuts)-1])
        {
            captiveNutBoltHole(captive_nuts[t][0], captive_nuts[t][1], captive_nuts[t][2], thickness);
        }
        for (t = [0:1:len(captive_nut_holes)-1])
        {
            captiveNutHole(captive_nut_holes[t][0], captive_nut_holes[t][1], captive_nut_holes[t][2], thickness);
        }
        for (t = [0:1:len(circles_remove)-1])
        {
            circlesRemove(circles_remove[t][0], circles_remove[t][1], circles_remove[t][2], thickness);
        }
        for (t = [0:1:len(slits)-1])
        {
               simpleSlit(slits[t][0], slits[t][1], slits[t][2], slits[t][3], thickness);
        }
        for (t = [0:1:len(cutouts)-1])
        {
               simpleCutouts(cutouts[t][0], cutouts[t][1], cutouts[t][2], cutouts[t][3], thickness);
        }

    }

    if ($children) translate([0, max_y(points) + thickness*3, 0])
            children();

    if (generate == 1)
    {
        output = str(
        "[LC] lasercutout(thickness=", thickness, ", \n
            points= ", points, ", \n
            simple_tabs = ",simple_tabs,", \n
            simple_tab_holes = ",simple_tab_holes,", \n
            captive_nuts = ",captive_nuts,", \n
            captive_nut_holes = ",captive_nut_holes,", \n
            finger_joints = ",finger_joints,", \n
            circles_add = ",circles_add,", \n
            circles_remove = ",circles_remove,", \n
            slits = ",slits,", \n
            cutouts = ",cutouts,") \n"
        );
        echo(output);
    }
}

module simpleTab(angle, x, y, thickness)
{
    translate([x,y,0]) rotate([0,0,angle]) translate([-thickness/2,0,0]) cube([thickness, thickness, thickness]);
}

module simpleTabHole(angle, x, y, thickness)
{
     // Special case does not go past edge - so make only 1 thickness y
     if (angle == 360)
     {
         translate([x,y,0]) rotate([0,0,0]) translate([0,0,-thickness]) cube([thickness, thickness, thickness*3]);
     }
     else
     {
         translate([x,y,0]) rotate([0,0,angle-180]) translate([-thickness/2,-thickness,-thickness]) cube([thickness, thickness*2, thickness*3]);
     }
}

module captiveNutBoltHole(angle, x, y, thickness)
{
    translate([x,y,0]) rotate([0,0,angle-180]) union()
    {
        translate([-thickness/2,-thickness,-thickness]) cube([thickness, thickness*4, thickness*3]);
        translate([-thickness/2-thickness,thickness,-thickness]) cube([thickness*3, thickness, thickness*3]);
    }
}

module fingerJoint(angle, start_up, fingers, thickness, max_y, min_y, max_x, min_x)
{
    if ( angle == UP )
    {
        range_min = min_x;
        range_max = max_x;
        t_x = min_x;
        t_y = max_y;
        fingers(angle, start_up, fingers, thickness, range_min, range_max, t_x, t_y);
    }
    if ( angle == DOWN )
    {
        range_min = min_x;
        range_max = max_x;
        t_x = max_x;
        t_y = min_y;
        fingers(angle, start_up, fingers, thickness, range_min, range_max, t_x, t_y);
    }
    if ( angle == LEFT )
    {
        range_min = min_y;
        range_max = max_y;
        t_x = min_x;
        t_y = min_y;
        fingers(angle, start_up, fingers, thickness, range_min, range_max, t_x, t_y);
    }
    if ( angle == RIGHT )
    {
        range_min = min_y;
        range_max = max_y;
        t_x = max_x;
        t_y = max_y;
        fingers(angle, start_up, fingers, thickness, range_min, range_max, t_x, t_y);
    }

}

module fingers(angle, start_up, fingers, thickness, range_min, range_max, t_x, t_y)
{
    // The tweaks to y translate([0, -thickness,0]) ... thickness*2 rather than *1
    // Are to avoid edge cases and make the dxf export better.
    // All fun
    translate([t_x, t_y,0]) rotate([0,0,angle]) translate([0, -thickness,0])
    {
        for ( i = [range_min : (range_max-range_min)/fingers : range_max - (range_max-range_min)/fingers] )
        {
            if(start_up == 1)
                {
                    translate([i,0,0]) cube([ (range_max-range_min)/(fingers*2), thickness*2, thickness]);
                }
            else
            {
                    translate([i+(range_max-range_min)/(fingers*2),0,0]) cube([ (range_max-range_min)/(fingers*2), thickness*2, thickness]);

            }
        }
    }

}

module captiveNutTab(angle, x, y, thickness)
{
    translate([x,y,0]) rotate([0,0,angle]) union()
    {
        translate([-thickness/2+thickness*2,-thickness,0]) cube([thickness*3, thickness*2, thickness]);
        translate([-thickness/2-thickness*4,-thickness,0]) cube([thickness*3, thickness*2, thickness]);
    }
}

module captiveNutHole(angle, x, y, thickness)
{
    translate([x,y,0]) rotate([0,0,angle]) union()
    {
        translate([-thickness/2,-thickness*2,-thickness]) cube([thickness, thickness, thickness*3]);
        translate([-thickness/2+thickness*2,-thickness*2,-thickness]) cube([thickness*3, thickness, thickness*3]);
        translate([-thickness/2-thickness*4,-thickness*2,-thickness]) cube([thickness*3, thickness, thickness*3]);
    }
}

module circlesAdd(radius, x, y, thickness)
{
    translate([x,y,0]) cylinder(h=thickness, r=radius);
}

module circlesRemove(radius, x, y, thickness)
{
    translate([x,y,-thickness]) cylinder(h=thickness*3, r=radius);
}

module simpleSlit(angle, x, y, length, thickness)
{
     translate([x,y,0]) rotate([0,0,angle-180]) translate([-thickness/2,-thickness,-thickness]) cube([thickness, length+thickness, thickness*3]);
}

module simpleCutouts(x, y, width, height, thickness)
{
     translate([x,y,0]) rotate([0,0,0]) translate([0,0,-thickness]) cube([width, height, thickness*3]);
}

module lasercutoutBox(thickness = 3.1, x=0, y=0, z=0, sides=6,
        simple_tab_holes_a=[],
        captive_nuts_a=[], captive_nut_holes_a=[],
        circles_add_a=[],
        circles_remove_a=[],
        slits_a = [],
        cutouts_a = [],
)
{
    if (sides==4)
    {
        fj = [
            [ [UP, 1, 4], [DOWN, 1, 4],    ],
            [ [UP, 0, 4], [DOWN, 1, 4],    ],
            [ [UP, 1, 4], [DOWN, 0, 4],    ],
            [ [UP, 1, 4], [DOWN, 1, 4],    ],
        ];
        translate([0,thickness,0]) lasercutoutBoxAdjustedFJ(thickness = thickness, x=x, y=y-thickness*2 , z=z-thickness*2, sides=sides, fj=fj,
            simple_tab_holes_a=simple_tab_holes_a,
            captive_nuts_a=captive_nuts_a, captive_nut_holes_a=captive_nut_holes_a,
            circles_add_a=circles_add_a,
            circles_remove_a=circles_remove_a,
            slits_a=slits_a,
            cutouts_a=cutouts_a);
    }
    if (sides==5)
    {
        st = [
        [[DOWN, -thickness/2, 0], [DOWN, -thickness/2, y-thickness] ],
        [[DOWN, -thickness/2, 0], ],
        [[UP, -thickness/2, z-thickness*2], ],
        ];
        fj = [
            [ [UP, 1, 4], [DOWN, 1, 4], [LEFT, 1, 4],   ],
            [ [UP, 0, 4], [DOWN, 1, 4], [LEFT, 1, 4],   ],
            [ [UP, 1, 4], [DOWN, 0, 4], [LEFT, 1, 4],   ],
            [ [UP, 1, 4], [DOWN, 1, 4], [LEFT, 1, 4],   ],
            [ [UP, 0, 4], [DOWN, 1, 4], [LEFT, 0, 4], [RIGHT, 1, 4]  ],
        ];

        translate([thickness,thickness,0]) lasercutoutBoxAdjustedFJ(thickness = thickness, x=x-thickness, y=y-thickness*2 , z=z-thickness*2, sides=sides, fj=fj, st=st,
            simple_tab_holes_a=simple_tab_holes_a,
            captive_nuts_a=captive_nuts_a, captive_nut_holes_a=captive_nut_holes_a,
            circles_add_a=circles_add_a,
            circles_remove_a=circles_remove_a,
            slits_a=slits_a,
            cutouts_a=cutouts_a);
        }
    if (sides==6)
    {
        st = [
        [[DOWN, -thickness/2, 0], [DOWN, -thickness/2, y-thickness] , [RIGHT, x-thickness*2, -thickness/2]],
        [[DOWN, -thickness/2, 0], [RIGHT, x-thickness*2, -thickness/2]],
        [[UP, -thickness/2, z-thickness*2], [DOWN, x-thickness*1.5, 0], ],
        [[UP, z-thickness*1.5, y-thickness*2], ],
        ];
        fj = [
            [ [UP, 1, 4], [DOWN, 1, 4], [LEFT, 1, 4], [RIGHT, 0, 4]  ],
            [ [UP, 0, 4], [DOWN, 1, 4], [LEFT, 1, 4], [RIGHT, 0, 4]  ],
            [ [UP, 1, 4], [DOWN, 0, 4], [LEFT, 1, 4], [RIGHT, 0, 4]  ],
            [ [UP, 1, 4], [DOWN, 1, 4], [LEFT, 1, 4], [RIGHT, 0, 4]  ],
            [ [UP, 0, 4], [DOWN, 1, 4], [LEFT, 0, 4], [RIGHT, 1, 4]  ],
            [ [UP, 0, 4], [DOWN, 1, 4], [LEFT, 0, 4], [RIGHT, 1, 4]  ],
        ];
        translate([thickness,thickness,0]) lasercutoutBoxAdjustedFJ(thickness = thickness, x=x-thickness*2, y=y-thickness*2 , z=z-thickness*2, sides=sides, fj=fj, st=st,
            simple_tab_holes_a=simple_tab_holes_a,
            captive_nuts_a=captive_nuts_a, captive_nut_holes_a=captive_nut_holes_a,
            circles_add_a=circles_add_a,
            circles_remove_a=circles_remove_a,
            slits_a=slits_a,
            cutouts_a=cutouts_a);
    }
}

module lasercutoutBoxAdjustedFJ(thickness = 3.1, x=0, y=0, z=0, sides=6, fj=[], st=[],
        simple_tab_holes_a=[],
        captive_nuts_a=[], captive_nut_holes_a = [],
        circles_add_a = [],
        circles_remove_a = [],
        slits_a = [],
        cutouts_a = [])
{
    translate([0,0,0]) lasercutoutSquare(x=x, y=y, simple_tabs = st[0], finger_joints = fj[0],
                                simple_tab_holes=simple_tab_holes_a[0], captive_nuts=captive_nuts_a[0],
                                captive_nut_holes = captive_nut_holes_a[0],
                                circles_add = circles_add_a[0], circles_remove = circles_remove_a[0],
                                slits = slits_a[0],
                                cutouts = cutouts_a[0]);

    translate([0,0,z+thickness]) lasercutoutSquare(x=x, y=y, simple_tabs = st[1], finger_joints = fj[1],
                                simple_tab_holes=simple_tab_holes_a[1], captive_nuts=captive_nuts_a[1],
                                captive_nut_holes = captive_nut_holes_a[1],
                                circles_add = circles_add_a[1], circles_remove = circles_remove_a[1],
                                slits = slits_a[1],
                                cutouts = cutouts_a[1]);

    translate([0,0,thickness]) rotate([90,0,0]) lasercutoutSquare(x=x, y=z, finger_joints = fj[2],
                                simple_tab_holes=simple_tab_holes_a[2], captive_nuts=captive_nuts_a[2],
                                captive_nut_holes = captive_nut_holes_a[2],
                                circles_add = circles_add_a[2], circles_remove = circles_remove_a[2],
                                slits = slits_a[2],
                                cutouts = cutouts_a[2]);

    translate([0,y+thickness,thickness]) rotate([90,0,0]) lasercutoutSquare(x=x, y=z, simple_tabs = st[2],
                                finger_joints = fj[3],
                                simple_tab_holes=simple_tab_holes_a[3], captive_nuts=captive_nuts_a[3],
                                captive_nut_holes = captive_nut_holes_a[3],
                                circles_add = circles_add_a[3], circles_remove = circles_remove_a[3],
                                slits = slits_a[3],
                                cutouts = cutouts_a[3]);

    if (sides>4)
    {
        translate([0,0,thickness]) rotate([0,-90,0]) lasercutoutSquare(x=z, y=y, finger_joints = fj[4],
                                simple_tab_holes=simple_tab_holes_a[4], captive_nuts=captive_nuts_a[4],
                                captive_nut_holes = captive_nut_holes_a[4],
                                circles_add = circles_add_a[4], circles_remove = circles_remove_a[4],
                                slits = slits_a[4],
                                cutouts = cutouts_a[4]);
        }

    if (sides>5)
    {
        translate([x+thickness,0,thickness]) rotate([0,-90,0]) lasercutoutSquare(x=z, y=y,
                                simple_tabs = st[3], finger_joints = fj[5],
                                simple_tab_holes=simple_tab_holes_a[5], captive_nuts=captive_nuts_a[5],
                                captive_nut_holes = captive_nut_holes_a[5],
                                circles_add = circles_add_a[5], circles_remove = circles_remove_a[5],
                                slits = slits_a[5],
                                cutouts = cutouts_a[5]);
    }
}

UP = 0;
DOWN = 180;
LEFT = 90;
RIGHT = 270;
MID = 360;

generate = $generate == undef ? 0: $generate;

if (generate == 1)
{
    // openscad examples.scad -D generate=1 -o /tmp/d.csg 2>&1 >/dev/null  | sed 's/ECHO: \"\[LC\] //' | sed 's/"$//' | sed '$a;' >./generate.scad ; openscad ./generate.scad -o generate.dxf
    // (from http://forum.openscad.org/Sharing-Dump-echo-output-to-a-file-td12529.html)
    echo("[LC] use <lasercut.scad>; \nprojection(cut = false) \n\t");
}

module lasercutoutSquare(thickness=3.1, x=0, y=0,
        simple_tabs=[], simple_tab_holes=[],
        captive_nuts=[], captive_nut_holes = [],
        finger_joints = [],
        circles_add = [],
        circles_remove = [],
        slits = [],
        cutouts = [],
)
{
    points = [[0,0], [x,0], [x,y], [0,y], [0,0]];
lasercutout(thickness=thickness,
        points = points,
        simple_tabs = simple_tabs,
        simple_tab_holes = simple_tab_holes,
        captive_nuts = captive_nuts,
        captive_nut_holes = captive_nut_holes,
        finger_joints = finger_joints,
        circles_add = circles_add,
        circles_remove = circles_remove,
        slits = slits,
        cutouts = cutouts
    );
}

module lasercutout(thickness=3.1,  points= [],
        simple_tabs=[], simple_tab_holes=[],
        captive_nuts=[], captive_nut_holes = [],
        finger_joints = [],
        circles_add = [],
        circles_remove = [],
        slits = [],
        cutouts = [],
)
{
    path_total = len(points);
    path = [ for (p = [0 : 1 : path_total]) p ];

    function max_y(points) = max([for (a = [0:1:len(points)-1])  points[a][1]]);
    function min_y(points) = min([for (a = [0:1:len(points)-1])  points[a][1]]);
    function max_x(points) = max([for (a = [0:1:len(points)-1])  points[a][0]]);
    function min_x(points) = min([for (a = [0:1:len(points)-1])  points[a][0]]);

    max_y = max_y(points);
    min_y = min_y(points);
    max_x = max_x(points);
    min_x = min_x(points);

    difference()
    {
        union()
        {
            linear_extrude(height = thickness , center = false)  polygon(points=points, path=path);
            for (t = [0:1:len(simple_tabs)-1])
            {
                simpleTab(simple_tabs[t][0], simple_tabs[t][1], simple_tabs[t][2], thickness);
            }
            for (t = [0:1:len(captive_nuts)-1])
            {
                captiveNutTab(captive_nuts[t][0], captive_nuts[t][1], captive_nuts[t][2], thickness);
            }
            for (t = [0:1:len(circles_add)-1])
            {
                circlesAdd(circles_add[t][0], circles_add[t][1], circles_add[t][2], thickness);
            }
            for (t = [0:1:len(finger_joints)-1])
            {
                fingerJoint(finger_joints[t][0], finger_joints[t][1], finger_joints[t][2], thickness, max_y, min_y, max_x, min_x) ;
            }

        } // end union

        for (t = [0:1:len(simple_tab_holes)-1])
        {
               simpleTabHole(simple_tab_holes[t][0], simple_tab_holes[t][1], simple_tab_holes[t][2], thickness);
        }
        for (t = [0:1:len(captive_nuts)-1])
        {
            captiveNutBoltHole(captive_nuts[t][0], captive_nuts[t][1], captive_nuts[t][2], thickness);
        }
        for (t = [0:1:len(captive_nut_holes)-1])
        {
            captiveNutHole(captive_nut_holes[t][0], captive_nut_holes[t][1], captive_nut_holes[t][2], thickness);
        }
        for (t = [0:1:len(circles_remove)-1])
        {
            circlesRemove(circles_remove[t][0], circles_remove[t][1], circles_remove[t][2], thickness);
        }
        for (t = [0:1:len(slits)-1])
        {
               simpleSlit(slits[t][0], slits[t][1], slits[t][2], slits[t][3], thickness);
        }
        for (t = [0:1:len(cutouts)-1])
        {
               simpleCutouts(cutouts[t][0], cutouts[t][1], cutouts[t][2], cutouts[t][3], thickness);
        }

    }

    if ($children) translate([0, max_y(points) + thickness*3, 0])
            children();

    if (generate == 1)
    {
        output = str(
        "[LC] lasercutout(thickness=", thickness, ", \n
            points= ", points, ", \n
            simple_tabs = ",simple_tabs,", \n
            simple_tab_holes = ",simple_tab_holes,", \n
            captive_nuts = ",captive_nuts,", \n
            captive_nut_holes = ",captive_nut_holes,", \n
            finger_joints = ",finger_joints,", \n
            circles_add = ",circles_add,", \n
            circles_remove = ",circles_remove,", \n
            slits = ",slits,", \n
            cutouts = ",cutouts,") \n"
        );
        echo(output);
    }
}

module simpleTab(angle, x, y, thickness)
{
    translate([x,y,0]) rotate([0,0,angle]) translate([-thickness/2,0,0]) cube([thickness, thickness, thickness]);
}

module simpleTabHole(angle, x, y, thickness)
{
     // Special case does not go past edge - so make only 1 thickness y
     if (angle == 360)
     {
         translate([x,y,0]) rotate([0,0,0]) translate([0,0,-thickness]) cube([thickness, thickness, thickness*3]);
     }
     else
     {
         translate([x,y,0]) rotate([0,0,angle-180]) translate([-thickness/2,-thickness,-thickness]) cube([thickness, thickness*2, thickness*3]);
     }
}

module captiveNutBoltHole(angle, x, y, thickness)
{
    translate([x,y,0]) rotate([0,0,angle-180]) union()
    {
        translate([-thickness/2,-thickness,-thickness]) cube([thickness, thickness*4, thickness*3]);
        translate([-thickness/2-thickness,thickness,-thickness]) cube([thickness*3, thickness, thickness*3]);
    }
}

module fingerJoint(angle, start_up, fingers, thickness, max_y, min_y, max_x, min_x)
{
    if ( angle == UP )
    {
        range_min = min_x;
        range_max = max_x;
        t_x = min_x;
        t_y = max_y;
        fingers(angle, start_up, fingers, thickness, range_min, range_max, t_x, t_y);
    }
    if ( angle == DOWN )
    {
        range_min = min_x;
        range_max = max_x;
        t_x = max_x;
        t_y = min_y;
        fingers(angle, start_up, fingers, thickness, range_min, range_max, t_x, t_y);
    }
    if ( angle == LEFT )
    {
        range_min = min_y;
        range_max = max_y;
        t_x = min_x;
        t_y = min_y;
        fingers(angle, start_up, fingers, thickness, range_min, range_max, t_x, t_y);
    }
    if ( angle == RIGHT )
    {
        range_min = min_y;
        range_max = max_y;
        t_x = max_x;
        t_y = max_y;
        fingers(angle, start_up, fingers, thickness, range_min, range_max, t_x, t_y);
    }

}

module fingers(angle, start_up, fingers, thickness, range_min, range_max, t_x, t_y)
{
    // The tweaks to y translate([0, -thickness,0]) ... thickness*2 rather than *1
    // Are to avoid edge cases and make the dxf export better.
    // All fun
    translate([t_x, t_y,0]) rotate([0,0,angle]) translate([0, -thickness,0])
    {
        for ( i = [range_min : (range_max-range_min)/fingers : range_max - (range_max-range_min)/fingers] )
        {
            if(start_up == 1)
                {
                    translate([i,0,0]) cube([ (range_max-range_min)/(fingers*2), thickness*2, thickness]);
                }
            else
            {
                    translate([i+(range_max-range_min)/(fingers*2),0,0]) cube([ (range_max-range_min)/(fingers*2), thickness*2, thickness]);

            }
        }
    }

}

module captiveNutTab(angle, x, y, thickness)
{
    translate([x,y,0]) rotate([0,0,angle]) union()
    {
        translate([-thickness/2+thickness*2,0,0]) cube([thickness*3, thickness, thickness]);
        translate([-thickness/2-thickness*4,0,0]) cube([thickness*3, thickness, thickness]);
    }
}

module captiveNutHole(angle, x, y, thickness)
{
    translate([x,y,0]) rotate([0,0,angle]) union()
    {
        translate([-thickness/2,-thickness*2,-thickness]) cube([thickness, thickness, thickness*3]);
        translate([-thickness/2+thickness*2,-thickness*2,-thickness]) cube([thickness*3, thickness, thickness*3]);
        translate([-thickness/2-thickness*4,-thickness*2,-thickness]) cube([thickness*3, thickness, thickness*3]);
    }
}

module circlesAdd(radius, x, y, thickness)
{
    translate([x,y,0]) cylinder(h=thickness, r=radius);
}

module circlesRemove(radius, x, y, thickness)
{
    translate([x,y,-thickness]) cylinder(h=thickness*3, r=radius);
}

module simpleSlit(angle, x, y, length, thickness)
{
     translate([x,y,0]) rotate([0,0,angle-180]) translate([-thickness/2,-thickness,-thickness]) cube([thickness, length+thickness, thickness*3]);
}

module simpleCutouts(x, y, width, height, thickness)
{
     translate([x,y,0]) rotate([0,0,0]) translate([0,0,-thickness]) cube([width, height, thickness*3]);
}

module lasercutoutBox(thickness = 3.1, x=0, y=0, z=0, sides=6,
        simple_tab_holes_a=[],
        captive_nuts_a=[], captive_nut_holes_a=[],
        circles_add_a=[],
        circles_remove_a=[],
        slits_a = [],
        cutouts_a = [],
)
{
    if (sides==4)
    {
        fj = [
            [ [UP, 1, 4], [DOWN, 1, 4],    ],
            [ [UP, 0, 4], [DOWN, 1, 4],    ],
            [ [UP, 1, 4], [DOWN, 0, 4],    ],
            [ [UP, 1, 4], [DOWN, 1, 4],    ],
        ];
        translate([0,thickness,0]) lasercutoutBoxAdjustedFJ(thickness = thickness, x=x, y=y-thickness*2 , z=z-thickness*2, sides=sides, fj=fj,
            simple_tab_holes_a=simple_tab_holes_a,
            captive_nuts_a=captive_nuts_a, captive_nut_holes_a=captive_nut_holes_a,
            circles_add_a=circles_add_a,
            circles_remove_a=circles_remove_a,
            slits_a=slits_a,
            cutouts_a=cutouts_a);
    }
    if (sides==5)
    {
        st = [
        [[DOWN, -thickness/2, 0], [DOWN, -thickness/2, y-thickness] ],
        [[DOWN, -thickness/2, 0], ],
        [[UP, -thickness/2, z-thickness*2], ],
        ];
        fj = [
            [ [UP, 1, 4], [DOWN, 1, 4], [LEFT, 1, 4],   ],
            [ [UP, 0, 4], [DOWN, 1, 4], [LEFT, 1, 4],   ],
            [ [UP, 1, 4], [DOWN, 0, 4], [LEFT, 1, 4],   ],
            [ [UP, 1, 4], [DOWN, 1, 4], [LEFT, 1, 4],   ],
            [ [UP, 0, 4], [DOWN, 1, 4], [LEFT, 0, 4], [RIGHT, 1, 4]  ],
        ];

        translate([thickness,thickness,0]) lasercutoutBoxAdjustedFJ(thickness = thickness, x=x-thickness, y=y-thickness*2 , z=z-thickness*2, sides=sides, fj=fj, st=st,
            simple_tab_holes_a=simple_tab_holes_a,
            captive_nuts_a=captive_nuts_a, captive_nut_holes_a=captive_nut_holes_a,
            circles_add_a=circles_add_a,
            circles_remove_a=circles_remove_a,
            slits_a=slits_a,
            cutouts_a=cutouts_a);
        }
    if (sides==6)
    {
        st = [
        [[DOWN, -thickness/2, 0], [DOWN, -thickness/2, y-thickness] , [RIGHT, x-thickness*2, -thickness/2]],
        [[DOWN, -thickness/2, 0], [RIGHT, x-thickness*2, -thickness/2]],
        [[UP, -thickness/2, z-thickness*2], [DOWN, x-thickness*1.5, 0], ],
        [[UP, z-thickness*1.5, y-thickness*2], ],
        ];
        fj = [
            [ [UP, 1, 4], [DOWN, 1, 4], [LEFT, 1, 4], [RIGHT, 0, 4]  ],
            [ [UP, 0, 4], [DOWN, 1, 4], [LEFT, 1, 4], [RIGHT, 0, 4]  ],
            [ [UP, 1, 4], [DOWN, 0, 4], [LEFT, 1, 4], [RIGHT, 0, 4]  ],
            [ [UP, 1, 4], [DOWN, 1, 4], [LEFT, 1, 4], [RIGHT, 0, 4]  ],
            [ [UP, 0, 4], [DOWN, 1, 4], [LEFT, 0, 4], [RIGHT, 1, 4]  ],
            [ [UP, 0, 4], [DOWN, 1, 4], [LEFT, 0, 4], [RIGHT, 1, 4]  ],
        ];
        translate([thickness,thickness,0]) lasercutoutBoxAdjustedFJ(thickness = thickness, x=x-thickness*2, y=y-thickness*2 , z=z-thickness*2, sides=sides, fj=fj, st=st,
            simple_tab_holes_a=simple_tab_holes_a,
            captive_nuts_a=captive_nuts_a, captive_nut_holes_a=captive_nut_holes_a,
            circles_add_a=circles_add_a,
            circles_remove_a=circles_remove_a,
            slits_a=slits_a,
            cutouts_a=cutouts_a);
    }
}

module lasercutoutBoxAdjustedFJ(thickness = 3.1, x=0, y=0, z=0, sides=6, fj=[], st=[],
        simple_tab_holes_a=[],
        captive_nuts_a=[], captive_nut_holes_a = [],
        circles_add_a = [],
        circles_remove_a = [],
        slits_a = [],
        cutouts_a = [])
{
    translate([0,0,0]) lasercutoutSquare(x=x, y=y, simple_tabs = st[0], finger_joints = fj[0],
                                simple_tab_holes=simple_tab_holes_a[0], captive_nuts=captive_nuts_a[0],
                                captive_nut_holes = captive_nut_holes_a[0],
                                circles_add = circles_add_a[0], circles_remove = circles_remove_a[0],
                                slits = slits_a[0],
                                cutouts = cutouts_a[0]);

    translate([0,0,z+thickness]) lasercutoutSquare(x=x, y=y, simple_tabs = st[1], finger_joints = fj[1],
                                simple_tab_holes=simple_tab_holes_a[1], captive_nuts=captive_nuts_a[1],
                                captive_nut_holes = captive_nut_holes_a[1],
                                circles_add = circles_add_a[1], circles_remove = circles_remove_a[1],
                                slits = slits_a[1],
                                cutouts = cutouts_a[1]);

    translate([0,0,thickness]) rotate([90,0,0]) lasercutoutSquare(x=x, y=z, finger_joints = fj[2],
                                simple_tab_holes=simple_tab_holes_a[2], captive_nuts=captive_nuts_a[2],
                                captive_nut_holes = captive_nut_holes_a[2],
                                circles_add = circles_add_a[2], circles_remove = circles_remove_a[2],
                                slits = slits_a[2],
                                cutouts = cutouts_a[2]);

    translate([0,y+thickness,thickness]) rotate([90,0,0]) lasercutoutSquare(x=x, y=z, simple_tabs = st[2],
                                finger_joints = fj[3],
                                simple_tab_holes=simple_tab_holes_a[3], captive_nuts=captive_nuts_a[3],
                                captive_nut_holes = captive_nut_holes_a[3],
                                circles_add = circles_add_a[3], circles_remove = circles_remove_a[3],
                                slits = slits_a[3],
                                cutouts = cutouts_a[3]);

    if (sides>4)
    {
        translate([0,0,thickness]) rotate([0,-90,0]) lasercutoutSquare(x=z, y=y, finger_joints = fj[4],
                                simple_tab_holes=simple_tab_holes_a[4], captive_nuts=captive_nuts_a[4],
                                captive_nut_holes = captive_nut_holes_a[4],
                                circles_add = circles_add_a[4], circles_remove = circles_remove_a[4],
                                slits = slits_a[4],
                                cutouts = cutouts_a[4]);
        }

    if (sides>5)
    {
        translate([x+thickness,0,thickness]) rotate([0,-90,0]) lasercutoutSquare(x=z, y=y,
                                simple_tabs = st[3], finger_joints = fj[5],
                                simple_tab_holes=simple_tab_holes_a[5], captive_nuts=captive_nuts_a[5],
                                captive_nut_holes = captive_nut_holes_a[5],
                                circles_add = circles_add_a[5], circles_remove = circles_remove_a[5],
                                slits = slits_a[5],
                                cutouts = cutouts_a[5]);
    }
}


SD card reader and reset button

SD card reader and reset button

use <lcd.scad>
use <menu_knob.scad>
use <menu_knob_manipulator.scad>
use <reset_button.scad>
use <sdcard_reader.scad>

boxSize = [143, 148, 6+7];
lcdOffset = boxSize - [98+5, 60+5, 12];
encoderOffset = [20-14/2, boxSize[1]-30/2-15/2-3, boxSize[2]-(6+7)];
sdCardOffset = [35, 80, boxSize[2]-6];
resetButtonOffset = [encoderOffset[0]+14/2, 68, 1];

//rotate([180, 0, 0])
{
  difference()
  {
    // main working area
    cube(boxSize);
    // prusa's framework  corner
    translate([15, 0, 0])
      cube([143-15, 53, boxSize[2]]);
    // LCD
    translate(lcdOffset)
      lcdBoard();
    // encoder
    translate(encoderOffset)
      menuKnob();
    // SD card mount
    translate(sdCardOffset)
      rotate([0,180,0])
        sdCardReader();
    // reset button
    translate(resetButtonOffset)
      resetButton();
  }

  // main mounting legs
  for(offset = [ [boxSize[0]-16, 0, boxSize[2]-7], [boxSize[0]-70, 0, boxSize[2]-7] ])
    translate(offset)
      cube([16, 40+13, 7]);
  // side mounting wing
  translate([15, 0, 6])
    cube([18, 20, 7]);

  // LCD stub
  %translate(lcdOffset)
    lcdBoard();
  // encoder stub
  %translate(encoderOffset)
  {
    menuKnob();
    translate([7, 6, 23+4])
      rotate([180,0,0])
        menuKnobManipulator();
  }
  // SD card mount stub
  %translate(sdCardOffset)
    rotate([0,180,0])
      sdCardReader();
  // reset button stub
  %translate(resetButtonOffset)
    resetButton();
}

use <lcd.scad>
use <menu_knob.scad>
use <menu_knob_manipulator.scad>
use <reset_button.scad>
use <sdcard_reader.scad>

boxSize = [143, 148, 6+7];
lcdOffset = boxSize - [98+5, 60+5, 12];
encoderOffset = [20-14/2, boxSize[1]-30/2-15/2, boxSize[2]-(6+7)];

//rotate([180, 0, 0])
{
  difference()
  {
    // main working area
    cube(boxSize);
    // prusa's framework  corner
    translate([15+5, 0, 0])
      cube([143-(15+5), 53, 6]);
    // LCD
    translate(lcdOffset)
      lcdBoard();
    // encoder
    translate(encoderOffset)
      menuKnob();
  }

  // LCD stub
  %translate(lcdOffset)
    lcdBoard();
  // encoder stub
  %translate(encoderOffset)
  {
    menuKnob();
    translate([7, 6, 23+4])
      rotate([180,0,0])
        menuKnobManipulator();
  }
}


add ankle bracelet

add ankle bracelet

// measures are in mm
// copied and adapted from the blueprints of CS:R from R2 Builders Club

h = 2.5;

module triangle(l, w, h) {
	CubePoints = [
		[0, 0, 0], // 0
		[l, 0, 0], // 1
		[l, w, 0], // 2
		[0, 0, h], // 3
		[l, 0, h], // 4
		[l, w, h], // 5
	];

	CubeFaces = [
		[0, 1, 2], // bottom
		[0, 3, 4, 1], // front
		[5, 4, 3], // top
		[1, 4, 5, 2], // right
		[2, 5, 3, 0], // back, left
	]; // left

	polyhedron(CubePoints, CubeFaces);
}

module the_triangle() {
	translate([-64.5/2, 4.3+35+17.7-0.02, 0])
	triangle(64.5/2-8.7/2, 7, h);
}

module outer() {
	intersection() {
		translate([-107.5/2,0,0])
		cube(size=[107.5,76.5,h]);

		cylinder(h, 77, 77);
	}
}

module inner() {
	translate([0, 62, 0])
	cylinder(h, 8.7, 8.7);

	translate([-87/2, 4.3, 0])
	cube(size=[87, 35, h]);

	translate([-64.5/2, 4.3+35-0.01, 0])
	cube(size=[64.5, 17.7, h]);

	the_triangle();
	mirror([1, 0, 0])
	the_triangle();
}

module all() {
	difference() {
		outer();

		translate([0, 0, -h/2])
		scale([1, 1, 2])
		inner();
	}
}

all();

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


The variables were prepared a little for Thingiverse Customizer.

// preview[view:south, tilt:top]

/**
 * This script renders onebeartoe Fidget Spinners!
 *
 * Author: Roberto Marquez
 *
 * https://plus.google.com/u/0/104101484714566033177
 * https://twitter.com/onebeartoe
 * https://www.youtube.com/user/onebeartoe/videos
 *
 *  Originally from: http://www.thingiverse.com/thing:1937473
 *  Original Author: Geoff Buttsworth
 *  Original Filename: moneySpinner02
 *  Borrowed from Version: 0.5
 *  Twitter: @gbutts9
 *
 *  This version is designed to work with the Thingiverse Customizer.
 *  Ref: http://customizer.makerbot.com/docs
 */

use <../../external-resources/adafruit/fidget-spinner/ada-spinner-flat.scad>
use <../../external-resources/aquaman/aquaman-logo.scad>
use <../../external-resources/batman/batman.scad>
use <../../external-resources/time-bandits/fidget-outline.scad>
use <../../external-resources/knurled-surface/aubenc/knurledFinishLib_v2.scad>
use <../../external-resources/oshw/oshw-logo-800-px.scad>;
use <../../external-resources/rebel-alliance/rebel-alliance.scad>
use <../../external-resources/star-trek/logo/star-trek-logo.scad>
use <../../shapes/clover/clover.scad>
use <../../shapes/heart/heart.scad>
use <../../shapes/pacman/pacman.scad>
use <../../shapes/spurs/spurs-a.scad>
use <../../shapes/star/star.scad>;

/* [Spinner_Parameters] */

cutoutName = "Star";    // [Adafruit, Aqua Dude, Bat, Clover, Fidget (Time Bandits), Heart, Pacman, OSHW, Rebel Alliance, Spur, Star, Star Trek]

cutoutHolderType = "Knurl"; // [Cylinder, Knurl]

//How many spokes should the spinner have?
Number_of_Spokes = 3; //[2,3,4,5,6,7]
spokeNumber = Number_of_Spokes;

/* [Hidden] */

//Which coin do you want to use?
coin_list = 8; //[1:AU $2,2:AU 5c,3:US 10c,4:UK £1,5:UK 5p,6:EU €1,7:QA 0.5QAR,8:US 5c]
coin = coin_list;

//Flush for easier printing
Flush_or_Raised_Hub = 1; //[0:Hub,1:Flush]
flush = Flush_or_Raised_Hub; //Flush is easier to print, the hub is height of bearing and may print with support

//Allocate coin data

// !!!!!to make the spoke count this is needed, so far.  or maybe not!!!!
//coinName = coin == 1 ? "AU $2" :
//        (coin == 2 ? "AU 5c" :
//        (coin == 3 ? "US 10c" :
//        (coin == 4 ? "UK £1" :
//        (coin == 5 ? "UK 5p" :
//        (coin == 6 ? "EU €1" :
//        (coin == 7 ? "QA 0.5QAR" : "US 5c")
//        )
//        )
//        )
//        )
//        );

coin_d = coin == 1 ? 20.5 :
            (coin == 2 ? 19.4 :
                (coin == 3 ? 17.9 :
                    (coin == 4 ? 22.5 :
                        (coin == 5 ? 18.0 :
                            (coin == 6 ? 23.3 :
                                (coin == 7 ? 25.0 : 21.3)
                                                            )
                                                            )
                                                            )
                                                            )
                                                            );

coin_z = coin == 1 ? 2.8 :
        (coin == 2 ? 1.3 :
        (coin == 3 ? 1.35 :
        (coin == 4 ? 3.15 :
        (coin == 5 ? 1.8 :
        (coin == 6 ? 2.35 :
        (coin == 7 ? 1.78 : 1.95)
        )
        )
        )
        )
        );

stack = coin == 2 ? 3 :
        (coin == 3 ? 3 :
        (coin == 5 ? 3 :
        (coin == 7 ? 3 :
        (coin == 8 ? 4 : 2)
        )
        )
        );

//Default values
$fa = 0.8;
$fs = 0.8;

//Variables:

rim = 5;
holder_d = coin_d + rim;
holder_z = coin_z*stack;

brg_d = 22.0;
brg_z = 7.0;

spoke_x = coin_d / 2;
spoke_y = coin_d / 3;
spoke_z = holder_z;

//If flush: hub will be height of coin stack otherwise height of bearing
hub_z = flush == 1 ? coin_z*stack : brg_z;

//SpinnerModules

module cutout()
{
    if(cutoutName == "Adafruit")
    {
        scale([0.3, 0.3, 5])
        AdaSpinnerFlat_001();
    }
    if(cutoutName == "Aqua Dude")
    {
        aquamanThumbnail(height=10);
    }
    else if(cutoutName == "Bat")
    {
        translate([0, 0, 4])
        scale([1, 1, 4])
        batmanLogoThumbnail();
    }
    else if(cutoutName == "Clover")
    {
        translate([0, 0, -1])
        cloverThumbnail(height=10);
    }
    else if(cutoutName == "Fidget (Time Bandits)")
    {
        fidgetOutlineThumbnail(height = 10);
    }
    else if(cutoutName == "Heart")
    {
        heartThumbnail(height=10);
    }
    else if(cutoutName == "Rebel Alliance")
    {
        rebelAllianceThumbnail(height=10);
    }
    else if(cutoutName == "OSHW")
    {
        translate(0,0,10)
        scale([1, 1, 17])
        oshwLogoThumbnail();
    }
    else if(cutoutName == "Pacman")
    {
        translate([0, 0, 8])
        pacmanThumbnail(height = 15);
    }
    else if(cutoutName == "Spur")
    {
        scale([0.17, 0.17, 1])
        spur(height = 10);
    }
    else if(cutoutName == "Star")
    {
    	starThumbnail(height = 10);
    }
    else if(cutoutName == "Star Trek")
    {
        xyScale = 0.35;

        scale([xyScale, xyScale, 1])
        Star_Trek_Logo_Blank_fixed();
    }
}

module cutoutHolder()
{
    if(cutoutHolderType == "Cylinder")
    {
        cylinder(h=holder_z, d=holder_d);
    }
    else if( cutoutHolderType == "Knurl")
    {
        knurl(k_cyl_hg = holder_z, k_cyl_od = holder_d);
    }
    else
    {
        // Rounded Cylinder
    }
}

module dollarHolder()
{
    translate([0, coin_d / 2 + spoke_y, 0])
    difference()
    {
        // TODO: this next item looks like where the knurl can be swapped out for
        //       a regular cylinder or rounded cylinder.
        translate([0, 0, -coin_z * stack / 2])
        cutoutHolder();

        xyScale = 0.75;
        translate([0, 0, -5])
        scale([xyScale, xyScale, 1])
        cutout();
    }
}

module hub() {
    cylinder(h = hub_z, d = brg_d + rim, center = true);
}

module spoke() {
    translate([0, spoke_y / 2, 0])
    cube([spoke_x, spoke_y, spoke_z], center = true);
}

module brgVoid() {
    cylinder(h = brg_z + 2, d = brg_d, center = true);
}

module holderSpoke() {

    translate([0, brg_d / 2, 0])
            union() {
        dollarHolder();
        spoke();
    }
}

//Rotate
rA = 360 / spokeNumber; //rotational angle
fA = round(360 - rA); //final angle

module spinner() {
    for (i = [0 : rA : fA]) {
        rotate([0, 0, i])
        holderSpoke();
    }
}

//Finalize

module plus()
{
    hub();
    spinner();
}

module minus()
{
    brgVoid();
}

//Render

difference()
{
    plus();
    minus();
}

/**
 * This script renders onebeartoe Fidget Spinners!
 *
 * Author: Roberto Marquez
 *
 * https://plus.google.com/u/0/104101484714566033177
 * https://twitter.com/onebeartoe
 * https://www.youtube.com/user/onebeartoe/videos
 *
 *  Originally from: http://www.thingiverse.com/thing:1937473
 *  Original Author: Geoff Buttsworth
 *  Original Filename: moneySpinner02
 *  Borrowed from Version: 0.5
 *  Twitter: @gbutts9
 *
 *  This version is designed to work with the Thingiverse Customizer.
 *  Ref: http://customizer.makerbot.com/docs
 */

use <../../external-resources/adafruit/fidget-spinner/ada-spinner-flat.scad>
use <../../external-resources/aquaman/aquaman-logo.scad>
use <../../external-resources/batman/batman.scad>
use <../../external-resources/time-bandits/fidget-outline.scad>
use <../../external-resources/knurled-surface/aubenc/knurledFinishLib_v2.scad>
use <../../external-resources/oshw/oshw-logo-800-px.scad>;
use <../../external-resources/rebel-alliance/rebel-alliance.scad>
use <../../external-resources/star-trek/logo/star-trek-logo.scad>
use <../../shapes/clover/clover.scad>
use <../../shapes/heart/heart.scad>
use <../../shapes/pacman/pacman.scad>
use <../../shapes/spurs/spurs-a.scad>
use <../../shapes/star/star.scad>;

/* [Spinner_Parameters] */
//Which coin do you want to use?
coin_list = 8; //[1:AU $2,2:AU 5c,3:US 10c,4:UK £1,5:UK 5p,6:EU €1,7:QA 0.5QAR,8:US 5c]
coin = coin_list;

cutoutName = "Star";    // [Adafruit, Aqua Dude, Bat, Clover, Fidget (Time Bandits), Heart, Pacman, OSHW, Rebel Alliance, Spur, Star, Star Trek]

cutoutHolderType = "Knurl"; // [Cylinder, Knurl]

//How many spokes should the spinner have?
Number_of_Spokes = 3; //[2,3,4,5,6,7]
spokeNumber = Number_of_Spokes;

//Flush for easier printing
Flush_or_Raised_Hub = 1; //[0:Hub,1:Flush]
flush = Flush_or_Raised_Hub; //Flush is easier to print, the hub is height of bearing and may print with support

//Allocate coin data

// !!!!!to make the spoke count this is needed, so far.  or maybe not!!!!
//coinName = coin == 1 ? "AU $2" :
//        (coin == 2 ? "AU 5c" :
//        (coin == 3 ? "US 10c" :
//        (coin == 4 ? "UK £1" :
//        (coin == 5 ? "UK 5p" :
//        (coin == 6 ? "EU €1" :
//        (coin == 7 ? "QA 0.5QAR" : "US 5c")
//        )
//        )
//        )
//        )
//        );

coin_d = coin == 1 ? 20.5 :
            (coin == 2 ? 19.4 :
                (coin == 3 ? 17.9 :
                    (coin == 4 ? 22.5 :
                        (coin == 5 ? 18.0 :
                            (coin == 6 ? 23.3 :
                                (coin == 7 ? 25.0 : 21.3)
                                                            )
                                                            )
                                                            )
                                                            )
                                                            );

coin_z = coin == 1 ? 2.8 :
        (coin == 2 ? 1.3 :
        (coin == 3 ? 1.35 :
        (coin == 4 ? 3.15 :
        (coin == 5 ? 1.8 :
        (coin == 6 ? 2.35 :
        (coin == 7 ? 1.78 : 1.95)
        )
        )
        )
        )
        );

stack = coin == 2 ? 3 :
        (coin == 3 ? 3 :
        (coin == 5 ? 3 :
        (coin == 7 ? 3 :
        (coin == 8 ? 4 : 2)
        )
        )
        );

//Default values
$fa = 0.8;
$fs = 0.8;

//Variables:

rim = 5;
holder_d = coin_d + rim;
holder_z = coin_z*stack;

brg_d = 22.0;
brg_z = 7.0;

spoke_x = coin_d / 2;
spoke_y = coin_d / 3;
spoke_z = holder_z;

//If flush: hub will be height of coin stack otherwise height of bearing
hub_z = flush == 1 ? coin_z*stack : brg_z;

//SpinnerModules

module cutout()
{
    if(cutoutName == "Adafruit")
    {
        scale([0.3, 0.3, 5])
        AdaSpinnerFlat_001();
    }
    if(cutoutName == "Aqua Dude")
    {
        aquamanThumbnail(height=10);
    }
    else if(cutoutName == "Bat")
    {
        translate([0, 0, 4])
        scale([1, 1, 4])
        batmanLogoThumbnail();
    }
    else if(cutoutName == "Clover")
    {
        translate([0, 0, -1])
        cloverThumbnail(height=10);
    }
    else if(cutoutName == "Fidget (Time Bandits)")
    {
        fidgetOutlineThumbnail(height = 10);
    }
    else if(cutoutName == "Heart")
    {
        heartThumbnail(height=10);
    }
    else if(cutoutName == "Rebel Alliance")
    {
        rebelAllianceThumbnail(height=10);
    }
    else if(cutoutName == "OSHW")
    {
        translate(0,0,10)
        scale([1, 1, 17])
        oshwLogoThumbnail();
    }
    else if(cutoutName == "Pacman")
    {
        translate([0, 0, 8])
        pacmanThumbnail(height = 15);
    }
    else if(cutoutName == "Spur")
    {
        scale([0.17, 0.17, 1])
        spur(height = 10);
    }
    else if(cutoutName == "Star")
    {
    	starThumbnail(height = 10);
    }
    else if(cutoutName == "Star Trek")
    {
        xyScale = 0.35;

        scale([xyScale, xyScale, 1])
        Star_Trek_Logo_Blank_fixed();
    }
}

module cutoutHolder()
{
    if(cutoutHolderType == "Cylinder")
    {
        cylinder(h=holder_z, d=holder_d);
    }
    else if( cutoutHolderType == "Knurl")
    {
        knurl(k_cyl_hg = holder_z, k_cyl_od = holder_d);
    }
    else
    {
        // Rounded Cylinder
    }
}

module dollarHolder()
{
    translate([0, coin_d / 2 + spoke_y, 0])
    difference()
    {
        // TODO: this next item looks like where the knurl can be swapped out for
        //       a regular cylinder or rounded cylinder.
        translate([0, 0, -coin_z * stack / 2])
        cutoutHolder();

        xyScale = 0.75;
        translate([0, 0, -5])
        scale([xyScale, xyScale, 1])
        cutout();
    }
}

module hub() {
    cylinder(h = hub_z, d = brg_d + rim, center = true);
}

module spoke() {
    translate([0, spoke_y / 2, 0])
    cube([spoke_x, spoke_y, spoke_z], center = true);
}

module brgVoid() {
    cylinder(h = brg_z + 2, d = brg_d, center = true);
}

module holderSpoke() {

    translate([0, brg_d / 2, 0])
            union() {
        dollarHolder();
        spoke();
    }
}

//Rotate
rA = 360 / spokeNumber; //rotational angle
fA = round(360 - rA); //final angle

module spinner() {
    for (i = [0 : rA : fA]) {
        rotate([0, 0, i])
        holderSpoke();
    }
}

//Finalize

module plus()
{
    hub();
    spinner();
}

module minus()
{
    brgVoid();
}

//Render

difference()
{
    plus();
    minus();
}


Version 2 of the GJB marker designed by Keya

This one is a complete rectangular hole shape, not a c shape

use <../common/extrusions.scad>
LARGE = 100;

module marker_thing() {
    w = 4.5*25.4;
    thick = 3;
    d = 2*25.4;
    h = 2*25.4;
    ri =  3;
    ro = 1;

     O_channel(
    w+2*thick, h+thick,
    w, LARGE,
    thick, thick, ro, ri, d);
    //cube([w, d, h]);
}

module marker_thing2() {
    w = 4*25.4;
    thick = 3;
    d = 2*25.4;
    h = 4*25.4;
    ri =  3;
    ro = 1;

     O_channel(
    w+2*thick, h+2*thick,
    w, h,
    thick, thick, ro, ri, d);
    //cube([w, d, h]);
}

marker_thing2();

use <../common/extrusions.scad>
LARGE = 100;

module marker_thing() {
    w = 4.5*25.4;
    thick = 3;
    d = 2*25.4;
    h = 2*25.4;
    ri =  3;
    ro = 1;

     O_channel(
    w+2*thick, h+thick,
    w, LARGE,
    thick, thick, ro, ri, d);
    //cube([w, d, h]);
}

marker_thing();


misc: remove assert (now supported by openscad!)

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

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

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

function vec_add(vec,v) = [for(vv=vec) vv+v];

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

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

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

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

function v_abs(v, start=0) = [for(i=[start:1:len(v)-1]) abs(v[i])];
function v_sign(v, start=0) = [for(i=[start:1:len(v)-1]) sign(v[i])];
function v_max(v, m, start=0) = [for(i=[start:1:len(v)-1]) max(v[i],m)];
function v_min(v, m, start=0) = [for(i=[start:1:len(v)-1]) min(v[i],m)];
function v_clamp(v, v1, v2, start=0) = [for(i=[start:1:len(v)-1]) clamp(v[i],v1,v2)];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// echo(fallback(undef,1));
function fallback(a, b) = a==undef?b:a;

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

// Called when an assertion fails (invokes infinite recursion).
function assertion_failed() = (assertion_failed());

// Assert that `bool` is true.
//
// If `bool` is false, emit the error `msg`,
//  and terminate compilation (via infinite recursion).
module assert(bool, msg = "")
{
    if(bool == false)
    {
        echo("Assertion Failed: ", msg);
        echo("", assertion_failed());
    }
}


spoke prototype complete

spoke prototype complete

$fn = 64;

// interference fit adjustment for 3D printer
iFitAdjust = .4;
iFitAdjust_d = .25;

// latch buttons
latchButtonDiameter = 13;
latchButtonHeight = 30;
module latch_button(color="green") {
    color(color)
        cylinder(h = latchButtonHeight, d = latchButtonDiameter, center = false);
}

// led momentary buttons
ledButtonDiameter = 24;
ledButtonHeight = 33;
module momentary_button(color="white") {
    color(color)
        cylinder(h = ledButtonHeight, d = ledButtonDiameter, center = false);
}

// encoders
encoderShaftDiameter = 7;
encoderShaftHeight = 18;
encoderWidth = 18;
encoderLength = 31;
encoderDepth = 8.2;
module encoder() {
    // encoder shaft
    cylinder(h = encoderShaftHeight, d = encoderShaftDiameter, center=false);
    // encoder base
    translate([-encoderWidth/2, -encoderLength/2, -encoderDepth/2])
        cube([encoderWidth, encoderLength, encoderDepth]);
}

// string
stringWallThickness = 1;
stringRecess = 5;
stringLength = 60;
photoresistorWidth = 5.5 + iFitAdjust_d;
photoresistorLength = photoresistorWidth;
photoresistorWireSep = 3.5;
ledLaserDiameter = 6.5 + iFitAdjust_d;
ledLaserLength = 10;
wireCasingDepth = 3;
module photoresistor_holder() {
    difference() {
        cylinder(h = stringRecess + stringWallThickness * 2, d = 2 * stringWallThickness + photoresistorWidth + iFitAdjust_d, center = false);
        translate([0,0,-.1])
            cylinder(h = stringRecess + stringWallThickness * 2 + 1, d = photoresistorWidth + iFitAdjust_d, center = false);
        /*
        for (i = [-1,1])
            translate([i * photoresistorWireSep / 2, 0, -1])
                cylinder(h = 20, d = 1, center = false);
        */
    }
    rotate([0, 0, 180])
        translate([-(ledLaserDiameter + iFitAdjust_d + 2 * stringWallThickness)/2, -(ledLaserDiameter )/2 - 1, -(wireCasingDepth + stringWallThickness * 2)])
        difference() {
            cube([ledLaserDiameter + iFitAdjust_d + 2 * stringWallThickness, ledLaserDiameter + ledLaserDiameter / 2 + 1, wireCasingDepth + stringWallThickness * 2], center=false);
            translate([stringWallThickness, stringWallThickness * 2 + 1, stringWallThickness])
                cube([ledLaserDiameter + iFitAdjust_d , ledLaserDiameter + ledLaserDiameter / 2 - stringWallThickness, wireCasingDepth], center=false);
            translate([ledLaserDiameter / 2 + stringWallThickness, ledLaserDiameter / 2 + stringWallThickness, stringWallThickness])
                cylinder(h=wireCasingDepth + 2 * stringWallThickness, d = ledLaserDiameter + iFitAdjust, center=false);
        }
}
module led_holder() {
    difference() {
        cylinder(h = ledLaserLength + stringWallThickness * 2, d = 2 * stringWallThickness + ledLaserDiameter + iFitAdjust_d, center = false);
        translate([0,0,-.1])
            cylinder(h = ledLaserLength + stringWallThickness * 2 + 1, d = ledLaserDiameter + iFitAdjust_d, center = false);
        /*
        for (i = [-1,1])
            translate([i * photoresistorWireSep / 2, 0, -1])
                cylinder(h = 20, d = 1, center = false);
        */
    }
    translate([-(ledLaserDiameter + iFitAdjust_d + 2 * stringWallThickness)/2, -(ledLaserDiameter )/2 - 1, -(wireCasingDepth + stringWallThickness * 2)])
        difference() {
            cube([ledLaserDiameter + iFitAdjust_d + 2 * stringWallThickness, ledLaserDiameter + ledLaserDiameter / 2 + 1, wireCasingDepth + stringWallThickness * 2], center=false);
            translate([stringWallThickness, stringWallThickness * 2 + 1, stringWallThickness])
                cube([ledLaserDiameter + iFitAdjust_d , ledLaserDiameter + ledLaserDiameter / 2 - stringWallThickness, wireCasingDepth], center=false);
            translate([ledLaserDiameter / 2 + stringWallThickness, ledLaserDiameter / 2 + stringWallThickness, stringWallThickness])
                cylinder(h=wireCasingDepth + 2 * stringWallThickness, d = ledLaserDiameter + iFitAdjust, center=false);
        }
}
module string() {
 rotate([0, 0, 90])
  translate([0, -(wireCasingDepth + stringWallThickness * 2), ledLaserDiameter]) rotate([90, 0, 0])
   union() {
    photoresistor_holder();
    translate([0, 0, stringLength])
        rotate([180, 0, 0])
            led_holder();
    difference() {
        translate([-(ledLaserDiameter + 2 * stringWallThickness + iFitAdjust_d)/2, - ledLaserDiameter, 0])
            cube([ledLaserDiameter + iFitAdjust_d + 2 * stringWallThickness, ledLaserDiameter / 2, stringLength ], center = false);
        cylinder(h=100, d = ledLaserDiameter + iFitAdjust, center=false);
    }
   }
}

// display
    OLEDWidth = 27;
i2cOLEDHeight = 16;
i2cOLEDScrewWidth = 20;
i2cOLEDScrewHeight = 22;
module display() {
}

lidWidth = 100;
lidThickness = 2.5;
rowWidth = ledButtonDiameter + 10;
lidLength = rowWidth * 2 + ledButtonDiameter / 2;
controlOffset = 10;
lidScrewDiameter = 3.5;
lidScrewBoundaryOffset = 10;
module spoke_lid() {
    union() {
        difference () {
            cube([lidWidth, lidLength, lidThickness]);
            for (i = [0:1]) {
                translate([0, ledButtonDiameter + i * rowWidth, -lidThickness * 2]) {
            // led buttons
                    translate([ledButtonDiameter / 2 + controlOffset, 0, 0])
                        momentary_button();
            // latch buttons
                    translate([ledButtonDiameter / 2 + controlOffset + 2 * controlOffset + latchButtonDiameter / 2, 0, 0])
                        latch_button();
            // encoders
                    translate([ledButtonDiameter / 2 + controlOffset + 2 * controlOffset + latchButtonDiameter / 2 + encoderLength * 1.2, 0, 0])
                    rotate([0, 0, 90])
                        encoder();

                }
            }
            translate([lidScrewBoundaryOffset/2, lidScrewBoundaryOffset/2, -.1])
                cylinder(h=lidThickness * 2, d=lidScrewDiameter);
            translate([lidWidth - lidScrewBoundaryOffset/2, lidScrewBoundaryOffset/2, -.1])
                cylinder(h=lidThickness * 2, d=lidScrewDiameter);
            translate([lidScrewBoundaryOffset/2, lidLength - lidScrewBoundaryOffset/2, -.1])
                cylinder(h=lidThickness * 2, d=lidScrewDiameter);
            translate([lidWidth - lidScrewBoundaryOffset/2, lidLength - lidScrewBoundaryOffset/2, -.1])
                cylinder(h=lidThickness * 2, d=lidScrewDiameter);
        }
    }
    //string();

}

spoke_lid();

$fn = 64;

// interference fit adjustment for 3D printer
iFitAdjust = .4;
iFitAdjust_d = .25;

// latch buttons
latchButtonDiameter = 13;
latchButtonHeight = 30;
module latch_button(color="green") {
    color(color)
        cylinder(h = latchButtonHeight, d = latchButtonDiameter, center = false);
}

// led momentary buttons
ledButtonDiameter = 24;
ledButtonHeight = 33;
module momentary_button(color="white") {
    color(color)
        cylinder(h = ledButtonHeight, d = ledButtonDiameter, center = false);
}

// encoders
encoderShaftDiameter = 7;
encoderShaftHeight = 18;
encoderWidth = 18;
encoderLength = 31;
encoderDepth = 8.2;
module encoder() {
    // encoder shaft
    cylinder(h = encoderShaftHeight, d = encoderShaftDiameter, center=false);
    // encoder base
    translate([-encoderWidth/2, -encoderLength/2, -encoderDepth/2])
        cube([encoderWidth, encoderLength, encoderDepth]);
}

// string
stringWallThickness = 1;
stringRecess = 5;
stringLength = 60;
photoresistorWidth = 5.5 + iFitAdjust_d;
photoresistorLength = photoresistorWidth;
photoresistorWireSep = 3.5;
ledLaserDiameter = 6.5 + iFitAdjust_d;
ledLaserLength = 10;
wireCasingDepth = 3;
module photoresistor_holder() {
    difference() {
        cylinder(h = stringRecess + stringWallThickness * 2, d = 2 * stringWallThickness + photoresistorWidth + iFitAdjust_d, center = false);
        translate([0,0,-.1])
            cylinder(h = stringRecess + stringWallThickness * 2 + 1, d = photoresistorWidth + iFitAdjust_d, center = false);
        /*
        for (i = [-1,1])
            translate([i * photoresistorWireSep / 2, 0, -1])
                cylinder(h = 20, d = 1, center = false);
        */
    }
    rotate([0, 0, 180])
        translate([-(ledLaserDiameter + iFitAdjust_d + 2 * stringWallThickness)/2, -(ledLaserDiameter )/2 - 1, -(wireCasingDepth + stringWallThickness * 2)])
        difference() {
            cube([ledLaserDiameter + iFitAdjust_d + 2 * stringWallThickness, ledLaserDiameter + ledLaserDiameter / 2 + 1, wireCasingDepth + stringWallThickness * 2], center=false);
            translate([stringWallThickness, stringWallThickness * 2 + 1, stringWallThickness])
                cube([ledLaserDiameter + iFitAdjust_d , ledLaserDiameter + ledLaserDiameter / 2 - stringWallThickness, wireCasingDepth], center=false);
            translate([ledLaserDiameter / 2 + stringWallThickness, ledLaserDiameter / 2 + stringWallThickness, stringWallThickness])
                cylinder(h=wireCasingDepth + 2 * stringWallThickness, d = ledLaserDiameter + iFitAdjust, center=false);
        }
}
module led_holder() {
    difference() {
        cylinder(h = ledLaserLength + stringWallThickness * 2, d = 2 * stringWallThickness + ledLaserDiameter + iFitAdjust_d, center = false);
        translate([0,0,-.1])
            cylinder(h = ledLaserLength + stringWallThickness * 2 + 1, d = ledLaserDiameter + iFitAdjust_d, center = false);
        /*
        for (i = [-1,1])
            translate([i * photoresistorWireSep / 2, 0, -1])
                cylinder(h = 20, d = 1, center = false);
        */
    }
    translate([-(ledLaserDiameter + iFitAdjust_d + 2 * stringWallThickness)/2, -(ledLaserDiameter )/2 - 1, -(wireCasingDepth + stringWallThickness * 2)])
        difference() {
            cube([ledLaserDiameter + iFitAdjust_d + 2 * stringWallThickness, ledLaserDiameter + ledLaserDiameter / 2 + 1, wireCasingDepth + stringWallThickness * 2], center=false);
            translate([stringWallThickness, stringWallThickness * 2 + 1, stringWallThickness])
                cube([ledLaserDiameter + iFitAdjust_d , ledLaserDiameter + ledLaserDiameter / 2 - stringWallThickness, wireCasingDepth], center=false);
            translate([ledLaserDiameter / 2 + stringWallThickness, ledLaserDiameter / 2 + stringWallThickness, stringWallThickness])
                cylinder(h=wireCasingDepth + 2 * stringWallThickness, d = ledLaserDiameter + iFitAdjust, center=false);
        }
}
module string() {
 rotate([0, 0, 90])
  translate([0, -(wireCasingDepth + stringWallThickness * 2), ledLaserDiameter]) rotate([90, 0, 0])
   union() {
    photoresistor_holder();
    translate([0, 0, stringLength])
        rotate([180, 0, 0])
            led_holder();
    difference() {
        translate([-(ledLaserDiameter + 2 * stringWallThickness + iFitAdjust_d)/2, - ledLaserDiameter, 0])
            cube([ledLaserDiameter + iFitAdjust_d + 2 * stringWallThickness, ledLaserDiameter / 2, stringLength ], center = false);
        cylinder(h=100, d = ledLaserDiameter + iFitAdjust, center=false);
    }
   }
}

// display
    OLEDWidth = 27;
i2cOLEDHeight = 16;
i2cOLEDScrewWidth = 20;
i2cOLEDScrewHeight = 22;
module display() {
}

module lid() {
    string();
}

lid();


Fix issue with array of strings in flatten()

/**
 * @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.
 *
 * Operations on lists.
 *
 * @package core/list
 * @author jsconan
 */

/**
 * Fills an array with a value upon the requested length.
 *
 * @param * value - The value to fill with. Can be of any type.
 * @param Number length - The length of the array to fill.
 * @returns Array
 */
function fill(value, length) =
    let( length = float(length) )
    length > 0 ? [ for (i = [0 : length - 1]) value ]
               : []
;

/**
 * Fills an array with a range of values.
 *
 * @param Number start - The start of the range, or the length of the range
 * @param Number [end] - The end of the range.
 * @param Number [step] - The range increment.
 * @returns Array
 */
function range(start, end, step) =
    let(
        s = float(start),
        simple = s && isUndef(end),
        start = simple ? 0 : s,
        end = simple ? s : float(end),
        step = or(number(step), start > end ? -1 : 1)
    )
    start || end ? [ for (i = [start : step : end]) i ]
                 : []
;

/**
 * Returns a list range with the ideal step to cover this range by the required count.
 *
 * @param Number start - The start of the range.
 * @param Number count - The number of steps to cover the range.
 * @param Number end - The end of the range.
 * @returns Number
 */
function steps(start, count, end) =
    let(
        start = float(start),
        end = float(end),
        step = (end - start) / divisor(float(count) - 1)
    )
    [ start : step : end ]
;

/**
 * Reverses an array.
 *
 * @param Array collection - The array to reverse.
 * @returns Array
 */
function reverse(collection) =
    let( collection = arrayOr(collection, []) )
    [ for (i = [len(collection) - 1 : -1 : 0]) collection[i] ]
;

/**
 * Flattens a multi level array to a less deep array.
 *
 * @param Array collection - The array to flatten.
 * @returns Array
 */
function flatten(collection) = [ for (a = arrayOr(collection, [])) for (b = isString(a) ? [a] : a) b ];

/**
 * Finds the index of a key in a collection.
 *
 * @param Array collection - The collection in which search.
 * @param * key - The key to search in the collection.
 * @returns Number - The position of the key in the collection, or -1 if not found.
 */
function find(collection, key) = numberOr(search([key], collection)[0], -1);

/**
 * Fetches a record from a collection with respect to the provided key.
 *
 * @param Array collection - The collection in which search.
 * @param * key - The key for which fetch the record.
 * @returns * - The record that relates to the key, or undef if not found.
 */
function fetch(collection, key) = collection[find(collection, key)];

/**
 * Checks if an element exists in an array.
 *
 * @param Array collection - The collection from which search the element.
 * @param * elem - The element to search.
 * @returns Boolean
 */
function inArray(collection, elem) = find(collection, elem) > -1;

/**
 * Completes a list with start and end elements, at the condition there are not aleady present.
 *
 * @param Array collection - The list  to complete.
 * @param * start - The start element to add.
 * @param * end - The end element to add.
 * @returns Array
 */
function complete(collection, start, end) =
    let(
        collection = arrayOr(collection, []),
        first = start && start != collection[0] && start != collection[len(collection) - 1] ? concat([start], collection) : collection,
        second = end && end != first[0] && end != first[len(first) - 1] ? concat(first, [end]) : first
    )
    second
;

/**
 * Gets an array slice.
 *
 * @param Array collection - The collection to slice.
 * @param Number [start] - The start index of the slice. If negative, will start from the end.
 * @param Number [end] - The end index of the slice, exclusive.
 * @returns Array
 */
function slice(collection, start, end) =
    let(
        collection = arrayOr(collection, []),
        length = len(collection),
        start = start < 0 ? max(0, length + start) : integer(start),
        end = (end < 0 ? max(start, length + end) : numberOr(end, length)) - 1
    )
    length && start <= end ? [ for (i = [start : end]) if (i < length) collection[i] ]
                           : []
;

/**
 * Changes the contents of an array by removing existing elements and/or adding new elements.
 *
 * @param Array collection - The collection to slice.
 * @param Number [start] - The index at which to start changing the array. If negative, will start from the end.
 * @param Number [remove] - The number of elements to remove.
 * @param Array [elems] - The elements to add to the array, beginning at the start index.
 * @returns Array
 */
function splice(collection, start, remove, elems) =
    let(
        collection = arrayOr(collection, []),
        elems = arrayOr(elems, []),
        length = len(collection),
        start = min(start < 0 ? max(0, length + start) : integer(start), length),
        next = start + numberOr(remove, length - start),
        last = length - 1
    )
    length || len(elems)
   ?concat(
       length && start > 0 ? [ for (i = [0 : start - 1]) collection[i] ] : [],
       elems,
       length && next <= last ? [ for (i = [next : last]) collection[i] ] : []
    )
   :[]
;

/**
 * Gets the N first elements of an array.
 *
 * @param Array collection - The collection from which get the elements.
 * @param Number [count] - The number of elements to get.
 * @returns Array|*
 */
function first(collection, count) =
    let( count = numberOr(count, 1) )
    count > 1 ? slice(collection, 0, count)
          : arrayOr(collection, [])[0]
;

/**
 * Gets the N last elements of an array.
 *
 * @param Array collection - The collection from which get the elements.
 * @param Number [count] - The number of elements to get.
 * @returns *
 */
function last(collection, count) =
    let( count = numberOr(count, 1) )
    count > 1 ? slice(collection, -count)
          : arrayOr(collection, [])[len(collection) - 1]
;

/**
 * Removes the last element of an array.
 *
 * @param Array collection - The collection from which removes the element.
 * @returns Array
 */
function pop(collection) = slice(collection, end=-1);

/**
 * Removes the first element of an array.
 *
 * @param Array collection - The collection from which removes the element.
 * @returns Array
 */
function shift(collection) = slice(collection, 1);

/**
 * Adds an element at the end of an array.
 *
 * @param Array collection - The collection to which add the element.
 * @param * elem - The element to add.
 * @returns Array
 */
function push(collection, elem) = concat(arrayOr(collection, []), [elem]);

/**
 * Adds an element at the beginning of an array.
 *
 * @param Array collection - The collection to which add the element.
 * @param * elem - The element to add.
 * @returns Array
 */
function unshift(collection, elem) = concat([elem], arrayOr(collection, []));

/**
 * Picks elements in an array to build another array.
 *
 * @param Array collection - The collection in which pick the elements.
 * @param Array what - The indexes of the elements to pick.
 * @returns Array
 */
function pick(collection, what) =
    let(
        collection = arrayOr(collection, []),
        length = len(collection),
        what = arrayOr(what, [])
    )
    [ for (i = what) if (i < length) collection[i] ]
;

/**
 * Sorts an array.
 *
 * @param Array collection - The collection to sort.
 * @returns Array
 */
function sort(collection,
              // internal
              nocheck=false) =
    let( length = len(collection) )
    !length || !nocheck && isString(collection) ? []            // not array or empty
   :let( middle = collection[floor(length / 2)] )
    concat(
        sort([ for (i = collection) if (i < middle) i ], true), // lesser
        [ for (i = collection) if (i == middle) i ],            // equal
        sort([ for (i = collection) if (i > middle) i ], true)  // greater
    )
;

/**
 * @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.
 *
 * Operations on lists.
 *
 * @package core/list
 * @author jsconan
 */

/**
 * Fills an array with a value upon the requested length.
 *
 * @param * value - The value to fill with. Can be of any type.
 * @param Number length - The length of the array to fill.
 * @returns Array
 */
function fill(value, length) =
    let( length = float(length) )
    length > 0 ? [ for (i = [0 : length - 1]) value ]
               : []
;

/**
 * Fills an array with a range of values.
 *
 * @param Number start - The start of the range, or the length of the range
 * @param Number [end] - The end of the range.
 * @param Number [step] - The range increment.
 * @returns Array
 */
function range(start, end, step) =
    let(
        s = float(start),
        simple = s && isUndef(end),
        start = simple ? 0 : s,
        end = simple ? s : float(end),
        step = or(number(step), start > end ? -1 : 1)
    )
    start || end ? [ for (i = [start : step : end]) i ]
                 : []
;

/**
 * Returns a list range with the ideal step to cover this range by the required count.
 *
 * @param Number start - The start of the range.
 * @param Number count - The number of steps to cover the range.
 * @param Number end - The end of the range.
 * @returns Number
 */
function steps(start, count, end) =
    let(
        start = float(start),
        end = float(end),
        step = (end - start) / divisor(float(count) - 1)
    )
    [ start : step : end ]
;

/**
 * Reverses an array.
 *
 * @param Array collection - The array to reverse.
 * @returns Array
 */
function reverse(collection) =
    let( collection = arrayOr(collection, []) )
    [ for (i = [len(collection) - 1 : -1 : 0]) collection[i] ]
;

/**
 * Flattens a multi level array to a less deep array.
 *
 * @param Array collection - The array to flatten.
 * @returns Array
 */
function flatten(collection) = [ for (a = arrayOr(collection, [])) for (b = a) b ];

/**
 * Finds the index of a key in a collection.
 *
 * @param Array collection - The collection in which search.
 * @param * key - The key to search in the collection.
 * @returns Number - The position of the key in the collection, or -1 if not found.
 */
function find(collection, key) = numberOr(search([key], collection)[0], -1);

/**
 * Fetches a record from a collection with respect to the provided key.
 *
 * @param Array collection - The collection in which search.
 * @param * key - The key for which fetch the record.
 * @returns * - The record that relates to the key, or undef if not found.
 */
function fetch(collection, key) = collection[find(collection, key)];

/**
 * Checks if an element exists in an array.
 *
 * @param Array collection - The collection from which search the element.
 * @param * elem - The element to search.
 * @returns Boolean
 */
function inArray(collection, elem) = find(collection, elem) > -1;

/**
 * Completes a list with start and end elements, at the condition there are not aleady present.
 *
 * @param Array collection - The list  to complete.
 * @param * start - The start element to add.
 * @param * end - The end element to add.
 * @returns Array
 */
function complete(collection, start, end) =
    let(
        collection = arrayOr(collection, []),
        first = start && start != collection[0] && start != collection[len(collection) - 1] ? concat([start], collection) : collection,
        second = end && end != first[0] && end != first[len(first) - 1] ? concat(first, [end]) : first
    )
    second
;

/**
 * Gets an array slice.
 *
 * @param Array collection - The collection to slice.
 * @param Number [start] - The start index of the slice. If negative, will start from the end.
 * @param Number [end] - The end index of the slice, exclusive.
 * @returns Array
 */
function slice(collection, start, end) =
    let(
        collection = arrayOr(collection, []),
        length = len(collection),
        start = start < 0 ? max(0, length + start) : integer(start),
        end = (end < 0 ? max(start, length + end) : numberOr(end, length)) - 1
    )
    length && start <= end ? [ for (i = [start : end]) if (i < length) collection[i] ]
                           : []
;

/**
 * Changes the contents of an array by removing existing elements and/or adding new elements.
 *
 * @param Array collection - The collection to slice.
 * @param Number [start] - The index at which to start changing the array. If negative, will start from the end.
 * @param Number [remove] - The number of elements to remove.
 * @param Array [elems] - The elements to add to the array, beginning at the start index.
 * @returns Array
 */
function splice(collection, start, remove, elems) =
    let(
        collection = arrayOr(collection, []),
        elems = arrayOr(elems, []),
        length = len(collection),
        start = min(start < 0 ? max(0, length + start) : integer(start), length),
        next = start + numberOr(remove, length - start),
        last = length - 1
    )
    length || len(elems)
   ?concat(
       length && start > 0 ? [ for (i = [0 : start - 1]) collection[i] ] : [],
       elems,
       length && next <= last ? [ for (i = [next : last]) collection[i] ] : []
    )
   :[]
;

/**
 * Gets the N first elements of an array.
 *
 * @param Array collection - The collection from which get the elements.
 * @param Number [count] - The number of elements to get.
 * @returns Array|*
 */
function first(collection, count) =
    let( count = numberOr(count, 1) )
    count > 1 ? slice(collection, 0, count)
          : arrayOr(collection, [])[0]
;

/**
 * Gets the N last elements of an array.
 *
 * @param Array collection - The collection from which get the elements.
 * @param Number [count] - The number of elements to get.
 * @returns *
 */
function last(collection, count) =
    let( count = numberOr(count, 1) )
    count > 1 ? slice(collection, -count)
          : arrayOr(collection, [])[len(collection) - 1]
;

/**
 * Removes the last element of an array.
 *
 * @param Array collection - The collection from which removes the element.
 * @returns Array
 */
function pop(collection) = slice(collection, end=-1);

/**
 * Removes the first element of an array.
 *
 * @param Array collection - The collection from which removes the element.
 * @returns Array
 */
function shift(collection) = slice(collection, 1);

/**
 * Adds an element at the end of an array.
 *
 * @param Array collection - The collection to which add the element.
 * @param * elem - The element to add.
 * @returns Array
 */
function push(collection, elem) = concat(arrayOr(collection, []), [elem]);

/**
 * Adds an element at the beginning of an array.
 *
 * @param Array collection - The collection to which add the element.
 * @param * elem - The element to add.
 * @returns Array
 */
function unshift(collection, elem) = concat([elem], arrayOr(collection, []));

/**
 * Picks elements in an array to build another array.
 *
 * @param Array collection - The collection in which pick the elements.
 * @param Array what - The indexes of the elements to pick.
 * @returns Array
 */
function pick(collection, what) =
    let(
        collection = arrayOr(collection, []),
        length = len(collection),
        what = arrayOr(what, [])
    )
    [ for (i = what) if (i < length) collection[i] ]
;

/**
 * Sorts an array.
 *
 * @param Array collection - The collection to sort.
 * @returns Array
 */
function sort(collection,
              // internal
              nocheck=false) =
    let( length = len(collection) )
    !length || !nocheck && isString(collection) ? []            // not array or empty
   :let( middle = collection[floor(length / 2)] )
    concat(
        sort([ for (i = collection) if (i < middle) i ], true), // lesser
        [ for (i = collection) if (i == middle) i ],            // equal
        sort([ for (i = collection) if (i > middle) i ], true)  // greater
    )
;


=> rules coding

=> rules coding