diff --git a/attachments.scad b/attachments.scad index b20e7bf..bd9fd53 100644 --- a/attachments.scad +++ b/attachments.scad @@ -708,11 +708,19 @@ module align(anchor,align=CENTER,inside=false,inset=0,shiftout=0,overlap) 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); - $pos = pos_anch[1]; $attach_alignment = thisedge-factor*thisface; $attach_anchor=list_set(pos_anch,2,UP); - translate(pos_anch[1]-inset*thisedge+shiftout*(thisedge-factor*thisface)-overlap*thisface) + translate(pos_anch[1] + +inset*inset_dir + +shiftout*(thisface_anch[2]-inset_dir) + -overlap*thisface_anch[2]) default_tag("remove",inside) children(); } } @@ -724,7 +732,7 @@ 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","trapzeoid"]) ? [for(v=anchor) _quant_anch(v)] - : in_list(geom[0], ["conoid", "extrusion_extent"]) ? [$anchor.x,anchor.y, _quant_anch(anchor.z)] + : in_list(geom[0], ["conoid", "extrusion_extent"]) ? [anchor.x,anchor.y, _quant_anch(anchor.z)] : anchor; @@ -735,18 +743,19 @@ function _make_anchor_legal(anchor,geom) = // Topics: Attachments // See Also: attachable(), position(), align(), face_profile(), edge_profile(), corner_profile() // Usage: -// PARENT() attach(parent, child, [align=], [spin=], [overlap=]) CHILDREN; +// PARENT() attach(parent, child, [align=], [spin=], [overlap=], [inside=]) CHILDREN; // PARENT() attach(parent, [overlap=], [spin=]) CHILDREN; // Description: // Attaches children to a parent object at an anchor point or points, oriented in the anchor direction. // This module differs from {{position()}} and {{align()}} in that it rotates the children to // the anchor direction, which generally means it places the children on the surface of a parent. -// There are two modes of operation, single argument and double argument. +// There are two modes of operation, parent anchor (single argument) and parent-child anchor (double argument). // . -// The 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 connects the `child` anchor on the child to the `parent` anchor on the parent. -// They are connected to the parent by pointing their anchor arrows at each other. The most basic case +// 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. @@ -765,7 +774,11 @@ function _make_anchor_legal(anchor,geom) = // 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. +// child but using the `spin=` parameter to `attach()` is more likely to be useful. +// . +// 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. // . // 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 @@ -803,7 +816,7 @@ function _make_anchor_legal(anchor,geom) = // attach(FRONT, BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5); // } -module attach(parent, child, overlap, align, spin=0, norot, from, to) +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") @@ -820,15 +833,20 @@ module attach(parent, child, overlap, align, spin=0, norot, from, to) assert(is_undef(align) || (is_vector(align) && (len(align)==2 || len(align)==3)), "align must be a 2-vector or 3-vector") 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="); + overlap = (overlap!=undef)? overlap : $overlap; anchors = (is_vector(parent)||is_string(parent))? [parent] : parent; two_d = _attach_geom_2d($parent_geom); - parent = one_defined([parent,from],"parent,from"); + parent = first_defined([parent,from]); dummy4 = assert(is_string(parent) || is_list(parent), "Invalid parent anchor or anchor list"); - child = two_d ? _force_anchor_2d(child) : child; + child_temp = first_defined([child,to]); + child = two_d ? _force_anchor_2d(child_temp) : child_temp; align = is_undef(align) ? undef : two_d ? _force_anchor_2d(align) : point3d(align); - dummy2=assert(is_undef(align) || is_def(child), "Cannot use 'align' without 'child'"); + dummy2=assert(is_undef(align) || 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'"); for ($idx = idx(anchors)) { dummy2= assert(is_string(anchors[$idx]) || (is_vector(anchors[$idx]) && (len(anchors[$idx])==2 || len(anchors[$idx])==3)), @@ -842,30 +860,32 @@ module attach(parent, child, overlap, align, spin=0, norot, from, to) str("align (",align,") cannot include component parallel to parent anchor (",anchr,")")); anch = _find_anchor(anchr, $parent_geom); pos = is_undef(align) ? anch[1] : _find_anchor(anchr+align, $parent_geom)[1]; - $attach_to = child; + factor = inside?-1:1; + $attach_to = u_mul(factor,child); $attach_anchor = list_set(anch, 1, pos); /// startdir = anchr==UP || anchr==DOWN ? BACK : UP; enddir = is_undef(child) || child.z==0 ? UP : BACK; anchor_adjustment = is_undef(align)? CTR - : two_d ? zrot(spin, rot(to=child,from=-anchr,p=align)) - : apply( frame_map(x=child, z=enddir) + : two_d ? zrot(spin, rot(to=factor*child,from=-anchr,p=align)) + : apply( frame_map(x=factor*child, z=enddir) *frame_map(x=-anchr, z=startdir, reverse=true) *rot(v=parent,-spin), align); - - $anchor_override=all_zero(anchor_adjustment)?undef + $anchor_override=all_zero(anchor_adjustment)? inside?child:undef :child+anchor_adjustment; - olap = two_d? [0,-overlap,0] : [0,0,-overlap]; - anchrvec = two_d? BACK : UP; + reference = two_d? BACK : UP; + offsetdir = is_undef(align) ? CTR + : apply(zrot(-spin)*frame_map(x=reference, z=BACK)*frame_map(x=anchr, z=startdir, reverse=true), + align); spinaxis = two_d? UP : anch[2]; - if (norot || (approx(anch[2],anchrvec) && anch[3]==0)) { - translate(pos) rot(v=spinaxis,a=spin) translate(olap) children(); + olap = - overlap * reference - inset*offsetdir - shiftout * (-offsetdir - reference); + if (norot || (approx(anch[2],reference) && anch[3]==0)) { + translate(pos) rot(v=spinaxis,a=spin) translate(olap) default_tag("remove",inside) children(); } else { translate(pos) rot(v=spinaxis,a=spin) - rot(anch[3],from=anchrvec,to=anch[2]) + rot(anch[3],from=reference,to=anch[2]){ translate(olap) - children(); - } + default_tag("remove",inside) children();}} } } diff --git a/tutorials/Attachments.md b/tutorials/Attachments.md index b39e207..522b895 100644 --- a/tutorials/Attachments.md +++ b/tutorials/Attachments.md @@ -722,7 +722,7 @@ right(60)cylinder(d1=30,d2=15,h=25) attach(BOT,BOT) anchor_arrow(30); ```openscad-3D include prismoid([50,50],[35,35],h=50,anchor=BOT) - attach(RIGHT,BOT) ylinder(d1=30,d2=15,h=25); + 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 @@ -841,7 +841,32 @@ color_this("orange") } ``` -The last feature provided by the double argument form of `attach()` is +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 +cuboid(50) + attach(TOP,BOT,overlap=15) + color("green")cuboid(20); +``` + +```openscad-3D +include +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. @@ -889,51 +914,92 @@ cube(30) } ``` -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. +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 -cylinder(d1=30,d2=15,h=25) attach(CENTER) anchor_arrow(40); -right(40)cylinder(d1=30,d2=15,h=25) attach(CENTER) anchor_arrow(40); +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 -cylinder(d1=30,d2=15,h=25) - attach(CENTER,CENTER) - cylinder(d1=30,d2=15,h=25); +cube(50,anchor=BOT) attach(TOP) anchor_arrow(30); +right(60)cylinder(d1=30,d2=15,h=25) attach(TOP) anchor_arrow(30); ``` -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. +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 +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 +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 +back_half(s=200) +diff() cuboid(50) - attach(TOP,BOT,overlap=15) - color("green")cuboid(20); + 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 -cube(50,center=true) - attach(TOP,BOT,overlap=-20) - cyl(d=20,h=20); +diff() +cuboid(10) + attach(TOP,TOP,align=RIGHT+FWD,inside=true,shiftout=.01) + cuboid([2,5,9]); ``` -As with `position()`, you can still apply your own translations and +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 @@ -966,6 +1032,24 @@ cuboid(50){ } ``` +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. + +```openscad-3D +include +cylinder(d1=30,d2=15,h=25) attach(CENTER) anchor_arrow(40); +right(40)cylinder(d1=30,d2=15,h=25) attach(CENTER) anchor_arrow(40); +``` + +```openscad-3D +include +cylinder(d1=30,d2=15,h=25) + attach(CENTER,CENTER) + cylinder(d1=30,d2=15,h=25); +``` ## Parent Anchor Attachment (Single Argument Attachment)