added connectors to stepper-motors

added connectors to stepper-motors

//
// Mendel90
//
// GNU GPL v2
// nop.head@gmail.com
// hydraraptor.blogspot.com
//
// NEMA stepper motor model
//

//                       corner  body    boss    boss          shaft
//         side, length, radius, radius, radius, depth, shaft, length, holes, typesuffix
NEMA17  = [42.3, 47,     53.6/2, 25,     11,     2,     5,     24,     31,    "17"];
NEMA17S = [42.3, 34,     53.6/2, 25,     11,     2,     5,     24,     31,    "17S" ];
NEMA14  = [35.2, 36,     46.4/2, 21,     11,     2,     5,     21,     26,    "14" ];
NEMA11  = [28.2, 51,     18,   18,     22/2,   2,     5,     21,     23,      "11" ];

stepper_body_color = grey20;
stepper_cap_color  = grey80;

function NEMA_width(motor)    = motor[0];
function NEMA_length(motor)   = motor[1];
function NEMA_radius(motor)   = motor[2];
function NEMA_holes(motor)    = [-motor[8]/2, motor[8]/2];
function NEMA_big_hole(motor) = motor[4] + 0.2;
function NEMA_shaft_dia(motor) = motor[6];
function NEMA_shaft_length(motor) = motor[7];
function NEMA_hole_pitch(motor) = motor[8];

// connectors
NEMA_Con_Default = [[0,0,0], [0,0,-1], 0,0,0];

function NEMA_Con_Fixings(m) = [
    [[m[8]/2, m[8]/2, 0], [0,0,-1], 0,0,0],
    [[m[8]/2, -m[8]/2, 0], [0,0,-1], 0,0,0],
    [[-m[8]/2, -m[8]/2, 0], [0,0,-1], 0,0,0],
    [[-m[8]/2, m[8]/2, 0], [0,0,-1], 0,0,0]
];

module NEMA(motor) {
    side = NEMA_width(motor);
    length = NEMA_length(motor);
    body_rad = motor[3];
    boss_rad = motor[4];
    boss_height = motor[5];
    shaft_rad = NEMA_shaft_dia(motor) / 2;
    cap = 8;
    //vitamin(str("NEMA", round(motor[0] / 2.54), length * 10, ": NEMA", round(motor[0] / 2.54), " x ", length, "mm stepper motor"));

    tn = motor[9];
     vitamin("vitamins/stepper-motors.scad", str("NEMA",tn," Stepper Motor"), str("NEMA(NEMA",tn,")")) {
        view(t=[0,-7,-12]);
    }

    if (DebugCoordinateFrames) frame();
    if (DebugConnectors) {
        connector(NEMA_Con_Default);
        for (i=[0:3])
            connector(NEMA_Con_Fixings(motor)[i]);
    }

    union() {
        color(stepper_body_color) render()                                                     // black laminations
            translate([0,0, -length / 2])
                intersection() {
                    cube([side, side, length - cap * 2],center = true);
                    cylinder(r = body_rad, h = 2 * length, center = true);
                }
        color(stepper_cap_color) render() {                                     // aluminium end caps
            difference() {
                union() {
                    intersection() {
                        union() {
                            translate([0,0, -cap / 2])
                                cube([side,side,cap], center = true);
                            translate([0,0, -length + cap / 2])
                                cube([side,side,cap], center = true);
                        }
                        cylinder(r = NEMA_radius(motor), h = 3 * length, center = true);
                    }
                    difference() {
                        cylinder(r = boss_rad, h = boss_height * 2, center = true);                 // raised boss
                        cylinder(r = shaft_rad + 2, h = boss_height * 2 + 1, center = true);
                    }
                    cylinder(r = shaft_rad, h = NEMA_shaft_length(motor) * 2, center = true);  // shaft
                }
                for(x = NEMA_holes(motor))
                    for(y = NEMA_holes(motor))
                        translate([x, y, 0])
                            cylinder(r = 3/2, h = 9, center = true);
            }
        }

        translate([0, side / 2, -length + cap / 2])
            rotate([90, 0, 0])
                for(i = [0:3])
                    rotate([0, 0, 225 + i * 90])
                        color(["red", "blue","green","black"][i]) render()
                            translate([1, 0, 0])
                                cylinder(r = 1.5 / 2, h = 12, center = true);

    }
}

module NEMA_screws(motor, n = 4, screw_length = 8, screw_type = M3_pan_screw) {
    for(a = [0: 90 : 90 * (n - 1)])
        rotate([0, 0, a])
            translate([motor[8]/2, motor[8]/2, 0])
                screw_and_washer(screw_type, screw_length, true);
}

//
// Mendel90
//
// GNU GPL v2
// nop.head@gmail.com
// hydraraptor.blogspot.com
//
// NEMA stepper motor model
//

//                       corner  body    boss    boss          shaft
//         side, length, radius, radius, radius, depth, shaft, length, holes, typesuffix
NEMA17  = [42.3, 47,     53.6/2, 25,     11,     2,     5,     24,     31,    "17"];
NEMA17S = [42.3, 34,     53.6/2, 25,     11,     2,     5,     24,     31,    "17S" ];
NEMA14  = [35.2, 36,     46.4/2, 21,     11,     2,     5,     21,     26,    "14" ];
NEMA11  = [28.2, 51,     18,   18,     22/2,   2,     5,     21,     23,      "11" ];

stepper_body_color = grey20;
stepper_cap_color  = grey80;

function NEMA_width(motor)    = motor[0];
function NEMA_length(motor)   = motor[1];
function NEMA_radius(motor)   = motor[2];
function NEMA_holes(motor)    = [-motor[8]/2, motor[8]/2];
function NEMA_big_hole(motor) = motor[4] + 0.2;
function NEMA_shaft_dia(motor) = motor[6];
function NEMA_shaft_length(motor) = motor[7];
function NEMA_hole_pitch(motor) = motor[8];

module NEMA(motor) {
    side = NEMA_width(motor);
    length = NEMA_length(motor);
    body_rad = motor[3];
    boss_rad = motor[4];
    boss_height = motor[5];
    shaft_rad = NEMA_shaft_dia(motor) / 2;
    cap = 8;
    //vitamin(str("NEMA", round(motor[0] / 2.54), length * 10, ": NEMA", round(motor[0] / 2.54), " x ", length, "mm stepper motor"));

    tn = motor[9];
     vitamin("vitamins/stepper-motors.scad", str("NEMA",tn," Stepper Motor"), str("NEMA(NEMA",tn,")")) {
        view(t=[0,-7,-12]);
    }

    union() {
        color(stepper_body_color) render()                                                     // black laminations
            translate([0,0, -length / 2])
                intersection() {
                    cube([side, side, length - cap * 2],center = true);
                    cylinder(r = body_rad, h = 2 * length, center = true);
                }
        color(stepper_cap_color) render() {                                     // aluminium end caps
            difference() {
                union() {
                    intersection() {
                        union() {
                            translate([0,0, -cap / 2])
                                cube([side,side,cap], center = true);
                            translate([0,0, -length + cap / 2])
                                cube([side,side,cap], center = true);
                        }
                        cylinder(r = NEMA_radius(motor), h = 3 * length, center = true);
                    }
                    difference() {
                        cylinder(r = boss_rad, h = boss_height * 2, center = true);                 // raised boss
                        cylinder(r = shaft_rad + 2, h = boss_height * 2 + 1, center = true);
                    }
                    cylinder(r = shaft_rad, h = NEMA_shaft_length(motor) * 2, center = true);  // shaft
                }
                for(x = NEMA_holes(motor))
                    for(y = NEMA_holes(motor))
                        translate([x, y, 0])
                            cylinder(r = 3/2, h = 9, center = true);
            }
        }

        translate([0, side / 2, -length + cap / 2])
            rotate([90, 0, 0])
                for(i = [0:3])
                    rotate([0, 0, 225 + i * 90])
                        color(["red", "blue","green","black"][i]) render()
                            translate([1, 0, 0])
                                cylinder(r = 1.5 / 2, h = 12, center = true);

    }
}

module NEMA_screws(motor, n = 4, screw_length = 8, screw_type = M3_pan_screw) {
    for(a = [0: 90 : 90 * (n - 1)])
        rotate([0, 0, a])
            translate([motor[8]/2, motor[8]/2, 0])
                screw_and_washer(screw_type, screw_length, true);
}


new model CITOSINE

new model CITOSINE


Change to Pro Mini with serial header pins

/*
	Assembly: LogoBotAssembly

	Master assembly for the LogoBot

	Local Frame:
		Centred on the origin, such that bottom edge of wheels sit on XY plane.
		Front of the robot faces towards y+

	Parameters:
		PenLift - Set to true to include a PenLift, defaults to false

	Returns:
		Complete LogoBot model

*/

// Connectors
// ----------
// These are used within this module to layout the various vitamins/sub-assemblies
// The same connectors are used to shape associated portions of the LogoBotBase_STL

// dynamic connector for a given fixing in the grid
function LogoBot_Con_GridFixing(x,y,r) = [[x*10, y*10, dw], [0,0,-1], r, 0,0];

LogoBot_Con_Breadboard          = [[-18, 40, 12],[0,0,-1], 0,0,0];

LogoBot_Con_LeftWheel           = [[-40,-20, dw],[0,0,-1],90,0,0];
LogoBot_Con_RightWheel           = [[40,20, dw],[0,0,-1],-90,0,0];

LogoBot_Con_LeftMotorDriver     = [[-46, 16, 10],[0,-1,0],0,0,0];
LogoBot_Con_RightMotorDriver    = [[46, 16, 10],[0,-1,0],0,0,0];

LogoBot_Con_Caster = [ [0, -BaseDiameter/2 + 10, 0], [0,0,1], 0, 0, 0];

LogoBot_Con_PenLift_Front = [ [0, -20, 2], [0,0,1], 0, 0, 0];
LogoBot_Con_PenLift_Rear = [ [0, 20, 2], [0,0,1], 0, 0, 0];
LogoBot_Con_PenLiftServo = [[-12, -6, -6], [1, 0, 0], 90, 0, 0];

LogoBot_Con_SlideSwitch = [[-25, 0, 0], [0,0,-1], 90, 0,0];

// Assembly
// --------

function LogoBotAssembly_NumSteps() = 10 + (PenLift ? 1 : 0);

module LogoBotAssembly ( PenLift=false, Shell=true ) {

    assembly("assemblies/LogoBot.scad", "Final Assembly", str("LogoBotAssembly(PenLift=",PenLift,")")) {

        translate([0, 0, GroundClearance]) {

            // Default Design Elements
            // -----------------------

            // Base
            LogoBotBase_STL();

            *step(1, "Connect the breadboard assembly to the underside of the base") {
                view(t=[0,17,12], r=[112,0,222], d=513);
                attach(LogoBot_Con_Breadboard, Breadboard_Con_BottomLeft(Breadboard_170), ExplodeSpacing=-20)
                    BreadboardAssembly();
            }

            step(1, "Plug the Pro Mini Clips into the base and then snap the Arduino Pro Mini into them") {
                view(t=[0,17,12], r=[112,0,222], d=513);

                attach(LogoBot_Con_GridFixing(1,4,90), DefConUp)
                    ProMiniClip_STL();

                attach(LogoBot_Con_GridFixing(-1,4,90), DefConUp)
                    ProMiniClip_STL();

                attach(offsetConnector(LogoBot_Con_GridFixing(0,4,-90), [0,0,3]), ArduinoPro_Con_Center, ExplodeSpacing=20)
                    ArduinoPro(ArduinoPro_Mini, ArduinoPro_Pins_Opposite, true);
            }

            // Bumper assemblies (x2)
            step(2, "Connect the two bumper assemblies using four of the push pins with flat heads" ) {
                view(t=[0,0,0], r=[63,0,146], d=400);
					for (i=[0,1])
					translate([0, 0, -8])
					rotate([0, 0, i*180]) {
						attach([Bumper_Con_LeftPin[0], Bumper_Con_LeftPin[1], 0, 0, 0], Bumper_Con_LeftPin, ExplodeSpacing=20)
							BumperAssembly();

						attachWithOffset(DefCon, DefCon, [0, 0, -6*layers])
							BumperStabiliser_STL();

						attach(Bumper_Con_LeftPin, DefConDown, ExplodeSpacing=-20, offset=[0,0,20])
							pintack(side=false, h=7.8+2+2.5+6*layers, bh=2);

						attach(Bumper_Con_RightPin, DefConDown, ExplodeSpacing=-20)
							pintack(side=false, h=7.8+2+2.5+6*layers, bh=2);
					}
            }

            // Motor + Wheel assemblies (x2)

            //LogoBot_Con_LeftWheel
            step(4, "Clip the two wheels assemblies onto the base and
                    connect the motor leads to the the motor drivers") {
                view(t=[-4,6,47], r=[66,0,190], d=740);

                attach(LogoBot_Con_LeftWheel, MotorClip_Con_Fixing1, ExplodeSpacing = 40)
                    LeftWheelAssembly();

                attach(LogoBot_Con_RightWheel, MotorClip_Con_Fixing1, ExplodeSpacing = 40)
                    RightWheelAssembly();
            }

            // Connect jumper wires
            step(5,
                "Connect the jumper wires between the motor drivers and the Arduino") {
                view(t=[9,26,54], r=[38,0,161], d=766);
                view(title="plan", t=[0,32,16], r=[0,0,0], d=337);

                // Left
                JumperWire(
                    type = JumperWire_FF4,
                    con1 = attachedConnector(
                        LogoBot_Con_LeftMotorDriver,
                        ULN2003DriverBoard_Con_UpperLeft,
                        ULN2003DriverBoard_Con_Arduino,
                        ExplodeSpacing=20
                    ),
                    con2 = attachedConnector(
                        LogoBot_Con_GridFixing(0,4,-90),
                        DefConDown,
                        [[-7.5, -5, 7],[0,0,-1],0,0,0],
                        ExplodeSpacing=20
                    ),
                    length = 100,
                    conVec1 = attachedDirection(
                        LogoBot_Con_LeftMotorDriver,
                        ULN2003DriverBoard_Con_UpperLeft,
                        [0,[1,0,0]]),
                    conVec2 = [-1,0,0],
                    midVec = [0.5,-1,0]
                );

                // Right
                JumperWire(
                    type = JumperWire_FF4,
                    con1 = attachedConnector(
                        LogoBot_Con_RightMotorDriver,
                        ULN2003DriverBoard_Con_UpperRight,
                        ULN2003DriverBoard_Con_Arduino,
                        ExplodeSpacing=20
                    ),
                    con2 = attachedConnector(
                        LogoBot_Con_GridFixing(0,4,-90),
                        DefConDown,
                        [[-7.5, -15, 7],[0,0,-1],0,0,0],
                        ExplodeSpacing=20
                    ),
                    length = 100,
                    conVec1 = attachedDirection(
                        LogoBot_Con_RightMotorDriver,
                        ULN2003DriverBoard_Con_UpperRight,
                        [0,[1,0,0]]),
                    conVec2 = [-1,0,0],
                    midVec = [0.5,-1,0]
                );
            }

            // TODO: Add velcro or to hold down battery pack
            // Battery assembly
            step(6, "Clip in the battery pack") {
                view(t=[-6,7,19], r=[64,1,212], d=625);

                attach(DefConDown, DefConDown, ExplodeSpacing=20)
                    translate([-25, -45, 12])
                        rotate([90, 0, 90]) {
                            BatteryPack(BatteryPack_AA);
                            battery_pack_double(BatteryPack_AA, 2, 4);
                        }
            }

            // TODO: Replace with toggle switch
            //Power Switch
            *step(7, "Clip the power switch into place") {
                view(t=[-6,7,19], r=[64,1,212], d=625);

                attach(LogoBot_Con_SlideSwitch, DefConUp)
                    SlideSwitch();
            }

            // TODO: LED
            *step(8, "Clip the LED into place") {
                view(t=[-6,7,19], r=[64,1,212], d=625);

                attach(DefConDown, DefConDown, ExplodeSpacing=20)
                    translate([0, -10, BaseDiameter/2])
                    LED();
            }

            // TODO: Piezo
            *step(9, "Clip the piezo sounder into place") {
                view(t=[-6,7,19], r=[64,1,212], d=625);

                attach(DefConDown, DefConDown, ExplodeSpacing=20)
                    translate([-37, -32, 10])
                    murata_7BB_12_9();
            }

            // Caster
            step(10, "Align the caster assembly with the base, then insert a short pin to lock it to the base") {
                view(t=[-6,7,19], r=[115,1,26], d=625);

                attach(LogoBot_Con_Caster, MarbleCaster_Con_Default, ExplodeSpacing=15)
                    MarbleCasterAssembly();

                attach(offsetConnector(invertConnector(LogoBot_Con_Caster), [0,0,dw]), MarbleCaster_Con_Default)
                    pintack(side=false, h=dw+0.6+2+1.5, lh=2, bh=2);
            }

            // Conditional Design Elements
            // ---------------------------

            // PenLift
            //   Placeholder of a micro servo to illustrate having conditional design elements
            if (PenLift) {
                step(12, "Fit the pen lift assembly using two of the pins and zip tie the servo under the base.") {
                    view(t=[-6,7,19], r=[64,1,212], d=625);

					attach(LogoBot_Con_PenLift_Front, PenLiftSlider_Con_BaseFront)
						PenLiftAssembly();

					attach(LogoBot_Con_PenLift_Front, offsetConnector(DefConDown, [0, 0, 2 + 2.5]))
						pintack(side=false, h=2.5+4+2.5, bh=2);
					attach(LogoBot_Con_PenLift_Rear, offsetConnector(DefConDown, [0, 0, 2 + 2.5]))
						pintack(side=false, h=2.5+4+2.5, bh=2);

                    attach(LogoBot_Con_PenLiftServo, MicroServo_Con_Horn) {
						MicroServo();
						attach(MicroServo_Con_Horn, ServoHorn_Con_Default)
							ServoHorn();
					}
                }
            }

            // Shell + fixings
			if (Shell) {
				step(PenLift ? 13 : 12,
					"Push the shell down onto the base and twist to lock into place") {
					view(t=[11,-23,65], r=[66,0,217], d=625);

					attach(DefConDown, DefConDown, ExplodeSpacing=BaseDiameter/2)
						ShellAssembly(PenLift? false : true);
				}
            }
        }

	}
}

/*
	Assembly: LogoBotAssembly

	Master assembly for the LogoBot

	Local Frame:
		Centred on the origin, such that bottom edge of wheels sit on XY plane.
		Front of the robot faces towards y+

	Parameters:
		PenLift - Set to true to include a PenLift, defaults to false

	Returns:
		Complete LogoBot model

*/

// Connectors
// ----------
// These are used within this module to layout the various vitamins/sub-assemblies
// The same connectors are used to shape associated portions of the LogoBotBase_STL

// dynamic connector for a given fixing in the grid
function LogoBot_Con_GridFixing(x,y,r) = [[x*10, y*10, dw], [0,0,-1], r, 0,0];

LogoBot_Con_Breadboard          = [[-18, 40, 12],[0,0,-1], 0,0,0];

LogoBot_Con_LeftWheel           = [[-40,-20, dw],[0,0,-1],90,0,0];
LogoBot_Con_RightWheel           = [[40,20, dw],[0,0,-1],-90,0,0];

LogoBot_Con_LeftMotorDriver     = [[-46, 16, 10],[0,-1,0],0,0,0];
LogoBot_Con_RightMotorDriver    = [[46, 16, 10],[0,-1,0],0,0,0];

LogoBot_Con_Caster = [ [0, -BaseDiameter/2 + 10, 0], [0,0,1], 0, 0, 0];

LogoBot_Con_PenLift_Front = [ [0, -20, 2], [0,0,1], 0, 0, 0];
LogoBot_Con_PenLift_Rear = [ [0, 20, 2], [0,0,1], 0, 0, 0];
LogoBot_Con_PenLiftServo = [[-12, -6, -6], [1, 0, 0], 90, 0, 0];

LogoBot_Con_SlideSwitch = [[-25, 0, 0], [0,0,-1], 90, 0,0];

// Assembly
// --------

function LogoBotAssembly_NumSteps() = 10 + (PenLift ? 1 : 0);

module LogoBotAssembly ( PenLift=false, Shell=true ) {

    assembly("assemblies/LogoBot.scad", "Final Assembly", str("LogoBotAssembly(PenLift=",PenLift,")")) {

        translate([0, 0, GroundClearance]) {

            // Default Design Elements
            // -----------------------

            // Base
            LogoBotBase_STL();

            *step(1, "Connect the breadboard assembly to the underside of the base") {
                view(t=[0,17,12], r=[112,0,222], d=513);
                attach(LogoBot_Con_Breadboard, Breadboard_Con_BottomLeft(Breadboard_170), ExplodeSpacing=-20)
                    BreadboardAssembly();
            }

            step(1, "Plug the Pro Mini Clips into the base and then snap the Arduino Pro Mini into them") {
                view(t=[0,17,12], r=[112,0,222], d=513);

                attach(LogoBot_Con_GridFixing(1,4,90), DefConUp)
                    ProMiniClip_STL();

                attach(LogoBot_Con_GridFixing(-1,4,90), DefConUp)
                    ProMiniClip_STL();

                attach(offsetConnector(LogoBot_Con_GridFixing(0,4,-90), [0,0,3]), ArduinoPro_Con_Center, ExplodeSpacing=20)
                    ArduinoPro(ArduinoPro_Micro, ArduinoPro_Pins_Opposite);
            }

            // Bumper assemblies (x2)
            step(2, "Connect the two bumper assemblies using four of the push pins with flat heads" ) {
                view(t=[0,0,0], r=[63,0,146], d=400);
					for (i=[0,1])
					translate([0, 0, -8])
					rotate([0, 0, i*180]) {
						attach([Bumper_Con_LeftPin[0], Bumper_Con_LeftPin[1], 0, 0, 0], Bumper_Con_LeftPin, ExplodeSpacing=20)
							BumperAssembly();

						attachWithOffset(DefCon, DefCon, [0, 0, -6*layers])
							BumperStabiliser_STL();

						attach(Bumper_Con_LeftPin, DefConDown, ExplodeSpacing=-20, offset=[0,0,20])
							pintack(side=false, h=7.8+2+2.5+6*layers, bh=2);

						attach(Bumper_Con_RightPin, DefConDown, ExplodeSpacing=-20)
							pintack(side=false, h=7.8+2+2.5+6*layers, bh=2);
					}
            }

            // Motor + Wheel assemblies (x2)

            //LogoBot_Con_LeftWheel
            step(4, "Clip the two wheels assemblies onto the base and
                    connect the motor leads to the the motor drivers") {
                view(t=[-4,6,47], r=[66,0,190], d=740);

                attach(LogoBot_Con_LeftWheel, MotorClip_Con_Fixing1, ExplodeSpacing = 40)
                    LeftWheelAssembly();

                attach(LogoBot_Con_RightWheel, MotorClip_Con_Fixing1, ExplodeSpacing = 40)
                    RightWheelAssembly();
            }

            // Connect jumper wires
            step(5,
                "Connect the jumper wires between the motor drivers and the Arduino") {
                view(t=[9,26,54], r=[38,0,161], d=766);
                view(title="plan", t=[0,32,16], r=[0,0,0], d=337);

                // Left
                JumperWire(
                    type = JumperWire_FF4,
                    con1 = attachedConnector(
                        LogoBot_Con_LeftMotorDriver,
                        ULN2003DriverBoard_Con_UpperLeft,
                        ULN2003DriverBoard_Con_Arduino,
                        ExplodeSpacing=20
                    ),
                    con2 = attachedConnector(
                        LogoBot_Con_GridFixing(0,4,-90),
                        DefConDown,
                        [[-7.5, -5, 7],[0,0,-1],0,0,0],
                        ExplodeSpacing=20
                    ),
                    length = 100,
                    conVec1 = attachedDirection(
                        LogoBot_Con_LeftMotorDriver,
                        ULN2003DriverBoard_Con_UpperLeft,
                        [0,[1,0,0]]),
                    conVec2 = [-1,0,0],
                    midVec = [0.5,-1,0]
                );

                // Right
                JumperWire(
                    type = JumperWire_FF4,
                    con1 = attachedConnector(
                        LogoBot_Con_RightMotorDriver,
                        ULN2003DriverBoard_Con_UpperRight,
                        ULN2003DriverBoard_Con_Arduino,
                        ExplodeSpacing=20
                    ),
                    con2 = attachedConnector(
                        LogoBot_Con_GridFixing(0,4,-90),
                        DefConDown,
                        [[-7.5, -15, 7],[0,0,-1],0,0,0],
                        ExplodeSpacing=20
                    ),
                    length = 100,
                    conVec1 = attachedDirection(
                        LogoBot_Con_RightMotorDriver,
                        ULN2003DriverBoard_Con_UpperRight,
                        [0,[1,0,0]]),
                    conVec2 = [-1,0,0],
                    midVec = [0.5,-1,0]
                );
            }

            // TODO: Add velcro or to hold down battery pack
            // Battery assembly
            step(6, "Clip in the battery pack") {
                view(t=[-6,7,19], r=[64,1,212], d=625);

                attach(DefConDown, DefConDown, ExplodeSpacing=20)
                    translate([-25, -45, 12])
                        rotate([90, 0, 90]) {
                            BatteryPack(BatteryPack_AA);
                            battery_pack_double(BatteryPack_AA, 2, 4);
                        }
            }

            // TODO: Replace with toggle switch
            //Power Switch
            *step(7, "Clip the power switch into place") {
                view(t=[-6,7,19], r=[64,1,212], d=625);

                attach(LogoBot_Con_SlideSwitch, DefConUp)
                    SlideSwitch();
            }

            // TODO: LED
            *step(8, "Clip the LED into place") {
                view(t=[-6,7,19], r=[64,1,212], d=625);

                attach(DefConDown, DefConDown, ExplodeSpacing=20)
                    translate([0, -10, BaseDiameter/2])
                    LED();
            }

            // TODO: Piezo
            *step(9, "Clip the piezo sounder into place") {
                view(t=[-6,7,19], r=[64,1,212], d=625);

                attach(DefConDown, DefConDown, ExplodeSpacing=20)
                    translate([-37, -32, 10])
                    murata_7BB_12_9();
            }

            // Caster
            step(10, "Align the caster assembly with the base, then insert a short pin to lock it to the base") {
                view(t=[-6,7,19], r=[115,1,26], d=625);

                attach(LogoBot_Con_Caster, MarbleCaster_Con_Default, ExplodeSpacing=15)
                    MarbleCasterAssembly();

                attach(offsetConnector(invertConnector(LogoBot_Con_Caster), [0,0,dw]), MarbleCaster_Con_Default)
                    pintack(side=false, h=dw+0.6+2+1.5, lh=2, bh=2);
            }

            // Conditional Design Elements
            // ---------------------------

            // PenLift
            //   Placeholder of a micro servo to illustrate having conditional design elements
            if (PenLift) {
                step(12, "Fit the pen lift assembly using two of the pins and zip tie the servo under the base.") {
                    view(t=[-6,7,19], r=[64,1,212], d=625);

					attach(LogoBot_Con_PenLift_Front, PenLiftSlider_Con_BaseFront)
						PenLiftAssembly();

					attach(LogoBot_Con_PenLift_Front, offsetConnector(DefConDown, [0, 0, 2 + 2.5]))
						pintack(side=false, h=2.5+4+2.5, bh=2);
					attach(LogoBot_Con_PenLift_Rear, offsetConnector(DefConDown, [0, 0, 2 + 2.5]))
						pintack(side=false, h=2.5+4+2.5, bh=2);

                    attach(LogoBot_Con_PenLiftServo, MicroServo_Con_Horn) {
						MicroServo();
						attach(MicroServo_Con_Horn, ServoHorn_Con_Default)
							ServoHorn();
					}
                }
            }

            // Shell + fixings
			if (Shell) {
				step(PenLift ? 13 : 12,
					"Push the shell down onto the base and twist to lock into place") {
					view(t=[11,-23,65], r=[66,0,217], d=625);

					attach(DefConDown, DefConDown, ExplodeSpacing=BaseDiameter/2)
						ShellAssembly(PenLift? false : true);
				}
            }
        }

	}
}


Create R2-3D.scad

Create R2-3D.scad

the main compile file, just includes everything

include <AluFrame.scad>
include <Cornermount.scad>
include <MountPlate.scad>
include <StepperMount.scad>

error: pathspec 'R2-3D.scad' did not match any file(s) known to git


The ring width was parameterized.

The ring width was parameterized.

// source: https://www.thingiverse.com/thing:2989760

echo("hello nrs");

// Variables' comments formatted for Thingiverse Customizer.

// Cut a notch out of the rings to preview how the interior fits. Disable before printing!
preview_cut = false;

// Style of rings. Biconical rings can slide axially as well as tumbling; spherical rings are close-fitting and more likely to jam.
type = "biconical";  // [biconical:Biconical, spherical:Spherical, spherical_one_hole:Spherical (closed on top)]

// For biconical rings, the faces of the rings will have a radius this many millimeters smaller than the center. Ignored for spherical rings.
bicone_opening_reduction = 3.1;

// For spherical rings, the width of a centered thinner band of each ring, in millimeters. This is intended for rings printed with transparent material in that band. Set to zero to disable.
spherical_thin_band_width = 0;

// For spherical rings, the thickness of a centered thinner band of each ring, in millimeters. This is intended for rings printed with transparent material in that band. Cannot be larger than the ring thickness. Ignored if thin band width is zero.
spherical_thin_band_thickness = 0.6;

// For spherical rings, the amount of space between adjacent rings, in millimeters. Set this as low as you can without the surfaces fusing when printed.
spherical_gap = 0.6;

/* [Hidden] */

spherical = type == "spherical" || type == "spherical_one_hole";

// Chosen constants and not-exposed calculations
SMOOTH_FACETS = 240;

//gap = spherical ? spherical_gap : bicone_gap;

facets = spherical ? SMOOTH_FACETS / 2 : SMOOTH_FACETS;  // make spheres somewhat less super-expensive

// Derived values / aliases
zradius = stockNestedRings_ringWidth() / 2;

module nestedRings(outer_radius, ring_count)
{
    difference()
	{
        main(outer_radius = outer_radius,
			 ring_count = ring_count);

        if (preview_cut)
        translate([0, 0, -500])
        cube([1000, 1000, 1000]);
    }
}

module main(outer_radius,
		    ring_count)
{
	step = stockNestedRings_step();

	ring_thickness = stockNestedRings_ringThickness();

//	ring_count = 1;

    for (i = [0:ring_count - 1])
	{
        radius = outer_radius - i * step;

        unit(r2 = radius,
			 ring_thickness = ring_thickness);
    }
}

module unit(r2, ring_thickness) {
    if (spherical) {
        rotate_extrude($fn = facets)
        intersection() {
            difference() {
                circle(r = r2, $fn = facets);
                circle(r = r2 - ring_thickness, $fn = facets);

                if (spherical_thin_band_width > 0) {
                    minkowski() {
                        intersection() {
                            circle(r = r2 - ring_thickness, $fn = facets);

                            translate([0, -spherical_thin_band_width / 2])
                            square([r2 * 1.5, spherical_thin_band_width]);
                        }

                        // diamond shape to create 45° overhangs
                        circle(r=ring_thickness - spherical_thin_band_thickness, $fn=4);
                    }
                }
            }

            translate([0, -zradius])
            square([r2 * 1.5, type == "spherical_one_hole" ? 1000 : zradius * 2]);
        }

    } else if (type == "biconical") {
        r1 = r2 - bicone_opening_reduction;
        //linear_extrude(2)
        rotate_extrude($fn = facets)
        polygon([
            [r1, zradius],
            [r2, 0],
            [r1, -zradius],
            [r1 - ring_thickness, -zradius],
            [r2 - ring_thickness, 0],
            [r1 - ring_thickness, zradius],
        ]);

    } else {
        echo("Error: ring type not valid");
    }
}

// Constants and derived values follow

// Thickness of each ring, in millimeters. Setting this too low will result in rings which pop apart too easily.
function stockNestedRings_ringThickness() = 1.4;

// Radius of the midline of the largest ring, in millimeters.
function stockNestedRings_outerRadius() = 33;

// Number of rings to generate. The size is determined starting from the center, so more rings is larger.
function stockNestedRings_ringCount() = 6;

// Width of the rings (z axis height as printed), in millimeters.
function stockNestedRings_ringWidth() = 20;

// For biconical rings, the amount of space between adjacent rings (when they are concentric), in millimeters. Must be smaller than Bicone Opening Reduction to prevent the rings from just falling apart.
function stockNestedRings_biconeGap() = 2;

function stockNestedRings_step() = stockNestedRings_biconeGap() + stockNestedRings_ringThickness();

// source: https://www.thingiverse.com/thing:2989760

echo("hello nrs");

// Variables' comments formatted for Thingiverse Customizer.

// Cut a notch out of the rings to preview how the interior fits. Disable before printing!
preview_cut = false;

// Style of rings. Biconical rings can slide axially as well as tumbling; spherical rings are close-fitting and more likely to jam.
type = "biconical";  // [biconical:Biconical, spherical:Spherical, spherical_one_hole:Spherical (closed on top)]

// Width of the rings (z axis height as printed), in millimeters.
ring_width = 20;

// For biconical rings, the faces of the rings will have a radius this many millimeters smaller than the center. Ignored for spherical rings.
bicone_opening_reduction = 3.1;

// For spherical rings, the width of a centered thinner band of each ring, in millimeters. This is intended for rings printed with transparent material in that band. Set to zero to disable.
spherical_thin_band_width = 0;

// For spherical rings, the thickness of a centered thinner band of each ring, in millimeters. This is intended for rings printed with transparent material in that band. Cannot be larger than the ring thickness. Ignored if thin band width is zero.
spherical_thin_band_thickness = 0.6;

// For spherical rings, the amount of space between adjacent rings, in millimeters. Set this as low as you can without the surfaces fusing when printed.
spherical_gap = 0.6;

/* [Hidden] */

spherical = type == "spherical" || type == "spherical_one_hole";

// Chosen constants and not-exposed calculations
SMOOTH_FACETS = 240;

//gap = spherical ? spherical_gap : bicone_gap;

facets = spherical ? SMOOTH_FACETS / 2 : SMOOTH_FACETS;  // make spheres somewhat less super-expensive

// Derived values / aliases
zradius = ring_width / 2;

module nestedRings(outer_radius, ring_count)
{
	echo("hello nr");

    difference()
	{
        main(outer_radius = outer_radius,
			 ring_count = ring_count);

        if (preview_cut)
        translate([0, 0, -500])
        cube([1000, 1000, 1000]);
    }
}

module main(outer_radius,
		    ring_count)
{
	echo("hello m");

	step = stockNestedRings_step();

	ring_thickness = stockNestedRings_ringThickness();

    for (i = [0:ring_count - 1])
	{
        radius = outer_radius - i * step;

        unit(r2 = radius,
			 ring_thickness = ring_thickness);
    }
}

module unit(r2, ring_thickness) {
    if (spherical) {
        rotate_extrude($fn = facets)
        intersection() {
            difference() {
                circle(r = r2, $fn = facets);
                circle(r = r2 - ring_thickness, $fn = facets);

                if (spherical_thin_band_width > 0) {
                    minkowski() {
                        intersection() {
                            circle(r = r2 - ring_thickness, $fn = facets);

                            translate([0, -spherical_thin_band_width / 2])
                            square([r2 * 1.5, spherical_thin_band_width]);
                        }

                        // diamond shape to create 45° overhangs
                        circle(r=ring_thickness - spherical_thin_band_thickness, $fn=4);
                    }
                }
            }

            translate([0, -zradius])
            square([r2 * 1.5, type == "spherical_one_hole" ? 1000 : zradius * 2]);
        }

    } else if (type == "biconical") {
        r1 = r2 - bicone_opening_reduction;
        //linear_extrude(2)
        rotate_extrude($fn = facets)
        polygon([
            [r1, zradius],
            [r2, 0],
            [r1, -zradius],
            [r1 - ring_thickness, -zradius],
            [r2 - ring_thickness, 0],
            [r1 - ring_thickness, zradius],
        ]);

    } else {
        echo("Error: ring type not valid");
    }
}

// Constants and derived values follow

// Thickness of each ring, in millimeters. Setting this too low will result in rings which pop apart too easily.
function stockNestedRings_ringThickness() = 1.4;

// Radius of the midline of the largest ring, in millimeters.
function stockNestedRings_outerRadius() = 33;

// Number of rings to generate. The size is determined starting from the center, so more rings is larger.
function stockNestedRings_ringCount() = 6;

// For biconical rings, the amount of space between adjacent rings (when they are concentric), in millimeters. Must be smaller than Bicone Opening Reduction to prevent the rings from just falling apart.
function stockNestedRings_biconeGap() = 2;

function stockNestedRings_step() = stockNestedRings_biconeGap() + stockNestedRings_ringThickness();


Checkpoint scad file

Checkpoint scad file

// in mm

// Tube module -- radius is outer radius
module tube(height, radius, wall) {
  difference() {
    cylinder(h=height, r=radius);
    translate([0, 0, -1]) cylinder(h=height+2, r=radius-wall);
  }
}

// Flange module
module flange() {
  color("gray") tube(6, 57/2, 7/2+12/2);
  translate([0, 0, 6]) color("gray") tube(21.5-6, 50/2, 12/2);
}

// --- main unit --

// Top flange
flange();

// Flange connector
translate([0, 0, 31/2+6]) color("blue") tube(4, 50/2, 12/2);
// TODO add screw holes

// Camera
translate([0, 0, -2]) union() {
  translate([-25/2+2.5, -10, 22.3]) cube([20, 5, 2.5]);
  translate([-25/2, -10, 22.3]) cube([25, 24.5, 1]);
  cylinder(h=22.3, r=8.3);
}

// Camera sled
//translate([2.5, 0, 59-19]) cube([25, 24.5, 2]);

// Electronics sled
//translate([2.5, 0, 59-19]) cube([15, 11, 19]);

// Pi Zero
translate([-15, -4, 35]) cube([30, 1, 65]);

// Ethernet
translate([-10, 2, 35+65-59]) union() {
  cube([20, 1, 59]);
  translate([2.5, 0, 59-19]) cube([15, 11, 19]);
}

// Power supply
translate([-9, 6, 35]) cube([18, 5, 17]);

// -- tube and bottom flange, attached later --

// Bottom flange
translate([0, 0, 132]) rotate([0, 180, 0]) flange();

// Tube
color("white", 0.1) translate([0, 0, 6]) tube(120, 57.2/2, 3.2);

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


cleanup

cleanup

use <../../../basics/rounded-edges/doughnuts/doughnuts.scad>;

use <../../../basics/rounded-edges/rounded-pyramid/rounded-pyramid.scad>;

use <../../../shapes/geometry/arc/extruded/extruded-arc.scad>;

module halfCirclePhoneStand(height = 19.125,
                            minkowskiSphereRadius = 0.5,
                            arcRadius = 36,
                            arcRadiusExtension = 1.5)
{
    halfCirclePhoneStand_cradle(height = height,
                                minkowskiSphereRadius = minkowskiSphereRadius,
                                radius = arcRadius,
                                radiusExtension = arcRadiusExtension);

    halfCirclePhoneStand_stand(height = height,
                               minkowskiSphereRadius = minkowskiSphereRadius);
}

// sub-modules and functions follow

function halfCirclePhoneStand_cradle_cutout_zLength() = 4.2;

function halfCirclePhoneStand_cradle_cutout_zTranslate(minkowskiSphereRadius,
                                                        bedHeight,
                                                        zLength) = minkowskiSphereRadius + (bedHeight / 2.0) - (zLength / 2.0);

module halfCirclePhoneStand_cradle(height,
                                   minkowskiSphereRadius,
                                   radius,
                                   radiusExtension)
{
    xTranslate = 30;
    yTranslate = 32.7;

    difference()
    {
        halfCirclePhoneStand_cradle_bed(height = height,
                                           minkowskiSphereRadius = minkowskiSphereRadius,
                                           radius = radius,
                                           radiusExtension = radiusExtension,
                                           xTranslate = xTranslate,
                                           yTranslate = yTranslate);

        halfCirclePhoneStand_cradle_cutout(bedHeight = height,
                                        minkowskiSphereRadius = minkowskiSphereRadius,
                                        radius = radius,
                                        radiusExtension = radiusExtension,
                                        bed_xTranslate = xTranslate,
                                        bed_yTranslate = yTranslate);
    }

    halfCirclePhoneStand_cradle_ends(height = height,
                                     minkowskiSphereRadius = minkowskiSphereRadius);
}

module halfCirclePhoneStand_cradle_bed(height,
                                       minkowskiSphereRadius,
                                       radius,
                                       radiusExtension,
                                       xTranslate,
                                       yTranslate)
{
    zTranslate = minkowskiSphereRadius;
    color("magenta")
    translate([xTranslate, yTranslate, zTranslate])
    rotate([0, 0, 213])
    roundedRectangularArc(angle = 150,
                          height = height,
                          minkowskiRadius = minkowskiSphereRadius,
                          radius = radius,
                          radiusExtension = radiusExtension);
}

module halfCirclePhoneStand_cradle_cutout(bedHeight,
                                          minkowskiSphereRadius,
                                          radius,
                                          radiusExtension,
                                          bed_xTranslate,
                                          bed_yTranslate)
{
    zLength = halfCirclePhoneStand_cradle_cutout_zLength();

    cutoutRadiusExtension = radiusExtension + 3;

    xTranslate = bed_xTranslate + 0.2;
    yTranslate = bed_yTranslate + 0.2;
    zTranslate = halfCirclePhoneStand_cradle_cutout_zTranslate(minkowskiSphereRadius = minkowskiSphereRadius,
                                                                bedHeight = bedHeight,
                                                                zLength = zLength);

    color("DarkTurquoise")
    translate([xTranslate, yTranslate, zTranslate])
    rotate([0, 0, 227])
    roundedRectangularArc(angle = 37,
                          height = zLength,
                          minkowskiRadius = minkowskiSphereRadius,
                          radius = radius,
                          radiusExtension = cutoutRadiusExtension);
}

module halfCirclePhoneStand_cradle_ends(height,
                                        minkowskiSphereRadius)
{
    xTranslate = 40.3;
    yTranslate = -2.5;
    zTranslate = minkowskiSphereRadius;

    // bottom end
    color("PaleTurquoise")
    translate([xTranslate, yTranslate, zTranslate])
    roundedPyramid(h = height,
                   cornerRadius = minkowskiSphereRadius,
                   cylinderFn = 20,
                   r1 = 2.57,
                   r2 = 2.57,
                   sides = 30);
}

module halfCirclePhoneStand_stand(height,
                                  minkowskiSphereRadius)
{
    halfCirclePhoneStand_stand_top(height = height,
                                   minkowskiSphereRadius = minkowskiSphereRadius);

    halfCirclePhoneStand_stand_connectorBar(bedHeight = height,
                                            minkowskiSphereRadius = minkowskiSphereRadius);

    halfCirclePhoneStand_stand_base(height = height,
                                    minkowskiSphereRadius = minkowskiSphereRadius);
}

module halfCirclePhoneStand_stand_base(height,
                                       minkowskiSphereRadius)
{
    color("green")
    translate([0, 0, minkowskiSphereRadius])
    roundDoughnut(height = height,
                innerRadius = 4.9,      // 4.9
                outerRadius = 5.59,     // 5.59
                minkowskiSphereRadius = minkowskiSphereRadius);
}

module halfCirclePhoneStand_stand_connectorBar(minkowskiSphereRadius, bedHeight)
{
    xLength = 5;
    yLength = 3;
    zLength = halfCirclePhoneStand_cradle_cutout_zLength() + minkowskiSphereRadius;

    xTranslate = 5;
    yTranslate = 5;
    zTranslate = halfCirclePhoneStand_cradle_cutout_zTranslate(minkowskiSphereRadius = minkowskiSphereRadius,
                                                                bedHeight = bedHeight,
                                                                zLength = zLength)
                    + (zLength / 2.0);

    color("blue")
    translate([xTranslate, yTranslate, zTranslate])
    rotate([0, 0, 45])
    cube([xLength, yLength, zLength], center = true);
}

module halfCirclePhoneStand_stand_top(height,
                                      minkowskiSphereRadius)
{
    xTranslate = 8.105;  // 8.05     // 8.1
    yTranslate = 8.85;  // 8.85     // 8.9

    color("green")
    translate([xTranslate, yTranslate, minkowskiSphereRadius])
    roundDoughnut(height = height,
                innerRadius = 1.9,  // 2.0
                outerRadius = 2.6,  // 2.6
                minkowskiSphereRadius = minkowskiSphereRadius);
}

use <../../../basics/rounded-edges/doughnuts/doughnuts.scad>;

use <../../../basics/rounded-edges/rounded-pyramid/rounded-pyramid.scad>;

use <../../../shapes/geometry/arc/extruded/extruded-arc.scad>;

module halfCirclePhoneStand(height = 19.125,
                            minkowskiSphereRadius = 0.5,
                            arcRadius = 36,
                            arcRadiusExtension = 1.5)
{
    halfCirclePhoneStand_cradle(height = height,
                                minkowskiSphereRadius = minkowskiSphereRadius,
                                radius = arcRadius,
                                radiusExtension = arcRadiusExtension);

    halfCirclePhoneStand_stand(height = height,
                               minkowskiSphereRadius = minkowskiSphereRadius);
}

// sub-modules and functions follow

function halfCirclePhoneStand_cradle_cutout_zLength() = 4.2;

function halfCirclePhoneStand_cradle_cutout_zTranslate(minkowskiSphereRadius,
                                                        bedHeight,
                                                        zLength) = minkowskiSphereRadius + (bedHeight / 2.0) - (zLength / 2.0);

module halfCirclePhoneStand_cradle(height,
                                   minkowskiSphereRadius,
                                   radius,
                                   radiusExtension)
{
    xTranslate = 30;
    yTranslate = 32.7;

    difference()
    {
        halfCirclePhoneStand_cradle_bed(height = height,
                                           minkowskiSphereRadius = minkowskiSphereRadius,
                                           radius = radius,
                                           radiusExtension = radiusExtension,
                                           xTranslate = xTranslate,
                                           yTranslate = yTranslate);

        halfCirclePhoneStand_cradle_cutout(bedHeight = height,
                                        minkowskiSphereRadius = minkowskiSphereRadius,
                                        radius = radius,
                                        radiusExtension = radiusExtension,
                                        bed_xTranslate = xTranslate,
                                        bed_yTranslate = yTranslate);
    }

    halfCirclePhoneStand_cradle_ends(height = height,
                                     minkowskiSphereRadius = minkowskiSphereRadius);
}

module halfCirclePhoneStand_cradle_bed(height,
                                       minkowskiSphereRadius,
                                       radius,
                                       radiusExtension,
                                       xTranslate,
                                       yTranslate)
{
    zTranslate = minkowskiSphereRadius;
    color("magenta")
    translate([xTranslate, yTranslate, zTranslate])
    rotate([0, 0, 213])
    roundedRectangularArc(angle = 150,
                          height = height,
                          minkowskiRadius = minkowskiSphereRadius,
                          radius = radius,
                          radiusExtension = radiusExtension);
}

module halfCirclePhoneStand_cradle_cutout(bedHeight,
                                          minkowskiSphereRadius,
                                          radius,
                                          radiusExtension,
                                          bed_xTranslate,
                                          bed_yTranslate)
{
    zLength = halfCirclePhoneStand_cradle_cutout_zLength();

    cutoutRadiusExtension = radiusExtension + 3;

    xTranslate = bed_xTranslate + 0.2;
    yTranslate = bed_yTranslate + 0.2;
    zTranslate = halfCirclePhoneStand_cradle_cutout_zTranslate(minkowskiSphereRadius = minkowskiSphereRadius,
                                                                bedHeight = bedHeight,
                                                                zLength = zLength);

    color("DarkTurquoise")
    translate([xTranslate, yTranslate, zTranslate])
    rotate([0, 0, 227])
    roundedRectangularArc(angle = 37,
                          height = zLength,
                          minkowskiRadius = minkowskiSphereRadius,
                          radius = radius,
                          radiusExtension = cutoutRadiusExtension);
}

module halfCirclePhoneStand_cradle_ends(height,
                                        minkowskiSphereRadius)
{
    xTranslate = 40.3;     // 40.57
    yTranslate = -2.5;      // -2.0
    zTranslate = minkowskiSphereRadius;

    // bottom end
    color("PaleTurquoise")
    translate([xTranslate, yTranslate, zTranslate])
    roundedPyramid(h = height,
                   cornerRadius = minkowskiSphereRadius,
                   cylinderFn = 20,
                   r1 = 2.57,      // 3
                   r2 = 2.57,      // 3
                   sides = 30);
}

module halfCirclePhoneStand_stand(height,
                                  minkowskiSphereRadius)
{
    halfCirclePhoneStand_stand_top(height = height,
                                   minkowskiSphereRadius = minkowskiSphereRadius);

    halfCirclePhoneStand_stand_connectorBar(bedHeight = height,
                                            minkowskiSphereRadius = minkowskiSphereRadius);

    halfCirclePhoneStand_stand_base(height = height,
                                    minkowskiSphereRadius = minkowskiSphereRadius);
}

module halfCirclePhoneStand_stand_base(height,
                                       minkowskiSphereRadius)
{
    color("green")
    translate([0, 0, minkowskiSphereRadius])
    roundDoughnut(height = height,
                innerRadius = 4.9,      // 4.9
                outerRadius = 5.59,     // 5.59
                minkowskiSphereRadius = minkowskiSphereRadius);
}

module halfCirclePhoneStand_stand_connectorBar(minkowskiSphereRadius, bedHeight)
{
    xLength = 5;
    yLength = 3;
    zLength = halfCirclePhoneStand_cradle_cutout_zLength() + minkowskiSphereRadius;

    xTranslate = 5;
    yTranslate = 5;
    zTranslate = halfCirclePhoneStand_cradle_cutout_zTranslate(minkowskiSphereRadius = minkowskiSphereRadius,
                                                                bedHeight = bedHeight,
                                                                zLength = zLength)
                    + (zLength / 2.0);

    color("blue")
    translate([xTranslate, yTranslate, zTranslate])
    rotate([0, 0, 45])
    cube([xLength, yLength, zLength], center = true);
}

module halfCirclePhoneStand_stand_top(height,
                                      minkowskiSphereRadius)
{
    xTranslate = 8.105;  // 8.05     // 8.1
    yTranslate = 8.85;  // 8.85     // 8.9

    color("green")
    translate([xTranslate, yTranslate, minkowskiSphereRadius])
    roundDoughnut(height = height,
                innerRadius = 1.9,  // 2.0
                outerRadius = 2.6,  // 2.6
                minkowskiSphereRadius = minkowskiSphereRadius);
}


transforms: add spreadxy/xyz/zx/yz

transforms: add spreadxy/xyz/zx/yz

/*include <../scad-utils/transformations.scad>*/

include <system.scad>
use <misc.scad>

module t(dist)
{
    assert(dist!=U, "t(): dist==U");
    assert(is_list(dist), dist);
    assert(len(dist)==3, "t(): len(dist)!=3");
    translate(dist)
    children();
}

module tx(dist)
{
    assert(dist!=U, "tx(): dist==U");
    assert(is_num(dist), "tx(): dist is not a number");
    translate(X*dist)
    children();
}

module ty(dist)
{
    assert(dist!=U, "ty(): dist==U");
    assert(is_num(dist), "ty(): dist is not a number");
    translate(Y*dist)
    children();
}

module tz(dist)
{
    assert(dist!=U, "tz(): dist==U");
    assert(is_num(dist), "tz(): dist is not a number");
    translate(Z*dist)
    children();
}

module txy(off)
{
    assert(off!=U, "txy(): off==U");
    assert(len(off)==3, "txy(): len(off)!=3");
    tx(off.x)
    ty(off.y)
    children();
}

module txz(off)
{
    assert(off!=U, "txz(): off==U");
    assert(len(off)==3, "txz(): len(off)!=3");
    tx(off.x)
    tz(off.z)
    children();
}

module tyz(off)
{
    assert(off!=U, "tyz(): off==U");
    assert(len(off)==3, "tyz(): len(off)!=3");
    ty(off.y)
    tz(off.z)
    children();
}

module r(a, v)
{
    if(is_list(a))
    {
        assert(is_undef(v), v);
        assert_v3n(a);
        rotate(a)
        children();
    }
    else
    {
        assert(is_num(a), a);
        assert_v3n(v);
        rotate(a,v)
        children();
    }
}

module rx(degrees)
{
    rotate(X*degrees)
    children();
}

module ry(degrees)
{
    rotate(Y*degrees)
    children();
}

module rz(degrees)
{
    rotate(Z*degrees)
    children();
}

module mx(m=true)
{
    assert(!is_undef(m));
    assert(is_bool(m));
    mirror(X*(m?1:0))
    children();
}

module my(m=true)
{
    assert(!is_undef(m));
    assert(is_bool(m));
    mirror(Y*(m?1:0))
    children();
}

module mz(m=true)
{
    assert(!is_undef(m));
    assert(is_bool(m));
    mirror(Z*(m?1:0))
    children();
}

// translate children
module position(positions)
{
    assert(positions!=U, "positions==U");
    for(pos=positions)
    translate(pos)
    children();
}

// translate children
module linup(arr=U)
{
    if($children>0)
    {
        for (i = [0 : $children-1])
        translate(arr[i]) children(i);
    }
}

module stack(dist=10, distances=U, axis=Z)
{
    if($children>0)
    {
        if(dist == U && distances != U)
        {
            for (i = [0:len(distances)-1])
            {
                offset = v_sum(distances,i);
                translate(axis*offset)
                    child(i);
            }
        }
        else if(dist != U && distances == U)
        {
            for (i = [0 : $children-1])
                translate(axis*(dist*i))
                    children(i);
        }
    }
}

module spread(axis=N, dist=0, iter=[-1,1])
{
    assert(is_list(axis));
    assert(is_num(dist));
    assert(is_list(iter));

    for(i=iter)
    translate(i*axis*dist)
    children();
}

module spreadx(dist=0)
{
    spread(axis=X,dist=dist)
    children();
}

module spreadxy(dist=0)
{
    spreadx(dist)
    spready(dist)
    children();
}

module spreadxyz(dist=0)
{
    spreadx(dist)
    spready(dist)
    spreadz(dist)
    children();
}

module spreadxz(dist=0)
{
    spreadx(dist)
    spready(dist)
    children();
}

module spready(dist=0)
{
    spread(axis=Y,dist=dist)
    children();
}

module spreadyz(dist=0)
{
    spready(dist)
    spreadz(dist)
    children();
}

module spreadz(dist=0)
{
    spread(axis=Z,dist=dist)
    children();
}

module orient(axis=U, axis_ref=U, roll=0, extra_roll, extra_roll_orient)
{
    rotate(extra_roll_orient==U||extra_roll==U?0:extra_roll*extra_roll_orient)

    // orient to reference axis
    /*rotate(axis_ref==U?0:_orient_angles(axis_ref))*/
    /*multmatrix(axis_ref==U?0:v_rotate(90, axis_ref))*/
    rotate(axis_ref==U?0:_orient_angles(axis_ref))

    // roll around orient axis
    rotate(axis==U?0:roll*axis)

    // orient to axis
    /*multmatrix(axis==U?0:v_rotate(90, axis))*/
    rotate(axis==U?0:_orient_angles(axis))
    children();
}

module orient_(axis=U, axis_ref=U, roll=0, extra_roll, extra_roll_orient)
{
    rotate(extra_roll_orient==U||extra_roll==U?0:extra_roll*extra_roll_orient)

    // orient to reference axis
    multmatrix(axis_ref==U?0:v_rotate(90, axis_ref))

    // roll around orient axis
    rotate(axis==U?0:roll*axis)

    // orient to axis
    multmatrix(axis_ref==U?0:v_rotate(90, axis))
    children();
}

function _orient_bounds(orient, size) =
    (_rotate_matrix(_orient_angles(orient)) * [size.x,size.y,size.z,1]);

function _orient_t(orient, align, size) =
    let(bounds = _orient_bounds(orient, size))
    (hadamard(align, [abs(bounds.x/2),abs(bounds.y/2),abs(bounds.z/2)]));

module size_align(size=[10,10,10], extra_size=N, align=N, extra_align=N, orient=Z, orient_ref=Z, roll=0, extra_roll, extra_roll_orient)
{
    assert(orient != N);
    assert_v3n(align);
    assert_v3n(orient);
    assert_v3n(size);
    t = orient==U?N:_orient_t(orient, align, size);
    extra_t = (orient==U||extra_size==U||extra_size==[U,U,U]||extra_size==[0,0,0]||extra_align==U) ?
        N : _orient_t(orient, extra_align, extra_size);
    assert_v3n(t);
    assert_v3n(extra_t);
    /*assert(extra_t[0] < 0 && extra_t[0] == 0 && extra_t[0] > 0 || extra_t[);*/
    translate(t+extra_t)
    {
        orient(axis=orient, axis_ref=orient_ref, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
        {
            children();
        }
    }
}

module hull_pairwise()
{
    for (i= [1:1:$children-1])
    {
        hull()
        {
            children(i-1);
            children(i);
        }
    }
}

module proj_extrude_axis(axis=Z, h=1, offset=0, cut=false)
{
    translate(-offset*axis)
    hull()
    {
        orient_(axis=axis, axis_ref=Z)
        {
            linear_extrude(h, center=false)
            projection(cut=cut)
            orient_(axis=Z, axis_ref=axis)
            translate(offset*axis)
            children();

            orient_(axis=Z, axis_ref=axis)
            translate(offset*axis)
            children();
        }
    }
}

module all_axes()
{
    for($axis=concat(AXES,-AXES))
    {
        $color = v_abs($axis*.3 + v_clamp(v_sign($axis),0,1)*.7);
        children();
    }
}

// test proj_extruder_axis
if(false)
{
    all_axes()
    color($color)
    translate(5*$axis)
    proj_extrude_axis(axis=$axis)
    translate(20*$axis)
    sphere(d=10);
}

if(false)
{
    translate(Y*10)
    proj_extrude_axis(axis=Y, offset=10)
    {
        sphere(d=10);
    }
}

module proj_extrude_axis_part(axis=Z, h=1, offset=0, cut=false)
{
    translate(-offset*axis)
    {
        orient_(axis=axis, axis_ref=Z)
        {
            hull()
            linear_extrude(offset, center=false)
            projection(cut=cut)
            orient_(axis=Z, axis_ref=axis)
            translate(offset*axis)
            children();

            orient_(axis=Z, axis_ref=axis)
            translate(offset*axis)
            children();
        }
    }
}

if(false)
{
    tz(10)
    proj_extrude_axis_part(axis=Z, offset=10)
    {
        x = 15;
        cylinder(d=5, h=20);

        tx(x)
        cylinder(d=5, h=20);
    }
}

// translate by pos, with extra (depending on the sign of pos)
// an "explode" function
module te(pos, extra=[0,0,0])
{
    assert(len(pos) == 3);
    assert(len(extra) == 3);

    t(pos + v_mul(extra , v_sign(pos)))
    children();
}

/*include <../scad-utils/transformations.scad>*/

include <system.scad>
use <misc.scad>

module t(dist)
{
    assert(dist!=U, "t(): dist==U");
    assert(is_list(dist), dist);
    assert(len(dist)==3, "t(): len(dist)!=3");
    translate(dist)
    children();
}

module tx(dist)
{
    assert(dist!=U, "tx(): dist==U");
    assert(is_num(dist), "tx(): dist is not a number");
    translate(X*dist)
    children();
}

module ty(dist)
{
    assert(dist!=U, "ty(): dist==U");
    assert(is_num(dist), "ty(): dist is not a number");
    translate(Y*dist)
    children();
}

module tz(dist)
{
    assert(dist!=U, "tz(): dist==U");
    assert(is_num(dist), "tz(): dist is not a number");
    translate(Z*dist)
    children();
}

module txy(off)
{
    assert(off!=U, "txy(): off==U");
    assert(len(off)==3, "txy(): len(off)!=3");
    tx(off.x)
    ty(off.y)
    children();
}

module txz(off)
{
    assert(off!=U, "txz(): off==U");
    assert(len(off)==3, "txz(): len(off)!=3");
    tx(off.x)
    tz(off.z)
    children();
}

module tyz(off)
{
    assert(off!=U, "tyz(): off==U");
    assert(len(off)==3, "tyz(): len(off)!=3");
    ty(off.y)
    tz(off.z)
    children();
}

module r(a, v)
{
    if(is_list(a))
    {
        assert(is_undef(v), v);
        assert_v3n(a);
        rotate(a)
        children();
    }
    else
    {
        assert(is_num(a), a);
        assert_v3n(v);
        rotate(a,v)
        children();
    }
}

module rx(degrees)
{
    rotate(X*degrees)
    children();
}

module ry(degrees)
{
    rotate(Y*degrees)
    children();
}

module rz(degrees)
{
    rotate(Z*degrees)
    children();
}

module mx(m=true)
{
    assert(!is_undef(m));
    assert(is_bool(m));
    mirror(X*(m?1:0))
    children();
}

module my(m=true)
{
    assert(!is_undef(m));
    assert(is_bool(m));
    mirror(Y*(m?1:0))
    children();
}

module mz(m=true)
{
    assert(!is_undef(m));
    assert(is_bool(m));
    mirror(Z*(m?1:0))
    children();
}

// translate children
module position(positions)
{
    assert(positions!=U, "positions==U");
    for(pos=positions)
    translate(pos)
    children();
}

// translate children
module linup(arr=U)
{
    if($children>0)
    {
        for (i = [0 : $children-1])
        translate(arr[i]) children(i);
    }
}

module stack(dist=10, distances=U, axis=Z)
{
    if($children>0)
    {
        if(dist == U && distances != U)
        {
            for (i = [0:len(distances)-1])
            {
                offset = v_sum(distances,i);
                translate(axis*offset)
                    child(i);
            }
        }
        else if(dist != U && distances == U)
        {
            for (i = [0 : $children-1])
                translate(axis*(dist*i))
                    children(i);
        }
    }
}

module spread(axis=N, dist=0, iter=[-1,1])
{
    assert(is_list(axis));
    assert(is_num(dist));
    assert(is_list(iter));

    for(i=iter)
    translate(i*axis*dist)
    children();
}

module spreadx(dist=0)
{
    spread(axis=X,dist=dist)
    children();
}

module spready(dist=0)
{
    spread(axis=Y,dist=dist)
    children();
}

module spreadz(dist=0)
{
    spread(axis=Z,dist=dist)
    children();
}

module orient(axis=U, axis_ref=U, roll=0, extra_roll, extra_roll_orient)
{
    rotate(extra_roll_orient==U||extra_roll==U?0:extra_roll*extra_roll_orient)

    // orient to reference axis
    /*rotate(axis_ref==U?0:_orient_angles(axis_ref))*/
    /*multmatrix(axis_ref==U?0:v_rotate(90, axis_ref))*/
    rotate(axis_ref==U?0:_orient_angles(axis_ref))

    // roll around orient axis
    rotate(axis==U?0:roll*axis)

    // orient to axis
    /*multmatrix(axis==U?0:v_rotate(90, axis))*/
    rotate(axis==U?0:_orient_angles(axis))
    children();
}

module orient_(axis=U, axis_ref=U, roll=0, extra_roll, extra_roll_orient)
{
    rotate(extra_roll_orient==U||extra_roll==U?0:extra_roll*extra_roll_orient)

    // orient to reference axis
    multmatrix(axis_ref==U?0:v_rotate(90, axis_ref))

    // roll around orient axis
    rotate(axis==U?0:roll*axis)

    // orient to axis
    multmatrix(axis_ref==U?0:v_rotate(90, axis))
    children();
}

function _orient_bounds(orient, size) =
    (_rotate_matrix(_orient_angles(orient)) * [size.x,size.y,size.z,1]);

function _orient_t(orient, align, size) =
    let(bounds = _orient_bounds(orient, size))
    (hadamard(align, [abs(bounds.x/2),abs(bounds.y/2),abs(bounds.z/2)]));

module size_align(size=[10,10,10], extra_size=N, align=N, extra_align=N, orient=Z, orient_ref=Z, roll=0, extra_roll, extra_roll_orient)
{
    assert(orient != N);
    assert_v3n(align);
    assert_v3n(orient);
    assert_v3n(size);
    t = orient==U?N:_orient_t(orient, align, size);
    extra_t = (orient==U||extra_size==U||extra_size==[U,U,U]||extra_size==[0,0,0]||extra_align==U) ?
        N : _orient_t(orient, extra_align, extra_size);
    assert_v3n(t);
    assert_v3n(extra_t);
    /*assert(extra_t[0] < 0 && extra_t[0] == 0 && extra_t[0] > 0 || extra_t[);*/
    translate(t+extra_t)
    {
        orient(axis=orient, axis_ref=orient_ref, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
        {
            children();
        }
    }
}

module hull_pairwise()
{
    for (i= [1:1:$children-1])
    {
        hull()
        {
            children(i-1);
            children(i);
        }
    }
}

module proj_extrude_axis(axis=Z, h=1, offset=0, cut=false)
{
    translate(-offset*axis)
    hull()
    {
        orient_(axis=axis, axis_ref=Z)
        {
            linear_extrude(h, center=false)
            projection(cut=cut)
            orient_(axis=Z, axis_ref=axis)
            translate(offset*axis)
            children();

            orient_(axis=Z, axis_ref=axis)
            translate(offset*axis)
            children();
        }
    }
}

module all_axes()
{
    for($axis=concat(AXES,-AXES))
    {
        $color = v_abs($axis*.3 + v_clamp(v_sign($axis),0,1)*.7);
        children();
    }
}

// test proj_extruder_axis
if(false)
{
    all_axes()
    color($color)
    translate(5*$axis)
    proj_extrude_axis(axis=$axis)
    translate(20*$axis)
    sphere(d=10);
}

if(false)
{
    translate(Y*10)
    proj_extrude_axis(axis=Y, offset=10)
    {
        sphere(d=10);
    }
}

module proj_extrude_axis_part(axis=Z, h=1, offset=0, cut=false)
{
    translate(-offset*axis)
    {
        orient_(axis=axis, axis_ref=Z)
        {
            hull()
            linear_extrude(offset, center=false)
            projection(cut=cut)
            orient_(axis=Z, axis_ref=axis)
            translate(offset*axis)
            children();

            orient_(axis=Z, axis_ref=axis)
            translate(offset*axis)
            children();
        }
    }
}

if(false)
{
    tz(10)
    proj_extrude_axis_part(axis=Z, offset=10)
    {
        x = 15;
        cylinder(d=5, h=20);

        tx(x)
        cylinder(d=5, h=20);
    }
}

// translate by pos, with extra (depending on the sign of pos)
// an "explode" function
module te(pos, extra=[0,0,0])
{
    assert(len(pos) == 3);
    assert(len(extra) == 3);

    t(pos + v_mul(extra , v_sign(pos)))
    children();
}


misc: add factorial

misc: add factorial

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

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

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

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

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

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

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

// cumulative sum of vector [1,2,3] = [1,3,6]
function v_cumsum(v, start=0) = [for(i=[start:1:len(v)-1]) 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;


shapes/rcylinder: fix extra_d param

shapes/rcylinder: fix extra_d param

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

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

module rcubea(size=[10,10,10], round_r=1, align=N, extrasize=N, extrasize_align=N, orient=Z, orient_ref=Z, roll=0, extra_roll, extra_roll_orient)
{
    size_align(size=size,extra_size=extrasize, align=align, extra_align=extrasize_align, orient=orient, orient_ref=orient_ref, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
    {
        rcube(size=size+extrasize, round_r=round_r);
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

    h_ = h+extra_h;

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

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

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

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

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

// positive angles go from start to end counterclockwise
// negative angles are allowed
module pie_slice_shape(r, start_angle, end_angle) {
    R = r * sqrt(2) + 1;
    a0 = (4 * start_angle + 0 * end_angle) / 4;
    a1 = (3 * start_angle + 1 * end_angle) / 4;
    a2 = (2 * start_angle + 2 * end_angle) / 4;
    a3 = (1 * start_angle + 3 * end_angle) / 4;
    a4 = (0 * start_angle + 4 * end_angle) / 4;
    intersection() {
        circle(r);
        polygon([
                [0,0],
                [R * cos(a0), R * sin(a0)],
                [R * cos(a1), R * sin(a1)],
                [R * cos(a2), R * sin(a2)],
                [R * cos(a3), R * sin(a3)],
                [R * cos(a4), R * sin(a4)],
                [0,0]
        ]);
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        rcylindera(h=10,r=5);

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

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

}

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

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

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

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

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

module rcubea(size=[10,10,10], round_r=1, align=N, extrasize=N, extrasize_align=N, orient=Z, orient_ref=Z, roll=0, extra_roll, extra_roll_orient)
{
    size_align(size=size,extra_size=extrasize, align=align, extra_align=extrasize_align, orient=orient, orient_ref=orient_ref, roll=roll, extra_roll=extra_roll, extra_roll_orient=extra_roll_orient)
    {
        rcube(size=size+extrasize, round_r=round_r);
    }
}

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

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

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

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

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

    extra_r_ = v_fallback(extra_r, [0]);

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

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

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

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

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

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

    h_ = h+extra_h;

    extra_r_ = v_fallback(extra_r, [0]);

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

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

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

// positive angles go from start to end counterclockwise
// negative angles are allowed
module pie_slice_shape(r, start_angle, end_angle) {
    R = r * sqrt(2) + 1;
    a0 = (4 * start_angle + 0 * end_angle) / 4;
    a1 = (3 * start_angle + 1 * end_angle) / 4;
    a2 = (2 * start_angle + 2 * end_angle) / 4;
    a3 = (1 * start_angle + 3 * end_angle) / 4;
    a4 = (0 * start_angle + 4 * end_angle) / 4;
    intersection() {
        circle(r);
        polygon([
                [0,0],
                [R * cos(a0), R * sin(a0)],
                [R * cos(a1), R * sin(a1)],
                [R * cos(a2), R * sin(a2)],
                [R * cos(a3), R * sin(a3)],
                [R * cos(a4), R * sin(a4)],
                [0,0]
        ]);
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        rcylindera(h=10,r=5);

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

if(false)
{
    all_axes()
    translate($axis*r*2)
    color($color)
    orient(axis=$axis, axis_ref=Z)
    translate(Z*10)
    rcylindera(r1=5, r2=3, h=10);
}

if(false)
{
    r=5*mm;
    all_axes()
    translate($axis*r*2)
    color($color)
    {
        orient(axis=$axis, axis_ref=Z)
        hull()
        {
            cubea(size=[5,8,7]);
            translate(5*Z)
            cubea(size=[2,2,2]);
        }
    }
    /*rcylindera(h=20, r1=r, r2=r/2, align=$axis, orient=$axis, extra_h=r, extra_align=-$axis, $fn=16);*/
}

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

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