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