1# Attachments Tutorial
   2
   3<!-- TOC -->
   4
   5## Attachables
   6BOSL2 introduces the concept of attachables.  You can do the following
   7things with attachable shapes:
   8
   9* Control where the shape appears and how it is oriented by anchoring and specifying orientation and spin
  10* Position or attach shapes relative to parent objects
  11* Tag objects and then color them or control boolean operations based on their tags.
  12
  13The various attachment features may seem complex at first, but 
  14attachability is one of the most important features of the BOSL2
  15library.  It enables you to position objects relative to other objects
  16in your model instead of having to keep track of absolute positions.
  17It makes models simpler, more intuitive, and easier to maintain.
  18
  19Almost all objects defined by BOSL2 are attachable.  In addition,
  20BOSL2 overrides the built-in definitions for `cube()`, `cylinder()`,
  21`sphere()`, `square()`, and `circle()` and makes them attachable as
  22well.
  23
  24
  25## Anchoring
  26Anchoring allows you to align a specified part of an object or point
  27on an object with the origin.  The alignment point can be the center
  28of a side, the center of an edge, a corner, or some other
  29distinguished point on the object.  This is done by passing a vector
  30or text string into the `anchor=` argument.  For roughly cubical
  31or prismoidal shapes, that vector points in the general direction of the side, edge, or
  32corner that will be aligned to.  For example, a vector of [1,0,-1] refers to the lower-right
  33edge of the shape.  Each vector component should be -1, 0, or 1:
  34
  35```openscad-3D
  36include <BOSL2/std.scad>
  37// Anchor at upper-front-left corner
  38cube([40,30,50], anchor=[-1,-1,1]);
  39```
  40
  41```openscad-3D
  42include <BOSL2/std.scad>
  43// Anchor at upper-right edge
  44cube([40,30,50], anchor=[1,0,1]);
  45```
  46
  47```openscad-3D
  48include <BOSL2/std.scad>
  49// Anchor at bottom face
  50cube([40,30,50], anchor=[0,0,-1]);
  51```
  52
  53Since manually written vectors are not very intuitive, BOSL2 defines some standard directional
  54vector constants that can be added together:
  55
  56Constant | Direction | Value
  57-------- | --------- | -----------
  58`LEFT`   | X-        | `[-1, 0, 0]`
  59`RIGHT`  | X+        | `[ 1, 0, 0]`
  60`FRONT`/`FORWARD`/`FWD` | Y− | `[ 0, −1, 0]`
  61`BACK`   | Y+        | `[ 0, 1, 0]`
  62`BOTTOM`/`BOT`/`DOWN` | Z− (Y− in 2D) | `[ 0, 0, −1]` (`[0, −1]` in 2D.)
  63`TOP`/`UP` | Z+ (Y+ in 2D)      | `[ 0, 0, 1]` (`[0, 1]` in 2D.)
  64`CENTER`/`CTR` | Centered | `[ 0, 0, 0]`
  65
  66If you want a vector pointing towards the bottom−left edge, just add the `BOTTOM` and `LEFT` vector
  67constants together like `BOTTOM + LEFT`.  This will result in a vector of `[−1,0,−1]`.  You can pass
  68that to the `anchor=` argument for a clearly understandable anchoring:  
  69
  70```openscad-3D
  71include <BOSL2/std.scad>
  72cube([40,30,50], anchor=BACK+TOP);
  73```
  74
  75```openscad-3D
  76include <BOSL2/std.scad>
  77cube([40,30,50], anchor=FRONT);
  78```
  79
  80---
  81
  82For cylindrical type attachables, the Z component of the vector will be −1, 0, or 1, referring
  83to the bottom rim, the middle side, or the top rim of the cylindrical or conical shape.
  84The X and Y components can be any value, pointing towards the circular perimeter of the cone.
  85These combined let you point at any place on the bottom or top rims, or at an arbitrary
  86side wall:
  87
  88```openscad-3D
  89include <BOSL2/std.scad>
  90cylinder(r1=25, r2=15, h=60, anchor=TOP+LEFT);
  91```
  92
  93```openscad-3D
  94include <BOSL2/std.scad>
  95cylinder(r1=25, r2=15, h=60, anchor=BOTTOM+FRONT);
  96```
  97
  98```openscad-3D
  99include <BOSL2/std.scad>
 100cylinder(r1=25, r2=15, h=60, anchor=UP+spherical_to_xyz(1,30,90));
 101```
 102
 103---
 104
 105For Spherical type attachables, you can pass a vector that points at any arbitrary place on
 106the surface of the sphere:
 107p
 108```openscad-3D
 109include <BOSL2/std.scad>
 110sphere(r=50, anchor=TOP);
 111```
 112
 113```openscad-3D
 114include <BOSL2/std.scad>
 115sphere(r=50, anchor=TOP+FRONT);
 116```
 117
 118```openscad-3D
 119include <BOSL2/std.scad>
 120sphere(r=50, anchor=spherical_to_xyz(1,-30,60));
 121```
 122
 123---
 124
 125Some attachable shapes may provide specific named anchors for shape-specific anchoring.  These
 126will be given as strings and will be specific to that type of
 127attachable.  When named anchors are supported, they are listed in a
 128"Named Anchors" section of the documentation for the module.  The
 129`teardrop()` attachable, for example, has a named anchor called "cap" and in 2D the
 130`star()` attachable has anchors labeled by tip number: 
 131
 132```openscad-3D
 133include <BOSL2/std.scad>
 134teardrop(d=100, l=20, anchor="cap");
 135```
 136
 137```openscad-2D
 138include <BOSL2/std.scad>
 139star(n=7, od=30, id=20, anchor="tip2");
 140```
 141
 142---
 143
 144Some shapes, for backwards compatibility reasons, can take a `center=` argument.  This just
 145overrides the `anchor=` argument.  A `center=true` argument is the same as `anchor=CENTER`.
 146A `center=false` argument chooses the anchor to match the behavior of
 147the builtin version:  for a cube it is the same as `anchor=[-1,-1,-1]` but for a
 148cylinder, it is the same as `anchor=BOTTOM`.
 149
 150```openscad-3D
 151include <BOSL2/std.scad>
 152cube([50,40,30],center=true);
 153```
 154
 155```openscad-3D
 156include <BOSL2/std.scad>
 157cube([50,40,30],center=false);
 158```
 159
 160---
 161
 162Most 2D shapes provided by BOSL2 are also anchorable.  The built-in `square()` and `circle()`
 163modules have been overridden to make them attachable..  The `anchor=` options for 2D
 164shapes treat 2D vectors as expected.  Special handling occurs with 3D
 165vectors:  if the Y coordinate is zero and the Z coordinate is nonzero,
 166then the Z coordinate is used to replace the Y coordinate.  This is
 167done so that you can use the TOP and BOTTOM names as anchor for 2D
 168shapes.  
 169
 170
 171```openscad-2D
 172include <BOSL2/std.scad>
 173square([40,30], anchor=BACK+LEFT);
 174```
 175
 176```openscad-2D
 177include <BOSL2/std.scad>
 178circle(d=50, anchor=BACK);
 179```
 180
 181```openscad-2D
 182include <BOSL2/std.scad>
 183hexagon(d=50, anchor=LEFT);
 184```
 185
 186```openscad-2D
 187include <BOSL2/std.scad>
 188ellipse(d=[50,30], anchor=FRONT);
 189
 190This final 2D example shows using the 3D anchor, TOP, with a 2D
 191object.  Also notice how the pentagon anchors to its most extreme point on
 192the Y+ axis.  
 193
 194```openscad-2D
 195include <BOSL2/std.scad>
 196pentagon(d=50, anchor=TOP);
 197```
 198
 199
 200## Spin
 201You can spin attachable objects around the origin using the `spin=`
 202argument.  The spin applies **after** anchoring, so depending on how
 203you anchor an object, its spin may not be about its center.  This
 204means that spin can have an effect even on rotationally symmetric
 205objects like spheres and cylinders.  You specify the spin in degrees.
 206A positive number will result in a counter-clockwise spin around the Z
 207axis (as seen from above), and a negative number will make a clockwise
 208spin:
 209
 210```openscad-3D
 211include <BOSL2/std.scad>
 212cube([20,20,40], center=true, spin=45);
 213```
 214
 215You can also spin around other axes, or multiple axes at once, by giving 3 angles (in degrees) to
 216`spin=` as a vector, like [Xang,Yang,Zang].  Similarly to `rotate()`,
 217the rotations apply in the order given, X-axis spin, then Y-axis, then Z-axis:
 218
 219```openscad-3D
 220include <BOSL2/std.scad>
 221cube([20,20,40], center=true, spin=[10,20,30]);
 222```
 223
 224This example shows a cylinder which has been anchored at its FRONT,
 225with a rotated copy in gray.  The rotation is performed around the
 226origin, but the cylinder is off the origin, so the rotation **does**
 227have an effect on the cylinder, even though the cylinder has
 228rotational symmetry.
 229
 230```openscad-3D
 231include <BOSL2/std.scad>
 232cylinder(h=40,d=20,anchor=FRONT+BOT);
 233%cylinder(h=40.2,d=20,anchor=FRONT+BOT,spin=40);
 234```
 235
 236
 237
 238You can also apply spin to 2D shapes from BOSL2, though only by scalar angle:
 239
 240```openscad-2D
 241include <BOSL2/std.scad>
 242square([40,30], spin=30);
 243```
 244
 245```openscad-2D
 246include <BOSL2/std.scad>
 247ellipse(d=[40,30], spin=30);
 248```
 249
 250
 251## Orientation
 252Another way to specify a rotation for an attachable shape, is to pass a 3D vector via the
 253`orient=` argument.  This lets you specify what direction to tilt the top of the shape towards.
 254For example, you can make a cone that is tilted up and to the right like this:
 255
 256```openscad-3D
 257include <BOSL2/std.scad>
 258cylinder(h=100, r1=50, r2=20, orient=UP+RIGHT);
 259```
 260
 261More precisely, the Z direction of the shape is rotated to align with
 262the vector you specify.  Two dimensional attachables, which have no Z vector,
 263do not accept the `orient=` argument.  
 264
 265
 266## Mixing Anchoring, Spin, and Orientation
 267When giving `anchor=`, `spin=`, and `orient=`, they are applied anchoring first, spin second,
 268then orient last.  For example, here's a cube:
 269
 270```openscad-3D
 271include <BOSL2/std.scad>
 272cube([20,20,50]);
 273```
 274
 275You can center it with an `anchor=CENTER` argument:
 276
 277```openscad-3D
 278include <BOSL2/std.scad>
 279cube([20,20,50], anchor=CENTER);
 280```
 281
 282Add a 45 degree spin:
 283
 284```openscad-3D
 285include <BOSL2/std.scad>
 286cube([20,20,50], anchor=CENTER, spin=45);
 287```
 288
 289Now tilt the top up and forward:
 290
 291```openscad-3D
 292include <BOSL2/std.scad>
 293cube([20,20,50], anchor=CENTER, spin=45, orient=UP+FWD);
 294```
 295
 296For 2D shapes, you can mix `anchor=` with `spin=`, but not with `orient=`.
 297
 298```openscad-2D
 299include <BOSL2/std.scad>
 300square([40,30], anchor=BACK+LEFT, spin=30);
 301```
 302
 303## Positioning Children
 304
 305Positioning is a powerful method for placing an object relative to
 306another object.  You do this by making the second object a child of
 307the first object.  By default, the child's anchor point will be
 308aligned with the center of the parent.  The default anchor for `cyl()`
 309is CENTER, and in this case, the cylinder is centered on the cube's center
 310
 311```openscad-3D
 312include <BOSL2/std.scad>
 313up(13) cube(50)
 314    cyl(d=25,l=95);
 315```
 316
 317With `cylinder()` the default anchor is BOTTOM.  It's hard to tell,
 318but the cylinder's bottom is placed at the center of the cube.  
 319
 320```openscad-3D
 321include <BOSL2/std.scad>
 322cube(50)
 323    cylinder(d=25,l=75);
 324```
 325
 326If you explicitly anchor the child object then the anchor you choose will be aligned
 327with the center point of the parent object.  In this example the right
 328side of the cylinder is aligned with the center of the cube.  
 329
 330
 331```openscad-3D
 332include <BOSL2/std.scad>
 333cube(50,anchor=FRONT)     
 334    cylinder(d=25,l=95,anchor=RIGHT);
 335```
 336
 337The `position()` module enables you to specify where on the parent to
 338position the child object.  You give `position()` an anchor point on
 339the parent, and the child's anchor point is aligned with the specified
 340parent anchor point.  In this example the LEFT anchor of the cylinder is positioned on the
 341RIGHT anchor of the cube.  
 342
 343```openscad-3D
 344include <BOSL2/std.scad>
 345cube(50,anchor=FRONT)     
 346    position(RIGHT) cylinder(d=25,l=75,anchor=LEFT);
 347```
 348
 349Using this mechanism you can position objects relative to other
 350objects which are in turn positioned relative to other objects without
 351having to keep track of the transformation math.
 352
 353```openscad-3D
 354include <BOSL2/std.scad>
 355cube([50,50,30],center=true)
 356    position(TOP+RIGHT) cube([25,40,10], anchor=RIGHT+BOT)
 357       position(LEFT+FRONT+TOP) cube([12,12,8], anchor=LEFT+FRONT+BOT)
 358         cylinder(l=10,r=3);
 359```
 360
 361The positioning mechanism is not magical: it simply applies a
 362`translate()` operation to the child.  You can still apply your own
 363additional translations or other transformations if you wish.  For
 364example, you can position an object 5 units from the right edge:
 365
 366```openscad-3D
 367include<BOSL2/std.scad>
 368cube([50,50,20],center=true)
 369    position(TOP+RIGHT) left(5) cube([4,50,10], anchor=RIGHT+BOT);
 370```
 371
 372
 373Positioning objects works the same way in 2D.
 374
 375```openscad-2D
 376include<BOSL2/std.scad>
 377square(10)
 378    position(RIGHT) square(3,anchor=LEFT);
 379```
 380
 381## Using position() with orient()
 382
 383When positioning an object near an edge or corner you may wish to
 384orient the object relative to some face other than the TOP face that
 385meets at that edge or corner.  You can always apply a `rotation()` to 
 386change the orientation of the child object, but in order to do this,
 387you need to figure out the correct rotation.  The `orient()` module provides a
 388mechanism for re-orienting the child() that eases this burden.
 389Using its `anchor=` argument you can orient the
 390child relative to the parent anchor directions.  This is different
 391than giving an `orient=` argument to the child, because that orients
 392relative to the parent's global coordinate system by just using the vector
 393directly instead of orienting to the parent's anchor, which takes
 394account of face orientation.  A series of three
 395examples shows the different results.  In the first example, we use
 396only `position()`.  The child cube is erected pointing upwards, in the
 397Z direction.  In the second example we use `orient=RIGHT` in the child
 398and the result is that the child object points in the X+ direction,
 399without regard for the shape of the parent object.  In the final
 400example we apply `orient(anchor=RIGHT)` and the child is oriented
 401relative to the slanted right face of the parent using the parent
 402RIGHT anchor.   
 403
 404```openscad-3D
 405include<BOSL2/std.scad>
 406prismoid([50,50],[30,30],h=40)
 407  position(RIGHT+TOP)
 408     cube([15,15,25],anchor=RIGHT+BOT);
 409```
 410
 411
 412```openscad-3D
 413include<BOSL2/std.scad>
 414prismoid([50,50],[30,30],h=40)
 415  position(RIGHT+TOP)
 416     cube([15,15,25],orient=RIGHT,anchor=LEFT+BOT);
 417```
 418
 419
 420```openscad-3D
 421include<BOSL2/std.scad>
 422prismoid([50,50],[30,30],h=40)
 423  position(RIGHT+TOP)
 424     orient(anchor=RIGHT)
 425        cube([15,15,25],anchor=BACK+BOT);
 426```
 427
 428You may have noticed that the children in the above three examples
 429have different anchors.  Why is that?  The first and second examples
 430differ because anchoring up and anchoring to the right require
 431anchoring on opposite sides of the child.  But the third case differs
 432because the spin has changed.  The examples below show the same models
 433but with arrows replacing the child cube.  The red flags on the arrows
 434mark the zero spin direction.  Examine the red flags to see how the spin
 435changes.  The Y+ direction of the child will point towards that red
 436flag.  
 437
 438```openscad-3D
 439include<BOSL2/std.scad>
 440prismoid([50,50],[30,30],h=40)
 441  position(RIGHT+TOP)
 442     anchor_arrow(40);
 443```
 444
 445
 446```openscad-3D
 447include<BOSL2/std.scad>
 448prismoid([50,50],[30,30],h=40)
 449  position(RIGHT+TOP)
 450     anchor_arrow(40, orient=RIGHT);
 451```
 452
 453```openscad-3D
 454include<BOSL2/std.scad>
 455prismoid([50,50],[30,30],h=40)
 456  position(RIGHT+TOP)
 457     orient(anchor=RIGHT)
 458        anchor_arrow(40);
 459```
 460
 461
 462Note also that `orient()` can be used to orient the child relative to
 463the parent global coordinate system using its first argument, `dir=`.  This
 464use of `orient()` is the same as using the `orient=` argument for the
 465child object.    
 466
 467
 468## Attachment overview
 469
 470Attachables get their name from their ability to be attached to each
 471other.  Unlike with positioning, attaching changes the orientation of
 472the child object.  When you attach an object, it appears on the parent
 473relative to the local coordinate system of the parent at the anchor point.  To understand
 474what this means, imagine the perspective of an ant walking on a
 475sphere.  The meaning of UP varies depending on where on the sphere the
 476ant is standing.  If you **attach** a cylinder to the sphere then the cylinder will
 477be "up" from the ant's perspective.   The first example shows a
 478cylinder placed with `position()` so it points up in the global parent
 479coordinate system.  The second example shows how `attach()` points the
 480cylinder UP from the perspective of an ant standing at the anchor
 481point on the sphere.  
 482
 483```openscad-3D
 484include<BOSL2/std.scad>
 485sphere(40)
 486    position(RIGHT+TOP) cylinder(r=8,l=20);
 487```
 488
 489
 490```openscad-3D
 491include<BOSL2/std.scad>
 492sphere(40)
 493    attach(RIGHT+TOP) cylinder(r=8,l=20);
 494```
 495
 496In the example above, the cylinder's center point is attached to the
 497sphere, pointing "up" from the perspective of the sphere's surface.
 498For a sphere, a surface normal is defined everywhere that specifies
 499what "up" means.  But for other objects, it may not be so obvious.
 500Usually at edges and corners the direction is the average of the
 501direction of the faces that meet there.
 502
 503When you specify an anchor you are actually specifying both an anchor
 504point but also an anchor direction.  If you want to visualize this
 505direction you can use anchor arrows.  
 506
 507
 508## Anchor Directions and Anchor Arrows
 509For the ant on the sphere it is obvious which direction is UP; that
 510direction corresponds to the Z+ axis.  The location of the X and Y
 511axes is less clear and in fact it may be arbitrary.  One way that is
 512useful to show the position and orientation of an anchor point is by
 513attaching an anchor arrow to that anchor.  As noted before, the small
 514red flag points in the direction of the anchor's Y+ axis when the spin
 515is zero.
 516
 517```openscad-3D
 518include <BOSL2/std.scad>
 519cube(18, center=true)
 520    attach(LEFT+TOP)
 521        anchor_arrow();
 522```
 523
 524For large objects, you can change the size of the arrow with the `s=` argument.
 525
 526```openscad-3D
 527include <BOSL2/std.scad>
 528sphere(d=100)
 529    attach(LEFT+TOP)
 530        anchor_arrow(s=50);
 531```
 532
 533To show all the standard cardinal anchor points, you can use the `show_anchors()` module.
 534
 535```openscad-3D
 536include <BOSL2/std.scad>
 537cube(40, center=true)
 538    show_anchors();
 539```
 540
 541```openscad-3D
 542include <BOSL2/std.scad>
 543cylinder(h=40, d=40, center=true)
 544    show_anchors();
 545```
 546
 547```openscad-3D
 548include <BOSL2/std.scad>
 549sphere(d=40)
 550    show_anchors();
 551```
 552
 553For large objects, you can again change the size of the arrows with the `s=` argument.
 554
 555```openscad-3D
 556include <BOSL2/std.scad>
 557cylinder(h=100, d=100, center=true)
 558    show_anchors(s=30);
 559```
 560
 561## Basic Attachment
 562
 563The simplest form of attachment is to attach using the `attach()`
 564module with a single argument, which specifies the anchor on the parent
 565where the child will attach.  This will attach the bottom of the child
 566to the given anchor point on the parent.  The child appears on the parent with its
 567Z direction aligned parallel to the parent's anchor direction, and
 568its Y direction pointing in the zero spin direction for the
 569parent anchor.  The anchor direction of the child does not affect the result in this
 570case.
 571
 572```openscad-3D
 573include <BOSL2/std.scad>
 574cube(50,center=true)
 575    attach(RIGHT)cylinder(d1=30,d2=15,l=25);
 576```
 577
 578```openscad-3D
 579include <BOSL2/std.scad>
 580cube(50,center=true)
 581    attach(RIGHT+TOP)cylinder(d1=30,d2=15,l=25);
 582```
 583
 584In the second example, the child object points diagonally away
 585from the cube.  If you want the child at at edge of the parent it's
 586likely that this result will not be what you want.  To get a different
 587result, use `position()` with `orient(anchor=)`, if needed. 
 588
 589If you give an anchor point to the child object it moves the child
 590around (in the attached coordinate system).  Or alternatively you can
 591think that it moves the object first, and then it gets attached.
 592
 593```openscad-3D
 594include <BOSL2/std.scad>
 595cube(50,center=true)
 596    attach(RIGHT)cylinder(d1=30,d2=15,l=25,anchor=FRONT);
 597```
 598
 599In the above example we anchor the child to its FRONT and then attach
 600it to the RIGHT.  An ambiguity exists regarding the spin of the
 601parent's coordinate system.  How is this resolved?   The small flags
 602on the anchor arrows show the position of zero spin by pointing
 603towards the local Y+ direction, which is also the BACK direction of the child.  For the above
 604cube, the arrow looks like this:
 605
 606```openscad-3D
 607include <BOSL2/std.scad>
 608cube(50,center=true)
 609    attach(RIGHT)anchor_arrow(30);
 610```
 611
 612The red flag points up, which explains why the attached cylinder
 613appeared above the anchor point.  The CENTER anchor generally has a
 614direction that points upward, so an attached object will keep its
 615orientation if attached to the CENTER of a parent.
 616
 617By default, `attach()` places the child exactly flush with the surface of the parent.  Sometimes
 618it's useful to have the child overlap the parent by insetting a bit.  You can do this with the
 619`overlap=` argument to `attach()`.  A positive value will inset the child into the parent, and
 620a negative value will outset out from the parent, which may be helpful
 621when doing differences.  
 622
 623```openscad-3D
 624include <BOSL2/std.scad>
 625cube(50,center=true)
 626    attach(TOP,overlap=10)
 627        cylinder(d=20,l=20);
 628```
 629
 630```openscad-3D
 631include <BOSL2/std.scad>
 632cube(50,center=true)
 633    attach(TOP,overlap=-20)
 634        cylinder(d=20,l=20);
 635```
 636
 637As with `position()`, you can still apply your own translations and
 638other transformations even after attaching an object.  However, the
 639order of operations now matters.  If you apply a translation outside
 640of the anchor then it acts in the parent's global coordinate system, so the
 641child moves up in this example:
 642
 643```openscad-3D
 644include <BOSL2/std.scad>
 645cube(50,center=true)
 646    up(13)
 647        attach(RIGHT)
 648            cylinder(d1=30,d2=15,l=25);
 649```
 650
 651On the other hand, if you put the translation between the attach and
 652the object in your code, then it will act in the local coordinate system of
 653the parent at the parent's anchor, so in the example below it moves to the right.  
 654
 655```openscad-3D
 656include <BOSL2/std.scad>
 657cube(50,center=true)
 658    attach(RIGHT)
 659        up(13)
 660            cylinder(d1=30,d2=15,l=25);
 661```
 662
 663
 664## Attachment With Parent and Child Anchors
 665
 666The `attach()` module can also take a second argument, the child anchor.
 667In this case, the attachment behavior
 668is quite different.  The objects are still attached with their anchor
 669points aligned, but the child is reoriented so that its anchor
 670direction is the opposite of the parent anchor direction.  It's like
 671you assemble the parts by pushing them together in the direction of
 672their anchor arrows.  Two examples appear below, where first we show
 673two objects with their anchors and then we show the result of
 674attaching with those anchors. 
 675
 676```openscad-3D
 677include <BOSL2/std.scad>
 678cube(50,anchor=BOT) attach(TOP) anchor_arrow(30);
 679right(60)cylinder(d1=30,d2=15,l=25) attach(TOP) anchor_arrow(30);
 680```
 681
 682```openscad-3D
 683include <BOSL2/std.scad>
 684cube(50,anchor=BOT)
 685  attach(TOP,TOP) cylinder(d1=30,d2=15,l=25);
 686```
 687
 688```openscad-3D
 689include <BOSL2/std.scad>
 690cube(50,center=true) attach(RIGHT) anchor_arrow(30);
 691right(80)cylinder(d1=30,d2=15,l=25) attach(LEFT) anchor_arrow(30);
 692```
 693
 694```openscad-3D
 695include <BOSL2/std.scad>
 696cube(50,center=true)
 697  attach(RIGHT,LEFT) cylinder(d1=30,d2=15,l=25);
 698```
 699
 700Note that when you attach with two anchors like this, the attachment
 701operation **overrides any anchor or orientation specified in the
 702child**.  That means the child's `anchor=` and `orient=` options are
 703ignored.
 704
 705Attachment with CENTER anchors can be surprising because the anchors
 706point upwards, so in the example below, the child's CENTER anchor
 707points up, so it is inverted when it is attached to the parent cone.
 708Note that the anchors are CENTER anchors, so the bases of the anchors are
 709hidden in the middle of the objects. 
 710
 711```openscad-3D
 712include <BOSL2/std.scad>
 713cylinder(d1=30,d2=15,l=25) attach(CENTER) anchor_arrow(40);
 714right(40)cylinder(d1=30,d2=15,l=25) attach(CENTER) anchor_arrow(40);
 715```
 716
 717```openscad-3D
 718include <BOSL2/std.scad>
 719cylinder(d1=30,d2=15,l=25)
 720    attach(CENTER,CENTER)
 721        cylinder(d1=30,d2=15,l=25);
 722```
 723
 724
 725## Positioning and Attaching Multiple Children
 726
 727You can attach or position more than one child at a time by enclosing them all in braces:
 728
 729```openscad-3D
 730include <BOSL2/std.scad>
 731cube(50, center=true) {
 732    attach(TOP) cylinder(d1=50,d2=20,l=20);
 733    position(RIGHT) cylinder(d1=50,d2=20,l=20);
 734}
 735```
 736
 737If you want to attach the same shape to multiple places on the same parent, you can pass the
 738desired anchors as a list to the `attach()` or `position()` modules:
 739
 740```openscad-3D
 741include <BOSL2/std.scad>
 742cube(50, center=true)
 743    attach([RIGHT,FRONT],TOP) cylinder(d1=35,d2=20,l=25);
 744```
 745
 746```openscad-3D
 747include <BOSL2/std.scad>
 748cube(50, center=true)
 749    position([TOP,RIGHT,FRONT]) cylinder(d1=35,d2=20,l=25);
 750```
 751
 752
 753## Attaching 2D Children
 754You can use attachments in 2D as well.  As usual for the 2D case you
 755can use TOP and BOTTOM as alternative to BACK and FORWARD.  
 756
 757```openscad-2D
 758include <BOSL2/std.scad>
 759square(50,center=true)
 760    attach(RIGHT,FRONT)
 761        trapezoid(w1=30,w2=0,h=30);
 762```
 763
 764```openscad-2D
 765include <BOSL2/std.scad>
 766circle(d=50)
 767    attach(TOP,BOT,overlap=5)
 768        trapezoid(w1=30,w2=0,h=30);
 769```
 770
 771
 772
 773
 774## Tagged Operations
 775BOSL2 introduces the concept of tags.  Tags are names that can be given to attachables, so that
 776you can refer to them when performing `diff()`, `intersect()`, and `conv_hull()` operations.
 777Each object can have no more than one tag at a time.  
 778
 779### `diff([remove], [keep])`
 780The `diff()` operator is used to difference away all shapes marked with the tag(s) given to
 781`remove`, from the other shapes.  
 782
 783For example, to difference away a child cylinder from the middle of a parent cube, you can
 784do this:
 785
 786```openscad-3D
 787include <BOSL2/std.scad>
 788diff("hole")
 789cube(100, center=true)
 790    tag("hole")cylinder(h=101, d=50, center=true);
 791```
 792
 793The `keep=` argument takes tags for shapes that you want to keep in the output.
 794
 795```openscad-3D
 796include <BOSL2/std.scad>
 797diff("dish", keep="antenna")
 798cube(100, center=true)
 799    attach([FRONT,TOP], overlap=33) {
 800        tag("dish") cylinder(h=33.1, d1=0, d2=95);
 801        tag("antenna") cylinder(h=33.1, d=10);
 802    }
 803```
 804
 805Remember that tags are inherited by children.  In this case, we need to explicitly
 806untag the first cylinder (or change its tag to something else), or it
 807will inherit the "keep" tag and get kept.  
 808
 809```openscad-3D
 810include <BOSL2/std.scad>
 811diff("hole", "keep")
 812tag("keep")cube(100, center=true)
 813    attach([RIGHT,TOP]) {
 814        tag("") cylinder(d=95, h=5);
 815        tag("hole") cylinder(d=50, h=11, anchor=CTR);
 816    }
 817```
 818
 819
 820You can of course apply `tag()` to several children.
 821
 822```openscad-3D
 823include <BOSL2/std.scad>
 824diff("hole")
 825cube(100, center=true)
 826    attach([FRONT,TOP], overlap=20)
 827        tag("hole") {
 828            cylinder(h=20.1, d1=0, d2=95);
 829            down(10) cylinder(h=30, d=30);
 830        }
 831```
 832
 833Many of the modules that use tags have default values for their tags.  For diff the default
 834remove tag is "remove" and the default keep tag is "keep".  In this example we rely on the
 835default values:
 836
 837```openscad-3D
 838include <BOSL2/std.scad>
 839diff()
 840sphere(d=100) {
 841    tag("keep")xcyl(d=40, l=120);
 842    tag("remove")cuboid([40,120,100]);
 843}
 844```
 845
 846
 847The parent object can be differenced away from other shapes.  Tags are inherited by children,
 848though, so you will need to set the tags of the children as well as the parent.
 849
 850```openscad-3D
 851include <BOSL2/std.scad>
 852diff("hole")
 853tag("hole")cube([20,11,45], center=true)
 854    tag("body")cube([40,10,90], center=true);
 855```
 856
 857Tags (and therefore tag-based operations like `diff()`) only work correctly with attachable
 858children.  However, a number of built-in modules for making shapes are *not* attachable.
 859Some notable non-attachable modules are `text()`, `linear_extrude()`, `rotate_extrude()`,
 860`polygon()`, `polyhedron()`, `import()`, `surface()`, `union()`, `difference()`,
 861`intersection()`, `offset()`, `hull()`, and `minkowski()`.
 862
 863To allow you to use tags-based operations with non-attachable shapes, you can wrap them with the
 864`force_tag()` module to specify their tags.  For example:
 865
 866```openscad-3D
 867include <BOSL2/std.scad>
 868diff("hole")
 869cuboid(50)
 870  attach(TOP)
 871    force_tag("hole")
 872      rotate_extrude()
 873        right(15)
 874          square(10,center=true);
 875```
 876
 877### `intersect([intersect], [keep])`
 878
 879To perform an intersection of attachables, you can use the `intersect()` module.  This is
 880specifically intended to address the situation where you want intersections involving a parent
 881and a child, something that is impossible with the native `intersection()` module.  This module
 882treats the children in three groups: objects matching the `intersect` tags, objects matching
 883the tags listed in `keep` and the remaining objects that don't match any listed tags.  The
 884intersection is computed between the union of the `intersect` tagged objects and the union of
 885the objects that don't match any listed tags.  Finally the objects listed in `keep` are union
 886ed with the result.  
 887
 888In this example the parent is intersected with a conical bounding shape.  
 889
 890```openscad-3D
 891include <BOSL2/std.scad>
 892intersect("bounds")
 893cube(100, center=true)
 894    tag("bounds") cylinder(h=100, d1=120, d2=95, center=true, $fn=72);
 895```
 896
 897In this example the child objects are intersected with the bounding box parent.  
 898
 899```openscad-3D
 900include <BOSL2/std.scad>
 901intersect("pole cap")
 902cube(100, center=true)
 903    attach([TOP,RIGHT]) {
 904        tag("pole")cube([40,40,80],center=true);
 905        tag("cap")sphere(d=40*sqrt(2));
 906    }
 907```
 908
 909The default `intersect` tag is "intersect" and the default `keep` tag is "keep".  Here is an
 910example where "keep" is used to keep the pole from being removed by the intersection. 
 911
 912```openscad-3D
 913include <BOSL2/std.scad>
 914intersect()
 915cube(100, center=true) {
 916    tag("intersect")cylinder(h=100, d1=120, d2=95, center=true, $fn=72);
 917    tag("keep")zrot(45) xcyl(h=140, d=20, $fn=36);
 918}
 919```
 920
 921### `conv_hull([keep])`
 922You can use the `conv_hull()` module to hull shapes together.  Objects
 923marked with the keep tags are excluded from the hull and unioned into the final result.
 924The default keep tag is "keep".  
 925
 926
 927```openscad-3D
 928include <BOSL2/std.scad>
 929conv_hull()
 930cube(50, center=true) {
 931    cyl(h=100, d=20);
 932    tag("keep")xcyl(h=100, d=20);
 933}
 934```
 935
 936
 937## 3D Masking Attachments
 938To make it easier to mask away shapes from various edges of an attachable parent shape, there
 939are a few specialized alternatives to the `attach()` and `position()` modules.
 940
 941### `edge_mask()`
 942If you have a 3D mask shape that you want to difference away from various edges, you can use
 943the `edge_mask()` module.  This module will take a vertically oriented shape, and will rotate
 944and move it such that the BACK, RIGHT (X+,Y+) side of the shape will be aligned with the given
 945edges.  The shape will be tagged as a "remove" so that you can use
 946`diff()` with its default "remove" tag.  For example,
 947here's a shape for rounding an edge:
 948
 949```openscad-3D
 950include <BOSL2/std.scad>
 951module round_edge(l,r) difference() {
 952    translate([-1,-1,-l/2])
 953        cube([r+1,r+1,l]);
 954    translate([r,r])
 955        cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
 956}
 957round_edge(l=30, r=19);
 958```
 959
 960You can use that mask to round various edges of a cube:
 961
 962```openscad-3D
 963include <BOSL2/std.scad>
 964module round_edge(l,r) difference() {
 965    translate([-1,-1,-l/2])
 966        cube([r+1,r+1,l]);
 967    translate([r,r])
 968        cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
 969}
 970diff()
 971cube([50,60,70],center=true)
 972    edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT])
 973        round_edge(l=71,r=10);
 974```
 975
 976### `corner_mask()`
 977If you have a 3D mask shape that you want to difference away from various corners, you can use
 978the `corner_mask()` module.  This module will take a shape and rotate and move it such that the
 979BACK RIGHT TOP (X+,Y+,Z+) side of the shape will be aligned with the given corner.  The shape
 980will be tagged as a "remove" so that you can use `diff()` with its
 981default "remove" tag.  For example, here's a shape for
 982rounding a corner:
 983
 984```openscad-3D
 985include <BOSL2/std.scad>
 986module round_corner(r) difference() {
 987    translate(-[1,1,1])
 988        cube(r+1);
 989    translate([r,r,r])
 990        sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
 991}
 992round_corner(r=10);
 993```
 994
 995You can use that mask to round various corners of a cube:
 996
 997```openscad-3D
 998include <BOSL2/std.scad>
 999module round_corner(r) difference() {
1000    translate(-[1,1,1])
1001        cube(r+1);
1002    translate([r,r,r])
1003        sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
1004}
1005diff()
1006cube([50,60,70],center=true)
1007    corner_mask([TOP,FRONT],LEFT+FRONT+TOP)
1008        round_corner(r=10);
1009```
1010
1011### Mix and Match Masks
1012You can use `edge_mask()` and `corner_mask()` together as well:
1013
1014```openscad-3D
1015include <BOSL2/std.scad>
1016module round_corner(r) difference() {
1017    translate(-[1,1,1])
1018        cube(r+1);
1019    translate([r,r,r])
1020        sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
1021}
1022module round_edge(l,r) difference() {
1023    translate([-1,-1,-l/2])
1024        cube([r+1,r+1,l]);
1025    translate([r,r])
1026        cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
1027}
1028diff()
1029cube([50,60,70],center=true) {
1030    edge_mask("ALL") round_edge(l=71,r=10);
1031    corner_mask("ALL") round_corner(r=10);
1032}
1033```
1034
1035## 2D Profile Mask Attachments
1036While 3D mask shapes give you a great deal of control, you need to make sure they are correctly
1037sized, and you need to provide separate mask shapes for corners and edges.  Often, a single 2D
1038profile could be used to describe the edge mask shape (via `linear_extrude()`), and the corner
1039mask shape (via `rotate_extrude()`).  This is where `edge_profile()`, `corner_profile()`, and
1040`face_profile()` come in.
1041
1042### `edge_profile()`
1043Using the `edge_profile()` module, you can provide a 2D profile shape and it will be linearly
1044extruded to a mask of the appropriate length for each given edge.  The resultant mask will be
1045tagged with "remove" so that you can difference it away with `diff()`
1046with the default "remove" tag.  The 2D profile is
1047assumed to be oriented with the BACK, RIGHT (X+,Y+) quadrant as the "cutter edge" that gets
1048re-oriented towards the edges of the parent shape.  A typical mask profile for chamfering an
1049edge may look like:
1050
1051```openscad-2D
1052include <BOSL2/std.scad>
1053mask2d_roundover(10);
1054```
1055
1056Using that mask profile, you can mask the edges of a cube like:
1057
1058```openscad-3D
1059include <BOSL2/std.scad>
1060diff()
1061cube([50,60,70],center=true)
1062   edge_profile("ALL")
1063       mask2d_roundover(10);
1064```
1065
1066### `corner_profile()`
1067You can use the same profile to make a rounded corner mask as well:
1068
1069```openscad-3D
1070include <BOSL2/std.scad>
1071diff()
1072cube([50,60,70],center=true)
1073   corner_profile("ALL", r=10)
1074       mask2d_roundover(10);
1075```
1076
1077### `face_profile()`
1078As a simple shortcut to apply a profile mask to all edges and corners of a face, you can use the
1079`face_profile()` module:
1080
1081```openscad-3D
1082include <BOSL2/std.scad>
1083diff()
1084cube([50,60,70],center=true)
1085   face_profile(TOP, r=10)
1086       mask2d_roundover(10);
1087```
1088
1089
1090## Coloring Attachables
1091Usually, when coloring a shape with the `color()` module, the parent color overrides the colors of
1092all children.  This is often not what you want:
1093
1094```openscad-3D
1095include <BOSL2/std.scad>
1096$fn = 24;
1097color("red") spheroid(d=3) {
1098    attach(CENTER,BOT) color("white") cyl(h=10, d=1) {
1099        attach(TOP,BOT) color("green") cyl(h=5, d1=3, d2=0);
1100    }
1101}
1102```
1103
1104If you use the `recolor()` module, however, the child's color overrides the color of the parent.
1105This is probably easier to understand by example:
1106
1107```openscad-3D
1108include <BOSL2/std.scad>
1109$fn = 24;
1110recolor("red") spheroid(d=3) {
1111    attach(CENTER,BOT) recolor("white") cyl(h=10, d=1) {
1112        attach(TOP,BOT) recolor("green") cyl(h=5, d1=3, d2=0);
1113    }
1114}
1115```
1116
1117
1118## Making Attachables
1119To make a shape attachable, you just need to wrap it with an `attachable()` module with a
1120basic description of the shape's geometry.  By default, the shape is expected to be centered
1121at the origin.  The `attachable()` module expects exactly two children.  The first will be
1122the shape to make attachable, and the second will be `children()`, literally.
1123
1124### Prismoidal/Cuboidal Attachables
1125To make a cuboidal or prismoidal shape attachable, you use the `size`, `size2`, and `offset`
1126arguments of `attachable()`.
1127
1128In the most basic form, where the shape is fully cuboid, with top and bottom of the same size,
1129and directly over one another, you can just use `size=`.
1130
1131```openscad-3D
1132include <BOSL2/std.scad>
1133module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
1134    attachable(anchor,spin,orient, size=[s*3,s,s]) {
1135        union() {
1136            xcopies(2*s) cube(s, center=true);
1137            xcyl(h=2*s, d=s/4);
1138        }
1139        children();
1140    }
1141}
1142cubic_barbell(100) show_anchors(30);
1143```
1144
1145When the shape is prismoidal, where the top is a different size from the bottom, you can use
1146the `size2=` argument as well. While `size=` takes all three axes sizes, the `size2=` argument
1147only takes the [X,Y] sizes of the top of the shape.
1148
1149```openscad-3D
1150include <BOSL2/std.scad>
1151module prismoidal(size=[100,100,100], scale=0.5, anchor=CENTER, spin=0, orient=UP) {
1152    attachable(anchor,spin,orient, size=size, size2=[size.x, size.y]*scale) {
1153        hull() {
1154            up(size.z/2-0.005)
1155                linear_extrude(height=0.01, center=true)
1156                    square([size.x,size.y]*scale, center=true);
1157            down(size.z/2-0.005)
1158                linear_extrude(height=0.01, center=true)
1159                    square([size.x,size.y], center=true);
1160        }
1161        children();
1162    }
1163}
1164prismoidal([100,60,30], scale=0.5) show_anchors(20);
1165```
1166
1167When the top of the prismoid can be shifted away from directly above the bottom, you can use
1168the `shift=` argument.  The `shift=` argument takes an [X,Y] vector of the offset of the center
1169of the top from the XY center of the bottom of the shape.
1170
1171```openscad-3D
1172include <BOSL2/std.scad>
1173module prismoidal(size=[100,100,100], scale=0.5, shift=[0,0], anchor=CENTER, spin=0, orient=UP) {
1174    attachable(anchor,spin,orient, size=size, size2=[size.x, size.y]*scale, shift=shift) {
1175        hull() {
1176            translate([shift.x, shift.y, size.z/2-0.005])
1177                linear_extrude(height=0.01, center=true)
1178                    square([size.x,size.y]*scale, center=true);
1179            down(size.z/2-0.005)
1180                linear_extrude(height=0.01, center=true)
1181                    square([size.x,size.y], center=true);
1182        }
1183        children();
1184    }
1185}
1186prismoidal([100,60,30], scale=0.5, shift=[-30,20]) show_anchors(20);
1187```
1188
1189In the case that the prismoid is not oriented vertically, (ie, where the `shift=` or `size2=`
1190arguments should refer to a plane other than XY) you can use the `axis=` argument.  This lets
1191you make prismoids naturally oriented forwards/backwards or sideways.
1192
1193```openscad-3D
1194include <BOSL2/std.scad>
1195module yprismoidal(
1196    size=[100,100,100], scale=0.5, shift=[0,0],
1197    anchor=CENTER, spin=0, orient=UP
1198) {
1199    attachable(
1200        anchor, spin, orient,
1201        size=size, size2=point2d(size)*scale,
1202        shift=shift, axis=BACK
1203    ) {
1204        xrot(-90) hull() {
1205            translate([shift.x, shift.y, size.z/2-0.005])
1206                linear_extrude(height=0.01, center=true)
1207                    square([size.x,size.y]*scale, center=true);
1208            down(size.z/2-0.005)
1209                linear_extrude(height=0.01, center=true)
1210                    square([size.x,size.y], center=true);
1211        }
1212        children();
1213    }
1214}
1215yprismoidal([100,60,30], scale=1.5, shift=[20,20]) show_anchors(20);
1216```
1217
1218
1219### Cylindrical Attachables
1220To make a cylindrical shape attachable, you use the `l`, and `r`/`d`, args of `attachable()`.
1221
1222```openscad-3D
1223include <BOSL2/std.scad>
1224module twistar(l,r,d, anchor=CENTER, spin=0, orient=UP) {
1225    r = get_radius(r=r,d=d,dflt=1);
1226    attachable(anchor,spin,orient, r=r, l=l) {
1227        linear_extrude(height=l, twist=90, slices=20, center=true, convexity=4)
1228            star(n=20, r=r, ir=r*0.9);
1229        children();
1230    }
1231}
1232twistar(l=100, r=40) show_anchors(20);
1233```
1234
1235If the cylinder is elipsoidal in shape, you can pass the unequal X/Y sizes as a 2-item vector
1236to the `r=` or `d=` argument.
1237
1238```openscad-3D
1239include <BOSL2/std.scad>
1240module ovalstar(l,rx,ry, anchor=CENTER, spin=0, orient=UP) {
1241    attachable(anchor,spin,orient, r=[rx,ry], l=l) {
1242        linear_extrude(height=l, center=true, convexity=4)
1243            scale([1,ry/rx,1])
1244                star(n=20, r=rx, ir=rx*0.9);
1245        children();
1246    }
1247}
1248ovalstar(l=100, rx=50, ry=30) show_anchors(20);
1249```
1250
1251For cylindrical shapes that aren't oriented vertically, use the `axis=` argument.
1252
1253```openscad-3D
1254include <BOSL2/std.scad>
1255module ytwistar(l,r,d, anchor=CENTER, spin=0, orient=UP) {
1256    r = get_radius(r=r,d=d,dflt=1);
1257    attachable(anchor,spin,orient, r=r, l=l, axis=BACK) {
1258        xrot(-90)
1259            linear_extrude(height=l, twist=90, slices=20, center=true, convexity=4)
1260                star(n=20, r=r, ir=r*0.9);
1261        children();
1262    }
1263}
1264ytwistar(l=100, r=40) show_anchors(20);
1265```
1266
1267### Conical Attachables
1268To make a conical shape attachable, you use the `l`, `r1`/`d1`, and `r2`/`d2`, args of
1269`attachable()`.
1270
1271```openscad-3D
1272include <BOSL2/std.scad>
1273module twistar(l, r,r1,r2, d,d1,d2, anchor=CENTER, spin=0, orient=UP) {
1274    r1 = get_radius(r1=r1,r=r,d1=d1,d=d,dflt=1);
1275    r2 = get_radius(r1=r2,r=r,d1=d2,d=d,dflt=1);
1276    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
1277        linear_extrude(height=l, twist=90, scale=r2/r1, slices=20, center=true, convexity=4)
1278            star(n=20, r=r1, ir=r1*0.9);
1279        children();
1280    }
1281}
1282twistar(l=100, r1=40, r2=20) show_anchors(20);
1283```
1284
1285If the cone is ellipsoidal in shape, you can pass the unequal X/Y sizes as a 2-item vectors
1286to the `r1=`/`r2=` or `d1=`/`d2=` arguments.
1287
1288```openscad-3D
1289include <BOSL2/std.scad>
1290module ovalish(l,rx1,ry1,rx2,ry2, anchor=CENTER, spin=0, orient=UP) {
1291    attachable(anchor,spin,orient, r1=[rx1,ry1], r2=[rx2,ry2], l=l) {
1292        hull() {
1293            up(l/2-0.005)
1294                linear_extrude(height=0.01, center=true)
1295                    ellipse([rx2,ry2]);
1296            down(l/2-0.005)
1297                linear_extrude(height=0.01, center=true)
1298                    ellipse([rx1,ry1]);
1299        }
1300        children();
1301    }
1302}
1303ovalish(l=100, rx1=50, ry1=30, rx2=30, ry2=50) show_anchors(20);
1304```
1305
1306For conical shapes that are not oriented vertically, use the `axis=` argument to indicate the
1307direction of the primary shape axis:
1308
1309```openscad-3D
1310include <BOSL2/std.scad>
1311module ytwistar(l, r,r1,r2, d,d1,d2, anchor=CENTER, spin=0, orient=UP) {
1312    r1 = get_radius(r1=r1,r=r,d1=d1,d=d,dflt=1);
1313    r2 = get_radius(r1=r2,r=r,d1=d2,d=d,dflt=1);
1314    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK) {
1315        xrot(-90)
1316            linear_extrude(height=l, twist=90, scale=r2/r1, slices=20, center=true, convexity=4)
1317                star(n=20, r=r1, ir=r1*0.9);
1318        children();
1319    }
1320}
1321ytwistar(l=100, r1=40, r2=20) show_anchors(20);
1322```
1323
1324### Spherical Attachables
1325To make a spherical shape attachable, you use the `r`/`d` args of `attachable()`.
1326
1327```openscad-3D
1328include <BOSL2/std.scad>
1329module spikeball(r, d, anchor=CENTER, spin=0, orient=UP) {
1330    r = get_radius(r=r,d=d,dflt=1);
1331    attachable(anchor,spin,orient, r=r*1.1) {
1332        union() {
1333            sphere_copies(r=r, n=512, cone_ang=180) cylinder(r1=r/10, r2=0, h=r/10);
1334            sphere(r=r);
1335        }
1336        children();
1337    }
1338}
1339spikeball(r=50) show_anchors(20);
1340```
1341
1342If the shape is an ellipsoid, you can pass a 3-item vector of sizes to `r=` or `d=`.
1343
1344```openscad-3D
1345include <BOSL2/std.scad>
1346module spikeball(r, d, scale, anchor=CENTER, spin=0, orient=UP) {
1347    r = get_radius(r=r,d=d,dflt=1);
1348    attachable(anchor,spin,orient, r=r*1.1*scale) {
1349        union() {
1350            sphere_copies(r=r, n=512, scale=scale, cone_ang=180) cylinder(r1=r/10, r2=0, h=r/10);
1351            scale(scale) sphere(r=r);
1352        }
1353        children();
1354    }
1355}
1356spikeball(r=50, scale=[0.75,1,1.5]) show_anchors(20);
1357```
1358
1359### VNF Attachables
1360If the shape just doesn't fit into any of the above categories, and you constructed it as a
1361[VNF](vnf.scad), you can use the VNF itself to describe the geometry with the `vnf=` argument.
1362
1363There are two variations to how anchoring can work for VNFs. When `extent=true`, (the default)
1364then a plane is projected out from the origin, perpendicularly in the direction of the anchor,
1365to the furthest distance that intersects with the VNF shape.  The anchor point is then the
1366center of the points that still intersect that plane.
1367
1368```openscad-FlatSpin,VPD=500
1369include <BOSL2/std.scad>
1370module stellate_cube(s=100, anchor=CENTER, spin=0, orient=UP) {
1371    s2 = 3 * s;
1372    verts = [
1373        [0,0,-s2*sqrt(2)/2],
1374        each down(s/2, p=path3d(square(s,center=true))),
1375        each zrot(45, p=path3d(square(s2,center=true))),
1376        each up(s/2, p=path3d(square(s,center=true))),
1377        [0,0,s2*sqrt(2)/2]
1378    ];
1379    faces = [
1380        [0,2,1], [0,3,2], [0,4,3], [0,1,4],
1381        [1,2,6], [1,6,9], [6,10,9], [2,10,6],
1382        [1,5,4], [1,9,5], [9,12,5], [5,12,4],
1383        [4,8,3], [4,12,8], [12,11,8], [11,3,8],
1384        [2,3,7], [3,11,7], [7,11,10], [2,7,10],
1385        [9,10,13], [10,11,13], [11,12,13], [12,9,13]
1386    ];
1387    vnf = [verts, faces];
1388    attachable(anchor,spin,orient, vnf=vnf) {
1389        vnf_polyhedron(vnf);
1390        children();
1391    }
1392}
1393stellate_cube(25) {
1394    attach(UP+RIGHT) {
1395        anchor_arrow(20);
1396        %cube([100,100,0.1],center=true);
1397    }
1398}
1399```
1400
1401When `extent=false`, then the anchor point will be the furthest intersection of the VNF with
1402the anchor ray from the origin. The orientation of the anchor point will be the normal of the
1403face at the intersection.  If the intersection is at an edge or corner, then the orientation
1404will bisect the angles between the faces.
1405
1406```openscad-VPD=1250
1407include <BOSL2/std.scad>
1408module stellate_cube(s=100, anchor=CENTER, spin=0, orient=UP) {
1409    s2 = 3 * s;
1410    verts = [
1411        [0,0,-s2*sqrt(2)/2],
1412        each down(s/2, p=path3d(square(s,center=true))),
1413        each zrot(45, p=path3d(square(s2,center=true))),
1414        each up(s/2, p=path3d(square(s,center=true))),
1415        [0,0,s2*sqrt(2)/2]
1416    ];
1417    faces = [
1418        [0,2,1], [0,3,2], [0,4,3], [0,1,4],
1419        [1,2,6], [1,6,9], [6,10,9], [2,10,6],
1420        [1,5,4], [1,9,5], [9,12,5], [5,12,4],
1421        [4,8,3], [4,12,8], [12,11,8], [11,3,8],
1422        [2,3,7], [3,11,7], [7,11,10], [2,7,10],
1423        [9,10,13], [10,11,13], [11,12,13], [12,9,13]
1424    ];
1425    vnf = [verts, faces];
1426    attachable(anchor,spin,orient, vnf=vnf, extent=false) {
1427        vnf_polyhedron(vnf);
1428        children();
1429    }
1430}
1431stellate_cube() show_anchors(50);
1432```
1433
1434```openscad-3D
1435include <BOSL2/std.scad>
1436$fn=32;
1437R = difference(circle(10), right(2, circle(9)));
1438linear_sweep(R,height=10,atype="hull")
1439    attach(RIGHT) anchor_arrow();
1440```
1441
1442
1443## Making Named Anchors
1444While vector anchors are often useful, sometimes there are logically extra attachment points that
1445aren't on the perimeter of the shape.  This is what named string anchors are for.  For example,
1446the `teardrop()` shape uses a cylindrical geometry for it's vector anchors, but it also provides
1447a named anchor "cap" that is at the tip of the hat of the teardrop shape.
1448
1449Named anchors are passed as an array of `named_anchor()`s to the `anchors=` argument of `attachable()`.
1450The `named_anchor()` call takes a name string, a positional point, an orientation vector, and a spin.
1451The name is the name of the anchor.  The positional point is where the anchor point is at.  The
1452orientation vector is the direction that a child attached at that anchor point should be oriented.
1453The spin is the number of degrees that an attached child should be rotated counter-clockwise around
1454the orientation vector.  Spin is optional, and defaults to 0.
1455
1456To make a simple attachable shape similar to a `teardrop()` that provides a "cap" anchor, you may
1457define it like this:
1458
1459```openscad-3D
1460include <BOSL2/std.scad>
1461module raindrop(r, thick, anchor=CENTER, spin=0, orient=UP) {
1462    anchors = [
1463        named_anchor("cap", [0,r/sin(45),0], BACK, 0)
1464    ];
1465    attachable(anchor,spin,orient, r=r, l=thick, anchors=anchors) {
1466        linear_extrude(height=thick, center=true) {
1467            circle(r=r);
1468            back(r*sin(45)) zrot(45) square(r, center=true);
1469        }
1470        children();
1471    }
1472}
1473raindrop(r=25, thick=20, anchor="cap");
1474```
1475
1476If you want multiple named anchors, just add them to the list of anchors:
1477
1478```openscad-FlatSpin,VPD=150
1479include <BOSL2/std.scad>
1480module raindrop(r, thick, anchor=CENTER, spin=0, orient=UP) {
1481    anchors = [
1482        named_anchor("captop", [0,r/sin(45), thick/2], BACK+UP,   0),
1483        named_anchor("cap",    [0,r/sin(45), 0      ], BACK,      0),
1484        named_anchor("capbot", [0,r/sin(45),-thick/2], BACK+DOWN, 0)
1485    ];
1486    attachable(anchor,spin,orient, r=r, l=thick, anchors=anchors) {
1487        linear_extrude(height=thick, center=true) {
1488            circle(r=r);
1489            back(r*sin(45)) zrot(45) square(r, center=true);
1490        }
1491        children();
1492    }
1493}
1494raindrop(r=15, thick=10) show_anchors();
1495```
1496
1497Sometimes the named anchor you want to add may be at a point that is reached through a complicated
1498set of translations and rotations.  One quick way to calculate that point is to reproduce those
1499transformations in a transformation matrix chain.  This is simplified by how you can use the
1500function forms of almost all the transformation modules to get the transformation matrices, and
1501chain them together with matrix multiplication.  For example, if you have:
1502
1503```
1504scale([1.1, 1.2, 1.3]) xrot(15) zrot(25) right(20) sphere(d=1);
1505```
1506
1507and you want to calculate the center point of the sphere, you can do it like:
1508
1509```
1510sphere_pt = apply(
1511    scale([1.1, 1.2, 1.3]) * xrot(15) * zrot(25) * right(20),
1512    [0,0,0]
1513);
1514```
1515
1516