Standardise parents, brackets and spaces

/*  text on ....
    This is a rework of version 3 of write.scad to use the new OpenSCAD internal text() primitive.
    All credit to Harlan Martin (harlan@sutlog.com) for his great effort on the original.
    Great thanks to t-paul (and the OpenSCAD dev team) on adding the new text() primitive.
*/

/*
The arguments to be provided for each function are explictly in the modules.

All modules take close to all the arguments to text() [Not all features are supported in all text_on_object() modules though -- try them out warnings will be given if it is not supported]

text() params:
t
size
spacing
font
direction -- ltr, ttb, btt or rtl
language
script
halign -- left, right or center
valign -- baseline, bottom, top or center

All modules also take the additional arguments:
extrusion_height = depth of the letter, i.e. how far it sits proud
rotate
center //center the text at the location it is being written (NOT that the object is centered)

ALL faux-objects that the text is applied to have center=true
TODO: object_center=true/false (already done for cylinder)

The above arguemnts can be seen in text_extrude() as all modules call that module evetually (often multiple times).

locn_vector -- Where the faux-object has been translated to. TODO: Rename object to translate_vector?

Additional arguments for text_on_cube():

Additional arguments for text_on_cylinder():

Additional arguments for text_on_circle():

Additional arguments for text_on_sphere():

*/

// These control the default values for text_extrude(), text_on_sphere(), text_on_cube(), text_on_cylinder(), text_on_circle()
// if the arguments are not included in the call to the module.

// Defaults
// Defaults for all modules
default_t = "text_on";
default_size = 4; // TODO - Can we make this 10?? To match internal size? There is an underlying value in text() -- This masks that.
default_font = "Liberation Mono";
default_spacing = 1; // Spacing between characters. There is an underlying value in text() -- This masks that. We try and match it here.
default_rotate = 0; // text rotation (clockwise)
default_center = true; //Text-centering
default_scale = [1,1,1];
default_extrusion_height = 2; //mm letter extrusion height

//Defaults for Cube
default_cube_face = "front"; // default face (top,bottom,left,right,back,front)
default_sphere_rounded = false; // default for rounded letters on text_on_sphere
default_cube_updown = 0; // mm up (-down) from center on face of cube
default_cube_rightleft = 0; // mm right(-left) from center on face of cube

//Defaults for Sphere
default_sphere_northsouth = 0;
default_sphere_eastwest = 0;
default_sphere_spin = 0; // TODO:Different to rotate? or up/down. Better name?

//Defaults for Cylinder (including circle as it is on top/bottom)
default_circle_middle = 0; // (mm toward middle of circle)
default_circle_ccw = false; // write on top or bottom in a ccw direction
default_circle_eastwest = 0;
default_cylinder_face = "side";
default_cylinder_updown = 0;

// Internal values - don't play with these :)
// This is much more accurate than the PI constant internal to Openscad.
internal_pi = 3.1415926535897932384626433832795028841971693993751058209;
internal_pi2 = internal_pi * 2;

// Internal values - You might want to play with these if you are using a proportional font
internal_space_fudge = 0.80; // Fudge for working out lengths (widths) of strings

debug = true;

// ---- Helper Functions ----
// String width (length from left to right or RTL) in OpenSCAD units
// NOTE: These are innacurate because we don't know the real spacing of the chars and so have to approximate
// They work for monospaced (fixed-width) fonts well
function width_of_text_char(size, spacing) = size * internal_space_fudge * spacing;
function width_of_text_string_num_length(length, size, spacing) = width_of_text_char(size, spacing) * length;
function width_of_text_string(t, size, spacing) = width_of_text_string_num_length(len(t), size, spacing);

function cylinder_center_adjusted_top(height, center) = (center == true) ? height / 2 : height;
function cylinder_center_adjusted_bottom(height, center) = (center == true) ? height / 2 : 0;

//Angle that measures width of letters on perimeter of a circle (and sphere and cylinder)
function rotation_for_character(size, spacing, r, rotate = 0) = (width_of_text_char( size, spacing ) / (internal_pi2 * r)) * 360 * (1 - abs(rotate) / 90);

//Rotate 1/2 width of text if centering
//One less -- if we have a single char we are already centred..
function rotation_for_center_text_string(t, size, spacing, r, rotate, center) = (center) ? (width_of_text_string_num_length(len(t) - 1, size, spacing) / 2 / (internal_pi2 * r) * 360) : 0;

//Rotate according to rotate and if centred text also 1/2 width of text
function rotation_for_center_text_string_and_rotate(t, size, spacing,r,rotate,center) = ((center) ? (width_of_text_string(t, size, spacing) / 2 / (internal_pi2 * r) * 360) : 1) * (1 - abs(rotate) / 90);

//---- Text on Object Functions ----
//Text on the top or side of a cylinder.
//Supports writing on normal and slanty cylinders
//cylinder(h,r,center)
//cylinder(h,r1,r2,center)
module text_on_cylinder(t = default_t,
                        //Object-specific
                        locn_vec = [0,0,0],
                        r,
                        r1,
                        r2,
                        h,
                        face = default_cylinder_face,
                        updown = default_cylinder_updown,
                        rotate = default_rotate,
                        eastwest = default_circle_eastwest,
                        middle = default_circle_middle,
                        ccw = default_circle_ccw,
                        cylinder_center = false, //if the cylinder object we are writing on is center==true
                        //TODO: add rounded

                        //All objects
                        extrusion_height = default_extrusion_height,
                        center = default_center,
                        //All objects -- arguments as for text()
                        font = undef,
                        size = default_size,
                        direction = undef,
                        halign = undef,
                        valign = undef,
                        language = undef,
                        script = undef,
                        spacing = default_spacing) {

//    echo (str("text_on_cylinder:There are " ,len(t) ," letters in t" , t));
//    echo (str("text_on_cylinder:","h" , h));
//    echo (str("text_on_cylinder:","r=" , r));
//    echo (str("text_on_cylinder:","r1=" , r1));
//    echo (str("text_on_cylinder:","r2=" , r2));

    if((halign != undef) || (halign != undef)) {
        echo(str("text_on_cylinder:","WARNING " , "halign and valign are NOT supported."));
    }

    rtl_sign = (direction == "rtl") ? -1 : 1;
    if((face == "top") || (face == "bottom")) {
        //Work on a circle
        locn_offset_vec = (face == "top" ) ? [0, 0, cylinder_center_adjusted_top(h, cylinder_center)] : [0, 0, cylinder_center_adjusted_bottom(h, cylinder_center)];
        rotation_angle = (face == "top" ) ? 0 : 180;
        int_radius = (r == undef) ? ((face == "top" ) ? r2 : r1 ) : r;  //Use the top/bottom radius for slanty-cylinders
        rotate(rotation_angle, [1, 0, 0])
        text_on_circle(t,
                locn_vec + locn_offset_vec,
                r = int_radius-size,
                font = font,size=size,
                spacing = spacing,
                direction = direction,
                language = language,
                script = script,
                halign = halign,
                valign = valign,
                extrusion_height = extrusion_height,
                rotate = rotate,
                eastwest = eastwest,
                middle = middle,
                ccw = ccw);
    } else {
        if((middle != undef) && (middle != default_circle_middle)) {
            //TODO: Which others?
            //TODO: Which side things aren't supported on the circle
            echo(str("text_on_cylinder:","WARNING " , "middle NOT supported for the SIDE of a cylinder."));
        }
        //Work on the side
        locn_offset_vec = (cylinder_center == true) ? [0, 0, 0] : [0, 0, h / 2];
        rotate(-rtl_sign * rotation_for_center_text_string_and_rotate(t, size, spacing, r, rotate, center), [0, 0, 1])
        translate(locn_vec + locn_offset_vec)
        __internal_text_on_cylinder_side(t,
                locn_vec,
                r = r,
                h = h,
                r1 = r1,
                r2 = r2,
                cylinder_center = cylinder_center,
                center = center,
                font = font,
                size = size,
                spacing = spacing,
                direction = direction,
                language = language,
                script = script,
                halign = halign,
                valign = valign,
                extrusion_height = extrusion_height,
                rotate = rotate,
                face = face,
                updown = updown,
                eastwest = eastwest);
    }
}

//circle(r)
module text_on_circle(t = default_t,
                      //Object-specific
                      locn_vec = [0, 0, 0],
                      r,
                      eastwest = default_circle_eastwest,
                      middle = default_circle_middle,
                      ccw = default_circle_ccw,

                      //All objects
                      extrusion_height = default_extrusion_height,
                      rotate = default_rotate,
                      center = default_center,

                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      halign = undef,
                      valign = undef,
                      language = undef,
                      script = undef,
                      spacing = default_spacing) {
//    echo (str("text_on_circle:","There are " ,len(t) ," letters in t" , t));
//    echo (str("text_on_circle:","rotate=" , rotate));
//    echo (str("text_on_circle:","eastwest=" , eastwest));

    if((halign != undef) || (halign != undef)) {
        echo(str("text_on_circle:","WARNING " , "halign and valign are NOT supported."));
    }

    ccw_sign = (ccw == true) ? 1 : -1;
    rtl_sign = (direction == "rtl") ? -1 : 1;
    ttb_btt_inaction = (direction == "ttb" || direction == "btt") ? 0 : 1;
    rotate_z_outer = -rotate + ccw_sign * eastwest;
    rotate_z_inner = -rtl_sign * ccw_sign * ttb_btt_inaction * rotation_for_center_text_string(t, size, spacing, r-middle, rotate, center);
    rotate(rotate_z_outer, [0, 0, 1] )
    rotate(rotate_z_inner, [0, 0, 1] )
    translate(locn_vec)
    for(l = [0 : len(t) - 1]) {
        //TTB/BTT means no per letter rotation
        rotate_z_inner2 = -ccw_sign * 90 + ttb_btt_inaction * rtl_sign * ccw_sign * l * rotation_for_character(size, spacing, r - middle, rotate = 0);   //Bottom out=-270+r
        //TTB means we go toward center, BTT means away
        vert_x_offset = (direction == "ttb" || direction == "btt") ? (l * size * ((direction == "btt") ? -1 : 1)) : 0;
        rotate(rotate_z_inner2, [0, 0, 1])
        translate([r - middle - vert_x_offset, 0, 0])
        rotate(-ccw_sign * 270, [0, 0, 1]) // flip text (botom out = -270)
        text_extrude(t[l],
                center = true,
                font = font,
                size = size,
                rotate = undef,
                spacing = spacing,
                direction = undef, //We don't pass direction ( misaligns inside text() ). TODO: Investigate why
                language = language,
                script = script,
                halign = halign,
                valign = valign,
                extrusion_height = extrusion_height);
    }
}

//Only expected to be called from another function. No defaults as expected to be done in parent.
//NOTE: This is hacked. Requires more mathematics than I feel like to do properly today....
//TODO: Refactor the two operations - side of normal and slanty
//No default checks in this funciton -- done in parent
module __internal_text_on_cylinder_side(t = default_t,
                      //Object-specific
                      locn_vec = [0, 0, 0],
                      r,
                      r1,
                      r2,
                      h,
                      cylinder_center,

                      //All objects
                      extrusion_height = default_extrusion_height,
                      center = undef,
                      rotate = default_rotate,

                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      halign = undef, //Overridden
                      valign = undef,
                      language = undef,
                      script = undef,
                      spacing = default_spacing) {
//    echo (str("__internal_text_on_cylinder_side:There are " ,len(t) ," letters in t" , t));
//    echo (str("__internal_text_on_cylinder_side:","h=" , h));
//    echo (str("__internal_text_on_cylinder_side:","r=" , r));
//    echo (str("__internal_text_on_cylinder_side:","r1=" , r1));
//    echo (str("__internal_text_on_cylinder_side:","r2=" , r2));
//    echo (str("__internal_text_on_cylinder_side:","direction=" , direction));

    if((halign != undef) || (halign != undef)) {
        echo(str("text_on_cylinder:","WARNING " , "halign and valign are NOT supported."));
    }

    wid = width_of_text_char( size, spacing ); //TODO: refactor this out?

    function calc_radius_at_height_offset(r1, r2, h_total, h_offset) = r1 + ((r2 - r1) * (h_total - h_offset) / h_total);

    //Has to factor in up/downedness
    //Starts at middle height then height displacement is reduced under rotation and by the slope of the uneven radii
    function calc_height_offset_at_length(r1, r2, h, length, rotate, updown) = h / 2 - updown + length * rotate / 90 * cos(atan((r2 - r1) / h));

    function calc_radius_at_length(r1, r2, h, length, rotate, updown) = calc_radius_at_height_offset(r1, r2, h, calc_height_offset_at_length(r1, r2, h, length, rotate, updown));

    if(r == undef) {
        //Slanty-cylinder (truncated cone) warnings
        if((direction == "btt") || (direction == "ttb")) {
            //TODO: After the issues are resolved refactoring should give this pretty easily
            echo(str("text_on_cylinder:","WARNING" , " - Slanty-cylinders definitely don't directions TTB or BTT on sides yet. Setting direction to ltr."));
        }
        if(center == true) {
            echo(str("text_on_cylinder:","WARNING" , " - Slanty-cylinders don't support centering yet. Setting center to false. You can adjust updown and eastwest and achieve most of what you are after...."));
        }
    }

    r1 = (r1 != undef) ? r1 : r;
    r2 = (r2 != undef) ? r2 : r;
    //NOTE: r is not used after here. We operate on generalised slanty cone (r1 and r2)

    //TODO: Look at changing this to extrusion_height_center
    //and renaming the other as text_center
    rr1 = (center) ? r1 - extrusion_height / 2 : r1;
    rr2 = (center) ? r2 - extrusion_height / 2 : r2;

    ccenter = (r != undef) ? center : false; //We don't do (text) centering for the slanty cones/helical paths (yet? ever?). Needs to do quite a few offset calcs.
    ddirection = ((r == undef) && ((direction == "ttb") || (direction == "btt"))) ? "ltr" : direction; //We don't do ttb or btt directions on slanty
    rtl_sign = (ddirection == "rtl") ? -1 : 1;

        //This code takes care of slanty cylinders and "normal" cylinders
    translate([0, 0, updown])
    rotate(eastwest, [0, 0, 1])
    for(l = [0 : len(t) - 1]) {
        //TODO: TTB and BTT need to have a different concept of path/length than this for RTL/LTR
           //width_of_... is half a char too long -- add 0.5 (counting from zero)
        length_to_center_of_char = width_of_text_string_num_length(l + 0.5, size, spacing);
        radius_here = calc_radius_at_length(rr1, rr2, h, length_to_center_of_char, rotate, updown);
        //Rotating into position and tangentially to surface -- Don't rotate per character for ttb/btt
        //-90 is to get centering at origin
        rotate_z_inner = -90 + rtl_sign * rotation_for_character(size, spacing, radius_here, rotate) * ((ddirection == "ttb" || ddirection== "btt") ?  0 : l);
        rotate(rotate_z_inner, [0, 0, 1]) {
            //Positioning - based on (somewhat innacurate) string length
            //Offset per character to go up/down the side in ttb/btt -- TTB down, BTT up
            vert_z_char_offset = (ddirection == "ttb" || ddirection == "btt") ?  (l * size * ((ddirection == "ttb") ? -1 : 1 )) :  0 ;
            //Only if RTL/LTR and if center -- center the text (starts off in a more visually appealing location)
            vert_z_half_text_offset_tmp = (len(t) -1) / 2 * (rotate / 90 * wid);
            vert_z_half_text_offset = ((ddirection == "ttb" || ddirection == "btt") || (ccenter == false)) ? 0 : vert_z_half_text_offset_tmp;
            translate([ radius_here , 0, vert_z_char_offset - l * (rotate / 90 * wid) + vert_z_half_text_offset])

            //Flip to tangent on the sloping side (without respecting rotation impact on the tangent -- rotate seems a little off. TODO: Investigate).
            rotate(atan((rr2 - rr1) /h), [0, 1, 0])
            //Flip onto face of "normal" cylinder
            rotate(90, [1, 0, 0])
            rotate(90, [0, 1, 0])

            //Modify the offset of the baselined text to center
            translate([0, (ccenter) ? -size / 2 : 0, 0])

            text_extrude(t[l],
                    center = false,
                    rotate = rotate,
                    font = font,
                    size = size,
                    spacing = spacing,
                    direction = undef, //We don't pass direction ( misaligns inside text() ). TODO: Investigate why
                    language = language,
                    script = script,
                    halign = "center", //This can be relaxed eventually
                    valign = "baseline", //Need this to keep all letters on the same line (could also support top -- otherw will make
                    extrusion_height = extrusion_height);
        }
    }
}

//sphere(r)
//NB: Iterates through characters/glyphs and presents them individually (but supports actions across the entire string still)
//supports all features of text() except: halign and valign
module text_on_sphere(t = default_t,
                      //Object-specific
                      locn_vec = [0,0,0],
                      r,
                      eastwest = default_sphere_eastwest,
                      northsouth = default_sphere_northsouth,
                      rotate  = default_rotate,
                      spin = default_sphere_spin,
                      rounded  = default_sphere_rounded,
                      //All objects
                      extrusion_height = default_extrusion_height,
                      center = default_center, //center = false doesn't really even make sense to do (IMHO)
                      scale = default_scale,
                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      halign = undef, //Not supported - only here to enable a warning
                      valign = undef, //Not supported - only here to enable a warning
                      language = undef,
                      script = undef,
                      spacing = default_spacing) {
    //echo ("text_on_sphere:There are " ,len(t) ," letters in t" , t);

    if((halign != undef) || (halign != undef)) {
        echo(str("text_on_sphere:","WARNING " , "halign and valign are NOT supported (functionality used in centering)."));
    }

    //TODO: refactor this?
    rtl_sign = (direction == "rtl") ? -1 : 1;

    //TODO: Look at changing this to extrusion_height_center
    //and renaming the other as text_center
    //If we are centered - we sink the radius by half the
    //extrusion height to draw "just below the surface"
    rr = (center) ? r - extrusion_height / 2 : r ;

    rotate(eastwest, [0, 0, 1])
    rotate(-northsouth, [1, 0, 0])
    rotate(spin, [0, 1, 0]) {
        //This tries to center the text (for RTL text).
        ttb_btt_inaction = (direction == "ttb" || direction == "btt") ? 0 : 1;
        rotate(-rtl_sign * ttb_btt_inaction * rotation_for_center_text_string(t, size, spacing, rr, rotate, center), [0, 0, 1]) {
            translate(locn_vec)
            if (rounded == false) {
                __internal_text_on_sphere_helper(t = t,
                        r = rr,
                        rotate = rotate,
                        center = center,
                        scale = scale,
                        font = font,
                        size = size,
                        spacing = spacing,
                        direction = direction,
                        language = language,
                        script = script,
                        halign = halign,
                        valign = valign,
                        extrusion_height = extrusion_height);
            } else {
                //If rounding then clip the text inside an inner sphere and outer sphere
                intersection() {
                    __internal_text_on_sphere_helper(t = t,
                            r = rr,
                            rotate = rotate,
                            center = center,
                            scale = scale,
                            font = font,
                            size = size,
                            spacing = spacing,
                            direction = direction,
                            language = language,
                            script = script,
                            halign = halign,
                            valign = valign,
                            extrusion_height = extrusion_height * 2); //Make it proud to clip it off.
                    //Shell - bounding inner and outer
                    difference() { //rounded outside
                        sphere(rr + extrusion_height);
                        // rounded inside for indented text
                        sphere( rr  );
                    }
                }
            }
        }
    }
}

//internal function -- don't call directly
//No default checks in this funciton -- done in parent
module __internal_text_on_sphere_helper(t = default_t,
                      //Object-specific
                      r,
                      //All objects
                      extrusion_height = default_extrusion_height,
                      center = default_center,
                      scale = default_scale,
                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      //halign           = undef,
                      //valign           = undef,
                      language = undef,
                      script = undef,
                      spacing = default_spacing) {
    rtl_sign = (direction == "rtl") ? -1 : 1;
    ttb_btt_inaction = (direction == "ttb" || direction == "btt") ? 0 : 1;
    ttb_btt_action = (direction == "ttb" || direction == "btt") ? 1 : 0;

    for(l = [0 : len(t) - 1]) {
        rotate_z_inner = -90 + rtl_sign * ttb_btt_inaction * l * rotation_for_character(size, spacing, r, 0);
        translate_sign = (direction == "ttb" || direction == "btt") ? ((direction == "btt") ? 1 : -1)  : 0 ;
        //translate_effect = (direction=="ttb" || direction=="btt") ? 1  :  0 ;
        //Translate letter to be located on the sphere in up/down direction when we are doing TTB and BTT
        rotate(rotate_z_inner, [0, 0, 1])
        translate(r * [cos(0 + ttb_btt_action * rotation_for_character(size, spacing, r, rotate) * l), 0, translate_sign * ttb_btt_action * sin(0 +  rotation_for_character(size, spacing, r, rotate) * l)])
        //Rotate letter to be tangential to the new part of the sphere when we are doing TTB and BTT
        rotate(-90 * translate_sign * (0 + ttb_btt_action * rotation_for_character(size, spacing, r, rotate) * l) / 90 , [0, 1, 0])
        //Flip character into position to be flush against sphere
        rotate(90, [1, 0, 0])
        rotate(90, [0, 1, 0])

        //Modify the offset of the baselined text to center
        translate([0, (center) ? -size / 2 : 0 , 0])
        text_extrude(t[l],
                center = false,
                rotate = rotate,
                scale = scale,
                font = font,
                size = size,
                spacing = spacing,
                direction = undef, //We don't pass direction ( misaligns inside text() ). TODO: Investigate why
                language = language,
                script = script,
                halign = "center", //This can be relaxed eventually
                valign = "baseline", //Need this to keep all letters on the same line (could also support top -- otherw will make wonky letters)
                extrusion_height = extrusion_height);
    }
}

//cube(size)
//cube([width,h,depth])
//NB: cube_size is used instead of size (the name size is used in the text module)
//NB: Passes through text as strings -- supports all features of text()
module text_on_cube(  t = default_t,
                      //Object-specific
                      cube_size, //number or 3-vector
                      face = default_cube_face,
                      rightleft = default_cube_rightleft,
                      updown = default_cube_updown,
                      //All objects
                      locn_vec = [0, 0, 0],
                      extrusion_height = default_extrusion_height,
                      center = undef,
                      rotate = default_rotate,
                      scale = default_scale,
                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      halign = undef,
                      valign = undef,
                      language = undef,
                      script = undef,
                      spacing = default_spacing) {
    //echo (str("text_on_cube:","There are " ,len(t) ," letters in t" , t));
    //echo (str("text_on_cube:","cube_size" , cube_size));

    if((halign != undef) || (halign != undef)) {
        echo(str("text_on_cube:","WARNING " , "halign and valign are NOT supported."));
    }

    int_cube_size = (str(cube_size)[0] != "[") ? [cube_size, cube_size, cube_size] : cube_size;
    rotate_x = ((face == "front") || (face == "back") || (face == "left") || (face == "right")) ? 90 : ((face == "bottom") ? 180 : 0); // Top is zero
    rotate_y = ((face == "back") || (face == "left") || (face == "right")) ? (face == "back") ? 180 : ((face == "left") ? -90 : 90) : 0 ; // Right is 90, Top, bottom, front are zero
    locn_vec_offset =
            (face == "front") ?               [+rightleft, -int_cube_size[1] / 2, +updown]
            : ((face == "back") ?             [+rightleft, +int_cube_size[1] / 2, +updown]
                : ((face== "left") ?          [-int_cube_size[0] / 2, +rightleft, +updown]
                    : ((face == "right") ?    [+int_cube_size[0] / 2, +rightleft, +updown]
                        : ((face == "top") ?  [+rightleft, +updown, +int_cube_size[2] / 2]
                            :                 [+rightleft, -updown, -int_cube_size[2] / 2]
                            )))); // bottom

    translate(locn_vec + locn_vec_offset)
    rotate(rotate_x, [1, 0, 0])
    rotate(rotate_y, [0, 1, 0]) // rotate around the y axis (z before rotation)
    text_extrude(t,
            center = center,
            rotate = rotate,
            scale = scale,
            font = font,
            size = size,
            spacing = spacing,
            direction = direction, // Does this misalign inside text()? the individual character modules do.
            language = language,
            script = script,
            halign = halign,
            valign = valign,
            extrusion_height = extrusion_height );
}

function default_if_undef(val, default_val) = (val != undef) ? val : default_val;

// Print a single character or a string at the desired extrusion height
// Passes on values to text() that are passed in
// TODO: Add x,y,z rotations get text facing the right way from the start)
module text_extrude( t = default_t,
                     extrusion_height = default_extrusion_height,
                     center = default_center, // Fudgy. YMMV. // TODO:center_extrusion, or extrusion offset??
                     rotate = default_rotate,
                     scale = default_scale, // For scaling by different on axes (for widening etc)
                     // Following are test() params (in addition to t=)
                     font = default_font,
                     size = default_size,
                     direction = undef,
                     halign  = undef,
                     valign = undef,
                     language = undef,
                     script = undef,
                     spacing = default_spacing) {
    //echo (str("text_extrude:","There are " ,len(t) ," letters in text" , t));
    //echo (str("text_extrude:","extrusion_height=" , extrusion_height));

    if(center == true) {
        if((halign != undef) || (valign != undef)) {
            echo(str("text_extrude:","WARNING " , "When center is true, then halign and valign are NOT supported."));
        }
    }

    // As arguments are explicitly set as undef (from higher functions) as they come down (they don't get defaults in this function as they are set as something)
    // we need to check and replace any that we don't want to be undef
    t = default_if_undef(t, default_t);
    font = default_if_undef(font, default_font);
    extrusion_height = default_if_undef(extrusion_height, default_extrusion_height);
    center = default_if_undef(center, default_center);
    rotate = default_if_undef(rotate, default_rotate);
    spacing = default_if_undef(spacing, default_spacing);
    size = default_if_undef(size, default_size);

    halign = (center) ? "center" : halign ;
    valign = (center) ? "center" : valign ;
    extrusion_center = (center) ? true : false ;

    scale(scale)
    rotate(rotate, [0, 0, -1]) //TODO: Do we want to make this so that the entire vector can be set?
    linear_extrude(height = extrusion_height, convexity = 10, center = extrusion_center)
    text(text = t,
            size = size,
            $fn = 40,
            font = font,
            direction = direction,
            spacing = spacing,
            halign = halign,
            valign = valign,
            language = language,
            script = script);
}

/*  text on ....
    This is a rework of version 3 of write.scad to use the new OpenSCAD internal text() primitive.
    All credit to Harlan Martin (harlan@sutlog.com) for his great effort on the original.
    Great thanks to t-paul (and the OpenSCAD dev team) on adding the new text() primitive.
*/

/*
The arguments to be provided for each function are explictly in the modules.

All modules take close to all the arguments to text() [Not all features are supported in all text_on_object() modules though -- try them out warnings will be given if it is not supported]

text() params:
t
size
spacing
font
direction -- ltr, ttb, btt or rtl
language
script
halign -- left, right or center
valign -- baseline, bottom, top or center

All modules also take the additional arguments:
extrusion_height = depth of the letter, i.e. how far it sits proud
rotate
center //center the text at the location it is being written (NOT that the object is centered)

ALL faux-objects that the text is applied to have center=true
TODO: object_center=true/false (already done for cylinder)

The above arguemnts can be seen in text_extrude() as all modules call that module evetually (often multiple times).

locn_vector -- Where the faux-object has been translated to. TODO: Rename object to translate_vector?

Additional arguments for text_on_cube():

Additional arguments for text_on_cylinder():

Additional arguments for text_on_circle():

Additional arguments for text_on_sphere():

*/

// These control the default values for text_extrude(), text_on_sphere(), text_on_cube(), text_on_cylinder(), text_on_circle()
// if the arguments are not included in the call to the module.

// Defaults
// Defaults for all modules
default_t = "text_on";
default_size = 4; //TODO - Can we make this 10?? To match internal size? There is an underlying value in text() -- This masks that.
default_font = "Liberation Mono";
default_spacing = 1; //Spacing between characters. There is an underlying value in text() -- This masks that. We try and match it here.
default_rotate = 0; // text rotation (clockwise)
default_center = true; //Text-centering
default_scale = [1,1,1];
default_extrusion_height = 2; //mm letter extrusion height

//Defaults for Cube
default_cube_face = "front";     // default face (top,bottom,left,right,back,front)
default_sphere_rounded = false;     //default for rounded letters on text_on_sphere
default_cube_updown = 0; //mm up (-down) from center on face of cube
default_cube_rightleft = 0;         //mm right(-left) from center on face of cube

//Defaults for Sphere
default_sphere_northsouth = 0;
default_sphere_eastwest = 0;
default_sphere_spin = 0; //TODO:Different to rotate? or up/down. Better name?

//Defaults for Cylinder (including circle as it is on top/bottom)
default_circle_middle = 0;      //(mm toward middle of circle)
default_circle_ccw = false;  //write on top or bottom in a ccw direction
default_circle_eastwest = 0;
default_cylinder_face = "side";
default_cylinder_updown = 0;

//Internal values - don't play with these :)
//This is much more accurate than the PI constant internal to Openscad.
internal_pi = 3.1415926535897932384626433832795028841971693993751058209;
internal_pi2 = internal_pi * 2;

//Internal values - You might want to play with these if you are using a proportional font
internal_space_fudge = 0.80; //Fudge for working out lengths (widths) of strings

debug = true;

//---- Helper Functions ----
//String width (length from left to right or RTL) in OpenSCAD units
//NOTE: These are innacurate because we don't know the real spacing of the chars and so have to approximate
//They work for monospaced (fixed-width) fonts well
function width_of_text_char( size, spacing ) = (size * internal_space_fudge * spacing);
function width_of_text_string_num_length(length, size, spacing) = ( width_of_text_char( size, spacing ) * (length));
function width_of_text_string( t, size, spacing ) = ( width_of_text_string_num_length(len(t), size, spacing) );

function cylinder_center_adjusted_top( height, center ) = (center==true) ? height / 2 : height;
function cylinder_center_adjusted_bottom( height, center ) = (center==true) ? height / 2 : 0;

//Angle that measures width of letters on perimeter of a circle (and sphere and cylinder)
function rotation_for_character(size, spacing, r, rotate = 0) = ( width_of_text_char( size, spacing ) /(internal_pi2 * r)) * 360 * (1 - abs(rotate) / 90);

//Rotate 1/2 width of text if centering
//One less -- if we have a single char we are already centred..
function rotation_for_center_text_string( t, size, spacing, r, rotate, center) = (center) ? (width_of_text_string_num_length( len(t) - 1, size, spacing ) / 2 / (internal_pi2 * r) * 360) : 0;

//Rotate according to rotate and if centred text also 1/2 width of text
function rotation_for_center_text_string_and_rotate( t, size, spacing,r,rotate,center) = ((center) ? (width_of_text_string( t, size, spacing ) / 2 / (internal_pi2 * r) * 360) : 1) * (1 - abs(rotate) / 90);

//---- Text on Object Functions ----
//Text on the top or side of a cylinder.
//Supports writing on normal and slanty cylinders
//cylinder(h,r,center)
//cylinder(h,r1,r2,center)
module text_on_cylinder(t = default_t,
                        //Object-specific
                        locn_vec = [0,0,0],
                        r,
                        r1,
                        r2,
                        h,
                        face = default_cylinder_face,
                        updown = default_cylinder_updown,
                        rotate = default_rotate,
                        eastwest = default_circle_eastwest,
                        middle = default_circle_middle,
                        ccw = default_circle_ccw,
                        cylinder_center = false, //if the cylinder object we are writing on is center==true
                        //TODO: add rounded

                        //All objects
                        extrusion_height = default_extrusion_height,
                        center = default_center,
                        //All objects -- arguments as for text()
                        font = undef,
                        size = default_size,
                        direction = undef,
                        halign = undef,
                        valign = undef,
                        language = undef,
                        script = undef,
                        spacing = default_spacing
){
//    echo (str("text_on_cylinder:There are " ,len(t) ," letters in t" , t));
//    echo (str("text_on_cylinder:","h" , h));
//    echo (str("text_on_cylinder:","r=" , r));
//    echo (str("text_on_cylinder:","r1=" , r1));
//    echo (str("text_on_cylinder:","r2=" , r2));

    if((halign != undef) || (halign != undef))
    {
        echo (str("text_on_cylinder:","WARNING " , "halign and valign are NOT supported."));
    }

    rtl_sign = (direction == "rtl") ? -1 : 1;
    if ((face == "top") || (face == "bottom")){
        //Work on a circle
        locn_offset_vec = (face == "top" ) ? [0, 0, cylinder_center_adjusted_top(h, cylinder_center)] : [0, 0, cylinder_center_adjusted_bottom(h, cylinder_center)];
        rotation_angle = (face == "top" ) ? 0 : 180;
        int_radius = (r == undef) ? ((face == "top" ) ? r2 : r1 ) : r;  //Use the top/bottom radius for slanty-cylinders
        rotate(rotation_angle, [1, 0, 0])
        text_on_circle(t,
                locn_vec + locn_offset_vec,
                r = int_radius-size,
                font = font,size=size,
                spacing = spacing,
                direction = direction,
                language = language,
                script = script,
                halign = halign,
                valign = valign,
                extrusion_height = extrusion_height,
                rotate = rotate,
                eastwest = eastwest,
                middle = middle,
                ccw = ccw);
    }else{
        if((middle != undef) && (middle != default_circle_middle))
        {
            //TODO: Which others?
            //TODO: Which side things aren't supported on the circle
            echo (str("text_on_cylinder:","WARNING " , "middle NOT supported for the SIDE of a cylinder."));
        }
        //Work on the side
        locn_offset_vec = (cylinder_center == true) ? [0, 0, 0] : [0, 0, h / 2];
        rotate(-rtl_sign * rotation_for_center_text_string_and_rotate(t, size, spacing, r, rotate, center), [0, 0, 1])
        translate(locn_vec + locn_offset_vec)
        __internal_text_on_cylinder_side(t,
                locn_vec,
                r = r,
                h = h,
                r1 = r1,
                r2 = r2,
                cylinder_center = cylinder_center,
                center = center,
                font = font,
                size = size,
                spacing = spacing,
                direction = direction,
                language = language,
                script = script,
                halign = halign,
                valign = valign,
                extrusion_height = extrusion_height,
                rotate = rotate,
                face = face,
                updown = updown,
                eastwest = eastwest);
    }
}

//circle(r)
module text_on_circle(t = default_t,
                      //Object-specific
                      locn_vec = [0, 0, 0],
                      r,
                      eastwest = default_circle_eastwest,
                      middle = default_circle_middle,
                      ccw = default_circle_ccw,

                      //All objects
                      extrusion_height = default_extrusion_height,
                      rotate = default_rotate,
                      center = default_center,

                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      halign = undef,
                      valign = undef,
                      language = undef,
                      script = undef,
                      spacing = default_spacing
){
//    echo (str("text_on_circle:","There are " ,len(t) ," letters in t" , t));
//    echo (str("text_on_circle:","rotate=" , rotate));
//    echo (str("text_on_circle:","eastwest=" , eastwest));

    if((halign != undef) || (halign != undef))
    {
        echo (str("text_on_circle:","WARNING " , "halign and valign are NOT supported."));
    }

    ccw_sign = (ccw == true) ? 1 : -1;
    rtl_sign = (direction == "rtl") ? -1 : 1;
    ttb_btt_inaction = (direction == "ttb" || direction == "btt") ? 0 : 1;
    rotate_z_outer = -rotate + ccw_sign * eastwest;
    rotate_z_inner = -rtl_sign * ccw_sign * ttb_btt_inaction * rotation_for_center_text_string(t, size, spacing, r-middle, rotate, center);
    rotate( rotate_z_outer, [0, 0, 1] )
    rotate( rotate_z_inner, [0, 0, 1] )
    translate(locn_vec)
    for (l = [0 : len(t) - 1]){
        //TTB/BTT means no per letter rotation
        rotate_z_inner2 = -ccw_sign * 90 + ttb_btt_inaction * rtl_sign * ccw_sign * l * rotation_for_character(size, spacing, r - middle, rotate = 0);   //Bottom out=-270+r
        //TTB means we go toward center, BTT means away
        vert_x_offset = (direction == "ttb" || direction == "btt") ? (l * size * ((direction == "btt") ? -1 : 1)) : 0;
        rotate( rotate_z_inner2, [0, 0, 1] )
        translate([r - middle - vert_x_offset, 0, 0])
        rotate(-ccw_sign * 270, [0, 0, 1]) // flip text (botom out = -270)
        text_extrude(t[l],
                center = true,
                font = font,
                size = size,
                rotate = undef,
                spacing = spacing,
                direction = undef, //We don't pass direction ( misaligns inside text() ). TODO: Investigate why
                language = language,
                script = script,
                halign = halign,
                valign = valign,
                extrusion_height = extrusion_height);
    }
}

//Only expected to be called from another function. No defaults as expected to be done in parent.
//NOTE: This is hacked. Requires more mathematics than I feel like to do properly today....
//TODO: Refactor the two operations - side of normal and slanty
//No default checks in this funciton -- done in parent
module __internal_text_on_cylinder_side(t = default_t,
                      //Object-specific
                      locn_vec = [0,0,0],
                      r,
                      r1,
                      r2,
                      h,
                      cylinder_center,

                      //All objects
                      extrusion_height = default_extrusion_height,
                      center = undef,
                      rotate = default_rotate,

                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      halign = undef, //Overridden
                      valign = undef,
                      language = undef,
                      script = undef,
                      spacing = default_spacing
)
{
//    echo (str("__internal_text_on_cylinder_side:There are " ,len(t) ," letters in t" , t));
//    echo (str("__internal_text_on_cylinder_side:","h=" , h));
//    echo (str("__internal_text_on_cylinder_side:","r=" , r));
//    echo (str("__internal_text_on_cylinder_side:","r1=" , r1));
//    echo (str("__internal_text_on_cylinder_side:","r2=" , r2));
//    echo (str("__internal_text_on_cylinder_side:","direction=" , direction));

    if((halign != undef) || (halign != undef))
    {
        echo (str("text_on_cylinder:","WARNING " , "halign and valign are NOT supported."));
    }

    wid = width_of_text_char( size, spacing ); //TODO: refactor this out?

    function calc_radius_at_height_offset(r1, r2, h_total, h_offset) = ( r1 + ((r2 - r1) * (h_total - h_offset) / h_total) );

    //Has to factor in up/downedness
    //Starts at middle height then height displacement is reduced under rotation and by the slope of the uneven radii
    function calc_height_offset_at_length(r1, r2, h, length, rotate, updown) = ( h / 2 - updown + length * rotate / 90 * cos( atan( (r2 - r1) / h ) )  );

    function calc_radius_at_length(r1, r2, h, length, rotate, updown) = ( calc_radius_at_height_offset(r1, r2, h, calc_height_offset_at_length(r1, r2, h, length, rotate, updown)));

    if(r == undef)
    {
        //Slanty-cylinder (truncated cone) warnings
        if((direction == "btt") || (direction == "ttb"))
        {
            //TODO: After the issues are resolved refactoring should give this pretty easily
            echo (str("text_on_cylinder:","WARNING" , " - Slanty-cylinders definitely don't directions TTB or BTT on sides yet. Setting direction to ltr."));
        }
        if(center == true)
        {
            echo (str("text_on_cylinder:","WARNING" , " - Slanty-cylinders don't support centering yet. Setting center to false. You can adjust updown and eastwest and achieve most of what you are after...."));
        }
    }

    r1 = (r1 != undef) ? r1 : r;
    r2 = (r2 != undef) ? r2 : r;
    //NOTE: r is not used after here. We operate on generalised slanty cone (r1 and r2)

      //TODO: Look at changing this to extrusion_height_center
    //and renaming the other as text_center
    rr1 = (center) ? r1 - extrusion_height / 2 : r1;
    rr2 = (center) ? r2 - extrusion_height / 2 : r2;

    ccenter = (r != undef) ? center : false; //We don't do (text) centering for the slanty cones/helical paths (yet? ever?). Needs to do quite a few offset calcs.
    ddirection = ((r == undef) && ((direction == "ttb") || (direction == "btt"))) ? "ltr" : direction; //We don't do ttb or btt directions on slanty
    rtl_sign = (ddirection == "rtl") ? -1 : 1;

        //This code takes care of slanty cylinders and "normal" cylinders
    translate([0, 0, updown])
    rotate(eastwest, [0, 0, 1])
    for (l = [0 : len(t) - 1]){
        //TODO: TTB and BTT need to have a different concept of path/length than this for RTL/LTR
           //width_of_... is half a char too long -- add 0.5 (counting from zero)
        length_to_center_of_char = width_of_text_string_num_length(l + 0.5, size, spacing);
        radius_here = calc_radius_at_length(rr1, rr2, h, length_to_center_of_char, rotate, updown);
        //Rotating into position and tangentially to surface -- Don't rotate per character for ttb/btt
        //-90 is to get centering at origin
        rotate_z_inner = -90 + rtl_sign * rotation_for_character(size, spacing, radius_here, rotate) * ((ddirection == "ttb" || ddirection== "btt") ?  0 : l);
        rotate(rotate_z_inner, [0, 0, 1])
        {
            //Positioning - based on (somewhat innacurate) string length
            //Offset per character to go up/down the side in ttb/btt -- TTB down, BTT up
            vert_z_char_offset = (ddirection == "ttb" || ddirection == "btt") ?  (l * size * ((ddirection == "ttb") ? -1 : 1 )) :  0 ;
            //Only if RTL/LTR and if center -- center the text (starts off in a more visually appealing location)
            vert_z_half_text_offset_tmp = (len(t) -1) / 2 * (rotate / 90 * wid);
            vert_z_half_text_offset = ((ddirection == "ttb" || ddirection == "btt") || (ccenter == false)) ? 0 : vert_z_half_text_offset_tmp;
            translate([ radius_here , 0, vert_z_char_offset - l * ((rotate) / 90 * wid) + vert_z_half_text_offset])

            //Flip to tangent on the sloping side (without respecting rotation impact on the tangent -- rotate seems a little off. TODO: Investigate).
            rotate( atan( (rr2 - rr1) /h), [0, 1, 0])
            //Flip onto face of "normal" cylinder
            rotate(90, [1, 0, 0])
            rotate(90, [0, 1, 0])

            //Modify the offset of the baselined text to center
            translate([0, (ccenter) ? -size / 2 : 0, 0])

            text_extrude(t[l],
                    center = false,
                    rotate = rotate,
                    font = font,
                    size = size,
                    spacing = spacing,
                    direction = undef, //We don't pass direction ( misaligns inside text() ). TODO: Investigate why
                    language = language,
                    script = script,
                    halign = "center", //This can be relaxed eventually
                    valign = "baseline", //Need this to keep all letters on the same line (could also support top -- otherw will make
                    extrusion_height = extrusion_height
                    );
        }
    }
}

//sphere(r)
//NB: Iterates through characters/glyphs and presents them individually (but supports actions across the entire string still)
//supports all features of text() except: halign and valign
module text_on_sphere(t = default_t,
                      //Object-specific
                      locn_vec = [0,0,0],
                      r,
                      eastwest = default_sphere_eastwest,
                      northsouth = default_sphere_northsouth,
                      rotate  = default_rotate,
                      spin = default_sphere_spin,
                      rounded  = default_sphere_rounded,
                      //All objects
                      extrusion_height = default_extrusion_height,
                      center = default_center, //center = false doesn't really even make sense to do (IMHO)
                      scale = default_scale,
                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      halign = undef, //Not supported - only here to enable a warning
                      valign = undef, //Not supported - only here to enable a warning
                      language = undef,
                      script = undef,
                      spacing = default_spacing
){
    //echo ("text_on_sphere:There are " ,len(t) ," letters in t" , t);

    if((halign != undef) || (halign != undef))
    {
        echo (str("text_on_sphere:","WARNING " , "halign and valign are NOT supported (functionality used in centering)."));
    }

    //TODO: refactor this?
    rtl_sign = (direction == "rtl") ? -1 : 1;

    //TODO: Look at changing this to extrusion_height_center
    //and renaming the other as text_center
    //If we are centered - we sink the radius by half the
    //extrusion height to draw "just below the surface"
    rr = (center) ? r - extrusion_height / 2 : r ;

    rotate(eastwest, [0, 0, 1])
    rotate(-northsouth, [1, 0, 0])
    rotate(spin, [0, 1, 0])
    {
    //This tries to center the text (for RTL text).
    ttb_btt_inaction = (direction == "ttb" || direction == "btt") ? 0 : 1;
    rotate( -rtl_sign * ttb_btt_inaction * rotation_for_center_text_string(t, size, spacing, rr, rotate, center), [0, 0, 1])
    {
        translate(locn_vec)
        if ( rounded == false ){
            __internal_text_on_sphere_helper(t = t,
                    r = rr,
                    rotate = rotate,
                    center = center,
                    scale = scale,
                    font = font,
                    size = size,
                    spacing = spacing,
                    direction = direction,
                    language = language,
                    script = script,
                    halign = halign,
                    valign = valign,
                    extrusion_height = extrusion_height
                );
        }else{
            //If rounding then clip the text inside an inner sphere and outer sphere
            intersection()
            {
            __internal_text_on_sphere_helper(t = t,
                    r = rr,
                    rotate = rotate,
                    center = center,
                    scale = scale,
                    font = font,
                    size = size,
                    spacing = spacing,
                    direction = direction,
                    language = language,
                    script = script,
                    halign = halign,
                    valign = valign,
                    extrusion_height = extrusion_height * 2 //Make it proud to clip it off.
            );

            //Shell - bounding inner and outer
            difference()
            {   //rounded outside
                sphere(rr + extrusion_height);
                // rounded inside for indented text
                sphere( rr  );
            }

            }
        }
        }
    }
}

//internal function -- don't call directly
//No default checks in this funciton -- done in parent
module __internal_text_on_sphere_helper(t = default_t,
                      //Object-specific
                      r,
                      //All objects
                      extrusion_height = default_extrusion_height,
                      center = default_center,
                      scale = default_scale,
                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      //halign           = undef,
                      //valign           = undef,
                      language = undef,
                      script = undef,
                      spacing = default_spacing
)
{
    rtl_sign = (direction == "rtl") ? -1 : 1;
    ttb_btt_inaction = (direction == "ttb" || direction == "btt") ? 0 : 1;
    ttb_btt_action = (direction == "ttb" || direction == "btt") ? 1 : 0;

    for (l = [0 : len(t) - 1]){
        rotate_z_inner = -90 + rtl_sign * ttb_btt_inaction * l * rotation_for_character(size, spacing, r, 0);

        rotate( rotate_z_inner, [0, 0, 1] )
        {
        translate_sign = (direction == "ttb" || direction == "btt") ? ((direction == "btt") ? 1 : -1)  : 0 ;
        //translate_effect = (direction=="ttb" || direction=="btt") ? 1  :  0 ;
        //Translate letter to be located on the sphere in up/down direction when we are doing TTB and BTT
        translate(r * [cos(0 + ttb_btt_action * rotation_for_character(size, spacing, r, rotate) * l), 0, translate_sign * ttb_btt_action * sin(0 +  rotation_for_character(size, spacing, r, rotate) * l ) ])
        //Rotate letter to be tangential to the new part of the sphere when we are doing TTB and BTT
        rotate(-90 * translate_sign * (0 + ttb_btt_action * rotation_for_character(size, spacing, r, rotate) * l ) / 90 , [0, 1, 0])
        //Flip character into position to be flush against sphere
        rotate(90, [1, 0, 0])
        rotate(90, [0, 1, 0])

        //Modify the offset of the baselined text to center
        translate([0, (center) ? -size / 2 : 0 , 0])

        text_extrude(t[l],
            center = false,
            rotate = rotate,
            scale = scale,
            font = font,
            size = size,
            spacing = spacing,
            direction = undef, //We don't pass direction ( misaligns inside text() ). TODO: Investigate why
            language = language,
            script = script,
            halign = "center", //This can be relaxed eventually
            valign = "baseline", //Need this to keep all letters on the same line (could also support top -- otherw will make wonky letters)
            extrusion_height = extrusion_height );
        }
    }
}

//cube(size)
//cube([width,h,depth])
//NB: cube_size is used instead of size (the name size is used in the text module)
//NB: Passes through text as strings -- supports all features of text()
module text_on_cube(  t = default_t,
                      //Object-specific
                      cube_size, //number or 3-vector
                      face = default_cube_face,
                      rightleft = default_cube_rightleft,
                      updown = default_cube_updown,
                      //All objects
                      locn_vec = [0, 0, 0],
                      extrusion_height = default_extrusion_height,
                      center = undef,
                      rotate = default_rotate,
                      scale = default_scale,
                      //All objects -- arguments as for text()
                      font = undef,
                      size = default_size,
                      direction = undef,
                      halign = undef,
                      valign = undef,
                      language = undef,
                      script = undef,
                      spacing = default_spacing
){
    //echo (str("text_on_cube:","There are " ,len(t) ," letters in t" , t));
    //echo (str("text_on_cube:","cube_size" , cube_size));

    if((halign != undef) || (halign != undef))
    {
        echo (str("text_on_cube:","WARNING " , "halign and valign are NOT supported."));
    }

    int_cube_size = (str(cube_size)[0] != "[") ? [cube_size, cube_size, cube_size] : cube_size;
    rotate_x = ( (face == "front") || (face == "back") || (face == "left") || (face == "right") ) ? 90
                : ((face == "bottom") ? 180 : 0) ; //Top is zero
    rotate_y = ( (face == "back") || (face == "left") || (face == "right") ) ? (face == "back") ? 180 : ((face == "left") ? -90 : 90) //Right is 90
                : 0 ; //Top, bottom, front are zero
    locn_vec_offset =
            (face == "front") ?               [+rightleft, -int_cube_size[1] / 2, +updown]
            :((face == "back") ?              [+rightleft, +int_cube_size[1] / 2, +updown]
                : ((fac e== "left") ?         [-int_cube_size[0] / 2, +rightleft, +updown]
                    : ((face == "right") ?    [+int_cube_size[0] / 2, +rightleft, +updown]
                        : ((face == "top") ?  [+rightleft, +updown, +int_cube_size[2] / 2]
                            :                 [+rightleft, -updown, -int_cube_size[2] / 2]
                            )))); //bottom

    translate(locn_vec + locn_vec_offset)
    rotate(rotate_x, [1, 0, 0])
    rotate(rotate_y, [0, 1, 0]) // rotate around the y axis (z before rotation)
    text_extrude(t,
            center = center,
            rotate = rotate,
            scale = scale,
            font = font,
            size = size,
            spacing = spacing,
            direction = direction, //Does this misalign inside text()? the individual character modules do.
            language = language,
            script = script,
            halign = halign,
            valign = valign,
            extrusion_height = extrusion_height );
}

function default_if_undef(val, default_val) = (val != undef) ? val : default_val;

//Print a single character or a string at the desired extrusion height
//Passes on values to text() that are passed in
//TODO: Add x,y,z rotations get text facing the right way from the start)
module text_extrude( t = default_t,
                     extrusion_height = default_extrusion_height,
                     center = default_center, //Fudgy. YMMV. //TODO:center_extrusion, or extrusion offset??
                     rotate = default_rotate,
                     scale = default_scale, //For scaling by different on axes (for widening etc)
                     //Following are test() params (in addition to t=)
                     font = default_font,
                     size = default_size,
                     direction = undef,
                     halign  = undef,
                     valign = undef,
                     language = undef,
                     script = undef,
                     spacing = default_spacing
)
{
    //echo (str("text_extrude:","There are " ,len(t) ," letters in text" , t));
    //echo (str("text_extrude:","extrusion_height=" , extrusion_height));

    if(center == true)
    {
        if((halign != undef) || (valign != undef))
        {
            echo (str("text_extrude:","WARNING " , "When center is true, then halign and valign are NOT supported."));
        }
    }

    //As arguments are explicitly set as undef (from higher functions) as they come down (they don't get defaults in this function as they are set as something)
    //we need to check and replace any that we don't want to be undef
    t = default_if_undef(t, default_t);
    font = default_if_undef(font, default_font);
    extrusion_height = default_if_undef(extrusion_height, default_extrusion_height);
    center = default_if_undef(center, default_center);
    rotate = default_if_undef(rotate, default_rotate);
    spacing = default_if_undef(spacing, default_spacing);
    size = default_if_undef(size, default_size);

    halign = (center) ? "center" : halign ;
    valign = (center) ? "center" : valign ;
    extrusion_center = (center) ? true : false ;

    scale( scale )
    rotate(rotate, [0, 0, -1]) //TODO: Do we want to make this so that the entire vector can be set?
    {
        linear_extrude(height = extrusion_height, convexity = 10, center = extrusion_center)
        text(text = t,
                size = size,
                $fn = 40,
                font = font,
                direction = direction,
                spacing = spacing,
                halign = halign,
                valign = valign,
                language = language,
                script = script);
    }
}


main: move axis

main: move axis

include <thing_libutils/metric-thread.scad>;
include <thing_libutils/metric-hexnut.scad>;
use <thing_libutils/shapes.scad>;
use <thing_libutils/misc.scad>;
use <thing_libutils/attach.scad>;
use <thing_libutils/triangles.scad>
use <thing_libutils/linear-extrusion.scad>;
include <thing_libutils/timing-belts.scad>;

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

include <misc.scad>
include <extruder-direct.scad>
include <x-axis-end.scad>
include <x-carriage.scad>
include <y-axis-motor-mount.scad>
include <y-axis-carriage-bearing-mount.scad>
include <y-axis-carriage-belt-clamp.scad>
include <y-axis-idler.scad>
include <z-axis-motor-mount.scad>
include <z-axis-upper-gantry-connector.scad>
include <psu.scad>
include <rod-clamps.scad>

use <scad-utils/trajectory.scad>
use <scad-utils/trajectory_path.scad>
use <scad-utils/transformations.scad>
use <scad-utils/shapes.scad>
use <list-comprehension-demos/skin.scad>

axis_pos_x = -213/2*mm;
axis_pos_y = 0*mm;
axis_range_z=[85,380];
axis_pos_z = 80;//axis_range_z[1]*mm/2;

module x_axis()
{
    // x axis
    translate([0,0,axis_pos_z])
    {
        if(!preview_mode)
        {
            zrod_offset = zmotor_mount_rod_offset_x;
            translate([-main_width/2-zrod_offset+xaxis_end_motor_offset[0], xaxis_zaxis_distance_y, 0])
                belt_path(main_width+2*(zrod_offset)+xaxis_end_motor_offset[0], 6, xaxis_pulley_inner_d, orient=[1,0,0], align=[1,0,0]);
        }

        translate([axis_pos_x,0,0])
        {
            // x carriage
            attach(xaxis_carriage_conn, [[0,-xaxis_zaxis_distance_y,0],[0,0,0]])
            {
                x_carriage(show_bearings=true);
            }

            if(!preview_mode)
            {
                attach([[35, -0, -0], [1,0,0]], extruder_conn_xcarriage)
                {
                    extruder();
                }
            }
        }

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

        for(x=[-1,1])
        {
            translate([x*(main_width/2), 0, 0])
            {
                translate([0, xaxis_zaxis_distance_y, 0])
                translate([x*zmotor_mount_rod_offset_x, 0, 0])
                xaxis_end(with_motor=x==-1, show_nut=true);
            }
        }
    }
}

module y_axis()
{
    // y axis
    attach([[yaxis_belt_path_offset_x,main_depth/2,yaxis_motor_offset_z], [0,-1,0]], yaxis_motor_mount_conn)
    {
        yaxis_motor_mount(show_motor=true);
    }

    extrusion_idler_conn = [[yaxis_belt_path_offset_x, -main_depth/2-extrusion_size, -extrusion_size+yaxis_idler_offset_z], [0,-1,0]];
    attach(extrusion_idler_conn, yaxis_idler_conn)
    {
        yaxis_idler();
        attach(yaxis_idler_conn_pulleyblock, yaxis_idler_pulleyblock_conn)
        {
            yaxis_idler_pulleyblock(show_pulley=true);
        }
    }

    translate([yaxis_belt_path_offset_x,0,yaxis_belt_path_offset_z])
    {
        if(!preview_mode)
        {
            translate([0,main_depth/2-yaxis_motor_offset_x,0])
            {
                /*cubea([10,main_depth-yaxis_motor_offset_x,10], align=[0,-1,0]);*/
                belt_path(main_depth-yaxis_motor_offset_x-yaxis_idler_pulley_offset_y, 6, yaxis_pulley_inner_d, align=[0,-1,0], orient=[0,1,0]);
            }
        }
    }

    translate([0,-axis_pos_y,yaxis_bearing[0]/2])
    attach(yaxis_carriage_bearing_mount_conn_bearing, yaxis_belt_mount_conn)
    {
        yaxis_belt_holder();
    }

    translate([0,0,yaxis_bearing[0]/2])
    {
        for(i=[-1,1])
        for(j=[-1,1])
        {
            attach([[i*(yaxis_rod_distance/2),j*(main_depth/2+extrusion_size/2),0],[0,0,1]],mount_rod_clamp_conn_rod)
            {
                mount_rod_clamp_full(rod_d=zaxis_rod_d, thick=4, width=extrusion_size, thread=zmotor_mount_clamp_thread, orient=[0,1,0]);
            }
        }

        for(i=[-1,1])
        translate([i*(yaxis_rod_distance/2), 0, 0])
        {
            fncylindera(h=yaxis_rod_l,d=yaxis_rod_d, orient=[0,1,0]);

            for(j=[-1,1])
            {
                attach([[0,j*(yaxis_bearing_distance_y/2)-axis_pos_y,0],[0,0,-1]], yaxis_carriage_bearing_mount_conn_bearing)
                {
                    yaxis_carriage_bearing_mount(show_bearing=true, show_zips=true);
                }
            }
        }

        attach(yaxis_carriage_bearing_mount_conn_bearing, [[0,axis_pos_y,0],[0,0,1]])
        {
            /*translate([x*yaxis_carriage_size[0]/2, y*(yaxis_carriage_size[1]-16*mm)/2, 0])*/
            // y axis plate
            for(x=[-1,1])
            for(y=[-1,1])
            hull()
            {
                translate([x*yaxis_carriage_size[0]/2, y*(yaxis_carriage_size[1])/2, 0])
                {
                    fncylindera(d=10*mm, h=yaxis_carriage_size[2], align=[-x,-y,1]);
                }
                translate([x*(yaxis_carriage_size[0]/2-16*mm), y*(yaxis_carriage_size[1]/2-16*mm), 0])
                {
                    fncylindera(d=10*mm, h=yaxis_carriage_size[2], align=[-x,-y,1]);
                }
            }

            cubea([yaxis_carriage_size[0]-16*mm*2,yaxis_carriage_size[1]-16*mm*2, yaxis_carriage_size[2]], align=[0,0,1]);

            translate([0,0,10*mm])
                cubea(yaxis_carriage_size, align=[0,0,1]);
        }
    }

}

module z_axis()
{
    // z axis
    for(i=[-1,1])
    {
        translate([i*(main_width/2),0,main_height])
        {
            mirror([i==-1?1:0,0,0])
            {
                zaxis_upper_gantry_zrod_connector();

                translate([zmotor_mount_rod_offset_x, 0, extrusion_size/2])
                    mount_rod_clamp_half(
                            rod_d=zaxis_rod_d,
                            screw_dist=zmotor_mount_clamp_dist,
                            thick=5,
                            base_thick=5,
                            width=zmotor_mount_thickness_h,
                            thread=zmotor_mount_clamp_thread);
            }
        }

        translate([i*(main_width/2), 0, 0])
        {
            translate([0,0,zaxis_motor_offset_z])
                mirror([i==-1?1:0,0,0])
                {
                    zaxis_motor_mount(show_motor=true);

                    translate([zmotor_mount_rod_offset_x, 0, zmotor_mount_thickness_h/2])
                    {
                        mount_rod_clamp_half(
                                rod_d=zaxis_rod_d,
                                screw_dist=zmotor_mount_clamp_dist,
                                thick=5,
                                base_thick=5,
                                width=zmotor_mount_thickness_h,
                                thread=zmotor_mount_clamp_thread);
                    }
                }

            // z smooth rods
            translate([i*(zmotor_mount_rod_offset_x),0,0])
            {
                // z rods
                translate([0,0,zaxis_motor_offset_z-50])
                    fncylindera(h=zaxis_rod_l,d=zaxis_rod_d, align=[0,0,1]);

                for(j=[-1,1])
                    translate([0,0,axis_pos_z-j*xaxis_rod_distance/2])
                        bearing(zaxis_bearing);

            }
        }
    }

}

module main()
{
    color(extrusion_color)
    gantry_lower();

    color(extrusion_color)
    translate([0,0,-main_lower_dist_z])
    gantry_lower();

    color(extrusion_color)
    for(i=[-1,1])
    translate([0,i*(main_upper_dist_y/2),0])
    gantry_upper();

    x_axis();
    y_axis();
    z_axis();

    if(!preview_mode)
    {
        for(x=[-1,1])
        translate([x*(main_width/2-extrusion_size),main_depth/2,-main_lower_dist_z-extrusion_size])
        {
            translate([-x*psu_w/2, -psu_d/2, psu_h/2])
            {
                psu();

                mirror([x==-1?1:0,0,0])
                    translate([0, -psu_screw_dist_y/2, 0])
                    psu_extrusion_bracket_side();

                translate([0, psu_screw_dist_y/2, 0])
                    psu_extrusion_bracket_back();
            }
        }

        translate([0,-main_depth/2,-main_lower_dist_z-extrusion_size])
            translate([-100,100,0])
            rotate([0,0,270])
            import("stl/RAMPS1_4.STL");
    }
}

module gantry_upper()
{
    for(i=[-1,1])
    translate([i*(main_width/2), 0, 0])
    {
        if(preview_mode)
        {
            cubea(size=[extrusion_size, extrusion_size, main_height], align=[-i,0,1]);
        }
        else
        {
            linear_extrusion(h=main_height, align=[-i,0,1], orient=[0,0,1]);
        }
    }

    translate([0, 0, main_height])
    {
        if(preview_mode)
        {
            cubea(size=[main_upper_width, extrusion_size, extrusion_size], align=[0,0,1]);
        }
        else
        {
            linear_extrusion(h=main_upper_width, align=[0,0,1], orient=[1,0,0]);
        }
    }
}

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

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

module enclosure()
{
    w=60*cm;
    h=60*cm;
    d=60*cm;
    wallthick=2*cm;
    backthick=0.5*cm;

    translate([0, 0, h/2])
    {
        // left/right walls
        for(i=[-1,1])
            translate([i*w/2,0,0])
                cubea([wallthick,d,h], align=[-i, 0, 0]);

        // back plate
        translate([0,d/2,0])
            cubea([w,backthick,h], align=[0,-1, 0]);

        // top/bottom plate
        for(i=[-1,1])
        translate([0,0,i*h/2])
            cubea([w-wallthick*2,d,wallthick], align=[0,0, -i]);
    }
}

translate([0, 0, main_lower_dist_z+extrusion_size*2])
main();

/*psu();*/

/*%enclosure();*/

include <thing_libutils/metric-thread.scad>;
include <thing_libutils/metric-hexnut.scad>;
use <thing_libutils/shapes.scad>;
use <thing_libutils/misc.scad>;
use <thing_libutils/attach.scad>;
use <thing_libutils/triangles.scad>
use <thing_libutils/linear-extrusion.scad>;
include <thing_libutils/timing-belts.scad>;

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

include <misc.scad>
include <extruder-direct.scad>
include <x-axis-end.scad>
include <x-carriage.scad>
include <y-axis-motor-mount.scad>
include <y-axis-carriage-bearing-mount.scad>
include <y-axis-carriage-belt-clamp.scad>
include <y-axis-idler.scad>
include <z-axis-motor-mount.scad>
include <z-axis-upper-gantry-connector.scad>
include <psu.scad>
include <rod-clamps.scad>

use <scad-utils/trajectory.scad>
use <scad-utils/trajectory_path.scad>
use <scad-utils/transformations.scad>
use <scad-utils/shapes.scad>
use <list-comprehension-demos/skin.scad>

axis_pos_x = 0*mm;
axis_pos_y = 0*mm;
axis_range_z=[85,380];
axis_pos_z = axis_range_z[1]*mm/2;

module x_axis()
{
    // x axis
    translate([0,0,axis_pos_z])
    {
        if(!preview_mode)
        {
            zrod_offset = zmotor_mount_rod_offset_x;
            translate([-main_width/2-zrod_offset+xaxis_end_motor_offset[0], xaxis_zaxis_distance_y, 0])
                belt_path(main_width+2*(zrod_offset)+xaxis_end_motor_offset[0], 6, xaxis_pulley_inner_d, orient=[1,0,0], align=[1,0,0]);
        }

        translate([axis_pos_x,0,0])
        {
            // x carriage
            attach(xaxis_carriage_conn, [[0,-xaxis_zaxis_distance_y,0],[0,0,0]])
            {
                x_carriage(show_bearings=true);
            }

            if(!preview_mode)
            {
                attach([[35, -0, -0], [1,0,0]], extruder_conn_xcarriage)
                {
                    extruder();
                }
            }
        }

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

        for(x=[-1,1])
        {
            translate([x*(main_width/2), 0, 0])
            {
                translate([0, xaxis_zaxis_distance_y, 0])
                translate([x*zmotor_mount_rod_offset_x, 0, 0])
                xaxis_end(with_motor=x==-1, show_nut=true);
            }
        }
    }
}

module y_axis()
{
    // y axis
    attach([[yaxis_belt_path_offset_x,main_depth/2,yaxis_motor_offset_z], [0,-1,0]], yaxis_motor_mount_conn)
    {
        yaxis_motor_mount(show_motor=true);
    }

    extrusion_idler_conn = [[yaxis_belt_path_offset_x, -main_depth/2-extrusion_size, -extrusion_size+yaxis_idler_offset_z], [0,-1,0]];
    attach(extrusion_idler_conn, yaxis_idler_conn)
    {
        yaxis_idler();
        attach(yaxis_idler_conn_pulleyblock, yaxis_idler_pulleyblock_conn)
        {
            yaxis_idler_pulleyblock(show_pulley=true);
        }
    }

    translate([yaxis_belt_path_offset_x,0,yaxis_belt_path_offset_z])
    {
        if(!preview_mode)
        {
            translate([0,main_depth/2-yaxis_motor_offset_x,0])
            {
                /*cubea([10,main_depth-yaxis_motor_offset_x,10], align=[0,-1,0]);*/
                belt_path(main_depth-yaxis_motor_offset_x-yaxis_idler_pulley_offset_y, 6, yaxis_pulley_inner_d, align=[0,-1,0], orient=[0,1,0]);
            }
        }
    }

    translate([0,-axis_pos_y,yaxis_bearing[0]/2])
    attach(yaxis_carriage_bearing_mount_conn_bearing, yaxis_belt_mount_conn)
    {
        yaxis_belt_holder();
    }

    translate([0,0,yaxis_bearing[0]/2])
    {
        for(i=[-1,1])
        for(j=[-1,1])
        {
            attach([[i*(yaxis_rod_distance/2),j*(main_depth/2+extrusion_size/2),0],[0,0,1]],mount_rod_clamp_conn_rod)
            {
                mount_rod_clamp_full(rod_d=zaxis_rod_d, thick=4, width=extrusion_size, thread=zmotor_mount_clamp_thread, orient=[0,1,0]);
            }
        }

        for(i=[-1,1])
        translate([i*(yaxis_rod_distance/2), 0, 0])
        {
            fncylindera(h=yaxis_rod_l,d=yaxis_rod_d, orient=[0,1,0]);

            for(j=[-1,1])
            {
                attach([[0,j*(yaxis_bearing_distance_y/2)-axis_pos_y,0],[0,0,-1]], yaxis_carriage_bearing_mount_conn_bearing)
                {
                    yaxis_carriage_bearing_mount(show_bearing=true, show_zips=true);
                }
            }
        }

        attach(yaxis_carriage_bearing_mount_conn_bearing, [[0,axis_pos_y,0],[0,0,1]])
        {
            /*translate([x*yaxis_carriage_size[0]/2, y*(yaxis_carriage_size[1]-16*mm)/2, 0])*/
            // y axis plate
            for(x=[-1,1])
            for(y=[-1,1])
            hull()
            {
                translate([x*yaxis_carriage_size[0]/2, y*(yaxis_carriage_size[1])/2, 0])
                {
                    fncylindera(d=10*mm, h=yaxis_carriage_size[2], align=[-x,-y,1]);
                }
                translate([x*(yaxis_carriage_size[0]/2-16*mm), y*(yaxis_carriage_size[1]/2-16*mm), 0])
                {
                    fncylindera(d=10*mm, h=yaxis_carriage_size[2], align=[-x,-y,1]);
                }
            }

            cubea([yaxis_carriage_size[0]-16*mm*2,yaxis_carriage_size[1]-16*mm*2, yaxis_carriage_size[2]], align=[0,0,1]);

            translate([0,0,10*mm])
                cubea(yaxis_carriage_size, align=[0,0,1]);
        }
    }

}

module z_axis()
{
    // z axis
    for(i=[-1,1])
    {
        translate([i*(main_width/2),0,main_height])
        {
            mirror([i==-1?1:0,0,0])
            {
                zaxis_upper_gantry_zrod_connector();

                translate([zmotor_mount_rod_offset_x, 0, extrusion_size/2])
                    mount_rod_clamp_half(
                            rod_d=zaxis_rod_d,
                            screw_dist=zmotor_mount_clamp_dist,
                            thick=5,
                            base_thick=5,
                            width=zmotor_mount_thickness_h,
                            thread=zmotor_mount_clamp_thread);
            }
        }

        translate([i*(main_width/2), 0, 0])
        {
            translate([0,0,zaxis_motor_offset_z])
                mirror([i==-1?1:0,0,0])
                {
                    zaxis_motor_mount(show_motor=true);

                    translate([zmotor_mount_rod_offset_x, 0, zmotor_mount_thickness_h/2])
                    {
                        mount_rod_clamp_half(
                                rod_d=zaxis_rod_d,
                                screw_dist=zmotor_mount_clamp_dist,
                                thick=5,
                                base_thick=5,
                                width=zmotor_mount_thickness_h,
                                thread=zmotor_mount_clamp_thread);
                    }
                }

            // z smooth rods
            translate([i*(zmotor_mount_rod_offset_x),0,0])
            {
                // z rods
                translate([0,0,zaxis_motor_offset_z-50])
                    fncylindera(h=zaxis_rod_l,d=zaxis_rod_d, align=[0,0,1]);

                for(j=[-1,1])
                    translate([0,0,axis_pos_z-j*xaxis_rod_distance/2])
                        bearing(zaxis_bearing);

            }
        }
    }

}

module main()
{
    color(extrusion_color)
    gantry_lower();

    color(extrusion_color)
    translate([0,0,-main_lower_dist_z])
    gantry_lower();

    color(extrusion_color)
    for(i=[-1,1])
    translate([0,i*(main_upper_dist_y/2),0])
    gantry_upper();

    x_axis();
    y_axis();
    z_axis();

    if(!preview_mode)
    {
        for(x=[-1,1])
        translate([x*(main_width/2-extrusion_size),main_depth/2,-main_lower_dist_z-extrusion_size])
        {
            translate([-x*psu_w/2, -psu_d/2, psu_h/2])
            {
                psu();

                mirror([x==-1?1:0,0,0])
                    translate([0, -psu_screw_dist_y/2, 0])
                    psu_extrusion_bracket_side();

                translate([0, psu_screw_dist_y/2, 0])
                    psu_extrusion_bracket_back();
            }
        }

        translate([0,-main_depth/2,-main_lower_dist_z-extrusion_size])
            translate([-100,100,0])
            rotate([0,0,270])
            import("stl/RAMPS1_4.STL");
    }
}

module gantry_upper()
{
    for(i=[-1,1])
    translate([i*(main_width/2), 0, 0])
    {
        if(preview_mode)
        {
            cubea(size=[extrusion_size, extrusion_size, main_height], align=[-i,0,1]);
        }
        else
        {
            linear_extrusion(h=main_height, align=[-i,0,1], orient=[0,0,1]);
        }
    }

    translate([0, 0, main_height])
    {
        if(preview_mode)
        {
            cubea(size=[main_upper_width, extrusion_size, extrusion_size], align=[0,0,1]);
        }
        else
        {
            linear_extrusion(h=main_upper_width, align=[0,0,1], orient=[1,0,0]);
        }
    }
}

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

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

module enclosure()
{
    w=60*cm;
    h=60*cm;
    d=60*cm;
    wallthick=2*cm;
    backthick=0.5*cm;

    translate([0, 0, h/2])
    {
        // left/right walls
        for(i=[-1,1])
            translate([i*w/2,0,0])
                cubea([wallthick,d,h], align=[-i, 0, 0]);

        // back plate
        translate([0,d/2,0])
            cubea([w,backthick,h], align=[0,-1, 0]);

        // top/bottom plate
        for(i=[-1,1])
        translate([0,0,i*h/2])
            cubea([w-wallthick*2,d,wallthick], align=[0,0, -i]);
    }
}

translate([0, 0, main_lower_dist_z+extrusion_size*2])
main();

/*psu();*/

/*%enclosure();*/


y/idler: fix nut trap

y/idler: fix nut trap

include <y-idler.h>

include <thing_libutils/materials.scad>
use <thing_libutils/screws.scad>

module yaxis_idler(part)
{
    tighten_screw_dia_outer = yaxis_idler_mount_tightscrew_dia*4;
    mount_screw_dist = (yidler_w/2+yaxis_idler_mount_thread_dia*3)*1.3;

    if(part==U)
    {
        difference()
        {
            yaxis_idler(part="pos");
            yaxis_idler(part="neg");
        }
        yaxis_idler(part="vit");
    }
    else if(part=="pos")
    material(Mat_Plastic)
    {
        // front plate
        tx(-extrusion_size/2)
        rcubea(
            size=[yaxis_idler_mount_thickness, yidler_mount_width, extrusion_size],
            align=-X,
            extra_size=Z*yaxis_idler_mount_thickness,
            extra_align=Z
            );

        // top mount plate
        tx(-yaxis_idler_mount_thickness)
        tz(extrusion_size/2)
        rcubea(
            size=[extrusion_size, yidler_mount_width, yaxis_idler_mount_thickness],
            align=Z,
            extra_size=[yaxis_idler_mount_thickness,0,0],
            extra_align=X
            );

        intersection()
        {
            tz(extrusion_size/2)
            cubea([100, yidler_mount_width, 100], align=Z);

            hull()
            {
                for(y=[-1,1])
                ty(y*yaxis_idler_tightscrew_dist)
                tx(-yaxis_idler_mount_thickness)
                tz(extrusion_size/2)
                {
                    translate([yaxis_idler_mount_thickness/2,0,yaxis_belt_path_offset_z])
                    rcylindera(
                        h=extrusion_size+yaxis_idler_mount_thickness,
                        d=tighten_screw_dia_outer,
                        align=N,
                        orient=X
                        );
                    rcubea(
                        size=[extrusion_size, mount_screw_dist-yaxis_idler_mount_tightscrew_dia*3, yaxis_idler_mount_thickness],
                        align=Z,
                        extra_size=[yaxis_idler_mount_thickness,0,0],
                        extra_align=X
                        );
                }

                // adjustment screw
                tx(-yaxis_idler_mount_thickness)
                tz(extrusion_size/2)
                t(yaxis_idler_mount_adjustscrew_offset)
                rcylindera(d=yaxis_idler_mount_adjustscrew_hexnut_dia*2, orient=X, align=X);

                // adjustment screw
                tx(-yaxis_idler_mount_thickness)
                tz(extrusion_size/2)
                t([yaxis_idler_mount_thickness/2,0,yaxis_belt_path_offset_z])
                t(yaxis_idler_mount_adjustscrew_offset)
                rcylindera(
                    h=extrusion_size+yaxis_idler_mount_thickness,
                    d=yaxis_idler_mount_adjustscrew_hexnut_dia*sqrt(2),
                    align=N,
                    orient=X
                    );
                /*rcylindera(d=yaxis_idler_mount_adjustscrew_hexnut_dia*2, orient=X, align=X);*/
            }

        }

    }
    else if(part=="neg")
    {
        // top/vertical mount screws (to extrusion)
        tz(extrusion_size/2+yaxis_idler_mount_thickness)
        for(y=[-1,1])
        ty(y*mount_screw_dist/2)
        screw_cut(nut=extrusion_nut, head="button", h=yaxis_idler_mount_thickness+5*mm, with_nut=false, orient=-Z, align=-Z);

        // mount screws for pulley block
        for(y=[-1,1])
        tx(-extrusion_size/2-yaxis_idler_mount_thickness)
        ty(y*yaxis_idler_tightscrew_dist)
        tz(extrusion_size/2+yaxis_belt_path_offset_z)
        screw_cut(yaxis_idler_mount_tightscrew_hexnut, head_embed=false, h=extrusion_size+yaxis_idler_mount_thickness+10*mm, with_nut=false, orient=X, align=X);

        // pulley block adjustment screw
        tx(-extrusion_size/2)
        tx(-yaxis_idler_mount_thickness)
        tz(extrusion_size/2+yaxis_belt_path_offset_z)
        t(yaxis_idler_mount_adjustscrew_offset)
        screw_cut(yaxis_idler_mount_adjustscrew_hexnut, head="button", h=extrusion_size+yaxis_idler_mount_thickness+10*mm, head_embed=false, with_nut=false, orient=X, align=X);

        // front/horizontal mount screws (to extrusion)
        tx(-extrusion_size/2)
        tx(-yaxis_idler_mount_thickness)
        for(y=[-1,1])
        ty(y*(yaxis_idler_mount_thread_dia*2.5))
        screw_cut(nut=extrusion_nut, head="button", h=yaxis_idler_mount_thickness+5*mm, with_nut=false, orient=X, align=X);
    }
}

module yaxis_idler_pulleyblock(part, show_pulley=false)
{
    washer_h=.8*mm + .1*mm;
    h = yaxis_idler_pulley_h + 2*washer_h + 3*mm*2;
    d=yaxis_idler_mount_tightscrew_hexnut_dia*2;

    if(part==U)
    {
        difference()
        {
            yaxis_idler_pulleyblock(part="pos");
            yaxis_idler_pulleyblock(part="neg");
        }
        %yaxis_idler_pulleyblock(part="vit");
    }
    else if(part=="pos")
    material(Mat_Plastic)
    {
        hull()
        {
            // idler
            tz(h/2)
            rcylindera(d=yaxis_idler_pulley_thread_dia+15*mm, h=h, orient=-Z, align=-Z);

            // tension screws
            tx(-yaxis_idler_pulleyblock_supportsize/2-yaxis_idler_pulley_tight_len)
            for(y=[-1,1])
            ty(y*yaxis_idler_tightscrew_dist)
            rcubea([d,d,yaxis_idler_pulley_tight_len], orient=X, align=X);

            // adjustment screw
            tx(-yaxis_idler_pulleyblock_supportsize/2-yaxis_idler_pulley_tight_len)
            t(yaxis_idler_mount_adjustscrew_offset)
            rcylindera(d=yaxis_idler_mount_adjustscrew_hexnut_dia*sqrt(2), orient=X, align=X);
        }
    }
    else if(part=="neg")
    {

        // pulley+washers cutout
        tx(-3*mm)
        cubea(
            size=[yaxis_idler_pulley_outer_d, yaxis_idler_pulley_outer_d*3, yaxis_idler_pulley_h+0.5+2*washer_h],
            align=N,
            extra_size=[20*mm,0,0], extra_align=X
            );

        // pulley screw
        tz(h/2)
        screw_cut(yaxis_idler_pulley_nut, head="button", h=h+5*mm, orient=-Z, align=-Z);

        // tension screws
        for(y=[-1,1])
        tx(-yaxis_idler_pulleyblock_supportsize/2-yaxis_idler_pulley_tight_len)
        ty(y*yaxis_idler_tightscrew_dist)
        screw_cut(yaxis_idler_mount_tightscrew_hexnut, head_embed=false, nut_offset=2*mm, h=yaxis_idler_pulley_tight_len, orient=X, align=X);

        // adjustment screw
        tx(-yaxis_idler_pulleyblock_supportsize/2-yaxis_idler_pulley_tight_len)
        t(yaxis_idler_mount_adjustscrew_offset)
        {
        tx(yaxis_idler_pulley_tight_len/2)
            tx(9*mm)
            nut_trap_cut(nut=yaxis_idler_mount_adjustscrew_hexnut, screw_l=10*mm, trap_axis=-Z, orient=-X, align=X);

            screw_cut(nut=yaxis_idler_mount_adjustscrew_hexnut, screw_l=10*mm, with_nut=false, trap_axis=-Z, orient=X, align=X);
        }
    }
    else if(part=="vit")
    {
        pulley(pulley_2GT_20T_idler);
        // TODO: add washers
    }

}

module part_y_idler()
{
    attach([N, -Z], yaxis_idler_conn)
    {
        yaxis_idler();
    }
}

module part_y_idler_pulleyblock()
{
    attach([N, Z], yaxis_idler_pulleyblock_conn_print)
    {
        yaxis_idler_pulleyblock(show_pulley=false);
    }
}

if(false)
{
    yaxis_idler();
    yaxis_idler_pulleyblock();
}

if(false)
{
    attach([N,-X], yaxis_idler_conn)
    {
        yaxis_idler();
        attach(yaxis_idler_conn_pulleyblock, yaxis_idler_pulleyblock_conn)
        {
            yaxis_idler_pulleyblock(show_pulley=true);
        }
    }
}

include <y-idler.h>

include <thing_libutils/materials.scad>
use <thing_libutils/screws.scad>

module yaxis_idler(part)
{
    tighten_screw_dia_outer = yaxis_idler_mount_tightscrew_dia*4;
    mount_screw_dist = (yidler_w/2+yaxis_idler_mount_thread_dia*3)*1.3;

    if(part==U)
    {
        difference()
        {
            yaxis_idler(part="pos");
            yaxis_idler(part="neg");
        }
        yaxis_idler(part="vit");
    }
    else if(part=="pos")
    material(Mat_Plastic)
    {
        // front plate
        tx(-extrusion_size/2)
        rcubea(
            size=[yaxis_idler_mount_thickness, yidler_mount_width, extrusion_size],
            align=-X,
            extra_size=Z*yaxis_idler_mount_thickness,
            extra_align=Z
            );

        // top mount plate
        tx(-yaxis_idler_mount_thickness)
        tz(extrusion_size/2)
        rcubea(
            size=[extrusion_size, yidler_mount_width, yaxis_idler_mount_thickness],
            align=Z,
            extra_size=[yaxis_idler_mount_thickness,0,0],
            extra_align=X
            );

        intersection()
        {
            tz(extrusion_size/2)
            cubea([100, yidler_mount_width, 100], align=Z);

            hull()
            {
                for(y=[-1,1])
                ty(y*yaxis_idler_tightscrew_dist)
                tx(-yaxis_idler_mount_thickness)
                tz(extrusion_size/2)
                {
                    translate([yaxis_idler_mount_thickness/2,0,yaxis_belt_path_offset_z])
                    rcylindera(
                        h=extrusion_size+yaxis_idler_mount_thickness,
                        d=tighten_screw_dia_outer,
                        align=N,
                        orient=X
                        );
                    rcubea(
                        size=[extrusion_size, mount_screw_dist-yaxis_idler_mount_tightscrew_dia*3, yaxis_idler_mount_thickness],
                        align=Z,
                        extra_size=[yaxis_idler_mount_thickness,0,0],
                        extra_align=X
                        );
                }

                // adjustment screw
                tx(-yaxis_idler_mount_thickness)
                tz(extrusion_size/2)
                t(yaxis_idler_mount_adjustscrew_offset)
                rcylindera(d=yaxis_idler_mount_adjustscrew_hexnut_dia*2, orient=X, align=X);

                // adjustment screw
                tx(-yaxis_idler_mount_thickness)
                tz(extrusion_size/2)
                t([yaxis_idler_mount_thickness/2,0,yaxis_belt_path_offset_z])
                t(yaxis_idler_mount_adjustscrew_offset)
                rcylindera(
                    h=extrusion_size+yaxis_idler_mount_thickness,
                    d=yaxis_idler_mount_adjustscrew_hexnut_dia*sqrt(2),
                    align=N,
                    orient=X
                    );
                /*rcylindera(d=yaxis_idler_mount_adjustscrew_hexnut_dia*2, orient=X, align=X);*/
            }

        }

    }
    else if(part=="neg")
    {
        // top/vertical mount screws (to extrusion)
        tz(extrusion_size/2+yaxis_idler_mount_thickness)
        for(y=[-1,1])
        ty(y*mount_screw_dist/2)
        screw_cut(nut=extrusion_nut, head="button", h=yaxis_idler_mount_thickness+5*mm, with_nut=false, orient=-Z, align=-Z);

        // mount screws for pulley block
        for(y=[-1,1])
        tx(-extrusion_size/2-yaxis_idler_mount_thickness)
        ty(y*yaxis_idler_tightscrew_dist)
        tz(extrusion_size/2+yaxis_belt_path_offset_z)
        screw_cut(yaxis_idler_mount_tightscrew_hexnut, head_embed=false, h=extrusion_size+yaxis_idler_mount_thickness+10*mm, with_nut=false, orient=X, align=X);

        // pulley block adjustment screw
        tx(-extrusion_size/2)
        tx(-yaxis_idler_mount_thickness)
        tz(extrusion_size/2+yaxis_belt_path_offset_z)
        t(yaxis_idler_mount_adjustscrew_offset)
        screw_cut(yaxis_idler_mount_adjustscrew_hexnut, head="button", h=extrusion_size+yaxis_idler_mount_thickness+10*mm, head_embed=false, with_nut=false, orient=X, align=X);

        // front/horizontal mount screws (to extrusion)
        tx(-extrusion_size/2)
        tx(-yaxis_idler_mount_thickness)
        for(y=[-1,1])
        ty(y*(yaxis_idler_mount_thread_dia*2.5))
        screw_cut(nut=extrusion_nut, head="button", h=yaxis_idler_mount_thickness+5*mm, with_nut=false, orient=X, align=X);
    }
}

module yaxis_idler_pulleyblock(part, show_pulley=false)
{
    washer_h=.8*mm + .1*mm;
    h = yaxis_idler_pulley_h + 2*washer_h + 3*mm*2;
    d=yaxis_idler_mount_tightscrew_hexnut_dia*2;

    if(part==U)
    {
        difference()
        {
            yaxis_idler_pulleyblock(part="pos");
            yaxis_idler_pulleyblock(part="neg");
        }
        %yaxis_idler_pulleyblock(part="vit");
    }
    else if(part=="pos")
    material(Mat_Plastic)
    {
        hull()
        {
            // idler
            tz(h/2)
            rcylindera(d=yaxis_idler_pulley_thread_dia+15*mm, h=h, orient=-Z, align=-Z);

            // tension screws
            tx(-yaxis_idler_pulleyblock_supportsize/2-yaxis_idler_pulley_tight_len)
            for(y=[-1,1])
            ty(y*yaxis_idler_tightscrew_dist)
            rcubea([d,d,yaxis_idler_pulley_tight_len], orient=X, align=X);

            // adjustment screw
            tx(-yaxis_idler_pulleyblock_supportsize/2-yaxis_idler_pulley_tight_len)
            t(yaxis_idler_mount_adjustscrew_offset)
            rcylindera(d=yaxis_idler_mount_adjustscrew_hexnut_dia*sqrt(2), orient=X, align=X);
        }
    }
    else if(part=="neg")
    {

        // pulley+washers cutout
        tx(-3*mm)
        cubea(
            size=[yaxis_idler_pulley_outer_d, yaxis_idler_pulley_outer_d*3, yaxis_idler_pulley_h+0.5+2*washer_h],
            align=N,
            extra_size=[20*mm,0,0], extra_align=X
            );

        // pulley screw
        tz(h/2)
        screw_cut(yaxis_idler_pulley_nut, head="button", h=h+5*mm, orient=-Z, align=-Z);

        // tension screws
        for(y=[-1,1])
        tx(-yaxis_idler_pulleyblock_supportsize/2-yaxis_idler_pulley_tight_len)
        ty(y*yaxis_idler_tightscrew_dist)
        screw_cut(yaxis_idler_mount_tightscrew_hexnut, head_embed=false, nut_offset=2*mm, h=yaxis_idler_pulley_tight_len, orient=X, align=X);

        // adjustment screw
        tx(-yaxis_idler_pulleyblock_supportsize/2-yaxis_idler_pulley_tight_len)
        tx(yaxis_idler_pulley_tight_len/2)
        t(yaxis_idler_mount_adjustscrew_offset)
        {
            tx(yaxis_idler_pulley_tight_len/2)
            tx(5*mm)
            nut_trap_cut(nut=yaxis_idler_mount_adjustscrew_hexnut, screw_l=12*mm, trap_axis=-Z, orient=-X, align=X);

            tx(-yaxis_idler_pulley_tight_len/2)
            screw_cut(nut=yaxis_idler_mount_adjustscrew_hexnut, h=7*mm, with_nut=false, orient=X, align=X);
        }
    }
    else if(part=="vit")
    {
        pulley(pulley_2GT_20T_idler);
        // TODO: add washers
    }

}

module part_y_idler()
{
    attach([N, -Z], yaxis_idler_conn)
    {
        yaxis_idler();
    }
}

module part_y_idler_pulleyblock()
{
    attach([N, Z], yaxis_idler_pulleyblock_conn_print)
    {
        yaxis_idler_pulleyblock(show_pulley=false);
    }
}

if(false)
{
    yaxis_idler();
    yaxis_idler_pulleyblock();
}

if(false)
{
    attach([N,-X], yaxis_idler_conn)
    {
        yaxis_idler();
        attach(yaxis_idler_conn_pulleyblock, yaxis_idler_pulleyblock_conn)
        {
            yaxis_idler_pulleyblock(show_pulley=true);
        }
    }
}