From 05eec08542efc144bd61d2adf6881d9dcbbc353b Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 22 Mar 2023 15:58:49 -0400 Subject: [PATCH 1/4] Change orient() to have only the anchor argument --- attachments.scad | 62 +++++++++++++--------------------------- tutorials/Attachments.md | 21 +++++--------- 2 files changed, 27 insertions(+), 56 deletions(-) diff --git a/attachments.scad b/attachments.scad index 6b4026c..e747b7c 100644 --- a/attachments.scad +++ b/attachments.scad @@ -429,73 +429,51 @@ module position(from) // Module: orient() // Usage: -// orient(dir, [spin=]) CHILDREN; -// PARENT() orient(anchor=, [spin=]) CHILDREN; +// PARENT() orient(anchor, [spin]) CHILDREN; // Topics: Attachments // Description: -// Orients children such that their top is tilted towards the given direction, or towards the -// direction of a given anchor point on the parent. For a step-by-step explanation of -// attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. +// Orients children such that their top is tilted in the direction of the specified parent anchor point. +// For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. // Arguments: -// dir = The direction to orient towards. -// --- -// anchor = The anchor on the parent which you want to match the orientation of. Use instead of `dir`. +// anchor = The anchor on the parent which you want to match the orientation of. // spin = The spin to add to the children. (Overrides anchor spin.) // Side Effects: // `$attach_anchor` is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for the `anchor=`, if given. // `$attach_to` is set to `undef`. // `$attach_norot` is set to `true`. // See Also: attachable(), attach(), orient() -// Example: Orienting by Vector +// Example: When orienting to an anchor, the spin of the anchor may cause confusion: // prismoid([50,50],[30,30],h=40) { // position(TOP+RIGHT) // orient(RIGHT) // prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT); // } -// Example: When orienting to an anchor, the spin of the anchor may cause confusion: -// prismoid([50,50],[30,30],h=40) { -// position(TOP+RIGHT) -// orient(anchor=RIGHT) -// prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT); -// } // Example: You can override anchor spin with `spin=`. // prismoid([50,50],[30,30],h=40) { // position(TOP+RIGHT) -// orient(anchor=RIGHT,spin=0) +// orient(RIGHT,spin=0) // prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT); // } // Example: Or you can anchor the child from the back // prismoid([50,50],[30,30],h=40) { // position(TOP+RIGHT) -// orient(anchor=RIGHT) +// orient(RIGHT) // prismoid([30,30],[0,5],h=20,anchor=BOT+BACK); // } -module orient(dir, anchor, spin) { +module orient(anchor, spin) { req_children($children); - if (!is_undef(dir)) { - spin = default(spin, 0); - check = - assert(anchor==undef, "Only one of dir= or anchor= may be given to orient()") - assert(is_vector(dir)) - assert(is_finite(spin)); - two_d = _attach_geom_2d($parent_geom); - fromvec = two_d? BACK : UP; - rot(spin, from=fromvec, to=dir) children(); - } else { - check= - assert(dir==undef, "Only one of dir= or anchor= may be given to orient()") - assert($parent_geom != undef, "No parent to orient from!") - assert(is_string(anchor) || is_vector(anchor)); - anch = _find_anchor(anchor, $parent_geom); - two_d = _attach_geom_2d($parent_geom); - fromvec = two_d? BACK : UP; - $attach_to = undef; - $attach_anchor = anch; - $attach_norot = true; - spin = default(spin, anch[3]); - assert(is_finite(spin)); - rot(spin, from=fromvec, to=anch[2]) children(); - } + check= + assert($parent_geom != undef, "No parent to orient from!") + assert(is_string(anchor) || is_vector(anchor)); + anch = _find_anchor(anchor, $parent_geom); + two_d = _attach_geom_2d($parent_geom); + fromvec = two_d? BACK : UP; + $attach_to = undef; + $attach_anchor = anch; + $attach_norot = true; + spin = default(spin, anch[3]); + assert(is_finite(spin)); + rot(spin, from=fromvec, to=anch[2]) children(); } diff --git a/tutorials/Attachments.md b/tutorials/Attachments.md index 9790331..12f4ac4 100644 --- a/tutorials/Attachments.md +++ b/tutorials/Attachments.md @@ -391,19 +391,18 @@ orient the object relative to some face other than the TOP face that meets at that edge or corner. You can always apply a `rotation()` to change the orientation of the child object, but in order to do this, you need to figure out the correct rotation. The `orient()` module provides a -mechanism for re-orienting the child() that eases this burden. -Using its `anchor=` argument you can orient the -child relative to the parent anchor directions. This is different +mechanism for re-orienting the child() that eases this burden: +it can orient the child relative to the parent anchor directions. This is different than giving an `orient=` argument to the child, because that orients relative to the parent's global coordinate system by just using the vector -directly instead of orienting to the parent's anchor, which takes +directly, instead of orienting to the parent's anchor, which takes account of face orientation. A series of three examples shows the different results. In the first example, we use only `position()`. The child cube is erected pointing upwards, in the Z direction. In the second example we use `orient=RIGHT` in the child and the result is that the child object points in the X+ direction, without regard for the shape of the parent object. In the final -example we apply `orient(anchor=RIGHT)` and the child is oriented +example we apply `orient(RIGHT)` and the child is oriented relative to the slanted right face of the parent using the parent RIGHT anchor. @@ -427,7 +426,7 @@ prismoid([50,50],[30,30],h=40) include prismoid([50,50],[30,30],h=40) position(RIGHT+TOP) - orient(anchor=RIGHT) + orient(RIGHT) cube([15,15,25],anchor=BACK+BOT); ``` @@ -460,17 +459,11 @@ prismoid([50,50],[30,30],h=40) include prismoid([50,50],[30,30],h=40) position(RIGHT+TOP) - orient(anchor=RIGHT) + orient(RIGHT) anchor_arrow(40); ``` -Note also that `orient()` can be used to orient the child relative to -the parent global coordinate system using its first argument, `dir=`. This -use of `orient()` is the same as using the `orient=` argument for the -child object. - - ## Attachment overview Attachables get their name from their ability to be attached to each @@ -590,7 +583,7 @@ cube(50,center=true) In the second example, the child object points diagonally away from the cube. If you want the child at at edge of the parent it's likely that this result will not be what you want. To get a different -result, use `position()` with `orient(anchor=)`, if needed. +result, use `position()` with `orient()`, if needed. If you give an anchor point to the child object it moves the child around (in the attached coordinate system). Or alternatively you can From 654e3cd86a82b3eeb5a2da2921da25453fb047b9 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 22 Mar 2023 16:11:31 -0400 Subject: [PATCH 2/4] fix parse_frac bug for leading space leading to infinite recursion --- strings.scad | 35 +++++++++++++++++------------------ tests/test_strings.scad | 6 ++++++ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/strings.scad b/strings.scad index cef528e..032a40f 100644 --- a/strings.scad +++ b/strings.scad @@ -468,24 +468,23 @@ function parse_float(str) = // parse_frac("-2 12/4",mixed=false); // Returns nan // parse_frac("2 1/4",mixed=false); // Returns nan function parse_frac(str,mixed=true,improper=true,signed=true) = - str == undef ? undef : - len(str)==0 ? 0 : - signed && str[0]=="-" ? -parse_frac(substr(str,1),mixed=mixed,improper=improper,signed=false) : - signed && str[0]=="+" ? parse_frac(substr(str,1),mixed=mixed,improper=improper,signed=false) : - mixed ? ( - !in_list(str_find(str," "), [undef,0]) || is_undef(str_find(str,"/"))? ( - let(whole = str_split(str,[" "])) - _parse_int_recurse(whole[0],10,len(whole[0])-1) + parse_frac(whole[1], mixed=false, improper=improper, signed=false) - ) : parse_frac(str,mixed=false, improper=improper) - ) : ( - let(split = str_split(str,"/")) - len(split)!=2 ? (0/0) : - let( - numerator = _parse_int_recurse(split[0],10,len(split[0])-1), - denominator = _parse_int_recurse(split[1],10,len(split[1])-1) - ) !improper && numerator>=denominator? (0/0) : - denominator<0 ? (0/0) : numerator/denominator - ); + str == undef ? undef + : len(str)==0 ? 0 + : str[0]==" " ? NAN + : signed && str[0]=="-" ? -parse_frac(substr(str,1),mixed=mixed,improper=improper,signed=false) + : signed && str[0]=="+" ? parse_frac(substr(str,1),mixed=mixed,improper=improper,signed=false) + : mixed && (str_find(str," ")!=undef || str_find(str,"/")==undef)? // Mixed allowed and there is a space or no slash + let(whole = str_split(str,[" "])) + _parse_int_recurse(whole[0],10,len(whole[0])-1) + parse_frac(whole[1], mixed=false, improper=improper, signed=false) + : let(split = str_split(str,"/")) + len(split)!=2 ? NAN + : let( + numerator = _parse_int_recurse(split[0],10,len(split[0])-1), + denominator = _parse_int_recurse(split[1],10,len(split[1])-1) + ) + !improper && numerator>=denominator? NAN + : denominator<0 ? NAN + : numerator/denominator; // Function: parse_num() diff --git a/tests/test_strings.scad b/tests/test_strings.scad index 09aa4d6..97c7683 100644 --- a/tests/test_strings.scad +++ b/tests/test_strings.scad @@ -215,6 +215,12 @@ module test_parse_frac() { assert(parse_frac("3/0") == INF); assert(parse_frac("-3/0") == -INF); assert(is_nan(parse_frac("0/0"))); + assert(is_nan(parse_frac("-77/9", improper=false))); + assert(is_nan(parse_frac("-2 12/4",improper=false))); + assert(is_nan(parse_frac("-2 12/4",signed=false))); + assert(is_nan(parse_frac("-2 12/4",mixed=false))); + assert(is_nan(parse_frac("2 1/4",mixed=false))); + assert(is_nan(parse_frac("2", mixed=false))); } test_parse_frac(); From ce14880420b2243aac7b589918215ad8303a6552 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 22 Mar 2023 16:12:31 -0400 Subject: [PATCH 3/4] improve error message for bad screw length --- screws.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screws.scad b/screws.scad index 315d9ac..7c40496 100644 --- a/screws.scad +++ b/screws.scad @@ -1324,7 +1324,7 @@ function _parse_screw_name(name) = ) assert(len(commasplit)<=2, str("More than one comma found in screw name, \"",name,"\"")) assert(len(xdash)<=2, str("Screw name has too many '-' or 'x' characters, \"",name,"\"")) - assert(len(commasplit)==1 || is_num(length), str("Invalid length in screw name, \"",name,"\"")) + assert(len(commasplit)==1 || is_num(length), str("Invalid length \"", commasplit[1],"\" in screw name, \"",name,"\"")) assert(len(xdash)==1 || all_nonnegative(thread),str("Thread pitch not a valid number in screw name, \"",name,"\"")) type[0] == "M" || type[0] == "m" ? let(diam = parse_float(substr(type,1))) From 3b130c03567b11baba998aa724b7110fcd339c1f Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 22 Mar 2023 16:37:50 -0400 Subject: [PATCH 4/4] Fix vertical offset attachment bug for linear_sweep --- attachments.scad | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/attachments.scad b/attachments.scad index e747b7c..846ed0e 100644 --- a/attachments.scad +++ b/attachments.scad @@ -2255,8 +2255,8 @@ function attach_geom( assert(is_vector(scale,2)) assert(is_num(twist)) extent==true - ? ["xrgn_extent", region, l, twist, scale, shift, cp, offset, anchors] - : ["xrgn_isect", region, l, twist, scale, shift, cp, offset, anchors] + ? ["extrusion_extent", region, l, twist, scale, shift, cp, offset, anchors] + : ["extrusion_isect", region, l, twist, scale, shift, cp, offset, anchors] ) : let( r1 = get_radius(r1=r1,d1=d1,r=r,d=d,dflt=undef) @@ -2351,7 +2351,7 @@ function _attach_geom_size(geom) = mm = pointlist_bounds(geom[1][0]), delt = mm[1]-mm[0] ) delt - ) : type == "xrgn_isect" || type == "xrgn_extent"? ( //path, l + ) : type == "extrusion_isect" || type == "extrusion_extent"? ( //path, l let( mm = pointlist_bounds(flatten(geom[1])), delt = mm[1]-mm[0] @@ -2457,7 +2457,7 @@ function _get_cp(geom) = : let( type = in_list(geom[0],["vnf_extent","vnf_isect"]) ? "vnf" : in_list(geom[0],["rgn_extent","rgn_isect"]) ? "path" - : in_list(geom[0],["xrgn_extent","xrgn_isect"]) ? "xpath" + : in_list(geom[0],["extrusion_extent","extrusion_isect"]) ? "xpath" : "other" ) assert(type!="other", "Invalid cp value") @@ -2466,11 +2466,29 @@ function _get_cp(geom) = [each centroid(geom[1]), if (type=="xpath") 0] ) : let(points = type=="vnf"?geom[1][0]:flatten(force_region(geom[1]))) - cp=="mean" ? [each mean(points), if (type=="xpath") geom[2]/2] - : cp=="box" ?[each mean(pointlist_bounds(points)), if (type=="xpath") geom[2]/2] + cp=="mean" ? [each mean(points), if (type=="xpath") 0] + : cp=="box" ?[each mean(pointlist_bounds(points)), if (type=="xpath") 0] : assert(false,"Invalid cp specification"); +function _get_cp(geom) = + let(cp=select(geom,-3)) + is_vector(cp) ? cp + : let( + is_vnf = in_list(geom[0],["vnf_extent","vnf_isect"]) + ) + cp == "centroid" ? ( + is_vnf && len(geom[1][1])==0 + ? [0,0,0] + : centroid(geom[1]) + ) + : let(points = is_vnf?geom[1][0]:flatten(force_region(geom[1]))) + cp=="mean" ? mean(points) + : cp=="box" ? mean(pointlist_bounds(points)) + : assert(false,"Invalid cp specification"); + + + function _force_anchor_2d(anchor) = assert(anchor.y==0 || anchor.z==0, "Anchor for a 2D shape cannot be fully 3D. It must have either Y or Z component equal to zero.") anchor.y==0 ? [anchor.x,anchor.z] : point2d(anchor); @@ -2701,7 +2719,7 @@ function _find_anchor(anchor, geom) = midy = (min(ys)+max(ys))/2, pos = rot(from=RIGHT, to=anchor, p=[maxx,midy]) ) [anchor, pos, unit(anchor,BACK), 0] - ) : type=="xrgn_extent" || type=="xrgn_isect" ? ( // extruded region + ) : type=="extrusion_extent" || type=="extrusion_isect" ? ( // extruded region assert(in_list(anchor.z,[-1,0,1]), "The Z component of an anchor for an extruded 2D shape must be -1, 0, or 1.") let( anchor_xy = point2d(anchor), @@ -2716,12 +2734,12 @@ function _find_anchor(anchor, geom) = twmat = zrot(lerp(0, -twist, u)), mat = shmat * scmat * twmat ) - approx(anchor_xy,[0,0]) ? [anchor, apply(mat, up(anchor.z*L/2,cp)), unit(anchor, UP), oang] : + approx(anchor_xy,[0,0]) ? [anchor, apply(mat, point3d(cp,anchor.z*L/2)), unit(anchor, UP), oang] : let( newrgn = apply(mat, rgn), - newgeom = attach_geom(two_d=true, region=newrgn, extent=type=="xrgn_extent", cp=cp), + newgeom = attach_geom(two_d=true, region=newrgn, extent=type=="extrusion_extent", cp=cp), result2d = _find_anchor(anchor_xy, newgeom), - pos = point3d(result2d[1], cp.z+anchor.z*L/2), + pos = point3d(result2d[1], anchor.z*L/2), vec = unit(point3d(result2d[2], anchor.z),UP), oang = atan2(vec.y,vec.x) + 90 )