diff --git a/attachments.scad b/attachments.scad index 3ec8c60..327a22a 100644 --- a/attachments.scad +++ b/attachments.scad @@ -21,6 +21,7 @@ $overlap = 0; $color = "default"; $save_color = undef; // Saved color to revert back for children +$anchor_override = undef; $attach_to = undef; $attach_anchor = [CENTER, CENTER, UP, 0]; $attach_norot = false; @@ -474,52 +475,20 @@ _ANCHOR_TYPES = ["intersect","hull"]; // of attachments, see the [Attachments Tutorial](Tutorial-Attachments). // Arguments: // from = The vector, or name of the parent anchor point to attach to. -// from = The vector, or name of the parent anchor point to attach to. // Side Effects: // `$attach_anchor` for each `from=` anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor. // `$attach_to` is set to `undef`. // `$attach_norot` is set to `true`. -// `$align` set to the anchor that will position a child flush to the edge of the parent at specified position. // Example: // spheroid(d=20) { // position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM); // position(RIGHT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM); // position(FRONT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM); // } -// Example: Child would require anchor of RIGHT+FRONT+BOT if given explicitly. -// cuboid([50,40,15]) -// position(RIGHT+FRONT+TOP) -// color("lightblue")prismoid([10,5],[7,4],height=4, anchor=$align); -// Example: Child requires a different anchor for each position, so explicit specification of the anchor is impossible in this case. -// cuboid([50,40,15]) -// position([RIGHT+TOP,LEFT+TOP]) -// color("lightblue")prismoid([10,5],[7,4],height=4, anchor=$align); -// Example: If you try to spin your child, the spin happens after the position anchor, so the child will not be flush: -// cuboid([50,40,15]) -// position([RIGHT+TOP]) -// color("lightblue")prismoid([10,5],[7,4],height=4, -// anchor=$align, spin=90); -// Example: You can instead spin the attached children using {{orient()}}. In this example, the required anchor is BOT+FWD, which is less obvious. -// cuboid([50,40,15]) -// position(RIGHT+TOP) -// orient(TOP, spin=90) -// color("lightblue")prismoid([10,5],[7,4],height=4, anchor=$align); -// Example: Of course, {{orient()}} can also place children on different sides of the parent. In this case you don't have to figure out that the required anchor is BOT+BACK. -// cuboid([50,40,15]) -// position(RIGHT+TOP) -// orient(RIGHT) -// color("lightblue")prismoid([10,5],[7,4],height=4, anchor=$align); -// Example: You can combine this with {{diff()}} to remove the child. Note that it's more intuitive to shift the child after positioning, relative to the global coordinate system: -// diff() -// cuboid([50,40,15]) -// right(.1)up(.1) -// position(RIGHT+TOP) -// orient(LEFT) -// tag("remove")cuboid([10,5,4], anchor=$align); module position(from) { req_children($children); - assert($parent_geom != undef, "No object to attach to!"); + dummy1=assert($parent_geom != undef, "No object to position relative to."); anchors = (is_vector(from)||is_string(from))? [from] : from; two_d = _attach_geom_2d($parent_geom); for (anchr = anchors) { @@ -527,19 +496,17 @@ module position(from) $attach_to = undef; $attach_anchor = anch; $attach_norot = true; - $align=two_d && anchr.y!=0 ? [anchr.x,-anchr.y] - :!two_d && anchr.z!=0 ? [anchr.x, anchr.y, -anchr.z] - : -anchr; translate(anch[1]) children(); } } + // Module: orient() // Synopsis: Orients children's tops in the directon of the specified anchor. // SynTags: Trans // Topics: Attachments -// See Also: attachable(), attach(), orient() +// See Also: attachable(), attach(), position() // Usage: // PARENT() orient(anchor, [spin]) CHILDREN; // Description: @@ -552,7 +519,6 @@ module position(from) // `$attach_anchor` is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for the `anchor=`, if given. // `$attach_to` is set to `undef`. // `$attach_norot` is set to `true`. -// `$align` is set to the anchor that will position the child flush on the parent at a designated {{position()}} // // Example: When orienting to an anchor, the spin of the anchor may cause confusion: // prismoid([50,50],[30,30],h=40) { @@ -581,13 +547,8 @@ module orient(anchor, spin) { two_d = _attach_geom_2d($parent_geom); fromvec = two_d? BACK : UP; spin = default(spin, anch[3]); - assert(is_finite(spin)); + dummy=assert(is_finite(spin)); - $align = is_undef($attach_anchor) ? undef - : two_d ? let(newalign=rot(from=anch[2], to=fromvec, p=zrot(-spin,$attach_anchor[0]))) - [sign(newalign.x), -1] - : let(newalign=rot(spin, from=fromvec, to=anch[2], reverse=true, p=$attach_anchor[0])) - [sign(newalign.x), sign(newalign.y), -1]; $attach_to = undef; $attach_anchor = anch; $attach_norot = true; @@ -599,6 +560,111 @@ module orient(anchor, spin) { +// Module: align() +// Synopsis: Position and orient children with alignment to parent edges. +// SynTags: Trans +// Topics: Attachments +// See Also: attachable(), attach(), position(), orient() +// Usage: +// PARENT() align(anchor, [orient], [spin]) CHILDREN; +// Description: +// Positions children to the specified anchor(s) on the parent and anchors the +// children so that they are aligned with the edge(s) of the parent at those parent anchors. +// You can specify a parent anchor point in `orient` and in this case, the top of the child +// is tilted in the direction of that anchor. +// This means you can easily place children so they are aligned flush with edges of the parent. +// In contrast, with {{position()}} you will have to work out the correct anchor for the children +// which is not always obvious. It also enables you to place several children that have different +// anchors, which would otherwise require several {{position()}} calls. The inside argument +// causes the object to appear inside the parent for use with {{diff()}}. +// . +// When you use `align()`, the `orient=` and `anchor=` arguments to the child objects are overriden, +// so they do not have any effect. The `spin=` argument to the child still applies. +// Arguments: +// anchor = parent anchor or list of parent anchors for positioning children +// orient = parent anchor to give direction for orienting the children. Default: UP +// spin = spin in degrees for rotating the children. Default: Derived from orient anchor +// inside = if true, place object inside the parent instead of outside. Default: false +// Example: Child would require anchor of RIGHT+FRONT+BOT if placed with {{position()}}. +// cuboid([50,40,15]) +// align(RIGHT+FRONT+TOP) +// color("lightblue")prismoid([10,5],[7,4],height=4); +// Example: Child requires a different anchor for each position, so explicit specification of the anchor for children is impossible in this case, without using two separate commands. +// cuboid([50,40,15]) +// align([RIGHT+TOP,LEFT+TOP]) +// color("lightblue")prismoid([10,5],[7,4],height=4); +// Example: If you try to spin your child, the spin happens after the alignment anchor, so the child will not be flush: +// cuboid([50,40,15]) +// align([RIGHT+TOP]) +// color("lightblue") +// prismoid([10,5],[7,4],height=4,spin=90); +// Example: You can instead spin the attached children using the spin parameter to `align()`. In this example, the required anchor is BOT+FWD, which is less obvious. +// cuboid([50,40,15]) +// align(RIGHT+TOP,spin=90) +// color("lightblue")prismoid([10,5],[7,4],height=4); +// Example: Here the child is oriented to the RIGHT, so it appears flush with the top. In this case you don't have to figure out that the required child anchor is BOT+BACK. +// cuboid([50,40,15]) +// align(RIGHT+TOP,RIGHT) +// color("lightblue")prismoid([10,5],[7,4],height=4); +// Example: If you change the orientation the child still appears aligned flush in its changed orientation: +// cuboid([50,40,15]) +// align(RIGHT+TOP,DOWN) +// color("lightblue")prismoid([10,5],[7,4],height=4); +// Example: Objects on the right already have nonzero spin by default, so setting spin=0 changes the spin: +// prismoid(50,30,25){ +// align(RIGHT+TOP,RIGHT,spin=0) +// color("lightblue")prismoid([10,5],[7,4],height=4); +// align(RIGHT+BOT,RIGHT) +// color("green")prismoid([10,5],[7,4],height=4); +// } +// Example: Setting inside=true enables us to subtract the child from the parent with {{diff()}. +// diff() +// cuboid([40,30,10]) +// move(.1*[0,-1,1]) +// align(FRONT+TOP,inside=true) +// tag("remove") +// prismoid([10,5],[7,5],height=4); + + +module align(anchor,orient=UP,spin,inside=false) +{ + req_children($children); + dummy1=assert($parent_geom != undef, "No object to align to.") + assert(is_string(orient) || is_vector(orient),"Bad orient value"); + position_anchors = (is_vector(anchor)||is_string(anchor))? [anchor] : anchor; + two_d = _attach_geom_2d($parent_geom); + fromvec = two_d? BACK : UP; + + orient_anch = _find_anchor(orient, $parent_geom); + spin = default(spin, orient_anch[3]); + dummy2=assert(is_finite(spin)); + + $attach_to = undef; + $attach_norot = true; + + factor = inside?1:-1; + + for (thisanch = position_anchors) { + pos_anch = _find_anchor(thisanch, $parent_geom); + init_anch = two_d ? rot(from=orient_anch[2], to=fromvec, p=zrot(-spin,pos_anch[0])) + : rot(spin, from=fromvec, to=orient_anch[2], reverse=true, p=pos_anch[0]); + quant_anch = [for(v=init_anch) sign(round(v))]; + $anchor_override = two_d && quant_anch.y!=0 ? [quant_anch.x,factor*quant_anch.y] + : !two_d && quant_anch.z!=0 ? [quant_anch.x,quant_anch.y, factor*quant_anch.z] + : factor*quant_anch; + $attach_anchor = pos_anch; + translate(pos_anch[1]) { + if (two_d) + rot(spin)rot(from=fromvec, to=orient_anch[2]) children(); + else + rot(spin, from=fromvec, to=orient_anch[2]) children(); + } + } +} + + + + // Module: attach() // Synopsis: Attaches children to a parent object at an anchor point and orientation. @@ -2331,9 +2397,9 @@ module attachable( assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor)) assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Got: ",spin)) assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient)); - anchor = default(anchor, CENTER); + anchor = first_defined([$anchor_override, anchor, CENTER]); spin = default(spin, 0); - orient = default(orient, UP); + orient = is_def($anchor_override)? UP : default(orient, UP); region = !is_undef(region)? region : !is_undef(path)? [path] : undef; @@ -2354,7 +2420,7 @@ module attachable( $parent_geom = geom; $parent_size = _attach_geom_size(geom); $attach_to = undef; - $align=undef; + $anchor_override=undef; if (_is_shown()) _color($color) children(0); if (is_def($save_color)) { @@ -2838,6 +2904,7 @@ function _attach_transform(anchor, spin, orient, geom, p) = assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient)) let( anchor = default(anchor, CENTER), + spin = default(spin, 0), orient = default(orient, UP), two_d = _attach_geom_2d(geom), diff --git a/tutorials/Attachments.md b/tutorials/Attachments.md index 0a22073..e94d6d6 100644 --- a/tutorials/Attachments.md +++ b/tutorials/Attachments.md @@ -375,31 +375,6 @@ cube([50,50,20],center=true) position(TOP+RIGHT) left(5) cube([4,50,10], anchor=RIGHT+BOT); ``` -When you position a child at an edge or corner of a parent object, you very likely -need to change the anchor for the child in a matching way to align the child with the surface and edge -of the parent. You can automate this process by making use of the `$align` special variable, which -is set by `position()`. To align a child with a corner you can do: - -```openscad-3D -include -cuboid([50,40,15]) - position(RIGHT+FRONT+TOP) - color("lightblue")prismoid([10,5],[7,4],height=4, anchor=$align); -``` - -In this example, the `$align` variable replaces the explicit anchor of `RIGHT+FRONT+BOT` that -would be required for this case. The `position()` module can also accept a list of positions, -and it will place a copy of the child at each location. However, if you want to align multiple -children to the parent in different locations, each child needs a different anchor. This can -be achieved using the `$align` variable: - -```openscad-3D -include -cuboid([50,40,15]) - position([RIGHT+TOP,LEFT+TOP]) - color("lightblue")prismoid([10,5],[7,4],height=4, anchor=$align); -``` - Positioning objects works the same way in 2D. @@ -489,28 +464,102 @@ prismoid([50,50],[30,30],h=40) anchor_arrow(40); ``` -The potential confusion that spin creates for the proper anchoring of children -can be eliminated using the `$align` variable which takes into account the spin -that the `orient()` module applies. + +## Aligning children with align() + +You may have noticed that with position() and orient(), specifying the +child anchors to position objects flush with their parent can be +annoying, or sometimes even tricky. You can simplify this task by +using the align() module. This module positions children at specified +anchor points on the parent while picking the correct anchor points on +the children so that they line up with faces on the parent object. + +In the simplest case, if you want to place a child on the RIGHT side +of its parent, you need to anchor the child to its LEFT anchor: ```openscad-3D include cuboid([50,40,15]) - position(RIGHT+TOP) - orient(RIGHT) - color("lightblue")prismoid([10,5],[7,4],height=4, anchor=$align); + position(RIGHT) + color("lightblue")cuboid(5,anchor=LEFT); ``` -In this case, the correct anchor is chosen to place the child on the right side -with the appropriate anchor taking the spin into account. You can use this method -for children placed on the top by using orient with a TOP direction, but nonzero spin. +Using align(), the determination of the anchor is automatic. Any +anchor you do specify is ignored. ```openscad-3D include cuboid([50,40,15]) - position(RIGHT+TOP) - orient(TOP, spin=90) - color("lightblue")prismoid([10,5],[7,4],height=4, anchor=$align); + align(RIGHT) + color("lightblue")cuboid(5); +``` + +To place the child on top of the parent in the corner you can do use +align as shown below instead of specifying the RIGHT+FRONT+BOT anchor +with position(): + +```openscad-3D +include +cuboid([50,40,15]) + align(RIGHT+FRONT+TOP) + color("lightblue")prismoid([10,5],[7,4],height=4); +``` + +Both position() and align() can accept a list of anchor locations and +makes several copies of the children, but +if you want the children positioned flush, each copy +requires a different anchor, so it is impossible to do this with a +singlke call to position(), but easily done using align(): + +```openscad-3D +include +cuboid([50,40,15]) + align([RIGHT+TOP,LEFT+TOP]) + color("lightblue")prismoid([10,5],[7,4],height=4); +``` + +Align also accepts a spin argument, which lets you spin the child +while still aligning it: + +```openscad-3D +include +cuboid([50,40,15]) + align(RIGHT+TOP,spin=90) + color("lightblue")prismoid([10,5],[7,4],height=4); +``` + +Note that this is different than using the spin argument to the child +object, which will apply after alignment has been done. + + +```openscad-3D +include +cuboid([50,40,15]) + align(RIGHT+TOP) + color("lightblue")prismoid([10,5],[7,4],height=4,spin=90); +``` + +If you orient the object DOWN it will be attached from its top anchor: + +```openscad-3D +include +cuboid([50,40,15]) + align(RIGHT+TOP,DOWN) + color("lightblue")prismoid([10,5],[7,4],height=4); +``` + +When placing children on the RIGHT and LEFT, there is a spin applied. +This means that setting spin=0 changes the orientation. Here we have +one object with the default and one object with zero spin: + +```openscad-3D +include +prismoid(50,30,25){ + align(RIGHT+TOP,RIGHT,spin=0) + color("lightblue")prismoid([10,5],[7,4],height=4); + align(RIGHT+BOT,RIGHT) + color("green")prismoid([10,5],[7,4],height=4); +} ```