forked from BelfrySCAD/BOSL2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathattachments.scad
3273 lines (3112 loc) · 139 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 different ways that 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 = "";
$tag_prefix = "";
$overlap = 0;
$color = "default";
$save_color = undef; // Saved color to revert back for children
$attach_to = undef;
$attach_anchor = [CENTER, CENTER, UP, 0];
$attach_norot = false;
$parent_anchor = BOTTOM;
$parent_spin = 0;
$parent_orient = UP;
$parent_size = undef;
$parent_geom = undef;
$tags_shown = "ALL";
$tags_hidden = [];
_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.
// Subsection: Spin
// Spin is specified with the `spin` argument in most shape modules. Specifying a scalar `spin`
// when creating an object will rotate the object counter-clockwise around the Z axis by the given
// number of degrees. If given as a 3D vector, the object will be rotated around each of the X, Y, Z
// axes by the number of degrees in each component of the vector. Spin is always applied after
// anchoring, and before orientation. Since spin is applied after anchoring it is not what
// you might think of intuitively as spinning the shape. To do that, apply `zrot()` to the shape before anchoring.
// 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. ie: `LEFT+BACK`
// 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 or 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]"]);
// }
// Section: Attachment Positioning
// Module: position()
// Usage:
// position(from) CHILDREN;
//
// Topics: Attachments
// See Also: attachable(), attach(), orient()
//
// 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:
// from = The vector, or name of the parent anchor point to attach to.
// 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(from)
{
req_children($children);
assert($parent_geom != undef, "No object to attach to!");
anchors = (is_vector(from)||is_string(from))? [from] : from;
for (anchr = anchors) {
anch = _find_anchor(anchr, $parent_geom);
$attach_to = undef;
$attach_anchor = anch;
$attach_norot = true;
translate(anch[1]) children();
}
}
// Module: orient()
// Usage:
// orient(dir, [spin=]) CHILDREN;
// orient(anchor=, [spin=]) CHILDREN;
// Topics: Attachments
// Description:
// Orients children such that their top is tilted towards the given direction, or towards the
// direction of a given anchor point on the parent. For a step-by-step explanation of
// attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Arguments:
// dir = The direction to orient towards.
// ---
// anchor = The anchor on the parent which you want to match the orientation of. Use instead of `dir`.
// spin = The spin to add to the children. (Overrides anchor spin.)
// See Also: attachable(), attach(), orient()
// Example: Orienting by Vector
// prismoid([50,50],[30,30],h=40) {
// position(TOP+RIGHT)
// orient(RIGHT)
// prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
// }
// 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(anchor=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(anchor=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(anchor=RIGHT)
// prismoid([30,30],[0,5],h=20,anchor=BOT+BACK);
// }
module orient(dir, anchor, spin) {
req_children($children);
if (!is_undef(dir)) {
spin = default(spin, 0);
check =
assert(anchor==undef, "Only one of dir= or anchor= may be given to orient()")
assert(is_vector(dir))
assert(is_finite(spin));
two_d = _attach_geom_2d($parent_geom);
fromvec = two_d? BACK : UP;
rot(spin, from=fromvec, to=dir) children();
} else {
check=
assert(dir==undef, "Only one of dir= or anchor= may be given to orient()")
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;
$attach_to = undef;
$attach_anchor = anch;
$attach_norot = true;
spin = default(spin, anch[3]);
assert(is_finite(spin));
rot(spin, from=fromvec, to=anch[2]) children();
}
}
// Module: attach()
// Usage:
// attach(from, [overlap=], [norot=]) CHILDREN;
// attach(from, to, [overlap=], [norot=]) CHILDREN;
// Topics: Attachments
// See Also: attachable(), position(), face_profile(), edge_profile(), corner_profile()
// Description:
// Attaches children to a parent object at an anchor point and orientation. Attached objects will
// be overlapped into the parent object by a little bit, as specified by the `$overlap`
// value (0 by default), or by the overriding `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. For a step-by-step explanation of
// attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Arguments:
// from = The vector, or name of the parent anchor point to attach to.
// to = Optional name of the child anchor point. If given, orients the child such that the named anchors align together rotationally.
// ---
// 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.
// norot = If true, don't rotate children when attaching to the anchor point. Only translate to the anchor point.
// Example:
// spheroid(d=20) {
// attach(TOP) down(1.5) cyl(l=11.5, d1=10, d2=5, anchor=BOTTOM);
// attach(RIGHT, BOTTOM) down(1.5) cyl(l=11.5, d1=10, d2=5);
// attach(FRONT, BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5);
// }
module attach(from, to, overlap, norot=false)
{
req_children($children);
assert($parent_geom != undef, "No object to attach to!");
overlap = (overlap!=undef)? overlap : $overlap;
anchors = (is_vector(from)||is_string(from))? [from] : from;
for (anchr = anchors) {
anch = _find_anchor(anchr, $parent_geom);
two_d = _attach_geom_2d($parent_geom);
$attach_to = to;
$attach_anchor = anch;
$attach_norot = norot;
olap = two_d? [0,-overlap,0] : [0,0,-overlap];
if (norot || (norm(anch[2]-UP)<1e-9 && anch[3]==0)) {
translate(anch[1]) translate(olap) children();
} else {
fromvec = two_d? BACK : UP;
translate(anch[1]) rot(anch[3],from=fromvec,to=anch[2]) translate(olap) children();
}
}
}
// Section: Tagging
// Module: tag()
// Usage:
// tag(tag) CHILDREN;
// Topics: Attachments
// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
// Description:
// Assigns the specified tag to all of the children. Note that if you want
// to apply a tag to non-tag-aware objects you need to use {{force_tag()}} instead.
// This works by setting the `$tag` variable, but it provides extra error checking and
// handling of scopes. You may set `$tag` directly yourself, but this is not recommended.
// .
// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Arguments:
// tag = tag string, which must not contain any spaces.
// Side Effects:
// Sets `$tag` to the tag you specify, possibly with a scope prefix.
// Example(3D): Applies the tag to both cuboids instead of having to repeat `$tag="remove"` for each one.
// diff("remove")
// cuboid(10){
// position(TOP) cuboid(3);
// tag("remove")
// {
// position(FRONT) cuboid(3);
// position(RIGHT) cuboid(3);
// }
// }
module tag(tag)
{
req_children($children);
check=
assert(is_string(tag),"tag must be a string")
assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
$tag = str($tag_prefix,tag);
children();
}
// Module: force_tag()
// Usage:
// force_tag([tag]) CHILDREN;
// Topics: Attachments
// See Also: tag(), recolor(), hide(), show_only(), diff(), intersect()
// Description:
// You use this module when you want to make a non-attachable or non-BOSL2 module respect tags.
// It applies to its children the tag specified (or the tag currently in force if you don't specify a tag),
// making a final determination about whether to show or hide the children.
// This means that tagging in children's children will be ignored.
// This module is specifically provided for operating on children that are not tag aware such as modules
// that don't use {{attachable()}} or built in modules such as
// - `polygon()`
// - `projection()`
// - `polyhedron()` (or use [`vnf_polyhedron()`](vnf.scad#vnf_polyhedron))
// - `linear_extrude()` (or use [`linear_sweep()`](regions.scad#linear_sweep))
// - `rotate_extrude()`
// - `surface()`
// - `import()`
// - `difference()`
// - `intersection()`
// - `hull()`
// .
// When you use tag-based modules like {{diff()}} with a non-attachable module, the result may be puzzling.
// Any time a test occurs for display of child() that test will succeed. This means that when diff() checks
// to see if it should show a module it will show it, and when diff() checks to see if it should subtract the module
// it will subtract it. The result will be a hole, possibly with zero-thickness edges or faces. In order to
// get the correct behavior, every non-attachable module needs an invocation of force_tag, even ones
// that are not tagged.
// .
// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Arguments:
// tag = tag string, which must not contain any spaces
// Side Effects:
// Sets `$tag` to the tag you specify, possibly with a scope prefix.
// Example(2D): This example produces the full square without subtracting the "remove" item. When you use non-attachable modules with tags, results are unpredictable.
// diff()
// {
// polygon(square(10));
// move(-[.01,.01])polygon(square(5),$tag="remove");
// }
// Example(2D): Adding force_tag() fixes the model. Note you need to add it to *every* non-attachable module, even the untagged ones, as shown here.
// diff()
// {
// force_tag()
// polygon(square(10));
// force_tag("remove")
// move(-[.01,.01])polygon(square(5));
// }
module force_tag(tag)
{
req_children($children);
check1=assert(is_undef(tag) || is_string(tag),"tag must be a string");
$tag = str($tag_prefix,default(tag,$tag));
assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"));
if(_is_shown())
show_all()
children();
}
// Module: default_tag()
// Usage:
// default_tag(tag) CHILDREN;
// Topics: Attachments
// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
// Description:
// Sets a default tag for all of the children. This is intended to be used to set a tag for a whole module
// that is then used outside the module, such as setting the tag to "remove" for easy operation with {{diff()}}.
// The default_tag() module sets the `$tag` variable only if it is not already
// set so you can have a module set a default tag of "remove" but that tag can be overridden by a {{tag()}}
// in force from a parent. If you use {{tag()}} it will override any previously
// specified tag from a parent, which can be very confusing to a user trying to change the tag on a module.
// .
// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Arguments:
// tag = tag string, which must not contain any spaces.
// Side Effects:
// Sets `$tag` to the tag you specify, possibly with a scope prefix.
// Example(3D): The module thing() is defined with {{tag()}} and the user applied tag of "keep_it" is ignored, leaving the user puzzled.
// module thing() { tag("remove") cuboid(10);}
// diff()
// cuboid(20){
// position(TOP) thing();
// position(RIGHT) tag("keep_it") thing();
// }
// Example(3D): Using default_tag() fixes this problem: the user applied tag does not get overridden by the tag hidden in the module definition.
// module thing() { default_tag("remove") cuboid(10);}
// diff()
// cuboid(20){
// position(TOP) thing();
// position(RIGHT) tag("keep_it") thing();
// }
module default_tag(tag)
{
if ($tag=="") tag(tag) children();
else children();
}
// Module: tag_scope()
// Usage:
// tag_scope([scope]) CHILDREN;
// Description:
// Creates a tag scope with locally altered tag names to avoid tag name conflict with other code.
// This is necessary when writing modules because the module's caller might happen to use the same tags.
// Note that if you directly set the `$tag` variable then tag scoping will not work correctly.
// Example: In this example the ring module uses "remove" tags which will conflict with use of the same tags by the parent.
// module ring(r,h,w=1,anchor,spin,orient)
// {
// tag_scope("ringscope")
// attachable(anchor,spin,orient,r=r,h=h){
// diff()
// cyl(r=r,h=h)
// tag("remove") cyl(r=r-w,h=h+1);
// children();
// }
// }
// // Calling the module using "remove" tags
// // will conflict with internal tag use in
// // the ring module.
// $fn=32;
// diff(){
// ring(10,7,w=4);
// tag("remove")ring(8,8);
// tag("remove")diff("rem"){
// ring(9.5,8,w=1);
// tag("rem")ring(9.5,8,w=.3);
// }
// }
module tag_scope(scope){
req_children($children);
scope = is_undef(scope) ? rand_str(20) : scope;
assert(is_string(scope), "scope must be a string");
assert(undef==str_find(scope," "),str("Scope string \"",scope,"\" contains a space, which is not allowed"));
$tag_prefix=scope;
children();
}
// Section: Attachment Modifiers
// Module: diff()
// Usage:
// diff([remove], [keep]) CHILDREN;
// Topics: Attachments
// See Also: tag(), force_tag(), recolor(), show_only(), hide(), tag_diff(), intersect(), tag_intersect()
// Description:
// Perform a differencing operation using tags to control what happens. This is specifically intended to
// address the situation where you want differences between a parent and child object, something
// that is impossible with the native difference() module.
// The children to diff are grouped into three categories, regardless of nesting level.
// The `remove` argument is a space delimited list of tags specifying objects to
// subtract. The `keep` argument is a similar list of tags giving objects to be kept.
// Objects not matching either the `remove` or `keep` lists form the third category of base objects.
// To produce its output, diff() forms the union of all the base objects and then
// subtracts all the objects with tags in `remove`. Finally it adds in objects listed in `keep`.
// Attachable objects should be tagged using {{tag()}}
// and non-attachable objects with {{force_tag()}}.
// .
// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Arguments:
// remove = String containing space delimited set of tag names of children to difference away. Default: `"remove"`
// keep = String containing space delimited set of tag names of children to keep; that is, to union into the model after differencing is completed. Default: `"keep"`
// Example: Diffing using default tags
// diff()
// cuboid(50) {
// tag("remove") attach(TOP) sphere(d=40);
// tag("keep") attach(CTR) cylinder(h=40, d=10);
// }
// Example: The "hole" items are subtracted from everything else. The other tags can be anything you find convenient.
// diff("hole")
// tag("body")sphere(d=100) {
// tag("pole") zcyl(d=55, h=100); // attach() not needed for center-to-center.
// tag("hole") {
// xcyl(d=55, h=101);
// ycyl(d=55, h=101);
// }
// tag("axle")zcyl(d=15, h=140);
// }
// Example:
// diff(keep="axle")
// sphere(d=100) {
// tag("axle")xcyl(d=40, l=120);
// tag("remove")cuboid([40,120,100]);
// }
// Example: Masking
// diff()
// cube([80,90,100], center=true) {
// edge_mask(FWD)
// rounding_edge_mask(l=max($parent_size)*1.01, r=25);
// }
// Example: Here we subtract the parent object from the child. Because tags propagate to children we need to clear the "remove" tag from the child.
// diff()
// tag("remove")cuboid(10)
// tag("")position(RIGHT+BACK)cyl(r=8,h=9);
// Example(3D,VPR=[104,0,200], VPT=[-0.9,3.03, -0.74], VPD=19,NoAxes,NoScales): A pipe module that subtracts its interior when you call it using diff(). Normally if you union two pipes together, you'll get interfering walls at the intersection, but not here:
// $fn=16;
// // This module must be called by subtracting with "diff"
// module pipe(length, od, id) {
// // Strip the tag the user is using to subtract
// tag("")cylinder(h=length, d=od, center=true);
// // Leave the tag along here, so this one is removed
// cylinder(h=length+.02, d=id, center=true);
// }
// // Draw some intersecting pipes
// diff(){
// tag("remove"){
// pipe(length=5, od=2, id=1.9);
// zrot(10)xrot(75)
// pipe(length=5, od=2, id=1.9);
// }
// // The orange bar has its center removed
// color("orange") down(1) xcyl(h=8, d=1);
// // "keep" prevents interior of the blue bar intact
// tag("keep") recolor("blue") up(1) xcyl(h=8, d=1);
// }
// // Objects outside the diff don't have pipe interiors removed
// color("purple") down(2.2) ycyl(h=8, d=0.3);
// Example(3D,NoScales,NoAxes): Nested diff() calls work as expected, but be careful of reusing tag names, even hidden in submodules.
// $fn=32;
// diff("rem1")
// cyl(r=10,h=10){
// diff("rem2",$tag="rem1"){
// cyl(r=8,h=11);
// tag("rem2")diff("rem3"){
// cyl(r=6,h=12);
// tag("rem3")cyl(r=4,h=13);
// }
// }
// }
// Example: This example shows deep nesting, where all the differences cross levels. Unlike the preceding example, each cylinder is positioned relative to its parent. Note that it suffices to use two remove tags, alternating between them at each level.
// $fn=32;
// diff("remA")
// cyl(r=9, h=6)
// tag("remA")diff("remB")
// left(.2)position(RIGHT)cyl(r=8,h=7,anchor=RIGHT)
// tag("remB")diff("remA")
// left(.2)position(LEFT)cyl(r=7,h=7,anchor=LEFT)
// tag("remA")diff("remB")
// left(.2)position(LEFT)cyl(r=6,h=8,anchor=LEFT)
// tag("remB")diff("remA")
// right(.2)position(RIGHT)cyl(r=5,h=9,anchor=RIGHT)
// tag("remA")diff("remB")
// right(.2)position(RIGHT)cyl(r=4,h=10,anchor=RIGHT)
// tag("remB")left(.2)position(LEFT)cyl(r=3,h=11,anchor=LEFT);
// Example(3D,NoAxes,NoScales): When working with Non-Attachables like rotate_extrude() you must apply {{force_tag()}} to every non-attachable object.
// back_half()
// diff("remove")
// cuboid(40) {
// attach(TOP)
// recolor("lightgreen")
// cyl(l=10,d=30);
// position(TOP+RIGHT)
// force_tag("remove")
// xrot(90)
// rotate_extrude()
// right(20)
// circle(5);
// }
// Example: Here is another example where two children are intersected using the native intersection operator, and then tagged with {{force_tag()}}. Note that because the children are at the same level, you don't need to use a tagged operator for their intersection.
// $fn=32;
// diff()
// cuboid(10){
// force_tag("remove")intersection()
// {
// position(RIGHT) cyl(r=7,h=15);
// position(LEFT) cyl(r=7,h=15);
// }
// tag("keep")cyl(r=1,h=9);
// }
// Example: In this example the children that are subtracted are each at different nesting levels, with a kept object in between.
// $fn=32;
// diff()
// cuboid(10){
// tag("remove")cyl(r=4,h=11)
// tag("keep")cyl(r=3,h=17)
// tag("remove")position(RIGHT)cyl(r=2,h=18);
// }
// Example: Combining tag operators can be tricky. Here the `diff()` operation keeps two tags, "fullkeep" and "keep". Then {{intersect()}} intersects the "keep" tagged item with everything else, but keeps the "fullkeep" object.
// $fn=32;
// intersect("keep","fullkeep")
// diff(keep="fullkeep keep")
// cuboid(10){
// tag("remove")cyl(r=4,h=11);
// tag("keep") position(RIGHT)cyl(r=8,h=12);
// tag("fullkeep")cyl(r=1,h=12);
// }
// Example: In this complex example we form an intersection, subtract an object, and keep some objects. Note that for the small cylinders on either side, marking them as "keep" or removing their tag gives the same effect. This is because without a tag they become part of the intersection and the result ends up the same. For the two cylinders at the back, however, the result is different. With "keep" the cylinder on the left appears whole, but without it, the cylinder at the back right is subject to intersection.
// $fn=64;
// diff()
// intersect(keep="remove keep")
// cuboid(10,$thing="cube"){
// tag("intersect"){
// position(RIGHT) cyl(r=5.5,h=15)
// tag("")cyl(r=2,h=10);
// position(LEFT) cyl(r=5.54,h=15)
// tag("keep")cyl(r=2,h=10);
// }
// // Untagged it is in the intersection
// tag("") position(BACK+RIGHT)
// cyl(r=2,h=10,anchor=CTR);
// // With keep the full cylinder appears
// tag("keep") position(BACK+LEFT)
// cyl(r=2,h=10,anchor=CTR);
// tag("remove") cyl(r=3,h=15);
// }
module diff(remove="remove", keep="keep")
{
req_children($children);
assert(is_string(remove),"remove must be a string of tags");
assert(is_string(keep),"keep must be a string of tags");
if (_is_shown())
{
difference() {
hide(str(remove," ",keep)) children();
show_only(remove) children();
}
}
show_int(keep)children();
}
// Module: tag_diff()
// Usage:
// tag_diff(tag, [remove], [keep]) CHILDREN;
// Topics: Attachments
// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), intersect(), tag_intersect()
// Description:
// Perform a differencing operation in the manner of {{diff()}} using tags to control what happens,
// and then tag the resulting difference object with the specified tag. This forces the specified
// tag to be resolved at the level of the difference operation. In most cases, this is not necessary,
// but if you have kept objects and want to operate on this difference object as a whole object using
// more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
// .
// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Arguments:
// tag = Tag string to apply to this difference object
// remove = String containing space delimited set of tag names of children to difference away. Default: `"remove"`
// keep = String containing space delimited set of tag names of children to keep; that is, to union into the model after differencing is completed. Default: `"keep"`
// Side Effects:
// Sets `$tag` to the tag you specify, possibly with a scope prefix.
// Example: In this example we have a difference with a kept object that is then subtracted from a cube, but we don't want the kept object to appear in the final output, so this result is wrong:
// diff("rem"){
// cuboid([20,10,30],anchor=FRONT);
// tag("rem")diff("remove","keep"){
// cuboid([10,10,20]);
// tag("remove")cuboid([11,11,5]);
// tag("keep")cuboid([2,2,20]);
// }
// }
// Example: Using tag_diff corrects the problem:
// diff("rem"){
// cuboid([20,10,30],anchor=FRONT);
// tag_diff("rem","remove","keep"){
// cuboid([10,10,20]);
// tag("remove")cuboid([11,11,5]);
// tag("keep")cuboid([2,2,20]);
// }
// }
// Example: This concentric cylinder example uses "keep" and produces the wrong result. The kept cylinder gets kept in the final output instead of subtracted. This happens even when we make sure to change the `keep` argument at the top level {{diff()}} call.
// diff("rem","nothing")
// cyl(r=8,h=6)
// tag("rem")diff()
// cyl(r=7,h=7)
// tag("remove")cyl(r=6,h=8)
// tag("keep")cyl(r=5,h=9);
// Example: Changing to tag_diff() causes the kept cylinder to be subtracted, producing the desired result:
// diff("rem")
// cyl(r=8,h=6)
// tag_diff("rem")
// cyl(r=7,h=7)
// tag("remove")cyl(r=6,h=8)
// tag("keep")cyl(r=5,h=9);
//
module tag_diff(tag,remove="remove", keep="keep")
{
req_children($children);
assert(is_string(remove),"remove must be a string of tags");
assert(is_string(keep),"keep must be a string of tags");
assert(is_string(tag),"tag must be a string");
assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
$tag=str($tag_prefix,tag);
if (_is_shown())
show_all(){
difference() {
hide(str(remove," ",keep)) children();
show_only(remove) children();
}
show_only(keep)children();
}
}
// Module: intersect()
// Usage:
// intersect([intersect], [keep]) CHILDREN;
// Topics: Attachments
// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), tag_intersect()
// Description:
// Performs an intersection operation on its children, using tags to
// determine what happens. This is specifically intended to address
// the situation where you want intersections involving a parent and
// child object, something that is impossible with the native
// intersection() module. This module treats the children in three
// groups: objects matching the tags listed in `intersect`, objects
// matching tags listed in `keep`, and the remaining objects that
// don't match any of the listed tags. The intersection is computed
// between the union of the `intersect` tagged objects and union of the objects that don't
// match any of the listed tags. Finally the objects listed in `keep` are
// unioned with the result. Attachable objects should be tagged using {{tag()}}
// and non-attachable objects with {{force_tag()}}.
// .
// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
// Arguments:
// intersect = String containing space delimited set of tag names of children to intersect. Default: "intersect"
// keep = String containing space delimited set of tag names of children to keep whole. Default: "keep"
// Example:
// intersect("mask", keep="axle")