This commit is contained in:
Adrian Mariano 2020-08-26 21:28:51 -04:00
commit dcf02a3990
29 changed files with 2060 additions and 1399 deletions

View file

@ -18,20 +18,6 @@
// Section: List Query Operations
// Function: is_simple_list()
// Description:
// Returns true just when all elements of `list` are simple values.
// Usage:
// is_simple_list(list)
// Arguments:
// list = The list to check.
// Example:
// a = is_simple_list([3,4,5,6,7,8,9]); Returns: true
// b = is_simple_list([3,4,5,[6],7,8]); Returns: false
function is_simple_list(list) =
is_list(list)
&& []==[for(e=list) if(is_list(e)) 0];
// Function: select()
// Description:
@ -73,9 +59,6 @@ function select(list, start, end=undef) =
: concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ;
// Function: slice()
// Description:
// Returns a slice of a list. The first item is index 0.
@ -101,24 +84,23 @@ function slice(list,start,end) =
) [for (i=[s:1:e-1]) if (e>s) list[i]];
// Function: in_list()
// Description: Returns true if value `val` is in list `list`. When `val==NAN` the answer will be false for any list.
// Arguments:
// val = The simple value to search for.
// list = The list to search.
// idx = If given, searches the given subindexes for matches for `val`.
// idx = If given, searches the given subindex for matches for `val`.
// Example:
// in_list("bar", ["foo", "bar", "baz"]); // Returns true.
// in_list("bee", ["foo", "bar", "baz"]); // Returns false.
// in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true.
function in_list(val,list,idx=undef) =
assert( is_list(list) && (is_undef(idx) || is_finite(idx)),
"Invalid input." )
let( s = search([val], list, num_returns_per_match=1, index_col_num=idx)[0] )
s==[] || s[0]==[] ? false
s==[] || s==[[]] ? false
: is_undef(idx) ? val==list[s]
: val==list[s][idx];
// Function: min_index()
@ -209,7 +191,6 @@ function repeat(val, n, i=0) =
[for (j=[1:1:n[i]]) repeat(val, n, i+1)];
// Function: list_range()
// Usage:
// list_range(n, [s], [e])
@ -249,7 +230,6 @@ function list_range(n=undef, s=0, e=undef, step=undef) =
// Section: List Manipulation
// Function: reverse()
@ -315,8 +295,6 @@ function deduplicate(list, closed=false, eps=EPSILON) =
: [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]];
// Function: deduplicate_indexed()
// Usage:
// new_idxs = deduplicate_indexed(list, indices, [closed], [eps]);
@ -351,8 +329,6 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
];
// Function: repeat_entries()
// Usage:
// newlist = repeat_entries(list, N)
@ -390,8 +366,6 @@ function repeat_entries(list, N, exact = true) =
: [for (val=reps_guess) round(val)]
)
[for(i=[0:length-1]) each repeat(list[i],reps[i])];
// Function: list_set()
@ -431,7 +405,6 @@ function list_set(list=[],indices,values,dflt=0,minlen=0) =
dflt ,
each repeat(dflt, minlen-max(indices))
];
// Function: list_insert()
@ -465,8 +438,6 @@ function list_insert(list, indices, values, _i=0) =
];
// Function: list_remove()
// Usage:
// list_remove(list, indices)
@ -494,8 +465,6 @@ function list_remove(list, indices) =
];
// Function: list_remove_values()
// Usage:
// list_remove_values(list,values,all=false) =
@ -565,8 +534,6 @@ function list_bset(indexset, valuelist, dflt=0) =
);
// Section: List Length Manipulation
// Function: list_shortest()
@ -579,7 +546,6 @@ function list_shortest(array) =
min([for (v = array) len(v)]);
// Function: list_longest()
// Description:
// Returns the length of the longest sublist in a list of lists.
@ -629,7 +595,6 @@ function list_fit(array, length, fill) =
: list_pad(array,length,fill);
// Section: List Shuffling and Sorting
// Function: shuffle()
@ -684,6 +649,7 @@ function _sort_vectors2(arr) =
)
concat( _sort_vectors2(lesser), equal, _sort_vectors2(greater) );
// Sort a vector of vectors based on the first three entries of each vector
// Lexicographic order, remaining entries of vector ignored
function _sort_vectors3(arr) =
@ -711,7 +677,6 @@ function _sort_vectors3(arr) =
) concat( _sort_vectors3(lesser), equal, _sort_vectors3(greater) );
// Sort a vector of vectors based on the first four entries of each vector
// Lexicographic order, remaining entries of vector ignored
function _sort_vectors4(arr) =
@ -742,45 +707,38 @@ function _sort_vectors4(arr) =
&& y[3]>pivot[3] ))))))
y ]
) concat( _sort_vectors4(lesser), equal, _sort_vectors4(greater) );
// when idx==undef, returns the sorted array
// otherwise, returns the indices of the sorted array
function _sort_general(arr, idx=undef) =
(len(arr)<=1) ? arr :
is_undef(idx)
? _sort_scalar(arr)
: let( arrind=[for(k=[0:len(arr)-1], ark=[arr[k]]) [ k, [for (i=idx) ark[i]] ] ] )
_indexed_sort(arrind);
// given a list of pairs, return the first element of each pair of the list sorted by the second element of the pair
// the sorting is done using compare_vals()
function _indexed_sort(arrind) =
arrind==[] ? [] : len(arrind)==1? [arrind[0][0]] :
let( pivot = arrind[floor(len(arrind)/2)][1] )
let(
pivot = arr[floor(len(arr)/2)],
pivotval = idx==undef? pivot : [for (i=idx) pivot[i]],
compare =
is_undef(idx) ? [for(entry=arr) compare_vals(entry, pivotval) ] :
[ for (entry = arr)
let( val = [for (i=idx) entry[i] ] )
compare_vals(val, pivotval) ] ,
lesser = [ for (i = [0:1:len(arr)-1]) if (compare[i] < 0) arr[i] ],
equal = [ for (i = [0:1:len(arr)-1]) if (compare[i] ==0) arr[i] ],
greater = [ for (i = [0:1:len(arr)-1]) if (compare[i] > 0) arr[i] ]
)
concat(_sort_general(lesser,idx), equal, _sort_general(greater,idx));
lesser = [ for (entry=arrind) if (compare_vals(entry[1], pivot) <0 ) entry ],
equal = [ for (entry=arrind) if (compare_vals(entry[1], pivot)==0 ) entry[0] ],
greater = [ for (entry=arrind) if (compare_vals(entry[1], pivot) >0 ) entry ]
)
concat(_indexed_sort(lesser), equal, _indexed_sort(greater));
// returns true for valid index specifications idx in the interval [imin, imax)
// note that idx can't have any value greater or EQUAL to imax
function _valid_idx(idx,imin,imax) =
is_undef(idx)
|| ( is_finite(idx) && idx>=imin && idx< imax )
|| ( is_list(idx) && min(idx)>=imin && max(idx)< imax )
|| ( valid_range(idx) && idx[0]>=imin && idx[2]< imax );
function _sort_general(arr, idx=undef) =
(len(arr)<=1) ? arr :
let(
pivot = arr[floor(len(arr)/2)],
pivotval = idx==undef? pivot : [for (i=idx) pivot[i]],
compare = [
for (entry = arr) let(
val = idx==undef? entry : [for (i=idx) entry[i]],
cmp = compare_vals(val, pivotval)
) cmp
],
lesser = [ for (i = [0:1:len(arr)-1]) if (compare[i] < 0) arr[i] ],
equal = [ for (i = [0:1:len(arr)-1]) if (compare[i] ==0) arr[i] ],
greater = [ for (i = [0:1:len(arr)-1]) if (compare[i] > 0) arr[i] ]
)
concat(_sort_general(lesser,idx), equal, _sort_general(greater,idx));
// Function: sort()
// Usage:
@ -799,20 +757,21 @@ function _sort_general(arr, idx=undef) =
// sorted = sort(l); // Returns [2,3,8,9,12,16,23,34,37,45,89]
function sort(list, idx=undef) =
!is_list(list) || len(list)<=1 ? list :
assert( is_undef(idx) || is_finite(idx) || is_vector(idx) || is_range(idx) , "Invalid indices.")
is_def(idx) ? _sort_general(list,idx) :
let(size = array_dim(list))
len(size)==1 ? _sort_scalars(list) :
len(size)==2 && size[1] <=4
? (
size[1]==0 ? list :
size[1]==1 ? _sort_vectors1(list) :
size[1]==2 ? _sort_vectors2(list) :
size[1]==3 ? _sort_vectors3(list)
/*size[1]==4*/ : _sort_vectors4(list)
)
: _sort_general(list);
is_def(idx)
? assert( _valid_idx(idx,0,len(list)) , "Invalid indices.")
let( sarr = _sort_general(list,idx) )
[for(i=[0:len(sarr)-1]) list[sarr[i]] ]
: let(size = array_dim(list))
len(size)==1 ? _sort_scalars(list) :
len(size)==2 && size[1] <=4
? (
size[1]==0 ? list :
size[1]==1 ? _sort_vectors1(list) :
size[1]==2 ? _sort_vectors2(list) :
size[1]==3 ? _sort_vectors3(list)
/*size[1]==4*/ : _sort_vectors4(list)
)
: _sort_general(list);
// Function: sortidx()
@ -838,13 +797,13 @@ function sort(list, idx=undef) =
// 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 to sort." )
assert( is_undef(idx) || is_finite(idx) || is_vector(idx) , "Invalid indices.")
assert( _valid_idx(idx,0,len(list)) , "Invalid indices.")
list==[] ? [] :
let(
size = array_dim(list),
aug = is_undef(idx) && (len(size) == 1 || (len(size) == 2 && size[1]<=4))
? zip(list, list_range(len(list)))
: enumerate(list,idx=idx)
: 0
)
is_undef(idx) && len(size) == 1? subindex(_sort_vectors1(aug),1) :
is_undef(idx) && len(size) == 2 && size[1] <=4
@ -856,25 +815,8 @@ function sortidx(list, idx=undef) =
/*size[1]==4*/ : subindex(_sort_vectors4(aug),4)
)
: // general case
subindex(_sort_general(aug, idx=list_range(s=1,n=len(aug)-1)), 0);
_sort_general(list,idx);
function sortidx(list, idx=undef) =
list==[] ? [] : let(
size = array_dim(list),
aug = is_undef(idx) && (len(size) == 1 || (len(size) == 2 && size[1]<=4))?
zip(list, list_range(len(list))) :
enumerate(list,idx=idx)
)
is_undef(idx) && len(size) == 1? subindex(_sort_vectors1(aug),1) :
is_undef(idx) && len(size) == 2 && size[1] <=4? (
size[1]==0? list_range(len(arr)) :
size[1]==1? subindex(_sort_vectors1(aug),1) :
size[1]==2? subindex(_sort_vectors2(aug),2) :
size[1]==3? subindex(_sort_vectors3(aug),3) :
/*size[1]==4*/ subindex(_sort_vectors4(aug),4)
) :
// general case
subindex(_sort_general(aug, idx=list_range(s=1,n=len(aug)-1)), 0);
// sort() does not accept strings but sortidx does; isn't inconsistent ?
@ -896,7 +838,6 @@ function unique(arr) =
];
// Function: unique_count()
// Usage:
// unique_count(arr);
@ -913,8 +854,6 @@ function unique_count(arr) =
[ select(arr,ind), deltas( concat(ind,[len(arr)]) ) ];
// Section: List Iteration Helpers
// Function: idx()
@ -952,10 +891,10 @@ function idx(list, step=1, end=-1,start=0) =
// for (p=enumerate(colors)) right(20*p[0]) color(p[1]) circle(d=10);
function enumerate(l,idx=undef) =
assert(is_list(l)||is_string(list), "Invalid input." )
assert(is_undef(idx)||is_finite(idx)||is_vector(idx) ||is_range(idx), "Invalid index/indices." )
assert( _valid_idx(idx,0,len(l)), "Invalid index/indices." )
(idx==undef)
? [for (i=[0:1:len(l)-1]) [i,l[i]]]
: [for (i=[0:1:len(l)-1]) concat([i], [for (j=idx) l[i][j]])];
: [for (i=[0:1:len(l)-1]) [ i, for (j=idx) l[i][j]] ];
// Function: force_list()
@ -1109,8 +1048,6 @@ function set_union(a, b, get_indices=false) =
) [idxs, nset];
// Function: set_difference()
// Usage:
// s = set_difference(a, b);
@ -1130,7 +1067,6 @@ function set_difference(a, b) =
[ for (i=idx(a)) if(found[i]==[]) a[i] ];
// Function: set_intersection()
// Usage:
// s = set_intersection(a, b);
@ -1151,7 +1087,6 @@ function set_intersection(a, b) =
// Section: Array Manipulation
// Function: add_scalar()
@ -1170,26 +1105,60 @@ function add_scalar(v,s) =
is_finite(s) ? [for (x=v) is_list(x)? add_scalar(x,s) : is_finite(x) ? x+s: x] : v;
// Function: subindex()
// Usage:
// subindex(M, idx)
// Description:
// For each array item, return the indexed subitem.
// Returns a list of the values of each vector at the specfied
// index list or range. If the index list or range has
// only one entry the output list is flattened.
// Extracts the entries listed in idx from each entry in M. For a matrix this means
// selecting a specified set of columns. If idx is a number the return is a vector,
// otherwise it is a list of lists (the submatrix).
// This function will return `undef` at all entry positions indexed by idx not found in the input list M.
// Arguments:
// v = The given list of lists.
// M = The given list of lists.
// idx = The index, list of indices, or range of indices to fetch.
// Example:
// v = [[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
// subindex(v,2); // Returns [3, 7, 11, 15]
// subindex(v,[2,1]); // Returns [[3, 2], [7, 6], [11, 10], [15, 14]]
// subindex(v,[1:3]); // Returns [[2, 3, 4], [6, 7, 8], [10, 11, 12], [14, 15, 16]]
function subindex(v, idx) =
[ for(val=v)
let( value=[for(i=idx) val[i]] )
len(value)==1 ? value[0] : value
];
// M = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
// subindex(M,2); // Returns [3, 7, 11, 15]
// subindex(M,[2]); // Returns [[3], [7], [11], [15]]
// subindex(M,[2,1]); // Returns [[3, 2], [7, 6], [11, 10], [15, 14]]
// subindex(M,[1:3]); // Returns [[2, 3, 4], [6, 7, 8], [10, 11, 12], [14, 15, 16]]
// N = [ [1,2], [3], [4,5], [6,7,8] ];
// subindex(N,[0,1]); // Returns [ [1,2], [3,undef], [4,5], [6,7] ]
function subindex(M, idx) =
assert( is_list(M), "The input is not a list." )
assert( !is_undef(idx) && _valid_idx(idx,0,1/0), "Invalid index input." )
is_finite(idx)
? [for(row=M) row[idx]]
: [for(row=M) [for(i=idx) row[i]]];
// Function: submatrix()
// Usage: 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.
// Arguments:
// M = Given list of lists
// idx1 = rows index list or range
// idx2 = column index list or range
// Example:
// M = [[ 1, 2, 3, 4, 5],
// [ 6, 7, 8, 9,10],
// [11,12,13,14,15],
// [16,17,18,19,20],
// [21,22,23,24,25]];
// submatrix(M,[1:2],[3:4]); // Returns [[9, 10], [14, 15]]
// submatrix(M,[1], [3,4])); // Returns [[9,10]]
// submatrix(M,1, [3,4])); // Returns [[9,10]]
// submatrix(M,1,3)); // Returns [[9]]
// submatrix(M, [3,4],1); // Returns [[17],[22]]);
// submatrix(M, [1,3],[2,4]); // Returns [[8,10],[18,20]]);
// A = [[true, 17, "test"],
// [[4,2], 91, false],
// [6, [3,4], undef]];
// submatrix(A,[0,2],[1,2]); // Returns [[17, "test"], [[3, 4], undef]]
function submatrix(M,idx1,idx2) =
[for(i=idx1) [for(j=idx2) M[i][j] ] ];
// Function: zip()
@ -1318,6 +1287,10 @@ function array_dim(v, depth=undef) =
// Function: transpose()
// Description: Returns the transposition of the given array.
// When reverse=true, the transposition is done in respect to the secondary diagonal, that is:
// .
// reverse(transpose(reverse(arr))) == transpose(arr, reverse=true)
// By default, reverse=false.
// Example:
// arr = [
// ["a", "b", "c"],
@ -1344,16 +1317,32 @@ function array_dim(v, depth=undef) =
// // ["c", "f"],
// // ]
// Example:
// arr = [
// ["a", "b", "c"],
// ["d", "e", "f"],
// ["g", "h", "i"]
// ];
// t = transpose(arr, reverse=true);
// // Returns:
// // [
// // ["i", "f", "c"],
// // ["h", "e", "b"],
// // ["g", "d", "a"]
// // ]
// Example:
// transpose([3,4,5]); // Returns: [3,4,5]
function transpose(arr) =
let( a0 = arr[0] )
is_list(a0)
? assert([for(a=arr) if(len(a)!=len(a0)) 1]==[], "The array is not a matrix." )
[for (i=[0:1:len(a0)-1])
[ for (j=[0:1:len(arr)-1]) arr[j][i] ] ]
: arr;
function transpose(arr, reverse=false) =
assert( is_list(arr) && len(arr)>0, "The array is not a vector neither a matrix." )
is_list(arr[0])
? let( l0 = len(arr[0]) )
assert([for(a=arr) if(!is_list(a) || len(a)!=l0) 1 ]==[], "The array is not a vector neither a matrix." )
reverse
? [for (i=[0:1:l0-1])
[ for (j=[0:1:len(arr)-1]) arr[len(arr)-1-j][l0-1-i] ] ]
: [for (i=[0:1:l0-1])
[ for (j=[0:1:len(arr)-1]) arr[j][i] ] ]
: assert( is_vector(arr), "The array is not a vector neither a matrix." )
arr;
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -285,7 +285,7 @@ function attach_geom_size(geom) =
) [2*maxxr,2*maxyr,l]
) : type == "spheroid"? ( //r
let( r=geom[1] )
is_num(r)? [2,2,2]*r : vmul([2,2,2],r)
is_num(r)? [2,2,2]*r : vmul([2,2,2],point3d(r))
) : type == "vnf_extent" || type=="vnf_isect"? ( //vnf
let(
mm = pointlist_bounds(geom[1][0]),
@ -298,7 +298,7 @@ function attach_geom_size(geom) =
) [maxx, size.y]
) : type == "circle"? ( //r
let( r=geom[1] )
is_num(r)? [2,2]*r : vmul([2,2],r)
is_num(r)? [2,2]*r : vmul([2,2],point2d(r))
) : type == "path_isect" || type == "path_extent"? ( //path
let(
mm = pointlist_bounds(geom[1]),
@ -430,8 +430,8 @@ function find_anchor(anchor, geom) =
) : type == "cyl"? ( //r1, r2, l, shift
let(
rr1=geom[1], rr2=geom[2], l=geom[3], shift=point2d(geom[4]),
r1 = is_num(rr1)? [rr1,rr1] : rr1,
r2 = is_num(rr2)? [rr2,rr2] : rr2,
r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1),
r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2),
u = (anchor.z+1)/2,
axy = unit(point2d(anchor),[0,0]),
bot = point3d(vmul(r1,axy), -l/2),
@ -447,9 +447,9 @@ function find_anchor(anchor, geom) =
) : type == "spheroid"? ( //r
let(
rr = geom[1],
r = is_num(rr)? [rr,rr,rr] : rr,
r = is_num(rr)? [rr,rr,rr] : point3d(rr),
anchor = unit(point3d(anchor),CENTER),
pos = point3d(cp) + vmul(r,anchor) + offset,
pos = point3d(cp) + vmul(r,anchor) + point3d(offset),
vec = unit(vmul(r,anchor),UP)
) [anchor, pos, vec, oang]
) : type == "vnf_isect"? ( //vnf
@ -458,10 +458,9 @@ function find_anchor(anchor, geom) =
eps = 1/2048,
points = vnf[0],
faces = vnf[1],
rpts = rot(from=anchor, to=RIGHT, p=move(point3d(-cp), p=points)),
rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), points),
hits = [
for (i = idx(faces)) let(
face = faces[i],
for (face = faces) let(
verts = select(rpts, face)
) if (
max(subindex(verts,0)) >= -eps &&
@ -470,35 +469,40 @@ function find_anchor(anchor, geom) =
min(subindex(verts,1)) <= eps &&
min(subindex(verts,2)) <= eps
) let(
pt = polygon_line_intersection(
select(points, face),
[CENTER,anchor], eps=eps
)
) if (!is_undef(pt)) [norm(pt), i, pt]
poly = select(points, face),
pt = polygon_line_intersection(poly, [cp,cp+anchor], bounded=[true,false], eps=eps)
) if (!is_undef(pt)) let(
plane = plane_from_polygon(poly),
n = unit(plane_normal(plane))
)
[norm(pt-cp), n, pt]
]
)
assert(len(hits)>0, "Anchor vector does not intersect with the shape. Attachment failed.")
let(
furthest = max_index(subindex(hits,0)),
pos = point3d(cp) + hits[furthest][2],
dist = hits[furthest][0],
nfaces = [for (hit = hits) if(approx(hit[0],dist,eps=eps)) hit[1]],
n = unit(
sum([
for (i = nfaces) let(
faceverts = select(points, faces[i]),
faceplane = plane_from_points(faceverts),
nrm = plane_normal(faceplane)
) nrm
]) / len(nfaces),
UP
)
pos = hits[furthest][2],
hitnorms = [for (hit = hits) if (approx(hit[0],dist,eps=eps)) hit[1]],
unorms = len(hitnorms) > 7
? unique([for (nn = hitnorms) quant(nn,1e-9)])
: [
for (i = idx(hitnorms)) let(
nn = hitnorms[i],
isdup = [
for (j = [i+1:1:len(hitnorms)-1])
if (approx(nn, hitnorms[j])) 1
] != []
) if (!isdup) nn
],
n = unit(sum(unorms)),
oang = approx(point2d(n), [0,0])? 0 : atan2(n.y, n.x) + 90
)
[anchor, pos, n, oang]
) : type == "vnf_extent"? ( //vnf
let(
vnf=geom[1],
rpts = rot(from=anchor, to=RIGHT, p=move(point3d(-cp), p=vnf[0])),
rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
maxx = max(subindex(rpts,0)),
idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
mm = pointlist_bounds(select(rpts,idxs)),
@ -519,10 +523,10 @@ function find_anchor(anchor, geom) =
) : type == "circle"? ( //r
let(
rr = geom[1],
r = is_num(rr)? [rr,rr] : rr,
pos = point2d(cp) + vmul(r,anchor) + offset,
r = is_num(rr)? [rr,rr] : point2d(rr),
anchor = unit(point2d(anchor),[0,0]),
vec = unit(vmul([r.y,r.x],anchor),[0,1])
pos = point2d(cp) + vmul(r,anchor) + point2d(offset),
vec = unit(vmul(r,anchor),[0,1])
) [anchor, pos, vec, 0]
) : type == "path_isect"? ( //path
let(
@ -849,7 +853,7 @@ module attachable(
// Module: position()
// Usage:
// position(from, [overlap]) ...
// position(from) ...
// Description:
// Attaches children to a parent object at an anchor point.
// Arguments:
@ -970,7 +974,8 @@ module edge_profile(edges=EDGES_ALL, except=[], convexity=10) {
$attach_anchor = anch;
$attach_norot = true;
$tags = "mask";
length = sum(vmul($parent_size, [for (x=vec) x?0:1]))+0.1;
psize = point3d($parent_size);
length = [for (i=[0:2]) if(!vec[i]) psize[i]][0]+0.1;
rotang =
vec.z<0? [90,0,180+vang(point2d(vec))] :
vec.z==0 && sign(vec.x)==sign(vec.y)? 135+vang(point2d(vec)) :
@ -1230,17 +1235,20 @@ module show(tags="")
// }
module diff(neg, pos=undef, keep=undef)
{
difference() {
if (pos != undef) {
show(pos) children();
} else {
if (keep == undef) {
hide(neg) children();
// Don't perform the operation if the current tags are hidden
if (attachment_is_shown($tags)) {
difference() {
if (pos != undef) {
show(pos) children();
} else {
hide(str(neg," ",keep)) children();
if (keep == undef) {
hide(neg) children();
} else {
hide(str(neg," ",keep)) children();
}
}
show(neg) children();
}
show(neg) children();
}
if (keep!=undef) {
show(keep) children();
@ -1275,17 +1283,20 @@ module diff(neg, pos=undef, keep=undef)
// }
module intersect(a, b=undef, keep=undef)
{
intersection() {
if (b != undef) {
show(b) children();
} else {
if (keep == undef) {
hide(a) children();
// Don't perform the operation if the current tags are hidden
if (attachment_is_shown($tags)) {
intersection() {
if (b != undef) {
show(b) children();
} else {
hide(str(a," ",keep)) children();
if (keep == undef) {
hide(a) children();
} else {
hide(str(a," ",keep)) children();
}
}
show(a) children();
}
show(a) children();
}
if (keep!=undef) {
show(keep) children();

View file

@ -129,18 +129,13 @@ function is_list_of(list,pattern) =
is_list(list) &&
[]==[for(entry=0*list) if (entry != pattern) entry];
function _list_pattern(list) =
is_list(list) ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0]
: 0;
// Function: is_consistent()
// Usage:
// is_consistent(list)
// Description:
// Tests whether input is a list of entries which all have the same list structure
// and are filled with finite numerical data.
// and are filled with finite numerical data. It returns `true`for the empty list.
// Example:
// is_consistent([3,4,5]); // Returns true
// is_consistent([[3,4],[4,5],[6,7]]); // Returns true
@ -148,7 +143,7 @@ function _list_pattern(list) =
// is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]); // Returns true
// is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]); // Returns false
function is_consistent(list) =
is_list(list) && is_list_of(list, _list_pattern(list[0]));
/*is_list(list) &&*/ is_list_of(list, _list_pattern(list[0]));
//Internal function
@ -198,11 +193,11 @@ function first_defined(v,recursive=false,_i=0) =
is_undef(first_defined(v[_i],recursive=recursive))
)
)? first_defined(v,recursive=recursive,_i=_i+1) : v[_i];
// Function: one_defined()
// Usage:
// one_defined(vars, names, [required])
// one_defined(vars, names, <required>)
// Description:
// Examines the input list `vars` and returns the entry which is not `undef`. If more
// than one entry is `undef` then issues an assertion specifying "Must define exactly one of" followed
@ -221,8 +216,7 @@ function one_defined(vars, names, required=true) =
// Function: num_defined()
// Description: Counts how many items in list `v` are not `undef`.
function num_defined(v,_i=0,_cnt=0) = _i>=len(v)? _cnt : num_defined(v,_i+1,_cnt+(is_undef(v[_i])? 0 : 1));
function num_defined(v) = len([for(vi=v) if(!is_undef(vi)) 1]);
// Function: any_defined()
// Description:
@ -239,8 +233,8 @@ function any_defined(v,recursive=false) = first_defined(v,recursive=recursive) !
// Arguments:
// v = The list whose items are being checked.
// recursive = If true, any sublists are evaluated recursively.
function all_defined(v,recursive=false) = max([for (x=v) is_undef(x)||(recursive&&is_list(x)&&!all_defined(x))? 1 : 0])==0;
function all_defined(v,recursive=false) =
[]==[for (x=v) if(is_undef(x)||(recursive && is_list(x) && !all_defined(x,recursive))) 0 ];
@ -249,7 +243,7 @@ function all_defined(v,recursive=false) = max([for (x=v) is_undef(x)||(recursive
// Function: get_anchor()
// Usage:
// get_anchor(anchor,center,[uncentered],[dflt]);
// get_anchor(anchor,center,<uncentered>,<dflt>);
// Description:
// Calculated the correct anchor from `anchor` and `center`. In order:
// - If `center` is not `undef` and `center` evaluates as true, then `CENTER` (`[0,0,0]`) is returned.
@ -270,7 +264,7 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
// Function: get_radius()
// Usage:
// get_radius([r1], [r2], [r], [d1], [d2], [d], [dflt]);
// get_radius(<r1>, <r2>, <r>, <d1>, <d2>, <d>, <dflt>);
// Description:
// Given various radii and diameters, returns the most specific radius.
// If a diameter is most specific, returns half its value, giving the radius.
@ -288,19 +282,23 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
// r = Most general radius.
// d = Most general diameter.
// dflt = Value to return if all other values given are `undef`.
function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) = (
!is_undef(r1)? assert(is_undef(r2)&&is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") r1 :
!is_undef(r2)? assert(is_undef(d1)&&is_undef(d2), "Conflicting or redundant radius/diameter arguments given.") r2 :
!is_undef(d1)? d1/2 :
!is_undef(d2)? d2/2 :
!is_undef(r)? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.") r :
!is_undef(d)? d/2 :
dflt
);
function get_radius(r1=undef, r2=undef, r=undef, d1=undef, d2=undef, d=undef, dflt=undef) =
assert(num_defined([r1,d1,r2,d2])<2, "Conflicting or redundant radius/diameter arguments given.")
!is_undef(r1) ? assert(is_finite(r1), "Invalid radius r1." ) r1
: !is_undef(r2) ? assert(is_finite(r2), "Invalid radius r2." ) r2
: !is_undef(d1) ? assert(is_finite(d1), "Invalid diameter d1." ) d1/2
: !is_undef(d2) ? assert(is_finite(d2), "Invalid diameter d2." ) d2/2
: !is_undef(r)
? assert(is_undef(d), "Conflicting or redundant radius/diameter arguments given.")
assert(is_finite(r) || is_vector(r,1) || is_vector(r,2), "Invalid radius r." )
r
: !is_undef(d) ? assert(is_finite(d) || is_vector(d,1) || is_vector(d,2), "Invalid diameter d." ) d/2
: dflt;
// Function: get_height()
// Usage:
// get_height([h],[l],[height],[dflt])
// get_height(<h>,<l>,<height>,<dflt>)
// Description:
// Given several different parameters for height check that height is not multiply defined
// and return a single value. If the three values `l`, `h`, and `height` are all undefined
@ -317,7 +315,7 @@ function get_height(h=undef,l=undef,height=undef,dflt=undef) =
// Function: scalar_vec3()
// Usage:
// scalar_vec3(v, [dflt]);
// scalar_vec3(v, <dflt>);
// Description:
// If `v` is a scalar, and `dflt==undef`, returns `[v, v, v]`.
// If `v` is a scalar, and `dflt!=undef`, returns `[v, dflt, dflt]`.
@ -346,6 +344,19 @@ function segs(r) =
// Module: no_children()
// Usage:
// no_children($children);
// Description:
// Assert that the calling module does not support children. Prints an error message to this effect and fails if children are present,
// as indicated by its argument.
// Arguments:
// $children = number of children the module has.
module no_children(count) {
assert(count==0, str("Module ",parent_module(1),"() does not support child modules"));
}
// Section: Testing Helpers
@ -356,7 +367,7 @@ function _valstr(x) =
// Module: assert_approx()
// Usage:
// assert_approx(got, expected, [info]);
// assert_approx(got, expected, <info>);
// Description:
// Tests if the value gotten is what was expected. If not, then
// the expected and received values are printed to the console and
@ -383,7 +394,7 @@ module assert_approx(got, expected, info) {
// Module: assert_equal()
// Usage:
// assert_equal(got, expected, [info]);
// assert_equal(got, expected, <info>);
// Description:
// Tests if the value gotten is what was expected. If not, then
// the expected and received values are printed to the console and
@ -410,7 +421,7 @@ module assert_equal(got, expected, info) {
// Module: shape_compare()
// Usage:
// shape_compare([eps]) {test_shape(); expected_shape();}
// shape_compare(<eps>) {test_shape(); expected_shape();}
// Description:
// Compares two child shapes, returning empty geometry if they are very nearly the same shape and size.
// Returns the differential geometry if they are not nearly the same shape and size.

View file

@ -949,7 +949,7 @@ module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=tr
for ($idx = idx(theta_phis)) {
tp = theta_phis[$idx];
xyz = spherical_to_xyz(r, tp[0], tp[1]);
$pos = vmul(xyz,scale);
$pos = vmul(xyz,point3d(scale,1));
$theta = tp[0];
$phi = tp[1];
$rad = r;

View file

@ -1,126 +0,0 @@
//////////////////////////////////////////////////////////////////////
// LibFile: errors.scad
// Functions and modules to facilitate error reporting.
// To use, include this line at the top of your file:
// ```
// use <BOSL2/std.scad>
// ```
//////////////////////////////////////////////////////////////////////
// Section: Warnings and Errors
// Module: no_children()
// Usage:
// no_children($children);
// Description:
// Assert that the calling module does not support children. Prints an error message to this effect and fails if children are present,
// as indicated by its argument.
// Arguments:
// $children = number of children the module has.
module no_children(count) {
assert(count==0, str("Module ",parent_module(1),"() does not support child modules"));
}
// Function&Module: echo_error()
// Usage:
// echo_error(msg, [pfx]);
// Description:
// Emulates printing of an error message. The text will be shaded red.
// You can also use this as a function call from a function.
// Arguments:
// msg = The message to print.
// pfx = The prefix to print before `msg`. Default: `ERROR`
module echo_error(msg, pfx="ERROR") {
echo(str("<p style=\"background-color: #ffb0b0\"><b>", pfx, ":</b> ", msg, "</p>"));
}
function echo_error(msg, pfx="ERROR") =
echo(str("<p style=\"background-color: #ffb0b0\"><b>", pfx, ":</b> ", msg, "</p>"));
// Function&Module: echo_warning()
// Usage:
// echo_warning(msg, [pfx]);
// Description:
// Emulates printing of a warning message. The text will be shaded yellow.
// You can also use this as a function call from a function.
// Arguments:
// msg = The message to print.
// pfx = The prefix to print before `msg`. Default: `WARNING`
module echo_warning(msg, pfx="WARNING") {
echo(str("<p style=\"background-color: #ffffb0\"><b>", pfx, ":</b> ", msg, "</p>"));
}
function echo_warning(msg, pfx="WARNING") =
echo(str("<p style=\"background-color: #ffffb0\"><b>", pfx, ":</b> ", msg, "</p>"));
// Function&Module: deprecate()
// Usage:
// deprecate(name, [suggest]);
// Description:
// Show module deprecation warnings.
// You can also use this as a function call from a function.
// Arguments:
// name = The name of the module that is deprecated.
// suggest = If given, the module to recommend using instead.
module deprecate(name, suggest=undef) {
echo_warning(pfx="DEPRECATED",
str(
"`<code>", name, "</code>` is deprecated and should not be used.",
is_undef(suggest)? "" : str(
" You should use `<code>", suggest, "</code>` instead."
)
)
);
}
function deprecate(name, suggest=undef) =
echo_warning(pfx="DEPRECATED",
str(
"`<code>", name, "</code>` is deprecated and should not be used.",
is_undef(suggest)? "" : str(
" You should use `<code>", suggest, "</code>` instead."
)
)
);
// Function&Module: deprecate_argument()
// Usage:
// deprecate(name, arg, [suggest]);
// Description:
// Show argument deprecation warnings.
// You can also use this as a function call from a function.
// Arguments:
// name = The name of the module/function the deprecated argument is used in.
// arg = The name of the deprecated argument.
// suggest = If given, the argument to recommend using instead.
module deprecate_argument(name, arg, suggest=undef) {
echo_warning(pfx="DEPRECATED ARG", str(
"In `<code>", name, "</code>`, ",
"the argument `<code>", arg, "</code>` ",
"is deprecated and should not be used.",
is_undef(suggest)? "" : str(
" You should use `<code>", suggest, "</code>` instead."
)
));
}
function deprecate_argument(name, arg, suggest=undef) =
echo_warning(pfx="DEPRECATED ARG", str(
"In `<code>", name, "</code>`, ",
"the argument `<code>", arg, "</code>` ",
"is deprecated and should not be used.",
is_undef(suggest)? "" : str(
" You should use `<code>", suggest, "</code>` instead."
)
));
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

File diff suppressed because it is too large Load diff

View file

@ -92,7 +92,7 @@ function hull2d_path(points) =
assert(is_path(points,2),"Invalid input to hull2d_path")
len(points) < 2 ? []
: len(points) == 2 ? [0,1]
: let(tri=find_noncollinear_points(points, error=false))
: let(tri=noncollinear_triple(points, error=false))
tri == [] ? _hull_collinear(points)
: let(
remaining = [ for (i = [0:1:len(points)-1]) if (i != tri[0] && i!=tri[1] && i!=tri[2]) i ],
@ -170,7 +170,7 @@ function hull3d_faces(points) =
assert(is_path(points,3),"Invalid input to hull3d_faces")
len(points) < 3 ? list_range(len(points))
: let ( // start with a single non-collinear triangle
tri = find_noncollinear_points(points, error=false)
tri = noncollinear_triple(points, error=false)
)
tri==[] ? _hull_collinear(points)
: let(
@ -250,7 +250,7 @@ function _find_conflicts(point, planes) = [
function _find_first_noncoplanar(plane, points, i) =
(i >= len(points) || !coplanar(plane, points[i]))? i :
(i >= len(points) || !points_on_plane([points[i]],plane))? i :
_find_first_noncoplanar(plane, points, i+1);

View file

@ -564,6 +564,7 @@ module dovetail(gender, length, l, width, w, height, h, angle, slope, taper, bac
}
// Section: Tension Clips
// h is total height above 0 of the nub
// nub extends below xy plane by distance nub/2
@ -780,5 +781,244 @@ module snap_pin_socket(size, r, radius, l,length, d,diameter,nub_depth, snap, fi
// Module: rabbit_clip()
// Usage:
// rabbit_clip(type, length, width, snap, thickness, depth, [compression], [clearance], [lock],
// [lock_clearance], [splineteps], [anchor], [orient], [spin])
// Description:
// Creates a clip with two flexible ears to lock into a mating socket, or create a mask to produce the appropriate
// mating socket. The clip can be made to insert and release easily, or to hold much better, or it can be
// created with locking flanges that will make it very hard or impossible to remove. Unlike the snap pin, this clip
// is rectangular and can be made at any height, so a suitable clip could be very thin. It's also possible to get a
// solid connection with a short pin.
// .
// The type parameters specifies whether to make a clip, a socket mask, or a double clip. The length is the
// total nominal length of the clip. (The actual length will be very close, but not equal to this.) The width
// gives the nominal width of the clip, which is the actual width of the clip at its base. The snap parameter
// gives the depth of the clip sides, which controls how easy the clip is to insert and remove. The clip "ears" are
// made over-wide by the compression value. A nonzero compression helps make the clip secure in its socket.
// The socket's width and length are increased by the clearance value which creates some space and can compensate
// for printing inaccuracy. The socket will be slightly longer than the nominal width. The thickness is the thickness
// curved line that forms the clip. The clip depth is the amount the basic clip shape is extruded. Be sure that you
// make the socket with a larger depth than the clip (try 0.4 mm) to allow ease of insertion of the clip. The clearance
// value does not apply to the depth. The splinesteps parameter increases the sampling of the clip curves.
// .
// By default clips appear with orient=UP and sockets with orient=DOWN.
// .
// The first figure shows the dimensions of the rabbit clip. The second figure shows the clip in red overlayed on
// its socket in yellow. The left clip has a nonzero clearance, so its socket is bigger than the clip all around.
// The right hand locking clip has no clearance, but it has a lock clearance, which provides some space behind
// the lock to allow the clip to fit. (Note that depending on your printer, this can be set to zero.)
//
// Figure(2DMed):
// snap=1.5;
// comp=0.75;
// mid = 8.053; // computed in rabbit_clip
// tip = [-4.58,18.03];
// translate([9,3]){
// back_half()
// rabbit_clip("pin",width=12, length=18, depth=1, thickness = 1, compression=comp, snap=snap, orient=BACK);
// color("blue"){
// stroke([[6,0],[6,18]],width=0.1);
// stroke([[6+comp, 12], [6+comp, 18]], width=.1);
// }
// color("red"){
// stroke([[6-snap,mid], [6,mid]], endcaps="arrow2",width=0.15);
// translate([6+.4,mid-.15])text("snap",size=1,valign="center");
// translate([6+comp/2,19.5])text("compression", size=1, halign="center");
// stroke([[6+comp/2,19.3], [6+comp/2,17.7]], endcap2="arrow2", width=.15);
// fwd(1.1)text("width",size=1,halign="center");
// xflip_copy()stroke([[2,-.7], [6,-.7]], endcap2="arrow2", width=.15);
// move([-6.7,mid])rot(90)text("length", size=1, halign="center");
// stroke([[-7,10.3], [-7,18]], width=.15, endcap2="arrow2");
// stroke([[-7,0], [-7,5.8]], width=.15,endcap1="arrow2");
// stroke([tip, tip-[0,1]], width=.15);
// move([tip.x+2,19.5])text("thickness", halign="center",size=1);
// stroke([[tip.x+2, 19.3], tip+[.1,.1]], width=.15, endcap2="arrow2");
// }
// }
//
// Figure(2DMed):
// snap=1.5;
// comp=0;
// translate([29,3]){
// back_half()
// rabbit_clip("socket", width=12, length=18, depth=1, thickness = 1, compression=comp, snap=snap, orient=BACK,lock=true);
// color("red")back_half()
// rabbit_clip("pin",width=12, length=18, depth=1, thickness = 1, compression=comp, snap=snap,
// orient=BACK,lock=true,lock_clearance=1);
// }
// translate([9,3]){
// back_half()
// rabbit_clip("socket", clearance=.5,width=12, length=18, depth=1, thickness = 1,
// compression=comp, snap=snap, orient=BACK,lock=false);
// color("red")back_half()
// rabbit_clip("pin",width=12, length=18, depth=1, thickness = 1, compression=comp, snap=snap,
// orient=BACK,lock=false,lock_clearance=1);
// }
// Arguments:
// type = One of "pin", "socket", "male", "female" or "double" to specify what to make.
// length = nominal clip length
// width = nominal clip width
// snap = depth of hollow on the side of the clip
// thickness = thickness of the clip "line"
// depth = amount to extrude clip (give extra room for the socket, about 0.4mm)
// compression = excess width at the "ears" to lock more tightly. Default: 0.1
// clearance = extra space in the socket for easier insertion. Default: 0.1
// lock = set to true to make a locking clip that may be irreversible. Default: false
// lock_clearance = give clearance for the lock. Default: 0
// splinesteps = number of samples in the curves of the clip. Default: 8
// anchor = anchor point for clip
// orient = clip orientation. Default: UP for pins, DOWN for sockets
// spin = spin the clip. Default: 0
//
// Example: Here are several sizes that work printed in PLA on a Prusa MK3, with default clearance of 0.1 and a depth of 5
// module test_pair(length, width, snap, thickness, compression, lock=false)
// {
// depth = 5;
// extra_depth = 10;// Change this to 0.4 for closed sockets
// cuboid([max(width+5,12),12, depth], chamfer=.5, edges=[FRONT,"Y"], anchor=BOTTOM)
// attach(BACK)
// rabbit_clip(type="pin",length=length, width=width,snap=snap,thickness=thickness,depth=depth,
// compression=compression,lock=lock);
// right(width+13)
// diff("remove")
// cuboid([width+8,max(12,length+2),depth+3], chamfer=.5, edges=[FRONT,"Y"], anchor=BOTTOM)
// attach(BACK)
// rabbit_clip(type="socket",length=length, width=width,snap=snap,thickness=thickness,depth=depth+extra_depth,
// lock=lock,compression=0,$tags="remove");
// }
// left(37)ydistribute(spacing=28){
// test_pair(length=6, width=7, snap=0.25, thickness=0.8, compression=0.1);
// test_pair(length=3.5, width=7, snap=0.1, thickness=0.8, compression=0.1); // snap = 0.2 gives a firmer connection
// test_pair(length=3.5, width=5, snap=0.1, thickness=0.8, compression=0.1); // hard to take apart
// }
// right(17)ydistribute(spacing=28){
// test_pair(length=12, width=10, snap=1, thickness=1.2, compression=0.2);
// test_pair(length=8, width=7, snap=0.75, thickness=0.8, compression=0.2, lock=true); // With lock, very firm and irreversible
// test_pair(length=8, width=7, snap=0.75, thickness=0.8, compression=0.2, lock=true); // With lock, very firm and irreversible
// }
// Example: Double clip to connect two sockets
// rabbit_clip("double",length=8, width=7, snap=0.75, thickness=0.8, compression=0.2,depth=5);
// Example: A modified version of the clip that acts like a backpack strap clip, where it locks tightly but you can squeeze to release.
// cuboid([25,15,5],anchor=BOTTOM)
// attach(BACK)rabbit_clip("pin", length=25, width=25, thickness=1.5, snap=2, compression=0, lock=true, depth=5, lock_clearance=3);
// left(32)
// diff("remove")
// cuboid([30,30,11],orient=BACK,anchor=BACK){
// attach(BACK)rabbit_clip("socket", length=25, width=25, thickness=1.5, snap=2, compression=0, lock=true, depth=5.5, lock_clearance=3,$tags="remove");
// xflip_copy()
// position(FRONT+LEFT)
// xscale(0.8)
// zcyl(l=20,r=13.5, $tags="remove",$fn=64);
// }
module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1, clearance=.1, lock=false, lock_clearance=0,
splinesteps=8, anchor, orient, spin=0)
{
assert(is_num(width) && width>0,"Width must be a positive value");
assert(is_num(length) && length>0, "Length must be a positive value");
assert(is_num(thickness) && thickness>0, "Thickness must be a positive value");
assert(is_num(snap) && snap>=0, "Snap must be a non-negative value");
assert(is_num(depth) && depth>0, "Depth must be a positive value");
assert(is_num(compression) && compression >= 0, "Compression must be a nonnegative value");
assert(is_bool(lock));
assert(is_num(lock_clearance));
legal_types = ["pin","socket","male","female","double"];
assert(in_list(type,legal_types),str("type must be one of ",legal_types));
if (type=="double") {
attachable(size=[width+2*compression, depth, 2*length], anchor=default(anchor,BACK), spin=spin, orient=default(orient,BACK)){
union(){
rabbit_clip("pin", length=length, width=width, snap=snap, thickness=thickness, depth=depth, compression=compression,
lock=lock, anchor=BOTTOM, orient=UP);
rabbit_clip("pin", length=length, width=width, snap=snap, thickness=thickness, depth=depth, compression=compression,
lock=lock, anchor=BOTTOM, orient=DOWN);
cuboid([width-thickness, depth, thickness]);
}
children();
}
} else {
anchor = default(anchor,BOTTOM);
is_pin = in_list(type,["pin","male"]);
default_overlap = 0.01 * (is_pin?1:-1); // Shift by this much to undo default overlap
extra = 0.02; // Amount of extension below nominal based position for the socket, must exceed default overlap of 0.01
clearance = is_pin ? 0 : clearance;
compression = is_pin ? compression : 0;
orient = is_def(orient) ? orient
: is_pin ? UP
: DOWN;
earwidth = 2*thickness+snap;
point_length = earwidth/2.15;
// The adjustment is using cos(theta)*earwidth/2 and sin(theta)*point_length, but the computation
// is obscured because theta is atan(length/2/snap)
scaled_len = length - 0.5 * (earwidth * snap + point_length * length) / sqrt(sqr(snap)+sqr(length/2));
bottom_pt = [0,max(scaled_len*0.15+thickness, 2*thickness)];
ctr = [width/2,scaled_len] + line_normal([width/2-snap, scaled_len/2], [width/2, scaled_len]) * earwidth/2;
inside_pt = circle_circle_tangents(bottom_pt, 0, ctr, earwidth/2)[0][1];
sidepath =[
[width/2,0],
[width/2-snap,scaled_len/2],
[width/2+(is_pin?compression:0), scaled_len],
ctr - point_length * line_normal([width/2,scaled_len], inside_pt),
inside_pt
];
fullpath = concat(
sidepath,
[bottom_pt],
reverse(apply(xflip(),sidepath))
);
assert(fullpath[4].y < fullpath[3].y, "Pin is too wide for its length");
snapmargin = -snap + select(sidepath,-1).x;// - compression;
if (is_pin){
if (snapmargin<0) echo("WARNING: The snap is too large for the clip to squeeze to fit its socket")
echo(snapmargin=snapmargin);
}
// Force tangent to be vertical at the outer edge of the clip to avoid overshoot
fulltangent = list_set(path_tangents(fullpath, uniform=false),[2,8], [[0,1],[0,-1]]);
subset = is_pin ? [0:10] : [0,1,2,3, 7,8,9,10]; // Remove internal points from the socket
tangent = select(fulltangent, subset);
path = select(fullpath, subset);
socket_smooth = .04;
pin_smooth = [.075, .075, .15, .12, .06];
smoothing = is_pin
? concat(pin_smooth, reverse(pin_smooth))
: let(side_smooth=select(pin_smooth, 0, 2))
concat(side_smooth, [socket_smooth], reverse(side_smooth));
bez = path_to_bezier(path,relsize=smoothing,tangents=tangent);
rounded = bezier_polyline(bez,splinesteps=splinesteps);
bounds = pointlist_bounds(rounded);
kk = search([bounds[1].y], subindex(rounded,1));
echo(rounded[kk[0]]);
extrapt = is_pin ? [] : [rounded[0] - [0,extra]];
finalpath = is_pin ? rounded
: let(withclearance=offset(rounded, r=-clearance))
concat( [[withclearance[0].x,-extra]],
withclearance,
[[-withclearance[0].x,-extra]]);
attachable(size=[bounds[1].x-bounds[0].x, depth, bounds[1].y-bounds[0].y], anchor=anchor, spin=spin, orient=orient){
xrot(90)
translate([0,-(bounds[1].y-bounds[0].y)/2+default_overlap,-depth/2])
linear_extrude(height=depth, convexity=10) {
if (lock)
xflip_copy()
right(clearance)
polygon([sidepath[1]+[-thickness/10,lock_clearance],
sidepath[2],
[sidepath[2].x,sidepath[1].y+lock_clearance]]);
if (is_pin)
offset_stroke(finalpath, width=[thickness,0]);
else
polygon(finalpath);
}
children();
}
}
}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -12,8 +12,8 @@
// Module: angle_pie_mask()
// Usage:
// angle_pie_mask(r|d, l, ang);
// angle_pie_mask(r1|d1, r2|d2, l, ang);
// angle_pie_mask(r|d, l, ang, [excess]);
// angle_pie_mask(r1|d1, r2|d2, l, ang, [excess]);
// Description:
// Creates a pie wedge shape that can be used to mask other shapes.
// Arguments:
@ -25,6 +25,7 @@
// d = Diameter of circle wedge is created from. (optional)
// d1 = Bottom diameter of cone that wedge is created from. (optional)
// d2 = Upper diameter of cone that wedge is created from. (optional)
// excess = The extra thickness of the mask. Default: `0.1`.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
@ -34,14 +35,14 @@ module angle_pie_mask(
ang=45, l=undef,
r=undef, r1=undef, r2=undef,
d=undef, d1=undef, d2=undef,
h=undef,
h=undef, excess=0.1,
anchor=CENTER, spin=0, orient=UP
) {
l = first_defined([l, h, 1]);
r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10);
r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10);
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
pie_slice(ang=ang, l=l+0.1, r1=r1, r2=r2, anchor=CENTER);
pie_slice(ang=ang, l=l+excess, r1=r1, r2=r2, anchor=CENTER);
children();
}
}
@ -49,13 +50,13 @@ module angle_pie_mask(
// Module: cylinder_mask()
// Usage: Mask objects
// cylinder_mask(l, r|d, chamfer, [chamfang], [from_end], [circum], [overage], [ends_only]);
// cylinder_mask(l, r|d, rounding, [circum], [overage], [ends_only]);
// cylinder_mask(l, r|d, [chamfer1|rounding1], [chamfer2|rounding2], [chamfang1], [chamfang2], [from_end], [circum], [overage], [ends_only]);
// cylinder_mask(l, r|d, chamfer, [chamfang], [from_end], [circum], [excess], [ends_only]);
// cylinder_mask(l, r|d, rounding, [circum], [excess], [ends_only]);
// cylinder_mask(l, r|d, [chamfer1|rounding1], [chamfer2|rounding2], [chamfang1], [chamfang2], [from_end], [circum], [excess], [ends_only]);
// Usage: Masking operators
// cylinder_mask(l, r|d, chamfer, [chamfang], [from_end], [circum], [overage], [ends_only]) ...
// cylinder_mask(l, r|d, rounding, [circum], [overage], [ends_only]) ...
// cylinder_mask(l, r|d, [chamfer1|rounding1], [chamfer2|rounding2], [chamfang1], [chamfang2], [from_end], [circum], [overage], [ends_only]) ...
// cylinder_mask(l, r|d, chamfer, [chamfang], [from_end], [circum], [excess], [ends_only]) ...
// cylinder_mask(l, r|d, rounding, [circum], [excess], [ends_only]) ...
// cylinder_mask(l, r|d, [chamfer1|rounding1], [chamfer2|rounding2], [chamfang1], [chamfang2], [from_end], [circum], [excess], [ends_only]) ...
// Description:
// If passed children, bevels/chamfers and/or rounds one or both
// ends of the origin-centered cylindrical region specified. If
@ -83,7 +84,7 @@ module angle_pie_mask(
// rounding2 = The radius of the rounding on the axis-positive end of the region.
// circum = If true, region will circumscribe the circle of the given radius/diameter.
// from_end = If true, chamfer/bevel size is measured from end of region. If false, chamfer/bevel is measured outset from the radius of the region. (Default: false)
// overage = The extra thickness of the mask. Default: `10`.
// excess = The extra thickness of the mask. Default: `10`.
// ends_only = If true, only mask the ends and not around the middle of the cylinder.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
@ -105,7 +106,7 @@ module cylinder_mask(
chamfang=undef, chamfang1=undef, chamfang2=undef,
rounding=undef, rounding1=undef, rounding2=undef,
circum=false, from_end=false,
overage=10, ends_only=false,
excess=10, ends_only=false,
anchor=CENTER, spin=0, orient=UP
) {
r1 = get_radius(r=r, d=d, r1=r1, d1=d1, dflt=1);
@ -132,12 +133,12 @@ module cylinder_mask(
chlen1 = cham1 / (from_end? 1 : tan(ang1));
chlen2 = cham2 / (from_end? 1 : tan(ang2));
if (!ends_only) {
cylinder(r=maxd+overage, h=l+2*overage, center=true);
cylinder(r=maxd+excess, h=l+2*excess, center=true);
} else {
if (cham2>0) up(l/2-chlen2) cylinder(r=maxd+overage, h=chlen2+overage, center=false);
if (cham1>0) down(l/2+overage) cylinder(r=maxd+overage, h=chlen1+overage, center=false);
if (fil2>0) up(l/2-fil2) cylinder(r=maxd+overage, h=fil2+overage, center=false);
if (fil1>0) down(l/2+overage) cylinder(r=maxd+overage, h=fil1+overage, center=false);
if (cham2>0) up(l/2-chlen2) cylinder(r=maxd+excess, h=chlen2+excess, center=false);
if (cham1>0) down(l/2+excess) cylinder(r=maxd+excess, h=chlen1+excess, center=false);
if (fil2>0) up(l/2-fil2) cylinder(r=maxd+excess, h=fil2+excess, center=false);
if (fil1>0) down(l/2+excess) cylinder(r=maxd+excess, h=fil1+excess, center=false);
}
}
cyl(r1=sc*r1, r2=sc*r2, l=l, chamfer1=cham1, chamfer2=cham2, chamfang1=ang1, chamfang2=ang2, from_end=from_end, rounding1=fil1, rounding2=fil2);
@ -154,14 +155,15 @@ module cylinder_mask(
// Module: chamfer_mask()
// Usage:
// chamfer_mask(l, chamfer);
// chamfer_mask(l, chamfer, [excess]);
// Description:
// Creates a shape that can be used to chamfer a 90 degree edge.
// Difference it from the object to be chamfered. The center of
// the mask object should align exactly with the edge to be chamfered.
// Arguments:
// l = Length of mask.
// chamfer = Size of chamfer
// chamfer = Size of chamfer.
// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1`
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
@ -170,9 +172,9 @@ module cylinder_mask(
// cube(50, anchor=BOTTOM+FRONT);
// #chamfer_mask(l=50, chamfer=10, orient=RIGHT);
// }
module chamfer_mask(l=1, chamfer=1, anchor=CENTER, spin=0, orient=UP) {
module chamfer_mask(l=1, chamfer=1, excess=0.1, anchor=CENTER, spin=0, orient=UP) {
attachable(anchor,spin,orient, size=[chamfer*2, chamfer*2, l]) {
cylinder(r=chamfer, h=l+0.1, center=true, $fn=4);
cylinder(r=chamfer, h=l+excess, center=true, $fn=4);
children();
}
}
@ -180,14 +182,15 @@ module chamfer_mask(l=1, chamfer=1, anchor=CENTER, spin=0, orient=UP) {
// Module: chamfer_mask_x()
// Usage:
// chamfer_mask_x(l, chamfer, [anchor]);
// chamfer_mask_x(l, chamfer, [excess]);
// Description:
// Creates a shape that can be used to chamfer a 90 degree edge along the X axis.
// Difference it from the object to be chamfered. The center of the mask
// object should align exactly with the edge to be chamfered.
// Arguments:
// l = Height of mask
// chamfer = size of chamfer
// l = Length of mask.
// chamfer = Size of chamfer.
// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1`
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the X axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// Example:
@ -195,21 +198,22 @@ module chamfer_mask(l=1, chamfer=1, anchor=CENTER, spin=0, orient=UP) {
// cube(50, anchor=BOTTOM+FRONT);
// #chamfer_mask_x(l=50, chamfer=10);
// }
module chamfer_mask_x(l=1.0, chamfer=1.0, anchor=CENTER, spin=0) {
chamfer_mask(l=l, chamfer=chamfer, anchor=anchor, spin=spin, orient=RIGHT) children();
module chamfer_mask_x(l=1.0, chamfer=1.0, excess=0.1, anchor=CENTER, spin=0) {
chamfer_mask(l=l, chamfer=chamfer, excess=excess, anchor=anchor, spin=spin, orient=RIGHT) children();
}
// Module: chamfer_mask_y()
// Usage:
// chamfer_mask_y(l, chamfer, [anchor]);
// chamfer_mask_y(l, chamfer, [excess]);
// Description:
// Creates a shape that can be used to chamfer a 90 degree edge along the Y axis.
// Difference it from the object to be chamfered. The center of the mask
// object should align exactly with the edge to be chamfered.
// Arguments:
// l = Height of mask
// chamfer = size of chamfer
// l = Length of mask.
// chamfer = Size of chamfer.
// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1`
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Y axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// Example:
@ -217,21 +221,22 @@ module chamfer_mask_x(l=1.0, chamfer=1.0, anchor=CENTER, spin=0) {
// cube(50, anchor=BOTTOM+RIGHT);
// #chamfer_mask_y(l=50, chamfer=10);
// }
module chamfer_mask_y(l=1.0, chamfer=1.0, anchor=CENTER, spin=0) {
chamfer_mask(l=l, chamfer=chamfer, anchor=anchor, spin=spin, orient=BACK) children();
module chamfer_mask_y(l=1.0, chamfer=1.0, excess=0.1, anchor=CENTER, spin=0) {
chamfer_mask(l=l, chamfer=chamfer, excess=excess, anchor=anchor, spin=spin, orient=BACK) children();
}
// Module: chamfer_mask_z()
// Usage:
// chamfer_mask_z(l, chamfer, [anchor]);
// chamfer_mask_z(l, chamfer, [excess]);
// Description:
// Creates a shape that can be used to chamfer a 90 degree edge along the Z axis.
// Difference it from the object to be chamfered. The center of the mask
// object should align exactly with the edge to be chamfered.
// Arguments:
// l = Height of mask
// chamfer = size of chamfer
// l = Length of mask.
// chamfer = Size of chamfer.
// excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1`
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// Example:
@ -239,8 +244,8 @@ module chamfer_mask_y(l=1.0, chamfer=1.0, anchor=CENTER, spin=0) {
// cube(50, anchor=FRONT+RIGHT);
// #chamfer_mask_z(l=50, chamfer=10);
// }
module chamfer_mask_z(l=1.0, chamfer=1.0, anchor=CENTER, spin=0) {
chamfer_mask(l=l, chamfer=chamfer, anchor=anchor, spin=spin, orient=UP) children();
module chamfer_mask_z(l=1.0, chamfer=1.0, excess=0.1, anchor=CENTER, spin=0) {
chamfer_mask(l=l, chamfer=chamfer, excess=excess, anchor=anchor, spin=spin, orient=UP) children();
}
@ -313,7 +318,7 @@ module chamfer_cylinder_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=fa
// Module: chamfer_hole_mask()
// Usage:
// chamfer_hole_mask(r|d, chamfer, [ang], [from_end]);
// chamfer_hole_mask(r|d, chamfer, [ang], [from_end], [excess]);
// Description:
// Create a mask that can be used to bevel/chamfer the end of a cylindrical hole.
// Difference it from the hole to be chamfered. The center of the mask object
@ -324,7 +329,7 @@ module chamfer_cylinder_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=fa
// chamfer = Size of the chamfer. (Default: 0.25)
// ang = Angle of chamfer in degrees from vertical. (Default: 45)
// from_end = If true, chamfer size is measured from end of hole. If false, chamfer is measured outset from the radius of the hole. (Default: false)
// overage = The extra thickness of the mask. Default: `0.1`.
// excess = The extra thickness of the mask. Default: `0.1`.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
@ -341,8 +346,8 @@ module chamfer_cylinder_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=fa
// up(50) chamfer_hole_mask(d=50, chamfer=10);
// }
// Example:
// chamfer_hole_mask(d=100, chamfer=25, ang=30, overage=10);
module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, overage=0.1, anchor=CENTER, spin=0, orient=UP)
// chamfer_hole_mask(d=100, chamfer=25, ang=30, excess=10);
module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, excess=0.1, anchor=CENTER, spin=0, orient=UP)
{
r = get_radius(r=r, d=d, dflt=1);
h = chamfer * (from_end? 1 : tan(90-ang));
@ -350,7 +355,7 @@ module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false,
$fn = segs(r);
attachable(anchor,spin,orient, r1=r, r2=r2, l=h*2) {
union() {
cylinder(r=r2, h=overage, center=false);
cylinder(r=r2, h=excess, center=false);
down(h) cylinder(r1=r, r2=r2, h=h, center=false);
}
children();
@ -735,14 +740,14 @@ module rounding_corner_mask(r=1.0, anchor=CENTER, spin=0, orient=UP)
// }
module rounding_cylinder_mask(r=1.0, rounding=0.25)
{
cylinder_mask(l=rounding*3, r=r, rounding2=rounding, overage=rounding, ends_only=true, anchor=TOP);
cylinder_mask(l=rounding*3, r=r, rounding2=rounding, excess=rounding, ends_only=true, anchor=TOP);
}
// Module: rounding_hole_mask()
// Usage:
// rounding_hole_mask(r|d, rounding);
// rounding_hole_mask(r|d, rounding, [excess]);
// Description:
// Create a mask that can be used to round the edge of a circular hole.
// Difference it from the hole to be rounded. The center of the
@ -752,7 +757,7 @@ module rounding_cylinder_mask(r=1.0, rounding=0.25)
// r = Radius of hole.
// d = Diameter of hole to rounding.
// rounding = Radius of the rounding. (Default: 0.25)
// overage = The extra thickness of the mask. Default: `0.1`.
// excess = The extra thickness of the mask. Default: `0.1`.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
@ -770,13 +775,13 @@ module rounding_cylinder_mask(r=1.0, rounding=0.25)
// }
// Example:
// rounding_hole_mask(r=40, rounding=20, $fa=2, $fs=2);
module rounding_hole_mask(r=undef, d=undef, rounding=0.25, overage=0.1, anchor=CENTER, spin=0, orient=UP)
module rounding_hole_mask(r=undef, d=undef, rounding=0.25, excess=0.1, anchor=CENTER, spin=0, orient=UP)
{
r = get_radius(r=r, d=d, dflt=1);
attachable(anchor,spin,orient, r=r+rounding, l=2*rounding) {
rotate_extrude(convexity=4) {
difference() {
right(r-overage) fwd(rounding) square(rounding+overage, center=false);
right(r-excess) fwd(rounding) square(rounding+excess, center=false);
right(r+rounding) fwd(rounding) circle(r=rounding);
}
}

386
math.scad
View file

@ -36,7 +36,7 @@ NAN = acos(2); // The value `nan`, useful for comparisons.
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 neither a number nor a list of numbers.");
assert(is_finite(x) || is_vector(x), "Input is not a number nor a list of numbers.");
// Function: log2()
@ -84,7 +84,7 @@ function hypot(x,y,z=0) =
// y = factorial(6); // Returns: 720
// z = factorial(9); // Returns: 362880
function factorial(n,d=0) =
assert(is_int(n) && is_int(d) && n>=0 && d>=0, "Factorial is not defined for negative numbers")
assert(is_int(n) && is_int(d) && n>=0 && d>=0, "Factorial is defined only for non negative integers")
assert(d<=n, "d cannot be larger than n")
product([1,for (i=[n:-1:d+1]) i]);
@ -164,7 +164,7 @@ function binomial_coefficient(n,k) =
function lerp(a,b,u) =
assert(same_shape(a,b), "Bad or inconsistent inputs to lerp")
is_finite(u)? (1-u)*a + u*b :
assert(is_finite(u) || is_vector(u) || valid_range(u), "Input u to lerp must be a number, vector, or range.")
assert(is_finite(u) || is_vector(u) || valid_range(u), "Input u to lerp must be a number, vector, or valid range.")
[for (v = u) (1-v)*a + v*b ];
@ -387,12 +387,13 @@ function modang(x) =
// modrange(90,270,360, step=-45); // Returns: [90,45,0,315,270]
// modrange(270,90,360, step=-45); // Returns: [270,225,180,135,90]
function modrange(x, y, m, step=1) =
assert( is_finite(x+y+step+m) && !approx(m,0), "Input must be finite numbers. The module value cannot be zero.")
assert( is_finite(x+y+step+m) && !approx(m,0), "Input must be finite numbers and the module value cannot be zero." )
let(
a = posmod(x, m),
b = posmod(y, m),
c = step>0? (a>b? b+m : b) : (a<b? b-m : b)
) [for (i=[a:step:c]) (i%m+m)%m];
c = step>0? (a>b? b+m : b)
: (a<b? b-m : b)
) [for (i=[a:step:c]) (i%m+m)%m ];
@ -536,9 +537,13 @@ function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
// cumsum([2,2,2]); // returns [2,4,6]
// cumsum([1,2,3]); // returns [1,3,6]
// cumsum([[1,2,3], [3,4,5], [5,6,7]]); // returns [[1,2,3], [4,6,8], [9,12,15]]
function cumsum(v,_i=0,_acc=[]) =
function cumsum(v) =
assert(is_consistent(v), "The input is not consistent." )
_cumsum(v,_i=0,_acc=[]);
function _cumsum(v,_i=0,_acc=[]) =
_i==len(v) ? _acc :
cumsum(
_cumsum(
v, _i+1,
concat(
_acc,
@ -598,7 +603,7 @@ function deltas(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.
// If passed a list of square matrices, returns a the resulting product matrix.
// If passed a list of square matrices, returns the resulting product matrix.
// Arguments:
// v = The list to get the product of.
// Example:
@ -606,7 +611,7 @@ function deltas(v) =
// product([[1,2,3], [3,4,5], [5,6,7]]); // returns [15, 48, 105]
function product(v) =
assert( is_vector(v) || is_matrix(v) || ( is_matrix(v[0],square=true) && is_consistent(v)),
"Invalid input.")
"Invalid input.")
_product(v, 1, v[0]);
function _product(v, i=0, _tot) =
@ -641,17 +646,6 @@ function mean(v) =
sum(v)/len(v);
// Function: median()
// Usage:
// x = median(v);
// Description:
// Given a list of numbers or vectors, finds the median value or midpoint.
// If passed a list of vectors, returns the vector of the median of each component.
function median(v) =
is_vector(v) ? (min(v)+max(v))/2 :
is_matrix(v) ? [for(ti=transpose(v)) (min(ti)+max(ti))/2 ]
: assert(false , "Invalid input.");
// Function: convolve()
// Usage:
// x = convolve(p,q);
@ -681,7 +675,7 @@ function convolve(p,q) =
// Usage: 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.
// 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
@ -692,7 +686,7 @@ function linear_solve(A,b) =
m = len(A),
n = len(A[0])
)
assert(is_vector(b,m) || is_matrix(b,m),"Incompatible matrix and right hand side")
assert(is_vector(b,m) || is_matrix(b,m),"Invalid right hand side or incompatible with the matrix")
let (
qr = m<n? qr_factor(transpose(A)) : qr_factor(A),
maxdim = max(n,m),
@ -702,9 +696,11 @@ function linear_solve(A,b) =
zeros = [for(i=[0:mindim-1]) if (approx(R[i][i],0)) i]
)
zeros != [] ? [] :
m<n ? Q*back_substitute(R,b,transpose=true) :
back_substitute(R, transpose(Q)*b);
m<n
// avoiding input validation in back_substitute
? let( n = len(R) )
Q*reverse(_back_substitute(transpose(R, reverse=true), reverse(b)))
: _back_substitute(R, transpose(Q)*b);
// Function: matrix_inverse()
// Usage:
@ -719,18 +715,6 @@ function matrix_inverse(A) =
linear_solve(A,ident(len(A)));
// Function: submatrix()
// Usage: submatrix(M, ind1, ind2)
// Description:
// Returns a submatrix with the specified index ranges or index sets.
function submatrix(M,ind1,ind2) =
assert( is_matrix(M), "Input must be a matrix." )
[for(i=ind1)
[for(j=ind2)
assert( ! is_undef(M[i][j]), "Invalid indexing." )
M[i][j] ] ];
// Function: qr_factor()
// Usage: qr = qr_factor(A)
// Description:
@ -743,13 +727,14 @@ function qr_factor(A) =
n = len(A[0])
)
let(
qr =_qr_factor(A, column=0, m = m, n=n, Q=ident(m)),
Rzero = [
for(i=[0:m-1]) [
for(j=[0:n-1])
i>j ? 0 : qr[1][i][j]
qr = _qr_factor(A, Q=ident(m), 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];
function _qr_factor(A,Q, column, m, n) =
@ -760,7 +745,13 @@ function _qr_factor(A,Q, column, m, n) =
u = x - concat([alpha],repeat(0,m-1)),
v = alpha==0 ? u : u / norm(u),
Qc = ident(len(x)) - 2*outer_product(v,v),
Qf = [for(i=[0:m-1]) [for(j=[0:m-1]) i<column || j<column ? (i==j ? 1 : 0) : Qc[i-column][j-column]]]
Qf = [for(i=[0:m-1])
[for(j=[0:m-1])
i<column || j<column
? (i==j ? 1 : 0)
: Qc[i-column][j-column]
]
]
)
_qr_factor(Qf*A, Q*Qf, column+1, m, n);
@ -773,26 +764,25 @@ function _qr_factor(A,Q, column, m, n) =
// You can supply a compatible matrix b and it will produce the solution for every column of b. Note that if you want to
// solve Rx=b1 and Rx=b2 you must set b to transpose([b1,b2]) and then take the transpose of the result. If the matrix
// is singular (e.g. has a zero on the diagonal) then it returns [].
function back_substitute(R, b, x=[],transpose = false) =
function back_substitute(R, b, transpose = false) =
assert(is_matrix(R, square=true))
let(n=len(R))
assert(is_vector(b,n) || is_matrix(b,n),str("R and b are not compatible in back_substitute ",n, len(b)))
!is_vector(b) ? transpose([for(i=[0:len(b[0])-1]) back_substitute(R,subindex(b,i),transpose=transpose)]) :
transpose?
reverse(back_substitute(
[for(i=[0:n-1]) [for(j=[0:n-1]) R[n-1-j][n-1-i]]],
reverse(b), x, false
)) :
len(x) == n ? x :
let(
ind = n - len(x) - 1
)
R[ind][ind] == 0 ? [] :
let(
newvalue =
len(x)==0? b[ind]/R[ind][ind] :
(b[ind]-select(R[ind],ind+1,-1) * x)/R[ind][ind]
) back_substitute(R, b, concat([newvalue],x));
transpose
? reverse(_back_substitute(transpose(R, reverse=true), reverse(b)))
: _back_substitute(R,b);
function _back_substitute(R, b, x=[]) =
let(n=len(R))
len(x) == n ? x
: let(ind = n - len(x) - 1)
R[ind][ind] == 0 ? []
: let(
newvalue = len(x)==0
? b[ind]/R[ind][ind]
: (b[ind]-select(R[ind],ind+1,-1) * x)/R[ind][ind]
)
_back_substitute(R, b, concat([newvalue],x));
// Function: det2()
@ -804,7 +794,7 @@ function back_substitute(R, b, x=[],transpose = false) =
// M = [ [6,-2], [1,8] ];
// det = det2(M); // Returns: 50
function det2(M) =
assert( is_matrix(M,2,2), "Matrix should be 2x2." )
assert( 0*M==[[0,0],[0,0]], "Matrix should be 2x2." )
M[0][0] * M[1][1] - M[0][1]*M[1][0];
@ -817,7 +807,7 @@ function det2(M) =
// M = [ [6,4,-2], [1,-2,8], [1,5,7] ];
// det = det3(M); // Returns: -334
function det3(M) =
assert( is_matrix(M,3,3), "Matrix should be 3x3." )
assert( 0*M==[[0,0,0],[0,0,0],[0,0,0]], "Matrix should 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]);
@ -857,7 +847,7 @@ function determinant(M) =
// 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. Note if you
// 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
@ -866,14 +856,131 @@ function determinant(M) =
// square = set to true to require a square matrix. 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]));
&& ( 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]));
// Section: Comparisons and Logic
// Function: is_zero()
// Usage:
// is_zero(x);
// Description:
// Returns true if the number passed to it is approximately zero, to within `eps`.
// If passed a list, recursively checks if all items in the list are approximately zero.
// Otherwise, returns false.
// Arguments:
// x = The value to check.
// eps = The maximum allowed variance. Default: `EPSILON` (1e-9)
// Example:
// is_zero(0); // Returns: true.
// is_zero(1e-3); // Returns: false.
// is_zero([0,0,0]); // Returns: true.
// is_zero([0,0,1e-3]); // Returns: false.
function is_zero(x, eps=EPSILON) =
is_list(x)? (x != [] && [for (xx=x) if(!is_zero(xx,eps=eps)) 1] == []) :
is_num(x)? approx(x,eps) :
false;
// Function: is_positive()
// Usage:
// is_positive(x);
// Description:
// Returns true if the number passed to it is greater than zero.
// If passed a list, recursively checks if all items in the list are positive.
// Otherwise, returns false.
// Arguments:
// x = The value to check.
// Example:
// is_positive(-2); // Returns: false.
// is_positive(0); // Returns: false.
// is_positive(2); // Returns: true.
// is_positive([0,0,0]); // Returns: false.
// is_positive([0,1,2]); // Returns: false.
// is_positive([3,1,2]); // Returns: true.
// is_positive([3,-1,2]); // Returns: false.
function is_positive(x) =
is_list(x)? (x != [] && [for (xx=x) if(!is_positive(xx)) 1] == []) :
is_num(x)? x>0 :
false;
// Function: is_negative()
// Usage:
// is_negative(x);
// Description:
// Returns true if the number passed to it is less than zero.
// If passed a list, recursively checks if all items in the list are negative.
// Otherwise, returns false.
// Arguments:
// x = The value to check.
// Example:
// is_negative(-2); // Returns: true.
// is_negative(0); // Returns: false.
// is_negative(2); // Returns: false.
// is_negative([0,0,0]); // Returns: false.
// is_negative([0,1,2]); // Returns: false.
// is_negative([3,1,2]); // Returns: false.
// is_negative([3,-1,2]); // Returns: false.
// is_negative([-3,-1,-2]); // Returns: true.
function is_negative(x) =
is_list(x)? (x != [] && [for (xx=x) if(!is_negative(xx)) 1] == []) :
is_num(x)? x<0 :
false;
// Function: is_nonpositive()
// Usage:
// is_nonpositive(x);
// Description:
// Returns true if the number passed to it is less than or equal to zero.
// If passed a list, recursively checks if all items in the list are nonpositive.
// Otherwise, returns false.
// Arguments:
// x = The value to check.
// Example:
// is_nonpositive(-2); // Returns: true.
// is_nonpositive(0); // Returns: true.
// is_nonpositive(2); // Returns: false.
// is_nonpositive([0,0,0]); // Returns: true.
// is_nonpositive([0,1,2]); // Returns: false.
// is_nonpositive([3,1,2]); // Returns: false.
// is_nonpositive([3,-1,2]); // Returns: false.
// is_nonpositive([-3,-1,-2]); // Returns: true.
function is_nonpositive(x) =
is_list(x)? (x != [] && [for (xx=x) if(!is_nonpositive(xx)) 1] == []) :
is_num(x)? x<=0 :
false;
// Function: is_nonnegative()
// Usage:
// is_nonnegative(x);
// Description:
// Returns true if the number passed to it is greater than or equal to zero.
// If passed a list, recursively checks if all items in the list are nonnegative.
// Otherwise, returns false.
// Arguments:
// x = The value to check.
// Example:
// is_nonnegative(-2); // Returns: false.
// is_nonnegative(0); // Returns: true.
// is_nonnegative(2); // Returns: true.
// is_nonnegative([0,0,0]); // Returns: true.
// is_nonnegative([0,1,2]); // Returns: true.
// is_nonnegative([0,-1,-2]); // Returns: false.
// is_nonnegative([3,1,2]); // Returns: true.
// is_nonnegative([3,-1,2]); // Returns: false.
// is_nonnegative([-3,-1,-2]); // Returns: false.
function is_nonnegative(x) =
is_list(x)? (x != [] && [for (xx=x) if(!is_nonnegative(xx)) 1] == []) :
is_num(x)? x>=0 :
false;
// Function: approx()
// Usage:
// approx(a,b,[eps])
@ -959,13 +1066,16 @@ function compare_lists(a, b) =
// any([1,5,true]); // Returns true.
// any([[0,0], [0,0]]); // Returns false.
// any([[0,0], [1,0]]); // Returns true.
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])
);
function any(l) =
assert(is_list(l), "The input is not a list." )
_any(l, i=0, succ=false);
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])
);
// Function: all()
@ -982,12 +1092,15 @@ function any(l, i=0, succ=false) =
// all([[0,0], [1,0]]); // Returns false.
// all([[1,1], [1,1]]); // Returns true.
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]
) ;
assert( is_list(l), "The input is not a list." )
_all(l, i=0, fail=false);
function _all(l, i=0, fail=false) =
(i>=len(l) || fail)? !fail :
_all( l,
i+1,
fail = is_list(l[i]) ? !_all(l[i]) : !l[i]
) ;
// Function: count_true()
@ -1010,16 +1123,6 @@ 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(l, nmax=undef, i=0, cnt=0) =
(i>=len(l) || (nmax!=undef && cnt>=nmax))? cnt :
count_true(
l=l, nmax=nmax, i=i+1, cnt=cnt+(
is_list(l[i])? count_true(l[i], nmax=nmax-cnt) :
(l[i]? 1 : 0)
)
);
function count_true(l, nmax) =
!is_list(l) ? !(!l) ? 1: 0 :
let( c = [for( i = 0,
@ -1120,19 +1223,21 @@ function _deriv_nonuniform(data, h, closed) =
// closed = boolean to indicate if the data set should be wrapped around from the end to the start.
function deriv2(data, h=1, closed=false) =
assert( is_consistent(data) , "Input list is not consistent or not numerical.")
assert( len(data)>=3, "Input list has less than 3 elements.")
assert( is_finite(h), "The sampling `h` must be a number." )
let( L = len(data) )
closed? [
assert( L>=3, "Input list has less than 3 elements.")
closed
? [
for(i=[0:1:L-1])
(data[(i+1)%L]-2*data[i]+data[(L+i-1)%L])/h/h
] :
]
:
let(
first = L<3? undef :
first =
L==3? data[0] - 2*data[1] + data[2] :
L==4? 2*data[0] - 5*data[1] + 4*data[2] - data[3] :
(35*data[0] - 104*data[1] + 114*data[2] - 56*data[3] + 11*data[4])/12,
last = L<3? undef :
last =
L==3? data[L-1] - 2*data[L-2] + data[L-3] :
L==4? -2*data[L-1] + 5*data[L-2] - 4*data[L-3] + data[L-4] :
(35*data[L-1] - 104*data[L-2] + 114*data[L-3] - 56*data[L-4] + 11*data[L-5])/12
@ -1212,34 +1317,13 @@ function C_div(z1,z2) =
// The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]
// where a_n is the z^n coefficient. Polynomial coefficients are real.
// The result is a number if `z` is a number and a complex number otherwise.
// Note: this should probably be recoded to use division by [1,-z], which is more accurate
// and avoids overflow with large coefficients, but requires poly_div to support complex coefficients.
function polynomial(p, z, _k, _zk, _total) =
is_undef(_k)
? assert( is_vector(p), "Input polynomial coefficients must be a vector." )
let(p = _poly_trim(p))
assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
polynomial( p,
z,
len(p)-1,
is_num(z)? 1 : [1,0],
is_num(z) ? 0 : [0,0])
: _k==0
? _total + +_zk*p[0]
: polynomial( p,
z,
_k-1,
is_num(z) ? _zk*z : C_times(_zk,z),
_total+_zk*p[_k]);
function polynomial(p,z,k,total) =
     is_undef(k)
   ?    assert( is_vector(p) , "Input polynomial coefficients must be a vector." )
    is_undef(k)
    ?   assert( is_vector(p) , "Input polynomial coefficients must be a vector." )
        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
        polynomial( _poly_trim(p), z, 0, is_num(z) ? 0 : [0,0])
   : k==len(p) ? total
   : polynomial(p,z,k+1, is_num(z) ? total*z+p[k] : C_times(total,z)+[p[k],0]);
    : k==len(p) ? total
    : polynomial(p,z,k+1, is_num(z) ? total*z+p[k] : C_times(total,z)+[p[k],0]);
// Function: poly_mult()
// Usage:
@ -1248,36 +1332,16 @@ function polynomial(p,z,k,total) =
// 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.
function poly_mult(p,q) =
is_undef(q) ?
assert( is_list(p)
&& []==[for(pi=p) if( !is_vector(pi) && pi!=[]) 0],
"Invalid arguments to poly_mult")
len(p)==2 ? poly_mult(p[0],p[1])
: poly_mult(p[0], poly_mult(select(p,1,-1)))
:
_poly_trim(
[
for(n = [len(p)+len(q)-2:-1:0])
sum( [for(i=[0:1:len(p)-1])
let(j = len(p)+len(q)- 2 - n - i)
if (j>=0 && j<len(q)) p[i]*q[j]
])
]);
function poly_mult(p,q) =
    is_undef(q) ?
       len(p)==2 ? poly_mult(p[0],p[1])
                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
        len(p)==2
? poly_mult(p[0],p[1])
        : poly_mult(p[0], poly_mult(select(p,1,-1)))
    :
    assert( is_vector(p) && is_vector(q),"Invalid arguments to poly_mult")
_poly_trim( [
                  for(n = [len(p)+len(q)-2:-1:0])
                      sum( [for(i=[0:1:len(p)-1])
                           let(j = len(p)+len(q)- 2 - n - i)
                           if (j>=0 && j<len(q)) p[i]*q[j]
                               ])
                   ]);
p*p==0 || q*q==0
? [0]
: _poly_trim(convolve(p,q));
// Function: poly_div()
@ -1288,19 +1352,23 @@ function poly_mult(p,q) =
// a list of two polynomials, [quotient, remainder]. If the division has no remainder then
// the zero polynomial [] is returned for the remainder. Similarly if the quotient is zero
// the returned quotient will be [].
function poly_div(n,d,q) =
is_undef(q)
? assert( is_vector(n) && is_vector(d) , "Invalid polynomials." )
let( d = _poly_trim(d) )
assert( d!=[0] , "Denominator cannot be a zero polynomial." )
poly_div(n,d,q=[])
: len(n)<len(d) ? [q,_poly_trim(n)] :
let(
t = n[0] / d[0],
newq = concat(q,[t]),
newn = [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
)
poly_div(newn,d,newq);
function poly_div(n,d) =
assert( is_vector(n) && is_vector(d) , "Invalid polynomials." )
let( d = _poly_trim(d),
n = _poly_trim(n) )
assert( d!=[0] , "Denominator cannot be a zero polynomial." )
n==[0]
? [[0],[0]]
: _poly_div(n,d,q=[]);
function _poly_div(n,d,q) =
len(n)<len(d) ? [q,_poly_trim(n)] :
let(
t = n[0] / d[0],
newq = concat(q,[t]),
newn = [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
)
_poly_div(newn,d,newq);
// Internal Function: _poly_trim()

View file

@ -31,9 +31,13 @@ _partition_cutpaths = [
function _partition_cutpath(l, h, cutsize, cutpath, gap) =
let(
check = assert(is_finite(l))
assert(is_finite(h))
assert(is_finite(gap))
assert(is_finite(cutsize) || is_vector(cutsize,2))
assert(is_string(cutpath) || is_path(cutpath,2)),
cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize],
cutpath = is_path(cutpath)? cutpath : (
assert(is_string(cutpath), "cutpath must be a 2D path or a string.")
let(idx = search([cutpath], _partition_cutpaths))
idx==[[]]? assert(in_list(cutpath,_partition_cutpaths,idx=0)) :
_partition_cutpaths[idx.x][1]
@ -79,7 +83,7 @@ function _partition_cutpath(l, h, cutsize, cutpath, gap) =
// partition_mask(w=20, cutpath="jigsaw");
module partition_mask(l=100, w=100, h=100, cutsize=10, cutpath=undef, gap=0, inverse=false, spin=0, orient=UP)
{
cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize];
cutsize = is_vector(cutsize)? point2d(cutsize) : [cutsize*2, cutsize];
path = _partition_cutpath(l, h, cutsize, cutpath, gap);
fullpath = concat(path, [[l/2,w*(inverse?-1:1)], [-l/2,w*(inverse?-1:1)]]);
rot(from=UP,to=orient) {

View file

@ -43,10 +43,12 @@ include <triangulation.scad>
// dim = list of allowed dimensions of the vectors in the path. Default: [2,3]
// fast = set to true for fast check that only looks at first entry. Default: false
function is_path(list, dim=[2,3], fast=false) =
fast? is_list(list) && is_vector(list[0]) :
is_list(list) && is_list(list[0]) && len(list)>1 &&
(is_undef(dim) || in_list(len(list[0]), force_list(dim))) &&
is_list_of(list, repeat(0,len(list[0])));
fast
? is_list(list) && is_vector(list[0])
: is_matrix(list)
&& len(list)>1
&& len(list[0])>0
&& (is_undef(dim) || in_list(len(list[0]), force_list(dim)));
// Function: is_closed_path()
@ -105,32 +107,51 @@ function path_subselect(path, s1, u1, s2, u2, closed=false) =
// Function: simplify_path()
// Description:
// Takes a path and removes unnecessary collinear points.
// Takes a path and removes unnecessary subsequent collinear points.
// Usage:
// simplify_path(path, [eps])
// Arguments:
// path = A list of 2D path points.
// path = A list of path points of any dimension.
// eps = Largest positional variance allowed. Default: `EPSILON` (1-e9)
function simplify_path(path, eps=EPSILON) =
len(path)<=2? path : let(
indices = concat([0], [for (i=[1:1:len(path)-2]) if (!collinear_indexed(path, i-1, i, i+1, eps=eps)) i], [len(path)-1])
) [for (i = indices) path[i]];
assert( is_path(path), "Invalid path." )
assert( is_undef(eps) || (is_finite(eps) && (eps>=0) ), "Invalid tolerance." )
len(path)<=2 ? path
: let(
indices = [ 0,
for (i=[1:1:len(path)-2])
if (!collinear(path[i-1],path[i],path[i+1], eps=eps)) i,
len(path)-1
]
)
[for (i = indices) path[i] ];
// Function: simplify_path_indexed()
// Description:
// Takes a list of points, and a path as a list of indices into `points`,
// and removes all path points that are unecessarily collinear.
// Takes a list of points, and a list of indices into `points`,
// and removes from the list all indices of subsequent indexed points that are unecessarily collinear.
// Returns the list of the remained indices.
// Usage:
// simplify_path_indexed(path, eps)
// simplify_path_indexed(points,indices, eps)
// Arguments:
// points = A list of points.
// path = A list of indices into `points` that forms a path.
// indices = A list of indices into `points` that forms a path.
// eps = Largest angle variance allowed. Default: EPSILON (1-e9) degrees.
function simplify_path_indexed(points, path, eps=EPSILON) =
len(path)<=2? path : let(
indices = concat([0], [for (i=[1:1:len(path)-2]) if (!collinear_indexed(points, path[i-1], path[i], path[i+1], eps=eps)) i], [len(path)-1])
) [for (i = indices) path[i]];
function simplify_path_indexed(points, indices, eps=EPSILON) =
len(indices)<=2? indices
: let(
indices = concat( indices[0],
[for (i=[1:1:len(indices)-2])
let(
i1 = indices[i-1],
i2 = indices[i],
i3 = indices[i+1]
)
if (!collinear(points[i1],points[i2],points[i3], eps=eps)) indices[i]],
indices[len(indices)-1] )
)
indices;
// Function: path_length()

View file

@ -710,7 +710,7 @@ function regular_polyhedron_info(
info == "center" ? translation :
info == "type" ? entry[class] :
info == "name" ? entry[pname] :
echo_warning(str("Unknown info type '",info,"' requested"));
assert(false, str("Unknown info type '",info,"' requested"));

View file

@ -339,7 +339,7 @@ function region_faces(region, transform, reverse=false, vnf=EMPTY_VNF) =
// linear_sweep(orgn,height=20,convexity=16) show_anchors();
module linear_sweep(region, height=1, center, twist=0, scale=1, slices, maxseg, style="default", convexity, anchor_isect=false, anchor, spin=0, orient=UP) {
region = is_path(region)? [region] : region;
cp = median(flatten(region));
cp = mean(pointlist_bounds(flatten(region)));
anchor = get_anchor(anchor, center, "origin", "origin");
vnf = linear_sweep(
region, height=height,

View file

@ -458,10 +458,10 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
// Module: offset_sweep()
// Function&Module: offset_sweep()
//
// Description:
// Takes a 2d path as input and extrudes it upwards and/or downward. Each layer in the extrusion is produced using `offset()` to expand or shrink the previous layer.
// Takes a 2d path as input and extrudes it upwards and/or downward. Each layer in the extrusion is produced using `offset()` to expand or shrink the previous layer. When invoked as a function returns a VNF; when invoked as a module produces geometry.
// You can specify a sequence of offsets values, or you can use several built-in offset profiles that are designed to provide end treatments such as roundovers.
// The path is shifted by `offset()` multiple times in sequence
// to produce the final shape (not multiple shifts from one parent), so coarse definition of the input path will degrade
@ -543,8 +543,12 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
// angle = default angle for chamfers. Default: 45
// joint = default joint value for smooth roundover.
// k = default curvature parameter value for "smooth" roundover
// convexity = convexity setting for use with polyhedron. Default: 10
//
// convexity = convexity setting for use with polyhedron. (module only) Default: 10
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0
// orient = Vector to rotate top towards after spin (module only)
// extent = use extent method for computing anchors. (module only) Default: false
// cp = set centerpoint for anchor computation. (module only) Default: object centroid
// Example: Rounding a star shaped prism with postive radius values
// star = star(5, r=22, ir=13);
// rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24);
@ -650,118 +654,118 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
// up(1)
// offset_sweep(offset(rhex,r=-1), height=9.5, bottom=os_circle(r=2), top=os_teardrop(r=-4));
// }
module offset_sweep(
path, height, h, l,
top=[], bottom=[],
offset="round", r=0, steps=16,
quality=1, check_valid=true,
offset_maxstep=1, extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45,
convexity=10
) {
// This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce
// the inputs for the polyhedron module.
function make_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, maxstep, offsetind=0, vertexcount=0, vertices=[], faces=[] )=
offsetind==len(offsets)? (
let(
bottom = list_range(n=len(path),s=vertexcount),
oriented_bottom = !flip_faces? bottom : reverse(bottom)
) [vertices, concat(faces,[oriented_bottom])]
) : (
let(
this_offset = offsetind==0? offsets[0][0] : offsets[offsetind][0] - offsets[offsetind-1][0],
delta = offset_type=="delta" || offset_type=="chamfer" ? this_offset : undef,
r = offset_type=="round"? this_offset : undef,
do_chamfer = offset_type == "chamfer"
// This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce
// the inputs for the polyhedron module.
function _make_offset_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, maxstep, offsetind=0,
vertexcount=0, vertices=[], faces=[] )=
offsetind==len(offsets)? (
let(
bottom = list_range(n=len(path),s=vertexcount),
oriented_bottom = !flip_faces? bottom : reverse(bottom)
) [vertices, concat(faces,[oriented_bottom])]
) : (
let(
this_offset = offsetind==0? offsets[0][0] : offsets[offsetind][0] - offsets[offsetind-1][0],
delta = offset_type=="delta" || offset_type=="chamfer" ? this_offset : undef,
r = offset_type=="round"? this_offset : undef,
do_chamfer = offset_type == "chamfer"
)
let(
vertices_faces = offset(
path, r=r, delta=delta, chamfer = do_chamfer, closed=true,
check_valid=check_valid, quality=quality,
maxstep=maxstep, return_faces=true,
firstface_index=vertexcount,
flip_faces=flip_faces
)
assert(num_defined([r,delta])==1,"Must set `offset` to \"round\" or \"delta")
let(
vertices_faces = offset(
path, r=r, delta=delta, chamfer = do_chamfer, closed=true,
check_valid=check_valid, quality=quality,
maxstep=maxstep, return_faces=true,
firstface_index=vertexcount,
flip_faces=flip_faces
)
)
make_polyhedron(
vertices_faces[0], offsets, offset_type,
flip_faces, quality, check_valid, maxstep,
offsetind+1, vertexcount+len(path),
vertices=concat(
vertices,
zip(vertices_faces[0],repeat(offsets[offsetind][1],len(vertices_faces[0])))
),
faces=concat(faces, vertices_faces[1])
)
);
argspec = [
["r",r],
["extra",extra],
["type","circle"],
["check_valid",check_valid],
["quality",quality],
["offset_maxstep", offset_maxstep],
["steps",steps],
["offset",offset],
["chamfer_width",chamfer_width],
["chamfer_height",chamfer_height],
["angle",angle],
["cut",cut],
["joint",joint],
["k", k],
["points", []],
];
path = check_and_fix_path(path, [2], closed=true);
clockwise = polygon_is_clockwise(path);
top = struct_set(argspec, top, grow=false);
bottom = struct_set(argspec, bottom, grow=false);
// This code does not work. It hits the error in make_polyhedron from offset being wrong
// before this code executes. Had to move the test into make_polyhedron, which is ugly since it's in the loop
//offsetsok = in_list(struct_val(top, "offset"),["round","delta"]) &&
// in_list(struct_val(bottom, "offset"),["round","delta"]);
//assert(offsetsok,"Offsets must be one of \"round\" or \"delta\"");
offsets_bot = _rounding_offsets(bottom, -1);
offsets_top = _rounding_offsets(top, 1);
if (offset == "chamfer" && (len(offsets_bot)>5 || len(offsets_top)>5)) {
echo("WARNING: You have selected offset=\"chamfer\", which leads to exponential growth in the vertex count and requested many layers. This can be slow or run out of recursion depth.");
}
// "Extra" height enlarges the result beyond the requested height, so subtract it
bottom_height = len(offsets_bot)==0 ? 0 : abs(select(offsets_bot,-1)[1]) - struct_val(bottom,"extra");
top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra");
height = get_height(l=l,h=h,height=height,dflt=bottom_height+top_height);
assert(height>=0, "Height must be nonnegative");
middle = height-bottom_height-top_height;
assert(
middle>=0, str(
"Specified end treatments (bottom height = ",bottom_height,
" top_height = ",top_height,") are too large for extrusion height (",height,")"
)
_make_offset_polyhedron(
vertices_faces[0], offsets, offset_type,
flip_faces, quality, check_valid, maxstep,
offsetind+1, vertexcount+len(path),
vertices=concat(
vertices,
zip(vertices_faces[0],repeat(offsets[offsetind][1],len(vertices_faces[0])))
),
faces=concat(faces, vertices_faces[1])
)
);
initial_vertices_bot = path3d(path);
vertices_faces_bot = make_polyhedron(
function offset_sweep(
path, height, h, l,
top=[], bottom=[],
offset="round", r=0, steps=16,
quality=1, check_valid=true,
offset_maxstep=1, extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45
) =
let(
argspec = [
["r",r],
["extra",extra],
["type","circle"],
["check_valid",check_valid],
["quality",quality],
["offset_maxstep", offset_maxstep],
["steps",steps],
["offset",offset],
["chamfer_width",chamfer_width],
["chamfer_height",chamfer_height],
["angle",angle],
["cut",cut],
["joint",joint],
["k", k],
["points", []],
],
path = check_and_fix_path(path, [2], closed=true),
clockwise = polygon_is_clockwise(path),
top = struct_set(argspec, top, grow=false),
bottom = struct_set(argspec, bottom, grow=false),
// This code does not work. It hits the error in _make_offset_polyhedron from offset being wrong
// before this code executes. Had to move the test into _make_offset_polyhedron, which is ugly since it's in the loop
offsetsok = in_list(struct_val(top, "offset"),["round","delta"])
&& in_list(struct_val(bottom, "offset"),["round","delta"])
)
assert(offsetsok,"Offsets must be one of \"round\" or \"delta\"")
let(
offsets_bot = _rounding_offsets(bottom, -1),
offsets_top = _rounding_offsets(top, 1),
dummy = offset == "chamfer" && (len(offsets_bot)>5 || len(offsets_top)>5)
? echo("WARNING: You have selected offset=\"chamfer\", which leads to exponential growth in the vertex count and requested more than 5 layers. This can be slow or run out of recursion depth.")
: 0,
// "Extra" height enlarges the result beyond the requested height, so subtract it
bottom_height = len(offsets_bot)==0 ? 0 : abs(select(offsets_bot,-1)[1]) - struct_val(bottom,"extra"),
top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra"),
height = get_height(l=l,h=h,height=height,dflt=bottom_height+top_height),
middle = height-bottom_height-top_height
)
assert(height>=0, "Height must be nonnegative")
assert(middle>=0, str("Specified end treatments (bottom height = ",bottom_height,
" top_height = ",top_height,") are too large for extrusion height (",height,")"
)
)
let(
initial_vertices_bot = path3d(path),
vertices_faces_bot = _make_offset_polyhedron(
path, offsets_bot, struct_val(bottom,"offset"), clockwise,
struct_val(bottom,"quality"),
struct_val(bottom,"check_valid"),
struct_val(bottom,"offset_maxstep"),
vertices=initial_vertices_bot
);
),
top_start_ind = len(vertices_faces_bot[0]);
initial_vertices_top = zip(path, repeat(middle,len(path)));
vertices_faces_top = make_polyhedron(
top_start_ind = len(vertices_faces_bot[0]),
initial_vertices_top = zip(path, repeat(middle,len(path))),
vertices_faces_top = _make_offset_polyhedron(
path, move(p=offsets_top,[0,middle]),
struct_val(top,"offset"), !clockwise,
struct_val(top,"quality"),
@ -769,20 +773,39 @@ module offset_sweep(
struct_val(top,"offset_maxstep"),
vertexcount=top_start_ind,
vertices=initial_vertices_top
);
),
middle_faces = middle==0 ? [] : [
for(i=[0:len(path)-1]) let(
oneface=[i, (i+1)%len(path), top_start_ind+(i+1)%len(path), top_start_ind+i]
) !clockwise ? reverse(oneface) : oneface
];
up(bottom_height) {
polyhedron(
concat(vertices_faces_bot[0],vertices_faces_top[0]),
faces=concat(vertices_faces_bot[1], vertices_faces_top[1], middle_faces),
convexity=convexity
);
}
}
]
)
[up(bottom_height, concat(vertices_faces_bot[0],vertices_faces_top[0])), // Vertices
concat(vertices_faces_bot[1], vertices_faces_top[1], middle_faces)]; // Faces
module offset_sweep(path, height, h, l,
top=[], bottom=[],
offset="round", r=0, steps=16,
quality=1, check_valid=true,
offset_maxstep=1, extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45,
convexity=10,anchor="origin",cp,
spin=0, orient=UP, extent=false)
{
vnf = offset_sweep(path=path, height=height, h=h, l=l, top=top, bottom=bottom, offset=offset, r=0, steps=steps,
quality=quality, check_valid=true, offset_maxstep=1, extra=0, cut=cut, chamfer_width=chamfer_width,
chamfer_height=chamfer_height, joint=joint, k=k, angle=angle);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
vnf_polyhedron(vnf,convexity=convexity);
children();
}
}
function os_circle(r,cut,extra,check_valid, quality,steps, offset_maxstep, offset) =
assert(num_defined([r,cut])==1, "Must define exactly one of `r` and `cut`")
@ -924,7 +947,6 @@ function os_profile(points, extra,check_valid, quality, offset_maxstep, offset)
// joint = default joint value for smooth roundover.
// k = default curvature parameter value for "smooth" roundover
// convexity = convexity setting for use with polyhedron. Default: 10
//
// Example: Chamfered elliptical prism. If you stretch a chamfered cylinder the chamfer will be uneven.
// convex_offset_extrude(bottom = os_chamfer(height=-2), top=os_chamfer(height=1), height=7)
// xscale(4)circle(r=6,$fn=64);
@ -1364,8 +1386,6 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true,
}
}
function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
let(
N = len(top),
@ -1395,8 +1415,8 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
let(
prev_corner = prev_offset + abs(rtop_in)*in_prev,
next_corner = next_offset + abs(rtop_in)*in_next,
prev_degenerate = is_undef(ray_intersection([far_corner, far_corner+prev], [prev_offset, prev_offset+in_prev])),
next_degenerate = is_undef(ray_intersection([far_corner, far_corner+next], [next_offset, next_offset+in_next]))
prev_degenerate = is_undef(ray_intersection(path2d([far_corner, far_corner+prev]), path2d([prev_offset, prev_offset+in_prev]))),
next_degenerate = is_undef(ray_intersection(path2d([far_corner, far_corner+next]), path2d([next_offset, next_offset+in_next])))
)
[ prev_degenerate ? far_corner : prev_corner,
far_corner,
@ -1452,6 +1472,11 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
// splinesteps = number of segments to use for curved patches. Default: 16
// debug = turn on debug mode which displays illegal polyhedra and shows the bezier corner patches for troubleshooting purposes. Default: False
// convexity = convexity parameter for polyhedron(), only for module version. Default: 10
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0
// orient = Vector to rotate top towards after spin (module only)
// extent = use extent method for computing anchors. (module only) Default: false
// cp = set centerpoint for anchor computation. (module only) Default: object centroid
// Example: Uniformly rounded pentagonal prism
// rounded_prism(pentagon(3), height=3, joint_top=0.5, joint_bot=0.5, joint_sides=0.5);
// Example: Maximum possible rounding.
@ -1500,15 +1525,21 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
// rounded_prism(apply(yrot(95),path3d(hexagon(3))), apply(yrot(95), path3d(hexagon(3),3)), joint_top=2, joint_bot=1, joint_sides=1);
module rounded_prism(bottom, top, joint_bot, joint_top, joint_sides, k_bot, k_top, k_sides,
k=0.5, splinesteps=16, h, length, l, height, convexity=10, debug=false)
k=0.5, splinesteps=16, h, length, l, height, convexity=10, debug=false,
anchor="origin",cp,spin=0, orient=UP, extent=false)
{
result = rounded_prism(bottom=bottom, top=top, joint_bot=joint_bot, joint_top=joint_top, joint_sides=joint_sides,
k_bot=k_bot, k_top=k_top, k_sides=k_sides, k=k, splinesteps=splinesteps, h=h, length=length, height=height, l=l,debug=debug);
if (debug){
vnf_polyhedron(result[1], convexity=convexity);
trace_bezier_patches(result[0], showcps=true, splinesteps=splinesteps, $fn=16, showdots=false, showpatch=false);
vnf = debug ? result[1] : result;
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
if (debug){
vnf_polyhedron(vnf, convexity=convexity);
trace_bezier_patches(result[0], showcps=true, splinesteps=splinesteps, $fn=16, showdots=false, showpatch=false);
}
else vnf_polyhedron(vnf,convexity=convexity);
children();
}
else vnf_polyhedron(result,convexity=convexity);
}
@ -1532,7 +1563,7 @@ function rounded_prism(bottom, top, joint_bot, joint_top, joint_sides, k_bot, k_
// Determine which points are concave by making bottom 2d if necessary
bot_proj = len(bottom[0])==2 ? bottom : project_plane(bottom, select(bottom,0,2)),
bottom_sign = polygon_is_clockwise(bot_proj) ? 1 : -1,
concave = [for(i=[0:N-1]) bottom_sign*sign(point_left_of_segment2d(select(bot_proj,i+1), select(bot_proj, i-1,i)))>0],
concave = [for(i=[0:N-1]) bottom_sign*sign(point_left_of_line2d(select(bot_proj,i+1), select(bot_proj, i-1,i)))>0],
top = is_undef(top) ? path3d(bottom,height/2) :
len(top[0])==2 ? path3d(top,height/2) :
top,
@ -1547,16 +1578,16 @@ function rounded_prism(bottom, top, joint_bot, joint_top, joint_sides, k_bot, k_
assert(jsvecok || jssingleok,
str("Argument joint_sides is invalid. All entries must be nonnegative, and it must be a number, 2-vector, or a length ",N," list those."))
assert(is_num(k_sides) || is_vector(k_sides,N), str("Curvature parameter k_sides must be a number or length ",N," vector"))
assert(points_are_coplanar(bottom))
assert(points_are_coplanar(top))
assert(coplanar(bottom))
assert(coplanar(top))
assert(!is_num(k_sides) || (k_sides>=0 && k_sides<=1), "Curvature parameter k_sides must be in interval [0,1]")
let(
non_coplanar=[for(i=[0:N-1]) if (!points_are_coplanar(concat(select(top,i,i+1), select(bottom,i,i+1)))) [i,(i+1)%N]],
non_coplanar=[for(i=[0:N-1]) if (!coplanar(concat(select(top,i,i+1), select(bottom,i,i+1)))) [i,(i+1)%N]],
k_sides_vec = is_num(k_sides) ? repeat(k_sides, N) : k_sides,
kbad = [for(i=[0:N-1]) if (k_sides_vec[i]<0 || k_sides_vec[i]>1) i],
joint_sides_vec = jssingleok ? repeat(joint_sides,N) : joint_sides,
top_collinear = [for(i=[0:N-1]) if (points_are_collinear(select(top,i-1,i+1))) i],
bot_collinear = [for(i=[0:N-1]) if (points_are_collinear(select(bottom,i-1,i+1))) i]
top_collinear = [for(i=[0:N-1]) if (collinear(select(top,i-1,i+1))) i],
bot_collinear = [for(i=[0:N-1]) if (collinear(select(bottom,i-1,i+1))) i]
)
assert(non_coplanar==[], str("Side faces are non-coplanar at edges: ",non_coplanar))
assert(top_collinear==[], str("Top has collinear or duplicated points at indices: ",top_collinear))
@ -1622,14 +1653,14 @@ function rounded_prism(bottom, top, joint_bot, joint_top, joint_sides, k_bot, k_
vline = concat(select(subindex(top_patch[i],j),2,4),
select(subindex(bot_patch[i],j),2,4))
)
if (!points_are_collinear(vline)) [i,j]],
if (!collinear(vline)) [i,j]],
//verify horiz edges
verify_horiz=[for(i=[0:N-1], j=[0:4])
let(
hline_top = concat(select(top_patch[i][j],2,4), select(select(top_patch, i+1)[j],0,2)),
hline_bot = concat(select(bot_patch[i][j],2,4), select(select(bot_patch, i+1)[j],0,2))
)
if (!points_are_collinear(hline_top) || !points_are_collinear(hline_bot)) [i,j]]
if (!collinear(hline_top) || !collinear(hline_bot)) [i,j]]
)
assert(debug || top_intersections==[],
"Roundovers interfere with each other on top face: either input is self intersecting or top joint length is too large")
@ -1880,7 +1911,7 @@ function _circle_mask(r) =
// $fn=128;
// difference(){
// tube(or=r, wall=2, h=45);
// bent_cutout_mask(r-1, 2.1, apply(back(15),subdivide_path(round_corners(star(n=7,ir=5,or=10), cut=flatten(repeat([0.5,0],7))),14*15,closed=true)));
// bent_cutout_mask(r-1, 2.1, apply(back(15),subdivide_path(round_corners(star(n=7,ir=5,or=10), cut=flatten(repeat([0.5,0],7)),$fn=32),14*15,closed=true)));
// }
// }
// Example(2D): Cutting a slot in a cylinder is tricky if you want rounded corners at the top. This slot profile has slightly angled top edges to blend into the top edge of the cylinder.
@ -1944,6 +1975,7 @@ function _circle_mask(r) =
module bent_cutout_mask(r, thickness, path, convexity=10)
{
no_children($children);
assert(is_path(path,2),"Input path must be a 2d path")
assert(r-thickness>0, "Thickness too large for radius");
assert(thickness>0, "Thickness must be positive");
@ -1962,4 +1994,4 @@ module bent_cutout_mask(r, thickness, path, convexity=10)
}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -19,7 +19,7 @@ done
if [[ "$FILES" != "" ]]; then
PREVIEW_LIBS="$FILES"
else
PREVIEW_LIBS="affine arrays attachments beziers bottlecaps common constants coords cubetruss debug distributors edges errors geometry hingesnaps hull involute_gears joiners knurling linear_bearings masks math metric_screws mutators nema_steppers partitions paths phillips_drive polyhedra primitives quaternions queues regions rounding screws shapes shapes2d skin sliders stacks strings structs threading torx_drive transforms triangulation vectors version vnf walls wiring"
PREVIEW_LIBS="affine arrays attachments beziers bottlecaps common constants coords cubetruss debug distributors edges geometry hingesnaps hull involute_gears joiners knurling linear_bearings masks math metric_screws mutators nema_steppers partitions paths phillips_drive polyhedra primitives quaternions queues regions rounding screws shapes shapes2d skin sliders stacks strings structs threading torx_drive transforms triangulation vectors version vnf walls wiring"
fi
dir="$(basename $PWD)"

View file

@ -102,8 +102,8 @@ module cuboid(
if (edges == EDGES_ALL && trimcorners) {
if (chamfer<0) {
cube(size, center=true) {
attach(TOP) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
attach(BOT) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
attach(TOP,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
attach(BOT,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
}
} else {
isize = [for (v = size) max(0.001, v-2*chamfer)];

View file

@ -785,7 +785,7 @@ function rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) =
assert(is_num(rounding) || len(rounding)==4)
let(
size = is_num(size)? [size,size] : point2d(size),
anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT),
anchor = point2d(get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT)),
complex = rounding!=0 || chamfer!=0
)
(rounding==0 && chamfer==0)? let(

View file

@ -16,7 +16,8 @@ include <vnf.scad>
// Function&Module: skin()
// Usage: As module:
// skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z]);
// skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z], [convexity],
// [anchor],[cp],[spin],[orient],[extent]);
// Usage: As function:
// vnf = skin(profiles, [slices], [refine], [method], [sampling], [caps], [closed], [z]);
// Description:
@ -117,6 +118,12 @@ include <vnf.scad>
// caps = true to create endcap faces when closed is false. Can be a length 2 boolean array. Default is true if closed is false.
// method = method for connecting profiles, one of "distance", "tangent", "direct" or "reindex". Default: "direct".
// z = array of height values for each profile if the profiles are 2d
// convexity = convexity setting for use with polyhedron. (module only) Default: 10
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0
// orient = Vector to rotate top towards after spin (module only)
// extent = use extent method for computing anchors. (module only) Default: false
// cp = set centerpoint for anchor computation. (module only) Default: object centroid
// Example:
// skin([octagon(4), circle($fn=70,r=2)], z=[0,3], slices=10);
// Example: Rotating the pentagon place the zero index at different locations, giving a twist
@ -315,11 +322,15 @@ include <vnf.scad>
// stroke(zrot(30, p=yscale(0.5, p=circle(d=120))),width=10,closed=true);
// }
// }
module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, convexity=10)
module skin(profiles, slices, refine=1, method="direct", sampling, caps, closed=false, z, convexity=10,
anchor="origin",cp,spin=0, orient=UP, extent=false)
{
vnf_polyhedron(skin(profiles, slices, refine, method, sampling, caps, closed, z), convexity=convexity);
vnf = skin(profiles, slices, refine, method, sampling, caps, closed, z);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
vnf_polyhedron(vnf,convexity=convexity);
children();
}
}
@ -803,6 +814,12 @@ function associate_vertices(polygons, split, curpoly=0) =
// transformations = list of 4x4 matrices to apply
// closed = set to true to form a closed (torus) model. Default: false
// caps = true to create endcap faces when closed is false. Can be a singe boolean to specify endcaps at both ends, or a length 2 boolean array. Default is true if closed is false.
// convexity = convexity setting for use with polyhedron. (module only) Default: 10
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0
// orient = Vector to rotate top towards after spin (module only)
// extent = use extent method for computing anchors. (module only) Default: false
// cp = set centerpoint for anchor computation. (module only) Default: object centroid
// Example: This is the "sweep-drop" example from list-comprehension-demos.
// function drop(t) = 100 * 0.5 * (1 - cos(180 * t)) * sin(180 * t) + 1;
// function path(t) = [0, 0, 80 + 80 * cos(180 * t)];
@ -839,9 +856,16 @@ function sweep(shape, transformations, closed=false, caps) =
assert(!closed || !caps, "Cannot make closed shape with caps")
_skin_core([for(i=[0:len(transformations)-(closed?0:1)]) apply(transformations[i%len(transformations)],path3d(shape))],caps=fullcaps);
module sweep(shape, transformations, closed=false, caps, convexity=10) {
vnf_polyhedron(sweep(shape, transformations, closed, caps), convexity=convexity);
}
module sweep(shape, transformations, closed=false, caps, convexity=10,
anchor="origin",cp,spin=0, orient=UP, extent=false)
{
vnf = sweep(shape, transformations, closed, caps);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
vnf_polyhedron(vnf,convexity=convexity);
children();
}
}
// Function&Module: path_sweep()
@ -906,8 +930,13 @@ module sweep(shape, transformations, closed=false, caps, convexity=10) {
// tangent = a list of tangent vectors in case you need more accuracy (particularly at the end points of your curve)
// relaxed = set to true with the "manual" method to relax the orthogonality requirement of cross sections to the path tangent. Default: false
// caps = Can be a boolean or vector of two booleans. Set to false to disable caps at the two ends. Default: true
// transforms = set to true to return transforms instead of a VNF. These transforms can be manipulated and passed to sweep(). Default: false.
// convexity = convexity parameter for polyhedron(). Only accepted by the module version. Default: 10
// transforms = set to true to return transforms instead of a VNF. These transforms can be manipulated and passed to sweep(). Default: false.
// anchor = Translate so anchor point is at the origin. (module only) Default: "origin"
// spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0
// orient = Vector to rotate top towards after spin (module only)
// extent = use extent method for computing anchors. (module only) Default: false
// cp = set centerpoint for anchor computation. (module only) Default: object centroid
//
// Example(2D): We'll use this shape in several examples
// ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]];
@ -1121,13 +1150,19 @@ module sweep(shape, transformations, closed=false, caps, convexity=10) {
// outside = [for(i=[0:len(trans)-1]) trans[i]*scale(lerp(1,1.5,i/(len(trans)-1)))];
// inside = [for(i=[len(trans)-1:-1:0]) trans[i]*scale(lerp(1.1,1.4,i/(len(trans)-1)))];
// sweep(shape, concat(outside,inside),closed=true);
module path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
symmetry=1, last_normal, tangent, relaxed=false, caps, convexity=10)
symmetry=1, last_normal, tangent, relaxed=false, caps, convexity=10,
anchor="origin",cp,spin=0, orient=UP, extent=false)
{
vnf_polyhedron(path_sweep(shape, path, method, normal, closed, twist, twist_by_length,
symmetry, last_normal, tangent, relaxed, caps), convexity=convexity);
}
vnf = path_sweep(shape, path, method, normal, closed, twist, twist_by_length,
symmetry, last_normal, tangent, relaxed, caps);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
{
vnf_polyhedron(vnf,convexity=convexity);
children();
}
}
function path_sweep(shape, path, method="incremental", normal, closed=false, twist=0, twist_by_length=true,
symmetry=1, last_normal, tangent, relaxed=false, caps, transforms=false) =
@ -1202,7 +1237,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
let (pathnormal = path_normals(path, tangents, closed))
assert(all_defined(pathnormal),"Natural normal vanishes on your curve, select a different method")
let( testnormals = [for(i=[0:len(pathnormal)-1-(closed?1:2)]) pathnormal[i]*select(pathnormal,i+2)],
dummy = min(testnormals) < .5 ? echo_warning("abrupt change in normal direction. Consider a different method") :0
dummy = min(testnormals) < .5 ? echo("WARNING: ***** Abrupt change in normal direction. Consider a different method *****") :0
)
[for(i=[0:L-(closed?0:1)]) let(
rotation = affine_frame_map(x=pathnormal[i%L], z=tangents[i%L])
@ -1216,7 +1251,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
end = reindex_polygon(start, apply(transform_list[L],path3d(shape)))
)
all([for(i=idx(start)) approx(start[i],end[i])]),
dummy = ends_match ? 0 :echo_warning("The points do not match when closing the model")
dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model *****")
)
transforms ? transform_list : sweep(shape, transform_list, closed=false, caps=fullcaps);

View file

@ -14,7 +14,6 @@ include <version.scad>
include <constants.scad>
include <edges.scad>
include <common.scad>
include <errors.scad>
include <arrays.scad>
include <strings.scad>
include <vnf.scad>

View file

@ -3,14 +3,6 @@ include <../std.scad>
// Section: List Query Operations
module test_is_simple_list() {
assert(is_simple_list([1,2,3,4]));
assert(is_simple_list([]));
assert(!is_simple_list([1,2,[3,4]]));
}
test_is_simple_list();
module test_select() {
l = [3,4,5,6,7,8,9];
assert(select(l, 5, 6) == [8,9]);
@ -358,12 +350,34 @@ test_add_scalar();
module test_subindex() {
v = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
assert(subindex(v,2) == [3, 7, 11, 15]);
assert(subindex(v,[2]) == [[3], [7], [11], [15]]);
assert(subindex(v,[2,1]) == [[3, 2], [7, 6], [11, 10], [15, 14]]);
assert(subindex(v,[1:3]) == [[2, 3, 4], [6, 7, 8], [10, 11, 12], [14, 15, 16]]);
}
test_subindex();
// Need decision about behavior for out of bounds ranges, empty ranges
module test_submatrix(){
M = [[1,2,3,4,5],
[6,7,8,9,10],
[11,12,13,14,15],
[16,17,18,19,20],
[21,22,23,24,25]];
assert_equal(submatrix(M,[1:2], [3:4]), [[9,10],[14,15]]);
assert_equal(submatrix(M,[1], [3,4]), [[9,10]]);
assert_equal(submatrix(M,1, [3,4]), [[9,10]]);
assert_equal(submatrix(M, [3,4],1), [[17],[22]]);
assert_equal(submatrix(M, [1,3],[2,4]), [[8,10],[18,20]]);
assert_equal(submatrix(M, 1,3), [[9]]);
A = [[true, 17, "test"],
[[4,2], 91, false],
[6, [3,4], undef]];
assert_equal(submatrix(A,[0,2],[1,2]),[[17, "test"], [[3, 4], undef]]);
}
test_submatrix();
module test_force_list() {
assert_equal(force_list([3,4,5]), [3,4,5]);
assert_equal(force_list(5), [5]);
@ -466,6 +480,7 @@ test_array_dim();
module test_transpose() {
assert(transpose([[1,2,3],[4,5,6],[7,8,9]]) == [[1,4,7],[2,5,8],[3,6,9]]);
assert(transpose([[1,2,3],[4,5,6]]) == [[1,4],[2,5],[3,6]]);
assert(transpose([[1,2,3],[4,5,6]],reverse=true) == [[6,3], [5,2], [4,1]]);
assert(transpose([3,4,5]) == [3,4,5]);
}
test_transpose();

View file

@ -29,6 +29,7 @@ test_point3d();
module test_path3d() {
assert(path3d([[1,2], [3,4], [5,6], [7,8]])==[[1,2,0],[3,4,0],[5,6,0],[7,8,0]]);
assert(path3d([[1,2], [3,4], [5,6], [7,8]],9)==[[1,2,9],[3,4,9],[5,6,9],[7,8,9]]);
assert(path3d([[1,2,3], [2,3,4], [3,4,5], [4,5,6]])==[[1,2,3],[2,3,4],[3,4,5],[4,5,6]]);
assert(path3d([[1,2,3,4], [2,3,4,5], [3,4,5,6], [4,5,6,7]])==[[1,2,3],[2,3,4],[3,4,5],[4,5,6]]);
}
@ -41,6 +42,9 @@ module test_point4d() {
assert(point4d([1,2,3])==[1,2,3,0]);
assert(point4d([2,3])==[2,3,0,0]);
assert(point4d([1])==[1,0,0,0]);
assert(point4d([1,2,3],9)==[1,2,3,9]);
assert(point4d([2,3],9)==[2,3,9,9]);
assert(point4d([1],9)==[1,9,9,9]);
}
test_point4d();

View file

@ -1,11 +0,0 @@
include <../std.scad>
// Can't test echo output as yet. Include these for coverage calculations.
module test_echo_error() {}
module test_echo_warning() {}
module test_deprecate() {}
module test_deprecate_argument() {}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1,6 +1,146 @@
include <../std.scad>
//the commented lines are for tests to be written
//the tests are ordered as they appear in geometry.scad
test_point_on_segment2d();
test_point_left_of_line2d();
test_collinear();
test_distance_from_line();
test_line_normal();
test_line_intersection();
//test_line_ray_intersection();
test_line_segment_intersection();
//test_ray_intersection();
//test_ray_segment_intersection();
test_segment_intersection();
test_line_closest_point();
//test_ray_closest_point();
test_segment_closest_point();
test_line_from_points();
test_tri_calc();
//test_hyp_opp_to_adj();
//test_hyp_ang_to_adj();
//test_opp_ang_to_adj();
//test_hyp_adj_to_opp();
//test_hyp_ang_to_opp();
//test_adj_ang_to_opp();
//test_adj_opp_to_hyp();
//test_adj_ang_to_hyp();
//test_opp_ang_to_hyp();
//test_hyp_adj_to_ang();
//test_hyp_opp_to_ang();
//test_adj_opp_to_ang();
test_triangle_area();
test_plane3pt();
test_plane3pt_indexed();
//test_plane_from_normal();
test_plane_from_points();
//test_plane_from_polygon();
test_plane_normal();
//test_plane_offset();
//test_plane_transform();
test_projection_on_plane();
//test_plane_point_nearest_origin();
test_distance_from_plane();
test_find_circle_2tangents();
test_find_circle_3points();
test_circle_point_tangents();
test_tri_functions();
//test_closest_point_on_plane();
//test__general_plane_line_intersection();
//test_plane_line_angle();
//test_plane_line_intersection();
//test_polygon_line_intersection();
//test_plane_intersection();
test_coplanar();
test_points_on_plane();
test_in_front_of_plane();
//test_find_circle_2tangents();
//test_find_circle_3points();
//test_circle_point_tangents();
//test_circle_circle_tangents();
test_noncollinear_triple();
test_pointlist_bounds();
test_closest_point();
test_furthest_point();
test_polygon_area();
test_is_convex_polygon();
test_polygon_shift();
test_polygon_shift_to_closest_point();
test_reindex_polygon();
test_align_polygon();
test_centroid();
test_point_in_polygon();
test_polygon_is_clockwise();
test_clockwise_polygon();
test_ccw_polygon();
test_reverse_polygon();
//test_polygon_normal();
//test_split_polygons_at_each_x();
//test_split_polygons_at_each_y();
//test_split_polygons_at_each_z();
//tests to migrate to other files
test_is_path();
test_is_closed_path();
test_close_path();
test_cleanup_path();
test_simplify_path();
test_simplify_path_indexed();
test_is_region();
// to be used when there are two alternative symmetrical outcomes
// from a function like a plane output.
function standardize(v) =
v==[]? [] :
sign([for(vi=v) if( ! approx(vi,0)) vi,0 ][0])*v;
module assert_std(vc,ve) { assert(standardize(vc)==standardize(ve)); }
module test_points_on_plane() {
pts = [for(i=[0:40]) rands(-1,1,3) ];
dir = rands(-10,10,3);
normal0 = unit([1,2,3]);
ang = rands(0,360,1)[0];
normal = rot(a=ang,p=normal0);
plane = [each normal, normal*dir];
prj_pts = projection_on_plane(plane,pts);
assert(points_on_plane(prj_pts,plane));
assert(!points_on_plane(concat(pts,[normal-dir]),plane));
}
*test_points_on_plane();
module test_projection_on_plane(){
ang = rands(0,360,1)[0];
dir = rands(-10,10,3);
normal0 = unit([1,2,3]);
normal = rot(a=ang,p=normal0);
plane0 = [each normal0, 0];
plane = [each normal, 0];
planem = [each normal, normal*dir];
pts = [for(i=[1:10]) rands(-1,1,3)];
assert_approx( projection_on_plane(plane,pts),
projection_on_plane(plane,projection_on_plane(plane,pts)));
assert_approx( projection_on_plane(plane,pts),
rot(a=ang,p=projection_on_plane(plane0,rot(a=-ang,p=pts))));
assert_approx( move((-normal*dir)*normal,p=projection_on_plane(planem,pts)),
projection_on_plane(plane,pts));
assert_approx( move((normal*dir)*normal,p=projection_on_plane(plane,pts)),
projection_on_plane(planem,pts));
}
*test_projection_on_plane();
module test_line_from_points() {
assert_approx(line_from_points([[1,0],[0,0],[-1,0]]),[[-1,0],[1,0]]);
assert_approx(line_from_points([[1,1],[0,1],[-1,1]]),[[-1,1],[1,1]]);
assert(line_from_points([[1,1],[0,1],[-1,0]])==undef);
assert(line_from_points([[1,1],[0,1],[-1,0]],fast=true)== [[-1,0],[1,1]]);
}
*test_line_from_points();
module test_point_on_segment2d() {
assert(point_on_segment2d([-15,0], [[-10,0], [10,0]]) == false);
assert(point_on_segment2d([-10,0], [[-10,0], [10,0]]) == true);
@ -29,42 +169,28 @@ module test_point_on_segment2d() {
assert(point_on_segment2d([ 10, 10], [[-10,-10], [10,10]]) == true);
assert(point_on_segment2d([ 15, 15], [[-10,-10], [10,10]]) == false);
}
test_point_on_segment2d();
*test_point_on_segment2d();
module test_point_left_of_segment() {
assert(point_left_of_segment2d([ -3, 0], [[-10,-10], [10,10]]) > 0);
assert(point_left_of_segment2d([ 0, 0], [[-10,-10], [10,10]]) == 0);
assert(point_left_of_segment2d([ 3, 0], [[-10,-10], [10,10]]) < 0);
module test_point_left_of_line2d() {
assert(point_left_of_line2d([ -3, 0], [[-10,-10], [10,10]]) > 0);
assert(point_left_of_line2d([ 0, 0], [[-10,-10], [10,10]]) == 0);
assert(point_left_of_line2d([ 3, 0], [[-10,-10], [10,10]]) < 0);
}
test_point_left_of_segment();
*test_point_left_of_line2d();
module test_collinear() {
assert(collinear([-10,-10], [-15, -16], [10,10]) == false);
assert(collinear([[-10,-10], [-15, -16], [10,10]]) == false);
assert(collinear([-10,-10], [-15, -15], [10,10]) == true);
assert(collinear([[-10,-10], [-15, -15], [10,10]]) == true);
assert(collinear([-10,-10], [ -3, 0], [10,10]) == false);
assert(collinear([-10,-10], [ 0, 0], [10,10]) == true);
assert(collinear([-10,-10], [ 3, 0], [10,10]) == false);
assert(collinear([-10,-10], [ 15, 15], [10,10]) == true);
assert(collinear([-10,-10], [ 15, 16], [10,10]) == false);
}
test_collinear();
module test_collinear_indexed() {
pts = [
[-20,-20], [-10,-20], [0,-10], [10,0], [20,10], [20,20], [15,30]
];
assert(collinear_indexed(pts, 0,1,2) == false);
assert(collinear_indexed(pts, 1,2,3) == true);
assert(collinear_indexed(pts, 2,3,4) == true);
assert(collinear_indexed(pts, 3,4,5) == false);
assert(collinear_indexed(pts, 4,5,6) == false);
assert(collinear_indexed(pts, 4,3,2) == true);
assert(collinear_indexed(pts, 0,5,6) == false);
}
test_collinear_indexed();
*test_collinear();
module test_distance_from_line() {
@ -73,7 +199,7 @@ module test_distance_from_line() {
assert(abs(distance_from_line([[-10,-10,-10], [10,10,10]], [1,-1,0]) - sqrt(2)) < EPSILON);
assert(abs(distance_from_line([[-10,-10,-10], [10,10,10]], [8,-8,0]) - 8*sqrt(2)) < EPSILON);
}
test_distance_from_line();
*test_distance_from_line();
module test_line_normal() {
@ -97,7 +223,7 @@ module test_line_normal() {
assert(approx(n2, n1));
}
}
test_line_normal();
*test_line_normal();
module test_line_intersection() {
@ -110,7 +236,7 @@ module test_line_intersection() {
assert(line_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [-10, 10]]) == [0,0]);
assert(line_intersection([[ -8, 0], [ 12, 4]], [[ 12, 0], [ -8, 4]]) == [2,2]);
}
test_line_intersection();
*test_line_intersection();
module test_segment_intersection() {
@ -126,7 +252,7 @@ module test_segment_intersection() {
assert(segment_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [-10, 10]]) == [0,0]);
assert(segment_intersection([[ -8, 0], [ 12, 4]], [[ 12, 0], [ -8, 4]]) == [2,2]);
}
test_segment_intersection();
*test_segment_intersection();
module test_line_segment_intersection() {
@ -141,7 +267,7 @@ module test_line_segment_intersection() {
assert(line_segment_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [ 1, -1]]) == undef);
assert(line_segment_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [ -1, 1]]) == [0,0]);
}
test_line_segment_intersection();
*test_line_segment_intersection();
module test_line_closest_point() {
@ -151,7 +277,7 @@ module test_line_closest_point() {
assert(approx(line_closest_point([[-10,-20], [10,20]], [1,2]+[2,-1]), [1,2]));
assert(approx(line_closest_point([[-10,-20], [10,20]], [13,31]), [15,30]));
}
test_line_closest_point();
*test_line_closest_point();
module test_segment_closest_point() {
@ -162,10 +288,10 @@ module test_segment_closest_point() {
assert(approx(segment_closest_point([[-10,-20], [10,20]], [13,31]), [10,20]));
assert(approx(segment_closest_point([[-10,-20], [10,20]], [15,25]), [10,20]));
}
test_segment_closest_point();
*test_segment_closest_point();
module test_find_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]));
@ -174,9 +300,9 @@ module test_find_circle_2tangents() {
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(find_circle_2tangents(polar_to_xy(10,60),[0,0],[10,0],r=10)[0],polar_to_xy(20,30));
}
test_find_circle_2tangents();
*test_find_circle_2tangents();
module test_find_circle_3points() {
@ -291,7 +417,7 @@ module test_find_circle_3points() {
}
}
}
test_find_circle_3points();
*test_find_circle_3points();
module test_circle_point_tangents() {
@ -304,7 +430,7 @@ module test_circle_point_tangents() {
assert(approx(flatten(got), flatten(expected)));
}
}
test_circle_point_tangents();
*test_circle_point_tangents();
module test_tri_calc() {
@ -327,23 +453,9 @@ module test_tri_calc() {
assert(approx(tri_calc(hyp=hyp, ang2=ang2), expected));
}
}
test_tri_calc();
*test_tri_calc();
// Dummy modules to show up in coverage check script.
module test_hyp_opp_to_adj();
module test_hyp_ang_to_adj();
module test_opp_ang_to_adj();
module test_hyp_adj_to_opp();
module test_hyp_ang_to_opp();
module test_adj_ang_to_opp();
module test_adj_opp_to_hyp();
module test_adj_ang_to_hyp();
module test_opp_ang_to_hyp();
module test_hyp_adj_to_ang();
module test_hyp_opp_to_ang();
module test_adj_opp_to_ang();
module test_tri_functions() {
sides = rands(1,100,100,seed_value=8181);
for (p = pair_wrap(sides)) {
@ -365,7 +477,7 @@ module test_tri_functions() {
assert_approx(adj_opp_to_ang(adj,opp), ang);
}
}
test_tri_functions();
*test_tri_functions();
module test_triangle_area() {
@ -373,55 +485,53 @@ module test_triangle_area() {
assert(abs(triangle_area([0,0], [0,10], [0,15])) < EPSILON);
assert(abs(triangle_area([0,0], [10,0], [0,10]) - 50) < EPSILON);
}
test_triangle_area();
*test_triangle_area();
module test_plane3pt() {
assert(plane3pt([0,0,20], [0,10,10], [0,0,0]) == [1,0,0,0]);
assert(plane3pt([2,0,20], [2,10,10], [2,0,0]) == [1,0,0,2]);
assert(plane3pt([0,0,0], [10,0,10], [0,0,20]) == [0,1,0,0]);
assert(plane3pt([0,2,0], [10,2,10], [0,2,20]) == [0,1,0,2]);
assert(plane3pt([0,0,0], [10,10,0], [20,0,0]) == [0,0,1,0]);
assert(plane3pt([0,0,2], [10,10,2], [20,0,2]) == [0,0,1,2]);
assert_std(plane3pt([0,0,20], [0,10,10], [0,0,0]), [1,0,0,0]);
assert_std(plane3pt([2,0,20], [2,10,10], [2,0,0]), [1,0,0,2]);
assert_std(plane3pt([0,0,0], [10,0,10], [0,0,20]), [0,1,0,0]);
assert_std(plane3pt([0,2,0], [10,2,10], [0,2,20]), [0,1,0,2]);
assert_std(plane3pt([0,0,0], [10,10,0], [20,0,0]), [0,0,1,0]);
assert_std(plane3pt([0,0,2], [10,10,2], [20,0,2]), [0,0,1,2]);
}
test_plane3pt();
*test_plane3pt();
module test_plane3pt_indexed() {
pts = [ [0,0,0], [10,0,0], [0,10,0], [0,0,10] ];
s13 = sqrt(1/3);
assert(plane3pt_indexed(pts, 0,3,2) == [1,0,0,0]);
assert(plane3pt_indexed(pts, 0,2,3) == [-1,0,0,0]);
assert(plane3pt_indexed(pts, 0,1,3) == [0,1,0,0]);
assert(plane3pt_indexed(pts, 0,3,1) == [0,-1,0,0]);
assert(plane3pt_indexed(pts, 0,2,1) == [0,0,1,0]);
assert(plane3pt_indexed(pts, 0,1,2) == [0,0,-1,0]);
assert(plane3pt_indexed(pts, 3,2,1) == [s13,s13,s13,10*s13]);
assert(plane3pt_indexed(pts, 1,2,3) == [-s13,-s13,-s13,-10*s13]);
assert_std(plane3pt_indexed(pts, 0,3,2), [1,0,0,0]);
assert_std(plane3pt_indexed(pts, 0,2,3), [-1,0,0,0]);
assert_std(plane3pt_indexed(pts, 0,1,3), [0,1,0,0]);
assert_std(plane3pt_indexed(pts, 0,3,1), [0,-1,0,0]);
assert_std(plane3pt_indexed(pts, 0,2,1), [0,0,1,0]);
assert_approx(plane3pt_indexed(pts, 0,1,2), [0,0,-1,0]);
assert_approx(plane3pt_indexed(pts, 3,2,1), [s13,s13,s13,10*s13]);
assert_approx(plane3pt_indexed(pts, 1,2,3), [-s13,-s13,-s13,-10*s13]);
}
test_plane3pt_indexed();
*test_plane3pt_indexed();
module test_plane_from_points() {
assert(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]) == [1,0,0,0]);
assert(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]) == [1,0,0,2]);
assert(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]) == [0,1,0,0]);
assert(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]) == [0,1,0,2]);
assert(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]) == [0,0,1,0]);
assert(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]) == [0,0,1,2]);
assert_std(plane_from_points([[0,0,20], [0,10,10], [0,0,0], [0,5,3]]), [1,0,0,0]);
assert_std(plane_from_points([[2,0,20], [2,10,10], [2,0,0], [2,3,4]]), [1,0,0,2]);
assert_std(plane_from_points([[0,0,0], [10,0,10], [0,0,20], [5,0,7]]), [0,1,0,0]);
assert_std(plane_from_points([[0,2,0], [10,2,10], [0,2,20], [4,2,3]]), [0,1,0,2]);
assert_std(plane_from_points([[0,0,0], [10,10,0], [20,0,0], [8,3,0]]), [0,0,1,0]);
assert_std(plane_from_points([[0,0,2], [10,10,2], [20,0,2], [3,4,2]]), [0,0,1,2]);
}
test_plane_from_points();
*test_plane_from_points();
module test_plane_normal() {
assert(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])) == [1,0,0]);
assert(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])) == [1,0,0]);
assert(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])) == [0,1,0]);
assert(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])) == [0,1,0]);
assert(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])) == [0,0,1]);
assert(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])) == [0,0,1]);
assert_std(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])), [1,0,0]);
assert_std(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])), [1,0,0]);
assert_std(plane_normal(plane3pt([0,0,0], [10,0,10], [0,0,20])), [0,1,0]);
assert_std(plane_normal(plane3pt([0,2,0], [10,2,10], [0,2,20])), [0,1,0]);
assert_std(plane_normal(plane3pt([0,0,0], [10,10,0], [20,0,0])), [0,0,1]);
assert_std(plane_normal(plane3pt([0,0,2], [10,10,2], [20,0,2])), [0,0,1]);
}
test_plane_normal();
*test_plane_normal();
module test_distance_from_plane() {
@ -429,20 +539,16 @@ module test_distance_from_plane() {
assert(distance_from_plane(plane1, [0,0,5]) == 5);
assert(distance_from_plane(plane1, [5,5,8]) == 8);
}
test_distance_from_plane();
*test_distance_from_plane();
module test_coplanar() {
plane = plane3pt([0,0,0], [0,10,10], [10,0,10]);
assert(coplanar(plane, [5,5,10]) == true);
assert(coplanar(plane, [10/3,10/3,20/3]) == true);
assert(coplanar(plane, [0,0,0]) == true);
assert(coplanar(plane, [1,1,0]) == false);
assert(coplanar(plane, [-1,1,0]) == true);
assert(coplanar(plane, [1,-1,0]) == true);
assert(coplanar(plane, [5,5,5]) == false);
assert(coplanar([ [5,5,1],[0,0,1],[-1,-1,1] ]) == false);
assert(coplanar([ [5,5,1],[0,0,0],[-1,-1,1] ]) == true);
assert(coplanar([ [0,0,0],[1,0,1],[1,1,1], [0,1,2] ]) == false);
assert(coplanar([ [0,0,0],[1,0,1],[1,1,2], [0,1,1] ]) == true);
}
test_coplanar();
*test_coplanar();
module test_in_front_of_plane() {
@ -455,7 +561,7 @@ module test_in_front_of_plane() {
assert(in_front_of_plane(plane, [0,0,5]) == true);
assert(in_front_of_plane(plane, [0,0,-5]) == false);
}
test_in_front_of_plane();
*test_in_front_of_plane();
module test_is_path() {
@ -470,35 +576,43 @@ module test_is_path() {
assert(is_path([[1,2,3],[4,5,6]]));
assert(is_path([[1,2,3],[4,5,6],[7,8,9]]));
}
test_is_path();
*test_is_path();
module test_is_closed_path() {
assert(!is_closed_path([[1,2,3],[4,5,6],[1,8,9]]));
assert(is_closed_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]));
}
test_is_closed_path();
*test_is_closed_path();
module test_close_path() {
assert(close_path([[1,2,3],[4,5,6],[1,8,9]]) == [[1,2,3],[4,5,6],[1,8,9],[1,2,3]]);
assert(close_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]) == [[1,2,3],[4,5,6],[1,8,9],[1,2,3]]);
}
test_close_path();
*test_close_path();
module test_cleanup_path() {
assert(cleanup_path([[1,2,3],[4,5,6],[1,8,9]]) == [[1,2,3],[4,5,6],[1,8,9]]);
assert(cleanup_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]) == [[1,2,3],[4,5,6],[1,8,9]]);
}
test_cleanup_path();
*test_cleanup_path();
module test_polygon_area() {
assert(approx(polygon_area([[1,1],[-1,1],[-1,-1],[1,-1]]), 4));
assert(approx(polygon_area(circle(r=50,$fn=1000)), -PI*50*50, eps=0.1));
}
test_polygon_area();
*test_polygon_area();
module test_is_convex_polygon() {
assert(is_convex_polygon([[1,1],[-1,1],[-1,-1],[1,-1]]));
assert(is_convex_polygon(circle(r=50,$fn=1000)));
assert(!is_convex_polygon([[1,1],[0,0],[-1,1],[-1,-1],[1,-1]]));
}
*test_is_convex_polygon();
module test_polygon_shift() {
@ -506,7 +620,7 @@ module test_polygon_shift() {
assert(polygon_shift(path,1) == [[-1,1],[-1,-1],[1,-1],[1,1]]);
assert(polygon_shift(path,2) == [[-1,-1],[1,-1],[1,1],[-1,1]]);
}
test_polygon_shift();
*test_polygon_shift();
module test_polygon_shift_to_closest_point() {
@ -516,56 +630,45 @@ module test_polygon_shift_to_closest_point() {
assert(polygon_shift_to_closest_point(path,[-1.1,-1.1]) == [[-1,-1],[1,-1],[1,1],[-1,1]]);
assert(polygon_shift_to_closest_point(path,[1.1,-1.1]) == [[1,-1],[1,1],[-1,1],[-1,-1]]);
}
test_polygon_shift_to_closest_point();
*test_polygon_shift_to_closest_point();
/*
module test_first_noncollinear(){
pts = [
[1,1], [2,2], [3,3], [4,4], [4,5], [5,6]
];
assert(first_noncollinear(0,1,pts) == 4);
assert(first_noncollinear(1,0,pts) == 4);
assert(first_noncollinear(0,2,pts) == 4);
assert(first_noncollinear(2,0,pts) == 4);
assert(first_noncollinear(1,2,pts) == 4);
assert(first_noncollinear(2,1,pts) == 4);
assert(first_noncollinear(0,3,pts) == 4);
assert(first_noncollinear(3,0,pts) == 4);
assert(first_noncollinear(1,3,pts) == 4);
assert(first_noncollinear(3,1,pts) == 4);
assert(first_noncollinear(2,3,pts) == 4);
assert(first_noncollinear(3,2,pts) == 4);
assert(first_noncollinear(0,4,pts) == 1);
assert(first_noncollinear(4,0,pts) == 1);
assert(first_noncollinear(1,4,pts) == 0);
assert(first_noncollinear(4,1,pts) == 0);
assert(first_noncollinear(2,4,pts) == 0);
assert(first_noncollinear(4,2,pts) == 0);
assert(first_noncollinear(3,4,pts) == 0);
assert(first_noncollinear(4,3,pts) == 0);
assert(first_noncollinear(0,5,pts) == 1);
assert(first_noncollinear(5,0,pts) == 1);
assert(first_noncollinear(1,5,pts) == 0);
assert(first_noncollinear(5,1,pts) == 0);
assert(first_noncollinear(2,5,pts) == 0);
assert(first_noncollinear(5,2,pts) == 0);
assert(first_noncollinear(3,5,pts) == 0);
assert(first_noncollinear(5,3,pts) == 0);
assert(first_noncollinear(4,5,pts) == 0);
assert(first_noncollinear(5,4,pts) == 0);
module test_reindex_polygon() {
pent = subdivide_path([for(i=[0:4])[sin(72*i),cos(72*i)]],5);
circ = circle($fn=5,r=2.2);
assert_approx(reindex_polygon(circ,pent), [[0.951056516295,0.309016994375],[0.587785252292,-0.809016994375],[-0.587785252292,-0.809016994375],[-0.951056516295,0.309016994375],[0,1]]);
poly = [[-1,1],[-1,-1],[1,-1],[1,1],[0,0]];
ref = [for(i=[0:4])[sin(72*i),cos(72*i)]];
assert_approx(reindex_polygon(ref,poly),[[0,0],[1,1],[1,-1],[-1,-1],[-1,1]]);
}
test_first_noncollinear();
*/
*test_reindex_polygon();
module test_find_noncollinear_points() {
assert(find_noncollinear_points([[1,1],[2,2],[3,3],[4,4],[4,5],[5,6]]) == [0,5,3]);
assert(find_noncollinear_points([[1,1],[2,2],[8,3],[4,4],[4,5],[5,6]]) == [0,2,5]);
module test_align_polygon() {
pentagon = subdivide_path(pentagon(side=2),10);
hexagon = subdivide_path(hexagon(side=2.7),10);
aligned = [[2.7,0],[2.025,-1.16913429511],[1.35,-2.33826859022],
[-1.35,-2.33826859022],[-2.025,-1.16913429511],[-2.7,0],
[-2.025,1.16913429511],[-1.35,2.33826859022],[1.35,2.33826859022],
[2.025,1.16913429511]];
assert_approx(align_polygon(pentagon,hexagon,[0:10:359]), aligned);
aligned2 = [[1.37638192047,0],[1.37638192047,-1],[0.425325404176,-1.30901699437],
[-0.525731112119,-1.61803398875],[-1.11351636441,-0.809016994375],
[-1.7013016167,0],[-1.11351636441,0.809016994375],
[-0.525731112119,1.61803398875],[0.425325404176,1.30901699437],
[1.37638192047,1]];
assert_approx(align_polygon(hexagon,pentagon,[0:10:359]), aligned2);
}
*test_align_polygon();
module test_noncollinear_triple() {
assert(noncollinear_triple([[1,1],[2,2],[3,3],[4,4],[4,5],[5,6]]) == [0,5,3]);
assert(noncollinear_triple([[1,1],[2,2],[8,3],[4,4],[4,5],[5,6]]) == [0,2,5]);
u = unit([5,3]);
assert_equal(find_noncollinear_points([for(i = [2,3,4,5,7,12,15]) i * u], error=false),[]);
assert_equal(noncollinear_triple([for(i = [2,3,4,5,7,12,15]) i * u], error=false),[]);
}
test_find_noncollinear_points();
*test_noncollinear_triple();
module test_centroid() {
@ -573,15 +676,18 @@ module test_centroid() {
assert_approx(centroid(circle(d=100)), [0,0]);
assert_approx(centroid(rect([40,60],rounding=10,anchor=LEFT)), [20,0]);
assert_approx(centroid(rect([40,60],rounding=10,anchor=FWD)), [0,30]);
poly = [for(a=[0:90:360])
move([1,2.5,3.1], rot(p=[cos(a),sin(a),0],from=[0,0,1],to=[1,1,1])) ];
assert_approx(centroid(poly), [1,2.5,3.1]);
}
test_centroid();
*test_centroid();
module test_simplify_path() {
path = [[-20,-20], [-10,-20], [0,-10], [10,0], [20,10], [20,20], [15,30]];
assert(simplify_path(path) == [[-20,-20], [-10,-20], [20,10], [20,20], [15,30]]);
}
test_simplify_path();
*test_simplify_path();
module test_simplify_path_indexed() {
@ -589,23 +695,29 @@ module test_simplify_path_indexed() {
path = [4,6,1,0,3,2,5];
assert(simplify_path_indexed(pts, path) == [4,6,3,2,5]);
}
test_simplify_path_indexed();
*test_simplify_path_indexed();
module test_point_in_polygon() {
poly = [for (a=[0:30:359]) 10*[cos(a),sin(a)]];
poly2 = [ [-3,-3],[2,-3],[2,1],[-1,1],[-1,-1],[1,-1],[1,2],[-3,2] ];
assert(point_in_polygon([0,0], poly) == 1);
assert(point_in_polygon([20,0], poly) == -1);
assert(point_in_polygon([20,0], poly,EPSILON,nonzero=false) == -1);
assert(point_in_polygon([5,5], poly) == 1);
assert(point_in_polygon([-5,5], poly) == 1);
assert(point_in_polygon([-5,-5], poly) == 1);
assert(point_in_polygon([5,-5], poly) == 1);
assert(point_in_polygon([5,-5], poly,EPSILON,nonzero=false) == 1);
assert(point_in_polygon([-10,-10], poly) == -1);
assert(point_in_polygon([10,0], poly) == 0);
assert(point_in_polygon([0,10], poly) == 0);
assert(point_in_polygon([0,-10], poly) == 0);
assert(point_in_polygon([0,-10], poly,EPSILON,nonzero=false) == 0);
assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=true) == 1);
assert(point_in_polygon([0,0], poly2,EPSILON,nonzero=false) == -1);
}
test_point_in_polygon();
*test_point_in_polygon();
module test_pointlist_bounds() {
@ -626,16 +738,16 @@ module test_pointlist_bounds() {
];
assert(pointlist_bounds(pts2d) == [[-63,-42],[84,42]]);
pts5d = [
[-53,27,12,-53,12],
[-63,97,36,-63,36],
[84,-32,-5,84,-5],
[63,-24,42,63,42],
[23,57,-42,23,-42]
[-53, 27, 12,-53, 12],
[-63, 97, 36,-63, 36],
[ 84,-32, -5, 84, -5],
[ 63,-24, 42, 63, 42],
[ 23, 57,-42, 23,-42]
];
assert(pointlist_bounds(pts5d) == [[-63,-32,-42,-63,-42],[84,97,42,84,42]]);
assert(pointlist_bounds([[3,4,5,6]]), [[3,4,5,6],[3,4,5,6]]);
}
test_pointlist_bounds();
*test_pointlist_bounds();
module test_closest_point() {
@ -648,7 +760,7 @@ module test_closest_point() {
assert(mindist == dists[pidx]);
}
}
test_closest_point();
*test_closest_point();
module test_furthest_point() {
@ -661,7 +773,7 @@ module test_furthest_point() {
assert(mindist == dists[pidx]);
}
}
test_furthest_point();
*test_furthest_point();
module test_polygon_is_clockwise() {
@ -670,7 +782,7 @@ module test_polygon_is_clockwise() {
assert(polygon_is_clockwise(circle(d=100)));
assert(polygon_is_clockwise(square(100)));
}
test_polygon_is_clockwise();
*test_polygon_is_clockwise();
module test_clockwise_polygon() {
@ -679,7 +791,7 @@ module test_clockwise_polygon() {
assert(clockwise_polygon(path) == path);
assert(clockwise_polygon(rpath) == path);
}
test_clockwise_polygon();
*test_clockwise_polygon();
module test_ccw_polygon() {
@ -688,7 +800,7 @@ module test_ccw_polygon() {
assert(ccw_polygon(path) == rpath);
assert(ccw_polygon(rpath) == rpath);
}
test_ccw_polygon();
*test_ccw_polygon();
module test_reverse_polygon() {
@ -697,7 +809,7 @@ module test_reverse_polygon() {
assert(reverse_polygon(path) == rpath);
assert(reverse_polygon(rpath) == path);
}
test_reverse_polygon();
*test_reverse_polygon();
module test_is_region() {
@ -709,7 +821,7 @@ module test_is_region() {
assert(!is_region(true));
assert(!is_region("foo"));
}
test_is_region();
*test_is_region();

View file

@ -100,6 +100,106 @@ module test_is_matrix() {
test_is_matrix();
module test_is_zero() {
assert(is_zero(0));
assert(is_zero([0,0,0]));
assert(is_zero([[0,0,0],[0,0]]));
assert(is_zero([EPSILON/2,EPSILON/2,EPSILON/2]));
assert(!is_zero(1e-3));
assert(!is_zero([0,0,1e-3]));
assert(!is_zero([EPSILON*10,0,0]));
assert(!is_zero([0,EPSILON*10,0]));
assert(!is_zero([0,0,EPSILON*10]));
assert(!is_zero(true));
assert(!is_zero(false));
assert(!is_zero(INF));
assert(!is_zero(-INF));
assert(!is_zero(NAN));
assert(!is_zero("foo"));
assert(!is_zero([]));
assert(!is_zero([0:1:2]));
}
test_is_zero();
module test_is_positive() {
assert(!is_positive(-2));
assert(!is_positive(0));
assert(is_positive(2));
assert(!is_positive([0,0,0]));
assert(!is_positive([0,1,2]));
assert(is_positive([3,1,2]));
assert(!is_positive([3,-1,2]));
assert(!is_positive([]));
assert(!is_positive(true));
assert(!is_positive(false));
assert(!is_positive("foo"));
assert(!is_positive([0:1:2]));
}
test_is_positive();
module test_is_negative() {
assert(is_negative(-2));
assert(!is_negative(0));
assert(!is_negative(2));
assert(!is_negative([0,0,0]));
assert(!is_negative([0,1,2]));
assert(!is_negative([3,1,2]));
assert(!is_negative([3,-1,2]));
assert(is_negative([-3,-1,-2]));
assert(!is_negative([-3,1,-2]));
assert(is_negative([[-5,-7],[-3,-1,-2]]));
assert(!is_negative([[-5,-7],[-3,1,-2]]));
assert(!is_negative([]));
assert(!is_negative(true));
assert(!is_negative(false));
assert(!is_negative("foo"));
assert(!is_negative([0:1:2]));
}
test_is_negative();
module test_is_nonpositive() {
assert(is_nonpositive(-2));
assert(is_nonpositive(0));
assert(!is_nonpositive(2));
assert(is_nonpositive([0,0,0]));
assert(!is_nonpositive([0,1,2]));
assert(is_nonpositive([0,-1,-2]));
assert(!is_nonpositive([3,1,2]));
assert(!is_nonpositive([3,-1,2]));
assert(!is_nonpositive([]));
assert(!is_nonpositive(true));
assert(!is_nonpositive(false));
assert(!is_nonpositive("foo"));
assert(!is_nonpositive([0:1:2]));
}
test_is_nonpositive();
module test_is_nonnegative() {
assert(!is_nonnegative(-2));
assert(is_nonnegative(0));
assert(is_nonnegative(2));
assert(is_nonnegative([0,0,0]));
assert(is_nonnegative([0,1,2]));
assert(is_nonnegative([3,1,2]));
assert(!is_nonnegative([3,-1,2]));
assert(!is_nonnegative([-3,-1,-2]));
assert(!is_nonnegative([[-5,-7],[-3,-1,-2]]));
assert(!is_nonnegative([[-5,-7],[-3,1,-2]]));
assert(!is_nonnegative([[5,7],[3,-1,2]]));
assert(is_nonnegative([[5,7],[3,1,2]]));
assert(!is_nonnegative([]));
assert(!is_nonnegative(true));
assert(!is_nonnegative(false));
assert(!is_nonnegative("foo"));
assert(!is_nonnegative([0:1:2]));
}
test_is_nonnegative();
module test_approx() {
assert_equal(approx(PI, 3.141592653589793236), true);
assert_equal(approx(PI, 3.1415926), false);
@ -391,11 +491,13 @@ module test_mean() {
}
test_mean();
/*
module test_median() {
assert_equal(median([2,3,7]), 4.5);
assert_equal(median([[1,2,3], [3,4,5], [8,9,10]]), [4.5,5.5,6.5]);
}
test_median();
*/
module test_convolve() {
@ -851,22 +953,6 @@ module test_real_roots(){
}
test_real_roots();
// Need decision about behavior for out of bounds ranges, empty ranges
module test_submatrix(){
M = [[1,2,3,4,5],
[6,7,8,9,10],
[11,12,13,14,15],
[16,17,18,19,20],
[21,22,23,24,25]];
assert_equal(submatrix(M,[1:2], [3:4]), [[9,10],[14,15]]);
assert_equal(submatrix(M,[1], [3,4]), [[9,10]]);
assert_equal(submatrix(M,1, [3,4]), [[9,10]]);
assert_equal(submatrix(M, [3,4],1), [[17],[22]]);
assert_equal(submatrix(M, [1,3],[2,4]), [[8,10],[18,20]]);
}
test_submatrix();
module test_qr_factor() {
// Check that R is upper triangular
@ -911,23 +997,21 @@ test_qr_factor();
module test_poly_mult(){
assert_equal(poly_mult([3,2,1],[4,5,6,7]),[12,23,32,38,20,7]);
assert_equal(poly_mult([3,2,1],[0]),[0]);
// assert_equal(poly_mult([3,2,1],[]),[]);
assert_equal(poly_mult([[1,2],[3,4],[5,6]]), [15,68,100,48]);
assert_equal(poly_mult([3,2,1],[0]),[0]);
assert_equal(poly_mult([[1,2],[0],[5,6]]), [0]);
// assert_equal(poly_mult([[1,2],[],[5,6]]), []);
assert_equal(poly_mult([[3,4,5],[0,0,0]]),[0]);
// assert_equal(poly_mult([[3,4,5],[0,0,0]]),[]);
assert_equal(poly_mult([[3,4,5],[0,0,0]]), [0]);
assert_equal(poly_mult([[0],[0,0,0]]),[0]);
}
test_poly_mult();
module test_poly_div(){
assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[0]]);
// assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[]]);
assert_equal(poly_div([1,2,3,4],[1,2,3,4,5]), [[], [1,2,3,4]]);
assert_equal(poly_div(poly_add(poly_mult([1,2,3,4],[2,0,2]), [1,1,2]), [1,2,3,4]), [[2,0,2],[1,1,2]]);
assert_equal(poly_div([1,2,3,4], [1,-3]), [[1,5,18],[58]]);
assert_equal(poly_div([0], [1,-3]), [[0],[0]]);
}
test_poly_div();

View file

@ -306,11 +306,11 @@ function up(z=0,p=undef) = move([0,0,z],p=p);
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
// * Called as a function without a `p` argument, and `planar` is true, returns the affine2d rotational matrix.
// * Called as a function without a `p` argument, and `planar` is true, returns the affine2d rotational matrix. Requires that `a` is a finite scalar.
// * Called as a function without a `p` argument, and `planar` is false, returns the affine3d rotational matrix.
//
// Arguments:
// a = Scalar angle or vector of XYZ rotation angles to rotate by, in degrees.
// a = Scalar angle or vector of XYZ rotation angles to rotate by, in degrees. If `planar` is true and `p` is not given, then `a` must be a finite scalar. Default: `0`
// v = vector for the axis of rotation. Default: [0,0,1] or UP
// cp = centerpoint to rotate around. Default: [0,0,0]
// from = Starting vector for vector-based rotations.
@ -343,16 +343,21 @@ module rot(a=0, v=undef, cp=undef, from=undef, to=undef, reverse=false)
function rot(a=0, v, cp, from, to, reverse=false, planar=false, p, _m) =
assert(is_undef(from)==is_undef(to), "from and to must be specified together.")
assert(is_undef(from) || is_vector(from, zero=false), "'from' must be a non-zero vector.")
assert(is_undef(to) || is_vector(to, zero=false), "'to' must be a non-zero vector.")
assert(is_undef(v) || is_vector(v, zero=false), "'v' must be a non-zero vector.")
assert(is_undef(cp) || is_vector(cp), "'cp' must be a vector.")
assert(is_finite(a) || is_vector(a), "'a' must be a finite scalar or a vector.")
assert(is_bool(reverse))
assert(is_bool(planar))
is_undef(p)? (
planar? let(
check = assert(is_num(a)),
cp = is_undef(cp)? cp : point2d(cp),
m1 = is_undef(from)? affine2d_zrot(a) :
assert(is_vector(from))
assert(!approx(norm(from),0))
assert(approx(point3d(from).z, 0))
assert(is_vector(to))
assert(!approx(norm(to),0))
assert(approx(point3d(to).z, 0))
assert(a==0, "'from' and 'to' cannot be used with 'a' when 'planar' is true.")
assert(approx(point3d(from).z, 0), "'from' must be a 2D vector when 'planar' is true.")
assert(approx(point3d(to).z, 0), "'to' must be a 2D vector when 'planar' is true.")
affine2d_zrot(
vang(point2d(to)) -
vang(point2d(from))
@ -364,13 +369,10 @@ function rot(a=0, v, cp, from, to, reverse=false, planar=false, p, _m) =
to = is_undef(to)? undef : point3d(to),
cp = is_undef(cp)? undef : point3d(cp),
m1 = !is_undef(from)? (
assert(is_vector(from))
assert(!approx(norm(from),0))
assert(is_vector(to))
assert(!approx(norm(to),0))
assert(is_num(a))
affine3d_rot_from_to(from,to) * affine3d_zrot(a)
) :
!is_undef(v)? affine3d_rot_by_axis(v,a) :
!is_undef(v)? assert(is_num(a)) affine3d_rot_by_axis(v,a) :
is_num(a)? affine3d_zrot(a) :
affine3d_zrot(a.z) * affine3d_yrot(a.y) * affine3d_xrot(a.x),
m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)),
@ -558,12 +560,12 @@ function scale(v=1, p=undef) =
len(v)==2? affine2d_scale(v) : affine3d_scale(point3d(v))
) : (
assert(is_list(p))
is_num(p.x)? vmul(p,v) :
is_vector(p)? ( len(p)==2? vmul(p,point2d(v)) : vmul(p,point3d(v,1)) ) :
is_vnf(p)? let(inv=product([for (x=v) x<0? -1 : 1])) [
scale(v=v,p=p.x),
inv>=0? p.y : [for (l=p.y) reverse(l)]
scale(v=v, p=p[0]),
inv>=0? p[1] : [for (l=p[1]) reverse(l)]
] :
[for (l=p) is_vector(l)? vmul(l,v) : scale(v=v, p=l)]
[ for (pp=p) scale(v=v, p=pp) ]
);

View file

@ -64,13 +64,10 @@ function vang(v) =
// Example:
// vmul([3,4,5], [8,7,6]); // Returns [24, 28, 30]
function vmul(v1, v2) =
// this thighter check can be done yet because it would break other codes in the library
// assert( is_vector(v1) && is_vector(v2,len(v1)), "Incompatible vectors")
assert( is_vector(v1) && is_vector(v2), "Invalid vector(s)")
assert( is_vector(v1) && is_vector(v2,len(v1)), "Incompatible vectors")
[for (i = [0:1:len(v1)-1]) v1[i]*v2[i]];
// Function: vdiv()
// Description:
// Element-wise vector division. Divides each element of vector `v1` by

View file

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

View file

@ -403,6 +403,16 @@ function _triangulate_planar_convex_polygons(polys) =
outtris = concat(tris, newtris, newtris2)
) outtris;
//**
// this function may produce degenerate triangles:
// _triangulate_planar_convex_polygons([ [for(i=[0:1]) [i,i],
// [1,-1], [-1,-1],
// for(i=[-1:0]) [i,i] ] ] )
// == [[[-1, -1], [ 0, 0], [0, 0]]
// [[-1, -1], [-1, -1], [0, 0]]
// [[ 1, -1], [-1, -1], [0, 0]]
// [[ 0, 0], [ 1, 1], [1, -1]] ]
//
// Function: vnf_bend()
// Usage:
@ -647,7 +657,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
nonplanars = unique([
for (face = faces) let(
faceverts = [for (k=face) varr[k]]
) if (!points_are_coplanar(faceverts)) [
) if (!coplanar(faceverts)) [
"ERROR",
"NONPLANAR",
"Face vertices are not coplanar",