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