1//////////////////////////////////////////////////////////////////////
   2// LibFile: shapes3d.scad
   3//   Some standard modules for making 3d shapes with attachment support, and function forms
   4//   that produce a VNF.  Also included are shortcuts cylinders in each orientation and extended versions of
   5//   the standard modules that provide roundovers and chamfers.  The spheroid() module provides
   6//   several different ways to make a sphere, and the text modules let you write text on a path
   7//   so you can place it on a curved object.  A ruler lets you measure objects.
   8// Includes:
   9//   include <BOSL2/std.scad>
  10// FileGroup: Basic Modeling
  11// FileSummary: Attachable cubes, cylinders, spheres, ruler, and text.  Many can produce a VNF.
  12// FileFootnotes: STD=Included in std.scad
  13//////////////////////////////////////////////////////////////////////
  14
  15use <builtins.scad>
  16
  17
  18// Section: Cuboids, Prismoids and Pyramids
  19
  20// Function&Module: cube()
  21// Synopsis: Creates a cube with anchors for attaching children.
  22// SynTags: Geom, VNF
  23// Topics: Shapes (3D), Attachable, VNF Generators
  24// See Also: cuboid(), prismoid()
  25// Usage: As Module (as in native OpenSCAD)
  26//   cube(size, [center]);
  27// Usage: With BOSL2 Attachment extensions
  28//   cube(size, [center], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
  29// Usage: As Function (BOSL2 extension)
  30//   vnf = cube(size, ...);
  31// Description:
  32//   Creates a 3D cubic object.
  33//   This module extends the built-in cube()` module by providing support for attachments and a function form.  
  34//   When called as a function, returns a [VNF](vnf.scad) for a cube.
  35// Arguments:
  36//   size = The size of the cube.
  37//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=FRONT+LEFT+BOTTOM`.
  38//   ---
  39//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
  40//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
  41//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
  42// Example: Simple cube.
  43//   cube(40);
  44// Example: Rectangular cube.
  45//   cube([20,40,50]);
  46// Example: Anchoring.
  47//   cube([20,40,50], anchor=BOTTOM+FRONT);
  48// Example: Spin.
  49//   cube([20,40,50], anchor=BOTTOM+FRONT, spin=30);
  50// Example: Orientation.
  51//   cube([20,40,50], anchor=BOTTOM+FRONT, spin=30, orient=FWD);
  52// Example: Standard Connectors.
  53//   cube(40, center=true) show_anchors();
  54// Example: Called as Function
  55//   vnf = cube([20,40,50]);
  56//   vnf_polyhedron(vnf);
  57
  58module cube(size=1, center, anchor, spin=0, orient=UP)
  59{
  60    anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
  61    size = scalar_vec3(size);
  62    attachable(anchor,spin,orient, size=size) {
  63        _cube(size, center=true);
  64        children();
  65    }
  66}
  67
  68function cube(size=1, center, anchor, spin=0, orient=UP) =
  69    let(
  70        siz = scalar_vec3(size)
  71    )
  72    assert(all_positive(siz), "All size components must be positive.")
  73    let(
  74        anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]),
  75        unscaled = [
  76            [-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1],
  77            [-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1],
  78        ]/2,
  79        verts = is_num(size)? unscaled * size :
  80            is_vector(size,3)? [for (p=unscaled) v_mul(p,size)] :
  81            assert(is_num(size) || is_vector(size,3)),
  82        faces = [
  83            [0,1,2], [0,2,3],  //BOTTOM
  84            [0,4,5], [0,5,1],  //FRONT
  85            [1,5,6], [1,6,2],  //RIGHT
  86            [2,6,7], [2,7,3],  //BACK
  87            [3,7,4], [3,4,0],  //LEFT
  88            [6,4,7], [6,5,4]   //TOP
  89        ]
  90    ) [reorient(anchor,spin,orient, size=siz, p=verts), faces];
  91
  92
  93
  94// Module: cuboid()
  95// Synopsis: Creates a cube with chamfering and roundovers.
  96// SynTags: Geom
  97// Topics: Shapes (3D), Attachable, VNF Generators
  98// See Also: prismoid(), rounded_prism()
  99// Usage: Standard Cubes
 100//   cuboid(size, [anchor=], [spin=], [orient=]);
 101//   cuboid(size, p1=, ...);
 102//   cuboid(p1=, p2=, ...);
 103// Usage: Chamfered Cubes
 104//   cuboid(size, [chamfer=], [edges=], [except=], [trimcorners=], ...);
 105// Usage: Rounded Cubes
 106//   cuboid(size, [rounding=], [teardrop=], [edges=], [except=], [trimcorners=], ...);
 107// Usage: Attaching children
 108//   cuboid(...) ATTACHMENTS;
 109//
 110// Description:
 111//   Creates a cube or cuboid object, with optional chamfering or rounding of edges and corners.
 112//   You cannot mix chamfering and rounding: just one edge treatment with the same size applies to all selected edges.
 113//   Negative chamfers and roundings can be applied to create external fillets, but they
 114//   only apply to edges around the top or bottom faces.  If you specify an edge set other than "ALL"
 115//   with negative roundings or chamfers then you will get an error.  See [Specifying Edges](attachments.scad#section-specifying-edges)
 116//   for information on how to specify edge sets.
 117// Arguments:
 118//   size = The size of the cube, a number or length 3 vector.
 119//   ---
 120//   chamfer = Size of chamfer, inset from sides.  Default: No chamfering.
 121//   rounding = Radius of the edge rounding.  Default: No rounding.
 122//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#section-specifying-edges).  Default: all edges.
 123//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#section-specifying-edges).  Default: No edges.
 124//   trimcorners = If true, rounds or chamfers corners where three chamfered/rounded edges meet.  Default: `true`
 125//   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`
 126//   p1 = Align the cuboid's corner at `p1`, if given.  Forces `anchor=FRONT+LEFT+BOTTOM`.
 127//   p2 = If given with `p1`, defines the cornerpoints of the cuboid.
 128//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 129//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 130//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 131// Example: Simple regular cube.
 132//   cuboid(40);
 133// Example: Cube with minimum cornerpoint given.
 134//   cuboid(20, p1=[10,0,0]);
 135// Example: Rectangular cube, with given X, Y, and Z sizes.
 136//   cuboid([20,40,50]);
 137// Example: Cube by Opposing Corners.
 138//   cuboid(p1=[0,10,0], p2=[20,30,30]);
 139// Example: Chamferred Edges and Corners.
 140//   cuboid([30,40,50], chamfer=5);
 141// Example: Chamferred Edges, Untrimmed Corners.
 142//   cuboid([30,40,50], chamfer=5, trimcorners=false);
 143// Example: Rounded Edges and Corners
 144//   cuboid([30,40,50], rounding=10);
 145// Example(VPR=[100,0,25],VPD=180): Rounded Edges and Corners with Teardrop Bottoms
 146//   cuboid([30,40,50], rounding=10, teardrop=true);
 147// Example: Rounded Edges, Untrimmed Corners
 148//   cuboid([30,40,50], rounding=10, trimcorners=false);
 149// Example: Chamferring Selected Edges
 150//   cuboid(
 151//       [30,40,50], chamfer=5,
 152//       edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
 153//       $fn=24
 154//   );
 155// Example: Rounding Selected Edges
 156//   cuboid(
 157//       [30,40,50], rounding=5,
 158//       edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
 159//       $fn=24
 160//   );
 161// Example: Negative Chamferring
 162//   cuboid(
 163//       [30,40,50], chamfer=-5,
 164//       edges=[TOP,BOT], except=RIGHT,
 165//       $fn=24
 166//   );
 167// Example: Negative Chamferring, Untrimmed Corners
 168//   cuboid(
 169//       [30,40,50], chamfer=-5,
 170//       edges=[TOP,BOT], except=RIGHT,
 171//       trimcorners=false, $fn=24
 172//   );
 173// Example: Negative Rounding
 174//   cuboid(
 175//       [30,40,50], rounding=-5,
 176//       edges=[TOP,BOT], except=RIGHT,
 177//       $fn=24
 178//   );
 179// Example: Negative Rounding, Untrimmed Corners
 180//   cuboid(
 181//       [30,40,50], rounding=-5,
 182//       edges=[TOP,BOT], except=RIGHT,
 183//       trimcorners=false, $fn=24
 184//   );
 185// Example: Roundings and Chamfers can be as large as the full size of the cuboid, so long as the edges would not interfere.
 186//   cuboid([40,20,10], rounding=20, edges=[FWD+RIGHT,BACK+LEFT]);
 187// Example: Standard Connectors
 188//   cuboid(40) show_anchors();
 189
 190module cuboid(
 191    size=[1,1,1],
 192    p1, p2,
 193    chamfer,
 194    rounding,
 195    edges=EDGES_ALL,
 196    except=[],
 197    except_edges,
 198    trimcorners=true,
 199    teardrop=false,
 200    anchor=CENTER,
 201    spin=0,
 202    orient=UP
 203) {
 204    module trunc_cube(s,corner) {
 205        multmatrix(
 206            (corner.x<0? xflip() : ident(4)) *
 207            (corner.y<0? yflip() : ident(4)) *
 208            (corner.z<0? zflip() : ident(4)) *
 209            scale(s+[1,1,1]*0.001) *
 210            move(-[1,1,1]/2)
 211        ) polyhedron(
 212            [[1,1,1],[1,1,0],[1,0,0],[0,1,1],[0,1,0],[1,0,1],[0,0,1]],
 213            [[0,1,2],[2,5,0],[0,5,6],[0,6,3],[0,3,4],[0,4,1],[1,4,2],[3,6,4],[5,2,6],[2,4,6]]
 214        );
 215    }
 216    module xtcyl(l,r) {
 217        if (teardrop) {
 218            teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=90, orient=DOWN);
 219        } else {
 220            yrot(90) cyl(l=l, r=r);
 221        }
 222    }
 223    module ytcyl(l,r) {
 224        if (teardrop) {
 225            teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=0, orient=DOWN);
 226        } else {
 227            zrot(90) yrot(90) cyl(l=l, r=r);
 228        }
 229    }
 230    module tsphere(r) {
 231        if (teardrop) {
 232            onion(r=r, cap_h=r, ang=teardrop, orient=DOWN);
 233        } else {
 234            spheroid(r=r, style="octa", orient=DOWN);
 235        }
 236    }
 237    module corner_shape(corner) {
 238        e = _corner_edges(edges, corner);
 239        cnt = sum(e);
 240        r = first_defined([chamfer, rounding]);
 241        dummy = assert(is_finite(r) && !approx(r,0));
 242        c = [r,r,r];
 243        m = 0.01;
 244        c2 = v_mul(corner,c/2);
 245        c3 = v_mul(corner,c-[1,1,1]*m/2);
 246        $fn = is_finite(chamfer)? 4 : quantup(segs(r),4);
 247        translate(v_mul(corner, size/2-c)) {
 248            if (cnt == 0 || approx(r,0)) {
 249                translate(c3) cube(m, center=true);
 250            } else if (cnt == 1) {
 251                if (e.x) {
 252                    right(c3.x) {
 253                        intersection() {
 254                            xtcyl(l=m, r=r);
 255                            multmatrix(
 256                                (corner.y<0? yflip() : ident(4)) *
 257                                (corner.z<0? zflip() : ident(4))
 258                            ) {
 259                                yrot(-90) linear_extrude(height=m+0.1, center=true) {
 260                                    polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
 261                                }
 262                            }
 263                        }
 264                    }
 265                } else if (e.y) {
 266                    back(c3.y) {
 267                        intersection() {
 268                            ytcyl(l=m, r=r);
 269                            multmatrix(
 270                                (corner.x<0? xflip() : ident(4)) *
 271                                (corner.z<0? zflip() : ident(4))
 272                            ) {
 273                                xrot(90) linear_extrude(height=m+0.1, center=true) {
 274                                    polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
 275                                }
 276                            }
 277                        }
 278                    }
 279                } else if (e.z) {
 280                    up(c3.z) {
 281                        intersection() {
 282                            zcyl(l=m, r=r);
 283                            multmatrix(
 284                                (corner.x<0? xflip() : ident(4)) *
 285                                (corner.y<0? yflip() : ident(4))
 286                            ) {
 287                                linear_extrude(height=m+0.1, center=true) {
 288                                    polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
 289                                }
 290                            }
 291                        }
 292                    }
 293                }
 294            } else if (cnt == 2) {
 295                intersection() {
 296                    if (!e.x) {
 297                        intersection() {
 298                            ytcyl(l=c.y*2, r=r);
 299                            zcyl(l=c.z*2, r=r);
 300                        }
 301                    } else if (!e.y) {
 302                        intersection() {
 303                            xtcyl(l=c.x*2, r=r);
 304                            zcyl(l=c.z*2, r=r);
 305                        }
 306                    } else {
 307                        intersection() {
 308                            xtcyl(l=c.x*2, r=r);
 309                            ytcyl(l=c.y*2, r=r);
 310                        }
 311                    }
 312                    translate(c2) trunc_cube(c,corner); // Trim to just the octant.
 313                }
 314            } else {
 315                intersection() {
 316                    if (trimcorners) {
 317                        tsphere(r=r);
 318                    } else {
 319                        intersection() {
 320                            xtcyl(l=c.x*2, r=r);
 321                            ytcyl(l=c.y*2, r=r);
 322                            zcyl(l=c.z*2, r=r);
 323                        }
 324                    }
 325                    translate(c2) trunc_cube(c,corner); // Trim to just the octant.
 326                }
 327            }
 328        }
 329    }
 330
 331    size = scalar_vec3(size);
 332    edges = _edges(edges, except=first_defined([except_edges,except]));
 333    teardrop = is_bool(teardrop)&&teardrop? 45 : teardrop;
 334    chamfer = approx(chamfer,0) ? undef : chamfer;
 335    rounding = approx(rounding,0) ? undef : rounding;
 336    checks =
 337        assert(is_vector(size,3))
 338        assert(all_nonnegative(size), "All components of size= must be >=0")
 339        assert(is_undef(chamfer) || is_finite(chamfer),"chamfer must be a finite value")
 340        assert(is_undef(rounding) || is_finite(rounding),"rounding must be a finite value")
 341        assert(is_undef(rounding) || is_undef(chamfer), "Cannot specify nonzero value for both chamfer and rounding")
 342        assert(teardrop==false || (is_finite(teardrop) && teardrop>0 && teardrop<=90), "teardrop must be either false or an angle number between 0 and 90")
 343        assert(is_undef(p1) || is_vector(p1))
 344        assert(is_undef(p2) || is_vector(p2))
 345        assert(is_bool(trimcorners));
 346    if (!is_undef(p1)) {
 347        if (!is_undef(p2)) {
 348            translate(pointlist_bounds([p1,p2])[0]) {
 349                cuboid(size=v_abs(p2-p1), chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=-[1,1,1]) children();
 350            }
 351        } else {
 352            translate(p1) {
 353                cuboid(size=size, chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=-[1,1,1]) children();
 354            }
 355        }
 356    } else {
 357        rr = max(default(chamfer,0), default(rounding,0));
 358        if (rr>0) {
 359            minx = max(
 360                edges.y[0] + edges.y[1], edges.y[2] + edges.y[3],
 361                edges.z[0] + edges.z[1], edges.z[2] + edges.z[3],
 362                edges.y[0] + edges.z[1], edges.y[0] + edges.z[3],
 363                edges.y[1] + edges.z[0], edges.y[1] + edges.z[2],
 364                edges.y[2] + edges.z[1], edges.y[2] + edges.z[3],
 365                edges.y[3] + edges.z[0], edges.y[3] + edges.z[2]
 366            ) * rr;
 367            miny = max(
 368                edges.x[0] + edges.x[1], edges.x[2] + edges.x[3],
 369                edges.z[0] + edges.z[2], edges.z[1] + edges.z[3],
 370                edges.x[0] + edges.z[2], edges.x[0] + edges.z[3],
 371                edges.x[1] + edges.z[0], edges.x[1] + edges.z[1],
 372                edges.x[2] + edges.z[2], edges.x[2] + edges.z[3],
 373                edges.x[3] + edges.z[0], edges.x[3] + edges.z[1]
 374            ) * rr;
 375            minz = max(
 376                edges.x[0] + edges.x[2], edges.x[1] + edges.x[3],
 377                edges.y[0] + edges.y[2], edges.y[1] + edges.y[3],
 378                edges.x[0] + edges.y[2], edges.x[0] + edges.y[3],
 379                edges.x[1] + edges.y[2], edges.x[1] + edges.y[3],
 380                edges.x[2] + edges.y[0], edges.x[2] + edges.y[1],
 381                edges.x[3] + edges.y[0], edges.x[3] + edges.y[1]
 382            ) * rr;
 383            check =
 384                assert(minx <= size.x, "Rounding or chamfering too large for cuboid size in the X axis.")
 385                assert(miny <= size.y, "Rounding or chamfering too large for cuboid size in the Y axis.")
 386                assert(minz <= size.z, "Rounding or chamfering too large for cuboid size in the Z axis.")
 387            ;
 388        }
 389        majrots = [[0,90,0], [90,0,0], [0,0,0]];
 390        attachable(anchor,spin,orient, size=size) {
 391            if (is_finite(chamfer) && !approx(chamfer,0)) {
 392                if (edges == EDGES_ALL && trimcorners) {
 393                    if (chamfer<0) {
 394                        cube(size, center=true) {
 395                            attach(TOP,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
 396                            attach(BOT,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
 397                        }
 398                    } else {
 399                        isize = [for (v = size) max(0.001, v-2*chamfer)];
 400                        hull() {
 401                            cube([ size.x, isize.y, isize.z], center=true);
 402                            cube([isize.x,  size.y, isize.z], center=true);
 403                            cube([isize.x, isize.y,  size.z], center=true);
 404                        }
 405                    }
 406                } else if (chamfer<0) {
 407                    checks = assert(edges == EDGES_ALL || edges[2] == [0,0,0,0], "Cannot use negative chamfer with Z aligned edges.");
 408                    ach = abs(chamfer);
 409                    cube(size, center=true);
 410
 411                    // External-Chamfer mask edges
 412                    difference() {
 413                        union() {
 414                            for (i = [0:3], axis=[0:1]) {
 415                                if (edges[axis][i]>0) {
 416                                    vec = EDGE_OFFSETS[axis][i];
 417                                    translate(v_mul(vec/2, size+[ach,ach,-ach])) {
 418                                        rotate(majrots[axis]) {
 419                                            cube([ach, ach, size[axis]], center=true);
 420                                        }
 421                                    }
 422                                }
 423                            }
 424
 425                            // Add multi-edge corners.
 426                            if (trimcorners) {
 427                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
 428                                    ce = _corner_edges(edges, [xa,ya,za]);
 429                                    if (ce.x + ce.y > 1) {
 430                                        translate(v_mul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
 431                                            cube([ach+0.01,ach+0.01,ach], center=true);
 432                                        }
 433                                    }
 434                                }
 435                            }
 436                        }
 437
 438                        // Remove bevels from overhangs.
 439                        for (i = [0:3], axis=[0:1]) {
 440                            if (edges[axis][i]>0) {
 441                                vec = EDGE_OFFSETS[axis][i];
 442                                translate(v_mul(vec/2, size+[2*ach,2*ach,-2*ach])) {
 443                                    rotate(majrots[axis]) {
 444                                        zrot(45) cube([ach*sqrt(2), ach*sqrt(2), size[axis]+2.1*ach], center=true);
 445                                    }
 446                                }
 447                            }
 448                        }
 449                    }
 450                } else {
 451                    hull() {
 452                        corner_shape([-1,-1,-1]);
 453                        corner_shape([ 1,-1,-1]);
 454                        corner_shape([-1, 1,-1]);
 455                        corner_shape([ 1, 1,-1]);
 456                        corner_shape([-1,-1, 1]);
 457                        corner_shape([ 1,-1, 1]);
 458                        corner_shape([-1, 1, 1]);
 459                        corner_shape([ 1, 1, 1]);
 460                    }
 461                }
 462            } else if (is_finite(rounding) && !approx(rounding,0)) {
 463                sides = quantup(segs(rounding),4);
 464                if (edges == EDGES_ALL) {
 465                    if(rounding<0) {
 466                        cube(size, center=true);
 467                        zflip_copy() {
 468                            up(size.z/2) {
 469                                difference() {
 470                                    down(-rounding/2) cube([size.x-2*rounding, size.y-2*rounding, -rounding], center=true);
 471                                    down(-rounding) {
 472                                        ycopies(size.y-2*rounding) xcyl(l=size.x-3*rounding, r=-rounding);
 473                                        xcopies(size.x-2*rounding) ycyl(l=size.y-3*rounding, r=-rounding);
 474                                    }
 475                                }
 476                            }
 477                        }
 478                    } else {
 479                        isize = [for (v = size) max(0.001, v-2*rounding)];
 480                        minkowski() {
 481                            cube(isize, center=true);
 482                            if (trimcorners) {
 483                                tsphere(r=rounding, $fn=sides);
 484                            } else {
 485                                intersection() {
 486                                    xtcyl(r=rounding, l=rounding*2, $fn=sides);
 487                                    ytcyl(r=rounding, l=rounding*2, $fn=sides);
 488                                    cyl(r=rounding, h=rounding*2, $fn=sides);
 489                                }
 490                            }
 491                        }
 492                    }
 493                } else if (rounding<0) {
 494                    checks = assert(edges == EDGES_ALL || edges[2] == [0,0,0,0], "Cannot use negative rounding with Z aligned edges.");
 495                    ard = abs(rounding);
 496                    cube(size, center=true);
 497
 498                    // External-Rounding mask edges
 499                    difference() {
 500                        union() {
 501                            for (i = [0:3], axis=[0:1]) {
 502                                if (edges[axis][i]>0) {
 503                                    vec = EDGE_OFFSETS[axis][i];
 504                                    translate(v_mul(vec/2, size+[ard,ard,-ard]-[0.01,0.01,0])) {
 505                                        rotate(majrots[axis]) {
 506                                            cube([ard, ard, size[axis]], center=true);
 507                                        }
 508                                    }
 509                                }
 510                            }
 511
 512                            // Add multi-edge corners.
 513                            if (trimcorners) {
 514                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
 515                                    ce = _corner_edges(edges, [xa,ya,za]);
 516                                    if (ce.x + ce.y > 1) {
 517                                        translate(v_mul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
 518                                            cube([ard+0.01,ard+0.01,ard], center=true);
 519                                        }
 520                                    }
 521                                }
 522                            }
 523                        }
 524
 525                        // Remove roundings from overhangs.
 526                        for (i = [0:3], axis=[0:1]) {
 527                            if (edges[axis][i]>0) {
 528                                vec = EDGE_OFFSETS[axis][i];
 529                                translate(v_mul(vec/2, size+[2*ard,2*ard,-2*ard])) {
 530                                    rotate(majrots[axis]) {
 531                                        cyl(l=size[axis]+2.1*ard, r=ard);
 532                                    }
 533                                }
 534                            }
 535                        }
 536                    }
 537                } else {
 538                    hull() {
 539                        corner_shape([-1,-1,-1]);
 540                        corner_shape([ 1,-1,-1]);
 541                        corner_shape([-1, 1,-1]);
 542                        corner_shape([ 1, 1,-1]);
 543                        corner_shape([-1,-1, 1]);
 544                        corner_shape([ 1,-1, 1]);
 545                        corner_shape([-1, 1, 1]);
 546                        corner_shape([ 1, 1, 1]);
 547                    }
 548                }
 549            } else {
 550                cube(size=size, center=true);
 551            }
 552            children();
 553        }
 554    }
 555}
 556
 557
 558function cuboid(
 559    size=[1,1,1],
 560    p1, p2,
 561    chamfer,
 562    rounding,
 563    edges=EDGES_ALL,
 564    except_edges=[],
 565    trimcorners=true,
 566    anchor=CENTER,
 567    spin=0,
 568    orient=UP
 569) = no_function("cuboid");
 570
 571
 572
 573// Function&Module: prismoid()
 574// Synopsis: Creates a rectangular prismoid shape with optional roundovers and chamfering.
 575// SynTags: Geom, VNF
 576// Topics: Shapes (3D), Attachable, VNF Generators
 577// See Also: cuboid(), rounded_prism(), trapezoid()
 578// Usage: 
 579//   prismoid(size1, size2, [h|l|height|length], [shift], [xang=], [yang=], ...) [ATTACHMENTS];
 580// Usage: Chamfered and/or Rounded Prismoids
 581//   prismoid(size1, size2, h|l|height|length, [chamfer=], [rounding=]...) [ATTACHMENTS];
 582//   prismoid(size1, size2, h|l|height|length, [chamfer1=], [chamfer2=], [rounding1=], [rounding2=], ...) [ATTACHMENTS];
 583// Usage: As Function
 584//   vnf = prismoid(...);
 585// Description:
 586//   Creates a rectangular prismoid shape with optional roundovers and chamfering.
 587//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
 588//   specify rounding and/or chamferring per-edge, and for top and bottom separately.
 589//   If you want to round the bottom or top edges see {{rounded_prism()}}.
 590//   .
 591//   Specification of the prismoid is similar to specification for {{trapezoid()}}.  You can specify the dimensions of the
 592//   bottom and top and its height to get a symmetric prismoid.  You can use the shift argument to shift the top face around.
 593//   You can also specify base angles either in the X direction, Y direction or both.  In order to avoid overspecification,
 594//   you may need to specify a parameter such as size2 as a list of two values, one of which is undef.  For example,
 595//   specifying `size2=[100,undef]` sets the size in the X direction but allows the size in the Y direction to be computed based on yang.
 596// Arguments:
 597//   size1 = [width, length] of the bottom end of the prism.
 598//   size2 = [width, length] of the top end of the prism.
 599//   h/l/height/length = Height of the prism.
 600//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
 601//   ---
 602//   xang = base angle in the X direction.  Can be a scalar or list of two values, one of which may be undef
 603//   yang = base angle in the Y direction.  Can be a scalar or list of two values, one of which may be undef
 604//   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)
 605//   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-].
 606//   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-].
 607//   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)
 608//   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-].
 609//   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-].
 610//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `BOTTOM`
 611//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 612//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 613//
 614// Example: Truncated Pyramid
 615//   prismoid(size1=[35,50], size2=[20,30], h=20);
 616// Example: Rectangular Pyramid
 617//   prismoid([40,40], [0,0], h=20);
 618// Example: Prism
 619//   prismoid(size1=[40,40], size2=[0,40], h=20);
 620// Example: Wedge
 621//   prismoid(size1=[60,35], size2=[30,0], h=30);
 622// Example: Truncated Tetrahedron
 623//   prismoid(size1=[10,40], size2=[40,10], h=40);
 624// Example: Inverted Truncated Pyramid
 625//   prismoid(size1=[15,5], size2=[30,20], h=20);
 626// Example: Right Prism
 627//   prismoid(size1=[30,60], size2=[0,60], shift=[-15,0], h=30);
 628// Example(FlatSpin,VPD=160,VPT=[0,0,10]): Shifting/Skewing
 629//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]);
 630// Example: Specifying bottom, height and angle
 631//   prismoid(size1=[100,75], h=30, xang=50, yang=70);
 632// Example: Specifying top, height and angle, with asymmetric angles
 633//   prismoid(size2=[100,75], h=30, xang=[50,60], yang=[70,40]);
 634// Example: Specifying top, bottom and angle for X and using that to define height.  Note that giving yang here would likely give a conflicting height calculation, which is not allowed.  
 635//   prismoid(size1=[100,75], size2=[75,35], xang=50);
 636// Example: The same as the previous example but we give a shift in Y.  Note that shift.x must be undef because you cannot give combine an angle with a shift, so a shift.x value would conflict with xang being defined.  
 637//   prismoid(size1=[100,75], size2=[75,35], xang=50, shift=[undef,20]);
 638// Example:  The X dimensions defined by the base length, angle and height; the Y dimensions defined by the top length, angle, and height. 
 639//   prismoid(size1=[100,undef], size2=[undef,75], h=30, xang=[20,90], yang=30);
 640// Example: Rounding
 641//   prismoid(100, 80, rounding=10, h=30);
 642// Example: Chamfers
 643//   prismoid(100, 80, chamfer=5, h=30);
 644// Example: Gradiant Rounding
 645//   prismoid(100, 80, rounding1=10, rounding2=0, h=30);
 646// Example: Per Corner Rounding
 647//   prismoid(100, 80, rounding=[0,5,10,15], h=30);
 648// Example: Per Corner Chamfer
 649//   prismoid(100, 80, chamfer=[0,5,10,15], h=30);
 650// Example: Mixing Chamfer and Rounding
 651//   prismoid(
 652//       100, 80, h=30,
 653//       chamfer=[0,5,0,10],
 654//       rounding=[5,0,10,0]
 655//   );
 656// Example: Really Mixing It Up
 657//   prismoid(
 658//       size1=[100,80], size2=[80,60], h=20,
 659//       chamfer1=[0,5,0,10], chamfer2=[5,0,10,0],
 660//       rounding1=[5,0,10,0], rounding2=[0,5,0,10]
 661//   );
 662// Example(Spin,VPD=160,VPT=[0,0,10]): Standard Connectors
 663//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5])
 664//       show_anchors();
 665
 666module prismoid(
 667    size1=undef, size2=undef, h, shift=[undef,undef],
 668    xang, yang,
 669    rounding=0, rounding1, rounding2,
 670    chamfer=0, chamfer1, chamfer2,
 671    l, height, length, center,
 672    anchor, spin=0, orient=UP
 673)
 674{
 675    vnf_s1_s2_shift = prismoid(
 676        size1=size1, size2=size2, h=h, shift=shift,
 677        xang=xang, yang=yang, 
 678        rounding=rounding, chamfer=chamfer, 
 679        rounding1=rounding1, rounding2=rounding2,
 680        chamfer1=chamfer1, chamfer2=chamfer2,
 681        l=l, height=height, length=length, anchor=BOT, _return_dim=true
 682    );
 683    anchor = get_anchor(anchor, center, BOT, BOT);
 684    attachable(anchor,spin,orient, size=vnf_s1_s2_shift[1], size2=vnf_s1_s2_shift[2], shift=vnf_s1_s2_shift[3]) {
 685        down(vnf_s1_s2_shift[1].z/2)
 686            vnf_polyhedron(vnf_s1_s2_shift[0], convexity=4);
 687        children();
 688    }
 689}
 690
 691function prismoid(
 692    size1, size2, h, shift=[0,0],
 693    rounding=0, rounding1, rounding2,
 694    chamfer=0, chamfer1, chamfer2,
 695    l, height, length, center,
 696    anchor=DOWN, spin=0, orient=UP, xang, yang,
 697    _return_dim=false
 698    
 699) =
 700    assert(is_undef(shift) || is_num(shift) || len(shift)==2, "shift must be a number or list of length 2")
 701    assert(is_undef(size1) || is_num(size1) || len(size1)==2, "size1 must be a number or list of length 2")
 702    assert(is_undef(size2) || is_num(size2) || len(size2)==2, "size2 must be a number or list of length 2")  
 703    let(
 704        xang = force_list(xang,2),
 705        yang = force_list(yang,2),
 706        yangOK = len(yang)==2 && (yang==[undef,undef] || (all_positive(yang) && yang[0]<180 && yang[1]<180)),
 707        xangOK = len(xang)==2 && (xang==[undef,undef] || (all_positive(xang) && xang[0]<180 && xang[1]<180)),
 708        size1=force_list(size1,2),
 709        size2=force_list(size2,2),
 710        h=first_defined([l,h,length,height]),
 711        shift = force_list(shift,2)
 712    )
 713    assert(xangOK, "prismoid angles must be scalar or 2-vector, strictly between 0 and 180")
 714    assert(yangOK, "prismoid angles must be scalar or 2-vector, strictly between 0 and 180")
 715    assert(xang==[undef,undef] || shift.x==undef, "Cannot specify xang and a shift.x value together")
 716    assert(yang==[undef,undef] || shift.y==undef, "Cannot specify yang and a shift.y value together")
 717    assert(all_positive([h]) || is_undef(h), "h must be a positive value")
 718    let(
 719        hx = _trapezoid_dims(h,size1.x,size2.x,shift.x,xang)[0],
 720        hy = _trapezoid_dims(h,size1.y,size2.y,shift.y,yang)[0]
 721    )
 722    assert(num_defined([hx,hy])>0, "Height not given and specification does not determine prismoid height")
 723    assert(hx==undef || hy==undef || approx(hx,hy),
 724           str("X and Y angle specifications give rise to conflicting height values ",hx," and ",hy))
 725    let(
 726        h = first_defined([hx,hy]),
 727        x_h_w1_w2_shift = _trapezoid_dims(h,size1.x,size2.x,shift.x,xang),
 728        y_h_w1_w2_shift = _trapezoid_dims(h,size1.y,size2.y,shift.y,yang)
 729    )
 730    let(
 731        s1 = [x_h_w1_w2_shift[1], y_h_w1_w2_shift[1]],
 732        s2 = [x_h_w1_w2_shift[2], y_h_w1_w2_shift[2]],
 733        shift = [x_h_w1_w2_shift[3], y_h_w1_w2_shift[3]]
 734    )
 735    assert(is_vector(s1,2), "Insufficient information to define prismoid")
 736    assert(is_vector(s2,2), "Insufficient information to define prismoid")
 737    assert(all_nonnegative(concat(s1,s2)),"Degenerate prismoid geometry")
 738    assert(s1.x+s2.x>0 && s1.y+s2.y>0, "Degenerate prismoid geometry")
 739    assert(is_num(rounding) || is_vector(rounding,4), "rounding must be a number or 4-vector")
 740    assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "rounding1 must be a number or 4-vector")
 741    assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "rounding2 must be a number or 4-vector")
 742    assert(is_num(chamfer) || is_vector(chamfer,4), "chamfer must be a number or 4-vector")
 743    assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "chamfer1 must be a number or 4-vector")
 744    assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "chamfer2 must be a number or 4-vector")
 745    let(
 746        chamfer1=force_list(default(chamfer1,chamfer),4),
 747        chamfer2=force_list(default(chamfer2,chamfer),4),
 748        rounding1=force_list(default(rounding1,rounding),4),
 749        rounding2=force_list(default(rounding2,rounding),4)
 750    )
 751    assert(all_nonnegative(chamfer1), "chamfer/chamfer1 must be non-negative")
 752    assert(all_nonnegative(chamfer2), "chamfer/chamfer2 must be non-negative")
 753    assert(all_nonnegative(rounding1), "rounding/rounding1 must be non-negative")
 754    assert(all_nonnegative(rounding2), "rounding/rounding2 must be non-negative")        
 755    assert(all_zero(v_mul(rounding1,chamfer1),0),
 756           "rounding1 and chamfer1 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner")
 757    assert(all_zero(v_mul(rounding2,chamfer2),0),
 758           "rounding2 and chamfer2 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner")
 759    let(
 760        rounding1 = default(rounding1, rounding),
 761        rounding2 = default(rounding2, rounding),
 762        chamfer1 = default(chamfer1, chamfer),
 763        chamfer2 = default(chamfer2, chamfer),
 764        anchor = get_anchor(anchor, center, BOT, BOT),
 765        path1 = rect(s1, rounding=rounding1, chamfer=chamfer1, anchor=CTR),
 766        path2 = rect(s2, rounding=rounding2, chamfer=chamfer2, anchor=CTR),
 767        points = [
 768                    each path3d(path1, -h/2),
 769                    each path3d(move(shift, path2), +h/2),
 770                 ],
 771        faces = hull(points),
 772        vnf = [points, faces]
 773    )
 774    _return_dim ? [reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf),point3d(s1,h),s2,shift]
 775                : reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf);
 776
 777
 778// Function&Module: octahedron()
 779// Synopsis: Creates an octahedron with axis-aligned points.
 780// SynTags: Geom, VNF
 781// Topics: Shapes (3D), Attachable, VNF Generators
 782// See Also: prismoid()
 783// Usage: As Module
 784//   octahedron(size, ...) [ATTACHMENTS];
 785// Usage: As Function
 786//   vnf = octahedron(size, ...);
 787// Description:
 788//   When called as a module, creates an octahedron with axis-aligned points.
 789//   When called as a function, creates a [VNF](vnf.scad) of an octahedron with axis-aligned points.
 790// Arguments:
 791//   size = Width of the octahedron, tip to tip.
 792//   ---
 793//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 794//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 795//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 796// Example:
 797//   octahedron(size=40);
 798// Example: Anchors
 799//   octahedron(size=40) show_anchors();
 800
 801module octahedron(size=1, anchor=CENTER, spin=0, orient=UP) {
 802    vnf = octahedron(size=size);
 803    attachable(anchor,spin,orient, vnf=vnf, extent=true) {
 804        vnf_polyhedron(vnf, convexity=2);
 805        children();
 806    }
 807}
 808
 809function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
 810    let(
 811        size = scalar_vec3(size),
 812        s = size/2,
 813        vnf = [
 814            [ [0,0,s.z], [s.x,0,0], [0,s.y,0], [-s.x,0,0], [0,-s.y,0], [0,0,-s.z] ],
 815            [ [0,2,1], [0,3,2], [0,4,3], [0,1,4], [5,1,2], [5,2,3], [5,3,4], [5,4,1] ]
 816        ]
 817    ) reorient(anchor,spin,orient, vnf=vnf, extent=true, p=vnf);
 818
 819
 820// Module: rect_tube()
 821// Synopsis: Creates a rectangular tube.
 822// SynTags: Geom
 823// Topics: Shapes (3D), Attachable, VNF Generators
 824// See Also: tube()
 825// Usage: Typical Rectangular Tubes
 826//   rect_tube(h, size, isize, [center], [shift]);
 827//   rect_tube(h, size, wall=, [center=]);
 828//   rect_tube(h, isize=, wall=, [center=]);
 829// Usage: Tapering Rectangular Tubes
 830//   rect_tube(h, size1=, size2=, wall=, ...);
 831//   rect_tube(h, isize1=, isize2=, wall=, ...);
 832//   rect_tube(h, size1=, size2=, isize1=, isize2=, ...);
 833// Usage: Chamfered
 834//   rect_tube(h, size, isize, chamfer=, ...);
 835//   rect_tube(h, size, isize, chamfer1=, chamfer2= ...);
 836//   rect_tube(h, size, isize, ichamfer=, ...);
 837//   rect_tube(h, size, isize, ichamfer1=, ichamfer2= ...);
 838//   rect_tube(h, size, isize, chamfer=, ichamfer=, ...);
 839// Usage: Rounded
 840//   rect_tube(h, size, isize, rounding=, ...);
 841//   rect_tube(h, size, isize, rounding1=, rounding2= ...);
 842//   rect_tube(h, size, isize, irounding=, ...);
 843//   rect_tube(h, size, isize, irounding1=, irounding2= ...);
 844//   rect_tube(h, size, isize, rounding=, irounding=, ...);
 845// Usage: Attaching Children
 846//   rect_tube(...) ATTACHMENTS;
 847//
 848// Description:
 849//   Creates a rectangular or prismoid tube with optional roundovers and/or chamfers.
 850//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
 851//   specify rounding and/or chamferring per-edge, and for top and bottom, inside and
 852//   outside  separately.
 853//   .
 854//   By default if you specify a chamfer or rounding then it applies as specified to the
 855//   outside, and an inside rounding is calculated that will maintain constant width
 856//   if your wall thickness is uniform.  If the wall thickness is not uniform, the default
 857//   inside rounding is calculated based on the smaller of the two wall thicknesses.
 858//   Note that the values of the more specific chamfers and roundings inherit from the
 859//   more general ones, so `rounding2` is determined from `rounding`.  The constant
 860//   width default will apply when the inner rounding and chamfer are both undef.
 861//   You can give an inner chamfer or rounding as a list with undef entries if you want to specify
 862//   some corner roundings and allow others to be computed.  
 863// Arguments:
 864//   h/l/height/length = The height or length of the rectangular tube.  Default: 1
 865//   size = The outer [X,Y] size of the rectangular tube.
 866//   isize = The inner [X,Y] size of the rectangular tube.
 867//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
 868//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
 869//   ---
 870//   wall = The thickness of the rectangular tube wall.
 871//   size1 = The [X,Y] size of the outside of the bottom of the rectangular tube.
 872//   size2 = The [X,Y] size of the outside of the top of the rectangular tube.
 873//   isize1 = The [X,Y] size of the inside of the bottom of the rectangular tube.
 874//   isize2 = The [X,Y] size of the inside of the top of the rectangular tube.
 875//   rounding = The roundover radius for the outside edges of the rectangular tube.
 876//   rounding1 = The roundover radius for the outside bottom corner of the rectangular tube.
 877//   rounding2 = The roundover radius for the outside top corner of the rectangular tube.
 878//   chamfer = The chamfer size for the outside edges of the rectangular tube.
 879//   chamfer1 = The chamfer size for the outside bottom corner of the rectangular tube.
 880//   chamfer2 = The chamfer size for the outside top corner of the rectangular tube.
 881//   irounding = The roundover radius for the inside edges of the rectangular tube. Default: Computed for uniform wall thickness (see above)
 882//   irounding1 = The roundover radius for the inside bottom corner of the rectangular tube.
 883//   irounding2 = The roundover radius for the inside top corner of the rectangular tube.
 884//   ichamfer = The chamfer size for the inside edges of the rectangular tube.  Default: Computed for uniform wall thickness (see above)
 885//   ichamfer1 = The chamfer size for the inside bottom corner of the rectangular tube.
 886//   ichamfer2 = The chamfer size for the inside top corner of the rectangular tube.
 887//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `BOTTOM`
 888//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 889//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 890// Examples:
 891//   rect_tube(size=50, wall=5, h=30);
 892//   rect_tube(size=[100,60], wall=5, h=30);
 893//   rect_tube(isize=[60,80], wall=5, h=30);
 894//   rect_tube(size=[100,60], isize=[90,50], h=30);
 895//   rect_tube(size1=[100,60], size2=[70,40], wall=5, h=30);
 896// Example:
 897//   rect_tube(
 898//       size1=[100,60], size2=[70,40],
 899//       isize1=[40,20], isize2=[65,35], h=15
 900//   );
 901// Example: With rounding
 902//   rect_tube(size=100, wall=5, rounding=10, h=30);
 903// Example: With rounding
 904//   rect_tube(size=100, wall=5, chamfer=10, h=30);
 905// Example: Outer Rounding Only
 906//   rect_tube(size=100, wall=5, rounding=10, irounding=0, h=30);
 907// Example: Outer Chamfer Only
 908//   rect_tube(size=100, wall=5, chamfer=5, ichamfer=0, h=30);
 909// Example: Outer Rounding, Inner Chamfer
 910//   rect_tube(size=100, wall=5, rounding=10, ichamfer=8, h=30);
 911// Example: Inner Rounding, Outer Chamfer
 912//   rect_tube(size=100, wall=5, chamfer=10, irounding=8, h=30);
 913// Example: Gradiant Rounding
 914//   rect_tube(
 915//       size1=100, size2=80, wall=5, h=30,
 916//       rounding1=10, rounding2=0,
 917//       irounding1=8, irounding2=0
 918//   );
 919// Example: Per Corner Rounding
 920//   rect_tube(
 921//       size=100, wall=10, h=30,
 922//       rounding=[0,5,10,15], irounding=0
 923//   );
 924// Example: Per Corner Chamfer
 925//   rect_tube(
 926//       size=100, wall=10, h=30,
 927//       chamfer=[0,5,10,15], ichamfer=0
 928//   );
 929// Example: Mixing Chamfer and Rounding
 930//   rect_tube(
 931//       size=100, wall=10, h=30,
 932//       chamfer=[0,10,0,20], 
 933//       rounding=[10,0,20,0]
 934//   );
 935// Example: Really Mixing It Up
 936//   rect_tube(
 937//       size1=[100,80], size2=[80,60],
 938//       isize1=[50,30], isize2=[70,50], h=20,
 939//       chamfer1=[0,5,0,10], ichamfer1=[0,3,0,8],
 940//       chamfer2=[5,0,10,0], ichamfer2=[3,0,8,0],
 941//       rounding1=[5,0,10,0], irounding1=[3,0,8,0],
 942//       rounding2=[0,5,0,10], irounding2=[0,3,0,8]
 943//   );
 944// Example: Some interiors chamfered, others with default rounding
 945//   rect_tube(
 946//       size=100, wall=10, h=30,
 947//       rounding=[0,10,20,30], ichamfer=[8,8,undef,undef]
 948//   );
 949
 950
 951
 952function _rect_tube_rounding(factor,ir,r,alternative,size,isize) =
 953    let(wall = min(size-isize)/2*factor)
 954    [for(i=[0:3])
 955      is_def(ir[i]) ? ir[i]
 956    : is_undef(alternative[i]) ? max(0,r[i]-wall)
 957    : 0
 958    ];
 959    
 960module rect_tube(
 961    h, size, isize, center, shift=[0,0],
 962    wall, size1, size2, isize1, isize2,
 963    rounding=0, rounding1, rounding2,
 964    irounding=undef, irounding1=undef, irounding2=undef,
 965    chamfer=0, chamfer1, chamfer2,
 966    ichamfer=undef, ichamfer1=undef, ichamfer2=undef,
 967    anchor, spin=0, orient=UP,
 968    l, length, height
 969) {
 970    h = one_defined([h,l,length,height],"h,l,length,height");
 971    checks =
 972        assert(is_num(h), "l or h argument required.")
 973        assert(is_vector(shift,2));
 974    s1 = is_num(size1)? [size1, size1] :
 975        is_vector(size1,2)? size1 :
 976        is_num(size)? [size, size] :
 977        is_vector(size,2)? size :
 978        undef;
 979    s2 = is_num(size2)? [size2, size2] :
 980        is_vector(size2,2)? size2 :
 981        is_num(size)? [size, size] :
 982        is_vector(size,2)? size :
 983        undef;
 984    is1 = is_num(isize1)? [isize1, isize1] :
 985        is_vector(isize1,2)? isize1 :
 986        is_num(isize)? [isize, isize] :
 987        is_vector(isize,2)? isize :
 988        undef;
 989    is2 = is_num(isize2)? [isize2, isize2] :
 990        is_vector(isize2,2)? isize2 :
 991        is_num(isize)? [isize, isize] :
 992        is_vector(isize,2)? isize :
 993        undef;
 994    size1 = is_def(s1)? s1 :
 995        (is_def(wall) && is_def(is1))? (is1+2*[wall,wall]) :
 996        undef;
 997    size2 = is_def(s2)? s2 :
 998        (is_def(wall) && is_def(is2))? (is2+2*[wall,wall]) :
 999        undef;
1000    isize1 = is_def(is1)? is1 :
1001        (is_def(wall) && is_def(s1))? (s1-2*[wall,wall]) :
1002        undef;
1003    isize2 = is_def(is2)? is2 :
1004        (is_def(wall) && is_def(s2))? (s2-2*[wall,wall]) :
1005        undef;
1006    checks2 =
1007        assert(wall==undef || is_num(wall))
1008        assert(size1!=undef, "Bad size/size1 argument.")
1009        assert(size2!=undef, "Bad size/size2 argument.")
1010        assert(isize1!=undef, "Bad isize/isize1 argument.")
1011        assert(isize2!=undef, "Bad isize/isize2 argument.")
1012        assert(isize1.x < size1.x, "Inner size is larger than outer size.")
1013        assert(isize1.y < size1.y, "Inner size is larger than outer size.")
1014        assert(isize2.x < size2.x, "Inner size is larger than outer size.")
1015        assert(isize2.y < size2.y, "Inner size is larger than outer size.")
1016        assert(is_num(rounding) || is_vector(rounding,4), "rounding must be a number or 4-vector")
1017        assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "rounding1 must be a number or 4-vector")
1018        assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "rounding2 must be a number or 4-vector")
1019        assert(is_num(chamfer) || is_vector(chamfer,4), "chamfer must be a number or 4-vector")
1020        assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "chamfer1 must be a number or 4-vector")
1021        assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "chamfer2 must be a number or 4-vector")
1022        assert(is_undef(irounding) || is_num(irounding) || (is_list(irounding) && len(irounding)==4), "irounding must be a number or 4-vector")
1023        assert(is_undef(irounding1) || is_num(irounding1) || (is_list(irounding1) && len(irounding1)==4), "irounding1 must be a number or 4-vector")
1024        assert(is_undef(irounding2) || is_num(irounding2) || (is_list(irounding2) && len(irounding2)==4), "irounding2 must be a number or 4-vector")      
1025        assert(is_undef(ichamfer) || is_num(ichamfer) || (is_list(ichamfer) && len(ichamfer)==4), "ichamfer must be a number or 4-vector")
1026        assert(is_undef(ichamfer1) || is_num(ichamfer1) || (is_list(ichamfer1) && len(ichamfer1)==4), "ichamfer1 must be a number or 4-vector")
1027        assert(is_undef(ichamfer2) || is_num(ichamfer2) || (is_list(ichamfer2) && len(ichamfer2)==4), "ichamfer2 must be a number or 4-vector");
1028    chamfer1=force_list(default(chamfer1,chamfer),4);
1029    chamfer2=force_list(default(chamfer2,chamfer),4);
1030    rounding1=force_list(default(rounding1,rounding),4);
1031    rounding2=force_list(default(rounding2,rounding),4);
1032    checks3 =
1033        assert(all_nonnegative(chamfer1), "chamfer/chamfer1 must be non-negative")
1034        assert(all_nonnegative(chamfer2), "chamfer/chamfer2 must be non-negative")
1035        assert(all_nonnegative(rounding1), "rounding/rounding1 must be non-negative")
1036        assert(all_nonnegative(rounding2), "rounding/rounding2 must be non-negative")        
1037        assert(all_zero(v_mul(rounding1,chamfer1),0), "rounding1 and chamfer1 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner")
1038        assert(all_zero(v_mul(rounding2,chamfer2),0), "rounding2 and chamfer2 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner");
1039    irounding1_temp = force_list(default(irounding1,irounding),4);
1040    irounding2_temp = force_list(default(irounding2,irounding),4);    
1041    ichamfer1_temp = force_list(default(ichamfer1,ichamfer),4);
1042    ichamfer2_temp = force_list(default(ichamfer2,ichamfer),4);
1043    checksignr1 = [for(entry=irounding1_temp) if (is_def(entry) && entry<0) 1]==[];
1044    checksignr2 = [for(entry=irounding2_temp) if (is_def(entry) && entry<0) 1]==[];    
1045    checksignc1 = [for(entry=ichamfer1_temp) if (is_def(entry) && entry<0) 1]==[];
1046    checksignc2 = [for(entry=ichamfer2_temp) if (is_def(entry) && entry<0) 1]==[];
1047    checkconflict1 = [for(i=[0:3]) if (is_def(irounding1_temp[i]) && is_def(ichamfer1_temp[i]) && irounding1_temp[i]!=0 && ichamfer1_temp[i]!=0) 1]==[];
1048    checkconflict2 = [for(i=[0:3]) if (is_def(irounding2_temp[i]) && is_def(ichamfer2_temp[i]) && irounding2_temp[i]!=0 && ichamfer2_temp[i]!=0) 1]==[];
1049    checks4 =
1050        assert(checksignr1, "irounding/irounding1 must be non-negative")
1051        assert(checksignr2, "irounding/irounding2 must be non-negative")
1052        assert(checksignc1, "ichamfer/ichamfer1 must be non-negative")
1053        assert(checksignc2, "ichamfer/ichamfer2 must be non-negative")
1054        assert(checkconflict1, "irounding1 and ichamfer1 (possibly inherited from irounding and ichamfer) cannot both be nonzero at the swame corner")
1055        assert(checkconflict2, "irounding2 and ichamfer2 (possibly inherited from irounding and ichamfer) cannot both be nonzero at the swame corner");
1056    irounding1 = _rect_tube_rounding(1,irounding1_temp, rounding1, ichamfer1_temp, size1, isize1);
1057    irounding2 = _rect_tube_rounding(1,irounding2_temp, rounding2, ichamfer2_temp, size2, isize2);
1058    ichamfer1 = _rect_tube_rounding(1/sqrt(2),ichamfer1_temp, chamfer1, irounding1_temp, size1, isize1);
1059    ichamfer2 = _rect_tube_rounding(1/sqrt(2),ichamfer2_temp, chamfer2, irounding2_temp, size2, isize2);
1060    anchor = get_anchor(anchor, center, BOT, BOT);
1061    attachable(anchor,spin,orient, size=[each size1, h], size2=size2, shift=shift) {
1062        down(h/2) {
1063            difference() {
1064                prismoid(
1065                    size1, size2, h=h, shift=shift,
1066                    rounding1=rounding1, rounding2=rounding2,
1067                    chamfer1=chamfer1, chamfer2=chamfer2,
1068                    anchor=BOT
1069                );
1070                down(0.01) prismoid(
1071                    isize1, isize2, h=h+0.02, shift=shift,
1072                    rounding1=irounding1, rounding2=irounding2,
1073                    chamfer1=ichamfer1, chamfer2=ichamfer2,
1074                    anchor=BOT
1075                );
1076            }
1077        }
1078        children();
1079    }
1080}
1081
1082function rect_tube(
1083    h, size, isize, center, shift=[0,0],
1084    wall, size1, size2, isize1, isize2,
1085    rounding=0, rounding1, rounding2,
1086    irounding, irounding1, irounding2,
1087    chamfer=0, chamfer1, chamfer2,
1088    ichamfer, ichamfer1, ichamfer2,
1089    anchor, spin=0, orient=UP,
1090    l, length, height
1091) = no_function("rect_tube");
1092
1093
1094// Function&Module: wedge()
1095// Synopsis: Creates a 3d triangular wedge.
1096// SynTags: Geom, VNF
1097// Topics: Shapes (3D), Attachable, VNF Generators
1098// See also: prismoid(), rounded_prism(), pie_slice()
1099// Usage: As Module
1100//   wedge(size, [center], ...) [ATTACHMENTS];
1101// Usage: As Function
1102//   vnf = wedge(size, [center], ...);
1103//
1104// Description:
1105//   When called as a module, creates a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
1106//   When called as a function, creates a VNF for a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
1107//
1108// Arguments:
1109//   size = [width, thickness, height]
1110//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
1111//   ---
1112//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `FRONT+LEFT+BOTTOM`
1113//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1114//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1115//
1116// Extra Anchors:
1117//   hypot = Center of angled wedge face, perpendicular to that face.
1118//   hypot_left = Left side of angled wedge face, bisecting the angle between the left side and angled faces.
1119//   hypot_right = Right side of angled wedge face, bisecting the angle between the right side and angled faces.
1120//
1121// Example: Centered
1122//   wedge([20, 40, 15], center=true);
1123// Example: *Non*-Centered
1124//   wedge([20, 40, 15]);
1125// Example: Standard Anchors
1126//   wedge([40, 80, 30], center=true)
1127//       show_anchors(custom=false);
1128//   color([0.5,0.5,0.5,0.1])
1129//       cube([40, 80, 30], center=true);
1130// Example: Named Anchors
1131//   wedge([40, 80, 30], center=true)
1132//       show_anchors(std=false);
1133
1134module wedge(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
1135{
1136    size = scalar_vec3(size);
1137    anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
1138    vnf = wedge(size, anchor="origin");
1139    anchors = [
1140        named_anchor("hypot", CTR, unit([0,size.z,size.y],UP)),
1141        named_anchor("hypot_left", [-size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+LEFT)),
1142        named_anchor("hypot_right", [size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+RIGHT)),
1143    ];
1144    attachable(anchor,spin,orient, size=size, anchors=anchors) {
1145        if (size.z > 0) {
1146            vnf_polyhedron(vnf);
1147        }
1148        children();
1149    }
1150}
1151
1152
1153function wedge(size=[1,1,1], center, anchor, spin=0, orient=UP) =
1154    let(
1155        size = scalar_vec3(size),
1156        anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]),
1157        pts = [
1158            [ 1,1,-1], [ 1,-1,-1], [ 1,-1,1],
1159            [-1,1,-1], [-1,-1,-1], [-1,-1,1],
1160        ],
1161        faces = [
1162            [0,1,2], [3,5,4], [0,3,1], [1,3,4],
1163            [1,4,2], [2,4,5], [2,5,3], [0,2,3],
1164        ],
1165        vnf = [scale(size/2,p=pts), faces],
1166        anchors = [
1167            named_anchor("hypot", CTR, unit([0,size.z,size.y],UP)),
1168            named_anchor("hypot_left", [-size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+LEFT)),
1169            named_anchor("hypot_right", [size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+RIGHT)),
1170        ]
1171    )
1172    reorient(anchor,spin,orient, size=size, anchors=anchors, p=vnf);
1173
1174
1175// Section: Cylinders
1176
1177
1178// Function&Module: cylinder()
1179// Synopsis: Creates an attachable cylinder.
1180// SynTags: Geom, VNF
1181// Topics: Shapes (3D), Attachable, VNF Generators
1182// See Also: cyl()
1183// Usage: As Module (as in native OpenSCAD)
1184//   cylinder(h, r=/d=, [center=]);
1185//   cylinder(h, r1/d1=, r2/d2=, [center=]);
1186// Usage: With BOSL2 anchoring and attachment extensions
1187//   cylinder(h, r=/d=, [center=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
1188//   cylinder(h, r1/d1=, r2/d2=, [center=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
1189// Usage: As Function (BOSL2 extension)
1190//   vnf = cylinder(h, r=/d=, ...);
1191//   vnf = cylinder(h, r1/d1=, r2/d2=, ...);
1192// Description:
1193//   Creates a 3D cylinder or conic object.
1194//   This modules extends the built-in `cylinder()` module by adding support for attachment and by adding a function version.   
1195//   When called as a function, returns a [VNF](vnf.scad) for a cylinder.  
1196// Arguments:
1197//   h = The height of the cylinder.
1198//   r1 = The bottom radius of the cylinder.  (Before orientation.)
1199//   r2 = The top radius of the cylinder.  (Before orientation.)
1200//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=BOTTOM`.  Default: false
1201//   ---
1202//   d1 = The bottom diameter of the cylinder.  (Before orientation.)
1203//   d2 = The top diameter of the cylinder.  (Before orientation.)
1204//   r = The radius of the cylinder.
1205//   d = The diameter of the cylinder.
1206//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1207//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1208//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1209// Example: By Radius
1210//   xdistribute(30) {
1211//       cylinder(h=40, r=10);
1212//       cylinder(h=40, r1=10, r2=5);
1213//   }
1214// Example: By Diameter
1215//   xdistribute(30) {
1216//       cylinder(h=40, d=25);
1217//       cylinder(h=40, d1=25, d2=10);
1218//   }
1219// Example(Med): Anchoring
1220//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT);
1221// Example(Med): Spin
1222//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45);
1223// Example(Med): Orient
1224//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45, orient=FWD);
1225// Example(Big): Standard Connectors
1226//   xdistribute(40) {
1227//       cylinder(h=30, d=25) show_anchors();
1228//       cylinder(h=30, d1=25, d2=10) show_anchors();
1229//   }
1230
1231module cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP)
1232{
1233    anchor = get_anchor(anchor, center, BOTTOM, BOTTOM);
1234    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1235    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1236    h = default(h,1);
1237    attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
1238        _cylinder(h=h, r1=r1, r2=r2, center=true);
1239        children();
1240    }
1241}
1242
1243function cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP) =
1244    let(
1245        anchor = get_anchor(anchor, center, BOTTOM, BOTTOM),
1246        r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
1247        r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
1248        l = default(h,1),
1249        sides = segs(max(r1,r2)),
1250        verts = [
1251            for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r1*cos(a),r1*sin(a),-l/2],
1252            for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r2*cos(a),r2*sin(a), l/2],
1253        ],
1254        faces = [
1255            [for (i=[0:1:sides-1]) sides-1-i],
1256            for (i=[0:1:sides-1]) [i, ((i+1)%sides)+sides, i+sides],
1257            for (i=[0:1:sides-1]) [i, (i+1)%sides, ((i+1)%sides)+sides],
1258            [for (i=[0:1:sides-1]) sides+i]
1259        ]
1260    ) [reorient(anchor,spin,orient, l=l, r1=r1, r2=r2, p=verts), faces];
1261
1262
1263
1264// Function&Module: cyl()
1265// Synopsis: Creates an attachable cylinder with roundovers and chamfering.
1266// SynTags: Geom, VNF
1267// Topics: Cylinders, Textures, Rounding, Chamfers
1268// See Also: texture(), rotate_sweep(), cylinder()
1269// Usage: Normal Cylinders
1270//   cyl(l|h|length|height, r, [center], [circum=], [realign=]) [ATTACHMENTS];
1271//   cyl(l|h|length|height, d=, ...) [ATTACHMENTS];
1272//   cyl(l|h|length|height, r1=, r2=, ...) [ATTACHMENTS];
1273//   cyl(l|h|length|height, d1=, d2=, ...) [ATTACHMENTS];
1274//
1275// Usage: Chamferred Cylinders
1276//   cyl(l|h|length|height, r|d, chamfer=, [chamfang=], [from_end=], ...);
1277//   cyl(l|h|length|height, r|d, chamfer1=, [chamfang1=], [from_end=], ...);
1278//   cyl(l|h|length|height, r|d, chamfer2=, [chamfang2=], [from_end=], ...);
1279//   cyl(l|h|length|height, r|d, chamfer1=, chamfer2=, [chamfang1=], [chamfang2=], [from_end=], ...);
1280//
1281// Usage: Rounded End Cylinders
1282//   cyl(l|h|length|height, r|d, rounding=, ...);
1283//   cyl(l|h|length|height, r|d, rounding1=, ...);
1284//   cyl(l|h|length|height, r|d, rounding2=, ...);
1285//   cyl(l|h|length|height, r|d, rounding1=, rounding2=, ...);
1286//
1287// Usage: Textured Cylinders
1288//   cyl(l|h|length|height, r|d, texture=, [tex_size=]|[tex_counts=], [tex_scale=], [tex_rot=], [tex_samples=], [tex_style=], [tex_taper=], [tex_inset=], ...);
1289//   cyl(l|h|length|height, r1=, r2=, texture=, [tex_size=]|[tex_counts=], [tex_scale=], [tex_rot=], [tex_samples=], [tex_style=], [tex_taper=], [tex_inset=], ...);
1290//   cyl(l|h|length|height, d1=, d2=, texture=, [tex_size=]|[tex_counts=], [tex_scale=], [tex_rot=], [tex_samples=], [tex_style=], [tex_taper=], [tex_inset=], ...);
1291//
1292// Usage: Caled as a function to get a VNF
1293//   vnf = cyl(...);
1294//
1295// Description:
1296//   Creates cylinders in various anchorings and orientations, with optional rounding, chamfers, or textures.
1297//   You can use `h` and `l` interchangably, and all variants allow specifying size by either `r`|`d`,
1298//   or `r1`|`d1` and `r2`|`d2`.  Note: the chamfers and rounding cannot be cumulatively longer than
1299//   the cylinder or cone's sloped side.  The more specific parameters like chamfer1 or rounding2 override the more
1300//   general ones like chamfer or rounding, so if you specify `rounding=3, chamfer2=3` you will get a chamfer at the top and
1301//   rounding at the bottom.
1302// Figure(2D,Big,NoAxes,VPR = [0, 0, 0], VPT = [0,0,0], VPD = 82): Chamfers on cones can be tricky.  This figure shows chamfers of the same size and same angle, A=30 degrees.  Note that the angle is measured on the inside, and produces a quite different looking chamfer at the top and bottom of the cone.  Straight black arrows mark the size of the chamfers, which may not even appear the same size visually.  When you do not give an angle, the triangle that is cut off will be isoceles, like the triangle at the top, with two equal angles.
1303//  color("lightgray")
1304//  projection()
1305//      cyl(r2=10, r1=20, l=20,chamfang=30, chamfer=0,orient=BACK);
1306//  projection()
1307//      cyl(r2=10, r1=20, l=20,chamfang=30, chamfer=8,orient=BACK);
1308//  color("black"){
1309//      fwd(9.6)right(20-4.8)text("A",size=1.3);
1310//      fwd(-8.4)right(10-4.9)text("A",size=1.3);
1311//      right(20-8)fwd(10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1312//      right(10-8)fwd(-10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1313//      stroke(arc(cp=[2,10], angle=[0,-30], n=20, r=5), width=.18, endcaps="arrow2");
1314//      stroke(arc(cp=[12,-10], angle=[0,30], n=20, r=5), width=.18, endcaps="arrow2");
1315//  }
1316// Figure(2D,Big,NoAxes,VPR = [0, 0, 0], VPT = [0,0,0], VPD = 82): The cone in this example is narrow but has the same slope.  With negative chamfers, the angle A=30 degrees is on the outside.  The chamfers are again quite different looking.  As before, the default will feature two congruent angles, and in this case it happens at the bottom of the cone but not the top.  The straight arrows again show the size of the chamfer.
1317//  r1=10-7.5;r2=20-7.5;
1318//  color("lightgray")
1319//  projection()
1320//      cyl(r2=r1, r1=r2, l=20,chamfang=30, chamfer=-8,orient=BACK);
1321//  projection()
1322//      cyl(r2=r1, r1=r2, l=20,chamfang=30, chamfer=0,orient=BACK);
1323//  color("black"){
1324//      fwd(9.7)right(r2+3.8)text("A",size=1.3);
1325//      fwd(-8.5)right(r1+3.7)text("A",size=1.3);
1326//      right(r2)fwd(10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1327//      right(r1)fwd(-10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1328//      stroke(arc(cp=[r1+8,10], angle=[180,180+30], n=20, r=5), width=.18, endcaps="arrow2");
1329//      stroke(arc(cp=[r2+8,-10], angle=[180-30,180], n=20, r=5), width=.18, endcaps="arrow2");
1330//  }
1331// Arguments:
1332//   l / h / length / height = Length of cylinder along oriented axis.  Default: 1
1333//   r = Radius of cylinder.  Default: 1
1334//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
1335//   ---
1336//   r1 = Radius of the negative (X-, Y-, Z-) end of cylinder.
1337//   r2 = Radius of the positive (X+, Y+, Z+) end of cylinder.
1338//   d = Diameter of cylinder.
1339//   d1 = Diameter of the negative (X-, Y-, Z-) end of cylinder.
1340//   d2 = Diameter of the positive (X+, Y+, Z+) end of cylinder.
1341//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1342//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
1343//   chamfer = The size of the chamfers on the ends of the cylinder.  (Also see: `from_end=`)  Default: none.
1344//   chamfer1 = The size of the chamfer on the bottom end of the cylinder.  (Also see: `from_end1=`)  Default: none.
1345//   chamfer2 = The size of the chamfer on the top end of the cylinder.  (Also see: `from_end2=`)  Default: none.
1346//   chamfang = The angle in degrees of the chamfers away from the ends of the cylinder.  Default: Chamfer angle is halfway between the endcap and cone face.
1347//   chamfang1 = The angle in degrees of the bottom chamfer away from the bottom end of the cylinder.  Default: Chamfer angle is halfway between the endcap and cone face.
1348//   chamfang2 = The angle in degrees of the top chamfer away from the top end of the cylinder.  Default: Chamfer angle is halfway between the endcap and cone face.
1349//   from_end = If true, chamfer is measured along the conic face from the ends of the cylinder, instead of inset from the edge.  Default: `false`.
1350//   from_end1 = If true, chamfer on the bottom end of the cylinder is measured along the conic face from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1351//   from_end2 = If true, chamfer on the top end of the cylinder is measured along the conic face from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1352//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1353//   rounding1 = The radius of the rounding on the bottom end of the cylinder.
1354//   rounding2 = The radius of the rounding on the top end of the cylinder.
1355//   realign = If true, rotate the cylinder by half the angle of one face.
1356//   teardrop = If given as a number, rounding around the bottom edge of the cylinder won't exceed this many degrees from vertical.  If true, the limit angle is 45 degrees.  Default: `false`
1357//   texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to vertical surfaces.  See {{texture()}} for what named textures are supported.
1358//   tex_size = An optional 2D target size for the textures.  Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]`
1359//   tex_counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height.
1360//   tex_inset = If numeric, lowers the texture into the surface by that amount, before the tex_scale multiplier is applied.  If `true`, insets by exactly `1`.  Default: `false`
1361//   tex_rot = If true, rotates the texture 90º.
1362//   tex_scale = Scaling multiplier for the texture depth.
1363//   tex_samples = Minimum number of "bend points" to have in VNF texture tiles.  Default: 8
1364//   tex_style = {{vnf_vertex_array()}} style used to triangulate heightfield textures.  Default: "min_edge"
1365//   tex_taper = If given as a number, tapers the texture height to zero over the first and last given percentage of the path.  If given as a lookup table with indices between 0 and 100, uses the percentage lookup table to ramp the texture heights.  Default: `undef` (no taper)
1366//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1367//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1368//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1369//
1370//
1371// Example: By Radius
1372//   xdistribute(30) {
1373//       cyl(l=40, r=10);
1374//       cyl(l=40, r1=10, r2=5);
1375//   }
1376//
1377// Example: By Diameter
1378//   xdistribute(30) {
1379//       cyl(l=40, d=25);
1380//       cyl(l=40, d1=25, d2=10);
1381//   }
1382//
1383// Example: Chamferring
1384//   xdistribute(60) {
1385//       // Shown Left to right.
1386//       cyl(l=40, d=40, chamfer=7);  // Default chamfang=45
1387//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=false);
1388//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=true);
1389//   }
1390//
1391// Example: Rounding
1392//   cyl(l=40, d=40, rounding=10);
1393//
1394// Example(VPD=175;VPR=[90,0,0]): Teardrop Bottom Rounding
1395//   cyl(l=40, d=40, rounding=10, teardrop=true);
1396//
1397// Example: Heterogenous Chamfers and Rounding
1398//   ydistribute(80) {
1399//       // Shown Front to Back.
1400//       cyl(l=40, d=40, rounding1=15, orient=UP);
1401//       cyl(l=40, d=40, chamfer2=5, orient=UP);
1402//       cyl(l=40, d=40, chamfer1=12, rounding2=10, orient=UP);
1403//   }
1404//
1405// Example: Putting it all together
1406//   cyl(
1407//       l=20, d1=25, d2=15,
1408//       chamfer1=5, chamfang1=60,
1409//       from_end=true, rounding2=5
1410//   );
1411//
1412// Example: External Chamfers
1413//   cyl(l=50, r=30, chamfer=-5, chamfang=30, $fa=1, $fs=1);
1414//
1415// Example: External Roundings
1416//   cyl(l=50, r=30, rounding1=-5, rounding2=5, $fa=1, $fs=1);
1417//
1418// Example(Med): Standard Connectors
1419//   xdistribute(40) {
1420//       cyl(l=30, d=25) show_anchors();
1421//       cyl(l=30, d1=25, d2=10) show_anchors();
1422//   }
1423//
1424// Example: Texturing with heightfield diamonds
1425//   cyl(h=40, r=20, texture="diamonds", tex_size=[5,5]);
1426//
1427// Example: Texturing with heightfield pyramids
1428//   cyl(h=40, r1=20, r2=15,
1429//       texture="pyramids", tex_size=[5,5],
1430//       tex_style="convex");
1431//
1432// Example: Texturing with heightfield truncated pyramids
1433//   cyl(h=40, r1=20, r2=15, chamfer=5,
1434//       texture="trunc_pyramids",
1435//       tex_size=[5,5], tex_style="convex");
1436//
1437// Example: Texturing with VNF tile "dots"
1438//   cyl(h=40, r1=20, r2=15, rounding=9,
1439//       texture="dots", tex_size=[5,5],
1440//       tex_samples=6);
1441//
1442// Example: Texturing with VNF tile "bricks_vnf"
1443//   cyl(h=50, r1=25, r2=20, shift=[0,10], rounding1=-10,
1444//       texture="bricks_vnf", tex_size=[10,10],
1445//       tex_scale=0.5, tex_style="concave");
1446//
1447// Example: No Texture Taper
1448//   cyl(d1=25, d2=20, h=30, rounding=5,
1449//       texture="trunc_ribs", tex_size=[5,1]);
1450//
1451// Example: Taper Texure at Extreme Ends
1452//   cyl(d1=25, d2=20, h=30, rounding=5,
1453//       texture="trunc_ribs", tex_taper=0,
1454//       tex_size=[5,1]);
1455//
1456// Example: Taper Texture over First and Last 10%
1457//   cyl(d1=25, d2=20, h=30, rounding=5,
1458//       texture="trunc_ribs", tex_taper=10,
1459//       tex_size=[5,1]);
1460//
1461// Example: Making a Clay Pattern Roller
1462//   tex = [
1463//       [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1464//       [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1465//       [1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,],
1466//       [1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,],
1467//       [0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,],
1468//       [0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,],
1469//       [0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,],
1470//       [0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,],
1471//       [0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,],
1472//       [0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,],
1473//       [0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,],
1474//       [0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,],
1475//       [0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,],
1476//       [0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,],
1477//       [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,],
1478//       [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,],
1479//       [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1480//   ];
1481//   diff()
1482//   cyl(d=20*10/PI, h=10, chamfer=0,
1483//       texture=tex, tex_counts=[20,1], tex_scale=-1,
1484//       tex_taper=undef, tex_style="concave") {
1485//           attach([TOP,BOT]) {
1486//               cyl(d1=20*10/PI, d2=30, h=5, anchor=BOT)
1487//                   attach(TOP) {
1488//                       tag("remove") zscale(0.5) up(3) sphere(d=15);
1489//                   }
1490//           }
1491//   }
1492
1493function cyl(
1494    h, r, center,
1495    l, r1, r2,
1496    d, d1, d2,
1497    length, height,
1498    chamfer, chamfer1, chamfer2,
1499    chamfang, chamfang1, chamfang2,
1500    rounding, rounding1, rounding2,
1501    circum=false, realign=false, shift=[0,0],
1502    teardrop=false,
1503    from_end, from_end1, from_end2,
1504    texture, tex_size=[5,5], tex_counts,
1505    tex_inset=false, tex_rot=false,
1506    tex_scale=1, tex_samples, length, height, 
1507    tex_taper, tex_style="min_edge", 
1508    anchor, spin=0, orient=UP
1509) =
1510    let(
1511        l = one_defined([l, h, length, height],"l,h,length,height",dflt=1),
1512        _r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
1513        _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
1514        sides = segs(max(_r1,_r2)),
1515        sc = circum? 1/cos(180/sides) : 1,
1516        r1 = _r1 * sc,
1517        r2 = _r2 * sc,
1518        phi = atan2(l, r2-r1),
1519        anchor = get_anchor(anchor,center,BOT,CENTER)
1520    )
1521    assert(is_finite(l), "l/h/length/height must be a finite number.")
1522    assert(is_finite(r1), "r/r1/d/d1 must be a finite number.")
1523    assert(is_finite(r2), "r2 or d2 must be a finite number.")
1524    assert(is_vector(shift,2), "shift must be a 2D vector.")
1525    let(
1526        vnf = !any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, texture])
1527          ? cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides)
1528          : let(
1529                vang = atan2(r1-r2,l),
1530                _chamf1 = first_defined([chamfer1, if (is_undef(rounding1)) chamfer, 0]),
1531                _chamf2 = first_defined([chamfer2, if (is_undef(rounding2)) chamfer, 0]),
1532                _fromend1 = first_defined([from_end1, from_end, false]),
1533                _fromend2 = first_defined([from_end2, from_end, false]),
1534                chang1 = first_defined([chamfang1, chamfang, 45+sign(_chamf1)*vang/2]),
1535                chang2 = first_defined([chamfang2, chamfang, 45-sign(_chamf2)*vang/2]),
1536                round1 = first_defined([rounding1, if (is_undef(chamfer1)) rounding, 0]),
1537                round2 = first_defined([rounding2, if (is_undef(chamfer2)) rounding, 0]),
1538                checks1 =
1539                    assert(is_finite(_chamf1), "chamfer1 must be a finite number if given.")
1540                    assert(is_finite(_chamf2), "chamfer2 must be a finite number if given.")
1541                    assert(is_finite(chang1) && chang1>0, "chamfang1 must be a positive number if given.")
1542                    assert(is_finite(chang2) && chang2>0, "chamfang2 must be a positive number if given.")
1543                    assert(chang1<90+sign(_chamf1)*vang, "chamfang1 must be smaller than the cone face angle")
1544                    assert(chang2<90-sign(_chamf2)*vang, "chamfang2 must be smaller than the cone face angle")
1545                    assert(num_defined([chamfer1,rounding1])<2, "cannot define both chamfer1 and rounding1")
1546                    assert(num_defined([chamfer2,rounding2])<2, "cannot define both chamfer2 and rounding2")
1547                    assert(num_defined([chamfer,rounding])<2, "cannot define both chamfer and rounding")                                
1548                    undef,
1549                chamf1r = !_chamf1? 0
1550                        : !_fromend1? _chamf1
1551                        : law_of_sines(a=_chamf1, A=chang1, B=180-chang1-(90-sign(_chamf2)*vang)),
1552                chamf2r = !_chamf2? 0
1553                        : !_fromend2? _chamf2
1554                        : law_of_sines(a=_chamf2, A=chang2, B=180-chang2-(90+sign(_chamf2)*vang)),
1555                chamf1l = !_chamf1? 0
1556                        : _fromend1? abs(_chamf1)
1557                        : abs(law_of_sines(a=_chamf1, A=180-chang1-(90-sign(_chamf1)*vang), B=chang1)),
1558                chamf2l = !_chamf2? 0
1559                        : _fromend2? abs(_chamf2)
1560                        : abs(law_of_sines(a=_chamf2, A=180-chang2-(90+sign(_chamf2)*vang), B=chang2)),
1561                facelen = adj_ang_to_hyp(l, abs(vang)),
1562
1563                cp1 = [r1,-l/2],
1564                cp2 = [r2,+l/2],
1565                roundlen1 = round1 >= 0 ? round1/tan(45-vang/2)
1566                                        : round1/tan(45+vang/2),
1567                roundlen2 = round2 >=0 ? round2/tan(45+vang/2)
1568                                       : round2/tan(45-vang/2),
1569                dy1 = abs(_chamf1 ? chamf1l : round1 ? roundlen1 : 0), 
1570                dy2 = abs(_chamf2 ? chamf2l : round2 ? roundlen2 : 0),
1571
1572                td_ang = teardrop == true? 45 :
1573                    teardrop == false? 90 :
1574                    assert(is_finite(teardrop))
1575                    assert(teardrop>=0 && teardrop<=90)
1576                    teardrop,
1577
1578                checks2 =
1579                    assert(is_finite(round1), "rounding1 must be a number if given.")
1580                    assert(is_finite(round2), "rounding2 must be a number if given.")
1581                    assert(chamf1r <= r1, "chamfer1 is larger than the r1 radius of the cylinder.")
1582                    assert(chamf2r <= r2, "chamfer2 is larger than the r2 radius of the cylinder.")
1583                    assert(roundlen1 <= r1, "size of rounding1 is larger than the r1 radius of the cylinder.")
1584                    assert(roundlen2 <= r2, "size of rounding2 is larger than the r2 radius of the cylinder.")
1585                    assert(dy1+dy2 <= facelen, "Chamfers/roundings don't fit on the cylinder/cone.  They exceed the length of the cylinder/cone face.")
1586                    undef,
1587                path = [
1588                    if (texture==undef) [0,-l/2],
1589
1590                    if (!approx(chamf1r,0))
1591                        each [
1592                            [r1, -l/2] + polar_to_xy(chamf1r,180),
1593                            [r1, -l/2] + polar_to_xy(chamf1l,90+vang),
1594                        ]
1595                    else if (!approx(round1,0) && td_ang < 90)
1596                        each _teardrop_corner(r=round1, corner=[[max(0,r1-2*roundlen1),-l/2],[r1,-l/2],[r2,l/2]], ang=td_ang)
1597                    else if (!approx(round1,0) && td_ang >= 90)
1598                        each arc(r=abs(round1), corner=[[max(0,r1-2*roundlen1),-l/2],[r1,-l/2],[r2,l/2]])
1599                    else [r1,-l/2],
1600
1601                    if (is_finite(chamf2r) && !approx(chamf2r,0))
1602                        each [
1603                            [r2, l/2] + polar_to_xy(chamf2l,270+vang),
1604                            [r2, l/2] + polar_to_xy(chamf2r,180),
1605                        ]
1606                    else if (is_finite(round2) && !approx(round2,0))
1607                        each arc(r=abs(round2), corner=[[r1,-l/2],[r2,l/2],[max(0,r2-2*roundlen2),l/2]])
1608                    else [r2,l/2],
1609
1610                    if (texture==undef) [0,l/2],
1611                ]
1612            ) rotate_sweep(path,
1613                texture=texture, tex_counts=tex_counts, tex_size=tex_size,
1614                tex_inset=tex_inset, tex_rot=tex_rot,
1615                tex_scale=tex_scale, tex_samples=tex_samples,
1616                tex_taper=tex_taper, style=tex_style, closed=false
1617            ),
1618        skmat = down(l/2) *
1619            skew(sxz=shift.x/l, syz=shift.y/l) *
1620            up(l/2) *
1621            zrot(realign? 180/sides : 0),
1622        ovnf = apply(skmat, vnf)
1623    )
1624    reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift, p=ovnf);
1625
1626
1627function _teardrop_corner(r, corner, ang=45) =
1628    let(
1629        check = assert(len(corner)==3)
1630            assert(is_finite(r))
1631            assert(is_finite(ang)),
1632        cp = circle_2tangents(abs(r), corner)[0],
1633        path1 = arc(r=abs(r), corner=corner),
1634        path2 = [
1635            for (p = select(path1,0,-2))
1636                if (abs(modang(v_theta(p-cp)-90)) <= 180-ang) p,
1637            last(path1)
1638        ],
1639        path = [
1640            line_intersection([corner[0],corner[1]],[path2[0],path2[0]+polar_to_xy(1,-90-ang*sign(r))]),
1641            each path2
1642        ]
1643    ) path;
1644
1645
1646module cyl(
1647    h, r, center,
1648    l, r1, r2,
1649    d, d1, d2,
1650    chamfer, chamfer1, chamfer2,
1651    chamfang, chamfang1, chamfang2,
1652    rounding, rounding1, rounding2,
1653    circum=false, realign=false, shift=[0,0],
1654    teardrop=false,
1655    from_end, from_end1, from_end2,
1656    texture, tex_size=[5,5], tex_counts,
1657    tex_inset=false, tex_rot=false,
1658    tex_scale=1, tex_samples, length, height, 
1659    tex_taper, tex_style="min_edge",
1660    anchor, spin=0, orient=UP
1661) {
1662    l = one_defined([l, h, length, height],"l,h,length,height",dflt=1);
1663    _r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1664    _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1665    sides = segs(max(_r1,_r2));
1666    sc = circum? 1/cos(180/sides) : 1;
1667    r1 = _r1 * sc;
1668    r2 = _r2 * sc;
1669    phi = atan2(l, r2-r1);
1670    anchor = get_anchor(anchor,center,BOT,CENTER);
1671    skmat = down(l/2) * skew(sxz=shift.x/l, syz=shift.y/l) * up(l/2);
1672    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift) {
1673        multmatrix(skmat)
1674        zrot(realign? 180/sides : 0) {
1675            if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, texture])) {
1676                cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides);
1677            } else {
1678                vnf = cyl(
1679                    l=l, r1=r1, r2=r2, center=true, $fn=sides,
1680                    chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1681                    chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1682                    rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1683                    from_end=from_end, from_end1=from_end1, from_end2=from_end2,
1684                    teardrop=teardrop,
1685                    texture=texture, tex_size=tex_size,
1686                    tex_counts=tex_counts, tex_scale=tex_scale,
1687                    tex_inset=tex_inset, tex_rot=tex_rot,
1688                    tex_style=tex_style, tex_taper=tex_taper,
1689                    tex_samples=tex_samples
1690                );
1691                vnf_polyhedron(vnf, convexity=texture!=undef? 2 : 10);
1692            }
1693        }
1694        children();
1695    }
1696}
1697
1698
1699
1700// Module: xcyl()
1701// Synopsis: creates a cylinder oriented along the X axis.
1702// SynTags: Geom
1703// Topics: Cylinders, Textures, Rounding, Chamfers
1704// See Also: texture(), rotate_sweep(), cyl()
1705// Description:
1706//   Creates an attachable cylinder with roundovers and chamfering oriented along the X axis.
1707//
1708// Usage: Typical
1709//   xcyl(l|h|length|height, r|d=, [anchor=], ...) [ATTACHMENTS];
1710//   xcyl(l|h|length|height, r1=|d1=, r2=|d2=, [anchor=], ...) [ATTACHMENTS];
1711//
1712// Arguments:
1713//   l / h / length / height = Length of cylinder along oriented axis. Default: 1
1714//   r = Radius of cylinder.  Default: 1
1715//   ---
1716//   r1 = Optional radius of left (X-) end of cylinder.
1717//   r2 = Optional radius of right (X+) end of cylinder.
1718//   d = Optional diameter of cylinder. (use instead of `r`)
1719//   d1 = Optional diameter of left (X-) end of cylinder.
1720//   d2 = Optional diameter of right (X+) end of cylinder.
1721//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1722//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
1723//   chamfer1 = The size of the chamfer on the left end of the cylinder.  Default: none.
1724//   chamfer2 = The size of the chamfer on the right end of the cylinder.  Default: none.
1725//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1726//   chamfang1 = The angle in degrees of the chamfer on the left end of the cylinder.
1727//   chamfang2 = The angle in degrees of the chamfer on the right end of the cylinder.
1728//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1729//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1730//   rounding1 = The radius of the rounding on the left end of the cylinder.
1731//   rounding2 = The radius of the rounding on the right end of the cylinder.
1732//   realign = If true, rotate the cylinder by half the angle of one face.
1733//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1734//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1735//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1736//
1737// Example: By Radius
1738//   ydistribute(50) {
1739//       xcyl(l=35, r=10);
1740//       xcyl(l=35, r1=15, r2=5);
1741//   }
1742//
1743// Example: By Diameter
1744//   ydistribute(50) {
1745//       xcyl(l=35, d=20);
1746//       xcyl(l=35, d1=30, d2=10);
1747//   }
1748
1749function xcyl(
1750    h, r, d, r1, r2, d1, d2, l, 
1751    chamfer, chamfer1, chamfer2,
1752    chamfang, chamfang1, chamfang2,
1753    rounding, rounding1, rounding2,
1754    circum=false, realign=false, from_end=false, length, height,
1755    anchor=CENTER, spin=0, orient=UP
1756) = no_function("xcyl");
1757
1758module xcyl(
1759    h, r, d, r1, r2, d1, d2, l, 
1760    chamfer, chamfer1, chamfer2,
1761    chamfang, chamfang1, chamfang2,
1762    rounding, rounding1, rounding2,
1763    circum=false, realign=false, from_end=false, length, height,
1764    anchor=CENTER, spin=0, orient=UP
1765) {
1766    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1767    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1768    l = one_defined([l,h,length,height],"l,h,length,height",1);
1769    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=RIGHT) {
1770        cyl(
1771            l=l, r1=r1, r2=r2,
1772            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1773            chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1774            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1775            circum=circum, realign=realign, from_end=from_end,
1776            anchor=CENTER, orient=RIGHT
1777        );
1778        children();
1779    }
1780}
1781
1782
1783// Module: ycyl()
1784// Synopsis: Creates a cylinder oriented along the y axis.
1785// SynTags: Geom
1786// Topics: Cylinders, Textures, Rounding, Chamfers
1787// See Also: texture(), rotate_sweep(), cyl()
1788// Description:
1789//   Creates an attachable cylinder with roundovers and chamfering oriented along the y axis.
1790//
1791// Usage: Typical
1792//   ycyl(l|h|length|height, r|d=, [anchor=], ...) [ATTACHMENTS];
1793//   ycyl(l|h|length|height, r1=|d1=, r2=|d2=, [anchor=], ...) [ATTACHMENTS];
1794//
1795// Arguments:
1796//   l / h / length / height = Length of cylinder along oriented axis. (Default: `1.0`)
1797//   r = Radius of cylinder.
1798//   ---
1799//   r1 = Radius of front (Y-) end of cone.
1800//   r2 = Radius of back (Y+) end of one.
1801//   d = Diameter of cylinder.
1802//   d1 = Diameter of front (Y-) end of one.
1803//   d2 = Diameter of back (Y+) end of one.
1804//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1805//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
1806//   chamfer1 = The size of the chamfer on the front end of the cylinder.  Default: none.
1807//   chamfer2 = The size of the chamfer on the back end of the cylinder.  Default: none.
1808//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1809//   chamfang1 = The angle in degrees of the chamfer on the front end of the cylinder.
1810//   chamfang2 = The angle in degrees of the chamfer on the back end of the cylinder.
1811//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1812//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1813//   rounding1 = The radius of the rounding on the front end of the cylinder.
1814//   rounding2 = The radius of the rounding on the back end of the cylinder.
1815//   realign = If true, rotate the cylinder by half the angle of one face.
1816//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1817//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1818//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1819//
1820// Example: By Radius
1821//   xdistribute(50) {
1822//       ycyl(l=35, r=10);
1823//       ycyl(l=35, r1=15, r2=5);
1824//   }
1825//
1826// Example: By Diameter
1827//   xdistribute(50) {
1828//       ycyl(l=35, d=20);
1829//       ycyl(l=35, d1=30, d2=10);
1830//   }
1831
1832function ycyl(
1833    h, r, d, r1, r2, d1, d2, l,
1834    chamfer, chamfer1, chamfer2,
1835    chamfang, chamfang1, chamfang2,
1836    rounding, rounding1, rounding2,
1837    circum=false, realign=false, from_end=false,height,length,
1838    anchor=CENTER, spin=0, orient=UP
1839) = no_function("ycyl");
1840
1841
1842module ycyl(
1843    h, r, d, r1, r2, d1, d2, l,
1844    chamfer, chamfer1, chamfer2,
1845    chamfang, chamfang1, chamfang2,
1846    rounding, rounding1, rounding2,
1847    circum=false, realign=false, from_end=false,height,length,
1848    anchor=CENTER, spin=0, orient=UP
1849) {
1850    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1851    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1852    l = one_defined([l,h,length,height],"l,h,length,height",1);
1853    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK) {
1854        cyl(
1855            l=l, r1=r1, r2=r2,
1856            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1857            chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1858            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1859            circum=circum, realign=realign, from_end=from_end,
1860            anchor=CENTER, orient=BACK
1861        );
1862        children();
1863    }
1864}
1865
1866
1867
1868// Module: zcyl()
1869// Synopsis: Creates a cylinder oriented along the Z axis.
1870// SynTags: Geom
1871// Topics: Cylinders, Textures, Rounding, Chamfers
1872// See Also: texture(), rotate_sweep(), cyl()
1873// Description:
1874//   Creates an attachable cylinder with roundovers and chamfering oriented along the Z axis.
1875//
1876// Usage: Typical
1877//   zcyl(l|h|length|height, r|d=, [anchor=],...) [ATTACHMENTS];
1878//   zcyl(l|h|length|height, r1=|d1=, r2=|d2=, [anchor=],...);
1879//
1880// Arguments:
1881//   l / h / length / height = Length of cylinder along oriented axis. (Default: 1.0)
1882//   r = Radius of cylinder.
1883//   ---
1884//   r1 = Radius of front (Y-) end of cone.
1885//   r2 = Radius of back (Y+) end of one.
1886//   d = Diameter of cylinder.
1887//   d1 = Diameter of front (Y-) end of one.
1888//   d2 = Diameter of back (Y+) end of one.
1889//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1890//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
1891//   chamfer1 = The size of the chamfer on the bottom end of the cylinder.  Default: none.
1892//   chamfer2 = The size of the chamfer on the top end of the cylinder.  Default: none.
1893//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1894//   chamfang1 = The angle in degrees of the chamfer on the bottom end of the cylinder.
1895//   chamfang2 = The angle in degrees of the chamfer on the top end of the cylinder.
1896//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1897//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1898//   rounding1 = The radius of the rounding on the bottom end of the cylinder.
1899//   rounding2 = The radius of the rounding on the top end of the cylinder.
1900//   realign = If true, rotate the cylinder by half the angle of one face.
1901//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1902//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1903//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1904//
1905// Example: By Radius
1906//   xdistribute(50) {
1907//       zcyl(l=35, r=10);
1908//       zcyl(l=35, r1=15, r2=5);
1909//   }
1910//
1911// Example: By Diameter
1912//   xdistribute(50) {
1913//       zcyl(l=35, d=20);
1914//       zcyl(l=35, d1=30, d2=10);
1915//   }
1916
1917function zcyl(
1918    h, r, d, r1, r2, d1, d2, l,
1919    chamfer, chamfer1, chamfer2,
1920    chamfang, chamfang1, chamfang2,
1921    rounding, rounding1, rounding2,
1922    circum=false, realign=false, from_end=false, length, height,
1923    anchor=CENTER, spin=0, orient=UP
1924) = no_function("zcyl");
1925
1926module zcyl(
1927    h, r, d, r1, r2, d1, d2, l,
1928    chamfer, chamfer1, chamfer2,
1929    chamfang, chamfang1, chamfang2,
1930    rounding, rounding1, rounding2,
1931    circum=false, realign=false, from_end=false, length, height,
1932    anchor=CENTER, spin=0, orient=UP
1933) {
1934    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1935    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1936    l = one_defined([l,h,length,height],"l,h,length,height",1);
1937    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
1938        cyl(
1939            l=l, r1=r1, r2=r2,
1940            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1941            chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1942            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1943            circum=circum, realign=realign, from_end=from_end,
1944            anchor=CENTER
1945        );
1946        children();
1947    }
1948}
1949
1950
1951
1952// Module: tube()
1953// Synopsis: Creates a cylindrical or conical tube.
1954// SynTags: Geom
1955// Topics: Shapes (3D), Attachable, VNF Generators
1956// See Also: rect_tube()
1957// Description:
1958//   Makes a hollow tube that can be cylindrical or conical by specifying inner and outer dimensions or by giving one dimension and
1959//   wall thickness. 
1960// Usage: Basic cylindrical tube, specifying inner and outer radius or diameter
1961//   tube(h|l, or, ir, [center], [realign=], [anchor=], [spin=],[orient=]) [ATTACHMENTS];
1962//   tube(h|l, od=, id=, ...)  [ATTACHMENTS];
1963// Usage: Specify wall thickness
1964//   tube(h|l, or|od=|ir=|id=, wall=, ...) [ATTACHMENTS];
1965// Usage: Conical tubes
1966//   tube(h|l, ir1=|id1=, ir2=|id2=, or1=|od1=, or2=|od2=, ...) [ATTACHMENTS];
1967//   tube(h|l, or1=|od1=, or2=|od2=, wall=, ...) [ATTACHMENTS];
1968// Arguments:
1969//   h / l = height of tube. Default: 1
1970//   or = Outer radius of tube. Default: 1
1971//   ir = Inner radius of tube.
1972//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
1973//   ---
1974//   od = Outer diameter of tube.
1975//   id = Inner diameter of tube.
1976//   wall = horizontal thickness of tube wall. Default 1
1977//   or1 = Outer radius of bottom of tube.  Default: value of r)
1978//   or2 = Outer radius of top of tube.  Default: value of r)
1979//   od1 = Outer diameter of bottom of tube.
1980//   od2 = Outer diameter of top of tube.
1981//   ir1 = Inner radius of bottom of tube.
1982//   ir2 = Inner radius of top of tube.
1983//   id1 = Inner diameter of bottom of tube.
1984//   id2 = Inner diameter of top of tube.
1985//   realign = If true, rotate the tube by half the angle of one face.
1986//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1987//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1988//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1989//
1990// Example: These all Produce the Same Tube
1991//   tube(h=30, or=40, wall=5);
1992//   tube(h=30, ir=35, wall=5);
1993//   tube(h=30, or=40, ir=35);
1994//   tube(h=30, od=80, id=70);
1995// Example: These all Produce the Same Conical Tube
1996//   tube(h=30, or1=40, or2=25, wall=5);
1997//   tube(h=30, ir1=35, or2=20, wall=5);
1998//   tube(h=30, or1=40, or2=25, ir1=35, ir2=20);
1999// Example: Circular Wedge
2000//   tube(h=30, or1=40, or2=30, ir1=20, ir2=30);
2001// Example: Standard Connectors
2002//   tube(h=30, or=40, wall=5) show_anchors();
2003
2004function tube(
2005     h, or, ir, center,
2006    od, id, wall,
2007    or1, or2, od1, od2,
2008    ir1, ir2, id1, id2,
2009    realign=false, l, length, height,
2010    anchor, spin=0, orient=UP
2011) = no_function("tube");
2012
2013module tube(
2014    h, or, ir, center,
2015    od, id, wall,
2016    or1, or2, od1, od2,
2017    ir1, ir2, id1, id2,
2018    realign=false, l, length, height,
2019    anchor, spin=0, orient=UP
2020) {
2021    h = one_defined([h,l,height,length],"h,l,height,length",dflt=1);
2022    orr1 = get_radius(r1=or1, r=or, d1=od1, d=od, dflt=undef);
2023    orr2 = get_radius(r1=or2, r=or, d1=od2, d=od, dflt=undef);
2024    irr1 = get_radius(r1=ir1, r=ir, d1=id1, d=id, dflt=undef);
2025    irr2 = get_radius(r1=ir2, r=ir, d1=id2, d=id, dflt=undef);
2026    wall = default(wall, 1);
2027    r1 = default(orr1, u_add(irr1,wall));
2028    r2 = default(orr2, u_add(irr2,wall));
2029    ir1 = default(irr1, u_sub(orr1,wall));
2030    ir2 = default(irr2, u_sub(orr2,wall));
2031    checks =
2032        assert(all_defined([r1, r2, ir1, ir2]), "Must specify two of inner radius/diam, outer radius/diam, and wall width.")
2033        assert(ir1 <= r1, "Inner radius is larger than outer radius.")
2034        assert(ir2 <= r2, "Inner radius is larger than outer radius.");
2035    sides = segs(max(r1,r2));
2036    anchor = get_anchor(anchor, center, BOT, CENTER);
2037    attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
2038        zrot(realign? 180/sides : 0) {
2039            difference() {
2040                cyl(h=h, r1=r1, r2=r2, $fn=sides) children();
2041                cyl(h=h+0.05, r1=ir1, r2=ir2);
2042            }
2043        }
2044        children();
2045    }
2046}
2047
2048
2049
2050// Function&Module: pie_slice()
2051// Synopsis: Creates a pie slice shape.
2052// SynTags: Geom, VNF
2053// Topics: Shapes (3D), Attachable, VNF Generators
2054// See Also: wedge()
2055// Description:
2056//   Creates a pie slice shape.
2057//
2058// Usage: As Module
2059//   pie_slice(l|h=|height=|length=, r, ang, [center]);
2060//   pie_slice(l|h=|height=|length=, d=, ang=, ...);
2061//   pie_slice(l|h=|height=|length=, r1=|d1=, r2=|d2=, ang=, ...);
2062// Usage: As Function
2063//   vnf = pie_slice(l|h=|height=|length=, r, ang, [center]);
2064//   vnf = pie_slice(l|h=|height=|length=, d=, ang=, ...);
2065//   vnf = pie_slice(l|h=|height=|length=, r1=|d1=, r2=|d2=, ang=, ...);
2066// Usage: Attaching Children
2067//   pie_slice(l|h, r, ang, ...) ATTACHMENTS;
2068//
2069// Arguments:
2070//   h / l / height / length = height of pie slice.
2071//   r = radius of pie slice.
2072//   ang = pie slice angle in degrees.
2073//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
2074//   ---
2075//   r1 = bottom radius of pie slice.
2076//   r2 = top radius of pie slice.
2077//   d = diameter of pie slice.
2078//   d1 = bottom diameter of pie slice.
2079//   d2 = top diameter of pie slice.
2080//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2081//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2082//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2083//
2084// Example: Cylindrical Pie Slice
2085//   pie_slice(ang=45, l=20, r=30);
2086// Example: Conical Pie Slice
2087//   pie_slice(ang=60, l=20, d1=50, d2=70);
2088// Example: Big Slice
2089//   pie_slice(ang=300, l=20, d1=50, d2=70);
2090// Example: Generating a VNF
2091//   vnf = pie_slice(ang=150, l=20, r1=30, r2=50);
2092//   vnf_polyhedron(vnf);
2093
2094module pie_slice(
2095    h, r, ang=30, center,
2096    r1, r2, d, d1, d2, l, length, height,
2097    anchor, spin=0, orient=UP
2098) {
2099    l = one_defined([l, h,height,length],"l,h,height,length",dflt=1);
2100    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10);
2101    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10);
2102    maxd = max(r1,r2)+0.1;
2103    anchor = get_anchor(anchor, center, BOT, BOT);
2104    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
2105        difference() {
2106            cyl(r1=r1, r2=r2, h=l);
2107            if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
2108            difference() {
2109                fwd(maxd/2) cube([2*maxd, maxd, l+0.2], center=true);
2110                if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
2111            }
2112        }
2113        children();
2114    }
2115}
2116
2117function pie_slice(
2118    h, r, ang=30, center,
2119    r1, r2, d, d1, d2, l, length, height,
2120    anchor, spin=0, orient=UP
2121) = let(
2122        anchor = get_anchor(anchor, center, BOT, BOT),
2123        l = one_defined([l, h,height,length],"l,h,height,length",dflt=1),
2124        r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10),
2125        r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10),
2126        maxd = max(r1,r2)+0.1,
2127        sides = ceil(segs(max(r1,r2))*ang/360),
2128        step = ang/sides,
2129        vnf = vnf_vertex_array(
2130            points=[
2131                for (u = [0,1]) let(
2132                    h = lerp(-l/2,l/2,u),
2133                    r = lerp(r1,r2,u)
2134                ) [
2135                    for (theta = [0:step:ang+EPSILON])
2136                        cylindrical_to_xyz(r,theta,h),
2137                    [0,0,h]
2138                ]
2139            ],
2140            col_wrap=true, caps=true, reverse=true
2141        )
2142    ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, p=vnf);
2143
2144
2145
2146// Section: Other Round Objects
2147
2148
2149// Function&Module: sphere()
2150// Synopsis: Creates an attachable spherical object.
2151// SynTags: Geom, VNF
2152// Topics: Shapes (3D), Attachable, VNF Generators
2153// See Also: spheroid()
2154// Usage: As Module (native OpenSCAD)
2155//   sphere(r|d=);
2156// Usage: Using BOSL2 attachments extensions
2157//   sphere(r|d=, [anchor=], [spin=], [orient=]) [ATTACHMENTS];
2158// Usage: As Function (BOSL2 extension)
2159//   vnf = sphere(r|d=, [anchor=], [spin=], [orient=]) [ATTACHMENTS];
2160// Description:
2161//   Creates a sphere object.
2162//   This module extends the built-in `sphere()` module by providing support for BOSL2 anchoring and attachments, and a function form. 
2163//   When called as a function, returns a [VNF](vnf.scad) for a sphere.
2164// Arguments:
2165//   r = Radius of the sphere.
2166//   ---
2167//   d = Diameter of the sphere.
2168//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2169//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2170//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2171// Example: By Radius
2172//   sphere(r=50);
2173// Example: By Diameter
2174//   sphere(d=100);
2175// Example: Anchoring
2176//   sphere(d=100, anchor=FRONT);
2177// Example: Spin
2178//   sphere(d=100, anchor=FRONT, spin=45);
2179// Example: Orientation
2180//   sphere(d=100, anchor=FRONT, spin=45, orient=FWD);
2181// Example: Standard Connectors
2182//   sphere(d=50) show_anchors();
2183
2184module sphere(r, d, anchor=CENTER, spin=0, orient=UP) {
2185    r = get_radius(r=r, d=d, dflt=1);
2186    attachable(anchor,spin,orient, r=r) {
2187            _sphere(r=r);
2188            children();
2189    }
2190}
2191
2192function sphere(r, d, anchor=CENTER, spin=0, orient=UP) =
2193    spheroid(r=r, d=d, style="orig", anchor=anchor, spin=spin, orient=orient);
2194
2195
2196// Function&Module: spheroid()
2197// Synopsis: Creates an attachable spherical object with controllable triangulation.
2198// SynTags: Geom, VNF
2199// Topics: Shapes (3D), Attachable, VNF Generators
2200// See Also: sphere()
2201// Usage: Typical
2202//   spheroid(r|d, [circum], [style]) [ATTACHMENTS];
2203// Usage: As Function
2204//   vnf = spheroid(r|d, [circum], [style]);
2205// Description:
2206//   Creates a spheroid object, with support for anchoring and attachments.
2207//   This is a drop-in replacement for the built-in `sphere()` module.
2208//   When called as a function, returns a [VNF](vnf.scad) for a spheroid.
2209//   The exact triangulation of this spheroid can be controlled via the `style=`
2210//   argument, where the value can be one of `"orig"`, `"aligned"`, `"stagger"`,
2211//   `"octa"`, or `"icosa"`.
2212//   - `style="orig"` constructs a sphere the same way that the OpenSCAD `sphere()` built-in does.
2213//   - `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.
2214//   - `style="stagger"` forms a sphere where all faces are triangular, but the top and bottom poles have thinner triangles.
2215//   - `style="octa"` forms a sphere by subdividing an octahedron.  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.  This is used in constructing rounded corners for various other shapes.
2216//   - `style="icosa"` forms a sphere by subdividing an icosahedron.  This makes even more uniform faces over the whole sphere.  The effective `$fn` value is quantized to a multiple of 5.  This sphere has a guaranteed bounding box when `$fn` is a multiple of 10.
2217//   .
2218//   By default the object spheroid() produces is a polyhedron whose vertices all lie on the requested sphere.  This means
2219//   the approximating polyhedron is inscribed in the sphere.
2220//   The `circum` argument requests a circumscribing sphere, where the true sphere is
2221//   inside and tangent to all the faces of the approximating polyhedron.  To produce
2222//   a circumscribing polyhedron, we use the dual polyhedron of the basic form.  The dual of a polyhedron is
2223//   a new polyhedron whose vertices are obtained from the faces of the parent polyhedron.
2224//   The "orig" and "align" forms are duals of each other.  If you request a circumscribing polyhedron in
2225//   these styles then the polyhedron will look the same as the default inscribing form.  But for the other
2226//   styles, the duals are completely different from their parents, and from each other.  Generation of the circumscribed versions (duals)
2227//   for "octa" and "icosa" is fast if you use the module form but can be very slow (several minutes) if you use the functional
2228//   form and choose a large $fn value.
2229//   .
2230//   With style="align", the circumscribed sphere has its maximum radius on the X and Y axes
2231//   but is undersized on the Z axis.  With style="octa" the circumscribed sphere has faces at each axis, so
2232//   the radius on the axes is equal to the specified radius, which is the *minimum* radius of the circumscribed sphere.
2233//   The same thing is true for style="icosa" when $fn is a multiple of 10.  This would enable you to create spherical
2234//   holes with guaranteed on-axis dimensions.
2235// Arguments:
2236//   r = Radius of the spheroid.
2237//   style = The style of the spheroid's construction. One of "orig", "aligned", "stagger", "octa", or "icosa".  Default: "aligned"
2238//   ---
2239//   d = Diameter of the spheroid.
2240//   circum = If true, the approximate sphere circumscribes the true sphere of the requested size.  Otherwise inscribes.  Note that for some styles, the circumscribed sphere looks different than the inscribed sphere.  Default: false (inscribes)
2241//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2242//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2243//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2244// Example: By Radius
2245//   spheroid(r=50);
2246// Example: By Diameter
2247//   spheroid(d=100);
2248// Example: style="orig"
2249//   spheroid(d=100, style="orig", $fn=10);
2250// Example: style="aligned"
2251//   spheroid(d=100, style="aligned", $fn=10);
2252// Example: style="stagger"
2253//   spheroid(d=100, style="stagger", $fn=10);
2254// Example: style="stagger" with circum=true
2255//   spheroid(d=100, style="stagger", circum=true, $fn=10);
2256// Example: style="octa", octahedral based tesselation.  In this style, $fn is quantized to a multiple of 4.
2257//   spheroid(d=100, style="octa", $fn=10);
2258// Example: style="octa", with circum=true, produces mostly very irregular hexagonal faces
2259//   spheroid(d=100, style="octa", circum=true, $fn=16);
2260// Example: style="icosa", icosahedral based tesselation.  In this style, $fn is quantized to a multiple of 5.
2261//   spheroid(d=100, style="icosa", $fn=10);
2262// Example: style="icosa", circum=true.  This style has hexagons and 12 pentagons, similar to (but not the same as) a soccer ball.
2263//   spheroid(d=100, style="icosa", circum=true, $fn=10);
2264// Example: Anchoring
2265//   spheroid(d=100, anchor=FRONT);
2266// Example: Spin
2267//   spheroid(d=100, anchor=FRONT, spin=45);
2268// Example: Orientation
2269//   spheroid(d=100, anchor=FRONT, spin=45, orient=FWD);
2270// Example: Standard Connectors
2271//   spheroid(d=50) show_anchors();
2272// Example: Called as Function
2273//   vnf = spheroid(d=100, style="icosa");
2274//   vnf_polyhedron(vnf);
2275// Example: With "orig" the circumscribing sphere has the same form.  The green sphere is a tiny bit oversized so it pokes through the low points in the circumscribed sphere with low $fn.  This demonstrates that these spheres are in fact circumscribing.
2276//   color("green")spheroid(r=10.01, $fn=256);
2277//   spheroid(r=10, style="orig", circum=true, $fn=16);
2278// Example: With "aligned" the same is true: the circumscribing sphere is also aligned, if $fn is divisible by 4.
2279//   color("green")spheroid(r=10.01, $fn=256);
2280//   spheroid(r=10, style="aligned", circum=true, $fn=16);
2281// Example: For the other styles, the circumscribing sphere is different, as shown here with "stagger"
2282//   color("green")spheroid(r=10.01, $fn=256);
2283//   spheroid(r=10, style="stagger", circum=true, $fn=16);
2284// Example: The dual of "octa" that provides the circumscribing sphere has weird asymmetric hexagonal faces:
2285//   color("green")spheroid(r=10.01, $fn=256);
2286//   spheroid(r=10, style="octa", circum=true, $fn=16);
2287// Example: The dual of "icosa" features hexagons and always 12 pentagons:
2288//   color("green")spheroid(r=10.01, $fn=256);
2289//   spheroid(r=10, style="icosa", circum=true, $fn=16);
2290
2291module spheroid(r, style="aligned", d, circum=false, dual=false, anchor=CENTER, spin=0, orient=UP)
2292{
2293    r = get_radius(r=r, d=d, dflt=1);
2294    sides = segs(r);
2295    vsides = ceil(sides/2);
2296    attachable(anchor,spin,orient, r=r) {
2297        if (style=="orig" && !circum) {
2298            merids = [ for (i=[0:1:vsides-1]) 90-(i+0.5)*180/vsides ];
2299            path = [
2300                let(a = merids[0]) [0, sin(a)],
2301                for (a=merids) [cos(a), sin(a)],
2302                let(a = last(merids)) [0, sin(a)]
2303            ];
2304            scale(r) rotate(180) rotate_extrude(convexity=2,$fn=sides) polygon(path);
2305        }
2306        // Don't now how to construct faces for these efficiently, so use hull_points, which
2307        // is very much faster than using hull() as happens in the spheroid() function
2308        else if (circum && (style=="octa" || style=="icosa")) {
2309            orig_sphere = spheroid(r,style,circum=false);
2310            dualvert = _dual_vertices(orig_sphere);
2311            hull_points(dualvert,fast=true);
2312        } else {
2313            vnf = spheroid(r=r, circum=circum, style=style);
2314            vnf_polyhedron(vnf, convexity=2);
2315        }
2316        children();
2317    }
2318}
2319
2320
2321// p is a list of 3 points defining a triangle in any dimension.  N is the number of extra points
2322// to add, so output triangle has N+2 points on each side.
2323function _subsample_triangle(p,N) =
2324    [for(i=[0:N+1]) [for (j=[0:N+1-i]) unit(lerp(p[0],p[1],i/(N+1)) + (p[2]-p[0])*j/(N+1))]];
2325
2326
2327// Input should have only triangular faces
2328function _dual_vertices(vnf) =
2329  let(vert=vnf[0])
2330  [for(face=vnf[1])
2331      let(planes = select(vert,face))
2332      //linear_solve3(planes, [for(p=planes) p*p])
2333      linear_solve3(select(planes,0,2), [for(i=[0:2]) planes[i]*planes[i]]) // Handle larger faces, maybe?
2334  ];
2335
2336
2337function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orient=UP) =
2338    let(
2339        r = get_radius(r=r, d=d, dflt=1),
2340        hsides = segs(r),
2341        vsides = max(2,ceil(hsides/2)),
2342        octa_steps = round(max(4,hsides)/4),
2343        icosa_steps = round(max(5,hsides)/5),
2344        stagger = style=="stagger"
2345     )
2346     circum && style=="orig" ?
2347         let(
2348              orig_sphere = spheroid(r,"aligned",circum=false),
2349              dualvert = zrot(360/hsides/2,_dual_vertices(orig_sphere)),
2350              culledvert = [
2351                              [for(i=[0:2:2*hsides-1]) dualvert[i]],
2352                              for(j=[1:vsides-2])
2353                                 [for(i=[0:2:2*hsides-1]) dualvert[j*2*hsides+i]],
2354                              [for(i=[1:2:2*hsides-1]) dualvert[i]]
2355                           ],
2356              vnf = vnf_vertex_array(culledvert,col_wrap=true,caps=true)
2357          )
2358          [reorient(anchor,spin,orient, r=r, p=vnf[0]), vnf[1]]
2359     :
2360     circum && (style=="octa" || style=="icosa") ?
2361         let(
2362              orig_sphere = spheroid(r,style,circum=false),
2363              dualvert = _dual_vertices(orig_sphere),
2364              faces = hull(dualvert)
2365         )
2366         [reorient(anchor,spin,orient, r=r, p=dualvert), faces]
2367     :
2368     style=="icosa" ?    // subdivide faces of an icosahedron and project them onto a sphere
2369         let(
2370             N = icosa_steps-1,
2371             // construct an icosahedron
2372             icovert=[ for(i=[-1,1], j=[-1,1]) each [[0,i,j*PHI], [i,j*PHI,0], [j*PHI,0,i]]],
2373             icoface = hull(icovert),
2374             // Subsample face 0 of the icosahedron
2375             face0 = select(icovert,icoface[0]),
2376             sampled = r * _subsample_triangle(face0,N),
2377             dir0 = mean(face0),
2378             point0 = face0[0]-dir0,
2379             // Make a rotated copy of the subsampled triangle on each icosahedral face
2380             tri_list = [sampled,
2381                         for(i=[1:1:len(icoface)-1])
2382                 let(face = select(icovert,icoface[i]))
2383                 apply(frame_map(z=mean(face),x=face[0]-mean(face))
2384                        *frame_map(z=dir0,x=point0,reverse=true),
2385                       sampled)],
2386             // faces for the first triangle group
2387             faces = vnf_tri_array(tri_list[0],reverse=true)[1],
2388             size = repeat((N+2)*(N+3)/2,3),
2389             // Expand to full face list
2390             fullfaces = [for(i=idx(tri_list)) each [for(f=faces) f+i*size]],
2391             fullvert = flatten(flatten(tri_list))    // eliminate triangle structure
2392         )
2393         [reorient(anchor,spin,orient, r=r, p=fullvert), fullfaces]
2394     :
2395     let(
2396        verts = circum && style=="stagger" ? _dual_vertices(spheroid(r,style,circum=false))
2397              : circum && style=="aligned" ?
2398                     let(
2399                         orig_sphere = spheroid(r,"orig",circum=false),
2400                         dualvert = _dual_vertices(orig_sphere),
2401                         culledvert = zrot(360/hsides/2,
2402                                           [dualvert[0],
2403                                            for(i=[2:2:len(dualvert)-1]) dualvert[i],
2404                                            dualvert[1]])
2405                      )
2406                      culledvert
2407              : style=="orig"? [
2408                                 for (i=[0:1:vsides-1])
2409                                     let(phi = (i+0.5)*180/(vsides))
2410                                     for (j=[0:1:hsides-1])
2411                                         let(theta = j*360/hsides)
2412                                         spherical_to_xyz(r, theta, phi),
2413                               ]
2414              : style=="aligned" || style=="stagger"?
2415                         [ spherical_to_xyz(r, 0, 0),
2416                           for (i=[1:1:vsides-1])
2417                               let(phi = i*180/vsides)
2418                               for (j=[0:1:hsides-1])
2419                                   let(theta = (j+((stagger && i%2!=0)?0.5:0))*360/hsides)
2420                                   spherical_to_xyz(r, theta, phi),
2421                           spherical_to_xyz(r, 0, 180)
2422                         ]
2423              : style=="octa"?
2424                      let(
2425                           meridians = [
2426                                        1,
2427                                        for (i = [1:1:octa_steps]) i*4,
2428                                        for (i = [octa_steps-1:-1:1]) i*4,
2429                                        1,
2430                                       ]
2431                      )
2432                      [
2433                       for (i=idx(meridians), j=[0:1:meridians[i]-1])
2434                           spherical_to_xyz(r, j*360/meridians[i], i*180/(len(meridians)-1))
2435                      ]
2436              : assert(in_list(style,["orig","aligned","stagger","octa","icosa"])),
2437        lv = len(verts),
2438        faces = circum && style=="stagger" ?
2439                     let(ptcount=2*hsides)
2440                     [
2441                       [for(i=[ptcount-2:-2:0]) i],
2442                       for(j=[0:hsides-1])
2443                           [j*2, (j*2+2)%ptcount,ptcount+(j*2+2)%ptcount,ptcount+(j*2+3)%ptcount,ptcount+j*2],
2444                       for(i=[1:vsides-3])
2445                           let(base=ptcount*i)
2446                           for(j=[0:hsides-1])
2447                               i%2==0 ? [base+2*j, base+(2*j+1)%ptcount, base+(2*j+2)%ptcount,
2448                                        base+ptcount+(2*j)%ptcount, base+ptcount+(2*j+1)%ptcount, base+ptcount+(2*j-2+ptcount)%ptcount]
2449                                      : [base+(1+2*j)%ptcount, base+(2*j)%ptcount, base+(2*j+3)%ptcount,
2450                                         base+ptcount+(3+2*j)%ptcount, base+ptcount+(2*j+2)%ptcount,base+ptcount+(2*j+1)%ptcount],
2451                       for(j=[0:hsides-1])
2452                          vsides%2==0
2453                            ? [(j*2+3)%ptcount, j*2+1, lv-ptcount+(2+j*2)%ptcount, lv-ptcount+(3+j*2)%ptcount, lv-ptcount+(4+j*2)%ptcount]
2454                            : [(j*2+3)%ptcount, j*2+1, lv-ptcount+(1+j*2)%ptcount, lv-ptcount+(j*2)%ptcount, lv-ptcount+(3+j*2)%ptcount],
2455                       [for(i=[1:2:ptcount-1]) i],
2456                     ]
2457              : style=="aligned" || style=="stagger" ?  // includes case of aligned with circum == true
2458                     [
2459                       for (i=[0:1:hsides-1])
2460                           let(b2 = lv-2-hsides)
2461                           each [
2462                                 [i+1, 0, ((i+1)%hsides)+1],
2463                                 [lv-1, b2+i+1, b2+((i+1)%hsides)+1],
2464                                ],
2465                       for (i=[0:1:vsides-3], j=[0:1:hsides-1])
2466                           let(base = 1 + hsides*i)
2467                           each (
2468                                 (stagger && i%2!=0)? [
2469                                     [base+j, base+hsides+j%hsides, base+hsides+(j+hsides-1)%hsides],
2470                                     [base+j, base+(j+1)%hsides, base+hsides+j],
2471                                 ] : [
2472                                     [base+j, base+(j+1)%hsides, base+hsides+(j+1)%hsides],
2473                                     [base+j, base+hsides+(j+1)%hsides, base+hsides+j],
2474                                 ]
2475                           )
2476                     ]
2477              : style=="orig"? [
2478                                [for (i=[0:1:hsides-1]) hsides-i-1],
2479                                [for (i=[0:1:hsides-1]) lv-hsides+i],
2480                                for (i=[0:1:vsides-2], j=[0:1:hsides-1])
2481                                    each [
2482                                          [(i+1)*hsides+j, i*hsides+j, i*hsides+(j+1)%hsides],
2483                                          [(i+1)*hsides+j, i*hsides+(j+1)%hsides, (i+1)*hsides+(j+1)%hsides],
2484                                    ]
2485                               ]
2486              : /*style=="octa"?*/
2487                     let(
2488                         meridians = [
2489                                      0, 1,
2490                                      for (i = [1:1:octa_steps]) i*4,
2491                                      for (i = [octa_steps-1:-1:1]) i*4,
2492                                      1,
2493                                     ],
2494                         offs = cumsum(meridians),
2495                         pc = last(offs)-1,
2496                         os = octa_steps * 2
2497                     )
2498                     [
2499                      for (i=[0:1:3]) [0, 1+(i+1)%4, 1+i],
2500                      for (i=[0:1:3]) [pc-0, pc-(1+(i+1)%4), pc-(1+i)],
2501                      for (i=[1:1:octa_steps-1])
2502                          let(m = meridians[i+2]/4)
2503                          for (j=[0:1:3], k=[0:1:m-1])
2504                              let(
2505                                  m1 = meridians[i+1],
2506                                  m2 = meridians[i+2],
2507                                  p1 = offs[i+0] + (j*m1/4 + k+0) % m1,
2508                                  p2 = offs[i+0] + (j*m1/4 + k+1) % m1,
2509                                  p3 = offs[i+1] + (j*m2/4 + k+0) % m2,
2510                                  p4 = offs[i+1] + (j*m2/4 + k+1) % m2,
2511                                  p5 = offs[os-i+0] + (j*m1/4 + k+0) % m1,
2512                                  p6 = offs[os-i+0] + (j*m1/4 + k+1) % m1,
2513                                  p7 = offs[os-i-1] + (j*m2/4 + k+0) % m2,
2514                                  p8 = offs[os-i-1] + (j*m2/4 + k+1) % m2
2515                              )
2516                              each [
2517                                    [p1, p4, p3],
2518                                    if (k<m-1) [p1, p2, p4],
2519                                    [p5, p7, p8],
2520                                    if (k<m-1) [p5, p8, p6],
2521                                   ],
2522                     ]
2523    ) [reorient(anchor,spin,orient, r=r, p=verts), faces];
2524
2525
2526
2527// Function&Module: torus()
2528// Synopsis: Creates an attachable torus.
2529// SynTags: Geom, VNF
2530// Topics: Shapes (3D), Attachable, VNF Generators
2531// See Also: spheroid(), cyl()
2532//
2533// Usage: As Module
2534//   torus(r_maj|d_maj, r_min|d_min, [center], ...) [ATTACHMENTS];
2535//   torus(or|od, ir|id, ...) [ATTACHMENTS];
2536//   torus(r_maj|d_maj, or|od, ...) [ATTACHMENTS];
2537//   torus(r_maj|d_maj, ir|id, ...) [ATTACHMENTS];
2538//   torus(r_min|d_min, or|od, ...) [ATTACHMENTS];
2539//   torus(r_min|d_min, ir|id, ...) [ATTACHMENTS];
2540// Usage: As Function
2541//   vnf = torus(r_maj|d_maj, r_min|d_min, [center], ...);
2542//   vnf = torus(or|od, ir|id, ...);
2543//   vnf = torus(r_maj|d_maj, or|od, ...);
2544//   vnf = torus(r_maj|d_maj, ir|id, ...);
2545//   vnf = torus(r_min|d_min, or|od, ...);
2546//   vnf = torus(r_min|d_min, ir|id, ...);
2547//
2548// Description:
2549//   Creates an attachable toroidal shape.
2550//
2551// Figure(2D,Med):
2552//   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));
2553//   r = 75; r2 = 30;
2554//   down(r2+0.1) #torus(r_maj=r, r_min=r2, $fn=72);
2555//   color("blue") linear_extrude(height=0.01) {
2556//       dashcirc(r=r,start=15,angle=45);
2557//       dashcirc(r=r-r2, start=90+15, angle=60);
2558//       dashcirc(r=r+r2, start=180+45, angle=30);
2559//       dashcirc(r=r+r2, start=15, angle=30);
2560//   }
2561//   rot(240) color("blue") linear_extrude(height=0.01) {
2562//       stroke([[0,0],[r+r2,0]], endcaps="arrow2",width=2);
2563//       right(r) fwd(9) rot(-240) text("or",size=10,anchor=CENTER);
2564//   }
2565//   rot(135) color("blue") linear_extrude(height=0.01) {
2566//       stroke([[0,0],[r-r2,0]], endcaps="arrow2",width=2);
2567//       right((r-r2)/2) back(8) rot(-135) text("ir",size=10,anchor=CENTER);
2568//   }
2569//   rot(45) color("blue") linear_extrude(height=0.01) {
2570//       stroke([[0,0],[r,0]], endcaps="arrow2",width=2);
2571//       right(r/2) back(8) text("r_maj",size=9,anchor=CENTER);
2572//   }
2573//   rot(30) color("blue") linear_extrude(height=0.01) {
2574//       stroke([[r,0],[r+r2,0]], endcaps="arrow2",width=2);
2575//       right(r+r2/2) fwd(8) text("r_min",size=7,anchor=CENTER);
2576//   }
2577//
2578// Arguments:
2579//   r_maj = major radius of torus ring. (use with 'r_min', or 'd_min')
2580//   r_min = minor radius of torus ring. (use with 'r_maj', or 'd_maj')
2581//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
2582//   ---
2583//   d_maj  = major diameter of torus ring. (use with 'r_min', or 'd_min')
2584//   d_min = minor diameter of torus ring. (use with 'r_maj', or 'd_maj')
2585//   or = outer radius of the torus. (use with 'ir', or 'id')
2586//   ir = inside radius of the torus. (use with 'or', or 'od')
2587//   od = outer diameter of the torus. (use with 'ir' or 'id')
2588//   id = inside diameter of the torus. (use with 'or' or 'od')
2589//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2590//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2591//
2592// Example:
2593//   // These all produce the same torus.
2594//   torus(r_maj=22.5, r_min=7.5);
2595//   torus(d_maj=45, d_min=15);
2596//   torus(or=30, ir=15);
2597//   torus(od=60, id=30);
2598//   torus(d_maj=45, id=30);
2599//   torus(d_maj=45, od=60);
2600//   torus(d_min=15, id=30);
2601//   torus(d_min=15, od=60);
2602//   vnf_polyhedron(torus(d_min=15, od=60), convexity=4);
2603// Example: Standard Connectors
2604//   torus(od=60, id=30) show_anchors();
2605
2606module torus(
2607    r_maj, r_min, center,
2608    d_maj, d_min,
2609    or, od, ir, id,
2610    anchor, spin=0, orient=UP
2611) {
2612    _or = get_radius(r=or, d=od, dflt=undef);
2613    _ir = get_radius(r=ir, d=id, dflt=undef);
2614    _r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef);
2615    _r_min = get_radius(r=r_min, d=d_min, dflt=undef);
2616    maj_rad = is_finite(_r_maj)? _r_maj :
2617        is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
2618        is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
2619        is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
2620        assert(false, "Bad Parameters");
2621    min_rad = is_finite(_r_min)? _r_min :
2622        is_finite(_ir)? (maj_rad - _ir) :
2623        is_finite(_or)? (_or - maj_rad) :
2624        assert(false, "Bad Parameters");
2625    anchor = get_anchor(anchor, center, BOT, CENTER);
2626    attachable(anchor,spin,orient, r=(maj_rad+min_rad), l=min_rad*2) {
2627        rotate_extrude(convexity=4) {
2628            right_half(s=min_rad*2, planar=true)
2629                right(maj_rad)
2630                    circle(r=min_rad);
2631        }
2632        children();
2633    }
2634}
2635
2636
2637function torus(
2638    r_maj, r_min, center,
2639    d_maj, d_min,
2640    or, od, ir, id,
2641    anchor, spin=0, orient=UP
2642) = let(
2643    _or = get_radius(r=or, d=od, dflt=undef),
2644    _ir = get_radius(r=ir, d=id, dflt=undef),
2645    _r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef),
2646    _r_min = get_radius(r=r_min, d=d_min, dflt=undef),
2647    maj_rad = is_finite(_r_maj)? _r_maj :
2648        is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
2649        is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
2650        is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
2651        assert(false, "Bad Parameters"),
2652    min_rad = is_finite(_r_min)? _r_min :
2653        is_finite(_ir)? (maj_rad - _ir) :
2654        is_finite(_or)? (_or - maj_rad) :
2655        assert(false, "Bad Parameters"),
2656    anchor = get_anchor(anchor, center, BOT, CENTER),
2657    maj_sides = segs(maj_rad+min_rad),
2658    maj_step = 360 / maj_sides,
2659    min_sides = segs(min_rad),
2660    min_step = 360 / min_sides,
2661    xyprofile = min_rad <= maj_rad? right(maj_rad, p=circle(r=min_rad)) :
2662        right_half(p=right(maj_rad, p=circle(r=min_rad)))[0],
2663    profile = xrot(90, p=path3d(xyprofile)),
2664    vnf = vnf_vertex_array(
2665        points=[for (a=[0:maj_step:360-EPSILON]) zrot(a, p=profile)],
2666        caps=false, col_wrap=true, row_wrap=true, reverse=true
2667    )
2668) reorient(anchor,spin,orient, r=(maj_rad+min_rad), l=min_rad*2, p=vnf);
2669
2670
2671// Function&Module: teardrop()
2672// Synopsis: Creates a teardrop shape.
2673// SynTags: Geom, VNF
2674// Topics: Shapes (3D), Attachable, VNF Generators, FDM Optimized
2675// See Also: onion(), teardrop2d()
2676// Description:
2677//   Makes a teardrop shape in the XZ plane. Useful for 3D printable holes.
2678//   Optional chamfers can be added with positive or negative distances.  A positive distance
2679//   specifies the amount to inset the chamfer along the front/back faces of the shape.
2680//   The chamfer will extend the same y distance into the shape.  If the radii are the same
2681//   then the chamfer will be a 45 degree chamfer, but in other cases it will not.
2682//   Note that with caps, the chamfer must not be so big that it makes the cap height illegal.  
2683//
2684// Usage: Typical
2685//   teardrop(h|l=|length=|height=, r, [ang], [cap_h], [chamfer=], ...) [ATTACHMENTS];
2686//   teardrop(h|l=|length=|height=, d=, [ang=], [cap_h=], [chamfer=], ...) [ATTACHMENTS];
2687// Usage: Psuedo-Conical
2688//   teardrop(h|l=|height=|length=, r1=, r2=, [ang=], [cap_h1=], [cap_h2=], ...)  [ATTACHMENTS];
2689//   teardrop(h|l=|height=|length=, d1=, d2=, [ang=], [cap_h1=], [cap_h2=], ...)  [ATTACHMENTS];
2690// Usage: As Function
2691//   vnf = teardrop(h|l=|height=|length=, r|d=, [ang=], [cap_h=], ...);
2692//   vnf = teardrop(h|l=|height=|length=, r1=|d1=, r2=|d2=, [ang=], [cap_h=], ...);
2693//   vnf = teardrop(h|l=|height=|length=, r1=|d1=, r2=|d2=, [ang=], [cap_h1=], [cap_h2=], ...);
2694//
2695// Arguments:
2696//   h / l / height / length = Thickness of teardrop. Default: 1
2697//   r = Radius of circular part of teardrop.  Default: 1
2698//   ang = Angle of hat walls from the Z axis.  Default: 45 degrees
2699//   cap_h = If given, height above center where the shape will be truncated. Default: `undef` (no truncation)
2700//   ---
2701//   circum = produce a circumscribing teardrop shape.  Default: false
2702//   r1 = Radius of circular portion of the front end of the teardrop shape.
2703//   r2 = Radius of circular portion of the back end of the teardrop shape.
2704//   d = Diameter of circular portion of the teardrop shape.
2705//   d1 = Diameter of circular portion of the front end of the teardrop shape.
2706//   d2 = Diameter of circular portion of the back end of the teardrop shape.
2707//   cap_h1 = If given, height above center where the shape will be truncated, on the front side. Default: `undef` (no truncation)
2708//   cap_h2 = If given, height above center where the shape will be truncated, on the back side. Default: `undef` (no truncation)
2709//   chamfer = Specifies size of chamfer as distance along the bottom and top faces.  Default: 0
2710//   chamfer1 = Specifies size of chamfer on bottom as distance along bottom face.  Default: 0
2711//   chamfer2 = Specifies size of chamfer on top as distance along top face.  Default: 0
2712//   realign = Passes realign option to teardrop2d, which shifts face alignment.  Default: false
2713//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2714//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2715//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2716//
2717// Extra Anchors:
2718//   cap = The center of the top of the cap, oriented with the cap face normal.
2719//   cap_fwd = The front edge of the cap.
2720//   cap_back = The back edge of the cap.
2721//
2722// Example: Typical Shape
2723//   teardrop(r=30, h=10, ang=30);
2724// Example: Crop Cap
2725//   teardrop(r=30, h=10, ang=30, cap_h=40);
2726// Example: Close Crop
2727//   teardrop(r=30, h=10, ang=30, cap_h=20);
2728// Example: Psuedo-Conical
2729//   teardrop(r1=20, r2=30, h=40, cap_h1=25, cap_h2=35);
2730// Example: Adding chamfers can be useful for a teardrop hole mask
2731//   teardrop(r=10, l=50, chamfer1=2, chamfer2=-1.5);
2732// Example: Getting a VNF
2733//   vnf = teardrop(r1=25, r2=30, l=20, cap_h1=25, cap_h2=35);
2734//   vnf_polyhedron(vnf);
2735// Example: Standard Conical Connectors
2736//   teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
2737//       show_anchors(custom=false);
2738// Example(Spin,VPD=150,Med): Named Conical Connectors
2739//   teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
2740//       show_anchors(std=false);
2741
2742module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, length, height, circum=false, realign=false,
2743                chamfer, chamfer1, chamfer2,anchor=CENTER, spin=0, orient=UP)
2744{
2745    length = one_defined([l, h, length, height],"l,h,length,height");
2746    dummy=assert(is_finite(length) && length>0, "length must be positive");
2747    r1 = get_radius(r=r, r1=r1, d=d, d1=d1);
2748    r2 = get_radius(r=r, r1=r2, d=d, d1=d2);
2749    tip_y1 = r1/cos(90-ang);
2750    tip_y2 = r2/cos(90-ang);
2751    _cap_h1 = min(default(cap_h1, tip_y1), tip_y1);
2752    _cap_h2 = min(default(cap_h2, tip_y2), tip_y2);
2753    capvec = unit([0, _cap_h1-_cap_h2, length]);
2754    anchors = [
2755        named_anchor("cap",      [0,0,(_cap_h1+_cap_h2)/2], capvec),
2756        named_anchor("cap_fwd",  [0,-length/2,_cap_h1],         unit((capvec+FWD)/2)),
2757        named_anchor("cap_back", [0,+length/2,_cap_h2],         unit((capvec+BACK)/2), 180),
2758    ];
2759    attachable(anchor,spin,orient, r1=r1, r2=r2, l=length, axis=BACK, anchors=anchors)
2760    {
2761        vnf_polyhedron(teardrop(ang=ang,cap_h=cap_h,r1=r1,r2=r2,cap_h1=cap_h1,cap_h2=cap_h2,circum=circum,realign=realign,
2762                                length=length, chamfer1=chamfer1,chamfer2=chamfer2,chamfer=chamfer));
2763        children();
2764    }
2765}
2766
2767
2768function teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2,  chamfer, chamfer1, chamfer2, circum=false, realign=false,
2769                  l, length, height, anchor=CENTER, spin=0, orient=UP) =
2770    let(
2771        r1 = get_radius(r=r, r1=r1, d=d, d1=d1, dflt=1),
2772        r2 = get_radius(r=r, r1=r2, d=d, d1=d2, dflt=1),
2773        length = one_defined([l, h, length, height],"l,h,length,height"),
2774        dummy0=assert(is_finite(length) && length>0, "length must be positive"),
2775        cap_h1 = first_defined([cap_h1, cap_h]),
2776        cap_h2 = first_defined([cap_h2, cap_h]),
2777        chamfer1 = first_defined([chamfer1,chamfer,0]),
2778        chamfer2 = first_defined([chamfer2,chamfer,0]),    
2779        sides = segs(max(r1,r2)),
2780        profile1 = teardrop2d(r=r1, ang=ang, cap_h=cap_h1, $fn=sides, circum=circum, realign=realign,_extrapt=true),
2781        profile2 = teardrop2d(r=r2, ang=ang, cap_h=cap_h2, $fn=sides, circum=circum, realign=realign,_extrapt=true),
2782        tip_y1 = r1/cos(90-ang),
2783        tip_y2 = r2/cos(90-ang),
2784        _cap_h1 = min(default(cap_h1, tip_y1), tip_y1),
2785        _cap_h2 = min(default(cap_h2, tip_y2), tip_y2),
2786        capvec = unit([0, _cap_h1-_cap_h2, length]),
2787        dummy=
2788          assert(abs(chamfer1)+abs(chamfer2) <= length,"chamfers are too big to fit in the length")
2789          assert(chamfer1<=r1 && chamfer2<=r2, "Chamfers cannot be larger than raduis")
2790          assert(is_undef(cap_h1) || cap_h1-chamfer1 > r1*sin(ang), "chamfer1 is too big to work with the specified cap_h1")
2791          assert(is_undef(cap_h2) || cap_h2-chamfer2 > r2*sin(ang), "chamfer2 is too big to work with the specified cap_h2"),
2792        cprof1 = r1==chamfer1 ? repeat([0,0],len(profile1))
2793                              : teardrop2d(r=r1-chamfer1, ang=ang, cap_h=u_add(cap_h1,-chamfer1), $fn=sides, circum=circum, realign=realign,_extrapt=true),
2794        cprof2 = r2==chamfer2 ? repeat([0,0],len(profile2))
2795                              : teardrop2d(r=r2-chamfer2, ang=ang, cap_h=u_add(cap_h2,-chamfer2), $fn=sides, circum=circum, realign=realign,_extrapt=true),
2796        anchors = [
2797            named_anchor("cap",      [0,0,(_cap_h1+_cap_h2)/2], capvec),
2798            named_anchor("cap_fwd",  [0,-length/2,_cap_h1],         unit((capvec+FWD)/2)),
2799            named_anchor("cap_back", [0,+length/2,_cap_h2],         unit((capvec+BACK)/2), 180),
2800        ],
2801        vnf = vnf_vertex_array(
2802            points = [
2803                if (chamfer1!=0) fwd(length/2, xrot(90, path3d(cprof1))),
2804                fwd(length/2-abs(chamfer1), xrot(90, path3d(profile1))),
2805                back(length/2-abs(chamfer2), xrot(90, path3d(profile2))),
2806                if (chamfer2!=0) back(length/2, xrot(90, path3d(cprof2))),
2807            ],
2808            caps=true, col_wrap=true, reverse=true
2809        )
2810    ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK, anchors=anchors, p=vnf);
2811
2812
2813// Function&Module: onion()
2814// Synopsis: Creates an attachable onion-like shape.
2815// SynTags: Geom, VNF
2816// Topics: Shapes (3D), Attachable, VNF Generators, FDM Optimized
2817// See Also: teardrop(), teardrop2d()
2818// Description:
2819//   Creates a sphere with a conical hat, to make a 3D teardrop.
2820//
2821// Usage: As Module
2822//   onion(r|d=, [ang=], [cap_h=], [circum=], [realign=], ...) [ATTACHMENTS];
2823// Usage: As Function
2824//   vnf = onion(r|d=, [ang=], [cap_h=], [circum=], [realign=], ...);
2825//
2826// Arguments:
2827//   r = radius of spherical portion of the bottom. Default: 1
2828//   ang = Angle of cone on top from vertical. Default: 45 degrees
2829//   cap_h = If given, height above sphere center to truncate teardrop shape.  Default: `undef` (no truncation)
2830//   ---
2831//   circum = set to true to circumscribe the specified radius/diameter.  Default: False
2832//   realign = adjust point alignment to determine if bottom is flat or pointy.  Default: False
2833//   d = diameter of spherical portion of bottom.
2834//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2835//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2836//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2837//
2838// Extra Anchors:
2839//   cap = The center of the top of the cap, oriented with the cap face normal.
2840//   tip = The position where an un-capped onion would come to a point, oriented in the direction the point is from the center.
2841//
2842// Example: Typical Shape
2843//   onion(r=30, ang=30);
2844// Example: Crop Cap
2845//   onion(r=30, ang=30, cap_h=40);
2846// Example: Close Crop
2847//   onion(r=30, ang=30, cap_h=20);
2848// Example: Onions are useful for making the tops of large cylindrical voids.
2849//   difference() {
2850//       cuboid([100,50,100], anchor=FWD+BOT);
2851//       down(0.1)
2852//           cylinder(h=50,d=50,anchor=BOT)
2853//               attach(TOP)
2854//                   onion(d=50, cap_h=30);
2855//   }
2856// Example: Standard Connectors
2857//   onion(d=30, ang=30, cap_h=20) show_anchors();
2858
2859module onion(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CENTER, spin=0, orient=UP)
2860{
2861    r = get_radius(r=r, d=d, dflt=1);
2862    xyprofile = teardrop2d(r=r, ang=ang, cap_h=cap_h, circum=circum, realign=realign);
2863    tip_h = max(column(xyprofile,1));
2864    _cap_h = min(default(cap_h,tip_h), tip_h);
2865    anchors = [
2866        ["cap", [0,0,_cap_h], UP, 0],
2867        ["tip", [0,0,tip_h], UP, 0]
2868    ];
2869    attachable(anchor,spin,orient, r=r, anchors=anchors) {
2870        rotate_extrude(convexity=2) {
2871            difference() {
2872                polygon(xyprofile);
2873                square([2*r,2*max(_cap_h,r)+1], anchor=RIGHT);
2874            }
2875        }
2876        children();
2877    }
2878}
2879
2880
2881function onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP) =
2882    let(
2883        r = get_radius(r=r, d=d, dflt=1),
2884        xyprofile = right_half(p=teardrop2d(r=r, ang=ang, cap_h=cap_h))[0],
2885        profile = xrot(90, p=path3d(xyprofile)),
2886        tip_h = max(column(xyprofile,1)),
2887        _cap_h = min(default(cap_h,tip_h), tip_h),
2888        anchors = [
2889            ["cap", [0,0,_cap_h], UP, 0],
2890            ["tip", [0,0,tip_h], UP, 0]
2891        ],
2892        sides = segs(r),
2893        step = 360 / sides,
2894        vnf = vnf_vertex_array(
2895            points=[for (a = [0:step:360-EPSILON]) zrot(a, p=profile)],
2896            caps=false, col_wrap=true, row_wrap=true, reverse=true
2897        )
2898    ) reorient(anchor,spin,orient, r=r, anchors=anchors, p=vnf);
2899
2900
2901// Section: Text
2902
2903// Module: text3d()
2904// Synopsis: Creates an attachable 3d text block.
2905// SynTags: Geom
2906// Topics: Attachments, Text
2907// See Also: path_text(), text() 
2908// Usage:
2909//   text3d(text, [h], [size], [font], [language=], [script=], [direction=], [atype=], [anchor=], [spin=], [orient=]);
2910// Description:
2911//   Creates a 3D text block that supports anchoring and attachment to attachable objects.  You cannot attach children to text.
2912//   .
2913//   Historically fonts were specified by their "body size", the height of the metal body
2914//   on which the glyphs were cast.  This means the size was an upper bound on the size
2915//   of the font glyphs, not a direct measurement of their size.  In digital typesetting,
2916//   the metal body is replaced by an invisible box, the em square, whose side length is
2917//   defined to be the font's size.  The glyphs can be contained in that square, or they
2918//   can extend beyond it, depending on the choices made by the font designer.  As a
2919//   result, the meaning of font size varies between fonts: two fonts at the "same" size
2920//   can differ significantly in the actual size of their characters.  Typographers
2921//   customarily specify the size in the units of "points".  A point is 1/72 inch.  In
2922//   OpenSCAD, you specify the size in OpenSCAD units (often treated as millimeters for 3d
2923//   printing), so if you want points you will need to perform a suitable unit conversion.
2924//   In addition, the OpenSCAD font system has a bug: if you specify size=s you will
2925//   instead get a font whose size is s/0.72.  For many fonts this means the size of
2926//   capital letters will be approximately equal to s, because it is common for fonts to
2927//   use about 70% of their height for the ascenders in the font.  To get the customary
2928//   font size, you should multiply your desired size by 0.72.
2929//   .
2930//   To find the fonts that you have available in your OpenSCAD installation,
2931//   go to the Help menu and select "Font List".
2932// Arguments:
2933//   text = Text to create.
2934//   h / height / thickness = Extrusion height for the text.  Default: 1
2935//   size = The font will be created at this size divided by 0.72.   Default: 10
2936//   font = Font to use.  Default: "Liberation Sans" (standard OpenSCAD default)
2937//   ---
2938//   spacing = The relative spacing multiplier between characters.  Default: `1.0`
2939//   direction = The text direction.  `"ltr"` for left to right.  `"rtl"` for right to left. `"ttb"` for top to bottom. `"btt"` for bottom to top.  Default: `"ltr"`
2940//   language = The language the text is in.  Default: `"en"`
2941//   script = The script the text is in.  Default: `"latin"`
2942//   atype = Change vertical center between "baseline" and "center".  Default: "baseline"
2943//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `"baseline"`
2944//   center = Center the text.  Equivalent to `atype="center", anchor=CENTER`.  Default: false
2945//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2946//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2947// Anchor Types:
2948//   baseline = Anchor center is relative to text baseline
2949//   ycenter = Anchor center is relative to the actualy y direction center of the text
2950// Examples:
2951//   text3d("Fogmobar", h=3, size=10);
2952//   text3d("Fogmobar", h=2, size=12, font="Helvetica");
2953//   text3d("Fogmobar", h=2, anchor=CENTER);
2954//   text3d("Fogmobar", h=2, anchor=CENTER, atype="ycenter");
2955//   text3d("Fogmobar", h=2, anchor=RIGHT);
2956//   text3d("Fogmobar", h=2, anchor=RIGHT+BOT, atype="ycenter");
2957module text3d(text, h, size=10, font="Helvetica", spacing=1.0, direction="ltr", language="en", script="latin",
2958              height, thickness, atype, center=false,
2959              anchor, spin=0, orient=UP) {
2960    no_children($children);
2961    h = one_defined([h,height,thickness],"h,height,thickness",dflt=1);
2962    assert(is_undef(atype) || in_list(atype,["ycenter","baseline"]), "atype must be \"center\" or \"baseline\"");
2963    assert(is_bool(center));
2964    atype = default(atype, center?"ycenter":"baseline");
2965    anchor = default(anchor, center?CENTER:LEFT);
2966    geom = attach_geom(size=[size,size,h]);
2967    ha = anchor.x<0? "left" 
2968       : anchor.x>0? "right" 
2969       : "center";
2970    va = anchor.y<0? "bottom" 
2971       : anchor.y>0? "top" 
2972       : atype=="baseline"? "baseline"
2973       : "center";
2974    m = _attach_transform([0,0,anchor.z],spin,orient,geom);
2975    multmatrix(m) {
2976        $parent_anchor = anchor;
2977        $parent_spin   = spin;
2978        $parent_orient = orient;
2979        $parent_geom   = geom;
2980        $parent_size   = _attach_geom_size(geom);
2981        $attach_to   = undef;
2982        if (_is_shown()) {
2983            _color($color) {
2984                linear_extrude(height=h, center=true)
2985                    _text(
2986                        text=text, size=size, font=font,
2987                        halign=ha, valign=va, spacing=spacing,
2988                        direction=direction, language=language,
2989                        script=script
2990                    );
2991            }
2992        }
2993    }
2994}
2995
2996
2997// This could be replaced with _cut_to_seg_u_form
2998function _cut_interp(pathcut, path, data) =
2999  [for(entry=pathcut)
3000    let(
3001       a = path[entry[1]-1],
3002        b = path[entry[1]],
3003        c = entry[0],
3004        i = max_index(v_abs(b-a)),
3005        factor = (c[i]-a[i])/(b[i]-a[i])
3006    )
3007    (1-factor)*data[entry[1]-1]+ factor * data[entry[1]]
3008  ];
3009
3010
3011// Module: path_text()
3012// Synopsis: Creates 2d or 3d text placed along a path.
3013// SynTags: Geom
3014// Topics: Text, Paths, Paths (2D), Paths (3D), Path Generators, Path Generators (2D)
3015// See Also, text(), text2d()
3016// Usage:
3017//   path_text(path, text, [size], [thickness], [font], [lettersize=], [offset=], [reverse=], [normal=], [top=], [textmetrics=], [kern=])
3018// Description:
3019//   Place the text letter by letter onto the specified path using textmetrics (if available and requested)
3020//   or user specified letter spacing.  The path can be 2D or 3D.  In 2D the text appears along the path with letters upright
3021//   as determined by the path direction.  In 3D by default letters are positioned on the tangent line to the path with the path normal
3022//   pointing toward the reader.  The path normal points away from the center of curvature (the opposite of the normal produced
3023//   by path_normals()).  Note that this means that if the center of curvature switches sides the text will flip upside down.
3024//   If you want text on such a path you must supply your own normal or top vector.
3025//   .
3026//   Text appears starting at the beginning of the path, so if the 3D path moves right to left
3027//   then a left-to-right reading language will display in the wrong order. (For a 2D path text will appear upside down.)
3028//   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
3029//   curve from the center of curvature).  If you need the text to read properly from the inside, you can set reverse to
3030//   true to flip the text, or supply your own normal.
3031//   .
3032//   If you do not have the experimental textmetrics feature enabled then you must specify the space for the letters
3033//   using lettersize, which can be a scalar or array.  You will have the easiest time getting good results by using
3034//   a monospace font such as Courier.  Note that even with text metrics, spacing may be different because path_text()
3035//   doesn't do kerning to adjust positions of individual glyphs.  Also if your font has ligatures they won't be used.
3036//   .
3037//   By default letters appear centered on the path.  The offset can be specified to shift letters toward the reader (in
3038//   the direction of the normal).
3039//   .
3040//   You can specify your own normal by setting `normal` to a direction or a list of directions.  Your normal vector should
3041//   point toward the reader.  You can also specify
3042//   top, which directs the top of the letters in a desired direction.  If you specify your own directions and they
3043//   are not perpendicular to the path then the direction you specify will take priority and the
3044//   letters will not rest on the tangent line of the path.  Note that the normal or top directions that you
3045//   specify must not be parallel to the path.
3046//   .
3047//   Historically fonts were specified by their "body size", the height of the metal body
3048//   on which the glyphs were cast.  This means the size was an upper bound on the size
3049//   of the font glyphs, not a direct measurement of their size.  In digital typesetting,
3050//   the metal body is replaced by an invisible box, the em square, whose side length is
3051//   defined to be the font's size.  The glyphs can be contained in that square, or they
3052//   can extend beyond it, depending on the choices made by the font designer.  As a
3053//   result, the meaning of font size varies between fonts: two fonts at the "same" size
3054//   can differ significantly in the actual size of their characters.  Typographers
3055//   customarily specify the size in the units of "points".  A point is 1/72 inch.  In
3056//   OpenSCAD, you specify the size in OpenSCAD units (often treated as millimeters for 3d
3057//   printing), so if you want points you will need to perform a suitable unit conversion.
3058//   In addition, the OpenSCAD font system has a bug: if you specify size=s you will
3059//   instead get a font whose size is s/0.72.  For many fonts this means the size of
3060//   capital letters will be approximately equal to s, because it is common for fonts to
3061//   use about 70% of their height for the ascenders in the font.  To get the customary
3062//   font size, you should multiply your desired size by 0.72.
3063//   .
3064//   To find the fonts that you have available in your OpenSCAD installation,
3065//   go to the Help menu and select "Font List".
3066// Arguments:
3067//   path = path to place the text on
3068//   text = text to create
3069//   size = The font will be created at this size divided by 0.72.   
3070//   thickness / h / height = thickness of letters (not allowed for 2D path)
3071//   font = font to use.  Default: "Liberation Sans"
3072//   ---
3073//   lettersize = scalar or array giving size of letters
3074//   center = center text on the path instead of starting at the first point.  Default: false
3075//   offset = distance to shift letters "up" (towards the reader).  Not allowed for 2D path.  Default: 0
3076//   normal = direction or list of directions pointing towards the reader of the text.  Not allowed for 2D path.
3077//   top = direction or list of directions pointing toward the top of the text
3078//   reverse = reverse the letters if true.  Not allowed for 2D path.  Default: false
3079//   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
3080//   valign = align text to the path using "top", "bottom", "center" or "baseline".  You can also adjust position with a numerical offset as in "top-5" or "bottom+2".  This only works with textmetrics enabled.  You can give a simple numerical offset, which will be relative to the baseline and works even without textmetrics.  Default: "baseline"
3081//   kern = scalar or array giving spacing adjusments between each letter.  If it's an array it should have one less entry than the text string.  Default: 0
3082//   language = text language, passed to OpenSCAD `text()`.  Default: "en"
3083//   script = text script, passed to OpenSCAD `text()`.  Default: "latin" 
3084// Example(3D,NoScales):  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.
3085//   path = path3d(arc(100, r=25, angle=[245, 370]));
3086//   color("red")stroke(path, width=.3);
3087//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2);
3088// Example(3D,NoScales): By setting the normal to UP we can get text that lies flat, for writing around the edge of a disk:
3089//   path = path3d(arc(100, r=25, angle=[245, 370]));
3090//   color("red")stroke(path, width=.3);
3091//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, normal=UP);
3092// Example(3D,NoScales):  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.
3093//   path = reverse(path3d(arc(100, r=25, angle=[65, 190])));
3094//   color("red")stroke(path, width=.3);
3095//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, reverse=true);
3096// Example(3D,Med,NoScales): text debossed onto a cylinder in a spiral.  The text is 1 unit deep because it is half in, half out.
3097//   text = ("A long text example to wrap around a cylinder, possibly for a few times.");
3098//   L = 5*len(text);
3099//   maxang = 360*L/(PI*50);
3100//   spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
3101//   difference(){
3102//     cyl(d=50, l=50, $fn=120);
3103//     path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
3104//   }
3105// Example(3D,Med,NoScales): Same example but text embossed.  Make sure you have enough depth for the letters to fully overlap the object.
3106//   text = ("A long text example to wrap around a cylinder, possibly for a few times.");
3107//   L = 5*len(text);
3108//   maxang = 360*L/(PI*50);
3109//   spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
3110//   cyl(d=50, l=50, $fn=120);
3111//   path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
3112// Example(3D,NoScales): Here the text baseline sits on the path.  (Note the default orientation makes text readable from below, so we specify the normal.)
3113//   path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
3114//   color("red")stroke(path,width=.2);
3115//   path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", normal=FRONT);
3116// Example(3D,NoScales): If we use top to orient the text upward, the text baseline is no longer aligned with the path.
3117//   path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
3118//   color("red")stroke(path,width=.2);
3119//   path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", top=UP);
3120// Example(3D,Med,NoScales): 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.
3121//   path = [for(theta = [0:360]) [25*cos(theta), 25*sin(theta), 4*cos(theta*4)]];
3122//   normal = [for(theta = [0:360]) [cos(theta), sin(theta),0]];
3123//   zrot(-120)
3124//   difference(){
3125//     cyl(r=25, h=20, $fn=120);
3126//     path_text(path, "A sine wave wiggles", font="Courier", lettersize=5/1.2, size=5, normal=normal);
3127//   }
3128// Example(3D,Med,NoScales): The path center of curvature changes, and the text flips.
3129//   path =  zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
3130//   color("red")stroke(path,width=.2);
3131//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier", thickness=2);
3132// Example(3D,Med,NoScales): We can fix it with top:
3133//   path =  zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
3134//   color("red")stroke(path,width=.2);
3135//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier", thickness=2, top=UP);
3136// Example(2D,NoScales): With a 2D path instead of 3D there's no ambiguity about direction and it works by default:
3137//   path =  zrot(-120,p=concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180]))));
3138//   color("red")stroke(path,width=.2);
3139//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier");
3140// Example(3D,NoScales): The kern parameter lets you adjust the letter spacing either with a uniform value for each letter, or with an array to make adjustments throughout the text.  Here we show a case where adding some extra space gives a better look in a tight circle.  When textmetrics are off, `lettersize` can do this job, but with textmetrics, you'll need to use `kern` to make adjustments relative to the text metric sizes.
3141//   path = path3d(arc(100, r=12, angle=[150, 450]));
3142//   color("red")stroke(path, width=.3);
3143//   kern = [1,1.2,1,1,.3,-.2,1,0,.8,1,1.1];
3144//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, kern=kern, normal=UP);
3145
3146module path_text(path, text, font, size, thickness, lettersize, offset=0, reverse=false, normal, top, center=false,
3147                 textmetrics=false, kern=0, height,h, valign="baseline", language, script)
3148{
3149  no_children($children);
3150  dummy2=assert(is_path(path,[2,3]),"Must supply a 2d or 3d path")
3151         assert(num_defined([normal,top])<=1, "Cannot define both \"normal\" and \"top\"")
3152         assert(all_positive([size]), "Must give positive text size");
3153  dim = len(path[0]);
3154  normalok = is_undef(normal) || is_vector(normal,3) || (is_path(normal,3) && len(normal)==len(path));
3155  topok = is_undef(top) || is_vector(top,dim) || (dim==2 && is_vector(top,3) && top[2]==0)
3156                        || (is_path(top,dim) && len(top)==len(path));
3157  dummy4 = assert(dim==3 || !any_defined([thickness,h,height]), "Cannot give a thickness or height with 2d path")
3158           assert(dim==3 || !reverse, "Reverse not allowed with 2d path")
3159           assert(dim==3 || offset==0, "Cannot give offset with 2d path")
3160           assert(dim==3 || is_undef(normal), "Cannot define \"normal\" for a 2d path, only \"top\"")
3161           assert(normalok,"\"normal\" must be a vector or path compatible with the given path")
3162           assert(topok,"\"top\" must be a vector or path compatible with the given path");
3163  thickness = one_defined([thickness,h,height],"thickness,h,height",dflt=1);
3164  normal = is_vector(normal) ? repeat(normal, len(path))
3165         : is_def(normal) ? normal
3166         : undef;
3167
3168  top = is_vector(top) ? repeat(dim==2?point2d(top):top, len(path))
3169         : is_def(top) ? top
3170         : undef;
3171
3172  kern = force_list(kern, len(text)-1);
3173  dummy3 = assert(is_list(kern) && len(kern)==len(text)-1, "kern must be a scalar or list whose length is len(text)-1");
3174
3175  lsize = is_def(lettersize) ? force_list(lettersize, len(text))
3176        : textmetrics ? [for(letter=text) let(t=textmetrics(letter, font=font, size=size)) t.advance[0]]
3177        : assert(false, "textmetrics disabled: Must specify letter size");
3178  lcenter = convolve(lsize,[1,1]/2)+[0,each kern,0] ;
3179  textlength = sum(lsize)+sum(kern);
3180
3181  ascent = !textmetrics ? undef
3182         : textmetrics(text, font=font, size=size).ascent;
3183  descent = !textmetrics ? undef
3184          : textmetrics(text, font=font, size=size).descent;
3185
3186  vadjustment = is_num(valign) ? -valign
3187              : !textmetrics ? assert(valign=="baseline","valign requires textmetrics support") 0
3188              : let(
3189                     table = [
3190                              ["baseline", 0],
3191                              ["top", -ascent],
3192                              ["bottom", descent],
3193                              ["center", (descent-ascent)/2]
3194                             ],
3195                     match = [for(i=idx(table)) if (starts_with(valign,table[i][0])) i]
3196                )
3197                assert(len(match)==1, "Invalid valign value")
3198                table[match[0]][1] - parse_num(substr(valign,len(table[match[0]][0])));
3199
3200  dummy1 = assert(textlength<=path_length(path),"Path is too short for the text");
3201
3202  start = center ? (path_length(path) - textlength)/2 : 0;
3203   
3204  pts = path_cut_points(path, add_scalar(cumsum(lcenter),start), direction=true);
3205
3206  usernorm = is_def(normal);
3207  usetop = is_def(top);
3208  normpts = is_undef(normal) ? (reverse?1:-1)*column(pts,3) : _cut_interp(pts,path, normal);
3209  toppts = is_undef(top) ? undef : _cut_interp(pts,path,top);
3210  attachable(){
3211    for (i = idx(text)) {
3212      tangent = pts[i][2];
3213      checks =
3214          assert(!usetop || !approx(tangent*toppts[i],norm(top[i])*norm(tangent)),
3215                 str("Specified top direction parallel to path at character ",i))
3216          assert(usetop || !approx(tangent*normpts[i],norm(normpts[i])*norm(tangent)),
3217                 str("Specified normal direction parallel to path at character ",i));
3218      adjustment = usetop ?  (tangent*toppts[i])*toppts[i]/(toppts[i]*toppts[i])
3219                 : usernorm ?  (tangent*normpts[i])*normpts[i]/(normpts[i]*normpts[i])
3220                 : [0,0,0];
3221      move(pts[i][0]) {
3222        if (dim==3) {
3223          frame_map(
3224            x=tangent-adjustment,
3225            z=usetop ? undef : normpts[i],
3226            y=usetop ? toppts[i] : undef
3227          ) up(offset-thickness/2) {
3228            linear_extrude(height=thickness)
3229              back(vadjustment)
3230              {
3231              left(lsize[i]/2)
3232                text(text[i], font=font, size=size, language=language, script=script);
3233              }
3234          }
3235        } else {
3236            frame_map(
3237              x=point3d(tangent-adjustment),
3238              y=point3d(usetop ? toppts[i] : -normpts[i])
3239            ) left(lsize[0]/2) {
3240                text(text[i], font=font, size=size, language=language, script=script);
3241            }
3242        }
3243      }
3244    }
3245    union();
3246  }
3247}
3248
3249
3250
3251// Section: Miscellaneous
3252
3253
3254
3255
3256// Module: fillet()
3257// Synopsis: Creates a smooth fillet between two faces.
3258// SynTags: Geom, VNF
3259// Topics: Shapes (3D), Attachable
3260// See Also: mask2d_roundover()
3261// Description:
3262//   Creates a shape that can be unioned into a concave joint between two faces, to fillet them.
3263//   Center this part along the concave edge to be chamfered and union it in.
3264//
3265// Usage: Typical
3266//   fillet(l, r, [ang], [overlap], ...) [ATTACHMENTS];
3267//   fillet(l|length=|h=|height=, d=, [ang=], [overlap=], ...) [ATTACHMENTS];
3268//
3269// Arguments:
3270//   l / length / h / height = Length of edge to fillet.
3271//   r = Radius of fillet.
3272//   ang = Angle between faces to fillet.
3273//   overlap = Overlap size for unioning with faces.
3274//   ---
3275//   d = Diameter of fillet.
3276//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `FRONT+LEFT`
3277//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3278//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3279//
3280// Example:
3281//   union() {
3282//     translate([0,2,-4])
3283//       cube([20, 4, 24], anchor=BOTTOM);
3284//     translate([0,-10,-4])
3285//       cube([20, 20, 4], anchor=BOTTOM);
3286//     color("green")
3287//       fillet(
3288//         l=20, r=10,
3289//         spin=180, orient=RIGHT
3290//       );
3291//   }
3292//
3293// Examples:
3294//   fillet(l=10, r=20, ang=60);
3295//   fillet(l=10, r=20, ang=90);
3296//   fillet(l=10, r=20, ang=120);
3297//
3298// Example: Using with Attachments
3299//   cube(50,center=true) {
3300//     position(FRONT+LEFT)
3301//       fillet(l=50, r=10, spin=-90);
3302//     position(BOT+FRONT)
3303//       fillet(l=50, r=10, spin=180, orient=RIGHT);
3304//   }
3305
3306module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anchor=CENTER, spin=0, orient=UP)
3307{
3308    deprecate("fillet");
3309    fillet(l,r,ang,overlap,d,length,h,height,anchor,spin,orient);
3310}
3311
3312
3313module fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anchor=CENTER, spin=0, orient=UP) {
3314    l = one_defined([l,length,h,height],"l,length,h,height");
3315    r = get_radius(r=r, d=d, dflt=1);
3316    steps = ceil(segs(r)*(180-ang)/360);
3317    arc = arc(n=steps+1, r=r, corner=[polar_to_xy(r,ang),[0,0],[r,0]]);
3318    maxx = last(arc).x;
3319    maxy = arc[0].y;
3320    path = [
3321        [maxx, -overlap],
3322        polar_to_xy(overlap, 180+ang/2),
3323        arc[0] + polar_to_xy(overlap, 90+ang),
3324        each arc
3325    ];
3326    attachable(anchor,spin,orient, size=[2*maxx,2*maxy,l]) {
3327        if (l > 0) {
3328            linear_extrude(height=l, convexity=4, center=true) {
3329                polygon(path);
3330            }
3331        }
3332        children();
3333    }
3334}
3335
3336
3337// Function&Module: heightfield()
3338// Synopsis: Generates a 3D surface from a 2D grid of values.
3339// SynTags: Geom, VNF
3340// Topics: Textures, Heightfield
3341// See Also: cylindrical_heightfield()
3342// Usage: As Module
3343//   heightfield(data, [size], [bottom], [maxz], [xrange], [yrange], [style], [convexity], ...) [ATTACHMENTS];
3344// Usage: As Function
3345//   vnf = heightfield(data, [size], [bottom], [maxz], [xrange], [yrange], [style], ...);
3346// Description:
3347//   Given a regular rectangular 2D grid of scalar values, or a function literal, generates a 3D
3348//   surface where the height at any given point is the scalar value for that position.
3349//   One script to convert a grayscale image to a heightfield array in a .scad file can be found at:
3350//   https://raw.githubusercontent.com/BelfrySCAD/BOSL2/master/scripts/img2scad.py
3351// Arguments:
3352//   data = This is either the 2D rectangular array of heights, or a function literal that takes X and Y arguments.
3353//   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]`
3354//   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
3355//   maxz = The maximum height to model.  Truncates anything taller to this height.  Default: 99
3356//   xrange = A range of values to iterate X over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3357//   yrange = A range of values to iterate Y over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3358//   style = The style of subdividing the quads into faces.  Valid options are "default", "alt", and "quincunx".  Default: "default"
3359//   ---
3360//   convexity = Max number of times a line could intersect a wall of the surface being formed. Module only.  Default: 10
3361//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3362//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3363//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3364// Example:
3365//   heightfield(size=[100,100], bottom=-20, data=[
3366//       for (y=[-180:4:180]) [
3367//           for(x=[-180:4:180])
3368//           10*cos(3*norm([x,y]))
3369//       ]
3370//   ]);
3371// Example:
3372//   intersection() {
3373//       heightfield(size=[100,100], data=[
3374//           for (y=[-180:5:180]) [
3375//               for(x=[-180:5:180])
3376//               10+5*cos(3*x)*sin(3*y)
3377//           ]
3378//       ]);
3379//       cylinder(h=50,d=100);
3380//   }
3381// Example: Heightfield by Function
3382//   fn = function (x,y) 10*sin(x*360)*cos(y*360);
3383//   heightfield(size=[100,100], data=fn);
3384// Example: Heightfield by Function, with Specific Ranges
3385//   fn = function (x,y) 2*cos(5*norm([x,y]));
3386//   heightfield(
3387//       size=[100,100], bottom=-20, data=fn,
3388//       xrange=[-180:2:180], yrange=[-180:2:180]
3389//   );
3390
3391module 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)
3392{
3393    size = is_num(size)? [size,size] : point2d(size);
3394    vnf = heightfield(data=data, size=size, xrange=xrange, yrange=yrange, bottom=bottom, maxz=maxz, style=style);
3395    attachable(anchor,spin,orient, vnf=vnf) {
3396        vnf_polyhedron(vnf, convexity=convexity);
3397        children();
3398    }
3399}
3400
3401
3402function 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) =
3403    assert(is_list(data) || is_function(data))
3404    let(
3405        size = is_num(size)? [size,size] : point2d(size),
3406        xvals = is_list(data)
3407          ? [for (i=idx(data[0])) i]
3408          : assert(is_list(xrange)||is_range(xrange)) [for (x=xrange) x],
3409        yvals = is_list(data)
3410          ? [for (i=idx(data)) i]
3411          : assert(is_list(yrange)||is_range(yrange)) [for (y=yrange) y],
3412        xcnt = len(xvals),
3413        minx = min(xvals),
3414        maxx = max(xvals),
3415        ycnt = len(yvals),
3416        miny = min(yvals),
3417        maxy = max(yvals),
3418        verts = is_list(data) ? [
3419                for (y = [0:1:ycnt-1]) [
3420                    for (x = [0:1:xcnt-1]) [
3421                        size.x * (x/(xcnt-1)-0.5),
3422                        size.y * (y/(ycnt-1)-0.5),
3423                        data[y][x]
3424                    ]
3425                ]
3426            ] : [
3427                for (y = yrange) [
3428                    for (x = xrange) let(
3429                        z = data(x,y)
3430                    ) [
3431                        size.x * ((x-minx)/(maxx-minx)-0.5),
3432                        size.y * ((y-miny)/(maxy-miny)-0.5),
3433                        min(maxz, max(bottom+0.1, default(z,0)))
3434                    ]
3435                ]
3436            ],
3437        vnf = vnf_join([
3438            vnf_vertex_array(verts, style=style, reverse=true),
3439            vnf_vertex_array([
3440                verts[0],
3441                [for (v=verts[0]) [v.x, v.y, bottom]],
3442            ]),
3443            vnf_vertex_array([
3444                [for (v=verts[ycnt-1]) [v.x, v.y, bottom]],
3445                verts[ycnt-1],
3446            ]),
3447            vnf_vertex_array([
3448                [for (r=verts) let(v=r[0]) [v.x, v.y, bottom]],
3449                [for (r=verts) let(v=r[0]) v],
3450            ]),
3451            vnf_vertex_array([
3452                [for (r=verts) let(v=r[xcnt-1]) v],
3453                [for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom]],
3454            ]),
3455            vnf_vertex_array([
3456                [
3457                    for (v=verts[0]) [v.x, v.y, bottom],
3458                    for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom],
3459                ], [
3460                    for (r=verts) let(v=r[0]) [v.x, v.y, bottom],
3461                    for (v=verts[ycnt-1]) [v.x, v.y, bottom],
3462                ]
3463            ])
3464        ])
3465    ) reorient(anchor,spin,orient, vnf=vnf, p=vnf);
3466
3467
3468// Function&Module: cylindrical_heightfield()
3469// Synopsis: Generates a cylindrical 3d surface from a 2D grid of values.
3470// SynTags: Geom, VNF
3471// Topics: Extrusion, Textures, Knurling, Heightfield
3472// See Also: heightfield()
3473// Usage: As Function
3474//   vnf = cylindrical_heightfield(data, l|length=|h=|height=, r|d=, [base=], [transpose=], [aspect=]);
3475// Usage: As Module
3476//   cylindrical_heightfield(data, l|length=|h=|height=, r|d=, [base=], [transpose=], [aspect=]) [ATTACHMENTS];
3477// Description:
3478//   Given a regular rectangular 2D grid of scalar values, or a function literal of signature (x,y), generates
3479//   a cylindrical 3D surface where the height at any given point above the radius `r=`, is the scalar value
3480//   for that position.
3481//   One script to convert a grayscale image to a heightfield array in a .scad file can be found at:
3482//   https://raw.githubusercontent.com/BelfrySCAD/BOSL2/master/scripts/img2scad.py
3483// Arguments:
3484//   data = This is either the 2D rectangular array of heights, or a function literal of signature `(x, y)`.
3485//   l / length / h / height = The length of the cylinder to wrap around.
3486//   r = The radius of the cylinder to wrap around.
3487//   ---
3488//   r1 = The radius of the bottom of the cylinder to wrap around.
3489//   r2 = The radius of the top of the cylinder to wrap around.
3490//   d = The diameter of the cylinder to wrap around.
3491//   d1 = The diameter of the bottom of the cylinder to wrap around.
3492//   d2 = The diameter of the top of the cylinder to wrap around.
3493//   base = The radius for the bottom of the heightfield object to create.  Any heights smaller than this will be truncated to very slightly above this height.  Default: -20
3494//   transpose = If true, swaps the radial and length axes of the data.  Default: false
3495//   aspect = The aspect ratio of the generated heightfield at the surface of the cylinder.  Default: 1
3496//   xrange = A range of values to iterate X over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3497//   yrange = A range of values to iterate Y over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3498//   maxh = The maximum height above the radius to model.  Truncates anything taller to this height.  Default: 99
3499//   style = The style of subdividing the quads into faces.  Valid options are "default", "alt", and "quincunx".  Default: "default"
3500//   convexity = Max number of times a line could intersect a wall of the surface being formed. Module only.  Default: 10
3501//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3502//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3503//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3504// Example(VPD=400;VPR=[55,0,150]):
3505//   cylindrical_heightfield(l=100, r=30, base=5, data=[
3506//       for (y=[-180:4:180]) [
3507//           for(x=[-180:4:180])
3508//           5*cos(5*norm([x,y]))+5
3509//       ]
3510//   ]);
3511// Example(VPD=400;VPR=[55,0,150]):
3512//   cylindrical_heightfield(l=100, r1=60, r2=30, base=5, data=[
3513//       for (y=[-180:4:180]) [
3514//           for(x=[-180:4:180])
3515//           5*cos(5*norm([x,y]))+5
3516//       ]
3517//   ]);
3518// Example(VPD=400;VPR=[55,0,150]): Heightfield by Function
3519//   fn = function (x,y) 5*sin(x*360)*cos(y*360)+5;
3520//   cylindrical_heightfield(l=100, r=30, data=fn);
3521// Example(VPD=400;VPR=[55,0,150]): Heightfield by Function, with Specific Ranges
3522//   fn = function (x,y) 2*cos(5*norm([x,y]));
3523//   cylindrical_heightfield(
3524//       l=100, r=30, base=5, data=fn,
3525//       xrange=[-180:2:180], yrange=[-180:2:180]
3526//   );
3527
3528function cylindrical_heightfield(
3529    data, l, r, base=1,
3530    transpose=false, aspect=1,
3531    style="min_edge", maxh=99,
3532    xrange=[-1:0.01:1],
3533    yrange=[-1:0.01:1],
3534    r1, r2, d, d1, d2, h, height, length, 
3535    anchor=CTR, spin=0, orient=UP
3536) =
3537    let(
3538        l = one_defined([l, h, height, length], "l,h,height,l"),
3539        r1 = get_radius(r1=r1, r=r, d1=d1, d=d),
3540        r2 = get_radius(r1=r2, r=r, d1=d2, d=d)
3541    )
3542    assert(is_finite(l) && l>0, "Must supply one of l=, h=, or height= as a finite positive number.")
3543    assert(is_finite(r1) && r1>0, "Must supply one of r=, r1=, d=, or d1= as a finite positive number.")
3544    assert(is_finite(r2) && r2>0, "Must supply one of r=, r2=, d=, or d2= as a finite positive number.")
3545    assert(is_finite(base) && base>0, "Must supply base= as a finite positive number.")
3546    assert(is_matrix(data)||is_function(data), "data= must be a function literal, or contain a 2D array of numbers.")
3547    let(
3548        xvals = is_list(data)? [for (x = idx(data[0])) x] :
3549            is_range(xrange)? [for (x = xrange) x] :
3550            assert(false, "xrange= must be given as a range if data= is a function literal."),
3551        yvals = is_list(data)? [for (y = idx(data)) y] :
3552            is_range(yrange)? [for (y = yrange) y] :
3553            assert(false, "yrange= must be given as a range if data= is a function literal."),
3554        xlen = len(xvals),
3555        ylen = len(yvals),
3556        stepy = l / (ylen-1),
3557        stepx = stepy * aspect,
3558        maxr = max(r1,r2),
3559        circ = 2 * PI * maxr,
3560        astep = 360 / circ * stepx,
3561        arc = astep * (xlen-1),
3562        bsteps = round(segs(maxr-base) * arc / 360),
3563        bstep = arc / bsteps
3564    )
3565    assert(stepx*xlen <= circ, str("heightfield (",xlen," x ",ylen,") needs a radius of at least ",maxr*stepx*xlen/circ))
3566    let(
3567        verts = [
3568            for (yi = idx(yvals)) let(
3569                z = yi * stepy - l/2,
3570                rr = lerp(r1, r2, yi/(ylen-1))
3571            ) [
3572                cylindrical_to_xyz(rr-base, -arc/2, z),
3573                for (xi = idx(xvals)) let( a = xi*astep )
3574                    let(
3575                        rad = transpose? (
3576                                is_list(data)? data[xi][yi] : data(yvals[yi],xvals[xi])
3577                            ) : (
3578                                is_list(data)? data[yi][xi] : data(xvals[xi],yvals[yi])
3579                            ),
3580                        rad2 = constrain(rad, 0.01-base, maxh)
3581                    )
3582                    cylindrical_to_xyz(rr+rad2, a-arc/2, z),
3583                cylindrical_to_xyz(rr-base, arc/2, z),
3584                for (b = [1:1:bsteps-1]) let( a = arc/2-b*bstep )
3585                    cylindrical_to_xyz((z>0?r2:r1)-base, a, l/2*(z>0?1:-1)),
3586            ]
3587        ],
3588        vnf = vnf_vertex_array(verts, caps=true, col_wrap=true, reverse=true, style=style)
3589    ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, p=vnf);
3590
3591
3592module cylindrical_heightfield(
3593    data, l, r, base=1,
3594    transpose=false, aspect=1,
3595    style="min_edge", convexity=10,
3596    xrange=[-1:0.01:1], yrange=[-1:0.01:1],
3597    maxh=99, r1, r2, d, d1, d2, h, height, length,
3598    anchor=CTR, spin=0, orient=UP
3599) {
3600    l = one_defined([l, h, height, length], "l,h,height,length");
3601    r1 = get_radius(r1=r1, r=r, d1=d1, d=d);
3602    r2 = get_radius(r1=r2, r=r, d1=d2, d=d);
3603    vnf = cylindrical_heightfield(
3604        data, l=l, r1=r1, r2=r2, base=base,
3605        xrange=xrange, yrange=yrange,
3606        maxh=maxh, transpose=transpose,
3607        aspect=aspect, style=style
3608    );
3609    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
3610        vnf_polyhedron(vnf, convexity=convexity);
3611        children();
3612    }
3613}
3614
3615
3616// Module: ruler()
3617// Synopsis: Creates a ruler.
3618// SynTags: Geom
3619// Topics: Distance
3620// Usage:
3621//   ruler(length, width, [thickness=], [depth=], [labels=], [pipscale=], [maxscale=], [colors=], [alpha=], [unit=], [inch=]) [ATTACHMENTS];
3622// Description:
3623//   Creates an attachable ruler for checking dimensions of the model.
3624// Arguments:
3625//   length = length of the ruler.  Default 100
3626//   width = width of the ruler.  Default: size of the largest unit division
3627//   ---
3628//   thickness = thickness of the ruler. Default: 1
3629//   depth = the depth of mark subdivisions. Default: 3
3630//   labels = draw numeric labels for depths where labels are larger than 1.  Default: false
3631//   pipscale = width scale of the pips relative to the next size up.  Default: 1/3
3632//   maxscale = log10 of the maximum width divisions to display.  Default: based on input length
3633//   colors = colors to use for the ruler, a list of two values.  Default: `["black","white"]`
3634//   alpha = transparency value.  Default: 1.0
3635//   unit = unit to mark.  Scales the ruler marks to a different length.  Default: 1
3636//   inch = set to true for a ruler scaled to inches (assuming base dimension is mm).  Default: false
3637//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `LEFT+BACK+TOP`
3638//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3639//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3640// Examples(2D,Big):
3641//   ruler(100,depth=3);
3642//   ruler(100,depth=3,labels=true);
3643//   ruler(27);
3644//   ruler(27,maxscale=0);
3645//   ruler(100,pipscale=3/4,depth=2);
3646//   ruler(100,width=2,depth=2);
3647// Example(2D,Big):  Metric vs Imperial
3648//   ruler(12,width=50,inch=true,labels=true,maxscale=0);
3649//   fwd(50)ruler(300,width=50,labels=true);
3650
3651module ruler(length=100, width, thickness=1, depth=3, labels=false, pipscale=1/3, maxscale,
3652             colors=["black","white"], alpha=1.0, unit=1, inch=false, anchor=LEFT+BACK+TOP, spin=0, orient=UP)
3653{
3654    inchfactor = 25.4;
3655    checks =
3656        assert(depth<=5, "Cannot render scales smaller than depth=5")
3657        assert(len(colors)==2, "colors must contain a list of exactly two colors.");
3658    length = inch ? inchfactor * length : length;
3659    unit = inch ? inchfactor*unit : unit;
3660    maxscale = is_def(maxscale)? maxscale : floor(log(length/unit-EPSILON));
3661    scales = unit * [for(logsize = [maxscale:-1:maxscale-depth+1]) pow(10,logsize)];
3662    widthfactor = (1-pipscale) / (1-pow(pipscale,depth));
3663    width = default(width, scales[0]);
3664    widths = width * widthfactor * [for(logsize = [0:-1:-depth+1]) pow(pipscale,-logsize)];
3665    offsets = concat([0],cumsum(widths));
3666    attachable(anchor,spin,orient, size=[length,width,thickness]) {
3667        translate([-length/2, -width/2, 0])
3668        for(i=[0:1:len(scales)-1]) {
3669            count = ceil(length/scales[i]);
3670            fontsize = 0.5*min(widths[i], scales[i]/ceil(log(count*scales[i]/unit)));
3671            back(offsets[i]) {
3672                xcopies(scales[i], n=count, sp=[0,0,0]) union() {
3673                    actlen = ($idx<count-1) || approx(length%scales[i],0) ? scales[i] : length % scales[i];
3674                    color(colors[$idx%2], alpha=alpha) {
3675                        w = i>0 ? quantup(widths[i],1/1024) : widths[i];    // What is the i>0 test supposed to do here?
3676                        cube([quantup(actlen,1/1024),quantup(w,1/1024),thickness], anchor=FRONT+LEFT);
3677                    }
3678                    mark =
3679                        i == 0 && $idx % 10 == 0 && $idx != 0 ? 0 :
3680                        i == 0 && $idx % 10 == 9 && $idx != count-1 ? 1 :
3681                        $idx % 10 == 4 ? 1 :
3682                        $idx % 10 == 5 ? 0 : -1;
3683                    flip = 1-mark*2;
3684                    if (mark >= 0) {
3685                        marklength = min(widths[i]/2, scales[i]*2);
3686                        markwidth = marklength*0.4;
3687                        translate([mark*scales[i], widths[i], 0]) {
3688                            color(colors[1-$idx%2], alpha=alpha) {
3689                                linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
3690                                    polygon(scale([flip*markwidth, marklength],p=[[0,0], [1, -1], [0,-0.9]]));
3691                                }
3692                            }
3693                        }
3694                    }
3695                    if (labels && scales[i]/unit+EPSILON >= 1) {
3696                        color(colors[($idx+1)%2], alpha=alpha) {
3697                            linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
3698                                back(scales[i]*.02) {
3699                                    text(text=str( $idx * scales[i] / unit), size=fontsize, halign="left", valign="baseline");
3700                                }
3701                            }
3702                        }
3703                    }
3704
3705                }
3706            }
3707        }
3708        children();
3709    }
3710}
3711
3712
3713
3714
3715
3716// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap