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()`,
1246literally.
1247
1248### Pass-through Attachables
1249The simplest way to make your own attachable module is to simply pass
1250through to a pre-existing attachable submodule. This could be
1251appropriate if you want to rename a module, or if the anchors of an
1252existing module are suited to (or good enough for) your object. In
1253order for your attachable module to work properly you need to accept
1254the `anchor`, `spin` and `orient` parameters, give them suitable
1255defaults, and pass them to the attachable submodule. Don't forget to
1256pass the children to the attachable submodule as well, or your new
1257module will ignore its children.
1258
1259```openscad-3D
1260include <BOSL2/std.scad>
1261$fn=32;
1262module cutcube(anchor=CENTER,spin=0,orient=UP)
1263{
1264 tag_scope(){
1265 diff()
1266 cuboid(15, rounding=2, anchor=anchor,spin=spin,orient=orient){
1267 tag("remove")attach(TOP)cuboid(5);
1268 children();
1269 }
1270 }
1271}
1272diff()
1273cutcube()
1274 tag("remove")attach(RIGHT) cyl(d=2,h=8);
1275```
1276
1277### Prismoidal/Cuboidal Attachables
1278To make a cuboidal or prismoidal shape attachable, you use the `size`, `size2`, and `offset`
1279arguments of `attachable()`.
1280
1281In the most basic form, where the shape is fully cuboid, with top and bottom of the same size,
1282and directly over one another, you can just use `size=`.
1283
1284```openscad-3D
1285include <BOSL2/std.scad>
1286module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
1287 attachable(anchor,spin,orient, size=[s*3,s,s]) {
1288 union() {
1289 xcopies(2*s) cube(s, center=true);
1290 xcyl(h=2*s, d=s/4);
1291 }
1292 children();
1293 }
1294}
1295cubic_barbell(100) show_anchors(60);
1296```
1297
1298When the shape is prismoidal, where the top is a different size from the bottom, you can use
1299the `size2=` argument as well. While `size=` takes all three axes sizes, the `size2=` argument
1300only takes the [X,Y] sizes of the top of the shape.
1301
1302```openscad-3D
1303include <BOSL2/std.scad>
1304module prismoidal(size=[100,100,100], scale=0.5, anchor=CENTER, spin=0, orient=UP) {
1305 attachable(anchor,spin,orient, size=size, size2=[size.x, size.y]*scale) {
1306 hull() {
1307 up(size.z/2-0.005)
1308 linear_extrude(height=0.01, center=true)
1309 square([size.x,size.y]*scale, center=true);
1310 down(size.z/2-0.005)
1311 linear_extrude(height=0.01, center=true)
1312 square([size.x,size.y], center=true);
1313 }
1314 children();
1315 }
1316}
1317prismoidal([100,60,30], scale=0.5) show_anchors(20);
1318```
1319
1320When the top of the prismoid can be shifted away from directly above the bottom, you can use
1321the `shift=` argument. The `shift=` argument takes an [X,Y] vector of the offset of the center
1322of the top from the XY center of the bottom of the shape.
1323
1324```openscad-3D
1325include <BOSL2/std.scad>
1326module prismoidal(size=[100,100,100], scale=0.5, shift=[0,0], anchor=CENTER, spin=0, orient=UP) {
1327 attachable(anchor,spin,orient, size=size, size2=[size.x, size.y]*scale, shift=shift) {
1328 hull() {
1329 translate([shift.x, shift.y, size.z/2-0.005])
1330 linear_extrude(height=0.01, center=true)
1331 square([size.x,size.y]*scale, center=true);
1332 down(size.z/2-0.005)
1333 linear_extrude(height=0.01, center=true)
1334 square([size.x,size.y], center=true);
1335 }
1336 children();
1337 }
1338}
1339prismoidal([100,60,30], scale=0.5, shift=[-30,20]) show_anchors(20);
1340```
1341
1342In the case that the prismoid is not oriented vertically, (ie, where the `shift=` or `size2=`
1343arguments should refer to a plane other than XY) you can use the `axis=` argument. This lets
1344you make prismoids naturally oriented forwards/backwards or sideways.
1345
1346```openscad-3D
1347include <BOSL2/std.scad>
1348module yprismoidal(
1349 size=[100,100,100], scale=0.5, shift=[0,0],
1350 anchor=CENTER, spin=0, orient=UP
1351) {
1352 attachable(
1353 anchor, spin, orient,
1354 size=size, size2=point2d(size)*scale,
1355 shift=shift, axis=BACK
1356 ) {
1357 xrot(-90) hull() {
1358 translate([shift.x, shift.y, size.z/2-0.005])
1359 linear_extrude(height=0.01, center=true)
1360 square([size.x,size.y]*scale, center=true);
1361 down(size.z/2-0.005)
1362 linear_extrude(height=0.01, center=true)
1363 square([size.x,size.y], center=true);
1364 }
1365 children();
1366 }
1367}
1368yprismoidal([100,60,30], scale=1.5, shift=[20,20]) show_anchors(20);
1369```
1370
1371
1372### Cylindrical Attachables
1373To make a cylindrical shape attachable, you use the `l`, and `r`/`d`, args of `attachable()`.
1374
1375```openscad-3D
1376include <BOSL2/std.scad>
1377module twistar(l,r,d, anchor=CENTER, spin=0, orient=UP) {
1378 r = get_radius(r=r,d=d,dflt=1);
1379 attachable(anchor,spin,orient, r=r, l=l) {
1380 linear_extrude(height=l, twist=90, slices=20, center=true, convexity=4)
1381 star(n=20, r=r, ir=r*0.9);
1382 children();
1383 }
1384}
1385twistar(l=100, r=40) show_anchors(20);
1386```
1387
1388If the cylinder is elipsoidal in shape, you can pass the unequal X/Y sizes as a 2-item vector
1389to the `r=` or `d=` argument.
1390
1391```openscad-3D
1392include <BOSL2/std.scad>
1393module ovalstar(l,rx,ry, anchor=CENTER, spin=0, orient=UP) {
1394 attachable(anchor,spin,orient, r=[rx,ry], l=l) {
1395 linear_extrude(height=l, center=true, convexity=4)
1396 scale([1,ry/rx,1])
1397 star(n=20, r=rx, ir=rx*0.9);
1398 children();
1399 }
1400}
1401ovalstar(l=100, rx=50, ry=30) show_anchors(20);
1402```
1403
1404For cylindrical shapes that aren't oriented vertically, use the `axis=` argument.
1405
1406```openscad-3D
1407include <BOSL2/std.scad>
1408module ytwistar(l,r,d, anchor=CENTER, spin=0, orient=UP) {
1409 r = get_radius(r=r,d=d,dflt=1);
1410 attachable(anchor,spin,orient, r=r, l=l, axis=BACK) {
1411 xrot(-90)
1412 linear_extrude(height=l, twist=90, slices=20, center=true, convexity=4)
1413 star(n=20, r=r, ir=r*0.9);
1414 children();
1415 }
1416}
1417ytwistar(l=100, r=40) show_anchors(20);
1418```
1419
1420### Conical Attachables
1421To make a conical shape attachable, you use the `l`, `r1`/`d1`, and `r2`/`d2`, args of
1422`attachable()`.
1423
1424```openscad-3D
1425include <BOSL2/std.scad>
1426module twistar(l, r,r1,r2, d,d1,d2, anchor=CENTER, spin=0, orient=UP) {
1427 r1 = get_radius(r1=r1,r=r,d1=d1,d=d,dflt=1);
1428 r2 = get_radius(r1=r2,r=r,d1=d2,d=d,dflt=1);
1429 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
1430 linear_extrude(height=l, twist=90, scale=r2/r1, slices=20, center=true, convexity=4)
1431 star(n=20, r=r1, ir=r1*0.9);
1432 children();
1433 }
1434}
1435twistar(l=100, r1=40, r2=20) show_anchors(20);
1436```
1437
1438If the cone is ellipsoidal in shape, you can pass the unequal X/Y sizes as a 2-item vectors
1439to the `r1=`/`r2=` or `d1=`/`d2=` arguments.
1440
1441```openscad-3D
1442include <BOSL2/std.scad>
1443module ovalish(l,rx1,ry1,rx2,ry2, anchor=CENTER, spin=0, orient=UP) {
1444 attachable(anchor,spin,orient, r1=[rx1,ry1], r2=[rx2,ry2], l=l) {
1445 hull() {
1446 up(l/2-0.005)
1447 linear_extrude(height=0.01, center=true)
1448 ellipse([rx2,ry2]);
1449 down(l/2-0.005)
1450 linear_extrude(height=0.01, center=true)
1451 ellipse([rx1,ry1]);
1452 }
1453 children();
1454 }
1455}
1456ovalish(l=100, rx1=50, ry1=30, rx2=30, ry2=50) show_anchors(20);
1457```
1458
1459For conical shapes that are not oriented vertically, use the `axis=` argument to indicate the
1460direction of the primary shape axis:
1461
1462```openscad-3D
1463include <BOSL2/std.scad>
1464module ytwistar(l, r,r1,r2, d,d1,d2, anchor=CENTER, spin=0, orient=UP) {
1465 r1 = get_radius(r1=r1,r=r,d1=d1,d=d,dflt=1);
1466 r2 = get_radius(r1=r2,r=r,d1=d2,d=d,dflt=1);
1467 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK) {
1468 xrot(-90)
1469 linear_extrude(height=l, twist=90, scale=r2/r1, slices=20, center=true, convexity=4)
1470 star(n=20, r=r1, ir=r1*0.9);
1471 children();
1472 }
1473}
1474ytwistar(l=100, r1=40, r2=20) show_anchors(20);
1475```
1476
1477### Spherical Attachables
1478To make a spherical shape attachable, you use the `r`/`d` args of `attachable()`.
1479
1480```openscad-3D
1481include <BOSL2/std.scad>
1482module spikeball(r, d, anchor=CENTER, spin=0, orient=UP) {
1483 r = get_radius(r=r,d=d,dflt=1);
1484 attachable(anchor,spin,orient, r=r*1.1) {
1485 union() {
1486 sphere_copies(r=r, n=512, cone_ang=180) cylinder(r1=r/10, r2=0, h=r/10);
1487 sphere(r=r);
1488 }
1489 children();
1490 }
1491}
1492spikeball(r=50) show_anchors(20);
1493```
1494
1495If the shape is an ellipsoid, you can pass a 3-item vector of sizes to `r=` or `d=`.
1496
1497```openscad-3D
1498include <BOSL2/std.scad>
1499module spikeball(r, d, scale, anchor=CENTER, spin=0, orient=UP) {
1500 r = get_radius(r=r,d=d,dflt=1);
1501 attachable(anchor,spin,orient, r=r*1.1*scale) {
1502 union() {
1503 sphere_copies(r=r, n=512, scale=scale, cone_ang=180) cylinder(r1=r/10, r2=0, h=r/10);
1504 scale(scale) sphere(r=r);
1505 }
1506 children();
1507 }
1508}
1509spikeball(r=50, scale=[0.75,1,1.5]) show_anchors(20);
1510```
1511
1512### VNF Attachables
1513If the shape just doesn't fit into any of the above categories, and you constructed it as a
1514[VNF](vnf.scad), you can use the VNF itself to describe the geometry with the `vnf=` argument.
1515
1516There are two variations to how anchoring can work for VNFs. When `extent=true`, (the default)
1517then a plane is projected out from the origin, perpendicularly in the direction of the anchor,
1518to the furthest distance that intersects with the VNF shape. The anchor point is then the
1519center of the points that still intersect that plane.
1520
1521```openscad-FlatSpin,VPD=500
1522include <BOSL2/std.scad>
1523module stellate_cube(s=100, anchor=CENTER, spin=0, orient=UP) {
1524 s2 = 3 * s;
1525 verts = [
1526 [0,0,-s2*sqrt(2)/2],
1527 each down(s/2, p=path3d(square(s,center=true))),
1528 each zrot(45, p=path3d(square(s2,center=true))),
1529 each up(s/2, p=path3d(square(s,center=true))),
1530 [0,0,s2*sqrt(2)/2]
1531 ];
1532 faces = [
1533 [0,2,1], [0,3,2], [0,4,3], [0,1,4],
1534 [1,2,6], [1,6,9], [6,10,9], [2,10,6],
1535 [1,5,4], [1,9,5], [9,12,5], [5,12,4],
1536 [4,8,3], [4,12,8], [12,11,8], [11,3,8],
1537 [2,3,7], [3,11,7], [7,11,10], [2,7,10],
1538 [9,10,13], [10,11,13], [11,12,13], [12,9,13]
1539 ];
1540 vnf = [verts, faces];
1541 attachable(anchor,spin,orient, vnf=vnf) {
1542 vnf_polyhedron(vnf);
1543 children();
1544 }
1545}
1546stellate_cube(25) {
1547 attach(UP+RIGHT) {
1548 anchor_arrow(20);
1549 %cube([100,100,0.1],center=true);
1550 }
1551}
1552```
1553
1554When `extent=false`, then the anchor point will be the furthest intersection of the VNF with
1555the anchor ray from the origin. The orientation of the anchor point will be the normal of the
1556face at the intersection. If the intersection is at an edge or corner, then the orientation
1557will bisect the angles between the faces.
1558
1559```openscad-VPD=1250
1560include <BOSL2/std.scad>
1561module stellate_cube(s=100, anchor=CENTER, spin=0, orient=UP) {
1562 s2 = 3 * s;
1563 verts = [
1564 [0,0,-s2*sqrt(2)/2],
1565 each down(s/2, p=path3d(square(s,center=true))),
1566 each zrot(45, p=path3d(square(s2,center=true))),
1567 each up(s/2, p=path3d(square(s,center=true))),
1568 [0,0,s2*sqrt(2)/2]
1569 ];
1570 faces = [
1571 [0,2,1], [0,3,2], [0,4,3], [0,1,4],
1572 [1,2,6], [1,6,9], [6,10,9], [2,10,6],
1573 [1,5,4], [1,9,5], [9,12,5], [5,12,4],
1574 [4,8,3], [4,12,8], [12,11,8], [11,3,8],
1575 [2,3,7], [3,11,7], [7,11,10], [2,7,10],
1576 [9,10,13], [10,11,13], [11,12,13], [12,9,13]
1577 ];
1578 vnf = [verts, faces];
1579 attachable(anchor,spin,orient, vnf=vnf, extent=false) {
1580 vnf_polyhedron(vnf);
1581 children();
1582 }
1583}
1584stellate_cube() show_anchors(50);
1585```
1586
1587```openscad-3D
1588include <BOSL2/std.scad>
1589$fn=32;
1590R = difference(circle(10), right(2, circle(9)));
1591linear_sweep(R,height=10,atype="hull")
1592 attach(RIGHT) anchor_arrow();
1593```
1594
1595
1596## Making Named Anchors
1597While vector anchors are often useful, sometimes there are logically extra attachment points that
1598aren't on the perimeter of the shape. This is what named string anchors are for. For example,
1599the `teardrop()` shape uses a cylindrical geometry for it's vector anchors, but it also provides
1600a named anchor "cap" that is at the tip of the hat of the teardrop shape.
1601
1602Named anchors are passed as an array of `named_anchor()`s to the `anchors=` argument of `attachable()`.
1603The `named_anchor()` call takes a name string, a positional point, an orientation vector, and a spin.
1604The name is the name of the anchor. The positional point is where the anchor point is at. The
1605orientation vector is the direction that a child attached at that anchor point should be oriented.
1606The spin is the number of degrees that an attached child should be rotated counter-clockwise around
1607the orientation vector. Spin is optional, and defaults to 0.
1608
1609To make a simple attachable shape similar to a `teardrop()` that provides a "cap" anchor, you may
1610define it like this:
1611
1612```openscad-3D
1613include <BOSL2/std.scad>
1614module raindrop(r, thick, anchor=CENTER, spin=0, orient=UP) {
1615 anchors = [
1616 named_anchor("cap", [0,r/sin(45),0], BACK, 0)
1617 ];
1618 attachable(anchor,spin,orient, r=r, l=thick, anchors=anchors) {
1619 linear_extrude(height=thick, center=true) {
1620 circle(r=r);
1621 back(r*sin(45)) zrot(45) square(r, center=true);
1622 }
1623 children();
1624 }
1625}
1626raindrop(r=25, thick=20, anchor="cap");
1627```
1628
1629If you want multiple named anchors, just add them to the list of anchors:
1630
1631```openscad-FlatSpin,VPD=150
1632include <BOSL2/std.scad>
1633module raindrop(r, thick, anchor=CENTER, spin=0, orient=UP) {
1634 anchors = [
1635 named_anchor("captop", [0,r/sin(45), thick/2], BACK+UP, 0),
1636 named_anchor("cap", [0,r/sin(45), 0 ], BACK, 0),
1637 named_anchor("capbot", [0,r/sin(45),-thick/2], BACK+DOWN, 0)
1638 ];
1639 attachable(anchor,spin,orient, r=r, l=thick, anchors=anchors) {
1640 linear_extrude(height=thick, center=true) {
1641 circle(r=r);
1642 back(r*sin(45)) zrot(45) square(r, center=true);
1643 }
1644 children();
1645 }
1646}
1647raindrop(r=15, thick=10) show_anchors();
1648```
1649
1650Sometimes the named anchor you want to add may be at a point that is reached through a complicated
1651set of translations and rotations. One quick way to calculate that point is to reproduce those
1652transformations in a transformation matrix chain. This is simplified by how you can use the
1653function forms of almost all the transformation modules to get the transformation matrices, and
1654chain them together with matrix multiplication. For example, if you have:
1655
1656```
1657scale([1.1, 1.2, 1.3]) xrot(15) zrot(25) right(20) sphere(d=1);
1658```
1659
1660and you want to calculate the center point of the sphere, you can do it like:
1661
1662```
1663sphere_pt = apply(
1664 scale([1.1, 1.2, 1.3]) * xrot(15) * zrot(25) * right(20),
1665 [0,0,0]
1666);
1667```
1668
1669
1670## Overriding Standard Anchors
1671
1672Sometimes you may want to use the standard anchors but override some
1673of them. Returning to the square barebell example above, the anchors
1674at the right and left sides are on the cubes at each end, but the
1675anchors at x=0 are in floating in space. For prismoidal/cubic anchors
1676in 3D and trapezoidal/rectangular anchors in 2D we can override a single anchor by
1677specifying the override option and giving the anchor that is being
1678overridden, and then the replacement in the form
1679`[position, direction, spin]`. Most often you will only want to
1680override the position. If you omit the other list items then the
1681value drived from the standard anchor will be used. Below we override
1682position of the FWD anchor:
1683
1684```
1685module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
1686 override = [
1687 [FWD, [[0,-s/8,0]]]
1688 ];
1689 attachable(anchor,spin,orient, size=[s*3,s,s],override=override) {
1690 union() {
1691 xcopies(2*s) cube(s, center=true);
1692 xcyl(h=2*s, d=s/4);
1693 }
1694 children();
1695 }
1696}
1697cubic_barbell(100) show_anchors(60);
1698```
1699
1700Note how the FWD anchor is now rooted on the cylindrical portion. If
1701you wanted to also change its direction and spin you could do it like
1702this:
1703
1704```
1705module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
1706 override = [
1707 [FWD, [[0,-s/8,0], FWD+LEFT, 225]]
1708 ];
1709 attachable(anchor,spin,orient, size=[s*3,s,s],override=override) {
1710 union() {
1711 xcopies(2*s) cube(s, center=true);
1712 xcyl(h=2*s, d=s/4);
1713 }
1714 children();
1715 }
1716}
1717cubic_barbell(100) show_anchors(60);
1718```
1719
1720In the above example we give three values for the override. As
1721before, the first one places the anchor on the cylinder. We have
1722added the second entry which points the anchor off to the left.
1723The third entry gives a spin override, whose effect is shown by the
1724position of the red flag on the arrow. If you want to override all of
1725the x=0 anchors to be on the cylinder, with their standard directions,
1726you can do that by supplying a list:
1727```
1728module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
1729 override = [
1730 for(j=[-1:1:1], k=[-1:1:1])
1731 if ([j,k]!=[0,0]) [[0,j,k], [s/8*unit([0,j,k])]]
1732 ];
1733 attachable(anchor,spin,orient, size=[s*3,s,s],override=override) {
1734 union() {
1735 xcopies(2*s) cube(s, center=true);
1736 xcyl(h=2*s, d=s/4);
1737 }
1738 children();
1739 }
1740}
1741cubic_barbell(100) show_anchors(30);
1742```
1743
1744Now all of the anchors in the middle are all rooted to the cylinder. Another
1745way to do the same thing is to use a function literal for override.
1746It will be called with the anchor as its argument and needs to return undef to just use
1747the default, or a `[position, direction, spin]` triple to override the
1748default. As before, you can omit values to keep their default.
1749Here is the same example using a function literal for the override:
1750
1751```
1752module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
1753 override = function (anchor)
1754 anchor.x!=0 || anchor==CTR ? undef // Keep these
1755 : [s/8*unit(anchor)];
1756 attachable(anchor,spin,orient, size=[s*3,s,s],override=override) {
1757 union() {
1758 xcopies(2*s) cube(s, center=true);
1759 xcyl(h=2*s, d=s/4);
1760 }
1761 children();
1762 }
1763}
1764cubic_barbell(100) show_anchors(30);
1765```