Various arrays.scad docs updates. Bugfixes.

This commit is contained in:
Garth Minette 2021-01-24 23:26:39 -08:00
parent 330647dd18
commit 320518194f
13 changed files with 205 additions and 175 deletions

View file

@ -24,7 +24,7 @@
// Returns true when the list have elements of same type up to the depth `depth`. // Returns true when the list have elements of same type up to the depth `depth`.
// Booleans and numbers are not distinguinshed as of distinct types. // Booleans and numbers are not distinguinshed as of distinct types.
// Arguments: // Arguments:
// list = the list to check // l = the list to check
// depth = the lowest level the check is done // depth = the lowest level the check is done
// Example: // Example:
// a = is_homogeneous([[1,["a"]], [2,["b"]]]); // Returns true // a = is_homogeneous([[1,["a"]], [2,["b"]]]); // Returns true
@ -71,20 +71,22 @@ function _same_type(a,b, depth) =
// g = select(l, -2); // Returns 8 // g = select(l, -2); // Returns 8
// h = select(l, [1:3]); // Returns [4,5,6] // h = select(l, [1:3]); // Returns [4,5,6]
// i = select(l, [1,3]); // Returns [4,6] // i = select(l, [1,3]); // Returns [4,6]
function select(list, start, end=undef) = function select(list, start, end) =
assert( is_list(list) || is_string(list), "Invalid list.") assert( is_list(list) || is_string(list), "Invalid list.")
let(l=len(list)) let(l=len(list))
l==0 ? [] l==0
: end==undef? ? []
is_num(start)? : end==undef
list[ (start%l+l)%l ] ? is_num(start)
: assert( is_list(start) || is_range(start), "Invalid start parameter") ? list[ (start%l+l)%l ]
: assert( is_list(start) || is_range(start), "Invalid start parameter")
[for (i=start) list[ (i%l+l)%l ] ] [for (i=start) list[ (i%l+l)%l ] ]
: assert(is_finite(start), "Invalid start parameter.") : assert(is_finite(start), "Invalid start parameter.")
assert(is_finite(end), "Invalid end parameter.") assert(is_finite(end), "Invalid end parameter.")
let( s = (start%l+l)%l, e = (end%l+l)%l ) let( s = (start%l+l)%l, e = (end%l+l)%l )
(s <= e)? [for (i = [s:1:e]) list[i]] (s <= e)
: concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ; ? [for (i = [s:1:e]) list[i]]
: concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ;
// Function: last() // Function: last()
@ -97,19 +99,22 @@ function select(list, start, end=undef) =
// Example: // Example:
// l = [3,4,5,6,7,8,9]; // l = [3,4,5,6,7,8,9];
// x = last(l); // Returns 9. // x = last(l); // Returns 9.
function last(list) = list[len(list)-1]; function last(list) =
list[len(list)-1];
// Function: delete_last() // Function: delete_last()
// Usage: // Usage:
// list = delete_last(list); // list = delete_last(list);
// Description: // Description:
// Returns a list of all but the last entry. If input is empty, returns empty list. // Returns a list with all but the last entry from the input list. If input is empty, returns empty list.
// Usage: // Example:
// delete_last(list) // nlist = delete_last(["foo", "bar", "baz"]); // Returns: ["foo", "bar"]
function delete_last(list) = function delete_last(list) =
assert(is_list(list)) assert(is_list(list))
list==[] ? [] : slice(list,0,-2); list==[] ? [] : slice(list,0,-2);
// Function: slice() // Function: slice()
// Usage: // Usage:
// list = slice(list,start,end); // list = slice(list,start,end);
@ -130,10 +135,11 @@ function slice(list,start,end) =
assert( is_list(list), "Invalid list" ) assert( is_list(list), "Invalid list" )
assert( is_finite(start) && is_finite(end), "Invalid number(s)" ) assert( is_finite(start) && is_finite(end), "Invalid number(s)" )
let( l = len(list) ) let( l = len(list) )
l==0 ? [] l==0
: let( ? []
s = start<0? (l+start): start, : let(
e = end<0? (l+end+1): end s = start<0? (l+start) : start,
e = end<0? (l+end+1) : end
) [for (i=[s:1:e-1]) if (e>s) list[i]]; ) [for (i=[s:1:e-1]) if (e>s) list[i]];
@ -150,7 +156,7 @@ function slice(list,start,end) =
// a = in_list("bar", ["foo", "bar", "baz"]); // Returns true. // a = in_list("bar", ["foo", "bar", "baz"]); // Returns true.
// b = in_list("bee", ["foo", "bar", "baz"]); // Returns false. // b = in_list("bee", ["foo", "bar", "baz"]); // Returns false.
// c = in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true. // c = in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true.
function in_list(val,list,idx=undef) = function in_list(val,list,idx) =
assert( is_list(list) && (is_undef(idx) || is_finite(idx)), assert( is_list(list) && (is_undef(idx) || is_finite(idx)),
"Invalid input." ) "Invalid input." )
let( s = search([val], list, num_returns_per_match=1, index_col_num=idx)[0] ) let( s = search([val], list, num_returns_per_match=1, index_col_num=idx)[0] )
@ -248,18 +254,20 @@ function repeat(val, n, i=0) =
(i>=len(n))? val : (i>=len(n))? val :
[for (j=[1:1:n[i]]) repeat(val, n, i+1)]; [for (j=[1:1:n[i]]) repeat(val, n, i+1)];
// Function: list_range() // Function: list_range()
// Usage: // Usage:
// list = list_range(n, <s>, <e>); // list = list_range(n=, <s=>, <e=>);
// list = list_range(n, <s>, <step>); // list = list_range(n=, <s=>, <step=>);
// list = list_range(e, <step>); // list = list_range(e=, <step=>);
// list = list_range(s, e, <step>); // list = list_range(s=, e=, <step=>);
// Description: // Description:
// Returns a list, counting up from starting value `s`, by `step` increments, // Returns a list, counting up from starting value `s`, by `step` increments,
// until either `n` values are in the list, or it reaches the end value `e`. // until either `n` values are in the list, or it reaches the end value `e`.
// If both `n` and `e` are given, returns `n` values evenly spread from `s` // If both `n` and `e` are given, returns `n` values evenly spread from `s`
// to `e`, and `step` is ignored. // to `e`, and `step` is ignored.
// Arguments: // Arguments:
// ---
// n = Desired number of values in returned list, if given. // n = Desired number of values in returned list, if given.
// s = Starting value. Default: 0 // s = Starting value. Default: 0
// e = Ending value to stop at, if given. // e = Ending value to stop at, if given.
@ -275,14 +283,14 @@ function repeat(val, n, i=0) =
// h = list_range(s=3, e=8, step=2); // Returns [3,5,7] // h = list_range(s=3, e=8, step=2); // Returns [3,5,7]
// i = list_range(s=4, e=8.3, step=2); // Returns [4,6,8] // i = list_range(s=4, e=8.3, step=2); // Returns [4,6,8]
// j = list_range(n=4, s=[3,4], step=[2,3]); // Returns [[3,4], [5,7], [7,10], [9,13]] // j = list_range(n=4, s=[3,4], step=[2,3]); // Returns [[3,4], [5,7], [7,10], [9,13]]
function list_range(n=undef, s=0, e=undef, step=undef) = function list_range(n, s=0, e, step) =
assert( is_undef(n) || is_finite(n), "Parameter `n` must be a number.") assert( is_undef(n) || is_finite(n), "Parameter `n` must be a number.")
assert( is_undef(n) || is_undef(e) || is_undef(step), "At most 2 of n, e, and step can be given.") assert( is_undef(n) || is_undef(e) || is_undef(step), "At most 2 of n, e, and step can be given.")
let( step = (n!=undef && e!=undef)? (e-s)/(n-1) : default(step,1) ) let( step = (n!=undef && e!=undef)? (e-s)/(n-1) : default(step,1) )
is_undef(e) ? is_undef(e)
assert( is_consistent([s, step]), "Incompatible data.") ? 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.") : assert( is_vector([s,step,e]), "Start `s`, step `step` and end `e` must be numbers.")
[for (v=[s:step:e]) v] ; [for (v=[s:step:e]) v] ;
@ -379,10 +387,11 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
assert(is_list(list)||is_string(list), "Improper list or string.") assert(is_list(list)||is_string(list), "Improper list or string.")
indices==[]? [] : indices==[]? [] :
assert(is_vector(indices), "Indices must be a list of numbers.") assert(is_vector(indices), "Indices must be a list of numbers.")
let( l = len(indices), let(
end = l-(closed?0:1) ) l = len(indices),
[ for (i = [0:1:l-1]) end = l-(closed?0:1)
let( ) [
for (i = [0:1:l-1]) let(
a = list[indices[i]], a = list[indices[i]],
b = list[indices[(i+1)%l]], b = list[indices[(i+1)%l]],
eq = (a == b)? true : eq = (a == b)? true :
@ -455,22 +464,25 @@ function repeat_entries(list, N, exact=true) =
function list_set(list=[],indices,values,dflt=0,minlen=0) = function list_set(list=[],indices,values,dflt=0,minlen=0) =
assert(is_list(list)) assert(is_list(list))
!is_list(indices)? ( !is_list(indices)? (
(is_finite(indices) && indices<len(list))? (is_finite(indices) && indices<len(list))
concat([for (i=idx(list)) i==indices? values : list[i]], repeat(dflt, minlen-len(list))) ? concat([for (i=idx(list)) i==indices? values : list[i]], repeat(dflt, minlen-len(list)))
: list_set(list,[indices],[values],dflt) ) : list_set(list,[indices],[values],dflt)
:indices==[] && values==[] ? ) :
concat(list, repeat(dflt, minlen-len(list))) indices==[] && values==[]
:assert(is_vector(indices) && is_list(values) && len(values)==len(indices) , ? concat(list, repeat(dflt, minlen-len(list)))
: assert(is_vector(indices) && is_list(values) && len(values)==len(indices),
"Index list and value list must have the same length") "Index list and value list must have the same length")
let( midx = max(len(list)-1, max(indices)) ) let( midx = max(len(list)-1, max(indices)) )
[ for(i=[0:midx] ) [
let( j = search(i,indices,0), for (i=[0:1:midx]) let(
k = j[0] ) j = search(i,indices,0),
k = j[0]
)
assert( len(j)<2, "Repeated indices are not allowed." ) assert( len(j)<2, "Repeated indices are not allowed." )
k!=undef ? values[k] : k!=undef
i<len(list) ? list[i]: ? values[k]
dflt , : i<len(list) ? list[i] : dflt,
each repeat(dflt, minlen-max(len(list),max(indices))) each repeat(dflt, minlen-max(len(list),max(indices)))
]; ];
@ -484,29 +496,31 @@ function list_set(list=[],indices,values,dflt=0,minlen=0) =
// b = list_insert([3,6,9,12],[1,3],[5,11]); // Returns [3,5,6,9,11,12] // b = list_insert([3,6,9,12],[1,3],[5,11]); // Returns [3,5,6,9,11,12]
function list_insert(list, indices, values) = function list_insert(list, indices, values) =
assert(is_list(list)) assert(is_list(list))
!is_list(indices)? !is_list(indices) ?
assert( is_finite(indices) && is_finite(values), "Invalid indices/values." ) assert( is_finite(indices) && is_finite(values), "Invalid indices/values." )
assert( indices<=len(list), "Indices must be <= len(list) ." ) assert( indices<=len(list), "Indices must be <= len(list) ." )
[ [
for (i=idx(list)) each ( i==indices? [ values, list[i] ] : [ list[i] ] ), for (i=idx(list)) each ( i==indices? [ values, list[i] ] : [ list[i] ] ),
if (indices==len(list)) values if (indices==len(list)) values
] ] :
: indices==[] && values==[] ? list indices==[] && values==[] ? list :
: assert( is_vector(indices) && is_list(values) && len(values)==len(indices) , assert( is_vector(indices) && is_list(values) && len(values)==len(indices),
"Index list and value list must have the same length") "Index list and value list must have the same length")
assert( max(indices)<=len(list), "Indices must be <= len(list) ." ) assert( max(indices)<=len(list), "Indices must be <= len(list)." )
let( maxidx = max(indices), let(
minidx = min(indices) ) maxidx = max(indices),
[ for(i=[0:1:minidx-1] ) list[i], minidx = min(indices)
for(i=[minidx: min(maxidx, len(list)-1)] ) ) [
let( j = search(i,indices,0), for (i=[0:1:minidx-1] ) list[i],
k = j[0], for (i=[minidx : min(maxidx, len(list)-1)] )
x = assert( len(j)<2, "Repeated indices are not allowed." ) let(
) j = search(i,indices,0),
each ( k != undef ? [ values[k], list[i] ] : [ list[i] ] ), k = j[0],
for(i=[min(maxidx, len(list)-1)+1:1:len(list)-1] ) list[i], x = assert( len(j)<2, "Repeated indices are not allowed." )
if(maxidx==len(list)) values[max_index(indices)] ) each ( k != undef ? [ values[k], list[i] ] : [ list[i] ] ),
]; for ( i = [min(maxidx, len(list)-1)+1 : 1 : len(list)-1] ) list[i],
if (maxidx == len(list)) values[max_index(indices)]
];
// Function: list_remove() // Function: list_remove()
@ -616,6 +630,8 @@ function list_bset(indexset, valuelist, dflt=0) =
// Returns the length of the shortest sublist in a list of lists. // Returns the length of the shortest sublist in a list of lists.
// Arguments: // Arguments:
// array = A list of lists. // array = A list of lists.
// Example:
// slen = list_shortest([[3,4,5],[6,7,8,9]]); // Returns: 3
function list_shortest(array) = function list_shortest(array) =
assert(is_list(array), "Invalid input." ) assert(is_list(array), "Invalid input." )
min([for (v = array) len(v)]); min([for (v = array) len(v)]);
@ -628,6 +644,8 @@ function list_shortest(array) =
// Returns the length of the longest sublist in a list of lists. // Returns the length of the longest sublist in a list of lists.
// Arguments: // Arguments:
// array = A list of lists. // array = A list of lists.
// Example:
// llen = list_longest([[3,4,5],[6,7,8,9]]); // Returns: 4
function list_longest(array) = function list_longest(array) =
assert(is_list(array), "Invalid input." ) assert(is_list(array), "Invalid input." )
max([for (v = array) len(v)]); max([for (v = array) len(v)]);
@ -641,8 +659,11 @@ function list_longest(array) =
// Arguments: // Arguments:
// array = A list. // array = A list.
// minlen = The minimum length to pad the list to. // minlen = The minimum length to pad the list to.
// fill = The value to pad the list with. // fill = The value to pad the list with. Default: `undef`
function list_pad(array, minlen, fill=undef) = // Example:
// list = [3,4,5];
// nlist = list_pad(list,5,23); // Returns: [3,4,5,23,23]
function list_pad(array, minlen, fill) =
assert(is_list(array), "Invalid input." ) assert(is_list(array), "Invalid input." )
concat(array,repeat(fill,minlen-len(array))); concat(array,repeat(fill,minlen-len(array)));
@ -655,6 +676,9 @@ function list_pad(array, minlen, fill=undef) =
// Arguments: // Arguments:
// array = A list. // array = A list.
// minlen = The minimum length to pad the list to. // minlen = The minimum length to pad the list to.
// Example:
// list = [3,4,5,6,7,8];
// nlist = list_trim(list,4); // Returns: [3,4,5,6]
function list_trim(array, maxlen) = function list_trim(array, maxlen) =
assert(is_list(array), "Invalid input." ) assert(is_list(array), "Invalid input." )
[for (i=[0:1:min(len(array),maxlen)-1]) array[i]]; [for (i=[0:1:min(len(array),maxlen)-1]) array[i]];
@ -669,7 +693,13 @@ function list_trim(array, maxlen) =
// Arguments: // Arguments:
// array = A list. // array = A list.
// minlen = The minimum length to pad the list to. // minlen = The minimum length to pad the list to.
// fill = The value to pad the list with. // fill = The value to pad the list with. Default: `undef`
// Example:
// list = [3,4,5,6];
// nlist = list_fit(list,3); // Returns: [3,4,5]
// Example:
// list = [3,4,5,6];
// nlist = list_fit(list,6,23); // Returns: [3,4,5,6,23,23]
function list_fit(array, length, fill) = function list_fit(array, length, fill) =
assert(is_list(array), "Invalid input." ) assert(is_list(array), "Invalid input." )
let(l=len(array)) let(l=len(array))
@ -699,7 +729,7 @@ function _valid_idx(idx,imin,imax) =
// Function: shuffle() // Function: shuffle()
// Usage: // Usage:
// shuffled = shuffle(list,<seed>) // shuffled = shuffle(list,<seed>);
// Description: // Description:
// Shuffles the input list into random order. // Shuffles the input list into random order.
// If given a string, shuffles the characters within the string. // If given a string, shuffles the characters within the string.
@ -708,11 +738,17 @@ function _valid_idx(idx,imin,imax) =
// Arguments: // Arguments:
// list = The list to shuffle. // list = The list to shuffle.
// seed = Optional random number seed for the shuffling. // seed = Optional random number seed for the shuffling.
// Example:
// // Spades Hearts Diamonds Clubs
// suits = ["\u2660", "\u2661", "\u2662", "\u2663"];
// ranks = [2,3,4,5,6,7,8,9,10,"J","Q","K","A"];
// cards = [for (suit=suits, rank=ranks) str(rank,suit)];
// deck = shuffle(cards);
function shuffle(list,seed) = function shuffle(list,seed) =
assert(is_list(list)||is_string(list), "Invalid input." ) assert(is_list(list)||is_string(list), "Invalid input." )
is_string(list)? str_join(shuffle([for (x = list) x],seed=seed)) : is_string(list)? str_join(shuffle([for (x = list) x],seed=seed)) :
len(list)<=1 ? list : len(list)<=1 ? list :
let ( let(
rval = is_num(seed) ? rands(0,1,len(list),seed_value=seed) rval = is_num(seed) ? rands(0,1,len(list),seed_value=seed)
: rands(0,1,len(list)), : rands(0,1,len(list)),
left = [for (i=[0:len(list)-1]) if (rval[i]< 0.5) list[i]], left = [for (i=[0:len(list)-1]) if (rval[i]< 0.5) list[i]],
@ -743,7 +779,7 @@ function _sort_vectors(arr, _i=0) =
lesser = [ for (entry=arr) if (entry[_i] < pivot ) entry ], lesser = [ for (entry=arr) if (entry[_i] < pivot ) entry ],
equal = [ for (entry=arr) if (entry[_i] == pivot ) entry ], equal = [ for (entry=arr) if (entry[_i] == pivot ) entry ],
greater = [ for (entry=arr) if (entry[_i] > pivot ) entry ] greater = [ for (entry=arr) if (entry[_i] > pivot ) entry ]
) )
concat( concat(
_sort_vectors(lesser, _i ), _sort_vectors(lesser, _i ),
_sort_vectors(equal, _i+1 ), _sort_vectors(equal, _i+1 ),
@ -900,6 +936,8 @@ function sortidx(list, idx=undef) =
// Returns a sorted list with all repeated items removed. // Returns a sorted list with all repeated items removed.
// Arguments: // Arguments:
// list = The list to uniquify. // list = The list to uniquify.
// Example:
// sorted = unique([5,2,8,3,1,3,8,7,5]); // Returns: [1,2,3,5,7,8]
function unique(list) = function unique(list) =
assert(is_list(list)||is_string(list), "Invalid input." ) assert(is_list(list)||is_string(list), "Invalid input." )
is_string(list)? str_join(unique([for (x = list) x])) : is_string(list)? str_join(unique([for (x = list) x])) :
@ -919,6 +957,8 @@ function unique(list) =
// that `count[i]` gives the number of times that `sorted[i]` appears in `list`. // that `count[i]` gives the number of times that `sorted[i]` appears in `list`.
// Arguments: // Arguments:
// list = The list to analyze. // list = The list to analyze.
// Example:
// sorted = unique([5,2,8,3,1,3,8,3,5]); // Returns: [ [1,2,3,5,8], [1,1,3,2,2] ]
function unique_count(list) = function unique_count(list) =
assert(is_list(list) || is_string(list), "Invalid input." ) assert(is_list(list) || is_string(list), "Invalid input." )
list == [] ? [[],[]] : list == [] ? [[],[]] :
@ -931,21 +971,26 @@ function unique_count(list) =
// Function: idx() // Function: idx()
// Usage: // Usage:
// rng = idx(list, <step>, <end>, <start>); // rng = idx(list, <s=>, <e=>, <step=>);
// for(i=idx(list, <step>, <end>, <start>)) ... // for(i=idx(list, <s=>, <e=>, <step=>)) ...
// Description: // Description:
// Returns the range of indexes for the given list. // Returns the range of indexes for the given list.
// Arguments: // Arguments:
// list = The list to returns the index range of. // list = The list to returns the index range of.
// s = The starting index. Default: 0
// e = The delta from the end of the list. Default: -1 (end of list)
// step = The step size to stride through the list. Default: 1 // step = The step size to stride through the list. Default: 1
// end = The delta from the end of the list. Default: -1
// start = The starting index. Default: 0
// Example(2D): // Example(2D):
// colors = ["red", "green", "blue"]; // colors = ["red", "green", "blue"];
// for (i=idx(colors)) right(20*i) color(colors[i]) circle(d=10); // for (i=idx(colors)) right(20*i) color(colors[i]) circle(d=10);
function idx(list, step=1, end=-1,start=0) = function idx(list, s=0, e=-1, step=1) =
assert(is_list(list)||is_string(list), "Invalid input." ) assert(is_list(list)||is_string(list), "Invalid input." )
[start : step : len(list)+end]; let( ll = len(list) )
ll == 0 ? [0:1:-1] :
let(
_s = posmod(s,ll),
_e = posmod(e,ll)
) [_s : step : _e];
// Function: enumerate() // Function: enumerate()
@ -975,7 +1020,7 @@ function enumerate(l,idx=undef) =
// Function: force_list() // Function: force_list()
// Usage: // Usage:
// list = force_list(value, <n>, <fill>) // list = force_list(value, <n>, <fill>);
// Description: // Description:
// Coerces non-list values into a list. Makes it easy to treat a scalar input // Coerces non-list values into a list. Makes it easy to treat a scalar input
// consistently as a singleton list, as well as list inputs. // consistently as a singleton list, as well as list inputs.
@ -998,68 +1043,57 @@ function force_list(value, n=1, fill) =
// Function: pair() // Function: pair()
// Usage: // Usage:
// p = pair(v); // p = pair(list, <wrap>);
// for (p = pair(v)) ... // p contains a list of two adjacent items. // for (p = pair(list, <wrap>)) ... // On each iteration, p contains a list of two adjacent items.
// Description: // Description:
// Takes a list, and returns a list of adjacent pairs from it. // Takes a list, and returns a list of adjacent pairs from it, optionally wrapping back to the front.
// Example(2D): Note that the last point and first point do NOT get paired together. // Arguments:
// for (p = pair(circle(d=20, $fn=12))) // list = The list to iterate.
// move(p[0]) // wrap = If true, wrap back to the start from the end. ie: return the last and first items as the last pair. Default: false
// rot(from=BACK, to=p[1]-p[0]) // Example(2D): Does NOT wrap from end to start,
// trapezoid(w1=1, w2=0, h=norm(p[1]-p[0]), anchor=FRONT); // for (p = pair(circle(d=40, $fn=12)))
// stroke(p, endcap2="arrow2");
// Example(2D): Wraps around from end to start.
// for (p = pair(circle(d=40, $fn=12), wrap=true))
// stroke(p, endcap2="arrow2");
// Example: // Example:
// l = ["A","B","C","D"]; // l = ["A","B","C","D"];
// echo([for (p=pair(l)) str(p.y,p.x)]); // Outputs: ["BA", "CB", "DC"] // echo([for (p=pair(l)) str(p.y,p.x)]); // Outputs: ["BA", "CB", "DC"]
function pair(v) = function pair(list, wrap=false) =
assert(is_list(v)||is_string(v), "Invalid input." ) assert(is_list(list)||is_string(list), "Invalid input." )
[for (i=[0:1:len(v)-2]) [v[i],v[i+1]]]; assert(is_bool(wrap))
let(
ll = len(list)
// Function: pair_wrap() ) wrap
// Usage: ? [for (i=[0:1:ll-1]) [list[i], list[(i+1) % ll]]]
// p = pair_wrap(v); : [for (i=[0:1:ll-2]) [list[i], list[i+1]]];
// for (p = pair_wrap(v)) ...
// Description:
// Takes a list, and returns a list of adjacent pairs from it, wrapping around from the end to the start of the list.
// Example(2D):
// for (p = pair_wrap(circle(d=20, $fn=12)))
// move(p[0])
// rot(from=BACK, to=p[1]-p[0])
// trapezoid(w1=1, w2=0, h=norm(p[1]-p[0]), anchor=FRONT);
// Example:
// l = ["A","B","C","D"];
// echo([for (p=pair_wrap(l)) str(p.y,p.x)]); // Outputs: ["BA", "CB", "DC", "AD"]
function pair_wrap(v) =
assert(is_list(v)||is_string(v), "Invalid input." )
[for (i=[0:1:len(v)-1]) [v[i],v[(i+1)%len(v)]]];
// Function: triplet() // Function: triplet()
// Usage: // Usage:
// list = triplet(v); // list = triplet(list, <wrap>);
// for (t = triplet(v)) ... // for (t = triplet(list, <wrap>)) ...
// Description: // Description:
// Takes a list, and returns a list of adjacent triplets from it. // Takes a list, and returns a list of adjacent triplets from it, optionally wrapping back to the front.
// Example: // Example:
// l = ["A","B","C","D","E"]; // l = ["A","B","C","D","E"];
// echo([for (p=triplet(l)) str(p.z,p.y,p.x)]); // Outputs: ["CBA", "DCB", "EDC"] // echo([for (p=triplet(l)) str(p.z,p.y,p.x)]); // Outputs: ["CBA", "DCB", "EDC"]
function triplet(v) = // Example(2D):
assert(is_list(v)||is_string(v), "Invalid input." ) // path = [for (i=[0:24]) polar_to_xy(i*2, i*360/12)];
[for (i=[0:1:len(v)-3]) [v[i],v[i+1],v[i+2]]]; // for (t = triplet(path)) {
// a = t[0]; b = t[1]; c = t[2];
// v = unit(unit(a-b) + unit(c-b));
// Function: triplet_wrap() // translate(b) rot(from=FWD,to=v) anchor_arrow2d();
// Usage: // }
// list = triplet_wrap(v); // stroke(path);
// for (t = triplet_wrap(v)) ... function triplet(list, wrap=false) =
// Description: assert(is_list(list)||is_string(list), "Invalid input." )
// Takes a list, and returns a list of adjacent triplets from it, wrapping around from the end to the start of the list. assert(is_bool(wrap))
// Example: let(
// l = ["A","B","C","D"]; ll = len(list)
// echo([for (p=triplet_wrap(l)) str(p.z,p.y,p.x)]); // Outputs: ["CBA", "DCB", "ADC", "BAD"] ) wrap
function triplet_wrap(v) = ? [for (i=[0:1:ll-1]) [ list[i], list[(i+1)%ll], list[(i+2)%ll] ]]
assert(is_list(v)||is_string(v), "Invalid input." ) : [for (i=[0:1:ll-3]) [ list[i], list[i+1], list[i+2] ]];
[for (i=[0:1:len(v)-1]) [v[i],v[(i+1)%len(v)],v[(i+2)%len(v)]]];
// Function: permute() // Function: permute()
@ -1305,7 +1339,7 @@ function zip(v1, v2, v3, fit=false, fill=undef) =
function block_matrix(M) = function block_matrix(M) =
let( let(
bigM = [for(bigrow = M) each zip(bigrow)], bigM = [for(bigrow = M) each zip(bigrow)],
len0=len(bigM[0]), len0 = len(bigM[0]),
badrows = [for(row=bigM) if (len(row)!=len0) 1] badrows = [for(row=bigM) if (len(row)!=len0) 1]
) )
assert(badrows==[], "Inconsistent or invalid input") assert(badrows==[], "Inconsistent or invalid input")
@ -1381,7 +1415,9 @@ function array_group(v, cnt=2, dflt=0) =
// l = List to flatten. // l = List to flatten.
// Example: // Example:
// l = flatten([[1,2,3], [4,5,[6,7,8]]]); // returns [1,2,3,4,5,[6,7,8]] // l = flatten([[1,2,3], [4,5,[6,7,8]]]); // returns [1,2,3,4,5,[6,7,8]]
function flatten(l) = [for (a = l) each a]; function flatten(l) =
!is_list(l)? l :
[for (a=l) if (is_list(a)) (each a) else a];
// Function: full_flatten() // Function: full_flatten()
@ -1394,7 +1430,9 @@ function flatten(l) = [for (a = l) each a];
// l = List to flatten. // l = List to flatten.
// Example: // Example:
// l = full_flatten([[1,2,3], [4,5,[6,7,8]]]); // returns [1,2,3,4,5,6,7,8] // l = full_flatten([[1,2,3], [4,5,[6,7,8]]]); // returns [1,2,3,4,5,6,7,8]
function full_flatten(l) = [for(a=l) if(is_list(a)) (each full_flatten(a)) else a ]; function full_flatten(l) =
!is_list(l)? l :
[for (a=l) if (is_list(a)) (each full_flatten(a)) else a];
// Internal. Not exposed. // Internal. Not exposed.
@ -1421,14 +1459,12 @@ function _array_dim_recurse(v) =
// Usage: // Usage:
// dims = array_dim(v, <depth>); // dims = array_dim(v, <depth>);
// Description: // Description:
// Returns the size of a multi-dimensional array. Returns a list of // Returns the size of a multi-dimensional array. Returns a list of dimension lengths. The length
// dimension lengths. The length of `v` is the dimension `0`. The // of `v` is the dimension `0`. The length of the items in `v` is dimension `1`. The length of the
// length of the items in `v` is dimension `1`. The length of the // items in the items in `v` is dimension `2`, etc. For each dimension, if the length of items at
// items in the items in `v` is dimension `2`, etc. For each dimension, // that depth is inconsistent, `undef` will be returned. If no items of that dimension depth exist,
// if the length of items at that depth is inconsistent, `undef` will // `0` is returned. Otherwise, the consistent length of items in that dimensional depth is
// be returned. If no items of that dimension depth exist, `0` is // returned.
// returned. Otherwise, the consistent length of items in that
// dimensional depth is returned.
// Arguments: // Arguments:
// v = Array to get dimensions of. // v = Array to get dimensions of.
// depth = Dimension to get size of. If not given, returns a list of dimension lengths. // depth = Dimension to get size of. If not given, returns a list of dimension lengths.

View file

@ -573,7 +573,7 @@ function find_anchor(anchor, geom) =
path = move(-point2d(cp), p=geom[1]), path = move(-point2d(cp), p=geom[1]),
anchor = point2d(anchor), anchor = point2d(anchor),
isects = [ isects = [
for (t=triplet_wrap(path)) let( for (t=triplet(path,true)) let(
seg1 = [t[0],t[1]], seg1 = [t[0],t[1]],
seg2 = [t[1],t[2]], seg2 = [t[1],t[2]],
isect = ray_segment_intersection([[0,0],anchor], seg1), isect = ray_segment_intersection([[0,0],anchor], seg1),

View file

@ -345,7 +345,7 @@ function bezier_segment_length(curve, start_u=0, end_u=1, max_deflect=0.01) =
uvals = [for (i=[0:1:segs]) lerp(start_u, end_u, i/segs)], uvals = [for (i=[0:1:segs]) lerp(start_u, end_u, i/segs)],
path = bezier_points(curve,uvals), path = bezier_points(curve,uvals),
defl = max([ defl = max([
for (i=idx(path,end=-3)) let( for (i=idx(path,e=-3)) let(
mp = (path[i] + path[i+2]) / 2 mp = (path[i] + path[i+2]) / 2
) norm(path[i+1] - mp) ) norm(path[i+1] - mp)
]), ]),

View file

@ -2004,7 +2004,7 @@ function _split_polygon_at_x(poly, x) =
) (min(xs) >= x || max(xs) <= x)? [poly] : ) (min(xs) >= x || max(xs) <= x)? [poly] :
let( let(
poly2 = [ poly2 = [
for (p = pair_wrap(poly)) each [ for (p = pair(poly,true)) each [
p[0], p[0],
if( if(
(p[0].x < x && p[1].x > x) || (p[0].x < x && p[1].x > x) ||
@ -2034,7 +2034,7 @@ function _split_polygon_at_y(poly, y) =
) (min(ys) >= y || max(ys) <= y)? [poly] : ) (min(ys) >= y || max(ys) <= y)? [poly] :
let( let(
poly2 = [ poly2 = [
for (p = pair_wrap(poly)) each [ for (p = pair(poly,true)) each [
p[0], p[0],
if( if(
(p[0].y < y && p[1].y > y) || (p[0].y < y && p[1].y > y) ||
@ -2064,7 +2064,7 @@ function _split_polygon_at_z(poly, z) =
) (min(zs) >= z || max(zs) <= z)? [poly] : ) (min(zs) >= z || max(zs) <= z)? [poly] :
let( let(
poly2 = [ poly2 = [
for (p = pair_wrap(poly)) each [ for (p = pair(poly,true)) each [
p[0], p[0],
if( if(
(p[0].z < z && p[1].z > z) || (p[0].z < z && p[1].z > z) ||

View file

@ -1124,7 +1124,7 @@ function worm(
], ],
maxang = 360 / segs(d/2), maxang = 360 / segs(d/2),
refined_polars = [ refined_polars = [
for (i=idx(polars,end=-2)) let( for (i=idx(polars,e=-2)) let(
delta = polars[i+1].x - polars[i].x, delta = polars[i+1].x - polars[i].x,
steps = ceil(delta/maxang), steps = ceil(delta/maxang),
step = delta/steps step = delta/steps

View file

@ -824,7 +824,7 @@ function assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0,
[foundfrag, concat([path], remainder)] [foundfrag, concat([path], remainder)]
) : let( ) : let(
fragend = select(foundfrag,-1), fragend = select(foundfrag,-1),
hits = [for (i = idx(path,end=-2)) if(approx(path[i],fragend,eps=eps)) i] hits = [for (i = idx(path,e=-2)) if(approx(path[i],fragend,eps=eps)) i]
) hits? ( ) hits? (
let( let(
// Found fragment intersects with initial path // Found fragment intersects with initial path

View file

@ -367,7 +367,7 @@ function linear_sweep(region, height=1, center, twist=0, scale=1, slices, maxseg
for (path=rgn) let( for (path=rgn) let(
p = cleanup_path(path), p = cleanup_path(path),
path = is_undef(maxseg)? p : [ path = is_undef(maxseg)? p : [
for (seg=pair_wrap(p)) each for (seg=pair(p,true)) each
let(steps=ceil(norm(seg.y-seg.x)/maxseg)) let(steps=ceil(norm(seg.y-seg.x)/maxseg))
lerp(seg.x, seg.y, [0:1/steps:1-EPSILON]) lerp(seg.x, seg.y, [0:1/steps:1-EPSILON])
] ]
@ -380,7 +380,7 @@ function linear_sweep(region, height=1, center, twist=0, scale=1, slices, maxseg
for (pathnum = idx(rgn)) let( for (pathnum = idx(rgn)) let(
p = cleanup_path(rgn[pathnum]), p = cleanup_path(rgn[pathnum]),
path = is_undef(maxseg)? p : [ path = is_undef(maxseg)? p : [
for (seg=pair_wrap(p)) each for (seg=pair(p,true)) each
let(steps=ceil(norm(seg.y-seg.x)/maxseg)) let(steps=ceil(norm(seg.y-seg.x)/maxseg))
lerp(seg.x, seg.y, [0:1/steps:1-EPSILON]) lerp(seg.x, seg.y, [0:1/steps:1-EPSILON])
], ],

View file

@ -186,7 +186,7 @@ module stroke(
if (len(path[0]) == 2) { if (len(path[0]) == 2) {
// Straight segments // Straight segments
for (i = idx(path2,end=-2)) { for (i = idx(path2,e=-2)) {
seg = select(path2,i,i+1); seg = select(path2,i,i+1);
delt = seg[1] - seg[0]; delt = seg[1] - seg[0];
translate(seg[0]) { translate(seg[0]) {
@ -234,7 +234,7 @@ module stroke(
} }
} else { } else {
quatsums = Q_Cumulative([ quatsums = Q_Cumulative([
for (i = idx(path2,end=-2)) let( for (i = idx(path2,e=-2)) let(
vec1 = i==0? UP : unit(path2[i]-path2[i-1], UP), vec1 = i==0? UP : unit(path2[i]-path2[i-1], UP),
vec2 = unit(path2[i+1]-path2[i], UP), vec2 = unit(path2[i+1]-path2[i], UP),
axis = vector_axis(vec1,vec2), axis = vector_axis(vec1,vec2),
@ -243,12 +243,12 @@ module stroke(
]); ]);
rotmats = [for (q=quatsums) Q_Matrix4(q)]; rotmats = [for (q=quatsums) Q_Matrix4(q)];
sides = [ sides = [
for (i = idx(path2,end=-2)) for (i = idx(path2,e=-2))
quantup(segs(max(widths[i],widths[i+1])/2),4) quantup(segs(max(widths[i],widths[i+1])/2),4)
]; ];
// Straight segments // Straight segments
for (i = idx(path2,end=-2)) { for (i = idx(path2,e=-2)) {
dist = norm(path2[i+1] - path2[i]); dist = norm(path2[i+1] - path2[i]);
w1 = widths[i]/2; w1 = widths[i]/2;
w2 = widths[i+1]/2; w2 = widths[i+1]/2;

View file

@ -460,7 +460,7 @@ function _skin_core(profiles, caps) =
vertices = [for (prof=profiles) each prof], vertices = [for (prof=profiles) each prof],
plens = [for (prof=profiles) len(prof)], plens = [for (prof=profiles) len(prof)],
sidefaces = [ sidefaces = [
for(pidx=idx(profiles,end=-2)) for(pidx=idx(profiles,e=-2))
let( let(
prof1 = profiles[pidx%len(profiles)], prof1 = profiles[pidx%len(profiles)],
prof2 = profiles[(pidx+1)%len(profiles)], prof2 = profiles[(pidx+1)%len(profiles)],

View file

@ -266,9 +266,9 @@ test_list_fit();
module test_idx() { module test_idx() {
colors = ["red", "green", "blue", "cyan"]; colors = ["red", "green", "blue", "cyan"];
assert([for (i=idx(colors)) i] == [0,1,2,3]); assert([for (i=idx(colors)) i] == [0,1,2,3]);
assert([for (i=idx(colors,end=-2)) i] == [0,1,2]); assert([for (i=idx(colors,e=-2)) i] == [0,1,2]);
assert([for (i=idx(colors,start=1)) i] == [1,2,3]); assert([for (i=idx(colors,s=1)) i] == [1,2,3]);
assert([for (i=idx(colors,start=1,end=-2)) i] == [1,2]); assert([for (i=idx(colors,s=1,e=-2)) i] == [1,2]);
} }
test_idx(); test_idx();
@ -449,31 +449,25 @@ test_force_list();
module test_pair() { module test_pair() {
assert(pair([3,4,5,6]) == [[3,4], [4,5], [5,6]]); assert(pair([3,4,5,6]) == [[3,4], [4,5], [5,6]]);
assert(pair("ABCD") == [["A","B"], ["B","C"], ["C","D"]]); assert(pair("ABCD") == [["A","B"], ["B","C"], ["C","D"]]);
assert(pair([3,4,5,6],true) == [[3,4], [4,5], [5,6], [6,3]]);
assert(pair("ABCD",true) == [["A","B"], ["B","C"], ["C","D"], ["D","A"]]);
assert(pair([3,4,5,6],wrap=true) == [[3,4], [4,5], [5,6], [6,3]]);
assert(pair("ABCD",wrap=true) == [["A","B"], ["B","C"], ["C","D"], ["D","A"]]);
} }
test_pair(); test_pair();
module test_pair_wrap() {
assert(pair_wrap([3,4,5,6]) == [[3,4], [4,5], [5,6], [6,3]]);
assert(pair_wrap("ABCD") == [["A","B"], ["B","C"], ["C","D"], ["D","A"]]);
}
test_pair_wrap();
module test_triplet() { module test_triplet() {
assert(triplet([3,4,5,6,7]) == [[3,4,5], [4,5,6], [5,6,7]]); assert(triplet([3,4,5,6,7]) == [[3,4,5], [4,5,6], [5,6,7]]);
assert(triplet("ABCDE") == [["A","B","C"], ["B","C","D"], ["C","D","E"]]); assert(triplet("ABCDE") == [["A","B","C"], ["B","C","D"], ["C","D","E"]]);
assert(triplet([3,4,5,6],true) == [[3,4,5], [4,5,6], [5,6,3], [6,3,4]]);
assert(triplet("ABCD",true) == [["A","B","C"], ["B","C","D"], ["C","D","A"], ["D","A","B"]]);
assert(triplet([3,4,5,6],wrap=true) == [[3,4,5], [4,5,6], [5,6,3], [6,3,4]]);
assert(triplet("ABCD",wrap=true) == [["A","B","C"], ["B","C","D"], ["C","D","A"], ["D","A","B"]]);
} }
test_triplet(); test_triplet();
module test_triplet_wrap() {
assert(triplet_wrap([3,4,5,6]) == [[3,4,5], [4,5,6], [5,6,3], [6,3,4]]);
assert(triplet_wrap("ABCD") == [["A","B","C"], ["B","C","D"], ["C","D","A"], ["D","A","B"]]);
}
test_triplet_wrap();
module test_permute() { module test_permute() {
assert(permute([3,4,5,6]) == [[3,4],[3,5],[3,6],[4,5],[4,6],[5,6]]); assert(permute([3,4,5,6]) == [[3,4],[3,5],[3,6],[4,5],[4,6],[5,6]]);
assert(permute([3,4,5,6],n=3) == [[3,4,5],[3,4,6],[3,5,6],[4,5,6]]); assert(permute([3,4,5,6],n=3) == [[3,4,5],[3,4,6],[3,5,6],[4,5,6]]);

View file

@ -394,7 +394,7 @@ module test_line_normal() {
assert(line_normal([[0,0],[0,-10]]) == [1,0]); assert(line_normal([[0,0],[0,-10]]) == [1,0]);
assert(approx(line_normal([[0,0],[10,10]]), [-sqrt(2)/2,sqrt(2)/2])); assert(approx(line_normal([[0,0],[10,10]]), [-sqrt(2)/2,sqrt(2)/2]));
pts = [for (p=pair(rands(-100,100,1000,seed_value=4312))) p]; pts = [for (p=pair(rands(-100,100,1000,seed_value=4312))) p];
for (p = pair_wrap(pts)) { for (p = pair(pts,true)) {
p1 = p.x; p1 = p.x;
p2 = p.y; p2 = p.y;
n = unit(p2-p1); n = unit(p2-p1);
@ -619,7 +619,7 @@ module test_circle_point_tangents() {
module test_tri_calc() { module test_tri_calc() {
sides = rands(1,100,100,seed_value=8888); sides = rands(1,100,100,seed_value=8888);
for (p=pair_wrap(sides)) { for (p=pair(sides,true)) {
opp = p[0]; opp = p[0];
adj = p[1]; adj = p[1];
hyp = norm([opp,adj]); hyp = norm([opp,adj]);
@ -642,7 +642,7 @@ module test_tri_calc() {
module test_tri_functions() { module test_tri_functions() {
sides = rands(1,100,100,seed_value=8181); sides = rands(1,100,100,seed_value=8181);
for (p = pair_wrap(sides)) { for (p = pair(sides,true)) {
adj = p.x; adj = p.x;
opp = p.y; opp = p.y;
hyp = norm([opp,adj]); hyp = norm([opp,adj]);

View file

@ -6,7 +6,7 @@
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
BOSL_VERSION = [2,0,539]; BOSL_VERSION = [2,0,540];
// Section: BOSL Library Version Functions // Section: BOSL Library Version Functions

View file

@ -674,7 +674,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
varr = vnf[0], varr = vnf[0],
faces = vnf[1], faces = vnf[1],
edges = sort([ edges = sort([
for (face=faces, edge=pair_wrap(face)) for (face=faces, edge=pair(face,true))
edge[0]<edge[1]? edge : [edge[1],edge[0]] edge[0]<edge[1]? edge : [edge[1],edge[0]]
]), ]),
edgecnts = unique_count(edges), edgecnts = unique_count(edges),
@ -732,8 +732,8 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
for(i = idx(faces), j = idx(faces)) if(i != j) for(i = idx(faces), j = idx(faces)) if(i != j)
if(len(deduplicate(faces[i],closed=true))>=3) if(len(deduplicate(faces[i],closed=true))>=3)
if(len(deduplicate(faces[j],closed=true))>=3) if(len(deduplicate(faces[j],closed=true))>=3)
for(edge1 = pair_wrap(faces[i])) for(edge1 = pair(faces[i],true))
for(edge2 = pair_wrap(faces[j])) for(edge2 = pair(faces[j],true))
if(edge1 == edge2) // Valid adjacent faces will never have the same vertex ordering. if(edge1 == edge2) // Valid adjacent faces will never have the same vertex ordering.
if(_edge_not_reported(edge1, varr, overpop_edges)) if(_edge_not_reported(edge1, varr, overpop_edges))
[ [
@ -768,7 +768,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
f1 = faces[i], f1 = faces[i],
f2 = faces[j], f2 = faces[j],
shared_edges = [ shared_edges = [
for (edge1 = pair_wrap(f1), edge2 = pair_wrap(f2)) let( for (edge1 = pair(f1,true), edge2 = pair(f2,true)) let(
e1 = edge1[0]<edge1[1]? edge1 : [edge1[1],edge1[0]], e1 = edge1[0]<edge1[1]? edge1 : [edge1[1],edge1[0]],
e2 = edge2[0]<edge2[1]? edge2 : [edge2[1],edge2[0]] e2 = edge2[0]<edge2[1]? edge2 : [edge2[1],edge2[0]]
) if (e1==e2) 1 ) if (e1==e2) 1