diff --git a/arrays.scad b/arrays.scad index 6f0b597..c143001 100644 --- a/arrays.scad +++ b/arrays.scad @@ -145,18 +145,59 @@ function last(list) = list[len(list)-1]; -// Function: delete_last() +// Function: list_head() // Usage: -// list = delete_last(list); +// list = list_head(list,); // Topics: List Handling -// See Also: select(), slice(), subindex(), last() +// See Also: select(), slice(), list_tail(), last() // Description: -// Returns a list with all but the last entry from the input list. If input is empty, returns empty list. -// Example: -// nlist = delete_last(["foo", "bar", "baz"]); // Returns: ["foo", "bar"] -function delete_last(list) = +// Returns the head of the given list, from the first item up until the `to` index, inclusive. +// If the `to` index is negative, then the length of the list is added to it, such that +// `-1` is the last list item. `-2` is the second from last. `-3` is third from last, etc. +// If the list is shorter than the given index, then the full list is returned. +// Arguments: +// list = The list to get the head of. +// to = The last index to include. If negative, adds the list length to it. ie: -1 is the last list item. +// Examples: +// hlist = list_head(["foo", "bar", "baz"]); // Returns: ["foo", "bar"] +// hlist = list_head(["foo", "bar", "baz"], -3); // Returns: ["foo"] +// hlist = list_head(["foo", "bar", "baz"], 2); // Returns: ["foo","bar"] +// hlist = list_head(["foo", "bar", "baz"], -5); // Returns: [] +// hlist = list_head(["foo", "bar", "baz"], 5); // Returns: ["foo","bar","baz"] +function list_head(list, to=-2) = assert(is_list(list)) - list==[] ? [] : slice(list,0,-2); + assert(is_finite(to)) + to<0? [for (i=[0:1:len(list)+to]) list[i]] : + to); +// Topics: List Handling +// See Also: select(), slice(), list_tail(), last() +// Description: +// Returns the tail of the given list, from the `from` index up until the end of the list, inclusive. +// If the `from` index is negative, then the length of the list is added to it, such that +// `-1` is the last list item. `-2` is the second from last. `-3` is third from last, etc. +// If you want it to return the last three items of the list, use `from=-3`. +// Arguments: +// list = The list to get the tail of. +// from = The first index to include. If negative, adds the list length to it. ie: -1 is the last list item. +// Examples: +// tlist = list_tail(["foo", "bar", "baz"]); // Returns: ["bar", "baz"] +// tlist = list_tail(["foo", "bar", "baz"], -1); // Returns: ["baz"] +// tlist = list_tail(["foo", "bar", "baz"], 2); // Returns: ["baz"] +// tlist = list_tail(["foo", "bar", "baz"], -5); // Returns: ["foo","bar","baz"] +// tlist = list_tail(["foo", "bar", "baz"], 5); // Returns: [] +function list_tail(list, from=1) = + assert(is_list(list)) + assert(is_finite(from)) + from>=0? [for (i=[from:1:len(list)-1]) list[i]] : + let(from = from + len(list)) + from>=0? [for (i=[from:1:len(list)-1]) list[i]] : + list; // Function: list() diff --git a/beziers.scad b/beziers.scad index bae18c4..d6c2eb7 100644 --- a/beziers.scad +++ b/beziers.scad @@ -945,7 +945,7 @@ module bezier_polygon(bezier, splinesteps=16, N=3) { assert(is_int(splinesteps)); assert(len(bezier)%N == 1, str("A degree ",N," bezier path shound have a multiple of ",N," points in it, plus 1.")); polypoints=bezier_path(bezier, splinesteps, N); - polygon(points=slice(polypoints, 0, -1)); + polygon(points=polypoints); } diff --git a/common.scad b/common.scad index 86b7e71..e24c744 100644 --- a/common.scad +++ b/common.scad @@ -273,7 +273,6 @@ function is_bool_list(list, length) = // Topics: Undef Handling // See Also: first_defined(), one_defined(), num_defined() // Description: -// Returns the value given as `v` if it is not `undef`. Otherwise, returns the value of `dflt`. // Returns the value given as `v` if it is not `undef`. // Otherwise, returns the value of `dflt`. // Arguments: @@ -294,8 +293,7 @@ function default(v,dflt=undef) = is_undef(v)? dflt : v; // v = The list whose items are being checked. // recursive = If true, sublists are checked recursively for defined values. The first sublist that has a defined item is returned. // Examples: -// list = *** -// val = first_defined(list) +// val = first_defined([undef,7,undef,true]); // Returns: 1 function first_defined(v,recursive=false,_i=0) = _i) // Topics: Undef Handling // See Also: default(), first_defined(), num_defined(), any_defined(), all_defined() -// one_defined(vars, names, ) // Description: // Examines the input list `vals` and returns the entry which is not `undef`. // If more than one entry is not `undef` then an error is asserted, specifying @@ -608,15 +605,15 @@ function segs(r) = // Module: no_children() +// Topics: Error Checking // Usage: // no_children($children); -// Topics: Error Checking -// See Also: no_function(), no_module() // Description: -// Assert that the calling module does not support children. Prints an error message to this effect -// and fails if children are present, as indicated by its argument. +// Assert that the calling module does not support children. Prints an error message to this effect and fails if children are present, +// as indicated by its argument. // Arguments: // $children = number of children the module has. +// See Also: no_function(), no_module() // Example: // module foo() { // no_children($children); @@ -679,7 +676,7 @@ function _valstr(x) = // expected = The value that was expected. // info = Extra info to print out to make the error clearer. // Example: -// assert_approx(1/3, 0.333333333333333, str("number=",1,", demon=",3)); +// assert_approx(1/3, 0.333333333333333, str("numer=",1,", demon=",3)); module assert_approx(got, expected, info) { no_children($children); if (!approx(got, expected)) { @@ -778,8 +775,8 @@ module shape_compare(eps=1/1024) { // The syntax is: `[for (INIT; CONDITION; NEXT) RETVAL]` where: // - INIT is zero or more `let()` style assignments that are evaluated exactly one time, before the first loop. // - CONDITION is an expression evaluated at the start of each loop. If true, continues with the loop. -// - RETVAL is an expression that returns a list item at each loop beginning. -// - NEXT is one or more `let()` style assignments that is evaluated for each loop. +// - RETVAL is an expression that returns a list item for each loop. +// - NEXT is one or more `let()` style assignments that is evaluated at the end of each loop. // . // Since the INIT phase is only run once, and the CONDITION and RETVAL expressions cannot update // variables, that means that only the NEXT phase can be used for iterative calculations. @@ -824,7 +821,6 @@ function looping(state) = state < 2; // Function: loop_while() // Usage: -// state = loop_while(state, continue) // state = loop_while(state, continue); // Topics: Iteration // See Also: looping(), loop_done() @@ -843,7 +839,6 @@ function loop_while(state, continue) = // Function: loop_done() // Usage: -// loop_done(state) // bool = loop_done(state); // Topics: Iteration // See Also: looping(), loop_while() diff --git a/distributors.scad b/distributors.scad index 06b3712..588acb8 100644 --- a/distributors.scad +++ b/distributors.scad @@ -303,9 +303,9 @@ module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef) spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); for (i=[0:1:$children-1]) { - totspc = sum(concat([0], slice(gaps2, 0, i))); - $pos = spos + totspc * dir; + $pos = spos + spacings[i] * dir; $idx = i; translate($pos) children(i); } @@ -348,9 +348,9 @@ module xdistribute(spacing=10, sizes=undef, l=undef) spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); for (i=[0:1:$children-1]) { - totspc = sum(concat([0], slice(gaps2, 0, i))); - $pos = spos + totspc * dir; + $pos = spos + spacings[i] * dir; $idx = i; translate($pos) children(i); } @@ -393,9 +393,9 @@ module ydistribute(spacing=10, sizes=undef, l=undef) spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); for (i=[0:1:$children-1]) { - totspc = sum(concat([0], slice(gaps2, 0, i))); - $pos = spos + totspc * dir; + $pos = spos + spacings[i] * dir; $idx = i; translate($pos) children(i); } @@ -438,9 +438,9 @@ module zdistribute(spacing=10, sizes=undef, l=undef) spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); gaps2 = [for (gap = gaps) gap+spc]; spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); for (i=[0:1:$children-1]) { - totspc = sum(concat([0], slice(gaps2, 0, i))); - $pos = spos + totspc * dir; + $pos = spos + spacings[i] * dir; $idx = i; translate($pos) children(i); } diff --git a/geometry.scad b/geometry.scad index b145c6f..489be44 100644 --- a/geometry.scad +++ b/geometry.scad @@ -812,7 +812,9 @@ function adj_opp_to_ang(adj,opp) = // Returns the area of a triangle formed between three 2D or 3D vertices. // Result will be negative if the points are 2D and in clockwise order. // Arguments: -// a, b, c = The three vertices of the triangle. +// a = The first vertex of the triangle. +// b = The second vertex of the triangle. +// c = The third vertex of the triangle. // Examples: // triangle_area([0,0], [5,10], [10,0]); // Returns -50 // triangle_area([10,0], [5,10], [0,0]); // Returns 50 @@ -881,7 +883,7 @@ function plane3pt_indexed(points, i1, i2, i3) = // Description: // Returns a plane defined by a normal vector and a point. // Arguments: -// normal = Normal vector to the plane to find.. +// normal = Normal vector to the plane to find. // pt = Point 3D on the plane to find. // Example: // plane_from_normal([0,0,1], [2,2,2]); // Returns the xy plane passing through the point (2,2,2) @@ -1905,8 +1907,8 @@ function align_polygon(reference, poly, angles, cp) = // Description: // Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid. // Given a simple 3D planar polygon, returns the 3D coordinates of the polygon's centroid. -// Collinear points produce an error. -// The results are meaningless for self-intersecting polygons or an error is produced. +// Collinear points produce an error. The results are meaningless for self-intersecting +// polygons or an error is produced. // Arguments: // poly = Points of the polygon from which the centroid is calculated. // eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9) diff --git a/math.scad b/math.scad index ed3ea89..9e4f73a 100644 --- a/math.scad +++ b/math.scad @@ -543,9 +543,8 @@ function _lcm(a,b) = // Computes lcm for a list of values function _lcmlist(a) = - len(a)==1 - ? a[0] - : _lcmlist(concat(slice(a,0,len(a)-2),[lcm(a[len(a)-2],a[len(a)-1])])); + len(a)==1 ? a[0] : + _lcmlist(concat(lcm(a[0],a[1]),list_tail(a,2))); // Function: lcm() diff --git a/paths.scad b/paths.scad index c47bc2f..6db525a 100644 --- a/paths.scad +++ b/paths.scad @@ -885,7 +885,7 @@ function assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0, [outpath, newfrags] ) : let( // Path still incomplete. Continue building it. - newpath = concat(path, slice(foundfrag, 1, -1)), + newpath = concat(path, list_tail(foundfrag)), newfrags = concat([newpath], remainder) ) assemble_a_path_from_fragments( @@ -1428,7 +1428,7 @@ function path_cut(path,cutdist,closed) = cuts = len(cutlist) ) [ - [ each slice(path,0,cutlist[0][1]), + [ each list_head(path,cutlist[0][1]-1), if (!approx(cutlist[0][0], path[cutlist[0][1]-1])) cutlist[0][0] ], for(i=[0:1:cuts-2]) diff --git a/regions.scad b/regions.scad index 57552ef..b76785c 100644 --- a/regions.scad +++ b/regions.scad @@ -88,7 +88,7 @@ function check_and_fix_path(path, valid_dim=undef, closed=false, name="path") = is_list(valid_dim) ? str("one of ",valid_dim) : valid_dim ) ) - closed && approx(path[0],select(path,-1))? slice(path,0,-2) : path; + closed && approx(path[0], last(path))? list_head(path) : path; // Function: cleanup_region() diff --git a/rounding.scad b/rounding.scad index cc4125c..815e220 100644 --- a/rounding.scad +++ b/rounding.scad @@ -413,7 +413,7 @@ function _rounding_offsets(edgespec,z_dir=1) = assert(argsOK,str("Invalid specification with type ",edgetype)) let( offsets = - edgetype == "profile"? scale([-1,z_dir], p=slice(points,1,-1)) : + edgetype == "profile"? scale([-1,z_dir], p=list_tail(points)) : edgetype == "chamfer"? chamf_width==0 && chamf_height==0? [] : [[-chamf_width,z_dir*abs(chamf_height)]] : edgetype == "teardrop"? ( radius==0? [] : concat( diff --git a/shapes2d.scad b/shapes2d.scad index f9f53bb..e14467e 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -851,7 +851,7 @@ function _turtle_command(command, parm, parm2, state, index) = ) list_set( state, [path,step], [ - concat(state[path], slice(arcpath,1,-1)), + concat(state[path], list_tail(arcpath)), rot(lrsign * myangle,p=state[step],planar=true) ] ) : @@ -877,7 +877,7 @@ function _turtle_command(command, parm, parm2, state, index) = ) list_set( state, [path,step], [ - concat(state[path], slice(arcpath,1,-1)), + concat(state[path], list_tail(arcpath)), rot(delta_angle,p=state[step],planar=true) ] ) : diff --git a/tests/test_arrays.scad b/tests/test_arrays.scad index cf2c370..679f34c 100644 --- a/tests/test_arrays.scad +++ b/tests/test_arrays.scad @@ -48,24 +48,33 @@ module test_last() { } test_last(); -module test_delete_last() { + +module test_list_head() { list = [1,2,3,4]; - assert(delete_last(list) == [1,2,3]); - assert(delete_last([1]) == []); - assert(delete_last([]) == []); + assert_equal(list_head(list), [1,2,3]); + assert_equal(list_head([1]), []); + assert_equal(list_head([]), []); + assert_equal(list_head(list,-3), [1,2]); + assert_equal(list_head(list,1), [1,2]); + assert_equal(list_head(list,2), [1,2,3]); + assert_equal(list_head(list,6), [1,2,3,4]); + assert_equal(list_head(list,-6), []); } -test_delete_last(); +test_list_head(); -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) == []); +module test_list_tail() { + list = [1,2,3,4]; + assert_equal(list_tail(list), [2,3,4]); + assert_equal(list_tail([1]), []); + assert_equal(list_tail([]), []); + assert_equal(list_tail(list,-3), [2,3,4]); + assert_equal(list_tail(list,2), [3,4]); + assert_equal(list_tail(list,3), [4]); + assert_equal(list_tail(list,6), []); + assert_equal(list_tail(list,-6), [1,2,3,4]); } -test_slice(); +test_list_tail(); module test_in_list() { diff --git a/turtle3d.scad b/turtle3d.scad index b9f3035..7f1e4c5 100644 --- a/turtle3d.scad +++ b/turtle3d.scad @@ -540,28 +540,28 @@ function _turtle3d_command(command, parm, parm2, state, index) = command=="addlength" ? list_set(state, movestep, state[movestep]+parm) : command=="arcsteps" ? assert(is_int(parm) && parm>0, str("\"",command,"\" requires a postive integer argument at index ",index)) list_set(state, arcsteps, parm) : - command=="roll" ? list_set(state, trlist, concat(slice(state[trlist],0,-2), [lastT*xrot(parm)])): + command=="roll" ? list_set(state, trlist, concat(list_head(state[trlist]), [lastT*xrot(parm)])): in_list(command,["right","left","up","down"]) ? - list_set(state, trlist, concat(slice(state[trlist],0,-2), [lastT*_turtle3d_rotation(command,default(parm,state[angle]))])): + list_set(state, trlist, concat(list_head(state[trlist]), [lastT*_turtle3d_rotation(command,default(parm,state[angle]))])): in_list(command,["xrot","yrot","zrot"]) ? let( Trot = _rotpart(lastT), // Extract rotational part of lastT shift = _transpart(lastT) // Translation part of lastT ) - list_set(state, trlist, concat(slice(state[trlist],0,-2), + list_set(state, trlist, concat(list_head(state[trlist]), [move(shift)*_turtle3d_rotation(command,default(parm,state[angle])) * Trot])): command=="rot" ? let( Trot = _rotpart(lastT), // Extract rotational part of lastT shift = _transpart(lastT) // Translation part of lastT ) - list_set(state, trlist, concat(slice(state[trlist],0,-2),[move(shift) * parm * Trot])): + list_set(state, trlist, concat(list_head(state[trlist]),[move(shift) * parm * Trot])): command=="setdir" ? let( Trot = _rotpart(lastT), shift = _transpart(lastT) ) - list_set(state, trlist, concat(slice(state[trlist],0,-2), + list_set(state, trlist, concat(list_head(state[trlist]), [move(shift)*rot(from=apply(Trot,RIGHT),to=parm) * Trot ])): in_list(command,["arcleft","arcright","arcup","arcdown"]) ? assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))