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
104Here we convert a 30 deg angle into an anchor using [cylindrical_to_xyz()](https://github.com/BelfrySCAD/BOSL2/wiki/coords.scad#function-cylindrical_to_xyz)
105
106```openscad-3D
107include <BOSL2/std.scad>
108cylinder(r1=25, r2=15, h=60, anchor=cylindrical_to_xyz(1,30,1));
109```
110
111---
112
113For Spherical type attachables, you can pass a vector that points at any arbitrary place on
114the surface of the sphere:
115
116```openscad-3D
117include <BOSL2/std.scad>
118sphere(r=50, anchor=TOP);
119```
120
121```openscad-3D
122include <BOSL2/std.scad>
123sphere(r=50, anchor=TOP+FRONT);
124```
125
126Here the [spherical_to_xyz()](https://github.com/BelfrySCAD/BOSL2/wiki/coords.scad#function-spherical_to_xyz) function converts spherical coordinates into
127a vector you can use as an anchor:
128
129```openscad-3D
130include <BOSL2/std.scad>
131sphere(r=50, anchor=spherical_to_xyz(1,-30,60));
132```
133
134---
135
136Some attachable shapes may provide specific named anchors for shape-specific anchoring. These
137will be given as strings and will be specific to that type of
138attachable. When named anchors are supported, they are listed in a
139"Named Anchors" section of the documentation for the module. The
140`teardrop()` attachable, for example, has a named anchor called "cap" and in 2D the
141`star()` attachable has anchors labeled by tip number:
142
143```openscad-3D
144include <BOSL2/std.scad>
145teardrop(d=100, l=20, anchor="cap");
146```
147
148```openscad-2D
149include <BOSL2/std.scad>
150star(n=7, od=30, id=20, anchor="tip2");
151```
152
153---
154
155Some shapes, for backwards compatibility reasons, can take a `center=` argument. This just
156overrides the `anchor=` argument. A `center=true` argument is the same as `anchor=CENTER`.
157A `center=false` argument chooses the anchor to match the behavior of
158the builtin version: for a cube it is the same as `anchor=[-1,-1,-1]` but for a
159cylinder, it is the same as `anchor=BOTTOM`.
160
161```openscad-3D
162include <BOSL2/std.scad>
163cube([50,40,30],center=true);
164```
165
166```openscad-3D
167include <BOSL2/std.scad>
168cube([50,40,30],center=false);
169```
170
171---
172
173Most 2D shapes provided by BOSL2 are also anchorable. The built-in `square()` and `circle()`
174modules have been overridden to make them attachable.. The `anchor=` options for 2D
175shapes treat 2D vectors as expected. Special handling occurs with 3D
176vectors: if the Y coordinate is zero and the Z coordinate is nonzero,
177then the Z coordinate is used to replace the Y coordinate. This is
178done so that you can use the TOP and BOTTOM names as anchor for 2D
179shapes.
180
181
182```openscad-2D
183include <BOSL2/std.scad>
184square([40,30], anchor=BACK+LEFT);
185```
186
187```openscad-2D
188include <BOSL2/std.scad>
189circle(d=50, anchor=BACK);
190```
191
192```openscad-2D
193include <BOSL2/std.scad>
194hexagon(d=50, anchor=LEFT);
195```
196
197```openscad-2D
198include <BOSL2/std.scad>
199ellipse(d=[50,30], anchor=FRONT);
200
201This final 2D example shows using the 3D anchor, TOP, with a 2D
202object. Also notice how the pentagon anchors to its most extreme point on
203the Y+ axis.
204
205```openscad-2D
206include <BOSL2/std.scad>
207pentagon(d=50, anchor=TOP);
208```
209
210
211## Spin
212You can spin attachable objects around the origin using the `spin=`
213argument. The spin applies **after** anchoring, so depending on how
214you anchor an object, its spin may not be about its center. This
215means that spin can have an effect even on rotationally symmetric
216objects like spheres and cylinders. You specify the spin in degrees.
217A positive number will result in a counter-clockwise spin around the Z
218axis (as seen from above), and a negative number will make a clockwise
219spin:
220
221```openscad-3D
222include <BOSL2/std.scad>
223cube([20,20,40], center=true, spin=45);
224```
225
226You can also spin around other axes, or multiple axes at once, by giving 3 angles (in degrees) to
227`spin=` as a vector, like [Xang,Yang,Zang]. Similarly to `rotate()`,
228the rotations apply in the order given, X-axis spin, then Y-axis, then Z-axis:
229
230```openscad-3D
231include <BOSL2/std.scad>
232cube([20,20,40], center=true, spin=[10,20,30]);
233```
234
235This example shows a cylinder which has been anchored at its FRONT,
236with a rotated copy in gray. The rotation is performed around the
237origin, but the cylinder is off the origin, so the rotation **does**
238have an effect on the cylinder, even though the cylinder has
239rotational symmetry.
240
241```openscad-3D
242include <BOSL2/std.scad>
243cylinder(h=40,d=20,anchor=FRONT+BOT);
244%cylinder(h=40.2,d=20,anchor=FRONT+BOT,spin=40);
245```
246
247
248
249You can also apply spin to 2D shapes from BOSL2, though only by scalar angle:
250
251```openscad-2D
252include <BOSL2/std.scad>
253square([40,30], spin=30);
254```
255
256```openscad-2D
257include <BOSL2/std.scad>
258ellipse(d=[40,30], spin=30);
259```
260
261
262## Orientation
263Another way to specify a rotation for an attachable shape, is to pass a 3D vector via the
264`orient=` argument. This lets you specify what direction to tilt the top of the shape towards.
265For example, you can make a cone that is tilted up and to the right like this:
266
267```openscad-3D
268include <BOSL2/std.scad>
269cylinder(h=100, r1=50, r2=20, orient=UP+RIGHT);
270```
271
272More precisely, the Z direction of the shape is rotated to align with
273the vector you specify. Two dimensional attachables, which have no Z vector,
274do not accept the `orient=` argument.
275
276
277## Mixing Anchoring, Spin, and Orientation
278When giving `anchor=`, `spin=`, and `orient=`, they are applied anchoring first, spin second,
279then orient last. For example, here's a cube:
280
281```openscad-3D
282include <BOSL2/std.scad>
283cube([20,20,50]);
284```
285
286You can center it with an `anchor=CENTER` argument:
287
288```openscad-3D
289include <BOSL2/std.scad>
290cube([20,20,50], anchor=CENTER);
291```
292
293Add a 45 degree spin:
294
295```openscad-3D
296include <BOSL2/std.scad>
297cube([20,20,50], anchor=CENTER, spin=45);
298```
299
300Now tilt the top up and forward:
301
302```openscad-3D
303include <BOSL2/std.scad>
304cube([20,20,50], anchor=CENTER, spin=45, orient=UP+FWD);
305```
306
307For 2D shapes, you can mix `anchor=` with `spin=`, but not with `orient=`.
308
309```openscad-2D
310include <BOSL2/std.scad>
311square([40,30], anchor=BACK+LEFT, spin=30);
312```
313
314## Positioning Children
315
316Positioning is a powerful method for placing an object relative to
317another object. You do this by making the second object a child of
318the first object. By default, the child's anchor point will be
319aligned with the center of the parent. The default anchor for `cyl()`
320is CENTER, and in this case, the cylinder is centered on the cube's center
321
322```openscad-3D
323include <BOSL2/std.scad>
324up(13) cube(50)
325 cyl(d=25,l=95);
326```
327
328With `cylinder()` the default anchor is BOTTOM. It's hard to tell,
329but the cylinder's bottom is placed at the center of the cube.
330
331```openscad-3D
332include <BOSL2/std.scad>
333cube(50)
334 cylinder(d=25,h=75);
335```
336
337If you explicitly anchor the child object then the anchor you choose will be aligned
338with the center point of the parent object. In this example the right
339side of the cylinder is aligned with the center of the cube.
340
341
342```openscad-3D
343include <BOSL2/std.scad>
344cube(50,anchor=FRONT)
345 cylinder(d=25,h=95,anchor=RIGHT);
346```
347
348The `position()` module enables you to specify where on the parent to
349position the child object. You give `position()` an anchor point on
350the parent, and the child's anchor point is aligned with the specified
351parent anchor point. In this example the LEFT anchor of the cylinder is positioned on the
352RIGHT anchor of the cube.
353
354```openscad-3D
355include <BOSL2/std.scad>
356cube(50,anchor=FRONT)
357 position(RIGHT) cylinder(d=25,h=75,anchor=LEFT);
358```
359
360Using this mechanism you can position objects relative to other
361objects which are in turn positioned relative to other objects without
362having to keep track of the transformation math.
363
364```openscad-3D
365include <BOSL2/std.scad>
366cube([50,50,30],center=true)
367 position(TOP+RIGHT) cube([25,40,10], anchor=RIGHT+BOT)
368 position(LEFT+FRONT+TOP) cube([12,12,8], anchor=LEFT+FRONT+BOT)
369 cylinder(h=10,r=3);
370```
371
372The positioning mechanism is not magical: it simply applies a
373`translate()` operation to the child. You can still apply your own
374additional translations or other transformations if you wish. For
375example, you can position an object 5 units from the right edge:
376
377```openscad-3D
378include<BOSL2/std.scad>
379cube([50,50,20],center=true)
380 position(TOP+RIGHT) left(5) cube([4,50,10], anchor=RIGHT+BOT);
381```
382
383
384
385Positioning objects works the same way in 2D.
386
387```openscad-2D
388include<BOSL2/std.scad>
389square(10)
390 position(RIGHT) square(3,anchor=LEFT);
391```
392
393## Using position() with orient()
394
395When positioning an object near an edge or corner you may wish to
396orient the object relative to some face other than the TOP face that
397meets at that edge or corner. You can always apply `rot()` to
398change the orientation of the child object, but in order to do this,
399you need to figure out the correct rotation. The `orient()` module provides a
400mechanism for re-orienting the child() that eases this burden:
401it can orient the child relative to the parent anchor directions. This is different
402than giving an `orient=` argument to the child, because that orients
403relative to the parent's global coordinate system by just using the vector
404directly, instead of orienting to the parent's anchor, which takes
405account of face orientation. A series of three
406examples shows the different results. In the first example, we use
407only `position()`. The child cube is erected pointing upwards, in the
408Z direction. In the second example we use `orient=RIGHT` in the child
409and the result is that the child object points in the X+ direction,
410without regard for the shape of the parent object. In the final
411example we apply `orient(RIGHT)` and the child is oriented
412relative to the slanted right face of the parent using the parent
413RIGHT anchor.
414
415```openscad-3D
416include<BOSL2/std.scad>
417prismoid([50,50],[30,30],h=40)
418 position(RIGHT+TOP)
419 cube([15,15,25],anchor=RIGHT+BOT);
420```
421
422
423```openscad-3D
424include<BOSL2/std.scad>
425prismoid([50,50],[30,30],h=40)
426 position(RIGHT+TOP)
427 cube([15,15,25],orient=RIGHT,anchor=LEFT+BOT);
428```
429
430
431```openscad-3D
432include<BOSL2/std.scad>
433prismoid([50,50],[30,30],h=40)
434 position(RIGHT+TOP)
435 orient(RIGHT)
436 cube([15,15,25],anchor=BACK+BOT);
437```
438
439You may have noticed that the children in the above three examples
440have different anchors. Why is that? The first and second examples
441differ because anchoring up and anchoring to the right require
442anchoring on opposite sides of the child. But the third case differs
443because the spin has changed. The examples below show the same models
444but with arrows replacing the child cube. The red flags on the arrows
445mark the zero spin direction. Examine the red flags to see how the spin
446changes. The Y+ direction of the child will point towards that red
447flag.
448
449```openscad-3D
450include<BOSL2/std.scad>
451prismoid([50,50],[30,30],h=40)
452 position(RIGHT+TOP)
453 anchor_arrow(40);
454```
455
456
457```openscad-3D
458include<BOSL2/std.scad>
459prismoid([50,50],[30,30],h=40)
460 position(RIGHT+TOP)
461 anchor_arrow(40, orient=RIGHT);
462```
463
464```openscad-3D
465include<BOSL2/std.scad>
466prismoid([50,50],[30,30],h=40)
467 position(RIGHT+TOP)
468 orient(RIGHT)
469 anchor_arrow(40);
470```
471
472
473## Aligning children with align()
474
475You may have noticed that with position() and orient(), specifying the
476child anchors to position objects flush with their parent can be
477annoying, or sometimes even tricky. You can simplify this task by
478using the align() module. This module positions children on faces
479of a parent and aligns to edges or corners, while picking the correct anchor points on
480the children so that the children line up correctly with the parent.
481
482In the simplest case, if you want to place a child on the RIGHT side
483of its parent, you need to anchor the child to its LEFT anchor:
484
485```openscad-3D
486include<BOSL2/std.scad>
487cuboid([50,40,15])
488 position(RIGHT)
489 color("lightblue")cuboid(5,anchor=LEFT);
490```
491
492When you use align() it automatically determines the correct anchor to
493use for the child and this anchor overrides any anchor specified to
494the child: any anchor you specify for the child is ignored.
495
496```openscad-3D
497include<BOSL2/std.scad>
498cuboid([50,40,15])
499 align(RIGHT)
500 color("lightblue")cuboid(5);
501```
502
503To place the child on top of the parent in the corner you can do use
504align as shown below instead of specifying the RIGHT+FRONT+BOT anchor
505with position():
506
507```openscad-3D
508include<BOSL2/std.scad>
509cuboid([50,40,15])
510 align(TOP,RIGHT+FRONT)
511 color("lightblue")prismoid([10,5],[7,4],height=4);
512```
513
514Both position() and align() can accept a list of anchor locations and
515makes several copies of the children, but
516if you want the children positioned flush, each copy
517requires a different anchor, so it is impossible to do this with a
518single call to position(), but easily done using align():
519
520```openscad-3D
521include<BOSL2/std.scad>
522cuboid([50,40,15])
523 align(TOP,[RIGHT,LEFT])
524 color("lightblue")prismoid([10,5],[7,4],height=4);
525```
526
527If you want the children close to the edge but not actually flush you
528can use the `inset=` parameter of align to achieve this:
529
530```openscad-3D
531include<BOSL2/std.scad>
532cuboid([50,40,15])
533 align(TOP,[FWD,RIGHT,LEFT,BACK],inset=3)
534 color("lightblue")prismoid([10,5],[7,4],height=4);
535```
536
537If you spin the children then align will still do the right thing
538
539```openscad-3D
540include<BOSL2/std.scad>
541cuboid([50,40,15])
542 align(TOP,[RIGHT,LEFT])
543 color("lightblue")prismoid([10,5],[7,4],height=4,spin=90);
544```
545
546If you orient the object DOWN it will be attached from its top anchor,
547correctly aligned.
548
549```openscad-3D
550include<BOSL2/std.scad>
551cuboid([50,40,15])
552 align(TOP,RIGHT)
553 color("lightblue")prismoid([10,5],[7,4],height=4,orient=DOWN);
554```
555
556Note that align() never changes the orientation of the children. If
557you put the blue prismoid on the right side the anchors line up but
558the edges of the child and parent don't.
559
560```openscad-3D
561include<BOSL2/std.scad>
562prismoid(50,30,25){
563 align(RIGHT,TOP)
564 color("lightblue")prismoid([10,5],[7,4],height=4);
565}
566```
567
568If you apply spin that is not a multiple of 90 degrees then alignment
569will line up the corner
570
571```openscad-3D
572include<BOSL2/std.scad>
573cuboid([50,40,15])
574 align(TOP,RIGHT)
575 color("lightblue")cuboid(8,spin=33);
576```
577
578You can also attach objects to a cylinder. If you use the usual cubic
579anchors then a cube will attach on a face as shown here:
580
581```openscad-3D
582include<BOSL2/std.scad>
583cyl(h=20,d=10,$fn=128)
584 align(RIGHT,TOP)
585 color("lightblue")cuboid(5);
586```
587
588But with a cylinder you can choose an arbitrary horizontal angle for
589the anchor. If you do this, similar to the case of arbitrary spin,
590the cube will attach on the nearest corner.
591
592```openscad-3D
593include<BOSL2/std.scad>
594cyl(h=20,d=10,$fn=128)
595 align([1,.3],TOP)
596 color("lightblue")cuboid(5);
597```
598
599## Attachment overview
600
601Attachables get their name from their ability to be attached to each
602other. Unlike with positioning, attaching changes the orientation of
603the child object. Think of it like sticking two objects together:
604when you attach an object, it appears on the parent
605relative to the local coordinate system of the parent at the anchor point. To understand
606what this means, imagine the perspective of an ant walking on a
607sphere. The meaning of UP varies depending on where on the sphere the
608ant is standing. If you **attach** a cylinder to the sphere then the cylinder will
609be "up" from the ant's perspective. The first example shows a
610cylinder placed with `position()` so it points up in the global parent
611coordinate system. The second example shows how `attach()` points the
612cylinder UP from the perspective of an ant standing at the anchor
613point on the sphere.
614
615```openscad-3D
616include<BOSL2/std.scad>
617sphere(40)
618 position(RIGHT+TOP) cylinder(r=8,h=20);
619```
620
621
622```openscad-3D
623include<BOSL2/std.scad>
624sphere(40)
625 attach(RIGHT+TOP) cylinder(r=8,h=20);
626```
627
628In the example above, the cylinder's center point is attached to the
629sphere, pointing "up" from the perspective of the sphere's surface.
630For a sphere, a surface normal is defined everywhere that specifies
631what "up" means. But for other objects, it may not be so obvious.
632Usually at edges and corners the direction is the average of the
633direction of the faces that meet there.
634
635When you specify an anchor you are actually specifying both an anchor
636point but also an anchor direction. If you want to visualize this
637direction you can use anchor arrows.
638
639
640## Anchor Directions and Anchor Arrows
641For the ant on the sphere it is obvious which direction is UP; that
642direction corresponds to the Z+ axis. The location of the X and Y
643axes is less clear and in fact it may be arbitrary. One way that is
644useful to show the position and orientation of an anchor point is by
645attaching an anchor arrow to that anchor. As noted before, the small
646red flag points in the direction of the anchor's Y+ axis when the spin
647is zero.
648
649```openscad-3D
650include <BOSL2/std.scad>
651cube(18, center=true)
652 attach(LEFT+TOP)
653 anchor_arrow();
654```
655
656For large objects, you can change the size of the arrow with the `s=` argument.
657
658```openscad-3D
659include <BOSL2/std.scad>
660sphere(d=100)
661 attach(LEFT+TOP)
662 anchor_arrow(s=50);
663```
664
665To show all the standard cardinal anchor points, you can use the [show_anchors()](https://github.com/BelfrySCAD/BOSL2/wiki/attachments.scad#module-show_anchors) module.
666
667```openscad-3D;Big
668include <BOSL2/std.scad>
669cube(40, center=true)
670 show_anchors();
671```
672
673```openscad-3D;Big
674include <BOSL2/std.scad>
675cylinder(h=40, d=40, center=true)
676 show_anchors();
677```
678
679```openscad-3D;Big
680include <BOSL2/std.scad>
681sphere(d=40)
682 show_anchors();
683```
684
685For large objects, you can again change the size of the arrows with the `s=` argument.
686
687```openscad-3D;Big
688include <BOSL2/std.scad>
689prismoid(150,60,100)
690 show_anchors(s=35);
691```
692
693## Parent-Child Anchor Attachment (Double Argument Attachment)
694
695The `attach()` module has two different modes of operation,
696parent-child anchor attachment and parent anchor attachment. These
697are also called double argument attachment and single argument
698attachment. The parent-child anchor attachment, with two arguments,
699is usually easier to use and is more powerful because it supports
700alignment. When you use parent-child anchor attachment you give a
701parent anchor and a child anchor. Imagine pointing the anchor arrows
702on the two objects directly at each other and pushing them together in
703the direction of the arrows until they touch. In many of the examples
704below we show first the two objects with their anchor arrows and then
705the result of the attach operation using those anchors.
706
707```openscad-3D
708include <BOSL2/std.scad>
709cube(50,anchor=BOT) attach(TOP,BOT) anchor_arrow(30);
710right(60)cylinder(d1=30,d2=15,h=25) attach(BOT,BOT) anchor_arrow(30);
711```
712
713```openscad-3D
714include <BOSL2/std.scad>
715cube(50,anchor=BOT)
716 attach(TOP,BOT) cylinder(d1=30,d2=15,h=25);
717```
718
719This example produces the same result as using `align()`, but if the
720parent anchor is not horizontal, then the child is reoriented:
721
722```openscad-3D
723include <BOSL2/std.scad>
724prismoid([50,50],[35,35],h=50,anchor=BOT) attach(RIGHT,BOT) anchor_arrow(30);
725right(60)cylinder(d1=30,d2=15,h=25) attach(BOT,BOT) anchor_arrow(30);
726```
727
728```openscad-3D
729include <BOSL2/std.scad>
730prismoid([50,50],[35,35],h=50,anchor=BOT)
731 attach(RIGHT,BOT) cylinder(d1=30,d2=15,h=25);
732```
733
734In this case we attach the curved side of the cone to a cube by lining
735up the anchor arrows:
736
737```openscad-3D
738include <BOSL2/std.scad>
739cube(50,center=true) attach(RIGHT,BOT) anchor_arrow(30);
740right(80)cylinder(d1=30,d2=15,h=25) attach(LEFT,BOT) anchor_arrow(30);
741```
742
743```openscad-3D
744include <BOSL2/std.scad>
745cube(50,center=true)
746 attach(RIGHT,LEFT) cylinder(d1=30,d2=15,h=25);
747```
748
749Note that this form of attachent overrides any anchor or orientation
750specified in the child: **with parent-child anchor attachment the
751`anchor=` and `orient=` parameters to the child are ignored.**
752
753When you specify attachment using a pair of anchors, the attached
754child can spin around the parent anchor while still being attached at
755the designated anchors: specifying the anchors leaves one unspecified
756degree of freedom. As noted earlier, this ambiguity is resolved by anchors having a
757defined spin which specifies where the Y+ axis is located.
758The way that BOSL2 positions objects can be understood by viewing the
759anchor arrows as shown above, or you can remember these rules:
7601. When attaching to the TOP or BOTTOM: the FRONT of the child points to the front if possible; otherwise the TOP of the child points BACK.
7612. When attaching to other faces, if possible the child's UP anchor will point UP; otherwise, the BACK of the child points up (so the FRONT is pointed down).
762
763To show how this works we use this prismoid where the blue arrow is
764pointing to the front and the green arrow points up. Also note that
765the front left edge is the only right angle.
766
767```openscad-3D
768include <BOSL2/std.scad>
769color_this("orange")
770prismoid([8,8],[6,6],shift=-[1,1],h=8) {
771 attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
772 attach(FWD,BOT) anchor_arrow(s=12);
773}
774```
775
776If we attach this to the TOP by the LEFT side then we get the result
777below. Notice how the green UP arrow is pointing back.
778
779```openscad-3D
780include <BOSL2/std.scad>
781cube(30) attach(TOP,LEFT)
782color_this("orange")
783 prismoid([8,8],[6,6],shift=-[1,1],h=8) {
784 attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
785 attach(FWD,BOT) anchor_arrow(s=12);
786 }
787```
788
789If we attach to the RIGHT using the same LEFT side anchor on the
790prismoid then we get the result below. Note that the green UP anchor
791is pointing UP, in accordance with rule 2 from above.
792
793```openscad-3D
794include <BOSL2/std.scad>
795cube(30) attach(RIGHT,LEFT)
796color_this("orange")
797 prismoid([8,8],[6,6],shift=-[1,1],h=8) {
798 attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
799 attach(FWD,BOT) anchor_arrow(s=12);
800 }
801```
802
803The green UP arrow can always be arranged to point up unless we attach
804either the top or bottom to one of the cube's vertical faces. Here we
805attach the bottom so you can still see both arrows. The blue FRONT
806arrow on the object is pointing down, as expected based on rule 2.
807
808```openscad-3D
809include <BOSL2/std.scad>
810cube(30) attach(RIGHT,BOT)
811color_this("orange")
812 prismoid([8,8],[6,6],shift=-[1,1],h=8) {
813 attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
814 attach(FWD,BOT) anchor_arrow(s=12);
815 }
816```
817
818What do you do if the direction the child appears is not the direction
819you need? To address this issue `attach()` provides a `spin=`
820parameter which spins the attached child around the axis defined by
821the joined anchor vectors. Here is the last example with a rotation
822applied to bring the front anchor back to the front:
823
824```openscad-3D
825include <BOSL2/std.scad>
826cube(30) attach(RIGHT,BOT,spin=-90)
827color_this("orange")
828 prismoid([8,8],[6,6],shift=-[1,1],h=8) {
829 attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
830 attach(FWD,BOT) anchor_arrow(s=12);
831 }
832```
833
834Be aware that specifying `spin=` to `attach()` is not equivalent to
835using the `spin=` argument to the child. Unlike `orient=` and
836`anchor=`, which are ignored, the child `spin=` argument is still
837respected, but it may be difficult to figure out which axis it will
838rotate on. It is more intuitive to ignore the child spin parameter
839and only use the spin parameter to `attach()`. The spin must be
840scalar but need not be a multiple of 90 degrees.
841
842```openscad-3D
843include <BOSL2/std.scad>
844cube(30) attach(RIGHT,BOT,spin=-37)
845color_this("orange")
846 prismoid([8,8],[6,6],shift=-[1,1],h=8) {
847 attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
848 attach(FWD,BOT) anchor_arrow(s=12);
849 }
850```
851
852By default, `attach()` places the child exactly flush with the surface
853of the parent. Sometimes it's useful to have the child overlap the
854parent by translating it into the parent. You can do this with the
855`overlap=` argument to `attach()`. A positive value will cause the
856child to overlap the parent, and a negative value will move the child
857away from the parent, leaving a small gap. In the first example we use a very large value of
858overlap so the cube is sunk deeply into the parent. In the second
859example a large negative overlap value raises the child high above the
860parent.
861
862```openscad-3D
863include <BOSL2/std.scad>
864cuboid(50)
865 attach(TOP,BOT,overlap=15)
866 color("green")cuboid(20);
867```
868
869```openscad-3D
870include <BOSL2/std.scad>
871cube(50,center=true)
872 attach(TOP,BOT,overlap=-20)
873 cyl(d=20,h=20);
874```
875
876Another feature provided by the double argument form of `attach()` is
877alignment, which works in a similar way to `align()`. You can specify
878`align=` to align the attached child to an edge or corner. The
879example below shows five different alignments.
880
881```openscad-3D;Big
882include <BOSL2/std.scad>
883module thing(){
884 color_this("orange")
885 prismoid([8,8],[6,6],shift=-[1,1],h=8) {
886 attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
887 attach(FWD,BOT) anchor_arrow(s=12);
888 }
889}
890prismoid([50,50],[35,35],h=25,anchor=BOT){
891 attach(TOP,BOT,align=FRONT) thing();
892 attach(RIGHT,BOT,align=BOT) thing();
893 attach(RIGHT,BACK,align=FRONT) thing();
894 attach(FRONT,BACK,align=BOT,spin=45) thing();
895 attach(TOP,RIGHT,align=RIGHT,spin=90) thing();
896}
897```
898
899As with `align()` if you turn an object 90 degrees it can match up
900with parallel edges, but if you turn it an arbitrary angle, a corner
901of the child will contact the edge of the parent. Also like align()
902the anchor points of the parent and child are aligned but this does
903not necessarily mean that edges line up neatly when the shapes have
904varying angles. This misalignment is visible in the object attached
905at the RIGHT and aligned to the FRONT.
906
907You may be wondering why all this fuss with align is necessary.
908Couldn't you just attach an object at an anchor on an edge? When you
909do this, the object will be attached using the edge anchor, which is
910not perpendicular to the faces of the object. The example below shows
911attachment to an edge anchor and also a corner anchor.
912
913```openscad-3D
914include <BOSL2/std.scad>
915cube(30)
916 color("orange"){
917 attach(RIGHT+FRONT,BOT)
918 prismoid([8,8],[6,6],shift=-[1,1],h=8);
919 attach(TOP+LEFT+FWD,BOT)
920 prismoid([8,8],[6,6],shift=-[1,1],h=8);
921 }
922```
923
924When using the `align` option to `attach()` you can also set `inset`,
925which works the same way as the `inset` parameter to `align()`. It
926shifts the child away from the edge or edges where it is aligned by
927the specified amount.
928
929```openscad-3D
930include <BOSL2/std.scad>
931prismoid([50,50],[50,25],25){
932 attach(FWD,BOT,align=TOP,inset=3) color("lavender")cuboid(5);
933 attach(FWD,BOT,align=BOT+RIGHT,inset=3) color("purple")cuboid(5);
934}
935```
936
937The last capability provided by `attach()` is to attach the child
938**inside** the parent object. This is useful if you want to subtract
939the child from the parent. Doing this requires using tagged
940operations with `diff()` which is explained in more detail below.
941For the examples here, note that the `diff()` and `tag()` operations
942that appear cause the child to be subtracted. We return to the
943example that started this section, with anchor arrows shown on the two
944objects.
945
946```openscad-3D
947include <BOSL2/std.scad>
948cube(50,anchor=BOT) attach(TOP) anchor_arrow(30);
949right(60)cylinder(d1=30,d2=15,h=25) attach(TOP) anchor_arrow(30);
950```
951
952Inside attachment is activated using `inside=true` and it lines up the
953anchor arrows so they point together the **same** direction instead of
954opposite directions like regular outside attachment. The result in
955this case is appears below, where we have cut away the front half to
956show the interior:
957
958```openscad-3D
959include <BOSL2/std.scad>
960back_half(s=200)
961diff()
962cube(50,anchor=BOT)
963 attach(TOP,TOP,inside=true)
964 cylinder(d1=30,d2=15,h=25);
965```
966
967The top of the cavity has a thin layer on it, which occurs because the
968two objects share a face in the difference. To fix this you can use
969the `shiftout` parameter to `attach()`. In this case you could also
970use a negative `overlay` value, but the `shiftout` parameter shifts
971out in every direction that is needed, which may be three directions
972if you align the child at a corner. The above example looks like this
973with with the shift added:
974
975```openscad-3D
976include <BOSL2/std.scad>
977back_half(s=200)
978diff()
979cube(50,anchor=BOT)
980 attach(TOP,TOP,inside=true,shiftout=0.01)
981 cylinder(d1=30,d2=15,h=25);
982```
983
984Here is an example of connecting the same object on the right, but
985this time with the BOTTOM anchor. Note how the BOTTOM anchor is
986aligned to the RIGHT so it is parallel and pointing in the same
987direction as the RIGHT anchor.
988
989```openscad-3D
990include <BOSL2/std.scad>
991back_half(s=200)
992diff()
993cuboid(50)
994 attach(RIGHT,BOT,inside=true,shiftout=0.01)
995 cylinder(d1=30,d2=15,h=25);
996```
997
998Here is an example where alignment moves the object into the corner,
999and we benefit from shiftout providing 3 dimensions of adjustment:
1000
1001```openscad-3D
1002include <BOSL2/std.scad>
1003diff()
1004cuboid(10)
1005 attach(TOP,TOP,align=RIGHT+FWD,inside=true,shiftout=.01)
1006 cuboid([2,5,9]);
1007```
1008
1009As with `position()`, with any use of `attach()` you can still apply your own translations and
1010other transformations even after attaching an object. However, the
1011order of operations now matters. If you apply a translation outside
1012of the anchor then it acts in the parent's global coordinate system, so the
1013child moves up in this example, where the light gray shows the
1014untranslated object.
1015
1016```openscad-3D
1017include <BOSL2/std.scad>
1018cuboid(50){
1019 %attach(RIGHT,BOT)
1020 cyl(d1=30,d2=15,h=25);
1021 up(13)
1022 color("green") attach(RIGHT,BOT)
1023 cyl(d1=30,d2=15,h=25);
1024}
1025```
1026
1027On the other hand, if you put the translation between the attach and
1028the object in your code, then it will act in the local coordinate system of
1029the parent at the parent's anchor, so in the example below it moves to the right.
1030
1031```openscad-3D
1032include <BOSL2/std.scad>
1033cuboid(50){
1034 %attach(RIGHT,BOT)
1035 cyl(d1=30,d2=15,h=25);
1036 color("green") attach(RIGHT,BOT)
1037 up(13)
1038 cyl(d1=30,d2=15,h=25);
1039}
1040```
1041
1042Parent-child Anchor attachment with CENTER anchors can be surprising because the anchors
1043both point upwards, so in the example below, the child's CENTER anchor
1044points up, so it is inverted when it is attached to the parent cone.
1045Note that the anchors are CENTER anchors, so the bases of the anchors are
1046hidden in the middle of the objects.
1047
1048```openscad-3D
1049include <BOSL2/std.scad>
1050cylinder(d1=30,d2=15,h=25) attach(CENTER) anchor_arrow(40);
1051right(40)cylinder(d1=30,d2=15,h=25) attach(CENTER) anchor_arrow(40);
1052```
1053
1054```openscad-3D
1055include <BOSL2/std.scad>
1056cylinder(d1=30,d2=15,h=25)
1057 attach(CENTER,CENTER)
1058 cylinder(d1=30,d2=15,h=25);
1059```
1060
1061## Parent Anchor Attachment (Single Argument Attachment)
1062
1063The second form of attachment is parent anchor attachment, which just
1064uses a single argument. This form of attachment is less useful in
1065general and does not provide alignment. When you give `attach()` a parent anchor but no child anchor it
1066orients the child according to the parent anchor direction but then
1067simply places the child based on its internally defined anchor at the
1068parent anchor position. For most objects the default anchor is the
1069CENTER anchor, so objects will appear sunk half-way into the parent.
1070
1071```openscad-3D
1072include <BOSL2/std.scad>
1073cuboid(30)
1074 attach(TOP)
1075 color("green")cuboid(10);
1076```
1077
1078Some objects such as `cylinder()`, `prismoid()`, and `anchor_arrow()` have default anchors on the bottom, so they will appear
1079on the surface. For objects like this you can save a little bit of
1080typing by using parent anchor attachment. But in the case of `cube()`
1081the anchor is not centered, so the result is:
1082
1083```openscad-3D
1084include <BOSL2/std.scad>
1085cube(30)
1086 attach(TOP)
1087 color("green")cube(10);
1088```
1089
1090In order to make single argument attachment produce the results you
1091need you will probably need to change the child anchor. Note that unlike
1092parent-child anchor attachment, **with parent anchor attachment the `anchor=` and `orient=` arguments
1093are respected.** We could therefore place a cuboid like this:
1094
1095```openscad-3D
1096include <BOSL2/std.scad>
1097cuboid(30)
1098 attach(RIGHT)
1099 color("green")cuboid(10,anchor=BOT);
1100```
1101
1102If you need to place a cuboid at the anchor point but need it anchored
1103relative to one of the bottom edge or corner anchors then you can do
1104that with parent anchor attachment:
1105
1106```openscad-3D
1107include <BOSL2/std.scad>
1108cuboid(30)
1109 attach(RIGHT)
1110 color("green")cuboid(10,anchor=BOT+FWD);
1111```
1112
1113Another case where single argument attachment is useful is when the
1114child doesn't have proper attachment support.
1115If you use double argument attachment in such cases the results will
1116be incorrect because the child doesn't properly respond to the
1117internally propagated anchor directives. With single argument
1118attachment, this is not a problem: the origin
1119of the child will be placed at the parent anchor point. One module
1120without attachment support is `linear_extrude()`.
1121
1122```openscad-3D
1123include <BOSL2/std.scad>
1124cuboid(20)
1125 attach(RIGHT)
1126 color("red")linear_extrude(height=2) star(n=7,ir=3,or=7);
1127```
1128
1129As noted earlier, you can set `orient=` for children with parent
1130anchor attachment, though the behavior may not be intuitive because
1131the attachment process transforms the coordinate system and the
1132orientation is done in the attached coordinate system. It may be
1133helpful to start with the object attached to TOP and recall the rules
1134from the previous section about how orientation works. The same rules
1135apply here. Note that the forward arrow is pointing down after
1136attaching the object on the RIGHT face.
1137
1138```openscad-3D
1139include <BOSL2/std.scad>
1140cuboid(20){
1141 attach(RIGHT)
1142 color_this("red")cuboid([2,4,8],orient=RIGHT,anchor=RIGHT)
1143 attach(FWD) anchor_arrow();
1144 attach(TOP)
1145 color_this("red")cuboid([2,4,8],orient=RIGHT,anchor=RIGHT)
1146 attach(FWD) anchor_arrow();
1147}
1148```
1149
1150
1151
1152## Positioning and Attaching Multiple Children
1153
1154You can attach or position more than one child at a time by enclosing them all in braces:
1155
1156```openscad-3D
1157include <BOSL2/std.scad>
1158cube(50, center=true) {
1159 attach(TOP) cylinder(d1=50,d2=20,h=20);
1160 position(RIGHT) cylinder(d1=50,d2=20,h=20);
1161}
1162```
1163
1164If you want to attach the same shape to multiple places on the same parent, you can pass the
1165desired anchors as a list to the `attach()` or `position()` modules:
1166
1167```openscad-3D
1168include <BOSL2/std.scad>
1169cube(50, center=true)
1170 attach([RIGHT,FRONT],TOP) cylinder(d1=35,d2=20,h=25);
1171```
1172
1173```openscad-3D
1174include <BOSL2/std.scad>
1175cube(50, center=true)
1176 position([TOP,RIGHT,FRONT]) cylinder(d1=35,d2=20,h=25);
1177```
1178
1179
1180## Attaching 2D Children
1181You can use attachments in 2D as well. As usual for the 2D case you
1182can use TOP and BOTTOM as alternative to BACK and FORWARD. With
1183parent-child anchor attachment you cannot use the spin parameter to
1184`attach()` nor can you specify spin to the child. Spinning the child
1185on the Z axis would rotate the anchor arrows out of alignment.
1186
1187```openscad-2D
1188include <BOSL2/std.scad>
1189rect(50){
1190 attach(RIGHT,FRONT)
1191 color("red")trapezoid(w1=30,w2=0,h=30);
1192 attach(LEFT,FRONT,align=[FRONT,BACK],inset=3)
1193 color("green") trapezoid(w1=25, w2=0,h=30);
1194}
1195```
1196
1197```openscad-2D
1198include <BOSL2/std.scad>
1199diff()
1200circle(d=50){
1201 attach(TOP,BOT,overlap=5)
1202 trapezoid(w1=30,w2=0,h=30);
1203 attach(BOT,BOT,inside=true)
1204 tag("remove")
1205 trapezoid(w1=30,w2=0,h=30);
1206}
1207```
1208
1209
1210## Tagged Operations
1211BOSL2 introduces the concept of tags. Tags are names that can be given to attachables, so that
1212you can refer to them when performing `diff()`, `intersect()`, and `conv_hull()` operations.
1213Each object can have no more than one tag at a time.
1214
1215### `diff([remove], [keep])`
1216The `diff()` operator is used to difference away all shapes marked with the tag(s) given to
1217`remove`, from the other shapes.
1218
1219For example, to difference away a child cylinder from the middle of a parent cube, you can
1220do this:
1221
1222```openscad-3D
1223include <BOSL2/std.scad>
1224diff("hole")
1225cube(100, center=true)
1226 tag("hole")cylinder(h=101, d=50, center=true);
1227```
1228
1229The `keep=` argument takes tags for shapes that you want to keep in the output.
1230
1231```openscad-3D
1232include <BOSL2/std.scad>
1233diff("dish", keep="antenna")
1234cube(100, center=true)
1235 attach([FRONT,TOP], overlap=33) {
1236 tag("dish") cylinder(h=33.1, d1=0, d2=95);
1237 tag("antenna") cylinder(h=33.1, d=10);
1238 }
1239```
1240
1241Remember that tags are inherited by children. In this case, we need to explicitly
1242untag the first cylinder (or change its tag to something else), or it
1243will inherit the "keep" tag and get kept.
1244
1245```openscad-3D
1246include <BOSL2/std.scad>
1247diff("hole", "keep")
1248tag("keep")cube(100, center=true)
1249 attach([RIGHT,TOP]) {
1250 tag("") cylinder(d=95, h=5);
1251 tag("hole") cylinder(d=50, h=11, anchor=CTR);
1252 }
1253```
1254
1255
1256You can of course apply `tag()` to several children.
1257
1258```openscad-3D
1259include <BOSL2/std.scad>
1260diff("hole")
1261cube(100, center=true)
1262 attach([FRONT,TOP], overlap=20)
1263 tag("hole") {
1264 cylinder(h=20.1, d1=0, d2=95);
1265 down(10) cylinder(h=30, d=30);
1266 }
1267```
1268
1269Many of the modules that use tags have default values for their tags. For diff the default
1270remove tag is "remove" and the default keep tag is "keep". In this example we rely on the
1271default values:
1272
1273```openscad-3D
1274include <BOSL2/std.scad>
1275diff()
1276sphere(d=100) {
1277 tag("keep")xcyl(d=40, l=120);
1278 tag("remove")cuboid([40,120,100]);
1279}
1280```
1281
1282
1283The parent object can be differenced away from other shapes. Tags are inherited by children,
1284though, so you will need to set the tags of the children as well as the parent.
1285
1286```openscad-3D
1287include <BOSL2/std.scad>
1288diff("hole")
1289tag("hole")cube([20,11,45], center=true)
1290 tag("body")cube([40,10,90], center=true);
1291```
1292
1293Tags (and therefore tag-based operations like `diff()`) only work correctly with attachable
1294children. However, a number of built-in modules for making shapes are *not* attachable.
1295Some notable non-attachable modules are `text()`, `linear_extrude()`, `rotate_extrude()`,
1296`polygon()`, `polyhedron()`, `import()`, `surface()`, `union()`, `difference()`,
1297`intersection()`, `offset()`, `hull()`, and `minkowski()`.
1298
1299To allow you to use tags-based operations with non-attachable shapes, you can wrap them with the
1300`force_tag()` module to specify their tags. For example:
1301
1302```openscad-3D
1303include <BOSL2/std.scad>
1304diff("hole")
1305cuboid(50)
1306 attach(TOP)
1307 force_tag("hole")
1308 rotate_extrude()
1309 right(15)
1310 square(10,center=true);
1311```
1312
1313### `intersect([intersect], [keep])`
1314
1315To perform an intersection of attachables, you can use the `intersect()` module. This is
1316specifically intended to address the situation where you want intersections involving a parent
1317and a child, something that is impossible with the native `intersection()` module. This module
1318treats the children in three groups: objects matching the `intersect` tags, objects matching
1319the tags listed in `keep` and the remaining objects that don't match any listed tags. The
1320intersection is computed between the union of the `intersect` tagged objects and the union of
1321the objects that don't match any listed tags. Finally the objects listed in `keep` are union
1322ed with the result.
1323
1324In this example the parent is intersected with a conical bounding shape.
1325
1326```openscad-3D
1327include <BOSL2/std.scad>
1328intersect("bounds")
1329cube(100, center=true)
1330 tag("bounds") cylinder(h=100, d1=120, d2=95, center=true, $fn=72);
1331```
1332
1333In this example the child objects are intersected with the bounding box parent.
1334
1335```openscad-3D
1336include <BOSL2/std.scad>
1337intersect("pole cap")
1338cube(100, center=true)
1339 attach([TOP,RIGHT]) {
1340 tag("pole")cube([40,40,80],center=true);
1341 tag("cap")sphere(d=40*sqrt(2));
1342 }
1343```
1344
1345The default `intersect` tag is "intersect" and the default `keep` tag is "keep". Here is an
1346example where "keep" is used to keep the pole from being removed by the intersection.
1347
1348```openscad-3D
1349include <BOSL2/std.scad>
1350intersect()
1351cube(100, center=true) {
1352 tag("intersect")cylinder(h=100, d1=120, d2=95, center=true, $fn=72);
1353 tag("keep")zrot(45) xcyl(h=140, d=20, $fn=36);
1354}
1355```
1356
1357### `conv_hull([keep])`
1358You can use the `conv_hull()` module to hull shapes together. Objects
1359marked with the keep tags are excluded from the hull and unioned into the final result.
1360The default keep tag is "keep".
1361
1362
1363```openscad-3D
1364include <BOSL2/std.scad>
1365conv_hull()
1366cube(50, center=true) {
1367 cyl(h=100, d=20);
1368 tag("keep")xcyl(h=100, d=20);
1369}
1370```
1371
1372
1373## 3D Masking Attachments
1374To make it easier to mask away shapes from various edges of an attachable parent shape, there
1375are a few specialized alternatives to the `attach()` and `position()` modules.
1376
1377### `edge_mask()`
1378If you have a 3D mask shape that you want to difference away from various edges, you can use
1379the `edge_mask()` module. This module will take a vertically oriented shape, and will rotate
1380and move it such that the BACK, RIGHT (X+,Y+) side of the shape will be aligned with the given
1381edges. The shape will be tagged as a "remove" so that you can use
1382`diff()` with its default "remove" tag. For example,
1383here's a shape for rounding an edge:
1384
1385```openscad-3D
1386include <BOSL2/std.scad>
1387module round_edge(l,r) difference() {
1388 translate([-1,-1,-l/2])
1389 cube([r+1,r+1,l]);
1390 translate([r,r])
1391 cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
1392}
1393round_edge(l=30, r=19);
1394```
1395
1396You can use that mask to round various edges of a cube:
1397
1398```openscad-3D
1399include <BOSL2/std.scad>
1400module round_edge(l,r) difference() {
1401 translate([-1,-1,-l/2])
1402 cube([r+1,r+1,l]);
1403 translate([r,r])
1404 cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
1405}
1406diff()
1407cube([50,60,70],center=true)
1408 edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT])
1409 round_edge(l=71,r=10);
1410```
1411
1412### `corner_mask()`
1413If you have a 3D mask shape that you want to difference away from various corners, you can use
1414the `corner_mask()` module. This module will take a shape and rotate and move it such that the
1415BACK RIGHT TOP (X+,Y+,Z+) side of the shape will be aligned with the given corner. The shape
1416will be tagged as a "remove" so that you can use `diff()` with its
1417default "remove" tag. For example, here's a shape for
1418rounding a corner:
1419
1420```openscad-3D
1421include <BOSL2/std.scad>
1422module round_corner(r) difference() {
1423 translate(-[1,1,1])
1424 cube(r+1);
1425 translate([r,r,r])
1426 spheroid(r=r, style="aligned", $fn=quantup(segs(r),4));
1427}
1428round_corner(r=10);
1429```
1430
1431You can use that mask to round various corners of a cube:
1432
1433```openscad-3D
1434include <BOSL2/std.scad>
1435module round_corner(r) difference() {
1436 translate(-[1,1,1])
1437 cube(r+1);
1438 translate([r,r,r])
1439 spheroid(r=r, style="aligned", $fn=quantup(segs(r),4));
1440}
1441diff()
1442cube([50,60,70],center=true)
1443 corner_mask([TOP,FRONT],LEFT+FRONT+TOP)
1444 round_corner(r=10);
1445```
1446
1447### Mix and Match Masks
1448You can use `edge_mask()` and `corner_mask()` together as well:
1449
1450```openscad-3D
1451include <BOSL2/std.scad>
1452module round_corner(r) difference() {
1453 translate(-[1,1,1])
1454 cube(r+1);
1455 translate([r,r,r])
1456 spheroid(r=r, style="aligned", $fn=quantup(segs(r),4));
1457}
1458module round_edge(l,r) difference() {
1459 translate([-1,-1,-l/2])
1460 cube([r+1,r+1,l]);
1461 translate([r,r])
1462 cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
1463}
1464diff()
1465cube([50,60,70],center=true) {
1466 edge_mask("ALL") round_edge(l=71,r=10);
1467 corner_mask("ALL") round_corner(r=10);
1468}
1469```
1470
1471## 2D Profile Mask Attachments
1472While 3D mask shapes give you a great deal of control, you need to make sure they are correctly
1473sized, and you need to provide separate mask shapes for corners and edges. Often, a single 2D
1474profile could be used to describe the edge mask shape (via `linear_extrude()`), and the corner
1475mask shape (via `rotate_extrude()`). This is where `edge_profile()`, `corner_profile()`, and
1476`face_profile()` come in.
1477
1478### `edge_profile()`
1479Using the `edge_profile()` module, you can provide a 2D profile shape and it will be linearly
1480extruded to a mask of the appropriate length for each given edge. The resultant mask will be
1481tagged with "remove" so that you can difference it away with `diff()`
1482with the default "remove" tag. The 2D profile is
1483assumed to be oriented with the BACK, RIGHT (X+,Y+) quadrant as the "cutter edge" that gets
1484re-oriented towards the edges of the parent shape. A typical mask profile for chamfering an
1485edge may look like:
1486
1487```openscad-2D
1488include <BOSL2/std.scad>
1489mask2d_roundover(10);
1490```
1491
1492Using that mask profile, you can mask the edges of a cube like:
1493
1494```openscad-3D
1495include <BOSL2/std.scad>
1496diff()
1497cube([50,60,70],center=true)
1498 edge_profile("ALL")
1499 mask2d_roundover(10);
1500```
1501
1502### `corner_profile()`
1503You can use the same profile to make a rounded corner mask as well:
1504
1505```openscad-3D
1506include <BOSL2/std.scad>
1507diff()
1508cube([50,60,70],center=true)
1509 corner_profile("ALL", r=10)
1510 mask2d_roundover(10);
1511```
1512
1513### `face_profile()`
1514As a simple shortcut to apply a profile mask to all edges and corners of a face, you can use the
1515`face_profile()` module:
1516
1517```openscad-3D
1518include <BOSL2/std.scad>
1519diff()
1520cube([50,60,70],center=true)
1521 face_profile(TOP, r=10)
1522 mask2d_roundover(10);
1523```
1524
1525
1526## Coloring Attachables
1527Usually, when coloring a shape with the `color()` module, the parent color overrides the colors of
1528all children. This is often not what you want:
1529
1530```openscad-3D
1531include <BOSL2/std.scad>
1532$fn = 24;
1533color("red") spheroid(d=3) {
1534 attach(CENTER,BOT) color("white") cyl(h=10, d=1) {
1535 attach(TOP,BOT) color("green") cyl(h=5, d1=3, d2=0);
1536 }
1537}
1538```
1539
1540If you use the `recolor()` module, however, the child's color
1541overrides the color of the parent. This is probably easier to understand by example:
1542
1543```openscad-3D
1544include <BOSL2/std.scad>
1545$fn = 24;
1546recolor("red") spheroid(d=3) {
1547 attach(CENTER,BOT) recolor("white") cyl(h=10, d=1) {
1548 attach(TOP,BOT) recolor("green") cyl(h=5, d1=3, d2=0);
1549 }
1550}
1551```
1552
1553Be aware that `recolor()` will only work if you avoid using the native
1554`color()` module. Also note that `recolor()` still affects all its
1555children. If you want to color an object without affecting the
1556children you can use `color_this()`. See the difference below:
1557
1558```openscad-3D
1559include <BOSL2/std.scad>
1560$fn = 24;
1561recolor("red") spheroid(d=3) {
1562 attach(CENTER,BOT) recolor("white") cyl(h=10, d=1) {
1563 attach(TOP,BOT) cyl(h=5, d1=3, d2=0);
1564 }
1565}
1566right(5)
1567recolor("red") spheroid(d=3) {
1568 attach(CENTER,BOT) color_this("white") cyl(h=10, d=1) {
1569 attach(TOP,BOT) cyl(h=5, d1=3, d2=0);
1570 }
1571}
1572```
1573
1574As with all of the attachable features, these color modules only work
1575on attachable objects, so they will have no effect on objects you
1576create using `linear_extrude()` or `rotate_extrude()`.
1577
1578
1579## Making Attachables
1580To make a shape attachable, you just need to wrap it with an `attachable()` module with a
1581basic description of the shape's geometry. By default, the shape is expected to be centered
1582at the origin. The `attachable()` module expects exactly two children. The first will be
1583the shape to make attachable, and the second will be `children()`,
1584literally.
1585
1586### Pass-through Attachables
1587The simplest way to make your own attachable module is to simply pass
1588through to a pre-existing attachable submodule. This could be
1589appropriate if you want to rename a module, or if the anchors of an
1590existing module are suited to (or good enough for) your object. In
1591order for your attachable module to work properly you need to accept
1592the `anchor`, `spin` and `orient` parameters, give them suitable
1593defaults, and pass them to the attachable submodule. Don't forget to
1594pass the children to the attachable submodule as well, or your new
1595module will ignore its children.
1596
1597```openscad-3D
1598include <BOSL2/std.scad>
1599$fn=32;
1600module cutcube(anchor=CENTER,spin=0,orient=UP)
1601{
1602 tag_scope(){
1603 diff()
1604 cuboid(15, rounding=2, anchor=anchor,spin=spin,orient=orient){
1605 tag("remove")attach(TOP)cuboid(5);
1606 children();
1607 }
1608 }
1609}
1610diff()
1611cutcube()
1612 tag("remove")attach(RIGHT) cyl(d=2,h=8);
1613```
1614
1615### Prismoidal/Cuboidal Attachables
1616To make a cuboidal or prismoidal shape attachable, you use the `size`, `size2`, and `offset`
1617arguments of `attachable()`.
1618
1619In the most basic form, where the shape is fully cuboid, with top and bottom of the same size,
1620and directly over one another, you can just use `size=`.
1621
1622```openscad-3D
1623include <BOSL2/std.scad>
1624module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
1625 attachable(anchor,spin,orient, size=[s*3,s,s]) {
1626 union() {
1627 xcopies(2*s) cube(s, center=true);
1628 xcyl(h=2*s, d=s/4);
1629 }
1630 children();
1631 }
1632}
1633cubic_barbell(100) show_anchors(60);
1634```
1635
1636When the shape is prismoidal, where the top is a different size from the bottom, you can use
1637the `size2=` argument as well. While `size=` takes all three axes sizes, the `size2=` argument
1638only takes the [X,Y] sizes of the top of the shape.
1639
1640```openscad-3D
1641include <BOSL2/std.scad>
1642module prismoidal(size=[100,100,100], scale=0.5, anchor=CENTER, spin=0, orient=UP) {
1643 attachable(anchor,spin,orient, size=size, size2=[size.x, size.y]*scale) {
1644 hull() {
1645 up(size.z/2-0.005)
1646 linear_extrude(height=0.01, center=true)
1647 square([size.x,size.y]*scale, center=true);
1648 down(size.z/2-0.005)
1649 linear_extrude(height=0.01, center=true)
1650 square([size.x,size.y], center=true);
1651 }
1652 children();
1653 }
1654}
1655prismoidal([100,60,30], scale=0.5) show_anchors(20);
1656```
1657
1658When the top of the prismoid can be shifted away from directly above the bottom, you can use
1659the `shift=` argument. The `shift=` argument takes an [X,Y] vector of the offset of the center
1660of the top from the XY center of the bottom of the shape.
1661
1662```openscad-3D
1663include <BOSL2/std.scad>
1664module prismoidal(size=[100,100,100], scale=0.5, shift=[0,0], anchor=CENTER, spin=0, orient=UP) {
1665 attachable(anchor,spin,orient, size=size, size2=[size.x, size.y]*scale, shift=shift) {
1666 hull() {
1667 translate([shift.x, shift.y, size.z/2-0.005])
1668 linear_extrude(height=0.01, center=true)
1669 square([size.x,size.y]*scale, center=true);
1670 down(size.z/2-0.005)
1671 linear_extrude(height=0.01, center=true)
1672 square([size.x,size.y], center=true);
1673 }
1674 children();
1675 }
1676}
1677prismoidal([100,60,30], scale=0.5, shift=[-30,20]) show_anchors(20);
1678```
1679
1680In the case that the prismoid is not oriented vertically, (ie, where the `shift=` or `size2=`
1681arguments should refer to a plane other than XY) you can use the `axis=` argument. This lets
1682you make prismoids naturally oriented forwards/backwards or sideways.
1683
1684```openscad-3D
1685include <BOSL2/std.scad>
1686module yprismoidal(
1687 size=[100,100,100], scale=0.5, shift=[0,0],
1688 anchor=CENTER, spin=0, orient=UP
1689) {
1690 attachable(
1691 anchor, spin, orient,
1692 size=size, size2=point2d(size)*scale,
1693 shift=shift, axis=BACK
1694 ) {
1695 xrot(-90) hull() {
1696 translate([shift.x, shift.y, size.z/2-0.005])
1697 linear_extrude(height=0.01, center=true)
1698 square([size.x,size.y]*scale, center=true);
1699 down(size.z/2-0.005)
1700 linear_extrude(height=0.01, center=true)
1701 square([size.x,size.y], center=true);
1702 }
1703 children();
1704 }
1705}
1706yprismoidal([100,60,30], scale=1.5, shift=[20,20]) show_anchors(20);
1707```
1708
1709
1710### Cylindrical Attachables
1711To make a cylindrical shape attachable, you use the `l`, and `r`/`d`, args of `attachable()`.
1712
1713```openscad-3D
1714include <BOSL2/std.scad>
1715module twistar(l,r,d, anchor=CENTER, spin=0, orient=UP) {
1716 r = get_radius(r=r,d=d,dflt=1);
1717 attachable(anchor,spin,orient, r=r, l=l) {
1718 linear_extrude(height=l, twist=90, slices=20, center=true, convexity=4)
1719 star(n=20, r=r, ir=r*0.9);
1720 children();
1721 }
1722}
1723twistar(l=100, r=40) show_anchors(20);
1724```
1725
1726If the cylinder is elipsoidal in shape, you can pass the unequal X/Y sizes as a 2-item vector
1727to the `r=` or `d=` argument.
1728
1729```openscad-3D
1730include <BOSL2/std.scad>
1731module ovalstar(l,rx,ry, anchor=CENTER, spin=0, orient=UP) {
1732 attachable(anchor,spin,orient, r=[rx,ry], l=l) {
1733 linear_extrude(height=l, center=true, convexity=4)
1734 scale([1,ry/rx,1])
1735 star(n=20, r=rx, ir=rx*0.9);
1736 children();
1737 }
1738}
1739ovalstar(l=100, rx=50, ry=30) show_anchors(20);
1740```
1741
1742For cylindrical shapes that aren't oriented vertically, use the `axis=` argument.
1743
1744```openscad-3D
1745include <BOSL2/std.scad>
1746module ytwistar(l,r,d, anchor=CENTER, spin=0, orient=UP) {
1747 r = get_radius(r=r,d=d,dflt=1);
1748 attachable(anchor,spin,orient, r=r, l=l, axis=BACK) {
1749 xrot(-90)
1750 linear_extrude(height=l, twist=90, slices=20, center=true, convexity=4)
1751 star(n=20, r=r, ir=r*0.9);
1752 children();
1753 }
1754}
1755ytwistar(l=100, r=40) show_anchors(20);
1756```
1757
1758### Conical Attachables
1759To make a conical shape attachable, you use the `l`, `r1`/`d1`, and `r2`/`d2`, args of
1760`attachable()`.
1761
1762```openscad-3D
1763include <BOSL2/std.scad>
1764module twistar(l, r,r1,r2, d,d1,d2, anchor=CENTER, spin=0, orient=UP) {
1765 r1 = get_radius(r1=r1,r=r,d1=d1,d=d,dflt=1);
1766 r2 = get_radius(r1=r2,r=r,d1=d2,d=d,dflt=1);
1767 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
1768 linear_extrude(height=l, twist=90, scale=r2/r1, slices=20, center=true, convexity=4)
1769 star(n=20, r=r1, ir=r1*0.9);
1770 children();
1771 }
1772}
1773twistar(l=100, r1=40, r2=20) show_anchors(20);
1774```
1775
1776If the cone is ellipsoidal in shape, you can pass the unequal X/Y sizes as a 2-item vectors
1777to the `r1=`/`r2=` or `d1=`/`d2=` arguments.
1778
1779```openscad-3D
1780include <BOSL2/std.scad>
1781module ovalish(l,rx1,ry1,rx2,ry2, anchor=CENTER, spin=0, orient=UP) {
1782 attachable(anchor,spin,orient, r1=[rx1,ry1], r2=[rx2,ry2], l=l) {
1783 hull() {
1784 up(l/2-0.005)
1785 linear_extrude(height=0.01, center=true)
1786 ellipse([rx2,ry2]);
1787 down(l/2-0.005)
1788 linear_extrude(height=0.01, center=true)
1789 ellipse([rx1,ry1]);
1790 }
1791 children();
1792 }
1793}
1794ovalish(l=100, rx1=50, ry1=30, rx2=30, ry2=50) show_anchors(20);
1795```
1796
1797For conical shapes that are not oriented vertically, use the `axis=` argument to indicate the
1798direction of the primary shape axis:
1799
1800```openscad-3D
1801include <BOSL2/std.scad>
1802module ytwistar(l, r,r1,r2, d,d1,d2, anchor=CENTER, spin=0, orient=UP) {
1803 r1 = get_radius(r1=r1,r=r,d1=d1,d=d,dflt=1);
1804 r2 = get_radius(r1=r2,r=r,d1=d2,d=d,dflt=1);
1805 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK) {
1806 xrot(-90)
1807 linear_extrude(height=l, twist=90, scale=r2/r1, slices=20, center=true, convexity=4)
1808 star(n=20, r=r1, ir=r1*0.9);
1809 children();
1810 }
1811}
1812ytwistar(l=100, r1=40, r2=20) show_anchors(20);
1813```
1814
1815### Spherical Attachables
1816To make a spherical shape attachable, you use the `r`/`d` args of `attachable()`.
1817
1818```openscad-3D
1819include <BOSL2/std.scad>
1820module spikeball(r, d, anchor=CENTER, spin=0, orient=UP) {
1821 r = get_radius(r=r,d=d,dflt=1);
1822 attachable(anchor,spin,orient, r=r*1.1) {
1823 union() {
1824 sphere_copies(r=r, n=512, cone_ang=180) cylinder(r1=r/10, r2=0, h=r/10);
1825 sphere(r=r);
1826 }
1827 children();
1828 }
1829}
1830spikeball(r=50) show_anchors(20);
1831```
1832
1833If the shape is an ellipsoid, you can pass a 3-item vector of sizes to `r=` or `d=`.
1834
1835```openscad-3D
1836include <BOSL2/std.scad>
1837module spikeball(r, d, scale, anchor=CENTER, spin=0, orient=UP) {
1838 r = get_radius(r=r,d=d,dflt=1);
1839 attachable(anchor,spin,orient, r=r*1.1*scale) {
1840 union() {
1841 sphere_copies(r=r, n=512, scale=scale, cone_ang=180) cylinder(r1=r/10, r2=0, h=r/10);
1842 scale(scale) sphere(r=r);
1843 }
1844 children();
1845 }
1846}
1847spikeball(r=50, scale=[0.75,1,1.5]) show_anchors(20);
1848```
1849
1850### VNF Attachables
1851If the shape just doesn't fit into any of the above categories, and you constructed it as a
1852[VNF](vnf.scad), you can use the VNF itself to describe the geometry with the `vnf=` argument.
1853
1854There are two variations to how anchoring can work for VNFs. When `extent=true`, (the default)
1855then a plane is projected out from the origin, perpendicularly in the direction of the anchor,
1856to the furthest distance that intersects with the VNF shape. The anchor point is then the
1857center of the points that still intersect that plane.
1858
1859```openscad-FlatSpin,VPD=500
1860include <BOSL2/std.scad>
1861module stellate_cube(s=100, anchor=CENTER, spin=0, orient=UP) {
1862 s2 = 3 * s;
1863 verts = [
1864 [0,0,-s2*sqrt(2)/2],
1865 each down(s/2, p=path3d(square(s,center=true))),
1866 each zrot(45, p=path3d(square(s2,center=true))),
1867 each up(s/2, p=path3d(square(s,center=true))),
1868 [0,0,s2*sqrt(2)/2]
1869 ];
1870 faces = [
1871 [0,2,1], [0,3,2], [0,4,3], [0,1,4],
1872 [1,2,6], [1,6,9], [6,10,9], [2,10,6],
1873 [1,5,4], [1,9,5], [9,12,5], [5,12,4],
1874 [4,8,3], [4,12,8], [12,11,8], [11,3,8],
1875 [2,3,7], [3,11,7], [7,11,10], [2,7,10],
1876 [9,10,13], [10,11,13], [11,12,13], [12,9,13]
1877 ];
1878 vnf = [verts, faces];
1879 attachable(anchor,spin,orient, vnf=vnf) {
1880 vnf_polyhedron(vnf);
1881 children();
1882 }
1883}
1884stellate_cube(25) {
1885 attach(UP+RIGHT) {
1886 anchor_arrow(20);
1887 %cube([100,100,0.1],center=true);
1888 }
1889}
1890```
1891
1892When `extent=false`, then the anchor point will be the furthest intersection of the VNF with
1893the anchor ray from the origin. The orientation of the anchor point will be the normal of the
1894face at the intersection. If the intersection is at an edge or corner, then the orientation
1895will bisect the angles between the faces.
1896
1897```openscad-VPD=1250
1898include <BOSL2/std.scad>
1899module stellate_cube(s=100, anchor=CENTER, spin=0, orient=UP) {
1900 s2 = 3 * s;
1901 verts = [
1902 [0,0,-s2*sqrt(2)/2],
1903 each down(s/2, p=path3d(square(s,center=true))),
1904 each zrot(45, p=path3d(square(s2,center=true))),
1905 each up(s/2, p=path3d(square(s,center=true))),
1906 [0,0,s2*sqrt(2)/2]
1907 ];
1908 faces = [
1909 [0,2,1], [0,3,2], [0,4,3], [0,1,4],
1910 [1,2,6], [1,6,9], [6,10,9], [2,10,6],
1911 [1,5,4], [1,9,5], [9,12,5], [5,12,4],
1912 [4,8,3], [4,12,8], [12,11,8], [11,3,8],
1913 [2,3,7], [3,11,7], [7,11,10], [2,7,10],
1914 [9,10,13], [10,11,13], [11,12,13], [12,9,13]
1915 ];
1916 vnf = [verts, faces];
1917 attachable(anchor,spin,orient, vnf=vnf, extent=false) {
1918 vnf_polyhedron(vnf);
1919 children();
1920 }
1921}
1922stellate_cube() show_anchors(50);
1923```
1924
1925```openscad-3D
1926include <BOSL2/std.scad>
1927$fn=32;
1928R = difference(circle(10), right(2, circle(9)));
1929linear_sweep(R,height=10,atype="hull")
1930 attach(RIGHT) anchor_arrow();
1931```
1932
1933
1934## Making Named Anchors
1935While vector anchors are often useful, sometimes there are logically extra attachment points that
1936aren't on the perimeter of the shape. This is what named string anchors are for. For example,
1937the `teardrop()` shape uses a cylindrical geometry for it's vector anchors, but it also provides
1938a named anchor "cap" that is at the tip of the hat of the teardrop shape.
1939
1940Named anchors are passed as an array of `named_anchor()`s to the `anchors=` argument of `attachable()`.
1941The `named_anchor()` call takes a name string, a positional point, an orientation vector, and a spin.
1942The name is the name of the anchor. The positional point is where the anchor point is at. The
1943orientation vector is the direction that a child attached at that anchor point should be oriented.
1944The spin is the number of degrees that an attached child should be rotated counter-clockwise around
1945the orientation vector. Spin is optional, and defaults to 0.
1946
1947To make a simple attachable shape similar to a `teardrop()` that provides a "cap" anchor, you may
1948define it like this:
1949
1950```openscad-3D
1951include <BOSL2/std.scad>
1952module raindrop(r, thick, anchor=CENTER, spin=0, orient=UP) {
1953 anchors = [
1954 named_anchor("cap", [0,r/sin(45),0], BACK, 0)
1955 ];
1956 attachable(anchor,spin,orient, r=r, l=thick, anchors=anchors) {
1957 linear_extrude(height=thick, center=true) {
1958 circle(r=r);
1959 back(r*sin(45)) zrot(45) square(r, center=true);
1960 }
1961 children();
1962 }
1963}
1964raindrop(r=25, thick=20, anchor="cap");
1965```
1966
1967If you want multiple named anchors, just add them to the list of anchors:
1968
1969```openscad-FlatSpin,VPD=150
1970include <BOSL2/std.scad>
1971module raindrop(r, thick, anchor=CENTER, spin=0, orient=UP) {
1972 anchors = [
1973 named_anchor("captop", [0,r/sin(45), thick/2], BACK+UP, 0),
1974 named_anchor("cap", [0,r/sin(45), 0 ], BACK, 0),
1975 named_anchor("capbot", [0,r/sin(45),-thick/2], BACK+DOWN, 0)
1976 ];
1977 attachable(anchor,spin,orient, r=r, l=thick, anchors=anchors) {
1978 linear_extrude(height=thick, center=true) {
1979 circle(r=r);
1980 back(r*sin(45)) zrot(45) square(r, center=true);
1981 }
1982 children();
1983 }
1984}
1985raindrop(r=15, thick=10) show_anchors();
1986```
1987
1988Sometimes the named anchor you want to add may be at a point that is reached through a complicated
1989set of translations and rotations. One quick way to calculate that point is to reproduce those
1990transformations in a transformation matrix chain. This is simplified by how you can use the
1991function forms of almost all the transformation modules to get the transformation matrices, and
1992chain them together with matrix multiplication. For example, if you have:
1993
1994```
1995scale([1.1, 1.2, 1.3]) xrot(15) zrot(25) right(20) sphere(d=1);
1996```
1997
1998and you want to calculate the center point of the sphere, you can do it like:
1999
2000```
2001sphere_pt = apply(
2002 scale([1.1, 1.2, 1.3]) * xrot(15) * zrot(25) * right(20),
2003 [0,0,0]
2004);
2005```
2006
2007
2008## Overriding Standard Anchors
2009
2010Sometimes you may want to use the standard anchors but override some
2011of them. Returning to the square barebell example above, the anchors
2012at the right and left sides are on the cubes at each end, but the
2013anchors at x=0 are in floating in space. For prismoidal/cubic anchors
2014in 3D and trapezoidal/rectangular anchors in 2D we can override a single anchor by
2015specifying the override option and giving the anchor that is being
2016overridden, and then the replacement in the form
2017`[position, direction, spin]`. Most often you will only want to
2018override the position. If you omit the other list items then the
2019value drived from the standard anchor will be used. Below we override
2020position of the FWD anchor:
2021
2022```
2023module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
2024 override = [
2025 [FWD, [[0,-s/8,0]]]
2026 ];
2027 attachable(anchor,spin,orient, size=[s*3,s,s],override=override) {
2028 union() {
2029 xcopies(2*s) cube(s, center=true);
2030 xcyl(h=2*s, d=s/4);
2031 }
2032 children();
2033 }
2034}
2035cubic_barbell(100) show_anchors(60);
2036```
2037
2038Note how the FWD anchor is now rooted on the cylindrical portion. If
2039you wanted to also change its direction and spin you could do it like
2040this:
2041
2042```
2043module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
2044 override = [
2045 [FWD, [[0,-s/8,0], FWD+LEFT, 225]]
2046 ];
2047 attachable(anchor,spin,orient, size=[s*3,s,s],override=override) {
2048 union() {
2049 xcopies(2*s) cube(s, center=true);
2050 xcyl(h=2*s, d=s/4);
2051 }
2052 children();
2053 }
2054}
2055cubic_barbell(100) show_anchors(60);
2056```
2057
2058In the above example we give three values for the override. As
2059before, the first one places the anchor on the cylinder. We have
2060added the second entry which points the anchor off to the left.
2061The third entry gives a spin override, whose effect is shown by the
2062position of the red flag on the arrow. If you want to override all of
2063the x=0 anchors to be on the cylinder, with their standard directions,
2064you can do that by supplying a list:
2065```
2066module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
2067 override = [
2068 for(j=[-1:1:1], k=[-1:1:1])
2069 if ([j,k]!=[0,0]) [[0,j,k], [s/8*unit([0,j,k])]]
2070 ];
2071 attachable(anchor,spin,orient, size=[s*3,s,s],override=override) {
2072 union() {
2073 xcopies(2*s) cube(s, center=true);
2074 xcyl(h=2*s, d=s/4);
2075 }
2076 children();
2077 }
2078}
2079cubic_barbell(100) show_anchors(30);
2080```
2081
2082Now all of the anchors in the middle are all rooted to the cylinder. Another
2083way to do the same thing is to use a function literal for override.
2084It will be called with the anchor as its argument and needs to return undef to just use
2085the default, or a `[position, direction, spin]` triple to override the
2086default. As before, you can omit values to keep their default.
2087Here is the same example using a function literal for the override:
2088
2089```
2090module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
2091 override = function (anchor)
2092 anchor.x!=0 || anchor==CTR ? undef // Keep these
2093 : [s/8*unit(anchor)];
2094 attachable(anchor,spin,orient, size=[s*3,s,s],override=override) {
2095 union() {
2096 xcopies(2*s) cube(s, center=true);
2097 xcyl(h=2*s, d=s/4);
2098 }
2099 children();
2100 }
2101}
2102cubic_barbell(100) show_anchors(30);
2103```