From 0b17bf59304e723b6813d661b85c71cf30e83165 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 30 Mar 2021 00:46:59 -0700 Subject: [PATCH 01/18] Brought slice() in line with select() indexing, without wrapping. Replaced a lot of select() and slice() calls with last(), list_head(), and list_tail() calls. --- arrays.scad | 130 ++++++++++++++++++++++++++------------- attachments.scad | 2 +- beziers.scad | 2 +- debug.scad | 2 +- gears.scad | 10 +-- hull.scad | 2 +- joiners.scad | 2 +- math.scad | 12 ++-- modular_hose.scad | 4 +- paths.scad | 44 ++++++------- quaternions.scad | 2 +- regions.scad | 2 +- rounding.scad | 57 +++++++++-------- screws.scad | 2 +- shapes.scad | 4 +- shapes2d.scad | 28 ++++----- skin.scad | 26 ++++---- strings.scad | 2 +- tests/test_arrays.scad | 57 +++++++++++------ tests/test_geometry.scad | 16 ++--- tests/test_math.scad | 2 +- threading.scad | 4 +- turtle3d.scad | 6 +- vnf.scad | 18 +++--- 24 files changed, 250 insertions(+), 186 deletions(-) diff --git a/arrays.scad b/arrays.scad index e4de121..7d85d8c 100644 --- a/arrays.scad +++ b/arrays.scad @@ -53,13 +53,12 @@ function _same_type(a,b, depth) = // Function: select() +// Topics: List Handling // Description: // Returns a portion of a list, wrapping around past the beginning, if ends) list[i]]; +// f = slice([3,4,5,6,7,8,9], 4, 3; // Returns [] +function slice(list,s=0,e=-1) = + assert(is_list(list)) + assert(is_int(s)) + assert(is_int(e)) + !list? [] : + let( + l = len(list), + s = constrain(s + (s<0? l : 0), 0, l-1), + e = constrain(e + (e<0? l : 0), 0, l-1) + ) + [if (e>=s) for (i=[s:1:e]) list[i]]; // Function: last() @@ -367,7 +370,7 @@ function list_decreasing(list) = // Usage: // list = repeat(val, n); // Topics: List Handling -// See Also: list_range() +// See Also: range(), rangex() // Description: // Generates a list or array of `n` copies of the given value `val`. // If the count `n` is given as a list of counts, then this creates a @@ -387,37 +390,36 @@ function repeat(val, n, i=0) = [for (j=[1:1:n[i]]) repeat(val, n, i+1)]; -// Function: list_range() +// Function: range() // Usage: -// list = list_range(n=, , ); -// list = list_range(n=, , ); -// list = list_range(e=, ); -// list = list_range(s=, e=, ); +// list = range(n, , ); +// list = range(n, , ); +// list = range(e=, ); +// list = range(s=, e=, ); // Topics: List Handling -// See Also: repeat() // Description: // Returns a list, counting up from starting value `s`, by `step` increments, // until either `n` values are in the list, or it reaches the end value `e`. // If both `n` and `e` are given, returns `n` values evenly spread from `s` // to `e`, and `step` is ignored. // Arguments: -// --- // n = Desired number of values in returned list, if given. +// --- // s = Starting value. Default: 0 // e = Ending value to stop at, if given. // step = Amount to increment each value. Default: 1 +// See Also: repeat(), rangex() // Example: -// a = list_range(4); // Returns [0,1,2,3] -// b = list_range(n=4, step=2); // Returns [0,2,4,6] -// c = list_range(n=4, s=3, step=3); // Returns [3,6,9,12] -// d = list_range(n=5, s=0, e=10); // Returns [0, 2.5, 5, 7.5, 10] -// e = list_range(e=3); // Returns [0,1,2,3] -// f = list_range(e=7, step=2); // Returns [0,2,4,6] -// g = list_range(s=3, e=5); // Returns [3,4,5] -// h = list_range(s=3, e=8, step=2); // Returns [3,5,7] -// i = list_range(s=4, e=8.3, step=2); // Returns [4,6,8] -// j = list_range(n=4, s=[3,4], step=[2,3]); // Returns [[3,4], [5,7], [7,10], [9,13]] -function list_range(n, s=0, e, step) = +// a = range(4); // Returns [0,1,2,3] +// b = range(n=4, step=2); // Returns [0,2,4,6] +// c = range(n=4, s=3, step=3); // Returns [3,6,9,12] +// d = range(n=5, s=0, e=10); // Returns [0, 2.5, 5, 7.5, 10] +// e = range(e=3); // Returns [0,1,2,3] +// f = range(e=7, step=2); // Returns [0,2,4,6] +// g = range(s=3, e=5); // Returns [3,4,5] +// h = range(s=3, e=8, step=2); // Returns [3,5,7] +// i = range(s=4, e=8.3, step=2); // Returns [4,6,8] +function range(n, s=0, e, step) = assert( is_undef(n) || is_finite(n), "Parameter `n` must be a number.") assert( is_undef(n) || is_undef(e) || is_undef(step), "At most 2 of n, e, and step can be given.") let( step = (n!=undef && e!=undef)? (e-s)/(n-1) : default(step,1) ) @@ -428,6 +430,50 @@ function list_range(n, s=0, e, step) = [for (v=[s:step:e]) v] ; +// Function: rangex() +// Usage: +// list = rangex(n, , ); +// list = rangex(n, , ); +// list = rangex(e=, ); +// list = rangex(s=, e=, ); +// Topics: List Handling +// Description: +// Returns a list, counting up from starting value `s`, by `step` increments, until +// either `n` values are in the list, or it reaches the value just before the end value `e`. +// If both `n` and `e` are given, returns `n` values evenly spread from `s` to the value +// just before `e`, and `step` is ignored. +// Arguments: +// n = Desired number of values in returned list, if given. +// --- +// s = Starting value. Default: 0 +// e = Ending value to stop at, if given. +// step = Amount to increment each value. Default: 1 +// See Also: repeat(), range() +// Example: +// a = rangex(4); // Returns [0,1,2,3] +// b = rangex(5,e=1); // Returns [0, 0.2, 0.4, 0.6, 0.8] +// c = rangex(n=4, step=2); // Returns [0,2,4,6] +// d = rangex(n=4, step=0.25); // Returns [0, 0.25, 0.5, 0.75] +// e = rangex(n=4, s=3, step=3); // Returns [3,6,9,12] +// f = rangex(n=5, s=0, e=10); // Returns [0, 2, 4, 6, 8] +// g = rangex(e=3); // Returns [0,1,2] +// h = rangex(e=7, step=2); // Returns [0,2,4,6] +// i = rangex(s=3, e=5); // Returns [3,4] +// j = rangex(s=3, e=8, step=2); // Returns [3,5,7] +// k = rangex(s=2, e=8, step=2); // Returns [2,4,6] +// l = rangex(s=2, e=8.1, step=2); // Returns [2,4,6,8] +function rangex(n, s=0, e, step) = + assert( is_undef(n) || is_finite(n), "Parameter `n` must be a number.") + assert( is_undef(n) || is_undef(e) || is_undef(step), "At most 2 of n, e, and step can be given.") + let( step = (n!=undef && e!=undef)? (e-s)/n : default(step,1) ) + is_undef(e) + ? assert( is_consistent([s, step]), "Incompatible data.") + [for (i=[0:1:n-1]) s+step*i ] + : assert( is_vector([s,step,e]), "Start `s`, step `step` and end `e` must be numbers.") + let(steps=floor((e-s)/step+0.5)) + [for (i=[0:1:steps-1]) s+step*i ]; + + // Section: List Manipulation diff --git a/attachments.scad b/attachments.scad index 04d8a17..0a283d1 100644 --- a/attachments.scad +++ b/attachments.scad @@ -442,7 +442,7 @@ function find_anchor(anchor, geom) = let( cp = select(geom,-3), offset = anchor==CENTER? CENTER : select(geom,-2), - anchors = select(geom,-1), + anchors = last(geom), type = geom[0] ) is_string(anchor)? ( diff --git a/beziers.scad b/beziers.scad index 79fe650..fb49adc 100644 --- a/beziers.scad +++ b/beziers.scad @@ -769,7 +769,7 @@ function path_to_bezier(path, closed=false, tangents, uniform=false, size, relsi ) assert(min(sizevect)>0, "Size and relsize must be greater than zero") [ - for(i=[0:lastpt-1]) + for(i=[0:1:lastpt-1]) let( first = path[i], second = select(path,i+1), diff --git a/debug.scad b/debug.scad index 6e4bdae..fccc6c9 100644 --- a/debug.scad +++ b/debug.scad @@ -377,7 +377,7 @@ module show_anchors(s=10, std=true, custom=true) { } } if (custom) { - for (anchor=select($parent_geom,-1)) { + for (anchor=last($parent_geom)) { attach(anchor[0]) { if(two_d) { anchor_arrow2d(s, color="cyan"); diff --git a/gears.scad b/gears.scad index 5bff0fc..52ea9bd 100644 --- a/gears.scad +++ b/gears.scad @@ -960,11 +960,11 @@ function bevel_gear( each apply(xflip() * zrot(360*tooth/teeth) * m, path3d(profile)) ] ], - thickness = abs(verts1[0][0].z - select(verts1,-1)[0].z), + thickness = abs(verts1[0][0].z - last(verts1)[0].z), vertices = [for (x=verts1) down(thickness/2, p=reverse(x))], sides_vnf = vnf_vertex_array(vertices, caps=false, col_wrap=true, reverse=true), - top_verts = select(vertices,-1), - bot_verts = select(vertices,0), + top_verts = last(vertices), + bot_verts = vertices[0], gear_pts = len(top_verts), face_pts = gear_pts / teeth, top_faces =[ @@ -1431,8 +1431,8 @@ function worm_gear( ) ] ], - top_verts = select(profiles,-1), - bot_verts = select(profiles,0), + top_verts = last(profiles), + bot_verts = profiles[0], face_pts = len(tooth_profile), gear_pts = face_pts * teeth, top_faces =[ diff --git a/hull.scad b/hull.scad index 5658bec..a7b73c7 100644 --- a/hull.scad +++ b/hull.scad @@ -158,7 +158,7 @@ function _hull_collinear(points) = // %polyhedron(points=pts, faces=faces); function hull3d_faces(points) = assert(is_path(points,3),"Invalid input to hull3d_faces") - len(points) < 3 ? list_range(len(points)) + len(points) < 3 ? range(len(points)) : let ( // start with a single non-collinear triangle tri = noncollinear_triple(points, error=false) ) diff --git a/joiners.scad b/joiners.scad index 8c0625d..2dd8175 100644 --- a/joiners.scad +++ b/joiners.scad @@ -968,7 +968,7 @@ module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1 ); assert(fullpath[4].y < fullpath[3].y, "Pin is too wide for its length"); - snapmargin = -snap + select(sidepath,-1).x;// - compression; + snapmargin = -snap + last(sidepath).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); diff --git a/math.scad b/math.scad index c63efb8..2297a29 100644 --- a/math.scad +++ b/math.scad @@ -495,7 +495,7 @@ function rand_int(minval, maxval, N, seed=undef) = function gaussian_rands(mean, stddev, N=1, seed=undef) = assert( is_finite(mean+stddev+N) && (is_undef(seed) || is_finite(seed) ), "Input must be finite numbers.") let(nums = is_undef(seed)? rands(0,1,N*2) : rands(0,1,N*2,seed)) - [for (i = list_range(N)) mean + stddev*sqrt(-2*ln(nums[i*2]))*cos(360*nums[i*2+1])]; + [for (i = range(N)) mean + stddev*sqrt(-2*ln(nums[i*2]))*cos(360*nums[i*2+1])]; // Function: log_rands() @@ -612,7 +612,7 @@ function _cumsum(v,_i=0,_acc=[]) = v, _i+1, concat( _acc, - [_i==0 ? v[_i] : select(_acc,-1)+v[_i]] + [_i==0 ? v[_i] : last(_acc) + v[_i]] ) ); @@ -905,7 +905,7 @@ function _back_substitute(R, b, x=[]) = : let( newvalue = len(x)==0 ? b[ind]/R[ind][ind] - : (b[ind]-select(R[ind],ind+1,-1) * x)/R[ind][ind] + : (b[ind]-list_tail(R[ind],ind+1) * x)/R[ind][ind] ) _back_substitute(R, b, concat([newvalue],x)); @@ -1635,7 +1635,7 @@ 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))) + : poly_mult(p[0], poly_mult(list_tail(p))) : assert( is_vector(p) && is_vector(q),"Invalid arguments to poly_mult") p*p==0 || q*q==0 @@ -1678,7 +1678,7 @@ function _poly_div(n,d,q) = /// or give epsilon for approximate zeros. function _poly_trim(p,eps=0) = let( nz = [for(i=[0:1:len(p)-1]) if ( !approx(p[i],0,eps)) i]) - len(nz)==0 ? [0] : select(p,nz[0],-1); + len(nz)==0 ? [0] : list_tail(p,nz[0]); // Function: poly_add() @@ -1719,7 +1719,7 @@ function poly_roots(p,tol=1e-14,error_bound=false) = let( p = _poly_trim(p,eps=0) ) assert( p!=[0], "Input polynomial cannot be zero." ) p[len(p)-1] == 0 ? // Strip trailing zero coefficients - let( solutions = poly_roots(select(p,0,-2),tol=tol, error_bound=error_bound)) + let( solutions = poly_roots(list_head(p),tol=tol, error_bound=error_bound)) (error_bound ? [ [[0,0], each solutions[0]], [0, each solutions[1]]] : [[0,0], each solutions]) : len(p)==1 ? (error_bound ? [[],[]] : []) : // Nonzero constant case has no solutions diff --git a/modular_hose.scad b/modular_hose.scad index 365bab3..0a878a1 100644 --- a/modular_hose.scad +++ b/modular_hose.scad @@ -169,9 +169,9 @@ module modular_hose(size, type, clearance=0, waist_len, anchor=BOTTOM, spin=0,or type=="segment"? concat(back(midlength,p=smallend),yflip(p=bigend)) : type=="small" || type=="ball" ? concat(back(midlength,p=smallend), - [[select(smallend,-1).x,0],[ smallend[0].x,0]]) + [[last(smallend).x,0],[ smallend[0].x,0]]) : concat( back(midlength,p=bigend), - [[select(bigend,-1).x,0],[ bigend[0].x,0]]); + [[last(bigend).x,0],[ bigend[0].x,0]]); bounds = pointlist_bounds(shape); center = mean(bounds); attachable(anchor,spin,orient,l=bounds[1].y-bounds[0].y, r=bounds[1].x) diff --git a/paths.scad b/paths.scad index 28858fa..d5ebae7 100644 --- a/paths.scad +++ b/paths.scad @@ -183,7 +183,7 @@ function path_length(path,closed=false) = function path_segment_lengths(path, closed=false) = [ for (i=[0:1:len(path)-2]) norm(path[i+1]-path[i]), - if (closed) norm(path[0]-select(path,-1)) + if (closed) norm(path[0]-last(path)) ]; @@ -482,13 +482,13 @@ function path_chamfer_and_rounding(path, closed, chamfer, rounding) = p2 = select(path,i), crn1 = select(corner_paths,i-1), crn2 = corner_paths[i], - l1 = norm(select(crn1,-1)-p1), + l1 = norm(last(crn1)-p1), l2 = norm(crn2[0]-p2), needed = l1 + l2, seglen = norm(p2-p1), check = assert(seglen >= needed, str("Path segment ",i," is too short to fulfill rounding/chamfering for the adjacent corners.")) ) each crn2, - if (!closed) select(path,-1) + if (!closed) last(path) ] ) deduplicate(out); @@ -783,16 +783,16 @@ function _path_fast_defragment(fragments, eps=EPSILON, _done=[]) = len(fragments)==0? _done : let( path = fragments[0], - endpt = select(path,-1), + endpt = last(path), extenders = [ for (i = [1:1:len(fragments)-1]) let( test1 = approx(endpt,fragments[i][0],eps=eps), - test2 = approx(endpt,select(fragments[i],-1),eps=eps) + test2 = approx(endpt,last(fragments[i]),eps=eps) ) if (test1 || test2) (test1? i : -1) ] ) len(extenders) == 1 && extenders[0] >= 0? _path_fast_defragment( fragments=[ - concat(select(path,0,-2),fragments[extenders[0]]), + concat(list_head(path),fragments[extenders[0]]), for (i = [1:1:len(fragments)-1]) if (i != extenders[0]) fragments[i] ], @@ -814,7 +814,7 @@ function _extreme_angle_fragment(seg, fragments, rightmost=true, eps=EPSILON) = for (i = idx(fragments)) let( fragment = fragments[i], fwdmatch = approx(seg[1], fragment[0], eps=eps), - bakmatch = approx(seg[1], select(fragment,-1), eps=eps) + bakmatch = approx(seg[1], last(fragment), eps=eps) ) [ fwdmatch, bakmatch, @@ -872,12 +872,12 @@ function assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0, // Found fragment is already closed [foundfrag, concat([path], remainder)] ) : let( - fragend = select(foundfrag,-1), + fragend = last(foundfrag), hits = [for (i = idx(path,e=-2)) if(approx(path[i],fragend,eps=eps)) i] ) hits? ( let( // Found fragment intersects with initial path - hitidx = select(hits,-1), + hitidx = last(hits), newpath = list_head(path,hitidx), newfrags = concat(len(newpath)>1? [newpath] : [], remainder), outpath = concat(slice(path,hitidx,-2), foundfrag) @@ -1239,17 +1239,17 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals { length = path_length(path,closed); distances = is_def(sp)? ( - is_def(n) && is_def(spacing)? list_range(s=sp, step=spacing, n=n) : - is_def(n)? list_range(s=sp, e=length, n=n) : - list_range(s=sp, step=spacing, e=length) + is_def(n) && is_def(spacing)? range(s=sp, step=spacing, n=n) : + is_def(n)? range(s=sp, e=length, n=n) : + range(s=sp, step=spacing, e=length) ) : is_def(n) && is_undef(spacing)? ( closed? - let(range=list_range(s=0,e=length, n=n+1)) list_head(range) : - list_range(s=0, e=length, n=n) + let(range=range(s=0,e=length, n=n+1)) list_head(range) : + range(s=0, e=length, n=n) ) : ( let( n = is_def(n)? n : floor(length/spacing)+(closed?0:1), - ptlist = list_range(s=0,step=spacing,n=n), + ptlist = range(s=0,step=spacing,n=n), listcenter = mean(ptlist) ) closed? sort([for(entry=ptlist) posmod(entry-listcenter,length)]) : @@ -1333,7 +1333,7 @@ function path_cut_points(path, dists, closed=false, direction=false) = function _path_cut_points(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[]) = dind == len(dists) ? result : let( - lastpt = len(result)==0? [] : select(result,-1)[0], // location of last cut point + lastpt = len(result)==0? [] : last(result)[0], // location of last cut point dpartial = len(result)==0? 0 : norm(lastpt-select(path,pind)), // remaining length in segment nextpoint = dists[dind] < dpartial+dtotal // Do we have enough length left on the current segment? ? [lerp(lastpt,select(path,pind),(dists[dind]-dtotal)/dpartial),pind] @@ -1419,7 +1419,7 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) = function path_cut(path,cutdist,closed) = is_num(cutdist) ? path_cut(path,[cutdist],closed) : assert(is_vector(cutdist)) - assert(select(cutdist,-1)0, "Cut distances must be strictly positive") let( cutlist = path_cut_points(path,cutdist,closed=closed), @@ -1433,7 +1433,7 @@ function path_cut(path,cutdist,closed) = cutlist[i][0]==cutlist[i+1][0] ? [] : [ if (!approx(cutlist[i][0], select(path,cutlist[i][1]))) cutlist[i][0], - each slice(path, cutlist[i][1], cutlist[i+1][1]), + each slice(path, cutlist[i][1], cutlist[i+1][1]-1), if (!approx(cutlist[i+1][0], select(path,cutlist[i+1][1]-1))) cutlist[i+1][0], ], [ @@ -1553,7 +1553,7 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length lerp(path[i],select(path,i+1), j/(add[i]+1)) ] ], - closed? [] : [select(path,-1)] + closed? [] : [last(path)] ); @@ -1575,7 +1575,7 @@ function path_length_fractions(path, closed=false) = norm(select(path,i+1)-path[i]) ], partial_len = cumsum(lengths), - total_len = select(partial_len,-1) + total_len = last(partial_len) ) partial_len / total_len; @@ -1603,10 +1603,10 @@ function resample_path(path, N, spacing, closed=false) = length = path_length(path,closed), N = is_def(N) ? N : round(length/spacing) + (closed?0:1), spacing = length/(closed?N:N-1), // Note: worried about round-off error, so don't include - distlist = list_range(closed?N:N-1,step=spacing), // last point when closed=false + distlist = range(closed?N:N-1,step=spacing), // last point when closed=false cuts = path_cut_points(path, distlist, closed=closed) ) - concat(subindex(cuts,0),closed?[]:[select(path,-1)]); // Then add last point here + concat(subindex(cuts,0),closed?[]:[last(path)]); // Then add last point here diff --git a/quaternions.scad b/quaternions.scad index eaf9e16..1dc7192 100644 --- a/quaternions.scad +++ b/quaternions.scad @@ -223,7 +223,7 @@ function Q_Cumulative(v, _i=0, _acc=[]) = v, _i+1, concat( _acc, - [_i==0 ? v[_i] : Q_Mul(v[_i], select(_acc,-1))] + [_i==0 ? v[_i] : Q_Mul(v[_i], last(_acc))] ) ); diff --git a/regions.scad b/regions.scad index 8b5ca6b..b76785c 100644 --- a/regions.scad +++ b/regions.scad @@ -758,7 +758,7 @@ function offset( // Note if !closed the last corner doesn't matter, so exclude it parallelcheck = (len(sharpcorners)==2 && !closed) || - all_defined(select(sharpcorners,closed?0:1,-1)) + all_defined(closed? sharpcorners : list_tail(sharpcorners)) ) assert(parallelcheck, "Path contains sequential parallel segments (either 180 deg turn or 0 deg turn") let( diff --git a/rounding.scad b/rounding.scad index cf95e7b..315986a 100644 --- a/rounding.scad +++ b/rounding.scad @@ -194,7 +194,7 @@ include // square = [[0,0],[1,0],[1,1],[0,1]]; // spiral = flatten(repeat(concat(square,reverse(square)),5)); // Squares repeat 10 times, forward and backward // squareind = [for(i=[0:9]) each [i,i,i,i]]; // Index of the square for each point -// z = list_range(40)*.2+squareind; +// z = range(40)*.2+squareind; // path3d = hstack(spiral,z); // 3D spiral // rounding = squareind/20; // // Setting k=1 means curvature won't be continuous, but curves are as round as possible @@ -423,13 +423,12 @@ function _rounding_offsets(edgespec,z_dir=1) = ) : edgetype == "circle"? radius==0? [] : [for(i=[1:N]) [radius*(cos(i*90/N)-1), z_dir*abs(radius)*sin(i*90/N)]] : /* smooth */ joint==0 ? [] : - select( - _bezcorner([[0,0],[0,z_dir*abs(joint)],[-joint,z_dir*abs(joint)]], k, $fn=N+2), - 1, -1 + list_tail( + _bezcorner([[0,0],[0,z_dir*abs(joint)],[-joint,z_dir*abs(joint)]], k, $fn=N+2) ) ) - quant(extra > 0? concat(offsets, [select(offsets,-1)+[0,z_dir*extra]]) : offsets, 1/1024); + quant(extra > 0? concat(offsets, [last(offsets)+[0,z_dir*extra]]) : offsets, 1/1024); @@ -670,7 +669,7 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false) new_result = [each select(result,loop?nextcut[1]:0,len(revresult)-1-firstcut[1]), each bezpath, nextcut[0], - if (!loop) each select(nextpath,nextcut[1],-1) + if (!loop) each list_tail(nextpath,nextcut[1]) ] ) i==len(paths)-(closed?1:2) @@ -900,7 +899,7 @@ function _make_offset_polyhedron(path,offsets, offset_type, flip_faces, quality, vertexcount=0, vertices=[], faces=[] )= offsetind==len(offsets)? ( let( - bottom = list_range(n=len(path),s=vertexcount), + bottom = range(n=len(path),s=vertexcount), oriented_bottom = !flip_faces? bottom : reverse(bottom) ) [vertices, concat(faces,[oriented_bottom])] ) : ( @@ -980,8 +979,8 @@ function offset_sweep( : 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"), + bottom_height = len(offsets_bot)==0 ? 0 : abs(last(offsets_bot)[1]) - struct_val(bottom,"extra"), + top_height = len(offsets_top)==0 ? 0 : abs(last(offsets_top)[1]) - struct_val(top,"extra"), height = one_defined([l,h,height], "l,h,height", dflt=u_add(bottom_height,top_height)), middle = height-bottom_height-top_height @@ -1129,7 +1128,7 @@ function os_mask(mask, out=false, extra,check_valid, quality, offset_maxstep, of let( points = ([for(pt=polygon_shift(mask,origin_index[0])) [xfactor*max(pt.x,0),-max(pt.y,0)]]) ) - os_profile(deduplicate(move(-points[1],p=select(points,1,-1))), extra,check_valid,quality,offset_maxstep,offset); + os_profile(deduplicate(move(-points[1],p=list_tail(points))), extra,check_valid,quality,offset_maxstep,offset); // Module: convex_offset_extrude() @@ -1248,8 +1247,8 @@ module convex_offset_extrude( offsets_top = _rounding_offsets(top, 1); // "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"); + bottom_height = len(offsets_bot)==0 ? 0 : abs(last(offsets_bot)[1]) - struct_val(bottom,"extra"); + top_height = len(offsets_top)==0 ? 0 : abs(last(offsets_top)[1]) - struct_val(top,"extra"); height = one_defined([l,h,height], "l,h,height", dflt=u_add(bottom_height,top_height)); assert(height>=0, "Height must be nonnegative"); @@ -1583,11 +1582,11 @@ function _stroke_end(width,left, right, spec) = cutright = cut[1], // Create updated paths taking into account clipping for end rotation newright = intright? - concat([pathclip[0]],select(right,pathclip[1],-1)) : - concat([pathextend],select(right,1,-1)), + concat([pathclip[0]],list_tail(right,pathclip[1])) : + concat([pathextend],list_tail(right)), newleft = !intright? - concat([pathclip[0]],select(left,pathclip[1],-1)) : - concat([pathextend],select(left,1,-1)), + concat([pathclip[0]],list_tail(left,pathclip[1])) : + concat([pathextend],list_tail(left)), // calculate corner angles, which are different when the cut is negative (outside corner) leftangle = cutleft>=0? vector_angle([newleft[1],newleft[0],newright[0]])/2 : @@ -1953,11 +1952,11 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b function bezier_patch_degenerate(patch, splinesteps=16, reverse=false) = assert(is_num(splinesteps), "splinesteps must be a number") let( - top_degen = patch[0][0] == select(patch[0],-1), - bot_degen = select(patch,-1)[0] == select(select(patch,-1),-1), - left_degen = patch[0][0] == select(patch,-1)[0], - right_degen = select(patch[0],-1) == select(select(patch,-1),-1), - samplepts = list_range(splinesteps+1)/splinesteps + top_degen = patch[0][0] == last(patch[0]), + bot_degen = last(patch)[0] == last(last(patch)), + left_degen = patch[0][0] == last(patch)[0], + right_degen = last(patch[0]) == last(last(patch)), + samplepts = range(splinesteps+1)/splinesteps ) top_degen && bot_degen && left_degen && right_degen ? // fully degenerate case [repeat([patch[0][0]],4), EMPTY_VNF] : @@ -1965,19 +1964,19 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false) = let( pts = bezier_points(subindex(patch,0), samplepts) ) - [[pts,pts,[pts[0]],[select(pts,-1)]], EMPTY_VNF] : + [[pts,pts,[pts[0]],[last(pts)]], EMPTY_VNF] : left_degen && right_degen ? // double degenerate case (sides) let( pts = bezier_points(patch[0], samplepts) ) - [[[pts[0]], [select(pts,-1)], pts, pts], EMPTY_VNF] : + [[[pts[0]], [last(pts)], pts, pts], EMPTY_VNF] : !top_degen && !bot_degen ? // non-degenerate case let( k=echo("non-degenerate case"), pts = bezier_patch_points(patch, samplepts, samplepts) ) [ - [subindex(pts,0), subindex(pts,len(pts)-1), pts[0], select(pts,-1)], + [subindex(pts,0), subindex(pts,len(pts)-1), pts[0], last(pts)], vnf_vertex_array(pts, reverse=reverse) ] : bot_degen ? // only bottom is degenerate @@ -1990,22 +1989,22 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false) = ] : // at this point top_degen is true // only top is degenerate let( - full_degen = patch[1][0] == select(patch[1],-1), - rowmax = full_degen ? list_range(splinesteps+1) : + full_degen = patch[1][0] == last(patch[1]), + rowmax = full_degen ? range(splinesteps+1) : [for(j=[0:splinesteps]) j<=splinesteps/2 ? 2*j : splinesteps], vbb=echo("single degenerate case"), bpatch = [for(i=[0:1:len(patch[0])-1]) bezier_points(subindex(patch,i), samplepts)], pts = [ [bpatch[0][0]], - for(j=[1:splinesteps]) bezier_points(subindex(bpatch,j), list_range(rowmax[j]+1)/rowmax[j]) + for(j=[1:splinesteps]) bezier_points(subindex(bpatch,j), range(rowmax[j]+1)/rowmax[j]) ], vnf = vnf_tri_array(pts, reverse=reverse) ) [ [ subindex(pts,0), - [for(row=pts) select(row,-1)], + [for(row=pts) last(row)], pts[0], - select(pts,-1), + last(pts), ], vnf ]; diff --git a/screws.scad b/screws.scad index 27c6c5c..1ce6ec0 100644 --- a/screws.scad +++ b/screws.scad @@ -1085,7 +1085,7 @@ function _ISO_thread_tolerance(diameter, pitch, internal=false, tolerance=undef) ], rangepts = [0.99, 1.4, 2.8, 5.6, 11.2, 22.4, 45, 90, 180, 300], - d_ind = floor(lookup(diameter,hstack(rangepts,list_range(len(rangepts))))), + d_ind = floor(lookup(diameter,hstack(rangepts,range(len(rangepts))))), avgd = sqrt(rangepts[d_ind]* rangepts[d_ind+1]), T_d2_6 = 90*pow(P, 0.4)*pow(avgd,0.1), diff --git a/shapes.scad b/shapes.scad index 1a2ed21..c0eba7c 100644 --- a/shapes.scad +++ b/shapes.scad @@ -1339,7 +1339,7 @@ module spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orie path = [ let(a = merids[0]) [0, sin(a)], for (a=merids) [cos(a), sin(a)], - let(a = select(merids,-1)) [0, sin(a)] + let(a = last(merids)) [0, sin(a)] ]; scale(r) rotate(180) rotate_extrude(convexity=2,$fn=sides) polygon(path); } else { @@ -1441,7 +1441,7 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or 1, ], offs = cumsum(meridians), - pc = select(offs,-1)-1, + pc = last(offs)-1, os = octa_steps * 2 ) [ for (i=[0:1:3]) [0, 1+(i+1)%4, 1+i], diff --git a/shapes2d.scad b/shapes2d.scad index 34f92ae..5ba5c2a 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -209,10 +209,10 @@ module stroke( assert(is_undef(endcap_angle2)||is_num(endcap_angle2)); assert(is_undef(joint_angle)||is_num(joint_angle)); - endcap_shape1 = _shape_path(endcap1, select(width,0), endcap_width1, endcap_length1, endcap_extent1); - endcap_shape2 = _shape_path(endcap2, select(width,-1), endcap_width2, endcap_length2, endcap_extent2); + endcap_shape1 = _shape_path(endcap1, width[0], endcap_width1, endcap_length1, endcap_extent1); + endcap_shape2 = _shape_path(endcap2, last(width), endcap_width2, endcap_length2, endcap_extent2); - trim1 = select(width,0) * first_defined([ + trim1 = width[0] * first_defined([ trim1, trim, (endcap1=="arrow")? endcap_length1-0.01 : (endcap1=="arrow2")? endcap_length1*3/4 : @@ -220,7 +220,7 @@ module stroke( ]); assert(is_num(trim1)); - trim2 = select(width,-1) * first_defined([ + trim2 = last(width) * first_defined([ trim2, trim, (endcap2=="arrow")? endcap_length2-0.01 : (endcap2=="arrow2")? endcap_length2*3/4 : @@ -244,8 +244,8 @@ module stroke( [lerp(width[epos[0]], width[(epos[0]+1)%len(width)], epos[1])] ); - start_vec = select(path,0) - select(path,1); - end_vec = select(path,-1) - select(path,-2); + start_vec = path[0] - path[1]; + end_vec = last(path) - select(path,-2); if (len(path[0]) == 2) { // Straight segments @@ -301,7 +301,7 @@ module stroke( } // Endcap2 - translate(select(path,-1)) { + translate(last(path)) { mat = is_undef(endcap_angle2)? rot(from=BACK,to=end_vec) : zrot(endcap_angle2); multmatrix(mat) polygon(endcap_shape2); @@ -402,9 +402,9 @@ module stroke( } // Endcap2 - translate(select(path,-1)) { - multmatrix(select(rotmats,-1)) { - $fn = select(sides,-1); + translate(last(path)) { + multmatrix(last(rotmats)) { + $fn = last(sides); if (is_undef(endcap_angle2)) { rotate_extrude(convexity=convexity) { right_half(planar=true) { @@ -413,7 +413,7 @@ module stroke( } } else { rotate([90,0,endcap_angle2]) { - linear_extrude(height=max(select(widths,-1),0.001), center=true, convexity=convexity) { + linear_extrude(height=max(last(widths),0.001), center=true, convexity=convexity) { polygon(endcap_shape2); } } @@ -791,7 +791,7 @@ function _turtle_command(command, parm, parm2, state, index) = chvec = !in_list(command,needvec) || is_vector(parm,2), chnum = !in_list(command,neednum) || is_num(parm), vec_or_num = !in_list(command,needeither) || (is_num(parm) || is_vector(parm,2)), - lastpt = select(state[path],-1) + lastpt = last(state[path]) ) assert(chvec,str("\"",command,"\" requires a vector parameter at index ",index)) assert(chnum,str("\"",command,"\" requires a numeric parameter at index ",index)) @@ -2339,8 +2339,8 @@ function mask2d_ogee(pattern, excess, anchor=CENTER, spin=0) = 0 ) ])), - tot_x = select(x,-1), - tot_y = select(y,-1), + tot_x = last(x), + tot_y = last(y), data = [ for (i=idx(pattern,step=2)) let( type = pattern[i], diff --git a/skin.scad b/skin.scad index e53def9..8275c51 100644 --- a/skin.scad +++ b/skin.scad @@ -444,8 +444,8 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close // Define this to be 1 if a profile is used on either side by a resampling method, zero otherwise. profile_resampled = [for(i=idx(profiles)) 1-( - i==0 ? method_type[0] * (closed? select(method_type,-1) : 1) : - i==len(profiles)-1 ? select(method_type,-1) * (closed ? select(method_type,-2) : 1) : + i==0 ? method_type[0] * (closed? last(method_type) : 1) : + i==len(profiles)-1 ? last(method_type) * (closed ? select(method_type,-2) : 1) : method_type[i] * method_type[i-1])], parts = search(1,[1,for(i=[0:1:len(profile_resampled)-2]) profile_resampled[i]!=profile_resampled[i+1] ? 1 : 0],0), plen = [for(i=idx(parts)) (i== len(parts)-1? len(refined_len) : parts[i+1]) - parts[i]], @@ -534,8 +534,8 @@ function _skin_core(profiles, caps) = prof1 = profiles[0] ) [[for (i=idx(prof1)) plens[0]-1-i]], secondcap = !caps[1] ? [] : let( - prof2 = select(profiles,-1), - eoff = sum(select(plens,0,-2)) + prof2 = last(profiles), + eoff = sum(list_head(plens)) ) [[for (i=idx(prof2)) eoff+i]] ) [vertices, concat(sidefaces,firstcap,secondcap)]; @@ -812,7 +812,7 @@ function _skin_tangent_match(poly1, poly2) = small = swap ? poly2 : poly1, curve_offset = centroid(small)-centroid(big), cutpts = [for(i=[0:len(small)-1]) _find_one_tangent(big, select(small,i,i+1),curve_offset=curve_offset)], - shift = select(cutpts,-1)+1, + shift = last(cutpts)+1, newbig = polygon_shift(big, shift), repeat_counts = [for(i=[0:len(small)-1]) posmod(cutpts[i]-select(cutpts,i-1),len(big))], newsmall = repeat_entries(small,repeat_counts) @@ -894,7 +894,7 @@ function associate_vertices(polygons, split, curpoly=0) = str("Split ",cursplit," at polygon ",curpoly," has invalid vertices. Must be in [0:",polylen-1,"]")) len(cursplit)==0 ? associate_vertices(polygons,split,curpoly+1) : let( - splitindex = sort(concat(list_range(polylen), cursplit)), + splitindex = sort(concat(range(polylen), cursplit)), newpoly = [for(i=[0:len(polygons)-1]) i<=curpoly ? select(polygons[i],splitindex) : polygons[i]] ) associate_vertices(newpoly, split, curpoly+1); @@ -970,10 +970,10 @@ function sweep(shape, transforms, closed=false, caps) = rtrans = reverse(transforms), vnfs = [ for (rgn=regions) each [ - for (path=select(rgn,0,-1)) + for (path=rgn) sweep(path, transforms, closed=closed, caps=false), if (fullcaps[0]) region_faces(rgn, transform=transforms[0], reverse=true), - if (fullcaps[1]) region_faces(rgn, transform=select(transforms,-1)), + if (fullcaps[1]) region_faces(rgn, transform=last(transforms)), ], ], vnf = vnf_merge(vnfs) @@ -1311,7 +1311,7 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi assert(is_integer(symmetry) && symmetry>0, "symmetry must be a positive integer") // let(shape = check_and_fix_path(shape,valid_dim=2,closed=true,name="shape")) assert(is_path(path), "input path is not a path") - assert(!closed || !approx(path[0],select(path,-1)), "Closed path includes start point at the end") + assert(!closed || !approx(path[0],last(path)), "Closed path includes start point at the end") let( path = path3d(path), caps = is_def(caps) ? caps : @@ -1350,13 +1350,13 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi // X axis. Similarly, in the closed==false case the desired and actual transformations can only differ in the twist, // so we can need to calculate the twist angle so we can apply a correction, which we distribute uniformly over the whole path. reference_rot = closed ? rotations[0] : - is_undef(last_normal) ? select(rotations,-1) : + is_undef(last_normal) ? last(rotations) : let( - last_tangent = select(tangents,-1), + last_tangent = last(tangents), lastynormal = last_normal - (last_normal * last_tangent) * last_tangent ) affine3d_frame_map(y=lastynormal, z=last_tangent), - mismatch = transpose(select(rotations,-1)) * reference_rot, + mismatch = transpose(last(rotations)) * reference_rot, correction_twist = atan2(mismatch[1][0], mismatch[0][0]), // Spread out this extra twist over the whole sweep so that it doesn't occur // abruptly as an artifact at the last step. @@ -1492,7 +1492,7 @@ function _ofs_vmap(ofs,closed=false) = ) [ for(entry=ofs[1]) _ofs_face_edge(entry,firstlen), - if (!closed) _ofs_face_edge(select(ofs[1],-1),firstlen,second=true) + if (!closed) _ofs_face_edge(last(ofs[1]),firstlen,second=true) ]; diff --git a/strings.scad b/strings.scad index be287de..147d558 100644 --- a/strings.scad +++ b/strings.scad @@ -335,7 +335,7 @@ function _str_find_last(str,pattern,sindex) = (sindex >=0 ? sindex : undef); function _str_find_all(str,pattern) = - pattern == "" ? list_range(len(str)) : + pattern == "" ? range(len(str)) : [for(i=[0:1:len(str)-len(pattern)]) if (_str_cmp(str,i,pattern)) i]; diff --git a/tests/test_arrays.scad b/tests/test_arrays.scad index 5b460e2..679f34c 100644 --- a/tests/test_arrays.scad +++ b/tests/test_arrays.scad @@ -29,12 +29,14 @@ test_select(); module test_slice() { - assert(slice([3,4,5,6,7,8,9], 3, 5) == [6,7]); - assert(slice([3,4,5,6,7,8,9], 2, -1) == [5,6,7,8,9]); - assert(slice([3,4,5,6,7,8,9], 1, 1) == []); - assert(slice([3,4,5,6,7,8,9], 6, -1) == [9]); - assert(slice([3,4,5,6,7,8,9], 2, -2) == [5,6,7,8]); - assert(slice([], 2, -2) == []); + l = [3,4,5,6,7,8,9]; + assert(slice(l, 5, 6) == [8,9]); + assert(slice(l, 5, 8) == [8,9]); + assert(slice(l, 5, 2) == []); + assert(slice(l, -3, -1) == [7,8,9]); + assert(slice(l, 3, 3) == [6]); + assert(slice(l, 4) == [7,8,9]); + assert(slice(l, -2) == [8,9]); } test_slice(); @@ -131,19 +133,36 @@ module test_repeat() { test_repeat(); -module test_list_range() { - assert(list_range(4) == [0,1,2,3]); - assert(list_range(n=4, step=2) == [0,2,4,6]); - assert(list_range(n=4, s=3, step=3) == [3,6,9,12]); - assert(list_range(e=3) == [0,1,2,3]); - assert(list_range(e=6, step=2) == [0,2,4,6]); - assert(list_range(s=3, e=5) == [3,4,5]); - assert(list_range(s=3, e=8, step=2) == [3,5,7]); - assert(list_range(s=4, e=8, step=2) == [4,6,8]); - assert(list_range(e=4, n=3) == [0,2,4]); - assert(list_range(n=4, s=[3,4], step=[2,3]) == [[3,4], [5,7], [7,10], [9,13]]); +module test_range() { + assert(range(4) == [0,1,2,3]); + assert(range(n=4, step=2) == [0,2,4,6]); + assert(range(n=4, s=3, step=3) == [3,6,9,12]); + assert(range(e=3) == [0,1,2,3]); + assert(range(e=6, step=2) == [0,2,4,6]); + assert(range(s=3, e=5) == [3,4,5]); + assert(range(s=3, e=8, step=2) == [3,5,7]); + assert(range(s=4, e=8, step=2) == [4,6,8]); + assert(range(e=4, n=3) == [0,2,4]); + assert(range(n=4, s=[3,4], step=[2,3]) == [[3,4], [5,7], [7,10], [9,13]]); } -test_list_range(); +test_range(); + + +module test_rangex() { + assert_equal(rangex(4), [0,1,2,3]); + assert_approx(rangex(5,e=1), [0, 0.2, 0.4, 0.6, 0.8]); + assert_equal(rangex(n=4, step=2), [0,2,4,6]); + assert_approx(rangex(n=4, step=0.25), [0,0.25,0.5,0.75]); + assert_equal(rangex(n=4, s=3, step=3), [3,6,9,12]); + assert_equal(rangex(e=3), [0,1,2]); + assert_equal(rangex(e=6, step=2), [0,2,4]); + assert_equal(rangex(s=3, e=5), [3,4]); + assert_equal(rangex(s=3, e=8, step=2), [3,5,7]); + assert_equal(rangex(s=4, e=8, step=2), [4,6]); + assert_equal(rangex(e=6, n=3), [0,2,4]); + assert_equal(rangex(n=4, s=[3,4], step=[2,3]), [[3,4], [5,7], [7,10], [9,13]]); +} +test_rangex(); module test_reverse() { @@ -303,7 +322,7 @@ test_enumerate(); module test_shuffle() { - nums1 = [for (i=list_range(100)) i]; + nums1 = [for (i=range(100)) i]; nums2 = shuffle(nums1,33); nums3 = shuffle(nums2,99); assert(sort(nums2)==nums1); diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad index 817323d..5eb4b68 100644 --- a/tests/test_geometry.scad +++ b/tests/test_geometry.scad @@ -481,7 +481,7 @@ module test_circle_3points() { radii = rands(10,100,count,seed_value=390); angles = rands(0,360,count,seed_value=699); // 2D tests. - for(i = list_range(count)) { + for(i = range(count)) { cp = select(coords,i,i+1); r = radii[i]; angs = sort(select(angles,i,i+2)); @@ -506,7 +506,7 @@ module test_circle_3points() { assert(approx(res[2], UP)); } } - for(i = list_range(count)) { + for(i = range(count)) { cp = select(coords,i,i+1); r = radii[i]; angs = sort(select(angles,i,i+2)); @@ -532,7 +532,7 @@ module test_circle_3points() { } } // 3D tests. - for(i = list_range(count)) { + for(i = range(count)) { cp = select(coords,i,i+2); r = radii[i]; nrm = unit(select(coords,i+10,i+12)); @@ -559,7 +559,7 @@ module test_circle_3points() { assert(approx(res[2], n)); } } - for(i = list_range(count)) { + for(i = range(count)) { cp = select(coords,i,i+2); r = radii[i]; nrm = unit(select(coords,i+10,i+12)); @@ -984,8 +984,8 @@ module test_pointlist_bounds() { module test_closest_point() { - ptlist = [for (i=list_range(100)) rands(-100,100,2,seed_value=8463)]; - testpts = [for (i=list_range(100)) rands(-100,100,2,seed_value=6834)]; + ptlist = [for (i=range(100)) rands(-100,100,2,seed_value=8463)]; + testpts = [for (i=range(100)) rands(-100,100,2,seed_value=6834)]; for (pt = testpts) { pidx = closest_point(pt,ptlist); dists = [for (p=ptlist) norm(pt-p)]; @@ -997,8 +997,8 @@ module test_closest_point() { module test_furthest_point() { - ptlist = [for (i=list_range(100)) rands(-100,100,2,seed_value=8463)]; - testpts = [for (i=list_range(100)) rands(-100,100,2,seed_value=6834)]; + ptlist = [for (i=range(100)) rands(-100,100,2,seed_value=8463)]; + testpts = [for (i=range(100)) rands(-100,100,2,seed_value=6834)]; for (pt = testpts) { pidx = furthest_point(pt,ptlist); dists = [for (p=ptlist) norm(pt-p)]; diff --git a/tests/test_math.scad b/tests/test_math.scad index 103be26..e943756 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -1122,7 +1122,7 @@ module test_real_roots(){ // Wilkinson polynomial is a nasty test: assert_approx( sort(real_roots(poly_mult([[1,-1],[1,-2],[1,-3],[1,-4],[1,-5],[1,-6],[1,-7],[1,-8],[1,-9],[1,-10]]))), - list_range(n=10,s=1)); + range(n=10,s=1)); assert_equal(real_roots([3]), []); assert_equal(real_roots(poly_mult([[1,-2,5],[12,-24,24],[-2, -12, -20],[1,-10,50]])),[]); assert_equal(real_roots(poly_mult([[1,-2,5],[12,-24,24],[-2, -12, -20],[1,-10,50],[1,0,0]])),[0,0]); diff --git a/threading.scad b/threading.scad index ff5ee96..50e1336 100644 --- a/threading.scad +++ b/threading.scad @@ -228,13 +228,13 @@ module trapezoidal_threaded_rod( zrot(i*360/starts, p=vnf_vertex_array(thread_verts, reverse=left_handed)), for (i=[0:1:starts-1]) let( rmat = zrot(i*360/starts), - pts = deduplicate(select(thread_verts[0], 0, len(prof3d)+1)), + pts = deduplicate(list_head(thread_verts[0], len(prof3d)+1)), faces = [for (i=idx(pts,e=-2)) [0, i+1, i]], rfaces = left_handed? [for (x=faces) reverse(x)] : faces ) [apply(rmat,pts), rfaces], for (i=[0:1:starts-1]) let( rmat = zrot(i*360/starts), - pts = deduplicate(select(last(thread_verts), -len(prof3d)-2, -1)), + pts = deduplicate(list_tail(last(thread_verts), -len(prof3d)-2)), faces = [for (i=idx(pts,e=-2)) [len(pts)-1, i, i+1]], rfaces = left_handed? [for (x=faces) reverse(x)] : faces ) [apply(rmat,pts), rfaces] diff --git a/turtle3d.scad b/turtle3d.scad index c0631d0..7f1e4c5 100644 --- a/turtle3d.scad +++ b/turtle3d.scad @@ -479,7 +479,7 @@ function _tupdate(state, tran, pretran) = [ concat(state[0],tran), concat(state[1],pretran), - each select(state,2,-1) + each list_tail(state,2) ]; function _turtle3d_command(command, parm, parm2, state, index) = @@ -504,8 +504,8 @@ function _turtle3d_command(command, parm, parm2, state, index) = chnum = (!in_list(command,neednum) || is_num(parm)) && (!in_list(command,numornothing) || (is_undef(parm) || is_num(parm))), chtran = !in_list(command,needtran) || is_matrix(parm,4,4), - lastT = select(state[trlist],-1), - lastPre = select(state[prelist],-1), + lastT = last(state[trlist]), + lastPre = last(state[prelist]), lastpt = apply(lastT,[0,0,0]) ) assert(chvec,str("\"",command,"\" requires a 3d vector parameter at index ",index)) diff --git a/vnf.scad b/vnf.scad index 707f1c4..7635cf5 100644 --- a/vnf.scad +++ b/vnf.scad @@ -316,7 +316,7 @@ function vnf_vertex_array( ) ] ) - rows<=1 || cols<=1 ? vnf : + rows<=1 || cols<=1 ? vnf : vnf_merge(cleanup=true, [ vnf, [ verts, @@ -396,7 +396,7 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp=[0,0,0], anchor="origin" // Usage: // vnf_wireframe(vnf, ); // Description: -// Given a VNF, creates a wire frame ball-and-stick model of the polyhedron with a cylinder for each edge and a sphere at each vertex. +// Given a VNF, creates a wire frame ball-and-stick model of the polyhedron with a cylinder for each edge and a sphere at each vertex. // Arguments: // vnf = A vnf structure // r|d = radius or diameter of the cylinders forming the wire frame. Default: r=1 @@ -404,12 +404,12 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp=[0,0,0], anchor="origin" // $fn=32; // ball = sphere(r=20, $fn=6); // vnf_wireframe(ball,d=1); -// Example: +// Example: // include // $fn=32; // cube_oct = regular_polyhedron_info("vnf", name="cuboctahedron", or=20); // vnf_wireframe(cube_oct); -// Example: The spheres at the vertex are imperfect at aligning with the cylinders, so especially at low $fn things look prety ugly. This is normal. +// Example: The spheres at the vertex are imperfect at aligning with the cylinders, so especially at low $fn things look prety ugly. This is normal. // include // $fn=8; // octahedron = regular_polyhedron_info("vnf", name="octahedron", or=20); @@ -423,7 +423,7 @@ module vnf_wireframe(vnf, r, d) ]); for (e=edges) extrude_from_to(vertex[e[0]],vertex[e[1]]) circle(r=r); move_copies(vertex) sphere(r=r); -} +} // Function: vnf_volume() @@ -451,7 +451,7 @@ function vnf_volume(vnf) = // no holes; otherwise the results are undefined. // Divide the solid up into tetrahedra with the origin as one vertex. The centroid of a tetrahedron is the average of its vertices. -// The centroid of the total is the volume weighted average. +// The centroid of the total is the volume weighted average. function vnf_centroid(vnf) = let( verts = vnf[0], @@ -481,7 +481,7 @@ function _triangulate_planar_convex_polygons(polys) = tris = [for (poly=polys) if (len(poly)==3) poly], bigs = [for (poly=polys) if (len(poly)>3) poly], newtris = [for (poly=bigs) select(poly,-2,0)], - newbigs = [for (poly=bigs) select(poly,0,-2)], + newbigs = [for (poly=bigs) list_head(poly)], newtris2 = _triangulate_planar_convex_polygons(newbigs), outtris = concat(tris, newtris, newtris2) ) outtris; @@ -626,7 +626,7 @@ function vnf_bend(vnf,r,d,axis="Z") = // bad edges and vertices, overlaid on a transparent gray polyhedron of the VNF. // . // Currently checks for these problems: -// Type | Color | Code | Message +// Type | Color | Code | Message // ------- | -------- | ------------ | --------------------------------- // WARNING | Yellow | BIG_FACE | Face has more than 3 vertices, and may confuse CGAL. // WARNING | Brown | NULL_FACE | Face has zero area. @@ -695,7 +695,7 @@ function vnf_bend(vnf,r,d,axis="Z") = // move([75,35,30],p=vnf_triangulate(linear_sweep(square(100,center=true), height=100))) // ]); // vnf_validate(vnf,size=2,check_isects=true); -// Example: HOLE_EDGE Errors; Edges Adjacent to Holes. +// Example: HOLE_EDGE Errors; Edges Adjacent to Holes. // vnf = skin([ // path3d(regular_ngon(n=4, d=100),0), // path3d(regular_ngon(n=5, d=100),100) From 257f1ec1f8144bb4cb5b2241fc88e5c8d8dc3092 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 30 Mar 2021 15:27:57 -0700 Subject: [PATCH 02/18] Setup Problem Matcher. Added some deliberate docs errors to test with. --- .github/openscad_docsgen.json | 21 +++++++++++++++++++++ .github/workflows/main.yml | 1 + .gitignore | 1 + math.scad | 9 +++++++-- 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 .github/openscad_docsgen.json diff --git a/.github/openscad_docsgen.json b/.github/openscad_docsgen.json new file mode 100644 index 0000000..813bec6 --- /dev/null +++ b/.github/openscad_docsgen.json @@ -0,0 +1,21 @@ +{ + "problemMatcher": [ + { + "owner": "openscad_docsgen", + "pattern": [ + { + "regexp": "^!! (WARNING|ERROR) at ([^:]+):(\\d+): (.*)$", + "severity": 1, + "file": 2, + "line": 3, + "message": 4 + }, + { + "regexp": "^ (.*)$", + "message": 1, + "loop": true + } + ] + } + ] +} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 88f0279..69eed65 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,6 +56,7 @@ jobs: - name: Generating Docs run: | cd $GITHUB_WORKSPACE + echo "::add-matcher::.github/openscad_docsgen.json" export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) openscad-docsgen -Tm *.scad diff --git a/.gitignore b/.gitignore index c1a80aa..8289a30 100644 --- a/.gitignore +++ b/.gitignore @@ -109,4 +109,5 @@ ENV/ foo.scad BOSL2.wiki /ref/ +docsgen_report.json diff --git a/math.scad b/math.scad index 2297a29..10e8544 100644 --- a/math.scad +++ b/math.scad @@ -33,7 +33,7 @@ NAN = acos(2); // sqr(x); // Description: // If given a number, returns the square of that number, -// If given a vector, returns the sum-of-squares/dot product of the vector elements. +// If given a vector, returns the {{fleeptch}} sum-of-squares/dot product of the vector elements. // If given a matrix, returns the matrix multiplication of the matrix with itself. // Examples: // sqr(3); // Returns: 9 @@ -63,8 +63,9 @@ function log2(x) = // Function: hypot() // Usage: // l = hypot(x,y,); +// See Also: cthulhoo() // Description: -// Calculate hypotenuse length of a 2D or 3D triangle. +// Calculate hypotenuse length of {{gurptch}} a 2D or 3D triangle. // Arguments: // x = Length on the X axis. // y = Length on the Y axis. @@ -80,6 +81,8 @@ function hypot(x,y,z=0) = // Function: factorial() // Usage: // x = factorial(n,); +// See Also: +// tiddeebowl() // Description: // Returns the factorial of the given integer value, or n!/d! if d is given. // Arguments: @@ -106,6 +109,8 @@ function factorial(n,d=0) = // x = binomial(3); // Returns: [1,3,3,1] // y = binomial(4); // Returns: [1,4,6,4,1] // z = binomial(6); // Returns: [1,6,15,20,15,6,1] +// Example(2D): +// fleegul_numan(]); function binomial(n) = assert( is_int(n) && n>0, "Input is not an integer greater than 0.") [for( c = 1, i = 0; From 527215fa27a9d85044e52230956c9f57afcc7dca Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 30 Mar 2021 16:10:35 -0700 Subject: [PATCH 03/18] Tweak Problem Matcher. --- .github/openscad_docsgen.json | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/openscad_docsgen.json b/.github/openscad_docsgen.json index 813bec6..3520cfd 100644 --- a/.github/openscad_docsgen.json +++ b/.github/openscad_docsgen.json @@ -1,14 +1,13 @@ { "problemMatcher": [ { - "owner": "openscad_docsgen", + "owner": "docsgen_script", "pattern": [ { - "regexp": "^!! (WARNING|ERROR) at ([^:]+):(\\d+): (.*)$", + "regexp": "^!! (WARNING|ERROR) at ([^:]+):(\\d+): Failed OpenSCAD script:$", "severity": 1, "file": 2, - "line": 3, - "message": 4 + "line": 3 }, { "regexp": "^ (.*)$", @@ -16,6 +15,18 @@ "loop": true } ] + }, + { + "owner": "docsgen", + "pattern": [ + { + "regexp": "^!! (WARNING|ERROR) at ([^:]+):(\\d+): (.*[^:])$", + "severity": 1, + "file": 2, + "line": 3, + "message": 4 + } + ] } ] } From 1745dbbb7faa71ad2c9ef58e67e75b3be05fbc9f Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 30 Mar 2021 16:13:29 -0700 Subject: [PATCH 04/18] slice() cleanup tweak. --- vnf.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnf.scad b/vnf.scad index 7635cf5..9c05e0f 100644 --- a/vnf.scad +++ b/vnf.scad @@ -481,7 +481,7 @@ function _triangulate_planar_convex_polygons(polys) = tris = [for (poly=polys) if (len(poly)==3) poly], bigs = [for (poly=polys) if (len(poly)>3) poly], newtris = [for (poly=bigs) select(poly,-2,0)], - newbigs = [for (poly=bigs) list_head(poly)], + newbigs = [for (poly=bigs) select(poly,0,-2)], newtris2 = _triangulate_planar_convex_polygons(newbigs), outtris = concat(tris, newtris, newtris2) ) outtris; From 23372f2b2ee0b3542ee600b5b53bd28b04060f8e Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 30 Mar 2021 16:44:47 -0700 Subject: [PATCH 05/18] Removed delibrate docs errors. --- math.scad | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/math.scad b/math.scad index 10e8544..2297a29 100644 --- a/math.scad +++ b/math.scad @@ -33,7 +33,7 @@ NAN = acos(2); // sqr(x); // Description: // If given a number, returns the square of that number, -// If given a vector, returns the {{fleeptch}} sum-of-squares/dot product of the vector elements. +// If given a vector, returns the sum-of-squares/dot product of the vector elements. // If given a matrix, returns the matrix multiplication of the matrix with itself. // Examples: // sqr(3); // Returns: 9 @@ -63,9 +63,8 @@ function log2(x) = // Function: hypot() // Usage: // l = hypot(x,y,); -// See Also: cthulhoo() // Description: -// Calculate hypotenuse length of {{gurptch}} a 2D or 3D triangle. +// Calculate hypotenuse length of a 2D or 3D triangle. // Arguments: // x = Length on the X axis. // y = Length on the Y axis. @@ -81,8 +80,6 @@ function hypot(x,y,z=0) = // Function: factorial() // Usage: // x = factorial(n,); -// See Also: -// tiddeebowl() // Description: // Returns the factorial of the given integer value, or n!/d! if d is given. // Arguments: @@ -109,8 +106,6 @@ function factorial(n,d=0) = // x = binomial(3); // Returns: [1,3,3,1] // y = binomial(4); // Returns: [1,4,6,4,1] // z = binomial(6); // Returns: [1,6,15,20,15,6,1] -// Example(2D): -// fleegul_numan(]); function binomial(n) = assert( is_int(n) && n>0, "Input is not an integer greater than 0.") [for( c = 1, i = 0; From 21a8ec04ec6aefcb33501ace63efa4c014cafa04 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 30 Mar 2021 16:50:30 -0700 Subject: [PATCH 06/18] Updated docsgen workflow for Problem Matcher --- .github/workflows/docsgen.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docsgen.yml b/.github/workflows/docsgen.yml index bb854ca..dc46a53 100644 --- a/.github/workflows/docsgen.yml +++ b/.github/workflows/docsgen.yml @@ -36,6 +36,7 @@ jobs: run: | cd $GITHUB_WORKSPACE export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) + echo "::add-matcher::.github/openscad_docsgen.json" openscad-docsgen -m -i -t -c *.scad cd BOSL2.wiki git config user.name github-actions From 36fed2c670628a3cb8d4d404315b90ec58065b46 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 31 Mar 2021 01:57:07 +0000 Subject: [PATCH 07/18] Bump release version. --- version.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.scad b/version.scad index 14eed9e..d38bcfa 100644 --- a/version.scad +++ b/version.scad @@ -6,7 +6,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,597]; +BOSL_VERSION = [2,0,598]; // Section: BOSL Library Version Functions From dd1396704926332d0d835962e0e57d95113551f5 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Fri, 2 Apr 2021 16:59:29 -0400 Subject: [PATCH 08/18] Cleanup of path_resample, add tests for round_corners, fix bezier_curve and arc to always return n points for either endpoint setting. --- beziers.scad | 8 +++++--- paths.scad | 36 +++++++++++++++++++++--------------- rounding.scad | 2 +- shapes2d.scad | 4 ++-- tests/test_shapes2d.scad | 2 +- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/beziers.scad b/beziers.scad index fb49adc..721d8cf 100644 --- a/beziers.scad +++ b/beziers.scad @@ -435,9 +435,11 @@ function bezier_curvature(curve, u) = // bez = [[0,0], [5,15], [40,20], [60,-15], [80,0]]; // move_copies(bezier_curve(bez, 8)) sphere(r=1.5, $fn=12); // trace_bezier(bez, N=len(bez)-1); -function bezier_curve(curve,n,endpoint) = [each bezier_points(curve, [0:1/n:(n-0.5)/n]), - if (endpoint) curve[len(curve)-1] - ]; +function bezier_curve(curve,n,endpoint=true) = + [ + each bezier_points(curve, rangex(endpoint?n-1:n,0,1)), + if (endpoint) last(curve) + ]; // Function: bezier_segment_closest_point() // Usage: diff --git a/paths.scad b/paths.scad index d5ebae7..e7f765c 100644 --- a/paths.scad +++ b/paths.scad @@ -1238,15 +1238,17 @@ module path_extrude(path, convexity=10, clipsize=100) { module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=false) { length = path_length(path,closed); - distances = is_def(sp)? ( - is_def(n) && is_def(spacing)? range(s=sp, step=spacing, n=n) : - is_def(n)? range(s=sp, e=length, n=n) : - range(s=sp, step=spacing, e=length) - ) : is_def(n) && is_undef(spacing)? ( - closed? - let(range=range(s=0,e=length, n=n+1)) list_head(range) : - range(s=0, e=length, n=n) - ) : ( + distances = + is_def(sp)? ( // Start point given + is_def(n) && is_def(spacing)? range(s=sp, step=spacing, n=n) : + is_def(n)? range(s=sp, e=length, n=n) : + range(s=sp, step=spacing, e=length) + ) + : is_def(n) && is_undef(spacing)? ( let(a=echo(n=n)) // N alone given + closed ? rangex(s=0, e=length, n=n) + : range(s=0, e=length, n=n) + ) + : ( // No start point and spacing is given, N maybe given let( n = is_def(n)? n : floor(length/spacing)+(closed?0:1), ptlist = range(s=0,step=spacing,n=n), @@ -1600,13 +1602,17 @@ function resample_path(path, N, spacing, closed=false) = assert(num_defined([N,spacing])==1,"Must define exactly one of N and spacing") assert(is_bool(closed)) let( - length = path_length(path,closed), - N = is_def(N) ? N : round(length/spacing) + (closed?0:1), - spacing = length/(closed?N:N-1), // Note: worried about round-off error, so don't include - distlist = range(closed?N:N-1,step=spacing), // last point when closed=false - cuts = path_cut_points(path, distlist, closed=closed) + length = path_length(path,closed), + // In the open path case decrease N by 1 so that we don't try to get + // path_cut to return the endpoint (which might fail due to rounding) + // Add last point later + N = is_def(N) ? N-(closed?0:1) : round(length/spacing), + distlist = rangex(N,e=length), + cuts = path_cut_points(path, distlist, closed=closed) ) - concat(subindex(cuts,0),closed?[]:[last(path)]); // Then add last point here + [ each subindex(cuts,0), + if (!closed) last(path) // Then add last point here + ]; diff --git a/rounding.scad b/rounding.scad index 315986a..815e220 100644 --- a/rounding.scad +++ b/rounding.scad @@ -346,7 +346,7 @@ function _bezcorner(points, parm) = ] : _smooth_bez_fill(points,parm), N = max(3,$fn>0 ?$fn : ceil(bezier_segment_length(P)/$fs)) ) - bezier_curve(P,N,endpoint=true); + bezier_curve(P,N+1,endpoint=true); function _chamfcorner(points, parm) = let( diff --git a/shapes2d.scad b/shapes2d.scad index 5ba5c2a..e14467e 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -533,7 +533,7 @@ module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) { function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true) = assert(is_bool(endpoint)) !endpoint ? assert(!wedge, "endpoint cannot be false if wedge is true") - list_head(arc(N,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true)) : + list_head(arc(N+1,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true)) : assert(is_undef(N) || is_integer(N), "Number of points must be an integer") // First try for 2D arc specified by width and thickness is_def(width) && is_def(thickness)? ( @@ -1875,7 +1875,7 @@ function reuleaux_polygon(N=3, r, d, anchor=CENTER, spin=0) = sa = ca + 180 + (90/N), ea = ca + 180 - (90/N), cp = polar_to_xy(r, ca) - ) each arc(N=ssegs, r=slen, cp=cp, angle=[sa,ea], endpoint=false) + ) each arc(N=ssegs-1, r=slen, cp=cp, angle=[sa,ea], endpoint=false) ], anchors = [ for (i = [0:1:N-1]) let( diff --git a/tests/test_shapes2d.scad b/tests/test_shapes2d.scad index c13a33d..e31f022 100644 --- a/tests/test_shapes2d.scad +++ b/tests/test_shapes2d.scad @@ -40,7 +40,7 @@ test_turtle(); module test_arc() { assert_approx(arc(N=8, d=100, angle=135, cp=[10,10]), [[60,10],[57.1941665154,26.5139530978],[49.0915741234,41.1744900929],[36.6016038258,52.3362099614],[21.1260466978,58.7463956091],[4.40177619483,59.6856104947],[-11.6941869559,55.0484433951],[-25.3553390593,45.3553390593]]); - assert_approx(arc(N=8, d=100, angle=135, cp=[10,10],endpoint=false), [[60,10],[57.1941665154,26.5139530978],[49.0915741234,41.1744900929],[36.6016038258,52.3362099614],[21.1260466978,58.7463956091],[4.40177619483,59.6856104947],[-11.6941869559,55.0484433951]]); + assert_approx(arc(N=8, d=100, angle=135, cp=[10,10],endpoint=false), [[60,10],[57.8470167866,24.5142338627],[51.5734806151,37.778511651],[41.7196642082,48.6505226681],[29.1341716183,56.1939766256],[14.9008570165,59.7592363336],[0.245483899194,59.0392640202],[-13.5698368413,54.0960632174]]); assert_approx(arc(N=8, d=100, angle=[45,225], cp=[10,10]), [[45.3553390593,45.3553390593],[26.5139530978,57.1941665154],[4.40177619483,59.6856104947],[-16.6016038258,52.3362099614],[-32.3362099614,36.6016038258],[-39.6856104947,15.5982238052],[-37.1941665154,-6.51395309776],[-25.3553390593,-25.3553390593]]); assert_approx(arc(N=8, d=100, start=45, angle=135, cp=[10,10]), [[45.3553390593,45.3553390593],[31.6941869559,55.0484433951],[15.5982238052,59.6856104947],[-1.12604669782,58.7463956091],[-16.6016038258,52.3362099614],[-29.0915741234,41.1744900929],[-37.1941665154,26.5139530978],[-40,10]]); assert_approx(arc(N=8, d=100, start=45, angle=-90, cp=[10,10]), [[45.3553390593,45.3553390593],[52.3362099614,36.6016038258],[57.1941665154,26.5139530978],[59.6856104947,15.5982238052],[59.6856104947,4.40177619483],[57.1941665154,-6.51395309776],[52.3362099614,-16.6016038258],[45.3553390593,-25.3553390593]]); From 194f4a5b1fccac777e22502cfd8f4385d850b168 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Fri, 2 Apr 2021 17:00:58 -0400 Subject: [PATCH 09/18] rounding tests --- tests/test_rounding.scad | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/test_rounding.scad diff --git a/tests/test_rounding.scad b/tests/test_rounding.scad new file mode 100644 index 0000000..c3ebfb0 --- /dev/null +++ b/tests/test_rounding.scad @@ -0,0 +1,42 @@ +include <../std.scad> +include <../rounding.scad> + +module test_round_corners() { + + test1 = turtle(["move", 10, "move", 10, "left", 30, "move", 17, "left", 155, "move", 10, "right", 90, "move", 10,"left", 90, "move", 30]); + test2 = turtle(["move", 20, "left", 30, "move", 17, "left", 155, "move", 10, "right", 90, "move", 10,"left", 90, "move", 30]); + + assert_approx(round_corners(test2, cut=.5, $fn=8,closed=true), + [[-0.606288551887,1.51404651037],[0.280235443937,0.414087063263],[1.63092692777,0],[16.2021229436,0],[19.8705904774,0.482962913145],[23.2890580113,1.89893852818],[34.1829092195,8.18850645576],[34.2505074296,8.28809878531],[34.2143124553,8.40289457772],[34.1018154298,8.44570309758],[25.9629982589,7.73364886061],[25.0818786883,8.01146479408],[24.6552785953,8.83095594797],[23.994133744,16.387876178],[23.5675336511,17.2073673319],[22.6864140805,17.4851832654],[-5.00680751277,15.0623403194],[-5.5709968443,14.7138107731],[-5.62744081644,14.0530562616]]); + + assert_approx(round_corners(test2, cut=.5, $fn=8,closed=false), + [[0,0],[16.2021229436,0],[19.8705904774,0.482962913145],[23.2890580113,1.89893852818],[34.1829092195,8.18850645576],[34.2505074296,8.28809878531],[34.2143124553,8.40289457772],[34.1018154298,8.44570309758],[25.9629982589,7.73364886061],[25.0818786883,8.01146479408],[24.6552785953,8.83095594797],[23.994133744,16.387876178],[23.5675336511,17.2073673319],[22.6864140805,17.4851832654],[-5.99691348681,14.975717271]]); + + assert_approx(round_corners(test2, radius=[9,1.5,1.5,3], $fn=8,closed=false),[[0,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[-5.99691348681,14.975717271]]); + + assert_approx(round_corners(test2, radius=[0,9,1.5,1.5,3,0], $fn=8,closed=false),[[0,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[-5.99691348681,14.975717271]]); + + assert_approx(round_corners(test2, radius=[4,9,1.5,1.5,3,5], $fn=8,closed=true), [[-1.00632035384,2.51302092923],[0.46513599873,0.687303493898],[2.70701955022,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[0.712818162855,15.5627427257],[-3.11056954856,13.2008342139],[-3.49307800348,8.72304539674]]); + + assert_approx(round_corners(test1, radius=[4,0,9,1.5,1.5,3,5], $fn=8, closed=true),[[-1.00632035384,2.51302092923],[0.46513599873,0.687303493898],[2.70701955022,0],[10,0],[17.5884572681,0],[19.917828674,0.306667563398],[22.0884572681,1.20577136594],[28.8628496345,5.11696862225],[29.5970120924,6.19860893897],[29.2039100968,7.44536918473],[27.9821160204,7.91029877507],[26.2547769306,7.75917618664],[25.1598619019,8.1044015691],[24.6297512693,9.12273461966],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[0.712818162855,15.5627427257],[-3.11056954856,13.2008342139],[-3.49307800348,8.72304539674]]); + + assert_approx(round_corners(test1, joint=3, $fn=8, closed=true),[[-1.11523430308,2.78500492805],[0.515477620423,0.76169028093],[3,0],[7,0],[13,0],[17,0],[19.8977774789,0.381499642545],[22.5980762114,1.5],[32.124355653,7],[32.4498754498,7.47958777023],[32.2755782213,8.03238795439],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[25.5592389204,8.58036056568],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[23.090173419,16.6384715603],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.71130594915,14.1851659419],[-4.88167918373,12.190712343]]); + + assert_approx(round_corners(test1, joint=3, $fn=8, method="chamfer", closed=true), [[-1.11523430308,2.78500492805],[3,0],[7,0],[13,0],[17,0],[22.5980762114,1.5],[32.124355653,7],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.88167918373,12.190712343]]); + + assert_approx(round_corners(test1, joint=3, $fn=4, method="smooth", closed=true),[[-1.11523430308,2.78500492805],[-0.506080589514,1.46865494252],[0.353393568173,0.522188424009],[1.55153656203,0.0761524785012],[3,0],[7,0],[8.5,0],[10,0],[11.5,0],[13,0],[17,0],[18.4890098964,0.041015625],[19.9246392896,0.28125],[21.2880480021,0.791015625],[22.5980762114,1.5],[32.124355653,7],[33.2706335159,7.70183488048],[33.674933057,8.1697248947],[33.0753795745,8.32110126636],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[26.3293465324,7.8480447775],[25.2718192958,8.2378271955],[24.7043208711,9.21160321051],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[23.9450914683,16.0072289155],[23.3775930436,16.9810049305],[22.320065807,17.3707873485],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.39040765537,15.0374479012],[-5.22744753731,14.5025539523],[-5.32708255097,13.514211823],[-4.88167918373,12.190712343]]); + + assert_approx(round_corners(test1, joint=3, $fn=4, method="smooth", k=.1, closed=true), [[-1.11523430308,2.78500492805],[-0.374134800869,0.998685360916],[0.164916998481,0.243687931204],[1.06619720521,0.0239336361004],[3,0],[7,0],[8.95,0],[10,0],[11.05,0],[13,0],[17,0],[18.9465459674,0.012890625],[19.9648316685,0.13125],[20.9058726414,0.537890625],[22.5980762114,1.5],[32.124355653,7],[33.7650948284,7.95986239101],[34.2335990876,8.34587161753],[33.6284170693,8.39334886112],[31.7338477701,8.23853277176],[27.7490689777,7.88990980077],[25.829925477,7.74788623096],[24.9991076092,7.91282206324],[24.6924075141,8.70237713407],[24.4990176552,10.6170266668],[24.1503946842,14.6018054592],[23.9570048253,16.5164549919],[23.6503047302,17.3060100627],[22.8194868624,17.470945895],[20.9003433617,17.3289223252],[-3.00832939254,15.2371844993],[-4.91564186446,15.0455441488],[-5.63782937704,14.7549077223],[-5.57131429138,13.9792788941],[-4.88167918373,12.190712343]]); + + assert_approx(round_corners(test1, joint=[3,0,3,5,2,2,4], $fn=4, method="smooth", k=[.8,0,.7,.5,0,.4,1], closed=true),[[-1.11523430308,2.78500492805],[-0.605039930997,1.82113212873],[0.494750995442,0.731063793612],[1.91554107964,0.115316610302],[3,0],[10,0],[17,0],[18.2602418609,0.055078125],[19.9045431002,0.35625],[21.4791356824,0.917578125],[22.5980762114,1.5],[30.3923048454,6],[32.3027679503,7.1697248008],[32.9766005188,7.94954149117],[31.9773447146,8.20183544393],[29.7414583739,8.06422128626],[26.7528742796,7.80275405802],[25.3902084366,7.69137858706],[24.8741147528,7.76386137763],[24.713114411,8.25952793415],[24.5861733979,9.62083196871],[24.0632389414,15.5980001573],[23.9283556903,16.6198201409],[23.5934897955,17.2383006602],[22.9262565325,17.4606811745],[21.8965380598,17.4160780679],[-2.01213469444,15.324340242],[-2.97951536307,15.0445310318],[-4.28698915458,13.9242432294],[-4.69675267167,12.2519315552],[-4.50993441604,11.262377367]]); + + assert_approx(round_corners(test2, cut=.6, $fn=4, method="smooth", k=[.8,.7,.5,0,.4,1], closed=true), [[-0.758025389489,1.89296943206],[-0.411245984888,1.23782454268],[0.336282532724,0.496904475915],[1.30199436026,0.0783807655462],[2.03910170463,0],[15.1195326672,0],[17.1697224117,0.0896023299387],[19.8447085729,0.579555495773],[22.4062911263,1.49273668813],[24.2266086926,2.44023366641],[33.3031485364,7.68057638855],[33.9293399672,8.06397643682],[34.1502016939,8.3195765203],[33.8226761739,8.40227076906],[33.0898209499,8.35716505304],[31.5228787369,8.220075373],[26.897837498,7.84205448929],[25.1461574492,8.08806923839],[24.5997041686,9.77041731835],[24.1688520829,14.390836426],[24.116478533,14.9894688406],[23.9403981733,16.3233811597],[23.5032548901,17.1307628876],[22.6322299704,17.4210646163],[21.2880067431,17.3628384763],[-4.80585474409,15.0799214086],[-5.0950068559,14.9962858485],[-5.48581351579,14.6614294736],[-5.6082926909,14.161572005],[-5.55245232227,13.8657921816]]); + + assert_approx(round_corners([[0,0],[10,0],[10,10]], cut=1, method="chamfer",closed=false), [[0,0],[8.58578643763,0],[10,1.41421356237],[10,10]]); + +} +test_round_corners(); + + + +// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap From 957907200b7e4eaea8353cd0a7b4a3db1a28ff6e Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Fri, 2 Apr 2021 20:39:14 -0400 Subject: [PATCH 10/18] bezier doc fixes, test for complex() --- beziers.scad | 15 ++++++++------- tests/test_math.scad | 9 +++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/beziers.scad b/beziers.scad index 721d8cf..79ecac8 100644 --- a/beziers.scad +++ b/beziers.scad @@ -413,16 +413,17 @@ function bezier_curvature(curve, u) = // Topics: Bezier Segments // See Also: bezier_curvature(), bezier_tangent(), bezier_derivative(), bezier_points() // Description: -// Takes a list of bezier curve control points and generates n points along the bezier path. -// Points start at the first control point and are sampled every `1/n`th -// of the way along the bezier parameter, ending *before* the final control point by default. -// The distance between the points will *not* be equidistant. If you wish to add the -// endpoint you can set `endpoint` to true. The degree of the bezier curve is one -// less than the number of points in `curve`. +// Takes a list of bezier control points and generates n points along the bezier curve they define. +// Points start at the first control point and are sampled uniformly along the bezier parameter. +// The endpoints of the output will be *exactly* equal to the first and last bezier control points +// when endpoint is true. If endpoint is false the sampling stops one step before the final point +// of the bezier curve, but you still get n, more tightly spaced, points. +// The distance between the points will *not* be equidistant. +// The degree of the bezier curve is one less than the number of points in `curve`. // Arguments: // curve = The list of endpoints and control points for this bezier segment. // n = The number of points to generate along the bezier curve. -// endpoint = if true then add the endpoint (an extra point, giving n+1 points output). Default: False +// endpoint = if false then exclude the endpoint. Default: True // Example(2D): Quadratic (Degree 2) Bezier. // bez = [[0,0], [30,30], [80,0]]; // move_copies(bezier_curve(bez, 8)) sphere(r=1.5, $fn=12); diff --git a/tests/test_math.scad b/tests/test_math.scad index e943756..35098a1 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -830,6 +830,15 @@ module test_lcm() { test_lcm(); + +module test_complex(){ + assert_equal( complex(ident(4)), c_ident(4)); + assert_equal( complex(3), [3,0]); + assert_equal( complex([1,2]), [[1,0],[2,0]]); + assert_equal( complex([[1,2],[3,4]]), [[ [1,0],[2,0] ], [ [3,0],[4,0]]]); +} +test_complex(); + module test_c_mul() { assert_equal(c_mul([4,5],[9,-4]), [56,29]); assert_equal(c_mul([-7,2],[24,3]), [-174, 27]); From 47104bcfe034f2b5838578d5bbce521f61509b10 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Fri, 2 Apr 2021 20:44:11 -0400 Subject: [PATCH 11/18] remove echo --- paths.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paths.scad b/paths.scad index e7f765c..6db525a 100644 --- a/paths.scad +++ b/paths.scad @@ -1244,7 +1244,7 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals is_def(n)? range(s=sp, e=length, n=n) : range(s=sp, step=spacing, e=length) ) - : is_def(n) && is_undef(spacing)? ( let(a=echo(n=n)) // N alone given + : is_def(n) && is_undef(spacing)? ( // N alone given closed ? rangex(s=0, e=length, n=n) : range(s=0, e=length, n=n) ) From 56ccca140d5005cbb2208cdb42752cf0e1acd4e2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 3 Apr 2021 02:20:37 +0000 Subject: [PATCH 12/18] Bump release version. --- version.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.scad b/version.scad index d38bcfa..a227524 100644 --- a/version.scad +++ b/version.scad @@ -6,7 +6,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,598]; +BOSL_VERSION = [2,0,599]; // Section: BOSL Library Version Functions From a05af703f9dee3ef7f2797c7345b2e4408d89b4c Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 6 Apr 2021 16:57:26 -0700 Subject: [PATCH 13/18] Added is_func() --- common.scad | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/common.scad b/common.scad index ab24380..ba05fc8 100644 --- a/common.scad +++ b/common.scad @@ -185,6 +185,19 @@ function valid_range(x) = : ( x[1]<0 && x[0]>=x[2] ) ); +// Function: is_func() +// Usage: +// bool = is_func(x); +// Description: +// Returns true if OpenSCAD supports function literals, and the given item is one. +// Arguments: +// x = The value to check against. +// Example: +// f = function (a) a==2; +// bool = is_func(f); // Returns: true +function is_func(x) = version_num()>20210000 && is_function(x); + + // Function: is_consistent() // Usage: // bool = is_consistent(list, ); From b424bad5c21080c87223bb82389e706a5c5433ee Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 6 Apr 2021 16:59:56 -0700 Subject: [PATCH 14/18] Added lerpn(). Removed modrange(). any(), all(), and count_true() can now be given function literals. --- math.scad | 168 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 98 insertions(+), 70 deletions(-) diff --git a/math.scad b/math.scad index 2297a29..9e4f73a 100644 --- a/math.scad +++ b/math.scad @@ -174,6 +174,31 @@ function lerp(a,b,u) = [for (v = u) (1-v)*a + v*b ]; +// Function: lerpn() +// Usage: +// x = lerpn(a, b, n); +// x = lerpn(a, b, n, ); +// Description: +// Returns exactly `n` values, linearly interpolated between `a` and `b`. +// If `endpoint` is true, then the last value will exactly equal `b`. +// If `endpoint` is false, then the last value will about `a+(b-a)*(1-1/n)`. +// Arguments: +// a = First value or vector. +// b = Second value or vector. +// n = The number of values to return. +// endpoint = If true, the last value will be exactly `b`. If false, the last value will be one step less. +// Examples: +// l = lerpn(-4,4,9); // Returns: [-4,-3,-2,-1,0,1,2,3,4] +// l = lerpn(-4,4,8,false); // Returns: [-4,-3,-2,-1,0,1,2,3] +// l = lerpn(0,1,6); // Returns: [0, 0.2, 0.4, 0.6, 0.8, 1] +// l = lerpn(0,1,5,false); // Returns: [0, 0.2, 0.4, 0.6, 0.8] +function lerpn(a,b,n,endpoint=true) = + assert(same_shape(a,b), "Bad or inconsistent inputs to lerp") + assert(is_int(n)) + assert(is_bool(endpoint)) + let( d = n - (endpoint? 1 : 0) ) + [for (i=[0:1:n-1]) let(u=i/d) (1-u)*a + u*b]; + // Section: Undef Safe Math @@ -434,32 +459,6 @@ function modang(x) = let(xx = posmod(x,360)) xx<180? xx : xx-360; -// Function: modrange() -// Usage: -// modrange(x, y, m, ) -// Description: -// Returns a normalized list of numbers from `x` to `y`, by `step`, modulo `m`. Wraps if `x` > `y`. -// Arguments: -// x = The start value to constrain. -// y = The end value to constrain. -// m = Modulo value. -// step = Step by this amount. -// Examples: -// modrange(90,270,360, step=45); // Returns: [90,135,180,225,270] -// modrange(270,90,360, step=45); // Returns: [270,315,0,45,90] -// modrange(90,270,360, step=-45); // Returns: [90,45,0,315,270] -// modrange(270,90,360, step=-45); // Returns: [270,225,180,135,90] -function modrange(x, y, m, step=1) = - assert( is_finite(x+y+step+m) && !approx(m,0), "Input must be finite numbers and the module value cannot be zero." ) - let( - a = posmod(x, m), - b = posmod(y, m), - c = step>0? (a>b? b+m : b) - : (a=len(l) || succ)? succ : - _any( - l, i+1, - succ = is_list(l[i]) ? _any(l[i]) : !(!l[i]) - ); +function _any_func(l, func, i=0, out=false) = + i >= len(l) || out? out : + _any_func(l, func, i=i+1, out=out || func(l[i])); + +function _any_bool(l, i=0, out=false) = + i >= len(l) || out? out : + _any_bool(l, i=i+1, out=out || l[i]); // Function: all() // Usage: // b = all(l); +// b = all(l,func); // Description: -// Returns true if all items in list `l` evaluate as true. -// If `l` is a lists of lists, `all()` is applied recursively to each sublist. +// Returns true if all items in list `l` evaluate as true. If `func` is given a function liteal +// of signature (x), returning bool, then that function literal is evaluated for each list item. // Arguments: // l = The list to test for true items. +// func = An optional function literal of signature (x), returning bool, to test each list item with. // Example: // all([0,false,undef]); // Returns false. // all([1,false,undef]); // Returns false. // all([1,5,true]); // Returns true. -// all([[0,0], [0,0]]); // Returns false. -// all([[0,0], [1,0]]); // Returns false. +// all([[0,0], [0,0]]); // Returns true. +// all([[0,0], [1,0]]); // Returns true. // all([[1,1], [1,1]]); // Returns true. -function all(l) = - assert( is_list(l), "The input is not a list." ) - _all(l); +function all(l, func) = + assert(is_list(l), "The input is not a list.") + assert(func==undef || is_func(func)) + is_func(func) + ? _all_func(l, func) + : _all_bool(l); -function _all(l, i=0, fail=false) = - (i>=len(l) || fail)? !fail : - _all( - l, i+1, - fail = is_list(l[i]) ? !_all(l[i]) : !l[i] - ) ; +function _all_func(l, func, i=0, out=true) = + i >= len(l) || !out? out : + _all_func(l, func, i=i+1, out=out && func(l[i])); + +function _all_bool(l, i=0, out=true) = + i >= len(l) || !out? out : + _all_bool(l, i=i+1, out=out && l[i]); // Function: count_true() // Usage: -// n = count_true(l) +// n = count_true(l,) +// n = count_true(l,func,) // Description: // Returns the number of items in `l` that evaluate as true. // If `l` is a lists of lists, this is applied recursively to each @@ -1289,24 +1301,38 @@ function _all(l, i=0, fail=false) = // in all recursive sublists. // Arguments: // l = The list to test for true items. -// nmax = If given, stop counting if `nmax` items evaluate as true. +// func = An optional function literal of signature (x), returning bool, to test each list item with. +// --- +// nmax = Max number of true items to count. Default: `undef` (no limit) // Example: // count_true([0,false,undef]); // Returns 0. // count_true([1,false,undef]); // Returns 1. // count_true([1,5,false]); // Returns 2. // count_true([1,5,true]); // Returns 3. -// count_true([[0,0], [0,0]]); // Returns 0. -// count_true([[0,0], [1,0]]); // Returns 1. -// count_true([[1,1], [1,1]]); // Returns 4. -// count_true([[1,1], [1,1]], nmax=3); // Returns 3. -function _count_true_rec(l, nmax, _cnt=0, _i=0) = - _i>=len(l) || (is_num(nmax) && _cnt>=nmax)? _cnt : - _count_true_rec(l, nmax, _cnt=_cnt+(l[_i]?1:0), _i=_i+1); +// count_true([[0,0], [0,0]]); // Returns 2. +// count_true([[0,0], [1,0]]); // Returns 2. +// count_true([[1,1], [1,1]]); // Returns 2. +// count_true([[1,1], [1,1]], nmax=1); // Returns 1. +function count_true(l, func, nmax) = + assert(is_list(l)) + assert(func==undef || is_func(func)) + is_func(func) + ? _count_true_func(l, func, nmax) + : _count_true_bool(l, nmax); -function count_true(l, nmax) = - is_undef(nmax)? len([for (x=l) if(x) 1]) : - !is_list(l) ? ( l? 1: 0) : - _count_true_rec(l, nmax); +function _count_true_func(l, func, nmax, i=0, out=0) = + i >= len(l) || (nmax!=undef && out>=nmax) ? out : + _count_true_func( + l, func, nmax, i = i + 1, + out = out + (func(l[i])? 1:0) + ); + +function _count_true_bool(l, nmax, i=0, out=0) = + i >= len(l) || (nmax!=undef && out>=nmax) ? out : + _count_true_bool( + l, nmax, i = i + 1, + out = out + (l[i]? 1:0) + ); @@ -1573,6 +1599,7 @@ function c_ident(n) = [for (i = [0:1:n-1]) [for (j = [0:1:n-1]) (i==j)?[1,0]:[0, function c_norm(z) = norm_fro(z); + // Section: Polynomials // Function: quadratic_roots() @@ -1624,6 +1651,7 @@ function polynomial(p,z,k,total) = : k==len(p) ? total : polynomial(p,z,k+1, is_num(z) ? total*z+p[k] : c_mul(total,z)+[p[k],0]); + // Function: poly_mult() // Usage: // x = polymult(p,q) From b370e2540bb3367f4d8f489f4478bfa0aa7b27d2 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 6 Apr 2021 17:00:40 -0700 Subject: [PATCH 15/18] Added function line_of(). --- distributors.scad | 87 ++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/distributors.scad b/distributors.scad index fde784d..588acb8 100644 --- a/distributors.scad +++ b/distributors.scad @@ -40,24 +40,30 @@ module move_copies(a=[[0,0,0]]) } -// Module: line_of() +// Function&Module: line_of() // // Usage: Spread `n` copies by a given spacing -// line_of(spacing, [n], [p1]) ... +// line_of(spacing, , ) ... // Usage: Spread copies every given spacing along the line -// line_of(spacing, l, [p1]) ... +// line_of(spacing, , ) ... // Usage: Spread `n` copies along the length of the line -// line_of(l, [n], [p1]) ... +// line_of(, , ) ... // Usage: Spread `n` copies along the line from `p1` to `p2` -// line_of(p1, p2, [n]) ... +// line_of(, , ) ... // Usage: Spread copies every given spacing, centered along the line from `p1` to `p2` -// line_of(p1, p2, spacing) ... -// +// line_of(, , ) ... +// Usage: As a function +// pts = line_of(, , ); +// pts = line_of(, , ); +// pts = line_of(, , ); +// pts = line_of(, , ); +// pts = line_of(, , ); // Description: -// Copies `children()` at one or more evenly spread positions along a line. By default, the line -// will be centered at the origin, unless the starting point `p1` is given. The line will be -// pointed towards `RIGHT` (X+) unless otherwise given as a vector in `l`, `spacing`, or `p1`/`p2`. -// The spread is specified in one of several ways: +// When called as a function, returns a list of points at evenly spread positions along a line. +// When called as a module, copies `children()` at one or more evenly spread positions along a line. +// By default, the line will be centered at the origin, unless the starting point `p1` is given. +// The line will be pointed towards `RIGHT` (X+) unless otherwise given as a vector in `l`, +// `spacing`, or `p1`/`p2`. The spread is specified in one of several ways: // . // If You Know... | Then Use Something Like... // -------------------------------- | -------------------------------- @@ -74,6 +80,7 @@ module move_copies(a=[[0,0,0]]) // Arguments: // spacing = Either the scalar spacing distance along the X+ direction, or the vector giving both the direction and spacing distance between each set of copies. // n = Number of copies to distribute along the line. (Default: 2) +// --- // l = Either the scalar length of the line, or a vector giving both the direction and length of the line. // p1 = If given, specifies the starting point of the line. // p2 = If given with `p1`, specifies the ending point of line, and indirectly calculates the line length. @@ -102,40 +109,42 @@ module move_copies(a=[[0,0,0]]) // cube(size=[1,3,1],center=true); // cube(size=[3,1,1],center=true); // } +// Example(2D): +// pts = line_of([10,5],n=5); +// move_copies(pts) circle(d=2); module line_of(spacing, n, l, p1, p2) { - assert(is_undef(spacing) || is_finite(spacing) || is_vector(spacing)); - assert(is_undef(n) || is_finite(n)); - assert(is_undef(l) || is_finite(l) || is_vector(l)); - assert(is_undef(p1) || is_vector(p1)); - assert(is_undef(p2) || is_vector(p2)); - ll = ( - !is_undef(l)? scalar_vec3(l, 0) : - (!is_undef(spacing) && !is_undef(n))? (n * scalar_vec3(spacing, 0)) : - (!is_undef(p1) && !is_undef(p2))? point3d(p2-p1) : - undef - ); - cnt = ( - !is_undef(n)? n : - (!is_undef(spacing) && !is_undef(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) : - 2 - ); - spc = ( - cnt<=1? [0,0,0] : - is_undef(spacing)? (ll/(cnt-1)) : - is_num(spacing) && !is_undef(ll)? (ll/(cnt-1)) : - scalar_vec3(spacing, 0) - ); - assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `line_of()`."); - spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc; - for (i=[0:1:cnt-1]) { - pos = i * spc + spos; - $pos = pos; + pts = line_of(spacing=spacing, n=n, l=l, p1=p1, p2=p2); + for (i=idx(pts)) { $idx = i; - translate(pos) children(); + $pos = pts[i]; + translate($pos) children(); } } +function line_of(spacing, n, l, p1, p2) = + assert(is_undef(spacing) || is_finite(spacing) || is_vector(spacing)) + assert(is_undef(n) || is_finite(n)) + assert(is_undef(l) || is_finite(l) || is_vector(l)) + assert(is_undef(p1) || is_vector(p1)) + assert(is_undef(p2) || is_vector(p2)) + let( + ll = !is_undef(l)? scalar_vec3(l, 0) : + (!is_undef(spacing) && !is_undef(n))? (n * scalar_vec3(spacing, 0)) : + (!is_undef(p1) && !is_undef(p2))? point3d(p2-p1) : + undef, + cnt = !is_undef(n)? n : + (!is_undef(spacing) && !is_undef(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) : + 2, + spc = cnt<=1? [0,0,0] : + is_undef(spacing)? (ll/(cnt-1)) : + is_num(spacing) && !is_undef(ll)? (ll/(cnt-1)) : + scalar_vec3(spacing, 0) + ) + assert(!is_undef(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `line_of()`.") + let( spos = !is_undef(p1)? point3d(p1) : -(cnt-1)/2 * spc ) + [for (i=[0:1:cnt-1]) i * spc + spos]; + // Module: xcopies() // From 2b78f82063350ffd2d9bfea026c15822d8c33935 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 6 Apr 2021 17:01:41 -0700 Subject: [PATCH 16/18] Regressions fixes for any()/all()/count_true() changes. --- tests/test_math.scad | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/tests/test_math.scad b/tests/test_math.scad index e943756..5b599bb 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -299,15 +299,6 @@ module test_modang() { test_modang(); -module test_modrange() { - assert_equal(modrange(-5,5,3), [1,2]); - assert_equal(modrange(-1,4,3), [2,0,1]); - assert_equal(modrange(1,8,10,step=2), [1,3,5,7]); - assert_equal(modrange(5,12,10,step=2), [5,7,9,1]); -} -test_modrange(); - - module test_sqr() { assert_equal(sqr(-3), 9); assert_equal(sqr(0), 0); @@ -738,11 +729,14 @@ module test_any() { assert_equal(any([0,false,undef]), false); assert_equal(any([1,false,undef]), true); assert_equal(any([1,5,true]), true); - assert_equal(any([[0,0], [0,0]]), false); + assert_equal(any([[0,0], [0,0]]), true); assert_equal(any([[0,0], [1,0]]), true); assert_equal(any([[false,false],[[false,[false],[[[true]]]],false],[false,false]]), true); - assert_equal(any([[false,false],[[false,[false],[[[false]]]],false],[false,false]]), false); + assert_equal(any([[false,false],[[false,[false],[[[false]]]],false],[false,false]]), true); assert_equal(any([]), false); + assert_equal(any([1,3,5,7,9], function (a) a%2==0),false); + assert_equal(any([1,3,6,7,9], function (a) a%2==0),true); + assert_equal(any([1,3,5,7,9], function (a) a%2!=0),true); } test_any(); @@ -751,12 +745,15 @@ module test_all() { assert_equal(all([0,false,undef]), false); assert_equal(all([1,false,undef]), false); assert_equal(all([1,5,true]), true); - assert_equal(all([[0,0], [0,0]]), false); - assert_equal(all([[0,0], [1,0]]), false); + assert_equal(all([[0,0], [0,0]]), true); + assert_equal(all([[0,0], [1,0]]), true); assert_equal(all([[1,1], [1,1]]), true); - assert_equal(all([[true,true],[[true,[true],[[[true]]]],true],[true,true]]), true); - assert_equal(all([[true,true],[[true,[true],[[[false]]]],true],[true,true]]), false); + assert_equal(all([[true,true],[[true,[true],[[[true]]]],true],[true,true]]), true); + assert_equal(all([[true,true],[[true,[true],[[[false]]]],true],[true,true]]), true); assert_equal(all([]), true); + assert_equal(all([1,3,5,7,9], function (a) a%2==0),false); + assert_equal(all([1,3,6,8,9], function (a) a%2==0),false); + assert_equal(all([1,3,5,7,9], function (a) a%2!=0),true); } test_all(); @@ -770,6 +767,9 @@ module test_count_true() { assert_equal(count_true([[0,0], [1,0]]), 2); assert_equal(count_true([[1,1], [1,1]]), 2); assert_equal(count_true([1,1,1,1,1], nmax=3), 3); + assert_equal(count_true([1,3,5,7,9], function (a) a%2==0),0); + assert_equal(count_true([1,3,6,8,9], function (a) a%2==0),2); + assert_equal(count_true([1,3,5,7,9], function (a) a%2!=0),5); } test_count_true(); @@ -789,6 +789,7 @@ module test_factorial() { } test_factorial(); + module test_binomial() { assert_equal(binomial(1), [1,1]); assert_equal(binomial(2), [1,2,1]); @@ -797,6 +798,7 @@ module test_binomial() { } test_binomial(); + module test_binomial_coefficient() { assert_equal(binomial_coefficient(2,1), 2); assert_equal(binomial_coefficient(3,2), 3); @@ -815,8 +817,8 @@ module test_gcd() { assert_equal(gcd(39, 101),1); assert_equal(gcd(15,-25), 5); assert_equal(gcd(-15,25), 5); - assert_equal(gcd(5,0),5); - assert_equal(gcd(0,5),5); + assert_equal(gcd(5,0), 5); + assert_equal(gcd(0,5), 5); } test_gcd(); From f07f5b53c106869caad2ae964b551de3d2b5380b Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 6 Apr 2021 17:03:18 -0700 Subject: [PATCH 17/18] Added list(). Enable using function literals with find_first_match(). --- arrays.scad | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/arrays.scad b/arrays.scad index 7d85d8c..c143001 100644 --- a/arrays.scad +++ b/arrays.scad @@ -200,6 +200,24 @@ function list_tail(list, from=1) = list; +// Function: list() +// Topics: List Handling, Type Conversion +// Usage: +// list = list(l) +// Description: +// Expands a range into a full list. If given a list, returns it verbatim. +// If given a string, explodes it into a list of single letters. +// Arguments: +// l = The value to expand. +// See Also: scalar_vec3(), force_list(), range(), rangex() +// Example: +// l1 = list([3:2:9]); // Returns: [3,5,7,9] +// l2 = list([3,4,5]); // Returns: [3,4,5] +// l3 = list("Foo"); // Returns: ["F","o","o"] +// l4 = list(23); // Returns: [23] +function list(l) = is_list(l)? l : [for (x=l) x]; + + // Function: force_list() // Usage: // list = force_list(value, , ); @@ -274,20 +292,28 @@ function in_list(val,list,idx) = // Description: // Finds the first item in `list` that matches `val`, returning the index. // Arguments: -// val = The value to search for. +// val = The value to search for. If given a function literal of signature `function (x)`, uses that function to check list items. Returns true for a match. // list = The list to search through. // --- // start = The index to start searching from. // all = If true, returns a list of all matching item indices. // eps = The maximum allowed floating point rounding error for numeric comparisons. function find_first_match(val, list, start=0, all=false, eps=EPSILON) = - all? [for (i=[start:1:len(list)-1]) if(val==list[i] || approx(val, list[i], eps=eps)) i] : + all? [ + for (i=[start:1:len(list)-1]) + if ( + (!is_func(val) && approx(val, list[i], eps=eps)) || + (is_func(val) && val(list[i])) + ) i + ] : __find_first_match(val, list, eps=eps, i=start); function __find_first_match(val, list, eps, i=0) = i >= len(list)? undef : - approx(val, list[i], eps=eps)? i : - __find_first_match(val, list, eps=eps, i=i+1); + ( + (!is_func(val) && approx(val, list[i], eps=eps)) || + (is_func(val) && val(list[i])) + )? i : __find_first_match(val, list, eps=eps, i=i+1); // Function: min_index() @@ -425,7 +451,7 @@ function range(n, s=0, e, step) = let( step = (n!=undef && e!=undef)? (e-s)/(n-1) : default(step,1) ) is_undef(e) ? assert( is_consistent([s, step]), "Incompatible data.") - [for (i=[0:1:n-1]) s+step*i ] + [for (i=[0:1:n-1]) s+step*i] : assert( is_vector([s,step,e]), "Start `s`, step `step` and end `e` must be numbers.") [for (v=[s:step:e]) v] ; @@ -468,10 +494,10 @@ function rangex(n, s=0, e, step) = let( step = (n!=undef && e!=undef)? (e-s)/n : default(step,1) ) is_undef(e) ? assert( is_consistent([s, step]), "Incompatible data.") - [for (i=[0:1:n-1]) s+step*i ] + [for (i=[0:1:n-1]) s+step*i] : assert( is_vector([s,step,e]), "Start `s`, step `step` and end `e` must be numbers.") let(steps=floor((e-s)/step+0.5)) - [for (i=[0:1:steps-1]) s+step*i ]; + [for (i=[0:1:steps-1]) s+step*i]; From b1ab140e3c12ee3da834dbb417de726b8818c263 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 7 Apr 2021 00:11:32 +0000 Subject: [PATCH 18/18] Bump release version. --- version.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.scad b/version.scad index a227524..168b763 100644 --- a/version.scad +++ b/version.scad @@ -6,7 +6,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,599]; +BOSL_VERSION = [2,0,600]; // Section: BOSL Library Version Functions