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/math.scad b/math.scad index e5c4625..509db44 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 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/tests/test_math.scad b/tests/test_math.scad index 12f7d2e..c3eb601 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 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..d10b338 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,407]; +BOSL_VERSION = [2,0,410]; // Section: BOSL Library Version Functions