From ee86544a8172756b389b1f7fb4e07f1e1e6d8cb5 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 18 Aug 2020 19:09:26 -0700 Subject: [PATCH 1/6] Added excess= argument to most mask modules. --- masks.scad | 97 +++++++++++++++++++++++++++------------------------- version.scad | 2 +- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/masks.scad b/masks.scad index 4a75886..894e50d 100644 --- a/masks.scad +++ b/masks.scad @@ -12,8 +12,8 @@ // Module: angle_pie_mask() // Usage: -// angle_pie_mask(r|d, l, ang); -// angle_pie_mask(r1|d1, r2|d2, l, ang); +// angle_pie_mask(r|d, l, ang, [excess]); +// angle_pie_mask(r1|d1, r2|d2, l, ang, [excess]); // Description: // Creates a pie wedge shape that can be used to mask other shapes. // Arguments: @@ -25,6 +25,7 @@ // d = Diameter of circle wedge is created from. (optional) // d1 = Bottom diameter of cone that wedge is created from. (optional) // d2 = Upper diameter of cone that wedge is created from. (optional) +// excess = The extra thickness of the mask. Default: `0.1`. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` @@ -34,14 +35,14 @@ module angle_pie_mask( ang=45, l=undef, r=undef, r1=undef, r2=undef, d=undef, d1=undef, d2=undef, - h=undef, + h=undef, excess=0.1, anchor=CENTER, spin=0, orient=UP ) { l = first_defined([l, h, 1]); r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10); r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10); attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) { - pie_slice(ang=ang, l=l+0.1, r1=r1, r2=r2, anchor=CENTER); + pie_slice(ang=ang, l=l+excess, r1=r1, r2=r2, anchor=CENTER); children(); } } @@ -49,13 +50,13 @@ module angle_pie_mask( // Module: cylinder_mask() // Usage: Mask objects -// cylinder_mask(l, r|d, chamfer, [chamfang], [from_end], [circum], [overage], [ends_only]); -// cylinder_mask(l, r|d, rounding, [circum], [overage], [ends_only]); -// cylinder_mask(l, r|d, [chamfer1|rounding1], [chamfer2|rounding2], [chamfang1], [chamfang2], [from_end], [circum], [overage], [ends_only]); +// cylinder_mask(l, r|d, chamfer, [chamfang], [from_end], [circum], [excess], [ends_only]); +// cylinder_mask(l, r|d, rounding, [circum], [excess], [ends_only]); +// cylinder_mask(l, r|d, [chamfer1|rounding1], [chamfer2|rounding2], [chamfang1], [chamfang2], [from_end], [circum], [excess], [ends_only]); // Usage: Masking operators -// cylinder_mask(l, r|d, chamfer, [chamfang], [from_end], [circum], [overage], [ends_only]) ... -// cylinder_mask(l, r|d, rounding, [circum], [overage], [ends_only]) ... -// cylinder_mask(l, r|d, [chamfer1|rounding1], [chamfer2|rounding2], [chamfang1], [chamfang2], [from_end], [circum], [overage], [ends_only]) ... +// cylinder_mask(l, r|d, chamfer, [chamfang], [from_end], [circum], [excess], [ends_only]) ... +// cylinder_mask(l, r|d, rounding, [circum], [excess], [ends_only]) ... +// cylinder_mask(l, r|d, [chamfer1|rounding1], [chamfer2|rounding2], [chamfang1], [chamfang2], [from_end], [circum], [excess], [ends_only]) ... // Description: // If passed children, bevels/chamfers and/or rounds one or both // ends of the origin-centered cylindrical region specified. If @@ -83,7 +84,7 @@ module angle_pie_mask( // rounding2 = The radius of the rounding on the axis-positive end of the region. // circum = If true, region will circumscribe the circle of the given radius/diameter. // from_end = If true, chamfer/bevel size is measured from end of region. If false, chamfer/bevel is measured outset from the radius of the region. (Default: false) -// overage = The extra thickness of the mask. Default: `10`. +// excess = The extra thickness of the mask. Default: `10`. // ends_only = If true, only mask the ends and not around the middle of the cylinder. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` @@ -105,7 +106,7 @@ module cylinder_mask( chamfang=undef, chamfang1=undef, chamfang2=undef, rounding=undef, rounding1=undef, rounding2=undef, circum=false, from_end=false, - overage=10, ends_only=false, + excess=10, ends_only=false, anchor=CENTER, spin=0, orient=UP ) { r1 = get_radius(r=r, d=d, r1=r1, d1=d1, dflt=1); @@ -132,12 +133,12 @@ module cylinder_mask( chlen1 = cham1 / (from_end? 1 : tan(ang1)); chlen2 = cham2 / (from_end? 1 : tan(ang2)); if (!ends_only) { - cylinder(r=maxd+overage, h=l+2*overage, center=true); + cylinder(r=maxd+excess, h=l+2*excess, center=true); } else { - if (cham2>0) up(l/2-chlen2) cylinder(r=maxd+overage, h=chlen2+overage, center=false); - if (cham1>0) down(l/2+overage) cylinder(r=maxd+overage, h=chlen1+overage, center=false); - if (fil2>0) up(l/2-fil2) cylinder(r=maxd+overage, h=fil2+overage, center=false); - if (fil1>0) down(l/2+overage) cylinder(r=maxd+overage, h=fil1+overage, center=false); + if (cham2>0) up(l/2-chlen2) cylinder(r=maxd+excess, h=chlen2+excess, center=false); + if (cham1>0) down(l/2+excess) cylinder(r=maxd+excess, h=chlen1+excess, center=false); + if (fil2>0) up(l/2-fil2) cylinder(r=maxd+excess, h=fil2+excess, center=false); + if (fil1>0) down(l/2+excess) cylinder(r=maxd+excess, h=fil1+excess, center=false); } } cyl(r1=sc*r1, r2=sc*r2, l=l, chamfer1=cham1, chamfer2=cham2, chamfang1=ang1, chamfang2=ang2, from_end=from_end, rounding1=fil1, rounding2=fil2); @@ -154,14 +155,15 @@ module cylinder_mask( // Module: chamfer_mask() // Usage: -// chamfer_mask(l, chamfer); +// chamfer_mask(l, chamfer, [excess]); // Description: // Creates a shape that can be used to chamfer a 90 degree edge. // Difference it from the object to be chamfered. The center of // the mask object should align exactly with the edge to be chamfered. // Arguments: // l = Length of mask. -// chamfer = Size of chamfer +// chamfer = Size of chamfer. +// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1` // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` @@ -170,9 +172,9 @@ module cylinder_mask( // cube(50, anchor=BOTTOM+FRONT); // #chamfer_mask(l=50, chamfer=10, orient=RIGHT); // } -module chamfer_mask(l=1, chamfer=1, anchor=CENTER, spin=0, orient=UP) { +module chamfer_mask(l=1, chamfer=1, excess=0.1, anchor=CENTER, spin=0, orient=UP) { attachable(anchor,spin,orient, size=[chamfer*2, chamfer*2, l]) { - cylinder(r=chamfer, h=l+0.1, center=true, $fn=4); + cylinder(r=chamfer, h=l+excess, center=true, $fn=4); children(); } } @@ -180,14 +182,15 @@ module chamfer_mask(l=1, chamfer=1, anchor=CENTER, spin=0, orient=UP) { // Module: chamfer_mask_x() // Usage: -// chamfer_mask_x(l, chamfer, [anchor]); +// chamfer_mask_x(l, chamfer, [excess]); // Description: // Creates a shape that can be used to chamfer a 90 degree edge along the X axis. // Difference it from the object to be chamfered. The center of the mask // object should align exactly with the edge to be chamfered. // Arguments: -// l = Height of mask -// chamfer = size of chamfer +// l = Length of mask. +// chamfer = Size of chamfer. +// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1` // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the X axis after anchor. See [spin](attachments.scad#spin). Default: `0` // Example: @@ -195,21 +198,22 @@ module chamfer_mask(l=1, chamfer=1, anchor=CENTER, spin=0, orient=UP) { // cube(50, anchor=BOTTOM+FRONT); // #chamfer_mask_x(l=50, chamfer=10); // } -module chamfer_mask_x(l=1.0, chamfer=1.0, anchor=CENTER, spin=0) { - chamfer_mask(l=l, chamfer=chamfer, anchor=anchor, spin=spin, orient=RIGHT) children(); +module chamfer_mask_x(l=1.0, chamfer=1.0, excess=0.1, anchor=CENTER, spin=0) { + chamfer_mask(l=l, chamfer=chamfer, excess=excess, anchor=anchor, spin=spin, orient=RIGHT) children(); } // Module: chamfer_mask_y() // Usage: -// chamfer_mask_y(l, chamfer, [anchor]); +// chamfer_mask_y(l, chamfer, [excess]); // Description: // Creates a shape that can be used to chamfer a 90 degree edge along the Y axis. // Difference it from the object to be chamfered. The center of the mask // object should align exactly with the edge to be chamfered. // Arguments: -// l = Height of mask -// chamfer = size of chamfer +// l = Length of mask. +// chamfer = Size of chamfer. +// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1` // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Y axis after anchor. See [spin](attachments.scad#spin). Default: `0` // Example: @@ -217,21 +221,22 @@ module chamfer_mask_x(l=1.0, chamfer=1.0, anchor=CENTER, spin=0) { // cube(50, anchor=BOTTOM+RIGHT); // #chamfer_mask_y(l=50, chamfer=10); // } -module chamfer_mask_y(l=1.0, chamfer=1.0, anchor=CENTER, spin=0) { - chamfer_mask(l=l, chamfer=chamfer, anchor=anchor, spin=spin, orient=BACK) children(); +module chamfer_mask_y(l=1.0, chamfer=1.0, excess=0.1, anchor=CENTER, spin=0) { + chamfer_mask(l=l, chamfer=chamfer, excess=excess, anchor=anchor, spin=spin, orient=BACK) children(); } // Module: chamfer_mask_z() // Usage: -// chamfer_mask_z(l, chamfer, [anchor]); +// chamfer_mask_z(l, chamfer, [excess]); // Description: // Creates a shape that can be used to chamfer a 90 degree edge along the Z axis. // Difference it from the object to be chamfered. The center of the mask // object should align exactly with the edge to be chamfered. // Arguments: -// l = Height of mask -// chamfer = size of chamfer +// l = Length of mask. +// chamfer = Size of chamfer. +// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1` // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // Example: @@ -239,8 +244,8 @@ module chamfer_mask_y(l=1.0, chamfer=1.0, anchor=CENTER, spin=0) { // cube(50, anchor=FRONT+RIGHT); // #chamfer_mask_z(l=50, chamfer=10); // } -module chamfer_mask_z(l=1.0, chamfer=1.0, anchor=CENTER, spin=0) { - chamfer_mask(l=l, chamfer=chamfer, anchor=anchor, spin=spin, orient=UP) children(); +module chamfer_mask_z(l=1.0, chamfer=1.0, excess=0.1, anchor=CENTER, spin=0) { + chamfer_mask(l=l, chamfer=chamfer, excess=excess, anchor=anchor, spin=spin, orient=UP) children(); } @@ -313,7 +318,7 @@ module chamfer_cylinder_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=fa // Module: chamfer_hole_mask() // Usage: -// chamfer_hole_mask(r|d, chamfer, [ang], [from_end]); +// chamfer_hole_mask(r|d, chamfer, [ang], [from_end], [excess]); // Description: // Create a mask that can be used to bevel/chamfer the end of a cylindrical hole. // Difference it from the hole to be chamfered. The center of the mask object @@ -324,7 +329,7 @@ module chamfer_cylinder_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=fa // chamfer = Size of the chamfer. (Default: 0.25) // ang = Angle of chamfer in degrees from vertical. (Default: 45) // from_end = If true, chamfer size is measured from end of hole. If false, chamfer is measured outset from the radius of the hole. (Default: false) -// overage = The extra thickness of the mask. Default: `0.1`. +// excess = The extra thickness of the mask. Default: `0.1`. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` @@ -341,8 +346,8 @@ module chamfer_cylinder_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=fa // up(50) chamfer_hole_mask(d=50, chamfer=10); // } // Example: -// chamfer_hole_mask(d=100, chamfer=25, ang=30, overage=10); -module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, overage=0.1, anchor=CENTER, spin=0, orient=UP) +// chamfer_hole_mask(d=100, chamfer=25, ang=30, excess=10); +module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, excess=0.1, anchor=CENTER, spin=0, orient=UP) { r = get_radius(r=r, d=d, dflt=1); h = chamfer * (from_end? 1 : tan(90-ang)); @@ -350,7 +355,7 @@ module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, $fn = segs(r); attachable(anchor,spin,orient, r1=r, r2=r2, l=h*2) { union() { - cylinder(r=r2, h=overage, center=false); + cylinder(r=r2, h=excess, center=false); down(h) cylinder(r1=r, r2=r2, h=h, center=false); } children(); @@ -735,14 +740,14 @@ module rounding_corner_mask(r=1.0, anchor=CENTER, spin=0, orient=UP) // } module rounding_cylinder_mask(r=1.0, rounding=0.25) { - cylinder_mask(l=rounding*3, r=r, rounding2=rounding, overage=rounding, ends_only=true, anchor=TOP); + cylinder_mask(l=rounding*3, r=r, rounding2=rounding, excess=rounding, ends_only=true, anchor=TOP); } // Module: rounding_hole_mask() // Usage: -// rounding_hole_mask(r|d, rounding); +// rounding_hole_mask(r|d, rounding, [excess]); // Description: // Create a mask that can be used to round the edge of a circular hole. // Difference it from the hole to be rounded. The center of the @@ -752,7 +757,7 @@ module rounding_cylinder_mask(r=1.0, rounding=0.25) // r = Radius of hole. // d = Diameter of hole to rounding. // rounding = Radius of the rounding. (Default: 0.25) -// overage = The extra thickness of the mask. Default: `0.1`. +// excess = The extra thickness of the mask. Default: `0.1`. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` @@ -770,13 +775,13 @@ module rounding_cylinder_mask(r=1.0, rounding=0.25) // } // Example: // rounding_hole_mask(r=40, rounding=20, $fa=2, $fs=2); -module rounding_hole_mask(r=undef, d=undef, rounding=0.25, overage=0.1, anchor=CENTER, spin=0, orient=UP) +module rounding_hole_mask(r=undef, d=undef, rounding=0.25, excess=0.1, anchor=CENTER, spin=0, orient=UP) { r = get_radius(r=r, d=d, dflt=1); attachable(anchor,spin,orient, r=r+rounding, l=2*rounding) { rotate_extrude(convexity=4) { difference() { - right(r-overage) fwd(rounding) square(rounding+overage, center=false); + right(r-excess) fwd(rounding) square(rounding+excess, center=false); right(r+rounding) fwd(rounding) circle(r=rounding); } } diff --git a/version.scad b/version.scad index 1420f58..627ee3d 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,405]; +BOSL_VERSION = [2,0,406]; // Section: BOSL Library Version Functions From 5e981fb4a716f9fe2ab3dcf6c9f7245fc7d752c4 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 18 Aug 2020 19:25:05 -0700 Subject: [PATCH 2/6] Added tests for path3d() and path4d() with fille= --- tests/test_coords.scad | 4 ++++ version.scad | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_coords.scad b/tests/test_coords.scad index 4b89c12..9fccd65 100644 --- a/tests/test_coords.scad +++ b/tests/test_coords.scad @@ -29,6 +29,7 @@ test_point3d(); module test_path3d() { assert(path3d([[1,2], [3,4], [5,6], [7,8]])==[[1,2,0],[3,4,0],[5,6,0],[7,8,0]]); + assert(path3d([[1,2], [3,4], [5,6], [7,8]],9)==[[1,2,9],[3,4,9],[5,6,9],[7,8,9]]); assert(path3d([[1,2,3], [2,3,4], [3,4,5], [4,5,6]])==[[1,2,3],[2,3,4],[3,4,5],[4,5,6]]); assert(path3d([[1,2,3,4], [2,3,4,5], [3,4,5,6], [4,5,6,7]])==[[1,2,3],[2,3,4],[3,4,5],[4,5,6]]); } @@ -41,6 +42,9 @@ module test_point4d() { assert(point4d([1,2,3])==[1,2,3,0]); assert(point4d([2,3])==[2,3,0,0]); assert(point4d([1])==[1,0,0,0]); + assert(point4d([1,2,3],9)==[1,2,3,9]); + assert(point4d([2,3],9)==[2,3,9,9]); + assert(point4d([1],9)==[1,9,9,9]); } test_point4d(); diff --git a/version.scad b/version.scad index 627ee3d..3d457d6 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,406]; +BOSL_VERSION = [2,0,407]; // Section: BOSL Library Version Functions From e5a0a3cad795206d7832354b73274cf7d78ef94f Mon Sep 17 00:00:00 2001 From: Kelvie Wong Date: Sat, 22 Aug 2020 16:09:28 -0700 Subject: [PATCH 3/6] Fix diff/intersect when some tags are hidden When the first operand is hidden, some strange things happen, so this guards against that. Example: include part_to_show = "A"; // [A,B,C] part_to_hide = "B"; // [A,B,C] module my_part() { tags("A") left(10) cuboid(10); tags("B") diff("neg", "B") right(10) { cuboid(10); cyl(d=10, h=10, $tags="neg"); } tags("C") fwd(10) cuboid(10); } show(part_to_show) hide(part_to_hide) my_part(); Tag "B" here acts very strangely when it is supposed to be hidden, and never hides completely. --- attachments.scad | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/attachments.scad b/attachments.scad index c2c969e..48ed522 100644 --- a/attachments.scad +++ b/attachments.scad @@ -1235,17 +1235,20 @@ module show(tags="") // } module diff(neg, pos=undef, keep=undef) { - difference() { - if (pos != undef) { - show(pos) children(); - } else { - if (keep == undef) { - hide(neg) children(); + // Don't perform the operation if the current tags are hidden + if (attachment_is_shown($tags)) { + difference() { + if (pos != undef) { + show(pos) children(); } else { - hide(str(neg," ",keep)) children(); + if (keep == undef) { + hide(neg) children(); + } else { + hide(str(neg," ",keep)) children(); + } } + show(neg) children(); } - show(neg) children(); } if (keep!=undef) { show(keep) children(); @@ -1280,17 +1283,20 @@ module diff(neg, pos=undef, keep=undef) // } module intersect(a, b=undef, keep=undef) { - intersection() { - if (b != undef) { - show(b) children(); - } else { - if (keep == undef) { - hide(a) children(); + // Don't perform the operation if the current tags are hidden + if (attachment_is_shown($tags)) { + intersection() { + if (b != undef) { + show(b) children(); } else { - hide(str(a," ",keep)) children(); + if (keep == undef) { + hide(a) children(); + } else { + hide(str(a," ",keep)) children(); + } } + show(a) children(); } - show(a) children(); } if (keep!=undef) { show(keep) children(); From 90e02ad7a4bd17d74fb7c6fa9702164fa44fe15b Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 26 Aug 2020 15:59:35 -0700 Subject: [PATCH 4/6] Fix for #243. Added better docs and asserts to rot() --- transforms.scad | 28 +++++++++++++++------------- version.scad | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/transforms.scad b/transforms.scad index 965f5de..e9cfafd 100644 --- a/transforms.scad +++ b/transforms.scad @@ -306,11 +306,11 @@ function up(z=0,p=undef) = move([0,0,z],p=p); // * Called as a function with a `p` argument containing a list of points, returns the list of rotated points. // * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch. // * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF. -// * Called as a function without a `p` argument, and `planar` is true, returns the affine2d rotational matrix. +// * Called as a function without a `p` argument, and `planar` is true, returns the affine2d rotational matrix. Requires that `a` is a finite scalar. // * Called as a function without a `p` argument, and `planar` is false, returns the affine3d rotational matrix. // // Arguments: -// a = Scalar angle or vector of XYZ rotation angles to rotate by, in degrees. +// a = Scalar angle or vector of XYZ rotation angles to rotate by, in degrees. If `planar` is true and `p` is not given, then `a` must be a finite scalar. Default: `0` // v = vector for the axis of rotation. Default: [0,0,1] or UP // cp = centerpoint to rotate around. Default: [0,0,0] // from = Starting vector for vector-based rotations. @@ -343,16 +343,21 @@ module rot(a=0, v=undef, cp=undef, from=undef, to=undef, reverse=false) function rot(a=0, v, cp, from, to, reverse=false, planar=false, p, _m) = assert(is_undef(from)==is_undef(to), "from and to must be specified together.") + assert(is_undef(from) || is_vector(from, zero=false), "'from' must be a non-zero vector.") + assert(is_undef(to) || is_vector(to, zero=false), "'to' must be a non-zero vector.") + assert(is_undef(v) || is_vector(v, zero=false), "'v' must be a non-zero vector.") + assert(is_undef(cp) || is_vector(cp), "'cp' must be a vector.") + assert(is_finite(a) || is_vector(a), "'a' must be a finite scalar or a vector.") + assert(is_bool(reverse)) + assert(is_bool(planar)) is_undef(p)? ( planar? let( + check = assert(is_num(a)), cp = is_undef(cp)? cp : point2d(cp), m1 = is_undef(from)? affine2d_zrot(a) : - assert(is_vector(from)) - assert(!approx(norm(from),0)) - assert(approx(point3d(from).z, 0)) - assert(is_vector(to)) - assert(!approx(norm(to),0)) - assert(approx(point3d(to).z, 0)) + assert(a==0, "'from' and 'to' cannot be used with 'a' when 'planar' is true.") + assert(approx(point3d(from).z, 0), "'from' must be a 2D vector when 'planar' is true.") + assert(approx(point3d(to).z, 0), "'to' must be a 2D vector when 'planar' is true.") affine2d_zrot( vang(point2d(to)) - vang(point2d(from)) @@ -364,13 +369,10 @@ function rot(a=0, v, cp, from, to, reverse=false, planar=false, p, _m) = to = is_undef(to)? undef : point3d(to), cp = is_undef(cp)? undef : point3d(cp), m1 = !is_undef(from)? ( - assert(is_vector(from)) - assert(!approx(norm(from),0)) - assert(is_vector(to)) - assert(!approx(norm(to),0)) + assert(is_num(a)) affine3d_rot_from_to(from,to) * affine3d_zrot(a) ) : - !is_undef(v)? affine3d_rot_by_axis(v,a) : + !is_undef(v)? assert(is_num(a)) affine3d_rot_by_axis(v,a) : is_num(a)? affine3d_zrot(a) : affine3d_zrot(a.z) * affine3d_yrot(a.y) * affine3d_xrot(a.x), m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)), diff --git a/version.scad b/version.scad index 3d457d6..f2624a8 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,407]; +BOSL_VERSION = [2,0,408]; // Section: BOSL Library Version Functions From b679ea52dc52aa9098eb9d0c5c81e4e5608f76ca Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 26 Aug 2020 18:02:16 -0700 Subject: [PATCH 5/6] Add is_zero(), is_positive(), is_negative(), is_nonpositive(), is_nonnegative(). --- math.scad | 119 ++++++++++++++++++++++++++++++++++++++++++- tests/test_math.scad | 102 ++++++++++++++++++++++++++++++++++++- version.scad | 2 +- 3 files changed, 220 insertions(+), 3 deletions(-) diff --git a/math.scad b/math.scad index 888d048..90182f1 100644 --- a/math.scad +++ b/math.scad @@ -864,6 +864,123 @@ function is_matrix(A,m,n,square=false) = // Section: Comparisons and Logic +// Function: is_zero() +// Usage: +// is_zero(x); +// Description: +// Returns true if the number passed to it is approximately zero, to within `eps`. +// If passed a list, recursively checks if all items in the list are approximately zero. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// eps = The maximum allowed variance. Default: `EPSILON` (1e-9) +// Example: +// is_zero(0); // Returns: true. +// is_zero(1e-3); // Returns: false. +// is_zero([0,0,0]); // Returns: true. +// is_zero([0,0,1e-3]); // Returns: false. +function is_zero(x, eps=EPSILON) = + is_list(x)? (x != [] && [for (xx=x) if(!is_zero(xx,eps=eps)) 1] == []) : + is_num(x)? approx(x,eps) : + false; + + +// Function: is_positive() +// Usage: +// is_positive(x); +// Description: +// Returns true if the number passed to it is greater than zero. +// If passed a list, recursively checks if all items in the list are positive. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// Example: +// is_positive(-2); // Returns: false. +// is_positive(0); // Returns: false. +// is_positive(2); // Returns: true. +// is_positive([0,0,0]); // Returns: false. +// is_positive([0,1,2]); // Returns: false. +// is_positive([3,1,2]); // Returns: true. +// is_positive([3,-1,2]); // Returns: false. +function is_positive(x) = + is_list(x)? (x != [] && [for (xx=x) if(!is_positive(xx)) 1] == []) : + is_num(x)? x>0 : + false; + + +// Function: is_negative() +// Usage: +// is_negative(x); +// Description: +// Returns true if the number passed to it is less than zero. +// If passed a list, recursively checks if all items in the list are negative. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// Example: +// is_negative(-2); // Returns: true. +// is_negative(0); // Returns: false. +// is_negative(2); // Returns: false. +// is_negative([0,0,0]); // Returns: false. +// is_negative([0,1,2]); // Returns: false. +// is_negative([3,1,2]); // Returns: false. +// is_negative([3,-1,2]); // Returns: false. +// is_negative([-3,-1,-2]); // Returns: true. +function is_negative(x) = + is_list(x)? (x != [] && [for (xx=x) if(!is_negative(xx)) 1] == []) : + is_num(x)? x<0 : + false; + + +// Function: is_nonpositive() +// Usage: +// is_nonpositive(x); +// Description: +// Returns true if the number passed to it is less than or equal to zero. +// If passed a list, recursively checks if all items in the list are nonpositive. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// Example: +// is_nonpositive(-2); // Returns: true. +// is_nonpositive(0); // Returns: true. +// is_nonpositive(2); // Returns: false. +// is_nonpositive([0,0,0]); // Returns: true. +// is_nonpositive([0,1,2]); // Returns: false. +// is_nonpositive([3,1,2]); // Returns: false. +// is_nonpositive([3,-1,2]); // Returns: false. +// is_nonpositive([-3,-1,-2]); // Returns: true. +function is_nonpositive(x) = + is_list(x)? (x != [] && [for (xx=x) if(!is_nonpositive(xx)) 1] == []) : + is_num(x)? x<=0 : + false; + + +// Function: is_nonnegative() +// Usage: +// is_nonnegative(x); +// Description: +// Returns true if the number passed to it is greater than or equal to zero. +// If passed a list, recursively checks if all items in the list are nonnegative. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// Example: +// is_nonnegative(-2); // Returns: false. +// is_nonnegative(0); // Returns: true. +// is_nonnegative(2); // Returns: true. +// is_nonnegative([0,0,0]); // Returns: true. +// is_nonnegative([0,1,2]); // Returns: true. +// is_nonnegative([0,-1,-2]); // Returns: false. +// is_nonnegative([3,1,2]); // Returns: true. +// is_nonnegative([3,-1,2]); // Returns: false. +// is_nonnegative([-3,-1,-2]); // Returns: false. +function is_nonnegative(x) = + is_list(x)? (x != [] && [for (xx=x) if(!is_nonnegative(xx)) 1] == []) : + is_num(x)? x>=0 : + false; + + // Function: approx() // Usage: // approx(a,b,[eps]) @@ -1382,4 +1499,4 @@ function real_roots(p,eps=undef,tol=1e-14) = ? [for(z=roots) if (abs(z.y)/(1+norm(z)) Date: Wed, 26 Aug 2020 20:39:45 -0700 Subject: [PATCH 6/6] Implement Issue #2. Added diameter alternates for most radius options. --- beziers.scad | 14 ++++---- distributors.scad | 6 ++-- masks.scad | 87 ++++++++++++++++++++++++++++++----------------- mutators.scad | 2 +- paths.scad | 16 ++++++--- polyhedra.scad | 3 +- shapes.scad | 14 ++++---- version.scad | 2 +- 8 files changed, 89 insertions(+), 55 deletions(-) diff --git a/beziers.scad b/beziers.scad index 13a74ab..a8484b6 100644 --- a/beziers.scad +++ b/beziers.scad @@ -356,7 +356,7 @@ function bezier_segment_length(curve, start_u=0, end_u=1, max_deflect=0.01) = // Function: fillet3pts() // Usage: -// fillet3pts(p0, p1, p2, r); +// fillet3pts(p0, p1, p2, r|d); // Description: // Takes three points, defining two line segments, and works out the // cubic (degree 3) bezier segment (and surrounding control points) @@ -368,7 +368,8 @@ function bezier_segment_length(curve, start_u=0, end_u=1, max_deflect=0.01) = // p1 = The middle point. // p2 = The ending point. // r = The radius of the fillet/rounding. -// maxerr = Max amount bezier curve should diverge from actual radius curve. Default: 0.1 +// d = The diameter of the fillet/rounding. +// maxerr = Max amount bezier curve should diverge from actual curve. Default: 0.1 // Example(2D): // p0 = [40, 0]; // p1 = [0, 0]; @@ -376,7 +377,8 @@ function bezier_segment_length(curve, start_u=0, end_u=1, max_deflect=0.01) = // trace_polyline([p0,p1,p2], showpts=true, size=0.5, color="green"); // fbez = fillet3pts(p0,p1,p2, 10); // trace_bezier(slice(fbez, 1, -2), size=1); -function fillet3pts(p0, p1, p2, r, maxerr=0.1, w=0.5, dw=0.25) = let( +function fillet3pts(p0, p1, p2, r, d, maxerr=0.1, w=0.5, dw=0.25) = let( + r = get_radius(r=r,d=d), v0 = unit(p0-p1), v1 = unit(p2-p1), midv = unit((v0+v1)/2), @@ -391,8 +393,8 @@ function fillet3pts(p0, p1, p2, r, maxerr=0.1, w=0.5, dw=0.25) = let( bp = bezier_points([tp0, cp0, cp1, tp1], 0.5), tdist = norm(cp-bp) ) (abs(tdist-cpr) <= maxerr)? [tp0, tp0, cp0, cp1, tp1, tp1] : - (tdist0 && angle<90); diff --git a/mutators.scad b/mutators.scad index b801e48..4db899b 100644 --- a/mutators.scad +++ b/mutators.scad @@ -321,7 +321,7 @@ module chain_hull() // Usage: // cylindrical_extrude(size, ir|id, or|od, [convexity]) ... // Description: -// Cylindrically extrudes all 2D children, curved around a cylidrical shape. +// Extrudes all 2D children outwards, curved around a cylindrical shape. // Arguments: // or = The outer radius to extrude to. // od = The outer diameter to extrude to. diff --git a/paths.scad b/paths.scad index 93d82fa..60bc34e 100644 --- a/paths.scad +++ b/paths.scad @@ -418,7 +418,7 @@ function path_torsion(path, closed=false) = // scale = [X,Y] scaling factors for each axis. Default: `[1,1]` // Example(3D): // trace_polyline(path3d_spiral(turns=2.5, h=100, n=24, r=50), N=1, showpts=true); -function path3d_spiral(turns=3, h=100, n=12, r=undef, d=undef, cp=[0,0], scale=[1,1]) = let( +function path3d_spiral(turns=3, h=100, n=12, r, d, cp=[0,0], scale=[1,1]) = let( rr=get_radius(r=r, d=d, dflt=100), cnt=floor(turns*n), dz=h/cnt @@ -774,15 +774,19 @@ function assemble_path_fragments(fragments, eps=EPSILON, _finished=[]) = // Module: modulated_circle() +// Usage: +// modulated_circle(r|d, sines); // Description: // Creates a 2D polygon circle, modulated by one or more superimposed sine waves. // Arguments: -// r = radius of the base circle. +// r = Radius of the base circle. Default: 40 +// d = Diameter of the base circle. // sines = array of [amplitude, frequency] pairs, where the frequency is the number of times the cycle repeats around the circle. // Example(2D): // modulated_circle(r=40, sines=[[3, 11], [1, 31]], $fn=6); -module modulated_circle(r=40, sines=[10]) +module modulated_circle(r, sines=[10], d) { + r = get_radius(r=r, d=d, dflt=40); freqs = len(sines)>0? [for (i=sines) i[1]] : [5]; points = [ for (a = [0 : (360/segs(r)/max(freqs)) : 360]) @@ -829,7 +833,8 @@ module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slic // Arguments: // polyline = Array of points of a polyline path, to be extruded. // h = height of the spiral to extrude along. -// r = radius of the spiral to extrude along. +// r = Radius of the spiral to extrude along. Default: 50 +// d = Diameter of the spiral to extrude along. // twist = number of degrees of rotation to spiral up along height. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` @@ -838,7 +843,8 @@ module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slic // Example: // poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]]; // spiral_sweep(poly, h=200, r=50, twist=1080, $fn=36); -module spiral_sweep(polyline, h, r, twist=360, center, anchor, spin=0, orient=UP) { +module spiral_sweep(polyline, h, r, twist=360, center, d, anchor, spin=0, orient=UP) { + r = get_radius(r=r, d=d, dflt=50); polyline = path3d(polyline); pline_count = len(polyline); steps = ceil(segs(r)*(twist/360)); diff --git a/polyhedra.scad b/polyhedra.scad index e845acd..3231606 100644 --- a/polyhedra.scad +++ b/polyhedra.scad @@ -730,9 +730,10 @@ function stellate_faces(scalefactor,stellate,vertices,faces_normals) = ) [newfaces, normals, allpts]; -function trapezohedron(faces, r, side, longside, h) = +function trapezohedron(faces, r, side, longside, h, d) = assert(faces%2==0, "Number of faces must be even") let( + r = get_radius(r=r, d=d, dflt=1), N = faces/2, parmcount = num_defined([r,side,longside,h]) ) diff --git a/shapes.scad b/shapes.scad index d612b0a..b010121 100644 --- a/shapes.scad +++ b/shapes.scad @@ -1498,13 +1498,14 @@ module pie_slice( // Center this part along the concave edge to be chamfered and union it in. // // Usage: -// interior_fillet(l, r, [ang], [overlap]); +// interior_fillet(l, r|d, [ang], [overlap]); // // Arguments: -// l = length of edge to fillet. -// r = radius of fillet. -// ang = angle between faces to fillet. -// overlap = overlap size for unioning with faces. +// l = Length of edge to fillet. +// r = Radius of fillet. +// d = Diameter of fillet. +// ang = Angle between faces to fillet. +// overlap = Overlap size for unioning with faces. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `FRONT+LEFT` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` @@ -1526,7 +1527,8 @@ module pie_slice( // position(BOT+FRONT) // interior_fillet(l=50, r=10, spin=180, orient=RIGHT); // } -module interior_fillet(l=1.0, r=1.0, ang=90, overlap=0.01, anchor=FRONT+LEFT, spin=0, orient=UP) { +module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=FRONT+LEFT, spin=0, orient=UP) { + r = get_radius(r=r, d=d, dflt=1); dy = r/tan(ang/2); steps = ceil(segs(r)*ang/360); step = ang/steps; diff --git a/version.scad b/version.scad index c2e6cda..d10b338 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,409]; +BOSL_VERSION = [2,0,410]; // Section: BOSL Library Version Functions