Merge pull request #1421 from adrianVmariano/master

fix align() and add alignment to attach().
This commit is contained in:
Revar Desmera 2024-05-02 13:43:43 -07:00 committed by GitHub
commit 1cd3ac30d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 844 additions and 280 deletions

View file

@ -24,7 +24,7 @@ $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;
$attach_alignment = undef;
$parent_anchor = BOTTOM;
$parent_spin = 0;
@ -481,7 +481,6 @@ _ANCHOR_TYPES = ["intersect","hull"];
// 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`.
// Example:
// spheroid(d=20) {
// position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
@ -503,7 +502,6 @@ module position(at,from)
anch = _find_anchor(anchr, $parent_geom);
$attach_to = undef;
$attach_anchor = anch;
$attach_norot = true;
translate(anch[1]) children();
}
}
@ -525,7 +523,6 @@ module position(at,from)
// spin = The spin to add to the children. (Overrides anchor spin.)
// Side Effects:
// `$attach_to` is set to `undef`.
// `$attach_norot` is set to `true`.
// Example: When orienting to an anchor, the spin of the anchor may cause confusion:
// prismoid([50,50],[30,30],h=40) {
// position(TOP+RIGHT)
@ -556,7 +553,6 @@ module orient(anchor, spin) {
dummy=assert(is_finite(spin));
$attach_to = undef;
$attach_norot = true;
if (two_d)
rot(spin)rot(from=fromvec, to=anch[2]) children();
else
@ -564,174 +560,406 @@ module orient(anchor, spin) {
}
// Module: align()
// Synopsis: Position and orient children with alignment to parent edges.
// Synopsis: Position children with alignment to parent edges.
// SynTags: Trans
// Topics: Attachments
// See Also: attachable(), attach(), position(), orient()
// Usage:
// PARENT() align(anchor, [orient], [spin], [inside=]) CHILDREN;
// PARENT() align(anchor, [align], [inside=], [inset=], [shiftout=], [overlap=]) 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()}}.
// Place a child on the face identified by `anchor`. If align is not given or is CENTER
// then the child will be centered on top of the specified face, outside the parent object. The align parameter is a
// direction defining an edge or corner to align to. The child will be aligned to that edge or corner by
// choosing an appropriate anchor on the child.
// Like {{position()}} this module never rotates the child. If you give `anchor=RIGHT` then the child
// will be given the LEFT anchor and placed adjacent to the parent. You can use `orient=` or `spin=`
// with the child and the alignment will adjust to select the correct child anchor. Note that if
// you spin the child by an amount not a multiple of 90 degrees then an edge of the child will be
// placed against the parent. This module makes it easy to place children aligned flush with the edges
// of the parent, even after orienting them or spinning them. In contrast {{position()}} can
// do the same thing but you would have to figure out the correct child anchor, which is not always obvious.
// .
// 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.
// Because `align()` works by setting the child anchor, it overrides any anchor you specify to the child:
// any `anchor=` value given to the child is ignored.
// .
// Several options can adjust how the child is positioned. You can specify `inset=` to inset the
// aligned object from its alignment location. If you set `inside=true` then the
// child will appear inside the parent instead of on its surface so that you can use {{diff()}} to subract it.
// In this case the child recieved a default "remove" tag. The `shiftout=` option works with `inside=true` to
// shift the child out by the specified distance so that the child doesn't exactly align with the parent.
// .
// Note that in the description above the anchor was said to define a "face". You can also use this module
// with an edge anchor, in which case a corner of the child will be placed in contact with the specified
// edge and the align direction will shift the child to either end of the edge. You can even give a
// corner as the anchor point, but in that case the only allowed alignment is CENTER.
// .
// If you give a list of anchors and/or a list of align directions then all combinations are generated.
// In this way align() acts like a distributor, creating multiple copies of the child.
// Named anchors are not supported by `align()`.
// Arguments:
// anchor = parent anchor or list of parent anchors for positioning children
// 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
// anchor = parent anchor or list of parent anchors for positioning children.
// align = optional alignment direction or directions for aligning the children. Default: CENTER
// ---
// inside = if true, place object inside the parent instead of outside. Default: false
// inset = shift the child away from the alignment edge/corner by this amount. Default: 0
// shiftout = Shift an inside object outward so that it overlaps all the aligned faces. Default: 0
// overlap = Amount to sink the child into the parent. Defaults to `$overlap` which is zero by default.
// Side Effects:
// `$anchor` set to the anchor value used for the child.
// `$align` set to the align value used for the child.
// `$idx` set to a unique index for each child, increasing by alignment first.
// `$attach_anchor` for each anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
// `$attach_to` is set to `undef`.
// `$attach_norot` is set to `true`.
// `$anchor_override` is set to the anchor required for proper positioning of the child.
// if inside is true then set default tag to "remove"
// Example: Cuboid positioned on the right of its parent. Note that it is in its native orientation.
// cuboid([20,35,25])
// align(RIGHT)
// color("lightgreen")cuboid([5,1,9]);
// Example: Child would require anchor of RIGHT+FRONT+BOT if placed with {{position()}}.
// cuboid([50,40,15])
// align(RIGHT+FRONT+TOP)
// align(TOP,RIGHT+FRONT)
// 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.
// Example: Child requires a different anchor for each position, so a simple explicit specification of the anchor for children is impossible in this case, without using two separate commands.
// cuboid([50,40,15])
// align([RIGHT+TOP,LEFT+TOP])
// align(TOP,[RIGHT,LEFT])
// 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:
// Example: If you spin the child 90 deg it is still flush with the edge of the parent. In this case the required anchor for the child is BOT+FWD:
// cuboid([50,40,15])
// align([RIGHT+TOP])
// align(TOP,RIGHT)
// 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.
// Example: Here the child is placed on the RIGHT face. Notice how the TOP+LEFT anchor of the prismoid is aligned with the edge of the parent. The prismoid remains in the same orientation.
// cuboid([50,40,15])
// align(RIGHT+TOP,spin=90)
// align(RIGHT,TOP)
// 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.
// Example: If you change the orientation of the child it still appears aligned flush in its changed orientation:
// 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:
// align(TOP, RIGHT)
// color("lightblue")prismoid([10,5],[7,4],height=4,orient=DOWN);
// Example: The center of the cubes edge is lined up with the center of the prismoid edge, so this result is the expected result:
// prismoid(50,30,25)
// align(RIGHT,FRONT)
// color("lightblue")cuboid(8);
// Example: Spinning the cube means that the corner of the cube is the most extreme point, so that's what aligns with the front edge of the parent:
// cuboid([50,40,15])
// align(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()}. The "remove" tag is automatically applied when you set `inside=true`.
// align(TOP,FWD)
// color("lightblue")cuboid(9,spin=22);
// Example: A similar thing happens if you attach a cube to a cylinder with an arbitrary anchor angle:
// cyl(h=20,d=10,$fn=128)
// align([1,.3],TOP)
// color("lightblue")cuboid(5);
// Example: Orienting the child is done in the global coordinate system (as usual) not in the parent coordinate system. Note that the blue prismoid is not lined up with the parent face. (To place the child on the face used {{attach()}}.
// prismoid(50,30,25)
// align(RIGHT)
// color("lightblue")prismoid([10,5],[7,4],height=4,orient=RIGHT);
// Example: Setting inside=true enables us to subtract the child from the parent with {{diff()}. The "remove" tag is automatically applied when you set `inside=true`, and we used `out=0.01` to prevent z-fighting on the faces.
// diff()
// cuboid([40,30,10])
// move(.1*[0,-1,1])
// align(FRONT+TOP,inside=true)
// prismoid([10,5],[7,5],height=4);
module align(anchor,orient=UP,spin,inside=false)
// align(FRONT,TOP,inside=true,shiftout=.01)
// prismoid([10,5],[7,5],height=4);
// Example: Setting inset shifts all of the children away from their aligned edge, which is a different direction for each child.
// cuboid([40,30,30])
// align(FRONT,[TOP,BOT,LEFT,RIGHT,TOP+RIGHT,BOT+LEFT], inset=3)
// color("green") cuboid(2);
// Example: Changing the child characteristics based on the alignment
// cuboid([20,20,8])
// align(TOP,[for(i=[-1:1], j=[-1:1]) [i,j]])
// color("orange")
// if (norm($align)==0) cuboid([3,3,1]);
// else if (norm($align)==norm([1,1])) cuboid([3,3,4.5]);
// else cuboid(3);
// Example: In this example the pink cubes are positioned onto an edge. They meet edge-to-edge. Aligning left shifts the cube to the left end of the edge.
// cuboid([30,30,20])
// align(TOP+BACK,[CTR,LEFT])
// color("pink")cuboid(4);
// Example: Normally `overlap` is used to create a tiny overlap to keep CGAL happy, but you can also give it a large value as shown here:
// cuboid([30,30,20])
// align(TOP+BACK,[RIGHT,CTR,LEFT],overlap=2)
// color("lightblue")cuboid(4);
module align(anchor,align=CENTER,inside=false,inset=0,shiftout=0,overlap)
{
req_children($children);
overlap = (overlap!=undef)? overlap : $overlap;
dummy1=assert($parent_geom != undef, "No object to align to.")
assert(is_string(orient) || is_vector(orient),"Bad orient value");
position_anchors = (is_vector(anchor)||is_string(anchor))? [anchor] : anchor;
assert(is_undef($attach_to), "Cannot use align() as a child of attach()");
if (is_undef($align_msg) || $align_msg)
echo("ALERT: align() has changed, May 1, 2024. See the wiki and attach(align=). $align_msg=false disables this message");
anchor = is_vector(anchor) ? [anchor] : anchor;
align = is_vector(align) ? [align] : align;
two_d = _attach_geom_2d($parent_geom);
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])
default_tag("remove",inside) children();
else
rot(spin, from=fromvec, to=orient_anch[2])
default_tag("remove",inside) children();
factor = inside?-1:1;
for (i = idx(anchor)) {
$align_msg=false; // Remove me when removing the message above
face = anchor[i];
$anchor=face;
dummy=
assert(!is_string(face),
str("Named anchor \"",face,"\" given for anchor, but align() does not support named anchors"))
assert(is_vector(face) && (len(face)==2 || len(face)==3),
str("Invalid face ",face, ". Must be a 2-vector or 3-vector"));
thisface = two_d? _force_anchor_2d(face) : point3d(face);
for(j = idx(align)) {
edge=align[j];
$idx = j+len(align)*i;
$align=edge;
dummy1=assert(is_vector(edge) && (len(edge)==2 || len(edge)==3),
"align direction must be a 2-vector or 3-vector");
thisedge = two_d? _force_anchor_2d(edge) : point3d(edge);
dummy=assert(all_zero(v_mul(thisedge,thisface)),
str("align (",thisedge,") cannot include component parallel to anchor ",thisface));
thisface_anch = _find_anchor(thisface, $parent_geom);
inset_dir = two_d ? -thisface
: unit(thisface_anch[1]-_find_anchor([thisedge.x,0,0]+thisface, $parent_geom)[1],CTR)
+unit(thisface_anch[1]-_find_anchor([0,thisedge.y,0]+thisface, $parent_geom)[1],CTR)
+unit(thisface_anch[1]-_find_anchor([0,0,thisedge.z]+thisface, $parent_geom)[1],CTR);
pos_anch = _find_anchor(thisface+thisedge, $parent_geom);
$attach_alignment = thisedge-factor*thisface;
$attach_anchor=list_set(pos_anch,2,UP);
translate(pos_anch[1]
+inset*inset_dir
+shiftout*(thisface_anch[2]-inset_dir)
-overlap*thisface_anch[2])
default_tag("remove",inside) children();
}
}
}
// Quantize anchor entry to {-1,0,1}
function _quant_anch(x) = approx(x,0) ? 0 : sign(x);
// Make arbitrary anchor legal for a given geometry
function _make_anchor_legal(anchor,geom) =
in_list(geom[0], ["prismoid","trapezoid"]) ? [for(v=anchor) _quant_anch(v)]
: in_list(geom[0], ["conoid", "extrusion_extent"]) ? [anchor.x,anchor.y, _quant_anch(anchor.z)]
: anchor;
// Module: attach()
// Synopsis: Attaches children to a parent object at an anchor point and orientation.
// Synopsis: Attaches children to a parent object at an anchor point and with anchor orientation.
// SynTags: Trans
// Topics: Attachments
// See Also: attachable(), position(), face_profile(), edge_profile(), corner_profile()
// See Also: attachable(), position(), align(), face_profile(), edge_profile(), corner_profile()
// Usage:
// PARENT() attach(from, [overlap=], [norot=]) CHILDREN;
// PARENT() attach(from, to, [overlap=], [norot=]) CHILDREN;
// PARENT() attach(parent, child, [align=], [spin=], [overlap=], [inside=], [inset=], [shiftout=]) CHILDREN;
// PARENT() attach(parent, [overlap=], [spin=]) CHILDREN;
// Description:
// Attaches children to a parent object at an anchor point and orientation. Attached objects will
// be overlapped into the parent object by a little bit, as specified by the `$overlap`
// value (0 by default), or by the overriding `overlap=` argument. This is to prevent OpenSCAD
// Attaches children to a parent object at an anchor point or points, oriented in the anchor direction.
// This module differs from {{position()}} and {{align()}} in that it rotates the children to
// the anchor direction, which generally means it places the children on the surface of a parent.
// There are two modes of operation, parent anchor (single argument) and parent-child anchor (double argument).
// .
// The parent-child anchor (double argument) version is usually easier to use, and it is more powerful because it supports
// alignment. You provide an anchor on the parent (`parent`) and an anchor on the child (`child`).
// This module connects the `child` anchor on the child to the `parent` anchor on the parent.
// Imagine pointing the parent and child anchor arrows at each other and pushing the objects
// together until they meet at the anchor point. The most basic case
// is `attach(TOP,BOT)` which puts the bottom of the child onto the top of the parent. If you
// do `attach(RIGHT,BOT)` this puts the bottom of the child onto the right anchor of the parent.
// When an object is attached to the top or bottom its BACK direction will remaing pointing BACK.
// When an object is attached to one of the other anchors its FRONT will be pointed DOWN and its
// BACK pointed UP. You can change this using the `spin=` argument to attach(). Note that this spin
// rotates around the attachment vector and is not the same as the spin argument to the child, which
// will usually rotate around some other direction that may be hard to predict. For 2D objects you cannot
// give spin because it is not possible to spin around the attachment vector; spinning the object around the Z axis
// would change the child orientation so that the anchors are no longer parallel. Furthermore, any spin
// parameter you give to the child will be ignored so that the attachment condition of parallel anchors is preserved.
// .
// As with {{align()}} you can use the `align=` parameter to align the child to an edge or corner of the
// face where that child is attached. For example `attach(TOP,BOT,align=RIGHT)` would stand the child
// up on the top while aligning it with the right edge of the top face, and `attach(RIGHT,BOT,align=TOP)` which
// stand the object on the right face while aligning with the top edge. If you apply spin using the
// argument to `attach()` then it will be taken into account for the alignment. If you apply spin with
// a parameter to the child it will NOT be taken into account. Note that spin is not permitted for
// 2D objects because it would change the child orientation so that the anchors are no longer parallel.
// When you use `align=` you can also adjust the position using `inset=`, which shifts the child
// away from the edge or corner it is aligned to.
// .
// If you give `inside=true` then the anchor arrows are lined up so they are pointing the same direction and
// the child object will be located inside the parent. In this case a default "remove" tag is applied to
// the children.
// .
// Because the attachment process forces an orientation and anchor point for the child, it overrides
// any such specifications you give to the child: **both `anchor=` and `orient=` given to the child are
// ignored** with the **double argument** version of `attach()`. As noted above, you can give `spin=` to the
// child but using the `spin=` parameter to `attach()` is more likely to be useful.
// .
// For the single parameter version of `attach()` you give only the `parent` anchor. The `align` direction
// is not permitted. In this case the child is placed at the specified parent anchor point
// and rotated to the anchor direction. For example, `attach(TOP) cuboid(2);` will place a small
// cube **with its center** located at the TOP anchor of the parent, so just half the cube will project
// from the parent. If you want the cube sitting on the parent you need to anchor the cube to its bottom:
// `attach(TOP) cuboid(2,anchor=BOT);`.
// .
// The **single argument** version of `attach()` **respects `anchor=` and `orient=` given to the child.**
// These options will probably be necessary, in fact, to get the child correctly positioned. Note that
// giving `spin=` to `attach()` in this case is the same as applying `zrot()` to the child.
// .
// Attached children may be ovarlapped into the parent a bit, as given by the `$overlap` value
// which is 0 by default, or by the `overlap=` argument. This is to prevent OpenSCAD
// from making non-manifold objects. You can define `$overlap=` as an argument in a parent
// module to set the default for all attachments to it. For a step-by-step explanation of
// module to set the default for all attachments to it.
// .
// For a step-by-step explanation of
// attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// from = The vector, or name of the parent anchor point to attach to.
// to = Optional name of the child anchor point. If given, orients the child such that the named anchors align together rotationally.
// parent = The parent anchor point to attach to or a list of parent anchor points.
// child = Optional child anchor point. If given, orients the child to connect this anchor point to the parent anchor.
// ---
// align = If `child` is given you can specify alignment or list of alistnments to shift the child to an edge or corner of the parent.
// inset = Shift aligned children away from their alignment edge/corner by this amount. Default: 0
// overlap = Amount to sink child into the parent. Equivalent to `down(X)` after the attach. This defaults to the value in `$overlap`, which is `0` by default.
// norot = If true, don't rotate children when attaching to the anchor point. Only translate to the anchor point.
// inside = If `child` is given you can set `inside=true` to attach the child to the inside of the parent for diff() operations. Default: false
// shiftout = Shift an inside object outward so that it overlaps all the aligned faces. Default: 0
// spin = Amount to rotate the parent around the axis of the parent anchor. (Only permitted in 3D.)
// Side Effects:
// `$idx` is set to the index number of each anchor if a list of anchors is given. Otherwise is set to `0`.
// `$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 the value of the `to=` argument, if given. Otherwise, `undef`
// `$attach_norot` is set to the value of the `norot=` argument.
// Example:
// spheroid(d=20) {
// attach(TOP) down(1.5) cyl(l=11.5, d1=10, d2=5, anchor=BOTTOM);
// attach(RIGHT, BOTTOM) down(1.5) cyl(l=11.5, d1=10, d2=5);
// attach(FRONT, BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5);
// `$anchor` set to the parent anchor value used for the child.
// `$align` set to the align value used for the child.
// `$idx` set to a unique index for each child, increasing by alignment first.
// `$attach_anchor` for each anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
// if inside is true then set default tag to "remove"
// `$attach_to` is set to the value of the `child` argument, if given. Otherwise, `undef`
// Example: Cylinder placed on top of cube:
// cuboid(50)
// attach(TOP,BOT) cylinder(d1=30,d2=15,h=25);
// Example: Cylinder on right and front side of cube:
// cuboid(50)
// attach([RIGHT,FRONT],BOT) cylinder(d1=30,d2=15,h=25);
// Example: Using `align` can align child object(s) with edges
// prismoid(50,25,25) color("green"){
// attach(TOP,BOT,align=[BACK,FWD]) cuboid(4);
// attach(RIGHT,BOT,align=[TOP,BOT]) cuboid(4);
// }
module attach(from, to, overlap, norot=false)
// Example: One aligned to the corner upside down (light blue) and one inset fromt the corner (pink), one aligned on a side (orange) and one rotated and aligned (green).
// cuboid(30) {
// attach(TOP,TOP,align=FRONT+RIGHT) color("lightblue") prismoid(5,3,3);
// attach(TOP,BOT,inset=3,align=FRONT+LEFT) color("pink") prismoid(5,3,3);
// attach(FRONT,RIGHT,align=TOP) color("orange") prismoid(5,3,3);
// attach(FRONT,RIGHT,align=RIGHT,spin=90) color("lightgreen") prismoid(5,3,3);
// }
// Example: Rotation not a multiple of 90 degrees with alignment. The children are aligned on a corner.
// cuboid(30)
// attach(FRONT,BOT,spin=33,align=[RIGHT,LEFT,TOP,BOT,RIGHT+TOP])
// color("lightblue")cuboid(4);
// Example: Anchoring the cone onto the sphere gives a single point of contact.
// spheroid(d=20)
// attach([1,1.5,1], BOTTOM) cyl(l=11.5, d1=10, d2=5);
// Example: Using the `overlap` option can help:
// spheroid(d=20)
// attach([1,1.5,1], BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5);
// Example: Alignment works for cylinders but you can only align with either the top or bototm face:
// cyl(h=30,d=10)
// attach([LEFT,[1,1.3]], BOT,align=TOP) cuboid(6);
// Example: Attaching to edges. The light blue and orange objects are attached to edges. The purple object is attached to an edge and aligned.
// prismoid([20,10],[10,10],7){
// attach(RIGHT+TOP,BOT,align=FRONT) color("pink")cuboid(2);
// attach(BACK+TOP, BOT) color("lightblue")cuboid(2);
// attach(RIGHT+BOT, RIGHT,spin=90) color("orange")cyl(h=8,d=1);
// }
// Example: Attaching inside the parent. For inside attachment the anchors are lined up pointing the same direction, so the most natural way to anchor the child is using its TOP anchor. This is equivalent to anchoring outside with the BOTTOM anchor and then lowering the child into the parent by its full depth.
// back_half()
// diff()
// cuboid(20)
// attach(TOP,TOP,inside=true,shiftout=0.01) cyl(d1=10,d2=5,h=10);
// Example: Attaching inside the parent with alignment
// diff()
// cuboid(20){
// attach(TOP,TOP,inside=true,align=RIGHT,shiftout=.01) cuboid([8,7,3]);
// attach(TOP,TOP,inside=true,align=LEFT+FRONT,shiftout=0.01) cuboid([3,4,5]);
// attach(RIGHT+FRONT, TOP, inside=true) cuboid([10,3,5]);
// attach(RIGHT+FRONT, TOP, inside=true, align=TOP,shiftout=.01) cuboid([5,1,2]);
// }
module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, inside=false, from, to)
{
dummy3=
assert(num_defined([to,child])<2, "Cannot combine deprecated 'to' argument with 'child' parameter")
assert(num_defined([from,parent])<2, "Cannot combine deprecated 'from' argument with 'parent' parameter");
if (is_def(to))
echo("The 'to' option to attach() is deprecated and will be removed in the future. Use 'child' instead.");
if (is_def(from))
echo("The 'from' option to attach(0 is deprecated and will be removed in the future. Use 'parent' instead");
if (norot)
echo("The 'norot' option to attach() is deprecated and will be removed in the future. Use position() instead.");
req_children($children);
assert($parent_geom != undef, "No object to attach to!");
dummy=assert($parent_geom != undef, "No object to attach to!")
assert(is_undef(child) || is_string(child) || (is_vector(child) && (len(child)==2 || len(child)==3)), "child must be a named anchor (a string) or a 2-vector or 3-vector")
assert(is_undef(align) || !is_string(child), "child is a named anchor. Named anchors are not supported with align=");
two_d = _attach_geom_2d($parent_geom);
overlap = (overlap!=undef)? overlap : $overlap;
anchors = (is_vector(from)||is_string(from))? [from] : from;
for ($idx = idx(anchors)) {
anchr = anchors[$idx];
anch = _find_anchor(anchr, $parent_geom);
two_d = _attach_geom_2d($parent_geom);
$attach_to = to;
$attach_anchor = anch;
$attach_norot = norot;
olap = two_d? [0,-overlap,0] : [0,0,-overlap];
if (norot || (norm(anch[2]-UP)<1e-9 && anch[3]==0)) {
translate(anch[1]) translate(olap) children();
} else {
fromvec = two_d? BACK : UP;
translate(anch[1]) rot(anch[3],from=fromvec,to=anch[2]) translate(olap) children();
parent = first_defined([parent,from]);
anchors = is_vector(parent) || is_string(parent) ? [parent] : parent;
align_list = is_undef(align) ? [undef]
: is_vector(align) || is_string(align) ? [align] : align;
dummy4 = assert(is_string(parent) || is_list(parent), "Invalid parent anchor or anchor list")
assert(spin==0 || (!two_d || is_undef(child)), "spin is not allowed for 2d objects when 'child' is given");
child_temp = first_defined([child,to]);
child = two_d ? _force_anchor_2d(child_temp) : child_temp;
dummy2=assert(align_list==[undef] || is_def(child), "Cannot use 'align' without 'child'")
assert(!inside || is_def(child), "Cannot use 'inside' without 'child'")
assert(inset==0 || is_def(child), "Cannot specify 'inset' without 'child'")
assert(shiftout==0 || is_def(child), "Cannot specify 'shiftout' without 'child'");
factor = inside?-1:1;
$attach_to = child;
for (anch_ind = idx(anchors)) {
dummy=assert(is_string(anchors[anch_ind]) || (is_vector(anchors[anch_ind]) && (len(anchors[anch_ind])==2 || len(anchors[anch_ind])==3)),
str("parent[",anch_ind,"] is ",anchors[anch_ind]," but it must be a named anchor (string) or a 2-vector or 3-vector"))
assert(align_list==[undef] || !is_string(anchors[anch_ind]),
str("parent[",anch_ind,"] is a named anchor (",anchors[anch_ind],"), but named anchors are not supported with align="));
anchor = is_string(anchors[anch_ind])? anchors[anch_ind]
: two_d?_force_anchor_2d(anchors[anch_ind])
: point3d(anchors[anch_ind]);
anchor_data = _find_anchor(anchor, $parent_geom);
anchor_pos = anchor_data[1];
anchor_dir = factor*anchor_data[2];
anchor_spin = !inside || anchor_data[3]==0 ? anchor_data[3] : 180+anchor_data[3];
$anchor=anchor;
for(align_ind = idx(align_list)){
align = is_undef(align_list[align_ind]) ? undef
: assert(is_vector(align_list[align_ind],2) || is_vector(align_list[align_ind],3), "align direction must be a 2-vector or 3-vector")
two_d ? _force_anchor_2d(align_list[align_ind])
: point3d(align_list[align_ind]);
$idx = align_ind+len(align_list)*anch_ind;
$align=align;
dummy=assert(is_undef(align) || all_zero(v_mul(anchor,align)),
str("Invalid alignment: align value (",align,") includes component parallel to parent anchor (",anchor,")"));
pos = is_undef(align) ? anchor_data[1] : _find_anchor(anchor+align, $parent_geom)[1];
$attach_anchor = list_set(anchor_data, 1, pos); ///
startdir = two_d || is_undef(align)? undef
: anchor==UP || anchor==DOWN ? BACK
: UP - (anchor*UP)*anchor/(anchor*anchor);
enddir = is_undef(child) || child.z==0 ? UP : BACK;
child_adjustment = is_undef(align)? CTR
: two_d ? rot(to=child,from=-factor*anchor,p=align)
: apply( frame_map(x=child, z=enddir)
*frame_map(x=-factor*anchor, z=startdir, reverse=true)
*rot(v=anchor,-spin), align);
$anchor_override = all_zero(child_adjustment)? inside?child:undef
: child+child_adjustment;
reference = two_d? BACK : UP;
inset_dir = is_undef(align) ? CTR
: two_d ? rot(to=reference, from=anchor,p=align)
: apply(zrot(-factor*spin)*frame_map(x=reference, z=BACK)*frame_map(x=factor*anchor, z=startdir, reverse=true),
align);
spinaxis = two_d? UP : anchor_dir;
olap = - overlap * reference - inset*inset_dir + shiftout * (inset_dir + factor*reference);
if (norot || (approx(anchor_dir,reference) && anchor_spin==0)) {
translate(pos) rot(v=spinaxis,a=factor*spin) translate(olap) default_tag("remove",inside) children();
} else {
translate(pos)
rot(v=spinaxis,a=factor*spin)
rot(anchor_spin,from=reference,to=anchor_dir){
translate(olap)
default_tag("remove",inside) children();}}
}
}
}
// Section: Tagging
// Module: tag()
@ -1607,7 +1835,6 @@ module edge_mask(edges=EDGES_ALL, except=[]) {
anch = _find_anchor(vec, $parent_geom);
$attach_to = undef;
$attach_anchor = anch;
$attach_norot = true;
rotang =
vec.z<0? [90,0,180+v_theta(vec)] :
vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) :
@ -1659,7 +1886,6 @@ module corner_mask(corners=CORNERS_ALL, except=[]) {
anch = _find_anchor(vec, $parent_geom);
$attach_to = undef;
$attach_anchor = anch;
$attach_norot = true;
rotang = vec.z<0?
[ 0,0,180+v_theta(vec)-45] :
[180,0,-90+v_theta(vec)-45];
@ -1781,7 +2007,6 @@ module edge_profile(edges=EDGES_ALL, except=[], excess=0.01, convexity=10) {
post_T = path_angs_T[2];
$attach_to = undef;
$attach_anchor = anch;
$attach_norot = true;
$profile_type = "edge";
multmatrix(post_T) {
for (i = idx(path,e=-2)) {
@ -2087,7 +2312,6 @@ module edge_profile_asym(
mirT = _corner_orientation(pos, pvec);
$attach_to = undef;
$attach_anchor = _find_anchor(pos, $parent_geom);
$attach_norot = true;
$profile_type = "corner";
position(pos) {
multmatrix(mirT) {
@ -2144,7 +2368,6 @@ module edge_profile_asym(
for (i = idx(edge_string)) {
$attach_to = undef;
$attach_anchor = _find_anchor(edge_string[i], $parent_geom);
$attach_norot = true;
$profile_type = "edge";
edge_profile(edge_string[i], excess=excess, convexity=convexity) {
if (flipverts[i]) {
@ -2203,7 +2426,6 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
anch = _find_anchor(vec, $parent_geom);
$attach_to = undef;
$attach_anchor = anch;
$attach_norot = true;
$profile_type = "corner";
rotang = vec.z<0?
[ 0,0,180+v_theta(vec)-45] :
@ -2592,7 +2814,7 @@ 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 = first_defined([$anchor_override, anchor, CENTER]);
anchor = first_defined([anchor, CENTER]);
spin = default(spin, 0);
orient = is_def($anchor_override)? UP : default(orient, UP);
region = !is_undef(region)? region :
@ -2616,6 +2838,7 @@ module attachable(
$parent_size = _attach_geom_size(geom);
$attach_to = undef;
$anchor_override=undef;
$attach_alignment=undef;
if (expose_tags || _is_shown()){
if (!keep_color)
_color($color) children(0);
@ -2754,7 +2977,9 @@ function reorient(
cp=cp, offset=offset, anchors=anchors,
two_d=two_d, axis=axis, override=override
),
$attach_to = undef
$attach_to = undef,
$anchor_override= undef,
$attach_alignment = undef
) _attach_transform(anchor,spin,orient,geom,p);
@ -3226,6 +3451,7 @@ function _attach_geom_edge_path(geom, edge) =
/// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
/// geom = The geometry description of the shape.
/// p = If given as a VNF, path, or point, applies the affine3d transformation matrix to it and returns the result.
function _attach_transform(anchor, spin, orient, geom, p) =
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))
@ -3238,12 +3464,13 @@ function _attach_transform(anchor, spin, orient, geom, p) =
m = ($attach_to != undef)? (
let(
anch = _find_anchor($attach_to, geom),
pos = anch[1]
pos = is_undef($anchor_override) ? anch[1]
: _find_anchor(_make_anchor_legal($anchor_override,geom),geom)[1]
)
two_d?
assert(is_num(spin))
affine3d_zrot(spin)
* rot(to=FWD, from=point3d(anch[2]))
/*affine3d_zrot(spin) * */
rot(to=FWD, from=point3d(anch[2]))
* affine3d_translate(point3d(-pos))
:
assert(is_num(spin) || is_vector(spin,3))
@ -3260,6 +3487,9 @@ function _attach_transform(anchor, spin, orient, geom, p) =
* affine3d_translate(point3d(-pos))
) : (
let(
anchor = is_undef($attach_alignment) ? anchor
: two_d? _make_anchor_legal(zrot(-spin,$attach_alignment),geom)
: _make_anchor_legal(rot(spin, from=UP,to=orient,reverse=true,p=$attach_alignment),geom),
pos = _find_anchor(anchor, geom)[1]
)
two_d?
@ -3321,6 +3551,7 @@ function _get_cp(geom) =
function _force_anchor_2d(anchor) =
is_undef(anchor) || len(anchor)==2 ? anchor :
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.")
anchor.y==0 ? [anchor.x,anchor.z] : point2d(anchor);

View file

@ -2935,7 +2935,7 @@ function onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP) =
// Usage:
// text3d(text, [h], [size], [font], [language=], [script=], [direction=], [atype=], [anchor=], [spin=], [orient=]);
// Description:
// Creates a 3D text block that supports anchoring and attachment to attachable objects. You cannot attach children to text.
// Creates a 3D text block that supports anchoring and single-parameter attachment to attachable objects. You cannot attach children to text.
// .
// Historically fonts were specified by their "body size", the height of the metal body
// on which the glyphs were cast. This means the size was an upper bound on the size
@ -2988,6 +2988,7 @@ module text3d(text, h, size=10, font="Helvetica", spacing=1.0, direction="ltr",
h = one_defined([h,height,thickness],"h,height,thickness",dflt=1);
assert(is_undef(atype) || in_list(atype,["ycenter","baseline"]), "atype must be \"ycenter\" or \"baseline\"");
assert(is_bool(center));
assert(is_undef($attach_to),"text3d() does not support parent-child anchor attachment with two parameters");
atype = default(atype, center?"ycenter":"baseline");
anchor = default(anchor, center?CENTER:LEFT);
geom = attach_geom(size=[size,size,h]);

View file

@ -7,6 +7,7 @@
assert(version_num()>=20190500, "BOSL2 requires OpenSCAD version 2019.05 or later.");
include <version.scad>
include <constants.scad>

View file

@ -389,7 +389,7 @@ square(10)
When positioning an object near an edge or corner you may wish to
orient the object relative to some face other than the TOP face that
meets at that edge or corner. You can always apply a `rotation()` to
meets at that edge or corner. You can always apply `rot()` to
change the orientation of the child object, but in order to do this,
you need to figure out the correct rotation. The `orient()` module provides a
mechanism for re-orienting the child() that eases this burden:
@ -470,9 +470,9 @@ prismoid([50,50],[30,30],h=40)
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.
using the align() module. This module positions children on faces
of a parent and aligns to edges or corners, while picking the correct anchor points on
the children so that the children line up correctly with the parent.
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:
@ -501,7 +501,7 @@ with position():
```openscad-3D
include<BOSL2/std.scad>
cuboid([50,40,15])
align(RIGHT+FRONT+TOP)
align(TOP,RIGHT+FRONT)
color("lightblue")prismoid([10,5],[7,4],height=4);
```
@ -514,60 +514,88 @@ single call to position(), but easily done using align():
```openscad-3D
include<BOSL2/std.scad>
cuboid([50,40,15])
align([RIGHT+TOP,LEFT+TOP])
align(TOP,[RIGHT,LEFT])
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:
If you want the children close to the edge but not actually flush you
can use the `inset=` parameter of align to achieve this:
```openscad-3D
include<BOSL2/std.scad>
cuboid([50,40,15])
align(RIGHT+TOP,spin=90)
align(TOP,[FWD,RIGHT,LEFT,BACK],inset=3)
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.
If you spin the children then align will still do the right thing
```openscad-3D
include<BOSL2/std.scad>
cuboid([50,40,15])
align(RIGHT+TOP)
align(TOP,[RIGHT,LEFT])
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:
If you orient the object DOWN it will be attached from its top anchor,
correctly aligned.
```openscad-3D
include<BOSL2/std.scad>
cuboid([50,40,15])
align(RIGHT+TOP,DOWN)
color("lightblue")prismoid([10,5],[7,4],height=4);
align(TOP,RIGHT)
color("lightblue")prismoid([10,5],[7,4],height=4,orient=DOWN);
```
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:
Note that align() never changes the orientation of the children. If
you put the blue prismoid on the right side the anchors line up but
the edges of the child and parent don't.
```openscad-3D
include<BOSL2/std.scad>
prismoid(50,30,25){
align(RIGHT+TOP,RIGHT,spin=0)
align(RIGHT,TOP)
color("lightblue")prismoid([10,5],[7,4],height=4);
align(RIGHT+BOT,RIGHT)
color("green")prismoid([10,5],[7,4],height=4);
}
```
If you apply spin that is not a multiple of 90 degrees then alignment
will line up the corner
```openscad-3D
include<BOSL2/std.scad>
cuboid([50,40,15])
align(TOP,RIGHT)
color("lightblue")cuboid(8,spin=33);
```
You can also attach objects to a cylinder. If you use the usual cubic
anchors then a cube will attach on a face as shown here:
```openscad-3D
include<BOSL2/std.scad>
cyl(h=20,d=10,$fn=128)
align(RIGHT,TOP)
color("lightblue")cuboid(5);
```
But with a cylinder you can choose an arbitrary horizontal angle for
the anchor. If you do this, similar to the case of arbitrary spin,
the cube will attach on the nearest corner.
```openscad-3D
include<BOSL2/std.scad>
cyl(h=20,d=10,$fn=128)
align([1,.3],TOP)
color("lightblue")cuboid(5);
```
## Attachment overview
Attachables get their name from their ability to be attached to each
other. Unlike with positioning, attaching changes the orientation of
the child object. When you attach an object, it appears on the parent
the child object. Think of it like sticking two objects together:
when you attach an object, it appears on the parent
relative to the local coordinate system of the parent at the anchor point. To understand
what this means, imagine the perspective of an ant walking on a
sphere. The meaning of UP varies depending on where on the sphere the
@ -656,137 +684,54 @@ cylinder(h=100, d=100, center=true)
show_anchors(s=30);
```
## Basic Attachment
## Parent-Child Anchor Attachment (Double Argument Attachment)
The simplest form of attachment is to attach using the `attach()`
module with a single argument, which specifies the anchor on the parent
where the child will attach. This will attach the bottom of the child
to the given anchor point on the parent. The child appears on the parent with its
Z direction aligned parallel to the parent's anchor direction, and
its Y direction pointing in the zero spin direction for the
parent anchor. The anchor direction of the child does not affect the result in this
case.
The `attach()` module has two different modes of operation,
parent-child anchor attachment and parent anchor attachment. These
are also called double argument attachment and single argument
attachment. The parent-child anchor attachment, with two arguments,
is usually easier to use and is more powerful because it supports
alignment. When you use parent-child anchor attachment you give a
parent anchor and a child anchor. Imagine pointing the anchor arrows
on the two objects directly at each other and pushing them together in
the direction of the arrows until they touch. In many of the examples
below we show first the two objects with their anchor arrows and then
the result of the attach operation using those anchors.
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true)
attach(RIGHT)cylinder(d1=30,d2=15,h=25);
```
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true)
attach(RIGHT+TOP)cylinder(d1=30,d2=15,h=25);
```
In the second example, the child object points diagonally away
from the cube. If you want the child at at edge of the parent it's
likely that this result will not be what you want. To get a different
result, use `position()` with `orient()`, if needed.
If you give an anchor point to the child object it moves the child
around (in the attached coordinate system). Or alternatively you can
think that it moves the object first, and then it gets attached.
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true)
attach(RIGHT)cylinder(d1=30,d2=15,h=25,anchor=FRONT);
```
In the above example we anchor the child to its FRONT and then attach
it to the RIGHT. An ambiguity exists regarding the spin of the
parent's coordinate system. How is this resolved? The small flags
on the anchor arrows show the position of zero spin by pointing
towards the local Y+ direction, which is also the BACK direction of the child. For the above
cube, the arrow looks like this:
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true)
attach(RIGHT)anchor_arrow(30);
```
The red flag points up, which explains why the attached cylinder
appeared above the anchor point. The CENTER anchor generally has a
direction that points upward, so an attached object will keep its
orientation if attached to the CENTER of a parent.
By default, `attach()` places the child exactly flush with the surface of the parent. Sometimes
it's useful to have the child overlap the parent by insetting a bit. You can do this with the
`overlap=` argument to `attach()`. A positive value will inset the child into the parent, and
a negative value will outset out from the parent, which may be helpful
when doing differences.
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true)
attach(TOP,overlap=10)
cylinder(d=20,h=20);
```
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true)
attach(TOP,overlap=-20)
cylinder(d=20,h=20);
```
As with `position()`, you can still apply your own translations and
other transformations even after attaching an object. However, the
order of operations now matters. If you apply a translation outside
of the anchor then it acts in the parent's global coordinate system, so the
child moves up in this example:
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true)
up(13)
attach(RIGHT)
cylinder(d1=30,d2=15,h=25);
```
On the other hand, if you put the translation between the attach and
the object in your code, then it will act in the local coordinate system of
the parent at the parent's anchor, so in the example below it moves to the right.
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true)
attach(RIGHT)
up(13)
cylinder(d1=30,d2=15,h=25);
```
## Attachment With Parent and Child Anchors
The `attach()` module can also take a second argument, the child anchor.
In this case, the attachment behavior
is quite different. The objects are still attached with their anchor
points aligned, but the child is reoriented so that its anchor
direction is the opposite of the parent anchor direction. It's like
you assemble the parts by pushing them together in the direction of
their anchor arrows. Two examples appear below, where first we show
two objects with their anchors and then we show the result of
attaching with those anchors.
```openscad-3D
include <BOSL2/std.scad>
cube(50,anchor=BOT) attach(TOP) anchor_arrow(30);
right(60)cylinder(d1=30,d2=15,h=25) attach(TOP) anchor_arrow(30);
cube(50,anchor=BOT) attach(TOP,BOT) anchor_arrow(30);
right(60)cylinder(d1=30,d2=15,h=25) attach(BOT,BOT) anchor_arrow(30);
```
```openscad-3D
include <BOSL2/std.scad>
cube(50,anchor=BOT)
attach(TOP,TOP) cylinder(d1=30,d2=15,h=25);
attach(TOP,BOT) cylinder(d1=30,d2=15,h=25);
```
This example produces the same result as using `align()`, but if the
parent anchor is not horizontal, then the child is reoriented:
```openscad-3D
include <BOSL2/std.scad>
prismoid([50,50],[35,35],h=50,anchor=BOT) attach(RIGHT,BOT) anchor_arrow(30);
right(60)cylinder(d1=30,d2=15,h=25) attach(BOT,BOT) anchor_arrow(30);
```
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true) attach(RIGHT) anchor_arrow(30);
right(80)cylinder(d1=30,d2=15,h=25) attach(LEFT) anchor_arrow(30);
prismoid([50,50],[35,35],h=50,anchor=BOT)
attach(RIGHT,BOT) cylinder(d1=30,d2=15,h=25);
```
In this case we attach the curved side of the cone to a cube by lining
up the anchor arrows:
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true) attach(RIGHT,BOT) anchor_arrow(30);
right(80)cylinder(d1=30,d2=15,h=25) attach(LEFT,BOT) anchor_arrow(30);
```
```openscad-3D
@ -795,16 +740,303 @@ cube(50,center=true)
attach(RIGHT,LEFT) cylinder(d1=30,d2=15,h=25);
```
Note that when you attach with two anchors like this, the attachment
operation **overrides any anchor or orientation specified in the
child**. That means the child's `anchor=` and `orient=` options are
ignored.
Note that this form of attachent overrides any anchor or orientation
specified in the child: **with parent-child anchor attachment the
`anchor=` and `orient=` parameters to the child are ignored.**
When you specify attachment using a pair of anchors, the attached
child can spin around the parent anchor while still being attached.
As noted earlier, this ambiguity is resolved by anchors having a
defined spin which specifies where the Y+ axis is located.
The way that BOSL2 positions objects can be understood by viewing the
anchor arrows as shown above, or you can remember these rules:
1. When attaching to the TOP or BOTTOM the FRONT of the child points to the front if possible; otherwise the TOP of the child points BACK.
2. When attaching to other faces, if possible the child's UP anchor will point UP; otherwise, the BACK of the child points up (so the FRONT is pointed down).
To show how this works we use this prismoid where the blue arrow is
pointing to the front and the green arrow points up. Also note that
the front left edge is the only right angle.
```openscad-3D
include <BOSL2/std.scad>
color_this("orange")
prismoid([8,8],[6,6],shift=-[1,1],h=8) {
attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
attach(FWD,BOT) anchor_arrow(s=12);
}
```
If we attach this to the TOP by the LEFT side then we get the result
below. Notice how the green UP arrow is pointing back.
```openscad-3D
include <BOSL2/std.scad>
cube(30) attach(TOP,LEFT)
color_this("orange")
prismoid([8,8],[6,6],shift=-[1,1],h=8) {
attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
attach(FWD,BOT) anchor_arrow(s=12);
}
```
If we attach to the RIGHT using the same LEFT side anchor on the
prismoid then we get the result below. Note that the green UP anchor
is pointing (approximately) UP, in accordance with rule 2 from above.
```openscad-3D
include <BOSL2/std.scad>
cube(30) attach(TOP,LEFT)
color_this("orange")
prismoid([8,8],[6,6],shift=-[1,1],h=8) {
attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
attach(FWD,BOT) anchor_arrow(s=12);
}
```
The green UP arrow can always be arranged to point up unless we attach
either the top or bottom to one of the cube's vertical faces. Here we
attach the bottom so you can still see both arrows. The blue FRONT
arrow on the object is pointing down, as expected based on rule 2.
```openscad-3D
include <BOSL2/std.scad>
cube(30) attach(RIGHT,BOT)
color_this("orange")
prismoid([8,8],[6,6],shift=-[1,1],h=8) {
attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
attach(FWD,BOT) anchor_arrow(s=12);
}
```
What do you do if the direction the child appears is not the direction
you need? To address this issue `attach()` provides a `spin=`
parameter which spins the attached child around the axis defined by
the joined anchor vectors. Here is the last example with a rotation
applied to bring the front anchor back to the front:
```openscad-3D
include <BOSL2/std.scad>
cube(30) attach(RIGHT,BOT,spin=-90)
color_this("orange")
prismoid([8,8],[6,6],shift=-[1,1],h=8) {
attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
attach(FWD,BOT) anchor_arrow(s=12);
}
```
Be aware that specifying `spin=` to `attach()` is not equivalent to
using the `spin=` argument to the child. Unlike `orient=` and
`anchor=`, which are ignored, the child `spin=` argument is still
respected, but it may be difficult to figure out which axis it will
rotate on. It is more intuitive to ignore the child spin parameter
and only use the spin parameter to `attach()`. The spin must be
scalar but need not be a multiple of 90 degrees.
```openscad-3D
include <BOSL2/std.scad>
cube(30) attach(RIGHT,BOT,spin=-37)
color_this("orange")
prismoid([8,8],[6,6],shift=-[1,1],h=8) {
attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
attach(FWD,BOT) anchor_arrow(s=12);
}
```
By default, `attach()` places the child exactly flush with the surface
of the parent. Sometimes it's useful to have the child overlap the
parent by translating it into the parent. You can do this with the
`overlap=` argument to `attach()`. A positive value will cause the
child to overlap the parent, and a negative value will move the child
away from the parent, leaving a small gap, which may be helpful when
doing differences. In the first example we use a very large value of
overlap so the cube is sunk deeply into the parent. In the second
example a large negative overlap value raises the child high above the
parent.
```openscad-3D
include <BOSL2/std.scad>
cuboid(50)
attach(TOP,BOT,overlap=15)
color("green")cuboid(20);
```
```openscad-3D
include <BOSL2/std.scad>
cube(50,center=true)
attach(TOP,BOT,overlap=-20)
cyl(d=20,h=20);
```
Another feature provided by the double argument form of `attach()` is
alignment, which works in a similar way to `align()`. You can specify
`align=` to align the attached child to an edge or corner. The
example below shows five different alignments.
```openscad-3D
include <BOSL2/std.scad>
module thing(){
color_this("orange")
prismoid([8,8],[6,6],shift=-[1,1],h=8) {
attach(TOP,BOT) anchor_arrow(color=[0,1,0],s=12);
attach(FWD,BOT) anchor_arrow(s=12);
}
}
prismoid([50,50],[35,35],h=25,anchor=BOT){
attach(TOP,BOT,align=FRONT) thing();
attach(RIGHT,BOT,align=BOT) thing();
attach(RIGHT,BACK,align=FRONT) thing();
attach(FRONT,BACK,align=BOT,spin=45) thing();
attach(TOP,RIGHT,align=RIGHT,spin=90) thing();
}
```
As with `align()` if you turn an object 90 degrees it can match up
with parallel edges, but if you turn it an arbitrary angle, a corner
of the child will contact the edge of the parent. Also like align()
the anchor points of the parent and child are aligned but this does
not necessarily mean that edges line up neatly when the shapes have
varying angles. This misalignment is visible in the object attached
at the RIGHT and aligned to the FRONT.
You may be wondering why all this fuss with align is necessary.
Couldn't you just attach an object at an anchor on an edge? When you
do this, the object will be attached using the edge anchor, which is
not perpendicular to the faces of the object. The example below shows
attachment to an edge anchor and also a corner anchor.
```openscad-3D
include <BOSL2/std.scad>
cube(30)
color("orange"){
attach(RIGHT+FRONT,BOT)
prismoid([8,8],[6,6],shift=-[1,1],h=8);
attach(TOP+LEFT+FWD,BOT)
prismoid([8,8],[6,6],shift=-[1,1],h=8);
}
```
When using the `align` option to `attach()` you can also set `inset`,
which works the same way as the `inset` parameter to `align()`. It
shifts the child away from the edge or edges where it is aligned by
the specified amount.
```openscad-3D
include <BOSL2/std.scad>
prismoid([50,50],[50,25],25){
attach(FWD,BOT,align=TOP,inset=3) color("lavender")cuboid(5);
attach(FWD,BOT,align=BOT+RIGHT,inset=3) color("purple")cuboid(5);
}
```
The last capability provided by `attach()` is to attach the child
**inside** the parent object. This is useful if you want to subtract
the child from the parent. Doing this requires using tagged
operations with `diff()` which is explained in more detail below.
For the examples here, note that the `diff()` and `tag()` operations
that appear cause the child to be subtracted. We return to the
example that started this section, with anchor arrows shown on the two
objects.
```openscad-3D
include <BOSL2/std.scad>
cube(50,anchor=BOT) attach(TOP) anchor_arrow(30);
right(60)cylinder(d1=30,d2=15,h=25) attach(TOP) anchor_arrow(30);
```
Inside attachment is activated using `inside=true` and it lines up the
anchor arrows so they point together the **same** direction instead of
opposite directions like regular outside attachment. The result in
this case is appears below, where we have cut away the front half to
show the interior:
```openscad-3D
include <BOSL2/std.scad>
back_half(s=200)
diff()
cube(50,anchor=BOT)
attach(TOP,TOP,inside=true)
cylinder(d1=30,d2=15,h=25);
```
The top of the cavity has a thin layer on it, which occurs because the
two objects share a face in the difference. To fix this you can use
the `shiftout` parameter to `attach()`. In this case you could also
use a negative `overlay` value, but the `shiftout` parameter shifts
out in every direction that is needed, which may be three directions
if you align the child at a corner. The above example looks like this
with with the shift added:
```openscad-3D
include <BOSL2/std.scad>
back_half(s=200)
diff()
cube(50,anchor=BOT)
attach(TOP,TOP,inside=true,shiftout=0.01)
cylinder(d1=30,d2=15,h=25);
```
Here is an example of connecting the same object on the right, but
this time with the BOTTOM anchor. Note how the BOTTOM anchor is
aligned to the RIGHT so it is parallel and pointing in the same
direction as the RIGHT anchor.
```openscad-3D
include <BOSL2/std.scad>
back_half(s=200)
diff()
cuboid(50)
attach(RIGHT,BOT,inside=true,shiftout=0.01)
cylinder(d1=30,d2=15,h=25);
```
Here is an example where alignment moves the object into the corner,
and we benefit from shiftout providing 3 dimensions of adjustment:
```openscad-3D
include <BOSL2/std.scad>
diff()
cuboid(10)
attach(TOP,TOP,align=RIGHT+FWD,inside=true,shiftout=.01)
cuboid([2,5,9]);
```
As with `position()`, with any use of `attach()` you can still apply your own translations and
other transformations even after attaching an object. However, the
order of operations now matters. If you apply a translation outside
of the anchor then it acts in the parent's global coordinate system, so the
child moves up in this example, where the light gray shows the
untranslated object.
```openscad-3D
include <BOSL2/std.scad>
cuboid(50){
%attach(RIGHT,BOT)
cyl(d1=30,d2=15,h=25);
up(13)
color("green") attach(RIGHT,BOT)
cyl(d1=30,d2=15,h=25);
}
```
On the other hand, if you put the translation between the attach and
the object in your code, then it will act in the local coordinate system of
the parent at the parent's anchor, so in the example below it moves to the right.
```openscad-3D
include <BOSL2/std.scad>
cuboid(50){
%attach(RIGHT,BOT)
cyl(d1=30,d2=15,h=25);
color("green") attach(RIGHT,BOT)
up(13)
cyl(d1=30,d2=15,h=25);
}
```
Attachment with CENTER anchors can be surprising because the anchors
point upwards, so in the example below, the child's CENTER anchor
points up, so it is inverted when it is attached to the parent cone.
Note that the anchors are CENTER anchors, so the bases of the anchors are
hidden in the middle of the objects.
hidden in the middle of the objects.
```openscad-3D
include <BOSL2/std.scad>
@ -819,6 +1051,96 @@ cylinder(d1=30,d2=15,h=25)
cylinder(d1=30,d2=15,h=25);
```
## Parent Anchor Attachment (Single Argument Attachment)
The second form of attachment is parent anchor attachment, which just
uses a single argument. This form of attachment is less useful in
general and does not provide alignment. When you give `attach()` a parent anchor but no child anchor it
orients the child according to the pafrent anchor direction but then
simply places the child based on its internally defined anchor at the
parent anchor position. For most objects the default anchor is the
CENTER anchor, so objects will appear sunk half-way into the parent.
```openscad-3D
include <BOSL2/std.scad>
cuboid(30)
attach(TOP)
color("green")cuboid(10);
```
Some objects such as `cylinder()`, `prismoid()`, and `anchor_arrow()` have default anchors on the bottom, so they will appear
on the surface. For objects like this you can save a little bit of
typing by using parent anchor attachment. But in the case of `cube()`
the anchor is not centered, so the result is:
```openscad-3D
include <BOSL2/std.scad>
cube(30)
attach(TOP)
color("green")cube(10);
```
In order to make single argument attachment produce the results you
need you will probably need to change the child anchor. Note that unlike
parent-child anchor attachment, **with parent anchor attachment the `anchor=` and `orient=` arguments
are respected.** We could therefore place a cuboid like this:
```openscad-3D
include <BOSL2/std.scad>
cuboid(30)
attach(RIGHT)
color("green")cuboid(10,anchor=BOT);
```
If you need to place a cuboid at the anchor point but need it anchored
relative to one of the bottom edge or corner anchors then you can do
that with parent anchor attachment:
```openscad-3D
include <BOSL2/std.scad>
cuboid(30)
attach(RIGHT)
color("green")cuboid(10,anchor=BOT+FWD);
```
Another case where single argument attachment is useful is when the
child doesn't have proper attachment support.
If you use double argument attachment in such cases the results will
be incorrect because the child doesn't properly respond to the
internally propagated anchor directives. With single argument
attachment, this is not a problem: the origin
of the child will be placed at the parent anchor point. One module
without attachment support is `linear_extrude()`.
```openscad-3D
include <BOSL2/std.scad>
cuboid(20)
attach(RIGHT)
color("red")linear_extrude(height=2) star(n=7,ir=3,or=7);
```
As noted earlier, you can set `orient=` for children with parent
anchor attachment, though the behavior may not be intuitive because
the attachment process transforms the coordinate system and the
orientation is done in the attached coordinate system. It may be
helpful to start with the object attached to TOP and recall the rules
from the previous section about how orientation works. The same rules
apply here. Note that the forward arrow is pointing down after
attaching the object on the RIGHT face.
```openscad-3D
include <BOSL2/std.scad>
cuboid(20){
attach(RIGHT)
color_this("red")cuboid([2,4,8],orient=RIGHT,anchor=RIGHT)
attach(FWD) anchor_arrow();
attach(TOP)
color_this("red")cuboid([2,4,8],orient=RIGHT,anchor=RIGHT)
attach(FWD) anchor_arrow();
}
```
## Positioning and Attaching Multiple Children
@ -850,25 +1172,34 @@ cube(50, center=true)
## Attaching 2D Children
You can use attachments in 2D as well. As usual for the 2D case you
can use TOP and BOTTOM as alternative to BACK and FORWARD.
can use TOP and BOTTOM as alternative to BACK and FORWARD. With
parent-child anchor attachment you cannot use the spin parameter to
`attach()` nor can you specify spin to the child. Spinning the child
on the Z axis would rotate the anchor arrows out of alignment.
```openscad-2D
include <BOSL2/std.scad>
square(50,center=true)
rect(50){
attach(RIGHT,FRONT)
trapezoid(w1=30,w2=0,h=30);
color("red")trapezoid(w1=30,w2=0,h=30);
attach(LEFT,FRONT,align=[FRONT,BACK],inset=3)
color("green") trapezoid(w1=25, w2=0,h=30);
}
```
```openscad-2D
include <BOSL2/std.scad>
circle(d=50)
diff()
circle(d=50){
attach(TOP,BOT,overlap=5)
trapezoid(w1=30,w2=0,h=30);
attach(BOT,BOT,inside=true)
tag("remove")
trapezoid(w1=30,w2=0,h=30);
}
```
## Tagged Operations
BOSL2 introduces the concept of tags. Tags are names that can be given to attachables, so that
you can refer to them when performing `diff()`, `intersect()`, and `conv_hull()` operations.