diff --git a/arrays.scad b/arrays.scad index 7d85d8c..c143001 100644 --- a/arrays.scad +++ b/arrays.scad @@ -200,6 +200,24 @@ function list_tail(list, from=1) = list; +// Function: list() +// Topics: List Handling, Type Conversion +// Usage: +// list = list(l) +// Description: +// Expands a range into a full list. If given a list, returns it verbatim. +// If given a string, explodes it into a list of single letters. +// Arguments: +// l = The value to expand. +// See Also: scalar_vec3(), force_list(), range(), rangex() +// Example: +// l1 = list([3:2:9]); // Returns: [3,5,7,9] +// l2 = list([3,4,5]); // Returns: [3,4,5] +// l3 = list("Foo"); // Returns: ["F","o","o"] +// l4 = list(23); // Returns: [23] +function list(l) = is_list(l)? l : [for (x=l) x]; + + // Function: force_list() // Usage: // list = force_list(value, , ); @@ -274,20 +292,28 @@ function in_list(val,list,idx) = // Description: // Finds the first item in `list` that matches `val`, returning the index. // Arguments: -// val = The value to search for. +// val = The value to search for. If given a function literal of signature `function (x)`, uses that function to check list items. Returns true for a match. // list = The list to search through. // --- // start = The index to start searching from. // all = If true, returns a list of all matching item indices. // eps = The maximum allowed floating point rounding error for numeric comparisons. function find_first_match(val, list, start=0, all=false, eps=EPSILON) = - all? [for (i=[start:1:len(list)-1]) if(val==list[i] || approx(val, list[i], eps=eps)) i] : + all? [ + for (i=[start:1:len(list)-1]) + if ( + (!is_func(val) && approx(val, list[i], eps=eps)) || + (is_func(val) && val(list[i])) + ) i + ] : __find_first_match(val, list, eps=eps, i=start); function __find_first_match(val, list, eps, i=0) = i >= len(list)? undef : - approx(val, list[i], eps=eps)? i : - __find_first_match(val, list, eps=eps, i=i+1); + ( + (!is_func(val) && approx(val, list[i], eps=eps)) || + (is_func(val) && val(list[i])) + )? i : __find_first_match(val, list, eps=eps, i=i+1); // Function: min_index() @@ -425,7 +451,7 @@ function range(n, s=0, e, step) = let( step = (n!=undef && e!=undef)? (e-s)/(n-1) : default(step,1) ) is_undef(e) ? assert( is_consistent([s, step]), "Incompatible data.") - [for (i=[0:1:n-1]) s+step*i ] + [for (i=[0:1:n-1]) s+step*i] : assert( is_vector([s,step,e]), "Start `s`, step `step` and end `e` must be numbers.") [for (v=[s:step:e]) v] ; @@ -468,10 +494,10 @@ function rangex(n, s=0, e, step) = let( step = (n!=undef && e!=undef)? (e-s)/n : default(step,1) ) is_undef(e) ? assert( is_consistent([s, step]), "Incompatible data.") - [for (i=[0:1:n-1]) s+step*i ] + [for (i=[0:1:n-1]) s+step*i] : assert( is_vector([s,step,e]), "Start `s`, step `step` and end `e` must be numbers.") let(steps=floor((e-s)/step+0.5)) - [for (i=[0:1:steps-1]) s+step*i ]; + [for (i=[0:1:steps-1]) s+step*i]; diff --git a/common.scad b/common.scad index ab24380..ba05fc8 100644 --- a/common.scad +++ b/common.scad @@ -185,6 +185,19 @@ function valid_range(x) = : ( x[1]<0 && x[0]>=x[2] ) ); +// Function: is_func() +// Usage: +// bool = is_func(x); +// Description: +// Returns true if OpenSCAD supports function literals, and the given item is one. +// Arguments: +// x = The value to check against. +// Example: +// f = function (a) a==2; +// bool = is_func(f); // Returns: true +function is_func(x) = version_num()>20210000 && is_function(x); + + // Function: is_consistent() // Usage: // bool = is_consistent(list, ); diff --git a/distributors.scad b/distributors.scad index fde784d..588acb8 100644 --- a/distributors.scad +++ b/distributors.scad @@ -40,24 +40,30 @@ module move_copies(a=[[0,0,0]]) } -// Module: line_of() +// Function&Module: line_of() // // Usage: Spread `n` copies by a given spacing -// line_of(spacing, [n], [p1]) ... +// line_of(spacing, , ) ... // Usage: Spread copies every given spacing along the line -// line_of(spacing, l, [p1]) ... +// line_of(spacing, , ) ... // Usage: Spread `n` copies along the length of the line -// line_of(l, [n], [p1]) ... +// line_of(, , ) ... // Usage: Spread `n` copies along the line from `p1` to `p2` -// line_of(p1, p2, [n]) ... +// line_of(, , ) ... // Usage: Spread copies every given spacing, centered along the line from `p1` to `p2` -// line_of(p1, p2, spacing) ... -// +// line_of(, , ) ... +// Usage: As a function +// pts = line_of(, , ); +// pts = line_of(, , ); +// pts = line_of(, , ); +// pts = line_of(, , ); +// pts = line_of(, , ); // Description: -// Copies `children()` at one or more evenly spread positions along a line. By default, the line -// will be centered at the origin, unless the starting point `p1` is given. The line will be -// pointed towards `RIGHT` (X+) unless otherwise given as a vector in `l`, `spacing`, or `p1`/`p2`. -// The spread is specified in one of several ways: +// When called as a function, returns a list of points at evenly spread positions along a line. +// When called as a module, copies `children()` at one or more evenly spread positions along a line. +// By default, the line will be centered at the origin, unless the starting point `p1` is given. +// The line will be pointed towards `RIGHT` (X+) unless otherwise given as a vector in `l`, +// `spacing`, or `p1`/`p2`. The spread is specified in one of several ways: // . // If You Know... | Then Use Something Like... // -------------------------------- | -------------------------------- @@ -74,6 +80,7 @@ module move_copies(a=[[0,0,0]]) // Arguments: // spacing = Either the scalar spacing distance along the X+ direction, or the vector giving both the direction and spacing distance between each set of copies. // n = Number of copies to distribute along the line. (Default: 2) +// --- // l = Either the scalar length of the line, or a vector giving both the direction and length of the line. // p1 = If given, specifies the starting point of the line. // p2 = If given with `p1`, specifies the ending point of line, and indirectly calculates the line length. @@ -102,40 +109,42 @@ module move_copies(a=[[0,0,0]]) // cube(size=[1,3,1],center=true); // cube(size=[3,1,1],center=true); // } +// Example(2D): +// pts = line_of([10,5],n=5); +// move_copies(pts) circle(d=2); module line_of(spacing, n, l, p1, p2) { - assert(is_undef(spacing) || is_finite(spacing) || is_vector(spacing)); - assert(is_undef(n) || is_finite(n)); - assert(is_undef(l) || is_finite(l) || is_vector(l)); - assert(is_undef(p1) || is_vector(p1)); - assert(is_undef(p2) || is_vector(p2)); - ll = ( - !is_undef(l)? scalar_vec3(l, 0) : - (!is_undef(spacing) && !is_undef(n))? (n * scalar_vec3(spacing, 0)) : - (!is_undef(p1) && !is_undef(p2))? point3d(p2-p1) : - undef - ); - cnt = ( - !is_undef(n)? n : - (!is_undef(spacing) && !is_undef(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) : - 2 - ); - spc = ( - cnt<=1? [0,0,0] : - is_undef(spacing)? (ll/(cnt-1)) : - is_num(spacing) && !is_undef(ll)? (ll/(cnt-1)) : - scalar_vec3(spacing, 0) - ); - assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `line_of()`."); - spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc; - for (i=[0:1:cnt-1]) { - pos = i * spc + spos; - $pos = pos; + pts = line_of(spacing=spacing, n=n, l=l, p1=p1, p2=p2); + for (i=idx(pts)) { $idx = i; - translate(pos) children(); + $pos = pts[i]; + translate($pos) children(); } } +function line_of(spacing, n, l, p1, p2) = + assert(is_undef(spacing) || is_finite(spacing) || is_vector(spacing)) + assert(is_undef(n) || is_finite(n)) + assert(is_undef(l) || is_finite(l) || is_vector(l)) + assert(is_undef(p1) || is_vector(p1)) + assert(is_undef(p2) || is_vector(p2)) + let( + ll = !is_undef(l)? scalar_vec3(l, 0) : + (!is_undef(spacing) && !is_undef(n))? (n * scalar_vec3(spacing, 0)) : + (!is_undef(p1) && !is_undef(p2))? point3d(p2-p1) : + undef, + cnt = !is_undef(n)? n : + (!is_undef(spacing) && !is_undef(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) : + 2, + spc = cnt<=1? [0,0,0] : + is_undef(spacing)? (ll/(cnt-1)) : + is_num(spacing) && !is_undef(ll)? (ll/(cnt-1)) : + scalar_vec3(spacing, 0) + ) + assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `line_of()`.") + let( spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc ) + [for (i=[0:1:cnt-1]) i * spc + spos]; + // Module: xcopies() // diff --git a/math.scad b/math.scad index 2297a29..9e4f73a 100644 --- a/math.scad +++ b/math.scad @@ -174,6 +174,31 @@ function lerp(a,b,u) = [for (v = u) (1-v)*a + v*b ]; +// Function: lerpn() +// Usage: +// x = lerpn(a, b, n); +// x = lerpn(a, b, n, ); +// Description: +// Returns exactly `n` values, linearly interpolated between `a` and `b`. +// If `endpoint` is true, then the last value will exactly equal `b`. +// If `endpoint` is false, then the last value will about `a+(b-a)*(1-1/n)`. +// Arguments: +// a = First value or vector. +// b = Second value or vector. +// n = The number of values to return. +// endpoint = If true, the last value will be exactly `b`. If false, the last value will be one step less. +// Examples: +// l = lerpn(-4,4,9); // Returns: [-4,-3,-2,-1,0,1,2,3,4] +// l = lerpn(-4,4,8,false); // Returns: [-4,-3,-2,-1,0,1,2,3] +// l = lerpn(0,1,6); // Returns: [0, 0.2, 0.4, 0.6, 0.8, 1] +// l = lerpn(0,1,5,false); // Returns: [0, 0.2, 0.4, 0.6, 0.8] +function lerpn(a,b,n,endpoint=true) = + assert(same_shape(a,b), "Bad or inconsistent inputs to lerp") + assert(is_int(n)) + assert(is_bool(endpoint)) + let( d = n - (endpoint? 1 : 0) ) + [for (i=[0:1:n-1]) let(u=i/d) (1-u)*a + u*b]; + // Section: Undef Safe Math @@ -434,32 +459,6 @@ function modang(x) = let(xx = posmod(x,360)) xx<180? xx : xx-360; -// Function: modrange() -// Usage: -// modrange(x, y, m, ) -// Description: -// Returns a normalized list of numbers from `x` to `y`, by `step`, modulo `m`. Wraps if `x` > `y`. -// Arguments: -// x = The start value to constrain. -// y = The end value to constrain. -// m = Modulo value. -// step = Step by this amount. -// Examples: -// modrange(90,270,360, step=45); // Returns: [90,135,180,225,270] -// modrange(270,90,360, step=45); // Returns: [270,315,0,45,90] -// modrange(90,270,360, step=-45); // Returns: [90,45,0,315,270] -// modrange(270,90,360, step=-45); // Returns: [270,225,180,135,90] -function modrange(x, y, m, step=1) = - assert( is_finite(x+y+step+m) && !approx(m,0), "Input must be finite numbers and the module value cannot be zero." ) - let( - a = posmod(x, m), - b = posmod(y, m), - c = step>0? (a>b? b+m : b) - : (a=len(l) || succ)? succ : - _any( - l, i+1, - succ = is_list(l[i]) ? _any(l[i]) : !(!l[i]) - ); +function _any_func(l, func, i=0, out=false) = + i >= len(l) || out? out : + _any_func(l, func, i=i+1, out=out || func(l[i])); + +function _any_bool(l, i=0, out=false) = + i >= len(l) || out? out : + _any_bool(l, i=i+1, out=out || l[i]); // Function: all() // Usage: // b = all(l); +// b = all(l,func); // Description: -// Returns true if all items in list `l` evaluate as true. -// If `l` is a lists of lists, `all()` is applied recursively to each sublist. +// Returns true if all items in list `l` evaluate as true. If `func` is given a function liteal +// of signature (x), returning bool, then that function literal is evaluated for each list item. // Arguments: // l = The list to test for true items. +// func = An optional function literal of signature (x), returning bool, to test each list item with. // Example: // all([0,false,undef]); // Returns false. // all([1,false,undef]); // Returns false. // all([1,5,true]); // Returns true. -// all([[0,0], [0,0]]); // Returns false. -// all([[0,0], [1,0]]); // Returns false. +// all([[0,0], [0,0]]); // Returns true. +// all([[0,0], [1,0]]); // Returns true. // all([[1,1], [1,1]]); // Returns true. -function all(l) = - assert( is_list(l), "The input is not a list." ) - _all(l); +function all(l, func) = + assert(is_list(l), "The input is not a list.") + assert(func==undef || is_func(func)) + is_func(func) + ? _all_func(l, func) + : _all_bool(l); -function _all(l, i=0, fail=false) = - (i>=len(l) || fail)? !fail : - _all( - l, i+1, - fail = is_list(l[i]) ? !_all(l[i]) : !l[i] - ) ; +function _all_func(l, func, i=0, out=true) = + i >= len(l) || !out? out : + _all_func(l, func, i=i+1, out=out && func(l[i])); + +function _all_bool(l, i=0, out=true) = + i >= len(l) || !out? out : + _all_bool(l, i=i+1, out=out && l[i]); // Function: count_true() // Usage: -// n = count_true(l) +// n = count_true(l,) +// n = count_true(l,func,) // Description: // Returns the number of items in `l` that evaluate as true. // If `l` is a lists of lists, this is applied recursively to each @@ -1289,24 +1301,38 @@ function _all(l, i=0, fail=false) = // in all recursive sublists. // Arguments: // l = The list to test for true items. -// nmax = If given, stop counting if `nmax` items evaluate as true. +// func = An optional function literal of signature (x), returning bool, to test each list item with. +// --- +// nmax = Max number of true items to count. Default: `undef` (no limit) // Example: // count_true([0,false,undef]); // Returns 0. // count_true([1,false,undef]); // Returns 1. // count_true([1,5,false]); // Returns 2. // count_true([1,5,true]); // Returns 3. -// count_true([[0,0], [0,0]]); // Returns 0. -// count_true([[0,0], [1,0]]); // Returns 1. -// count_true([[1,1], [1,1]]); // Returns 4. -// count_true([[1,1], [1,1]], nmax=3); // Returns 3. -function _count_true_rec(l, nmax, _cnt=0, _i=0) = - _i>=len(l) || (is_num(nmax) && _cnt>=nmax)? _cnt : - _count_true_rec(l, nmax, _cnt=_cnt+(l[_i]?1:0), _i=_i+1); +// count_true([[0,0], [0,0]]); // Returns 2. +// count_true([[0,0], [1,0]]); // Returns 2. +// count_true([[1,1], [1,1]]); // Returns 2. +// count_true([[1,1], [1,1]], nmax=1); // Returns 1. +function count_true(l, func, nmax) = + assert(is_list(l)) + assert(func==undef || is_func(func)) + is_func(func) + ? _count_true_func(l, func, nmax) + : _count_true_bool(l, nmax); -function count_true(l, nmax) = - is_undef(nmax)? len([for (x=l) if(x) 1]) : - !is_list(l) ? ( l? 1: 0) : - _count_true_rec(l, nmax); +function _count_true_func(l, func, nmax, i=0, out=0) = + i >= len(l) || (nmax!=undef && out>=nmax) ? out : + _count_true_func( + l, func, nmax, i = i + 1, + out = out + (func(l[i])? 1:0) + ); + +function _count_true_bool(l, nmax, i=0, out=0) = + i >= len(l) || (nmax!=undef && out>=nmax) ? out : + _count_true_bool( + l, nmax, i = i + 1, + out = out + (l[i]? 1:0) + ); @@ -1573,6 +1599,7 @@ function c_ident(n) = [for (i = [0:1:n-1]) [for (j = [0:1:n-1]) (i==j)?[1,0]:[0, function c_norm(z) = norm_fro(z); + // Section: Polynomials // Function: quadratic_roots() @@ -1624,6 +1651,7 @@ function polynomial(p,z,k,total) = : k==len(p) ? total : polynomial(p,z,k+1, is_num(z) ? total*z+p[k] : c_mul(total,z)+[p[k],0]); + // Function: poly_mult() // Usage: // x = polymult(p,q) diff --git a/tests/test_math.scad b/tests/test_math.scad index 35098a1..8dfdb9d 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -299,15 +299,6 @@ module test_modang() { test_modang(); -module test_modrange() { - assert_equal(modrange(-5,5,3), [1,2]); - assert_equal(modrange(-1,4,3), [2,0,1]); - assert_equal(modrange(1,8,10,step=2), [1,3,5,7]); - assert_equal(modrange(5,12,10,step=2), [5,7,9,1]); -} -test_modrange(); - - module test_sqr() { assert_equal(sqr(-3), 9); assert_equal(sqr(0), 0); @@ -738,11 +729,14 @@ module test_any() { assert_equal(any([0,false,undef]), false); assert_equal(any([1,false,undef]), true); assert_equal(any([1,5,true]), true); - assert_equal(any([[0,0], [0,0]]), false); + assert_equal(any([[0,0], [0,0]]), true); assert_equal(any([[0,0], [1,0]]), true); assert_equal(any([[false,false],[[false,[false],[[[true]]]],false],[false,false]]), true); - assert_equal(any([[false,false],[[false,[false],[[[false]]]],false],[false,false]]), false); + assert_equal(any([[false,false],[[false,[false],[[[false]]]],false],[false,false]]), true); assert_equal(any([]), false); + assert_equal(any([1,3,5,7,9], function (a) a%2==0),false); + assert_equal(any([1,3,6,7,9], function (a) a%2==0),true); + assert_equal(any([1,3,5,7,9], function (a) a%2!=0),true); } test_any(); @@ -751,12 +745,15 @@ module test_all() { assert_equal(all([0,false,undef]), false); assert_equal(all([1,false,undef]), false); assert_equal(all([1,5,true]), true); - assert_equal(all([[0,0], [0,0]]), false); - assert_equal(all([[0,0], [1,0]]), false); + assert_equal(all([[0,0], [0,0]]), true); + assert_equal(all([[0,0], [1,0]]), true); assert_equal(all([[1,1], [1,1]]), true); - assert_equal(all([[true,true],[[true,[true],[[[true]]]],true],[true,true]]), true); - assert_equal(all([[true,true],[[true,[true],[[[false]]]],true],[true,true]]), false); + assert_equal(all([[true,true],[[true,[true],[[[true]]]],true],[true,true]]), true); + assert_equal(all([[true,true],[[true,[true],[[[false]]]],true],[true,true]]), true); assert_equal(all([]), true); + assert_equal(all([1,3,5,7,9], function (a) a%2==0),false); + assert_equal(all([1,3,6,8,9], function (a) a%2==0),false); + assert_equal(all([1,3,5,7,9], function (a) a%2!=0),true); } test_all(); @@ -770,6 +767,9 @@ module test_count_true() { assert_equal(count_true([[0,0], [1,0]]), 2); assert_equal(count_true([[1,1], [1,1]]), 2); assert_equal(count_true([1,1,1,1,1], nmax=3), 3); + assert_equal(count_true([1,3,5,7,9], function (a) a%2==0),0); + assert_equal(count_true([1,3,6,8,9], function (a) a%2==0),2); + assert_equal(count_true([1,3,5,7,9], function (a) a%2!=0),5); } test_count_true(); @@ -789,6 +789,7 @@ module test_factorial() { } test_factorial(); + module test_binomial() { assert_equal(binomial(1), [1,1]); assert_equal(binomial(2), [1,2,1]); @@ -797,6 +798,7 @@ module test_binomial() { } test_binomial(); + module test_binomial_coefficient() { assert_equal(binomial_coefficient(2,1), 2); assert_equal(binomial_coefficient(3,2), 3); @@ -815,8 +817,8 @@ module test_gcd() { assert_equal(gcd(39, 101),1); assert_equal(gcd(15,-25), 5); assert_equal(gcd(-15,25), 5); - assert_equal(gcd(5,0),5); - assert_equal(gcd(0,5),5); + assert_equal(gcd(5,0), 5); + assert_equal(gcd(0,5), 5); } test_gcd();