//////////////////////////////////////////////////////////////////////
// LibFile: shapes3d.scad
//   Some standard modules for making 3d shapes with attachment support, and function forms
//   that produce a VNF.  Also included are shortcuts cylinders in each orientation and extended versions of
//   the standard modules that provide roundovers and chamfers.  The sphereoid() module provides
//   several different ways to make a sphere, and the text modules let you write text on a path
//   so you can place it on a curved object.  
// Includes:
//   include <BOSL2/std.scad>
// FileGroup: Basic Modeling
// FileSummary: Attachable cubes, cylinders, spheres, ruler, and text.  Many can produce a VNF.
// FileFootnotes: STD=Included in std.scad
//////////////////////////////////////////////////////////////////////

use <builtins.scad>


// Section: Cuboids, Prismoids and Pyramids

// Function&Module: cube()
// Topics: Shapes (3D), Attachable, VNF Generators
// Usage: As Module
//   cube(size, [center], ...);
// Usage: With Attachments
//   cube(size, [center], ...) { attachments }
// Usage: As Function
//   vnf = cube(size, [center], ...);
// See Also: cuboid(), prismoid()
// Description:
//   Creates a 3D cubic object with support for anchoring and attachments.
//   This can be used as a drop-in replacement for the built-in `cube()` module.
//   When called as a function, returns a [VNF](vnf.scad) for a cube.
// Arguments:
//   size = The size of the cube.
//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=FRONT+LEFT+BOTTOM`.
//   ---
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example: Simple cube.
//   cube(40);
// Example: Rectangular cube.
//   cube([20,40,50]);
// Example: Anchoring.
//   cube([20,40,50], anchor=BOTTOM+FRONT);
// Example: Spin.
//   cube([20,40,50], anchor=BOTTOM+FRONT, spin=30);
// Example: Orientation.
//   cube([20,40,50], anchor=BOTTOM+FRONT, spin=30, orient=FWD);
// Example: Standard Connectors.
//   cube(40, center=true) show_anchors();
// Example: Called as Function
//   vnf = cube([20,40,50]);
//   vnf_polyhedron(vnf);
module cube(size=1, center, anchor, spin=0, orient=UP)
{
    anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
    size = scalar_vec3(size);
    attachable(anchor,spin,orient, size=size) {
        _cube(size, center=true);
        children();
    }
}

function cube(size=1, center, anchor, spin=0, orient=UP) =
    let(
        siz = scalar_vec3(size),
        anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]),
        unscaled = [
            [-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1],
            [-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1],
        ]/2,
        verts = is_num(size)? unscaled * size :
            is_vector(size,3)? [for (p=unscaled) v_mul(p,size)] :
            assert(is_num(size) || is_vector(size,3)),
        faces = [
            [0,1,2], [0,2,3],  //BOTTOM
            [0,4,5], [0,5,1],  //FRONT
            [1,5,6], [1,6,2],  //RIGHT
            [2,6,7], [2,7,3],  //BACK
            [3,7,4], [3,4,0],  //LEFT
            [6,4,7], [6,5,4]   //TOP
        ]
    ) [reorient(anchor,spin,orient, size=siz, p=verts), faces];



// Module: cuboid()
//
// Usage: Standard Cubes
//   cuboid(size, [anchor=], [spin=], [orient=]);
//   cuboid(size, p1=, ...);
//   cuboid(p1=, p2=, ...);
// Usage: Chamfered Cubes
//   cuboid(size, [chamfer=], [edges=], [except=], [trimcorners=], ...);
// Usage: Rounded Cubes
//   cuboid(size, [rounding=], [edges=], [except=], [trimcorners=], ...);
// Usage: Attaching children
//   cuboid(size, [anchor=], ...) [attachments];
//
// Description:
//   Creates a cube or cuboid object, with optional chamfering or rounding of edges and corners.
//   You cannot mix chamfering and rounding: just one edge treatment with the same size applies to all selected edges.
//   Negative chamfers and roundings can be applied to create external fillets, but they
//   only apply to edges around the top or bottom faces.  If you specify an edge set other than "ALL"
//   with negative roundings or chamfers then you will get an error.  See [Specifying Edges](attachments.scad#section-specifying-edges)
//   for information on how to specify edge sets.
// Arguments:
//   size = The size of the cube, a number or length 3 vector.
//   ---
//   chamfer = Size of chamfer, inset from sides.  Default: No chamfering.
//   rounding = Radius of the edge rounding.  Default: No rounding.
//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#section-specifying-edges).  Default: all edges.
//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#section-specifying-edges).  Default: No edges.
//   trimcorners = If true, rounds or chamfers corners where three chamfered/rounded edges meet.  Default: `true`
//   teardrop = If given as a number, rounding around the bottom edge of the cuboid won't exceed this many degrees from vertical.  If true, the limit angle is 45 degrees.  Default: `false`
//   p1 = Align the cuboid's corner at `p1`, if given.  Forces `anchor=FRONT+LEFT+BOTTOM`.
//   p2 = If given with `p1`, defines the cornerpoints of the cuboid.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example: Simple regular cube.
//   cuboid(40);
// Example: Cube with minimum cornerpoint given.
//   cuboid(20, p1=[10,0,0]);
// Example: Rectangular cube, with given X, Y, and Z sizes.
//   cuboid([20,40,50]);
// Example: Cube by Opposing Corners.
//   cuboid(p1=[0,10,0], p2=[20,30,30]);
// Example: Chamferred Edges and Corners.
//   cuboid([30,40,50], chamfer=5);
// Example: Chamferred Edges, Untrimmed Corners.
//   cuboid([30,40,50], chamfer=5, trimcorners=false);
// Example: Rounded Edges and Corners
//   cuboid([30,40,50], rounding=10);
// Example: Rounded Edges, Untrimmed Corners
//   cuboid([30,40,50], rounding=10, trimcorners=false);
// Example: Chamferring Selected Edges
//   cuboid(
//       [30,40,50], chamfer=5,
//       edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
//       $fn=24
//   );
// Example: Rounding Selected Edges
//   cuboid(
//       [30,40,50], rounding=5,
//       edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
//       $fn=24
//   );
// Example: Negative Chamferring
//   cuboid(
//       [30,40,50], chamfer=-5,
//       edges=[TOP,BOT], except=RIGHT,
//       $fn=24
//   );
// Example: Negative Chamferring, Untrimmed Corners
//   cuboid(
//       [30,40,50], chamfer=-5,
//       edges=[TOP,BOT], except=RIGHT,
//       trimcorners=false, $fn=24
//   );
// Example: Negative Rounding
//   cuboid(
//       [30,40,50], rounding=-5,
//       edges=[TOP,BOT], except=RIGHT,
//       $fn=24
//   );
// Example: Negative Rounding, Untrimmed Corners
//   cuboid(
//       [30,40,50], rounding=-5,
//       edges=[TOP,BOT], except=RIGHT,
//       trimcorners=false, $fn=24
//   );
// Example: Standard Connectors
//   cuboid(40) show_anchors();
module cuboid(
    size=[1,1,1],
    p1, p2,
    chamfer,
    rounding,
    edges=EDGES_ALL,
    except=[],
    except_edges,
    trimcorners=true,
    teardrop=false,
    anchor=CENTER,
    spin=0,
    orient=UP
) {
    module xtcyl(l,r) {
        if (teardrop) {
            teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=90, orient=DOWN);
        } else {
            yrot(90) cyl(l=l, r=r);
        }
    }
    module ytcyl(l,r) {
        if (teardrop) {
            teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=0, orient=DOWN);
        } else {
            zrot(90) yrot(90) cyl(l=l, r=r);
        }
    }
    module tsphere(r) {
        if (teardrop) {
            onion(r=r, cap_h=r, ang=teardrop, orient=DOWN);
        } else {
            spheroid(r=r, style="octa", orient=DOWN);
        }
    }
    module corner_shape(corner) {
        e = _corner_edges(edges, corner);
        cnt = sum(e);
        r = first_defined([chamfer, rounding]);
        dummy=assert(is_finite(r) && !approx(r,0));
        c = [min(r,size.x/2), min(r,size.y/2), min(r,size.z/2)];
        c2 = v_mul(corner,c/2);
        $fn = is_finite(chamfer)? 4 : quantup(segs(r),4);
        translate(v_mul(corner, size/2-c)) {
            if (cnt == 0 || approx(r,0)) {
                translate(c2) cube(c, center=true);
            } else if (cnt == 1) {
                if (e.x) right(c2.x) xtcyl(l=c.x, r=r);
                if (e.y) back (c2.y) ytcyl(l=c.y, r=r);
                if (e.z) up   (c2.z) zcyl(l=c.z, r=r);
            } else if (cnt == 2) {
                if (!e.x) {
                    intersection() {
                        ytcyl(l=c.y*2, r=r);
                        zcyl(l=c.z*2, r=r);
                    }
                } else if (!e.y) {
                    intersection() {
                        xtcyl(l=c.x*2, r=r);
                        zcyl(l=c.z*2, r=r);
                    }
                } else {
                    intersection() {
                        xtcyl(l=c.x*2, r=r);
                        ytcyl(l=c.y*2, r=r);
                    }
                }
            } else {
                if (trimcorners) {
                    tsphere(r=r);
                } else {
                    intersection() {
                        xtcyl(l=c.x*2, r=r);
                        ytcyl(l=c.y*2, r=r);
                        zcyl(l=c.z*2, r=r);
                    }
                }
            }
        }
    }

    size = scalar_vec3(size);
    edges = _edges(edges, except=first_defined([except_edges,except]));
    teardrop = is_bool(teardrop)&&teardrop? 45 : teardrop;
    chamfer = approx(chamfer,0) ? undef : chamfer;
    rounding = approx(rounding,0) ? undef : rounding;
    assert(is_vector(size,3));
    assert(all_positive(size));
    assert(is_undef(chamfer) || is_finite(chamfer),"chamfer must be a finite value");
    assert(is_undef(rounding) || is_finite(rounding),"rounding must be a finite value");
    assert(is_undef(rounding) || is_undef(chamfer), "Cannot specify nonzero value for both chamfer and rounding");
    assert(teardrop==false || (is_finite(teardrop) && teardrop>0 && teardrop<90), "teardrop must be either false or an angle number between 0 and 90")
    assert(is_undef(p1) || is_vector(p1));
    assert(is_undef(p2) || is_vector(p2));
    assert(is_bool(trimcorners));
    if (!is_undef(p1)) {
        if (!is_undef(p2)) {
            translate(pointlist_bounds([p1,p2])[0]) {
                cuboid(size=v_abs(p2-p1), chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=-[1,1,1]) children();
            }
        } else {
            translate(p1) {
                cuboid(size=size, chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=-[1,1,1]) children();
            }
        }
    } else {
        if (is_finite(chamfer)) {
            if (any(edges[0])) assert(chamfer <= size.y/2 && chamfer <=size.z/2, "chamfer must be smaller than half the cube length or height.");
            if (any(edges[1])) assert(chamfer <= size.x/2 && chamfer <=size.z/2, "chamfer must be smaller than half the cube width or height.");
            if (any(edges[2])) assert(chamfer <= size.x/2 && chamfer <=size.y/2, "chamfer must be smaller than half the cube width or length.");
        }
        if (is_finite(rounding)) {
            if (any(edges[0])) assert(rounding <= size.y/2 && rounding<=size.z/2, "rounding radius must be smaller than half the cube length or height.");
            if (any(edges[1])) assert(rounding <= size.x/2 && rounding<=size.z/2, "rounding radius must be smaller than half the cube width or height.");
            if (any(edges[2])) assert(rounding <= size.x/2 && rounding<=size.y/2, "rounding radius must be smaller than half the cube width or length.");
        }
        majrots = [[0,90,0], [90,0,0], [0,0,0]];
        attachable(anchor,spin,orient, size=size) {
            if (is_finite(chamfer) && !approx(chamfer,0)) {
                if (edges == EDGES_ALL && trimcorners) {
                    if (chamfer<0) {
                        cube(size, center=true) {
                            attach(TOP,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
                            attach(BOT,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
                        }
                    } else {
                        isize = [for (v = size) max(0.001, v-2*chamfer)];
                        hull() {
                            cube([ size.x, isize.y, isize.z], center=true);
                            cube([isize.x,  size.y, isize.z], center=true);
                            cube([isize.x, isize.y,  size.z], center=true);
                        }
                    }
                } else if (chamfer<0) {
                    assert(edges == EDGES_ALL || edges[2] == [0,0,0,0], "Cannot use negative chamfer with Z aligned edges.");
                    ach = abs(chamfer);
                    cube(size, center=true);

                    // External-Chamfer mask edges
                    difference() {
                        union() {
                            for (i = [0:3], axis=[0:1]) {
                                if (edges[axis][i]>0) {
                                    vec = EDGE_OFFSETS[axis][i];
                                    translate(v_mul(vec/2, size+[ach,ach,-ach])) {
                                        rotate(majrots[axis]) {
                                            cube([ach, ach, size[axis]], center=true);
                                        }
                                    }
                                }
                            }

                            // Add multi-edge corners.
                            if (trimcorners) {
                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
                                    ce = _corner_edges(edges, [xa,ya,za]);
                                    if (ce.x + ce.y > 1) {
                                        translate(v_mul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
                                            cube([ach+0.01,ach+0.01,ach], center=true);
                                        }
                                    }
                                }
                            }
                        }

                        // Remove bevels from overhangs.
                        for (i = [0:3], axis=[0:1]) {
                            if (edges[axis][i]>0) {
                                vec = EDGE_OFFSETS[axis][i];
                                translate(v_mul(vec/2, size+[2*ach,2*ach,-2*ach])) {
                                    rotate(majrots[axis]) {
                                        zrot(45) cube([ach*sqrt(2), ach*sqrt(2), size[axis]+2.1*ach], center=true);
                                    }
                                }
                            }
                        }
                    }
                } else {
                    hull() {
                        corner_shape([-1,-1,-1]);
                        corner_shape([ 1,-1,-1]);
                        corner_shape([-1, 1,-1]);
                        corner_shape([ 1, 1,-1]);
                        corner_shape([-1,-1, 1]);
                        corner_shape([ 1,-1, 1]);
                        corner_shape([-1, 1, 1]);
                        corner_shape([ 1, 1, 1]);
                    }
                }
            } else if (is_finite(rounding) && !approx(rounding,0)) {
                sides = quantup(segs(rounding),4);
                if (edges == EDGES_ALL) {
                    if(rounding<0) {
                        cube(size, center=true);
                        zflip_copy() {
                            up(size.z/2) {
                                difference() {
                                    down(-rounding/2) cube([size.x-2*rounding, size.y-2*rounding, -rounding], center=true);
                                    down(-rounding) {
                                        ycopies(size.y-2*rounding) xcyl(l=size.x-3*rounding, r=-rounding);
                                        xcopies(size.x-2*rounding) ycyl(l=size.y-3*rounding, r=-rounding);
                                    }
                                }
                            }
                        }
                    } else {
                        isize = [for (v = size) max(0.001, v-2*rounding)];
                        minkowski() {
                            cube(isize, center=true);
                            if (trimcorners) {
                                tsphere(r=rounding, $fn=sides);
                            } else {
                                intersection() {
                                    xtcyl(r=rounding, l=rounding*2, $fn=sides);
                                    ytcyl(r=rounding, l=rounding*2, $fn=sides);
                                    cyl(r=rounding, h=rounding*2, $fn=sides);
                                }
                            }
                        }
                    }
                } else if (rounding<0) {
                    assert(edges == EDGES_ALL || edges[2] == [0,0,0,0], "Cannot use negative rounding with Z aligned edges.");
                    ard = abs(rounding);
                    cube(size, center=true);

                    // External-Rounding mask edges
                    difference() {
                        union() {
                            for (i = [0:3], axis=[0:1]) {
                                if (edges[axis][i]>0) {
                                    vec = EDGE_OFFSETS[axis][i];
                                    translate(v_mul(vec/2, size+[ard,ard,-ard])) {
                                        rotate(majrots[axis]) {
                                            cube([ard, ard, size[axis]], center=true);
                                        }
                                    }
                                }
                            }

                            // Add multi-edge corners.
                            if (trimcorners) {
                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
                                    ce = _corner_edges(edges, [xa,ya,za]);
                                    if (ce.x + ce.y > 1) {
                                        translate(v_mul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
                                            cube([ard+0.01,ard+0.01,ard], center=true);
                                        }
                                    }
                                }
                            }
                        }

                        // Remove roundings from overhangs.
                        for (i = [0:3], axis=[0:1]) {
                            if (edges[axis][i]>0) {
                                vec = EDGE_OFFSETS[axis][i];
                                translate(v_mul(vec/2, size+[2*ard,2*ard,-2*ard])) {
                                    rotate(majrots[axis]) {
                                        cyl(l=size[axis]+2.1*ard, r=ard);
                                    }
                                }
                            }
                        }
                    }
                } else {
                    hull() {
                        corner_shape([-1,-1,-1]);
                        corner_shape([ 1,-1,-1]);
                        corner_shape([-1, 1,-1]);
                        corner_shape([ 1, 1,-1]);
                        corner_shape([-1,-1, 1]);
                        corner_shape([ 1,-1, 1]);
                        corner_shape([-1, 1, 1]);
                        corner_shape([ 1, 1, 1]);
                    }
                }
            } else {
                cube(size=size, center=true);
            }
            children();
        }
    }
}


function cuboid(
    size=[1,1,1],
    p1, p2,
    chamfer,
    rounding,
    edges=EDGES_ALL,
    except_edges=[],
    trimcorners=true,
    anchor=CENTER,
    spin=0,
    orient=UP
) = no_function("cuboid");



// Function&Module: prismoid()
//
// Usage: Typical Prismoids
//   prismoid(size1, size2, h|l, [shift], ...);
// Usage: Attaching Children
//   prismoid(size1, size2, h|l, [shift], ...) [attachments];
// Usage: Chamfered Prismoids
//   prismoid(size1, size2, h|l, [chamfer=], ...);
//   prismoid(size1, size2, h|l, [chamfer1=], [chamfer2=], ...);
// Usage: Rounded Prismoids
//   prismoid(size1, size2, h|l, [rounding=], ...);
//   prismoid(size1, size2, h|l, [rounding1=], [rounding2=], ...);
// Usage: As Function
//   vnf = prismoid(size1, size2, h|l, [shift], [rounding], [chamfer]);
//   vnf = prismoid(size1, size2, h|l, [shift], [rounding1], [rounding2], [chamfer1], [chamfer2]);
//
// Description:
//   Creates a rectangular prismoid shape with optional roundovers and chamfering.
//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
//   specify rounding and/or chamferring per-edge, and for top and bottom separately.
//
// Arguments:
//   size1 = [width, length] of the bottom end of the prism.
//   size2 = [width, length] of the top end of the prism.
//   h|l = Height of the prism.
//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
//   ---
//   rounding = The roundover radius for the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
//   rounding1 = The roundover radius for the bottom of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
//   rounding2 = The roundover radius for the top of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
//   chamfer = The chamfer size for the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].  Default: 0 (no chamfer)
//   chamfer1 = The chamfer size for the bottom of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
//   chamfer2 = The chamfer size for the top of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
//
// See Also: rounded_prism()
//
// Example: Rectangular Pyramid
//   prismoid([40,40], [0,0], h=20);
// Example: Prism
//   prismoid(size1=[40,40], size2=[0,40], h=20);
// Example: Truncated Pyramid
//   prismoid(size1=[35,50], size2=[20,30], h=20);
// Example: Wedge
//   prismoid(size1=[60,35], size2=[30,0], h=30);
// Example: Truncated Tetrahedron
//   prismoid(size1=[10,40], size2=[40,10], h=40);
// Example: Inverted Truncated Pyramid
//   prismoid(size1=[15,5], size2=[30,20], h=20);
// Example: Right Prism
//   prismoid(size1=[30,60], size2=[0,60], shift=[-15,0], h=30);
// Example(FlatSpin,VPD=160,VPT=[0,0,10]): Shifting/Skewing
//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]);
// Example: Rounding
//   prismoid(100, 80, rounding=10, h=30);
// Example: Outer Chamfer Only
//   prismoid(100, 80, chamfer=5, h=30);
// Example: Gradiant Rounding
//   prismoid(100, 80, rounding1=10, rounding2=0, h=30);
// Example: Per Corner Rounding
//   prismoid(100, 80, rounding=[0,5,10,15], h=30);
// Example: Per Corner Chamfer
//   prismoid(100, 80, chamfer=[0,5,10,15], h=30);
// Example: Mixing Chamfer and Rounding
//   prismoid(
//       100, 80, h=30,
//       chamfer=[0,5,0,10],
//       rounding=[5,0,10,0]
//   );
// Example: Really Mixing It Up
//   prismoid(
//       size1=[100,80], size2=[80,60], h=20,
//       chamfer1=[0,5,0,10], chamfer2=[5,0,10,0],
//       rounding1=[5,0,10,0], rounding2=[0,5,0,10]
//   );
// Example(Spin,VPD=160,VPT=[0,0,10]): Standard Connectors
//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5])
//       show_anchors();
module prismoid(
    size1, size2, h, shift=[0,0],
    rounding=0, rounding1, rounding2,
    chamfer=0, chamfer1, chamfer2,
    l, center,
    anchor, spin=0, orient=UP
) {
    assert(is_num(size1) || is_vector(size1,2));
    assert(is_num(size2) || is_vector(size2,2));
    assert(is_num(h) || is_num(l));
    assert(is_vector(shift,2));
    assert(is_num(rounding) || is_vector(rounding,4), "Bad rounding argument.");
    assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "Bad rounding1 argument.");
    assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "Bad rounding2 argument.");
    assert(is_num(chamfer) || is_vector(chamfer,4), "Bad chamfer argument.");
    assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "Bad chamfer1 argument.");
    assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "Bad chamfer2 argument.");
    eps = pow(2,-14);
    size1 = is_num(size1)? [size1,size1] : size1;
    size2 = is_num(size2)? [size2,size2] : size2;
    assert(all_nonnegative(size1));
    assert(all_nonnegative(size2));
    assert(size1.x + size2.x > 0);
    assert(size1.y + size2.y > 0);
    s1 = [max(size1.x, eps), max(size1.y, eps)];
    s2 = [max(size2.x, eps), max(size2.y, eps)];
    rounding1 = default(rounding1, rounding);
    rounding2 = default(rounding2, rounding);
    chamfer1 = default(chamfer1, chamfer);
    chamfer2 = default(chamfer2, chamfer);
    anchor = get_anchor(anchor, center, BOT, BOT);
    vnf = prismoid(
        size1=size1, size2=size2, h=h, shift=shift,
        rounding1=rounding1, rounding2=rounding2,
        chamfer1=chamfer1, chamfer2=chamfer2,
        l=l, center=CENTER
    );
    attachable(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift) {
        vnf_polyhedron(vnf, convexity=4);
        children();
    }
}

function prismoid(
    size1, size2, h, shift=[0,0],
    rounding=0, rounding1, rounding2,
    chamfer=0, chamfer1, chamfer2,
    l, center,
    anchor=DOWN, spin=0, orient=UP
) =
    assert(is_vector(size1,2))
    assert(is_vector(size2,2))
    assert(is_num(h) || is_num(l))
    assert(is_vector(shift,2))
    assert(
        (is_num(rounding) && rounding>=0) ||
        (is_vector(rounding,4) && all_nonnegative(rounding)),
        "Bad rounding argument."
    )
    assert(
        is_undef(rounding1) || (is_num(rounding1) && rounding1>=0) ||
        (is_vector(rounding1,4) && all_nonnegative(rounding1)),
        "Bad rounding1 argument."
    )
    assert(
        is_undef(rounding2) || (is_num(rounding2) && rounding2>=0) ||
        (is_vector(rounding2,4) && all_nonnegative(rounding2)),
        "Bad rounding2 argument."
    )
    assert(
        (is_num(chamfer) && chamfer>=0) ||
        (is_vector(chamfer,4) && all_nonnegative(chamfer)),
        "Bad chamfer argument."
    )
    assert(
        is_undef(chamfer1) || (is_num(chamfer1) && chamfer1>=0) ||
        (is_vector(chamfer1,4) && all_nonnegative(chamfer1)),
        "Bad chamfer1 argument."
    )
    assert(
        is_undef(chamfer2) || (is_num(chamfer2) && chamfer2>=0) ||
        (is_vector(chamfer2,4) && all_nonnegative(chamfer2)),
        "Bad chamfer2 argument."
    )
    let(
        eps = pow(2,-14),
        h = first_defined([h,l,1]),
        shiftby = point3d(point2d(shift)),
        s1 = [max(size1.x, eps), max(size1.y, eps)],
        s2 = [max(size2.x, eps), max(size2.y, eps)],
        rounding1 = default(rounding1, rounding),
        rounding2 = default(rounding2, rounding),
        chamfer1 = default(chamfer1, chamfer),
        chamfer2 = default(chamfer2, chamfer),
        anchor = get_anchor(anchor, center, BOT, BOT),
        vnf = (rounding1==0 && rounding2==0 && chamfer1==0 && chamfer2==0)? (
            let(
                corners = [[1,1],[1,-1],[-1,-1],[-1,1]] * 0.5,
                points = [
                    for (p=corners) point3d(v_mul(s2,p), +h/2) + shiftby,
                    for (p=corners) point3d(v_mul(s1,p), -h/2)
                ],
                faces=[
                    [0,1,2], [0,2,3], [0,4,5], [0,5,1],
                    [1,5,6], [1,6,2], [2,6,7], [2,7,3],
                    [3,7,4], [3,4,0], [4,7,6], [4,6,5],
                ]
            ) [points, faces]
        ) : (
            let(
                path1 = rect(size1, rounding=rounding1, chamfer=chamfer1, anchor=CTR),
                path2 = rect(size2, rounding=rounding2, chamfer=chamfer2, anchor=CTR),
                points = [
                    each path3d(path1, -h/2),
                    each path3d(move(shiftby, p=path2), +h/2),
                ],
                faces = hull(points)
            ) [points, faces]
        )
    ) reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf);


// Function&Module: octahedron()
// Usage: As Module
//   octahedron(size, ...);
// Usage: With Attachments
//   octahedron(size, ...) { attachments }
// Usage: As Function
//   vnf = octahedron(size, ...);
// Description:
//   When called as a module, creates an octahedron with axis-aligned points.
//   When called as a function, creates a [[VNF|vnf.scad]] of an octahedron with axis-aligned points.
// Arguments:
//   size = Width of the octahedron, tip to tip.
//   ---
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example:
//   octahedron(size=40);
// Example: Anchors
//   octahedron(size=40) show_anchors();
module octahedron(size=1, anchor=CENTER, spin=0, orient=UP) {
    vnf = octahedron(size=size);
    attachable(anchor,spin,orient, vnf=vnf, extent=true) {
        vnf_polyhedron(vnf, convexity=2);
        children();
    }
}

function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
    let(
        s = size / 2,
        vnf = [
            [ [0,0,s], [s,0,0], [0,s,0], [-s,0,0], [0,-s,0], [0,0,-s] ],
            [ [0,2,1], [0,3,2], [0,4,3], [0,1,4], [5,1,2], [5,2,3], [5,3,4], [5,4,1] ]
        ]
    ) reorient(anchor,spin,orient, vnf=vnf, extent=true, p=vnf);


// Module: rect_tube()
// Usage: Typical Rectangular Tubes
//   rect_tube(h, size, isize, [center], [shift]);
//   rect_tube(h, size, wall=, [center=]);
//   rect_tube(h, isize=, wall=, [center=]);
// Usage: Tapering Rectangular Tubes
//   rect_tube(h, size1=, size2=, wall=, ...);
//   rect_tube(h, isize1=, isize2=, wall=, ...);
//   rect_tube(h, size1=, size2=, isize1=, isize2=, ...);
// Usage: Chamfered
//   rect_tube(h, size, isize, chamfer=, ...);
//   rect_tube(h, size, isize, chamfer1=, chamfer2= ...);
//   rect_tube(h, size, isize, ichamfer=, ...);
//   rect_tube(h, size, isize, ichamfer1=, ichamfer2= ...);
//   rect_tube(h, size, isize, chamfer=, ichamfer=, ...);
// Usage: Rounded
//   rect_tube(h, size, isize, rounding=, ...);
//   rect_tube(h, size, isize, rounding1=, rounding2= ...);
//   rect_tube(h, size, isize, irounding=, ...);
//   rect_tube(h, size, isize, irounding1=, irounding2= ...);
//   rect_tube(h, size, isize, rounding=, irounding=, ...);
// Usage: Attaching Children
//   rect_tube(h, size, isize, ...) [attachments];
//
// Description:
//   Creates a rectangular or prismoid tube with optional roundovers and/or chamfers.
//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
//   specify rounding and/or chamferring per-edge, and for top and bottom, inside and
//   outside  separately.
// Arguments:
//   h|l = The height or length of the rectangular tube.  Default: 1
//   size = The outer [X,Y] size of the rectangular tube.
//   isize = The inner [X,Y] size of the rectangular tube.
//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
//   ---
//   wall = The thickness of the rectangular tube wall.
//   size1 = The [X,Y] size of the outside of the bottom of the rectangular tube.
//   size2 = The [X,Y] size of the outside of the top of the rectangular tube.
//   isize1 = The [X,Y] size of the inside of the bottom of the rectangular tube.
//   isize2 = The [X,Y] size of the inside of the top of the rectangular tube.
//   rounding = The roundover radius for the outside edges of the rectangular tube.
//   rounding1 = The roundover radius for the outside bottom corner of the rectangular tube.
//   rounding2 = The roundover radius for the outside top corner of the rectangular tube.
//   chamfer = The chamfer size for the outside edges of the rectangular tube.
//   chamfer1 = The chamfer size for the outside bottom corner of the rectangular tube.
//   chamfer2 = The chamfer size for the outside top corner of the rectangular tube.
//   irounding = The roundover radius for the inside edges of the rectangular tube. Default: Same as `rounding`
//   irounding1 = The roundover radius for the inside bottom corner of the rectangular tube.
//   irounding2 = The roundover radius for the inside top corner of the rectangular tube.
//   ichamfer = The chamfer size for the inside edges of the rectangular tube.  Default: Same as `chamfer`
//   ichamfer1 = The chamfer size for the inside bottom corner of the rectangular tube.
//   ichamfer2 = The chamfer size for the inside top corner of the rectangular tube.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `BOTTOM`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Examples:
//   rect_tube(size=50, wall=5, h=30);
//   rect_tube(size=[100,60], wall=5, h=30);
//   rect_tube(isize=[60,80], wall=5, h=30);
//   rect_tube(size=[100,60], isize=[90,50], h=30);
//   rect_tube(size1=[100,60], size2=[70,40], wall=5, h=30);
// Example:
//   rect_tube(
//       size1=[100,60], size2=[70,40],
//       isize1=[40,20], isize2=[65,35], h=15
//   );
// Example: Outer Rounding Only
//   rect_tube(size=100, wall=5, rounding=10, irounding=0, h=30);
// Example: Outer Chamfer Only
//   rect_tube(size=100, wall=5, chamfer=5, ichamfer=0, h=30);
// Example: Outer Rounding, Inner Chamfer
//   rect_tube(size=100, wall=5, rounding=10, ichamfer=8, h=30);
// Example: Inner Rounding, Outer Chamfer
//   rect_tube(size=100, wall=5, chamfer=10, irounding=8, h=30);
// Example: Gradiant Rounding
//   rect_tube(
//       size1=100, size2=80, wall=5, h=30,
//       rounding1=10, rounding2=0,
//       irounding1=8, irounding2=0
//   );
// Example: Per Corner Rounding
//   rect_tube(
//       size=100, wall=10, h=30,
//       rounding=[0,5,10,15], irounding=0
//   );
// Example: Per Corner Chamfer
//   rect_tube(
//       size=100, wall=10, h=30,
//       chamfer=[0,5,10,15], ichamfer=0
//   );
// Example: Mixing Chamfer and Rounding
//   rect_tube(
//       size=100, wall=10, h=30,
//       chamfer=[0,5,0,10], ichamfer=0,
//       rounding=[5,0,10,0], irounding=0
//   );
// Example: Really Mixing It Up
//   rect_tube(
//       size1=[100,80], size2=[80,60],
//       isize1=[50,30], isize2=[70,50], h=20,
//       chamfer1=[0,5,0,10], ichamfer1=[0,3,0,8],
//       chamfer2=[5,0,10,0], ichamfer2=[3,0,8,0],
//       rounding1=[5,0,10,0], irounding1=[3,0,8,0],
//       rounding2=[0,5,0,10], irounding2=[0,3,0,8]
//   );
module rect_tube(
    h, size, isize, center, shift=[0,0],
    wall, size1, size2, isize1, isize2,
    rounding=0, rounding1, rounding2,
    irounding=0, irounding1, irounding2,
    chamfer=0, chamfer1, chamfer2,
    ichamfer=0, ichamfer1, ichamfer2,
    anchor, spin=0, orient=UP,
    l
) {
    h = one_defined([h,l],"h,l");
    assert(is_num(h), "l or h argument required.");
    assert(is_vector(shift,2));
    s1 = is_num(size1)? [size1, size1] :
        is_vector(size1,2)? size1 :
        is_num(size)? [size, size] :
        is_vector(size,2)? size :
        undef;
    s2 = is_num(size2)? [size2, size2] :
        is_vector(size2,2)? size2 :
        is_num(size)? [size, size] :
        is_vector(size,2)? size :
        undef;
    is1 = is_num(isize1)? [isize1, isize1] :
        is_vector(isize1,2)? isize1 :
        is_num(isize)? [isize, isize] :
        is_vector(isize,2)? isize :
        undef;
    is2 = is_num(isize2)? [isize2, isize2] :
        is_vector(isize2,2)? isize2 :
        is_num(isize)? [isize, isize] :
        is_vector(isize,2)? isize :
        undef;
    size1 = is_def(s1)? s1 :
        (is_def(wall) && is_def(is1))? (is1+2*[wall,wall]) :
        undef;
    size2 = is_def(s2)? s2 :
        (is_def(wall) && is_def(is2))? (is2+2*[wall,wall]) :
        undef;
    isize1 = is_def(is1)? is1 :
        (is_def(wall) && is_def(s1))? (s1-2*[wall,wall]) :
        undef;
    isize2 = is_def(is2)? is2 :
        (is_def(wall) && is_def(s2))? (s2-2*[wall,wall]) :
        undef;
    assert(wall==undef || is_num(wall));
    assert(size1!=undef, "Bad size/size1 argument.");
    assert(size2!=undef, "Bad size/size2 argument.");
    assert(isize1!=undef, "Bad isize/isize1 argument.");
    assert(isize2!=undef, "Bad isize/isize2 argument.");
    assert(isize1.x < size1.x, "Inner size is larger than outer size.");
    assert(isize1.y < size1.y, "Inner size is larger than outer size.");
    assert(isize2.x < size2.x, "Inner size is larger than outer size.");
    assert(isize2.y < size2.y, "Inner size is larger than outer size.");
    anchor = get_anchor(anchor, center, BOT, BOT);
    attachable(anchor,spin,orient, size=[each size1, h], size2=size2, shift=shift) {
        diff("_H_o_L_e_")
        prismoid(
            size1, size2, h=h, shift=shift,
            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
            anchor=CTR
        ) {
            children();
            tags("_H_o_L_e_") prismoid(
                isize1, isize2, h=h+0.05, shift=shift,
                rounding=irounding, rounding1=irounding1, rounding2=irounding2,
                chamfer=ichamfer, chamfer1=ichamfer1, chamfer2=ichamfer2,
                anchor=CTR
            );
        }
        children();
    }
}

function rect_tube(
    h, size, isize, center, shift=[0,0],
    wall, size1, size2, isize1, isize2,
    rounding=0, rounding1, rounding2,
    irounding=0, irounding1, irounding2,
    chamfer=0, chamfer1, chamfer2,
    ichamfer=0, ichamfer1, ichamfer2,
    anchor, spin=0, orient=UP,
    l
) = no_function("rect_tube");


// Function&Module: wedge()
//
// Usage: As Module
//   wedge(size, [center], ...);
// Usage: With Attachments
//   wedge(size, [center], ...) { attachments }
// Usage: As Function
//   vnf = wedge(size, [center], ...);
//
// Description:
//   When called as a modulem creates a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
//   When called as a function creates a VNF for a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
//
// Arguments:
//   size = [width, thickness, height]
//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
//   ---
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `FRONT+LEFT+BOTTOM`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
//
// Example: Centered
//   wedge([20, 40, 15], center=true);
// Example: *Non*-Centered
//   wedge([20, 40, 15]);
// Example: Standard Connectors
//   wedge([20, 40, 15]) show_anchors();
module wedge(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
{
    size = scalar_vec3(size);
    anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
    vnf = wedge(size, center=true);
    attachable(anchor,spin,orient, size=size, size2=[size.x,0], shift=[0,-size.y/2]) {
        if (size.z > 0) {
            vnf_polyhedron(vnf);
        }
        children();
    }
}


function wedge(size=[1,1,1], center, anchor, spin=0, orient=UP) =
    let(
        size = scalar_vec3(size),
        anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]),
        pts = [
            [ 1,1,-1], [ 1,-1,-1], [ 1,-1,1],
            [-1,1,-1], [-1,-1,-1], [-1,-1,1],
        ],
        faces = [
            [0,1,2], [3,5,4], [0,3,1], [1,3,4],
            [1,4,2], [2,4,5], [2,5,3], [0,2,3],
        ],
        vnf = [scale(size/2,p=pts), faces]
    )
    reorient(anchor,spin,orient, size=size, size2=[size.x,0], shift=[0,-size.y/2], p=vnf);


// Section: Cylinders


// Function&Module: cylinder()
// Topics: Shapes (3D), Attachable, VNF Generators
// Usage: As Module
//   cylinder(h, r=/d=, [center=], ...);
//   cylinder(h, r1/d1=, r2/d2=, [center=], ...);
// Usage: With Attachments
//   cylinder(h, r=/d=, [center=]) {attachments}
// Usage: As Function
//   vnf = cylinder(h, r=/d=, [center=], ...);
//   vnf = cylinder(h, r1/d1=, r2/d2=, [center=], ...);
// See Also: cyl()
// Description:
//   Creates a 3D cylinder or conic object with support for anchoring and attachments.
//   This can be used as a drop-in replacement for the built-in `cylinder()` module.
//   When called as a function, returns a [VNF](vnf.scad) for a cylinder.
// Arguments:
//   l / h = The height of the cylinder.
//   r1 = The bottom radius of the cylinder.  (Before orientation.)
//   r2 = The top radius of the cylinder.  (Before orientation.)
//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=BOTTOM`.
//   ---
//   d1 = The bottom diameter of the cylinder.  (Before orientation.)
//   d2 = The top diameter of the cylinder.  (Before orientation.)
//   r = The radius of the cylinder.
//   d = The diameter of the cylinder.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example: By Radius
//   xdistribute(30) {
//       cylinder(h=40, r=10);
//       cylinder(h=40, r1=10, r2=5);
//   }
// Example: By Diameter
//   xdistribute(30) {
//       cylinder(h=40, d=25);
//       cylinder(h=40, d1=25, d2=10);
//   }
// Example(Med): Anchoring
//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT);
// Example(Med): Spin
//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45);
// Example(Med): Orient
//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45, orient=FWD);
// Example(Big): Standard Connectors
//   xdistribute(40) {
//       cylinder(h=30, d=25) show_anchors();
//       cylinder(h=30, d1=25, d2=10) show_anchors();
//   }
module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
{
    anchor = get_anchor(anchor, center, BOTTOM, BOTTOM);
    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
    l = first_defined([h, l, 1]);
    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
        _cylinder(h=l, r1=r1, r2=r2, center=true);
        children();
    }
}

function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) =
    let(
        anchor = get_anchor(anchor, center, BOTTOM, BOTTOM),
        r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
        r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
        l = first_defined([h, l, 1]),
        sides = segs(max(r1,r2)),
        verts = [
            for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r1*cos(a),r1*sin(a),-l/2],
            for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r2*cos(a),r2*sin(a), l/2],
        ],
        faces = [
            [for (i=[0:1:sides-1]) sides-1-i],
            for (i=[0:1:sides-1]) [i, ((i+1)%sides)+sides, i+sides],
            for (i=[0:1:sides-1]) [i, (i+1)%sides, ((i+1)%sides)+sides],
            [for (i=[0:1:sides-1]) sides+i]
        ]
    ) [reorient(anchor,spin,orient, l=l, r1=r1, r2=r2, p=verts), faces];



// Module: cyl()
//
// Description:
//   Creates cylinders in various anchorings and orientations, with optional rounding and chamfers.
//   You can use `h` and `l` interchangably, and all variants allow specifying size by either `r`|`d`,
//   or `r1`|`d1` and `r2`|`d2`.  Note: the chamfers and rounding cannot be cumulatively longer than
//   the cylinder's length.
//
// Usage: Normal Cylinders
//   cyl(l|h, r, [center], [circum=], [realign=]);
//   cyl(l|h, d=, ...);
//   cyl(l|h, r1=, r2=, ...);
//   cyl(l|h, d1=, d2=, ...);
//
// Usage: Chamferred Cylinders
//   cyl(l|h, r|d, chamfer=, [chamfang=], [from_end=], ...);
//   cyl(l|h, r|d, chamfer1=, [chamfang1=], [from_end=], ...);
//   cyl(l|h, r|d, chamfer2=, [chamfang2=], [from_end=], ...);
//   cyl(l|h, r|d, chamfer1=, chamfer2=, [chamfang1=], [chamfang2=], [from_end=], ...);
//
// Usage: Rounded End Cylinders
//   cyl(l|h, r|d, rounding=, ...);
//   cyl(l|h, r|d, rounding1=, ...);
//   cyl(l|h, r|d, rounding2=, ...);
//   cyl(l|h, r|d, rounding1=, rounding2=, ...);
//
// Arguments:
//   l / h = Length of cylinder along oriented axis.  Default: 1
//   r = Radius of cylinder.  Default: 1
//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
//   ---
//   r1 = Radius of the negative (X-, Y-, Z-) end of cylinder.
//   r2 = Radius of the positive (X+, Y+, Z+) end of cylinder.
//   d = Diameter of cylinder.
//   d1 = Diameter of the negative (X-, Y-, Z-) end of cylinder.
//   d2 = Diameter of the positive (X+, Y+, Z+) end of cylinder.
//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
//   chamfer1 = The size of the chamfer on the bottom end of the cylinder.  Default: none.
//   chamfer2 = The size of the chamfer on the top end of the cylinder.  Default: none.
//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
//   chamfang1 = The angle in degrees of the chamfer on the bottom end of the cylinder.
//   chamfang2 = The angle in degrees of the chamfer on the top end of the cylinder.
//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
//   rounding1 = The radius of the rounding on the bottom end of the cylinder.
//   rounding2 = The radius of the rounding on the top end of the cylinder.
//   realign = If true, rotate the cylinder by half the angle of one face.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
//
// Example: By Radius
//   xdistribute(30) {
//       cyl(l=40, r=10);
//       cyl(l=40, r1=10, r2=5);
//   }
//
// Example: By Diameter
//   xdistribute(30) {
//       cyl(l=40, d=25);
//       cyl(l=40, d1=25, d2=10);
//   }
//
// Example: Chamferring
//   xdistribute(60) {
//       // Shown Left to right.
//       cyl(l=40, d=40, chamfer=7);  // Default chamfang=45
//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=false);
//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=true);
//   }
//
// Example: Rounding
//   cyl(l=40, d=40, rounding=10);
//
// Example: Heterogenous Chamfers and Rounding
//   ydistribute(80) {
//       // Shown Front to Back.
//       cyl(l=40, d=40, rounding1=15, orient=UP);
//       cyl(l=40, d=40, chamfer2=5, orient=UP);
//       cyl(l=40, d=40, chamfer1=12, rounding2=10, orient=UP);
//   }
//
// Example: Putting it all together
//   cyl(
//       l=40, d1=25, d2=15,
//       chamfer1=10, chamfang1=30,
//       from_end=true, rounding2=5
//   );
//
// Example: External Chamfers
//   cyl(l=50, r=30, chamfer=-5, chamfang=30, $fa=1, $fs=1);
//
// Example: External Roundings
//   cyl(l=50, r=30, rounding1=-5, rounding2=5, $fa=1, $fs=1);
//
// Example: Standard Connectors
//   xdistribute(40) {
//       cyl(l=30, d=25) show_anchors();
//       cyl(l=30, d1=25, d2=10) show_anchors();
//   }
//
module cyl(
    h, r, center,
    l, r1, r2,
    d, d1, d2,
    chamfer, chamfer1, chamfer2,
    chamfang, chamfang1, chamfang2,
    rounding, rounding1, rounding2,
    circum=false, realign=false, from_end=false,
    anchor, spin=0, orient=UP
) {
    l = first_defined([l, h, 1]);
    _r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
    _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
    sides = segs(max(_r1,_r2));
    sc = circum? 1/cos(180/sides) : 1;
    r1=_r1*sc;
    r2=_r2*sc;
    phi = atan2(l, r2-r1);
    anchor = get_anchor(anchor,center,BOT,CENTER);
    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
        zrot(realign? 180/sides : 0) {
            if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])) {
                cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides);
            } else {
                vang = atan2(l, r1-r2)/2;
                chang1 = 90-first_defined([chamfang1, chamfang, vang]);
                chang2 = 90-first_defined([chamfang2, chamfang, 90-vang]);
                cham1 = u_mul(first_defined([chamfer1, chamfer]) , (from_end? 1 : tan(chang1)));
                cham2 = u_mul(first_defined([chamfer2, chamfer]) , (from_end? 1 : tan(chang2)));
                fil1 = first_defined([rounding1, rounding]);
                fil2 = first_defined([rounding2, rounding]);
                if (chamfer != undef) {
                    assert(chamfer <= r1,  "chamfer is larger than the r1 radius of the cylinder.");
                    assert(chamfer <= r2,  "chamfer is larger than the r2 radius of the cylinder.");
                }
                if (cham1 != undef) {
                    assert(cham1 <= r1,  "chamfer1 is larger than the r1 radius of the cylinder.");
                }
                if (cham2 != undef) {
                    assert(cham2 <= r2,  "chamfer2 is larger than the r2 radius of the cylinder.");
                }
                if (rounding != undef) {
                    assert(rounding <= r1,  "rounding is larger than the r1 radius of the cylinder.");
                    assert(rounding <= r2,  "rounding is larger than the r2 radius of the cylinder.");
                }
                if (fil1 != undef) {
                    assert(fil1 <= r1,  "rounding1 is larger than the r1 radius of the cylinder.");
                }
                if (fil2 != undef) {
                    assert(fil2 <= r2,  "rounding2 is larger than the r1 radius of the cylinder.");
                }
                dy1 = abs(first_defined([cham1, fil1, 0]));
                dy2 = abs(first_defined([cham2, fil2, 0]));
                assert(dy1+dy2 <= l, "Sum of fillets and chamfer sizes must be less than the length of the cylinder.");

                path = concat(
                    [[0,l/2]],

                    !is_undef(cham2)? (
                        let(
                            p1 = [r2-cham2/tan(chang2),l/2],
                            p2 = lerp([r2,l/2],[r1,-l/2],abs(cham2)/l)
                        ) [p1,p2]
                    ) : !is_undef(fil2)? (
                        let(
                            cn = circle_2tangents([r2-fil2,l/2], [r2,l/2], [r1,-l/2], r=abs(fil2)),
                            ang = fil2<0? phi : phi-180,
                            steps = ceil(abs(ang)/360*segs(abs(fil2))),
                            step = ang/steps,
                            pts = [for (i=[0:1:steps]) let(a=90+i*step) cn[0]+abs(fil2)*[cos(a),sin(a)]]
                        ) pts
                    ) : [[r2,l/2]],

                    !is_undef(cham1)? (
                        let(
                            p1 = lerp([r1,-l/2],[r2,l/2],abs(cham1)/l),
                            p2 = [r1-cham1/tan(chang1),-l/2]
                        ) [p1,p2]
                    ) : !is_undef(fil1)? (
                        let(
                            cn = circle_2tangents([r1-fil1,-l/2], [r1,-l/2], [r2,l/2], r=abs(fil1)),
                            ang = fil1<0? 180-phi : -phi,
                            steps = ceil(abs(ang)/360*segs(abs(fil1))),
                            step = ang/steps,
                            pts = [for (i=[0:1:steps]) let(a=(fil1<0?180:0)+(phi-90)+i*step) cn[0]+abs(fil1)*[cos(a),sin(a)]]
                        ) pts
                    ) : [[r1,-l/2]],

                    [[0,-l/2]]
                );
                rotate_extrude(convexity=2) {
                    polygon(path);
                }
            }
        }
        children();
    }
}



// Module: xcyl()
//
// Description:
//   Creates a cylinder oriented along the X axis.
//
// Usage: Typical
//   xcyl(l|h, r, [anchor=]);
//   xcyl(l|h, d=, [anchor=]);
//   xcyl(l|h, r1=|d1=, r2=|d2=, [anchor=]);
// Usage: Attaching Children
//   xcyl(l|h, r, [anchor=]) [attachments];
//
// Arguments:
//   l / h = Length of cylinder along oriented axis. Default: 1
//   r = Radius of cylinder.  Default: 1
//   ---
//   r1 = Optional radius of left (X-) end of cylinder.
//   r2 = Optional radius of right (X+) end of cylinder.
//   d = Optional diameter of cylinder. (use instead of `r`)
//   d1 = Optional diameter of left (X-) end of cylinder.
//   d2 = Optional diameter of right (X+) end of cylinder.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//
// Example: By Radius
//   ydistribute(50) {
//       xcyl(l=35, r=10);
//       xcyl(l=35, r1=15, r2=5);
//   }
//
// Example: By Diameter
//   ydistribute(50) {
//       xcyl(l=35, d=20);
//       xcyl(l=35, d1=30, d2=10);
//   }
module xcyl(h, r, d, r1, r2, d1, d2, l, anchor=CENTER)
{
    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
    l = first_defined([l, h, 1]);
    attachable(anchor,0,UP, r1=r1, r2=r2, l=l, axis=RIGHT) {
        cyl(l=l, r1=r1, r2=r2, orient=RIGHT, anchor=CENTER);
        children();
    }
}



// Module: ycyl()
//
// Description:
//   Creates a cylinder oriented along the Y axis.
//
// Usage: Typical
//   ycyl(l|h, r, [anchor=]);
//   ycyl(l|h, d=, [anchor=]);
//   ycyl(l|h, r1=|d1=, r2=|d2=, [anchor=]);
// Usage: Attaching Children
//   ycyl(l|h, r, [anchor=]) [attachments];
//
// Arguments:
//   l / h = Length of cylinder along oriented axis. (Default: `1.0`)
//   r = Radius of cylinder.
//   ---
//   r1 = Radius of front (Y-) end of cone.
//   r2 = Radius of back (Y+) end of one.
//   d = Diameter of cylinder.
//   d1 = Diameter of front (Y-) end of one.
//   d2 = Diameter of back (Y+) end of one.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//
// Example: By Radius
//   xdistribute(50) {
//       ycyl(l=35, r=10);
//       ycyl(l=35, r1=15, r2=5);
//   }
//
// Example: By Diameter
//   xdistribute(50) {
//       ycyl(l=35, d=20);
//       ycyl(l=35, d1=30, d2=10);
//   }
module ycyl(h, r, d, r1, r2, d1, d2, l, anchor=CENTER)
{
    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
    l = first_defined([l, h, 1]);
    attachable(anchor,0,UP, r1=r1, r2=r2, l=l, axis=BACK) {
        cyl(l=l, h=h, r1=r1, r2=r2, orient=BACK, anchor=CENTER);
        children();
    }
}



// Module: zcyl()
//
// Description:
//   Creates a cylinder oriented along the Z axis.
//
// Usage: Typical
//   zcyl(l|h, r, [anchor=]);
//   zcyl(l|h, d=, [anchor=]);
//   zcyl(l|h, r1=|d1=, r2=|d2=, [anchor=]);
// Usage: Attaching Children
//   zcyl(l|h, r, [anchor=]) [attachments];
//
// Arguments:
//   l / h = Length of cylinder along oriented axis. (Default: 1.0)
//   r = Radius of cylinder.
//   ---
//   r1 = Radius of front (Y-) end of cone.
//   r2 = Radius of back (Y+) end of one.
//   d = Diameter of cylinder.
//   d1 = Diameter of front (Y-) end of one.
//   d2 = Diameter of back (Y+) end of one.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//
// Example: By Radius
//   xdistribute(50) {
//       zcyl(l=35, r=10);
//       zcyl(l=35, r1=15, r2=5);
//   }
//
// Example: By Diameter
//   xdistribute(50) {
//       zcyl(l=35, d=20);
//       zcyl(l=35, d1=30, d2=10);
//   }
module zcyl(h, r, d, r1, r2, d1, d2, l, anchor=CENTER)
{
    cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=UP, anchor=anchor) children();
}



// Module: tube()
//
// Description:
//   Makes a hollow tube with the given outer size and wall thickness.
//
// Usage: Typical
//   tube(h|l, or, ir, [center], [realign=]);
//   tube(h|l, or=|od=, ir=|id=, ...);
//   tube(h|l, ir|id, wall, ...);
//   tube(h|l, or|od, wall, ...);
//   tube(h|l, ir1|id1, ir2|id2, wall, ...);
//   tube(h|l, or1|od1, or2|od2, wall, ...);
//   tube(h|l, ir1|id1, ir2|id2, or1|od1, or2|od2, [realign]);
// Usage: Attaching Children
//   tube(h|l, or, ir, [center]) [attachments];
//
// Arguments:
//   h / l = height of tube. Default: 1
//   or = Outer radius of tube. Default: 1
//   ir = Inner radius of tube.
//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
//   ---
//   od = Outer diameter of tube.
//   id = Inner diameter of tube.
//   wall = horizontal thickness of tube wall. Default 0.5
//   or1 = Outer radius of bottom of tube.  Default: value of r)
//   or2 = Outer radius of top of tube.  Default: value of r)
//   od1 = Outer diameter of bottom of tube.
//   od2 = Outer diameter of top of tube.
//   ir1 = Inner radius of bottom of tube.
//   ir2 = Inner radius of top of tube.
//   id1 = Inner diameter of bottom of tube.
//   id2 = Inner diameter of top of tube.
//   realign = If true, rotate the tube by half the angle of one face.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
//
// Example: These all Produce the Same Tube
//   tube(h=30, or=40, wall=5);
//   tube(h=30, ir=35, wall=5);
//   tube(h=30, or=40, ir=35);
//   tube(h=30, od=80, id=70);
// Example: These all Produce the Same Conical Tube
//   tube(h=30, or1=40, or2=25, wall=5);
//   tube(h=30, ir1=35, or2=20, wall=5);
//   tube(h=30, or1=40, or2=25, ir1=35, ir2=20);
// Example: Circular Wedge
//   tube(h=30, or1=40, or2=30, ir1=20, ir2=30);
// Example: Standard Connectors
//   tube(h=30, or=40, wall=5) show_anchors();
module tube(
    h, or, ir, center,
    od, id, wall,
    or1, or2, od1, od2,
    ir1, ir2, id1, id2,
    realign=false, l,
    anchor, spin=0, orient=UP
) {
    h = first_defined([h,l,1]);
    orr1 = get_radius(r1=or1, r=or, d1=od1, d=od, dflt=undef);
    orr2 = get_radius(r1=or2, r=or, d1=od2, d=od, dflt=undef);
    irr1 = get_radius(r1=ir1, r=ir, d1=id1, d=id, dflt=undef);
    irr2 = get_radius(r1=ir2, r=ir, d1=id2, d=id, dflt=undef);
    r1 = default(orr1, u_add(irr1,wall));
    r2 = default(orr2, u_add(irr2,wall));
    ir1 = default(irr1, u_sub(orr1,wall));
    ir2 = default(irr2, u_sub(orr2,wall));
    assert(ir1 <= r1, "Inner radius is larger than outer radius.");
    assert(ir2 <= r2, "Inner radius is larger than outer radius.");
    sides = segs(max(r1,r2));
    anchor = get_anchor(anchor, center, BOT, BOT);
    attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
        zrot(realign? 180/sides : 0) {
            difference() {
                cyl(h=h, r1=r1, r2=r2, $fn=sides) children();
                cyl(h=h+0.05, r1=ir1, r2=ir2);
            }
        }
        children();
    }
}



// Module: pie_slice()
//
// Description:
//   Creates a pie slice shape.
//
// Usage: Typical
//   pie_slice(l|h, r, ang, [center]);
//   pie_slice(l|h, d=, ang=, ...);
//   pie_slice(l|h, r1=|d1=, r2=|d2=, ang=, ...);
// Usage: Attaching Children
//   pie_slice(l|h, r, ang, ...) [attachments];
//
// Arguments:
//   h / l = height of pie slice.
//   r = radius of pie slice.
//   ang = pie slice angle in degrees.
//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
//   ---
//   r1 = bottom radius of pie slice.
//   r2 = top radius of pie slice.
//   d = diameter of pie slice.
//   d1 = bottom diameter of pie slice.
//   d2 = top diameter of pie slice.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
//
// Example: Cylindrical Pie Slice
//   pie_slice(ang=45, l=20, r=30);
// Example: Conical Pie Slice
//   pie_slice(ang=60, l=20, d1=50, d2=70);
module pie_slice(
    h, r, ang=30, center,
    r1, r2, d, d1, d2, l,
    anchor, spin=0, orient=UP
) {
    l = first_defined([l, h, 1]);
    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10);
    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10);
    maxd = max(r1,r2)+0.1;
    anchor = get_anchor(anchor, center, BOT, BOT);
    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
        difference() {
            cyl(r1=r1, r2=r2, h=l);
            if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
            difference() {
                fwd(maxd/2) cube([2*maxd, maxd, l+0.2], center=true);
                if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
            }
        }
        children();
    }
}



// Section: Other Round Objects


// Function&Module: sphere()
// Topics: Shapes (3D), Attachable, VNF Generators
// Usage: As Module
//   sphere(r|d=, [circum=], [style=], ...);
// Usage: With Attachments
//   sphere(r|d=, ...) { attachments }
// Usage: As Function
//   vnf = sphere(r|d=, [circum=], [style=], ...);
// See Also: spheroid()
// Description:
//   Creates a sphere object, with support for anchoring and attachments.
//   This is a drop-in replacement for the built-in `sphere()` module.
//   When called as a function, returns a [VNF](vnf.scad) for a sphere.
// Arguments:
//   r = Radius of the sphere.
//   ---
//   d = Diameter of the sphere.
//   circum = If true, the sphere is made large enough to circumscribe the sphere of the ideal side.  Otherwise inscribes.  Default: false (inscribes)
//   style = The style of the sphere's construction. One of "orig", "aligned", "stagger", "octa", or "icosa".  Default: "orig"
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example: By Radius
//   sphere(r=50);
// Example: By Diameter
//   sphere(d=100);
// Example: style="orig"
//   sphere(d=100, style="orig", $fn=10);
// Example: style="aligned"
//   sphere(d=100, style="aligned", $fn=10);
// Example: style="stagger"
//   sphere(d=100, style="stagger", $fn=10);
// Example: style="icosa"
//   sphere(d=100, style="icosa", $fn=10);
//   // In "icosa" style, $fn is quantized
//   //   to the nearest multiple of 5.
// Example: Anchoring
//   sphere(d=100, anchor=FRONT);
// Example: Spin
//   sphere(d=100, anchor=FRONT, spin=45);
// Example: Orientation
//   sphere(d=100, anchor=FRONT, spin=45, orient=FWD);
// Example: Standard Connectors
//   sphere(d=50) show_anchors();
// Example: Called as Function
//   vnf = sphere(d=100, style="icosa");
//   vnf_polyhedron(vnf);
module sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP) {
    r = get_radius(r=r, d=d, dflt=1);
    if (!circum && style=="orig" && is_num(r)) {
        attachable(anchor,spin,orient, r=r) {
            _sphere(r=r);
            children();
        }
    } else {
        spheroid(
            r=r, circum=circum, style=style,
            anchor=anchor, spin=spin, orient=orient
        ) children();
    }
}


function sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP) =
    spheroid(r=r, d=d, circum=circum, style=style, anchor=anchor, spin=spin, orient=orient);


// Function&Module: spheroid()
// Usage: Typical
//   spheroid(r|d, [circum], [style]);
// Usage: Attaching Children
//   spheroid(r|d, [circum], [style]) [attachments];
// Usage: As Function
//   vnf = spheroid(r|d, [circum], [style]);
// Description:
//   Creates a spheroid object, with support for anchoring and attachments.
//   This is a drop-in replacement for the built-in `sphere()` module.
//   When called as a function, returns a [VNF](vnf.scad) for a spheroid.
//   The exact triangulation of this spheroid can be controlled via the `style=`
//   argument, where the value can be one of `"orig"`, `"aligned"`, `"stagger"`,
//   `"octa"`, or `"icosa"`:
//   - `style="orig"` constructs a sphere the same way that the OpenSCAD `sphere()` built-in does.
//   - `style="aligned"` constructs a sphere where, if `$fn` is a multiple of 4, it has vertices at all axis maxima and minima.  ie: its bounding box is exactly the sphere diameter in length on all three axes.  This is the default.
//   - `style="stagger"` forms a sphere where all faces are triangular, but the top and bottom poles have thinner triangles.
//   - `style="octa"` forms a sphere by subdividing an octahedron (8-sided platonic solid).  This makes more uniform faces over the entirety of the sphere, and guarantees the bounding box is the sphere diameter in size on all axes.  The effective `$fn` value is quantized to a multiple of 4, though.  This is used in constructing rounded corners for various other shapes.
//   - `style="icosa"` forms a sphere by subdividing an icosahedron (20-sided platonic solid).  This makes even more uniform faces over the entirety of the sphere.  The effective `$fn` value is quantized to a multiple of 5, though.
// Arguments:
//   r = Radius of the spheroid.
//   style = The style of the spheroid's construction. One of "orig", "aligned", "stagger", "octa", or "icosa".  Default: "aligned"
//   ---
//   d = Diameter of the spheroid.
//   circum = If true, the spheroid is made large enough to circumscribe the sphere of the ideal side.  Otherwise inscribes.  Default: false (inscribes)
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example: By Radius
//   spheroid(r=50);
// Example: By Diameter
//   spheroid(d=100);
// Example: style="orig"
//   spheroid(d=100, style="orig", $fn=10);
// Example: style="aligned"
//   spheroid(d=100, style="aligned", $fn=10);
// Example: style="stagger"
//   spheroid(d=100, style="stagger", $fn=10);
// Example: style="octa", octahedral based tesselation.
//   spheroid(d=100, style="octa", $fn=10);
//   // In "octa" style, $fn is quantized
//   //   to the nearest multiple of 4.
// Example: style="icosa", icosahedral based tesselation.
//   spheroid(d=100, style="icosa", $fn=10);
//   // In "icosa" style, $fn is quantized
//   //   to the nearest multiple of 5.
// Example: Anchoring
//   spheroid(d=100, anchor=FRONT);
// Example: Spin
//   spheroid(d=100, anchor=FRONT, spin=45);
// Example: Orientation
//   spheroid(d=100, anchor=FRONT, spin=45, orient=FWD);
// Example: Standard Connectors
//   spheroid(d=50) show_anchors();
// Example: Called as Function
//   vnf = spheroid(d=100, style="icosa");
//   vnf_polyhedron(vnf);
module spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orient=UP)
{
    r = get_radius(r=r, d=d, dflt=1);
    sides = segs(r);
    vsides = ceil(sides/2);
    attachable(anchor,spin,orient, r=r) {
        if (style=="orig") {
            merids = [ for (i=[0:1:vsides-1]) 90-(i+0.5)*180/vsides ];
            path = [
                let(a = merids[0]) [0, sin(a)],
                for (a=merids) [cos(a), sin(a)],
                let(a = last(merids)) [0, sin(a)]
            ];
            scale(r) rotate(180) rotate_extrude(convexity=2,$fn=sides) polygon(path);
        } else {
            vnf = spheroid(r=r, circum=circum, style=style);
            vnf_polyhedron(vnf, convexity=2);
        }
        children();
    }
}


function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orient=UP) =
    let(
        r = get_radius(r=r, d=d, dflt=1),
        hsides = segs(r),
        vsides = max(2,ceil(hsides/2)),
        octa_steps = round(max(4,hsides)/4),
        icosa_steps = round(max(5,hsides)/5),
        rr = circum? (r / cos(90/vsides) / cos(180/hsides)) : r,
        stagger = style=="stagger",
        verts = style=="orig"? [
            for (i=[0:1:vsides-1]) let(phi = (i+0.5)*180/(vsides))
            for (j=[0:1:hsides-1]) let(theta = j*360/hsides)
            spherical_to_xyz(rr, theta, phi),
        ] : style=="aligned" || style=="stagger"? [
            spherical_to_xyz(rr, 0, 0),
            for (i=[1:1:vsides-1]) let(phi = i*180/vsides)
                for (j=[0:1:hsides-1]) let(theta = (j+((stagger && i%2!=0)?0.5:0))*360/hsides)
                    spherical_to_xyz(rr, theta, phi),
            spherical_to_xyz(rr, 0, 180)
        ] : style=="octa"? let(
            meridians = [
                1,
                for (i = [1:1:octa_steps]) i*4,
                for (i = [octa_steps-1:-1:1]) i*4,
                1,
            ]
        ) [
            for (i=idx(meridians), j=[0:1:meridians[i]-1])
            spherical_to_xyz(rr, j*360/meridians[i], i*180/(len(meridians)-1))
        ] : style=="icosa"? [
            for (tb=[0,1], j=[0,2], i = [0:1:4]) let(
                theta0 = i*360/5,
                theta1 = (i-0.5)*360/5,
                theta2 = (i+0.5)*360/5,
                phi0 = 180/3 * j,
                phi1 = 180/3,
                v0 = spherical_to_xyz(1,theta0,phi0),
                v1 = spherical_to_xyz(1,theta1,phi1),
                v2 = spherical_to_xyz(1,theta2,phi1),
                ax0 = vector_axis(v0, v1),
                ang0 = vector_angle(v0, v1),
                ax1 = vector_axis(v0, v2),
                ang1 = vector_angle(v0, v2)
            )
            for (k = [0:1:icosa_steps]) let(
                u = k/icosa_steps,
                vv0 = rot(ang0*u, ax0, p=v0),
                vv1 = rot(ang1*u, ax1, p=v0),
                ax2 = vector_axis(vv0, vv1),
                ang2 = vector_angle(vv0, vv1)
            )
            for (l = [0:1:k]) let(
                v = k? l/k : 0,
                pt = rot(ang2*v, v=ax2, p=vv0) * rr * (tb? -1 : 1)
            ) pt
        ] : assert(in_list(style,["orig","aligned","stagger","octa","icosa"])),
        lv = len(verts),
        faces = style=="orig"? [
            [for (i=[0:1:hsides-1]) hsides-i-1],
            [for (i=[0:1:hsides-1]) lv-hsides+i],
            for (i=[0:1:vsides-2], j=[0:1:hsides-1]) each [
                [(i+1)*hsides+j, i*hsides+j, i*hsides+(j+1)%hsides],
                [(i+1)*hsides+j, i*hsides+(j+1)%hsides, (i+1)*hsides+(j+1)%hsides],
            ]
        ] : style=="aligned" || style=="stagger"? [
            for (i=[0:1:hsides-1]) let(
                b2 = lv-2-hsides
            ) each [
                [i+1, 0, ((i+1)%hsides)+1],
                [lv-1, b2+i+1, b2+((i+1)%hsides)+1],
            ],
            for (i=[0:1:vsides-3], j=[0:1:hsides-1]) let(
                base = 1 + hsides*i
            ) each (
                (stagger && i%2!=0)? [
                    [base+j, base+hsides+j%hsides, base+hsides+(j+hsides-1)%hsides],
                    [base+j, base+(j+1)%hsides, base+hsides+j],
                ] : [
                    [base+j, base+(j+1)%hsides, base+hsides+(j+1)%hsides],
                    [base+j, base+hsides+(j+1)%hsides, base+hsides+j],
                ]
            )
        ] : style=="octa"? let(
            meridians = [
                0, 1,
                for (i = [1:1:octa_steps]) i*4,
                for (i = [octa_steps-1:-1:1]) i*4,
                1,
            ],
            offs = cumsum(meridians),
            pc = last(offs)-1,
            os = octa_steps * 2
        ) [
            for (i=[0:1:3]) [0, 1+(i+1)%4, 1+i],
            for (i=[0:1:3]) [pc-0, pc-(1+(i+1)%4), pc-(1+i)],
            for (i=[1:1:octa_steps-1]) let(
                m = meridians[i+2]/4
            )
            for (j=[0:1:3], k=[0:1:m-1]) let(
                m1 = meridians[i+1],
                m2 = meridians[i+2],
                p1 = offs[i+0] + (j*m1/4 + k+0) % m1,
                p2 = offs[i+0] + (j*m1/4 + k+1) % m1,
                p3 = offs[i+1] + (j*m2/4 + k+0) % m2,
                p4 = offs[i+1] + (j*m2/4 + k+1) % m2,
                p5 = offs[os-i+0] + (j*m1/4 + k+0) % m1,
                p6 = offs[os-i+0] + (j*m1/4 + k+1) % m1,
                p7 = offs[os-i-1] + (j*m2/4 + k+0) % m2,
                p8 = offs[os-i-1] + (j*m2/4 + k+1) % m2
            ) each [
                [p1, p4, p3],
                if (k<m-1) [p1, p2, p4],
                [p5, p7, p8],
                if (k<m-1) [p5, p8, p6],
            ],
        ] : style=="icosa"? let(
            pyr = [for (x=[0:1:icosa_steps+1]) x],
            tri = sum(pyr),
            soff = cumsum(pyr)
        ) [
            for (tb=[0,1], j=[0,1], i = [0:1:4]) let(
                base = ((((tb*2) + j) * 5) + i) * tri
            )
            for (k = [0:1:icosa_steps-1])
            for (l = [0:1:k]) let(
                v1 = base + soff[k] + l,
                v2 = base + soff[k+1] + l,
                v3 = base + soff[k+1] + (l + 1),
                faces = [
                    if(l>0) [v1-1,v1,v2],
                    [v1,v3,v2],
                ],
                faces2 = (tb+j)%2? [for (f=faces) reverse(f)] : faces
            ) each faces2
        ] : []
    ) [reorient(anchor,spin,orient, r=r, p=verts), faces];



// Module: torus()
//
// Usage: Typical
//   torus(r_maj|d_maj, r_min|d_min, [center], ...);
//   torus(or|od, ir|id, ...);
//   torus(r_maj|d_maj, or|od, ...);
//   torus(r_maj|d_maj, ir|id, ...);
//   torus(r_min|d_min, or|od, ...);
//   torus(r_min|d_min, ir|id, ...);
// Usage: Attaching Children
//   torus(or|od, ir|id, ...) [attachments];
//
// Description:
//   Creates a torus shape.
//
// Figure(2D,Med):
//   module text3d(t,size=8) text(text=t,size=size,font="Helvetica", halign="center",valign="center");
//   module dashcirc(r,start=0,angle=359.9,dashlen=5) let(step=360*dashlen/(2*r*PI)) for(a=[start:step:start+angle]) stroke(arc(r=r,start=a,angle=step/2));
//   r = 75; r2 = 30;
//   down(r2+0.1) #torus(r_maj=r, r_min=r2, $fn=72);
//   color("blue") linear_extrude(height=0.01) {
//       dashcirc(r=r,start=15,angle=45);
//       dashcirc(r=r-r2, start=90+15, angle=60);
//       dashcirc(r=r+r2, start=180+45, angle=30);
//       dashcirc(r=r+r2, start=15, angle=30);
//   }
//   rot(240) color("blue") linear_extrude(height=0.01) {
//       stroke([[0,0],[r+r2,0]], endcaps="arrow2",width=2);
//       right(r) fwd(9) rot(-240) text3d("or",size=10);
//   }
//   rot(135) color("blue") linear_extrude(height=0.01) {
//       stroke([[0,0],[r-r2,0]], endcaps="arrow2",width=2);
//       right((r-r2)/2) back(8) rot(-135) text3d("ir",size=10);
//   }
//   rot(45) color("blue") linear_extrude(height=0.01) {
//       stroke([[0,0],[r,0]], endcaps="arrow2",width=2);
//       right(r/2) back(8) text3d("r_maj",size=9);
//   }
//   rot(30) color("blue") linear_extrude(height=0.01) {
//       stroke([[r,0],[r+r2,0]], endcaps="arrow2",width=2);
//       right(r+r2/2) fwd(8) text3d("r_min",size=7);
//   }
//
// Arguments:
//   r_maj = major radius of torus ring. (use with 'r_min', or 'd_min')
//   r_min = minor radius of torus ring. (use with 'r_maj', or 'd_maj')
//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
//   ---
//   d_maj  = major diameter of torus ring. (use with 'r_min', or 'd_min')
//   d_min = minor diameter of torus ring. (use with 'r_maj', or 'd_maj')
//   or = outer radius of the torus. (use with 'ir', or 'id')
//   ir = inside radius of the torus. (use with 'or', or 'od')
//   od = outer diameter of the torus. (use with 'ir' or 'id')
//   id = inside diameter of the torus. (use with 'or' or 'od')
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
//
// Example:
//   // These all produce the same torus.
//   torus(r_maj=22.5, r_min=7.5);
//   torus(d_maj=45, d_min=15);
//   torus(or=30, ir=15);
//   torus(od=60, id=30);
//   torus(d_maj=45, id=30);
//   torus(d_maj=45, od=60);
//   torus(d_min=15, id=30);
//   torus(d_min=15, od=60);
// Example: Standard Connectors
//   torus(od=60, id=30) show_anchors();
module torus(
    r_maj, r_min, center,
    d_maj, d_min,
    or, od, ir, id,
    anchor, spin=0, orient=UP
) {
    _or = get_radius(r=or, d=od, dflt=undef);
    _ir = get_radius(r=ir, d=id, dflt=undef);
    _r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef);
    _r_min = get_radius(r=r_min, d=d_min, dflt=undef);
    majrad = is_finite(_r_maj)? _r_maj :
        is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
        is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
        is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
        assert(false, "Bad Parameters");
    minrad = is_finite(_r_min)? _r_min :
        is_finite(_ir)? (majrad - _ir) :
        is_finite(_or)? (_or - majrad) :
        assert(false, "Bad Parameters");
    anchor = get_anchor(anchor, center, BOT, CENTER);
    attachable(anchor,spin,orient, r=(majrad+minrad), l=minrad*2) {
        rotate_extrude(convexity=4) {
            right(majrad) circle(r=minrad);
        }
        children();
    }
}


// Module: teardrop()
//
// Description:
//   Makes a teardrop shape in the XZ plane. Useful for 3D printable holes.
//
// Usage: Typical
//   teardrop(h|l, r, [ang], [cap_h], ...);
//   teardrop(h|l, d=, [ang=], [cap_h=], ...);
// Usage: Psuedo-Conical
//   teardrop(h|l, r1=, r2=, [ang=], [cap_h1=], [cap_h2=], ...);
//   teardrop(h|l, d1=, d2=, [ang=], [cap_h1=], [cap_h2=], ...);
// Usage: Attaching Children
//   teardrop(h|l, r, ...) [attachments];
//
// Arguments:
//   h / l = Thickness of teardrop. Default: 1
//   r = Radius of circular part of teardrop.  Default: 1
//   ang = Angle of hat walls from the Z axis.  Default: 45 degrees
//   cap_h = If given, height above center where the shape will be truncated. Default: `undef` (no truncation)
//   ---
//   r1 = Radius of circular portion of the front end of the teardrop shape.
//   r2 = Radius of circular portion of the back end of the teardrop shape.
//   d = Diameter of circular portion of the teardrop shape.
//   d1 = Diameter of circular portion of the front end of the teardrop shape.
//   d2 = Diameter of circular portion of the back end of the teardrop shape.
//   cap_h1 = If given, height above center where the shape will be truncated, on the front side. Default: `undef` (no truncation)
//   cap_h2 = If given, height above center where the shape will be truncated, on the back side. Default: `undef` (no truncation)
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
//
// Extra Anchors:
//   cap = The center of the top of the cap, oriented with the cap face normal.
//   cap_fwd = The front edge of the cap.
//   cap_back = The back edge of the cap.
//
// Example: Typical Shape
//   teardrop(r=30, h=10, ang=30);
// Example: Crop Cap
//   teardrop(r=30, h=10, ang=30, cap_h=40);
// Example: Close Crop
//   teardrop(r=30, h=10, ang=30, cap_h=20);
// Example: Psuedo-Conical
//   teardrop(r1=20, r2=30, h=40, cap_h1=25, cap_h2=35);
// Example: Standard Conical Connectors
//   teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
//       show_anchors(custom=false);
// Example(Spin,VPD=275): Named Conical Connectors
//   teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
//       show_anchors(std=false);
module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, anchor=CENTER, spin=0, orient=UP)
{
    r1 = get_radius(r=r, r1=r1, d=d, d1=d1, dflt=1);
    r2 = get_radius(r=r, r1=r2, d=d, d1=d2, dflt=1);
    l = first_defined([l, h, 1]);
    tip_y1 = adj_ang_to_hyp(r1, 90-ang);
    tip_y2 = adj_ang_to_hyp(r2, 90-ang);
    cap_h1 = min(first_defined([cap_h1, cap_h, tip_y1]), tip_y1);
    cap_h2 = min(first_defined([cap_h2, cap_h, tip_y2]), tip_y2);
    capvec = unit([0, cap_h1-cap_h2, l]);
    anchors = [
        named_anchor("cap",      [0,0,(cap_h1+cap_h2)/2], capvec),
        named_anchor("cap_fwd",  [0,-l/2,cap_h1],         unit((capvec+FWD)/2)),
        named_anchor("cap_back", [0,+l/2,cap_h2],         unit((capvec+BACK)/2), 180),
    ];
    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK, anchors=anchors) {
        rot(from=UP,to=FWD) {
            if (l > 0) {
                if (r1 == r2) {
                    linear_extrude(height=l, center=true, slices=2) {
                        teardrop2d(r=r1, ang=ang, cap_h=cap_h);
                    }
                } else {
                    hull() {
                        up(l/2-0.001) {
                            linear_extrude(height=0.001, center=false) {
                                teardrop2d(r=r1, ang=ang, cap_h=cap_h1);
                            }
                        }
                        down(l/2) {
                            linear_extrude(height=0.001, center=false) {
                                teardrop2d(r=r2, ang=ang, cap_h=cap_h2);
                            }
                        }
                    }
                }
            }
        }
        children();
    }
}


// Module: onion()
//
// Description:
//   Creates a sphere with a conical hat, to make a 3D teardrop.
//
// Usage:
//   onion(r|d, [ang], [cap_h]);
// Usage: Typical
//   onion(r, [ang], [cap_h], ...);
//   onion(d=, [ang=], [cap_h=], ...);
// Usage: Attaching Children
//   onion(r, ...) [attachments];
//
// Arguments:
//   r = radius of spherical portion of the bottom. Default: 1
//   ang = Angle of cone on top from vertical. Default: 45 degrees
//   cap_h = If given, height above sphere center to truncate teardrop shape.  Default: `undef` (no truncation)
//   ---
//   d = diameter of spherical portion of bottom.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
//
// Example: Typical Shape
//   onion(r=30, ang=30);
// Example: Crop Cap
//   onion(r=30, ang=30, cap_h=40);
// Example: Close Crop
//   onion(r=30, ang=30, cap_h=20);
// Example: Onions are useful for making the tops of large cylingdrical voids.
//   difference() {
//       cuboid([100,50,100], anchor=FWD+BOT);
//       down(0.1)
//           cylinder(h=50,d=50,anchor=BOT)
//               attach(TOP)
//                   onion(d=50, cap_h=30);
//   }
// Example: Standard Connectors
//   onion(r=30, ang=30, cap_h=40) show_anchors();
module onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP)
{
    r = get_radius(r=r, d=d, dflt=1);
    tip_y = adj_ang_to_hyp(r, 90-ang);
    cap_h = min(default(cap_h,tip_y), tip_y);
    anchors = [
        ["cap", [0,0,cap_h], UP, 0]
    ];
    attachable(anchor,spin,orient, r=r, anchors=anchors) {
        rotate_extrude(convexity=2) {
            difference() {
                teardrop2d(r=r, ang=ang, cap_h=cap_h);
                left(r) square(size=[2*r,2*max(cap_h,r)+1], center=true);
            }
        }
        children();
    }
}


// Section: Text

// Module: atext()
// Topics: Attachments, Text
// Usage:
//   atext(text, [h], [size], [font]);
// Description:
//   Creates a 3D text block that can be attached to other attachable objects.
//   NOTE: This cannot have children attached to it.
// Arguments:
//   text = The text string to instantiate as an object.
//   h = The height to which the text should be extruded.  Default: 1
//   size = The font size used to create the text block.  Default: 10
//   font = The name of the font used to create the text block.  Default: "Courier"
//   ---
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `"baseline"`
//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// See Also: attachable()
// Extra Anchors:
//   "baseline" = Anchors at the baseline of the text, at the start of the string.
//   str("baseline",VECTOR) = Anchors at the baseline of the text, modified by the X and Z components of the appended vector.
// Examples:
//   atext("Foobar", h=3, size=10);
//   atext("Foobar", h=2, size=12, font="Helvetica");
//   atext("Foobar", h=2, anchor=CENTER);
//   atext("Foobar", h=2, anchor=str("baseline",CENTER));
//   atext("Foobar", h=2, anchor=str("baseline",BOTTOM+RIGHT));
// Example: Using line_of() distributor
//   txt = "This is the string.";
//   line_of(spacing=[10,-5],n=len(txt))
//       atext(txt[$idx], size=10, anchor=CENTER);
// Example: Using arc_of() distributor
//   txt = "This is the string";
//   arc_of(r=50, n=len(txt), sa=0, ea=180)
//       atext(select(txt,-1-$idx), size=10, anchor=str("baseline",CENTER), spin=-90);
module atext(text, h=1, size=9, font="Courier", anchor="baseline", spin=0, orient=UP) {
    no_children($children);
    dummy1 =
        assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
        assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
        assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient));
    anchor = default(anchor, CENTER);
    spin =   default(spin,   0);
    orient = default(orient, UP);
    geom = _attach_geom(size=[size,size,h]);
    anch = !any([for (c=anchor) c=="["])? anchor :
        let(
            parts = str_split(str_split(str_split(anchor,"]")[0],"[")[1],","),
            vec = [for (p=parts) parse_float(str_strip(p," ",start=true))]
        ) vec;
    ha = anchor=="baseline"? "left" :
        anchor==anch && is_string(anchor)? "center" :
        anch.x<0? "left" :
        anch.x>0? "right" :
        "center";
    va = starts_with(anchor,"baseline")? "baseline" :
        anchor==anch && is_string(anchor)? "center" :
        anch.y<0? "bottom" :
        anch.y>0? "top" :
        "center";
    base = anchor=="baseline"? CENTER :
        anchor==anch && is_string(anchor)? CENTER :
        anch.z<0? BOTTOM :
        anch.z>0? TOP :
        CENTER;
    m = _attach_transform(base,spin,orient,geom);
    multmatrix(m) {
        $parent_anchor = anchor;
        $parent_spin   = spin;
        $parent_orient = orient;
        $parent_geom   = geom;
        $parent_size   = _attach_geom_size(geom);
        $attach_to   = undef;
        do_show = _attachment_is_shown($tags);
        if (do_show) {
            if (is_undef($color)) {
                linear_extrude(height=h, center=true)
                    text(text=text, size=size, halign=ha, valign=va, font=font);
            } else color($color) {
                $color = undef;
                linear_extrude(height=h, center=true)
                    text(text=text, size=size, halign=ha, valign=va, font=font);
            }
        }
    }
}

// This could be replaced with _cut_to_seg_u_form
function _cut_interp(pathcut, path, data) =
  [for(entry=pathcut)
    let(
       a = path[entry[1]-1],
        b = path[entry[1]],
        c = entry[0],
        i = max_index(v_abs(b-a)),
        factor = (c[i]-a[i])/(b[i]-a[i])
    )
    (1-factor)*data[entry[1]-1]+ factor * data[entry[1]]
  ];



// Module: path_text()
// Usage:
//   path_text(path, text, [size], [thickness], [font], [lettersize], [offset], [reverse], [normal], [top], [textmetrics])
// Description:
//   Place the text letter by letter onto the specified path using textmetrics (if available and requested)
//   or user specified letter spacing.  The path can be 2D or 3D.  In 2D the text appears along the path with letters upright
//   as determined by the path direction.  In 3D by default letters are positioned on the tangent line to the path with the path normal
//   pointing toward the reader.  The path normal points away from the center of curvature (the opposite of the normal produced
//   by path_normals()).  Note that this means that if the center of curvature switches sides the text will flip upside down.
//   If you want text on such a path you must supply your own normal or top vector. 
//   . 
//   Text appears starting at the beginning of the path, so if the 3D path moves right to left
//   then a left-to-right reading language will display in the wrong order. (For a 2D path text will appear upside down.)
//   The text for a 3D path appears positioned to be read from "outside" of the curve (from a point on the other side of the
//   curve from the center of curvature).  If you need the text to read properly from the inside, you can set reverse to
//   true to flip the text, or supply your own normal.  
//   .
//   If you do not have the experimental textmetrics feature enabled then you must specify the space for the letters
//   using lettersize, which can be a scalar or array.  You will have the easiest time getting good results by using
//   a monospace font such as Courier.  Note that even with text metrics, spacing may be different because path_text()
//   doesn't do kerning to adjust positions of individual glyphs.  Also if your font has ligatures they won't be used.  
//   .
//   By default letters appear centered on the path.  The offset can be specified to shift letters toward the reader (in
//   the direction of the normal).  
//   .
//   You can specify your own normal by setting `normal` to a direction or a list of directions.  Your normal vector should
//   point toward the reader.  You can also specify
//   top, which directs the top of the letters in a desired direction.  If you specify your own directions and they
//   are not perpendicular to the path then the direction you specify will take priority and the
//   letters will not rest on the tangent line of the path.  Note that the normal or top directions that you
//   specify must not be parallel to the path.  
// Arguments:
//   path = path to place the text on
//   text = text to create
//   size = font size
//   thickness = thickness of letters (not allowed for 2D path)
//   font = font to use
//   ---
//   lettersize = scalar or array giving size of letters
//   center = center text on the path instead of starting at the first point.  Default: false
//   offset = distance to shift letters "up" (towards the reader).  Not allowed for 2D path.  Default: 0
//   normal = direction or list of directions pointing towards the reader of the text.  Not allowed for 2D path.
//   top = direction or list of directions pointing toward the top of the text
//   reverse = reverse the letters if true.  Not allowed for 2D path.  Default: false
//   textmetrics = if set to true and lettersize is not given then use the experimental textmetrics feature.  You must be running a dev snapshot that includes this feature and have the feature turned on in your preferences.  Default: false
// Example:  The examples use Courier, a monospaced font.  The width is 1/1.2 times the specified size for this font.  This text could wrap around a cylinder. 
//   path = path3d(arc(100, r=25, angle=[245, 370]));
//   color("red")stroke(path, width=.3);
//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2);
// Example: By setting the normal to UP we can get text that lies flat, for writing around the edge of a disk:
//   path = path3d(arc(100, r=25, angle=[245, 370]));
//   color("red")stroke(path, width=.3);
//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, normal=UP);
// Example:  If we want text that reads from the other side we can use reverse.  Note we have to reverse the direction of the path and also set the reverse option.  
//   path = reverse(path3d(arc(100, r=25, angle=[65, 190])));
//   color("red")stroke(path, width=.3);
//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, reverse=true);
// Example: text debossed onto a cylinder in a spiral.  The text is 1 unit deep because it is half in, half out. 
//   text = ("A long text example to wrap around a cylinder, possibly for a few times.");
//   L = 5*len(text);
//   maxang = 360*L/(PI*50);
//   spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
//   difference(){
//     cyl(d=50, l=50, $fn=120);
//     path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
//   }
// Example: Same example but text embossed.  Make sure you have enough depth for the letters to fully overlap the object. 
//   text = ("A long text example to wrap around a cylinder, possibly for a few times.");
//   L = 5*len(text);
//   maxang = 360*L/(PI*50);
//   spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
//   cyl(d=50, l=50, $fn=120);
//   path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
// Example: Here the text baseline sits on the path.  (Note the default orientation makes text readable from below, so we specify the normal.)
//   path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
//   color("red")stroke(path,width=.2);
//   path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", normal=FRONT);
// Example: If we use top to orient the text upward, the text baseline is no longer aligned with the path. 
//   path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
//   color("red")stroke(path,width=.2);
//   path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", top=UP);
// Example: This sine wave wrapped around the cylinder has a twisting normal that produces wild letter layout.  We fix it with a custom normal which is different at every path point. 
//   path = [for(theta = [0:360]) [25*cos(theta), 25*sin(theta), 4*cos(theta*4)]];
//   normal = [for(theta = [0:360]) [cos(theta), sin(theta),0]];
//   zrot(-120)
//   difference(){
//     cyl(r=25, h=20, $fn=120);
//     path_text(path, "A sine wave wiggles", font="Courier", lettersize=5/1.2, size=5, normal=normal);
//   }
// Example: The path center of curvature changes, and the text flips.  
//   path =  zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
//   color("red")stroke(path,width=.2);
//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier", thickness=2);
// Example: We can fix it with top:
//   path =  zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
//   color("red")stroke(path,width=.2);
//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier", thickness=2, top=UP);
// Example(2D): With a 2D path instead of 3D there's no ambiguity about direction and it works by default:
//   path =  zrot(-120,p=concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180]))));
//   color("red")stroke(path,width=.2);
//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier");
module path_text(path, text, font, size, thickness, lettersize, offset=0, reverse=false, normal, top, center=false, textmetrics=false)
{
  dummy2=assert(is_path(path,[2,3]),"Must supply a 2d or 3d path")
         assert(num_defined([normal,top])<=1, "Cannot define both \"normal\" and \"top\"");
  dim = len(path[0]);
  normalok = is_undef(normal) || is_vector(normal,3) || (is_path(normal,3) && len(normal)==len(path));
  topok = is_undef(top) || is_vector(top,dim) || (dim==2 && is_vector(top,3) && top[2]==0)
                        || (is_path(top,dim) && len(top)==len(path));
  dummy4 = assert(dim==3 || is_undef(thickness), "Cannot give a thickness with 2d path")
           assert(dim==3 || !reverse, "Reverse not allowed with 2d path")
           assert(dim==3 || offset==0, "Cannot give offset with 2d path")
           assert(dim==3 || is_undef(normal), "Cannot define \"normal\" for a 2d path, only \"top\"")
           assert(normalok,"\"normal\" must be a vector or path compatible with the given path")
           assert(topok,"\"top\" must be a vector or path compatible with the given path");
  thickness = first_defined([thickness,1]);
  normal = is_vector(normal) ? repeat(normal, len(path))
         : is_def(normal) ? normal
         : undef;

  top = is_vector(top) ? repeat(dim==2?point2d(top):top, len(path))
         : is_def(top) ? top
         : undef;

  lsize = is_def(lettersize) ? force_list(lettersize, len(text))
        : textmetrics ? [for(letter=text) let(t=textmetrics(letter, font=font, size=size)) t.advance[0]]
        : assert(false, "textmetrics disabled: Must specify letter size");

  textlength = sum(lsize);
  dummy1 = assert(textlength<=path_length(path),"Path is too short for the text");

  start = center ? (path_length(path) - textlength)/2 : 0;
   
  pts = _path_cut_points(path, add_scalar([0, each cumsum(lsize)],start+lsize[0]/2), direction=true);

  usernorm = is_def(normal);
  usetop = is_def(top);
  
  normpts = is_undef(normal) ? (reverse?1:-1)*column(pts,3) : _cut_interp(pts,path, normal);
  toppts = is_undef(top) ? undef : _cut_interp(pts,path,top);
  for(i=idx(text))
      let( tangent = pts[i][2] )
      assert(!usetop || !approx(tangent*toppts[i],norm(top[i])*norm(tangent)),
             str("Specified top direction parallel to path at character ",i))
      assert(usetop || !approx(tangent*normpts[i],norm(normpts[i])*norm(tangent)),
             str("Specified normal direction parallel to path at character ",i))
      let(
          adjustment = usetop ?  (tangent*toppts[i])*toppts[i]/(toppts[i]*toppts[i])
                     : usernorm ?  (tangent*normpts[i])*normpts[i]/(normpts[i]*normpts[i])
                     : [0,0,0]
      )
      move(pts[i][0])
      if(dim==3){
          frame_map(x=tangent-adjustment,
                    z=usetop ? undef : normpts[i],
                    y=usetop ? toppts[i] : undef)
          up(offset-thickness/2)
          linear_extrude(height=thickness)
          left(lsize[0]/2)text(text[i], font=font, size=size);
     } else {
          frame_map(x=point3d(tangent-adjustment), y=point3d(usetop ? toppts[i] : -normpts[i]))
          left(lsize[0]/2)text(text[i], font=font, size=size);
     }
}



// Section: Miscellaneous


// Module: interior_fillet()
//
// Description:
//   Creates a shape that can be unioned into a concave joint between two faces, to fillet them.
//   Center this part along the concave edge to be chamfered and union it in.
//
// Usage: Typical
//   interior_fillet(l, r, [ang], [overlap], ...);
//   interior_fillet(l, d=, [ang=], [overlap=], ...);
// Usage: Attaching Children
//   interior_fillet(l, r, [ang], [overlap], ...) [attachments];
//
// Arguments:
//   l = Length of edge to fillet.
//   r = Radius of fillet.
//   ang = Angle between faces to fillet.
//   overlap = Overlap size for unioning with faces.
//   ---
//   d = Diameter of fillet.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `FRONT+LEFT`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
//
// Example:
//   union() {
//       translate([0,2,-4])
//           cube([20, 4, 24], anchor=BOTTOM);
//       translate([0,-10,-4])
//           cube([20, 20, 4], anchor=BOTTOM);
//       color("green")
//           interior_fillet(
//               l=20, r=10,
//               spin=180, orient=RIGHT
//           );
//   }
//
// Example:
//   interior_fillet(l=40, r=10, spin=-90);
//
// Example: Using with Attachments
//   cube(50,center=true) {
//     position(FRONT+LEFT)
//       interior_fillet(l=50, r=10, spin=-90);
//     position(BOT+FRONT)
//       interior_fillet(l=50, r=10, spin=180, orient=RIGHT);
//   }
module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=FRONT+LEFT, spin=0, orient=UP) {
    r = get_radius(r=r, d=d, dflt=1);
    dy = r/tan(ang/2);
    steps = ceil(segs(r)*ang/360);
    step = ang/steps;
    attachable(anchor,spin,orient, size=[r,r,l]) {
        if (l > 0) {
            linear_extrude(height=l, convexity=4, center=true) {
                path = concat(
                    [[0,0]],
                    [for (i=[0:1:steps]) let(a=270-i*step) r*[cos(a),sin(a)]+[dy,r]]
                );
                translate(-[r,r]/2) polygon(path);
            }
        }
        children();
    }
}


// Function&Module: heightfield()
// Usage: As Module
//   heightfield(data, [size], [bottom], [maxz], [xrange], [yrange], [style], [convexity], ...);
// Usage: Attaching Children
//   heightfield(data, [size], ...) [attachments];
// Usage: As Function
//   vnf = heightfield(data, [size], [bottom], [maxz], [xrange], [yrange], [style], ...);
// Description:
//   Given a regular rectangular 2D grid of scalar values, or a function literal, generates a 3D
//   surface where the height at any given point is the scalar value for that position.
// Arguments:
//   data = This is either the 2D rectangular array of heights, or a function literal that takes X and Y arguments.
//   size = The [X,Y] size of the surface to create.  If given as a scalar, use it for both X and Y sizes. Default: `[100,100]`
//   bottom = The Z coordinate for the bottom of the heightfield object to create.  Any heights lower than this will be truncated to very slightly above this height.  Default: -20
//   maxz = The maximum height to model.  Truncates anything taller to this height.  Default: 99
//   xrange = A range of values to iterate X over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
//   yrange = A range of values to iterate Y over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
//   style = The style of subdividing the quads into faces.  Valid options are "default", "alt", and "quincunx".  Default: "default"
//   ---
//   convexity = Max number of times a line could intersect a wall of the surface being formed. Module only.  Default: 10
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example:
//   heightfield(size=[100,100], bottom=-20, data=[
//       for (y=[-180:4:180]) [
//           for(x=[-180:4:180])
//           10*cos(3*norm([x,y]))
//       ]
//   ]);
// Example:
//   intersection() {
//       heightfield(size=[100,100], data=[
//           for (y=[-180:5:180]) [
//               for(x=[-180:5:180])
//               10+5*cos(3*x)*sin(3*y)
//           ]
//       ]);
//       cylinder(h=50,d=100);
//   }
// Example: Heightfield by Function
//   fn = function (x,y) 10*sin(x*360)*cos(y*360);
//   heightfield(size=[100,100], data=fn);
// Example: Heightfield by Function, with Specific Ranges
//   fn = function (x,y) 2*cos(5*norm([x,y]));
//   heightfield(
//       size=[100,100], bottom=-20, data=fn,
//       xrange=[-180:2:180], yrange=[-180:2:180]
//   );
module heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04:1], yrange=[-1:0.04:1], style="default", convexity=10, anchor=CENTER, spin=0, orient=UP)
{
    size = is_num(size)? [size,size] : point2d(size);
    vnf = heightfield(data=data, size=size, xrange=xrange, yrange=yrange, bottom=bottom, maxz=maxz, style=style);
    attachable(anchor,spin,orient, vnf=vnf) {
        vnf_polyhedron(vnf, convexity=convexity);
        children();
    }
}


function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04:1], yrange=[-1:0.04:1], style="default", anchor=CENTER, spin=0, orient=UP) =
    assert(is_list(data) || is_function(data))
    let(
        size = is_num(size)? [size,size] : point2d(size),
        xvals = is_list(data)
          ? [for (i=idx(data[0])) i]
          : assert(is_list(xrange)||is_range(xrange)) [for (x=xrange) x],
        yvals = is_list(data)
          ? [for (i=idx(data)) i]
          : assert(is_list(yrange)||is_range(yrange)) [for (y=yrange) y],
        xcnt = len(xvals),
        minx = min(xvals),
        maxx = max(xvals),
        ycnt = len(yvals),
        miny = min(yvals),
        maxy = max(yvals),
        verts = is_list(data) ? [
                for (y = [0:1:ycnt-1]) [
                    for (x = [0:1:xcnt-1]) [
                        size.x * (x/(xcnt-1)-0.5),
                        size.y * (y/(ycnt-1)-0.5),
                        data[y][x]
                    ]
                ]
            ] : [
                for (y = yrange) [
                    for (x = xrange) let(
                        z = data(x,y)
                    ) [
                        size.x * ((x-minx)/(maxx-minx)-0.5),
                        size.y * ((y-miny)/(maxy-miny)-0.5),
                        min(maxz, max(bottom+0.1, default(z,0)))
                    ]
                ]
            ],
        vnf = vnf_join([
            vnf_vertex_array(verts, style=style, reverse=true),
            vnf_vertex_array([
                verts[0],
                [for (v=verts[0]) [v.x, v.y, bottom]],
            ]),
            vnf_vertex_array([
                [for (v=verts[ycnt-1]) [v.x, v.y, bottom]],
                verts[ycnt-1],
            ]),
            vnf_vertex_array([
                [for (r=verts) let(v=r[0]) [v.x, v.y, bottom]],
                [for (r=verts) let(v=r[0]) v],
            ]),
            vnf_vertex_array([
                [for (r=verts) let(v=r[xcnt-1]) v],
                [for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom]],
            ]),
            vnf_vertex_array([
                [
                    for (v=verts[0]) [v.x, v.y, bottom],
                    for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom],
                ], [
                    for (r=verts) let(v=r[0]) [v.x, v.y, bottom],
                    for (v=verts[ycnt-1]) [v.x, v.y, bottom],
                ]
            ])
        ])
    ) reorient(anchor,spin,orient, vnf=vnf, p=vnf);



// Module: ruler()
// Usage:
//   ruler(length, width, [thickness=], [depth=], [labels=], [pipscale=], [maxscale=], [colors=], [alpha=], [unit=], [inch=]);
// Description:
//   Creates a ruler for checking dimensions of the model
// Arguments:
//   length = length of the ruler.  Default 100
//   width = width of the ruler.  Default: size of the largest unit division
//   ---
//   thickness = thickness of the ruler. Default: 1
//   depth = the depth of mark subdivisions. Default: 3
//   labels = draw numeric labels for depths where labels are larger than 1.  Default: false
//   pipscale = width scale of the pips relative to the next size up.  Default: 1/3
//   maxscale = log10 of the maximum width divisions to display.  Default: based on input length
//   colors = colors to use for the ruler, a list of two values.  Default: `["black","white"]`
//   alpha = transparency value.  Default: 1.0
//   unit = unit to mark.  Scales the ruler marks to a different length.  Default: 1
//   inch = set to true for a ruler scaled to inches (assuming base dimension is mm).  Default: false
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `LEFT+BACK+TOP`
//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Examples(2D,Big):
//   ruler(100,depth=3);
//   ruler(100,depth=3,labels=true);
//   ruler(27);
//   ruler(27,maxscale=0);
//   ruler(100,pipscale=3/4,depth=2);
//   ruler(100,width=2,depth=2);
// Example(2D,Big):  Metric vs Imperial
//   ruler(12,width=50,inch=true,labels=true,maxscale=0);
//   fwd(50)ruler(300,width=50,labels=true);
module ruler(length=100, width, thickness=1, depth=3, labels=false, pipscale=1/3, maxscale,
             colors=["black","white"], alpha=1.0, unit=1, inch=false, anchor=LEFT+BACK+TOP, spin=0, orient=UP)
{
    inchfactor = 25.4;
    assert(depth<=5, "Cannot render scales smaller than depth=5");
    assert(len(colors)==2, "colors must contain a list of exactly two colors.");
    length = inch ? inchfactor * length : length;
    unit = inch ? inchfactor*unit : unit;
    maxscale = is_def(maxscale)? maxscale : floor(log(length/unit-EPSILON));
    scales = unit * [for(logsize = [maxscale:-1:maxscale-depth+1]) pow(10,logsize)];
    widthfactor = (1-pipscale) / (1-pow(pipscale,depth));
    width = default(width, scales[0]);
    widths = width * widthfactor * [for(logsize = [0:-1:-depth+1]) pow(pipscale,-logsize)];
    offsets = concat([0],cumsum(widths));
    attachable(anchor,spin,orient, size=[length,width,thickness]) {
        translate([-length/2, -width/2, 0]) 
        for(i=[0:1:len(scales)-1]) {
            count = ceil(length/scales[i]);
            fontsize = 0.5*min(widths[i], scales[i]/ceil(log(count*scales[i]/unit)));
            back(offsets[i]) {
                xcopies(scales[i], n=count, sp=[0,0,0]) union() {
                    actlen = ($idx<count-1) || approx(length%scales[i],0) ? scales[i] : length % scales[i];
                    color(colors[$idx%2], alpha=alpha) {
                        w = i>0 ? quantup(widths[i],1/1024) : widths[i];    // What is the i>0 test supposed to do here? 
                        cube([quantup(actlen,1/1024),quantup(w,1/1024),thickness], anchor=FRONT+LEFT);
                    }
                    mark =
                        i == 0 && $idx % 10 == 0 && $idx != 0 ? 0 :
                        i == 0 && $idx % 10 == 9 && $idx != count-1 ? 1 :
                        $idx % 10 == 4 ? 1 :
                        $idx % 10 == 5 ? 0 : -1;
                    flip = 1-mark*2;
                    if (mark >= 0) {
                        marklength = min(widths[i]/2, scales[i]*2);
                        markwidth = marklength*0.4;
                        translate([mark*scales[i], widths[i], 0]) {
                            color(colors[1-$idx%2], alpha=alpha) {
                                linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
                                    polygon(scale([flip*markwidth, marklength],p=[[0,0], [1, -1], [0,-0.9]]));
                                }
                            }
                        }
                    }
                    if (labels && scales[i]/unit+EPSILON >= 1) {
                        color(colors[($idx+1)%2], alpha=alpha) {
                            linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
                                back(scales[i]*.02) {
                                    text(text=str( $idx * scales[i] / unit), size=fontsize, halign="left", valign="baseline");
                                }
                            }
                        }
                    }

                }
            }
        }
        children();
    }
}





// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap