This commit is contained in:
Adrian Mariano 2020-10-05 17:42:58 -04:00
commit 0fb2491eb2
30 changed files with 1125 additions and 476 deletions

View File

@ -399,7 +399,8 @@ function affine3d_chain(affines, _m=undef, _i=0) =
// Function: apply()
// Usage: apply(transform, points)
// Usage:
// pts = apply(transform, points)
// Description:
// Applies the specified transformation matrix to a point list (or single point). Both inputs can be 2d or 3d, and it is also allowed
// to supply 3d transformations with 2d data as long as the the only action on the z coordinate is a simple scaling.
@ -423,7 +424,8 @@ function apply(transform,points) =
// Function: apply_list()
// Usage: apply_list(points, transform_list)
// Usage:
// pts = apply_list(points, transform_list)
// Description:
// Transforms the specified point list (or single point) using a list of transformation matrices. Transformations on
// the list are applied in the order they appear in the list (as in right multiplication of matrices). Both inputs can be

View File

@ -34,15 +34,19 @@
// is_homogeneous( [[1,["a"]], [2,[true]]], 1 ) // Returns true
// is_homogeneous( [[1,["a"]], [2,[true]]], 2 ) // Returns false
// is_homogeneous( [[1,["a"]], [true,["b"]]] ) // Returns true
function is_homogeneous(l, depth) =
function is_homogeneous(l, depth=10) =
!is_list(l) || l==[] ? false :
let( l0=l[0] )
[] == [for(i=[1:len(l)-1]) if( ! _same_type(l[i],l0, depth+1) ) 0 ];
function _same_type(a,b, depth) =
(depth==0) || (a>=b) || (a==b) || (a<=b)
|| ( is_list(a) && is_list(b) && len(a)==len(b)
&& []==[for(i=idx(a)) if( ! _same_type(a[i],b[i],depth-1) )0] );
(depth==0) ||
(is_undef(a) && is_undef(b)) ||
(is_bool(a) && is_bool(b)) ||
(is_num(a) && is_num(b)) ||
(is_string(a) && is_string(b)) ||
(is_list(a) && is_list(b) && len(a)==len(b)
&& []==[for(i=idx(a)) if( ! _same_type(a[i],b[i],depth-1) ) 0] );
// Function: select()
@ -199,7 +203,7 @@ function list_decreasing(list) =
// Usage:
// repeat(val, n)
// Description:
// Generates a list or array of `n` copies of the given `list`.
// Generates a list or array of `n` copies of the given value `val`.
// If the count `n` is given as a list of counts, then this creates a
// multi-dimensional array, filled with `val`.
// Arguments:
@ -259,14 +263,15 @@ function list_range(n=undef, s=0, e=undef, step=undef) =
// Section: List Manipulation
// Function: reverse()
// Description: Reverses a list/array.
// Description: Reverses a list/array or string.
// Arguments:
// list = The list to reverse.
// x = The list or string to reverse.
// Example:
// reverse([3,4,5,6]); // Returns [6,5,4,3]
function reverse(list) =
assert(is_list(list)||is_string(list))
[ for (i = [len(list)-1 : -1 : 0]) list[i] ];
function reverse(x) =
assert(is_list(x)||is_string(x))
let (elems = [ for (i = [len(x)-1 : -1 : 0]) x[i] ])
is_string(x)? str_join(elems) : elems;
// Function: list_rotate()
@ -275,6 +280,7 @@ function reverse(list) =
// Description:
// Rotates the contents of a list by `n` positions left.
// If `n` is negative, then the rotation is `abs(n)` positions to the right.
// If `list` is a string, then a string is returned with the characters rotates within the string.
// Arguments:
// list = The list to rotate.
// n = The number of positions to rotate by. If negative, rotated to the right. Positive rotates to the left. Default: 1
@ -291,7 +297,8 @@ function reverse(list) =
function list_rotate(list,n=1) =
assert(is_list(list)||is_string(list), "Invalid list or string.")
assert(is_finite(n), "Invalid number")
select(list,n,n+len(list)-1);
let (elems = select(list,n,n+len(list)-1))
is_string(list)? str_join(elems) : elems;
// Function: deduplicate()
@ -309,16 +316,18 @@ function list_rotate(list,n=1) =
// Examples:
// deduplicate([8,3,4,4,4,8,2,3,3,8,8]); // Returns: [8,3,4,8,2,3,8]
// deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]); // Returns: [8,3,4,8,2,3]
// deduplicate("Hello"); // Returns: ["H","e","l","o"]
// deduplicate("Hello"); // Returns: "Helo"
// deduplicate([[3,4],[7,2],[7,1.99],[1,4]],eps=0.1); // Returns: [[3,4],[7,2],[1,4]]
// deduplicate([[7,undef],[7,undef],[1,4],[1,4+1e-12]],eps=0); // Returns: [[7,undef],[1,4],[1,4+1e-12]]
function deduplicate(list, closed=false, eps=EPSILON) =
assert(is_list(list)||is_string(list))
let( l = len(list),
end = l-(closed?0:1) )
is_string(list) || (eps==0)
? [for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]]
: [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]];
let(
l = len(list),
end = l-(closed?0:1)
)
is_string(list) ? str_join([for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]]) :
eps==0 ? [for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]] :
[for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]];
// Function: deduplicate_indexed()
@ -377,9 +386,9 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
// exact = if true return exactly the requested number of points, possibly sacrificing uniformity. If false, return uniform points that may not match the number of points requested. Default: True
// Examples:
// list = [0,1,2,3];
// echo(repeat_entries(list, 6)); // Ouputs [0,0,1,2,2,3]
// echo(repeat_entries(list, 6, exact=false)); // Ouputs [0,0,1,1,2,2,3,3]
// echo(repeat_entries(list, [1,1,2,1], exact=false)); // Ouputs [0,1,2,2,3]
// echo(repeat_entries(list, 6)); // Outputs [0,0,1,2,2,3]
// echo(repeat_entries(list, 6, exact=false)); // Outputs [0,0,1,1,2,2,3,3]
// echo(repeat_entries(list, [1,1,2,1], exact=false)); // Outputs [0,1,2,2,3]
function repeat_entries(list, N, exact = true) =
assert(is_list(list) && len(list)>0, "The list cannot be void.")
assert((is_finite(N) && N>0) || is_vector(N,len(list)),
@ -414,7 +423,7 @@ function repeat_entries(list, N, exact = true) =
// list_set([2,3,4,5], 2, 21); // Returns: [2,3,21,5]
// list_set([2,3,4,5], [1,3], [81,47]); // Returns: [2,81,4,47]
function list_set(list=[],indices,values,dflt=0,minlen=0) =
assert(is_list(list)||is_string(list))
assert(is_list(list))
!is_list(indices)? (
(is_finite(indices) && indices<len(list))?
[for (i=idx(list)) i==indices? values : list[i]]
@ -442,7 +451,7 @@ function list_set(list=[],indices,values,dflt=0,minlen=0) =
// list_insert([3,6,9,12],1,5); // Returns [3,5,6,9,12]
// list_insert([3,6,9,12],[1,3],[5,11]); // Returns [3,5,6,9,11,12]
function list_insert(list, indices, values, _i=0) =
assert(is_list(list)||is_string(list))
assert(is_list(list))
! is_list(indices)?
assert( is_finite(indices) && is_finite(values), "Invalid indices/values." )
assert( indices<=len(list), "Indices must be <= len(list) ." )
@ -476,7 +485,7 @@ function list_insert(list, indices, values, _i=0) =
// list_insert([3,6,9,12],1); // Returns: [3,9,12]
// list_insert([3,6,9,12],[1,3]); // Returns: [3,9]
function list_remove(list, indices) =
assert(is_list(list)||is_string(list), "Invalid list/string." )
assert(is_list(list))
is_finite(indices) ?
[
for (i=[0:1:min(indices, len(list)-1)-1]) list[i],
@ -509,7 +518,7 @@ function list_remove(list, indices) =
// domestic = list_remove_values(animals, ["bat","rat"], all=true); // Returns: ["cat","dog"]
// animals4 = list_remove_values(animals, ["tucan","rat"], all=true); // Returns: ["bat","cat","dog","bat"]
function list_remove_values(list,values=[],all=false) =
assert(is_list(list)||is_string(list))
assert(is_list(list))
!is_list(values)? list_remove_values(list, values=[values], all=all) :
let(
idxs = all? flatten(search(values,list,0)) : search(values,list,1),
@ -530,6 +539,7 @@ function list_remove_values(list,values=[],all=false) =
function bselect(array,index) =
assert(is_list(array)||is_string(array), "Improper array." )
assert(is_list(index) && len(index)>=len(array) , "Improper index list." )
is_string(array)? str_join(bselect( [for (x=array) x], index)) :
[for(i=[0:len(array)-1]) if (index[i]) array[i]];
@ -568,7 +578,7 @@ function list_bset(indexset, valuelist, dflt=0) =
// Arguments:
// array = A list of lists.
function list_shortest(array) =
assert(is_list(array)||is_string(list), "Invalid input." )
assert(is_list(array), "Invalid input." )
min([for (v = array) len(v)]);
@ -578,7 +588,7 @@ function list_shortest(array) =
// Arguments:
// array = A list of lists.
function list_longest(array) =
assert(is_list(array)||is_string(list), "Invalid input." )
assert(is_list(array), "Invalid input." )
max([for (v = array) len(v)]);
@ -590,7 +600,7 @@ function list_longest(array) =
// minlen = The minimum length to pad the list to.
// fill = The value to pad the list with.
function list_pad(array, minlen, fill=undef) =
assert(is_list(array)||is_string(list), "Invalid input." )
assert(is_list(array), "Invalid input." )
concat(array,repeat(fill,minlen-len(array)));
@ -601,7 +611,7 @@ function list_pad(array, minlen, fill=undef) =
// array = A list.
// minlen = The minimum length to pad the list to.
function list_trim(array, maxlen) =
assert(is_list(array)||is_string(list), "Invalid input." )
assert(is_list(array), "Invalid input." )
[for (i=[0:1:min(len(array),maxlen)-1]) array[i]];
@ -614,7 +624,7 @@ function list_trim(array, maxlen) =
// minlen = The minimum length to pad the list to.
// fill = The value to pad the list with.
function list_fit(array, length, fill) =
assert(is_list(array)||is_string(list), "Invalid input." )
assert(is_list(array), "Invalid input." )
let(l=len(array))
l==length ? array :
l> length ? list_trim(array,length)
@ -643,8 +653,10 @@ function _valid_idx(idx,imin,imax) =
// Function: shuffle()
// Description:
// Shuffles the input list into random order.
// If given a string, shuffles the characters within the string.
function shuffle(list) =
assert(is_list(list)||is_string(list), "Invalid input." )
is_string(list)? str_join(shuffle([for (x = list) x])) :
len(list)<=1 ? list :
let (
rval = rands(0,1,len(list)),
@ -763,6 +775,8 @@ function _indexed_sort(arrind) =
// l3 = [[4,0],[7],[3,9],20,[4],[3,1],[8]];
// sorted3 = sort(l3); // Returns: [20,[3,1],[3,9],[4],[4,0],[7],[8]]
function sort(list, idx=undef) =
assert(is_list(list)||is_string(list), "Invalid input." )
is_string(list)? str_join(sort([for (x = list) x],idx)) :
!is_list(list) || len(list)<=1 ? list :
is_homogeneous(list,1)
? let(size = array_dim(list[0]))
@ -796,6 +810,7 @@ function sort(list, idx=undef) =
// idxs2 = sortidx(lst, idx=0); // Returns: [1,2,0,3]
// idxs3 = sortidx(lst, idx=[1,3]); // Returns: [3,0,2,1]
function sortidx(list, idx=undef) =
assert(is_list(list)||is_string(list), "Invalid input." )
!is_list(list) || len(list)<=1 ? list :
is_homogeneous(list,1)
? let(
@ -820,15 +835,16 @@ function sortidx(list, idx=undef) =
// Function: unique()
// Usage:
// unique(arr);
// l = unique(list);
// Description:
// Returns a sorted list with all repeated items removed.
// Arguments:
// arr = The list to uniquify.
function unique(arr) =
assert(is_list(arr)||is_string(arr), "Invalid input." )
len(arr)<=1? arr :
let( sorted = sort(arr))
// list = The list to uniquify.
function unique(list) =
assert(is_list(list)||is_string(list), "Invalid input." )
is_string(list)? str_join(unique([for (x = list) x])) :
len(list)<=1? list :
let( sorted = sort(list))
[ for (i=[0:1:len(sorted)-1])
if (i==0 || (sorted[i] != sorted[i-1]))
sorted[i]
@ -837,18 +853,18 @@ function unique(arr) =
// Function: unique_count()
// Usage:
// unique_count(arr);
// counts = unique_count(list);
// Description:
// Returns `[sorted,counts]` where `sorted` is a sorted list of the unique items in `arr` and `counts` is a list such
// that `count[i]` gives the number of times that `sorted[i]` appears in `arr`.
// Returns `[sorted,counts]` where `sorted` is a sorted list of the unique items in `list` and `counts` is a list such
// that `count[i]` gives the number of times that `sorted[i]` appears in `list`.
// Arguments:
// arr = The list to analyze.
function unique_count(arr) =
assert(is_list(arr) || is_string(arr), "Invalid input." )
arr == [] ? [[],[]] :
let( arr=sort(arr) )
let( ind = [0, for(i=[1:1:len(arr)-1]) if (arr[i]!=arr[i-1]) i] )
[ select(arr,ind), deltas( concat(ind,[len(arr)]) ) ];
// list = The list to analyze.
function unique_count(list) =
assert(is_list(list) || is_string(list), "Invalid input." )
list == [] ? [[],[]] :
let( list=sort(list) )
let( ind = [0, for(i=[1:1:len(list)-1]) if (list[i]!=list[i-1]) i] )
[ select(list,ind), deltas( concat(ind,[len(list)]) ) ];
// Section: List Iteration Helpers
@ -1130,9 +1146,10 @@ function subindex(M, idx) =
// Function: submatrix()
// Usage: submatrix(M, idx1, idx2)
// Usage:
// mat = submatrix(M, idx1, idx2)
// Description:
// The input must be a list of lists (a matrix or 2d array). Returns a submatrix by selecting the rows listed in idx1 and columsn listed in idx2.
// The input must be a list of lists (a matrix or 2d array). Returns a submatrix by selecting the rows listed in idx1 and columns listed in idx2.
// Arguments:
// M = Given list of lists
// idx1 = rows index list or range
@ -1229,7 +1246,8 @@ function diagonal_matrix(diag,offdiag=0) =
// Function: submatrix_set()
// Usage: submatrix_set(M,A,[m],[n])
// Usage:
// mat = submatrix_set(M,A,[m],[n])
// Description:
// Sets a submatrix of M equal to the matrix A. By default the top left corner of M is set to A, but
// you can specify offset coordinates m and n. If A (as adjusted by m and n) extends beyond the bounds
@ -1336,8 +1354,6 @@ function array_dim(v, depth=undef) =
// This function may return undef!
// Function: transpose()
// Usage:

View File

@ -90,13 +90,13 @@ function is_nan(x) = (x!=x);
// is_finite(x);
// Description:
// Returns true if a given value `x` is a finite number.
function is_finite(v) = is_num(0*v);
function is_finite(x) = is_num(x) && !is_nan(0*x);
// Function: is_range()
// Description:
// Returns true if its argument is a range
function is_range(x) = !is_list(x) && is_finite(x[0]+x[1]+x[2]) ;
function is_range(x) = !is_list(x) && is_finite(x[0]) && is_finite(x[1]) && is_finite(x[2]) ;
// Function: valid_range()
@ -458,5 +458,10 @@ module shape_compare(eps=1/1024) {
}
function loop_start() = 0;
function loop_done(x) = x==1;
function looping(x) = x<2;
function loop_next(x,b) = x>=1? 2 : (b? 0 : 1);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View File

@ -44,42 +44,73 @@ module move_copies(a=[[0,0,0]])
// Module: line_of()
//
// Description:
// Evenly distributes `n` copies of all children along a line.
// Copies every child at each position.
//
// Usage:
// line_of(l, [n], [p1]) ...
// line_of(l, spacing, [p1]) ...
// Usage: Spread `n` copies by a given spacing
// line_of(spacing, [n], [p1]) ...
// Usage: Spread copies every given spacing along the line
// line_of(spacing, l, [p1]) ...
// Usage: Spread `n` copies along the length of the line
// line_of(l, [n], [p1]) ...
// Usage: Spread `n` copies along the line from `p1` to `p2`
// line_of(p1, p2, [n]) ...
// Usage: Spread copies every given spacing, centered along the line from `p1` to `p2`
// line_of(p1, p2, spacing) ...
//
// 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:
// .
// If You Know... | Then Use Something Like...
// -------------------------------- | --------------------------------
// Spacing distance, Count | `line_of(spacing=10, n=5) ...` or `line_of(10, n=5) ...`
// Spacing vector, Count | `line_of(spacing=[10,5], n=5) ...` or `line_of([10,5], n=5) ...`
// Spacing distance, Line length | `line_of(spacing=10, l=50) ...` or `line_of(10, l=50) ...`
// Spacing distance, Line vector | `line_of(spacing=10, l=[50,30]) ...` or `line_of(10, l=[50,30]) ...`
// Spacing vector, Line length | `line_of(spacing=[10,5], l=50) ...` or `line_of([10,5], l=50) ...`
// Line length, Count | `line_of(l=50, n=5) ...`
// Line vector, Count | `line_of(l=[50,40], n=5) ...`
// Line endpoints, Count | `line_of(p1=[10,10], p2=[60,-10], n=5) ...`
// Line endpoints, Spacing distance | `line_of(p1=[10,10], p2=[60,-10], spacing=10) ...`
//
// Arguments:
// p1 = Starting point of line.
// p2 = Ending point of line.
// l = Length to spread copies over.
// spacing = A 3D vector indicating which direction and distance to place each subsequent copy at.
// 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.
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
// `$idx` is set to the index number of each child being copied.
//
// Example(FlatSpin):
// line_of([0,0,0], [5,5,20], n=6) cube(size=[3,2,1],center=true);
// Examples:
// line_of(l=40, n=6) cube(size=[3,2,1],center=true);
// line_of(l=[15,30], n=6) cube(size=[3,2,1],center=true);
// line_of(l=40, spacing=10) cube(size=[3,2,1],center=true);
// line_of(spacing=[5,5,0], n=5) cube(size=[3,2,1],center=true);
// Example:
// line_of(10) sphere(d=1);
// line_of(10, n=5) sphere(d=1);
// line_of([10,5], n=5) sphere(d=1);
// line_of(spacing=10, n=6) sphere(d=1);
// line_of(spacing=[10,5], n=6) sphere(d=1);
// line_of(spacing=10, l=50) sphere(d=1);
// line_of(spacing=10, l=[50,30]) sphere(d=1);
// line_of(spacing=[10,5], l=50) sphere(d=1);
// line_of(l=50, n=4) sphere(d=1);
// line_of(l=[50,-30], n=4) sphere(d=1);
// Example(FlatSpin):
// line_of(p1=[0,0,0], p2=[5,5,20], n=6) cube(size=[3,2,1],center=true);
// Example(FlatSpin):
// line_of(p1=[0,0,0], p2=[5,5,20], spacing=6) cube(size=[3,2,1],center=true);
// Example: All Children are Copied at Each Spread Position
// line_of(l=20, n=3) {
// cube(size=[1,3,1],center=true);
// cube(size=[3,1,1],center=true);
// }
module line_of(p1, p2, spacing, l, n)
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)) :
@ -92,6 +123,7 @@ module line_of(p1, p2, spacing, l, n)
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)

View File

@ -177,6 +177,7 @@ function line_ray_intersection(line,ray,eps=EPSILON) =
let(
isect = _general_line_intersection(line,ray,eps=eps)
)
is_undef(isect[0]) ? undef :
(isect[2]<0-eps) ? undef : isect[0];
@ -195,7 +196,10 @@ function line_segment_intersection(line,segment,eps=EPSILON) =
assert( _valid_line(line, dim=2,eps=eps) &&_valid_line(segment,dim=2,eps=eps), "Invalid line or segment." )
let(
isect = _general_line_intersection(line,segment,eps=eps)
) isect[2]<0-eps || isect[2]>1+eps ? undef : isect[0];
)
is_undef(isect[0]) ? undef :
isect[2]<0-eps || isect[2]>1+eps ? undef :
isect[0];
// Function: ray_intersection()
@ -214,6 +218,7 @@ function ray_intersection(r1,r2,eps=EPSILON) =
let(
isect = _general_line_intersection(r1,r2,eps=eps)
)
is_undef(isect[0]) ? undef :
isect[1]<0-eps || isect[2]<0-eps ? undef : isect[0];
@ -233,11 +238,9 @@ function ray_segment_intersection(ray,segment,eps=EPSILON) =
let(
isect = _general_line_intersection(ray,segment,eps=eps)
)
isect[1]<0-eps
|| isect[2]<0-eps
|| isect[2]>1+eps
? undef
: isect[0];
is_undef(isect[0]) ? undef :
isect[1]<0-eps || isect[2]<0-eps || isect[2]>1+eps ? undef :
isect[0];
// Function: segment_intersection()
@ -256,12 +259,9 @@ function segment_intersection(s1,s2,eps=EPSILON) =
let(
isect = _general_line_intersection(s1,s2,eps=eps)
)
isect[1]<0-eps
|| isect[1]>1+eps
|| isect[2]<0-eps
|| isect[2]>1+eps
? undef
: isect[0];
is_undef(isect[0]) ? undef :
isect[1]<0-eps || isect[1]>1+eps || isect[2]<0-eps || isect[2]>1+eps ? undef :
isect[0];
// Function: line_closest_point()
@ -1220,16 +1220,25 @@ function in_front_of_plane(plane, point) =
// Section: Circle Calculations
// Function: find_circle_2tangents()
// Usage:
// find_circle_2tangents(pt1, pt2, pt3, r|d, <tangents>);
// Function&Module: circle_2tangents()
// Usage: As Function
// circ = circle_2tangents(pt1, pt2, pt3, r|d, <tangents>);
// Usage: As Module
// circle_2tangents(pt1, pt2, pt3, r|d, <h>, <center>);
// Description:
// Given a pair of rays with a common origin, and a known circle radius/diameter, finds
// the centerpoint for the circle of that size that touches both rays tangentally.
// Both rays start at `pt2`, one passing through `pt1`, and the other through `pt3`.
// If the rays given are collinear, `undef` is returned. Otherwise, if `tangents` is
// true, then `[CP,NORMAL]` is returned. If `tangents` is false, the more extended
// `[CP,NORMAL,TANPT1,TANPT2,ANG1,ANG2]` is returned
// .
// When called as a module with an `h` height argument, creates a 3D cylinder of `h`
// length at the found centerpoint, aligned with the found normal.
// .
// When called as a module with 2D data and no `h` argument, creates a 2D circle of
// the given radius/diameter, tangentially touching both rays.
// .
// When called as a function with collinear rays, returns `undef`.
// Otherwise, when called as a function with `tangents=false`, returns `[CP,NORMAL]`.
// Otherwise, when called as a function with `tangents=true`, returns `[CP,NORMAL,TANPT1,TANPT2,ANG1,ANG2]`.
// - CP is the centerpoint of the circle.
// - NORMAL is the normal vector of the plane that the circle is on (UP or DOWN if the points are 2D).
// - TANPT1 is the point where the circle is tangent to the ray `[pt2,pt1]`.
@ -1242,13 +1251,15 @@ function in_front_of_plane(plane, point) =
// pt3 = A point that the second ray passes though.
// r = The radius of the circle to find.
// d = The diameter of the circle to find.
// h = Height of the cylinder to create, when called as a module.
// center = When called as a module, center the cylinder if true, Default: false
// tangents = If true, extended information about the tangent points is calculated and returned. Default: false
// Example(2D):
// pts = [[60,40], [10,10], [65,5]];
// rad = 10;
// stroke([pts[1],pts[0]], endcap2="arrow2");
// stroke([pts[1],pts[2]], endcap2="arrow2");
// circ = find_circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad);
// circ = circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad);
// translate(circ[0]) {
// color("green") {
// stroke(circle(r=rad),closed=true);
@ -1259,14 +1270,29 @@ function in_front_of_plane(plane, point) =
// translate(circ[0]) color("red") circle(d=2, $fn=12);
// labels = [[pts[0], "pt1"], [pts[1],"pt2"], [pts[2],"pt3"], [circ[0], "CP"], [circ[0]+[cos(315),sin(315)]*rad*0.7, "r"]];
// for(l=labels) translate(l[0]+[0,2]) color("black") text(text=l[1], size=2.5, halign="center");
function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
// Example(2D):
// pts = [[-5,25], [5,-25], [45,15]];
// rad = 12;
// color("blue") stroke(pts, width=0.75, endcaps="arrow2");
// circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad);
// Example: Non-centered Cylinder
// pts = [[45,15,10], [5,-25,5], [-5,25,20]];
// rad = 12;
// color("blue") stroke(pts, width=0.75, endcaps="arrow2");
// circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad, h=10, center=false);
// Example: Non-centered Cylinder
// pts = [[45,15,10], [5,-25,5], [-5,25,20]];
// rad = 12;
// color("blue") stroke(pts, width=0.75, endcaps="arrow2");
// circle_2tangents(pt1=pts[0], pt2=pts[1], pt3=pts[2], r=rad, h=10, center=true);
function circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
let(r = get_radius(r=r, d=d, dflt=undef))
assert(r!=undef, "Must specify either r or d.")
assert( ( is_path(pt1) && len(pt1)==3 && is_undef(pt2) && is_undef(pt3))
|| (is_matrix([pt1,pt2,pt3]) && (len(pt1)==2 || len(pt1)==3) ),
"Invalid input points." )
is_undef(pt2)
? find_circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents)
? circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents)
: collinear(pt1, pt2, pt3)? undef :
let(
v1 = unit(pt1 - pt2),
@ -1287,11 +1313,29 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
)
[cp, n, tp1, tp2, dang1, dang2];
module circle_2tangents(pt1, pt2, pt3, r, d, h, center=false) {
c = circle_2tangents(pt1=pt1, pt2=pt2, pt3=pt3, r=r, d=d);
assert(!is_undef(c), "Cannot find circle when both rays are collinear.");
cp = c[0]; n = c[1];
if (approx(point3d(cp).z,0) && approx(point2d(n),[0,0]) && is_undef(h)) {
translate(cp) circle(r=r, d=d);
} else {
assert(is_finite(h), "h argument required when result is not flat on the XY plane.");
translate(cp) {
rot(from=UP, to=n) {
cylinder(r=r, d=d, h=h, center=center);
}
}
}
}
// Function: find_circle_3points()
// Usage:
// find_circle_3points(pt1, pt2, pt3);
// find_circle_3points([pt1, pt2, pt3]);
// Function&Module: circle_3points()
// Usage: As Function
// circ = circle_3points(pt1, pt2, pt3);
// circ = circle_3points([pt1, pt2, pt3]);
// Usage: As Module
// circle_3points(pt1, pt2, pt3, <h>, <center>);
// circle_3points([pt1, pt2, pt3], <h>, <center>);
// Description:
// Returns the [CENTERPOINT, RADIUS, NORMAL] of the circle that passes through three non-collinear
// points where NORMAL is the normal vector of the plane that the circle is on (UP or DOWN if the points are 2D).
@ -1305,16 +1349,30 @@ function find_circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
// pt1 = The first point.
// pt2 = The second point.
// pt3 = The third point.
// h = Height of the cylinder to create, when called as a module.
// center = When called as a module, center the cylinder if true, Default: false
// Example(2D):
// pts = [[60,40], [10,10], [65,5]];
// circ = find_circle_3points(pts[0], pts[1], pts[2]);
// circ = circle_3points(pts[0], pts[1], pts[2]);
// translate(circ[0]) color("green") stroke(circle(r=circ[1]),closed=true,$fn=72);
// translate(circ[0]) color("red") circle(d=3, $fn=12);
// move_copies(pts) color("blue") circle(d=3, $fn=12);
function find_circle_3points(pt1, pt2, pt3) =
// Example(2D):
// pts = [[30,40], [10,20], [55,30]];
// circle_3points(pts[0], pts[1], pts[2]);
// move_copies(pts) color("blue") circle(d=3, $fn=12);
// Example: Non-Centered Cylinder
// pts = [[30,15,30], [10,20,15], [55,25,25]];
// circle_3points(pts[0], pts[1], pts[2], h=10, center=false);
// move_copies(pts) color("cyan") sphere(d=3, $fn=12);
// Example: Centered Cylinder
// pts = [[30,15,30], [10,20,15], [55,25,25]];
// circle_3points(pts[0], pts[1], pts[2], h=10, center=true);
// move_copies(pts) color("cyan") sphere(d=3, $fn=12);
function circle_3points(pt1, pt2, pt3) =
(is_undef(pt2) && is_undef(pt3) && is_list(pt1))
? find_circle_3points(pt1[0], pt1[1], pt1[2])
: assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3)
? circle_3points(pt1[0], pt1[1], pt1[2])
: assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3)
&& max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
"Invalid point(s)." )
collinear(pt1,pt2,pt3)? [undef,undef,undef] :
@ -1330,11 +1388,24 @@ function find_circle_3points(pt1, pt2, pt3) =
sc = plane_intersection(
[ each e1, e1*pm[es[1]] ], // planes orthogonal to 2 edges
[ each e2, e2*pm[es[2]] ],
[ each n, n*v[0] ] ) , // triangle plane
cp = len(pt1)+len(pt2)+len(pt3)>6 ? sc: [sc.x, sc.y],
[ each n, n*v[0] ]
), // triangle plane
cp = len(pt1)+len(pt2)+len(pt3)>6 ? sc : [sc.x, sc.y],
r = norm(sc-v[0])
)
[ cp, r, n ];
) [ cp, r, n ];
module circle_3points(pt1, pt2, pt3, h, center=false) {
c = circle_3points(pt1, pt2, pt3);
assert(!is_undef(c[0]), "Points cannot be collinear.");
cp = c[0]; r = c[1]; n = c[2];
if (approx(point3d(cp).z,0) && approx(point2d(n),[0,0]) && is_undef(h)) {
translate(cp) circle(r=r);
} else {
assert(is_finite(h));
translate(cp) rot(from=UP,to=n) cylinder(r=r, h=h, center=center);
}
}
// Function: circle_point_tangents()
@ -1342,7 +1413,7 @@ function find_circle_3points(pt1, pt2, pt3) =
// tangents = circle_point_tangents(r|d, cp, pt);
// Description:
// Given a 2d circle and a 2d point outside that circle, finds the 2d tangent point(s) on the circle for a
// line passing through the point. Returns list of zero or more sublists of [ANG, TANGPT]
// line passing through the point. Returns a list of zero or more 2D tangent points.
// Arguments:
// r = Radius of the circle.
// d = Diameter of the circle.
@ -1350,7 +1421,7 @@ function find_circle_3points(pt1, pt2, pt3) =
// pt = The coordinates of the 2d external point.
// Example:
// cp = [-10,-10]; r = 30; pt = [30,10];
// tanpts = subindex(circle_point_tangents(r=r, cp=cp, pt=pt),1);
// tanpts = circle_point_tangents(r=r, cp=cp, pt=pt);
// color("yellow") translate(cp) circle(r=r);
// color("cyan") for(tp=tanpts) {stroke([tp,pt]); stroke([tp,cp]);}
// color("red") move_copies(tanpts) circle(d=3,$fn=12);
@ -1368,11 +1439,12 @@ function circle_point_tangents(r, d, cp, pt) =
let(
relang = acos(r/dist),
angs = [baseang + relang, baseang - relang]
) [for (ang=angs) [ang, cp + r*[cos(ang),sin(ang)]]];
) [for (ang=angs) cp + r*[cos(ang),sin(ang)]];
// Function: circle_circle_tangents()
// Usage: circle_circle_tangents(c1, r1|d1, c2, r2|d2)
// Usage:
// segs = circle_circle_tangents(c1, r1|d1, c2, r2|d2);
// Description:
// Computes 2d lines tangents to a pair of circles in 2d. Returns a list of line endpoints [p1,p2] where
// p2 is the tangent point on circle 1 and p2 is the tangent point on circle 2.

View File

@ -128,7 +128,7 @@ function base_radius(pitch=5, teeth=11, PA=28) =
// Function bevel_pitch_angle()
// Usage:
// bevel_pitch_angle(teeth, mate_teeth, [drive_angle]);
// x = bevel_pitch_angle(teeth, mate_teeth, [drive_angle]);
// Description:
// Returns the correct pitch angle (bevelang) for a bevel gear with a given number of tooth, that is
// matched to another bevel gear with a (possibly different) number of teeth.
@ -150,6 +150,10 @@ function _gear_q7(f,r,b,r2,t,s) = _gear_q6(b,s,t,(1-f)*max(b,r)+f*r2); //
// Function&Module: gear_tooth_profile()
// Usage: As Module
// gear_tooth_profile(pitch, teeth, <PA>, <clearance>, <backlash>, <interior>, <valleys>);
// Usage: As Function
// path = gear_tooth_profile(pitch, teeth, <PA>, <clearance>, <backlash>, <interior>, <valleys>);
// Description:
// When called as a function, returns the 2D profile path for an individual gear tooth.
// When called as a module, creates the 2D profile shape for an individual gear tooth.
@ -157,8 +161,8 @@ function _gear_q7(f,r,b,r2,t,s) = _gear_q6(b,s,t,(1-f)*max(b,r)+f*r2); //
// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
// teeth = Total number of teeth along the rack
// PA = Controls how straight or bulged the tooth sides are. In degrees.
// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
// clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters)
// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
// interior = If true, create a mask for difference()ing from something else.
// valleys = If true, add the valley bottoms on either side of the tooth.
// Example(2D):
@ -169,8 +173,8 @@ function gear_tooth_profile(
pitch = 3,
teeth = 11,
PA = 28,
backlash = 0.0,
clearance = undef,
backlash = 0.0,
interior = false,
valleys = true
) = let(
@ -227,6 +231,10 @@ module gear_tooth_profile(
// Function&Module: gear2d()
// Usage: As Module
// gear2d(pitch, teeth, <hide>, <PA>, <clearance>, <backlash>, <interior>);
// Usage: As Function
// poly = gear2d(pitch, teeth, <hide>, <PA>, <clearance>, <backlash>, <interior>);
// Description:
// When called as a module, creates a 2D involute spur gear. When called as a function, returns a
// 2D path for the perimeter of a 2D involute spur gear. Normally, you should just specify the
@ -310,37 +318,29 @@ module gear2d(
// Module: gear()
// Usage:
// gear(pitch, teeth, thickness, <shaft_diam>, <hide>, <PA>, <clearance>, <backlash>, <helical>, <slices>, <interior>);
// Description:
// Creates a (potentially helical) involute spur gear.
// The module `gear()` gives an involute spur gear, with reasonable
// defaults for all the parameters. Normally, you should just choose
// the first 4 parameters, and let the rest be default values. The
// module `gear()` gives a gear in the XY plane, centered on the origin,
// with one tooth centered on the positive Y axis. The various functions
// below it take the same parameters, and return various measurements
// for the gear. The most important is `pitch_radius()`, which tells
// how far apart to space gears that are meshing, and `outer_radius()`,
// which gives the size of the region filled by the gear. A gear has
// a "pitch circle", which is an invisible circle that cuts through
// the middle of each tooth (though not the exact center). In order
// for two gears to mesh, their pitch circles should just touch. So
// the distance between their centers should be `pitch_radius()` for
// one, plus `pitch_radius()` for the other, which gives the radii of
// their pitch circles.
// In order for two gears to mesh, they must have the same `pitch`
// and `PA` parameters. `pitch` gives the number
// of millimeters of arc around the pitch circle covered by one tooth
// and one space between teeth. The `PA` controls how flat or
// bulged the sides of the teeth are. Common values include 14.5
// degrees and 20 degrees, and occasionally 25. Though I've seen 28
// recommended for plastic gears. Larger numbers bulge out more, giving
// stronger teeth, so 28 degrees is the default here.
// The ratio of `teeth` for two meshing gears gives how many
// times one will make a full revolution when the the other makes one
// full revolution. If the two numbers are coprime (i.e. are not
// both divisible by the same number greater than 1), then every tooth
// on one gear will meet every tooth on the other, for more even wear.
// So coprime numbers of teeth are good.
// Creates a (potentially helical) involute spur gear. The module `gear()` gives an involute spur
// gear, with reasonable defaults for all the parameters. Normally, you should just choose the
// first 4 parameters, and let the rest be default values. The module `gear()` gives a gear in the
// XY plane, centered on the origin, with one tooth centered on the positive Y axis. The various
// functions below it take the same parameters, and return various measurements for the gear. The
// most important is `pitch_radius()`, which tells how far apart to space gears that are meshing,
// and `outer_radius()`, which gives the size of the region filled by the gear. A gear has a "pitch
// circle", which is an invisible circle that cuts through the middle of each tooth (though not the
// exact center). In order for two gears to mesh, their pitch circles should just touch. So the
// distance between their centers should be `pitch_radius()` for one, plus `pitch_radius()` for the
// other, which gives the radii of their pitch circles. In order for two gears to mesh, they must
// have the same `pitch` and `PA` parameters. `pitch` gives the number of millimeters of arc around
// the pitch circle covered by one tooth and one space between teeth. The `PA` controls how flat or
// bulged the sides of the teeth are. Common values include 14.5 degrees and 20 degrees, and
// occasionally 25. Though I've seen 28 recommended for plastic gears. Larger numbers bulge out
// more, giving stronger teeth, so 28 degrees is the default here. The ratio of `teeth` for two
// meshing gears gives how many times one will make a full revolution when the the other makes one
// full revolution. If the two numbers are coprime (i.e. are not both divisible by the same number
// greater than 1), then every tooth on one gear will meet every tooth on the other, for more even
// wear. So coprime numbers of teeth are good.
// Arguments:
// pitch = The circular pitch, or distance between teeth around the pitch circle, in mm.
// teeth = Total number of teeth around the entire perimeter
@ -361,13 +361,33 @@ module gear2d(
// gear(pitch=5, teeth=20, thickness=8, shaft_diam=5);
// Example: Beveled Gear
// gear(pitch=5, teeth=20, thickness=10, shaft_diam=5, helical=-30, slices=12, $fa=1, $fs=1);
// Example: Assembly of Gears
// n1 = 11; //red gear number of teeth
// n2 = 20; //green gear
// n3 = 5; //blue gear
// n4 = 20; //orange gear
// n5 = 8; //gray rack
// pitch = 9; //all meshing gears need the same `pitch` (and the same `PA`)
// thickness = 6;
// hole = 3;
// height = 12;
// d1 =pitch_radius(pitch,n1);
// d12=pitch_radius(pitch,n1) + pitch_radius(pitch,n2);
// d13=pitch_radius(pitch,n1) + pitch_radius(pitch,n3);
// d14=pitch_radius(pitch,n1) + pitch_radius(pitch,n4);
// translate([ 0, 0, 0]) rotate([0,0, $t*360/n1]) color([1.00,0.75,0.75]) gear(pitch,n1,thickness,hole);
// translate([ 0, d12, 0]) rotate([0,0,-($t+n2/2-0*n1+1/2)*360/n2]) color([0.75,1.00,0.75]) gear(pitch,n2,thickness,hole);
// translate([ d13, 0, 0]) rotate([0,0,-($t-n3/4+n1/4+1/2)*360/n3]) color([0.75,0.75,1.00]) gear(pitch,n3,thickness,hole);
// translate([ d13, 0, 0]) rotate([0,0,-($t-n3/4+n1/4+1/2)*360/n3]) color([0.75,0.75,1.00]) gear(pitch,n3,thickness,hole);
// translate([-d14, 0, 0]) rotate([0,0,-($t-n4/4-n1/4+1/2-floor(n4/4)-3)*360/n4]) color([1.00,0.75,0.50]) gear(pitch,n4,thickness,hole,hide=n4-3);
// translate([(-floor(n5/2)-floor(n1/2)+$t+n1/2)*9, -d1+0.0, 0]) color([0.75,0.75,0.75]) rack(pitch=pitch,teeth=n5,thickness=thickness,height=height,anchor=CENTER);
module gear(
pitch = 3,
teeth = 11,
PA = 28,
thickness = 6,
hide = 0,
shaft_diam = 3,
hide = 0,
PA = 28,
clearance = undef,
backlash = 0.0,
helical = 0,
@ -405,6 +425,8 @@ module gear(
// Module: bevel_gear()
// Usage:
// bevel_gear(pitch, teeth, face_width, bevelang, <shaft_diam>, <hide>, <PA>, <clearance>, <backlash>, <spiral_rad>, <spiral_ang>, <slices>, <interior>);
// Description:
// Creates a (potentially spiral) bevel gear.
// The module `bevel_gear()` gives an bevel gear, with reasonable
@ -459,11 +481,11 @@ module gear(
module bevel_gear(
pitch = 3,
teeth = 11,
PA = 20,
face_width = 6,
bevelang = 45,
hide = 0,
shaft_diam = 3,
hide = 0,
PA = 20,
clearance = undef,
backlash = 0.0,
spiral_rad = 0,
@ -586,6 +608,8 @@ module bevel_gear(
// Module: rack()
// Usage:
// rack(pitch, teeth, thickness, height, <PA>, <backlash>);
// Description:
// The module `rack()` gives a rack, which is a bar with teeth. A
// rack can mesh with any gear that has the same `pitch` and
@ -668,35 +692,6 @@ module rack(
}
//////////////////////////////////////////////////////////////////////////////////////////////
//example gear train.
//Try it with OpenSCAD View/Animate command with 20 steps and 24 FPS.
//The gears will continue to be rotated to mesh correctly if you change the number of teeth.
/*
n1 = 11; //red gear number of teeth
n2 = 20; //green gear
n3 = 5; //blue gear
n4 = 20; //orange gear
n5 = 8; //gray rack
pitch = 9; //all meshing gears need the same `pitch` (and the same `PA`)
thickness = 6;
hole = 3;
height = 12;
d1 =pitch_radius(pitch,n1);
d12=pitch_radius(pitch,n1) + pitch_radius(pitch,n2);
d13=pitch_radius(pitch,n1) + pitch_radius(pitch,n3);
d14=pitch_radius(pitch,n1) + pitch_radius(pitch,n4);
translate([ 0, 0, 0]) rotate([0,0, $t*360/n1]) color([1.00,0.75,0.75]) gear(pitch,n1,thickness,hole);
translate([ 0, d12, 0]) rotate([0,0,-($t+n2/2-0*n1+1/2)*360/n2]) color([0.75,1.00,0.75]) gear(pitch,n2,thickness,hole);
translate([ d13, 0, 0]) rotate([0,0,-($t-n3/4+n1/4+1/2)*360/n3]) color([0.75,0.75,1.00]) gear(pitch,n3,thickness,hole);
translate([ d13, 0, 0]) rotate([0,0,-($t-n3/4+n1/4+1/2)*360/n3]) color([0.75,0.75,1.00]) gear(pitch,n3,thickness,hole);
translate([-d14, 0, 0]) rotate([0,0,-($t-n4/4-n1/4+1/2-floor(n4/4)-3)*360/n4]) color([1.00,0.75,0.50]) gear(pitch,n4,thickness,hole,hide=n4-3);
translate([(-floor(n5/2)-floor(n1/2)+$t+n1/2-1/2)*9, -d1+0.0, 0]) rotate([0,0,0]) color([0.75,0.75,0.75]) rack(pitch,n5,thickness,height);
*/
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

262
math.scad
View File

@ -26,17 +26,17 @@ NAN = acos(2); // The value `nan`, useful for comparisons.
// Usage:
// sqr(x);
// Description:
// Returns the square of the given number or entries in list
// If given a number, returns the square of that number,
// If given a vector, returns the sum-of-squares/dot product of the vector elements.
// If given a matrix, returns the matrix multiplication of the matrix with itself.
// Examples:
// sqr(3); // Returns: 9
// sqr(-4); // Returns: 16
// sqr([3,4]); // Returns: [9,16]
// sqr([[1,2],[3,4]]); // Returns [[1,4],[9,16]]
// sqr([[1,2],3]); // Returns [[1,4],9]
// sqr([2,3,4]); // Returns: 29
// sqr([[1,2],[3,4]]); // Returns [[7,10],[15,22]]
function sqr(x) =
is_list(x) ? [for(val=x) sqr(val)] :
is_finite(x) ? x*x :
assert(is_finite(x) || is_vector(x), "Input is not a number nor a list of numbers.");
assert(is_finite(x) || is_vector(x) || is_matrix(x), "Input is not a number nor a list of numbers.")
x*x;
// Function: log2()
@ -56,7 +56,7 @@ function log2(x) =
// Function: hypot()
// Usage:
// l = hypot(x,y,[z]);
// l = hypot(x,y,<z>);
// Description:
// Calculate hypotenuse length of a 2D or 3D triangle.
// Arguments:
@ -73,7 +73,7 @@ function hypot(x,y,z=0) =
// Function: factorial()
// Usage:
// x = factorial(n,[d]);
// x = factorial(n,<d>);
// Description:
// Returns the factorial of the given integer value, or n!/d! if d is given.
// Arguments:
@ -373,7 +373,7 @@ function modang(x) =
// Function: modrange()
// Usage:
// modrange(x, y, m, [step])
// modrange(x, y, m, <step>)
// Description:
// Returns a normalized list of numbers from `x` to `y`, by `step`, modulo `m`. Wraps if `x` > `y`.
// Arguments:
@ -401,7 +401,7 @@ function modrange(x, y, m, step=1) =
// Function: rand_int()
// Usage:
// rand_int(minval,maxval,N,[seed]);
// rand_int(minval,maxval,N,<seed>);
// Description:
// Return a list of random integers in the range of minval to maxval, inclusive.
// Arguments:
@ -421,7 +421,7 @@ function rand_int(minval, maxval, N, seed=undef) =
// Function: gaussian_rands()
// Usage:
// gaussian_rands(mean, stddev, [N], [seed])
// gaussian_rands(mean, stddev, <N>, <seed>)
// Description:
// Returns a random number with a gaussian/normal distribution.
// Arguments:
@ -437,7 +437,7 @@ function gaussian_rands(mean, stddev, N=1, seed=undef) =
// Function: log_rands()
// Usage:
// log_rands(minval, maxval, factor, [N], [seed]);
// log_rands(minval, maxval, factor, <N>, <seed>);
// Description:
// Returns a single random number, with a logarithmic distribution.
// Arguments:
@ -506,6 +506,8 @@ function lcm(a,b=[]) =
// Section: Sums, Products, Aggregate Functions.
// Function: sum()
// Usage:
// x = sum(v, <dflt>);
// Description:
// Returns the sum of all entries in the given consistent list.
// If passed an array of vectors, returns the sum the vectors.
@ -518,8 +520,7 @@ function lcm(a,b=[]) =
// sum([1,2,3]); // returns 6.
// sum([[1,2,3], [3,4,5], [5,6,7]]); // returns [9, 12, 15]
function sum(v, dflt=0) =
is_list(v) && len(v) == 0 ? dflt :
is_vector(v) || is_matrix(v)? [for(i=v) 1]*v :
v==[]? dflt :
assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
_sum(v,v[0]*0);
@ -527,6 +528,8 @@ function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
// Function: cumsum()
// Usage:
// sums = cumsum(v);
// Description:
// Returns a list where each item is the cumulative sum of all items up to and including the corresponding entry in the input list.
// If passed an array of vectors, returns a list of cumulative vectors sums.
@ -552,18 +555,6 @@ function _cumsum(v,_i=0,_acc=[]) =
);
// Function: sum_of_squares()
// Description:
// Returns the sum of the square of each element of a vector.
// Arguments:
// v = The vector to get the sum of.
// Example:
// sum_of_squares([1,2,3]); // Returns: 14.
// sum_of_squares([1,2,4]); // Returns: 21
// sum_of_squares([-3,-2,-1]); // Returns: 14
function sum_of_squares(v) = sum(vmul(v,v));
// Function: sum_of_sines()
// Usage:
// sum_of_sines(a,sines)
@ -585,6 +576,8 @@ function sum_of_sines(a, sines) =
// Function: deltas()
// Usage:
// delts = deltas(v);
// Description:
// Returns a list with the deltas of adjacent entries in the given list.
// The list should be a consistent list of numeric components (numbers, vectors, matrix, etc).
@ -600,6 +593,8 @@ function deltas(v) =
// Function: product()
// Usage:
// x = product(v);
// Description:
// Returns the product of all entries in the given list.
// If passed a list of vectors of same dimension, returns a vector of products of each part.
@ -623,6 +618,8 @@ function _product(v, i=0, _tot) =
// Function: outer_product()
// Usage:
// x = outer_product(u,v);
// Description:
// Compute the outer product of two vectors, a matrix.
// Usage:
@ -633,6 +630,8 @@ function outer_product(u,v) =
// Function: mean()
// Usage:
// x = mean(v);
// Description:
// Returns the arithmetic mean/average of all entries in the given array.
// If passed a list of vectors, returns a vector of the mean of each part.
@ -672,13 +671,14 @@ function convolve(p,q) =
// Section: Matrix math
// Function: linear_solve()
// Usage: linear_solve(A,b)
// Usage:
// solv = linear_solve(A,b)
// Description:
// Solves the linear system Ax=b. If A is square and non-singular the unique solution is returned. If A is overdetermined
// the least squares solution is returned. If A is underdetermined, the minimal norm solution is returned.
// If A is rank deficient or singular then linear_solve returns []. If b is a matrix that is compatible with A
// Solves the linear system Ax=b. If `A` is square and non-singular the unique solution is returned. If `A` is overdetermined
// the least squares solution is returned. If `A` is underdetermined, the minimal norm solution is returned.
// If `A` is rank deficient or singular then linear_solve returns `[]`. If `b` is a matrix that is compatible with `A`
// then the problem is solved for the matrix valued right hand side and a matrix is returned. Note that if you
// want to solve Ax=b1 and Ax=b2 that you need to form the matrix transpose([b1,b2]) for the right hand side and then
// want to solve Ax=b1 and Ax=b2 that you need to form the matrix `transpose([b1,b2])` for the right hand side and then
// transpose the returned value.
function linear_solve(A,b,pivot=true) =
assert(is_matrix(A), "Input should be a matrix.")
@ -702,37 +702,37 @@ function linear_solve(A,b,pivot=true) =
// Function: matrix_inverse()
// Usage:
// matrix_inverse(A)
// mat = matrix_inverse(A)
// Description:
// Compute the matrix inverse of the square matrix A. If A is singular, returns undef.
// Note that if you just want to solve a linear system of equations you should NOT
// use this function. Instead use linear_solve, or use qr_factor. The computation
// Compute the matrix inverse of the square matrix `A`. If `A` is singular, returns `undef`.
// Note that if you just want to solve a linear system of equations you should NOT use this function.
// Instead use [[`linear_solve()`|linear_solve]], or use [[`qr_factor()`|qr_factor]]. The computation
// will be faster and more accurate.
function matrix_inverse(A) =
assert(is_matrix(A,square=true),"Input to matrix_inverse() must be a square matrix")
assert(is_matrix(A) && len(A)==len(A[0]),"Input to matrix_inverse() must be a square matrix")
linear_solve(A,ident(len(A)));
// Function: null_space()
// Usage:
// null_space(A)
// x = null_space(A)
// Description:
// Returns an orthonormal basis for the null space of A, namely the vectors {x} such that Ax=0. If the null space
// is just the origin then returns an empty list.
// Returns an orthonormal basis for the null space of `A`, namely the vectors {x} such that Ax=0.
// If the null space is just the origin then returns an empty list.
function null_space(A,eps=1e-12) =
assert(is_matrix(A))
let(
Q_R=qr_factor(transpose(A),pivot=true),
R=Q_R[1],
zrow = [for(i=idx(R)) if (all_zero(R[i],eps)) i]
Q_R = qr_factor(transpose(A),pivot=true),
R = Q_R[1],
zrow = [for(i=idx(R)) if (all_zero(R[i],eps)) i]
)
len(zrow)==0
? []
: transpose(subindex(Q_R[0],zrow));
len(zrow)==0 ? [] :
transpose(subindex(Q_R[0],zrow));
// Function: qr_factor()
// Usage: qr = qr_factor(A,[pivot])
// Usage:
// qr = qr_factor(A,[pivot]);
// Description:
// Calculates the QR factorization of the input matrix A and returns it as the list [Q,R,P]. This factorization can be
// used to solve linear systems of equations. The factorization is A = Q*R*transpose(P). If pivot is false (the default)
@ -742,25 +742,24 @@ function null_space(A,eps=1e-12) =
function qr_factor(A, pivot=false) =
assert(is_matrix(A), "Input must be a matrix." )
let(
m = len(A),
n = len(A[0])
m = len(A),
n = len(A[0])
)
let(
qr =_qr_factor(A, Q=ident(m),P=ident(n), pivot=pivot, column=0, m = m, n=n),
Rzero =
let( R = qr[1] )
[ for(i=[0:m-1]) [
let( ri = R[i] )
for(j=[0:n-1]) i>j ? 0 : ri[j]
qr = _qr_factor(A, Q=ident(m),P=ident(n), pivot=pivot, column=0, m = m, n=n),
Rzero = let( R = qr[1]) [
for(i=[0:m-1]) [
let( ri = R[i] )
for(j=[0:n-1]) i>j ? 0 : ri[j]
]
]
) [qr[0],Rzero,qr[2]];
]
) [qr[0], Rzero, qr[2]];
function _qr_factor(A,Q,P, pivot, column, m, n) =
column >= min(m-1,n) ? [Q,A,P] :
let(
swap = !pivot ? 1
: _swap_matrix(n,column,column+max_index([for(i=[column:n-1]) sum_of_squares([for(j=[column:m-1]) A[j][i]])])),
: _swap_matrix(n,column,column+max_index([for(i=[column:n-1]) sqr([for(j=[column:m-1]) A[j][i]])])),
A = pivot ? A*swap : A,
x = [for(i=[column:1:m-1]) A[i][column]],
alpha = (x[0]<=0 ? 1 : -1) * norm(x),
@ -782,7 +781,8 @@ function _swap_matrix(n,i,j) =
// Function: back_substitute()
// Usage: back_substitute(R, b, [transpose])
// Usage:
// x = back_substitute(R, b, <transpose>);
// Description:
// Solves the problem Rx=b where R is an upper triangular square matrix. The lower triangular entries of R are
// ignored. If transpose==true then instead solve transpose(R)*x=b.
@ -811,6 +811,8 @@ function _back_substitute(R, b, x=[]) =
// Function: det2()
// Usage:
// d = det2(M);
// Description:
// Optimized function that returns the determinant for the given 2x2 square matrix.
// Arguments:
@ -819,11 +821,13 @@ function _back_substitute(R, b, x=[]) =
// M = [ [6,-2], [1,8] ];
// det = det2(M); // Returns: 50
function det2(M) =
assert( 0*M==[[0,0],[0,0]], "Matrix should be 2x2." )
assert(is_matrix(M,2,2), "Matrix must be 2x2.")
M[0][0] * M[1][1] - M[0][1]*M[1][0];
// Function: det3()
// Usage:
// d = det3(M);
// Description:
// Optimized function that returns the determinant for the given 3x3 square matrix.
// Arguments:
@ -832,13 +836,15 @@ function det2(M) =
// M = [ [6,4,-2], [1,-2,8], [1,5,7] ];
// det = det3(M); // Returns: -334
function det3(M) =
assert( 0*M==[[0,0,0],[0,0,0],[0,0,0]], "Matrix should be 3x3." )
assert(is_matrix(M,3,3), "Matrix must be 3x3.")
M[0][0] * (M[1][1]*M[2][2]-M[2][1]*M[1][2]) -
M[1][0] * (M[0][1]*M[2][2]-M[2][1]*M[0][2]) +
M[2][0] * (M[0][1]*M[1][2]-M[1][1]*M[0][2]);
// Function: determinant()
// Usage:
// d = determinant(M);
// Description:
// Returns the determinant for the given square matrix.
// Arguments:
@ -847,7 +853,7 @@ function det3(M) =
// M = [ [6,4,-2,9], [1,-2,8,3], [1,5,7,6], [4,2,5,1] ];
// det = determinant(M); // Returns: 2267
function determinant(M) =
assert(is_matrix(M,square=true), "Input should be a square matrix." )
assert(is_matrix(M, square=true), "Input should be a square matrix." )
len(M)==1? M[0][0] :
len(M)==2? det2(M) :
len(M)==3? det3(M) :
@ -868,23 +874,22 @@ function determinant(M) =
// Function: is_matrix()
// Usage:
// is_matrix(A,[m],[n],[square])
// is_matrix(A,<m>,<n>,<square>)
// Description:
// Returns true if A is a numeric matrix of height m and width n. If m or n
// are omitted or set to undef then true is returned for any positive dimension.
// If `square` is true then the matrix is required to be square.
// specify m != n and require a square matrix then the result will always be false.
// Arguments:
// A = matrix to test
// m = optional height of matrix
// n = optional width of matrix
// square = set to true to require a square matrix. Default: false
// A = The matrix to test.
// m = Is given, requires the matrix to have the given height.
// n = Is given, requires the matrix to have the given width.
// square = If true, requires the matrix to have a width equal to its height. Default: false
function is_matrix(A,m,n,square=false) =
is_list(A[0])
&& ( let(v = A*A[0]) is_num(0*(v*v)) ) // a matrix of finite numbers
&& (is_undef(n) || len(A[0])==n )
&& (is_undef(m) || len(A)==m )
&& ( !square || len(A)==len(A[0]));
is_list(A)
&& (( is_undef(m) && len(A) ) || len(A)==m)
&& is_list(A[0])
&& (( is_undef(n) && len(A[0]) ) || len(A[0])==n)
&& (!square || len(A) == len(A[0]))
&& is_consistent(A);
// Function: norm_fro()
@ -903,7 +908,7 @@ function norm_fro(A) =
// Function: all_zero()
// Usage:
// all_zero(x);
// x = all_zero(x, <eps>);
// Description:
// Returns true if the finite 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.
@ -924,7 +929,7 @@ function all_zero(x, eps=EPSILON) =
// Function: all_nonzero()
// Usage:
// all_nonzero(x);
// x = all_nonzero(x, <eps>);
// Description:
// Returns true if the finite number passed to it is not almost zero, to within `eps`.
// If passed a list, recursively checks if all items in the list are not almost zero.
@ -1042,7 +1047,7 @@ function all_nonnegative(x) =
// Function: approx()
// Usage:
// approx(a,b,[eps])
// b = approx(a,b,<eps>)
// Description:
// Compares two numbers or vectors, and returns true if they are closer than `eps` to each other.
// Arguments:
@ -1056,12 +1061,9 @@ function all_nonnegative(x) =
// approx(0.3333,1/3,eps=1e-3); // Returns: true
// approx(PI,3.1415926536); // Returns: true
function approx(a,b,eps=EPSILON) =
a==b? true :
a*0!=b*0? false :
is_list(a)
? ([for (i=idx(a)) if( !approx(a[i],b[i],eps=eps)) 1] == [])
: is_num(a) && is_num(b) && (abs(a-b) <= eps);
(a==b && is_bool(a) == is_bool(b)) ||
(is_num(a) && is_num(b) && abs(a-b) <= eps) ||
(is_list(a) && is_list(b) && len(a) == len(b) && [] == [for (i=idx(a)) if (!approx(a[i],b[i],eps=eps)) 1]);
function _type_num(x) =
@ -1075,7 +1077,7 @@ function _type_num(x) =
// Function: compare_vals()
// Usage:
// compare_vals(a, b);
// b = compare_vals(a, b);
// Description:
// Compares two values. Lists are compared recursively.
// Returns <0 if a<b. Returns >0 if a>b. Returns 0 if a==b.
@ -1093,7 +1095,7 @@ function compare_vals(a, b) =
// Function: compare_lists()
// Usage:
// compare_lists(a, b)
// b = compare_lists(a, b)
// Description:
// Compare contents of two lists using `compare_vals()`.
// Returns <0 if `a`<`b`.
@ -1114,6 +1116,8 @@ function compare_lists(a, b) =
// Function: any()
// Usage:
// b = any(l);
// Description:
// Returns true if any item in list `l` evaluates as true.
// If `l` is a lists of lists, `any()` is applied recursively to each sublist.
@ -1127,17 +1131,19 @@ function compare_lists(a, b) =
// any([[0,0], [1,0]]); // Returns true.
function any(l) =
assert(is_list(l), "The input is not a list." )
_any(l, i=0, succ=false);
_any(l);
function _any(l, i=0, succ=false) =
(i>=len(l) || succ)? succ :
_any( l,
i+1,
succ = is_list(l[i]) ? _any(l[i]) : !(!l[i])
);
_any(
l, i+1,
succ = is_list(l[i]) ? _any(l[i]) : !(!l[i])
);
// Function: all()
// Usage:
// b = all(l);
// 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.
@ -1150,21 +1156,21 @@ function _any(l, i=0, succ=false) =
// all([[0,0], [0,0]]); // Returns false.
// all([[0,0], [1,0]]); // Returns false.
// all([[1,1], [1,1]]); // Returns true.
function all(l, i=0, fail=false) =
function all(l) =
assert( is_list(l), "The input is not a list." )
_all(l, i=0, fail=false);
_all(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]
) ;
_all(
l, i+1,
fail = is_list(l[i]) ? !_all(l[i]) : !l[i]
) ;
// Function: count_true()
// Usage:
// count_true(l)
// n = count_true(l)
// 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
@ -1182,26 +1188,22 @@ function _all(l, i=0, fail=false) =
// 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);
function count_true(l, nmax) =
!is_list(l) ? !(!l) ? 1: 0 :
let( c = [for( i = 0,
n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
c = !is_undef(n)? n : count_true(l[i], nmax),
s = c;
i<len(l) && (is_undef(nmax) || s<nmax);
i = i+1,
n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
c = !is_undef(n) || (i==len(l))? n : count_true(l[i], nmax-s),
s = s+c
) s ] )
len(c)<len(l)? nmax: c[len(c)-1];
is_undef(nmax)? len([for (x=l) if(x) 1]) :
!is_list(l) ? ( l? 1: 0) :
_count_true_rec(l, nmax);
// Section: Calculus
// Function: deriv()
// Usage: deriv(data, [h], [closed])
// Usage:
// x = deriv(data, [h], [closed])
// Description:
// Computes a numerical derivative estimate of the data, which may be scalar or vector valued.
// The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly.
@ -1265,7 +1267,8 @@ function _deriv_nonuniform(data, h, closed) =
// Function: deriv2()
// Usage: deriv2(data, [h], [closed])
// Usage:
// x = deriv2(data, <h>, <closed>)
// Description:
// Computes a numerical estimate of the second derivative of the data, which may be scalar or vector valued.
// The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly.
@ -1308,7 +1311,8 @@ function deriv2(data, h=1, closed=false) =
// Function: deriv3()
// Usage: deriv3(data, [h], [closed])
// Usage:
// x = deriv3(data, <h>, <closed>)
// Description:
// Computes a numerical third derivative estimate of the data, which may be scalar or vector valued.
// The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly.
@ -1318,6 +1322,10 @@ function deriv2(data, h=1, closed=false) =
// f'''(t) = (-f(t-2*h)+2*f(t-h)-2*f(t+h)+f(t+2*h)) / 2h^3. At the first and second points from the end
// the estimates are f'''(t) = (-5*f(t)+18*f(t+h)-24*f(t+2*h)+14*f(t+3*h)-3*f(t+4*h)) / 2h^3 and
// f'''(t) = (-3*f(t-h)+10*f(t)-12*f(t+h)+6*f(t+2*h)-f(t+3*h)) / 2h^3.
// Arguments:
// data = the list of the elements to compute the derivative of.
// h = the constant parametric sampling of the data.
// closed = boolean to indicate if the data set should be wrapped around from the end to the start.
function deriv3(data, h=1, closed=false) =
assert( is_consistent(data) , "Input list is not consistent or not numerical.")
assert( len(data)>=5, "Input list has less than 5 elements.")
@ -1347,17 +1355,27 @@ function deriv3(data, h=1, closed=false) =
// Section: Complex Numbers
// Function: C_times()
// Usage: C_times(z1,z2)
// Usage:
// c = C_times(z1,z2)
// Description:
// Multiplies two complex numbers represented by 2D vectors.
// Returns a complex number as a 2D vector [REAL, IMAGINARY].
// Arguments:
// z1 = First complex number, given as a 2D vector [REAL, IMAGINARY]
// z2 = Second complex number, given as a 2D vector [REAL, IMAGINARY]
function C_times(z1,z2) =
assert( is_matrix([z1,z2],2,2), "Complex numbers should be represented by 2D vectors" )
[ z1.x*z2.x - z1.y*z2.y, z1.x*z2.y + z1.y*z2.x ];
// Function: C_div()
// Usage: C_div(z1,z2)
// Usage:
// x = C_div(z1,z2)
// Description:
// Divides two complex numbers represented by 2D vectors.
// Returns a complex number as a 2D vector [REAL, IMAGINARY].
// Arguments:
// z1 = First complex number, given as a 2D vector [REAL, IMAGINARY]
// z2 = Second complex number, given as a 2D vector [REAL, IMAGINARY]
function C_div(z1,z2) =
assert( is_vector(z1,2) && is_vector(z2), "Complex numbers should be represented by 2D vectors." )
assert( !approx(z2,0), "The divisor `z2` cannot be zero." )
@ -1370,16 +1388,14 @@ function C_div(z1,z2) =
// Function: quadratic_roots()
// Usage:
// roots = quadratic_roots(a,b,c,[real])
// roots = quadratic_roots(a,b,c,<real>)
// Description:
// Computes roots of the quadratic equation a*x^2+b*x+c==0, where the
// coefficients are real numbers. If real is true then returns only the
// real roots. Otherwise returns a pair of complex values. This method
// may be more reliable than the general root finder at distinguishing
// real roots from complex roots.
// https://people.csail.mit.edu/bkph/articles/Quadratics.pdf
// Algorithm from: https://people.csail.mit.edu/bkph/articles/Quadratics.pdf
function quadratic_roots(a,b,c,real=false) =
real ? [for(root = quadratic_roots(a,b,c,real=false)) if (root.y==0) root.x]
:
@ -1405,7 +1421,7 @@ function quadratic_roots(a,b,c,real=false) =
// Function: polynomial()
// Usage:
// polynomial(p, z)
// x = polynomial(p, z)
// Description:
// Evaluates specified real polynomial, p, at the complex or real input value, z.
// The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]
@ -1421,8 +1437,8 @@ function polynomial(p,z,k,total) =
// Function: poly_mult()
// Usage:
// polymult(p,q)
// polymult([p1,p2,p3,...])
// x = polymult(p,q)
// x = polymult([p1,p2,p3,...])
// Description:
// Given a list of polynomials represented as real coefficient lists, with the highest degree coefficient first,
// computes the coefficient list of the product polynomial.
@ -1493,7 +1509,7 @@ function poly_add(p,q) =
// Function: poly_roots()
// Usage:
// poly_roots(p,[tol])
// poly_roots(p,<tol>)
// Description:
// Returns all complex roots of the specified real polynomial p.
// The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]
@ -1563,7 +1579,7 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) =
// Function: real_roots()
// Usage:
// real_roots(p, [eps], [tol])
// real_roots(p, <eps>, <tol>)
// Description:
// Returns the real roots of the specified real polynomial p.
// The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]

View File

@ -316,7 +316,8 @@ function path_closest_point(path, pt) =
// Function: path_tangents()
// Usage: path_tangents(path, [closed], [uniform])
// Usage:
// tangs = path_tangents(path, <closed>, <uniform>);
// Description:
// Compute the tangent vector to the input path. The derivative approximation is described in deriv().
// The returns vectors will be normalized to length 1. If any derivatives are zero then
@ -348,7 +349,8 @@ function path_tangents(path, closed=false, uniform=true) =
// Function: path_normals()
// Usage: path_normals(path, [tangents], [closed])
// Usage:
// norms = path_normals(path, <tangents>, <closed>);
// Description:
// Compute the normal vector to the input path. This vector is perpendicular to the
// path tangent and lies in the plane of the curve. When there are collinear points,
@ -371,7 +373,8 @@ function path_normals(path, tangents, closed=false) =
// Function: path_curvature()
// Usage: path_curvature(path, [closed])
// Usage:
// curvs = path_curvature(path, <closed>);
// Description:
// Numerically estimate the curvature of the path (in any dimension).
function path_curvature(path, closed=false) =
@ -388,7 +391,8 @@ function path_curvature(path, closed=false) =
// Function: path_torsion()
// Usage: path_torsion(path, [closed])
// Usage:
// tortions = path_torsion(path, <closed>);
// Description:
// Numerically estimate the torsion of a 3d path.
function path_torsion(path, closed=false) =
@ -1161,10 +1165,11 @@ function _path_plane(path, ind, i,closed) =
function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) =
[for(ind=[0:len(cuts)-1])
let(
zeros = path[0]*0,
nextind = cuts[ind][1],
nextpath = unit(select(path, nextind+1)-select(path, nextind)),
thispath = unit(select(path, nextind) - path[nextind-1]),
lastpath = unit(path[nextind-1] - select(path, nextind-2)),
nextpath = unit(select(path, nextind+1)-select(path, nextind),zeros),
thispath = unit(select(path, nextind) - path[nextind-1],zeros),
lastpath = unit(path[nextind-1] - select(path, nextind-2),zeros),
nextdir =
nextind==len(path) && !closed? lastpath :
(nextind<=len(path)-2 || closed) && approx(cuts[ind][0], path[nextind],eps)?
@ -1214,6 +1219,7 @@ function _sum_preserving_round(data, index=0) =
// Arguments:
// path = path to subdivide
// N = scalar total number of points desired or with `method="segment"` can be a vector requesting `N[i]-1` points on segment i.
// refine = number of points to add each segment.
// closed = set to false if the path is open. Default: True
// exact = if true return exactly the requested number of points, possibly sacrificing uniformity. If false, return uniform point sample that may not match the number of points requested. Default: True
// method = One of `"length"` or `"segment"`. If `"length"`, adds vertices evenly along the total path length. If `"segment"`, adds points evenly among the segments. Default: `"length"`
@ -1251,7 +1257,11 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length
assert(is_path(path))
assert(method=="length" || method=="segment")
assert(num_defined([N,refine]),"Must give exactly one of N and refine")
let(N = first_defined([N,len(path)*refine]))
let(
N = !is_undef(N)? N :
!is_undef(refine)? len(path) * refine :
undef
)
assert((is_num(N) && N>0) || is_vector(N),"Parameter N to subdivide_path must be postive number or vector")
let(
count = len(path) - (closed?0:1),
@ -1284,7 +1294,8 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length
// Function: path_length_fractions()
// Usage: path_length_fractions(path, [closed])
// Usage:
// fracs = path_length_fractions(path, <closed>);
// Description:
// Returns the distance fraction of each point in the path along the path, so the first
// point is zero and the final point is 1. If the path is closed the length of the output
@ -1305,7 +1316,8 @@ function path_length_fractions(path, closed=false) =
// Function: resample_path()
// Usage: resample_path(path, N|spacing, [closed])
// Usage:
// newpath = resample_path(path, N|spacing, <closed>);
// Description:
// Compute a uniform resampling of the input path. If you specify `N` then the output path will have N
// points spaced uniformly (by linear interpolation along the input path segments). The only points of the

View File

@ -543,7 +543,8 @@ _stellated_polyhedra_ = [
// Function: regular_polyhedron_info()
//
// Usage: regular_polyhedron_info(info, ....)
// Usage:
// x = regular_polyhedron_info(info, ....);
//
// Description:
// Calculate characteristics of regular polyhedra or the selection set for regular_polyhedron().

View File

@ -144,7 +144,7 @@ function region_path_crossings(path, region, closed=true, eps=EPSILON) = sort([
) let (
isect = _general_line_intersection(segs[si], s2, eps=eps)
) if (
!is_undef(isect) &&
!is_undef(isect[0]) &&
isect[1] >= 0-eps && isect[1] < 1+eps &&
isect[2] >= 0-eps && isect[2] < 1+eps
)

View File

@ -458,6 +458,179 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
function _scalar_to_vector(value,length,varname) =
is_vector(value)
? assert(len(value)==length, str(varname," must be length ",length))
value
: assert(is_num(value), str(varname, " must be a numerical value"))
repeat(value, length);
// Function: path_join()
// Usage:
// path_join(paths, [joint], [k], [relocate], [closed]
// Description:
// Connect a sequence of paths together into a single path with optional rounding
// applied at the joints. By default the first path is taken as specified and subsequent paths are
// translated into position so that each path starts where the previous path ended.
// If you set relocate to false then this relocation is skipped.
// You specify rounding using the `joint` parameter, which specifies the distance away from the corner
// where the roundover should start. The path_join function may remove many path points to cut the path
// back by the joint length. Rounding is using continous curvature 4th order bezier splines and
// the parameter `k` specifies how smooth the curvature match is. This parameter ranges from 0 to 1 with
// a default of 0.5. Use a larger k value to get a curve that is bigger for the same joint value. When
// k=1 the curve may be similar to a circle if your curves are symmetric. As the path is built up, the joint
// parameter applies to the growing path, so if you pick a large joint parameter it may interact with the
// previous path sections.
// .
// The rounding is created by extending the two clipped paths to define a corner point. If the extensions of
// the paths do not intersect, the function issues an error. When closed=true the final path should actually close
// the shape, repeating the starting point of the shape. If it does not, then the rounding will fill the gap.
// .
// The number of segments in the roundovers is set based on $fn and $fs. If you use $fn it specifies the number of
// segments in the roundover, regardless of its angular extent.
// Arguments:
// paths = list of paths to join
// joint = joint distance, either a number, a pair (giving the previous and next joint distance) or a list of numbers and pairs. Default: 0
// k = curvature parameter, either a number or vector. Default: 0.5
// relocate = set to false to prevent paths from being arranged tail to head. Default: true
// closed = set to true to round the junction between the last and first paths. Default: false
// Example(2D): Connection of 3 simple paths.
// horiz = [[0,0],[10,0]];
// vert = [[0,0],[0,10]];
// stroke(path_join([horiz, vert, -horiz]));
// Example(2D): Adding curvature with joint of 3
// horiz = [[0,0],[10,0]];
// vert = [[0,0],[0,10]];
// stroke(path_join([horiz, vert, -horiz],joint=3,$fn=16));
// Example(2D): Setting k=1 increases the amount of curvature
// horiz = [[0,0],[10,0]];
// vert = [[0,0],[0,10]];
// stroke(path_join([horiz, vert, -horiz],joint=3,k=1,$fn=16));
// Example(2D): Specifying pairs of joint values at a path joint creates an asymmetric curve
// horiz = [[0,0],[10,0]];
// vert = [[0,0],[0,10]];
// stroke(path_join([horiz, vert, -horiz],joint=[[4,1],[1,4]],$fn=16),width=.3);
// Example(2D): A closed square
// horiz = [[0,0],[10,0]];
// vert = [[0,0],[0,10]];
// stroke(path_join([horiz, vert, -horiz, -vert],joint=3,k=1,closed=true,$fn=16),closed=true);
// Example(2D): Different curve at each corner by changing the joint size
// horiz = [[0,0],[10,0]];
// vert = [[0,0],[0,10]];
// stroke(path_join([horiz, vert, -horiz, -vert],joint=[3,0,1,2],k=1,closed=true,$fn=16),closed=true,width=0.4);
// Example(2D): Different curve at each corner by changing the curvature parameter. Note that k=0 still gives a small curve, unlike joint=0 which gives a sharp corner.
// horiz = [[0,0],[10,0]];
// vert = [[0,0],[0,10]];
// stroke(path_join([horiz, vert, -horiz, -vert],joint=3,k=[1,.5,0,.7],closed=true,$fn=16),closed=true,width=0.4);
// Example(2D): Joint value of 7 is larger than half the square so curves interfere with each other, which breaks symmetry because they are computed sequentially
// horiz = [[0,0],[10,0]];
// vert = [[0,0],[0,10]];
// stroke(path_join([horiz, vert, -horiz, -vert],joint=7,k=.4,closed=true,$fn=16),closed=true);
// Example(2D): Unlike round_corners, we can add curves onto curves.
// $fn=64;
// myarc = arc(width=20, thickness=5 );
// stroke(path_join(repeat(myarc,3), joint=4));
// Example(2D): Here we make a closed shape from two arcs and round the sharp tips
// arc1 = arc(width=20, thickness=4,$fn=75);
// arc2 = reverse(arc(width=20, thickness=2,$fn=75));
// stroke(path_join([arc1,arc2]),width=.3); // Join without rounding
// color("red")stroke(path_join([arc1,arc2], 3,k=1,closed=true), width=.3,closed=true,$fn=12); // Join with rounding
// Example(2D): Combining arcs with segments
// arc1 = arc(width=20, thickness=4,$fn=75);
// arc2 = reverse(arc(width=20, thickness=2,$fn=75));
// vpath = [[0,0],[0,-5]];
// stroke(path_join([arc1,vpath,arc2,reverse(vpath)]),width=.2);
// color("red")stroke(path_join([arc1,vpath,arc2,reverse(vpath)], [1,2,2,1],k=1,closed=true), width=.2,closed=true,$fn=12);
// Example(2D): Here relocation is off. We have three segments (in yellow) and add the curves to the segments. Notice that joint zero still produces a curve because it refers to the endpoints of the supplied paths.
// p1 = [[0,0],[2,0]];
// p2 = [[3,1],[1,3]];
// p3 = [[0,3],[-1,1]];
// color("red")stroke(path_join([p1,p2,p3], joint=0, relocate=false,closed=true),width=.3,$fn=12);
// for(x=[p1,p2,p3]) stroke(x,width=.3);
// Example(2D): If you specify closed=true when the last path doesn't meet the first one then it is similar to using relocate=false: the function tries to close the path using a curve. In the example below, this results in a long curve to the left, when given the unclosed three segments as input. Note that if the segments are parallel the function fails with an error. The extension of the curves must intersect in a corner for the rounding to be well-defined. To get a normal rounding of the closed shape, you must include a fourth path, the last segment that closes the shape.
// horiz = [[0,0],[10,0]];
// vert = [[0,0],[0,10]];
// h2 = [[0,-3],[10,0]];
// color("red")stroke(path_join([horiz, vert, -h2],closed=true,joint=3,$fn=25),closed=true,width=.5);
// stroke(path_join([horiz, vert, -h2]),width=.3);
// Example(2D): With a single path with closed=true the start and end junction is rounded.
// tri = regular_ngon(n=3, r=7);
// stroke(path_join([tri], joint=3,closed=true,$fn=12),closed=true,width=.5);
function path_join(paths,joint=0,k=0.5,relocate=true,closed=false)=
assert(is_list(paths),"Input paths must be a list of paths")
let(
badpath = [for(j=idx(paths)) if (!is_path(paths[j])) j]
)
assert(badpath==[], str("Entries in paths are not valid paths: ",badpath))
len(paths)==0 ? [] :
len(paths)==1 && !closed ? paths[0] :
let(
paths = !closed || len(paths)>1
? paths
: [close_path(paths[0])],
N = len(paths) + (closed?0:-1),
k = _scalar_to_vector(k,N),
repjoint = is_num(joint) || (is_vector(joint,2) && len(paths)!=3),
joint = repjoint ? repeat(joint,N) : joint
)
assert(all_nonnegative(k), "k must be nonnegative")
assert(len(joint)==N,str("Input joint must be scalar or length ",N))
let(
bad_j = [for(j=idx(joint)) if (!is_num(joint[j]) && !is_vector(joint[j],2)) j]
)
assert(bad_j==[], str("Invalid joint values at indices ",bad_j))
let(result=_path_join(paths,joint,k, relocate=relocate, closed=closed))
closed ? cleanup_path(result) : result;
function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false) =
let(
result = result==[] ? paths[0] : result,
loop = i==len(paths)-1,
revresult = reverse(result),
nextpath = loop ? result
: relocate ? move(revresult[0]-paths[i+1][0], p=paths[i+1])
: paths[i+1],
d_first = is_vector(joint[i]) ? joint[i][0] : joint[i],
d_next = is_vector(joint[i]) ? joint[i][1] : joint[i]
)
assert(d_first>=0 && d_next>=0, str("Joint value negative when adding path ",i+1))
let(
firstcut = path_cut(revresult, d_first, direction=true),
nextcut = path_cut(nextpath, d_next, direction=true)
)
assert(is_def(firstcut),str("Path ",i," is too short for specified cut distance ",d_first))
assert(is_def(nextcut),str("Path ",i+1," is too short for specified cut distance ",d_next))
assert(!loop || nextcut[1] < len(revresult)-1-firstcut[1], "Path is too short to close the loop")
let(
first_dir=firstcut[2],
next_dir=nextcut[2],
corner = ray_intersection([firstcut[0], firstcut[0]-first_dir], [nextcut[0], nextcut[0]-next_dir])
)
assert(is_def(corner), str("Curve directions at cut points don't intersect in a corner when ",
loop?"closing the path":str("adding path ",i+1)))
let(
bezpts = _smooth_bez_fill([firstcut[0], corner, nextcut[0]],k[i]),
N = max(3,$fn>0 ?$fn : ceil(bezier_segment_length(bezpts)/$fs)),
bezpath = approx(firstcut[0],corner) && approx(corner,nextcut[0])
? []
: bezier_curve(bezpts,N),
new_result = [each select(result,loop?nextcut[1]:0,len(revresult)-1-firstcut[1]),
each bezpath,
nextcut[0],
if (!loop) each select(nextpath,nextcut[1],-1)
]
)
i==len(paths)-(closed?1:2)
? new_result
: _path_join(paths,joint,k,i+1,new_result, relocate,closed);
// Function&Module: offset_sweep()
//
// Description:

View File

@ -405,6 +405,10 @@ class LeafNode(object):
dummy, title = line.split(":", 1)
title = title.strip()
lines, block = get_comment_block(lines, prefix)
if block == []:
print("Error: Usage header without any usage examples.")
print(line)
sys.exit(-2)
self.usages.append([title, block])
continue
if line.startswith("Description:"):

View File

@ -63,9 +63,9 @@
// cuboid(40) show_anchors();
module cuboid(
size=[1,1,1],
p1=undef, p2=undef,
chamfer=undef,
rounding=undef,
p1, p2,
chamfer,
rounding,
edges=EDGES_ALL,
except_edges=[],
trimcorners=true,
@ -73,6 +73,49 @@ module cuboid(
spin=0,
orient=UP
) {
module corner_shape(corner) {
e = corner_edges(edges, corner);
cnt = sum(e);
r = first_defined([chamfer, rounding, 0]);
$fn = is_finite(chamfer)? 4 : segs(r);
translate(vmul(corner,size/2-[r,r,r])) {
if (cnt == 0) {
cube(r*2, center=true);
} else if (cnt == 1) {
if (e.x) xcyl(l=r*2, r=r);
if (e.y) ycyl(l=r*2, r=r);
if (e.z) zcyl(l=r*2, r=r);
} else if (cnt == 2) {
if (!e.x) {
intersection() {
ycyl(l=r*2, r=r);
zcyl(l=r*2, r=r);
}
} else if (!e.y) {
intersection() {
xcyl(l=r*2, r=r);
zcyl(l=r*2, r=r);
}
} else {
intersection() {
xcyl(l=r*2, r=r);
ycyl(l=r*2, r=r);
}
}
} else {
if (trimcorners) {
spheroid(r=r, style="octa");
} else {
intersection() {
xcyl(l=r*2, r=r);
ycyl(l=r*2, r=r);
zcyl(l=r*2, r=r);
}
}
}
}
}
size = scalar_vec3(size);
edges = edges(edges, except=except_edges);
if (!is_undef(p1)) {
@ -156,32 +199,15 @@ module cuboid(
}
}
} else {
difference() {
cube(size, center=true);
// Chamfer edges
for (i = [0:3], axis=[0:2]) {
if (edges[axis][i]>0) {
translate(vmul(EDGE_OFFSETS[axis][i], size/2)) {
rotate(majrots[axis]) {
zrot(45) cube([chamfer*sqrt(2), chamfer*sqrt(2), size[axis]+0.01], center=true);
}
}
}
}
// Chamfer triple-edge corners.
if (trimcorners) {
for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
if (corner_edge_count(edges, [xa,ya,za]) > 2) {
translate(vmul([xa,ya,za]/2, size-[1,1,1]*chamfer*4/3)) {
rot(from=UP, to=[xa,ya,za]) {
cube(chamfer*3, anchor=BOTTOM);
}
}
}
}
}
hull() {
corner_shape([-1,-1,-1]);
corner_shape([ 1,-1,-1]);
corner_shape([-1, 1,-1]);
corner_shape([ 1, 1,-1]);
corner_shape([-1,-1, 1]);
corner_shape([ 1,-1, 1]);
corner_shape([-1, 1, 1]);
corner_shape([ 1, 1, 1]);
}
}
} else if (rounding != undef) {
@ -258,38 +284,15 @@ module cuboid(
}
}
} else {
difference() {
cube(size, center=true);
// Round edges.
for (i = [0:3], axis=[0:2]) {
if (edges[axis][i]>0) {
difference() {
translate(vmul(EDGE_OFFSETS[axis][i], size/2)) {
rotate(majrots[axis]) cube([rounding*2, rounding*2, size[axis]+0.1], center=true);
}
translate(vmul(EDGE_OFFSETS[axis][i], size/2 - [1,1,1]*rounding)) {
rotate(majrots[axis]) cyl(h=size[axis]+0.2, r=rounding, $fn=sides);
}
}
}
}
// Round triple-edge corners.
if (trimcorners) {
for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
if (corner_edge_count(edges, [xa,ya,za]) > 2) {
difference() {
translate(vmul([xa,ya,za], size/2)) {
cube(rounding*2, center=true);
}
translate(vmul([xa,ya,za], size/2-[1,1,1]*rounding)) {
spheroid(r=rounding, style="octa", $fn=sides);
}
}
}
}
}
hull() {
corner_shape([-1,-1,-1]);
corner_shape([ 1,-1,-1]);
corner_shape([-1, 1,-1]);
corner_shape([ 1, 1,-1]);
corner_shape([-1,-1, 1]);
corner_shape([ 1,-1, 1]);
corner_shape([-1, 1, 1]);
corner_shape([ 1, 1, 1]);
}
}
} else {
@ -673,7 +676,7 @@ module cyl(
) [p1,p2]
) : !is_undef(fil2)? (
let(
cn = find_circle_2tangents([r2-fil2,l/2], [r2,l/2], [r1,-l/2], r=abs(fil2)),
cn = circle_2tangents([r2-fil2,l/2], [r2,l/2], [r1,-l/2], r=abs(fil2)),
ang = fil2<0? phi : phi-180,
steps = ceil(abs(ang)/360*segs(abs(fil2))),
step = ang/steps,
@ -688,7 +691,7 @@ module cyl(
) [p1,p2]
) : !is_undef(fil1)? (
let(
cn = find_circle_2tangents([r1-fil1,-l/2], [r1,-l/2], [r2,l/2], r=abs(fil1)),
cn = circle_2tangents([r1-fil1,-l/2], [r1,-l/2], [r2,l/2], r=abs(fil1)),
ang = fil1<0? 180-phi : -phi,
steps = ceil(abs(ang)/360*segs(abs(fil1))),
step = ang/steps,

View File

@ -932,7 +932,10 @@ function oval(r, d, realign=false, circum=false, anchor=CENTER, spin=0) =
function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) =
let(
sc = 1/cos(180/n),
r = get_radius(r1=ir*sc, r2=or, r=r, d1=id*sc, d2=od, d=d, dflt=side/2/sin(180/n))
ir = is_finite(ir)? ir*sc : undef,
id = is_finite(id)? id*sc : undef,
side = is_finite(side)? side/2/sin(180/n) : undef,
r = get_radius(r1=ir, r2=or, r=r, d1=id, d2=od, d=d, dflt=side)
)
assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side.")
let(
@ -970,7 +973,10 @@ function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false
module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) {
sc = 1/cos(180/n);
r = get_radius(r1=ir*sc, r2=or, r=r, d1=id*sc, d2=od, d=d, dflt=side/2/sin(180/n));
ir = is_finite(ir)? ir*sc : undef;
id = is_finite(id)? id*sc : undef;
side = is_finite(side)? side/2/sin(180/n) : undef;
r = get_radius(r1=ir, r2=or, r=r, d1=id, d2=od, d=d, dflt=side);
assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side.");
path = regular_ngon(n=n, r=r, rounding=rounding, realign=realign);
inset = opp_ang_to_hyp(rounding, (180-360/n)/2);

View File

@ -512,8 +512,9 @@ function subdivide_and_slice(profiles, slices, numpoints, method="length", close
slice_profiles(fixpoly, slices, closed);
// Function slice_profiles()
// Usage: slice_profiles(profiles,slices,[closed])
// Function: slice_profiles()
// Usage:
// profs = slice_profiles(profiles,slices,<closed>);
// Description:
// Given an input list of profiles, linearly interpolate between each pair to produce a
// more finely sampled list. The parameters `slices` specifies the number of slices to
@ -640,7 +641,8 @@ function _dp_extract_map(map) =
// Internal Function: _skin_distance_match(poly1,poly2)
// Usage: _skin_distance_match(poly1,poly2)
// Usage:
// polys = _skin_distance_match(poly1,poly2);
// Description:
// Find a way of associating the vertices of poly1 and vertices of poly2
// that minimizes the sum of the length of the edges that connect the two polygons.
@ -686,16 +688,17 @@ function _skin_distance_match(poly1,poly2) =
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Internal Function: _skin_tangent_match()
// Usage: _skin_tangent_match(poly1, poly2)
// Usage:
// x = _skin_tangent_match(poly1, poly2)
// Description:
// Finds a mapping of the vertices of the larger polygon onto the smaller one. Whichever input is the
// shorter path is the polygon, and the longer input is the curve. For every edge of the polygon, the algorithm seeks a plane that contains that
// edge and is tangent to the curve. There will be more than one such point. To choose one, the algorithm centers the polygon and curve on their centroids
// and chooses the closer tangent point. The algorithm works its way around the polygon, computing a series of tangent points and then maps all of the
// points on the curve between two tangent points into one vertex of the polygon. This algorithm can fail if the curve has too few points or if it is concave.
// Finds a mapping of the vertices of the larger polygon onto the smaller one. Whichever input is the
// shorter path is the polygon, and the longer input is the curve. For every edge of the polygon, the algorithm seeks a plane that contains that
// edge and is tangent to the curve. There will be more than one such point. To choose one, the algorithm centers the polygon and curve on their centroids
// and chooses the closer tangent point. The algorithm works its way around the polygon, computing a series of tangent points and then maps all of the
// points on the curve between two tangent points into one vertex of the polygon. This algorithm can fail if the curve has too few points or if it is concave.
// Arguments:
// poly1 = input polygon
// poly2 = input polygon
// poly1 = input polygon
// poly2 = input polygon
function _skin_tangent_match(poly1, poly2) =
let(
swap = len(poly1)>len(poly2),
@ -794,7 +797,10 @@ function associate_vertices(polygons, split, curpoly=0) =
// Function&Module: sweep()
// Usage: sweep(shape, transformations, [closed], [caps])
// Usage: As Module
// sweep(shape, transformations, <closed<, <caps>)
// Usage: As Function
// vnf = sweep(shape, transformations, <closed>, <caps>);
// Description:
// The input `shape` must be a non-self-intersecting polygon in two dimensions, and `transformations`
// is a list of 4x4 transformation matrices. The sweep algorithm applies each transformation in sequence

View File

@ -196,7 +196,7 @@ function str_frac(str,mixed=true,improper=true,signed=true) =
signed && str[0]=="-" ? -str_frac(substr(str,1),mixed=mixed,improper=improper,signed=false) :
signed && str[0]=="+" ? str_frac(substr(str,1),mixed=mixed,improper=improper,signed=false) :
mixed ? (
str_find(str," ")>0 || is_undef(str_find(str,"/"))? (
!in_list(str_find(str," "), [undef,0]) || is_undef(str_find(str,"/"))? (
let(whole = str_split(str,[" "]))
_str_int_recurse(whole[0],10,len(whole[0])-1) + str_frac(whole[1], mixed=false, improper=improper, signed=false)
) : str_frac(str,mixed=false, improper=improper)
@ -293,11 +293,11 @@ function _str_cmp_recurse(str,sindex,pattern,plen,pindex=0,) =
// str_find(str,pattern,[last],[all],[start])
// Description:
// Searches input string `str` for the string `pattern` and returns the index or indices of the matches in `str`.
// By default str_find() returns the index of the first match in `str`. If `last` is true then it returns the index of the last match.
// By default `str_find()` returns the index of the first match in `str`. If `last` is true then it returns the index of the last match.
// If the pattern is the empty string the first match is at zero and the last match is the last character of the `str`.
// If `start` is set then the search begins at index start, working either forward and backward from that position. If you set `start`
// and `last` is true then the search will find the pattern if it begins at index `start`. If no match exists, returns undef.
// If you set `all` to true then all str_find() returns all of the matches in a list, or an empty list if there are no matches.
// and `last` is true then the search will find the pattern if it begins at index `start`. If no match exists, returns `undef`.
// If you set `all` to true then `str_find()` returns all of the matches in a list, or an empty list if there are no matches.
// Arguments:
// str = String to search.
// pattern = string pattern to search for

View File

@ -8,7 +8,7 @@ module test_is_homogeneous(){
assert(is_homogeneous([[1,["a"]], [2,[true]]])==false);
assert(is_homogeneous([[1,["a"]], [2,[true]]],1)==true);
assert(is_homogeneous([[1,["a"]], [2,[true]]],2)==false);
assert(is_homogeneous([[1,["a"]], [true,["b"]]])==true);
assert(is_homogeneous([[1,["a"]], [true,["b"]]])==false);
}
test_is_homogeneous();
@ -112,7 +112,7 @@ test_list_range();
module test_reverse() {
assert(reverse([3,4,5,6]) == [6,5,4,3]);
assert(reverse("abcd") == ["d","c","b","a"]);
assert(reverse("abcd") == "dcba");
assert(reverse([]) == []);
}
test_reverse();
@ -134,12 +134,12 @@ test_list_rotate();
module test_deduplicate() {
assert(deduplicate([8,3,4,4,4,8,2,3,3,8,8]) == [8,3,4,8,2,3,8]);
assert(deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]) == [8,3,4,8,2,3]);
assert(deduplicate("Hello") == ["H","e","l","o"]);
assert(deduplicate([[3,4],[7,1.99],[7,2],[1,4]],eps=0.1) == [[3,4],[7,2],[1,4]]);
assert(deduplicate([], closed=true) == []);
assert(deduplicate([[1,[1,[undef]]],[1,[1,[undef]]],[1,[2]],[1,[2,[0]]]])==[[1, [1,[undef]]],[1,[2]],[1,[2,[0]]]]);
assert_equal(deduplicate([8,3,4,4,4,8,2,3,3,8,8]), [8,3,4,8,2,3,8]);
assert_equal(deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]), [8,3,4,8,2,3]);
assert_equal(deduplicate("Hello"), "Helo");
assert_equal(deduplicate([[3,4],[7,1.99],[7,2],[1,4]],eps=0.1), [[3,4],[7,2],[1,4]]);
assert_equal(deduplicate([], closed=true), []);
assert_equal(deduplicate([[1,[1,[undef]]],[1,[1,[undef]]],[1,[2]],[1,[2,[0]]]]), [[1, [1,[undef]]],[1,[2]],[1,[2,[0]]]]);
}
test_deduplicate();

View File

@ -160,6 +160,25 @@ module test_is_nan() {
test_is_nan();
module test_is_finite() {
assert(!is_finite(undef));
assert(!is_finite(true));
assert(!is_finite(false));
assert(is_finite(-5));
assert(is_finite(0));
assert(is_finite(5));
assert(!is_finite(INF));
assert(!is_finite(-INF));
assert(!is_finite(""));
assert(!is_finite("foo"));
assert(!is_finite([]));
assert(!is_finite([3,4,5]));
assert(!is_finite([3:1:5]));
assert(!is_finite(NAN));
}
test_is_finite();
module test_is_range() {
assert(!is_range(undef));
assert(!is_range(true));
@ -189,10 +208,12 @@ module test_valid_range() {
assert(valid_range([0:-1:0]));
assert(valid_range([10:-1:0]));
assert(valid_range([2.1:-1.1:0.1]));
assert(!valid_range([10:1:0]));
assert(!valid_range([2.1:1.1:0.1]));
assert(!valid_range([0:-1:10]));
assert(!valid_range([0.1:-1.1:2.1]));
if (version_num() < 20200600) {
assert(!valid_range([10:1:0]));
assert(!valid_range([2.1:1.1:0.1]));
assert(!valid_range([0:-1:10]));
assert(!valid_range([0.1:-1.1:2.1]));
}
}
test_valid_range();

View File

@ -100,5 +100,111 @@ module test_corner_edge_count() {
test_corner_edge_count();
module test_corner_edges() {
edges = edges([TOP,FRONT+RIGHT]);
assert_equal(corner_edges(edges,TOP+FRONT+RIGHT), [1,1,1]);
assert_equal(corner_edges(edges,TOP+FRONT+LEFT), [1,1,0]);
assert_equal(corner_edges(edges,BOTTOM+FRONT+RIGHT), [0,0,1]);
assert_equal(corner_edges(edges,BOTTOM+FRONT+LEFT), [0,0,0]);
}
test_corner_edges();
module test_corners() {
assert_equal(corners(BOT + FRONT + LEFT ), [1,0,0,0,0,0,0,0]);
assert_equal(corners(BOT + FRONT + RIGHT), [0,1,0,0,0,0,0,0]);
assert_equal(corners(BOT + BACK + LEFT ), [0,0,1,0,0,0,0,0]);
assert_equal(corners(BOT + BACK + RIGHT), [0,0,0,1,0,0,0,0]);
assert_equal(corners(TOP + FRONT + LEFT ), [0,0,0,0,1,0,0,0]);
assert_equal(corners(TOP + FRONT + RIGHT), [0,0,0,0,0,1,0,0]);
assert_equal(corners(TOP + BACK + LEFT ), [0,0,0,0,0,0,1,0]);
assert_equal(corners(TOP + BACK + RIGHT), [0,0,0,0,0,0,0,1]);
assert_equal(corners(BOT + FRONT), [1,1,0,0,0,0,0,0]);
assert_equal(corners(BOT + BACK ), [0,0,1,1,0,0,0,0]);
assert_equal(corners(TOP + FRONT), [0,0,0,0,1,1,0,0]);
assert_equal(corners(TOP + BACK ), [0,0,0,0,0,0,1,1]);
assert_equal(corners(BOT + LEFT ), [1,0,1,0,0,0,0,0]);
assert_equal(corners(BOT + RIGHT), [0,1,0,1,0,0,0,0]);
assert_equal(corners(TOP + LEFT ), [0,0,0,0,1,0,1,0]);
assert_equal(corners(TOP + RIGHT), [0,0,0,0,0,1,0,1]);
assert_equal(corners(FRONT + LEFT ), [1,0,0,0,1,0,0,0]);
assert_equal(corners(FRONT + RIGHT), [0,1,0,0,0,1,0,0]);
assert_equal(corners(BACK + LEFT ), [0,0,1,0,0,0,1,0]);
assert_equal(corners(BACK + RIGHT), [0,0,0,1,0,0,0,1]);
assert_equal(corners(LEFT), [1,0,1,0,1,0,1,0]);
assert_equal(corners(RIGHT), [0,1,0,1,0,1,0,1]);
assert_equal(corners(FRONT), [1,1,0,0,1,1,0,0]);
assert_equal(corners(BACK), [0,0,1,1,0,0,1,1]);
assert_equal(corners(BOT), [1,1,1,1,0,0,0,0]);
assert_equal(corners(TOP), [0,0,0,0,1,1,1,1]);
assert_equal(corners([BOT + FRONT + LEFT ]), [1,0,0,0,0,0,0,0]);
assert_equal(corners([BOT + FRONT + RIGHT]), [0,1,0,0,0,0,0,0]);
assert_equal(corners([BOT + BACK + LEFT ]), [0,0,1,0,0,0,0,0]);
assert_equal(corners([BOT + BACK + RIGHT]), [0,0,0,1,0,0,0,0]);
assert_equal(corners([TOP + FRONT + LEFT ]), [0,0,0,0,1,0,0,0]);
assert_equal(corners([TOP + FRONT + RIGHT]), [0,0,0,0,0,1,0,0]);
assert_equal(corners([TOP + BACK + LEFT ]), [0,0,0,0,0,0,1,0]);
assert_equal(corners([TOP + BACK + RIGHT]), [0,0,0,0,0,0,0,1]);
assert_equal(corners([BOT + FRONT]), [1,1,0,0,0,0,0,0]);
assert_equal(corners([BOT + BACK ]), [0,0,1,1,0,0,0,0]);
assert_equal(corners([TOP + FRONT]), [0,0,0,0,1,1,0,0]);
assert_equal(corners([TOP + BACK ]), [0,0,0,0,0,0,1,1]);
assert_equal(corners([BOT + LEFT ]), [1,0,1,0,0,0,0,0]);
assert_equal(corners([BOT + RIGHT]), [0,1,0,1,0,0,0,0]);
assert_equal(corners([TOP + LEFT ]), [0,0,0,0,1,0,1,0]);
assert_equal(corners([TOP + RIGHT]), [0,0,0,0,0,1,0,1]);
assert_equal(corners([FRONT + LEFT ]), [1,0,0,0,1,0,0,0]);
assert_equal(corners([FRONT + RIGHT]), [0,1,0,0,0,1,0,0]);
assert_equal(corners([BACK + LEFT ]), [0,0,1,0,0,0,1,0]);
assert_equal(corners([BACK + RIGHT]), [0,0,0,1,0,0,0,1]);
assert_equal(corners([LEFT]), [1,0,1,0,1,0,1,0]);
assert_equal(corners([RIGHT]), [0,1,0,1,0,1,0,1]);
assert_equal(corners([FRONT]), [1,1,0,0,1,1,0,0]);
assert_equal(corners([BACK]), [0,0,1,1,0,0,1,1]);
assert_equal(corners([BOT]), [1,1,1,1,0,0,0,0]);
assert_equal(corners([TOP]), [0,0,0,0,1,1,1,1]);
assert_equal(corners([TOP,FRONT+RIGHT]), [0,1,0,0,1,1,1,1]);
}
test_corners();
module test_is_corner_array() {
edges = edges([TOP,FRONT+RIGHT]);
corners = corners([TOP,FRONT+RIGHT]);
assert(!is_corner_array(undef));
assert(!is_corner_array(true));
assert(!is_corner_array(false));
assert(!is_corner_array(INF));
assert(!is_corner_array(-INF));
assert(!is_corner_array(NAN));
assert(!is_corner_array(-4));
assert(!is_corner_array(0));
assert(!is_corner_array(4));
assert(!is_corner_array("foo"));
assert(!is_corner_array([]));
assert(!is_corner_array([4,5,6]));
assert(!is_corner_array([2:3:9]));
assert(!is_corner_array(edges));
assert(is_corner_array(corners));
}
test_is_corner_array();
module test_normalize_corners() {
assert_equal(normalize_corners([-2,-2,-2,-2,-2,-2,-2,-2]), [0,0,0,0,0,0,0,0]);
assert_equal(normalize_corners([-1,-1,-1,-1,-1,-1,-1,-1]), [0,0,0,0,0,0,0,0]);
assert_equal(normalize_corners([0,0,0,0,0,0,0,0]), [0,0,0,0,0,0,0,0]);
assert_equal(normalize_corners([1,1,1,1,1,1,1,1]), [1,1,1,1,1,1,1,1]);
assert_equal(normalize_corners([2,2,2,2,2,2,2,2]), [1,1,1,1,1,1,1,1]);
}
test_normalize_corners();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View File

@ -57,8 +57,8 @@ test_plane_intersection();
test_coplanar();
test_points_on_plane();
test_in_front_of_plane();
test_find_circle_2tangents();
test_find_circle_3points();
test_circle_2tangents();
test_circle_3points();
test_circle_point_tangents();
test_noncollinear_triple();
@ -470,22 +470,22 @@ module test_segment_closest_point() {
}
*test_segment_closest_point();
module test_find_circle_2tangents() {
module test_circle_2tangents() {
//** missing tests with arg tangent=true
assert(approx(find_circle_2tangents([10,10],[0,0],[10,-10],r=10/sqrt(2))[0],[10,0]));
assert(approx(find_circle_2tangents([-10,10],[0,0],[-10,-10],r=10/sqrt(2))[0],[-10,0]));
assert(approx(find_circle_2tangents([-10,10],[0,0],[10,10],r=10/sqrt(2))[0],[0,10]));
assert(approx(find_circle_2tangents([-10,-10],[0,0],[10,-10],r=10/sqrt(2))[0],[0,-10]));
assert(approx(find_circle_2tangents([0,10],[0,0],[10,0],r=10)[0],[10,10]));
assert(approx(find_circle_2tangents([10,0],[0,0],[0,-10],r=10)[0],[10,-10]));
assert(approx(find_circle_2tangents([0,-10],[0,0],[-10,0],r=10)[0],[-10,-10]));
assert(approx(find_circle_2tangents([-10,0],[0,0],[0,10],r=10)[0],[-10,10]));
assert_approx(find_circle_2tangents(polar_to_xy(10,60),[0,0],[10,0],r=10)[0],polar_to_xy(20,30));
assert(approx(circle_2tangents([10,10],[0,0],[10,-10],r=10/sqrt(2))[0],[10,0]));
assert(approx(circle_2tangents([-10,10],[0,0],[-10,-10],r=10/sqrt(2))[0],[-10,0]));
assert(approx(circle_2tangents([-10,10],[0,0],[10,10],r=10/sqrt(2))[0],[0,10]));
assert(approx(circle_2tangents([-10,-10],[0,0],[10,-10],r=10/sqrt(2))[0],[0,-10]));
assert(approx(circle_2tangents([0,10],[0,0],[10,0],r=10)[0],[10,10]));
assert(approx(circle_2tangents([10,0],[0,0],[0,-10],r=10)[0],[10,-10]));
assert(approx(circle_2tangents([0,-10],[0,0],[-10,0],r=10)[0],[-10,-10]));
assert(approx(circle_2tangents([-10,0],[0,0],[0,10],r=10)[0],[-10,10]));
assert_approx(circle_2tangents(polar_to_xy(10,60),[0,0],[10,0],r=10)[0],polar_to_xy(20,30));
}
*test_find_circle_2tangents();
*test_circle_2tangents();
module test_find_circle_3points() {
module test_circle_3points() {
count = 200;
coords = rands(-100,100,count,seed_value=888);
radii = rands(10,100,count,seed_value=390);
@ -496,7 +496,7 @@ module test_find_circle_3points() {
r = radii[i];
angs = sort(select(angles,i,i+2));
pts = [for (a=angs) cp+polar_to_xy(r,a)];
res = find_circle_3points(pts);
res = circle_3points(pts);
if (!approx(res[0], cp)) {
echo(cp=cp, r=r, angs=angs);
echo(pts=pts);
@ -521,7 +521,7 @@ module test_find_circle_3points() {
r = radii[i];
angs = sort(select(angles,i,i+2));
pts = [for (a=angs) cp+polar_to_xy(r,a)];
res = find_circle_3points(pts[0], pts[1], pts[2]);
res = circle_3points(pts[0], pts[1], pts[2]);
if (!approx(res[0], cp)) {
echo(cp=cp, r=r, angs=angs);
echo(pts=pts);
@ -549,7 +549,7 @@ module test_find_circle_3points() {
n = nrm.z<0? -nrm : nrm;
angs = sort(select(angles,i,i+2));
pts = translate(cp,p=rot(from=UP,to=n,p=[for (a=angs) point3d(polar_to_xy(r,a))]));
res = find_circle_3points(pts);
res = circle_3points(pts);
if (!approx(res[0], cp)) {
echo(cp=cp, r=r, angs=angs, n=n);
echo(pts=pts);
@ -576,7 +576,7 @@ module test_find_circle_3points() {
n = nrm.z<0? -nrm : nrm;
angs = sort(select(angles,i,i+2));
pts = translate(cp,p=rot(from=UP,to=n,p=[for (a=angs) point3d(polar_to_xy(r,a))]));
res = find_circle_3points(pts[0], pts[1], pts[2]);
res = circle_3points(pts[0], pts[1], pts[2]);
if (!approx(res[0], cp)) {
echo(cp=cp, r=r, angs=angs, n=n);
echo(pts=pts);
@ -597,17 +597,21 @@ module test_find_circle_3points() {
}
}
}
*test_find_circle_3points();
*test_circle_3points();
module test_circle_point_tangents() {
tangs = circle_point_tangents(r=50,cp=[0,0],pt=[50*sqrt(2),0]);
assert(approx(subindex(tangs,0), [45,-45]));
expected = [for (ang=subindex(tangs,0)) polar_to_xy(50,ang)];
got = subindex(tangs,1);
if (!approx(flatten(got), flatten(expected))) {
echo("TAN_PTS:", got=got, expected=expected, delta=got-expected);
assert(approx(flatten(got), flatten(expected)));
testvals = [
// cp r pt expect
[[0,0], 50, [50*sqrt(2),0], [polar_to_xy(50,45), polar_to_xy(50,-45)]],
[[5,10], 50, [5+50*sqrt(2),10], [[5,10]+polar_to_xy(50,45), [5,10]+polar_to_xy(50,-45)]],
[[0,0], 50, [0,50*sqrt(2)], [polar_to_xy(50,135), polar_to_xy(50,45)]],
[[5,10], 50, [5,10+50*sqrt(2)], [[5,10]+polar_to_xy(50,135), [5,10]+polar_to_xy(50,45)]]
];
for (v = testvals) {
cp = v[0]; r = v[1]; pt = v[2]; expect = v[3];
info = str("cp=",cp, ", r=",r, ", pt=",pt);
assert_approx(circle_point_tangents(r=r,cp=cp,pt=pt), expect, info);
}
}
*test_circle_point_tangents();
@ -660,6 +664,20 @@ module test_tri_functions() {
*test_tri_functions();
module test_hyp_opp_to_adj() nil(); // Covered in test_tri_functions()
module test_hyp_ang_to_adj() nil(); // Covered in test_tri_functions()
module test_opp_ang_to_adj() nil(); // Covered in test_tri_functions()
module test_hyp_adj_to_opp() nil(); // Covered in test_tri_functions()
module test_hyp_ang_to_opp() nil(); // Covered in test_tri_functions()
module test_adj_ang_to_opp() nil(); // Covered in test_tri_functions()
module test_adj_opp_to_hyp() nil(); // Covered in test_tri_functions()
module test_adj_ang_to_hyp() nil(); // Covered in test_tri_functions()
module test_opp_ang_to_hyp() nil(); // Covered in test_tri_functions()
module test_hyp_adj_to_ang() nil(); // Covered in test_tri_functions()
module test_hyp_opp_to_ang() nil(); // Covered in test_tri_functions()
module test_adj_opp_to_ang() nil(); // Covered in test_tri_functions()
module test_triangle_area() {
assert(abs(triangle_area([0,0], [0,10], [10,0]) + 50) < EPSILON);
assert(abs(triangle_area([0,0], [0,10], [0,15])) < EPSILON);

View File

@ -98,3 +98,87 @@ module test_hull() {
*/
}
test_hull();
module test_hull2d_path() {
assert_equal(hull([[3,4],[5,5]]), [0,1]);
assert_equal(hull([[3,4,1],[5,5,3]]), [0,1]);
test_collinear_2d = let(u = unit([5,3])) [ for(i = [9,2,3,4,5,7,12,15,13]) i * u ];
assert_equal(hull(test_collinear_2d), [7,1]);
test_collinear_3d = let(u = unit([5,3,2])) [ for(i = [9,2,3,4,5,7,12,15,13]) i * u ];
assert_equal(hull(test_collinear_3d), [7,1]);
rand10_2d = [[1.55356, -1.98965], [4.23157, -0.947788], [-4.06193, -1.55463],
[1.23889, -3.73133], [-1.02637, -4.0155], [4.26806, -4.61909],
[3.59556, -3.1574], [-2.77776, -4.21857], [-3.66253,-4.34458], [1.82324, 0.102025]];
assert_equal(sort(hull(rand10_2d)), [1,2,5,8,9]);
rand75_2d = [[-3.14743, -3.28139], [0.15343, -0.370249], [0.082565, 3.95939], [-2.56925, -3.16262], [-1.59463, 4.20893],
[-4.90744, -1.21374], [-1.0819, -1.93703], [-3.72723, -3.0744], [-3.34339, 1.53535], [3.15803, -0.307388], [4.23289,
4.46259], [1.73624, 1.38918], [3.72087, -1.55028], [1.2604, 2.30502], [-0.966431, 1.673], [-3.26866, -0.531443], [1.52605,
0.991804], [-1.26305, 1.0737], [-4.31943, 4.11932], [0.488101, 0.0425981], [1.0233, -0.723037], [-4.73406, 2.14568],
[-4.75915, 3.83262], [4.90999, -2.76668], [1.91971, -3.8604], [4.38594, -0.761767], [-0.352984, 1.55291], [2.02714,
-0.340099], [1.76052, 2.09196], [-1.27485, -4.39477], [4.36364, 3.84964], [0.593612, -4.00028], [3.06833, -3.67117],
[4.26834, -4.21213], [4.60226, -0.120432], [-2.45646, 2.60327], [-4.79461, 3.83724], [-3.29755, 0.760159], [0.218423,
4.1687], [-0.115829, -2.06242], [-3.96188, 3.21568], [4.3018, -2.5299], [-4.41694, 4.75173], [-3.8393, 2.82212], [-1.14268,
1.80751], [2.05805, 1.68593], [-3.0159, -2.91139], [-1.44828, -1.93564], [-0.265887, 0.519893], [-0.457361, -0.610096],
[-0.426359, -2.37315], [-3.1018, 2.31141], [0.179141, -3.56242], [-0.491786, 0.813055], [-3.28502, -1.18933], [0.0914813,
2.16122], [4.5777, 4.83972], [-1.07096, 2.74992], [-0.698689, 3.9032], [-1.21809, -1.54434], [3.14457, 4.92302], [-4.63176,
2.81952], [4.84414, 4.63699], [2.4259, -0.747268], [-1.52088, -4.58305], [1.6961, -3.73678], [-0.483003, -3.67283],
[-3.72746, -0.284265], [2.07629, 1.99902], [-3.12698, -0.96353], [4.02254, 3.41521], [-0.963391, -3.2143], [0.315255,
0.593049], [1.57006, 1.80436], [4.60957, -2.86325]];
assert_equal(sort(hull(rand75_2d)),[5,7,23,33,36,42,56,60,62,64]);
rand10_2d_rot = rot([22,44,12], p=path3d(rand10_2d));
assert_equal(sort(hull(rand10_2d_rot)), [1,2,5,8,9]);
rand75_2d_rot = rot([122,-44,32], p=path3d(rand75_2d));
assert_equal(sort(hull(rand75_2d_rot)), [5,7,23,33,36,42,56,60,62,64]);
}
test_hull2d_path();
module test_hull3d_faces() {
testpoints_on_sphere = [ for(p =
[
[1,PHI,0], [-1,PHI,0], [1,-PHI,0], [-1,-PHI,0],
[0,1,PHI], [0,-1,PHI], [0,1,-PHI], [0,-1,-PHI],
[PHI,0,1], [-PHI,0,1], [PHI,0,-1], [-PHI,0,-1]
])
unit(p)
];
assert_equal(hull(testpoints_on_sphere), [[8, 4, 0], [0, 4, 1], [4, 8, 5], [8, 2, 5], [2, 3, 5], [0, 1, 6], [3, 2, 7], [1, 4, 9], [4, 5, 9],
[5, 3, 9], [8, 0, 10], [2, 8, 10], [0, 6, 10], [6, 7, 10], [7, 2, 10], [6, 1, 11], [3, 7, 11], [7, 6, 11], [1, 9, 11], [9, 3, 11]]);
rand10_3d = [[14.0893, -15.2751, 21.0843], [-14.1564, 17.5751, 3.32094], [17.4966, 12.1717, 18.0607], [24.5489, 9.64591, 10.4738], [-12.0233, -24.4368, 13.1614],
[6.24019, -18.4135, 24.9554], [11.9438, -15.9724, -22.6454], [11.6147, 7.56059, 7.5667], [-19.7491, 9.42769, 15.3419], [-10.3726, 16.3559, 3.38503]];
assert_equal(hull(rand10_3d),[[3, 6, 0], [1, 3, 2], [3, 0, 2], [6, 1, 4], [0, 6, 5], [6, 4, 5], [2, 0, 5], [1, 2, 8], [2, 5, 8], [4, 1, 8], [5, 4, 8], [6, 3, 9], [3, 1, 9], [1, 6, 9]]);
rand25_3d = [[-20.5261, 14.5058, -11.6349], [16.4625, 20.1316, 12.9816], [-14.0268, 5.58802, 17.686], [-5.47944, 16.2501,
5.3086], [20.2168, -11.8466, 12.4598], [14.4633, -15.1479, 4.82151], [12.7897, 5.25704, 19.6205], [11.2456,
18.2794, -3.47074], [-1.87665, 22.9852, 1.99367], [-15.6052, -2.11009, 14.0096], [-10.7389, -14.569,
5.6121], [24.5965, 17.9039, 20.8313], [-13.7054, 13.3362, 1.50374], [10.1111, -23.1494, 19.9305], [14.154,
19.6682, -0.170182], [-22.6438, 22.7429, -0.776773], [-9.75056, 17.8896, -8.04152], [23.1746, 20.5475,
22.6957], [-10.5356, -4.32407, -7.0911], [2.20779, -8.30749, 6.87185], [23.2643, 2.64462, -19.0087],
[24.4055, 24.4504, 23.4777], [-3.84086, -6.98473, -10.2889], [0.178043, -16.07, 16.8081], [-8.86482,
-12.8256, 14.7418], [11.1759, -11.5614, -11.643], [7.16751, 13.9344, -19.1675], [2.26602, -10.5374,
0.125718], [-13.9053, 11.1143, -21.9289], [24.9018, -23.5307, -21.4684], [-13.6609, -19.6495, -8.91583],
[-16.5393, -22.4105, -6.91617], [-4.11378, -3.14362, -5.6881], [7.50883, -17.5284, -0.0615319], [-7.41739,
0.0721313, -7.47111], [22.6975, -7.99655, 14.0555], [-13.3644, 9.26993, 20.858], [-13.6889, 16.7462,
-14.5836], [16.5137, 3.90703, -5.49396], [-6.75614, -11.1444, -24.5309], [22.9868, 10.0028, 12.2866],
[-4.81079, -0.967785, -10.4726], [-0.949023, 23.1441, -2.08208], [16.1256, -8.2295, -24.0113], [6.45274,
-7.21416, 23.1409], [22.8274, 1.07038, 19.1756], [-10.6256, -10.0112, -6.12274], [6.29254, -7.81875,
-24.4037], [22.8538, 8.78163, -6.82567], [-1.96142, 19.1728, -1.726]];
assert_equal(hull(rand25_3d),[[21, 29, 11], [29, 21, 20], [21, 14, 20], [20, 14, 26], [15, 0, 28], [13, 29, 31], [0, 15,
31], [15, 9, 31], [9, 24, 31], [24, 13, 31], [28, 0, 31], [11, 29, 35], [29, 13, 35], [15,
21, 36], [9, 15, 36], [24, 9, 36], [13, 24, 36], [15, 28, 37], [28, 26, 37], [28, 31, 39],
[31, 29, 39], [14, 21, 42], [21, 15, 42], [26, 14, 42], [15, 37, 42], [37, 26, 42], [29, 20,
43], [39, 29, 43], [20, 26, 43], [26, 28, 43], [21, 13, 44], [13, 36, 44], [36, 21, 44],
[21, 11, 45], [11, 35, 45], [13, 21, 45], [35, 13, 45], [28, 39, 47], [39, 43, 47], [43, 28, 47]]);
}
test_hull3d_faces();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View File

@ -72,8 +72,7 @@ test_constrain();
module test_is_matrix() {
assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]]));
assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]],square=true));
assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]],square=false));
assert(is_matrix([[2,3],[5,6],[8,9]],3,2));
assert(is_matrix([[2,3],[5,6],[8,9]],m=3,n=2));
assert(is_matrix([[2,3,4],[5,6,7]],m=2,n=3));
assert(is_matrix([[2,3,4],[5,6,7]],2,3));
@ -82,8 +81,6 @@ module test_is_matrix() {
assert(is_matrix([[2,3,4],[5,6,7]],n=3));
assert(!is_matrix([[2,3,4],[5,6,7]],m=4));
assert(!is_matrix([[2,3,4],[5,6,7]],n=5));
assert(!is_matrix([[2,3,4],[5,6,7]],m=2,n=3,square=true));
assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]],square=false));
assert(!is_matrix([[2,3],[5,6],[8,9]],m=2,n=3));
assert(!is_matrix([[2,3,4],[5,6,7]],m=3,n=2));
assert(!is_matrix(undef));
@ -313,9 +310,8 @@ module test_sqr() {
assert_equal(sqr(2.5), 6.25);
assert_equal(sqr(3), 9);
assert_equal(sqr(16), 256);
assert_equal(sqr([2,3,4]), [4,9,16]);
assert_equal(sqr([[2,3,4],[3,5,7]]), [[4,9,16],[9,25,49]]);
assert_equal(sqr([]),[]);
assert_equal(sqr([2,3,4]), 29);
assert_equal(sqr([[2,3,4],[3,5,7],[3,5,1]]), [[25,41,33],[42,69,54],[24,39,48]]);
}
test_sqr();
@ -473,14 +469,6 @@ module test_cumsum() {
test_cumsum();
module test_sum_of_squares() {
assert_equal(sum_of_squares([1,2,3]), 14);
assert_equal(sum_of_squares([1,2,4]), 21);
assert_equal(sum_of_squares([-3,-2,-1]), 14);
}
test_sum_of_squares();
module test_sum_of_sines() {
assert_equal(sum_of_sines(0, [[3,4,0],[2,2,0]]), 0);
assert_equal(sum_of_sines(45, [[3,4,0],[2,2,0]]), 2);
@ -697,10 +685,10 @@ module test_count_true() {
assert_equal(count_true([1,false,undef]), 1);
assert_equal(count_true([1,5,false]), 2);
assert_equal(count_true([1,5,true]), 3);
assert_equal(count_true([[0,0], [0,0]]), 0);
assert_equal(count_true([[0,0], [1,0]]), 1);
assert_equal(count_true([[1,1], [1,1]]), 4);
assert_equal(count_true([[1,1], [1,1]], nmax=3), 3);
assert_equal(count_true([[0,0], [0,0]]), 2);
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);
}
test_count_true();

View File

@ -23,13 +23,13 @@ module verify_f(actual,expected) {
}
module test_is_quat() {
module test_Q_is_quat() {
verify_f(Q_is_quat([0]),false);
verify_f(Q_is_quat([0,0,0,0]),false);
verify_f(Q_is_quat([1,0,2,0]),true);
verify_f(Q_is_quat([1,0,2,0,0]),false);
}
test_is_quat();
test_Q_is_quat();
module test_Quat() {

View File

@ -0,0 +1,61 @@
include <../std.scad>
include <../torx_drive.scad>
module test_torx_outer_diam() {
assert_approx(torx_outer_diam(10), 2.80);
assert_approx(torx_outer_diam(15), 3.35);
assert_approx(torx_outer_diam(20), 3.95);
assert_approx(torx_outer_diam(25), 4.50);
assert_approx(torx_outer_diam(30), 5.60);
assert_approx(torx_outer_diam(40), 6.75);
}
test_torx_outer_diam();
module test_torx_inner_diam() {
assert_approx(torx_inner_diam(10), 2.05);
assert_approx(torx_inner_diam(15), 2.40);
assert_approx(torx_inner_diam(20), 2.85);
assert_approx(torx_inner_diam(25), 3.25);
assert_approx(torx_inner_diam(30), 4.05);
assert_approx(torx_inner_diam(40), 4.85);
}
test_torx_inner_diam();
module test_torx_depth() {
assert_approx(torx_depth(10), 3.56);
assert_approx(torx_depth(15), 3.81);
assert_approx(torx_depth(20), 4.07);
assert_approx(torx_depth(25), 4.45);
assert_approx(torx_depth(30), 4.95);
assert_approx(torx_depth(40), 5.59);
}
test_torx_depth();
module test_torx_tip_radius() {
assert_approx(torx_tip_radius(10), 0.229);
assert_approx(torx_tip_radius(15), 0.267);
assert_approx(torx_tip_radius(20), 0.305);
assert_approx(torx_tip_radius(25), 0.375);
assert_approx(torx_tip_radius(30), 0.451);
assert_approx(torx_tip_radius(40), 0.546);
}
test_torx_tip_radius();
module test_torx_rounding_radius() {
assert_approx(torx_rounding_radius(10), 0.598);
assert_approx(torx_rounding_radius(15), 0.716);
assert_approx(torx_rounding_radius(20), 0.859);
assert_approx(torx_rounding_radius(25), 0.920);
assert_approx(torx_rounding_radius(30), 1.194);
assert_approx(torx_rounding_radius(40), 1.428);
}
test_torx_rounding_radius();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View File

@ -4,6 +4,8 @@ include <../std.scad>
module test_is_vector() {
assert(is_vector([1,2,3]) == true);
assert(is_vector([[1,2,3]]) == false);
assert(is_vector([[1,2,3,4],[5,6,7,8]]) == false);
assert(is_vector([[1,2,3,4],[5,6]]) == false);
assert(is_vector(["foo"]) == false);
assert(is_vector([]) == false);
assert(is_vector(1) == false);
@ -41,8 +43,9 @@ test_vceil();
module test_vmul() {
assert(vmul([3,4,5], [8,7,6]) == [24,28,30]);
assert(vmul([1,2,3], [4,5,6]) == [4,10,18]);
assert_equal(vmul([3,4,5], [8,7,6]), [24,28,30]);
assert_equal(vmul([1,2,3], [4,5,6]), [4,10,18]);
assert_equal(vmul([[1,2,3],[4,5,6],[7,8,9]], [[4,5,6],[3,2,1],[5,9,3]]), [32,28,134]);
}
test_vmul();

View File

@ -65,6 +65,25 @@ module test_vnf_add_faces() {
test_vnf_add_faces();
module test_vnf_centroid() {
assert_approx(vnf_centroid(cube(100, center=false)), [50,50,50]);
assert_approx(vnf_centroid(cube(100, center=true)), [0,0,0]);
assert_approx(vnf_centroid(cube(100, anchor=ALLPOS)), [-50,-50,-50]);
assert_approx(vnf_centroid(cube(100, anchor=BOT)), [0,0,50]);
assert_approx(vnf_centroid(cube(100, anchor=TOP)), [0,0,-50]);
assert_approx(vnf_centroid(sphere(d=100, anchor=CENTER, $fn=36)), [0,0,0]);
assert_approx(vnf_centroid(sphere(d=100, anchor=BOT, $fn=36)), [0,0,50]);
}
test_vnf_centroid();
module test_vnf_volume() {
assert_approx(vnf_volume(cube(100, center=false)), 1000000);
assert(approx(vnf_volume(sphere(d=100, anchor=BOT, $fn=144)), 4/3*PI*pow(50,3), eps=1e3));
}
test_vnf_volume();
module test_vnf_merge() {
vnf1 = vnf_add_face(pts=[[-1,-1,-1],[1,-1,-1],[0,1,-1]]);
vnf2 = vnf_add_face(pts=[[1,1,1],[-1,1,1],[0,1,-1]]);

View File

@ -38,7 +38,7 @@
// is_vector([1,1,1],all_nonzero=false); // Returns true
// is_vector([],zero=false); // Returns false
function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) =
is_list(v) && is_num(0*(v*v))
is_list(v) && is_num(v[0]) && is_num(0*(v*v))
&& (is_undef(length) || len(v)==length)
&& (is_undef(zero) || ((norm(v) >= eps) == !zero))
&& (!all_nonzero || all_nonzero(v)) ;
@ -59,16 +59,15 @@ function vang(v) =
// Function: vmul()
// Description:
// Element-wise vector multiplication. Multiplies each element of vector `v1` by
// the corresponding element of vector `v2`. The vectors should have the same dimension.
// Returns a vector of the products.
// Element-wise multiplication. Multiplies each element of `v1` by the corresponding element of `v2`.
// Both `v1` and `v2` must be the same length. Returns a vector of the products.
// Arguments:
// v1 = The first vector.
// v2 = The second vector.
// Example:
// vmul([3,4,5], [8,7,6]); // Returns [24, 28, 30]
function vmul(v1, v2) =
assert( is_vector(v1) && is_vector(v2,len(v1)), "Incompatible vectors")
assert( is_list(v1) && is_list(v2) && len(v1)==len(v2), "Incompatible input")
[for (i = [0:1:len(v1)-1]) v1[i]*v2[i]];

View File

@ -8,7 +8,7 @@
//////////////////////////////////////////////////////////////////////
BOSL_VERSION = [2,0,425];
BOSL_VERSION = [2,0,440];
// Section: BOSL Library Version Functions

View File

@ -388,17 +388,24 @@ function vnf_volume(vnf) =
function vnf_centroid(vnf) =
let(
verts = vnf[0],
val = sum([ for(face=vnf[1], j=[1:1:len(face)-2])
let(
v0 = verts[face[0]],
v1 = verts[face[j]],
v2 = verts[face[j+1]],
vol = cross(v2,v1)*v0
)
[ vol, (v0+v1+v2)*vol ]
])
vol = sum([
for(face=vnf[1], j=[1:1:len(face)-2]) let(
v0 = verts[face[0]],
v1 = verts[face[j]],
v2 = verts[face[j+1]]
) cross(v2,v1)*v0
]),
pos = sum([
for(face=vnf[1], j=[1:1:len(face)-2]) let(
v0 = verts[face[0]],
v1 = verts[face[j]],
v2 = verts[face[j+1]],
vol = cross(v2,v1)*v0
)
(v0+v1+v2)*vol
])
)
val[1]/val[0]/4;
pos/vol/4;
function _triangulate_planar_convex_polygons(polys) =

View File

@ -93,10 +93,10 @@ module thinning_wall(h=50, l=100, thick=5, ang=30, braces=false, strut, wall, an
wall = is_num(wall)? wall : thick/2;
bevel_h = strut + (thick-wall)/2/tan(ang);
cp1 = find_circle_2tangents([0,0,h/2], [l2/2,0,h/2], [l1/2,0,-h/2], r=strut)[0];
cp2 = find_circle_2tangents([0,0,h/2], [l2/2,0,h/2], [l1/2,0,-h/2], r=bevel_h)[0];
cp3 = find_circle_2tangents([0,0,-h/2], [l1/2,0,-h/2], [l2/2,0,h/2], r=bevel_h)[0];
cp4 = find_circle_2tangents([0,0,-h/2], [l1/2,0,-h/2], [l2/2,0,h/2], r=strut)[0];
cp1 = circle_2tangents([0,0,h/2], [l2/2,0,h/2], [l1/2,0,-h/2], r=strut)[0];
cp2 = circle_2tangents([0,0,h/2], [l2/2,0,h/2], [l1/2,0,-h/2], r=bevel_h)[0];
cp3 = circle_2tangents([0,0,-h/2], [l1/2,0,-h/2], [l2/2,0,h/2], r=bevel_h)[0];
cp4 = circle_2tangents([0,0,-h/2], [l1/2,0,-h/2], [l2/2,0,h/2], r=strut)[0];
z1 = h/2;
z2 = cp1.z;