-
Notifications
You must be signed in to change notification settings - Fork 125
/
attachments.scad
5138 lines (4894 loc) · 238 KB
/
attachments.scad
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//////////////////////////////////////////////////////////////////////
// LibFile: attachments.scad
// The modules in this file allows you to attach one object to another by making one object the child of another object.
// You can place the child object in relation to its parent object and control the position and orientation
// relative to the parent. The modifiers allow you to treat children in ways different from simple union, such
// as differencing them from the parent, or changing their color. Attachment only works when the parent and child
// are both written to support attachment. Also included in this file are the tools to make your own "attachable" objects.
// Includes:
// include <BOSL2/std.scad>
// FileGroup: Basic Modeling
// FileSummary: Positioning objects on or relative to other objects. Making your own objects support attachment.
// FileFootnotes: STD=Included in std.scad
//////////////////////////////////////////////////////////////////////
// Default values for attachment code.
$tags=undef; // for backward compatibility
$tag = "";
$save_tag = undef;
$tag_prefix = "";
$overlap = 0;
$color = "default";
$save_color = undef; // Saved color to revert back for children
$anchor_override = undef;
$attach_to = undef;
$attach_anchor = [CENTER, CENTER, UP, 0];
$attach_alignment = undef;
$parent_anchor = BOTTOM;
$parent_spin = 0;
$parent_orient = UP;
$parent_size = undef;
$parent_geom = undef;
$edge_angle = undef;
$edge_length = undef;
$tags_shown = "ALL";
$tags_hidden = [];
$ghost_this=false;
$ghost=false;
$ghosting=false; // Ghosting is in effect, so don't apply it again
$highlight_this=false;
$highlight=false;
_ANCHOR_TYPES = ["intersect","hull"];
// Section: Terminology and Shortcuts
// This library adds the concept of anchoring, spin and orientation to the `cube()`, `cylinder()`
// and `sphere()` builtins, as well as to most of the shapes provided by this library itself.
// - An anchor is a place on an object which you can align the object to, or attach other objects
// to using `attach()` or `position()`. An anchor has a position, a direction, and a spin.
// The direction and spin are used to orient other objects to match when using `attach()`.
// - Spin is a simple rotation around the Z axis.
// - Orientation is rotating an object so that its top is pointed towards a given vector.
// .
// An object will first be translated to its anchor position, then spun, then oriented.
// For a detailed step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// .
// For describing directions, faces, edges, and corners the library provides a set of shortcuts
// all based on combinations of unit direction vectors. You can use these for anchoring and orienting
// attachable objects. You can also them to specify edge sets for rounding or chamfering cuboids,
// or for placing edge, face and corner masks.
// Subsection: Anchor
// Anchoring is specified with the `anchor` argument in most shape modules. Specifying `anchor`
// when creating an object will translate the object so that the anchor point is at the origin
// (0,0,0). Anchoring always occurs before spin and orientation are applied.
// .
// An anchor can be referred to in one of two ways; as a directional vector, or as a named anchor string.
// .
// When given as a vector, it points, in a general way, towards the face, edge, or corner of the
// object that you want the anchor for, relative to the center of the object. You can simply
// specify a vector like `[0,0,1]` to anchor an object at the Z+ end, but you can also use
// directional constants with names like `TOP`, `BOTTOM`, `LEFT`, `RIGHT` and `BACK` that you can add together
// to specify anchor points. See [specifying directions](attachments.scad#subsection-specifying-directions)
// below for the full list of pre-defined directional constants.
// .
// For example:
// - `[0,0,1]` is the same as `TOP` and refers to the center of the top face.
// - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge.
// - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner.
// .
// When the object is cubical or rectangular in shape the anchors must have zero or one values
// for their components and they refer to the face centers, edge centers, or corners of the object.
// The direction of a face anchor will be perpendicular to the face, pointing outward. The direction of a edge anchor
// will be the average of the anchor directions of the two faces the edge is between. The direction
// of a corner anchor will be the average of the anchor directions of the three faces the corner is
// on.
// .
// When the object is cylindrical, conical, or spherical in nature, the anchors will be located
// around the surface of the cylinder, cone, or sphere, relative to the center.
// You can generally use an arbitrary vector to get an anchor positioned anywhere on the curved
// surface of such an object, and the anchor direction will be the surface normal at the anchor location.
// However, for anchor component pointing toward the flat face should be either -1, 1, or 0, and
// anchors that point diagonally toward one of the flat faces will select a point on the edge.
// .
// For objects in two dimensions, the natural expectation is for TOP and BOTTOM to refer to the Y direction
// of the shape. To support this, if you give an anchor in 2D that has anchor.y=0 then the Z component
// will be mapped to the Y direction. This means you can use TOP and BOTTOM for anchors of 2D objects.
// But remember that TOP and BOTTOM are three dimensional vectors and this is a special interpretation
// for 2d anchoring.
// .
// Some more complex objects, like screws and stepper motors, have named anchors to refer to places
// on the object that are not at one of the standard faces, edges or corners. For example, stepper
// motors have anchors for `"screw1"`, `"screw2"`, etc. to refer to the various screwholes on the
// stepper motor shape. The names, positions, directions, and spins of these anchors are
// specific to the object, and are documented when they exist.
// .
// The anchor argument is ignored if you use {{align()}} or the two-argument form of {{attach()}} because
// these modules provide their own anchoring for their children.
// Subsection: Spin
// Spin is specified with the `spin` argument in most shape modules. Specifying a spin
// angle when creating an object will rotate the object counter-clockwise around the Z axis by the given
// number of degrees. Spin is always applied after anchoring, and before orientation.
// Since spin is applied **after** anchoring it does not, in general, rotate around the object's center,
// so it is not always what you might think of intuitively as spinning the shape.
// Subsection: Orient
// Orientation is specified with the `orient` argument in most shape modules. Specifying `orient`
// when creating an object will rotate the object such that the top of the object will be pointed
// at the vector direction given in the `orient` argument. Orientation is always applied after
// anchoring and spin. The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be
// added together to form the directional vector for this (e.g. `LEFT+BACK`). The orient parameter
// is ignored when you use {{attach()}} with two arguments, because {{attach()}} provides its own orientation.
// Subsection: Specifying Directions
// You can use direction vectors to specify anchors for objects or to specify edges, faces, and
// corners of cubes. You can simply specify these direction vectors numerically, but another
// option is to use named constants for direction vectors. These constants define unit vectors
// for the six axis directions as shown below.
// Figure(3D,Big,VPD=6): Named constants for direction vectors. Some directions have more than one name.
// $fn=12;
// stroke([[0,0,0],RIGHT], endcap2="arrow2", width=.05);
// color("black")right(.05)up(.05)move(RIGHT) text3d("RIGHT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
// stroke([[0,0,0],LEFT], endcap2="arrow2", width=.05);
// color("black")left(.05)up(.05)move(LEFT) text3d("LEFT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
// stroke([[0,0,0],FRONT], endcap2="arrow2", width=.05);
// color("black")
// left(.1){
// up(.12)move(FRONT) text3d("FRONT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
// move(FRONT) text3d("FWD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
// down(.12)move(FRONT) text3d("FORWARD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
// }
// stroke([[0,0,0],BACK], endcap2="arrow2", width=.05);
// right(.05)
// color("black")move(BACK) text3d("BACK",size=.1,h=.01,anchor=LEFT,orient=FRONT);
// stroke([[0,0,0],DOWN], endcap2="arrow2", width=.05);
// color("black")
// right(.1){
// up(.12)move(BOT) text3d("DOWN",size=.1,h=.01,anchor=LEFT,orient=FRONT);
// move(BOT) text3d("BOTTOM",size=.1,h=.01,anchor=LEFT,orient=FRONT);
// down(.12)move(BOT) text3d("BOT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
// }
// stroke([[0,0,0],TOP], endcap2="arrow2", width=.05);
// color("black")left(.05){
// up(.12)move(TOP) text3d("TOP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
// move(TOP) text3d("UP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
// }
// Figure(2D,Big): Named constants for direction vectors in 2D. For anchors the TOP and BOTTOM directions are collapsed into 2D as shown here, but do not try to use TOP or BOTTOM as 2D directions in other situations.
// $fn=12;
// stroke(path2d([[0,0,0],RIGHT]), endcap2="arrow2", width=.05);
// color("black")fwd(.22)left(.05)move(RIGHT) text("RIGHT",size=.1,anchor=RIGHT);
// stroke(path2d([[0,0,0],LEFT]), endcap2="arrow2", width=.05);
// color("black")right(.05)fwd(.22)move(LEFT) text("LEFT",size=.1,anchor=LEFT);
// stroke(path2d([[0,0,0],FRONT]), endcap2="arrow2", width=.05);
// color("black")
// fwd(.2)
// right(.15)
// color("black")move(BACK) { text("BACK",size=.1,anchor=LEFT); back(.14) text("(TOP)", size=.1, anchor=LEFT);}
// color("black")
// left(.15)back(.2+.14)move(FRONT){
// back(.14) text("FRONT",size=.1,anchor=RIGHT);
// text("FWD",size=.1,anchor=RIGHT);
// fwd(.14) text("FORWARD",size=.1,anchor=RIGHT);
// fwd(.28) text("(BOTTOM)",size=.1,anchor=RIGHT);
// fwd(.14*3) text("(BOT)",size=.1,anchor=RIGHT);
// }
// stroke(path2d([[0,0,0],BACK]), endcap2="arrow2", width=.05);
// Subsection: Specifying Faces
// Modules operating on faces accept a list of faces to describe the faces to operate on. Each
// face is given by a vector that points to that face. Attachments of cuboid objects onto their faces also
// work by choosing an attachment face with a single vector in the same manner.
// Figure(3D,Big,NoScales,VPD=275): The six faces of the cube. Some have faces have more than one name.
// ydistribute(50) {
// xdistribute(35){
// _show_cube_faces([BACK], botlabel=["BACK"]);
// _show_cube_faces([UP],botlabel=["TOP","UP"]);
// _show_cube_faces([RIGHT],botlabel=["RIGHT"]);
// }
// xdistribute(35){
// _show_cube_faces([FRONT],toplabel=["FRONT","FWD", "FORWARD"]);
// _show_cube_faces([DOWN],toplabel=["BOTTOM","BOT","DOWN"]);
// _show_cube_faces([LEFT],toplabel=["LEFT"]);
// }
// }
// Subsection: Specifying Edges
// Modules operating on edges use two arguments to describe the edge set they will use: The `edges` argument
// is a list of edge set descriptors to include in the edge set, and the `except` argument is a list of
// edge set descriptors to remove from the edge set.
// The default value for `edges` is `"ALL"`, the set of all edges.
// The default value for `except` is the empty set, meaning no edges are removed.
// If either argument is just a single edge set
// descriptor it can be passed directly rather than in a singleton list.
// Each edge set descriptor must be one of:
// - A vector pointing towards an edge, indicating that single edge.
// - A vector pointing towards a face, indicating all edges surrounding that face.
// - A vector pointing towards a corner, indicating all edges touching that corner.
// - The string `"X"`, indicating all X axis aligned edges.
// - The string `"Y"`, indicating all Y axis aligned edges.
// - The string `"Z"`, indicating all Z axis aligned edges.
// - The string `"ALL"`, indicating all edges.
// - The string `"NONE"`, indicating no edges at all.
// - A 3x4 array, where each entry corresponds to one of the 12 edges and is set to 1 if that edge is included and 0 if the edge is not. The edge ordering is:
// ```
// [
// [Y-Z-, Y+Z-, Y-Z+, Y+Z+],
// [X-Z-, X+Z-, X-Z+, X+Z+],
// [X-Y-, X+Y-, X-Y+, X+Y+]
// ]
// ```
// .
// You can specify edge descriptors directly by giving a vector, or you can use sums of the
// named direction vectors described above. Below we show all of the edge sets you can
// describe with sums of the direction vectors, and then we show some examples of combining
// edge set descriptors.
// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward an edge select that single edge
// ydistribute(50) {
// xdistribute(30) {
// _show_edges(edges=BOT+RIGHT);
// _show_edges(edges=BOT+BACK);
// _show_edges(edges=BOT+LEFT);
// _show_edges(edges=BOT+FRONT);
// }
// xdistribute(30) {
// _show_edges(edges=FWD+RIGHT);
// _show_edges(edges=BACK+RIGHT);
// _show_edges(edges=BACK+LEFT);
// _show_edges(edges=FWD+LEFT);
// }
// xdistribute(30) {
// _show_edges(edges=TOP+RIGHT);
// _show_edges(edges=TOP+BACK);
// _show_edges(edges=TOP+LEFT);
// _show_edges(edges=TOP+FRONT);
// }
// }
// Figure(3D,Med,VPD=205,NoScales): Vectors pointing toward a face select all edges surrounding that face.
// ydistribute(50) {
// xdistribute(30) {
// _show_edges(edges=LEFT);
// _show_edges(edges=FRONT);
// _show_edges(edges=RIGHT);
// }
// xdistribute(30) {
// _show_edges(edges=TOP);
// _show_edges(edges=BACK);
// _show_edges(edges=BOTTOM);
// }
// }
// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward a corner select all edges surrounding that corner.
// ydistribute(50) {
// xdistribute(30) {
// _show_edges(edges=FRONT+LEFT+TOP);
// _show_edges(edges=FRONT+RIGHT+TOP);
// _show_edges(edges=FRONT+LEFT+BOT);
// _show_edges(edges=FRONT+RIGHT+BOT);
// }
// xdistribute(30) {
// _show_edges(edges=TOP+LEFT+BACK);
// _show_edges(edges=TOP+RIGHT+BACK);
// _show_edges(edges=BOT+LEFT+BACK);
// _show_edges(edges=BOT+RIGHT+BACK);
// }
// }
// Figure(3D,Med,VPD=205,NoScales): Named Edge Sets
// ydistribute(50) {
// xdistribute(30) {
// _show_edges(edges="X");
// _show_edges(edges="Y");
// _show_edges(edges="Z");
// }
// xdistribute(30) {
// _show_edges(edges="ALL");
// _show_edges(edges="NONE");
// }
// }
// Figure(3D,Big,VPD=310,NoScales): Next are some examples showing how you can combine edge descriptors to obtain different edge sets. You can specify the top front edge with a numerical vector or by combining the named direction vectors. If you combine them as a list you get all the edges around the front and top faces. Adding `except` removes an edge.
// xdistribute(43){
// _show_edges(_edges([0,-1,1]),toplabel=["edges=[0,-1,1]"]);
// _show_edges(_edges(TOP+FRONT),toplabel=["edges=TOP+FRONT"]);
// _show_edges(_edges([TOP,FRONT]),toplabel=["edges=[TOP,FRONT]"]);
// _show_edges(_edges([TOP,FRONT],TOP+FRONT),toplabel=["edges=[TOP,FRONT]","except=TOP+FRONT"]);
// }
// Figure(3D,Big,VPD=310,NoScales): Using `except=BACK` removes the four edges surrounding the back face if they are present in the edge set. In the first example only one edge needs to be removed. In the second example we remove two of the Z-aligned edges. The third example removes all four back edges from the default edge set of all edges. You can explicitly give `edges="ALL"` but it is not necessary, since this is the default. In the fourth example, the edge set of Y-aligned edges contains no back edges, so the `except` parameter has no effect.
// xdistribute(43){
// _show_edges(_edges(BOT,BACK), toplabel=["edges=BOT","except=BACK"]);
// _show_edges(_edges("Z",BACK), toplabel=["edges=\"Z\"", "except=BACK"]);
// _show_edges(_edges("ALL",BACK), toplabel=["(edges=\"ALL\")", "except=BACK"]);
// _show_edges(_edges("Y",BACK), toplabel=["edges=\"Y\"","except=BACK"]);
// }
// Figure(3D,Big,NoScales,VPD=310): On the left `except` is a list to remove two edges. In the center we show a corner edge set defined by a numerical vector, and at the right we remove that same corner edge set with named direction vectors.
// xdistribute(52){
// _show_edges(_edges("ALL",[FRONT+RIGHT,FRONT+LEFT]),
// toplabel=["except=[FRONT+RIGHT,"," FRONT+LEFT]"]);
// _show_edges(_edges([1,-1,1]),toplabel=["edges=[1,-1,1]"]);
// _show_edges(_edges([TOP,BOT], TOP+RIGHT+FRONT),toplabel=["edges=[TOP,BOT]","except=TOP+RIGHT+FRONT"]);
// }
// Subsection: Specifying Corners
// Modules operating on corners use two arguments to describe the corner set they will use: The `corners` argument
// is a list of corner set descriptors to include in the corner set, and the `except` argument is a list of
// corner set descriptors to remove from the corner set.
// The default value for `corners` is `"ALL"`, the set of all corners.
// The default value for `except` is the empty set, meaning no corners are removed.
// If either argument is just a single corner set
// descriptor it can be passed directly rather than in a singleton list.
// Each corner set descriptor must be one of:
// - A vector pointing towards a corner, indicating that corner.
// - A vector pointing towards an edge indicating both corners at the ends of that edge.
// - A vector pointing towards a face, indicating all the corners of that face.
// - The string `"ALL"`, indicating all corners.
// - The string `"NONE"`, indicating no corners at all.
// - A length 8 vector where each entry corresponds to a corner and is 1 if the corner is included and 0 if it is excluded. The corner ordering is
// ```
// [X-Y-Z-, X+Y-Z-, X-Y+Z-, X+Y+Z-, X-Y-Z+, X+Y-Z+, X-Y+Z+, X+Y+Z+]
// ```
// .
// You can specify corner descriptors directly by giving a vector, or you can use sums of the
// named direction vectors described above. Below we show all of the corner sets you can
// describe with sums of the direction vectors and then we show some examples of combining
// corner set descriptors.
// Figure(3D,Big,NoScales,VPD=300): Vectors pointing toward a corner select that corner.
// ydistribute(55) {
// xdistribute(35) {
// _show_corners(corners=FRONT+LEFT+TOP);
// _show_corners(corners=FRONT+RIGHT+TOP);
// _show_corners(corners=FRONT+LEFT+BOT);
// _show_corners(corners=FRONT+RIGHT+BOT);
// }
// xdistribute(35) {
// _show_corners(corners=TOP+LEFT+BACK);
// _show_corners(corners=TOP+RIGHT+BACK);
// _show_corners(corners=BOT+LEFT+BACK);
// _show_corners(corners=BOT+RIGHT+BACK);
// }
// }
// Figure(3D,Big,NoScales,VPD=340): Vectors pointing toward an edge select the corners and the ends of the edge.
// ydistribute(55) {
// xdistribute(35) {
// _show_corners(corners=BOT+RIGHT);
// _show_corners(corners=BOT+BACK);
// _show_corners(corners=BOT+LEFT);
// _show_corners(corners=BOT+FRONT);
// }
// xdistribute(35) {
// _show_corners(corners=FWD+RIGHT);
// _show_corners(corners=BACK+RIGHT);
// _show_corners(corners=BACK+LEFT);
// _show_corners(corners=FWD+LEFT);
// }
// xdistribute(35) {
// _show_corners(corners=TOP+RIGHT);
// _show_corners(corners=TOP+BACK);
// _show_corners(corners=TOP+LEFT);
// _show_corners(corners=TOP+FRONT);
// }
// }
// Figure(3D,Med,NoScales,VPD=225): Vectors pointing toward a face select the corners of the face.
// ydistribute(55) {
// xdistribute(35) {
// _show_corners(corners=LEFT);
// _show_corners(corners=FRONT);
// _show_corners(corners=RIGHT);
// }
// xdistribute(35) {
// _show_corners(corners=TOP);
// _show_corners(corners=BACK);
// _show_corners(corners=BOTTOM);
// }
// }
// Figure(3D,Med,NoScales,VPD=200): Corners by name
// xdistribute(35) {
// _show_corners(corners="ALL");
// _show_corners(corners="NONE");
// }
// Figure(3D,Big,NoScales,VPD=300): Next are some examples showing how you can combine corner descriptors to obtain different corner sets. You can specify corner sets numerically or by adding together named directions. The third example shows a list of two corner specifications, giving all the corners on the front face or the right face.
// xdistribute(52){
// _show_corners(_corners([1,-1,-1]),toplabel=["corners=[1,-1,-1]"]);
// _show_corners(_corners(BOT+RIGHT+FRONT),toplabel=["corners=BOT+RIGHT+FRONT"]);
// _show_corners(_corners([FRONT,RIGHT]), toplabel=["corners=[FRONT,RIGHT]"]);
// }
// Figure(3D,Big,NoScales,VPD=300): Corners for one edge, two edges, and all the edges except the two on one edge. Note that since the default is all edges, you only need to give the except argument in this case:
// xdistribute(52){
// _show_corners(_corners(FRONT+TOP), toplabel=["corners=FRONT+TOP"]);
// _show_corners(_corners([FRONT+TOP,BOT+BACK]), toplabel=["corners=[FRONT+TOP,"," BOT+BACK]"]);
// _show_corners(_corners("ALL",FRONT+TOP), toplabel=["(corners=\"ALL\")","except=FRONT+TOP"]);
// }
// Figure(3D,Med,NoScales,VPD=240): The first example shows a single corner removed from the top corners using a numerical vector. The second one shows removing a set of two corner descriptors from the implied set of all corners.
// xdistribute(58){
// _show_corners(_corners(TOP,[1,1,1]), toplabel=["corners=TOP","except=[1,1,1]"]);
// _show_corners(_corners("ALL",[FRONT+RIGHT+TOP,FRONT+LEFT+BOT]),
// toplabel=["except=[FRONT+RIGHT+TOP,"," FRONT+LEFT+BOT]"]);
// }
// Subsection: Anchoring of Non-Rectangular Objects and Anchor Type (atype)
// We focused above on rectangular objects that have well-defined faces and edges aligned with the coordinate axes.
// Things get difficult when the objects are curved, or even when their edges are not neatly aligned with the coordinate axes.
// In these cases, the library may provide multiple different anchoring schemes, called the anchor types. When a module supports
// multiple anchor types, use the `atype=` parameter to select the anchor type you need.
// .
// First consider the case of a simple rectangle whose corners have been rounded. Where should the anchors lie?
// The default anchor type puts them in the same location as the anchors of an unrounded rectangle, which means that for
// positive rounding radii, they are not even located on the perimeter of the object.
// Figure(2D,Med,NoAxes): Default "box" atype anchors for a rounded {{rect()}}
// rect([100,50], rounding=[10,0,0,-20],chamfer=[0,10,-20,0]) show_anchors();
// Continues:
// This choice enables you to position the box, or attach things to it, without regard to its rounding or chamfers. If you need to
// anchor onto the roundovers or chamfers then you can use the "perim" anchor type:
// Figure(2D,Med,NoAxes): The "perim" atype for a rounded and chamfered {{rect()}}
// rect([100,50], rounding=[10,0,0,-20],chamfer=[0,10,-20,0],atype="perim") show_anchors();
// Continues:
// With this anchor type, the anchors are located on the perimeter. For positive roundings they point in the standard anchor direction;
// for negative roundings they are parallel to the base. As noted above, for circles, cylinders, and spheres, the anchor point is
// determined by choosing the point where the anchor vector intersects the shape. On a circle, this results in an anchor whose direction
// matches the user provided anchor vector. But on an ellipse, something else happens:
// Figure(2D,Med,NoAxes): Anchors on an ellipse. The red arrow shows a TOP+RIGHT anchor direction.
// ellipse([70,30]) show_anchors();
// stroke([[0,0],[45,45]], color="red",endcap2="arrow2");
// Continues:
// For a TOP+RIGHT anchor direction, the surface normal at the intersection point does not match the anchor direction,
// so the direction of the anchor shown in blue does not match the direction specified, in red.
// Anchors computed this way have anchor type "intersect". When a shape is concave, intersection anchors can produce
// a result buried inside the shape's concavity. Consider the RIGHT anchor of this supershape example:
// Figure(2D,Med,NoAxes): A supershape with "intersect" anchor type:
// supershape(n=150,r=75, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="intersect") show_anchors();
// Continues:
// A different anchor type called "hull" finds anchors that are on the convex hull of the shape.
// Figure(2D,Med,NoAxes): A supershape with "hull" anchor type:
// supershape(n=150,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="hull") show_anchors();
// Continues:
// Hull anchoring works by creating the line (or plane in 3D) that is normal to the specified anchor direction, and
// finding the point farthest from the center that intersects that line (or plane).
// Figure(2D,Med,NoAxes): Finding the RIGHT and BACK+LEFT "hull" anchors
// supershape(n=128,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="hull") {
// position(RIGHT) color_this("red")rect([1,90],anchor=LEFT);
// attach(RIGHT)anchor_arrow2d(13);
// attach(BACK+LEFT) {
// anchor_arrow2d(13);
// color_this("red")rect([30,1]);
// }
// }
// Continues:
// In the example the RIGHT anchor is found when the normal line (shown in red) is tangent to the shape at two points.
// The anchor is then taken to be the midpoint. The BACK+LEFT anchor occurs with a single tangent point, and the
// anchor point is located at the tangent point. For circles intersection is done to the exact circle, but for other
// shapes these calculations are done on the point lists that defines the shape, so if you change the number of points
// in the list, the precise location of the anchors can change. You can also get surprising results if your point list is badly chosen.
// Figure(2D,Med,NoAxes): Circle anchor in blue. The red anchor is computed to a point list of a circle with 17 segments.
// circle(r=31,$fn=128) attach(TOP)anchor_arrow2d(15);
// region(circle(r=33,$fn=17)) {color("red")attach(TOP)anchor_arrow2d(13);}
// Continues:
// The figure shows a large horizontal offset due to a poor choice of sampling for the circular shape when using the "hull" anchor type.
// The determination of "hull" or "intersect" anchors may depend on the location of the centerpoint used in the computation.
// Some of the modules allow you to change the centerpoint using a `cp=` argument. If you need to change the centerpoint for
// a module that does not provide this option, you can use the generic {{region()}} module, which will let you specify a centerpoint.
// The default center point is the centroid, specified by "centroid". You can also choose "mean", which gives the mean of all
// the data points, or "bbox", which gives the centerpoint of the bounding box for the data. Your last option for centerpoint is to
// choose an arbitrary point that meets your needs.
// Figure(2D,Med,NoAxes): The centerpoint for "intersect" anchors is located at the red dot
// region(supershape(n=128,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9),atype="intersect",cp=[0,30]) show_anchors();
// color("red")back(30)circle(r=2,$fn=16);
// Continues:
// Note that all the anchors for an object have to be determined based on one anchor type and relative to the same centerpoint.
// The supported anchor types for each module appear in the "Anchor Types" section of its entry.
// Section: Attachment Positioning
// Module: position()
// Synopsis: Attaches children to a parent object at an anchor point.
// SynTags: Trans
// Topics: Attachments
// See Also: attachable(), attach(), orient()
// Usage:
// PARENT() position(at) CHILDREN;
// Description:
// Attaches children to a parent object at an anchor point. For a step-by-step explanation
// of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// at = The vector, or name of the parent anchor point to attach to.
// Side Effects:
// `$attach_anchor` for each `from=` anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
// `$attach_to` is set to `undef`.
// `$edge_angle` is set to the angle of the edge if the anchor is on an edge and the parent is a prismoid, or vnf with "hull" anchoring
// `$edge_length` is set to the length of the edge if the anchor is on an edge and the parent is a prismoid, or vnf with "hull" anchoring
// Example:
// spheroid(d=20) {
// position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
// position(RIGHT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
// position(FRONT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
// }
module position(at,from)
{
if (is_def(from)){
echo("'from' argument of position() has changed to 'at' and will be removed in a future version");
}
dummy0=assert(num_defined([at,from])==1, "Cannot give both `at` argument and the deprectated `from` argument to position()");
at = first_defined([at,from]);
req_children($children);
dummy1=assert($parent_geom != undef, "No object to position relative to.");
anchors = (is_vector(at)||is_string(at))? [at] : at;
two_d = _attach_geom_2d($parent_geom);
for (anchr = anchors) {
anch = _find_anchor(anchr, $parent_geom);
$edge_angle = len(anch)==5 ? struct_val(anch[4],"edge_angle") : undef;
$edge_length = len(anch)==5 ? struct_val(anch[4],"edge_length") : undef;
$attach_to = undef;
$attach_anchor = anch;
translate(anch[1]) children();
}
}
// Module: orient()
// Synopsis: Orients children's tops in the directon of the specified anchor.
// SynTags: Trans
// Topics: Attachments
// See Also: attachable(), attach(), position()
// Usage:
// PARENT() orient(anchor, [spin]) CHILDREN;
// Description:
// Orients children such that their top is tilted in the direction of the specified parent anchor point.
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// anchor = The anchor on the parent which you want to match the orientation of.
// spin = The spin to add to the children. (Overrides anchor spin.)
// Side Effects:
// `$attach_to` is set to `undef`.
// Example: When orienting to an anchor, the spin of the anchor may cause confusion:
// prismoid([50,50],[30,30],h=40) {
// position(TOP+RIGHT)
// orient(RIGHT)
// prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
// }
// Example: You can override anchor spin with `spin=`.
// prismoid([50,50],[30,30],h=40) {
// position(TOP+RIGHT)
// orient(RIGHT,spin=0)
// prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
// }
// Example: Or you can anchor the child from the back
// prismoid([50,50],[30,30],h=40) {
// position(TOP+RIGHT)
// orient(RIGHT)
// prismoid([30,30],[0,5],h=20,anchor=BOT+BACK);
// }
module orient(anchor, spin) {
req_children($children);
check=
assert($parent_geom != undef, "No parent to orient from!")
assert(is_string(anchor) || is_vector(anchor));
anch = _find_anchor(anchor, $parent_geom);
two_d = _attach_geom_2d($parent_geom);
fromvec = two_d? BACK : UP;
spin = default(spin, anch[3]);
dummy=assert(is_finite(spin));
$attach_to = undef;
if (two_d)
rot(spin)rot(from=fromvec, to=anch[2]) children();
else
rot(spin, from=fromvec, to=anch[2]) children();
}
// Module: align()
// Synopsis: Position children with alignment to parent edges.
// SynTags: Trans
// Topics: Attachments
// See Also: attachable(), attach(), position(), orient()
// Usage:
// PARENT() align(anchor, [align], [inside=], [inset=], [shiftout=], [overlap=]) CHILDREN;
// Description:
// Place a child on the face identified by `anchor`. If align is not given or is CENTER
// then the child will be centered on top of the specified face, outside the parent object. The align parameter is a
// direction defining an edge or corner to align to. The child will be aligned to that edge or corner by
// choosing an appropriate anchor on the child.
// Like {{position()}} this module never rotates the child. If you give `anchor=RIGHT` then the child
// will be given the LEFT anchor and placed adjacent to the parent. You can use `orient=` or `spin=`
// with the child and the alignment will adjust to select the correct child anchor. Note that if
// you spin the child by an amount not a multiple of 90 degrees then an edge of the child will be
// placed against the parent. This module makes it easy to place children aligned flush with the edges
// of the parent, even after orienting them or spinning them. In contrast {{position()}} can
// do the same thing but you would have to figure out the correct child anchor, which is not always obvious.
// .
// Because `align()` works by setting the child anchor, it overrides any anchor you specify to the child:
// **any `anchor=` value given to the child is ignored.**
// .
// Several options can adjust how the child is positioned. You can specify `inset=` to inset the
// aligned object from its alignment location. If you set `inside=true` then the
// child will appear inside the parent instead of on its surface so that you can use {{diff()}} to subract it.
// In this case the child recieved a default "remove" tag. The `shiftout=` option works with `inside=true` to
// shift the child out by the specified distance so that the child doesn't exactly align with the parent.
// .
// Note that in the description above the anchor was said to define a "face". You can also use this module
// with an edge anchor, in which case a corner of the child will be placed in contact with the specified
// edge and the align direction will shift the child to either end of the edge. You can even give a
// corner as the anchor point, but in that case the only allowed alignment is CENTER.
// .
// If you give a list of anchors and/or a list of align directions then all combinations are generated.
// In this way align() acts like a distributor, creating multiple copies of the child.
// Named anchors are not supported by `align()`.
// Arguments:
// anchor = parent anchor or list of parent anchors for positioning children.
// align = optional alignment direction or directions for aligning the children. Default: CENTER
// ---
// inside = if true, place object inside the parent instead of outside. Default: false
// inset = shift the child away from the alignment edge/corner by this amount. Default: 0
// shiftout = Shift an inside object outward so that it overlaps all the aligned faces. Default: 0
// overlap = Amount to sink the child into the parent. Defaults to `$overlap` which is zero by default.
// Side Effects:
// `$anchor` set to the anchor value used for the child.
// `$align` set to the align value used for the child.
// `$idx` set to a unique index for each child, increasing by alignment first.
// `$attach_anchor` for each anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
// if `inside` is true then set default tag to "remove"
// Example: Cuboid positioned on the right of its parent. Note that it is in its native orientation.
// cuboid([20,35,25])
// align(RIGHT)
// color("lightgreen")cuboid([5,1,9]);
// Example: Child would require anchor of RIGHT+FRONT+BOT if placed with {{position()}}.
// cuboid([50,40,15])
// align(TOP,RIGHT+FRONT)
// color("lightblue")prismoid([10,5],[7,4],height=4);
// Example: Child requires a different anchor for each position, so a simple explicit specification of the anchor for children is impossible in this case, without using two separate commands.
// cuboid([50,40,15])
// align(TOP,[RIGHT,LEFT])
// color("lightblue")prismoid([10,5],[7,4],height=4);
// Example: If you spin the child 90 deg it is still flush with the edge of the parent. In this case the required anchor for the child is BOT+FWD:
// cuboid([50,40,15])
// align(TOP,RIGHT)
// color("lightblue")
// prismoid([10,5],[7,4],height=4,spin=90);
// Example: Here the child is placed on the RIGHT face. Notice how the TOP+LEFT anchor of the prismoid is aligned with the edge of the parent. The prismoid remains in the same orientation.
// cuboid([50,40,15])
// align(RIGHT,TOP)
// color("lightblue")prismoid([10,5],[7,4],height=4);
// Example: If you change the orientation of the child it still appears aligned flush in its changed orientation:
// cuboid([50,40,15])
// align(TOP, RIGHT)
// color("lightblue")prismoid([10,5],[7,4],height=4,orient=DOWN);
// Example: The center of the cubes edge is lined up with the center of the prismoid edge, so this result is the expected result:
// prismoid(50,30,25)
// align(RIGHT,FRONT)
// color("lightblue")cuboid(8);
// Example: Spinning the cube means that the corner of the cube is the most extreme point, so that's what aligns with the front edge of the parent:
// cuboid([50,40,15])
// align(TOP,FWD)
// color("lightblue")cuboid(9,spin=22);
// Example: A similar thing happens if you attach a cube to a cylinder with an arbitrary anchor angle:
// cyl(h=20,d=10,$fn=128)
// align([1,.3],TOP)
// color("lightblue")cuboid(5);
// Example: Orienting the child is done in the global coordinate system (as usual) not in the parent coordinate system. Note that the blue prismoid is not lined up with the parent face. (To place the child on the face use {{attach()}}.
// prismoid(50,30,25)
// align(RIGHT)
// color("lightblue")prismoid([10,5],[7,4],height=4,orient=RIGHT);
// Example: Setting `inside=true` enables us to subtract the child from the parent with {{diff()}}. The "remove" tag is automatically applied when you set `inside=true`, and we used `shiftout=0.01` to prevent z-fighting on the faces.
// diff()
// cuboid([40,30,10])
// align(FRONT,TOP,inside=true,shiftout=0.01)
// prismoid([10,5],[7,5],height=4);
// Example: Setting `inset` shifts all of the children away from their aligned edge, which is a different direction for each child.
// cuboid([40,30,30])
// align(FRONT,[TOP,BOT,LEFT,RIGHT,TOP+RIGHT,BOT+LEFT], inset=3)
// color("green") cuboid(5);
// Example: Changing the child characteristics based on the alignment
// cuboid([20,20,8])
// align(TOP,[for(i=[-1:1], j=[-1:1]) [i,j]])
// color("orange")
// if (norm($align)==0) cuboid([3,3,1]);
// else if (norm($align)==norm([1,1])) cuboid([3,3,4.5]);
// else cuboid(3);
// Example: In this example the pink cubes are positioned onto an edge. They meet edge-to-edge. Aligning left shifts the cube to the left end of the edge.
// cuboid([30,30,20])
// align(TOP+BACK,[CTR,LEFT])
// color("pink")cuboid(4);
// Example: Normally `overlap` is used to create a tiny overlap to keep CGAL happy, but you can also give it a large value as shown here:
// cuboid([30,30,20])
// align(TOP+BACK,[RIGHT,CTR,LEFT],overlap=2)
// color("lightblue")cuboid(4);
module align(anchor,align=CENTER,inside=false,inset=0,shiftout=0,overlap)
{
req_children($children);
overlap = (overlap!=undef)? overlap : $overlap;
dummy1=assert($parent_geom != undef, "No object to align to.")
assert(is_undef($attach_to), "Cannot use align() as a child of attach()");
if (is_undef($align_msg) || $align_msg)
echo("ALERT: align() has changed, May 1, 2024. See the wiki and attach(align=). $align_msg=false disables this message");
anchor = is_vector(anchor) ? [anchor] : anchor;
align = is_vector(align) ? [align] : align;
two_d = _attach_geom_2d($parent_geom);
factor = inside?-1:1;
for (i = idx(anchor)) {
$align_msg=false; // Remove me when removing the message above
face = anchor[i];
$anchor=face;
dummy=
assert(!is_string(face),
str("Named anchor \"",face,"\" given for anchor, but align() does not support named anchors"))
assert(is_vector(face) && (len(face)==2 || len(face)==3),
str("Invalid face ",face, ". Must be a 2-vector or 3-vector"));
thisface = two_d? _force_anchor_2d(face) : point3d(face);
for(j = idx(align)) {
edge=align[j];
$idx = j+len(align)*i;
$align=edge;
dummy1=assert(is_vector(edge) && (len(edge)==2 || len(edge)==3),
"align direction must be a 2-vector or 3-vector");
thisedge = two_d? _force_anchor_2d(edge) : point3d(edge);
dummy=assert(all_zero(v_mul(thisedge,thisface)),
str("align (",thisedge,") cannot include component parallel to anchor ",thisface));
thisface_anch = _find_anchor(thisface, $parent_geom);
inset_dir = two_d ? -thisface
: unit(thisface_anch[1]-_find_anchor([thisedge.x,0,0]+thisface, $parent_geom)[1],CTR)
+unit(thisface_anch[1]-_find_anchor([0,thisedge.y,0]+thisface, $parent_geom)[1],CTR)
+unit(thisface_anch[1]-_find_anchor([0,0,thisedge.z]+thisface, $parent_geom)[1],CTR);
pos_anch = _find_anchor(thisface+thisedge, $parent_geom);
$attach_alignment = thisedge-factor*thisface;
$attach_anchor=list_set(pos_anch,2,UP);
translate(pos_anch[1]
+inset*inset_dir
+shiftout*(thisface_anch[2]-inset_dir)
-overlap*thisface_anch[2])
default_tag("remove",inside) children();
}
}
}
// Quantize anchor entry to {-1,0,1}
function _quant_anch(x) = approx(x,0) ? 0 : sign(x);
// Make arbitrary anchor legal for a given geometry
function _make_anchor_legal(anchor,geom) =
in_list(geom[0], ["prismoid","trapezoid"]) ? [for(v=anchor) _quant_anch(v)]
: in_list(geom[0], ["conoid", "extrusion_extent"]) ? [anchor.x,anchor.y, _quant_anch(anchor.z)]
: anchor;
// Module: attach()
// Synopsis: Attaches children to a parent object at an anchor point and with anchor orientation.
// SynTags: Trans
// Topics: Attachments
// See Also: attachable(), position(), align(), face_profile(), edge_profile(), corner_profile()
// Usage:
// PARENT() attach(parent, child, [align=], [spin=], [overlap=], [inside=], [inset=], [shiftout=]) CHILDREN;
// PARENT() attach(parent, [overlap=], [spin=]) CHILDREN;
// Description:
// Attaches children to a parent object at an anchor point or points, oriented in the anchor direction.
// This module differs from {{position()}} and {{align()}} in that it rotates the children to
// the anchor direction, which generally means it places the children on the surface of a parent.
// There are two modes of operation, parent anchor (single argument) and parent-child anchor (double argument).
// .
// The parent-child anchor (double argument) version is usually easier to use, and it is more powerful because it supports
// alignment. You provide an anchor on the parent (`parent`) and an anchor on the child (`child`).
// This module connects the `child` anchor on the child to the `parent` anchor on the parent.
// Imagine pointing the parent and child anchor arrows at each other and pushing the objects
// together until they meet at the anchor point. The most basic case
// is `attach(TOP,BOT)` which puts the bottom of the child onto the top of the parent. If you
// do `attach(RIGHT,BOT)` this puts the bottom of the child onto the right anchor of the parent.
// When an object is attached to the top or bottom its BACK direction will remaing pointing BACK.
// When an object is attached to one of the other anchors its FRONT will be pointed DOWN and its
// BACK pointed UP. You can change this using the `spin=` argument to attach(). Note that this spin
// rotates around the attachment vector and is not the same as the spin argument to the child, which
// will usually rotate around some other direction that may be hard to predict. For 2D objects you cannot
// give spin because it is not possible to spin around the attachment vector; spinning the object around the Z axis
// would change the child orientation so that the anchors are no longer parallel. Furthermore, any spin
// parameter you give to the child will be ignored so that the attachment condition of parallel anchors is preserved.
// .
// As with {{align()}} you can use the `align=` parameter to align the child to an edge or corner of the
// face where that child is attached. For example `attach(TOP,BOT,align=RIGHT)` would stand the child
// up on the top while aligning it with the right edge of the top face, and `attach(RIGHT,BOT,align=TOP)` which
// stand the object on the right face while aligning with the top edge. If you apply spin using the
// argument to `attach()` then it will be taken into account for the alignment. If you apply spin with
// a parameter to the child it will NOT be taken into account. The special spin value "align" will
// spin the child so that the child's BACK direction is pointed towards the aligned edge on the parent.
// Note that spin is not permitted for
// 2D objects because it would change the child orientation so that the anchors are no longer parallel.
// When you use `align=` you can also adjust the position using `inset=`, which shifts the child
// away from the edge or corner it is aligned to.
// .
// Note that the concept of alignment doesn't always make sense for objects without corners, such as spheres or cylinders.
// In same cases the alignments using such children will be odd because the alignment computation is trying to
// place a non-existent corner somewhere. Because attach() doesn't have in formation about the child when
// it runs it cannot handle curved shapes differently from cubes, so this behavior cannot be changed.
// .
// If you give `inside=true` then the anchor arrows are lined up so they are pointing the same direction and
// the child object will be located inside the parent. In this case a default "remove" tag is applied to
// the children.
// .
// Because the attachment process forces an orientation and anchor point for the child, it overrides
// any such specifications you give to the child: **both `anchor=` and `orient=` given to the child are
// ignored** with the **double argument** version of `attach()`. As noted above, you can give `spin=` to the
// child but using the `spin=` parameter to `attach()` is more likely to be useful.
// .
// For the single parameter version of `attach()` you give only the `parent` anchor. The `align` direction
// is not permitted. In this case the child is placed at the specified parent anchor point
// and rotated to the anchor direction. For example, `attach(TOP) cuboid(2);` will place a small
// cube **with its center** located at the TOP anchor of the parent, so just half the cube will project
// from the parent. If you want the cube sitting on the parent you need to anchor the cube to its bottom:
// `attach(TOP) cuboid(2,anchor=BOT);`.
// .
// The **single argument** version of `attach()` **respects `anchor=` and `orient=` given to the child.**
// These options will probably be necessary, in fact, to get the child correctly positioned. Note that
// giving `spin=` to `attach()` in this case is the same as applying `zrot()` to the child.
// .
// You can overlap attached children into the parent by giving the `$overlap` value
// which is 0 by default, or by the `overlap=` argument. This is to prevent OpenSCAD
// from making non-manifold objects. You can define `$overlap=` as an argument in a parent
// module to set the default for all attachments to it. When you give `inside=true`, a positive overlap
// value shifts the child object outward.
// .
// If you specify an `inset=` value then the child is shifted away from any edges it is aligned to, towards the middle
// of the parent. The `shiftout=` parameter is intended to simplify differences with aligned objects
// placed inside the parent. It will shift the child outward along every direction where it is aligned with
// the parent. For an inside child this is equivalent to giving a positive overlap and negative inset value.
// For a child with `inside=false` it is equivalent to a negative overlap and negative inset.
// .
// For a step-by-step explanation of
// attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// parent = The parent anchor point to attach to or a list of parent anchor points.
// child = Optional child anchor point. If given, orients the child to connect this anchor point to the parent anchor.
// ---
// align = If `child` is given you can specify alignment or list of alistnments to shift the child to an edge or corner of the parent.
// inset = Shift aligned children away from their alignment edge/corner by this amount. Default: 0
// overlap = Amount to sink child into the parent. Equivalent to `down(X)` after the attach. This defaults to the value in `$overlap`, which is `0` by default.
// inside = If `child` is given you can set `inside=true` to attach the child to the inside of the parent for diff() operations. Default: false
// shiftout = Shift an inside object outward so that it overlaps all the aligned faces. Default: 0
// spin = Amount to rotate the parent around the axis of the parent anchor. Can set to "align" to align the child's BACK with the parent aligned edge. (Only permitted in 3D.)
// Side Effects:
// `$anchor` set to the parent anchor value used for the child.
// `$align` set to the align value used for the child.
// `$idx` set to a unique index for each child, increasing by alignment first.
// `$attach_anchor` for each anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
// if inside is true then set default tag to "remove"
// `$attach_to` is set to the value of the `child` argument, if given. Otherwise, `undef`
// `$edge_angle` is set to the angle of the edge if the anchor is on an edge and the parent is a prismoid or vnf with "hull" anchoring
// `$edge_length` is set to the length of the edge if the anchor is on an edge and the parent is a prismoid or vnf with "hull" anchoring
// Example: Cylinder placed on top of cube:
// cuboid(50)
// attach(TOP,BOT) cylinder(d1=30,d2=15,h=25);
// Example: Cylinder on right and front side of cube:
// cuboid(50)
// attach([RIGHT,FRONT],BOT) cylinder(d1=30,d2=15,h=25);
// Example: Using `align` can align child object(s) with edges
// prismoid(50,25,25) color("green"){
// attach(TOP,BOT,align=[BACK,FWD]) cuboid(4);
// attach(RIGHT,BOT,align=[TOP,BOT]) cuboid(4);
// }
// Example: One aligned to the corner upside down (light blue) and one inset fromt the corner (pink), one aligned on a side (orange) and one rotated and aligned (green).
// cuboid(30) {
// attach(TOP,TOP,align=FRONT+RIGHT) color("lightblue") prismoid(5,3,3);
// attach(TOP,BOT,inset=3,align=FRONT+LEFT) color("pink") prismoid(5,3,3);
// attach(FRONT,RIGHT,align=TOP) color("orange") prismoid(5,3,3);
// attach(FRONT,RIGHT,align=RIGHT,spin=90) color("lightgreen") prismoid(5,3,3);
// }
// Example: Rotation not a multiple of 90 degrees with alignment. The children are aligned on a corner.
// cuboid(30)
// attach(FRONT,BOT,spin=33,align=[RIGHT,LEFT,TOP,BOT,RIGHT+TOP])
// color("lightblue")cuboid(4);
// Example: Anchoring the cone onto the sphere gives a single point of contact.
// spheroid(d=20)
// attach([1,1.5,1], BOTTOM) cyl(l=11.5, d1=10, d2=5);
// Example: Using the `overlap` option can help:
// spheroid(d=20)
// attach([1,1.5,1], BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5);
// Example: Alignment works on the sides of cylinders but you can only align with either the top or bototm face:
// cyl(h=30,d=10)
// attach([LEFT,[1,1.3]], BOT,align=TOP) cuboid(6);
// Example: Attaching to edges. The light blue and orange objects are attached to edges. The purple object is attached to an edge and aligned.
// prismoid([20,10],[10,10],7){
// attach(RIGHT+TOP,BOT,align=FRONT) color("pink")cuboid(2);
// attach(BACK+TOP, BOT) color("lightblue")cuboid(2);
// attach(RIGHT+BOT, RIGHT) color("orange")cyl(h=8,d=1);
// }
// Example: Attaching inside the parent. For inside attachment the anchors are lined up pointing the same direction, so the most natural way to anchor the child is using its TOP anchor. This is equivalent to anchoring outside with the BOTTOM anchor and then lowering the child into the parent by its full depth.
// back_half()
// diff()
// cuboid(20)
// attach(TOP,TOP,inside=true,shiftout=0.01) cyl(d1=10,d2=5,h=10);
// Example: Attaching inside the parent with alignment
// diff()
// cuboid(20){
// attach(TOP,TOP,inside=true,align=RIGHT,shiftout=.01) cuboid([8,7,3]);
// attach(TOP,TOP,inside=true,align=LEFT+FRONT,shiftout=0.01) cuboid([3,4,5]);
// attach(RIGHT+FRONT, TOP, inside=true) cuboid([10,3,5]);
// attach(RIGHT+FRONT, TOP, inside=true, align=TOP,shiftout=.01) cuboid([5,1,2]);
// }
// Example: Attaching a 3d edge mask. Simple 2d masks can be done using {{edge_profile()}} but this mask varies along its length.
// module wavy_edge(length,cycles, r, steps, n)
// {
// rmin = is_vector(r) ? r[0] : 0.01;
// rmax = is_vector(r) ? r[1] : r;
// layers = [for(z=[0:steps])
// let(
// r=rmin+(rmax-rmin)/2*(cos(z*360*cycles/steps)+1)
// )
// path3d( concat([[0,0]],
// arc(corner=path2d([BACK,CTR,RIGHT]), n=n, r=r)),
// z/steps*length-length/2)
// ];
// attachable([rmax,rmax,length]){
// skin(layers,slices=0);
// children();
// }
// }
// diff()
// cuboid(25)
// attach([TOP+RIGHT,TOP+LEFT,TOP+FWD, FWD+RIGHT], FWD+LEFT, inside=true, shiftout=.01)
// wavy_edge(length=25.1,cycles=1.4,r=4,steps=24,n=15);
module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, inside=false, from, to)
{
dummy3=
assert(num_defined([to,child])<2, "Cannot combine deprecated 'to' argument with 'child' parameter")
assert(num_defined([from,parent])<2, "Cannot combine deprecated 'from' argument with 'parent' parameter")
assert(spin!="align" || is_def(align), "Can only set spin to \"align\" when the 'align' parameter is given")
assert(is_finite(spin) || spin=="align", "Spin must be a number (unless align is given)")
assert((is_undef(overlap) || is_finite(overlap)) && (is_def(overlap) || is_undef($overlap) || is_finite($overlap)),
str("Provided ",is_def(overlap)?"":"$","overlap is not valid."));
if (is_def(to))
echo("The 'to' option to attach() is deprecated and will be removed in the future. Use 'child' instead.");
if (is_def(from))
echo("The 'from' option to attach(0 is deprecated and will be removed in the future. Use 'parent' instead");
if (norot)
echo("The 'norot' option to attach() is deprecated and will be removed in the future. Use position() instead.");
req_children($children);
dummy=assert($parent_geom != undef, "No object to attach to!")
assert(is_undef(child) || is_string(child) || (is_vector(child) && (len(child)==2 || len(child)==3)),
"child must be a named anchor (a string) or a 2-vector or 3-vector")
assert(is_undef(align) || !is_string(child), "child is a named anchor. Named anchors are not supported with align=");
two_d = _attach_geom_2d($parent_geom);
basegeom = $parent_geom[0]=="conoid" ? attach_geom(r=2,h=2,axis=$parent_geom[5])
: $parent_geom[0]=="prismoid" ? attach_geom(size=[2,2,2],axis=$parent_geom[4])
: attach_geom(size=[2,2,2]);
childgeom = attach_geom([2,2,2]);
child_abstract_anchor = is_vector(child) && !two_d ? _find_anchor(_make_anchor_legal(child,childgeom), childgeom) : undef;
overlap = (overlap!=undef)? overlap : $overlap;
parent = first_defined([parent,from]);
anchors = is_vector(parent) || is_string(parent) ? [parent] : parent;
align_list = is_undef(align) ? [undef]
: is_vector(align) || is_string(align) ? [align] : align;
dummy4 = assert(is_string(parent) || is_list(parent), "Invalid parent anchor or anchor list")
assert(spin==0 || (!two_d || is_undef(child)), "spin is not allowed for 2d objects when 'child' is given");
child_temp = first_defined([child,to]);
child = two_d ? _force_anchor_2d(child_temp) : child_temp;
dummy2=assert(align_list==[undef] || is_def(child), "Cannot use 'align' without 'child'")
assert(!inside || is_def(child), "Cannot use 'inside' without 'child'")
assert(inset==0 || is_def(child), "Cannot specify 'inset' without 'child'")
assert(inset==0 || is_def(align), "Cannot specify 'inset' without 'align'")
assert(shiftout==0 || is_def(child), "Cannot specify 'shiftout' without 'child'");
factor = inside?-1:1;
$attach_to = child;
for (anch_ind = idx(anchors)) {
dummy=assert(is_string(anchors[anch_ind]) || (is_vector(anchors[anch_ind]) && (len(anchors[anch_ind])==2 || len(anchors[anch_ind])==3)),
str("parent[",anch_ind,"] is ",anchors[anch_ind]," but it must be a named anchor (string) or a 2-vector or 3-vector"))
assert(align_list==[undef] || !is_string(anchors[anch_ind]),
str("parent[",anch_ind,"] is a named anchor (",anchors[anch_ind],"), but named anchors are not supported with align="));
anchor = is_string(anchors[anch_ind])? anchors[anch_ind]
: two_d?_force_anchor_2d(anchors[anch_ind])
: point3d(anchors[anch_ind]);
$anchor=anchor;
anchor_data = _find_anchor(anchor, $parent_geom);
$edge_angle = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_angle") : undef;
$edge_length = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_length") : undef;
$edge_end1 = len(anchor_data)==5 ? struct_val(anchor_data[4],"vec") : undef;
anchor_pos = anchor_data[1];
anchor_dir = factor*anchor_data[2];
anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3]
: let(spin_dir = rot(anchor_data[3],from=UP, to=-anchor_dir, p=BACK))
_compute_spin(anchor_dir,spin_dir);
parent_abstract_anchor = is_vector(anchor) && !two_d ? _find_anchor(_make_anchor_legal(anchor,basegeom),basegeom) : undef;
for(align_ind = idx(align_list)){
align = is_undef(align_list[align_ind]) ? undef
: assert(is_vector(align_list[align_ind],2) || is_vector(align_list[align_ind],3), "align direction must be a 2-vector or 3-vector")
two_d ? _force_anchor_2d(align_list[align_ind])
: point3d(align_list[align_ind]);
spin = is_num(spin) ? spin
: align==CENTER ? 0