From ae73c6d9cf28acc3cffc4fbf077e4383d532623e Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Fri, 27 Sep 2024 18:31:30 -0400 Subject: [PATCH 01/11] attachments update --- attachments.scad | 303 ++++++++++++++++++++++++++++++--------- tutorials/Attachments.md | 24 ++-- 2 files changed, 240 insertions(+), 87 deletions(-) diff --git a/attachments.scad b/attachments.scad index fa8350f..554f4e0 100644 --- a/attachments.scad +++ b/attachments.scad @@ -16,6 +16,7 @@ // Default values for attachment code. $tags=undef; // for backward compatibility $tag = ""; +$save_tag = undef; $tag_prefix = ""; $overlap = 0; $color = "default"; @@ -100,18 +101,18 @@ _ANCHOR_TYPES = ["intersect","hull"]; // stepper motor shape. The names, positions, directions, and spins of these anchors are // specific to the object, and are documented when they exist. // Subsection: Spin -// Spin is specified with the `spin` argument in most shape modules. Specifying a scalar `spin` -// when creating an object will rotate the object counter-clockwise around the Z axis by the given -// number of degrees. If given as a 3D vector, the object will be rotated around each of the X, Y, Z -// axes by the number of degrees in each component of the vector. Spin is always applied after -// anchoring, and before orientation. Since spin is applied after anchoring it is not what -// you might think of intuitively as spinning the shape. To do that, apply `zrot()` to the shape before anchoring. +// Spin is specified with the `spin` argument in most shape modules. Specifying a spin` +// angle when creating an object will rotate the object counter-clockwise around the Z axis by the given +// number of degrees. Spin is always applied after anchoring, and before orientation. +// Since spin is applied after anchoring it is not always what you might think of intuitively +// as spinning the shape. To do that, apply `zrot()` to the shape before anchoring. // Subsection: Orient // Orientation is specified with the `orient` argument in most shape modules. Specifying `orient` // when creating an object will rotate the object such that the top of the object will be pointed // at the vector direction given in the `orient` argument. Orientation is always applied after // anchoring and spin. The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be -// added together to form the directional vector for this. ie: `LEFT+BACK` +// added together to form the directional vector for this (e.g. `LEFT+BACK`). The orient parameter +// is ignored when you use {{attach()}} because {{attach()}} provides its own orientation. // Subsection: Specifying Directions // You can use direction vectors to specify anchors for objects or to specify edges, faces, and // corners of cubes. You can simply specify these direction vectors numerically, but another @@ -772,7 +773,9 @@ function _make_anchor_legal(anchor,geom) = // 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 +// a parameter to the child it will NOT be taken into account. The special spin value "align" will +// spin the child so that the child's BACK direction is pointed towards the aligned edge on the parent. +// 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. @@ -820,7 +823,7 @@ function _make_anchor_legal(anchor,geom) = // 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. // 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.) +// spin = Amount to rotate the parent around the axis of the parent anchor. Can set to "align" to align the child's BACK with the parent aligned edge. (Only permitted in 3D.) // Side Effects: // `$anchor` set to the parent anchor value used for the child. // `$align` set to the align value used for the child. @@ -883,7 +886,11 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, { 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"); + assert(num_defined([from,parent])<2, "Cannot combine deprecated 'from' argument with 'parent' parameter") + assert(spin!="align" || is_def(align), "Can only set spin to \"align\" when the 'align' parameter is given") + assert(is_finite(spin) || spin=="align", "Spin must be a number (unless align is given)") + assert((is_undef(overlap) || is_finite(overlap)) && (is_def(overlap) || is_undef($overlap) || is_finite($overlap)), + str("Provided ",is_def(overlap)?"":"$","overlap is not valid.")); 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)) @@ -893,9 +900,13 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, req_children($children); 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(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="); - + + basegeom = $parent_geom[0]=="conoid" ? attach_geom(r=2,h=2) + : attach_geom(size=[2,2,2]); + child_abstract_anchor = is_vector(child)? _find_anchor(child, basegeom) : undef; two_d = _attach_geom_2d($parent_geom); overlap = (overlap!=undef)? overlap : $overlap; parent = first_defined([parent,from]); @@ -928,6 +939,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3] : let(spin_dir = rot(anchor_data[3],from=UP, to=-anchor_dir, p=BACK)) _compute_spin(anchor_dir,spin_dir); + parent_abstract_anchor = is_vector(parent) ? _find_anchor(parent,basegeom) : undef; 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") @@ -940,17 +952,17 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, // Now compute position on the parent (including alignment but not inset) where the child will be anchored pos = is_undef(align) ? anchor_data[1] : _find_anchor(anchor+align, $parent_geom)[1]; $attach_anchor = list_set(anchor_data, 1, pos); // Never used; For user informational use? Should this be set at all? - startdir = two_d || is_undef(align)? undef - : anchor==UP || anchor==DOWN ? BACK - : UP - (anchor*UP)*anchor/(anchor*anchor); // Component of UP perpendicular to anchor - enddir = is_undef(child) || child.z==0 ? UP : BACK; // Compute adjustment to the child anchor for position purposes. This adjustment // accounts for the change in the anchor needed to to alignment. 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); + : apply( rot(to=child_abstract_anchor[2],from=UP) + * affine3d_zrot(child_abstract_anchor[3]) + * affine3d_yrot(inside?0:180) + * affine3d_zrot(-parent_abstract_anchor[3]) + * rot(from=parent_abstract_anchor[2],to=UP) + * rot(v=anchor,-spin), + align); // The $anchor_override anchor value forces an override of the *position* only for the anchor // used when attachable() places the child $anchor_override = all_zero(child_adjustment)? inside?child:undef @@ -959,8 +971,11 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, // inset_dir is the direction for insetting when alignment is in effect 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); + : apply(affine3d_yrot(inside?180:0) + * affine3d_zrot(-parent_abstract_anchor[3]) + * rot(from=parent_abstract_anchor[2],to=UP) + * rot(v=anchor,-spin), + 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)) @@ -981,7 +996,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, // Module: tag() // Synopsis: Assigns a tag to an object // Topics: Attachments -// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect() +// See Also: tag_this(), force_tag(), recolor(), hide(), show_only(), diff(), intersect() // Usage: // PARENT() tag(tag) CHILDREN; // Description: @@ -1016,6 +1031,40 @@ module tag(tag) } + +// Module: tag_this() +// Synopsis: Assigns a tag to an object at the current level only. +// Topics: Attachments +// See Also: tag(), force_tag(), recolor(), hide(), show_only(), diff(), intersect() +// Usage: +// PARENT() tag(tag) CHILDREN; +// Description: +// Assigns the specified tag to the children at the current level only, with tags reverting to +// the previous tag in force for deeper descendents. This works using `$tag` and `$save_tag`. +// . +// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments). +// Arguments: +// tag = tag string, which must not contain any spaces. +// Side Effects: +// Sets `$tag` to the tag you specify, possibly with a scope prefix, and saves current tag in `$save_tag`. +// Example(3D): Here we subtract a cube while keeping its child. With {{tag()}} the child would inherit the "remove" tag and we would need to explicitly retag the child to prevent it from also being subtracted. +// diff() +// cuboid([10,10,4]) +// tag_this("remove")position(TOP) cuboid(3) // This cube is subtracted +// attach(TOP,BOT) cuboid(1); // Tag is reset so this cube displays + +module tag_this(tag) +{ + req_children($children); + check= + assert(is_string(tag),"tag must be a string") + assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed")); + $save_tag=default($tag,""); + $tag = str($tag_prefix,tag); + children(); +} + + // Module: force_tag() // Synopsis: Assigns a tag to a non-attachable object. // Topics: Attachments @@ -1683,6 +1732,40 @@ module hide(tags) } +// Module: hide_this() +// Synopsis: Hides attachable children at the current level +// Topics: Attachments +// See Also: hide(), tag_this(), tag(), recolor(), show_only(), show_all(), show_int(), diff(), intersect() +// Usage: +// hide_this() CHILDREN; +// Description: +// Hides all attachable children at the current level, while still displaying descendants. +// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments). +// Side Effects: +// Sets `$tag` and `$save_tag` +// Example: Use an invisible parent to position children. Unlike with {{hide()}} we do not need to explicitly use any tags. +// $fn=16; +// hide_this() cuboid(10) +// { +// attach(RIGHT,BOT) cyl(r=1,h=5); +// attach(LEFT,BOT) cyl(r=1,h=5); +// } +// Example: Nexting applications of hide_this() +// $fn=32; +// hide_this() cuboid(10) +// attach(TOP,BOT) cyl(r=2,h=5) +// hide_this() attach(TOP,BOT) cuboid(4) +// attach(RIGHT,BOT) cyl(r=1,h=2); + +module hide_this() +{ + tag_scope() + hide("child") + tag_this("child") + children(); +} + + // Module: show_only() // Synopsis: Show only the children with the listed tags. // See Also: tag(), recolor(), show_all(), show_int(), diff(), intersect() @@ -1690,7 +1773,7 @@ module hide(tags) // Usage: // show_only(tags) CHILDREN; // Description: -// Show only the children with the listed tags, which you sply as a space separated string. Only unhidden objects will be shown, so if an object is hidden either before or after the `show_only()` call then it will remain hidden. This overrides any previous `show_only()` calls. Unlike `hide()`, calls to `show_only()` are not cumulative. +// Show only the children with the listed tags, which you supply as a space separated string. Only unhidden objects will be shown, so if an object is hidden either before or after the `show_only()` call then it will remain hidden. This overrides any previous `show_only()` calls. Unlike `hide()`, calls to `show_only()` are not cumulative. // For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments). // Side Effects: // Sets `$tags_shown` to the tag you specify. @@ -1981,18 +2064,18 @@ module face_profile(faces=[], r, d, excess=0.01, convexity=10) { // cube([50,60,70],center=true) // edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) // mask2d_roundover(r=10, inset=2); -// Example: Using $edge_angle on a Conoid +// Example: Using $edge_angle on a conoid // diff() // cyl(d1=50, d2=30, l=40, anchor=BOT) { // edge_profile([TOP,BOT], excess=10, convexity=6) { // mask2d_roundover(r=8, inset=1, excess=1, mask_angle=$edge_angle); // } // } -// Example: Using $edge_angle on a Prismoid +// Example: Using $edge_angle on a prismoid // diff() // prismoid([60,50],[30,20],h=40,shift=[-25,15]) { // edge_profile(excess=10, convexity=20) { -// mask2d_roundover(r=5,inset=1,mask_angle=$edge_angle); +// mask2d_roundover(r=5,inset=1,mask_angle=$edge_angle,$fn=32); // } // } @@ -2864,11 +2947,23 @@ module attachable( children(0); } } - if (is_def($save_color)) { + if (is_def($save_tag) && is_def($save_color)){ + $tag=$save_tag; + $save_tag=undef; $color=$save_color; // Revert to the color before color_this() call $save_color=undef; children(1); } + else if (is_def($save_color)) { + $color=$save_color; // Revert to the color before color_this() call + $save_color=undef; + children(1); + } + else if (is_def($save_tag)) { + $tag=$save_tag; + $save_tag=undef; + children(1); + } else children(1); } } @@ -3474,7 +3569,8 @@ function _attach_transform(anchor, spin, orient, geom, p) = // if $anchor_override is set it defines the object position anchor (but note not direction or spin). // Otherwise we use the provided anchor for the object. pos = is_undef($anchor_override) ? anch[1] - : _find_anchor(_make_anchor_legal($anchor_override,geom),geom)[1] + : _find_anchor(_make_anchor_legal($anchor_override,geom),geom)[1], + f=is_undef($anchor_override) ? 0 : echo(geo=_find_anchor(_make_anchor_legal($anchor_override,geom),geom)[1]) ) two_d? assert(is_num(spin)) @@ -3482,13 +3578,8 @@ function _attach_transform(anchor, spin, orient, geom, p) = * rot(to=FWD, from=point3d(anch[2])) * affine3d_translate(point3d(-pos)) : - let( - spinT = is_num(spin) ? affine3d_zrot(-anch[3]-spin) - : affine3d_zrot(-spin.z) * affine3d_yrot(-spin.y) * affine3d_xrot(-spin.x) - * affine3d_zrot(-anch[3]) - ) affine3d_yrot(180) - * spinT + * affine3d_zrot(-anch[3]-spin) * rot(from=anch[2],to=UP) * affine3d_translate(point3d(-pos)) ) @@ -3610,7 +3701,7 @@ function _find_anchor(anchor, geom) = axy = point2d(anch), bot = point3d(v_mul(point2d(size )/2, axy), -h/2), top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2), - edge = top-bot, + edge = top-bot, pos = point3d(cp) + lerp(bot,top,u) + offset, // Find vectors of the faces involved in the anchor facevecs = @@ -3619,7 +3710,7 @@ function _find_anchor(anchor, geom) = if (anch.y!=0) unit(rot(from=UP, to=[0,edge.y,max(0.01,h)], p=[0,axy.y,0]), UP), if (anch.z!=0) unit([0,0,anch.z],UP) ], - dir = anchor==CENTER? UP + dir = anch==CENTER? UP : len(facevecs)==1? unit(facevecs[0],UP) : len(facevecs)==2? vector_bisect(facevecs[0],facevecs[1]) : let( @@ -3631,39 +3722,63 @@ function _find_anchor(anchor, geom) = v3 = unit(line[1]-line[0],UP) * anch.z ) unit(v3,UP), - final_dir = default(override[1],rot(from=UP, to=axis, p=dir)), + final_dir = default(override[1],anch==CENTER?UP:rot(from=UP, to=axis, p=dir)), final_pos = default(override[0],rot(from=UP, to=axis, p=pos)), + // If the anchor is on a face or horizontal edge we take the oang value for spin // If the anchor is on a vertical or sloped edge or corner we want to align the spin to point upward along the edge - // The "native" spin direction is the rotation of UP to the anchor direction - // The desired spin direction is the edge vector - // The axis of rotation is the direction vector, so we need component of edge perpendicular to dir - spin = anchor.x!=0 && anchor.y!=0 ? _compute_spin(dir, edge) //sign(anchor.x)*vector_angle(edge - (edge*dir)*dir/(dir*dir), rot(from=UP,to=dir,p=BACK)) - : oang + + spin = anch.x!=0 && anch.y!=0 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=edge)) // Set "vertical" edge and corner anchors point along the edge + : anch.z!=0 && sum(v_abs(anch))==2 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=anch.z*[anch.y,-anch.x,0])) // Horizontal anchors point clockwise + : norm(anch)==3 ? _compute_spin(final_dir, final_dir==DOWN || final_dir==UP ? BACK : UP) + : oang // face anchors point UP/BACK + //spin = anchor.x!=0 && anchor.y!=0 ? _compute_spin(dir, edge) + // : anchor.z!=0 && (anchor.x!=0 || anchor.y!=0) ? _compute_spin(dir, _canonical_edge([anchor.y,anchor.x,0])) + // : oang ) [anchor, final_pos, final_dir, default(override[2],spin)] ) : type == "conoid"? ( //r1, r2, l, shift assert(anchor.z == sign(anchor.z), "The Z component of an anchor for a cylinder/cone must be -1, 0, or 1") let( - rr1=geom[1], rr2=geom[2], l=geom[3], - shift=point2d(geom[4]), axis=point3d(geom[5]), + rr1=geom[1], + rr2=geom[2], + length=geom[3], + shift=point2d(geom[4]), + axis=point3d(geom[5]), r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1), r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2), anch = rot(from=axis, to=UP, p=anchor), offset = rot(from=axis, to=UP, p=offset), u = (anch.z+1)/2, + // Returns [point,tangent_dir] + solve_ellipse = function (r,dir) approx(dir,[0,0]) ? [[0,0],[0,0]] + : let( + x = r.x*dir.x*r.y / sqrt(dir.x^2*r.y^2+dir.y^2*r.x^2), + y = r.x*dir.y*r.y / sqrt(dir.x^2*r.y^2+dir.y^2*r.x^2) + ) + [[x,y], unit([y*r.x^2,-x*r.y^2],CTR)], + on_center = approx(point2d(anch), [0,0]), + botdata = solve_ellipse(r1,point2d(anch)), + topdata = solve_ellipse(r2,point2d(anch)), + bot = point3d(botdata[0], -length/2), + top = point3d(topdata[0], length/2), + tangent = lerp(botdata[1],topdata[1],u), + normal = [-tangent.y,tangent.x], axy = unit(point2d(anch),[0,0]), - bot = point3d(v_mul(r1,axy), -l/2), - top = point3d(v_mul(r2,axy)+shift, l/2), + obot = point3d(v_mul(r1,axy), -length/2), + otop = point3d(v_mul(r2,axy)+shift, length/2), pos = point3d(cp) + lerp(bot,top,u) + offset, - sidevec = rot(from=UP, to=top==bot?UP:top-bot, p=point3d(axy)), + sidevec = rot(from=UP, to=top==bot?UP:top-bot, p=point3d(normal)), vvec = anch==CENTER? UP : unit([0,0,anch.z],UP), - vec = anch==CENTER? CENTER : - approx(axy,[0,0])? unit(anch,UP) : - approx(anch.z,0)? sidevec : - unit((sidevec+vvec)/2,UP), + vec = on_center? unit(anch,UP) + : approx(anch.z,0)? sidevec + : unit((sidevec+vvec)/2,UP), pos2 = rot(from=UP, to=axis, p=pos), - vec2 = anch==CENTER? UP : rot(from=UP, to=axis, p=vec) - ) [anchor, pos2, vec2, oang] + vec2 = anch==CENTER? UP : rot(from=UP, to=axis, p=vec), + // Set spin for top/bottom to be clockwise + spin = anch.z!=0 && (anch.x!=0 || anch.y!=0) ? _compute_spin(vec2,rot(from=UP,to=axis,p=point3d(tangent)*anch.z)) + : anch.z==0 && norm(anch)>0 ? _compute_spin(vec2, (vec2==DOWN || vec2==UP)?BACK:UP) + : oang + ) [anchor, pos2, vec2, spin] ) : type == "point"? ( let( anchor = unit(point3d(anchor),CENTER), @@ -3742,24 +3857,59 @@ function _find_anchor(anchor, geom) = rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]), maxx = max(column(rpts,0)), idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i], - dir = len(idxs)>2 ? [anchor,oang] - : len(idxs)==2 ? - let( - edgefaces = _vnf_find_edge_faces(vnf,idxs), - edge = select(vnf[0],idxs) - ) - len(edgefaces)==0 ? [anchor,oang] - : assert(len(edgefaces)==2, "Invalid polyhedron encountered while computing VNF anchor") - edge[0]==edge[1] ? [anchor,oang] // two "edge" points are the same, so give up - : let( - direction= unit(mean([for(face=edgefaces) polygon_normal(select(vnf[0],vnf[1][face]))])), - edgedir = edge[1]-edge[0], - nz = [for(i=[0:2]) if (!approx(edgedir[i],0)) i], - flip = edgedir[last(nz)] < 0 ? -1 : 1, - spin = _compute_spin(direction, flip*edgedir) - ) - [direction,spin] - : let( + // We want to catch the case where the points lie on an edge. The complication is that the edge + // may appear twice WITH DIFFERENT VERTEX INDICES if repeated points appear in the vnf. + edges_faces = len(idxs)==2 ? // Simple case, no repeated points, [idxs] gives the edge + approx(vnf[0][idxs[0]],vnf[0][idxs[1]]) ? [] // Are edge points identical? + : [[idxs],_vnf_find_edge_faces(vnf,idxs)] + : len(idxs)!=4 ? [] // If we don't have four points it's not an edge pair + : let( + pts = select(vnf[0],idxs), + matchind = [for(i=[1:3]) if (approx(pts[i],pts[0])) i] // indices where actual vertex point is the same as point zero + ) + len(matchind)!=1 ? [] + : let( // After this runs we have two edges as index pairs, and their associated faces as index values + match1 = select(idxs,[0,matchind[0]]), + match2 = list_remove_values(idxs,match1), + face1 = _vnf_find_edge_faces(vnf,[match1[0],match2[0]]), + face2 = _vnf_find_edge_faces(vnf,[match1[0],match2[1]]), + edge1 = [match1[0], face1==[] ? match2[1] : match2[0]], + edge2 = list_remove_values(idxs,edge1), + face3 = _vnf_find_edge_faces(vnf,edge2), + allfaces = concat(face1,face2,face3) + ) + assert(len(allfaces)==2, "Invalid polyhedron encountered while computing VNF anchor") + [[edge1,edge2], allfaces], + dir = len(idxs)>2 && edges_faces==[] ? [anchor,oang] + : edges_faces!=[] ? + let( + faces = edges_faces[1], + edge = select(vnf[0],edges_faces[0][0]), + facenormals = [for(face=faces) polygon_normal(select(vnf[0],vnf[1][face]))], + direction= unit(mean(facenormals)), + projnormals = project_plane(point4d(cross(facenormals[0],facenormals[1])), facenormals), + ang = 180- posmod(v_theta(projnormals[1])-v_theta(projnormals[0]),360), + horiz_face = [for(i=[0:1]) if (approx(v_abs(facenormals[i]),UP)) i], + spin = horiz_face==[] ? + let( + edgedir = edge[1]-edge[0], + nz = [for(i=[0:2]) if (!approx(edgedir[i],0)) i], + flip = edgedir[last(nz)] < 0 ? -1 : 1 + ) + _compute_spin(direction, flip*edgedir) + : + let( + hedge = len(edges_faces[0])==1 ? edges_faces[0][0] + : edges_faces[0][horiz_face[0]], + face = select(vnf[1],faces[horiz_face[0]]), + edgeind = search([hedge[0]], face)[0], + flip = select(face,edgeind+1)== hedge[1] ? 1 : -1, + edgedir = edge[1]-edge[0] + ) + _compute_spin(direction, flip*edgedir) + ) + [direction,spin] + : let( // This section handles corner anchors, currently spins just point up vertices = vnf[0], faces = vnf[1], cornerfaces = _vnf_find_corner_faces(vnf,idxs[0]), // faces = [3,9,12] indicating which faces @@ -4634,12 +4784,23 @@ function _force_anchor_2d(anchor) = // direction and gives the spin angle that achieves it. function _compute_spin(anchor_dir, spin_dir) = let( + f=echo(ad=anchor_dir, spin_dir), native_dir = rot(from=UP, to=anchor_dir, p=BACK), spin_dir = spin_dir - (spin_dir*anchor_dir)*anchor_dir, // component of spin_dir perpendicular to anchor_dir + dummy = assert(!approx(spin_dir,[0,0,0]),"spin direction is parallel to anchor"), angle = vector_angle(native_dir,spin_dir), sign = cross(native_dir,spin_dir)*anchor_dir<0 ? -1 : 1 ) sign*angle; + +// Compute canonical edge direction so that edge is either Z+, Y+ or X+ in that order +function _canonical_edge(edge) = + let( + nz = [for(i=[0:2]) if (!approx(edge[i],0)) i], + flip = edge[last(nz)] < 0 ? -1 : 1 + ) + flip * edge; + // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/tutorials/Attachments.md b/tutorials/Attachments.md index 9e8c2db..07563ee 100644 --- a/tutorials/Attachments.md +++ b/tutorials/Attachments.md @@ -223,15 +223,6 @@ include cube([20,20,40], center=true, spin=45); ``` -You can also spin around other axes, or multiple axes at once, by giving 3 angles (in degrees) to -`spin=` as a vector, like [Xang,Yang,Zang]. Similarly to `rotate()`, -the rotations apply in the order given, X-axis spin, then Y-axis, then Z-axis: - -```openscad-3D -include -cube([20,20,40], center=true, spin=[10,20,30]); -``` - This example shows a cylinder which has been anchored at its FRONT, with a rotated copy in gray. The rotation is performed around the origin, but the cylinder is off the origin, so the rotation **does** @@ -666,13 +657,13 @@ To show all the standard cardinal anchor points, you can use the [show_anchors() ```openscad-3D;Big include -cube(40, center=true) +cube(20, center=true) show_anchors(); ``` ```openscad-3D;Big include -cylinder(h=40, d=40, center=true) +cylinder(h=25, d=25, center=true) show_anchors(); ``` @@ -687,7 +678,7 @@ For large objects, you can again change the size of the arrows with the `s=` arg ```openscad-3D;Big include prismoid(150,60,100) - show_anchors(s=35); + show_anchors(s=45); ``` ## Parent-Child Anchor Attachment (Double Argument Attachment) @@ -2019,7 +2010,7 @@ override the position. If you omit the other list items then the value drived from the standard anchor will be used. Below we override position of the FWD anchor: -``` +```openscad-3D module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = [ [FWD, [[0,-s/8,0]]] @@ -2039,7 +2030,7 @@ Note how the FWD anchor is now rooted on the cylindrical portion. If you wanted to also change its direction and spin you could do it like this: -``` +```openscad-3D module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = [ [FWD, [[0,-s/8,0], FWD+LEFT, 225]] @@ -2062,7 +2053,8 @@ The third entry gives a spin override, whose effect is shown by the position of the red flag on the arrow. If you want to override all of the x=0 anchors to be on the cylinder, with their standard directions, you can do that by supplying a list: -``` + +```openscad-3D module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = [ for(j=[-1:1:1], k=[-1:1:1]) @@ -2086,7 +2078,7 @@ the default, or a `[position, direction, spin]` triple to override the default. As before, you can omit values to keep their default. Here is the same example using a function literal for the override: -``` +```openscad-3D module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = function (anchor) anchor.x!=0 || anchor==CTR ? undef // Keep these From 9671a8a1713a4c43c211ff1076fd3fe0ff00ac71 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Fri, 27 Sep 2024 18:59:19 -0400 Subject: [PATCH 02/11] fix 2d attach() --- attachments.scad | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/attachments.scad b/attachments.scad index 554f4e0..c938952 100644 --- a/attachments.scad +++ b/attachments.scad @@ -904,10 +904,10 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, "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); basegeom = $parent_geom[0]=="conoid" ? attach_geom(r=2,h=2) : attach_geom(size=[2,2,2]); - child_abstract_anchor = is_vector(child)? _find_anchor(child, basegeom) : undef; - two_d = _attach_geom_2d($parent_geom); + child_abstract_anchor = is_vector(child) && !two_d ? _find_anchor(child, basegeom) : undef; overlap = (overlap!=undef)? overlap : $overlap; parent = first_defined([parent,from]); anchors = is_vector(parent) || is_string(parent) ? [parent] : parent; @@ -939,7 +939,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3] : let(spin_dir = rot(anchor_data[3],from=UP, to=-anchor_dir, p=BACK)) _compute_spin(anchor_dir,spin_dir); - parent_abstract_anchor = is_vector(parent) ? _find_anchor(parent,basegeom) : undef; + parent_abstract_anchor = is_vector(parent) && !two_d ? _find_anchor(parent,basegeom) : undef; 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") From c2f5fa735275c3358000099f62dfbca2270ea061 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Fri, 27 Sep 2024 19:59:38 -0400 Subject: [PATCH 03/11] attachments fixes --- attachments.scad | 12 ++++++------ tutorials/Attachments.md | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/attachments.scad b/attachments.scad index c938952..39a067f 100644 --- a/attachments.scad +++ b/attachments.scad @@ -906,6 +906,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, two_d = _attach_geom_2d($parent_geom); basegeom = $parent_geom[0]=="conoid" ? attach_geom(r=2,h=2) + : $parent_geom[0]=="spheroid" ? echo("here")attach_geom(r=2) : attach_geom(size=[2,2,2]); child_abstract_anchor = is_vector(child) && !two_d ? _find_anchor(child, basegeom) : undef; overlap = (overlap!=undef)? overlap : $overlap; @@ -939,7 +940,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3] : let(spin_dir = rot(anchor_data[3],from=UP, to=-anchor_dir, p=BACK)) _compute_spin(anchor_dir,spin_dir); - parent_abstract_anchor = is_vector(parent) && !two_d ? _find_anchor(parent,basegeom) : undef; + parent_abstract_anchor = is_vector(anchor) && !two_d ? _find_anchor(anchor,basegeom) : undef; 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") @@ -953,7 +954,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, pos = is_undef(align) ? anchor_data[1] : _find_anchor(anchor+align, $parent_geom)[1]; $attach_anchor = list_set(anchor_data, 1, pos); // Never used; For user informational use? Should this be set at all? // Compute adjustment to the child anchor for position purposes. This adjustment - // accounts for the change in the anchor needed to to alignment. + // accounts for the change in the anchor needed to to alignment. child_adjustment = is_undef(align)? CTR : two_d ? rot(to=child,from=-factor*anchor,p=align) : apply( rot(to=child_abstract_anchor[2],from=UP) @@ -3569,8 +3570,7 @@ function _attach_transform(anchor, spin, orient, geom, p) = // if $anchor_override is set it defines the object position anchor (but note not direction or spin). // Otherwise we use the provided anchor for the object. pos = is_undef($anchor_override) ? anch[1] - : _find_anchor(_make_anchor_legal($anchor_override,geom),geom)[1], - f=is_undef($anchor_override) ? 0 : echo(geo=_find_anchor(_make_anchor_legal($anchor_override,geom),geom)[1]) + : _find_anchor(_make_anchor_legal($anchor_override,geom),geom)[1] ) two_d? assert(is_num(spin)) @@ -3861,7 +3861,8 @@ function _find_anchor(anchor, geom) = // may appear twice WITH DIFFERENT VERTEX INDICES if repeated points appear in the vnf. edges_faces = len(idxs)==2 ? // Simple case, no repeated points, [idxs] gives the edge approx(vnf[0][idxs[0]],vnf[0][idxs[1]]) ? [] // Are edge points identical? - : [[idxs],_vnf_find_edge_faces(vnf,idxs)] + : let( facelist = _vnf_find_edge_faces(vnf,idxs)) + len(facelist)==2 ? [[idxs], facelist] : [] : len(idxs)!=4 ? [] // If we don't have four points it's not an edge pair : let( pts = select(vnf[0],idxs), @@ -4784,7 +4785,6 @@ function _force_anchor_2d(anchor) = // direction and gives the spin angle that achieves it. function _compute_spin(anchor_dir, spin_dir) = let( - f=echo(ad=anchor_dir, spin_dir), native_dir = rot(from=UP, to=anchor_dir, p=BACK), spin_dir = spin_dir - (spin_dir*anchor_dir)*anchor_dir, // component of spin_dir perpendicular to anchor_dir dummy = assert(!approx(spin_dir,[0,0,0]),"spin direction is parallel to anchor"), diff --git a/tutorials/Attachments.md b/tutorials/Attachments.md index 07563ee..1382b40 100644 --- a/tutorials/Attachments.md +++ b/tutorials/Attachments.md @@ -2011,6 +2011,7 @@ value drived from the standard anchor will be used. Below we override position of the FWD anchor: ```openscad-3D +include module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = [ [FWD, [[0,-s/8,0]]] @@ -2031,6 +2032,7 @@ you wanted to also change its direction and spin you could do it like this: ```openscad-3D +include module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = [ [FWD, [[0,-s/8,0], FWD+LEFT, 225]] @@ -2055,6 +2057,7 @@ the x=0 anchors to be on the cylinder, with their standard directions, you can do that by supplying a list: ```openscad-3D +include module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = [ for(j=[-1:1:1], k=[-1:1:1]) @@ -2079,6 +2082,7 @@ default. As before, you can omit values to keep their default. Here is the same example using a function literal for the override: ```openscad-3D +include module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = function (anchor) anchor.x!=0 || anchor==CTR ? undef // Keep these From 7da26d32cd7fd438222f4d5a0e1ee69de56531e9 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 28 Sep 2024 08:44:14 -0400 Subject: [PATCH 04/11] arc() bugfix --- drawing.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drawing.scad b/drawing.scad index ed63276..47fc168 100644 --- a/drawing.scad +++ b/drawing.scad @@ -786,7 +786,7 @@ function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge= plane = [corner[2], corner[0], corner[1]], points2d = project_plane(plane, corner) ) - lift_plane(plane,arc(n,corner=points2d,wedge=wedge,long=long)) + lift_plane(plane,arc(n,corner=points2d,wedge=wedge,r=r, d=d)) ) : assert(is_path(corner) && len(corner) == 3) let(col = is_collinear(corner[0],corner[1],corner[2])) From 55aafee003ac951a9572290d268fd6073b130d13 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 28 Sep 2024 14:46:31 -0400 Subject: [PATCH 05/11] attachments fixes/tweaks --- attachments.scad | 81 +++++++++++++++++++++++++++++++++++----- tutorials/Attachments.md | 49 ++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 10 deletions(-) diff --git a/attachments.scad b/attachments.scad index 39a067f..c4f2fc2 100644 --- a/attachments.scad +++ b/attachments.scad @@ -778,7 +778,12 @@ function _make_anchor_legal(anchor,geom) = // 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. +// away from the edge or corner it is aligned to. +// . +// Note that the concept of alignment doesn't always make sense for objects without corners, such as spheres or cylinders. +// In same cases the alignments using such children will be odd because the alignment computation is trying to +// place a non-existent corner somewhere. Because attach() doesn't have in formation about the child when +// it runs it cannot handle curved shapes differently from cubes, so this behavior cannot be changed. // . // 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 @@ -881,6 +886,29 @@ function _make_anchor_legal(anchor,geom) = // attach(RIGHT+FRONT, TOP, inside=true) cuboid([10,3,5]); // attach(RIGHT+FRONT, TOP, inside=true, align=TOP,shiftout=.01) cuboid([5,1,2]); // } +// Example: Attaching a 3d edge mask. Simple 2d masks can be done using {{edge_profile()}} but this mask varies along its length. +// module wavy_edge(length,cycles, r, steps, n) +// { +// rmin = is_vector(r) ? r[0] : 0.01; +// rmax = is_vector(r) ? r[1] : r; +// layers = [for(z=[0:steps]) +// let( +// r=rmin+(rmax-rmin)/2*(cos(z*360*cycles/steps)+1),ff=echo(r=r) +// ) +// path3d( concat([[0,0]], +// arc(corner=path2d([BACK,CTR,RIGHT]), n=n, r=r)), +// z/steps*length-length/2) +// ]; +// attachable([rmax,rmax,length]){ +// skin(layers,slices=0); +// children(); +// } +// } +// diff() +// cuboid(25) +// attach([TOP+RIGHT,TOP+LEFT,TOP+FWD, FWD+RIGHT], FWD+LEFT, inside=true, shiftout=.01) +// wavy_edge(length=25.1,cycles=1.4,r=4,steps=24,n=15); + module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, inside=false, from, to) { @@ -905,10 +933,11 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, 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); - basegeom = $parent_geom[0]=="conoid" ? attach_geom(r=2,h=2) - : $parent_geom[0]=="spheroid" ? echo("here")attach_geom(r=2) + basegeom = $parent_geom[0]=="conoid" ? attach_geom(r=2,h=2,axis=$parent_geom[5]) + : $parent_geom[0]=="prismoid" ? attach_geom(size=[2,2,2],axis=$parent_geom[4]) : attach_geom(size=[2,2,2]); - child_abstract_anchor = is_vector(child) && !two_d ? _find_anchor(child, basegeom) : undef; + childgeom = attach_geom([2,2,2]); + child_abstract_anchor = is_vector(child) && !two_d ? _find_anchor(_make_anchor_legal(child,childgeom), childgeom) : undef; overlap = (overlap!=undef)? overlap : $overlap; parent = first_defined([parent,from]); anchors = is_vector(parent) || is_string(parent) ? [parent] : parent; @@ -940,16 +969,42 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3] : let(spin_dir = rot(anchor_data[3],from=UP, to=-anchor_dir, p=BACK)) _compute_spin(anchor_dir,spin_dir); - parent_abstract_anchor = is_vector(anchor) && !two_d ? _find_anchor(anchor,basegeom) : undef; + parent_abstract_anchor = is_vector(anchor) && !two_d ? _find_anchor(_make_anchor_legal(anchor,basegeom),basegeom) : undef; 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]); + spin = is_num(spin) ? spin + : align==CENTER ? 0 + : sum(v_abs(anchor))==1 ? // parent anchor is a face + let( + spindir = in_list(anchor,[TOP,BOT]) ? BACK : UP, + proj = project_plane(point4d(anchor),[spindir,align]), + ang = v_theta(proj[1])-v_theta(proj[0]) + ) + ang + : // parent anchor is not a face, so must be an edge (corners not allowed) + let( + nativeback = apply(rot(to=parent_abstract_anchor[2],from=UP) + *affine3d_zrot(parent_abstract_anchor[3]), BACK) + ) + nativeback*align<0 ? -180:0; $idx = align_ind+len(align_list)*anch_ind; $align=align; + goodcyl = $parent_geom[0] != "conoid" || is_undef(align) || align==CTR ? true + : let( + align=rot(from=$parent_geom[5],to=UP,p=align), + anchor=rot(from=$parent_geom[5],to=UP,p=anchor) + ) + anchor==TOP || anchor==BOT || align==TOP || align==BOT; + badcorner = !in_list($parent_geom[0],["conoid","spheroid"]) && !is_undef(align) && align!=CTR && sum(v_abs(anchor))==3; + badsphere = $parent_geom[0]=="spheroid" && !is_undef(align) && align!=CTR; dummy=assert(is_undef(align) || all_zero(v_mul(anchor,align)), - str("Invalid alignment: align value (",align,") includes component parallel to parent anchor (",anchor,")")); + str("Invalid alignment: align value (",align,") includes component parallel to parent anchor (",anchor,")")) + assert(goodcyl, str("Cannot use align with an anchor on a curved edge or surface of a cylinder at parent anchor (",anchor,")")) + assert(!badcorner, str("Cannot use align at a corner anchor (",anchor,")")) + assert(!badsphere, "Cannot use align on spheres."); // Now compute position on the parent (including alignment but not inset) where the child will be anchored pos = is_undef(align) ? anchor_data[1] : _find_anchor(anchor+align, $parent_geom)[1]; $attach_anchor = list_set(anchor_data, 1, pos); // Never used; For user informational use? Should this be set at all? @@ -963,7 +1018,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, * affine3d_zrot(-parent_abstract_anchor[3]) * rot(from=parent_abstract_anchor[2],to=UP) * rot(v=anchor,-spin), - align); + align); // The $anchor_override anchor value forces an override of the *position* only for the anchor // used when attachable() places the child $anchor_override = all_zero(child_adjustment)? inside?child:undef @@ -3687,8 +3742,10 @@ function _find_anchor(anchor, geom) = let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[]) assert(all_comps_good, "All components of an anchor for a cuboid/prismoid must be -1, 0, or 1") let( - size=geom[1], size2=geom[2], - shift=point2d(geom[3]), axis=point3d(geom[4]), + size=geom[1], + size2=geom[2], + shift=point2d(geom[3]), + axis=point3d(geom[4]), override = geom[5](anchor) ) let( @@ -3737,7 +3794,6 @@ function _find_anchor(anchor, geom) = // : oang ) [anchor, final_pos, final_dir, default(override[2],spin)] ) : type == "conoid"? ( //r1, r2, l, shift - assert(anchor.z == sign(anchor.z), "The Z component of an anchor for a cylinder/cone must be -1, 0, or 1") let( rr1=geom[1], rr2=geom[2], @@ -3747,6 +3803,11 @@ function _find_anchor(anchor, geom) = r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1), r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2), anch = rot(from=axis, to=UP, p=anchor), + axisname = axis==UP ? "Z" + : axis==RIGHT ? "X" + : axis==BACK ? "Y" + : "", + dummy = assert(anch.z == sign(anch.z), str("The ",axisname," component of an anchor for the cylinder/cone must be -1, 0, or 1")), offset = rot(from=axis, to=UP, p=offset), u = (anch.z+1)/2, // Returns [point,tangent_dir] diff --git a/tutorials/Attachments.md b/tutorials/Attachments.md index 1382b40..6e9592b 100644 --- a/tutorials/Attachments.md +++ b/tutorials/Attachments.md @@ -681,6 +681,55 @@ prismoid(150,60,100) show_anchors(s=45); ``` +Is is also possible to attach to edges and corners of the parent +object. The anchors for edges spin the child so its BACK direction is +aligned with the edge. If the edge belongs to a top or bottom +horizontal face, then the BACK directions will point clockwise around +the face, as seen from outside the shape. (This is the same direction +required for construction of valid faces in OpenSCAD.) Otherwise, the +BACK direction will point upwards. + +Examine the red flags below, where only edge anchors appear on a +prismoid. The top face shows the red flags pointing clockwise. +The sloped side edges point along the edges, generally upward, and +the bottom ones appear to point counter-clockwise, but if we viewed +the shape from the bottom they would also appear clockwise. + +```openscad-3D;Big +include +prismoid([100,175],[55,88], h=55) + for(i=[-1:1], j=[-1:1], k=[-1:1]) + let(anchor=[i,j,k]) + if (sum(v_abs(anchor))==2) + attach(anchor,BOT)anchor_arrow(40); +``` + +In this example cylinders sink half-way into the top edges of the +prismoid: + +```openscad-3D;Big +include +$fn=16; +r=6; +prismoid([100,175],[55,88], h=55){ + attach([TOP+RIGHT,TOP+LEFT],LEFT,overlap=r/2) cyl(r=r,l=88+2*r,rounding=r); + attach([TOP+FWD,TOP+BACK],LEFT,overlap=r/2) cyl(r=r,l=55+2*r, rounding=r); +} +``` + +This type of edge attachment is useful for attaching 3d edge masks to +edges: + +```openscad-3D;Big +include +$fn=32; +diff() +cuboid(75) + attach([FRONT+LEFT, FRONT+RIGHT, BACK+LEFT, BACK+RIGHT], + FWD+LEFT,inside=true) + rounding_edge_mask(l=76, r1=8,r2=28); +``` + ## Parent-Child Anchor Attachment (Double Argument Attachment) The `attach()` module has two different modes of operation, From c88319ca8fe1ab088346d6629303c0d38a4a4f9d Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 28 Sep 2024 17:45:52 -0400 Subject: [PATCH 06/11] add $edge_angle and $edge_length --- attachments.scad | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/attachments.scad b/attachments.scad index c4f2fc2..94276d7 100644 --- a/attachments.scad +++ b/attachments.scad @@ -12,6 +12,7 @@ // FileFootnotes: STD=Included in std.scad ////////////////////////////////////////////////////////////////////// +include // Default values for attachment code. $tags=undef; // for backward compatibility @@ -836,6 +837,8 @@ function _make_anchor_legal(anchor,geom) = // `$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` +// `$edge_angle` is set to the angle of the edge if the anchor is on an edge and the parent is a prismoid or vnf with "hull" anchoring +// `$edge_length` is set to the length of the edge if the anchor is on an edge and the parent is a prismoid or vnf with "hull" anchoring // Example: Cylinder placed on top of cube: // cuboid(50) // attach(TOP,BOT) cylinder(d1=30,d2=15,h=25); @@ -964,6 +967,8 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, : point3d(anchors[anch_ind]); $anchor=anchor; anchor_data = _find_anchor(anchor, $parent_geom); + $edge_angle = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_angle") : undef; + $edge_length = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_length") : undef; anchor_pos = anchor_data[1]; anchor_dir = factor*anchor_data[2]; anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3] @@ -3714,7 +3719,7 @@ function _get_cp(geom) = /// Arguments: /// anchor = Vector or named anchor string. /// geom = The geometry description of the shape. -function _find_anchor(anchor, geom) = +function _find_anchor(anchor, geom)= is_string(anchor)? ( anchor=="origin"? [anchor, CENTER, UP, 0] // Ok that this returns 3d anchor in the 2d case? : let( @@ -3779,20 +3784,20 @@ function _find_anchor(anchor, geom) = v3 = unit(line[1]-line[0],UP) * anch.z ) unit(v3,UP), + edgeang = len(facevecs)==2 ? 180-vector_angle(facevecs[0], facevecs[1]) : undef, final_dir = default(override[1],anch==CENTER?UP:rot(from=UP, to=axis, p=dir)), final_pos = default(override[0],rot(from=UP, to=axis, p=pos)), // If the anchor is on a face or horizontal edge we take the oang value for spin // If the anchor is on a vertical or sloped edge or corner we want to align the spin to point upward along the edge - spin = anch.x!=0 && anch.y!=0 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=edge)) // Set "vertical" edge and corner anchors point along the edge - : anch.z!=0 && sum(v_abs(anch))==2 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=anch.z*[anch.y,-anch.x,0])) // Horizontal anchors point clockwise + // Set "vertical" edge and corner anchors point along the edge + spin = anch.x!=0 && anch.y!=0 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=edge)) + // Horizontal anchors point clockwise + : anch.z!=0 && sum(v_abs(anch))==2 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=anch.z*[anch.y,-anch.x,0])) : norm(anch)==3 ? _compute_spin(final_dir, final_dir==DOWN || final_dir==UP ? BACK : UP) : oang // face anchors point UP/BACK - //spin = anchor.x!=0 && anchor.y!=0 ? _compute_spin(dir, edge) - // : anchor.z!=0 && (anchor.x!=0 || anchor.y!=0) ? _compute_spin(dir, _canonical_edge([anchor.y,anchor.x,0])) - // : oang - ) [anchor, final_pos, final_dir, default(override[2],spin)] + ) [anchor, final_pos, final_dir, default(override[2],spin), if (is_def(edgeang)) [["edge_angle",edgeang],["edge_length",norm(edge)]]] ) : type == "conoid"? ( //r1, r2, l, shift let( rr1=geom[1], @@ -3969,8 +3974,8 @@ function _find_anchor(anchor, geom) = edgedir = edge[1]-edge[0] ) _compute_spin(direction, flip*edgedir) - ) - [direction,spin] + ) + [direction,spin,[["edge_angle",ang],["edge_length",norm(edge[0]-edge[1])]]] : let( // This section handles corner anchors, currently spins just point up vertices = vnf[0], faces = vnf[1], @@ -3989,7 +3994,7 @@ function _find_anchor(anchor, geom) = avep = sum(select(rpts,idxs))/len(idxs), mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : avep, pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt) - ) [anchor, default(override[0],pos),default(override[1],dir[0]),default(override[2],dir[1])] + ) [anchor, default(override[0],pos),default(override[1],dir[0]),default(override[2],dir[1]),if (len(dir)==3) dir[2]] ) : type == "trapezoid"? ( //size, size2, shift, override let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[]) assert(all_comps_good, "All components of an anchor for a rectangle/trapezoid must be -1, 0, or 1") From 2670ce0c95509f8952ae24adbbddc7ba814d0deb Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 29 Sep 2024 09:10:12 -0400 Subject: [PATCH 07/11] assorted doc fixes --- attachments.scad | 32 +++++---- shapes2d.scad | 5 +- tutorials/Attachments.md | 138 +++++++++++++++++++++------------------ 3 files changed, 95 insertions(+), 80 deletions(-) diff --git a/attachments.scad b/attachments.scad index 94276d7..1f71019 100644 --- a/attachments.scad +++ b/attachments.scad @@ -101,19 +101,22 @@ _ANCHOR_TYPES = ["intersect","hull"]; // motors have anchors for `"screw1"`, `"screw2"`, etc. to refer to the various screwholes on the // stepper motor shape. The names, positions, directions, and spins of these anchors are // specific to the object, and are documented when they exist. +// . +// The anchor argument is ignored if you use {{align()}} or the two-argument form of {{attach()}} because +// these modules provide their own anchoring for their children. // Subsection: Spin -// Spin is specified with the `spin` argument in most shape modules. Specifying a spin` +// Spin is specified with the `spin` argument in most shape modules. Specifying a spin // angle when creating an object will rotate the object counter-clockwise around the Z axis by the given // number of degrees. Spin is always applied after anchoring, and before orientation. -// Since spin is applied after anchoring it is not always what you might think of intuitively -// as spinning the shape. To do that, apply `zrot()` to the shape before anchoring. +// Since spin is applied **after** anchoring it does not, in general, rotate around the object's center, +// so it is not always what you might think of intuitively as spinning the shape. // Subsection: Orient // Orientation is specified with the `orient` argument in most shape modules. Specifying `orient` // when creating an object will rotate the object such that the top of the object will be pointed // at the vector direction given in the `orient` argument. Orientation is always applied after // anchoring and spin. The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be // added together to form the directional vector for this (e.g. `LEFT+BACK`). The orient parameter -// is ignored when you use {{attach()}} because {{attach()}} provides its own orientation. +// is ignored when you use {{attach()}} with two arguments, because {{attach()}} provides its own orientation. // Subsection: Specifying Directions // You can use direction vectors to specify anchors for objects or to specify edges, faces, and // corners of cubes. You can simply specify these direction vectors numerically, but another @@ -612,7 +615,7 @@ module orient(anchor, spin) { // `$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" +// 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) @@ -659,10 +662,10 @@ module orient(anchor, spin) { // cuboid([40,30,10]) // align(FRONT,TOP,inside=true,shiftout=0.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. +// 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); +// color("green") cuboid(5); // Example: Changing the child characteristics based on the alignment // cuboid([20,20,8]) // align(TOP,[for(i=[-1:1], j=[-1:1]) [i,j]]) @@ -867,14 +870,14 @@ function _make_anchor_legal(anchor,geom) = // 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: +// Example: Alignment works on the sides of 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); +// attach(RIGHT+BOT, RIGHT) 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() @@ -2973,8 +2976,9 @@ module attachable( dummy1 = assert($children==2, "attachable() expects exactly two children; the shape to manage, and the union of all attachment candidates.") assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) - assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Invalid spin: ",spin)) + assert(is_finite(spin), str("Invalid spin: ",spin)) assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)); + assert(in_list(axis,[UP,RIGHT,BACK]), "axis must be a positive coordinate direction, either UP, BACK or RIGHT") anchor = first_defined([anchor, CENTER]); spin = default(spin, 0); orient = is_def($anchor_override)? UP : default(orient, UP); @@ -3131,9 +3135,9 @@ function reorient( geom, p=undef ) = - 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)) + assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) + assert(is_finite(spin) str("Invalid spin: ",spin)) + assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)) let( anchor = default(anchor, CENTER), spin = default(spin, 0), @@ -3616,7 +3620,7 @@ function _attach_geom_edge_path(geom, edge) = function _attach_transform(anchor, spin, orient, geom, p) = assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) - assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Invalid spin: ",spin)) + assert(is_finite(spin), str("Invalid spin: ",spin)) assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)) let( anchor = default(anchor, CENTER), diff --git a/shapes2d.scad b/shapes2d.scad index 223be71..465f9af 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -2050,10 +2050,9 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = module text(text, size=10, font, halign, valign, spacing=1.0, direction="ltr", language="en", script="latin", anchor="baseline", spin=0) { no_children($children); dummy1 = - 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(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) + assert(is_finite(spin), str("Invalid spin: ",spin)); anchor = default(anchor, CENTER); - spin = default(spin, 0); geom = attach_geom(size=[size,size],two_d=true); anch = !any([for (c=anchor) c=="["])? anchor : let( diff --git a/tutorials/Attachments.md b/tutorials/Attachments.md index 6e9592b..715a2d3 100644 --- a/tutorials/Attachments.md +++ b/tutorials/Attachments.md @@ -681,54 +681,6 @@ prismoid(150,60,100) show_anchors(s=45); ``` -Is is also possible to attach to edges and corners of the parent -object. The anchors for edges spin the child so its BACK direction is -aligned with the edge. If the edge belongs to a top or bottom -horizontal face, then the BACK directions will point clockwise around -the face, as seen from outside the shape. (This is the same direction -required for construction of valid faces in OpenSCAD.) Otherwise, the -BACK direction will point upwards. - -Examine the red flags below, where only edge anchors appear on a -prismoid. The top face shows the red flags pointing clockwise. -The sloped side edges point along the edges, generally upward, and -the bottom ones appear to point counter-clockwise, but if we viewed -the shape from the bottom they would also appear clockwise. - -```openscad-3D;Big -include -prismoid([100,175],[55,88], h=55) - for(i=[-1:1], j=[-1:1], k=[-1:1]) - let(anchor=[i,j,k]) - if (sum(v_abs(anchor))==2) - attach(anchor,BOT)anchor_arrow(40); -``` - -In this example cylinders sink half-way into the top edges of the -prismoid: - -```openscad-3D;Big -include -$fn=16; -r=6; -prismoid([100,175],[55,88], h=55){ - attach([TOP+RIGHT,TOP+LEFT],LEFT,overlap=r/2) cyl(r=r,l=88+2*r,rounding=r); - attach([TOP+FWD,TOP+BACK],LEFT,overlap=r/2) cyl(r=r,l=55+2*r, rounding=r); -} -``` - -This type of edge attachment is useful for attaching 3d edge masks to -edges: - -```openscad-3D;Big -include -$fn=32; -diff() -cuboid(75) - attach([FRONT+LEFT, FRONT+RIGHT, BACK+LEFT, BACK+RIGHT], - FWD+LEFT,inside=true) - rounding_edge_mask(l=76, r1=8,r2=28); -``` ## Parent-Child Anchor Attachment (Double Argument Attachment) @@ -1098,6 +1050,55 @@ cylinder(d1=30,d2=15,h=25) cylinder(d1=30,d2=15,h=25); ``` +Is is also possible to attach to edges and corners of the parent +object. The anchors for edges spin the child so its BACK direction is +aligned with the edge. If the edge belongs to a top or bottom +horizontal face, then the BACK directions will point clockwise around +the face, as seen from outside the shape. (This is the same direction +required for construction of valid faces in OpenSCAD.) Otherwise, the +BACK direction will point upwards. + +Examine the red flags below, where only edge anchors appear on a +prismoid. The top face shows the red flags pointing clockwise. +The sloped side edges point along the edges, generally upward, and +the bottom ones appear to point counter-clockwise, but if we viewed +the shape from the bottom they would also appear clockwise. + +```openscad-3D;Big +include +prismoid([100,175],[55,88], h=55) + for(i=[-1:1], j=[-1:1], k=[-1:1]) + let(anchor=[i,j,k]) + if (sum(v_abs(anchor))==2) + attach(anchor,BOT)anchor_arrow(40); +``` + +In this example cylinders sink half-way into the top edges of the +prismoid: + +```openscad-3D;Big +include +$fn=16; +r=6; +prismoid([100,175],[55,88], h=55){ + attach([TOP+RIGHT,TOP+LEFT],LEFT,overlap=r/2) cyl(r=r,l=88+2*r,rounding=r); + attach([TOP+FWD,TOP+BACK],LEFT,overlap=r/2) cyl(r=r,l=55+2*r, rounding=r); +} +``` + +This type of edge attachment is useful for attaching 3d edge masks to +edges: + +```openscad-3D;Big +include +$fn=32; +diff() +cuboid(75) + attach([FRONT+LEFT, FRONT+RIGHT, BACK+LEFT, BACK+RIGHT], + FWD+LEFT,inside=true) + rounding_edge_mask(l=76, r1=8,r2=28); +``` + ## Parent Anchor Attachment (Single Argument Attachment) The second form of attachment is parent anchor attachment, which just @@ -1278,7 +1279,7 @@ cube(100, center=true) } ``` -Remember that tags are inherited by children. In this case, we need to explicitly +Remember that tags applied with `tag()` are inherited by children. In this case, we need to explicitly untag the first cylinder (or change its tag to something else), or it will inherit the "keep" tag and get kept. @@ -1292,6 +1293,16 @@ tag("keep")cube(100, center=true) } ``` +You can apply a tag that is not propagated to the children using +`tag_this()`. The above example could then be redone: + +diff("hole", "keep") +tag_this("keep")cube(100, center=true) + attach([RIGHT,TOP]) { + cylinder(d=95, h=5); + tag("hole") cylinder(d=50, h=11, anchor=CTR); + } + You can of course apply `tag()` to several children. @@ -1361,7 +1372,8 @@ intersection is computed between the union of the `intersect` tagged objects and the objects that don't match any listed tags. Finally the objects listed in `keep` are union ed with the result. -In this example the parent is intersected with a conical bounding shape. +In this example the parent (untagged) is intersected with a conical +bounding shape, which is tagged with the intersect tag. ```openscad-3D include @@ -1659,7 +1671,7 @@ arguments of `attachable()`. In the most basic form, where the shape is fully cuboid, with top and bottom of the same size, and directly over one another, you can just use `size=`. -```openscad-3D +```openscad-3D;Big include module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { attachable(anchor,spin,orient, size=[s*3,s,s]) { @@ -1677,7 +1689,7 @@ When the shape is prismoidal, where the top is a different size from the bottom, the `size2=` argument as well. While `size=` takes all three axes sizes, the `size2=` argument only takes the [X,Y] sizes of the top of the shape. -```openscad-3D +```openscad-3D;Big include module prismoidal(size=[100,100,100], scale=0.5, anchor=CENTER, spin=0, orient=UP) { attachable(anchor,spin,orient, size=size, size2=[size.x, size.y]*scale) { @@ -1699,7 +1711,7 @@ When the top of the prismoid can be shifted away from directly above the bottom, the `shift=` argument. The `shift=` argument takes an [X,Y] vector of the offset of the center of the top from the XY center of the bottom of the shape. -```openscad-3D +```openscad-3D;Big include module prismoidal(size=[100,100,100], scale=0.5, shift=[0,0], anchor=CENTER, spin=0, orient=UP) { attachable(anchor,spin,orient, size=size, size2=[size.x, size.y]*scale, shift=shift) { @@ -1721,7 +1733,7 @@ In the case that the prismoid is not oriented vertically, (ie, where the `shift= arguments should refer to a plane other than XY) you can use the `axis=` argument. This lets you make prismoids naturally oriented forwards/backwards or sideways. -```openscad-3D +```openscad-3D;Big include module yprismoidal( size=[100,100,100], scale=0.5, shift=[0,0], @@ -1750,7 +1762,7 @@ yprismoidal([100,60,30], scale=1.5, shift=[20,20]) show_anchors(20); ### Cylindrical Attachables To make a cylindrical shape attachable, you use the `l`, and `r`/`d`, args of `attachable()`. -```openscad-3D +```openscad-3D;Big include module twistar(l,r,d, anchor=CENTER, spin=0, orient=UP) { r = get_radius(r=r,d=d,dflt=1); @@ -1799,7 +1811,7 @@ ytwistar(l=100, r=40) show_anchors(20); To make a conical shape attachable, you use the `l`, `r1`/`d1`, and `r2`/`d2`, args of `attachable()`. -```openscad-3D +```openscad-3D;Big include module twistar(l, r,r1,r2, d,d1,d2, anchor=CENTER, spin=0, orient=UP) { r1 = get_radius(r1=r1,r=r,d1=d1,d=d,dflt=1); @@ -1816,7 +1828,7 @@ twistar(l=100, r1=40, r2=20) show_anchors(20); If the cone is ellipsoidal in shape, you can pass the unequal X/Y sizes as a 2-item vectors to the `r1=`/`r2=` or `d1=`/`d2=` arguments. -```openscad-3D +```openscad-3D;Big include module ovalish(l,rx1,ry1,rx2,ry2, anchor=CENTER, spin=0, orient=UP) { attachable(anchor,spin,orient, r1=[rx1,ry1], r2=[rx2,ry2], l=l) { @@ -1837,7 +1849,7 @@ ovalish(l=100, rx1=50, ry1=30, rx2=30, ry2=50) show_anchors(20); For conical shapes that are not oriented vertically, use the `axis=` argument to indicate the direction of the primary shape axis: -```openscad-3D +```openscad-3D;Big include module ytwistar(l, r,r1,r2, d,d1,d2, anchor=CENTER, spin=0, orient=UP) { r1 = get_radius(r1=r1,r=r,d1=d1,d=d,dflt=1); @@ -1855,7 +1867,7 @@ ytwistar(l=100, r1=40, r2=20) show_anchors(20); ### Spherical Attachables To make a spherical shape attachable, you use the `r`/`d` args of `attachable()`. -```openscad-3D +```openscad-3D;Big include module spikeball(r, d, anchor=CENTER, spin=0, orient=UP) { r = get_radius(r=r,d=d,dflt=1); @@ -2059,7 +2071,7 @@ override the position. If you omit the other list items then the value drived from the standard anchor will be used. Below we override position of the FWD anchor: -```openscad-3D +```openscad-3D;Big include module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = [ @@ -2080,7 +2092,7 @@ Note how the FWD anchor is now rooted on the cylindrical portion. If you wanted to also change its direction and spin you could do it like this: -```openscad-3D +```openscad-3D;Big include module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = [ @@ -2105,7 +2117,7 @@ position of the red flag on the arrow. If you want to override all of the x=0 anchors to be on the cylinder, with their standard directions, you can do that by supplying a list: -```openscad-3D +```openscad-3D;Big include module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = [ @@ -2130,7 +2142,7 @@ the default, or a `[position, direction, spin]` triple to override the default. As before, you can omit values to keep their default. Here is the same example using a function literal for the override: -```openscad-3D +```openscad-3D;Big include module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { override = function (anchor) From 846de460fd14bb37cbb7e08a89bbb51761445a9a Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 29 Sep 2024 09:14:39 -0400 Subject: [PATCH 08/11] small fix --- attachments.scad | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/attachments.scad b/attachments.scad index 1f71019..a0a56ab 100644 --- a/attachments.scad +++ b/attachments.scad @@ -2978,7 +2978,7 @@ module attachable( assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) assert(is_finite(spin), str("Invalid spin: ",spin)) assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)); - assert(in_list(axis,[UP,RIGHT,BACK]), "axis must be a positive coordinate direction, either UP, BACK or RIGHT") + assert(in_list(axis,[UP,RIGHT,BACK]), "axis must be a positive coordinate direction, either UP, BACK or RIGHT"); anchor = first_defined([anchor, CENTER]); spin = default(spin, 0); orient = is_def($anchor_override)? UP : default(orient, UP); @@ -3136,7 +3136,7 @@ function reorient( p=undef ) = assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) - assert(is_finite(spin) str("Invalid spin: ",spin)) + assert(is_finite(spin), str("Invalid spin: ",spin)) assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)) let( anchor = default(anchor, CENTER), From 79cafb8e2a49d6c331c2ad4a87a98a369608081b Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 29 Sep 2024 09:35:51 -0400 Subject: [PATCH 09/11] error check fixes --- attachments.scad | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/attachments.scad b/attachments.scad index a0a56ab..7bf0518 100644 --- a/attachments.scad +++ b/attachments.scad @@ -2960,7 +2960,7 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) { // recolor("blue") cyl(d=5,h=5); module attachable( - anchor, spin, orient, + anchor=CENTER, spin=0, orient, size, size2, shift, r,r1,r2, d,d1,d2, l,h, vnf, path, region, @@ -2975,12 +2975,10 @@ module attachable( ) { dummy1 = assert($children==2, "attachable() expects exactly two children; the shape to manage, and the union of all attachment candidates.") - assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) + assert(is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) assert(is_finite(spin), str("Invalid spin: ",spin)) assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)); assert(in_list(axis,[UP,RIGHT,BACK]), "axis must be a positive coordinate direction, either UP, BACK or RIGHT"); - anchor = first_defined([anchor, CENTER]); - spin = default(spin, 0); orient = is_def($anchor_override)? UP : default(orient, UP); region = !is_undef(region)? region : !is_undef(path)? [path] : @@ -3122,7 +3120,7 @@ module attachable( // axis = The vector pointing along the axis of a geometry. Default: UP // p = The VNF, path, or point to transform. function reorient( - anchor, spin, orient, + anchor=CENTER, spin=0, orient=UP, size, size2, shift, r,r1,r2, d,d1,d2, l,h, vnf, path, region, @@ -3135,13 +3133,10 @@ function reorient( geom, p=undef ) = - assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) + assert(is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) assert(is_finite(spin), str("Invalid spin: ",spin)) - assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)) + assert(is_vector(orient,3), str("Invalid orient: ",orient)) let( - anchor = default(anchor, CENTER), - spin = default(spin, 0), - orient = default(orient, UP), region = !is_undef(region)? region : !is_undef(path)? [path] : undef, @@ -3618,14 +3613,11 @@ function _attach_geom_edge_path(geom, edge) = /// 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("Invalid anchor: ",anchor)) +function _attach_transform(anchor=CENTER, spin=0, orient=UP, geom, p) = + assert(is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) assert(is_finite(spin), str("Invalid spin: ",spin)) - assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)) + assert(is_vector(orient,3), str("Invalid orient: ",orient)) let( - anchor = default(anchor, CENTER), - spin = default(spin, 0), - orient = default(orient, UP), two_d = _attach_geom_2d(geom), m = ($attach_to != undef) ? // $attach_to is the attachment point on this object ( // which will attach to the parent From 7b5683590379297df2c766d392ac2cadf5e107d9 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 29 Sep 2024 11:21:36 -0400 Subject: [PATCH 10/11] fixing the fix --- attachments.scad | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/attachments.scad b/attachments.scad index 7bf0518..d0e37be 100644 --- a/attachments.scad +++ b/attachments.scad @@ -2960,7 +2960,7 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) { // recolor("blue") cyl(d=5,h=5); module attachable( - anchor=CENTER, spin=0, orient, + anchor, spin, orient, size, size2, shift, r,r1,r2, d,d1,d2, l,h, vnf, path, region, @@ -2975,10 +2975,12 @@ module attachable( ) { dummy1 = assert($children==2, "attachable() expects exactly two children; the shape to manage, and the union of all attachment candidates.") - assert(is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) - assert(is_finite(spin), str("Invalid spin: ",spin)) + assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) + assert(is_undef(spin) || is_finite(spin), str("Invalid spin: ",spin)) assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)); assert(in_list(axis,[UP,RIGHT,BACK]), "axis must be a positive coordinate direction, either UP, BACK or RIGHT"); + anchor = default(anchor,CENTER); + spin = default(spin,0); orient = is_def($anchor_override)? UP : default(orient, UP); region = !is_undef(region)? region : !is_undef(path)? [path] : @@ -3120,7 +3122,7 @@ module attachable( // axis = The vector pointing along the axis of a geometry. Default: UP // p = The VNF, path, or point to transform. function reorient( - anchor=CENTER, spin=0, orient=UP, + anchor, spin, orient, size, size2, shift, r,r1,r2, d,d1,d2, l,h, vnf, path, region, @@ -3133,10 +3135,13 @@ function reorient( geom, p=undef ) = - assert(is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) - assert(is_finite(spin), str("Invalid spin: ",spin)) - assert(is_vector(orient,3), str("Invalid orient: ",orient)) + assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) + assert(is_undef(spin) || is_finite(spin), str("Invalid spin: ",spin)) + assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)) let( + anchor = default(anchor, CENTER), + spin = default(spin, 0), + orient = default(orient, UP), region = !is_undef(region)? region : !is_undef(path)? [path] : undef, @@ -3613,11 +3618,14 @@ function _attach_geom_edge_path(geom, edge) = /// 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=CENTER, spin=0, orient=UP, geom, p) = - assert(is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) - assert(is_finite(spin), str("Invalid spin: ",spin)) - assert(is_vector(orient,3), str("Invalid orient: ",orient)) +function _attach_transform(anchor, spin, orient, geom, p) = + assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) + assert(is_undef(spin) || is_finite(spin), str("Invalid spin: ",spin)) + assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)) let( + anchor=default(anchor,CENTER), + spin=default(spin,0), + orient=default(orient,UP), two_d = _attach_geom_2d(geom), m = ($attach_to != undef) ? // $attach_to is the attachment point on this object ( // which will attach to the parent From fdda08e07109c3d1705c5cd63e79d1d4eae0e3fc Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 29 Sep 2024 12:37:57 -0400 Subject: [PATCH 11/11] remove euler angle from Shapes3d tutorial, cubetruss needs DOWN axis so permit negative axis directions --- attachments.scad | 2 +- tutorials/Shapes3d.md | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/attachments.scad b/attachments.scad index d0e37be..4a9416e 100644 --- a/attachments.scad +++ b/attachments.scad @@ -2978,7 +2978,7 @@ module attachable( assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor)) assert(is_undef(spin) || is_finite(spin), str("Invalid spin: ",spin)) assert(is_undef(orient) || is_vector(orient,3), str("Invalid orient: ",orient)); - assert(in_list(axis,[UP,RIGHT,BACK]), "axis must be a positive coordinate direction, either UP, BACK or RIGHT"); + assert(in_list(v_abs(axis),[UP,RIGHT,BACK]), "axis must be a coordinate direction"); anchor = default(anchor,CENTER); spin = default(spin,0); orient = is_def($anchor_override)? UP : default(orient, UP); diff --git a/tutorials/Shapes3d.md b/tutorials/Shapes3d.md index 4aa924b..8ff179e 100644 --- a/tutorials/Shapes3d.md +++ b/tutorials/Shapes3d.md @@ -47,22 +47,13 @@ include cube([50,40,20], anchor=TOP+FRONT+LEFT); ``` -You can use `spin=` to rotate around the Z axis: +You can use `spin=` to rotate around the Z axis **after** anchoring: ```openscad-3D include cube([50,40,20], anchor=FRONT, spin=30); ``` -3D objects also gain the ability to use an extra trick with `spin=`; -if you pass a list of `[X,Y,Z]` rotation angles to `spin=`, it will -rotate by the three given axis angles, similar to using `rotate()`: - -```openscad-3D -include -cube([50,40,20], anchor=FRONT, spin=[15,0,30]); -``` - 3D objects also can be given an `orient=` argument as a vector, pointing to where the top of the shape should be rotated towards.