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