diff --git a/attachments.scad b/attachments.scad index c2c969e6..48ed522a 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(); diff --git a/beziers.scad b/beziers.scad index 13a74abc..a8484b62 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) 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(); @@ -363,8 +368,8 @@ module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, // Module: rounding_mask() // Usage: -// rounding_mask(l|h, r) -// rounding_mask(l|h, r1, r2) +// rounding_mask(l|h, r|d) +// rounding_mask(l|h, r1|d1, r2|d2) // Description: // Creates a shape that can be used to round a vertical 90 degree edge. // Difference it from the object to be rounded. The center of the mask @@ -374,6 +379,9 @@ module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, // r = Radius of the rounding. // r1 = Bottom radius of rounding. // r2 = Top radius of rounding. +// d = Diameter of the rounding. +// d1 = Bottom diameter of rounding. +// d2 = Top diameter of rounding. // 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` @@ -404,11 +412,11 @@ module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, // rounding_mask(l=p.x, r=25, spin=45, orient=RIGHT); // } // } -module rounding_mask(l=undef, r=undef, r1=undef, r2=undef, anchor=CENTER, spin=0, orient=UP, h=undef) +module rounding_mask(l, r, r1, r2, d, d1, d2, anchor=CENTER, spin=0, orient=UP, h=undef) { l = first_defined([l, h, 1]); - r1 = get_radius(r1=r1, r=r, dflt=1); - r2 = get_radius(r1=r2, r=r, dflt=1); + r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1); + r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1); sides = quantup(segs(max(r1,r2)),4); attachable(anchor,spin,orient, size=[2*r1,2*r1,l], size2=[2*r2,2*r2]) { if (r10 && angle<90); diff --git a/math.scad b/math.scad index 888d0485..90182f1d 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))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 e845acd7..3231606b 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 d612b0a2..b0101213 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/tests/test_coords.scad b/tests/test_coords.scad index 4b89c123..9fccd658 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/tests/test_math.scad b/tests/test_math.scad index 12f7d2ec..c3eb6011 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -100,6 +100,106 @@ module test_is_matrix() { test_is_matrix(); +module test_is_zero() { + assert(is_zero(0)); + assert(is_zero([0,0,0])); + assert(is_zero([[0,0,0],[0,0]])); + assert(is_zero([EPSILON/2,EPSILON/2,EPSILON/2])); + assert(!is_zero(1e-3)); + assert(!is_zero([0,0,1e-3])); + assert(!is_zero([EPSILON*10,0,0])); + assert(!is_zero([0,EPSILON*10,0])); + assert(!is_zero([0,0,EPSILON*10])); + assert(!is_zero(true)); + assert(!is_zero(false)); + assert(!is_zero(INF)); + assert(!is_zero(-INF)); + assert(!is_zero(NAN)); + assert(!is_zero("foo")); + assert(!is_zero([])); + assert(!is_zero([0:1:2])); +} +test_is_zero(); + + +module test_is_positive() { + assert(!is_positive(-2)); + assert(!is_positive(0)); + assert(is_positive(2)); + assert(!is_positive([0,0,0])); + assert(!is_positive([0,1,2])); + assert(is_positive([3,1,2])); + assert(!is_positive([3,-1,2])); + assert(!is_positive([])); + assert(!is_positive(true)); + assert(!is_positive(false)); + assert(!is_positive("foo")); + assert(!is_positive([0:1:2])); +} +test_is_positive(); + + +module test_is_negative() { + assert(is_negative(-2)); + assert(!is_negative(0)); + assert(!is_negative(2)); + assert(!is_negative([0,0,0])); + assert(!is_negative([0,1,2])); + assert(!is_negative([3,1,2])); + assert(!is_negative([3,-1,2])); + assert(is_negative([-3,-1,-2])); + assert(!is_negative([-3,1,-2])); + assert(is_negative([[-5,-7],[-3,-1,-2]])); + assert(!is_negative([[-5,-7],[-3,1,-2]])); + assert(!is_negative([])); + assert(!is_negative(true)); + assert(!is_negative(false)); + assert(!is_negative("foo")); + assert(!is_negative([0:1:2])); +} +test_is_negative(); + + +module test_is_nonpositive() { + assert(is_nonpositive(-2)); + assert(is_nonpositive(0)); + assert(!is_nonpositive(2)); + assert(is_nonpositive([0,0,0])); + assert(!is_nonpositive([0,1,2])); + assert(is_nonpositive([0,-1,-2])); + assert(!is_nonpositive([3,1,2])); + assert(!is_nonpositive([3,-1,2])); + assert(!is_nonpositive([])); + assert(!is_nonpositive(true)); + assert(!is_nonpositive(false)); + assert(!is_nonpositive("foo")); + assert(!is_nonpositive([0:1:2])); +} +test_is_nonpositive(); + + +module test_is_nonnegative() { + assert(!is_nonnegative(-2)); + assert(is_nonnegative(0)); + assert(is_nonnegative(2)); + assert(is_nonnegative([0,0,0])); + assert(is_nonnegative([0,1,2])); + assert(is_nonnegative([3,1,2])); + assert(!is_nonnegative([3,-1,2])); + assert(!is_nonnegative([-3,-1,-2])); + assert(!is_nonnegative([[-5,-7],[-3,-1,-2]])); + assert(!is_nonnegative([[-5,-7],[-3,1,-2]])); + assert(!is_nonnegative([[5,7],[3,-1,2]])); + assert(is_nonnegative([[5,7],[3,1,2]])); + assert(!is_nonnegative([])); + assert(!is_nonnegative(true)); + assert(!is_nonnegative(false)); + assert(!is_nonnegative("foo")); + assert(!is_nonnegative([0:1:2])); +} +test_is_nonnegative(); + + module test_approx() { assert_equal(approx(PI, 3.141592653589793236), true); assert_equal(approx(PI, 3.1415926), false); @@ -924,4 +1024,4 @@ module test_poly_add(){ } test_poly_add(); -// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap \ No newline at end of file +// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/transforms.scad b/transforms.scad index 965f5de3..e9cfafd2 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 1420f587..d10b3385 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,405]; +BOSL_VERSION = [2,0,410]; // Section: BOSL Library Version Functions