1//////////////////////////////////////////////////////////////////////
2// LibFile: attachments.scad
3// The modules in this file allows you to attach one object to another by making one object the child of another object.
4// You can place the child object in relation to its parent object and control the position and orientation
5// relative to the parent. The modifiers allow you to treat children in different ways that simple union, such
6// as differencing them from the parent, or changing their color. Attachment only works when the parent and child
7// are both written to support attachment. Also included in this file are the tools to make your own "attachable" objects.
8// Includes:
9// include <BOSL2/std.scad>
10// FileGroup: Basic Modeling
11// FileSummary: Positioning objects on or relative to other objects. Making your own objects support attachment.
12// FileFootnotes: STD=Included in std.scad
13//////////////////////////////////////////////////////////////////////
14
15
16// Default values for attachment code.
17$tags=undef; // for backward compatibility
18$tag = "";
19$tag_prefix = "";
20$overlap = 0;
21$color = "default";
22$save_color = undef; // Saved color to revert back for children
23
24$attach_to = undef;
25$attach_anchor = [CENTER, CENTER, UP, 0];
26$attach_norot = false;
27
28$parent_anchor = BOTTOM;
29$parent_spin = 0;
30$parent_orient = UP;
31
32$parent_size = undef;
33$parent_geom = undef;
34
35$tags_shown = "ALL";
36$tags_hidden = [];
37
38_ANCHOR_TYPES = ["intersect","hull"];
39
40
41// Section: Terminology and Shortcuts
42// This library adds the concept of anchoring, spin and orientation to the `cube()`, `cylinder()`
43// and `sphere()` builtins, as well as to most of the shapes provided by this library itself.
44// - An anchor is a place on an object which you can align the object to, or attach other objects
45// to using `attach()` or `position()`. An anchor has a position, a direction, and a spin.
46// The direction and spin are used to orient other objects to match when using `attach()`.
47// - Spin is a simple rotation around the Z axis.
48// - Orientation is rotating an object so that its top is pointed towards a given vector.
49// An object will first be translated to its anchor position, then spun, then oriented.
50// For a detailed step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
51// .
52// For describing directions, faces, edges, and corners the library provides a set of shortcuts
53// all based on combinations of unit direction vectors. You can use these for anchoring and orienting
54// attachable objects. You can also them to specify edge sets for rounding or chamfering cuboids,
55// or for placing edge, face and corner masks.
56// Subsection: Anchor
57// Anchoring is specified with the `anchor` argument in most shape modules. Specifying `anchor`
58// when creating an object will translate the object so that the anchor point is at the origin
59// (0,0,0). Anchoring always occurs before spin and orientation are applied.
60// .
61// An anchor can be referred to in one of two ways; as a directional vector, or as a named anchor string.
62// .
63// When given as a vector, it points, in a general way, towards the face, edge, or corner of the
64// object that you want the anchor for, relative to the center of the object. You can simply
65// specify a vector like `[0,0,1]` to anchor an object at the Z+ end, but you can also use
66// directional constants with names like `TOP`, `BOTTOM`, `LEFT`, `RIGHT` and `BACK` that you can add together
67// to specify anchor points. See [specifying directions](attachments.scad#subsection-specifying-directions)
68// below for the full list of pre-defined directional constants.
69// .
70// For example:
71// - `[0,0,1]` is the same as `TOP` and refers to the center of the top face.
72// - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge.
73// - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner.
74// .
75// When the object is cubical or rectangular in shape the anchors must have zero or one values
76// for their components and they refer to the face centers, edge centers, or corners of the object.
77// The direction of a face anchor will be perpendicular to the face, pointing outward. The direction of a edge anchor
78// will be the average of the anchor directions of the two faces the edge is between. The direction
79// of a corner anchor will be the average of the anchor directions of the three faces the corner is
80// on.
81// .
82// When the object is cylindrical, conical, or spherical in nature, the anchors will be located
83// around the surface of the cylinder, cone, or sphere, relative to the center.
84// You can generally use an arbitrary vector to get an anchor positioned anywhere on the curved
85// surface of such an object, and the anchor direction will be the surface normal at the anchor location.
86// However, for anchor component pointing toward the flat face should be either -1, 1, or 0, and
87// anchors that point diagonally toward one of the flat faces will select a point on the edge.
88// .
89// For objects in two dimensions, the natural expectation is for TOP and BOTTOM to refer to the Y direction
90// of the shape. To support this, if you give an anchor in 2D that has anchor.y=0 then the Z component
91// will be mapped to the Y direction. This means you can use TOP and BOTTOM for anchors of 2D objects.
92// But remember that TOP and BOTTOM are three dimensional vectors and this is a special interpretation
93// for 2d anchoring.
94// .
95// Some more complex objects, like screws and stepper motors, have named anchors to refer to places
96// on the object that are not at one of the standard faces, edges or corners. For example, stepper
97// motors have anchors for `"screw1"`, `"screw2"`, etc. to refer to the various screwholes on the
98// stepper motor shape. The names, positions, directions, and spins of these anchors are
99// specific to the object, and are documented when they exist.
100// Subsection: Spin
101// Spin is specified with the `spin` argument in most shape modules. Specifying a scalar `spin`
102// when creating an object will rotate the object counter-clockwise around the Z axis by the given
103// number of degrees. If given as a 3D vector, the object will be rotated around each of the X, Y, Z
104// axes by the number of degrees in each component of the vector. Spin is always applied after
105// anchoring, and before orientation. Since spin is applied after anchoring it is not what
106// you might think of intuitively as spinning the shape. To do that, apply `zrot()` to the shape before anchoring.
107// Subsection: Orient
108// Orientation is specified with the `orient` argument in most shape modules. Specifying `orient`
109// when creating an object will rotate the object such that the top of the object will be pointed
110// at the vector direction given in the `orient` argument. Orientation is always applied after
111// anchoring and spin. The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be
112// added together to form the directional vector for this. ie: `LEFT+BACK`
113// Subsection: Specifying Directions
114// You can use direction vectors to specify anchors for objects or to specify edges, faces, and
115// corners of cubes. You can simply specify these direction vectors numerically, but another
116// option is to use named constants for direction vectors. These constants define unit vectors
117// for the six axis directions as shown below.
118// Figure(3D,Big,VPD=6): Named constants for direction vectors. Some directions have more than one name.
119// $fn=12;
120// stroke([[0,0,0],RIGHT], endcap2="arrow2", width=.05);
121// color("black")right(.05)up(.05)move(RIGHT) text3d("RIGHT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
122// stroke([[0,0,0],LEFT], endcap2="arrow2", width=.05);
123// color("black")left(.05)up(.05)move(LEFT) text3d("LEFT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
124// stroke([[0,0,0],FRONT], endcap2="arrow2", width=.05);
125// color("black")
126// left(.1){
127// up(.12)move(FRONT) text3d("FRONT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
128// move(FRONT) text3d("FWD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
129// down(.12)move(FRONT) text3d("FORWARD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
130// }
131// stroke([[0,0,0],BACK], endcap2="arrow2", width=.05);
132// right(.05)
133// color("black")move(BACK) text3d("BACK",size=.1,h=.01,anchor=LEFT,orient=FRONT);
134// stroke([[0,0,0],DOWN], endcap2="arrow2", width=.05);
135// color("black")
136// right(.1){
137// up(.12)move(BOT) text3d("DOWN",size=.1,h=.01,anchor=LEFT,orient=FRONT);
138// move(BOT) text3d("BOTTOM",size=.1,h=.01,anchor=LEFT,orient=FRONT);
139// down(.12)move(BOT) text3d("BOT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
140// }
141// stroke([[0,0,0],TOP], endcap2="arrow2", width=.05);
142// color("black")left(.05){
143// up(.12)move(TOP) text3d("TOP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
144// move(TOP) text3d("UP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
145// }
146// 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.
147// $fn=12;
148// stroke(path2d([[0,0,0],RIGHT]), endcap2="arrow2", width=.05);
149// color("black")fwd(.22)left(.05)move(RIGHT) text("RIGHT",size=.1,anchor=RIGHT);
150// stroke(path2d([[0,0,0],LEFT]), endcap2="arrow2", width=.05);
151// color("black")right(.05)fwd(.22)move(LEFT) text("LEFT",size=.1,anchor=LEFT);
152// stroke(path2d([[0,0,0],FRONT]), endcap2="arrow2", width=.05);
153// color("black")
154// fwd(.2)
155// right(.15)
156// color("black")move(BACK) { text("BACK",size=.1,anchor=LEFT); back(.14) text("(TOP)", size=.1, anchor=LEFT);}
157// color("black")
158// left(.15)back(.2+.14)move(FRONT){
159// back(.14) text("FRONT",size=.1,anchor=RIGHT);
160// text("FWD",size=.1,anchor=RIGHT);
161// fwd(.14) text("FORWARD",size=.1,anchor=RIGHT);
162// fwd(.28) text("(BOTTOM)",size=.1,anchor=RIGHT);
163// fwd(.14*3) text("(BOT)",size=.1,anchor=RIGHT);
164// }
165// stroke(path2d([[0,0,0],BACK]), endcap2="arrow2", width=.05);
166// Subsection: Specifying Faces
167// Modules operating on faces accept a list of faces to describe the faces to operate on. Each
168// face is given by a vector that points to that face. Attachments of cuboid objects onto their faces also
169// work by choosing an attachment face with a single vector in the same manner.
170// Figure(3D,Big,NoScales,VPD=275): The six faces of the cube. Some have faces have more than one name.
171// ydistribute(50) {
172// xdistribute(35){
173// _show_cube_faces([BACK], botlabel=["BACK"]);
174// _show_cube_faces([UP],botlabel=["TOP","UP"]);
175// _show_cube_faces([RIGHT],botlabel=["RIGHT"]);
176// }
177// xdistribute(35){
178// _show_cube_faces([FRONT],toplabel=["FRONT","FWD", "FORWARD"]);
179// _show_cube_faces([DOWN],toplabel=["BOTTOM","BOT","DOWN"]);
180// _show_cube_faces([LEFT],toplabel=["LEFT"]);
181// }
182// }
183// Subsection: Specifying Edges
184// Modules operating on edges use two arguments to describe the edge set they will use: The `edges` argument
185// is a list of edge set descriptors to include in the edge set, and the `except` argument is a list of
186// edge set descriptors to remove from the edge set.
187// The default value for `edges` is `"ALL"`, the set of all edges.
188// The default value for `except` is the empty set, meaning no edges are removed.
189// If either argument is just a single edge set
190// descriptor it can be passed directly rather than in a singleton list.
191// Each edge set descriptor must be one of:
192// - A vector pointing towards an edge, indicating that single edge.
193// - A vector pointing towards a face, indicating all edges surrounding that face.
194// - A vector pointing towards a corner, indicating all edges touching that corner.
195// - The string `"X"`, indicating all X axis aligned edges.
196// - The string `"Y"`, indicating all Y axis aligned edges.
197// - The string `"Z"`, indicating all Z axis aligned edges.
198// - The string `"ALL"`, indicating all edges.
199// - The string `"NONE"`, indicating no edges at all.
200// - 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:
201// ```
202// [
203// [Y-Z-, Y+Z-, Y-Z+, Y+Z+],
204// [X-Z-, X+Z-, X-Z+, X+Z+],
205// [X-Y-, X+Y-, X-Y+, X+Y+]
206// ]
207// ```
208// You can specify edge descriptors directly by giving a vector, or you can use sums of the
209// named direction vectors described above. Below we show all of the edge sets you can
210// describe with sums of the direction vectors, and then we show some examples of combining
211// edge set descriptors.
212// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward an edge select that single edge
213// ydistribute(50) {
214// xdistribute(30) {
215// _show_edges(edges=BOT+RIGHT);
216// _show_edges(edges=BOT+BACK);
217// _show_edges(edges=BOT+LEFT);
218// _show_edges(edges=BOT+FRONT);
219// }
220// xdistribute(30) {
221// _show_edges(edges=FWD+RIGHT);
222// _show_edges(edges=BACK+RIGHT);
223// _show_edges(edges=BACK+LEFT);
224// _show_edges(edges=FWD+LEFT);
225// }
226// xdistribute(30) {
227// _show_edges(edges=TOP+RIGHT);
228// _show_edges(edges=TOP+BACK);
229// _show_edges(edges=TOP+LEFT);
230// _show_edges(edges=TOP+FRONT);
231// }
232// }
233// Figure(3D,Med,VPD=205,NoScales): Vectors pointing toward a face select all edges surrounding that face.
234// ydistribute(50) {
235// xdistribute(30) {
236// _show_edges(edges=LEFT);
237// _show_edges(edges=FRONT);
238// _show_edges(edges=RIGHT);
239// }
240// xdistribute(30) {
241// _show_edges(edges=TOP);
242// _show_edges(edges=BACK);
243// _show_edges(edges=BOTTOM);
244// }
245// }
246// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward a corner select all edges surrounding that corner.
247// ydistribute(50) {
248// xdistribute(30) {
249// _show_edges(edges=FRONT+LEFT+TOP);
250// _show_edges(edges=FRONT+RIGHT+TOP);
251// _show_edges(edges=FRONT+LEFT+BOT);
252// _show_edges(edges=FRONT+RIGHT+BOT);
253// }
254// xdistribute(30) {
255// _show_edges(edges=TOP+LEFT+BACK);
256// _show_edges(edges=TOP+RIGHT+BACK);
257// _show_edges(edges=BOT+LEFT+BACK);
258// _show_edges(edges=BOT+RIGHT+BACK);
259// }
260// }
261// Figure(3D,Med,VPD=205,NoScales): Named Edge Sets
262// ydistribute(50) {
263// xdistribute(30) {
264// _show_edges(edges="X");
265// _show_edges(edges="Y");
266// _show_edges(edges="Z");
267// }
268// xdistribute(30) {
269// _show_edges(edges="ALL");
270// _show_edges(edges="NONE");
271// }
272// }
273// 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.
274// xdistribute(43){
275// _show_edges(_edges([0,-1,1]),toplabel=["edges=[0,-1,1]"]);
276// _show_edges(_edges(TOP+FRONT),toplabel=["edges=TOP+FRONT"]);
277// _show_edges(_edges([TOP,FRONT]),toplabel=["edges=[TOP,FRONT]"]);
278// _show_edges(_edges([TOP,FRONT],TOP+FRONT),toplabel=["edges=[TOP,FRONT]","except=TOP+FRONT"]);
279// }
280// 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.
281// xdistribute(43){
282// _show_edges(_edges(BOT,BACK), toplabel=["edges=BOT","except=BACK"]);
283// _show_edges(_edges("Z",BACK), toplabel=["edges=\"Z\"", "except=BACK"]);
284// _show_edges(_edges("ALL",BACK), toplabel=["(edges=\"ALL\")", "except=BACK"]);
285// _show_edges(_edges("Y",BACK), toplabel=["edges=\"Y\"","except=BACK"]);
286// }
287// 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.
288// xdistribute(52){
289// _show_edges(_edges("ALL",[FRONT+RIGHT,FRONT+LEFT]),
290// toplabel=["except=[FRONT+RIGHT,"," FRONT+LEFT]"]);
291// _show_edges(_edges([1,-1,1]),toplabel=["edges=[1,-1,1]"]);
292// _show_edges(_edges([TOP,BOT], TOP+RIGHT+FRONT),toplabel=["edges=[TOP,BOT]","except=TOP+RIGHT+FRONT"]);
293// }
294// Subsection: Specifying Corners
295// Modules operating on corners use two arguments to describe the corner set they will use: The `corners` argument
296// is a list of corner set descriptors to include in the corner set, and the `except` argument is a list of
297// corner set descriptors to remove from the corner set.
298// The default value for `corners` is `"ALL"`, the set of all corners.
299// The default value for `except` is the empty set, meaning no corners are removed.
300// If either argument is just a single corner set
301// descriptor it can be passed directly rather than in a singleton list.
302// Each corner set descriptor must be one of:
303// - A vector pointing towards a corner, indicating that corner.
304// - A vector pointing towards an edge indicating both corners at the ends of that edge.
305// - A vector pointing towards a face, indicating all the corners of that face.
306// - The string `"ALL"`, indicating all corners.
307// - The string `"NONE"`, indicating no corners at all.
308// - 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
309// ```
310// [X-Y-Z-, X+Y-Z-, X-Y+Z-, X+Y+Z-, X-Y-Z+, X+Y-Z+, X-Y+Z+, X+Y+Z+]
311// ```
312// You can specify corner descriptors directly by giving a vector, or you can use sums of the
313// named direction vectors described above. Below we show all of the corner sets you can
314// describe with sums of the direction vectors and then we show some examples of combining
315// corner set descriptors.
316// Figure(3D,Big,NoScales,VPD=300): Vectors pointing toward a corner select that corner.
317// ydistribute(55) {
318// xdistribute(35) {
319// _show_corners(corners=FRONT+LEFT+TOP);
320// _show_corners(corners=FRONT+RIGHT+TOP);
321// _show_corners(corners=FRONT+LEFT+BOT);
322// _show_corners(corners=FRONT+RIGHT+BOT);
323// }
324// xdistribute(35) {
325// _show_corners(corners=TOP+LEFT+BACK);
326// _show_corners(corners=TOP+RIGHT+BACK);
327// _show_corners(corners=BOT+LEFT+BACK);
328// _show_corners(corners=BOT+RIGHT+BACK);
329// }
330// }
331// Figure(3D,Big,NoScales,VPD=340): Vectors pointing toward an edge select the corners and the ends of the edge.
332// ydistribute(55) {
333// xdistribute(35) {
334// _show_corners(corners=BOT+RIGHT);
335// _show_corners(corners=BOT+BACK);
336// _show_corners(corners=BOT+LEFT);
337// _show_corners(corners=BOT+FRONT);
338// }
339// xdistribute(35) {
340// _show_corners(corners=FWD+RIGHT);
341// _show_corners(corners=BACK+RIGHT);
342// _show_corners(corners=BACK+LEFT);
343// _show_corners(corners=FWD+LEFT);
344// }
345// xdistribute(35) {
346// _show_corners(corners=TOP+RIGHT);
347// _show_corners(corners=TOP+BACK);
348// _show_corners(corners=TOP+LEFT);
349// _show_corners(corners=TOP+FRONT);
350// }
351// }
352// Figure(3D,Med,NoScales,VPD=225): Vectors pointing toward a face select the corners of the face.
353// ydistribute(55) {
354// xdistribute(35) {
355// _show_corners(corners=LEFT);
356// _show_corners(corners=FRONT);
357// _show_corners(corners=RIGHT);
358// }
359// xdistribute(35) {
360// _show_corners(corners=TOP);
361// _show_corners(corners=BACK);
362// _show_corners(corners=BOTTOM);
363// }
364// }
365// Figure(3D,Med,NoScales,VPD=200): Corners by name
366// xdistribute(35) {
367// _show_corners(corners="ALL");
368// _show_corners(corners="NONE");
369// }
370// 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.
371// xdistribute(52){
372// _show_corners(_corners([1,-1,-1]),toplabel=["corners=[1,-1,-1]"]);
373// _show_corners(_corners(BOT+RIGHT+FRONT),toplabel=["corners=BOT+RIGHT+FRONT"]);
374// _show_corners(_corners([FRONT,RIGHT]), toplabel=["corners=[FRONT,RIGHT]"]);
375// }
376// 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:
377// xdistribute(52){
378// _show_corners(_corners(FRONT+TOP), toplabel=["corners=FRONT+TOP"]);
379// _show_corners(_corners([FRONT+TOP,BOT+BACK]), toplabel=["corners=[FRONT+TOP,"," BOT+BACK]"]);
380// _show_corners(_corners("ALL",FRONT+TOP), toplabel=["(corners=\"ALL\")","except=FRONT+TOP"]);
381// }
382// 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.
383// xdistribute(58){
384// _show_corners(_corners(TOP,[1,1,1]), toplabel=["corners=TOP","except=[1,1,1]"]);
385// _show_corners(_corners("ALL",[FRONT+RIGHT+TOP,FRONT+LEFT+BOT]),
386// toplabel=["except=[FRONT+RIGHT+TOP,"," FRONT+LEFT+BOT]"]);
387// }
388
389
390
391// Section: Attachment Positioning
392
393// Module: position()
394// Usage:
395// position(from) CHILDREN;
396//
397// Topics: Attachments
398// See Also: attachable(), attach(), orient()
399//
400// Description:
401// Attaches children to a parent object at an anchor point. For a step-by-step explanation
402// of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
403// Arguments:
404// from = The vector, or name of the parent anchor point to attach to.
405// Example:
406// spheroid(d=20) {
407// position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
408// position(RIGHT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
409// position(FRONT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
410// }
411module position(from)
412{
413 req_children($children);
414 assert($parent_geom != undef, "No object to attach to!");
415 anchors = (is_vector(from)||is_string(from))? [from] : from;
416 for (anchr = anchors) {
417 anch = _find_anchor(anchr, $parent_geom);
418 $attach_to = undef;
419 $attach_anchor = anch;
420 $attach_norot = true;
421 translate(anch[1]) children();
422 }
423}
424
425
426// Module: orient()
427// Usage:
428// orient(dir, [spin=]) CHILDREN;
429// orient(anchor=, [spin=]) CHILDREN;
430// Topics: Attachments
431// Description:
432// Orients children such that their top is tilted towards the given direction, or towards the
433// direction of a given anchor point on the parent. For a step-by-step explanation of
434// attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
435// Arguments:
436// dir = The direction to orient towards.
437// ---
438// anchor = The anchor on the parent which you want to match the orientation of. Use instead of `dir`.
439// spin = The spin to add to the children. (Overrides anchor spin.)
440// See Also: attachable(), attach(), orient()
441// Example: Orienting by Vector
442// prismoid([50,50],[30,30],h=40) {
443// position(TOP+RIGHT)
444// orient(RIGHT)
445// prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
446// }
447// Example: When orienting to an anchor, the spin of the anchor may cause confusion:
448// prismoid([50,50],[30,30],h=40) {
449// position(TOP+RIGHT)
450// orient(anchor=RIGHT)
451// prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
452// }
453// Example: You can override anchor spin with `spin=`.
454// prismoid([50,50],[30,30],h=40) {
455// position(TOP+RIGHT)
456// orient(anchor=RIGHT,spin=0)
457// prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
458// }
459// Example: Or you can anchor the child from the back
460// prismoid([50,50],[30,30],h=40) {
461// position(TOP+RIGHT)
462// orient(anchor=RIGHT)
463// prismoid([30,30],[0,5],h=20,anchor=BOT+BACK);
464// }
465module orient(dir, anchor, spin) {
466 req_children($children);
467 if (!is_undef(dir)) {
468 spin = default(spin, 0);
469 check =
470 assert(anchor==undef, "Only one of dir= or anchor= may be given to orient()")
471 assert(is_vector(dir))
472 assert(is_finite(spin));
473 two_d = _attach_geom_2d($parent_geom);
474 fromvec = two_d? BACK : UP;
475 rot(spin, from=fromvec, to=dir) children();
476 } else {
477 check=
478 assert(dir==undef, "Only one of dir= or anchor= may be given to orient()")
479 assert($parent_geom != undef, "No parent to orient from!")
480 assert(is_string(anchor) || is_vector(anchor));
481 anch = _find_anchor(anchor, $parent_geom);
482 two_d = _attach_geom_2d($parent_geom);
483 fromvec = two_d? BACK : UP;
484 $attach_to = undef;
485 $attach_anchor = anch;
486 $attach_norot = true;
487 spin = default(spin, anch[3]);
488 assert(is_finite(spin));
489 rot(spin, from=fromvec, to=anch[2]) children();
490 }
491}
492
493
494
495// Module: attach()
496// Usage:
497// attach(from, [overlap=], [norot=]) CHILDREN;
498// attach(from, to, [overlap=], [norot=]) CHILDREN;
499// Topics: Attachments
500// See Also: attachable(), position(), face_profile(), edge_profile(), corner_profile()
501// Description:
502// Attaches children to a parent object at an anchor point and orientation. Attached objects will
503// be overlapped into the parent object by a little bit, as specified by the `$overlap`
504// value (0 by default), or by the overriding `overlap=` argument. This is to prevent OpenSCAD
505// from making non-manifold objects. You can define `$overlap=` as an argument in a parent
506// module to set the default for all attachments to it. For a step-by-step explanation of
507// attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
508// Arguments:
509// from = The vector, or name of the parent anchor point to attach to.
510// to = Optional name of the child anchor point. If given, orients the child such that the named anchors align together rotationally.
511// ---
512// 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.
513// norot = If true, don't rotate children when attaching to the anchor point. Only translate to the anchor point.
514// Example:
515// spheroid(d=20) {
516// attach(TOP) down(1.5) cyl(l=11.5, d1=10, d2=5, anchor=BOTTOM);
517// attach(RIGHT, BOTTOM) down(1.5) cyl(l=11.5, d1=10, d2=5);
518// attach(FRONT, BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5);
519// }
520module attach(from, to, overlap, norot=false)
521{
522 req_children($children);
523 assert($parent_geom != undef, "No object to attach to!");
524 overlap = (overlap!=undef)? overlap : $overlap;
525 anchors = (is_vector(from)||is_string(from))? [from] : from;
526 for (anchr = anchors) {
527 anch = _find_anchor(anchr, $parent_geom);
528 two_d = _attach_geom_2d($parent_geom);
529 $attach_to = to;
530 $attach_anchor = anch;
531 $attach_norot = norot;
532 olap = two_d? [0,-overlap,0] : [0,0,-overlap];
533 if (norot || (norm(anch[2]-UP)<1e-9 && anch[3]==0)) {
534 translate(anch[1]) translate(olap) children();
535 } else {
536 fromvec = two_d? BACK : UP;
537 translate(anch[1]) rot(anch[3],from=fromvec,to=anch[2]) translate(olap) children();
538 }
539 }
540}
541
542// Section: Tagging
543
544// Module: tag()
545// Usage:
546// tag(tag) CHILDREN;
547// Topics: Attachments
548// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
549// Description:
550// Assigns the specified tag to all of the children. Note that if you want
551// to apply a tag to non-tag-aware objects you need to use {{force_tag()}} instead.
552// This works by setting the `$tag` variable, but it provides extra error checking and
553// handling of scopes. You may set `$tag` directly yourself, but this is not recommended.
554// .
555// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
556// Arguments:
557// tag = tag string, which must not contain any spaces.
558// Side Effects:
559// Sets `$tag` to the tag you specify, possibly with a scope prefix.
560// Example(3D): Applies the tag to both cuboids instead of having to repeat `$tag="remove"` for each one.
561// diff("remove")
562// cuboid(10){
563// position(TOP) cuboid(3);
564// tag("remove")
565// {
566// position(FRONT) cuboid(3);
567// position(RIGHT) cuboid(3);
568// }
569// }
570module tag(tag)
571{
572 req_children($children);
573 check=
574 assert(is_string(tag),"tag must be a string")
575 assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
576 $tag = str($tag_prefix,tag);
577 children();
578}
579
580
581// Module: force_tag()
582// Usage:
583// force_tag([tag]) CHILDREN;
584// Topics: Attachments
585// See Also: tag(), recolor(), hide(), show_only(), diff(), intersect()
586// Description:
587// You use this module when you want to make a non-attachable or non-BOSL2 module respect tags.
588// It applies to its children the tag specified (or the tag currently in force if you don't specify a tag),
589// making a final determination about whether to show or hide the children.
590// This means that tagging in children's children will be ignored.
591// This module is specifically provided for operating on children that are not tag aware such as modules
592// that don't use {{attachable()}} or built in modules such as
593// - `polygon()`
594// - `projection()`
595// - `polyhedron()` (or use [`vnf_polyhedron()`](vnf.scad#vnf_polyhedron))
596// - `linear_extrude()` (or use [`linear_sweep()`](regions.scad#linear_sweep))
597// - `rotate_extrude()`
598// - `surface()`
599// - `import()`
600// - `difference()`
601// - `intersection()`
602// - `hull()`
603// .
604// When you use tag-based modules like {{diff()}} with a non-attachable module, the result may be puzzling.
605// Any time a test occurs for display of child() that test will succeed. This means that when diff() checks
606// to see if it should show a module it will show it, and when diff() checks to see if it should subtract the module
607// it will subtract it. The result will be a hole, possibly with zero-thickness edges or faces. In order to
608// get the correct behavior, every non-attachable module needs an invocation of force_tag, even ones
609// that are not tagged.
610// .
611// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
612// Arguments:
613// tag = tag string, which must not contain any spaces
614// Side Effects:
615// Sets `$tag` to the tag you specify, possibly with a scope prefix.
616// Example(2D): This example produces the full square without subtracting the "remove" item. When you use non-attachable modules with tags, results are unpredictable.
617// diff()
618// {
619// polygon(square(10));
620// move(-[.01,.01])polygon(square(5),$tag="remove");
621// }
622// 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.
623// diff()
624// {
625// force_tag()
626// polygon(square(10));
627// force_tag("remove")
628// move(-[.01,.01])polygon(square(5));
629// }
630module force_tag(tag)
631{
632 req_children($children);
633 check1=assert(is_undef(tag) || is_string(tag),"tag must be a string");
634 $tag = str($tag_prefix,default(tag,$tag));
635 assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"));
636 if(_is_shown())
637 show_all()
638 children();
639}
640
641
642
643// Module: default_tag()
644// Usage:
645// default_tag(tag) CHILDREN;
646// Topics: Attachments
647// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
648// Description:
649// Sets a default tag for all of the children. This is intended to be used to set a tag for a whole module
650// that is then used outside the module, such as setting the tag to "remove" for easy operation with {{diff()}}.
651// The default_tag() module sets the `$tag` variable only if it is not already
652// set so you can have a module set a default tag of "remove" but that tag can be overridden by a {{tag()}}
653// in force from a parent. If you use {{tag()}} it will override any previously
654// specified tag from a parent, which can be very confusing to a user trying to change the tag on a module.
655// .
656// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
657// Arguments:
658// tag = tag string, which must not contain any spaces.
659// Side Effects:
660// Sets `$tag` to the tag you specify, possibly with a scope prefix.
661// Example(3D): The module thing() is defined with {{tag()}} and the user applied tag of "keep_it" is ignored, leaving the user puzzled.
662// module thing() { tag("remove") cuboid(10);}
663// diff()
664// cuboid(20){
665// position(TOP) thing();
666// position(RIGHT) tag("keep_it") thing();
667// }
668// Example(3D): Using default_tag() fixes this problem: the user applied tag does not get overridden by the tag hidden in the module definition.
669// module thing() { default_tag("remove") cuboid(10);}
670// diff()
671// cuboid(20){
672// position(TOP) thing();
673// position(RIGHT) tag("keep_it") thing();
674// }
675module default_tag(tag)
676{
677 if ($tag=="") tag(tag) children();
678 else children();
679}
680
681
682// Module: tag_scope()
683// Usage:
684// tag_scope([scope]) CHILDREN;
685// Description:
686// Creates a tag scope with locally altered tag names to avoid tag name conflict with other code.
687// This is necessary when writing modules because the module's caller might happen to use the same tags.
688// Note that if you directly set the `$tag` variable then tag scoping will not work correctly.
689// Example: In this example the ring module uses "remove" tags which will conflict with use of the same tags by the parent.
690// module ring(r,h,w=1,anchor,spin,orient)
691// {
692// tag_scope("ringscope")
693// attachable(anchor,spin,orient,r=r,h=h){
694// diff()
695// cyl(r=r,h=h)
696// tag("remove") cyl(r=r-w,h=h+1);
697// children();
698// }
699// }
700// // Calling the module using "remove" tags
701// // will conflict with internal tag use in
702// // the ring module.
703// $fn=32;
704// diff(){
705// ring(10,7,w=4);
706// tag("remove")ring(8,8);
707// tag("remove")diff("rem"){
708// ring(9.5,8,w=1);
709// tag("rem")ring(9.5,8,w=.3);
710// }
711// }
712module tag_scope(scope){
713 req_children($children);
714 scope = is_undef(scope) ? rand_str(20) : scope;
715 assert(is_string(scope), "scope must be a string");
716 assert(undef==str_find(scope," "),str("Scope string \"",scope,"\" contains a space, which is not allowed"));
717 $tag_prefix=scope;
718 children();
719}
720
721
722// Section: Attachment Modifiers
723
724// Module: diff()
725// Usage:
726// diff([remove], [keep]) CHILDREN;
727// Topics: Attachments
728// See Also: tag(), force_tag(), recolor(), show_only(), hide(), tag_diff(), intersect(), tag_intersect()
729// Description:
730// Perform a differencing operation using tags to control what happens. This is specifically intended to
731// address the situation where you want differences between a parent and child object, something
732// that is impossible with the native difference() module.
733// The children to diff are grouped into three categories, regardless of nesting level.
734// The `remove` argument is a space delimited list of tags specifying objects to
735// subtract. The `keep` argument is a similar list of tags giving objects to be kept.
736// Objects not matching either the `remove` or `keep` lists form the third category of base objects.
737// To produce its output, diff() forms the union of all the base objects and then
738// subtracts all the objects with tags in `remove`. Finally it adds in objects listed in `keep`.
739// Attachable objects should be tagged using {{tag()}}
740// and non-attachable objects with {{force_tag()}}.
741// .
742// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
743// Arguments:
744// remove = String containing space delimited set of tag names of children to difference away. Default: `"remove"`
745// 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"`
746// Example: Diffing using default tags
747// diff()
748// cuboid(50) {
749// tag("remove") attach(TOP) sphere(d=40);
750// tag("keep") attach(CTR) cylinder(h=40, d=10);
751// }
752// Example: The "hole" items are subtracted from everything else. The other tags can be anything you find convenient.
753// diff("hole")
754// tag("body")sphere(d=100) {
755// tag("pole") zcyl(d=55, h=100); // attach() not needed for center-to-center.
756// tag("hole") {
757// xcyl(d=55, h=101);
758// ycyl(d=55, h=101);
759// }
760// tag("axle")zcyl(d=15, h=140);
761// }
762// Example:
763// diff(keep="axle")
764// sphere(d=100) {
765// tag("axle")xcyl(d=40, l=120);
766// tag("remove")cuboid([40,120,100]);
767// }
768// Example: Masking
769// diff()
770// cube([80,90,100], center=true) {
771// edge_mask(FWD)
772// rounding_edge_mask(l=max($parent_size)*1.01, r=25);
773// }
774// 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.
775// diff()
776// tag("remove")cuboid(10)
777// tag("")position(RIGHT+BACK)cyl(r=8,h=9);
778// 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:
779// $fn=16;
780// // This module must be called by subtracting with "diff"
781// module pipe(length, od, id) {
782// // Strip the tag the user is using to subtract
783// tag("")cylinder(h=length, d=od, center=true);
784// // Leave the tag along here, so this one is removed
785// cylinder(h=length+.02, d=id, center=true);
786// }
787// // Draw some intersecting pipes
788// diff(){
789// tag("remove"){
790// pipe(length=5, od=2, id=1.9);
791// zrot(10)xrot(75)
792// pipe(length=5, od=2, id=1.9);
793// }
794// // The orange bar has its center removed
795// color("orange") down(1) xcyl(h=8, d=1);
796// // "keep" prevents interior of the blue bar intact
797// tag("keep") recolor("blue") up(1) xcyl(h=8, d=1);
798// }
799// // Objects outside the diff don't have pipe interiors removed
800// color("purple") down(2.2) ycyl(h=8, d=0.3);
801// Example(3D,NoScales,NoAxes): Nested diff() calls work as expected, but be careful of reusing tag names, even hidden in submodules.
802// $fn=32;
803// diff("rem1")
804// cyl(r=10,h=10){
805// diff("rem2",$tag="rem1"){
806// cyl(r=8,h=11);
807// tag("rem2")diff("rem3"){
808// cyl(r=6,h=12);
809// tag("rem3")cyl(r=4,h=13);
810// }
811// }
812// }
813// 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.
814// $fn=32;
815// diff("remA")
816// cyl(r=9, h=6)
817// tag("remA")diff("remB")
818// left(.2)position(RIGHT)cyl(r=8,h=7,anchor=RIGHT)
819// tag("remB")diff("remA")
820// left(.2)position(LEFT)cyl(r=7,h=7,anchor=LEFT)
821// tag("remA")diff("remB")
822// left(.2)position(LEFT)cyl(r=6,h=8,anchor=LEFT)
823// tag("remB")diff("remA")
824// right(.2)position(RIGHT)cyl(r=5,h=9,anchor=RIGHT)
825// tag("remA")diff("remB")
826// right(.2)position(RIGHT)cyl(r=4,h=10,anchor=RIGHT)
827// tag("remB")left(.2)position(LEFT)cyl(r=3,h=11,anchor=LEFT);
828// Example(3D,NoAxes,NoScales): When working with Non-Attachables like rotate_extrude() you must apply {{force_tag()}} to every non-attachable object.
829// back_half()
830// diff("remove")
831// cuboid(40) {
832// attach(TOP)
833// recolor("lightgreen")
834// cyl(l=10,d=30);
835// position(TOP+RIGHT)
836// force_tag("remove")
837// xrot(90)
838// rotate_extrude()
839// right(20)
840// circle(5);
841// }
842// 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.
843// $fn=32;
844// diff()
845// cuboid(10){
846// force_tag("remove")intersection()
847// {
848// position(RIGHT) cyl(r=7,h=15);
849// position(LEFT) cyl(r=7,h=15);
850// }
851// tag("keep")cyl(r=1,h=9);
852// }
853// Example: In this example the children that are subtracted are each at different nesting levels, with a kept object in between.
854// $fn=32;
855// diff()
856// cuboid(10){
857// tag("remove")cyl(r=4,h=11)
858// tag("keep")cyl(r=3,h=17)
859// tag("remove")position(RIGHT)cyl(r=2,h=18);
860// }
861// 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.
862// $fn=32;
863// intersect("keep","fullkeep")
864// diff(keep="fullkeep keep")
865// cuboid(10){
866// tag("remove")cyl(r=4,h=11);
867// tag("keep") position(RIGHT)cyl(r=8,h=12);
868// tag("fullkeep")cyl(r=1,h=12);
869// }
870// 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.
871// $fn=64;
872// diff()
873// intersect(keep="remove keep")
874// cuboid(10,$thing="cube"){
875// tag("intersect"){
876// position(RIGHT) cyl(r=5.5,h=15)
877// tag("")cyl(r=2,h=10);
878// position(LEFT) cyl(r=5.54,h=15)
879// tag("keep")cyl(r=2,h=10);
880// }
881// // Untagged it is in the intersection
882// tag("") position(BACK+RIGHT)
883// cyl(r=2,h=10,anchor=CTR);
884// // With keep the full cylinder appears
885// tag("keep") position(BACK+LEFT)
886// cyl(r=2,h=10,anchor=CTR);
887// tag("remove") cyl(r=3,h=15);
888// }
889module diff(remove="remove", keep="keep")
890{
891 req_children($children);
892 assert(is_string(remove),"remove must be a string of tags");
893 assert(is_string(keep),"keep must be a string of tags");
894 if (_is_shown())
895 {
896 difference() {
897 hide(str(remove," ",keep)) children();
898 show_only(remove) children();
899 }
900 }
901 show_int(keep)children();
902}
903
904
905// Module: tag_diff()
906// Usage:
907// tag_diff(tag, [remove], [keep]) CHILDREN;
908// Topics: Attachments
909// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), intersect(), tag_intersect()
910// Description:
911// Perform a differencing operation in the manner of {{diff()}} using tags to control what happens,
912// and then tag the resulting difference object with the specified tag. This forces the specified
913// tag to be resolved at the level of the difference operation. In most cases, this is not necessary,
914// but if you have kept objects and want to operate on this difference object as a whole object using
915// more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
916// .
917// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
918// Arguments:
919// tag = Tag string to apply to this difference object
920// remove = String containing space delimited set of tag names of children to difference away. Default: `"remove"`
921// 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"`
922// Side Effects:
923// Sets `$tag` to the tag you specify, possibly with a scope prefix.
924// 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:
925// diff("rem"){
926// cuboid([20,10,30],anchor=FRONT);
927// tag("rem")diff("remove","keep"){
928// cuboid([10,10,20]);
929// tag("remove")cuboid([11,11,5]);
930// tag("keep")cuboid([2,2,20]);
931// }
932// }
933// Example: Using tag_diff corrects the problem:
934// diff("rem"){
935// cuboid([20,10,30],anchor=FRONT);
936// tag_diff("rem","remove","keep"){
937// cuboid([10,10,20]);
938// tag("remove")cuboid([11,11,5]);
939// tag("keep")cuboid([2,2,20]);
940// }
941// }
942// 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.
943// diff("rem","nothing")
944// cyl(r=8,h=6)
945// tag("rem")diff()
946// cyl(r=7,h=7)
947// tag("remove")cyl(r=6,h=8)
948// tag("keep")cyl(r=5,h=9);
949// Example: Changing to tag_diff() causes the kept cylinder to be subtracted, producing the desired result:
950// diff("rem")
951// cyl(r=8,h=6)
952// tag_diff("rem")
953// cyl(r=7,h=7)
954// tag("remove")cyl(r=6,h=8)
955// tag("keep")cyl(r=5,h=9);
956//
957module tag_diff(tag,remove="remove", keep="keep")
958{
959 req_children($children);
960 assert(is_string(remove),"remove must be a string of tags");
961 assert(is_string(keep),"keep must be a string of tags");
962 assert(is_string(tag),"tag must be a string");
963 assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
964 $tag=str($tag_prefix,tag);
965 if (_is_shown())
966 show_all(){
967 difference() {
968 hide(str(remove," ",keep)) children();
969 show_only(remove) children();
970 }
971 show_only(keep)children();
972 }
973}
974
975
976// Module: intersect()
977// Usage:
978// intersect([intersect], [keep]) CHILDREN;
979// Topics: Attachments
980// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), tag_intersect()
981// Description:
982// Performs an intersection operation on its children, using tags to
983// determine what happens. This is specifically intended to address
984// the situation where you want intersections involving a parent and
985// child object, something that is impossible with the native
986// intersection() module. This module treats the children in three
987// groups: objects matching the tags listed in `intersect`, objects
988// matching tags listed in `keep`, and the remaining objects that
989// don't match any of the listed tags. The intersection is computed
990// between the union of the `intersect` tagged objects and union of the objects that don't
991// match any of the listed tags. Finally the objects listed in `keep` are
992// unioned with the result. Attachable objects should be tagged using {{tag()}}
993// and non-attachable objects with {{force_tag()}}.
994// .
995// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
996// Arguments:
997// intersect = String containing space delimited set of tag names of children to intersect. Default: "intersect"
998// keep = String containing space delimited set of tag names of children to keep whole. Default: "keep"
999// Example:
1000// intersect("mask", keep="axle")
1001// sphere(d=100) {
1002// tag("mask")cuboid([40,100,100]);
1003// tag("axle")xcyl(d=40, l=100);
1004// }
1005// 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.
1006// $fn=32;
1007// intersect("keep","fullkeep")
1008// diff(keep="fullkeep keep")
1009// cuboid(10){
1010// tag("remove")cyl(r=4,h=11);
1011// tag("keep") position(RIGHT)cyl(r=8,h=12);
1012// tag("fullkeep")cyl(r=1,h=12);
1013// }
1014// 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.
1015// $fn=64;
1016// diff()
1017// intersect(keep="remove keep")
1018// cuboid(10,$thing="cube"){
1019// tag("intersect"){
1020// position(RIGHT) cyl(r=5.5,h=15)
1021// tag("")cyl(r=2,h=10);
1022// position(LEFT) cyl(r=5.54,h=15)
1023// tag("keep")cyl(r=2,h=10);
1024// }
1025// // Untagged it is in the intersection
1026// tag("") position(BACK+RIGHT)
1027// cyl(r=2,h=10,anchor=CTR);
1028// // With keep the full cylinder appears
1029// tag("keep") position(BACK+LEFT)
1030// cyl(r=2,h=10,anchor=CTR);
1031// tag("remove") cyl(r=3,h=15);
1032// }
1033module intersect(intersect="intersect",keep="keep")
1034{
1035 assert(is_string(intersect),"intersect must be a string of tags");
1036 assert(is_string(keep),"keep must be a string of tags");
1037 intersection(){
1038 show_only(intersect) children();
1039 hide(str(intersect," ",keep)) children();
1040 }
1041 show_int(keep) children();
1042}
1043
1044
1045// Module: tag_intersect()
1046// Usage:
1047// tag_intersect(tag, [intersect], [keep]) CHILDREN;
1048// Topics: Attachments
1049// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), intersect()
1050// Description:
1051// Perform an intersection operation in the manner of {{intersect()}} using tags to control what happens,
1052// and then tag the resulting difference object with the specified tag. This forces the specified
1053// tag to be resolved at the level of the intersect operation. In most cases, this is not necessary,
1054// but if you have kept objects and want to operate on this difference object as a whole object using
1055// more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1056// .
1057// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1058// Arguments:
1059// tag = Tag to set for the intersection
1060// intersect = String containing space delimited set of tag names of children to intersect. Default: "intersect"
1061// keep = String containing space delimited set of tag names of children to keep whole. Default: "keep"
1062// Side Effects:
1063// Sets `$tag` to the tag you specify, possibly with a scope prefix.
1064// Example: Without `tag_intersect()` the kept object is not included in the difference.
1065// $fn=32;
1066// diff()
1067// cuboid([20,15,9])
1068// tag("remove")intersect()
1069// cuboid(10){
1070// tag("intersect")position(RIGHT) cyl(r=7,h=10);
1071// tag("keep")position(LEFT)cyl(r=4,h=10);
1072// }
1073// Example: Using tag_intersect corrects the problem.
1074// $fn=32;
1075// diff()
1076// cuboid([20,15,9])
1077// tag_intersect("remove")
1078// cuboid(10){
1079// tag("intersect")position(RIGHT) cyl(r=7,h=10);
1080// tag("keep")position(LEFT)cyl(r=4,h=10);
1081// }
1082module tag_intersect(tag,intersect="intersect",keep="keep")
1083{
1084 assert(is_string(intersect),"intersect must be a string of tags");
1085 assert(is_string(keep),"keep must be a string of tags");
1086 assert(is_string(tag),"tag must be a string");
1087 assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1088 $tag=str($tag_prefix,tag);
1089 if (_is_shown())
1090 show_all(){
1091 intersection(){
1092 show_only(intersect) children();
1093 hide(str(intersect," ",keep)) children();
1094 }
1095 show_only(keep) children();
1096 }
1097}
1098
1099
1100// Module: conv_hull()
1101// Usage:
1102// conv_hull([keep]) CHILDREN;
1103// Topics: Attachments
1104// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect()
1105// Description:
1106// Performs a hull operation on the children using tags to determine what happens. The items
1107// not tagged with the `keep` tags are combined into a convex hull, and the children tagged with the keep tags
1108// are unioned with the result.
1109// .
1110// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1111// Arguments:
1112// keep = String containing space delimited set of tag names of children to keep out of the hull. Default: "keep"
1113// Example:
1114// conv_hull("keep")
1115// sphere(d=100, $fn=64) {
1116// cuboid([40,90,90]);
1117// tag("keep")xcyl(d=40, l=120);
1118// }
1119// Example: difference combined with hull where all objects are relative to each other.
1120// $fn=32;
1121// diff()
1122// conv_hull("remove")
1123// cuboid(10)
1124// position(RIGHT+BACK)cyl(r=4,h=10)
1125// tag("remove")cyl(r=2,h=12);
1126module conv_hull(keep="keep")
1127{
1128 req_children($children);
1129 assert(is_string(keep),"keep must be a string of tags");
1130 if (_is_shown())
1131 hull() hide(keep) children();
1132 show_int(keep) children();
1133}
1134
1135
1136// Module: tag_conv_hull()
1137// Usage:
1138// tag_conv_hull(tag, [keep]) CHILDREN;
1139// Topics: Attachments
1140// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect()
1141// Description:
1142// Perform a convex hull operation in the manner of {{conv_hull()}} using tags to control what happens,
1143// and then tag the resulting hull object with the specified tag. This forces the specified
1144// tag to be resolved at the level of the hull operation. In most cases, this is not necessary,
1145// but if you have kept objects and want to operate on the hull object as a whole object using
1146// more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1147// .
1148// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1149// Arguments:
1150// keep = String containing space delimited set of tag names of children to keep out of the hull. Default: "keep"
1151// Side Effects:
1152// Sets `$tag` to the tag you specify, possibly with a scope prefix.
1153// Example: With a regular tag, the kept object is not handled as desired:
1154// diff(){
1155// cuboid([30,30,9])
1156// tag("remove")conv_hull("remove")
1157// cuboid(10,anchor=LEFT+FRONT){
1158// position(RIGHT+BACK)cyl(r=4,h=10);
1159// tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);
1160// }
1161// }
1162// Example: Using `tag_conv_hull()` fixes the problem:
1163// diff(){
1164// cuboid([30,30,9])
1165// tag_conv_hull("remove")
1166// cuboid(10,anchor=LEFT+FRONT){
1167// position(RIGHT+BACK)cyl(r=4,h=10);
1168// tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);
1169// }
1170// }
1171module tag_conv_hull(tag,keep="keep")
1172{
1173 req_children($children);
1174 assert(is_string(keep),"keep must be a string of tags");
1175 assert(is_string(tag),"tag must be a string");
1176 assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1177 $tag=str($tag_prefix,tag);
1178 if (_is_shown())
1179 show_all(){
1180 hull() hide(keep) children();
1181 show_only(keep) children();
1182 }
1183}
1184
1185
1186// Module: hide()
1187// Usage:
1188// hide(tags) CHILDREN;
1189// Topics: Attachments
1190// See Also: tag(), recolor(), show_only(), diff(), intersect()
1191// Description:
1192// Hides all attachable children with the given tags, which you supply as a space separated string. Previously hidden objects remain hidden, so hiding is cumulative, unlike `show_only()`.
1193// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1194// Side Effects:
1195// Sets `$tags_hidden` to include the tags you specify.
1196// Example: Hides part of the model.
1197// hide("A")
1198// tag("main") cube(50, anchor=CENTER, $tag="Main") {
1199// tag("A")attach(LEFT, BOTTOM) cylinder(d=30, l=30);
1200// tag("B")attach(RIGHT, BOTTOM) cylinder(d=30, l=30);
1201// }
1202// Example: Use an invisible parent to position children. Note that children must be retagged because they inherit the parent tag.
1203// $fn=16;
1204// hide("hidden")
1205// tag("hidden")cuboid(10)
1206// tag("visible") {
1207// position(RIGHT) cyl(r=1,h=12);
1208// position(LEFT) cyl(r=1,h=12);
1209// }
1210module hide(tags)
1211{
1212 req_children($children);
1213 dummy=assert(is_string(tags), "tags must be a string");
1214 taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1215 $tags_hidden = concat($tags_hidden,taglist);
1216 children();
1217}
1218
1219
1220// Module: show_only()
1221// Usage:
1222// show_only(tags) CHILDREN;
1223// Topics: Attachments
1224// See Also: tag(), recolor(), hide(), diff(), intersect()
1225// Description:
1226// Show only the children with the listed tags, which you sply as a space separated string. Only unhidden objects will be shown, so if an object is hidden either before or after the `show_only()` call then it will remain hidden. This overrides any previous `show_only()` calls. Unlike `hide()`, calls to `show_only()` are not cumulative.
1227// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1228// Side Effects:
1229// Sets `$tags_shown` to the tag you specify.
1230// Example: Display the attachments but not the parent
1231// show_only("visible")
1232// cube(50, anchor=CENTER)
1233// tag("visible"){
1234// attach(LEFT, BOTTOM) cylinder(d=30, l=30);
1235// attach(RIGHT, BOTTOM) cylinder(d=30, l=30);
1236// }
1237module show_only(tags)
1238{
1239 req_children($children);
1240 dummy=assert(is_string(tags), str("tags must be a string",tags));
1241 taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1242 $tags_shown = taglist;
1243 children();
1244}
1245
1246// Module: show_all()
1247// Usage;
1248// show_all() CHILDREN;
1249// Description:
1250// Shows all children. Clears the list of hidden tags and shown tags so that all child objects will be
1251// fully displayed.
1252// Side Effects:
1253// Sets `$tags_shown="ALL"`
1254// Sets `$tags_hidden=[]`
1255module show_all()
1256{
1257 req_children($children);
1258 $tags_shown="ALL";
1259 $tags_hidden=[];
1260 children();
1261}
1262
1263
1264// Module: show_int()
1265// Usage:
1266// show_int(tags) CHILDREN;
1267// Description:
1268// Show only the children with the listed tags which were already shown in the parent context.
1269// This intersects the current show list with the list of tags you provide.
1270// Arguments:
1271// tags = list of tags to show
1272// Side Effects:
1273// Sets `$tags_shown`
1274module show_int(tags)
1275{
1276 req_children($children);
1277 dummy=assert(is_string(tags), str("tags must be a string",tags));
1278 taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1279 $tags_shown = $tags_shown == "ALL" ? taglist : set_intersection($tags_shown,taglist);
1280 children();
1281}
1282
1283
1284// Section: Attachable Masks
1285
1286
1287// Module: edge_mask()
1288// Usage:
1289// edge_mask([edges], [except]) CHILDREN;
1290// Topics: Attachments
1291// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_mask()
1292// Description:
1293// Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be
1294// differenced away. The mask shape should be vertically oriented (Z-aligned) with the back-right
1295// quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape. If no tag is set
1296// then `edge_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1297// For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1298// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1299// Figure: A Typical Edge Rounding Mask
1300// module roundit(l,r) difference() {
1301// translate([-1,-1,-l/2])
1302// cube([r+1,r+1,l]);
1303// translate([r,r])
1304// cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
1305// }
1306// roundit(l=30,r=10);
1307// Arguments:
1308// edges = Edges to mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: All edges.
1309// except = Edges to explicitly NOT mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: No edges.
1310// Side Effects:
1311// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1312// Example:
1313// diff()
1314// cube([50,60,70],center=true)
1315// edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT])
1316// rounding_edge_mask(l=71,r=10);
1317module edge_mask(edges=EDGES_ALL, except=[]) {
1318 req_children($children);
1319 assert($parent_geom != undef, "No object to attach to!");
1320 edges = _edges(edges, except=except);
1321 vecs = [
1322 for (i = [0:3], axis=[0:2])
1323 if (edges[axis][i]>0)
1324 EDGE_OFFSETS[axis][i]
1325 ];
1326 for (vec = vecs) {
1327 vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1328 dummy=assert(vcount == 2, "Not an edge vector!");
1329 anch = _find_anchor(vec, $parent_geom);
1330 $attach_to = undef;
1331 $attach_anchor = anch;
1332 $attach_norot = true;
1333 rotang =
1334 vec.z<0? [90,0,180+v_theta(vec)] :
1335 vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) :
1336 vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] :
1337 [-90,0,180+v_theta(vec)];
1338 translate(anch[1]) rot(rotang)
1339 if ($tag=="") tag("remove") children();
1340 else children();
1341 }
1342}
1343
1344
1345// Module: corner_mask()
1346// Usage:
1347// corner_mask([corners], [except]) CHILDREN;
1348// Topics: Attachments
1349// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), edge_mask()
1350// Description:
1351// Takes a 3D mask shape, and attaches it to the specified corners, with the appropriate orientation to
1352// be differenced away. The 3D corner mask shape should be designed to mask away the X+Y+Z+ octant. If no tag is set
1353// then `corner_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1354// See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets.
1355// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1356// Arguments:
1357// corners = Corners to mask. See [Specifying Corners](attachments.scad#subsection-specifying-corners). Default: All corners.
1358// except = Corners to explicitly NOT mask. See [Specifying Corners](attachments.scad#subsection-specifying-corners). Default: No corners.
1359// Side Effects:
1360// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1361// Example:
1362// diff()
1363// cube(100, center=true)
1364// corner_mask([TOP,FRONT],LEFT+FRONT+TOP)
1365// difference() {
1366// translate(-0.01*[1,1,1]) cube(20);
1367// translate([20,20,20]) sphere(r=20);
1368// }
1369module corner_mask(corners=CORNERS_ALL, except=[]) {
1370 req_children($children);
1371 assert($parent_geom != undef, "No object to attach to!");
1372 corners = _corners(corners, except=except);
1373 vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]];
1374 for (vec = vecs) {
1375 vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1376 dummy=assert(vcount == 3, "Not an edge vector!");
1377 anch = _find_anchor(vec, $parent_geom);
1378 $attach_to = undef;
1379 $attach_anchor = anch;
1380 $attach_norot = true;
1381 rotang = vec.z<0?
1382 [ 0,0,180+v_theta(vec)-45] :
1383 [180,0,-90+v_theta(vec)-45];
1384 translate(anch[1]) rot(rotang)
1385 if ($tag=="") tag("remove") children();
1386 else children();
1387 }
1388}
1389
1390
1391// Module: face_profile()
1392// Usage:
1393// face_profile(faces, r|d=, [convexity=]) CHILDREN;
1394// Topics: Attachments
1395// See Also: attachable(), position(), attach(), edge_profile(), corner_profile()
1396// Description:
1397// Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face. If no tag is set
1398// then `face_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1399// See [Specifying Faces](attachments.scad#subsection-specifying-faces) for information on specifying faces.
1400// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1401// Arguments:
1402// faces = Faces to mask edges and corners of.
1403// r = Radius of corner mask.
1404// ---
1405// d = Diameter of corner mask.
1406// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10
1407// Side Effects:
1408// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1409// Example:
1410// diff()
1411// cube([50,60,70],center=true)
1412// face_profile(TOP,r=10)
1413// mask2d_roundover(r=10);
1414module face_profile(faces=[], r, d, convexity=10) {
1415 req_children($children);
1416 faces = is_vector(faces)? [faces] : faces;
1417 assert(all([for (face=faces) is_vector(face) && sum([for (x=face) x!=0? 1 : 0])==1]), "Vector in faces doesn't point at a face.");
1418 r = get_radius(r=r, d=d, dflt=undef);
1419 assert(is_num(r) && r>0);
1420 edge_profile(faces) children();
1421 corner_profile(faces, convexity=convexity, r=r) children();
1422}
1423
1424
1425// Module: edge_profile()
1426// Usage:
1427// edge_profile([edges], [except], [convexity]) CHILDREN;
1428// Topics: Attachments
1429// See Also: attachable(), position(), attach(), face_profile(), corner_profile()
1430// Description:
1431// Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation and
1432// extruded length to be `diff()`ed away, to give the edge a matching profile. If no tag is set
1433// then `edge_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1434// For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1435// For a step-by-step
1436// explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1437// Arguments:
1438// edges = Edges to mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: All edges.
1439// except = Edges to explicitly NOT mask. See [Specifying Edges](attachments.scad#subsection-specifying-edges). Default: No edges.
1440// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10
1441// Side Effects:
1442// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1443// Example:
1444// diff()
1445// cube([50,60,70],center=true)
1446// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
1447// mask2d_roundover(r=10, inset=2);
1448module edge_profile(edges=EDGES_ALL, except=[], convexity=10) {
1449 req_children($children);
1450 assert($parent_geom != undef, "No object to attach to!");
1451 edges = _edges(edges, except=except);
1452 vecs = [
1453 for (i = [0:3], axis=[0:2])
1454 if (edges[axis][i]>0)
1455 EDGE_OFFSETS[axis][i]
1456 ];
1457 for (vec = vecs) {
1458 vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1459 dummy=assert(vcount == 2, "Not an edge vector!");
1460 anch = _find_anchor(vec, $parent_geom);
1461 $attach_to = undef;
1462 $attach_anchor = anch;
1463 $attach_norot = true;
1464 psize = point3d($parent_size);
1465 length = [for (i=[0:2]) if(!vec[i]) psize[i]][0]+0.1;
1466 rotang =
1467 vec.z<0? [90,0,180+v_theta(vec)] :
1468 vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) :
1469 vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] :
1470 [-90,0,180+v_theta(vec)];
1471 translate(anch[1]) {
1472 rot(rotang) {
1473 linear_extrude(height=length, center=true, convexity=convexity) {
1474 if ($tag=="") tag("remove") children();
1475 else children();
1476 }
1477 }
1478 }
1479 }
1480}
1481
1482// Module: corner_profile()
1483// Usage:
1484// corner_profile([corners], [except], [r=|d=], [convexity=]) CHILDREN;
1485// Topics: Attachments
1486// See Also: attachable(), position(), attach(), face_profile(), edge_profile()
1487// Description:
1488// Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it
1489// to the selected corners with the appropriate orientation. If no tag is set
1490// then `corner_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1491// See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets.
1492// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1493// Arguments:
1494// corners = Corners to mask. See [Specifying Corners](attachments.scad#subsection-specifying-corners). Default: All corners.
1495// except = Corners to explicitly NOT mask. See [Specifying Corners](attachments.scad#subsection-specifying-corners). Default: No corners.
1496// ---
1497// r = Radius of corner mask.
1498// d = Diameter of corner mask.
1499// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10
1500// Side Effects:
1501// Tags the children with "remove" (and hence sets $tag) if no tag is already set.
1502// Example:
1503// diff()
1504// cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
1505// corner_profile(TOP,r=10)
1506// mask2d_teardrop(r=10, angle=40);
1507// }
1508module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
1509 assert($parent_geom != undef, "No object to attach to!");
1510 r = get_radius(r=r, d=d, dflt=undef);
1511 assert(is_num(r));
1512 corners = _corners(corners, except=except);
1513 vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]];
1514 for (vec = vecs) {
1515 vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1516 dummy=assert(vcount == 3, "Not an edge vector!");
1517 anch = _find_anchor(vec, $parent_geom);
1518 $attach_to = undef;
1519 $attach_anchor = anch;
1520 $attach_norot = true;
1521 rotang = vec.z<0?
1522 [ 0,0,180+v_theta(vec)-45] :
1523 [180,0,-90+v_theta(vec)-45];
1524 $tag = $tag=="" ? str($tag_prefix,"remove") : $tag;
1525 translate(anch[1]) {
1526 rot(rotang) {
1527 render(convexity=convexity)
1528 difference() {
1529 translate(-0.1*[1,1,1]) cube(r+0.1, center=false);
1530 right(r) back(r) zrot(180) {
1531 rotate_extrude(angle=90, convexity=convexity) {
1532 xflip() left(r) {
1533 difference() {
1534 square(r,center=false);
1535 children();
1536 }
1537 }
1538 }
1539 }
1540 }
1541 }
1542 }
1543 }
1544}
1545
1546
1547// Section: Making your objects attachable
1548
1549
1550// Module: attachable()
1551//
1552// Usage: Square/Trapezoid Geometry
1553// attachable(anchor, spin, two_d=true, size=, [size2=], [shift=], ...) {OBJECT; children();}
1554// Usage: Circle/Oval Geometry
1555// attachable(anchor, spin, two_d=true, r=|d=, ...) {OBJECT; children();}
1556// Usage: 2D Path/Polygon Geometry
1557// attachable(anchor, spin, two_d=true, path=, [extent=], ...) {OBJECT; children();}
1558// Usage: 2D Region Geometry
1559// attachable(anchor, spin, two_d=true, region=, [extent=], ...) {OBJECT; children();}
1560// Usage: Cubical/Prismoidal Geometry
1561// attachable(anchor, spin, [orient], size=, [size2=], [shift=], ...) {OBJECT; children();}
1562// Usage: Cylindrical Geometry
1563// attachable(anchor, spin, [orient], r=|d=, l=, [axis=], ...) {OBJECT; children();}
1564// Usage: Conical Geometry
1565// attachable(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...) {OBJECT; children();}
1566// Usage: Spheroid/Ovoid Geometry
1567// attachable(anchor, spin, [orient], r=|d=, ...) {OBJECT; children();}
1568// Usage: Extruded Path/Polygon Geometry
1569// attachable(anchor, spin, path=, l=|h=, [extent=], ...) {OBJECT; children();}
1570// Usage: Extruded Region Geometry
1571// attachable(anchor, spin, region=, l=|h=, [extent=], ...) {OBJECT; children();}
1572// Usage: VNF Geometry
1573// attachable(anchor, spin, [orient], vnf=, [extent=], ...) {OBJECT; children();}
1574// Usage: Pre-Specified Geometry
1575// attachable(anchor, spin, [orient], geom=) {OBJECT; children();}
1576//
1577// Topics: Attachments
1578// See Also: reorient()
1579//
1580// Description:
1581// Manages the anchoring, spin, orientation, and attachments for OBJECT, located in a 3D volume or 2D area.
1582// A managed 3D volume is assumed to be vertically (Z-axis) oriented, and centered.
1583// A managed 2D area is just assumed to be centered. The shape to be managed is given
1584// as the first child to this module, and the second child should be given as `children()`.
1585// For example, to manage a conical shape:
1586// ```openscad
1587// attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
1588// cyl(r1=r1, r2=r2, l=h);
1589// children();
1590// }
1591// ```
1592// .
1593// If this is *not* run as a child of `attach()` with the `to` argument
1594// given, then the following transformations are performed in order:
1595// * Translates so the `anchor` point is at the origin (0,0,0).
1596// * Rotates around the Z axis by `spin` degrees counter-clockwise.
1597// * Rotates so the top of the part points towards the vector `orient`.
1598// .
1599// If this is called as a child of `attach(from,to)`, then the info
1600// for the anchor points referred to by `from` and `to` are fetched,
1601// which will include position, direction, and spin. With that info,
1602// the following transformations are performed:
1603// * Translates this part so it's anchor position matches the parent's anchor position.
1604// * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
1605// * Rotates this part so it's anchor spin matches the parent's anchor spin.
1606// .
1607// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1608//
1609// Arguments:
1610// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1611// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1612// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1613// ---
1614// size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height. If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
1615// size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume. If given as a number, contains the back width of the trapezoidal shape.
1616// shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount. If given as a number, shifts the back of the trapezoidal shape right by that amount. Default: No shift.
1617// r = Radius of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
1618// d = Diameter of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
1619// r1 = Radius of the bottom of the conical volume. Can be a scalar, or a list of sizes per axis.
1620// r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis.
1621// d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis.
1622// d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis.
1623// l/h = Length of the cylindrical, conical, or extruded path volume along axis.
1624// vnf = The [VNF](vnf.scad) of the volume.
1625// path = The path to generate a polygon from.
1626// region = The region to generate a shape from.
1627// extent = If true, calculate anchors by extents, rather than intersection, for VNFs and paths. Default: true.
1628// cp = If given, specifies the centerpoint of the volume. Default: `[0,0,0]`
1629// offset = If given, offsets the perimeter of the volume around the centerpoint.
1630// anchors = If given as a list of anchor points, allows named anchor points.
1631// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
1632// axis = The vector pointing along the axis of a geometry. Default: UP
1633// geom = If given, uses the pre-defined (via {{attach_geom()}} geometry.
1634//
1635// Side Effects:
1636// `$parent_anchor` is set to the parent object's `anchor` value.
1637// `$parent_spin` is set to the parent object's `spin` value.
1638// `$parent_orient` is set to the parent object's `orient` value.
1639// `$parent_geom` is set to the parent object's `geom` value.
1640// `$parent_size` is set to the parent object's cubical `[X,Y,Z]` volume size.
1641// `$color` is used to set the color of the object
1642// `$save_color` is used to revert color to the parent's color
1643//
1644// Example(NORENDER): Cubical Shape
1645// attachable(anchor, spin, orient, size=size) {
1646// cube(size, center=true);
1647// children();
1648// }
1649//
1650// Example(NORENDER): Prismoidal Shape
1651// attachable(
1652// anchor, spin, orient,
1653// size=point3d(botsize,h),
1654// size2=topsize,
1655// shift=shift
1656// ) {
1657// prismoid(botsize, topsize, h=h, shift=shift);
1658// children();
1659// }
1660//
1661// Example(NORENDER): Cylindrical Shape, Z-Axis Aligned
1662// attachable(anchor, spin, orient, r=r, l=h) {
1663// cyl(r=r, l=h);
1664// children();
1665// }
1666//
1667// Example(NORENDER): Cylindrical Shape, Y-Axis Aligned
1668// attachable(anchor, spin, orient, r=r, l=h, axis=BACK) {
1669// cyl(r=r, l=h);
1670// children();
1671// }
1672//
1673// Example(NORENDER): Cylindrical Shape, X-Axis Aligned
1674// attachable(anchor, spin, orient, r=r, l=h, axis=RIGHT) {
1675// cyl(r=r, l=h);
1676// children();
1677// }
1678//
1679// Example(NORENDER): Conical Shape, Z-Axis Aligned
1680// attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
1681// cyl(r1=r1, r2=r2, l=h);
1682// children();
1683// }
1684//
1685// Example(NORENDER): Conical Shape, Y-Axis Aligned
1686// attachable(anchor, spin, orient, r1=r1, r2=r2, l=h, axis=BACK) {
1687// cyl(r1=r1, r2=r2, l=h);
1688// children();
1689// }
1690//
1691// Example(NORENDER): Conical Shape, X-Axis Aligned
1692// attachable(anchor, spin, orient, r1=r1, r2=r2, l=h, axis=RIGHT) {
1693// cyl(r1=r1, r2=r2, l=h);
1694// children();
1695// }
1696//
1697// Example(NORENDER): Spherical Shape
1698// attachable(anchor, spin, orient, r=r) {
1699// sphere(r=r);
1700// children();
1701// }
1702//
1703// Example(NORENDER): Extruded Polygon Shape, by Extents
1704// attachable(anchor, spin, orient, path=path, l=length) {
1705// linear_extrude(height=length, center=true)
1706// polygon(path);
1707// children();
1708// }
1709//
1710// Example(NORENDER): Extruded Polygon Shape, by Intersection
1711// attachable(anchor, spin, orient, path=path, l=length, extent=false) {
1712// linear_extrude(height=length, center=true)
1713// polygon(path);
1714// children();
1715// }
1716//
1717// Example(NORENDER): Arbitrary VNF Shape, by Extents
1718// attachable(anchor, spin, orient, vnf=vnf) {
1719// vnf_polyhedron(vnf);
1720// children();
1721// }
1722//
1723// Example(NORENDER): Arbitrary VNF Shape, by Intersection
1724// attachable(anchor, spin, orient, vnf=vnf, extent=false) {
1725// vnf_polyhedron(vnf);
1726// children();
1727// }
1728//
1729// Example(NORENDER): 2D Rectangular Shape
1730// attachable(anchor, spin, orient, two_d=true, size=size) {
1731// square(size, center=true);
1732// children();
1733// }
1734//
1735// Example(NORENDER): 2D Trapezoidal Shape
1736// attachable(
1737// anchor, spin, orient,
1738// two_d=true,
1739// size=[x1,y],
1740// size2=x2,
1741// shift=shift
1742// ) {
1743// trapezoid(w1=x1, w2=x2, h=y, shift=shift);
1744// children();
1745// }
1746//
1747// Example(NORENDER): 2D Circular Shape
1748// attachable(anchor, spin, orient, two_d=true, r=r) {
1749// circle(r=r);
1750// children();
1751// }
1752//
1753// Example(NORENDER): Arbitrary 2D Polygon Shape, by Extents
1754// attachable(anchor, spin, orient, two_d=true, path=path) {
1755// polygon(path);
1756// children();
1757// }
1758//
1759// Example(NORENDER): Arbitrary 2D Polygon Shape, by Intersection
1760// attachable(anchor, spin, orient, two_d=true, path=path, extent=false) {
1761// polygon(path);
1762// children();
1763// }
1764//
1765// Example(NORENDER): Using Pre-defined Geometry
1766// geom = atype=="perim"? attach_geom(two_d=true, path=path, extent=false) :
1767// atype=="extents"? attach_geom(two_d=true, path=path, extent=true) :
1768// atype=="circle"? attach_geom(two_d=true, r=r) :
1769// assert(false, "Bad atype");
1770// attachable(anchor, spin, orient, geom=geom) {
1771// polygon(path);
1772// children();
1773// }
1774//
1775// Example: An object can be designed to attach as negative space using {{diff()}}, but if you want an object to include both positive and negative space then you need to call attachable() twice, because tags inside the attachable() call don't work as expected. This example shows how you can call attachable twice to create an object with positive and negative space. Note, however, that children in the negative space are differenced away: the highlighted little cube does not survive into the final model.
1776// module thing(anchor,spin,orient) {
1777// tag("remove") attachable(size=[15,15,15],anchor=anchor,spin=spin,orient=orient){
1778// cuboid([10,10,16]);
1779// union(){} // dummy children
1780// }
1781// attachable(size=[15,15,15], anchor=anchor, spin=spin, orient=orient){
1782// cuboid([15,15,15]);
1783// children();
1784// }
1785// }
1786// diff()
1787// cube([19,10,19])
1788// attach([FRONT],overlap=-4)
1789// thing(anchor=TOP)
1790// # attach(TOP) cuboid(2,anchor=TOP);
1791// Example: Here is an example where the "keep" tag allows children to appear in the negative space. That tag is also needed for this module to produce the desired output. As above, the tag must be applied outside the attachable() call.
1792// module thing(anchor = CENTER, spin = 0, orient = UP) {
1793// tag("remove") attachable(anchor, spin, orient, d1=0,d2=95,h=33) {
1794// cylinder(h = 33.1, d1 = 0, d2 = 95, anchor=CENTER);
1795// union(){} // dummy children
1796// }
1797// tag("keep") attachable(anchor, spin, orient,d1=0,d2=95,h=33) {
1798// cylinder(h = 33, d = 10,anchor=CENTER);
1799// children();
1800// }
1801// }
1802// diff()
1803// cube(100)
1804// attach([FRONT,TOP],overlap=-4)
1805// thing(anchor=TOP)
1806// tube(ir=12,h=10);
1807module attachable(
1808 anchor, spin, orient,
1809 size, size2, shift,
1810 r,r1,r2, d,d1,d2, l,h,
1811 vnf, path, region,
1812 extent=true,
1813 cp=[0,0,0],
1814 offset=[0,0,0],
1815 anchors=[],
1816 two_d=false,
1817 axis=UP,
1818 geom
1819) {
1820 dummy1 =
1821 assert($children==2, "attachable() expects exactly two children; the shape to manage, and the union of all attachment candidates.")
1822 assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
1823 assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
1824 assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient));
1825 anchor = default(anchor, CENTER);
1826 spin = default(spin, 0);
1827 orient = default(orient, UP);
1828 region = !is_undef(region)? region :
1829 !is_undef(path)? [path] :
1830 undef;
1831 geom = is_def(geom)? geom :
1832 attach_geom(
1833 size=size, size2=size2, shift=shift,
1834 r=r, r1=r1, r2=r2, h=h,
1835 d=d, d1=d1, d2=d2, l=l,
1836 vnf=vnf, region=region, extent=extent,
1837 cp=cp, offset=offset, anchors=anchors,
1838 two_d=two_d, axis=axis
1839 );
1840 m = _attach_transform(anchor,spin,orient,geom);
1841 multmatrix(m) {
1842 $parent_anchor = anchor;
1843 $parent_spin = spin;
1844 $parent_orient = orient;
1845 $parent_geom = geom;
1846 $parent_size = _attach_geom_size(geom);
1847 $attach_to = undef;
1848 if (_is_shown())
1849 _color($color) children(0);
1850 if (is_def($save_color)) {
1851 $color=$save_color;
1852 $save_color=undef;
1853 children(1);
1854 }
1855 else children(1);
1856 }
1857}
1858
1859// Function: reorient()
1860//
1861// Usage: Square/Trapezoid Geometry
1862// mat = reorient(anchor, spin, [orient], two_d=true, size=, [size2=], [shift=], ...);
1863// pts = reorient(anchor, spin, [orient], two_d=true, size=, [size2=], [shift=], p=, ...);
1864// Usage: Circle/Oval Geometry
1865// mat = reorient(anchor, spin, [orient], two_d=true, r=|d=, ...);
1866// pts = reorient(anchor, spin, [orient], two_d=true, r=|d=, p=, ...);
1867// Usage: 2D Path/Polygon Geometry
1868// mat = reorient(anchor, spin, [orient], two_d=true, path=, [extent=], ...);
1869// pts = reorient(anchor, spin, [orient], two_d=true, path=, [extent=], p=, ...);
1870// Usage: 2D Region/Polygon Geometry
1871// mat = reorient(anchor, spin, [orient], two_d=true, region=, [extent=], ...);
1872// pts = reorient(anchor, spin, [orient], two_d=true, region=, [extent=], p=, ...);
1873// Usage: Cubical/Prismoidal Geometry
1874// mat = reorient(anchor, spin, [orient], size=, [size2=], [shift=], ...);
1875// pts = reorient(anchor, spin, [orient], size=, [size2=], [shift=], p=, ...);
1876// Usage: Cylindrical Geometry
1877// mat = reorient(anchor, spin, [orient], r=|d=, l=, [axis=], ...);
1878// pts = reorient(anchor, spin, [orient], r=|d=, l=, [axis=], p=, ...);
1879// Usage: Conical Geometry
1880// mat = reorient(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...);
1881// pts = reorient(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], p=, ...);
1882// Usage: Spheroid/Ovoid Geometry
1883// mat = reorient(anchor, spin, [orient], r|d=, ...);
1884// pts = reorient(anchor, spin, [orient], r|d=, p=, ...);
1885// Usage: Extruded Path/Polygon Geometry
1886// mat = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], ...);
1887// pts = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], p=, ...);
1888// Usage: Extruded Region Geometry
1889// mat = reorient(anchor, spin, [orient], region=, l=|h=, [extent=], ...);
1890// pts = reorient(anchor, spin, [orient], region=, l=|h=, [extent=], p=, ...);
1891// Usage: VNF Geometry
1892// mat = reorient(anchor, spin, [orient], vnf, [extent], ...);
1893// pts = reorient(anchor, spin, [orient], vnf, [extent], p=, ...);
1894//
1895// Topics: Attachments
1896// See Also: reorient(), attachable()
1897//
1898// Description:
1899// Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
1900// the transformation matrix needed to be applied to the contents of that volume. A managed 3D
1901// volume is assumed to be vertically (Z-axis) oriented, and centered. A managed 2D area is just
1902// assumed to be centered.
1903// .
1904// If `p` is not given, then the transformation matrix will be returned.
1905// If `p` contains a VNF, a new VNF will be returned with the vertices transformed by the matrix.
1906// If `p` contains a path, a new path will be returned with the vertices transformed by the matrix.
1907// If `p` contains a point, a new point will be returned, transformed by the matrix.
1908// .
1909// If `$attach_to` is not defined, then the following transformations are performed in order:
1910// * Translates so the `anchor` point is at the origin (0,0,0).
1911// * Rotates around the Z axis by `spin` degrees counter-clockwise.
1912// * Rotates so the top of the part points towards the vector `orient`.
1913// .
1914// If `$attach_to` is defined, as a consequence of `attach(from,to)`, then
1915// the following transformations are performed in order:
1916// * Translates this part so it's anchor position matches the parent's anchor position.
1917// * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
1918// * Rotates this part so it's anchor spin matches the parent's anchor spin.
1919// .
1920// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1921//
1922// Arguments:
1923// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1924// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1925// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1926// ---
1927// size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height. If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
1928// size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume. If given as a number, contains the back width of the trapezoidal shape.
1929// shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount. If given as a number, shifts the back of the trapezoidal shape right by that amount. Default: No shift.
1930// r = Radius of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
1931// d = Diameter of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
1932// r1 = Radius of the bottom of the conical volume. Can be a scalar, or a list of sizes per axis.
1933// r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis.
1934// d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis.
1935// d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis.
1936// l/h = Length of the cylindrical, conical, or extruded path volume along axis.
1937// vnf = The [VNF](vnf.scad) of the volume.
1938// path = The path to generate a polygon from.
1939// region = The region to generate a shape from.
1940// extent = If true, calculate anchors by extents, rather than intersection. Default: false.
1941// cp = If given, specifies the centerpoint of the volume. Default: `[0,0,0]`
1942// offset = If given, offsets the perimeter of the volume around the centerpoint.
1943// anchors = If given as a list of anchor points, allows named anchor points.
1944// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
1945// axis = The vector pointing along the axis of a geometry. Default: UP
1946// p = The VNF, path, or point to transform.
1947function reorient(
1948 anchor, spin, orient,
1949 size, size2, shift,
1950 r,r1,r2, d,d1,d2, l,h,
1951 vnf, path, region,
1952 extent=true,
1953 offset=[0,0,0],
1954 cp=[0,0,0],
1955 anchors=[],
1956 two_d=false,
1957 axis=UP,
1958 geom,
1959 p=undef
1960) =
1961 assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
1962 assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
1963 assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
1964 let(
1965 anchor = default(anchor, CENTER),
1966 spin = default(spin, 0),
1967 orient = default(orient, UP),
1968 region = !is_undef(region)? region :
1969 !is_undef(path)? [path] :
1970 undef
1971 )
1972 (anchor==CENTER && spin==0 && orient==UP && p!=undef)? p :
1973 let(
1974 geom = is_def(geom)? geom :
1975 attach_geom(
1976 size=size, size2=size2, shift=shift,
1977 r=r, r1=r1, r2=r2, h=h,
1978 d=d, d1=d1, d2=d2, l=l,
1979 vnf=vnf, region=region, extent=extent,
1980 cp=cp, offset=offset, anchors=anchors,
1981 two_d=two_d, axis=axis
1982 ),
1983 $attach_to = undef
1984 ) _attach_transform(anchor,spin,orient,geom,p);
1985
1986
1987// Function: named_anchor()
1988// Usage:
1989// a = named_anchor(name, pos, [orient], [spin]);
1990// Topics: Attachments
1991// See Also: reorient(), attachable()
1992// Description:
1993// Creates an anchor data structure. For a step-by-step explanation of attachments,
1994// see the [[Attachments Tutorial|Tutorial-Attachments]].
1995// Arguments:
1996// name = The string name of the anchor. Lowercase. Words separated by single dashes. No spaces.
1997// pos = The [X,Y,Z] position of the anchor.
1998// orient = A vector pointing in the direction parts should project from the anchor position. Default: UP
1999// spin = If needed, the angle to rotate the part around the direction vector. Default: 0
2000function named_anchor(name, pos, orient=UP, spin=0) = [name, pos, orient, spin];
2001
2002
2003// Function: attach_geom()
2004//
2005// Usage: Square/Trapezoid Geometry
2006// geom = attach_geom(two_d=true, size=, [size2=], [shift=], ...);
2007// Usage: Circle/Oval Geometry
2008// geom = attach_geom(two_d=true, r=|d=, ...);
2009// Usage: 2D Path/Polygon/Region Geometry
2010// geom = attach_geom(two_d=true, region=, [extent=], ...);
2011// Usage: Cubical/Prismoidal Geometry
2012// geom = attach_geom(size=, [size2=], [shift=], ...);
2013// Usage: Cylindrical Geometry
2014// geom = attach_geom(r=|d=, l=|h=, [axis=], ...);
2015// Usage: Conical Geometry
2016// geom = attach_geom(r1|d1=, r2=|d2=, l=, [axis=], ...);
2017// Usage: Spheroid/Ovoid Geometry
2018// geom = attach_geom(r=|d=, ...);
2019// Usage: Extruded 2D Path/Polygon/Region Geometry
2020// geom = attach_geom(region=, l=|h=, [extent=], [shift=], [scale=], [twist=], ...);
2021// Usage: VNF Geometry
2022// geom = attach_geom(vnf=, [extent=], ...);
2023//
2024// Topics: Attachments
2025// See Also: reorient(), attachable()
2026//
2027// Description:
2028// Given arguments that describe the geometry of an attachable object, returns the internal geometry description.
2029// This will probably not not ever need to be called by the end user.
2030//
2031// Arguments:
2032// ---
2033// size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height. If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
2034// size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume. If given as a number, contains the back width of the trapezoidal shape.
2035// shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount. If given as a number, shifts the back of the trapezoidal shape right by that amount. Default: No shift.
2036// scale = If given as number or a 2D vector, scales the top of the shape, relative to the bottom. Default: `[1,1]`
2037// twist = If given as number, rotates the top of the shape by the given number of degrees clockwise, relative to the bottom. Default: `0`
2038// r = Radius of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
2039// d = Diameter of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
2040// r1 = Radius of the bottom of the conical volume. Can be a scalar, or a list of sizes per axis.
2041// r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis.
2042// d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis.
2043// d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis.
2044// l/h = Length of the cylindrical, conical or extruded region volume along axis.
2045// vnf = The [VNF](vnf.scad) of the volume.
2046// region = The region to generate a shape from.
2047// extent = If true, calculate anchors by extents, rather than intersection. Default: true.
2048// cp = If given, specifies the centerpoint of the volume. Default: `[0,0,0]`
2049// offset = If given, offsets the perimeter of the volume around the centerpoint.
2050// anchors = If given as a list of anchor points, allows named anchor points.
2051// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
2052// axis = The vector pointing along the axis of a geometry. Default: UP
2053//
2054// Example(NORENDER): Cubical Shape
2055// geom = attach_geom(size=size);
2056//
2057// Example(NORENDER): Prismoidal Shape
2058// geom = attach_geom(
2059// size=point3d(botsize,h),
2060// size2=topsize, shift=shift
2061// );
2062//
2063// Example(NORENDER): Cylindrical Shape, Z-Axis Aligned
2064// geom = attach_geom(r=r, h=h);
2065//
2066// Example(NORENDER): Cylindrical Shape, Y-Axis Aligned
2067// geom = attach_geom(r=r, h=h, axis=BACK);
2068//
2069// Example(NORENDER): Cylindrical Shape, X-Axis Aligned
2070// geom = attach_geom(r=r, h=h, axis=RIGHT);
2071//
2072// Example(NORENDER): Conical Shape, Z-Axis Aligned
2073// geom = attach_geom(r1=r1, r2=r2, h=h);
2074//
2075// Example(NORENDER): Conical Shape, Y-Axis Aligned
2076// geom = attach_geom(r1=r1, r2=r2, h=h, axis=BACK);
2077//
2078// Example(NORENDER): Conical Shape, X-Axis Aligned
2079// geom = attach_geom(r1=r1, r2=r2, h=h, axis=RIGHT);
2080//
2081// Example(NORENDER): Spherical Shape
2082// geom = attach_geom(r=r);
2083//
2084// Example(NORENDER): Ovoid Shape
2085// geom = attach_geom(r=[r_x, r_y, r_z]);
2086//
2087// Example(NORENDER): Arbitrary VNF Shape, Anchored by Extents
2088// geom = attach_geom(vnf=vnf);
2089//
2090// Example(NORENDER): Arbitrary VNF Shape, Anchored by Intersection
2091// geom = attach_geom(vnf=vnf, extent=false);
2092//
2093// Example(NORENDER): 2D Rectangular Shape
2094// geom = attach_geom(two_d=true, size=size);
2095//
2096// Example(NORENDER): 2D Trapezoidal Shape
2097// geom = attach_geom(two_d=true, size=[x1,y], size2=x2, shift=shift);
2098//
2099// Example(NORENDER): 2D Circular Shape
2100// geom = attach_geom(two_d=true, r=r);
2101//
2102// Example(NORENDER): 2D Oval Shape
2103// geom = attach_geom(two_d=true, r=[r_x, r_y]);
2104//
2105// Example(NORENDER): Arbitrary 2D Region Shape, Anchored by Extents
2106// geom = attach_geom(two_d=true, region=region);
2107//
2108// Example(NORENDER): Arbitrary 2D Region Shape, Anchored by Intersection
2109// geom = attach_geom(two_d=true, region=region, extent=false);
2110//
2111// Example(NORENDER): Extruded Region, Anchored by Extents
2112// geom = attach_geom(region=region, l=height);
2113//
2114// Example(NORENDER): Extruded Region, Anchored by Intersection
2115// geom = attach_geom(region=region, l=length, extent=false);
2116//
2117function attach_geom(
2118 size, size2,
2119 shift, scale, twist,
2120 r,r1,r2, d,d1,d2, l,h,
2121 vnf, region,
2122 extent=true,
2123 cp=[0,0,0],
2124 offset=[0,0,0],
2125 anchors=[],
2126 two_d=false,
2127 axis=UP
2128) =
2129 assert(is_bool(extent))
2130 assert(is_vector(cp) || is_string(cp))
2131 assert(is_vector(offset))
2132 assert(is_list(anchors))
2133 assert(is_bool(two_d))
2134 assert(is_vector(axis))
2135 !is_undef(size)? (
2136 two_d? (
2137 let(
2138 size2 = default(size2, size.x),
2139 shift = default(shift, 0)
2140 )
2141 assert(is_vector(size,2))
2142 assert(is_num(size2))
2143 assert(is_num(shift))
2144 ["trapezoid", point2d(size), size2, shift, cp, offset, anchors]
2145 ) : (
2146 let(
2147 size2 = default(size2, point2d(size)),
2148 shift = default(shift, [0,0])
2149 )
2150 assert(is_vector(size,3))
2151 assert(is_vector(size2,2))
2152 assert(is_vector(shift,2))
2153 ["prismoid", size, size2, shift, axis, cp, offset, anchors]
2154 )
2155 ) : !is_undef(vnf)? (
2156 assert(is_vnf(vnf))
2157 assert(two_d == false)
2158 extent? ["vnf_extent", vnf, cp, offset, anchors] :
2159 ["vnf_isect", vnf, cp, offset, anchors]
2160 ) : !is_undef(region)? (
2161 assert(is_region(region),2)
2162 let( l = default(l, h) )
2163 two_d==true
2164 ? assert(is_undef(l))
2165 extent==true
2166 ? ["rgn_extent", region, cp, offset, anchors]
2167 : ["rgn_isect", region, cp, offset, anchors]
2168 : assert(is_finite(l))
2169 let(
2170 shift = default(shift, [0,0]),
2171 scale = is_num(scale)? [scale,scale] : default(scale, [1,1]),
2172 twist = default(twist, 0)
2173 )
2174 assert(is_vector(shift,2))
2175 assert(is_vector(scale,2))
2176 assert(is_num(twist))
2177 extent==true
2178 ? ["xrgn_extent", region, l, twist, scale, shift, cp, offset, anchors]
2179 : ["xrgn_isect", region, l, twist, scale, shift, cp, offset, anchors]
2180 ) :
2181 let(
2182 r1 = get_radius(r1=r1,d1=d1,r=r,d=d,dflt=undef)
2183 )
2184 !is_undef(r1)? (
2185 let( l = default(l, h) )
2186 !is_undef(l)? (
2187 let(
2188 shift = default(shift, [0,0]),
2189 r2 = get_radius(r1=r2,d1=d2,r=r,d=d,dflt=undef)
2190 )
2191 assert(is_num(r1) || is_vector(r1,2))
2192 assert(is_num(r2) || is_vector(r2,2))
2193 assert(is_num(l))
2194 assert(is_vector(shift,2))
2195 ["conoid", r1, r2, l, shift, axis, cp, offset, anchors]
2196 ) : (
2197 two_d? (
2198 assert(is_num(r1) || is_vector(r1,2))
2199 ["ellipse", r1, cp, offset, anchors]
2200 ) : (
2201 assert(is_num(r1) || is_vector(r1,3))
2202 ["spheroid", r1, cp, offset, anchors]
2203 )
2204 )
2205 ) :
2206 assert(false, "Unrecognizable geometry description.");
2207
2208
2209
2210
2211
2212
2213//////////////////////////////////////////////////////////////////////////////////////////////////////////////
2214//
2215// Attachment internal functions
2216
2217
2218/// Internal Function: _attach_geom_2d()
2219// Usage:
2220// bool = _attach_geom_2d(geom);
2221/// Topics: Attachments
2222/// See Also: reorient(), attachable()
2223// Description:
2224// Returns true if the given attachment geometry description is for a 2D shape.
2225function _attach_geom_2d(geom) =
2226 let( type = geom[0] )
2227 type == "trapezoid" || type == "ellipse" ||
2228 type == "rgn_isect" || type == "rgn_extent";
2229
2230
2231/// Internal Function: _attach_geom_size()
2232// Usage:
2233// bounds = _attach_geom_size(geom);
2234/// Topics: Attachments
2235/// See Also: reorient(), attachable()
2236// Description:
2237// Returns the `[X,Y,Z]` bounding size for the given attachment geometry description.
2238function _attach_geom_size(geom) =
2239 let( type = geom[0] )
2240 type == "prismoid"? ( //size, size2, shift, axis
2241 let(
2242 size=geom[1], size2=geom[2], shift=point2d(geom[3]),
2243 maxx = max(size.x,size2.x),
2244 maxy = max(size.y,size2.y),
2245 z = size.z
2246 ) [maxx, maxy, z]
2247 ) : type == "conoid"? ( //r1, r2, l, shift
2248 let(
2249 r1=geom[1], r2=geom[2], l=geom[3],
2250 shift=point2d(geom[4]), axis=point3d(geom[5]),
2251 rx1 = default(r1[0],r1),
2252 ry1 = default(r1[1],r1),
2253 rx2 = default(r2[0],r2),
2254 ry2 = default(r2[1],r2),
2255 maxxr = max(rx1,rx2),
2256 maxyr = max(ry1,ry2)
2257 )
2258 approx(axis,UP)? [2*maxxr,2*maxyr,l] :
2259 approx(axis,RIGHT)? [l,2*maxyr,2*maxxr] :
2260 approx(axis,BACK)? [2*maxxr,l,2*maxyr] :
2261 [2*maxxr, 2*maxyr, l]
2262 ) : type == "spheroid"? ( //r
2263 let( r=geom[1] )
2264 is_num(r)? [2,2,2]*r : v_mul([2,2,2],point3d(r))
2265 ) : type == "vnf_extent" || type=="vnf_isect"? ( //vnf
2266 let(
2267 vnf = geom[1]
2268 ) vnf==EMPTY_VNF? [0,0,0] :
2269 let(
2270 mm = pointlist_bounds(geom[1][0]),
2271 delt = mm[1]-mm[0]
2272 ) delt
2273 ) : type == "xrgn_isect" || type == "xrgn_extent"? ( //path, l
2274 let(
2275 mm = pointlist_bounds(flatten(geom[1])),
2276 delt = mm[1]-mm[0]
2277 ) [delt.x, delt.y, geom[2]]
2278 ) : type == "trapezoid"? ( //size, size2
2279 let(
2280 size=geom[1], size2=geom[2], shift=geom[3],
2281 maxx = max(size.x,size2+abs(shift))
2282 ) [maxx, size.y]
2283 ) : type == "ellipse"? ( //r
2284 let( r=geom[1] )
2285 is_num(r)? [2,2]*r : v_mul([2,2],point2d(r))
2286 ) : type == "rgn_isect" || type == "rgn_extent"? ( //path
2287 let(
2288 mm = pointlist_bounds(flatten(geom[1])),
2289 delt = mm[1]-mm[0]
2290 ) [delt.x, delt.y]
2291 ) :
2292 assert(false, "Unknown attachment geometry type.");
2293
2294
2295/// Internal Function: _attach_transform()
2296// Usage: To Get a Transformation Matrix
2297// mat = _attach_transform(anchor, spin, orient, geom);
2298// Usage: To Transform Points, Paths, Patches, or VNFs
2299// new_p = _attach_transform(anchor, spin, orient, geom, p);
2300/// Topics: Attachments
2301/// See Also: reorient(), attachable()
2302// Description:
2303// Returns the affine3d transformation matrix needed to `anchor`, `spin`, and `orient`
2304// the given geometry `geom` shape into position.
2305// Arguments:
2306// anchor = Anchor point to translate to the origin `[0,0,0]`. See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
2307// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
2308// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
2309// geom = The geometry description of the shape.
2310// p = If given as a VNF, path, or point, applies the affine3d transformation matrix to it and returns the result.
2311function _attach_transform(anchor, spin, orient, geom, p) =
2312 assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
2313 assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
2314 assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
2315 let(
2316 anchor = default(anchor, CENTER),
2317 spin = default(spin, 0),
2318 orient = default(orient, UP),
2319 two_d = _attach_geom_2d(geom),
2320 m = ($attach_to != undef)? (
2321 let(
2322 anch = _find_anchor($attach_to, geom),
2323 pos = anch[1]
2324 ) two_d? (
2325 assert(two_d && is_num(spin))
2326 affine3d_zrot(spin) *
2327 rot(to=FWD, from=point3d(anch[2])) *
2328 affine3d_translate(point3d(-pos))
2329 ) : (
2330 assert(is_num(spin) || is_vector(spin,3))
2331 let(
2332 ang = vector_angle(anch[2], DOWN),
2333 axis = vector_axis(anch[2], DOWN),
2334 ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3],
2335 axis2 = rot(p=axis,[0,0,ang2])
2336 )
2337 affine3d_rot_by_axis(axis2,ang) * (
2338 is_num(spin)? affine3d_zrot(ang2+spin) : (
2339 affine3d_zrot(spin.z) *
2340 affine3d_yrot(spin.y) *
2341 affine3d_xrot(spin.x) *
2342 affine3d_zrot(ang2)
2343 )
2344 ) * affine3d_translate(point3d(-pos))
2345 )
2346 ) : (
2347 let(
2348 pos = _find_anchor(anchor, geom)[1]
2349 ) two_d? (
2350 assert(two_d && is_num(spin))
2351 affine3d_zrot(spin) *
2352 affine3d_translate(point3d(-pos))
2353 ) : (
2354 assert(is_num(spin) || is_vector(spin,3))
2355 let(
2356 axis = vector_axis(UP,orient),
2357 ang = vector_angle(UP,orient)
2358 )
2359 affine3d_rot_by_axis(axis,ang) * (
2360 is_num(spin)? affine3d_zrot(spin) : (
2361 affine3d_zrot(spin.z) *
2362 affine3d_yrot(spin.y) *
2363 affine3d_xrot(spin.x)
2364 )
2365 ) * affine3d_translate(point3d(-pos))
2366 )
2367 )
2368 ) is_undef(p)? m :
2369 is_vnf(p)? [(p==EMPTY_VNF? p : apply(m, p[0])), p[1]] :
2370 apply(m, p);
2371
2372
2373function _get_cp(geom) =
2374
2375 let(cp=select(geom,-3))
2376 is_vector(cp) ? cp
2377 : let(
2378 type = in_list(geom[0],["vnf_extent","vnf_isect"]) ? "vnf"
2379 : in_list(geom[0],["rgn_extent","rgn_isect"]) ? "path"
2380 : in_list(geom[0],["xrgn_extent","xrgn_isect"]) ? "xpath"
2381 : "other"
2382 )
2383 assert(type!="other", "Invalid cp value")
2384 cp=="centroid" ? (
2385 type=="vnf" && (len(geom[1][0])==0 || len(geom[1][1])==0) ? [0,0,0] :
2386 [each centroid(geom[1]), if (type=="xpath") 0]
2387 )
2388 : let(points = type=="vnf"?geom[1][0]:flatten(force_region(geom[1])))
2389 cp=="mean" ? [each mean(points), if (type=="xpath") geom[2]/2]
2390 : cp=="box" ?[each mean(pointlist_bounds(points)), if (type=="xpath") geom[2]/2]
2391 : assert(false,"Invalid cp specification");
2392
2393
2394function _force_anchor_2d(anchor) =
2395 assert(anchor.y==0 || anchor.z==0, "Anchor for a 2D shape cannot be fully 3D. It must have either Y or Z component equal to zero.")
2396 anchor.y==0 ? [anchor.x,anchor.z] : point2d(anchor);
2397
2398
2399/// Internal Function: _find_anchor()
2400// Usage:
2401// anchorinfo = _find_anchor(anchor, geom);
2402/// Topics: Attachments
2403/// See Also: reorient(), attachable()
2404// Description:
2405// Calculates the anchor data for the given `anchor` vector or name, in the given attachment
2406// geometry. Returns `[ANCHOR, POS, VEC, ANG]` where `ANCHOR` is the requested anchorname
2407// or vector, `POS` is the anchor position, `VEC` is the direction vector of the anchor, and
2408// `ANG` is the angle to align with around the rotation axis of th anchor direction vector.
2409// Arguments:
2410// anchor = Vector or named anchor string.
2411// geom = The geometry description of the shape.
2412function _find_anchor(anchor, geom) =
2413 is_string(anchor)? (
2414 anchor=="origin"? [anchor, CENTER, UP, 0]
2415 : let(
2416 anchors = last(geom),
2417 found = search([anchor], anchors, num_returns_per_match=1)[0]
2418 )
2419 assert(found!=[], str("Unknown anchor: ",anchor))
2420 anchors[found]
2421 ) :
2422 let(
2423 cp = _get_cp(geom),
2424 offset_raw = select(geom,-2),
2425 offset = [for (i=[0:2]) anchor[i]==0? 0 : offset_raw[i]], // prevents bad centering.
2426 type = geom[0]
2427 )
2428 assert(is_vector(anchor),str("Invalid anchor: anchor=",anchor))
2429 let(
2430 anchor = point3d(anchor),
2431 oang = (
2432 approx(point2d(anchor), [0,0])? 0 :
2433 atan2(anchor.y, anchor.x)+90
2434 )
2435 )
2436 type == "prismoid"? ( //size, size2, shift, axis
2437 let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
2438 assert(all_comps_good, "All components of an anchor for a cuboid/prismoid must be -1, 0, or 1")
2439 let(
2440 size=geom[1], size2=geom[2],
2441 shift=point2d(geom[3]), axis=point3d(geom[4]),
2442 anch = rot(from=axis, to=UP, p=anchor),
2443 offset = rot(from=axis, to=UP, p=offset),
2444 h = size.z,
2445 u = (anch.z + 1) / 2, // u is one of 0, 0.5, or 1
2446 axy = point2d(anch),
2447 bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
2448 top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
2449 pos = point3d(cp) + lerp(bot,top,u) + offset,
2450 vecs = [
2451 if (anch.x!=0) unit(rot(from=UP, to=[(top-bot).x,0,h], p=[axy.x,0,0]), UP),
2452 if (anch.y!=0) unit(rot(from=UP, to=[0,(top-bot).y,h], p=[0,axy.y,0]), UP),
2453 if (anch.z!=0) anch==CENTER? UP : unit([0,0,anch.z],UP)
2454 ],
2455 vec = anchor==CENTER? UP : rot(from=UP, to=axis, p=unit(sum(vecs) / len(vecs))),
2456 pos2 = rot(from=UP, to=axis, p=pos)
2457 ) [anchor, pos2, vec, oang]
2458 ) : type == "conoid"? ( //r1, r2, l, shift
2459 assert(anchor.z == sign(anchor.z), "The Z component of an anchor for a cylinder/cone must be -1, 0, or 1")
2460 let(
2461 rr1=geom[1], rr2=geom[2], l=geom[3],
2462 shift=point2d(geom[4]), axis=point3d(geom[5]),
2463 r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1),
2464 r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2),
2465 anch = rot(from=axis, to=UP, p=anchor),
2466 offset = rot(from=axis, to=UP, p=offset),
2467 u = (anch.z+1)/2,
2468 axy = unit(point2d(anch),[0,0]),
2469 bot = point3d(v_mul(r1,axy), -l/2),
2470 top = point3d(v_mul(r2,axy)+shift, l/2),
2471 pos = point3d(cp) + lerp(bot,top,u) + offset,
2472 sidevec = rot(from=UP, to=top==bot?UP:top-bot, p=point3d(axy)),
2473 vvec = anch==CENTER? UP : unit([0,0,anch.z],UP),
2474 vec = anch==CENTER? CENTER :
2475 approx(axy,[0,0])? unit(anch,UP) :
2476 approx(anch.z,0)? sidevec :
2477 unit((sidevec+vvec)/2,UP),
2478 pos2 = rot(from=UP, to=axis, p=pos),
2479 vec2 = anch==CENTER? UP : rot(from=UP, to=axis, p=vec)
2480 ) [anchor, pos2, vec2, oang]
2481 ) : type == "spheroid"? ( //r
2482 let(
2483 rr = geom[1],
2484 r = is_num(rr)? [rr,rr,rr] : point3d(rr),
2485 anchor = unit(point3d(anchor),CENTER),
2486 pos = point3d(cp) + v_mul(r,anchor) + point3d(offset),
2487 vec = unit(v_mul(r,anchor),UP)
2488 ) [anchor, pos, vec, oang]
2489 ) : type == "vnf_isect"? ( //vnf
2490 let( vnf=geom[1] )
2491 approx(anchor,CTR)? [anchor, [0,0,0], UP, 0] :
2492 vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor), 0] :
2493 let(
2494 eps = 1/2048,
2495 points = vnf[0],
2496 faces = vnf[1],
2497 rpts = apply(rot(from=anchor, to=RIGHT) * move(-cp), points),
2498 hits = [
2499 for (face = faces)
2500 let(
2501 verts = select(rpts, face),
2502 ys = column(verts,1),
2503 zs = column(verts,2)
2504 )
2505 if (max(ys) >= -eps && max(zs) >= -eps &&
2506 min(ys) <= eps && min(zs) <= eps)
2507 let(
2508 poly = select(points, face),
2509 isect = polygon_line_intersection(poly, [cp,cp+anchor], eps=eps),
2510 ptlist = is_undef(isect) ? [] :
2511 is_vector(isect) ? [isect]
2512 : flatten(isect), // parallel to a face
2513 n = len(ptlist)>0 ? polygon_normal(poly) : undef
2514 )
2515 for(pt=ptlist) [anchor * (pt-cp), n, pt]
2516 ]
2517 )
2518 assert(len(hits)>0, "Anchor vector does not intersect with the shape. Attachment failed.")
2519 let(
2520 furthest = max_index(column(hits,0)),
2521 dist = hits[furthest][0],
2522 pos = hits[furthest][2],
2523 hitnorms = [for (hit = hits) if (approx(hit[0],dist,eps=eps)) hit[1]],
2524 unorms = [
2525 for (i = idx(hitnorms))
2526 let(
2527 thisnorm = hitnorms[i],
2528 isdup = [
2529 for (j = [i+1:1:len(hitnorms)-1])
2530 if (approx(thisnorm, hitnorms[j])) 1
2531 ] != []
2532 )
2533 if (!isdup) thisnorm
2534 ],
2535 n = unit(sum(unorms)),
2536 oang = approx(point2d(n), [0,0])? 0 : atan2(n.y, n.x) + 90
2537 )
2538 [anchor, pos, n, oang]
2539 ) : type == "vnf_extent"? ( //vnf
2540 let( vnf=geom[1] )
2541 approx(anchor,CTR)? [anchor, [0,0,0], UP, 0] :
2542 vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor,UP), 0] :
2543 let(
2544 rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
2545 maxx = max(column(rpts,0)),
2546 idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
2547 avep = sum(select(rpts,idxs))/len(idxs),
2548 mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : avep,
2549 pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt)
2550 ) [anchor, pos, anchor, oang]
2551 ) : type == "trapezoid"? ( //size, size2, shift
2552 let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
2553 assert(all_comps_good, "All components of an anchor for a rectangle/trapezoid must be -1, 0, or 1")
2554 let(
2555 anchor=_force_anchor_2d(anchor),
2556 size=geom[1], size2=geom[2], shift=geom[3],
2557 u = (anchor.y+1)/2, // 0<=u<=1
2558 frpt = [size.x/2*anchor.x, -size.y/2],
2559 bkpt = [size2/2*anchor.x+shift, size.y/2],
2560 pos = point2d(cp) + lerp(frpt, bkpt, u) + point2d(offset),
2561 svec = point3d(line_normal(bkpt,frpt)*anchor.x),
2562 vec = anchor.y < 0? (
2563 anchor.x == 0? FWD :
2564 size.x == 0? unit(-[shift,size.y], FWD) :
2565 unit((point3d(svec) + FWD) / 2, FWD)
2566 ) :
2567 anchor.y == 0? ( anchor.x == 0? BACK : svec ) :
2568 ( // anchor.y > 0
2569 anchor.x == 0? BACK :
2570 size2 == 0? unit([shift,size.y], BACK) :
2571 unit((point3d(svec) + BACK) / 2, BACK)
2572 )
2573 ) [anchor, pos, vec, 0]
2574 ) : type == "ellipse"? ( //r
2575 let(
2576 anchor = unit(_force_anchor_2d(anchor),[0,0]),
2577 r = force_list(geom[1],2),
2578 pos = approx(anchor.x,0) ? [0,sign(anchor.y)*r.y]
2579 : let(
2580 m = anchor.y/anchor.x,
2581 px = sign(anchor.x) * sqrt(1/(1/sqr(r.x) + m*m/sqr(r.y)))
2582 )
2583 [px,m*px],
2584 vec = unit([r.y/r.x*pos.x, r.x/r.y*pos.y],BACK)
2585 ) [anchor, point2d(cp+offset)+pos, vec, 0]
2586 ) : type == "rgn_isect"? ( //region
2587 let(
2588 anchor = _force_anchor_2d(anchor),
2589 rgn = force_region(move(-point2d(cp), p=geom[1]))
2590 )
2591 approx(anchor,[0,0])? [anchor, [0,0,0], BACK, 0] :
2592 let(
2593 isects = [
2594 for (path=rgn, t=triplet(path,true)) let(
2595 seg1 = [t[0],t[1]],
2596 seg2 = [t[1],t[2]],
2597 isect = line_intersection([[0,0],anchor], seg1, RAY, SEGMENT),
2598 n = is_undef(isect)? [0,1] :
2599 !approx(isect, t[1])? line_normal(seg1) :
2600 unit((line_normal(seg1)+line_normal(seg2))/2,[0,1]),
2601 n2 = vector_angle(anchor,n)>90? -n : n
2602 )
2603 if(!is_undef(isect) && !approx(isect,t[0])) [norm(isect), isect, n2]
2604 ]
2605 )
2606 assert(len(isects)>0, "Anchor vector does not intersect with the shape. Attachment failed.")
2607 let(
2608 maxidx = max_index(column(isects,0)),
2609 isect = isects[maxidx],
2610 pos = point2d(cp) + isect[1],
2611 vec = unit(isect[2],[0,1])
2612 ) [anchor, pos, vec, 0]
2613 ) : type == "rgn_extent"? ( //region
2614 let( anchor = _force_anchor_2d(anchor) )
2615 approx(anchor,[0,0])? [anchor, [0,0,0], BACK, 0] :
2616 let(
2617 rgn = force_region(geom[1]),
2618 rpts = rot(from=anchor, to=RIGHT, p=flatten(rgn)),
2619 maxx = max(column(rpts,0)),
2620 ys = [for (pt=rpts) if (approx(pt.x, maxx)) pt.y],
2621 midy = (min(ys)+max(ys))/2,
2622 pos = rot(from=RIGHT, to=anchor, p=[maxx,midy])
2623 ) [anchor, pos, unit(anchor,BACK), 0]
2624 ) : type=="xrgn_extent" || type=="xrgn_isect" ? ( // extruded region
2625 assert(in_list(anchor.z,[-1,0,1]), "The Z component of an anchor for an extruded 2D shape must be -1, 0, or 1.")
2626 let(
2627 anchor_xy = point2d(anchor),
2628 rgn = geom[1],
2629 L = geom[2],
2630 twist = geom[3],
2631 scale = geom[4],
2632 shift = geom[5],
2633 u = (anchor.z + 1) / 2,
2634 shmat = move(lerp([0,0], shift, u)),
2635 scmat = scale(lerp([1,1], scale, u)),
2636 twmat = zrot(lerp(0, -twist, u)),
2637 mat = shmat * scmat * twmat
2638 )
2639 approx(anchor_xy,[0,0]) ? [anchor, apply(mat, up(anchor.z*L/2,cp)), unit(anchor, UP), oang] :
2640 let(
2641 newrgn = apply(mat, rgn),
2642 newgeom = attach_geom(two_d=true, region=newrgn, extent=type=="xrgn_extent", cp=cp),
2643 result2d = _find_anchor(anchor_xy, newgeom),
2644 pos = point3d(result2d[1], cp.z+anchor.z*L/2),
2645 vec = unit(point3d(result2d[2], anchor.z),UP),
2646 oang = atan2(vec.y,vec.x) + 90
2647 )
2648 [anchor, pos, vec, oang]
2649 ) :
2650 assert(false, "Unknown attachment geometry type.");
2651
2652
2653/// Internal Function: _is_shown()
2654// Usage:
2655// bool = _is_shown();
2656/// Topics: Attachments
2657/// See Also: reorient(), attachable()
2658// Description:
2659// Returns true if objects should currently be shown based on the tag settings.
2660function _is_shown() =
2661 assert(is_list($tags_shown) || $tags_shown=="ALL")
2662 assert(is_list($tags_hidden))
2663 let(
2664 dummy=is_undef($tags) ? 0 : echo("Use tag() instead of $tags for specifying an object's tag."),
2665 $tag = default($tag,$tags)
2666 )
2667 assert(is_string($tag), str("Tag value (",$tag,") is not a string"))
2668 assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"))
2669 let(
2670 shown = $tags_shown=="ALL" || in_list($tag,$tags_shown),
2671 hidden = in_list($tag, $tags_hidden)
2672 )
2673 shown && !hidden;
2674
2675
2676// Section: Visualizing Anchors
2677
2678/// Internal Function: _standard_anchors()
2679/// Usage:
2680/// anchs = _standard_anchors([two_d]);
2681/// Description:
2682/// Return the vectors for all standard anchors.
2683/// Arguments:
2684/// two_d = If true, returns only the anchors where the Z component is 0. Default: false
2685function _standard_anchors(two_d=false) = [
2686 for (
2687 zv = [
2688 if (!two_d) TOP,
2689 CENTER,
2690 if (!two_d) BOTTOM
2691 ],
2692 yv = [FRONT, CENTER, BACK],
2693 xv = [LEFT, CENTER, RIGHT]
2694 ) xv+yv+zv
2695];
2696
2697
2698
2699// Module: show_anchors()
2700// Usage:
2701// PARENT() show_anchors([s], [std=], [custom=]);
2702// Description:
2703// Show all standard anchors for the parent object.
2704// Arguments:
2705// s = Length of anchor arrows.
2706// ---
2707// std = If true show standard anchors. Default: true
2708// custom = If true show named anchors. Default: true
2709// Example(FlatSpin,VPD=333):
2710// cube(50, center=true) show_anchors();
2711module show_anchors(s=10, std=true, custom=true) {
2712 check = assert($parent_geom != undef);
2713 two_d = _attach_geom_2d($parent_geom);
2714 if (std) {
2715 for (anchor=_standard_anchors(two_d=two_d)) {
2716 if(two_d) {
2717 attach(anchor) anchor_arrow2d(s);
2718 } else {
2719 attach(anchor) anchor_arrow(s);
2720 }
2721 }
2722 }
2723 if (custom) {
2724 for (anchor=last($parent_geom)) {
2725 attach(anchor[0]) {
2726 if(two_d) {
2727 anchor_arrow2d(s, color="cyan");
2728 } else {
2729 anchor_arrow(s, color="cyan");
2730 }
2731 color("black")
2732 tag("anchor-arrow") {
2733 xrot(two_d? 0 : 90) {
2734 back(s/3) {
2735 yrot_copies(n=2)
2736 up(two_d? 0.51 : s/30) {
2737 linear_extrude(height=0.01, convexity=12, center=true) {
2738 text(text=anchor[0], size=s/4, halign="center", valign="center", font="Helvetica", $fn=36);
2739 }
2740 }
2741 }
2742 }
2743 }
2744 color([1, 1, 1, 1])
2745 tag("anchor-arrow") {
2746 xrot(two_d? 0 : 90) {
2747 back(s/3) {
2748 cube([s/4.5*len(anchor[0]), s/3, 0.01], center=true);
2749 }
2750 }
2751 }
2752 }
2753 }
2754 }
2755 children();
2756}
2757
2758
2759// Module: anchor_arrow()
2760// Usage:
2761// anchor_arrow([s], [color], [flag], [anchor=], [orient=], [spin=]) [ATTACHMENTS];
2762// Description:
2763// Show an anchor orientation arrow. By default, tagged with the name "anchor-arrow".
2764// Arguments:
2765// s = Length of the arrows. Default: `10`
2766// color = Color of the arrow. Default: `[0.333, 0.333, 1]`
2767// flag = If true, draw the orientation flag on the arrowhead. Default: true
2768// ---
2769// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
2770// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
2771// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
2772// Example:
2773// anchor_arrow(s=20);
2774module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tag="anchor-arrow", $fn=12, anchor=BOT, spin=0, orient=UP) {
2775 attachable(anchor,spin,orient, r=s/6, l=s) {
2776 down(s/2)
2777 recolor("gray") spheroid(d=s/6) {
2778 attach(CENTER,BOT) recolor(color) cyl(h=s*2/3, d=s/15) {
2779 attach(TOP,BOT) cyl(h=s/3, d1=s/5, d2=0) {
2780 if(flag) {
2781 position(BOT)
2782 recolor([1,0.5,0.5])
2783 cuboid([s/100, s/6, s/4], anchor=FRONT+BOT);
2784 }
2785 }
2786 }
2787 }
2788 children();
2789 }
2790}
2791
2792
2793
2794// Module: anchor_arrow2d()
2795// Usage:
2796// anchor_arrow2d([s], [color], [flag]);
2797// Description:
2798// Show an anchor orientation arrow.
2799// Arguments:
2800// s = Length of the arrows.
2801// color = Color of the arrow.
2802// Example:
2803// anchor_arrow2d(s=20);
2804module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tag="anchor-arrow") {
2805 color(color) stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
2806}
2807
2808
2809
2810// Module: expose_anchors()
2811// Usage:
2812// expose_anchors(opacity) {child1() show_anchors(); child2() show_anchors(); ...}
2813// Description:
2814// Used in combination with show_anchors() to display an object in transparent gray with its anchors in solid color.
2815// Children will appear transparent and any anchor arrows drawn with will appear in solid color.
2816// Arguments:
2817// opacity = The opacity of the children. 0.0 is invisible, 1.0 is opaque. Default: 0.2
2818// Example(FlatSpin,VPD=333):
2819// expose_anchors() cube(50, center=true) show_anchors();
2820module expose_anchors(opacity=0.2) {
2821 show_only("anchor-arrow")
2822 children();
2823 hide("anchor-arrow")
2824 color(is_undef($color) || $color=="default" ? [0,0,0] :
2825 is_string($color) ? $color
2826 : point3d($color),
2827 opacity)
2828 children();
2829}
2830
2831
2832
2833// Module: frame_ref()
2834// Usage:
2835// frame_ref(s, opacity);
2836// Description:
2837// Displays X,Y,Z axis arrows in red, green, and blue respectively.
2838// Arguments:
2839// s = Length of the arrows.
2840// opacity = The opacity of the arrows. 0.0 is invisible, 1.0 is opaque. Default: 1.0
2841// Examples:
2842// frame_ref(25);
2843// frame_ref(30, opacity=0.5);
2844module frame_ref(s=15, opacity=1) {
2845 cube(0.01, center=true) {
2846 attach([1,0,0]) anchor_arrow(s=s, flag=false, color=[1.0, 0.3, 0.3, opacity]);
2847 attach([0,1,0]) anchor_arrow(s=s, flag=false, color=[0.3, 1.0, 0.3, opacity]);
2848 attach([0,0,1]) anchor_arrow(s=s, flag=false, color=[0.3, 0.3, 1.0, opacity]);
2849 children();
2850 }
2851}
2852
2853
2854////////////////////////////////////////////////////////////////////////////////////////////////////
2855////////////////////////////////////////////////////////////////////////////////////////////////////
2856////////////////////////////////////////////////////////////////////////////////////////////////////
2857////////////////////////////////////////////////////////////////////////////////////////////////////
2858///
2859/// Code after this is internal code for managing edge and corner sets and for displaying
2860/// edge and corners in the docs
2861///
2862
2863module _edges_text3d(txt,size=3) {
2864 if (is_list(txt)) {
2865 for (i=idx(txt)) {
2866 down((i-len(txt)/2+0.5)*size*1.5) {
2867 _edges_text3d(txt[i], size=size);
2868 }
2869 }
2870 } else {
2871 xrot(90) color("#000")
2872 linear_extrude(height=0.1) {
2873 text(text=txt, size=size, halign="center", valign="center");
2874 }
2875 }
2876}
2877
2878
2879function _edges_vec_txt(x) = is_string(x)? str("\"", x, "\"") :
2880 assert(is_string(x) || is_vector(x,3), str(x))
2881 let(
2882 lst = concat(
2883 x.z>0? ["TOP"] : x.z<0? ["BOT"] : [],
2884 x.y>0? ["BACK"] : x.y<0? ["FWD"] : [],
2885 x.x>0? ["RIGHT"] : x.x<0? ["LEFT"] : []
2886 ),
2887 out = [
2888 for (i = idx(lst))
2889 i>0? str("+",lst[i]) : lst[i]
2890 ]
2891 ) out;
2892
2893
2894function _edges_text(edges) =
2895 is_string(edges) ? [str("\"",edges,"\"")] :
2896 edges==EDGES_NONE ? ["EDGES_NONE"] :
2897 edges==EDGES_ALL ? ["EDGES_ALL"] :
2898 _is_edge_array(edges) ? [""] :
2899 is_vector(edges,3) ? _edges_vec_txt(edges) :
2900 is_list(edges) ? let(
2901 lst = [for (x=edges) each _edges_text(x)],
2902 out = [
2903 for (i=idx(lst))
2904 str(
2905 (i==0? "[" : ""),
2906 lst[i],
2907 (i<len(lst)-1? "," : ""),
2908 (i==len(lst)-1? "]" : "")
2909 )
2910 ]
2911 ) out :
2912 [""];
2913
2914
2915
2916/// Internal Constant: EDGES_NONE
2917/// Topics: Edges
2918/// See Also: EDGES_ALL, edges()
2919/// Description:
2920/// The set of no edges.
2921/// Figure(3D):
2922/// _show_edges(edges="NONE");
2923EDGES_NONE = [[0,0,0,0], [0,0,0,0], [0,0,0,0]];
2924
2925
2926/// Internal Constant: EDGES_ALL
2927/// Topics: Edges
2928/// See Also: EDGES_NONE, edges()
2929/// Description:
2930/// The set of all edges.
2931/// Figure(3D):
2932/// _show_edges(edges="ALL");
2933EDGES_ALL = [[1,1,1,1], [1,1,1,1], [1,1,1,1]];
2934
2935
2936/// Internal Constant: EDGES_OFFSETS
2937/// Topics: Edges
2938/// See Also: EDGES_NONE, EDGES_ALL, edges()
2939/// Description:
2940/// The vectors pointing to the center of each edge of a unit sized cube.
2941/// Each item in an edge array will have a corresponding vector in this array.
2942EDGE_OFFSETS = [
2943 [
2944 [ 0,-1,-1],
2945 [ 0, 1,-1],
2946 [ 0,-1, 1],
2947 [ 0, 1, 1]
2948 ], [
2949 [-1, 0,-1],
2950 [ 1, 0,-1],
2951 [-1, 0, 1],
2952 [ 1, 0, 1]
2953 ], [
2954 [-1,-1, 0],
2955 [ 1,-1, 0],
2956 [-1, 1, 0],
2957 [ 1, 1, 0]
2958 ]
2959];
2960
2961
2962
2963/// Internal Function: _is_edge_array()
2964/// Topics: Edges, Type Checking
2965/// Usage:
2966/// bool = _is_edge_array(x);
2967/// Description:
2968/// Returns true if the given value has the form of an edge array.
2969/// Arguments:
2970/// x = The item to check the type of.
2971/// See Also: edges(), EDGES_NONE, EDGES_ALL
2972function _is_edge_array(x) = is_list(x) && is_vector(x[0]) && len(x)==3 && len(x[0])==4;
2973
2974
2975function _edge_set(v) =
2976 _is_edge_array(v)? v : [
2977 for (ax=[0:2]) [
2978 for (b=[-1,1], a=[-1,1]) let(
2979 v2=[[0,a,b],[a,0,b],[a,b,0]][ax]
2980 ) (
2981 is_string(v)? (
2982 v=="X"? (ax==0) : // Return all X axis aligned edges.
2983 v=="Y"? (ax==1) : // Return all Y axis aligned edges.
2984 v=="Z"? (ax==2) : // Return all Z axis aligned edges.
2985 v=="ALL"? true : // Return all edges.
2986 v=="NONE"? false : // Return no edges.
2987 let(valid_values = ["X", "Y", "Z", "ALL", "NONE"])
2988 assert(
2989 in_list(v, valid_values),
2990 str(v, " must be a vector, edge array, or one of ", valid_values)
2991 ) v
2992 ) :
2993 let(nonz = sum(v_abs(v)))
2994 nonz==2? (v==v2) : // Edge: return matching edge.
2995 let(
2996 matches = num_true([
2997 for (i=[0:2]) v[i] && (v[i]==v2[i])
2998 ])
2999 )
3000 nonz==1? (matches==1) : // Face: return surrounding edges.
3001 (matches==2) // Corner: return touching edges.
3002 )? 1 : 0
3003 ]
3004];
3005
3006
3007/// Internal Function: _normalize_edges()
3008/// Topics: Edges
3009/// Usage:
3010/// edges = _normalize_edges(v);
3011/// Description:
3012/// Normalizes all values in an edge array to be `1`, if it was originally greater than `0`,
3013/// or `0`, if it was originally less than or equal to `0`.
3014/// See Also: edges(), EDGES_NONE, EDGES_ALL
3015function _normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]];
3016
3017
3018
3019
3020/// Internal Function: _edges()
3021/// Topics: Edges
3022/// Usage:
3023/// edgs = _edges(v);
3024/// edgs = _edges(v, except);
3025///
3026/// Description:
3027/// Takes a list of edge set descriptors, and returns a normalized edges array
3028/// that represents all those given edges.
3029/// Arguments:
3030/// v = The edge set to include.
3031/// except = The edge set to specifically exclude, even if they are in `v`.
3032///
3033/// See Also: EDGES_NONE, EDGES_ALL
3034///
3035function _edges(v, except=[]) =
3036 v==[] ? EDGES_NONE :
3037 (is_string(v) || is_vector(v) || _is_edge_array(v))? _edges([v], except=except) :
3038 (is_string(except) || is_vector(except) || _is_edge_array(except))? _edges(v, except=[except]) :
3039 except==[]? _normalize_edges(sum([for (x=v) _edge_set(x)])) :
3040 _normalize_edges(
3041 _normalize_edges(sum([for (x=v) _edge_set(x)])) -
3042 sum([for (x=except) _edge_set(x)])
3043 );
3044
3045
3046/// Internal Module: _show_edges()
3047/// Topics: Edges, Debugging
3048/// Usage:
3049/// _show_edges(edges, [size=], [text=], [txtsize=]);
3050/// Description:
3051/// Draws a semi-transparent cube with the given edges highlighted in red.
3052/// Arguments:
3053/// edges = The edges to highlight.
3054/// size = The scalar size of the cube.
3055/// text = The text to show on the front of the cube.
3056/// txtsize = The size of the text.
3057/// See Also: _edges(), EDGES_NONE, EDGES_ALL
3058/// Example:
3059/// _show_edges(size=30, edges=["X","Y"]);
3060module _show_edges(edges="ALL", size=20, text, txtsize=3,toplabel) {
3061 edge_set = _edges(edges);
3062 text = !is_undef(text) ? text : _edges_text(edges);
3063 color("red") {
3064 for (axis=[0:2], i=[0:3]) {
3065 if (edge_set[axis][i] > 0) {
3066 translate(EDGE_OFFSETS[axis][i]*size/2) {
3067 if (axis==0) xcyl(h=size, d=2);
3068 if (axis==1) ycyl(h=size, d=2);
3069 if (axis==2) zcyl(h=size, d=2);
3070 }
3071 }
3072 }
3073 }
3074 fwd(size/2) _edges_text3d(text, size=txtsize);
3075 color("yellow",0.7) cuboid(size=size);
3076 vpr = [55,0,25];
3077 color("black")
3078 if (is_def(toplabel))
3079 for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=0.1,orient=UP,anchor=FRONT);
3080}
3081
3082
3083
3084
3085/// Internal Constant: CORNERS_NONE
3086/// Topics: Corners
3087/// Description:
3088/// The set of no corners.
3089/// Figure(3D):
3090/// _show_corners(corners="NONE");
3091/// See Also: CORNERS_ALL, corners()
3092CORNERS_NONE = [0,0,0,0,0,0,0,0]; // No corners.
3093
3094
3095/// Internal Constant: CORNERS_ALL
3096/// Topics: Corners
3097/// Description:
3098/// The set of all corners.
3099/// Figure(3D):
3100/// _show_corners(corners="ALL");
3101/// See Also: CORNERS_NONE, _corners()
3102CORNERS_ALL = [1,1,1,1,1,1,1,1];
3103
3104
3105/// Internal Constant: CORNER_OFFSETS
3106/// Topics: Corners
3107/// Description:
3108/// The vectors pointing to each corner of a unit sized cube.
3109/// Each item in a corner array will have a corresponding vector in this array.
3110/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
3111CORNER_OFFSETS = [
3112 [-1,-1,-1], [ 1,-1,-1], [-1, 1,-1], [ 1, 1,-1],
3113 [-1,-1, 1], [ 1,-1, 1], [-1, 1, 1], [ 1, 1, 1]
3114];
3115
3116
3117
3118
3119/// Internal Function: _is_corner_array()
3120/// Topics: Corners, Type Checking
3121/// Usage:
3122/// bool = _is_corner_array(x)
3123/// Description:
3124/// Returns true if the given value has the form of a corner array.
3125/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
3126function _is_corner_array(x) = is_vector(x) && len(x)==8 && all([for (xx=x) xx==1||xx==0]);
3127
3128
3129/// Internal Function: _normalize_corners()
3130/// Topics: Corners
3131/// Usage:
3132/// corns = _normalize_corners(v);
3133/// Description:
3134/// Normalizes all values in a corner array to be `1`, if it was originally greater than `0`,
3135/// or `0`, if it was originally less than or equal to `0`.
3136/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
3137function _normalize_corners(v) = [for (x=v) x>0? 1 : 0];
3138
3139
3140function _corner_set(v) =
3141 _is_corner_array(v)? v : [
3142 for (i=[0:7]) let(
3143 v2 = CORNER_OFFSETS[i]
3144 ) (
3145 is_string(v)? (
3146 v=="ALL"? true : // Return all corners.
3147 v=="NONE"? false : // Return no corners.
3148 let(valid_values = ["ALL", "NONE"])
3149 assert(
3150 in_list(v, valid_values),
3151 str(v, " must be a vector, corner array, or one of ", valid_values)
3152 ) v
3153 ) :
3154 all([for (i=[0:2]) !v[i] || (v[i]==v2[i])])
3155 )? 1 : 0
3156];
3157
3158
3159/// Function: _corners()
3160/// Topics: Corners
3161/// Usage:
3162/// corns = _corners(v);
3163/// corns = _corners(v, except);
3164/// Description:
3165/// Takes a list of corner set descriptors, and returns a normalized corners array
3166/// that represents all those given corners. If the `except` argument is given
3167/// a list of corner set descriptors, then all those corners will be removed
3168/// from the returned corners array. If either argument only has a single corner
3169/// set descriptor, you do not have to pass it in a list.
3170function _corners(v, except=[]) =
3171 v==[] ? CORNERS_NONE :
3172 (is_string(v) || is_vector(v) || _is_corner_array(v))? _corners([v], except=except) :
3173 (is_string(except) || is_vector(except) || _is_corner_array(except))? _corners(v, except=[except]) :
3174 except==[]? _normalize_corners(sum([for (x=v) _corner_set(x)])) :
3175 let(
3176 a = _normalize_corners(sum([for (x=v) _corner_set(x)])),
3177 b = _normalize_corners(sum([for (x=except) _corner_set(x)]))
3178 ) _normalize_corners(a - b);
3179
3180
3181/// Internal Function: _corner_edges()
3182/// Topics: Corners
3183/// Description:
3184/// Returns [XCOUNT,YCOUNT,ZCOUNT] where each is the count of edges aligned with that
3185/// axis that are in the edge set and touch the given corner.
3186/// Arguments:
3187/// edges = Standard edges array.
3188/// v = Vector pointing to the corner to count edge intersections at.
3189/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
3190function _corner_edges(edges, v) =
3191 let(u = (v+[1,1,1])/2) [edges[0][u.y+u.z*2], edges[1][u.x+u.z*2], edges[2][u.x+u.y*2]];
3192
3193
3194/// InternalFunction: _corner_edge_count()
3195/// Topics: Corners
3196/// Description:
3197/// Counts how many given edges intersect at a specific corner.
3198/// Arguments:
3199/// edges = Standard edges array.
3200/// v = Vector pointing to the corner to count edge intersections at.
3201/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
3202function _corner_edge_count(edges, v) =
3203 let(u = (v+[1,1,1])/2) edges[0][u.y+u.z*2] + edges[1][u.x+u.z*2] + edges[2][u.x+u.y*2];
3204
3205
3206function _corners_text(corners) =
3207 is_string(corners) ? [str("\"",corners,"\"")] :
3208 corners==CORNERS_NONE ? ["CORNERS_NONE"] :
3209 corners==CORNERS_ALL ? ["CORNERS_ALL"] :
3210 _is_corner_array(corners) ? [""] :
3211 is_vector(corners,3) ? _edges_vec_txt(corners) :
3212 is_list(corners) ? let(
3213 lst = [for (x=corners) each _corners_text(x)],
3214 out = [
3215 for (i=idx(lst))
3216 str(
3217 (i==0? "[" : ""),
3218 lst[i],
3219 (i<len(lst)-1? "," : ""),
3220 (i==len(lst)-1? "]" : "")
3221 )
3222 ]
3223 ) out :
3224 [""];
3225
3226
3227/// Internal Module: _show_corners()
3228/// Topics: Corners, Debugging
3229/// Usage:
3230/// _show_corners(corners, [size=], [text=], [txtsize=]);
3231/// Description:
3232/// Draws a semi-transparent cube with the given corners highlighted in red.
3233/// Arguments:
3234/// corners = The corners to highlight.
3235/// size = The scalar size of the cube.
3236/// text = If given, overrides the text to be shown on the front of the cube.
3237/// txtsize = The size of the text.
3238/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
3239/// Example:
3240/// _show_corners(corners=FWD+RIGHT, size=30);
3241module _show_corners(corners="ALL", size=20, text, txtsize=3,toplabel) {
3242 corner_set = _corners(corners);
3243 text = !is_undef(text) ? text : _corners_text(corners);
3244 for (i=[0:7]) if (corner_set[i]>0)
3245 translate(CORNER_OFFSETS[i]*size/2)
3246 color("red") sphere(d=2, $fn=16);
3247 fwd(size/2) _edges_text3d(text, size=txtsize);
3248 color("yellow",0.7) cuboid(size=size);
3249 vpr = [55,0,25];
3250 color("black")
3251 if (is_def(toplabel))
3252 for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=.1,orient=UP,anchor=FRONT);
3253}
3254
3255module _show_cube_faces(faces, size=20, toplabel,botlabel) {
3256 color("red")
3257 for(f=faces){
3258 move(f*size/2) rot(from=UP,to=f)
3259 cuboid([size,size,.1]);
3260 }
3261
3262 vpr = [55,0,25];
3263 color("black"){
3264 if (is_def(toplabel))
3265 for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=.1,orient=UP,anchor=FRONT);
3266 if (is_def(botlabel))
3267 for(h=idx(botlabel)) down(26+6*h)rot(vpr) text3d(botlabel[h],size=3.3,h=.1,orient=UP,anchor=FRONT);
3268 }
3269 color("yellow",0.7) cuboid(size=size);
3270}
3271
3272// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap