From d7cb503ffc05a48fe58c995adacf260387da40a2 Mon Sep 17 00:00:00 2001
From: Garth Minette <gminette@gmail.com>
Date: Thu, 25 Mar 2021 00:23:36 -0700
Subject: [PATCH 1/5] Replace most trivial uses of slice() with faster
 list_head() and list_tail()

---
 arrays.scad            | 57 ++++++++++++++++++++++++++++++++++++------
 beziers.scad           |  4 +--
 distributors.scad      | 16 ++++++------
 math.scad              |  5 ++--
 paths.scad             | 10 ++++----
 regions.scad           |  2 +-
 rounding.scad          |  2 +-
 shapes2d.scad          |  6 ++---
 tests/test_arrays.scad | 51 ++++++++++++++++++++++++++-----------
 turtle3d.scad          | 10 ++++----
 10 files changed, 112 insertions(+), 51 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index 3fd8bbc..e4de121 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -142,18 +142,59 @@ function last(list) =
     list[len(list)-1];
 
 
-// Function: delete_last()
+// Function: list_head()
 // Usage:
-//   list = delete_last(list);
+//   list = list_head(list,<to>);
 // 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<len(list)? [for (i=[0:1:to]) list[i]] :
+   list;
+
+
+// Function: list_tail()
+// Usage:
+//   list = list_tail(list,<from>);
+// 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: force_list()
diff --git a/beziers.scad b/beziers.scad
index 2a74ac3..79fe650 100644
--- a/beziers.scad
+++ b/beziers.scad
@@ -574,7 +574,7 @@ function bezier_line_intersection(curve, line) =
 //   p2 = [30, 30];
 //   trace_path([p0,p1,p2], showpts=true, size=0.5, color="green");
 //   fbez = fillet3pts(p0,p1,p2, 10);
-//   trace_bezier(slice(fbez, 1, -2), size=1);
+//   trace_bezier(select(fbez,1,-2), size=1);
 function fillet3pts(p0, p1, p2, r, d, maxerr=0.1, w=0.5, dw=0.25) = let(
         r = get_radius(r=r,d=d),
         v0 = unit(p0-p1),
@@ -942,7 +942,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/distributors.scad b/distributors.scad
index 1710f03..fde784d 100644
--- a/distributors.scad
+++ b/distributors.scad
@@ -294,9 +294,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);
     }
@@ -339,9 +339,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);
     }
@@ -384,9 +384,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);
     }
@@ -429,9 +429,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/math.scad b/math.scad
index bf676e4..c63efb8 100644
--- a/math.scad
+++ b/math.scad
@@ -544,9 +544,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 5d391c3..036e008 100644
--- a/paths.scad
+++ b/paths.scad
@@ -878,14 +878,14 @@ function assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0,
         let(
             // Found fragment intersects with initial path
             hitidx = select(hits,-1),
-            newpath = slice(path,0,hitidx+1),
+            newpath = list_head(path,0,hitidx),
             newfrags = concat(len(newpath)>1? [newpath] : [], remainder),
             outpath = concat(slice(path,hitidx,-2), foundfrag)
         )
         [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(
@@ -1244,7 +1244,7 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals
         list_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)) slice(range,0,-2) :
+            let(range=list_range(s=0,e=length, n=n+1)) list_head(range) :
             list_range(s=0, e=length, n=n)
     ) : (
         let(
@@ -1426,14 +1426,14 @@ 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])
           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]),
             if (!approx(cutlist[i+1][0], select(path,cutlist[i+1][1]-1))) cutlist[i+1][0],
           ],
       [
diff --git a/regions.scad b/regions.scad
index b4befba..8b5ca6b 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 afebedb..cf95e7b 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 16cd306..34f92ae 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")
-               slice(arc(N,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true),0,-2) :
+               list_head(arc(N,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)? (
@@ -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 c8f86d0..5b460e2 100644
--- a/tests/test_arrays.scad
+++ b/tests/test_arrays.scad
@@ -27,21 +27,6 @@ module test_select() {
 }
 test_select();
 
-module test_last() {
-    list = [1,2,3,4];
-    assert(last(list)==4);
-    assert(last([])==undef);
-}
-test_last();
-
-module test_delete_last() {
-    list = [1,2,3,4];
-    assert(delete_last(list) == [1,2,3]);
-    assert(delete_last([1]) == []);
-    assert(delete_last([]) == []);
-}
-test_delete_last();
-
 
 module test_slice() {
     assert(slice([3,4,5,6,7,8,9], 3, 5) == [6,7]);
@@ -54,6 +39,42 @@ module test_slice() {
 test_slice();
 
 
+module test_last() {
+    list = [1,2,3,4];
+    assert(last(list)==4);
+    assert(last([])==undef);
+}
+test_last();
+
+
+module test_list_head() {
+    list = [1,2,3,4];
+    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_list_head();
+
+
+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_list_tail();
+
+
 module test_in_list() {
     assert(in_list("bar", ["foo", "bar", "baz"]));
     assert(!in_list("bee", ["foo", "bar", "baz"]));
diff --git a/turtle3d.scad b/turtle3d.scad
index 8f9176d..c0631d0 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))

From 19f08c253aa66fc20a572c3eeb5db09875d328f7 Mon Sep 17 00:00:00 2001
From: Garth Minette <gminette@gmail.com>
Date: Thu, 25 Mar 2021 00:29:52 -0700
Subject: [PATCH 2/5] Fix typo in assemble_a_path_from_fragments()

---
 paths.scad | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/paths.scad b/paths.scad
index 036e008..28858fa 100644
--- a/paths.scad
+++ b/paths.scad
@@ -878,7 +878,7 @@ function assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0,
         let(
             // Found fragment intersects with initial path
             hitidx = select(hits,-1),
-            newpath = list_head(path,0,hitidx),
+            newpath = list_head(path,hitidx),
             newfrags = concat(len(newpath)>1? [newpath] : [], remainder),
             outpath = concat(slice(path,hitidx,-2), foundfrag)
         )

From 9ffed887509474cddc9ee0c360feeca81e9938cd Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Fri, 26 Mar 2021 00:13:59 +0000
Subject: [PATCH 3/5] Bump release version.

---
 version.scad | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/version.scad b/version.scad
index f09143e..c4219e6 100644
--- a/version.scad
+++ b/version.scad
@@ -6,7 +6,7 @@
 //////////////////////////////////////////////////////////////////////
 
 
-BOSL_VERSION = [2,0,595];
+BOSL_VERSION = [2,0,596];
 
 
 // Section: BOSL Library Version Functions

From 316304d7d5648215760976e07fdb6d978b3a720c Mon Sep 17 00:00:00 2001
From: Adrian Mariano <avm4@cornell.edu>
Date: Sat, 27 Mar 2021 23:42:13 -0400
Subject: [PATCH 4/5] polyhedron_info "vnf" returns untriangulated (full)
 polyhedron faces

---
 polyhedra.scad | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/polyhedra.scad b/polyhedra.scad
index ac0d575..57cc7eb 100644
--- a/polyhedra.scad
+++ b/polyhedra.scad
@@ -699,7 +699,8 @@ function regular_polyhedron_info(
         face_normals,
         radius_scale*entry[in_radius]
     ] :
-    info == "vnf" ? [move(translation,p=scaled_points), stellate ? faces : face_triangles] : 
+    // info == "vnf" ? [move(translation,p=scaled_points), stellate ? faces : face_triangles] :
+    info == "vnf" ? [move(translation,p=scaled_points), faces] :
     info == "vertices" ? move(translation,p=scaled_points) :
     info == "faces" ? faces :
     info == "face normals" ? face_normals :

From f3ffcebb0e34ae255be1fe312d6ff68967454de2 Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Sun, 28 Mar 2021 21:12:39 +0000
Subject: [PATCH 5/5] Bump release version.

---
 version.scad | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/version.scad b/version.scad
index c4219e6..14eed9e 100644
--- a/version.scad
+++ b/version.scad
@@ -6,7 +6,7 @@
 //////////////////////////////////////////////////////////////////////
 
 
-BOSL_VERSION = [2,0,596];
+BOSL_VERSION = [2,0,597];
 
 
 // Section: BOSL Library Version Functions