From 6798083a11a42a3df6bb591fa8de2a111bceac16 Mon Sep 17 00:00:00 2001
From: Garth Minette <gminette@gmail.com>
Date: Tue, 21 Jul 2020 16:54:59 -0700
Subject: [PATCH 01/24] Added shape_compare()

---
 common.scad  | 36 ++++++++++++++++++++++++++++++++++++
 version.scad |  2 +-
 2 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/common.scad b/common.scad
index 8d0655a..648f3c7 100644
--- a/common.scad
+++ b/common.scad
@@ -379,5 +379,41 @@ module assert_equal(got, expected, info) {
 }
 
 
+// Module: shape_compare()
+// Usage:
+//   shape_compare([eps]) {test_shape(); expected_shape();}
+// Description:
+//   Compares two child shapes, returning empty geometry if they are very nearly the same shape and size.
+//   Returns the differential geometry if they are not nearly the same shape and size.
+// Arguments:
+//   eps = The surface of the two shapes must be within this size of each other.  Default: 1/1024
+module shape_compare(eps=1/1024) {
+    union() {
+        difference() {
+            children(0);
+            if (eps==0) {
+                children(1);
+            } else {
+                minkowski() {
+                    children(1);
+                    cube(eps, center=true);
+                }
+            }
+        }
+        difference() {
+            children(1);
+            if (eps==0) {
+                children(0);
+            } else {
+                minkowski() {
+                    children(0);
+                    cube(eps, center=true);
+                }
+            }
+        }
+    }
+}
+
+
 
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/version.scad b/version.scad
index 406f9e4..7efaa86 100644
--- a/version.scad
+++ b/version.scad
@@ -8,7 +8,7 @@
 //////////////////////////////////////////////////////////////////////
 
 
-BOSL_VERSION = [2,0,397];
+BOSL_VERSION = [2,0,398];
 
 
 // Section: BOSL Library Version Functions

From 39b4b7282d7b582600dd9df5d5ee11ebf5b7be55 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Fri, 24 Jul 2020 00:04:16 +0100
Subject: [PATCH 02/24] Update typeof(), int(), range(), list_of() and segs

---
 common.scad            | 31 ++++++++++++++++++++-----------
 tests/test_common.scad | 27 ++++++++++++++++++++++++++-
 2 files changed, 46 insertions(+), 12 deletions(-)

diff --git a/common.scad b/common.scad
index 8d0655a..6cc910e 100644
--- a/common.scad
+++ b/common.scad
@@ -15,7 +15,8 @@
 // Usage:
 //   typ = typeof(x);
 // Description:
-//   Returns a string representing the type of the value.  One of "undef", "boolean", "number", "nan", "string", "list", or "range"
+//   Returns a string representing the type of the value.  One of "undef", "boolean", "number", "nan", "string", "list", "range" or "invalid".
+//   Some malformed "ranges", like '[0:NAN:INF]' and '[0:"a":INF]', may be classified as "undef" or "invalid".
 function typeof(x) =
     is_undef(x)? "undef" :
     is_bool(x)? "boolean" :
@@ -23,8 +24,11 @@ function typeof(x) =
     is_nan(x)? "nan" :
     is_string(x)? "string" :
     is_list(x)? "list" :
-    "range";
+    is_range(x) ? "range" :
+    "invalid";
 
+//***
+// included "invalid"
 
 // Function: is_type()
 // Usage:
@@ -70,8 +74,8 @@ function is_str(x) = is_string(x);
 //   is_int(n)
 // Description:
 //   Returns true if the given value is an integer (it is a number and it rounds to itself).  
-function is_int(n) = is_num(n) && n == round(n);
-function is_integer(n) = is_num(n) && n == round(n);
+function is_int(n) = is_finite(n) && n == round(n);
+function is_integer(n) = is_finite(n) && n == round(n);
 
 
 // Function: is_nan()
@@ -93,7 +97,7 @@ function is_finite(v) = is_num(0*v);
 // Function: is_range()
 // Description:
 //   Returns true if its argument is a range
-function is_range(x) = is_num(x[0]) && !is_list(x);
+function is_range(x) = !is_list(x) && is_finite(x[0]+x[1]+x[2]) ;
 
 
 // Function: is_list_of()
@@ -106,13 +110,15 @@ function is_range(x) = is_num(x[0]) && !is_list(x);
 //   is_list_of([3,4,5], 0);            // Returns true
 //   is_list_of([3,4,undef], 0);        // Returns false
 //   is_list_of([[3,4],[4,5]], [1,1]);  // Returns true
+//   is_list_of([[3,"a"],[4,true]], [1,undef]);  // Returns true
 //   is_list_of([[3,4], 6, [4,5]], [1,1]);  // Returns false
-//   is_list_of([[1,[3,4]], [4,[5,6]]], [1,[2,3]]);    // Returne true
-//   is_list_of([[1,[3,INF]], [4,[5,6]]], [1,[2,3]]);  // Returne false
+//   is_list_of([[1,[3,4]], [4,[5,6]]], [1,[2,3]]);    // Returns true
+//   is_list_of([[1,[3,INF]], [4,[5,6]]], [1,[2,3]]);  // Returns false
+//   is_list_of([], [1,[2,3]]);                        // Returns true
 function is_list_of(list,pattern) =
     let(pattern = 0*pattern)
     is_list(list) &&
-    []==[for(entry=list) if (entry*0 != pattern) entry];
+    []==[for(entry=0*list) if (entry != pattern) entry];
 
 
 // Function: is_consistent()
@@ -311,10 +317,13 @@ function scalar_vec3(v, dflt=undef) =
 //   Calculate the standard number of sides OpenSCAD would give a circle based on `$fn`, `$fa`, and `$fs`.
 // Arguments:
 //   r = Radius of circle to get the number of segments for.
-function segs(r) =
+function segs(r) = 
     $fn>0? ($fn>3? $fn : 3) :
-    ceil(max(5, min(360/$fa, abs(r)*2*PI/$fs)));
+    let( r = is_finite(r)? r: 0 ) 
+		ceil(max(5, min(360/$fa, abs(r)*2*PI/$fs))) ;
 
+//***
+// avoids undef
 
 
 // Section: Testing Helpers
@@ -322,7 +331,7 @@ function segs(r) =
 
 function _valstr(x) =
     is_list(x)? str("[",str_join([for (xx=x) _valstr(xx)],","),"]") :
-    is_num(x)? fmt_float(x,12) : x;
+    is_finite(x)? fmt_float(x,12) : x;
 
 
 // Module: assert_approx()
diff --git a/tests/test_common.scad b/tests/test_common.scad
index ed97cae..1cad5e3 100644
--- a/tests/test_common.scad
+++ b/tests/test_common.scad
@@ -1,4 +1,4 @@
-include <BOSL2/std.scad>
+include <../std.scad>
 
 
 module test_typeof() {
@@ -18,6 +18,10 @@ module test_typeof() {
     assert(typeof([0:1:5]) == "range");
     assert(typeof([-3:2:5]) == "range");
     assert(typeof([10:-2:-10]) == "range");
+    assert(typeof([0:NAN:INF]) == "invalid");
+    assert(typeof([0:"a":INF]) == "undef"); 
+    assert(typeof([0:[]:INF]) == "undef"); 
+    assert(typeof([true:1:INF]) == "undef"); 
 }
 test_typeof();
 
@@ -102,6 +106,8 @@ module test_is_int() {
     assert(!is_int(-99.1));
     assert(!is_int(99.1));
     assert(!is_int(undef));
+    assert(!is_int(INF));
+    assert(!is_int(NAN));
     assert(!is_int(false));
     assert(!is_int(true));
     assert(!is_int("foo"));
@@ -124,6 +130,8 @@ module test_is_integer() {
     assert(!is_integer(-99.1));
     assert(!is_integer(99.1));
     assert(!is_integer(undef));
+    assert(!is_integer(INF));
+    assert(!is_integer(NAN));
     assert(!is_integer(false));
     assert(!is_integer(true));
     assert(!is_integer("foo"));
@@ -166,6 +174,9 @@ module test_is_range() {
     assert(!is_range("foo"));
     assert(!is_range([]));
     assert(!is_range([3,4,5]));
+    assert(!is_range([INF:4:5]));
+    assert(!is_range([3:NAN:5]));
+    assert(!is_range([3:4:"a"]));
     assert(is_range([3:1:5]));
 }
 test_is_nan();
@@ -331,11 +342,25 @@ module test_scalar_vec3() {
     assert(scalar_vec3([3]) == [3,0,0]);
     assert(scalar_vec3([3,4]) == [3,4,0]);
     assert(scalar_vec3([3,4],dflt=1) == [3,4,1]);
+    assert(scalar_vec3([3,"a"],dflt=1) == [3,"a",1]);
+    assert(scalar_vec3([3,[2]],dflt=1) == [3,[2],1]);
     assert(scalar_vec3([3],dflt=1) == [3,1,1]);
     assert(scalar_vec3([3,4,5]) == [3,4,5]);
     assert(scalar_vec3([3,4,5,6]) == [3,4,5]);
+    assert(scalar_vec3([3,4,5,6]) == [3,4,5]);
 }
 test_scalar_vec3();
 
 
+module test_segs() {
+    assert_equal(segs(50,$fn=8), 8);
+    assert_equal(segs(50,$fa=2,$fs=2), 158);
+    assert(segs(1)==5);
+    assert(segs(11)==30);
+  //  assert(segs(1/0)==5);
+  //  assert(segs(0/0)==5);
+  //  assert(segs(undef)==5);
+}
+test_segs();
+
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

From 88e2fc0f2953ff8bbee2ea884ebec9972cdbe068 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Fri, 24 Jul 2020 00:10:36 +0100
Subject: [PATCH 03/24] Input data check review and some refactoring

Some functions have been changed as a consequence of the dat checking review. vector_axis was fully refactored. add_scalar was moved to arrays.scad
---
 tests/test_vectors.scad |  15 ++---
 vectors.scad            | 136 +++++++++++++++++++++++-----------------
 2 files changed, 84 insertions(+), 67 deletions(-)

diff --git a/tests/test_vectors.scad b/tests/test_vectors.scad
index 399be01..be3e542 100644
--- a/tests/test_vectors.scad
+++ b/tests/test_vectors.scad
@@ -1,4 +1,4 @@
-include <BOSL2/std.scad>
+include <../std.scad>
 
 
 module test_is_vector() {
@@ -9,17 +9,14 @@ module test_is_vector() {
     assert(is_vector(1) == false);
     assert(is_vector("foo") == false);
     assert(is_vector(true) == false);
+    assert(is_vector([0,0],nonzero=true) == false);
+    assert(is_vector([0,1e-12,0],nonzero=true) == false);
+    assert(is_vector([0,1e-6,0],nonzero=true) == true);
+    assert(is_vector([0,1e-6,0],nonzero=true,eps=1e-4) == false);
 }
 test_is_vector();
 
 
-module test_add_scalar() {
-    assert(add_scalar([1,2,3],3) == [4,5,6]);
-    assert(add_scalar([[1,2,3],[3,4,5]],3) == [[4,5,6],[6,7,8]]);
-}
-test_add_scalar();
-
-
 module test_vfloor() {
     assert_equal(vfloor([2.0, 3.14, 18.9, 7]), [2,3,18,7]);
     assert_equal(vfloor([-2.0, -3.14, -18.9, -7]), [-2,-4,-19,-7]);
@@ -56,7 +53,7 @@ module test_vabs() {
 }
 test_vabs();
 
-include <BOSL2/strings.scad>
+include <../strings.scad>
 module test_vang() {
     assert(vang([1,0])==0);
     assert(vang([0,1])==90);
diff --git a/vectors.scad b/vectors.scad
index 0030160..92e4866 100644
--- a/vectors.scad
+++ b/vectors.scad
@@ -20,32 +20,29 @@
 //   v = The value to test to see if it is a vector.
 //   length = If given, make sure the vector is `length` items long.
 // Example:
-//   is_vector(4);              // Returns false
-//   is_vector([4,true,false]); // Returns false
-//   is_vector([3,4,INF,5]);    // Returns false
-//   is_vector([3,4,5,6]);      // Returns true
-//   is_vector([3,4,undef,5]);  // Returns false
-//   is_vector([3,4,5],3);      // Returns true
-//   is_vector([3,4,5],4);      // Returns true
-//   is_vector([]);             // Returns false
-function is_vector(v,length) =
-    is_list(v) && is_num(0*(v*v)) && (is_undef(length)||len(v)==length);
+//   is_vector(4);                          // Returns false
+//   is_vector([4,true,false]);             // Returns false
+//   is_vector([3,4,INF,5]);                // Returns false
+//   is_vector([3,4,5,6]);                  // Returns true
+//   is_vector([3,4,undef,5]);              // Returns false
+//   is_vector([3,4,5],3);                  // Returns true
+//   is_vector([3,4,5],4);                  // Returns true
+//   is_vector([]);                         // Returns false
+//   is_vector([0,4,0],3,nonzero=true);     // Returns true
+//   is_vector([0,0,0],nonzero=true);       // Returns false
+//   is_vector([0,0,1e-12],nonzero=true);   // Returns false
+//   is_vector([],nonzero=true);            // Returns false
+function is_vector(v,length, nonzero=false, eps=EPSILON) =
+    is_list(v) && is_num(0*(v*v)) 
+    && (is_undef(length)|| len(v)==length)
+    && ( ! nonzero || ([]!=[for(vi=v) if(abs(vi)>=eps) 1]) );
 
+//***
+// including non_zero option
+// extended examples
 
-// Function: add_scalar()
-// Usage:
-//   add_scalar(v,s);
-// Description:
-//   Given a vector and a scalar, returns the vector with the scalar added to each item in it.
-//   If given a list of vectors, recursively adds the scalar to the each vector.
-// Arguments:
-//   v = The initial list of values.
-//   s = A scalar value to add to every item in the vector.
-// Example:
-//   add_scalar([1,2,3],3);            // Returns: [4,5,6]
-//   add_scalar([[1,2,3],[3,4,5]],3);  // Returns: [[4,5,6],[6,7,8]]
-function add_scalar(v,s) = [for (x=v) is_list(x)? add_scalar(x,s) : x+s];
-
+//***
+// add_scalar() is an array operation: moved to array.scad 
 
 // Function: vang()
 // Usage:
@@ -55,6 +52,7 @@ function add_scalar(v,s) = [for (x=v) is_list(x)? add_scalar(x,s) : x+s];
 //   Given a 2D vector, returns the angle in degrees counter-clockwise from X+ on the XY plane.
 //   Given a 3D vector, returns [THETA,PHI] where THETA is the number of degrees counter-clockwise from X+ on the XY plane, and PHI is the number of degrees up from the X+ axis along the XZ plane.
 function vang(v) =
+    assert( is_vector(v,2) || is_vector(v,3) , "Invalid vector")
     len(v)==2? atan2(v.y,v.x) :
     let(res=xyz_to_spherical(v)) [res[1], 90-res[2]];
 
@@ -68,7 +66,9 @@ function vang(v) =
 //   v2 = The second vector.
 // Example:
 //   vmul([3,4,5], [8,7,6]);  // Returns [24, 28, 30]
-function vmul(v1, v2) = [for (i = [0:1:len(v1)-1]) v1[i]*v2[i]];
+function vmul(v1, v2) = 
+    assert( is_vector(v1) && is_vector(v2,len(v1)), "Incompatible vectors")
+    [for (i = [0:1:len(v1)-1]) v1[i]*v2[i]];
 
 
 // Function: vdiv()
@@ -80,7 +80,9 @@ function vmul(v1, v2) = [for (i = [0:1:len(v1)-1]) v1[i]*v2[i]];
 //   v2 = The second vector.
 // Example:
 //   vdiv([24,28,30], [8,7,6]);  // Returns [3, 4, 5]
-function vdiv(v1, v2) = [for (i = [0:1:len(v1)-1]) v1[i]/v2[i]];
+function vdiv(v1, v2) = 
+    assert( is_vector(v1) && is_vector(v2,len(v1)), "Incompatible vectors")
+    [for (i = [0:1:len(v1)-1]) v1[i]/v2[i]];
 
 
 // Function: vabs()
@@ -89,19 +91,25 @@ function vdiv(v1, v2) = [for (i = [0:1:len(v1)-1]) v1[i]/v2[i]];
 //   v = The vector to get the absolute values of.
 // Example:
 //   vabs([-1,3,-9]);  // Returns: [1,3,9]
-function vabs(v) = [for (x=v) abs(x)];
+function vabs(v) =
+    assert( is_vector(v), "Invalid vector" ) 
+    [for (x=v) abs(x)];
 
 
 // Function: vfloor()
 // Description:
 //   Returns the given vector after performing a `floor()` on all items.
-function vfloor(v) = [for (x=v) floor(x)];
+function vfloor(v) =
+    assert( is_vector(v), "Invalid vector" ) 
+    [for (x=v) floor(x)];
 
 
 // Function: vceil()
 // Description:
 //   Returns the given vector after performing a `ceil()` on all items.
-function vceil(v) = [for (x=v) ceil(x)];
+function vceil(v) =
+    assert( is_vector(v), "Invalid vector" ) 
+    [for (x=v) ceil(x)];
 
 
 // Function: unit()
@@ -129,6 +137,7 @@ function unit(v, error=[[["ASSERT"]]]) =
 // Function: vector_angle()
 // Usage:
 //   vector_angle(v1,v2);
+//   vector_angle([v1,v2]);
 //   vector_angle(PT1,PT2,PT3);
 //   vector_angle([PT1,PT2,PT3]);
 // Description:
@@ -148,34 +157,38 @@ function unit(v, error=[[["ASSERT"]]]) =
 //   vector_angle([10,0,10], [0,0,0], [-10,10,0]);  // Returns: 120
 //   vector_angle([[10,0,10], [0,0,0], [-10,10,0]]);  // Returns: 120
 function vector_angle(v1,v2,v3) =
-    let(
-        vecs = !is_undef(v3)? [v1-v2,v3-v2] :
-            !is_undef(v2)? [v1,v2] :
-            len(v1) == 3? [v1[0]-v1[1],v1[2]-v1[1]] :
-            len(v1) == 2? v1 :
-            assert(false, "Bad arguments to vector_angle()"),
-        is_valid = is_vector(vecs[0]) && is_vector(vecs[1]) && vecs[0]*0 == vecs[1]*0
+    assert( ( is_undef(v3) && ( is_undef(v2) || same_shape(v1,v2) ) )
+            || is_consistent([v1,v2,v3]) ,
+            "Bad arguments.")
+    assert( is_vector(v1) || is_consistent(v1), "Bad arguments.") 
+    let( vecs = ! is_undef(v3) ? [v1-v2,v3-v2] :
+                ! is_undef(v2) ? [v1,v2] :
+                len(v1) == 3   ? [v1[0]-v1[1], v1[2]-v1[1]] 
+                               : v1
     )
-    assert(is_valid, "Bad arguments to vector_angle()")
+    assert(is_vector(vecs[0],2) || is_vector(vecs[0],3), "Bad arguments.")
     let(
         norm0 = norm(vecs[0]),
         norm1 = norm(vecs[1])
     )
-    assert(norm0>0 && norm1>0,"Zero length vector given to vector_angle()")
+    assert(norm0>0 && norm1>0, "Zero length vector.")
     // NOTE: constrain() corrects crazy FP rounding errors that exceed acos()'s domain.
     acos(constrain((vecs[0]*vecs[1])/(norm0*norm1), -1, 1));
-
+    
+//***
+// completing input data check
 
 // Function: vector_axis()
 // Usage:
 //   vector_axis(v1,v2);
+//   vector_axis([v1,v2]);
 //   vector_axis(PT1,PT2,PT3);
 //   vector_axis([PT1,PT2,PT3]);
 // Description:
 //   If given a single list of two vectors, like `vector_axis([V1,V2])`, returns the vector perpendicular the two vectors V1 and V2.
-//   If given a single list of three points, like `vector_axis([A,B,C])`, returns the vector perpendicular the line segments AB and BC.
-//   If given two vectors, like `vector_axis(V1,V1)`, returns the vector perpendicular the two vectors V1 and V2.
-//   If given three points, like `vector_axis(A,B,C)`, returns the vector perpendicular the line segments AB and BC.
+//   If given a single list of three points, like `vector_axis([A,B,C])`, returns the vector perpendicular to the plane through a, B and C.
+//   If given two vectors, like `vector_axis(V1,V2)`, returns the vector perpendicular to the two vectors V1 and V2.
+//   If given three points, like `vector_axis(A,B,C)`, returns the vector perpendicular to the plane through a, B and C.
 // Arguments:
 //   v1 = First vector or point.
 //   v2 = Second vector or point.
@@ -188,22 +201,29 @@ function vector_angle(v1,v2,v3) =
 //   vector_axis([10,0,10], [0,0,0], [-10,10,0]);  // Returns: [-0.57735, -0.57735, 0.57735]
 //   vector_axis([[10,0,10], [0,0,0], [-10,10,0]]);  // Returns: [-0.57735, -0.57735, 0.57735]
 function vector_axis(v1,v2=undef,v3=undef) =
-    (is_list(v1) && is_list(v1[0]) && is_undef(v2) && is_undef(v3))? (
-        assert(is_vector(v1.x))
-        assert(is_vector(v1.y))
-        len(v1)==3? assert(is_vector(v1.z)) vector_axis(v1.x, v1.y, v1.z) :
-        len(v1)==2? vector_axis(v1.x, v1.y) :
-        assert(false, "Bad arguments.")
-    ) :
-    (is_vector(v1) && is_vector(v2) && is_vector(v3))? vector_axis(v1-v2, v3-v2) :
-    (is_vector(v1) && is_vector(v2) && is_undef(v3))? let(
-        eps = 1e-6,
-        v1 = point3d(v1/norm(v1)),
-        v2 = point3d(v2/norm(v2)),
-        v3 = (norm(v1-v2) > eps && norm(v1+v2) > eps)? v2 :
-            (norm(vabs(v2)-UP) > eps)? UP :
-            RIGHT
-    ) unit(cross(v1,v3)) : assert(false, "Bad arguments.");
+    is_vector(v3)
+    ?   assert(is_consistent([v3,v2,v1]), "Bad arguments.")
+        vector_axis(v1-v2, v3-v2)
+    :   assert( is_undef(v3), "Bad arguments.")
+        is_undef(v2)
+        ?   assert( is_list(v1), "Bad arguments.")
+            len(v1) == 2 
+            ?   vector_axis(v1[0],v1[1]) 
+            :   vector_axis(v1[0],v1[1],v1[2])
+        :   assert( is_vector(v1,nonzero=true) && is_vector(v2,nonzero=true) && is_consistent([v1,v2])
+                    , "Bad arguments.")  
+            let(
+              eps = 1e-6,
+              w1 = point3d(v1/norm(v1)),
+              w2 = point3d(v2/norm(v2)),
+              w3 = (norm(w1-w2) > eps && norm(w1+w2) > eps) ? w2 
+							     : (norm(vabs(w2)-UP) > eps)? UP 
+									 : RIGHT
+            ) unit(cross(w1,w3));
 
 
+//***
+// completing input data check and refactoring 
+// Note: vector_angle and vector_axis have the same kind of inputs and two code strategy alternatives
+
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

From 877a07b7113757cf2d70b1f61a949a86d5d4cad9 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Fri, 24 Jul 2020 00:19:49 +0100
Subject: [PATCH 04/24] Full review of input data checking, formating and
 improvement in some functions

Refactorigng of list_set(), list_insert() and list_remove() without any sorting and speed gain.

Some argument names changed for consistence between functions.

test_array() updated.
---
 arrays.scad            | 695 ++++++++++++++++++++++++-----------------
 tests/test_arrays.scad | 123 +++++---
 2 files changed, 492 insertions(+), 326 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index 9d9463a..ac18535 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -44,20 +44,24 @@
 //   select(l, [1:3]);  // Returns [4,5,6]
 //   select(l, [1,3]);  // Returns [4,6]
 function select(list, start, end=undef) =
+    assert( is_list(list) || is_string(list), "Invalid list.")
     let(l=len(list))
-    end==undef? (
-        is_num(start)?
-            let(s=(start%l+l)%l) list[s] :
-            assert(is_list(start) || is_range(start), "Invalid start parameter")
-            [for (i=start) list[(i%l+l)%l]]
-    ) : (
-        assert(is_num(start), "Invalid start parameter.")
-        assert(is_num(end), "Invalid end parameter.")
-        let(s=(start%l+l)%l, e=(end%l+l)%l)
-        (s<=e)?
-            [for (i = [s:1:e]) list[i]] :
-            concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]])
-    );
+    l==0 ? []
+    :   end==undef? 
+            is_num(start)?
+                list[ (start%l+l)%l ]
+            :   assert( is_list(start) || is_range(start), "Invalid start parameter")
+                [for (i=start) list[ (i%l+l)%l ] ]
+        :   assert(is_num(start), "Invalid start parameter.")
+            assert(is_num(end), "Invalid end parameter.")
+            let( s = (start%l+l)%l, e = (end%l+l)%l )
+            (s <= e)? [for (i = [s:1:e]) list[i]]
+            :   concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ;
+
+
+//***
+// 1. avoids undef when the list is void
+// 2. format
 
 
 // Function: slice()
@@ -65,8 +69,8 @@ function select(list, start, end=undef) =
 //   Returns a slice of a list.  The first item is index 0.
 //   Negative indexes are counted back from the end.  The last item is -1.
 // Arguments:
-//   arr = The array/list to get the slice of.
-//   st = The index of the first item to return.
+//   list = The array/list to get the slice of.
+//   start = The index of the first item to return.
 //   end = The index after the last item to return, unless negative, in which case the last item to return.
 // Example:
 //   slice([3,4,5,6,7,8,9], 3, 5);   // Returns [6,7]
@@ -74,24 +78,47 @@ function select(list, start, end=undef) =
 //   slice([3,4,5,6,7,8,9], 1, 1);   // Returns []
 //   slice([3,4,5,6,7,8,9], 6, -1);  // Returns [9]
 //   slice([3,4,5,6,7,8,9], 2, -2);  // Returns [5,6,7,8]
-function slice(arr,st,end) = let(
-        l=len(arr),
-        s=st<0?(l+st):st,
-        e=end<0?(l+end+1):end
-    ) [for (i=[s:1:e-1]) if (e>s) arr[i]];
+function slice(list,start,end) =
+    assert( is_list(list), "Invalid list" )
+    assert( is_finite(start) && is_finite(end), "Invalid number(s)" )
+    let( l = len(list) )
+    l==0 ? []
+    :   let(
+            s = start<0? (l+start): start,
+            e = end<0? (l+end+1): end
+        ) [for (i=[s:1:e-1]) if (e>s) list[i]];
 
 
+//***
+// 1. for sake of consistence, the list argument identifier was changed to list and st to start
+// 2. avoids undef when the list is void
+// 3. checks inputs of start and end
+
 // Function: in_list()
-// Description: Returns true if value `x` is in list `l`.
+// Description: Returns true if value `val` is in list `list`.
 // Arguments:
-//   x = The value to search for.
-//   l = The list to search.
-//   idx = If given, searches the given subindexes for matches for `x`.
+//   val = The simple value to search for.
+//   list = The list to search.
+//   idx = If given, searches the given subindexes for matches for `val`.
 // Example:
 //   in_list("bar", ["foo", "bar", "baz"]);  // Returns true.
 //   in_list("bee", ["foo", "bar", "baz"]);  // Returns false.
 //   in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1);  // Returns true.
-function in_list(x,l,idx=undef) = search([x], l, num_returns_per_match=1, index_col_num=idx) != [[]];
+//   in_list("bee", ["foo", "bar", ["bee",0]]);  // Returns true.
+//   in_list("bar", [[2,"foo"], [4,["bar"]], [3,"baz"]]);  // Returns false.
+// Note:
+//   When `val==NAN` the answer will be false for any list.
+//   `val` cannot be a boolean.
+//   When the some element in `list` is a list containing `val` at it first element, the return is also true. 
+function in_list(val,list,idx=undef) = 
+    let( s = search([val], list, num_returns_per_match=1, index_col_num=idx)[0] )
+    s==[] || s[0]==[] ? false
+    : is_undef(idx) ? val==list[s] 
+    : val==list[s][idx];
+    
+//***
+// 1. for sake of consistence, the arguments were changed to val and list
+// 2. in_list(0,[ 1, [0,1],2])) was returning true
 
 
 // Function: min_index()
@@ -104,11 +131,15 @@ function in_list(x,l,idx=undef) = search([x], l, num_returns_per_match=1, index_
 //   vals = vector of values
 //   all = set to true to return indices of all occurences of the minimum.  Default: false
 // Example:
-//   min_index([5,3,9,6,2,7,8,2,1]); // Returns: 4
-//   min_index([5,3,9,6,2,7,8,2,1],all=true); // Returns: [4,7]
+//   min_index([5,3,9,6,2,7,8,2,1]); // Returns: 8
+//   min_index([5,3,9,6,2,7,8,2,7],all=true); // Returns: [4,7]
 function min_index(vals, all=false) =
+    assert( is_vector(vals) && len(vals)>0 , "Invalid or empty list of numbers.")
     all ? search(min(vals),vals,0) : search(min(vals), vals)[0];
 
+//***
+// 1. corrected examples
+// 2. input data check
 
 // Function: max_index()
 // Usage:
@@ -123,8 +154,11 @@ function min_index(vals, all=false) =
 //   max_index([5,3,9,6,2,7,8,9,1]); // Returns: 2
 //   max_index([5,3,9,6,2,7,8,9,1],all=true); // Returns: [2,7]
 function max_index(vals, all=false) =
+    assert( is_vector(vals) && len(vals)>0 , "Invalid or empty list of numbers.")
     all ? search(max(vals),vals,0) : search(max(vals), vals)[0];
 
+//***
+// 1. input data check
 
 // Function: list_increasing()
 // Usage:
@@ -175,9 +209,12 @@ function list_decreasing(list) =
 //   repeat([1,2,3],3);   // Returns [[1,2,3], [1,2,3], [1,2,3]]
 function repeat(val, n, i=0) =
     is_num(n)? [for(j=[1:1:n]) val] :
+    assert( is_list(n), "Invalid count number.")
     (i>=len(n))? val :
     [for (j=[1:1:n[i]]) repeat(val, n, i+1)];
 
+//***
+// 1. input data check
 
 // Function: list_range()
 // Usage:
@@ -188,7 +225,7 @@ function repeat(val, n, i=0) =
 // 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 fron `s`
+//   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.
@@ -201,25 +238,28 @@ function repeat(val, n, i=0) =
 //   list_range(n=4, s=3, step=3);   // Returns [3,6,9,12]
 //   list_range(n=5, s=0, e=10);     // Returns [0, 2.5, 5, 7.5, 10]
 //   list_range(e=3);                // Returns [0,1,2,3]
-//   list_range(e=6, step=2);        // Returns [0,2,4,6]
+//   list_range(e=7, step=2);        // Returns [0,2,4,6]
 //   list_range(s=3, e=5);           // Returns [3,4,5]
 //   list_range(s=3, e=8, step=2);   // Returns [3,5,7]
-//   list_range(s=4, e=8, step=2);   // Returns [4,6,8]
+//   list_range(s=4, e=8.3, step=2); // Returns [4,6,8]
 //   list_range(n=4, s=[3,4], step=[2,3]);  // Returns [[3,4], [5,7], [7,10], [9,13]]
 function list_range(n=undef, s=0, e=undef, step=undef) =
-    (n!=undef && e!=undef)? (
-        assert(is_undef(n) || is_undef(e) || is_undef(step), "At most 2 of n, e, and step can be given.")
-        [for (i=[0:1:n-1]) s+(e-s)*i/(n-1)]
-    ) : let(step = default(step,1))
-    (n!=undef)? [for (i=[0:1:n-1]) let(v=s+step*i) v] :
-    (e!=undef)? [for (v=[s:step:e]) v] :
-    assert(e!=undef||n!=undef, "Must supply one of `n` or `e`.");
-
+    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) )
+    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.")
+        [for (v=[s:step:e]) v] ;
+    
+//***
+// 1. input data check
+// 2. reworked accordingly
 
 
 // Section: List Manipulation
 
-
 // Function: reverse()
 // Description: Reverses a list/array.
 // Arguments:
@@ -251,34 +291,44 @@ function reverse(list) =
 //   l8 = list_rotate([1,2,3,4,5],5);  // Returns: [1,2,3,4,5]
 //   l9 = list_rotate([1,2,3,4,5],6);  // Returns: [2,3,4,5,1]
 function list_rotate(list,n=1) =
-    assert(is_list(list)||is_string(list))
-    assert(is_num(n))
+    assert(is_list(list)||is_string(list), "Invalid list or string.")
+    assert(is_finite(n), "Invalid number")
     select(list,n,n+len(list)-1);
 
+//***
+// 1. review of the input data check
 
 // Function: deduplicate()
 // Usage:
-//   deduplicate(list);
+//   deduplicate(list,[close],[eps]);
 // Description:
 //   Removes consecutive duplicate items in a list.
+//   When `eps` is zero, the comparison between consecutive items is exact.
+//   Otherwise, when all list items and subitems are numbers, the comparison is within the tolerance `eps`.
 //   This is different from `unique()` in that the list is *not* sorted.
 // Arguments:
 //   list = The list to deduplicate.
 //   closed = If true, drops trailing items if they match the first list item.
-//   eps = The maximum difference to allow between numbers or vectors.
+//   eps = The maximum tolerance between items.
 // Examples:
 //   deduplicate([8,3,4,4,4,8,2,3,3,8,8]);  // Returns: [8,3,4,8,2,3,8]
 //   deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]);  // Returns: [8,3,4,8,2,3]
 //   deduplicate("Hello");  // Returns: ["H","e","l","o"]
 //   deduplicate([[3,4],[7,2],[7,1.99],[1,4]],eps=0.1);  // Returns: [[3,4],[7,2],[1,4]]
+//   deduplicate([[7,undef],[7,undef],[1,4],[1,4+1e-12],eps=0);    // Returns: [[7,undef],[1,4],[1,4+1e-12]]
 function deduplicate(list, closed=false, eps=EPSILON) =
     assert(is_list(list)||is_string(list))
-    let(
-        l = len(list),
-        end = l-(closed?0:1)
-    ) (is_num(list[0]) || is_vector(list[0]))?
-        [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]] :
-        [for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]];
+    let( l = len(list),
+         end = l-(closed?0:1) )
+    is_string(list) || (eps==0)
+    ? [for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]]
+    : [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]];
+
+//***
+// 1. change Usage:, Description and Arguments
+// 2. reworked accordingly
+// 3. when eps==0, doesn't call approx
+// 4. the list may contain non numerical itens
 
 
 // Function: deduplicate_indexed()
@@ -296,24 +346,27 @@ function deduplicate(list, closed=false, eps=EPSILON) =
 //   deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1]);  // Returns: [1,4,3,2,0,1]
 //   deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1], closed=true);  // Returns: [1,4,3,2,0]
 function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
-    assert(is_list(list)||is_string(list))
-    assert(indices==[] || is_vector(indices))
+    assert(is_list(list)||is_string(list), "Improper list or string.")
     indices==[]? [] :
-    let(
-        l = len(indices),
-        end = l-(closed?0:1)
-    ) [
-        for (i = [0:1:l-1]) let(
-            a = list[indices[i]],
-            b = list[indices[(i+1)%l]],
-            eq = (a == b)? true :
-                (a*0 != b*0)? false :
-                is_num(a)? approx(a, b, eps=eps) :
-                is_vector(a)? approx(a, b, eps=eps) :
-                false
-        ) if (i==end || !eq) indices[i]
+    assert(is_vector(indices), "Indices must be a list of numbers.")
+    let( l = len(indices),
+         end = l-(closed?0:1) ) 
+    [ for (i = [0:1:l-1]) 
+        let(
+           a = list[indices[i]],
+           b = list[indices[(i+1)%l]],
+           eq = (a == b)? true :
+                (a*0 != b*0) || (eps==0)? false :
+                is_num(a) || is_vector(a) ? approx(a, b, eps=eps) 
+                : false
+        ) 
+        if (i==end || !eq) indices[i]
     ];
 
+//**
+// 1. msg of asserts
+// 2. format
+
 
 // Function: repeat_entries()
 // Usage:
@@ -341,17 +394,21 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
 //   echo(repeat_entries(list, 6, exact=false));  // Ouputs [0,0,1,1,2,2,3,3]
 //   echo(repeat_entries(list, [1,1,2,1], exact=false));  // Ouputs [0,1,2,2,3]
 function repeat_entries(list, N, exact = true) =
-    assert(is_list(list))
-    assert((is_num(N) && N>0) || is_vector(N),"Parameter N to repeat_entries must be postive number or vector")
+    assert(is_list(list) && len(list)>0, "The list cannot be void.")
+    assert((is_finite(N) && N>0) || is_vector(N,len(list)),
+		        "Parameter N must be a number greater than zero or vector with the same length of `list`")
     let(
         length = len(list),
-        reps_guess = is_list(N)?
-            assert(len(N)==len(list), "Vector parameter N to repeat_entries has the wrong length")
-            N : repeat(N/length,length),
-        reps = exact? _sum_preserving_round(reps_guess) :
-            [for (val=reps_guess) round(val)]
+        reps_guess = is_list(N)? N : repeat(N/length,length),
+        reps = exact ?
+				         _sum_preserving_round(reps_guess) 
+               : [for (val=reps_guess) round(val)]
     )
     [for(i=[0:length-1]) each repeat(list[i],reps[i])];
+		
+//***
+// 1. Complete asserts
+// 2. Format
 
 
 // Function: list_set()
@@ -359,12 +416,11 @@ function repeat_entries(list, N, exact = true) =
 //   list_set(list, indices, values, [dflt], [minlen])
 // Description:
 //   Takes the input list and returns a new list such that `list[indices[i]] = values[i]` for all of
-//   the (index,value) pairs supplied.  If you supply `indices` that are beyond the length of the list
-//   then the list is extended and filled in with the `dflt` value.  If you set `minlen` then the list is
-//   lengthed, if necessary, by padding with `dflt` to that length.  The `indices` list can be in any
-//   order but run time will be (much) faster for long lists if it is already sorted.  Reptitions are
-//   not allowed.  If `indices` is given as a non-list scalar, then that index of the given `list` will
-//   be set to the value of `values`.
+//   the (index,value) pairs supplied and unchanged for other indices.  If you supply `indices` that are 
+//   beyond the length of the list then the list is extended and filled in with the `dflt` value.  
+//   If you set `minlen` then the list is lengthed, if necessary, by padding with `dflt` to that length.  
+//   Repetitions in `indices` are not allowed. The lists `indices` and `values` must have the same length.  
+//   If `indices` is given as a scalar, then that index of the given `list` will be set to the scalar value of `values`.
 // Arguments:
 //   list = List to set items in.  Default: []
 //   indices = List of indices into `list` to set.
@@ -374,92 +430,89 @@ function repeat_entries(list, N, exact = true) =
 // Examples:
 //   list_set([2,3,4,5], 2, 21);  // Returns: [2,3,21,5]
 //   list_set([2,3,4,5], [1,3], [81,47]);  // Returns: [2,81,4,47]
-function list_set(list=[],indices,values,dflt=0,minlen=0) =
+function list_set(list=[],indices,values,dflt=0,minlen=0) = 
     assert(is_list(list)||is_string(list))
     !is_list(indices)? (
-        (is_num(indices) && indices<len(list))? [for (i=idx(list)) i==indices? values : list[i]] :
-        list_set(list,[indices],[values],dflt)
-    ) :
-    assert(len(indices)==len(values),"Index list and value list must have the same length")
-    let(
-        sortind = list_increasing(indices) ? list_range(len(indices)) : sortidx(indices),
-        lastind = len(indices)==0 ? -1 : indices[select(sortind,-1)]
-    )
-    concat(
-        [for(j=[0:1:indices[sortind[0]]-1]) j>=len(list) ? dflt : list[j]],
-        [values[sortind[0]]], 
-        [for(i=[1:1:len(sortind)-1]) each
-            assert(indices[sortind[i]]!=indices[sortind[i-1]],"Repeated index")
-            concat(
-                [for(j=[1+indices[sortind[i-1]]:1:indices[sortind[i]]-1]) j>=len(list) ? dflt : list[j]],
-                [values[sortind[i]]]
-            )
-        ],
-        slice(list,1+lastind, len(list)),
-        repeat(dflt, minlen-lastind-1)
-    );
+        (is_finite(indices) && indices<len(list))? 
+				   [for (i=idx(list)) i==indices? values : list[i]]
+        :  list_set(list,[indices],[values],dflt) )
+    :   assert(is_vector(indices) && is_list(values) && len(values)==len(indices) ,
+		           "Index list and value list must have the same length")
+				let( midx = max(len(list)-1, max(indices)) )
+				[ for(i=[0:midx] ) 
+						let( j = search(i,indices,0),
+							   k = j[0] )
+						assert( len(j)<2, "Repeated indices are not acceptable." )
+						k!=undef ? values[k] :
+						i<len(list) ? list[i]:
+						    dflt ,
+					each repeat(dflt, minlen-max(indices))
+				];
+			
+//***
+// a full refactoring without sorting; its is quite faster than the original
 
 
 // Function: list_insert()
 // Usage:
-//   list_insert(list, pos, elements);
+//   list_insert(list, indices, values);
 // Description:
-//   Insert `elements` into `list` before position `pos`.
+//   Insert `values` into `list` before position `indices`.
 // Example:
 //   list_insert([3,6,9,12],1,5);  // Returns [3,5,6,9,12]
 //   list_insert([3,6,9,12],[1,3],[5,11]);  // Returns [3,5,6,9,11,12]
-function list_insert(list, pos, elements, _i=0) =
+function list_insert(list, indices, values, _i=0) = 
     assert(is_list(list)||is_string(list))
-    is_list(pos)? (
-        assert(len(pos)==len(elements))
-        let(
-            idxs = sortidx(pos),
-            lastidx = pos[idxs[len(idxs)-1]]
-        )
-        concat(
-            [
-                for(i=idx(idxs)) each concat(
-                    assert(pos[idxs[i]]<=len(list), "Indices in pos must be <= len(list)")
-                    [for (j=[(i==0?0:pos[idxs[i-1]]):1:pos[idxs[i]]-1]) list[j]],
-                    [elements[idxs[i]]]
-                )
-            ],
-            [for (j=[lastidx:1:len(list)-1]) list[j]]
-        )
-    ) : (
-        assert(pos<=len(list), "Indices in pos must be <= len(list)")
-        concat(
-            slice(list,0,pos),
-            elements,
-            (pos<len(list)? slice(list,pos,-1) : [])
-        )
-    );
+    ! is_list(indices)? 
+		    assert( is_finite(indices) && is_finite(values), "Invalid indices/values." ) 
+				assert( indices<=len(list), "Indices must be <= len(list) ." )
+        [for (i=idx(list)) each ( i==indices?  [ values, list[i] ] : [ list[i] ] ) ]
+    :   assert( is_vector(indices) && is_list(values) && len(values)==len(indices) ,
+		           "Index list and value list must have the same length")
+			  assert( max(indices)<=len(list), "Indices must be <= len(list) ." )
+				let( maxidx = max(indices),
+             minidx	= min(indices) )
+				[ for(i=[0:1:minidx-1] ) list[i],
+          for(i=[minidx: min(maxidx, len(list)-1)] ) 
+						let( j = search(i,indices,0),
+							   k = j[0],
+                 x = assert( len(j)<2, "Repeated indices are not acceptable." )
+							)
+						each ( k != undef  ? [ values[k], list[i] ] : [ list[i] ] ),
+					for(i=[min(maxidx, len(list)-1)+1:1:len(list)-1] ) list[i],
+					if(maxidx==len(list)) values[max_index(indices)]
+				];
+
+
+//***
+// Full refactoring without sorting
+// For sake of consistence, change `pos` and `elements` to `indices` and `values`
 
 
 // Function: list_remove()
 // Usage:
-//   list_remove(list, elements)
+//   list_remove(list, indices)
 // Description:
-//   Remove all items from `list` whose indexes are in `elements`.
+//   Remove all items from `list` whose indexes are in `indices`.
 // Arguments:
 //   list = The list to remove items from.
-//   elements = The list of indexes of items to remove.
+//   indices = The list of indexes of items to remove.
 // Example:
 //   list_insert([3,6,9,12],1);      // Returns: [3,9,12]
 //   list_insert([3,6,9,12],[1,3]);  // Returns: [3,9]
-function list_remove(list, elements) =
-    assert(is_list(list)||is_string(list))
-    !is_list(elements) ? list_remove(list,[elements]) :
-    len(elements)==0 ? list :
-    let(
-        sortind = list_increasing(elements) ? list_range(len(elements)) : sortidx(elements),
-        lastind = elements[select(sortind,-1)]
-    )
-    assert(lastind<len(list),"Element index beyond list end")
-    concat(slice(list, 0, elements[sortind[0]]),
-        [for(i=[1:1:len(sortind)-1]) each slice(list,1+elements[sortind[i-1]], elements[sortind[i]])],
-        slice(list,1+lastind, len(list))
-    );
+function list_remove(list, indices) =
+    assert(is_list(list)||is_string(list), "Invalid list/string." )
+    is_finite(indices) 
+		?   [  for(i=[0:1:min(indices, len(list)-1)-1]) list[i],
+           for(i=[min(indices, len(list)-1)+1:1:len(list)-1]) list[i]	]
+		:   assert( is_vector(indices), "Invalid list `indices`." )
+				len(indices)==0 ? list :
+		    [ for(i=[0:len(list)-1])
+						if ( []==search(i,indices,1) ) list[i] ]; 
+
+//***
+// Refactoring without sort
+// For sake of consistence, change `elements` to `indices`
 
 
 // Function: list_remove_values()
@@ -499,8 +552,8 @@ function list_remove_values(list,values=[],all=false) =
 // Example:
 //   bselect([3,4,5,6,7], [false,true,true,false,true]);  // Returns: [4,5,7]
 function bselect(array,index) =
-    assert(is_list(array)||is_string(array))
-    assert(is_list(index))
+    assert(is_list(array)||is_string(array), "Improper array." )
+    assert(is_list(index) && len(index)>=len(array) , "Improper index list." )
     [for(i=[0:len(array)-1]) if (index[i]) array[i]];
 
 
@@ -520,15 +573,17 @@ function bselect(array,index) =
 //   list_bset([false,true,false,true,false], [3,4]);  // Returns: [0,3,0,4,0]
 //   list_bset([false,true,false,true,false], [3,4],dflt=1);  // Returns: [1,3,1,4,1]
 function list_bset(indexset, valuelist, dflt=0) =
-    assert(is_list(indexset))
-    assert(is_list(valuelist))
-    let(
-        trueind = search([true], indexset,0)[0]
-    ) concat(
+    assert(is_list(indexset), "The index set is not a list." )
+    assert(is_list(valuelist), "The `valuelist` is not a list." )
+//       trueind = search([true], indexset,0)[0]
+    let( trueind = [for(i=[0:len(indexset)-1]) if(indexset[i]) i ] )
+    concat(
         list_set([],trueind, valuelist, dflt=dflt),    // Fill in all of the values
         repeat(dflt,len(indexset)-max(trueind)-1)  // Add trailing values so length matches indexset
     );
 
+//***
+// search might return false results depending if it identifies `true` with 1.
 
 
 // Section: List Length Manipulation
@@ -537,58 +592,65 @@ function list_bset(indexset, valuelist, dflt=0) =
 // Description:
 //   Returns the length of the shortest sublist in a list of lists.
 // Arguments:
-//   vecs = A list of lists.
-function list_shortest(vecs) =
-    assert(is_list(vecs)||is_string(list))
-    min([for (v = vecs) len(v)]);
+//   array = A list of lists.
+function list_shortest(array) =
+    assert(is_list(array)||is_string(list), "Invalid input." )
+    min([for (v = array) len(v)]);
 
 
+//***
+// parameter name changed here and in the following for sake of consistence. It was `vecs`
+
 // Function: list_longest()
 // Description:
 //   Returns the length of the longest sublist in a list of lists.
 // Arguments:
-//   vecs = A list of lists.
-function list_longest(vecs) =
-    assert(is_list(vecs)||is_string(list))
-    max([for (v = vecs) len(v)]);
+//   array = A list of lists.
+function list_longest(array) =
+    assert(is_list(array)||is_string(list), "Invalid input." )
+    max([for (v = array) len(v)]);
 
 
 // Function: list_pad()
 // Description:
-//   If the list `v` is shorter than `minlen` length, pad it to length with the value given in `fill`.
+//   If the list `array` is shorter than `minlen` length, pad it to length with the value given in `fill`.
 // Arguments:
-//   v = A list.
+//   array = A list.
 //   minlen = The minimum length to pad the list to.
 //   fill = The value to pad the list with.
-function list_pad(v, minlen, fill=undef) =
-    assert(is_list(v)||is_string(list))
-    concat(v,repeat(fill,minlen-len(v)));
+function list_pad(array, minlen, fill=undef) =
+    assert(is_list(array)||is_string(list), "Invalid input." )
+    concat(array,repeat(fill,minlen-len(array)));
 
 
 // Function: list_trim()
 // Description:
-//   If the list `v` is longer than `maxlen` length, truncates it to be `maxlen` items long.
+//   If the list `array` is longer than `maxlen` length, truncates it to be `maxlen` items long.
 // Arguments:
-//   v = A list.
+//   array = A list.
 //   minlen = The minimum length to pad the list to.
-function list_trim(v, maxlen) =
-    assert(is_list(v)||is_string(list))
-    [for (i=[0:1:min(len(v),maxlen)-1]) v[i]];
+function list_trim(array, maxlen) =
+    assert(is_list(array)||is_string(list), "Invalid input." )
+    [for (i=[0:1:min(len(array),maxlen)-1]) array[i]];
 
 
 // Function: list_fit()
 // Description:
-//   If the list `v` is longer than `length` items long, truncates it to be exactly `length` items long.
-//   If the list `v` is shorter than `length` items long, pad it to length with the value given in `fill`.
+//   If the list `array` is longer than `length` items long, truncates it to be exactly `length` items long.
+//   If the list `array` is shorter than `length` items long, pad it to length with the value given in `fill`.
 // Arguments:
-//   v = A list.
+//   array = A list.
 //   minlen = The minimum length to pad the list to.
 //   fill = The value to pad the list with.
-function list_fit(v, length, fill) =
-    assert(is_list(v)||is_string(list))
-    let(l=len(v)) (l==length)? v : (l>length)? list_trim(v,length) : list_pad(v,length,fill);
-
+function list_fit(array, length, fill) =
+    assert(is_list(array)||is_string(list), "Invalid input." )
+    let(l=len(array)) 
+		l==length ? array : 
+		l> length ? list_trim(array,length) 
+		          : list_pad(array,length,fill);
 
+//***
+// formating
 
 // Section: List Shuffling and Sorting
 
@@ -596,51 +658,59 @@ function list_fit(v, length, fill) =
 // Description:
 //   Shuffles the input list into random order.
 function shuffle(list) =
-    assert(is_list(list)||is_string(list))
+    assert(is_list(list)||is_string(list), "Invalid input." )
     len(list)<=1 ? list :
     let (
         rval = rands(0,1,len(list)),
         left  = [for (i=[0:len(list)-1]) if (rval[i]< 0.5) list[i]],
         right = [for (i=[0:len(list)-1]) if (rval[i]>=0.5) list[i]]
-    ) concat(shuffle(left), shuffle(right));
+    ) 
+		concat(shuffle(left), shuffle(right));
 
 
 // Sort a vector of scalar values
 function _sort_scalars(arr) =
-    len(arr)<=1 ? arr : let(
+    len(arr)<=1 ? arr : 
+		let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [ for (y = arr) if (y  < pivot) y ],
         equal   = [ for (y = arr) if (y == pivot) y ],
         greater = [ for (y = arr) if (y  > pivot) y ]
-    ) concat( _sort_scalars(lesser), equal, _sort_scalars(greater) );
+    ) 
+		concat( _sort_scalars(lesser), equal, _sort_scalars(greater) );
 
 
 // Sort a vector of vectors based on the first entry only of each vector
 function _sort_vectors1(arr) =
     len(arr)<=1 ? arr :
-    !(len(arr)>0) ? [] : let(
+    !(len(arr)>0) ? [] : 
+		let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [ for (y = arr) if (y[0]  < pivot[0]) y ],
         equal   = [ for (y = arr) if (y[0] == pivot[0]) y ],
         greater = [ for (y = arr) if (y[0]  > pivot[0]) y ]
-    ) concat( _sort_vectors1(lesser), equal, _sort_vectors1(greater) );
+    ) 
+		concat( _sort_vectors1(lesser), equal, _sort_vectors1(greater) );
 
 
 // Sort a vector of vectors based on the first two entries of each vector
 // Lexicographic order, remaining entries of vector ignored
 function _sort_vectors2(arr) =
     len(arr)<=1 ? arr :
-    !(len(arr)>0) ? [] : let(
+    !(len(arr)>0) ? [] : 
+		let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [ for (y = arr) if (y[0] < pivot[0] || (y[0]==pivot[0] && y[1]<pivot[1])) y ],
         equal   = [ for (y = arr) if (y[0] == pivot[0] && y[1]==pivot[1]) y ],
-        greater  = [ for (y = arr) if (y[0] > pivot[0] || (y[0]==pivot[0] && y[1]>pivot[1])) y ]
-    ) concat( _sort_vectors2(lesser), equal, _sort_vectors2(greater) );
+        greater = [ for (y = arr) if (y[0] > pivot[0] || (y[0]==pivot[0] && y[1]>pivot[1])) y ]
+    ) 
+		concat( _sort_vectors2(lesser), equal, _sort_vectors2(greater) );
 
 // Sort a vector of vectors based on the first three entries of each vector
 // Lexicographic order, remaining entries of vector ignored
 function _sort_vectors3(arr) =
-    len(arr)<=1 ? arr : let(
+    len(arr)<=1 ? arr : 
+		let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [
             for (y = arr) if (
@@ -671,13 +741,15 @@ function _sort_vectors3(arr) =
                 )
             ) y
         ]
-    ) concat( _sort_vectors3(lesser), equal, _sort_vectors3(greater) );
+    ) 
+		concat( _sort_vectors3(lesser), equal, _sort_vectors3(greater) );
 
 
 // Sort a vector of vectors based on the first four entries of each vector
 // Lexicographic order, remaining entries of vector ignored
 function _sort_vectors4(arr) =
-    len(arr)<=1 ? arr : let(
+    len(arr)<=1 ? arr : 
+		let(
         pivot = arr[floor(len(arr)/2)],
         lesser = [
             for (y = arr) if (
@@ -719,26 +791,34 @@ function _sort_vectors4(arr) =
                 )
             ) y
         ]
-    ) concat( _sort_vectors4(lesser), equal, _sort_vectors4(greater) );
+    ) 
+		concat( _sort_vectors4(lesser), equal, _sort_vectors4(greater) );
+		
 
+function _sort_eval(array, eval) =
+    let( ival  = [for(i=[0:len(array)-1]) [eval[i], i] ],
+         sival = _sort_vectors1(arr) )
+		[for(yi=sival) array[yi[1]] ];
 
 function _sort_general(arr, idx=undef) =
     (len(arr)<=1) ? arr :
     let(
         pivot = arr[floor(len(arr)/2)],
         pivotval = idx==undef? pivot : [for (i=idx) pivot[i]],
-        compare = [
-            for (entry = arr) let(
-                val = idx==undef? entry : [for (i=idx) entry[i]],
-                cmp = compare_vals(val, pivotval)
-            ) cmp
-        ],
+				compare = 
+				    is_undef(idx) ? [for(entry=arr) compare_vals(entry, pivotval) ] :
+            [ for (entry = arr) 
+					      let( val = [for (i=idx) entry[i] ] )
+                compare_vals(val, pivotval) ] ,
         lesser  = [ for (i = [0:1:len(arr)-1]) if (compare[i] < 0) arr[i] ],
         equal   = [ for (i = [0:1:len(arr)-1]) if (compare[i] ==0) arr[i] ],
         greater = [ for (i = [0:1:len(arr)-1]) if (compare[i] > 0) arr[i] ]
     )
     concat(_sort_general(lesser,idx), equal, _sort_general(greater,idx));
 
+//***
+// format simplification
+
 
 // Function: sort()
 // Usage:
@@ -757,17 +837,22 @@ function _sort_general(arr, idx=undef) =
 //   sorted = sort(l);  // Returns [2,3,8,9,12,16,23,34,37,45,89]
 function sort(list, idx=undef) =
     !is_list(list) || len(list)<=1 ? list :
+		assert( is_undef(idx) || is_finite(idx) || is_vector(idx), "Invalid indices.")
     is_def(idx) ? _sort_general(list,idx) :
     let(size = array_dim(list))
     len(size)==1 ? _sort_scalars(list) :
-    len(size)==2 && size[1] <=4 ? (
+    len(size)==2 && size[1] <=4 
+		? (
         size[1]==0 ? list :
         size[1]==1 ? _sort_vectors1(list) :
         size[1]==2 ? _sort_vectors2(list) :
-        size[1]==3 ? _sort_vectors3(list) :
-        /*size[1]==4*/ _sort_vectors4(list)
-    ) : _sort_general(list);
+        size[1]==3 ? _sort_vectors3(list)
+   /*size[1]==4*/  : _sort_vectors4(list)
+      ) 
+		: _sort_general(list);
 
+//***
+// Formating and input check
 
 // Function: sortidx()
 // Description:
@@ -791,22 +876,29 @@ function sort(list, idx=undef) =
 //   idxs2 = sortidx(lst, idx=0); // Returns: [1,2,0,3]
 //   idxs3 = sortidx(lst, idx=[1,3]); // Returns: [3,0,2,1]
 function sortidx(list, idx=undef) =
-    list==[] ? [] : let(
+    assert( is_list(list) || is_string(list) , "Invalid input." )
+		assert( is_undef(idx) || is_finite(idx) || is_vector(idx), "Invalid indices.")
+    list==[] ? [] : 
+		let(
         size = array_dim(list),
-        aug = is_undef(idx) && (len(size) == 1 || (len(size) == 2 && size[1]<=4))?
-            zip(list, list_range(len(list))) :
-            enumerate(list,idx=idx)
+        aug = is_undef(idx) && (len(size) == 1 || (len(size) == 2 && size[1]<=4))
+              ?  zip(list, list_range(len(list)))
+              :  enumerate(list,idx=idx)
     )
     is_undef(idx) && len(size) == 1? subindex(_sort_vectors1(aug),1) :
-    is_undef(idx) && len(size) == 2 && size[1] <=4? (
-        size[1]==0? list_range(len(arr)) :
-        size[1]==1? subindex(_sort_vectors1(aug),1) :
-        size[1]==2? subindex(_sort_vectors2(aug),2) :
-        size[1]==3? subindex(_sort_vectors3(aug),3) :
-        /*size[1]==4*/ subindex(_sort_vectors4(aug),4)
-    ) :
-    // general case
-    subindex(_sort_general(aug, idx=list_range(s=1,n=len(aug)-1)), 0);
+    is_undef(idx) && len(size) == 2 && size[1] <=4
+		? (
+        size[1]==0 ? list_range(len(arr)) :
+        size[1]==1 ? subindex(_sort_vectors1(aug),1) :
+        size[1]==2 ? subindex(_sort_vectors2(aug),2) :
+        size[1]==3 ? subindex(_sort_vectors3(aug),3)
+    /*size[1]==4*/ : subindex(_sort_vectors4(aug),4)
+      ) 
+		:   // general case
+        subindex(_sort_general(aug, idx=list_range(s=1,n=len(aug)-1)), 0);
+
+//***
+// Formating and input check
 
 
 // Function: unique()
@@ -817,15 +909,16 @@ function sortidx(list, idx=undef) =
 // Arguments:
 //   arr = The list to uniquify.
 function unique(arr) =
-    assert(is_list(arr)||is_string(arr))
-    len(arr)<=1? arr : let(
-        sorted = sort(arr)
-    ) [
-        for (i=[0:1:len(sorted)-1])
+    assert(is_list(arr)||is_string(arr), "Invalid input." )
+    len(arr)<=1? arr : 
+		let( sorted = sort(arr))
+		[   for (i=[0:1:len(sorted)-1])
             if (i==0 || (sorted[i] != sorted[i-1]))
                 sorted[i]
     ];
 
+//***
+// Formating and input check
 
 // Function: unique_count()
 // Usage:
@@ -836,12 +929,15 @@ function unique(arr) =
 // Arguments:
 //   arr = The list to analyze. 
 function unique_count(arr) =
-      assert(is_list(arr) || is_string(arr))
+      assert(is_list(arr) || is_string(arr), "Invalid input." )
       arr == [] ? [[],[]] : 
       let( arr=sort(arr) )
-      let(ind = [0,for(i=[1:1:len(arr)-1]) if (arr[i]!=arr[i-1]) i])
-      [select(arr,ind),
-       deltas(concat(ind,[len(arr)]))];
+      let( ind = [0, for(i=[1:1:len(arr)-1]) if (arr[i]!=arr[i-1]) i] )
+      [ select(arr,ind), deltas( concat(ind,[len(arr)]) ) ];
+
+//***
+// Formating and input check
+
 
 // Section: List Iteration Helpers
 
@@ -860,7 +956,7 @@ function unique_count(arr) =
 //   colors = ["red", "green", "blue"];
 //   for (i=idx(colors)) right(20*i) color(colors[i]) circle(d=10);
 function idx(list, step=1, end=-1,start=0) =
-    assert(is_list(list)||is_string(list))
+    assert(is_list(list)||is_string(list), "Invalid input." )
     [start : step : len(list)+end];
 
 
@@ -879,10 +975,11 @@ function idx(list, step=1, end=-1,start=0) =
 //   colors = ["red", "green", "blue"];
 //   for (p=enumerate(colors)) right(20*p[0]) color(p[1]) circle(d=10);
 function enumerate(l,idx=undef) =
-    assert(is_list(l)||is_string(list))
-    (idx==undef)?
-        [for (i=[0:1:len(l)-1]) [i,l[i]]] :
-        [for (i=[0:1:len(l)-1]) concat([i], [for (j=idx) l[i][j]])];
+    assert(is_list(l)||is_string(list), "Invalid input." )
+    assert(is_undef(idx)||is_finite(idx)||is_vector(idx) ||is_range(idx), "Invalid index/indices." )
+    (idx==undef)
+    ?   [for (i=[0:1:len(l)-1]) [i,l[i]]]
+    :   [for (i=[0:1:len(l)-1]) concat([i], [for (j=idx) l[i][j]])];
 
 
 // Function: force_list()
@@ -905,8 +1002,7 @@ function enumerate(l,idx=undef) =
 //   w = force_list(4, n=3, fill=1);  // Returns: [4,1,1]
 function force_list(value, n=1, fill) =
     is_list(value) ? value :
-    is_undef(fill)? [for (i=[1:1:n]) value] :
-    [value, for (i=[2:1:n]) fill];
+    is_undef(fill)? [for (i=[1:1:n]) value] : [value, for (i=[2:1:n]) fill];
 
 
 // Function: pair()
@@ -923,7 +1019,7 @@ function force_list(value, n=1, fill) =
 //   l = ["A","B","C","D"];
 //   echo([for (p=pair(l)) str(p.y,p.x)]);  // Outputs: ["BA", "CB", "DC"]
 function pair(v) =
-    assert(is_list(v)||is_string(v))
+    assert(is_list(v)||is_string(v), "Invalid input." )
     [for (i=[0:1:len(v)-2]) [v[i],v[i+1]]];
 
 
@@ -931,8 +1027,8 @@ function pair(v) =
 // Usage:
 //   pair_wrap(v)
 // Description:
-//   Takes a list, and returns a list of adjacent pairss from it, wrapping around from the end to the start of the list.
-// Example(2D): Note that the last point and first point DO get paired together.
+//   Takes a list, and returns a list of adjacent pairs from it, wrapping around from the end to the start of the list.
+// Example(2D): 
 //   for (p = pair_wrap(circle(d=20, $fn=12)))
 //       move(p[0])
 //           rot(from=BACK, to=p[1]-p[0])
@@ -941,7 +1037,7 @@ function pair(v) =
 //   l = ["A","B","C","D"];
 //   echo([for (p=pair_wrap(l)) str(p.y,p.x)]);  // Outputs: ["BA", "CB", "DC", "AD"]
 function pair_wrap(v) =
-    assert(is_list(v)||is_string(v))
+    assert(is_list(v)||is_string(v), "Invalid input." )
     [for (i=[0:1:len(v)-1]) [v[i],v[(i+1)%len(v)]]];
 
 
@@ -954,7 +1050,7 @@ function pair_wrap(v) =
 //   l = ["A","B","C","D","E"];
 //   echo([for (p=triplet(l)) str(p.z,p.y,p.x)]);  // Outputs: ["CBA", "DCB", "EDC"]
 function triplet(v) =
-    assert(is_list(v)||is_string(v))
+    assert(is_list(v)||is_string(v), "Invalid input." )
     [for (i=[0:1:len(v)-3]) [v[i],v[i+1],v[i+2]]];
 
 
@@ -967,7 +1063,7 @@ function triplet(v) =
 //   l = ["A","B","C","D"];
 //   echo([for (p=triplet_wrap(l)) str(p.z,p.y,p.x)]);  // Outputs: ["CBA", "DCB", "ADC", "BAD"]
 function triplet_wrap(v) =
-    assert(is_list(v)||is_string(v))
+    assert(is_list(v)||is_string(v), "Invalid input." )
     [for (i=[0:1:len(v)-1]) [v[i],v[(i+1)%len(v)],v[(i+2)%len(v)]]];
 
 
@@ -987,10 +1083,11 @@ function triplet_wrap(v) =
 // Example(2D):
 //   for (p=permute(regular_ngon(n=7,d=100))) stroke(p);
 function permute(l,n=2,_s=0) =
-    assert(is_list(l))
-    assert(len(l)-_s >= n)
-    n==1? [for (i=[_s:1:len(l)-1]) [l[i]]] :
-    [for (i=[_s:1:len(l)-n], p=permute(l,n=n-1,_s=i+1)) concat([l[i]], p)];
+    assert(is_list(l), "Invalid list." )
+		assert( is_finite(n) && n>=1 && n<=len(l), "Invalid number `n`." )
+    n==1
+		?   [for (i=[_s:1:len(l)-1]) [l[i]]] 
+    :   [for (i=[_s:1:len(l)-n], p=permute(l,n=n-1,_s=i+1)) concat([l[i]], p)];
 
 
 
@@ -1016,26 +1113,28 @@ function permute(l,n=2,_s=0) =
 //   set_v = set_union(set_a, set_b, get_indices=true);
 //   // set_v now equals [[5,0,1,2,6], [2,3,5,7,11,1,8]]
 function set_union(a, b, get_indices=false) =
+		assert( is_list(a) && is_list(b), "Invalid sets." )
     let(
         found1 = search(b, a),
         found2 = search(b, b),
-        c = [
-            for (i=idx(b))
-            if (found1[i] == [] && found2[i] == i)
-            b[i]
-        ],
+        c = [ for (i=idx(b))
+								if (found1[i] == [] && found2[i] == i)
+										b[i] 
+						],
         nset = concat(a, c)
-    ) !get_indices? nset :
+    ) 
+		! get_indices ? nset :
     let(
         la = len(a),
         found3 = search(b, c),
-        idxs = [
-            for (i=idx(b))
-            (found1[i] != [])? found1[i] :
-            la + found3[i]
-        ]
+        idxs =  [ for (i=idx(b))
+										(found1[i] != [])? found1[i] : la + found3[i]
+								]
     ) [idxs, nset];
 
+//***
+// Formating and input check
+
 
 // Function: set_difference()
 // Usage:
@@ -1051,10 +1150,12 @@ function set_union(a, b, get_indices=false) =
 //   set_d = set_difference(set_a, set_b);
 //   // set_d now equals [7,11]
 function set_difference(a, b) =
-    let(
-        found = search(a, b, num_returns_per_match=1)
-    ) [ for (i=idx(a)) if(found[i]==[]) a[i] ];
+		assert( is_list(a) && is_list(b), "Invalid sets." )
+    let( found = search(a, b, num_returns_per_match=1) )
+    [ for (i=idx(a)) if(found[i]==[]) a[i] ];
 
+//***
+// Formating and input check
 
 // Function: set_intersection()
 // Usage:
@@ -1070,14 +1171,34 @@ function set_difference(a, b) =
 //   set_i = set_intersection(set_a, set_b);
 //   // set_i now equals [2,3,5]
 function set_intersection(a, b) =
-    let(
-        found = search(a, b, num_returns_per_match=1)
-    ) [ for (i=idx(a)) if(found[i]!=[]) a[i] ];
+		assert( is_list(a) && is_list(b), "Invalid sets." )
+    let( found = search(a, b, num_returns_per_match=1) )
+    [ for (i=idx(a)) if(found[i]!=[]) a[i] ];
 
+//***
+// Formating and input check
 
 
 // Section: Array Manipulation
 
+// Function: add_scalar()
+// Usage:  
+//   add_scalar(v,s);
+// Description:
+//   Given an array and a scalar, returns the array with the scalar added to each item in it.
+//   If given a list of arrays, recursively adds the scalar to the each array.
+// Arguments:
+//   v = The initial array.
+//   s = A scalar value to add to every item in the array.
+// Example:
+//   add_scalar([1,2,3],3);            // Returns: [4,5,6]
+//   add_scalar([[1,2,3],[3,4,5]],3);  // Returns: [[4,5,6],[6,7,8]]
+function add_scalar(v,s) = 
+    is_finite(s) ? [for (x=v) is_list(x)? add_scalar(x,s) : is_finite(x) ? x+s: x] : v;
+
+//***
+// for sake of consistence, move it to here from vectors.scad
+
 // Function: subindex()
 // Description:
 //   For each array item, return the indexed subitem.
@@ -1092,10 +1213,11 @@ function set_intersection(a, b) =
 //   subindex(v,2);      // Returns [3, 7, 11, 15]
 //   subindex(v,[2,1]);  // Returns [[3, 2], [7, 6], [11, 10], [15, 14]]
 //   subindex(v,[1:3]);  // Returns [[2, 3, 4], [6, 7, 8], [10, 11, 12], [14, 15, 16]]
-function subindex(v, idx) = [
-    for(val=v) let(value=[for(i=idx) val[i]])
+function subindex(v, idx) = 
+		[ for(val=v) 
+		    let( value=[for(i=idx) val[i]] )
         len(value)==1 ? value[0] : value
-];
+    ];
 
 
 // Function: zip()
@@ -1127,15 +1249,16 @@ function subindex(v, idx) = [
 function zip(vecs, v2, v3, fit=false, fill=undef) =
     (v3!=undef)? zip([vecs,v2,v3], fit=fit, fill=fill) :
     (v2!=undef)? zip([vecs,v2], fit=fit, fill=fill) :
-    assert(in_list(fit, [false, "short", "long"]))
+    assert(in_list(fit, [false, "short", "long"]), "Invalid fit value." )
     assert(all([for(v=vecs) is_list(v)]), "One of the inputs to zip is not a list")
     let(
         minlen = list_shortest(vecs),
-        maxlen = list_longest(vecs),
-        dummy = (fit==false)? assert(minlen==maxlen, "Input vectors to zip must have the same length") : 0
-    ) (fit == "long")?
-        [for(i=[0:1:maxlen-1]) [for(v=vecs) for(x=(i<len(v)? v[i] : (fill==undef)? [fill] : fill)) x] ] :
-        [for(i=[0:1:minlen-1]) [for(v=vecs) for(x=v[i]) x] ];
+        maxlen = list_longest(vecs)
+		)
+    assert(fit!=false || minlen==maxlen, "Input vectors to zip must have the same length")
+    (fit == "long")
+    ?   [for(i=[0:1:maxlen-1]) [for(v=vecs) for(x=(i<len(v)? v[i] : (fill==undef)? [fill] : fill)) x] ] 
+    :   [for(i=[0:1:minlen-1]) [for(v=vecs) for(x=v[i]) x] ];
 
 
 // Function: array_group()
@@ -1165,15 +1288,16 @@ function flatten(l) = [for (a = l) each a];
 
 // Internal.  Not exposed.
 function _array_dim_recurse(v) =
-    !is_list(v[0])?  (
-        sum( [for(entry=v) is_list(entry) ? 1 : 0]) == 0 ? [] : [undef]
-    ) : let(
-        firstlen = len(v[0]),
-        first = sum( [for(entry = v) len(entry) == firstlen  ? 0 : 1]   ) == 0 ? firstlen : undef,
-        leveldown = flatten(v)
-    ) is_list(leveldown[0])? (
-        concat([first],_array_dim_recurse(leveldown))
-    ) : [first];
+    !is_list(v[0])
+    ?   sum( [for(entry=v) is_list(entry) ? 1 : 0] ) == 0 ? [] : [undef]
+    :   let(
+					firstlen = len(v[0]),
+					first = sum( [for(entry = v) len(entry) == firstlen  ? 0 : 1]   ) == 0 ? firstlen : undef,
+					leveldown = flatten(v)
+				) 
+				is_list(leveldown[0])
+        ?  concat([first],_array_dim_recurse(leveldown))
+        : [first];
 
 
 // Function: array_dim()
@@ -1197,15 +1321,18 @@ function _array_dim_recurse(v) =
 //   array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 2);  // Returns 3
 //   array_dim([[[1,2,3],[4,5,6]],[[7,8,9]]]);                // Returns [2,undef,3]
 function array_dim(v, depth=undef) =
-    (depth == undef)? (
-        concat([len(v)], _array_dim_recurse(v))
-    ) : (depth == 0)? (
-        len(v)
-    ) : (
-        let(dimlist = _array_dim_recurse(v))
-        (depth > len(dimlist))? 0 : dimlist[depth-1]
-    );
+    assert( is_undef(depth) || ( is_finite(depth) && depth>=0 ), "Invalid depth.")
+    ! is_list(v) ? 0 :
+    (depth == undef)
+    ?   concat([len(v)], _array_dim_recurse(v))
+    :   (depth == 0)
+		    ?  len(v)
+        :  let( dimlist = _array_dim_recurse(v))
+           (depth > len(dimlist))? 0 : dimlist[depth-1] ;
 
+//***
+// Formating
+// This function may return undef!
 
 
 // Function: transpose()
@@ -1238,9 +1365,15 @@ function array_dim(v, depth=undef) =
 // Example:
 //   transpose([3,4,5]);  // Returns: [3,4,5]
 function transpose(arr) =
-    is_list(arr[0])? [for (i=[0:1:len(arr[0])-1]) [for (j=[0:1:len(arr)-1]) arr[j][i]]] : arr;
-
+    let( a0 = arr[0] )
+    is_list(a0)
+		?   assert([for(a=arr) if(len(a)!=len(a0)) 1]==[], "The array is not a matrix." )
+		    [for (i=[0:1:len(a0)-1]) 
+		      [ for (j=[0:1:len(arr)-1]) arr[j][i] ] ] 
+		:  arr;
 
+//***
+// Input data check and formating
 
 
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/tests/test_arrays.scad b/tests/test_arrays.scad
index 0ed2bd6..d048afa 100644
--- a/tests/test_arrays.scad
+++ b/tests/test_arrays.scad
@@ -1,34 +1,7 @@
-include <BOSL2/std.scad>
+include <../std.scad>
 
 
-// List/Array Ops
-
-module test_repeat() {
-    assert(repeat(1, 4) == [1,1,1,1]);
-    assert(repeat(8, [2,3]) == [[8,8,8], [8,8,8]]);
-    assert(repeat(0, [2,2,3]) == [[[0,0,0],[0,0,0]], [[0,0,0],[0,0,0]]]);
-    assert(repeat([1,2,3],3) == [[1,2,3], [1,2,3], [1,2,3]]);
-}
-test_repeat();
-
-
-module test_in_list() {
-    assert(in_list("bar", ["foo", "bar", "baz"]));
-    assert(!in_list("bee", ["foo", "bar", "baz"]));
-    assert(in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1));
-}
-test_in_list();
-
-
-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]);
-}
-test_slice();
-
+// Section: List Query Operations
 
 module test_select() {
     l = [3,4,5,6,7,8,9];
@@ -45,6 +18,71 @@ module test_select() {
 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) == []);
+}
+test_slice();
+
+
+module test_in_list() {
+    assert(in_list("bar", ["foo", "bar", "baz"]));
+    assert(!in_list("bee", ["foo", "bar", "baz"]));
+    assert(in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1));
+		
+    assert(!in_list("bee", ["foo", "bar", ["bee"]]));
+    assert(in_list(NAN, [NAN])==false);
+}
+test_in_list();
+
+
+module test_min_index() {
+    assert(min_index([5,3,9,6,2,7,8,2,1])==8);
+    assert(min_index([5,3,9,6,2,7,8,2,7],all=true)==[4,7]);
+//    assert(min_index([],all=true)==[]);
+}
+test_min_index();
+
+
+module test_max_index() {
+    assert(max_index([5,3,9,6,2,7,8,9,1])==2);
+    assert(max_index([5,3,9,6,2,7,8,9,7],all=true)==[2,7]);
+//    assert(max_index([],all=true)==[]);
+}
+test_max_index();
+
+
+module test_list_increasing() {
+    assert(list_increasing([1,2,3,4]) == true);
+    assert(list_increasing([1,3,2,4]) == false);
+    assert(list_increasing([4,3,2,1]) == false);
+}
+test_list_increasing();
+
+
+module test_list_decreasing() {
+    assert(list_decreasing([1,2,3,4]) == false);
+    assert(list_decreasing([4,2,3,1]) == false);
+    assert(list_decreasing([4,3,2,1]) == true);
+}
+test_list_decreasing();
+
+// Section: Basic List Generation
+
+module test_repeat() {
+    assert(repeat(1, 4) == [1,1,1,1]);
+    assert(repeat(8, [2,3]) == [[8,8,8], [8,8,8]]);
+    assert(repeat(0, [2,2,3]) == [[[0,0,0],[0,0,0]], [[0,0,0],[0,0,0]]]);
+    assert(repeat([1,2,3],3) == [[1,2,3], [1,2,3], [1,2,3]]);
+    assert(repeat(4, [2,-1]) == [[], []]);
+}
+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]);
@@ -62,6 +100,8 @@ test_list_range();
 
 module test_reverse() {
     assert(reverse([3,4,5,6]) == [6,5,4,3]);
+    assert(reverse("abcd") == ["d","c","b","a"]);
+    assert(reverse([]) == []);
 }
 test_reverse();
 
@@ -86,6 +126,8 @@ module test_deduplicate() {
     assert(deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]) == [8,3,4,8,2,3]);
     assert(deduplicate("Hello") == ["H","e","l","o"]);
     assert(deduplicate([[3,4],[7,1.99],[7,2],[1,4]],eps=0.1) == [[3,4],[7,2],[1,4]]);
+    assert(deduplicate([], closed=true) == []);
+    assert(deduplicate([[1,[1,[undef]]],[1,[1,[undef]]],[1,[2]],[1,[2,[0]]]])==[[1, [1,[undef]]],[1,[2]],[1,[2,[0]]]]);
 }
 test_deduplicate();
 
@@ -144,22 +186,6 @@ module test_list_bset() {
 test_list_bset();
 
 
-module test_list_increasing() {
-    assert(list_increasing([1,2,3,4]) == true);
-    assert(list_increasing([1,3,2,4]) == false);
-    assert(list_increasing([4,3,2,1]) == false);
-}
-test_list_increasing();
-
-
-module test_list_decreasing() {
-    assert(list_decreasing([1,2,3,4]) == false);
-    assert(list_decreasing([4,2,3,1]) == false);
-    assert(list_decreasing([4,3,2,1]) == true);
-}
-test_list_decreasing();
-
-
 module test_list_shortest() {
     assert(list_shortest(["foobar", "bazquxx", "abcd"]) == 4);
 }
@@ -311,6 +337,13 @@ test_set_intersection();
 // Arrays
 
 
+module test_add_scalar() {
+    assert(add_scalar([1,2,3],3) == [4,5,6]);
+    assert(add_scalar([[1,2,3],[3,4,5]],3) == [[4,5,6],[6,7,8]]);
+}
+test_add_scalar();
+
+
 module test_subindex() {
     v = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
     assert(subindex(v,2) == [3, 7, 11, 15]);

From f61e30add2f458fb0f2e9bdc2912dea333dcb2c6 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Fri, 24 Jul 2020 00:23:07 +0100
Subject: [PATCH 05/24] Update

---
 arrays.scad | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index ac18535..a05d330 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -795,11 +795,6 @@ function _sort_vectors4(arr) =
 		concat( _sort_vectors4(lesser), equal, _sort_vectors4(greater) );
 		
 
-function _sort_eval(array, eval) =
-    let( ival  = [for(i=[0:len(array)-1]) [eval[i], i] ],
-         sival = _sort_vectors1(arr) )
-		[for(yi=sival) array[yi[1]] ];
-
 function _sort_general(arr, idx=undef) =
     (len(arr)<=1) ? arr :
     let(

From 464c65ce8ccf9377663844362ac82199145c75a5 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Fri, 24 Jul 2020 13:30:19 +0100
Subject: [PATCH 06/24] Eliminating tabs

---
 arrays.scad            | 174 ++++++++++++++++++++---------------------
 common.scad            |   2 +-
 tests/test_arrays.scad |   2 +-
 vectors.scad           |   4 +-
 4 files changed, 91 insertions(+), 91 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index a05d330..e29edef 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -396,16 +396,16 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
 function repeat_entries(list, N, exact = true) =
     assert(is_list(list) && len(list)>0, "The list cannot be void.")
     assert((is_finite(N) && N>0) || is_vector(N,len(list)),
-		        "Parameter N must be a number greater than zero or vector with the same length of `list`")
+            "Parameter N must be a number greater than zero or vector with the same length of `list`")
     let(
         length = len(list),
         reps_guess = is_list(N)? N : repeat(N/length,length),
         reps = exact ?
-				         _sum_preserving_round(reps_guess) 
+                 _sum_preserving_round(reps_guess) 
                : [for (val=reps_guess) round(val)]
     )
     [for(i=[0:length-1]) each repeat(list[i],reps[i])];
-		
+    
 //***
 // 1. Complete asserts
 // 2. Format
@@ -434,21 +434,21 @@ function list_set(list=[],indices,values,dflt=0,minlen=0) =
     assert(is_list(list)||is_string(list))
     !is_list(indices)? (
         (is_finite(indices) && indices<len(list))? 
-				   [for (i=idx(list)) i==indices? values : list[i]]
+           [for (i=idx(list)) i==indices? values : list[i]]
         :  list_set(list,[indices],[values],dflt) )
     :   assert(is_vector(indices) && is_list(values) && len(values)==len(indices) ,
-		           "Index list and value list must have the same length")
-				let( midx = max(len(list)-1, max(indices)) )
-				[ for(i=[0:midx] ) 
-						let( j = search(i,indices,0),
-							   k = j[0] )
-						assert( len(j)<2, "Repeated indices are not acceptable." )
-						k!=undef ? values[k] :
-						i<len(list) ? list[i]:
-						    dflt ,
-					each repeat(dflt, minlen-max(indices))
-				];
-			
+               "Index list and value list must have the same length")
+        let( midx = max(len(list)-1, max(indices)) )
+        [ for(i=[0:midx] ) 
+            let( j = search(i,indices,0),
+                 k = j[0] )
+            assert( len(j)<2, "Repeated indices are not acceptable." )
+            k!=undef ? values[k] :
+            i<len(list) ? list[i]:
+                dflt ,
+          each repeat(dflt, minlen-max(indices))
+        ];
+      
 //***
 // a full refactoring without sorting; its is quite faster than the original
 
@@ -464,24 +464,24 @@ function list_set(list=[],indices,values,dflt=0,minlen=0) =
 function list_insert(list, indices, values, _i=0) = 
     assert(is_list(list)||is_string(list))
     ! is_list(indices)? 
-		    assert( is_finite(indices) && is_finite(values), "Invalid indices/values." ) 
-				assert( indices<=len(list), "Indices must be <= len(list) ." )
+        assert( is_finite(indices) && is_finite(values), "Invalid indices/values." ) 
+        assert( indices<=len(list), "Indices must be <= len(list) ." )
         [for (i=idx(list)) each ( i==indices?  [ values, list[i] ] : [ list[i] ] ) ]
     :   assert( is_vector(indices) && is_list(values) && len(values)==len(indices) ,
-		           "Index list and value list must have the same length")
-			  assert( max(indices)<=len(list), "Indices must be <= len(list) ." )
-				let( maxidx = max(indices),
-             minidx	= min(indices) )
-				[ for(i=[0:1:minidx-1] ) list[i],
+               "Index list and value list must have the same length")
+        assert( max(indices)<=len(list), "Indices must be <= len(list) ." )
+        let( maxidx = max(indices),
+             minidx  = min(indices) )
+        [ for(i=[0:1:minidx-1] ) list[i],
           for(i=[minidx: min(maxidx, len(list)-1)] ) 
-						let( j = search(i,indices,0),
-							   k = j[0],
+            let( j = search(i,indices,0),
+                 k = j[0],
                  x = assert( len(j)<2, "Repeated indices are not acceptable." )
-							)
-						each ( k != undef  ? [ values[k], list[i] ] : [ list[i] ] ),
-					for(i=[min(maxidx, len(list)-1)+1:1:len(list)-1] ) list[i],
-					if(maxidx==len(list)) values[max_index(indices)]
-				];
+              )
+            each ( k != undef  ? [ values[k], list[i] ] : [ list[i] ] ),
+          for(i=[min(maxidx, len(list)-1)+1:1:len(list)-1] ) list[i],
+          if(maxidx==len(list)) values[max_index(indices)]
+        ];
 
 
 //***
@@ -503,12 +503,12 @@ function list_insert(list, indices, values, _i=0) =
 function list_remove(list, indices) =
     assert(is_list(list)||is_string(list), "Invalid list/string." )
     is_finite(indices) 
-		?   [  for(i=[0:1:min(indices, len(list)-1)-1]) list[i],
-           for(i=[min(indices, len(list)-1)+1:1:len(list)-1]) list[i]	]
-		:   assert( is_vector(indices), "Invalid list `indices`." )
-				len(indices)==0 ? list :
-		    [ for(i=[0:len(list)-1])
-						if ( []==search(i,indices,1) ) list[i] ]; 
+    ?   [  for(i=[0:1:min(indices, len(list)-1)-1]) list[i],
+           for(i=[min(indices, len(list)-1)+1:1:len(list)-1]) list[i]  ]
+    :   assert( is_vector(indices), "Invalid list `indices`." )
+        len(indices)==0 ? list :
+        [ for(i=[0:len(list)-1])
+            if ( []==search(i,indices,1) ) list[i] ]; 
 
 //***
 // Refactoring without sort
@@ -645,9 +645,9 @@ function list_trim(array, maxlen) =
 function list_fit(array, length, fill) =
     assert(is_list(array)||is_string(list), "Invalid input." )
     let(l=len(array)) 
-		l==length ? array : 
-		l> length ? list_trim(array,length) 
-		          : list_pad(array,length,fill);
+    l==length ? array : 
+    l> length ? list_trim(array,length) 
+              : list_pad(array,length,fill);
 
 //***
 // formating
@@ -665,32 +665,32 @@ function shuffle(list) =
         left  = [for (i=[0:len(list)-1]) if (rval[i]< 0.5) list[i]],
         right = [for (i=[0:len(list)-1]) if (rval[i]>=0.5) list[i]]
     ) 
-		concat(shuffle(left), shuffle(right));
+    concat(shuffle(left), shuffle(right));
 
 
 // Sort a vector of scalar values
 function _sort_scalars(arr) =
     len(arr)<=1 ? arr : 
-		let(
+    let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [ for (y = arr) if (y  < pivot) y ],
         equal   = [ for (y = arr) if (y == pivot) y ],
         greater = [ for (y = arr) if (y  > pivot) y ]
     ) 
-		concat( _sort_scalars(lesser), equal, _sort_scalars(greater) );
+    concat( _sort_scalars(lesser), equal, _sort_scalars(greater) );
 
 
 // Sort a vector of vectors based on the first entry only of each vector
 function _sort_vectors1(arr) =
     len(arr)<=1 ? arr :
     !(len(arr)>0) ? [] : 
-		let(
+    let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [ for (y = arr) if (y[0]  < pivot[0]) y ],
         equal   = [ for (y = arr) if (y[0] == pivot[0]) y ],
         greater = [ for (y = arr) if (y[0]  > pivot[0]) y ]
     ) 
-		concat( _sort_vectors1(lesser), equal, _sort_vectors1(greater) );
+    concat( _sort_vectors1(lesser), equal, _sort_vectors1(greater) );
 
 
 // Sort a vector of vectors based on the first two entries of each vector
@@ -698,19 +698,19 @@ function _sort_vectors1(arr) =
 function _sort_vectors2(arr) =
     len(arr)<=1 ? arr :
     !(len(arr)>0) ? [] : 
-		let(
+    let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [ for (y = arr) if (y[0] < pivot[0] || (y[0]==pivot[0] && y[1]<pivot[1])) y ],
         equal   = [ for (y = arr) if (y[0] == pivot[0] && y[1]==pivot[1]) y ],
         greater = [ for (y = arr) if (y[0] > pivot[0] || (y[0]==pivot[0] && y[1]>pivot[1])) y ]
     ) 
-		concat( _sort_vectors2(lesser), equal, _sort_vectors2(greater) );
+    concat( _sort_vectors2(lesser), equal, _sort_vectors2(greater) );
 
 // Sort a vector of vectors based on the first three entries of each vector
 // Lexicographic order, remaining entries of vector ignored
 function _sort_vectors3(arr) =
     len(arr)<=1 ? arr : 
-		let(
+    let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [
             for (y = arr) if (
@@ -742,14 +742,14 @@ function _sort_vectors3(arr) =
             ) y
         ]
     ) 
-		concat( _sort_vectors3(lesser), equal, _sort_vectors3(greater) );
+    concat( _sort_vectors3(lesser), equal, _sort_vectors3(greater) );
 
 
 // Sort a vector of vectors based on the first four entries of each vector
 // Lexicographic order, remaining entries of vector ignored
 function _sort_vectors4(arr) =
     len(arr)<=1 ? arr : 
-		let(
+    let(
         pivot = arr[floor(len(arr)/2)],
         lesser = [
             for (y = arr) if (
@@ -792,18 +792,18 @@ function _sort_vectors4(arr) =
             ) y
         ]
     ) 
-		concat( _sort_vectors4(lesser), equal, _sort_vectors4(greater) );
-		
+    concat( _sort_vectors4(lesser), equal, _sort_vectors4(greater) );
+    
 
 function _sort_general(arr, idx=undef) =
     (len(arr)<=1) ? arr :
     let(
         pivot = arr[floor(len(arr)/2)],
         pivotval = idx==undef? pivot : [for (i=idx) pivot[i]],
-				compare = 
-				    is_undef(idx) ? [for(entry=arr) compare_vals(entry, pivotval) ] :
+        compare = 
+            is_undef(idx) ? [for(entry=arr) compare_vals(entry, pivotval) ] :
             [ for (entry = arr) 
-					      let( val = [for (i=idx) entry[i] ] )
+                let( val = [for (i=idx) entry[i] ] )
                 compare_vals(val, pivotval) ] ,
         lesser  = [ for (i = [0:1:len(arr)-1]) if (compare[i] < 0) arr[i] ],
         equal   = [ for (i = [0:1:len(arr)-1]) if (compare[i] ==0) arr[i] ],
@@ -832,19 +832,19 @@ function _sort_general(arr, idx=undef) =
 //   sorted = sort(l);  // Returns [2,3,8,9,12,16,23,34,37,45,89]
 function sort(list, idx=undef) =
     !is_list(list) || len(list)<=1 ? list :
-		assert( is_undef(idx) || is_finite(idx) || is_vector(idx), "Invalid indices.")
+    assert( is_undef(idx) || is_finite(idx) || is_vector(idx), "Invalid indices.")
     is_def(idx) ? _sort_general(list,idx) :
     let(size = array_dim(list))
     len(size)==1 ? _sort_scalars(list) :
     len(size)==2 && size[1] <=4 
-		? (
+    ? (
         size[1]==0 ? list :
         size[1]==1 ? _sort_vectors1(list) :
         size[1]==2 ? _sort_vectors2(list) :
         size[1]==3 ? _sort_vectors3(list)
    /*size[1]==4*/  : _sort_vectors4(list)
       ) 
-		: _sort_general(list);
+    : _sort_general(list);
 
 //***
 // Formating and input check
@@ -872,9 +872,9 @@ function sort(list, idx=undef) =
 //   idxs3 = sortidx(lst, idx=[1,3]); // Returns: [3,0,2,1]
 function sortidx(list, idx=undef) =
     assert( is_list(list) || is_string(list) , "Invalid input." )
-		assert( is_undef(idx) || is_finite(idx) || is_vector(idx), "Invalid indices.")
+    assert( is_undef(idx) || is_finite(idx) || is_vector(idx), "Invalid indices.")
     list==[] ? [] : 
-		let(
+    let(
         size = array_dim(list),
         aug = is_undef(idx) && (len(size) == 1 || (len(size) == 2 && size[1]<=4))
               ?  zip(list, list_range(len(list)))
@@ -882,14 +882,14 @@ function sortidx(list, idx=undef) =
     )
     is_undef(idx) && len(size) == 1? subindex(_sort_vectors1(aug),1) :
     is_undef(idx) && len(size) == 2 && size[1] <=4
-		? (
+    ? (
         size[1]==0 ? list_range(len(arr)) :
         size[1]==1 ? subindex(_sort_vectors1(aug),1) :
         size[1]==2 ? subindex(_sort_vectors2(aug),2) :
         size[1]==3 ? subindex(_sort_vectors3(aug),3)
     /*size[1]==4*/ : subindex(_sort_vectors4(aug),4)
       ) 
-		:   // general case
+    :   // general case
         subindex(_sort_general(aug, idx=list_range(s=1,n=len(aug)-1)), 0);
 
 //***
@@ -906,8 +906,8 @@ function sortidx(list, idx=undef) =
 function unique(arr) =
     assert(is_list(arr)||is_string(arr), "Invalid input." )
     len(arr)<=1? arr : 
-		let( sorted = sort(arr))
-		[   for (i=[0:1:len(sorted)-1])
+    let( sorted = sort(arr))
+    [   for (i=[0:1:len(sorted)-1])
             if (i==0 || (sorted[i] != sorted[i-1]))
                 sorted[i]
     ];
@@ -1079,9 +1079,9 @@ function triplet_wrap(v) =
 //   for (p=permute(regular_ngon(n=7,d=100))) stroke(p);
 function permute(l,n=2,_s=0) =
     assert(is_list(l), "Invalid list." )
-		assert( is_finite(n) && n>=1 && n<=len(l), "Invalid number `n`." )
+    assert( is_finite(n) && n>=1 && n<=len(l), "Invalid number `n`." )
     n==1
-		?   [for (i=[_s:1:len(l)-1]) [l[i]]] 
+    ?   [for (i=[_s:1:len(l)-1]) [l[i]]] 
     :   [for (i=[_s:1:len(l)-n], p=permute(l,n=n-1,_s=i+1)) concat([l[i]], p)];
 
 
@@ -1108,23 +1108,23 @@ function permute(l,n=2,_s=0) =
 //   set_v = set_union(set_a, set_b, get_indices=true);
 //   // set_v now equals [[5,0,1,2,6], [2,3,5,7,11,1,8]]
 function set_union(a, b, get_indices=false) =
-		assert( is_list(a) && is_list(b), "Invalid sets." )
+    assert( is_list(a) && is_list(b), "Invalid sets." )
     let(
         found1 = search(b, a),
         found2 = search(b, b),
         c = [ for (i=idx(b))
-								if (found1[i] == [] && found2[i] == i)
-										b[i] 
-						],
+                if (found1[i] == [] && found2[i] == i)
+                    b[i] 
+            ],
         nset = concat(a, c)
     ) 
-		! get_indices ? nset :
+    ! get_indices ? nset :
     let(
         la = len(a),
         found3 = search(b, c),
         idxs =  [ for (i=idx(b))
-										(found1[i] != [])? found1[i] : la + found3[i]
-								]
+                    (found1[i] != [])? found1[i] : la + found3[i]
+                ]
     ) [idxs, nset];
 
 //***
@@ -1145,7 +1145,7 @@ function set_union(a, b, get_indices=false) =
 //   set_d = set_difference(set_a, set_b);
 //   // set_d now equals [7,11]
 function set_difference(a, b) =
-		assert( is_list(a) && is_list(b), "Invalid sets." )
+    assert( is_list(a) && is_list(b), "Invalid sets." )
     let( found = search(a, b, num_returns_per_match=1) )
     [ for (i=idx(a)) if(found[i]==[]) a[i] ];
 
@@ -1166,7 +1166,7 @@ function set_difference(a, b) =
 //   set_i = set_intersection(set_a, set_b);
 //   // set_i now equals [2,3,5]
 function set_intersection(a, b) =
-		assert( is_list(a) && is_list(b), "Invalid sets." )
+    assert( is_list(a) && is_list(b), "Invalid sets." )
     let( found = search(a, b, num_returns_per_match=1) )
     [ for (i=idx(a)) if(found[i]!=[]) a[i] ];
 
@@ -1209,8 +1209,8 @@ function add_scalar(v,s) =
 //   subindex(v,[2,1]);  // Returns [[3, 2], [7, 6], [11, 10], [15, 14]]
 //   subindex(v,[1:3]);  // Returns [[2, 3, 4], [6, 7, 8], [10, 11, 12], [14, 15, 16]]
 function subindex(v, idx) = 
-		[ for(val=v) 
-		    let( value=[for(i=idx) val[i]] )
+    [ for(val=v) 
+        let( value=[for(i=idx) val[i]] )
         len(value)==1 ? value[0] : value
     ];
 
@@ -1249,7 +1249,7 @@ function zip(vecs, v2, v3, fit=false, fill=undef) =
     let(
         minlen = list_shortest(vecs),
         maxlen = list_longest(vecs)
-		)
+    )
     assert(fit!=false || minlen==maxlen, "Input vectors to zip must have the same length")
     (fit == "long")
     ?   [for(i=[0:1:maxlen-1]) [for(v=vecs) for(x=(i<len(v)? v[i] : (fill==undef)? [fill] : fill)) x] ] 
@@ -1286,11 +1286,11 @@ function _array_dim_recurse(v) =
     !is_list(v[0])
     ?   sum( [for(entry=v) is_list(entry) ? 1 : 0] ) == 0 ? [] : [undef]
     :   let(
-					firstlen = len(v[0]),
-					first = sum( [for(entry = v) len(entry) == firstlen  ? 0 : 1]   ) == 0 ? firstlen : undef,
-					leveldown = flatten(v)
-				) 
-				is_list(leveldown[0])
+          firstlen = len(v[0]),
+          first = sum( [for(entry = v) len(entry) == firstlen  ? 0 : 1]   ) == 0 ? firstlen : undef,
+          leveldown = flatten(v)
+        ) 
+        is_list(leveldown[0])
         ?  concat([first],_array_dim_recurse(leveldown))
         : [first];
 
@@ -1321,7 +1321,7 @@ function array_dim(v, depth=undef) =
     (depth == undef)
     ?   concat([len(v)], _array_dim_recurse(v))
     :   (depth == 0)
-		    ?  len(v)
+        ?  len(v)
         :  let( dimlist = _array_dim_recurse(v))
            (depth > len(dimlist))? 0 : dimlist[depth-1] ;
 
@@ -1362,10 +1362,10 @@ function array_dim(v, depth=undef) =
 function transpose(arr) =
     let( a0 = arr[0] )
     is_list(a0)
-		?   assert([for(a=arr) if(len(a)!=len(a0)) 1]==[], "The array is not a matrix." )
-		    [for (i=[0:1:len(a0)-1]) 
-		      [ for (j=[0:1:len(arr)-1]) arr[j][i] ] ] 
-		:  arr;
+    ?   assert([for(a=arr) if(len(a)!=len(a0)) 1]==[], "The array is not a matrix." )
+        [for (i=[0:1:len(a0)-1]) 
+          [ for (j=[0:1:len(arr)-1]) arr[j][i] ] ] 
+    :  arr;
 
 //***
 // Input data check and formating
diff --git a/common.scad b/common.scad
index 6cc910e..f3fabd5 100644
--- a/common.scad
+++ b/common.scad
@@ -320,7 +320,7 @@ function scalar_vec3(v, dflt=undef) =
 function segs(r) = 
     $fn>0? ($fn>3? $fn : 3) :
     let( r = is_finite(r)? r: 0 ) 
-		ceil(max(5, min(360/$fa, abs(r)*2*PI/$fs))) ;
+    ceil(max(5, min(360/$fa, abs(r)*2*PI/$fs))) ;
 
 //***
 // avoids undef
diff --git a/tests/test_arrays.scad b/tests/test_arrays.scad
index d048afa..479f4bb 100644
--- a/tests/test_arrays.scad
+++ b/tests/test_arrays.scad
@@ -33,7 +33,7 @@ module test_in_list() {
     assert(in_list("bar", ["foo", "bar", "baz"]));
     assert(!in_list("bee", ["foo", "bar", "baz"]));
     assert(in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1));
-		
+    
     assert(!in_list("bee", ["foo", "bar", ["bee"]]));
     assert(in_list(NAN, [NAN])==false);
 }
diff --git a/vectors.scad b/vectors.scad
index 92e4866..82535e0 100644
--- a/vectors.scad
+++ b/vectors.scad
@@ -217,8 +217,8 @@ function vector_axis(v1,v2=undef,v3=undef) =
               w1 = point3d(v1/norm(v1)),
               w2 = point3d(v2/norm(v2)),
               w3 = (norm(w1-w2) > eps && norm(w1+w2) > eps) ? w2 
-							     : (norm(vabs(w2)-UP) > eps)? UP 
-									 : RIGHT
+                   : (norm(vabs(w2)-UP) > eps)? UP 
+                   : RIGHT
             ) unit(cross(w1,w3));
 
 

From bdba4c0821c372d8b06b382bafff595c51555837 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Fri, 24 Jul 2020 22:54:34 +0100
Subject: [PATCH 07/24] A broad review of input data check and code format

Some functions were changed for sake of clarity or better performance.
---
 arrays.scad             | 688 ++++++++++++++++++++++++----------------
 common.scad             |  31 +-
 tests/polyhedra.scad    |   4 +-
 tests/test_arrays.scad  | 125 +++++---
 tests/test_common.scad  |  25 ++
 tests/test_vectors.scad |   7 -
 vectors.scad            | 137 ++++----
 7 files changed, 598 insertions(+), 419 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index ea47d48..e29edef 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -44,20 +44,24 @@
 //   select(l, [1:3]);  // Returns [4,5,6]
 //   select(l, [1,3]);  // Returns [4,6]
 function select(list, start, end=undef) =
+    assert( is_list(list) || is_string(list), "Invalid list.")
     let(l=len(list))
-    end==undef? (
-        is_num(start)?
-            let(s=(start%l+l)%l) list[s] :
-            assert(is_list(start) || is_range(start), "Invalid start parameter")
-            [for (i=start) list[(i%l+l)%l]]
-    ) : (
-        assert(is_num(start), "Invalid start parameter.")
-        assert(is_num(end), "Invalid end parameter.")
-        let(s=(start%l+l)%l, e=(end%l+l)%l)
-        (s<=e)?
-            [for (i = [s:1:e]) list[i]] :
-            concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]])
-    );
+    l==0 ? []
+    :   end==undef? 
+            is_num(start)?
+                list[ (start%l+l)%l ]
+            :   assert( is_list(start) || is_range(start), "Invalid start parameter")
+                [for (i=start) list[ (i%l+l)%l ] ]
+        :   assert(is_num(start), "Invalid start parameter.")
+            assert(is_num(end), "Invalid end parameter.")
+            let( s = (start%l+l)%l, e = (end%l+l)%l )
+            (s <= e)? [for (i = [s:1:e]) list[i]]
+            :   concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ;
+
+
+//***
+// 1. avoids undef when the list is void
+// 2. format
 
 
 // Function: slice()
@@ -65,8 +69,8 @@ function select(list, start, end=undef) =
 //   Returns a slice of a list.  The first item is index 0.
 //   Negative indexes are counted back from the end.  The last item is -1.
 // Arguments:
-//   arr = The array/list to get the slice of.
-//   st = The index of the first item to return.
+//   list = The array/list to get the slice of.
+//   start = The index of the first item to return.
 //   end = The index after the last item to return, unless negative, in which case the last item to return.
 // Example:
 //   slice([3,4,5,6,7,8,9], 3, 5);   // Returns [6,7]
@@ -74,28 +78,47 @@ function select(list, start, end=undef) =
 //   slice([3,4,5,6,7,8,9], 1, 1);   // Returns []
 //   slice([3,4,5,6,7,8,9], 6, -1);  // Returns [9]
 //   slice([3,4,5,6,7,8,9], 2, -2);  // Returns [5,6,7,8]
-function slice(arr,st,end) = let(
-        l=len(arr),
-        s=st<0?(l+st):st,
-        e=end<0?(l+end+1):end
-    ) [for (i=[s:1:e-1]) if (e>s) arr[i]];
+function slice(list,start,end) =
+    assert( is_list(list), "Invalid list" )
+    assert( is_finite(start) && is_finite(end), "Invalid number(s)" )
+    let( l = len(list) )
+    l==0 ? []
+    :   let(
+            s = start<0? (l+start): start,
+            e = end<0? (l+end+1): end
+        ) [for (i=[s:1:e-1]) if (e>s) list[i]];
 
 
+//***
+// 1. for sake of consistence, the list argument identifier was changed to list and st to start
+// 2. avoids undef when the list is void
+// 3. checks inputs of start and end
+
 // Function: in_list()
-// Description: Returns true if value `x` is in list `l`.
+// Description: Returns true if value `val` is in list `list`.
 // Arguments:
-//   x = The value to search for.
-//   l = The list to search.
-//   idx = If given, searches the given subindexes for matches for `x`.
+//   val = The simple value to search for.
+//   list = The list to search.
+//   idx = If given, searches the given subindexes for matches for `val`.
 // Example:
 //   in_list("bar", ["foo", "bar", "baz"]);  // Returns true.
 //   in_list("bee", ["foo", "bar", "baz"]);  // Returns false.
 //   in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1);  // Returns true.
+//   in_list("bee", ["foo", "bar", ["bee",0]]);  // Returns true.
+//   in_list("bar", [[2,"foo"], [4,["bar"]], [3,"baz"]]);  // Returns false.
+// Note:
+//   When `val==NAN` the answer will be false for any list.
+//   `val` cannot be a boolean.
+//   When the some element in `list` is a list containing `val` at it first element, the return is also true. 
 function in_list(val,list,idx=undef) = 
     let( s = search([val], list, num_returns_per_match=1, index_col_num=idx)[0] )
-    s==[] ? false
+    s==[] || s[0]==[] ? false
     : is_undef(idx) ? val==list[s] 
     : val==list[s][idx];
+    
+//***
+// 1. for sake of consistence, the arguments were changed to val and list
+// 2. in_list(0,[ 1, [0,1],2])) was returning true
 
 
 // Function: min_index()
@@ -108,11 +131,15 @@ function in_list(val,list,idx=undef) =
 //   vals = vector of values
 //   all = set to true to return indices of all occurences of the minimum.  Default: false
 // Example:
-//   min_index([5,3,9,6,2,7,8,2,1]); // Returns: 4
-//   min_index([5,3,9,6,2,7,8,2,1],all=true); // Returns: [4,7]
+//   min_index([5,3,9,6,2,7,8,2,1]); // Returns: 8
+//   min_index([5,3,9,6,2,7,8,2,7],all=true); // Returns: [4,7]
 function min_index(vals, all=false) =
+    assert( is_vector(vals) && len(vals)>0 , "Invalid or empty list of numbers.")
     all ? search(min(vals),vals,0) : search(min(vals), vals)[0];
 
+//***
+// 1. corrected examples
+// 2. input data check
 
 // Function: max_index()
 // Usage:
@@ -127,8 +154,11 @@ function min_index(vals, all=false) =
 //   max_index([5,3,9,6,2,7,8,9,1]); // Returns: 2
 //   max_index([5,3,9,6,2,7,8,9,1],all=true); // Returns: [2,7]
 function max_index(vals, all=false) =
+    assert( is_vector(vals) && len(vals)>0 , "Invalid or empty list of numbers.")
     all ? search(max(vals),vals,0) : search(max(vals), vals)[0];
 
+//***
+// 1. input data check
 
 // Function: list_increasing()
 // Usage:
@@ -179,9 +209,12 @@ function list_decreasing(list) =
 //   repeat([1,2,3],3);   // Returns [[1,2,3], [1,2,3], [1,2,3]]
 function repeat(val, n, i=0) =
     is_num(n)? [for(j=[1:1:n]) val] :
+    assert( is_list(n), "Invalid count number.")
     (i>=len(n))? val :
     [for (j=[1:1:n[i]]) repeat(val, n, i+1)];
 
+//***
+// 1. input data check
 
 // Function: list_range()
 // Usage:
@@ -192,7 +225,7 @@ function repeat(val, n, i=0) =
 // 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 fron `s`
+//   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.
@@ -205,25 +238,28 @@ function repeat(val, n, i=0) =
 //   list_range(n=4, s=3, step=3);   // Returns [3,6,9,12]
 //   list_range(n=5, s=0, e=10);     // Returns [0, 2.5, 5, 7.5, 10]
 //   list_range(e=3);                // Returns [0,1,2,3]
-//   list_range(e=6, step=2);        // Returns [0,2,4,6]
+//   list_range(e=7, step=2);        // Returns [0,2,4,6]
 //   list_range(s=3, e=5);           // Returns [3,4,5]
 //   list_range(s=3, e=8, step=2);   // Returns [3,5,7]
-//   list_range(s=4, e=8, step=2);   // Returns [4,6,8]
+//   list_range(s=4, e=8.3, step=2); // Returns [4,6,8]
 //   list_range(n=4, s=[3,4], step=[2,3]);  // Returns [[3,4], [5,7], [7,10], [9,13]]
 function list_range(n=undef, s=0, e=undef, step=undef) =
-    (n!=undef && e!=undef)? (
-        assert(is_undef(n) || is_undef(e) || is_undef(step), "At most 2 of n, e, and step can be given.")
-        [for (i=[0:1:n-1]) s+(e-s)*i/(n-1)]
-    ) : let(step = default(step,1))
-    (n!=undef)? [for (i=[0:1:n-1]) let(v=s+step*i) v] :
-    (e!=undef)? [for (v=[s:step:e]) v] :
-    assert(e!=undef||n!=undef, "Must supply one of `n` or `e`.");
-
+    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) )
+    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.")
+        [for (v=[s:step:e]) v] ;
+    
+//***
+// 1. input data check
+// 2. reworked accordingly
 
 
 // Section: List Manipulation
 
-
 // Function: reverse()
 // Description: Reverses a list/array.
 // Arguments:
@@ -255,34 +291,44 @@ function reverse(list) =
 //   l8 = list_rotate([1,2,3,4,5],5);  // Returns: [1,2,3,4,5]
 //   l9 = list_rotate([1,2,3,4,5],6);  // Returns: [2,3,4,5,1]
 function list_rotate(list,n=1) =
-    assert(is_list(list)||is_string(list))
-    assert(is_num(n))
+    assert(is_list(list)||is_string(list), "Invalid list or string.")
+    assert(is_finite(n), "Invalid number")
     select(list,n,n+len(list)-1);
 
+//***
+// 1. review of the input data check
 
 // Function: deduplicate()
 // Usage:
-//   deduplicate(list);
+//   deduplicate(list,[close],[eps]);
 // Description:
 //   Removes consecutive duplicate items in a list.
+//   When `eps` is zero, the comparison between consecutive items is exact.
+//   Otherwise, when all list items and subitems are numbers, the comparison is within the tolerance `eps`.
 //   This is different from `unique()` in that the list is *not* sorted.
 // Arguments:
 //   list = The list to deduplicate.
 //   closed = If true, drops trailing items if they match the first list item.
-//   eps = The maximum difference to allow between numbers or vectors.
+//   eps = The maximum tolerance between items.
 // Examples:
 //   deduplicate([8,3,4,4,4,8,2,3,3,8,8]);  // Returns: [8,3,4,8,2,3,8]
 //   deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]);  // Returns: [8,3,4,8,2,3]
 //   deduplicate("Hello");  // Returns: ["H","e","l","o"]
 //   deduplicate([[3,4],[7,2],[7,1.99],[1,4]],eps=0.1);  // Returns: [[3,4],[7,2],[1,4]]
+//   deduplicate([[7,undef],[7,undef],[1,4],[1,4+1e-12],eps=0);    // Returns: [[7,undef],[1,4],[1,4+1e-12]]
 function deduplicate(list, closed=false, eps=EPSILON) =
     assert(is_list(list)||is_string(list))
-    let(
-        l = len(list),
-        end = l-(closed?0:1)
-    ) (is_num(list[0]) || is_vector(list[0]))?
-        [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]] :
-        [for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]];
+    let( l = len(list),
+         end = l-(closed?0:1) )
+    is_string(list) || (eps==0)
+    ? [for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]]
+    : [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]];
+
+//***
+// 1. change Usage:, Description and Arguments
+// 2. reworked accordingly
+// 3. when eps==0, doesn't call approx
+// 4. the list may contain non numerical itens
 
 
 // Function: deduplicate_indexed()
@@ -300,24 +346,27 @@ function deduplicate(list, closed=false, eps=EPSILON) =
 //   deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1]);  // Returns: [1,4,3,2,0,1]
 //   deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1], closed=true);  // Returns: [1,4,3,2,0]
 function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
-    assert(is_list(list)||is_string(list))
-    assert(indices==[] || is_vector(indices))
+    assert(is_list(list)||is_string(list), "Improper list or string.")
     indices==[]? [] :
-    let(
-        l = len(indices),
-        end = l-(closed?0:1)
-    ) [
-        for (i = [0:1:l-1]) let(
-            a = list[indices[i]],
-            b = list[indices[(i+1)%l]],
-            eq = (a == b)? true :
-                (a*0 != b*0)? false :
-                is_num(a)? approx(a, b, eps=eps) :
-                is_vector(a)? approx(a, b, eps=eps) :
-                false
-        ) if (i==end || !eq) indices[i]
+    assert(is_vector(indices), "Indices must be a list of numbers.")
+    let( l = len(indices),
+         end = l-(closed?0:1) ) 
+    [ for (i = [0:1:l-1]) 
+        let(
+           a = list[indices[i]],
+           b = list[indices[(i+1)%l]],
+           eq = (a == b)? true :
+                (a*0 != b*0) || (eps==0)? false :
+                is_num(a) || is_vector(a) ? approx(a, b, eps=eps) 
+                : false
+        ) 
+        if (i==end || !eq) indices[i]
     ];
 
+//**
+// 1. msg of asserts
+// 2. format
+
 
 // Function: repeat_entries()
 // Usage:
@@ -345,17 +394,21 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
 //   echo(repeat_entries(list, 6, exact=false));  // Ouputs [0,0,1,1,2,2,3,3]
 //   echo(repeat_entries(list, [1,1,2,1], exact=false));  // Ouputs [0,1,2,2,3]
 function repeat_entries(list, N, exact = true) =
-    assert(is_list(list))
-    assert((is_num(N) && N>0) || is_vector(N),"Parameter N to repeat_entries must be postive number or vector")
+    assert(is_list(list) && len(list)>0, "The list cannot be void.")
+    assert((is_finite(N) && N>0) || is_vector(N,len(list)),
+            "Parameter N must be a number greater than zero or vector with the same length of `list`")
     let(
         length = len(list),
-        reps_guess = is_list(N)?
-            assert(len(N)==len(list), "Vector parameter N to repeat_entries has the wrong length")
-            N : repeat(N/length,length),
-        reps = exact? _sum_preserving_round(reps_guess) :
-            [for (val=reps_guess) round(val)]
+        reps_guess = is_list(N)? N : repeat(N/length,length),
+        reps = exact ?
+                 _sum_preserving_round(reps_guess) 
+               : [for (val=reps_guess) round(val)]
     )
     [for(i=[0:length-1]) each repeat(list[i],reps[i])];
+    
+//***
+// 1. Complete asserts
+// 2. Format
 
 
 // Function: list_set()
@@ -363,12 +416,11 @@ function repeat_entries(list, N, exact = true) =
 //   list_set(list, indices, values, [dflt], [minlen])
 // Description:
 //   Takes the input list and returns a new list such that `list[indices[i]] = values[i]` for all of
-//   the (index,value) pairs supplied.  If you supply `indices` that are beyond the length of the list
-//   then the list is extended and filled in with the `dflt` value.  If you set `minlen` then the list is
-//   lengthed, if necessary, by padding with `dflt` to that length.  The `indices` list can be in any
-//   order but run time will be (much) faster for long lists if it is already sorted.  Reptitions are
-//   not allowed.  If `indices` is given as a non-list scalar, then that index of the given `list` will
-//   be set to the value of `values`.
+//   the (index,value) pairs supplied and unchanged for other indices.  If you supply `indices` that are 
+//   beyond the length of the list then the list is extended and filled in with the `dflt` value.  
+//   If you set `minlen` then the list is lengthed, if necessary, by padding with `dflt` to that length.  
+//   Repetitions in `indices` are not allowed. The lists `indices` and `values` must have the same length.  
+//   If `indices` is given as a scalar, then that index of the given `list` will be set to the scalar value of `values`.
 // Arguments:
 //   list = List to set items in.  Default: []
 //   indices = List of indices into `list` to set.
@@ -378,92 +430,89 @@ function repeat_entries(list, N, exact = true) =
 // Examples:
 //   list_set([2,3,4,5], 2, 21);  // Returns: [2,3,21,5]
 //   list_set([2,3,4,5], [1,3], [81,47]);  // Returns: [2,81,4,47]
-function list_set(list=[],indices,values,dflt=0,minlen=0) =
+function list_set(list=[],indices,values,dflt=0,minlen=0) = 
     assert(is_list(list)||is_string(list))
     !is_list(indices)? (
-        (is_num(indices) && indices<len(list))? [for (i=idx(list)) i==indices? values : list[i]] :
-        list_set(list,[indices],[values],dflt)
-    ) :
-    assert(len(indices)==len(values),"Index list and value list must have the same length")
-    let(
-        sortind = list_increasing(indices) ? list_range(len(indices)) : sortidx(indices),
-        lastind = len(indices)==0 ? -1 : indices[select(sortind,-1)]
-    )
-    concat(
-        [for(j=[0:1:indices[sortind[0]]-1]) j>=len(list) ? dflt : list[j]],
-        [values[sortind[0]]], 
-        [for(i=[1:1:len(sortind)-1]) each
-            assert(indices[sortind[i]]!=indices[sortind[i-1]],"Repeated index")
-            concat(
-                [for(j=[1+indices[sortind[i-1]]:1:indices[sortind[i]]-1]) j>=len(list) ? dflt : list[j]],
-                [values[sortind[i]]]
-            )
-        ],
-        slice(list,1+lastind, len(list)),
-        repeat(dflt, minlen-lastind-1)
-    );
+        (is_finite(indices) && indices<len(list))? 
+           [for (i=idx(list)) i==indices? values : list[i]]
+        :  list_set(list,[indices],[values],dflt) )
+    :   assert(is_vector(indices) && is_list(values) && len(values)==len(indices) ,
+               "Index list and value list must have the same length")
+        let( midx = max(len(list)-1, max(indices)) )
+        [ for(i=[0:midx] ) 
+            let( j = search(i,indices,0),
+                 k = j[0] )
+            assert( len(j)<2, "Repeated indices are not acceptable." )
+            k!=undef ? values[k] :
+            i<len(list) ? list[i]:
+                dflt ,
+          each repeat(dflt, minlen-max(indices))
+        ];
+      
+//***
+// a full refactoring without sorting; its is quite faster than the original
 
 
 // Function: list_insert()
 // Usage:
-//   list_insert(list, pos, elements);
+//   list_insert(list, indices, values);
 // Description:
-//   Insert `elements` into `list` before position `pos`.
+//   Insert `values` into `list` before position `indices`.
 // Example:
 //   list_insert([3,6,9,12],1,5);  // Returns [3,5,6,9,12]
 //   list_insert([3,6,9,12],[1,3],[5,11]);  // Returns [3,5,6,9,11,12]
-function list_insert(list, pos, elements, _i=0) =
+function list_insert(list, indices, values, _i=0) = 
     assert(is_list(list)||is_string(list))
-    is_list(pos)? (
-        assert(len(pos)==len(elements))
-        let(
-            idxs = sortidx(pos),
-            lastidx = pos[idxs[len(idxs)-1]]
-        )
-        concat(
-            [
-                for(i=idx(idxs)) each concat(
-                    assert(pos[idxs[i]]<=len(list), "Indices in pos must be <= len(list)")
-                    [for (j=[(i==0?0:pos[idxs[i-1]]):1:pos[idxs[i]]-1]) list[j]],
-                    [elements[idxs[i]]]
-                )
-            ],
-            [for (j=[lastidx:1:len(list)-1]) list[j]]
-        )
-    ) : (
-        assert(pos<=len(list), "Indices in pos must be <= len(list)")
-        concat(
-            slice(list,0,pos),
-            elements,
-            (pos<len(list)? slice(list,pos,-1) : [])
-        )
-    );
+    ! is_list(indices)? 
+        assert( is_finite(indices) && is_finite(values), "Invalid indices/values." ) 
+        assert( indices<=len(list), "Indices must be <= len(list) ." )
+        [for (i=idx(list)) each ( i==indices?  [ values, list[i] ] : [ list[i] ] ) ]
+    :   assert( is_vector(indices) && is_list(values) && len(values)==len(indices) ,
+               "Index list and value list must have the same length")
+        assert( max(indices)<=len(list), "Indices must be <= len(list) ." )
+        let( maxidx = max(indices),
+             minidx  = min(indices) )
+        [ for(i=[0:1:minidx-1] ) list[i],
+          for(i=[minidx: min(maxidx, len(list)-1)] ) 
+            let( j = search(i,indices,0),
+                 k = j[0],
+                 x = assert( len(j)<2, "Repeated indices are not acceptable." )
+              )
+            each ( k != undef  ? [ values[k], list[i] ] : [ list[i] ] ),
+          for(i=[min(maxidx, len(list)-1)+1:1:len(list)-1] ) list[i],
+          if(maxidx==len(list)) values[max_index(indices)]
+        ];
+
+
+//***
+// Full refactoring without sorting
+// For sake of consistence, change `pos` and `elements` to `indices` and `values`
 
 
 // Function: list_remove()
 // Usage:
-//   list_remove(list, elements)
+//   list_remove(list, indices)
 // Description:
-//   Remove all items from `list` whose indexes are in `elements`.
+//   Remove all items from `list` whose indexes are in `indices`.
 // Arguments:
 //   list = The list to remove items from.
-//   elements = The list of indexes of items to remove.
+//   indices = The list of indexes of items to remove.
 // Example:
 //   list_insert([3,6,9,12],1);      // Returns: [3,9,12]
 //   list_insert([3,6,9,12],[1,3]);  // Returns: [3,9]
-function list_remove(list, elements) =
-    assert(is_list(list)||is_string(list))
-    !is_list(elements) ? list_remove(list,[elements]) :
-    len(elements)==0 ? list :
-    let(
-        sortind = list_increasing(elements) ? list_range(len(elements)) : sortidx(elements),
-        lastind = elements[select(sortind,-1)]
-    )
-    assert(lastind<len(list),"Element index beyond list end")
-    concat(slice(list, 0, elements[sortind[0]]),
-        [for(i=[1:1:len(sortind)-1]) each slice(list,1+elements[sortind[i-1]], elements[sortind[i]])],
-        slice(list,1+lastind, len(list))
-    );
+function list_remove(list, indices) =
+    assert(is_list(list)||is_string(list), "Invalid list/string." )
+    is_finite(indices) 
+    ?   [  for(i=[0:1:min(indices, len(list)-1)-1]) list[i],
+           for(i=[min(indices, len(list)-1)+1:1:len(list)-1]) list[i]  ]
+    :   assert( is_vector(indices), "Invalid list `indices`." )
+        len(indices)==0 ? list :
+        [ for(i=[0:len(list)-1])
+            if ( []==search(i,indices,1) ) list[i] ]; 
+
+//***
+// Refactoring without sort
+// For sake of consistence, change `elements` to `indices`
 
 
 // Function: list_remove_values()
@@ -503,8 +552,8 @@ function list_remove_values(list,values=[],all=false) =
 // Example:
 //   bselect([3,4,5,6,7], [false,true,true,false,true]);  // Returns: [4,5,7]
 function bselect(array,index) =
-    assert(is_list(array)||is_string(array))
-    assert(is_list(index))
+    assert(is_list(array)||is_string(array), "Improper array." )
+    assert(is_list(index) && len(index)>=len(array) , "Improper index list." )
     [for(i=[0:len(array)-1]) if (index[i]) array[i]];
 
 
@@ -524,15 +573,17 @@ function bselect(array,index) =
 //   list_bset([false,true,false,true,false], [3,4]);  // Returns: [0,3,0,4,0]
 //   list_bset([false,true,false,true,false], [3,4],dflt=1);  // Returns: [1,3,1,4,1]
 function list_bset(indexset, valuelist, dflt=0) =
-    assert(is_list(indexset))
-    assert(is_list(valuelist))
-    let(
-        trueind = search([true], indexset,0)[0]
-    ) concat(
+    assert(is_list(indexset), "The index set is not a list." )
+    assert(is_list(valuelist), "The `valuelist` is not a list." )
+//       trueind = search([true], indexset,0)[0]
+    let( trueind = [for(i=[0:len(indexset)-1]) if(indexset[i]) i ] )
+    concat(
         list_set([],trueind, valuelist, dflt=dflt),    // Fill in all of the values
         repeat(dflt,len(indexset)-max(trueind)-1)  // Add trailing values so length matches indexset
     );
 
+//***
+// search might return false results depending if it identifies `true` with 1.
 
 
 // Section: List Length Manipulation
@@ -541,58 +592,65 @@ function list_bset(indexset, valuelist, dflt=0) =
 // Description:
 //   Returns the length of the shortest sublist in a list of lists.
 // Arguments:
-//   vecs = A list of lists.
-function list_shortest(vecs) =
-    assert(is_list(vecs)||is_string(list))
-    min([for (v = vecs) len(v)]);
+//   array = A list of lists.
+function list_shortest(array) =
+    assert(is_list(array)||is_string(list), "Invalid input." )
+    min([for (v = array) len(v)]);
 
 
+//***
+// parameter name changed here and in the following for sake of consistence. It was `vecs`
+
 // Function: list_longest()
 // Description:
 //   Returns the length of the longest sublist in a list of lists.
 // Arguments:
-//   vecs = A list of lists.
-function list_longest(vecs) =
-    assert(is_list(vecs)||is_string(list))
-    max([for (v = vecs) len(v)]);
+//   array = A list of lists.
+function list_longest(array) =
+    assert(is_list(array)||is_string(list), "Invalid input." )
+    max([for (v = array) len(v)]);
 
 
 // Function: list_pad()
 // Description:
-//   If the list `v` is shorter than `minlen` length, pad it to length with the value given in `fill`.
+//   If the list `array` is shorter than `minlen` length, pad it to length with the value given in `fill`.
 // Arguments:
-//   v = A list.
+//   array = A list.
 //   minlen = The minimum length to pad the list to.
 //   fill = The value to pad the list with.
-function list_pad(v, minlen, fill=undef) =
-    assert(is_list(v)||is_string(list))
-    concat(v,repeat(fill,minlen-len(v)));
+function list_pad(array, minlen, fill=undef) =
+    assert(is_list(array)||is_string(list), "Invalid input." )
+    concat(array,repeat(fill,minlen-len(array)));
 
 
 // Function: list_trim()
 // Description:
-//   If the list `v` is longer than `maxlen` length, truncates it to be `maxlen` items long.
+//   If the list `array` is longer than `maxlen` length, truncates it to be `maxlen` items long.
 // Arguments:
-//   v = A list.
+//   array = A list.
 //   minlen = The minimum length to pad the list to.
-function list_trim(v, maxlen) =
-    assert(is_list(v)||is_string(list))
-    [for (i=[0:1:min(len(v),maxlen)-1]) v[i]];
+function list_trim(array, maxlen) =
+    assert(is_list(array)||is_string(list), "Invalid input." )
+    [for (i=[0:1:min(len(array),maxlen)-1]) array[i]];
 
 
 // Function: list_fit()
 // Description:
-//   If the list `v` is longer than `length` items long, truncates it to be exactly `length` items long.
-//   If the list `v` is shorter than `length` items long, pad it to length with the value given in `fill`.
+//   If the list `array` is longer than `length` items long, truncates it to be exactly `length` items long.
+//   If the list `array` is shorter than `length` items long, pad it to length with the value given in `fill`.
 // Arguments:
-//   v = A list.
+//   array = A list.
 //   minlen = The minimum length to pad the list to.
 //   fill = The value to pad the list with.
-function list_fit(v, length, fill) =
-    assert(is_list(v)||is_string(list))
-    let(l=len(v)) (l==length)? v : (l>length)? list_trim(v,length) : list_pad(v,length,fill);
-
+function list_fit(array, length, fill) =
+    assert(is_list(array)||is_string(list), "Invalid input." )
+    let(l=len(array)) 
+    l==length ? array : 
+    l> length ? list_trim(array,length) 
+              : list_pad(array,length,fill);
 
+//***
+// formating
 
 // Section: List Shuffling and Sorting
 
@@ -600,51 +658,59 @@ function list_fit(v, length, fill) =
 // Description:
 //   Shuffles the input list into random order.
 function shuffle(list) =
-    assert(is_list(list)||is_string(list))
+    assert(is_list(list)||is_string(list), "Invalid input." )
     len(list)<=1 ? list :
     let (
         rval = rands(0,1,len(list)),
         left  = [for (i=[0:len(list)-1]) if (rval[i]< 0.5) list[i]],
         right = [for (i=[0:len(list)-1]) if (rval[i]>=0.5) list[i]]
-    ) concat(shuffle(left), shuffle(right));
+    ) 
+    concat(shuffle(left), shuffle(right));
 
 
 // Sort a vector of scalar values
 function _sort_scalars(arr) =
-    len(arr)<=1 ? arr : let(
+    len(arr)<=1 ? arr : 
+    let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [ for (y = arr) if (y  < pivot) y ],
         equal   = [ for (y = arr) if (y == pivot) y ],
         greater = [ for (y = arr) if (y  > pivot) y ]
-    ) concat( _sort_scalars(lesser), equal, _sort_scalars(greater) );
+    ) 
+    concat( _sort_scalars(lesser), equal, _sort_scalars(greater) );
 
 
 // Sort a vector of vectors based on the first entry only of each vector
 function _sort_vectors1(arr) =
     len(arr)<=1 ? arr :
-    !(len(arr)>0) ? [] : let(
+    !(len(arr)>0) ? [] : 
+    let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [ for (y = arr) if (y[0]  < pivot[0]) y ],
         equal   = [ for (y = arr) if (y[0] == pivot[0]) y ],
         greater = [ for (y = arr) if (y[0]  > pivot[0]) y ]
-    ) concat( _sort_vectors1(lesser), equal, _sort_vectors1(greater) );
+    ) 
+    concat( _sort_vectors1(lesser), equal, _sort_vectors1(greater) );
 
 
 // Sort a vector of vectors based on the first two entries of each vector
 // Lexicographic order, remaining entries of vector ignored
 function _sort_vectors2(arr) =
     len(arr)<=1 ? arr :
-    !(len(arr)>0) ? [] : let(
+    !(len(arr)>0) ? [] : 
+    let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [ for (y = arr) if (y[0] < pivot[0] || (y[0]==pivot[0] && y[1]<pivot[1])) y ],
         equal   = [ for (y = arr) if (y[0] == pivot[0] && y[1]==pivot[1]) y ],
-        greater  = [ for (y = arr) if (y[0] > pivot[0] || (y[0]==pivot[0] && y[1]>pivot[1])) y ]
-    ) concat( _sort_vectors2(lesser), equal, _sort_vectors2(greater) );
+        greater = [ for (y = arr) if (y[0] > pivot[0] || (y[0]==pivot[0] && y[1]>pivot[1])) y ]
+    ) 
+    concat( _sort_vectors2(lesser), equal, _sort_vectors2(greater) );
 
 // Sort a vector of vectors based on the first three entries of each vector
 // Lexicographic order, remaining entries of vector ignored
 function _sort_vectors3(arr) =
-    len(arr)<=1 ? arr : let(
+    len(arr)<=1 ? arr : 
+    let(
         pivot   = arr[floor(len(arr)/2)],
         lesser  = [
             for (y = arr) if (
@@ -675,13 +741,15 @@ function _sort_vectors3(arr) =
                 )
             ) y
         ]
-    ) concat( _sort_vectors3(lesser), equal, _sort_vectors3(greater) );
+    ) 
+    concat( _sort_vectors3(lesser), equal, _sort_vectors3(greater) );
 
 
 // Sort a vector of vectors based on the first four entries of each vector
 // Lexicographic order, remaining entries of vector ignored
 function _sort_vectors4(arr) =
-    len(arr)<=1 ? arr : let(
+    len(arr)<=1 ? arr : 
+    let(
         pivot = arr[floor(len(arr)/2)],
         lesser = [
             for (y = arr) if (
@@ -723,26 +791,29 @@ function _sort_vectors4(arr) =
                 )
             ) y
         ]
-    ) concat( _sort_vectors4(lesser), equal, _sort_vectors4(greater) );
-
+    ) 
+    concat( _sort_vectors4(lesser), equal, _sort_vectors4(greater) );
+    
 
 function _sort_general(arr, idx=undef) =
     (len(arr)<=1) ? arr :
     let(
         pivot = arr[floor(len(arr)/2)],
         pivotval = idx==undef? pivot : [for (i=idx) pivot[i]],
-        compare = [
-            for (entry = arr) let(
-                val = idx==undef? entry : [for (i=idx) entry[i]],
-                cmp = compare_vals(val, pivotval)
-            ) cmp
-        ],
+        compare = 
+            is_undef(idx) ? [for(entry=arr) compare_vals(entry, pivotval) ] :
+            [ for (entry = arr) 
+                let( val = [for (i=idx) entry[i] ] )
+                compare_vals(val, pivotval) ] ,
         lesser  = [ for (i = [0:1:len(arr)-1]) if (compare[i] < 0) arr[i] ],
         equal   = [ for (i = [0:1:len(arr)-1]) if (compare[i] ==0) arr[i] ],
         greater = [ for (i = [0:1:len(arr)-1]) if (compare[i] > 0) arr[i] ]
     )
     concat(_sort_general(lesser,idx), equal, _sort_general(greater,idx));
 
+//***
+// format simplification
+
 
 // Function: sort()
 // Usage:
@@ -761,17 +832,22 @@ function _sort_general(arr, idx=undef) =
 //   sorted = sort(l);  // Returns [2,3,8,9,12,16,23,34,37,45,89]
 function sort(list, idx=undef) =
     !is_list(list) || len(list)<=1 ? list :
+    assert( is_undef(idx) || is_finite(idx) || is_vector(idx), "Invalid indices.")
     is_def(idx) ? _sort_general(list,idx) :
     let(size = array_dim(list))
     len(size)==1 ? _sort_scalars(list) :
-    len(size)==2 && size[1] <=4 ? (
+    len(size)==2 && size[1] <=4 
+    ? (
         size[1]==0 ? list :
         size[1]==1 ? _sort_vectors1(list) :
         size[1]==2 ? _sort_vectors2(list) :
-        size[1]==3 ? _sort_vectors3(list) :
-        /*size[1]==4*/ _sort_vectors4(list)
-    ) : _sort_general(list);
+        size[1]==3 ? _sort_vectors3(list)
+   /*size[1]==4*/  : _sort_vectors4(list)
+      ) 
+    : _sort_general(list);
 
+//***
+// Formating and input check
 
 // Function: sortidx()
 // Description:
@@ -795,22 +871,29 @@ function sort(list, idx=undef) =
 //   idxs2 = sortidx(lst, idx=0); // Returns: [1,2,0,3]
 //   idxs3 = sortidx(lst, idx=[1,3]); // Returns: [3,0,2,1]
 function sortidx(list, idx=undef) =
-    list==[] ? [] : let(
+    assert( is_list(list) || is_string(list) , "Invalid input." )
+    assert( is_undef(idx) || is_finite(idx) || is_vector(idx), "Invalid indices.")
+    list==[] ? [] : 
+    let(
         size = array_dim(list),
-        aug = is_undef(idx) && (len(size) == 1 || (len(size) == 2 && size[1]<=4))?
-            zip(list, list_range(len(list))) :
-            enumerate(list,idx=idx)
+        aug = is_undef(idx) && (len(size) == 1 || (len(size) == 2 && size[1]<=4))
+              ?  zip(list, list_range(len(list)))
+              :  enumerate(list,idx=idx)
     )
     is_undef(idx) && len(size) == 1? subindex(_sort_vectors1(aug),1) :
-    is_undef(idx) && len(size) == 2 && size[1] <=4? (
-        size[1]==0? list_range(len(arr)) :
-        size[1]==1? subindex(_sort_vectors1(aug),1) :
-        size[1]==2? subindex(_sort_vectors2(aug),2) :
-        size[1]==3? subindex(_sort_vectors3(aug),3) :
-        /*size[1]==4*/ subindex(_sort_vectors4(aug),4)
-    ) :
-    // general case
-    subindex(_sort_general(aug, idx=list_range(s=1,n=len(aug)-1)), 0);
+    is_undef(idx) && len(size) == 2 && size[1] <=4
+    ? (
+        size[1]==0 ? list_range(len(arr)) :
+        size[1]==1 ? subindex(_sort_vectors1(aug),1) :
+        size[1]==2 ? subindex(_sort_vectors2(aug),2) :
+        size[1]==3 ? subindex(_sort_vectors3(aug),3)
+    /*size[1]==4*/ : subindex(_sort_vectors4(aug),4)
+      ) 
+    :   // general case
+        subindex(_sort_general(aug, idx=list_range(s=1,n=len(aug)-1)), 0);
+
+//***
+// Formating and input check
 
 
 // Function: unique()
@@ -821,15 +904,16 @@ function sortidx(list, idx=undef) =
 // Arguments:
 //   arr = The list to uniquify.
 function unique(arr) =
-    assert(is_list(arr)||is_string(arr))
-    len(arr)<=1? arr : let(
-        sorted = sort(arr)
-    ) [
-        for (i=[0:1:len(sorted)-1])
+    assert(is_list(arr)||is_string(arr), "Invalid input." )
+    len(arr)<=1? arr : 
+    let( sorted = sort(arr))
+    [   for (i=[0:1:len(sorted)-1])
             if (i==0 || (sorted[i] != sorted[i-1]))
                 sorted[i]
     ];
 
+//***
+// Formating and input check
 
 // Function: unique_count()
 // Usage:
@@ -840,12 +924,15 @@ function unique(arr) =
 // Arguments:
 //   arr = The list to analyze. 
 function unique_count(arr) =
-      assert(is_list(arr) || is_string(arr))
+      assert(is_list(arr) || is_string(arr), "Invalid input." )
       arr == [] ? [[],[]] : 
       let( arr=sort(arr) )
-      let(ind = [0,for(i=[1:1:len(arr)-1]) if (arr[i]!=arr[i-1]) i])
-      [select(arr,ind),
-       deltas(concat(ind,[len(arr)]))];
+      let( ind = [0, for(i=[1:1:len(arr)-1]) if (arr[i]!=arr[i-1]) i] )
+      [ select(arr,ind), deltas( concat(ind,[len(arr)]) ) ];
+
+//***
+// Formating and input check
+
 
 // Section: List Iteration Helpers
 
@@ -864,7 +951,7 @@ function unique_count(arr) =
 //   colors = ["red", "green", "blue"];
 //   for (i=idx(colors)) right(20*i) color(colors[i]) circle(d=10);
 function idx(list, step=1, end=-1,start=0) =
-    assert(is_list(list)||is_string(list))
+    assert(is_list(list)||is_string(list), "Invalid input." )
     [start : step : len(list)+end];
 
 
@@ -883,10 +970,11 @@ function idx(list, step=1, end=-1,start=0) =
 //   colors = ["red", "green", "blue"];
 //   for (p=enumerate(colors)) right(20*p[0]) color(p[1]) circle(d=10);
 function enumerate(l,idx=undef) =
-    assert(is_list(l)||is_string(list))
-    (idx==undef)?
-        [for (i=[0:1:len(l)-1]) [i,l[i]]] :
-        [for (i=[0:1:len(l)-1]) concat([i], [for (j=idx) l[i][j]])];
+    assert(is_list(l)||is_string(list), "Invalid input." )
+    assert(is_undef(idx)||is_finite(idx)||is_vector(idx) ||is_range(idx), "Invalid index/indices." )
+    (idx==undef)
+    ?   [for (i=[0:1:len(l)-1]) [i,l[i]]]
+    :   [for (i=[0:1:len(l)-1]) concat([i], [for (j=idx) l[i][j]])];
 
 
 // Function: force_list()
@@ -909,8 +997,7 @@ function enumerate(l,idx=undef) =
 //   w = force_list(4, n=3, fill=1);  // Returns: [4,1,1]
 function force_list(value, n=1, fill) =
     is_list(value) ? value :
-    is_undef(fill)? [for (i=[1:1:n]) value] :
-    [value, for (i=[2:1:n]) fill];
+    is_undef(fill)? [for (i=[1:1:n]) value] : [value, for (i=[2:1:n]) fill];
 
 
 // Function: pair()
@@ -927,7 +1014,7 @@ function force_list(value, n=1, fill) =
 //   l = ["A","B","C","D"];
 //   echo([for (p=pair(l)) str(p.y,p.x)]);  // Outputs: ["BA", "CB", "DC"]
 function pair(v) =
-    assert(is_list(v)||is_string(v))
+    assert(is_list(v)||is_string(v), "Invalid input." )
     [for (i=[0:1:len(v)-2]) [v[i],v[i+1]]];
 
 
@@ -935,8 +1022,8 @@ function pair(v) =
 // Usage:
 //   pair_wrap(v)
 // Description:
-//   Takes a list, and returns a list of adjacent pairss from it, wrapping around from the end to the start of the list.
-// Example(2D): Note that the last point and first point DO get paired together.
+//   Takes a list, and returns a list of adjacent pairs from it, wrapping around from the end to the start of the list.
+// Example(2D): 
 //   for (p = pair_wrap(circle(d=20, $fn=12)))
 //       move(p[0])
 //           rot(from=BACK, to=p[1]-p[0])
@@ -945,7 +1032,7 @@ function pair(v) =
 //   l = ["A","B","C","D"];
 //   echo([for (p=pair_wrap(l)) str(p.y,p.x)]);  // Outputs: ["BA", "CB", "DC", "AD"]
 function pair_wrap(v) =
-    assert(is_list(v)||is_string(v))
+    assert(is_list(v)||is_string(v), "Invalid input." )
     [for (i=[0:1:len(v)-1]) [v[i],v[(i+1)%len(v)]]];
 
 
@@ -958,7 +1045,7 @@ function pair_wrap(v) =
 //   l = ["A","B","C","D","E"];
 //   echo([for (p=triplet(l)) str(p.z,p.y,p.x)]);  // Outputs: ["CBA", "DCB", "EDC"]
 function triplet(v) =
-    assert(is_list(v)||is_string(v))
+    assert(is_list(v)||is_string(v), "Invalid input." )
     [for (i=[0:1:len(v)-3]) [v[i],v[i+1],v[i+2]]];
 
 
@@ -971,7 +1058,7 @@ function triplet(v) =
 //   l = ["A","B","C","D"];
 //   echo([for (p=triplet_wrap(l)) str(p.z,p.y,p.x)]);  // Outputs: ["CBA", "DCB", "ADC", "BAD"]
 function triplet_wrap(v) =
-    assert(is_list(v)||is_string(v))
+    assert(is_list(v)||is_string(v), "Invalid input." )
     [for (i=[0:1:len(v)-1]) [v[i],v[(i+1)%len(v)],v[(i+2)%len(v)]]];
 
 
@@ -991,10 +1078,11 @@ function triplet_wrap(v) =
 // Example(2D):
 //   for (p=permute(regular_ngon(n=7,d=100))) stroke(p);
 function permute(l,n=2,_s=0) =
-    assert(is_list(l))
-    assert(len(l)-_s >= n)
-    n==1? [for (i=[_s:1:len(l)-1]) [l[i]]] :
-    [for (i=[_s:1:len(l)-n], p=permute(l,n=n-1,_s=i+1)) concat([l[i]], p)];
+    assert(is_list(l), "Invalid list." )
+    assert( is_finite(n) && n>=1 && n<=len(l), "Invalid number `n`." )
+    n==1
+    ?   [for (i=[_s:1:len(l)-1]) [l[i]]] 
+    :   [for (i=[_s:1:len(l)-n], p=permute(l,n=n-1,_s=i+1)) concat([l[i]], p)];
 
 
 
@@ -1020,26 +1108,28 @@ function permute(l,n=2,_s=0) =
 //   set_v = set_union(set_a, set_b, get_indices=true);
 //   // set_v now equals [[5,0,1,2,6], [2,3,5,7,11,1,8]]
 function set_union(a, b, get_indices=false) =
+    assert( is_list(a) && is_list(b), "Invalid sets." )
     let(
         found1 = search(b, a),
         found2 = search(b, b),
-        c = [
-            for (i=idx(b))
-            if (found1[i] == [] && found2[i] == i)
-            b[i]
-        ],
+        c = [ for (i=idx(b))
+                if (found1[i] == [] && found2[i] == i)
+                    b[i] 
+            ],
         nset = concat(a, c)
-    ) !get_indices? nset :
+    ) 
+    ! get_indices ? nset :
     let(
         la = len(a),
         found3 = search(b, c),
-        idxs = [
-            for (i=idx(b))
-            (found1[i] != [])? found1[i] :
-            la + found3[i]
-        ]
+        idxs =  [ for (i=idx(b))
+                    (found1[i] != [])? found1[i] : la + found3[i]
+                ]
     ) [idxs, nset];
 
+//***
+// Formating and input check
+
 
 // Function: set_difference()
 // Usage:
@@ -1055,10 +1145,12 @@ function set_union(a, b, get_indices=false) =
 //   set_d = set_difference(set_a, set_b);
 //   // set_d now equals [7,11]
 function set_difference(a, b) =
-    let(
-        found = search(a, b, num_returns_per_match=1)
-    ) [ for (i=idx(a)) if(found[i]==[]) a[i] ];
+    assert( is_list(a) && is_list(b), "Invalid sets." )
+    let( found = search(a, b, num_returns_per_match=1) )
+    [ for (i=idx(a)) if(found[i]==[]) a[i] ];
 
+//***
+// Formating and input check
 
 // Function: set_intersection()
 // Usage:
@@ -1074,14 +1166,34 @@ function set_difference(a, b) =
 //   set_i = set_intersection(set_a, set_b);
 //   // set_i now equals [2,3,5]
 function set_intersection(a, b) =
-    let(
-        found = search(a, b, num_returns_per_match=1)
-    ) [ for (i=idx(a)) if(found[i]!=[]) a[i] ];
+    assert( is_list(a) && is_list(b), "Invalid sets." )
+    let( found = search(a, b, num_returns_per_match=1) )
+    [ for (i=idx(a)) if(found[i]!=[]) a[i] ];
 
+//***
+// Formating and input check
 
 
 // Section: Array Manipulation
 
+// Function: add_scalar()
+// Usage:  
+//   add_scalar(v,s);
+// Description:
+//   Given an array and a scalar, returns the array with the scalar added to each item in it.
+//   If given a list of arrays, recursively adds the scalar to the each array.
+// Arguments:
+//   v = The initial array.
+//   s = A scalar value to add to every item in the array.
+// Example:
+//   add_scalar([1,2,3],3);            // Returns: [4,5,6]
+//   add_scalar([[1,2,3],[3,4,5]],3);  // Returns: [[4,5,6],[6,7,8]]
+function add_scalar(v,s) = 
+    is_finite(s) ? [for (x=v) is_list(x)? add_scalar(x,s) : is_finite(x) ? x+s: x] : v;
+
+//***
+// for sake of consistence, move it to here from vectors.scad
+
 // Function: subindex()
 // Description:
 //   For each array item, return the indexed subitem.
@@ -1096,10 +1208,11 @@ function set_intersection(a, b) =
 //   subindex(v,2);      // Returns [3, 7, 11, 15]
 //   subindex(v,[2,1]);  // Returns [[3, 2], [7, 6], [11, 10], [15, 14]]
 //   subindex(v,[1:3]);  // Returns [[2, 3, 4], [6, 7, 8], [10, 11, 12], [14, 15, 16]]
-function subindex(v, idx) = [
-    for(val=v) let(value=[for(i=idx) val[i]])
+function subindex(v, idx) = 
+    [ for(val=v) 
+        let( value=[for(i=idx) val[i]] )
         len(value)==1 ? value[0] : value
-];
+    ];
 
 
 // Function: zip()
@@ -1131,15 +1244,16 @@ function subindex(v, idx) = [
 function zip(vecs, v2, v3, fit=false, fill=undef) =
     (v3!=undef)? zip([vecs,v2,v3], fit=fit, fill=fill) :
     (v2!=undef)? zip([vecs,v2], fit=fit, fill=fill) :
-    assert(in_list(fit, [false, "short", "long"]))
+    assert(in_list(fit, [false, "short", "long"]), "Invalid fit value." )
     assert(all([for(v=vecs) is_list(v)]), "One of the inputs to zip is not a list")
     let(
         minlen = list_shortest(vecs),
-        maxlen = list_longest(vecs),
-        dummy = (fit==false)? assert(minlen==maxlen, "Input vectors to zip must have the same length") : 0
-    ) (fit == "long")?
-        [for(i=[0:1:maxlen-1]) [for(v=vecs) for(x=(i<len(v)? v[i] : (fill==undef)? [fill] : fill)) x] ] :
-        [for(i=[0:1:minlen-1]) [for(v=vecs) for(x=v[i]) x] ];
+        maxlen = list_longest(vecs)
+    )
+    assert(fit!=false || minlen==maxlen, "Input vectors to zip must have the same length")
+    (fit == "long")
+    ?   [for(i=[0:1:maxlen-1]) [for(v=vecs) for(x=(i<len(v)? v[i] : (fill==undef)? [fill] : fill)) x] ] 
+    :   [for(i=[0:1:minlen-1]) [for(v=vecs) for(x=v[i]) x] ];
 
 
 // Function: array_group()
@@ -1169,15 +1283,16 @@ function flatten(l) = [for (a = l) each a];
 
 // Internal.  Not exposed.
 function _array_dim_recurse(v) =
-    !is_list(v[0])?  (
-        sum( [for(entry=v) is_list(entry) ? 1 : 0]) == 0 ? [] : [undef]
-    ) : let(
-        firstlen = len(v[0]),
-        first = sum( [for(entry = v) len(entry) == firstlen  ? 0 : 1]   ) == 0 ? firstlen : undef,
-        leveldown = flatten(v)
-    ) is_list(leveldown[0])? (
-        concat([first],_array_dim_recurse(leveldown))
-    ) : [first];
+    !is_list(v[0])
+    ?   sum( [for(entry=v) is_list(entry) ? 1 : 0] ) == 0 ? [] : [undef]
+    :   let(
+          firstlen = len(v[0]),
+          first = sum( [for(entry = v) len(entry) == firstlen  ? 0 : 1]   ) == 0 ? firstlen : undef,
+          leveldown = flatten(v)
+        ) 
+        is_list(leveldown[0])
+        ?  concat([first],_array_dim_recurse(leveldown))
+        : [first];
 
 
 // Function: array_dim()
@@ -1201,15 +1316,18 @@ function _array_dim_recurse(v) =
 //   array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 2);  // Returns 3
 //   array_dim([[[1,2,3],[4,5,6]],[[7,8,9]]]);                // Returns [2,undef,3]
 function array_dim(v, depth=undef) =
-    (depth == undef)? (
-        concat([len(v)], _array_dim_recurse(v))
-    ) : (depth == 0)? (
-        len(v)
-    ) : (
-        let(dimlist = _array_dim_recurse(v))
-        (depth > len(dimlist))? 0 : dimlist[depth-1]
-    );
+    assert( is_undef(depth) || ( is_finite(depth) && depth>=0 ), "Invalid depth.")
+    ! is_list(v) ? 0 :
+    (depth == undef)
+    ?   concat([len(v)], _array_dim_recurse(v))
+    :   (depth == 0)
+        ?  len(v)
+        :  let( dimlist = _array_dim_recurse(v))
+           (depth > len(dimlist))? 0 : dimlist[depth-1] ;
 
+//***
+// Formating
+// This function may return undef!
 
 
 // Function: transpose()
@@ -1242,9 +1360,15 @@ function array_dim(v, depth=undef) =
 // Example:
 //   transpose([3,4,5]);  // Returns: [3,4,5]
 function transpose(arr) =
-    is_list(arr[0])? [for (i=[0:1:len(arr[0])-1]) [for (j=[0:1:len(arr)-1]) arr[j][i]]] : arr;
-
+    let( a0 = arr[0] )
+    is_list(a0)
+    ?   assert([for(a=arr) if(len(a)!=len(a0)) 1]==[], "The array is not a matrix." )
+        [for (i=[0:1:len(a0)-1]) 
+          [ for (j=[0:1:len(arr)-1]) arr[j][i] ] ] 
+    :  arr;
 
+//***
+// Input data check and formating
 
 
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/common.scad b/common.scad
index 8d0655a..f3fabd5 100644
--- a/common.scad
+++ b/common.scad
@@ -15,7 +15,8 @@
 // Usage:
 //   typ = typeof(x);
 // Description:
-//   Returns a string representing the type of the value.  One of "undef", "boolean", "number", "nan", "string", "list", or "range"
+//   Returns a string representing the type of the value.  One of "undef", "boolean", "number", "nan", "string", "list", "range" or "invalid".
+//   Some malformed "ranges", like '[0:NAN:INF]' and '[0:"a":INF]', may be classified as "undef" or "invalid".
 function typeof(x) =
     is_undef(x)? "undef" :
     is_bool(x)? "boolean" :
@@ -23,8 +24,11 @@ function typeof(x) =
     is_nan(x)? "nan" :
     is_string(x)? "string" :
     is_list(x)? "list" :
-    "range";
+    is_range(x) ? "range" :
+    "invalid";
 
+//***
+// included "invalid"
 
 // Function: is_type()
 // Usage:
@@ -70,8 +74,8 @@ function is_str(x) = is_string(x);
 //   is_int(n)
 // Description:
 //   Returns true if the given value is an integer (it is a number and it rounds to itself).  
-function is_int(n) = is_num(n) && n == round(n);
-function is_integer(n) = is_num(n) && n == round(n);
+function is_int(n) = is_finite(n) && n == round(n);
+function is_integer(n) = is_finite(n) && n == round(n);
 
 
 // Function: is_nan()
@@ -93,7 +97,7 @@ function is_finite(v) = is_num(0*v);
 // Function: is_range()
 // Description:
 //   Returns true if its argument is a range
-function is_range(x) = is_num(x[0]) && !is_list(x);
+function is_range(x) = !is_list(x) && is_finite(x[0]+x[1]+x[2]) ;
 
 
 // Function: is_list_of()
@@ -106,13 +110,15 @@ function is_range(x) = is_num(x[0]) && !is_list(x);
 //   is_list_of([3,4,5], 0);            // Returns true
 //   is_list_of([3,4,undef], 0);        // Returns false
 //   is_list_of([[3,4],[4,5]], [1,1]);  // Returns true
+//   is_list_of([[3,"a"],[4,true]], [1,undef]);  // Returns true
 //   is_list_of([[3,4], 6, [4,5]], [1,1]);  // Returns false
-//   is_list_of([[1,[3,4]], [4,[5,6]]], [1,[2,3]]);    // Returne true
-//   is_list_of([[1,[3,INF]], [4,[5,6]]], [1,[2,3]]);  // Returne false
+//   is_list_of([[1,[3,4]], [4,[5,6]]], [1,[2,3]]);    // Returns true
+//   is_list_of([[1,[3,INF]], [4,[5,6]]], [1,[2,3]]);  // Returns false
+//   is_list_of([], [1,[2,3]]);                        // Returns true
 function is_list_of(list,pattern) =
     let(pattern = 0*pattern)
     is_list(list) &&
-    []==[for(entry=list) if (entry*0 != pattern) entry];
+    []==[for(entry=0*list) if (entry != pattern) entry];
 
 
 // Function: is_consistent()
@@ -311,10 +317,13 @@ function scalar_vec3(v, dflt=undef) =
 //   Calculate the standard number of sides OpenSCAD would give a circle based on `$fn`, `$fa`, and `$fs`.
 // Arguments:
 //   r = Radius of circle to get the number of segments for.
-function segs(r) =
+function segs(r) = 
     $fn>0? ($fn>3? $fn : 3) :
-    ceil(max(5, min(360/$fa, abs(r)*2*PI/$fs)));
+    let( r = is_finite(r)? r: 0 ) 
+    ceil(max(5, min(360/$fa, abs(r)*2*PI/$fs))) ;
 
+//***
+// avoids undef
 
 
 // Section: Testing Helpers
@@ -322,7 +331,7 @@ function segs(r) =
 
 function _valstr(x) =
     is_list(x)? str("[",str_join([for (xx=x) _valstr(xx)],","),"]") :
-    is_num(x)? fmt_float(x,12) : x;
+    is_finite(x)? fmt_float(x,12) : x;
 
 
 // Module: assert_approx()
diff --git a/tests/polyhedra.scad b/tests/polyhedra.scad
index 3a77fbc..8c66f2e 100644
--- a/tests/polyhedra.scad
+++ b/tests/polyhedra.scad
@@ -2,10 +2,10 @@ include<../std.scad>
 include<../polyhedra.scad>
 
 
-$fn=96;
-
 if (true) {
 
+   $fn=96;
+
   // Display of all solids with insphere, midsphere and circumsphere
     
   for(i=[0:len(_polyhedra_)-1]) {
diff --git a/tests/test_arrays.scad b/tests/test_arrays.scad
index 756e433..479f4bb 100644
--- a/tests/test_arrays.scad
+++ b/tests/test_arrays.scad
@@ -1,38 +1,7 @@
 include <../std.scad>
 
-// List/Array Ops
-
-module test_repeat() {
-    assert(repeat(1, 4) == [1,1,1,1]);
-    assert(repeat(8, [2,3]) == [[8,8,8], [8,8,8]]);
-    assert(repeat(0, [2,2,3]) == [[[0,0,0],[0,0,0]], [[0,0,0],[0,0,0]]]);
-    assert(repeat([1,2,3],3) == [[1,2,3], [1,2,3], [1,2,3]]);
-}
-test_repeat();
-
-
-module test_in_list() {
-    assert(in_list("bar", ["foo", "bar", "baz"]));
-    assert(!in_list("bee", ["foo", "bar", "baz"]));
-    assert(in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1));
-    assert(!in_list(undef, [3,4,5]));
-    assert(in_list(undef,[3,4,undef,5]));
-    assert(!in_list(3,[]));
-    assert(!in_list(3,[4,5,[3]]));
-    
-}
-test_in_list();
-
-
-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]);
-}
-test_slice();
 
+// Section: List Query Operations
 
 module test_select() {
     l = [3,4,5,6,7,8,9];
@@ -49,6 +18,71 @@ module test_select() {
 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) == []);
+}
+test_slice();
+
+
+module test_in_list() {
+    assert(in_list("bar", ["foo", "bar", "baz"]));
+    assert(!in_list("bee", ["foo", "bar", "baz"]));
+    assert(in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1));
+    
+    assert(!in_list("bee", ["foo", "bar", ["bee"]]));
+    assert(in_list(NAN, [NAN])==false);
+}
+test_in_list();
+
+
+module test_min_index() {
+    assert(min_index([5,3,9,6,2,7,8,2,1])==8);
+    assert(min_index([5,3,9,6,2,7,8,2,7],all=true)==[4,7]);
+//    assert(min_index([],all=true)==[]);
+}
+test_min_index();
+
+
+module test_max_index() {
+    assert(max_index([5,3,9,6,2,7,8,9,1])==2);
+    assert(max_index([5,3,9,6,2,7,8,9,7],all=true)==[2,7]);
+//    assert(max_index([],all=true)==[]);
+}
+test_max_index();
+
+
+module test_list_increasing() {
+    assert(list_increasing([1,2,3,4]) == true);
+    assert(list_increasing([1,3,2,4]) == false);
+    assert(list_increasing([4,3,2,1]) == false);
+}
+test_list_increasing();
+
+
+module test_list_decreasing() {
+    assert(list_decreasing([1,2,3,4]) == false);
+    assert(list_decreasing([4,2,3,1]) == false);
+    assert(list_decreasing([4,3,2,1]) == true);
+}
+test_list_decreasing();
+
+// Section: Basic List Generation
+
+module test_repeat() {
+    assert(repeat(1, 4) == [1,1,1,1]);
+    assert(repeat(8, [2,3]) == [[8,8,8], [8,8,8]]);
+    assert(repeat(0, [2,2,3]) == [[[0,0,0],[0,0,0]], [[0,0,0],[0,0,0]]]);
+    assert(repeat([1,2,3],3) == [[1,2,3], [1,2,3], [1,2,3]]);
+    assert(repeat(4, [2,-1]) == [[], []]);
+}
+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]);
@@ -66,6 +100,8 @@ test_list_range();
 
 module test_reverse() {
     assert(reverse([3,4,5,6]) == [6,5,4,3]);
+    assert(reverse("abcd") == ["d","c","b","a"]);
+    assert(reverse([]) == []);
 }
 test_reverse();
 
@@ -90,6 +126,8 @@ module test_deduplicate() {
     assert(deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]) == [8,3,4,8,2,3]);
     assert(deduplicate("Hello") == ["H","e","l","o"]);
     assert(deduplicate([[3,4],[7,1.99],[7,2],[1,4]],eps=0.1) == [[3,4],[7,2],[1,4]]);
+    assert(deduplicate([], closed=true) == []);
+    assert(deduplicate([[1,[1,[undef]]],[1,[1,[undef]]],[1,[2]],[1,[2,[0]]]])==[[1, [1,[undef]]],[1,[2]],[1,[2,[0]]]]);
 }
 test_deduplicate();
 
@@ -148,22 +186,6 @@ module test_list_bset() {
 test_list_bset();
 
 
-module test_list_increasing() {
-    assert(list_increasing([1,2,3,4]) == true);
-    assert(list_increasing([1,3,2,4]) == false);
-    assert(list_increasing([4,3,2,1]) == false);
-}
-test_list_increasing();
-
-
-module test_list_decreasing() {
-    assert(list_decreasing([1,2,3,4]) == false);
-    assert(list_decreasing([4,2,3,1]) == false);
-    assert(list_decreasing([4,3,2,1]) == true);
-}
-test_list_decreasing();
-
-
 module test_list_shortest() {
     assert(list_shortest(["foobar", "bazquxx", "abcd"]) == 4);
 }
@@ -315,6 +337,13 @@ test_set_intersection();
 // Arrays
 
 
+module test_add_scalar() {
+    assert(add_scalar([1,2,3],3) == [4,5,6]);
+    assert(add_scalar([[1,2,3],[3,4,5]],3) == [[4,5,6],[6,7,8]]);
+}
+test_add_scalar();
+
+
 module test_subindex() {
     v = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
     assert(subindex(v,2) == [3, 7, 11, 15]);
diff --git a/tests/test_common.scad b/tests/test_common.scad
index fa6bdbd..1cad5e3 100644
--- a/tests/test_common.scad
+++ b/tests/test_common.scad
@@ -18,6 +18,10 @@ module test_typeof() {
     assert(typeof([0:1:5]) == "range");
     assert(typeof([-3:2:5]) == "range");
     assert(typeof([10:-2:-10]) == "range");
+    assert(typeof([0:NAN:INF]) == "invalid");
+    assert(typeof([0:"a":INF]) == "undef"); 
+    assert(typeof([0:[]:INF]) == "undef"); 
+    assert(typeof([true:1:INF]) == "undef"); 
 }
 test_typeof();
 
@@ -102,6 +106,8 @@ module test_is_int() {
     assert(!is_int(-99.1));
     assert(!is_int(99.1));
     assert(!is_int(undef));
+    assert(!is_int(INF));
+    assert(!is_int(NAN));
     assert(!is_int(false));
     assert(!is_int(true));
     assert(!is_int("foo"));
@@ -124,6 +130,8 @@ module test_is_integer() {
     assert(!is_integer(-99.1));
     assert(!is_integer(99.1));
     assert(!is_integer(undef));
+    assert(!is_integer(INF));
+    assert(!is_integer(NAN));
     assert(!is_integer(false));
     assert(!is_integer(true));
     assert(!is_integer("foo"));
@@ -166,6 +174,9 @@ module test_is_range() {
     assert(!is_range("foo"));
     assert(!is_range([]));
     assert(!is_range([3,4,5]));
+    assert(!is_range([INF:4:5]));
+    assert(!is_range([3:NAN:5]));
+    assert(!is_range([3:4:"a"]));
     assert(is_range([3:1:5]));
 }
 test_is_nan();
@@ -331,11 +342,25 @@ module test_scalar_vec3() {
     assert(scalar_vec3([3]) == [3,0,0]);
     assert(scalar_vec3([3,4]) == [3,4,0]);
     assert(scalar_vec3([3,4],dflt=1) == [3,4,1]);
+    assert(scalar_vec3([3,"a"],dflt=1) == [3,"a",1]);
+    assert(scalar_vec3([3,[2]],dflt=1) == [3,[2],1]);
     assert(scalar_vec3([3],dflt=1) == [3,1,1]);
     assert(scalar_vec3([3,4,5]) == [3,4,5]);
     assert(scalar_vec3([3,4,5,6]) == [3,4,5]);
+    assert(scalar_vec3([3,4,5,6]) == [3,4,5]);
 }
 test_scalar_vec3();
 
 
+module test_segs() {
+    assert_equal(segs(50,$fn=8), 8);
+    assert_equal(segs(50,$fa=2,$fs=2), 158);
+    assert(segs(1)==5);
+    assert(segs(11)==30);
+  //  assert(segs(1/0)==5);
+  //  assert(segs(0/0)==5);
+  //  assert(segs(undef)==5);
+}
+test_segs();
+
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/tests/test_vectors.scad b/tests/test_vectors.scad
index 42f0a04..2d43eee 100644
--- a/tests/test_vectors.scad
+++ b/tests/test_vectors.scad
@@ -17,13 +17,6 @@ module test_is_vector() {
 test_is_vector();
 
 
-module test_add_scalar() {
-    assert(add_scalar([1,2,3],3) == [4,5,6]);
-    assert(add_scalar([[1,2,3],[3,4,5]],3) == [[4,5,6],[6,7,8]]);
-}
-test_add_scalar();
-
-
 module test_vfloor() {
     assert_equal(vfloor([2.0, 3.14, 18.9, 7]), [2,3,18,7]);
     assert_equal(vfloor([-2.0, -3.14, -18.9, -7]), [-2,-4,-19,-7]);
diff --git a/vectors.scad b/vectors.scad
index bb28ead..57359ee 100644
--- a/vectors.scad
+++ b/vectors.scad
@@ -19,41 +19,26 @@
 // Arguments:
 //   v = The value to test to see if it is a vector.
 //   length = If given, make sure the vector is `length` items long.
-//   zero = If false, require that the length of the vector is not approximately zero.  If true, require the length of the vector to be approx zero-length.  Default: `undef` (don't check vector length.)
-//   eps = The minimum vector length that is considered non-zero.  Default: `EPSILON` (`1e-9`)
 // Example:
-//   is_vector(4);              // Returns false
-//   is_vector([4,true,false]); // Returns false
-//   is_vector([3,4,INF,5]);    // Returns false
-//   is_vector([3,4,5,6]);      // Returns true
-//   is_vector([3,4,undef,5]);  // Returns false
-//   is_vector([3,4,5],3);      // Returns true
-//   is_vector([3,4,5],4);      // Returns true
-//   is_vector([]);             // Returns false
-//   is_vector([0,0,0],zero=true);   // Returns true
-//   is_vector([0,0,0],zero=false);  // Returns false
-//   is_vector([0,1,0],zero=true);   // Returns false
-//   is_vector([0,0,1],zero=false);  // Returns true
+//   is_vector(4);                          // Returns false
+//   is_vector([4,true,false]);             // Returns false
+//   is_vector([3,4,INF,5]);                // Returns false
+//   is_vector([3,4,5,6]);                  // Returns true
+//   is_vector([3,4,undef,5]);              // Returns false
+//   is_vector([3,4,5],3);                  // Returns true
+//   is_vector([3,4,5],4);                  // Returns true
+//   is_vector([]);                         // Returns false
+//   is_vector([0,4,0],3,zero=false);     // Returns true
+//   is_vector([0,0,0],zero=false);       // Returns false
+//   is_vector([0,0,1e-12],zero=false);   // Returns false
+//   is_vector([],zero=false);            // Returns false
 function is_vector(v,length,zero,eps=EPSILON) =
     is_list(v) && is_num(0*(v*v))
     && (is_undef(length) || len(v)==length)
     && (is_undef(zero) || ((norm(v) >= eps) == !zero));
 
-
-// Function: add_scalar()
-// Usage:
-//   add_scalar(v,s);
-// Description:
-//   Given a vector and a scalar, returns the vector with the scalar added to each item in it.
-//   If given a list of vectors, recursively adds the scalar to the each vector.
-// Arguments:
-//   v = The initial list of values.
-//   s = A scalar value to add to every item in the vector.
-// Example:
-//   add_scalar([1,2,3],3);            // Returns: [4,5,6]
-//   add_scalar([[1,2,3],[3,4,5]],3);  // Returns: [[4,5,6],[6,7,8]]
-function add_scalar(v,s) = [for (x=v) is_list(x)? add_scalar(x,s) : x+s];
-
+//***
+// add_scalar() is an array operation: moved to array.scad 
 
 // Function: vang()
 // Usage:
@@ -63,6 +48,7 @@ function add_scalar(v,s) = [for (x=v) is_list(x)? add_scalar(x,s) : x+s];
 //   Given a 2D vector, returns the angle in degrees counter-clockwise from X+ on the XY plane.
 //   Given a 3D vector, returns [THETA,PHI] where THETA is the number of degrees counter-clockwise from X+ on the XY plane, and PHI is the number of degrees up from the X+ axis along the XZ plane.
 function vang(v) =
+    assert( is_vector(v,2) || is_vector(v,3) , "Invalid vector")
     len(v)==2? atan2(v.y,v.x) :
     let(res=xyz_to_spherical(v)) [res[1], 90-res[2]];
 
@@ -76,7 +62,9 @@ function vang(v) =
 //   v2 = The second vector.
 // Example:
 //   vmul([3,4,5], [8,7,6]);  // Returns [24, 28, 30]
-function vmul(v1, v2) = [for (i = [0:1:len(v1)-1]) v1[i]*v2[i]];
+function vmul(v1, v2) = 
+//    assert( is_vector(v1) && is_vector(v2,len(v1)), "Incompatible vectors")
+    [for (i = [0:1:len(v1)-1]) v1[i]*v2[i]];
 
 
 // Function: vdiv()
@@ -88,7 +76,9 @@ function vmul(v1, v2) = [for (i = [0:1:len(v1)-1]) v1[i]*v2[i]];
 //   v2 = The second vector.
 // Example:
 //   vdiv([24,28,30], [8,7,6]);  // Returns [3, 4, 5]
-function vdiv(v1, v2) = [for (i = [0:1:len(v1)-1]) v1[i]/v2[i]];
+function vdiv(v1, v2) = 
+    assert( is_vector(v1) && is_vector(v2,len(v1)), "Incompatible vectors")
+    [for (i = [0:1:len(v1)-1]) v1[i]/v2[i]];
 
 
 // Function: vabs()
@@ -97,19 +87,25 @@ function vdiv(v1, v2) = [for (i = [0:1:len(v1)-1]) v1[i]/v2[i]];
 //   v = The vector to get the absolute values of.
 // Example:
 //   vabs([-1,3,-9]);  // Returns: [1,3,9]
-function vabs(v) = [for (x=v) abs(x)];
+function vabs(v) =
+    assert( is_vector(v), "Invalid vector" ) 
+    [for (x=v) abs(x)];
 
 
 // Function: vfloor()
 // Description:
 //   Returns the given vector after performing a `floor()` on all items.
-function vfloor(v) = [for (x=v) floor(x)];
+function vfloor(v) =
+    assert( is_vector(v), "Invalid vector" ) 
+    [for (x=v) floor(x)];
 
 
 // Function: vceil()
 // Description:
 //   Returns the given vector after performing a `ceil()` on all items.
-function vceil(v) = [for (x=v) ceil(x)];
+function vceil(v) =
+    assert( is_vector(v), "Invalid vector" ) 
+    [for (x=v) ceil(x)];
 
 
 // Function: unit()
@@ -137,6 +133,7 @@ function unit(v, error=[[["ASSERT"]]]) =
 // Function: vector_angle()
 // Usage:
 //   vector_angle(v1,v2);
+//   vector_angle([v1,v2]);
 //   vector_angle(PT1,PT2,PT3);
 //   vector_angle([PT1,PT2,PT3]);
 // Description:
@@ -156,34 +153,38 @@ function unit(v, error=[[["ASSERT"]]]) =
 //   vector_angle([10,0,10], [0,0,0], [-10,10,0]);  // Returns: 120
 //   vector_angle([[10,0,10], [0,0,0], [-10,10,0]]);  // Returns: 120
 function vector_angle(v1,v2,v3) =
-    let(
-        vecs = !is_undef(v3)? [v1-v2,v3-v2] :
-            !is_undef(v2)? [v1,v2] :
-            len(v1) == 3? [v1[0]-v1[1],v1[2]-v1[1]] :
-            len(v1) == 2? v1 :
-            assert(false, "Bad arguments to vector_angle()"),
-        is_valid = is_vector(vecs[0]) && is_vector(vecs[1]) && vecs[0]*0 == vecs[1]*0
+    assert( ( is_undef(v3) && ( is_undef(v2) || same_shape(v1,v2) ) )
+            || is_consistent([v1,v2,v3]) ,
+            "Bad arguments.")
+    assert( is_vector(v1) || is_consistent(v1), "Bad arguments.") 
+    let( vecs = ! is_undef(v3) ? [v1-v2,v3-v2] :
+                ! is_undef(v2) ? [v1,v2] :
+                len(v1) == 3   ? [v1[0]-v1[1], v1[2]-v1[1]] 
+                               : v1
     )
-    assert(is_valid, "Bad arguments to vector_angle()")
+    assert(is_vector(vecs[0],2) || is_vector(vecs[0],3), "Bad arguments.")
     let(
         norm0 = norm(vecs[0]),
         norm1 = norm(vecs[1])
     )
-    assert(norm0>0 && norm1>0,"Zero length vector given to vector_angle()")
+    assert(norm0>0 && norm1>0, "Zero length vector.")
     // NOTE: constrain() corrects crazy FP rounding errors that exceed acos()'s domain.
     acos(constrain((vecs[0]*vecs[1])/(norm0*norm1), -1, 1));
-
+    
+//***
+// completing input data check
 
 // Function: vector_axis()
 // Usage:
 //   vector_axis(v1,v2);
+//   vector_axis([v1,v2]);
 //   vector_axis(PT1,PT2,PT3);
 //   vector_axis([PT1,PT2,PT3]);
 // Description:
 //   If given a single list of two vectors, like `vector_axis([V1,V2])`, returns the vector perpendicular the two vectors V1 and V2.
-//   If given a single list of three points, like `vector_axis([A,B,C])`, returns the vector perpendicular the line segments AB and BC.
-//   If given two vectors, like `vector_axis(V1,V1)`, returns the vector perpendicular the two vectors V1 and V2.
-//   If given three points, like `vector_axis(A,B,C)`, returns the vector perpendicular the line segments AB and BC.
+//   If given a single list of three points, like `vector_axis([A,B,C])`, returns the vector perpendicular to the plane through a, B and C.
+//   If given two vectors, like `vector_axis(V1,V2)`, returns the vector perpendicular to the two vectors V1 and V2.
+//   If given three points, like `vector_axis(A,B,C)`, returns the vector perpendicular to the plane through a, B and C.
 // Arguments:
 //   v1 = First vector or point.
 //   v2 = Second vector or point.
@@ -199,28 +200,26 @@ function vector_axis(v1,v2=undef,v3=undef) =
     is_vector(v3)
     ?   assert(is_consistent([v3,v2,v1]), "Bad arguments.")
         vector_axis(v1-v2, v3-v2)
-    :
-    assert( is_undef(v3), "Bad arguments.")
-    is_undef(v2)
-    ?   assert( is_list(v1), "Bad arguments.")
-        len(v1) == 2
-            ? vector_axis(v1[0],v1[1])
-            : vector_axis(v1[0],v1[1],v1[2])
-    :
-    assert(
-        is_vector(v1,zero=false) &&
-            is_vector(v2,zero=false) &&
-            is_consistent([v1,v2]),
-        "Bad arguments."
-    )
-    let(
-        eps = 1e-6,
-        w1 = point3d(v1/norm(v1)),
-        w2 = point3d(v2/norm(v2)),
-        w3 = (norm(w1-w2) > eps && norm(w1+w2) > eps) ? w2
-           : (norm(vabs(w2)-UP) > eps) ? UP
-           : RIGHT
-    ) unit(cross(w1,w3));
+    :   assert( is_undef(v3), "Bad arguments.")
+        is_undef(v2)
+        ?   assert( is_list(v1), "Bad arguments.")
+            len(v1) == 2 
+            ?   vector_axis(v1[0],v1[1]) 
+            :   vector_axis(v1[0],v1[1],v1[2])
+        :   assert( is_vector(v1,zero=false) && is_vector(v2,zero=false) && is_consistent([v1,v2])
+                    , "Bad arguments.")  
+            let(
+              eps = 1e-6,
+              w1 = point3d(v1/norm(v1)),
+              w2 = point3d(v2/norm(v2)),
+              w3 = (norm(w1-w2) > eps && norm(w1+w2) > eps) ? w2 
+                   : (norm(vabs(w2)-UP) > eps)? UP 
+                   : RIGHT
+            ) unit(cross(w1,w3));
 
 
+//***
+// completing input data check and refactoring 
+// Note: vector_angle and vector_axis have the same kind of inputs and two code strategy alternatives
+
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

From b5d465cf1e744c58640ade5ea4b63881ac804bf1 Mon Sep 17 00:00:00 2001
From: Garth Minette <gminette@gmail.com>
Date: Mon, 27 Jul 2020 15:15:34 -0700
Subject: [PATCH 08/24] docs_gen.py now shows more docs errors.

---
 arrays.scad         |   2 +-
 attachments.scad    |  24 ++---
 beziers.scad        |  20 ++--
 distributors.scad   |  10 +-
 involute_gears.scad |  10 +-
 joiners.scad        |   6 +-
 math.scad           |   6 +-
 paths.scad          |   8 +-
 polyhedra.scad      |  18 ++--
 regions.scad        |   4 +-
 rounding.scad       |  68 ++++++-------
 scripts/docs_gen.py | 236 ++++++++++++++++++++++++--------------------
 shapes.scad         |   2 +-
 shapes2d.scad       |  10 +-
 skin.scad           |  30 +++---
 strings.scad        |   6 +-
 version.scad        |   2 +-
 vnf.scad            |   4 +-
 18 files changed, 245 insertions(+), 221 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index ea47d48..e21ccfb 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -331,7 +331,7 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
 //   that the final length is exactly as requested.  If you set it to `false` then the
 //   algorithm will favor uniformity and the output list may have a different number of
 //   entries due to rounding.
-//   
+//   .
 //   When applied to a path the output path is the same geometrical shape but has some vertices
 //   repeated.  This can be useful when you need to align paths with a different number of points.
 //   (See also subdivide_path for a different way to do that.) 
diff --git a/attachments.scad b/attachments.scad
index 1e7a9fb..8775832 100644
--- a/attachments.scad
+++ b/attachments.scad
@@ -37,16 +37,16 @@ $tags_hidden = [];
 //   * Spin is a simple rotation around the Z axis.
 //   * Orientation is rotating an object so that its top is pointed towards a given vector.
 //   An object will first be translated to its anchor position, then spun, then oriented.
-//   
+//   .
 //   ## Anchor
 //   Anchoring is specified with the `anchor` argument in most shape modules.
 //   Specifying `anchor` when creating an object will translate the object so
 //   that the anchor point is at the origin (0,0,0).  Anchoring always occurs
 //   before spin and orientation are applied.
-//   
+//   .
 //   An anchor can be referred to in one of two ways; as a directional vector,
 //   or as a named anchor string.
-//   
+//   .
 //   When given as a vector, it points, in a general way, towards the face, edge, or
 //   corner of the object that you want the anchor for, relative to the center of
 //   the object.  There are directional constants named `TOP`, `BOTTOM`, `FRONT`, `BACK`,
@@ -55,7 +55,7 @@ $tags_hidden = [];
 //    - `[0,0,1]` is the same as `TOP` and refers to the center of the top face.
 //    - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge.
 //    - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner.
-//   
+//   .
 //   The components of the directional vector should all be `1`, `0`, or `-1`.
 //   When the object is cylindrical, conical, or spherical in nature, the anchors will be
 //   located around the surface of the cylinder, cone, or sphere, relative to the center.
@@ -64,20 +64,20 @@ $tags_hidden = [];
 //   two faces the edge is between.  The direction of a corner anchor will be the average
 //   of the anchor directions of the three faces the corner is on.  The spin of all standard
 //   anchors is 0.
-//   
+//   .
 //   Some more complex objects, like screws and stepper motors, have named anchors
 //   to refer to places on the object that are not at one of the standard faces, edges
 //   or corners.  For example, stepper motors have anchors for `"screw1"`, `"screw2"`,
 //   etc. to refer to the various screwholes on the stepper motor shape.  The names,
 //   positions, directions, and spins of these anchors will be specific to the object,
 //   and will be documented when they exist.
-//   
+//   .
 //   ## Spin
 //   Spin is specified with the `spin` argument in most shape modules.  Specifying `spin`
 //   when creating an object will rotate the object counter-clockwise around the Z axis
 //   by the given number of degrees.  Spin is always applied after anchoring, and before
 //   orientation.
-//   
+//   .
 //   ## Orient
 //   Orientation is specified with the `orient` argument in most shape modules.  Specifying
 //   `orient` when creating an object will rotate the object such that the top of the
@@ -593,17 +593,17 @@ function attachment_is_shown(tags) =
 //   the transformation matrix needed to be applied to the contents of that volume.  A managed 3D
 //   volume is assumed to be vertically (Z-axis) oriented, and centered.  A managed 2D area is just
 //   assumed to be centered.
-//   
+//   .
 //   If `p` is not given, then the transformation matrix will be returned.
 //   If `p` contains a VNF, a new VNF will be returned with the vertices transformed by the matrix.
 //   If `p` contains a path, a new path will be returned with the vertices transformed by the matrix.
 //   If `p` contains a point, a new point will be returned, transformed by the matrix.
-//   
+//   .
 //   If `$attach_to` is not defined, then the following transformations are performed in order:
 //   * Translates so the `anchor` point is at the origin (0,0,0).
 //   * Rotates around the Z axis by `spin` degrees counter-clockwise.
 //   * Rotates so the top of the part points towards the vector `orient`.
-//   
+//   .
 //   If `$attach_to` is defined, as a consequence of `attach(from,to)`, then
 //   the following transformations are performed in order:
 //   * Translates this part so it's anchor position matches the parent's anchor position.
@@ -685,13 +685,13 @@ function reorient(
 //       children();
 //   }
 //   ```
-//   
+//   .
 //   If this is *not* run as a child of `attach()` with the `to` argument
 //   given, then the following transformations are performed in order:
 //   * Translates so the `anchor` point is at the origin (0,0,0).
 //   * Rotates around the Z axis by `spin` degrees counter-clockwise.
 //   * Rotates so the top of the part points towards the vector `orient`.
-//   
+//   .
 //   If this is called as a child of `attach(from,to)`, then the info
 //   for the anchor points referred to by `from` and `to` are fetched,
 //   which will include position, direction, and spin.  With that info,
diff --git a/beziers.scad b/beziers.scad
index 0407e49..13a74ab 100644
--- a/beziers.scad
+++ b/beziers.scad
@@ -13,26 +13,26 @@ include <skin.scad>
 
 // Section: Terminology
 //   **Polyline**: A series of points joined by straight line segements.
-//   
+//   .
 //   **Bezier Curve**: A mathematical curve that joins two endpoints, following a curve determined by one or more control points.
-//   
+//   .
 //   **Endpoint**: A point that is on the end of a bezier segment.  This point lies on the bezier curve.
-//   
+//   .
 //   **Control Point**: A point that influences the shape of the curve that connects two endpoints.  This is often *NOT* on the bezier curve.
-//   
+//   .
 //   **Degree**: The number of control points, plus one endpoint, needed to specify a bezier segment.  Most beziers are cubic (degree 3).
-//   
+//   .
 //   **Bezier Segment**: A list consisting of an endpoint, one or more control points, and a final endpoint.  The number of control points is one less than the degree of the bezier.  A cubic (degree 3) bezier segment looks something like:
 //       `[endpt1, cp1, cp2, endpt2]`
-//   
+//   .
 //   **Bezier Path**: A list of bezier segments flattened out into a list of points, where each segment shares the endpoint of the previous segment as a start point. A cubic Bezier Path looks something like:
 //       `[endpt1, cp1, cp2, endpt2, cp3, cp4, endpt3]`
 //   **NOTE**: A bezier path is *NOT* a polyline.  It is only the points and controls used to define the curve.
-//   
+//   .
 //   **Bezier Patch**: A surface defining grid of (N+1) by (N+1) bezier points.  If a Bezier Segment defines a curved line, a Bezier Patch defines a curved surface.
-//   
+//   .
 //   **Bezier Surface**: A surface defined by a list of one or more bezier patches.
-//   
+//   .
 //   **Spline Steps**: The number of straight-line segments to split a bezier segment into, to approximate the bezier curve.  The more spline steps, the closer the approximation will be to the curve, but the slower it will be to generate.  Usually defaults to 16.
 
 
@@ -1230,7 +1230,7 @@ function bezier_surface(patches=[], splinesteps=16, vnf=EMPTY_VNF, style="defaul
 
 
 // Module: bezier_polyhedron()
-// Useage:
+// Usage:
 //   bezier_polyhedron(patches, [splinesteps], [vnf], [style], [convexity])
 // Description:
 //   Takes a list of two or more bezier patches and attempts to make a complete polyhedron from them.
diff --git a/distributors.scad b/distributors.scad
index 603e50d..5738033 100644
--- a/distributors.scad
+++ b/distributors.scad
@@ -231,7 +231,7 @@ module zcopies(spacing, n, l, sp)
 //   dir = Vector direction to distribute copies along.
 //   l = Length to distribute copies along.
 //
-// Side Effect:
+// Side Effects:
 //   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
 //   `$idx` is set to the index number of each child being copied.
 //
@@ -275,7 +275,7 @@ module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef)
 //   sizes = Array containing how much space each child will need.
 //   l = Length to distribute copies along.
 //
-// Side Effect:
+// Side Effects:
 //   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
 //   `$idx` is set to the index number of each child being copied.
 //
@@ -320,7 +320,7 @@ module xdistribute(spacing=10, sizes=undef, l=undef)
 //   sizes = Array containing how much space each child will need.
 //   l = Length to distribute copies along.
 //
-// Side Effect:
+// Side Effects:
 //   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
 //   `$idx` is set to the index number of each child being copied.
 //
@@ -365,7 +365,7 @@ module ydistribute(spacing=10, sizes=undef, l=undef)
 //   sizes = Array containing how much space each child will need.
 //   l = Length to distribute copies along.
 //
-// Side Effect:
+// Side Effects:
 //   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
 //   `$idx` is set to the index number of each child being copied.
 //
@@ -538,7 +538,7 @@ module grid2d(spacing, n, size, stagger=false, inside=undef)
 //   n = Optional number of copies to have per axis.
 //   spacing = spacing of copies per axis. Use with `n`.
 //
-// Side Effect:
+// Side Effects:
 //   `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
 //   `$idx` is set to the [Xidx,Yidx,Zidx] index values of each child copy, when using `count` and `n`.
 //
diff --git a/involute_gears.scad b/involute_gears.scad
index f614868..6c7d238 100644
--- a/involute_gears.scad
+++ b/involute_gears.scad
@@ -1,22 +1,22 @@
 //////////////////////////////////////////////////////////////////////////////////////////////
 // LibFile: involute_gears.scad
 //   Involute Spur Gears and Racks
-//   
+//   .
 //   by Leemon Baird, 2011, Leemon@Leemon.com
 //   http://www.thingiverse.com/thing:5505
-//   
+//   .
 //   Additional fixes and improvements by Revar Desmera, 2017-2019, revarbat@gmail.com
-//   
+//   .
 //   This file is public domain.  Use it for any purpose, including commercial
 //   applications.  Attribution would be nice, but is not required.  There is
 //   no warranty of any kind, including its correctness, usefulness, or safety.
-//   
+//   .
 //   This is parameterized involute spur (or helical) gear.  It is much simpler
 //   and less powerful than others on Thingiverse.  But it is public domain.  I
 //   implemented it from scratch from the descriptions and equations on Wikipedia
 //   and the web, using Mathematica for calculations and testing, and I now
 //   release it into the public domain.
-//   
+//   .
 //   To use, add the following line to the beginning of your file:
 //   ```
 //   include <BOSL2/std.scad>
diff --git a/joiners.scad b/joiners.scad
index a8afb4d..1ab34e6 100644
--- a/joiners.scad
+++ b/joiners.scad
@@ -657,10 +657,10 @@ function _pin_size(size) =
 //    be printed.  When oriented UP the shaft of the pin runs in the Z direction and the flat sides are the front and back.  The default
 //    orientation (FRONT) and anchor (FRONT) places the pin in a printable configuration, flat side down on the xy plane.
 //    The tightness of fit is determined by `preload` and `clearance`.  To make pins tighter increase `preload` and/or decrease `clearance`.  
-//    
+//    .
 //    The "large" or "standard" size pin has a length of 10.8 and diameter of 7.  The "medium" pin has a length of 8 and diameter of 4.6.  The "small" pin
 //    has a length of 6 and diameter of 3.2.  The "tiny" pin has a length of 4 and a diameter of 2.5.  
-//    
+//    .
 //    This pin is based on https://www.thingiverse.com/thing:213310 by Emmett Lalishe
 //    and a modified version at https://www.thingiverse.com/thing:3218332 by acwest
 //    and distributed under the Creative Commons - Attribution - Share Alike License
@@ -724,7 +724,7 @@ module snap_pin(size,r,radius,d,diameter, l,length, nub_depth, snap, thickness,
 //   if you add a lubricant.  If `pointed` is true the socket is pointed to receive a pointed pin, otherwise it has a rounded and and
 //   will be shorter.  If `fins` is set to true then two fins are included inside the socket to act as supports (which may help when printing tip up,
 //   especially when `pointed=false`).  The default orientation is DOWN with anchor BOTTOM so that you can difference() the socket away from an object.
-//
+//   .
 //   The "large" or "standard" size pin has a length of 10.8 and diameter of 7.  The "medium" pin has a length of 8 and diameter of 4.6.  The "small" pin
 //   has a length of 6 and diameter of 3.2.  The "tiny" pin has a length of 4 and a diameter of 2.5.  
 // Arguments:
diff --git a/math.scad b/math.scad
index 7629cb1..82b08bb 100644
--- a/math.scad
+++ b/math.scad
@@ -916,7 +916,7 @@ function count_true(l, nmax=undef, i=0, cnt=0) =
 //   data[len(data)-1].  This function uses a symetric derivative approximation
 //   for internal points, f'(t) = (f(t+h)-f(t-h))/2h.  For the endpoints (when closed=false) the algorithm
 //   uses a two point method if sufficient points are available: f'(t) = (3*(f(t+h)-f(t)) - (f(t+2*h)-f(t+h)))/2h.
-// 
+//   .
 //   If `h` is a vector then it is assumed to be nonuniform, with h[i] giving the sampling distance
 //   between data[i+1] and data[i], and the data values will be linearly resampled at each corner
 //   to produce a uniform spacing for the derivative estimate.  At the endpoints a single point method
@@ -1066,10 +1066,10 @@ function polynomial(p, z, k, zk, total) =
 
 
 // Function: poly_mult()
-// Usage
+// Usage:
 //   polymult(p,q)
 //   polymult([p1,p2,p3,...])
-// Descriptoin:
+// Description:
 //   Given a list of polynomials represented as real coefficient lists, with the highest degree coefficient first, 
 //   computes the coefficient list of the product polynomial.  
 function poly_mult(p,q) = 
diff --git a/paths.scad b/paths.scad
index aad26cd..7d5f978 100644
--- a/paths.scad
+++ b/paths.scad
@@ -968,11 +968,11 @@ module path_extrude(path, convexity=10, clipsize=100) {
 //   sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
 //   stroke(sinwav,width=.1);
 //   color("red") path_spread(sinwav, n=5) rect([.2,1.5],anchor=FRONT);
-// Example(2D)): Open path, specify `n` and `spacing`
+// Example(2D): Open path, specify `n` and `spacing`
 //   sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
 //   stroke(sinwav,width=.1);
 //   color("red") path_spread(sinwav, n=5, spacing=1) rect([.2,1.5],anchor=FRONT);
-// Example(2D)): Closed path, specify `n` and `spacing`, copies centered around circle[0]
+// Example(2D): Closed path, specify `n` and `spacing`, copies centered around circle[0]
 //   circle = regular_ngon(n=64,or=10);
 //   stroke(circle,width=.1,closed=true);
 //   color("red") path_spread(circle, n=10, spacing=1, closed=true) rect([.2,1.5],anchor=FRONT);
@@ -1051,7 +1051,7 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals
 
 // Function: path_cut()
 //
-// Usage
+// Usage:
 //   path_cut(path, dists, [closed], [direction])
 //
 // Description:
@@ -1177,7 +1177,7 @@ function _sum_preserving_round(data, index=0) =
 //   the final length is exactly as requested.  If you set it to `false` then the
 //   algorithm will favor uniformity and the output path may have a different number of
 //   points due to rounding error.
-//   
+//   .
 //   With the `"segment"` method you can also specify a vector of lengths.  This vector, 
 //   `N` specfies the desired point count on each segment: with vector input, `subdivide_path`
 //   attempts to place `N[i]-1` points on segment `i`.  The reason for the -1 is to avoid
diff --git a/polyhedra.scad b/polyhedra.scad
index 3902ecf..b199fe9 100644
--- a/polyhedra.scad
+++ b/polyhedra.scad
@@ -42,7 +42,7 @@ function _unique_groups(m) = [
 //
 // Description:
 //   Creates a regular polyhedron with optional rounding.  Children are placed on the polyhedron's faces.
-//   
+//   .
 //   **Selecting the polyhedron:**
 //   You constrain the polyhedra list by specifying different characteristics, that must all be met
 //   * `name`: e.g. `"dodecahedron"` or `"pentagonal icositetrahedron"`
@@ -54,13 +54,13 @@ function _unique_groups(m) = [
 //   * hasfaces: The list of vertex counts for faces; at least one listed type must appear:
 //     * `hasfaces = 3`: polygon has at least one triangular face
 //     * `hasfaces = [5,6]`: polygon has a hexagonal or a pentagonal face
-//   
+//   .
 //   The result is a list of selected polyhedra.  You then specify `index` to choose which one of the
 //   remaining polyhedra you want.  If you don't give `index` the first one on the list is created.
 //   Two examples:
 //   * `faces=12, index=2`:  Creates the 3rd solid with 12 faces
 //   * `type="archimedean", faces=14`: Creates the first archimedean solid with 14 faces (there are 3)
-//   
+//   .
 //   **Choosing the size of your polyhedron:**
 //   The default is to create a polyhedron whose smallest edge has length 1.  You can specify the
 //   smallest edge length with the size option.  Alternatively you can specify the size of the
@@ -70,18 +70,18 @@ function _unique_groups(m) = [
 //   For the platonic solids every face meets the inscribed sphere and every corner touches the
 //   circumscribed sphere.  For the Archimedean solids the inscribed sphere will touch only some of
 //   the faces and for the Catalan solids the circumscribed sphere meets only some of the corners.
-//   
+//   .
 //   **Orientation:**
 //   Orientation is controled by the facedown parameter.  Set this to false to get the canonical orientation.
 //   Set it to true to get the largest face oriented down.  If you set it to a number the module searches for
 //   a face with the specified number of vertices and orients that face down.
-//   
+//   .
 //   **Rounding:**
 //   If you specify the rounding parameter the module makes a rounded polyhedron by first creating an
 //   undersized model and then expanding it with `minkowski()`.  This only produces the correct result
 //   if the in-sphere contacts all of the faces of the polyhedron, which is true for the platonic, the
 //   catalan solids and the trapezohedra but false for the archimedean solids.
-//   
+//   .
 //   **Children:**
 //   The module places children on the faces of the polyhedron.  The child coordinate system is
 //   positioned so that the origin is the center of the face.  If `rotate_children` is true (default)
@@ -90,7 +90,7 @@ function _unique_groups(m) = [
 //   With `repeat=false` each child is used once.  You can specify `draw=false` to suppress drawing of
 //   the polyhedron, e.g. to use for `difference()` operations.  The module sets various parameters
 //   you can use in your children (see the side effects list below).
-//   
+//   .
 //   **Stellation:**
 //   Technically stellation is an operation of shifting the polyhedron's faces to produce a new shape
 //   that may have self-intersecting faces.  OpenSCAD cannot handle self-intersecting faces, so we
@@ -99,7 +99,7 @@ function _unique_groups(m) = [
 //   no stellation is performed.  Otherwise stellate gives the pyramid height as a multiple of the
 //   edge length.  A negative pyramid height can be used to perform excavation, where a pyramid is
 //   removed from each face.
-//   
+//   .
 //   **Special Polyhedra:**
 //   These can be selected only by name and may require different parameters, or ignore some standard
 //   parameters.
@@ -112,7 +112,7 @@ function _unique_groups(m) = [
 //     * `h`: Distance from the center to the apex.
 //     * `r`: Radius of the polygon that defines the equatorial vertices.
 //     * `d`: Diameter of the polygon that defines the equatorial vertices.
-//   
+//   .
 //   * Named stellations: various polyhedra such as the Kepler-Poinsot solids are stellations with
 //    specific pyramid heights.  To make them easier to generate you can specify them by name.
 //    This is equivalent to giving the name of the appropriate base solid and the magic stellate
diff --git a/regions.scad b/regions.scad
index 0b7f91d..6169337 100644
--- a/regions.scad
+++ b/regions.scad
@@ -550,7 +550,7 @@ function _offset_region(
 //   Takes an input path and returns a path offset by the specified amount.  As with the built-in
 //   offset() module, you can use `r` to specify rounded offset and `delta` to specify offset with
 //   corners.  Positive offsets shift the path to the left (relative to the direction of the path).
-//   
+//   .
 //   When offsets shrink the path, segments cross and become invalid.  By default `offset()` checks
 //   for this situation.  To test validity the code checks that segments have distance larger than (r
 //   or delta) from the input path.  This check takes O(N^2) time and may mistakenly eliminate
@@ -560,7 +560,7 @@ function _offset_region(
 //   increases the number of samples on the segment that are checked.)  Run time will increase.  In
 //   some situations you may be able to decrease run time by setting quality to 0, which causes only
 //   segment ends to be checked.
-//   
+//   .
 //   For construction of polyhedra `offset()` can also return face lists.  These list faces between
 //   the original path and the offset path where the vertices are ordered with the original path
 //   first, starting at `firstface_index` and the offset path vertices appearing afterwords.  The
diff --git a/rounding.scad b/rounding.scad
index 8d6e2c6..254d035 100644
--- a/rounding.scad
+++ b/rounding.scad
@@ -25,7 +25,7 @@ include <structs.scad>
 //   and continuous curvature rounding using 4th order bezier curves.  Circular rounding can produce a
 //   tactile "bump" where the curvature changes from flat to circular.
 //   See https://hackernoon.com/apples-icons-have-that-shape-for-a-very-good-reason-720d4e7c8a14
-//   
+//   .
 //   You select the type of rounding using the `method` option, which should be `"smooth"` to
 //   get continuous curvature rounding, `"circle"` to get circular rounding, or `"chamfer"` to get chamfers.  The default is circle
 //   rounding.  Each method has two options you can use to specify the amount of rounding.
@@ -33,29 +33,29 @@ include <structs.scad>
 //   much of the corner to "cut" off.  This can be easier to understand than setting a circular radius, which can be
 //   unexpectedly extreme when the corner is very sharp.  It also allows a systematic specification of
 //   corner treatments that are the same size for all three methods.
-//   
+//   .
 //   For circular rounding you can also use the `radius` parameter, which sets a circular rounding
 //   radius.  For chamfers and smooth rounding you can specify the `joint` parameter, which specifies the distance
 //   away from the corner along the path where the roundover or chamfer should start.  The figure below shows
 //   the cut and joint distances for a given roundover.
-//   
+//   .
 //   The `"smooth"` method rounding also has a parameter that specifies how smooth the curvature match
 //   is.  This parameter, `k`, ranges from 0 to 1, with a default of 0.5.  Larger values give a more
 //   abrupt transition and smaller ones a more gradual transition.  If you set the value much higher
 //   than 0.8 the curvature changes abruptly enough that though it is theoretically continuous, it may
 //   not be continuous in practice.  If you set it very small then the transition is so gradual that
 //   the length of the roundover may be extremely long.
-//   
+//   .
 //   If you select curves that are too large to fit the function will fail with an error.  You can set `verbose=true` to
 //   get a message showing a list of scale factors you can apply to your rounding parameters so that the
 //   roundovers will fit on the curve.  If the scale factors are larger than one
 //   then they indicate how much you can increase the curve sizes before collisions will occur.
-//   
+//   .
 //   The parameters `radius`, `cut`, `joint` and `k` can be numbers, which round every corner using the same parameters, or you
 //   can specify a list to round each corner with different parameters.  If the curve is not closed then the first and last points
 //   of the curve are not rounded.  In this case you can specify a full list of points anyway, and the endpoint values are ignored,
 //   or you can specify a list that has length len(path)-2, omitting the two dummy values.
-//   
+//   .
 //   Examples:
 //   * `method="circle", radius=2`:
 //       Rounds every point with circular, radius 2 roundover
@@ -63,7 +63,7 @@ include <structs.scad>
 //       Rounds every point with continuous curvature rounding with a cut of 2, and a default 0.5 smoothing parameter
 //   * `method="smooth", cut=2, k=0.3`:
 //       Rounds every point with continuous curvature rounding with a cut of 2, and a very gentle 0.3 smoothness setting
-//   
+//   .
 //   The number of segments used for roundovers is determined by `$fa`, `$fs` and `$fn` as usual for
 //   circular roundovers.  For continuous curvature roundovers `$fs` and `$fn` are used and `$fa` is
 //   ignored.  Note that $fn is interpreted as the number of points on the roundover curve, which is
@@ -394,7 +394,7 @@ function _rounding_offsets(edgespec,z_dir=1) =
 //   and will match the tangents at every point.  If you do not specify tangents they will be computed using
 //   path_tangents with uniform=false by default.  Note that setting uniform to true with non-uniform
 //   sampling may be desirable in some cases but tends to produces curves that overshoot the point on the path.  
-//
+//   .
 //   The size or relsize parameter determines how far the curve can bend away from
 //   the input path.  In the case where the curve has a single hump, the size specifies the exact distance
 //   between the specified path and the curve.  If you give relsize then it is relative to the segment
@@ -474,21 +474,21 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
 //   disabling the validity check when it is needed can generate invalid polyhedra that will produce CGAL errors upon
 //   rendering.  Such validity errors will also occur if you specify a self-intersecting shape.
 //   The offset profile is quantized to 1/1024 steps to avoid failures in offset() that can occur with very tiny offsets.
-//   
+//   .
 //   The build-in profiles are: circular rounding, teardrop rounding, chamfer, continuous curvature rounding, and chamfer.
 //   Also note that when a rounding radius is negative the rounding will flare outwards.  The easiest way to specify
 //   the profile is by using the profile helper functions.  These functions take profile parameters, as well as some
 //   general settings and translate them into a profile specification, with error checking on your input.  The description below
 //   describes the helper functions and the parameters specific to each function.  Below that is a description of the generic
 //   settings that you can optionally use with all of the helper functions.
-//   
+//   .
 //   - profile: os_profile(points)
 //     Define the offset profile with a list of points.  The first point must be [0,0] and the roundover should rise in the positive y direction, with positive x values for inward motion (standard roundover) and negative x values for flaring outward.  If the y value ever decreases then you might create a self-intersecting polyhedron, which is invalid.  Such invalid polyhedra will create cryptic assertion errors when you render your model and it is your responsibility to avoid creating them.  Note that the starting point of the profile is the center of the extrusion.  If you use a profile as the top it will rise upwards.  If you use it as the bottom it will be inverted, and will go downward.
 //   - circle: os_circle(r|cut).  Define circular rounding either by specifying the radius or cut distance.
 //   - smooth: os_smooth(cut|joint).  Define continuous curvature rounding, with `cut` and `joint` as for round_corners.
 //   - teardrop: os_teardrop(r|cut).  Rounding using a 1/8 circle that then changes to a 45 degree chamfer.  The chamfer is at the end, and enables the object to be 3d printed without support.  The radius gives the radius of the circular part.
 //   - chamfer: os_chamfer([height], [width], [cut], [angle]).  Chamfer the edge at desired angle or with desired height and width.  You can specify height and width together and the angle will be ignored, or specify just one of height and width and the angle is used to determine the shape.  Alternatively, specify "cut" along with angle to specify the cut back distance of the chamfer.
-//   
+//   .
 //   The general settings that you can use with all of the helper functions are mostly used to control how offset_sweep() calls the offset() function.
 //   - extra: Add an extra vertical step of the specified height, to be used for intersections or differences.  This extra step will extend the resulting object beyond the height you specify.  Default: 0
 //   - check_valid: passed to offset().  Default: true
@@ -496,10 +496,10 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
 //   - steps: Number of vertical steps to use for the profile.  (Not used by os_profile).  Default: 16
 //   - offset_maxstep: The maxstep distance for offset() calls; controls the horizontal step density.  Set smaller if you don't get the expected rounding.  Default: 1
 //   - offset: Select "round" (r=) or "delta" (delta=) offset types for offset. You can also choose "chamfer" but this leads to exponential growth in the number of vertices with the steps parameter.  Default: "round"
-//   
+//   .
 //   Many of the arguments are described as setting "default" values because they establish settings which may be overridden by
 //   the top and bottom profile specifications.
-//   
+//   .
 //   You will generally want to use the above helper functions to generate the profiles.
 //   The profile specification is a list of pairs of keywords and values, e.g. ["r",12, type, "circle"]. The keywords are
 //   - "type" - type of rounding to apply, one of "circle", "teardrop", "chamfer", "smooth", or "profile" (Default: "circle")
@@ -517,7 +517,7 @@ function smooth_path(path, tangents, size, relsize, splinesteps=10, uniform=fals
 //   - "steps" - number of vertical steps to use for the roundover.  Default: 16.
 //   - "offset_maxstep" - maxstep distance for offset() calls; controls the horizontal step density.  Set smaller if you don't get expected rounding.  Default: 1
 //   - "offset" - select "round" (r=), "delta" (delta=), or "chamfer" offset type for offset.  Default: "round"
-//   
+//   .
 //   Note that if you set the "offset" parameter to "chamfer" then every exterior corner turns from one vertex into two vertices with
 //   each offset operation.  Since the offsets are done one after another, each on the output of the previous one, this leads to
 //   exponential growth in the number of vertices.  This can lead to long run times or yield models that
@@ -865,33 +865,33 @@ function os_profile(points, extra,check_valid, quality, offset_maxstep, offset)
 //   operates on 2d children rather than a point list.  Each offset is computed using
 //   the native `offset()` module from the input geometry.  If your geometry has internal holes or is too small for the specified offset then you may get
 //   unexpected results.
-//   
+//   .
 //   The build-in profiles are: circular rounding, teardrop rounding, chamfer, continuous curvature rounding, and chamfer.
 //   Also note that when a rounding radius is negative the rounding will flare outwards.  The easiest way to specify
 //   the profile is by using the profile helper functions.  These functions take profile parameters, as well as some
 //   general settings and translate them into a profile specification, with error checking on your input.  The description below
 //   describes the helper functions and the parameters specific to each function.  Below that is a description of the generic
 //   settings that you can optionally use with all of the helper functions.
-//
+//   .
 //   The final shape is created by combining convex hulls of small extrusions.  The thickness of these small extrusions may result
 //   your model being slightly too long (if the curvature at the end is flaring outward), so if the exact length is very important
 //   you may need to intersect with a bounding cube.  (Note that extra length can also be intentionally added with the `extra` argument.)
-//   
+//   .
 //   - profile: os_profile(points)
 //     Define the offset profile with a list of points.  The first point must be [0,0] and the roundover should rise in the positive y direction, with positive x values for inward motion (standard roundover) and negative x values for flaring outward.  If the y value ever decreases then you might create a self-intersecting polyhedron, which is invalid.  Such invalid polyhedra will create cryptic assertion errors when you render your model and it is your responsibility to avoid creating them.  Note that the starting point of the profile is the center of the extrusion.  If you use a profile as the top it will rise upwards.  If you use it as the bottom it will be inverted, and will go downward.
 //   - circle: os_circle(r|cut).  Define circular rounding either by specifying the radius or cut distance.
 //   - smooth: os_smooth(cut|joint).  Define continuous curvature rounding, with `cut` and `joint` as for round_corners.
 //   - teardrop: os_teardrop(r|cut).  Rounding using a 1/8 circle that then changes to a 45 degree chamfer.  The chamfer is at the end, and enables the object to be 3d printed without support.  The radius gives the radius of the circular part.
 //   - chamfer: os_chamfer([height], [width], [cut], [angle]).  Chamfer the edge at desired angle or with desired height and width.  You can specify height and width together and the angle will be ignored, or specify just one of height and width and the angle is used to determine the shape.  Alternatively, specify "cut" along with angle to specify the cut back distance of the chamfer.
-//   
+//   .
 //   The general settings that you can use with all of the helper functions are mostly used to control how offset_sweep() calls the offset() function.
 //   - extra: Add an extra vertical step of the specified height, to be used for intersections or differences.  This extra step will extend the resulting object beyond the height you specify.  Default: 0
 //   - steps: Number of vertical steps to use for the profile.  (Not used by os_profile).  Default: 16
 //   - offset: Select "round" (r=), "delta" (delta=), or "chamfer" offset types for offset.  Default: "round"
-//   
+//   .
 //   Many of the arguments are described as setting "default" values because they establish settings which may be overridden by
 //   the top and bottom profile specifications.
-//   
+//   .
 //   You will generally want to use the above helper functions to generate the profiles.
 //   The profile specification is a list of pairs of keywords and values, e.g. ["r",12, type, "circle"]. The keywords are
 //   - "type" - type of rounding to apply, one of "circle", "teardrop", "chamfer", "smooth", or "profile" (Default: "circle")
@@ -906,7 +906,7 @@ function os_profile(points, extra,check_valid, quality, offset_maxstep, offset)
 //   - "extra" - extra height added for unions/differences.  This makes the shape taller than the requested height.  (Default: 0)
 //   - "steps" - number of vertical steps to use for the roundover.  Default: 16.
 //   - "offset" - select "round" (r=) or "delta" (delta=) offset type for offset.  Default: "round"
-//   
+//   .
 //   Note that unlike `offset_sweep`, because the offset operation is always performed from the base shape, using chamfered offsets does not increase the
 //   number of vertices or lead to any special complications.
 //
@@ -1031,22 +1031,22 @@ function _remove_undefined_vals(list) =
 //   Uses `offset()` to compute a stroke for the input path.  Unlike `stroke`, the result does not need to be
 //   centered on the input path.  The corners can be rounded, pointed, or chamfered, and you can make the ends
 //   rounded, flat or pointed with the `start` and `end` parameters.
-//   
+//   .
 //   The `check_valid`, `quality` and `maxstep` parameters are passed through to `offset()`
-//   
+//   .
 //   If `width` is a scalar then the output will be a centered stroke of the specified width.  If width
 //   is a list of two values then those two values will define the stroke side positions relative to the center line, where
 //   as with offset(), the shift is to the left for open paths and outward for closed paths.  For example,
 //   setting `width` to `[0,1]` will create a stroke of width 1 that extends entirely to the left of the input, and and [-4,-6]
 //   will create a stroke of width 2 offset 4 units to the right of the input path.
-//   
+//   .
 //   If closed==false then the function form will return a path.  If closed==true then it will return a region.  The `start` and
 //   `end` parameters are forbidden for closed paths.
-//   
+//   .
 //   Three simple end treatments are supported, "flat" (the default), "round" and "pointed".  The "flat" treatment
 //   cuts off the ends perpendicular to the path and the "round" treatment applies a semicircle to the end.  The
 //   "pointed" end treatment caps the stroke with a centered triangle that has 45 degree angles on each side.
-//   
+//   .
 //   More complex end treatments are available through parameter lists with helper functions to ease parameter passing.  The parameter list
 //   keywords are
 //      - "type": the type of end treatment, one of "shifted_point", "roundover", or "flat"
@@ -1054,15 +1054,15 @@ function _remove_undefined_vals(list) =
 //      - "abs_angle": absolute angle (angle relative to x-axis)
 //      - "cut": cut distance for roundovers, a single value to round both corners identically or a list of two values for the two corners.  Negative values round outward.
 //      - "k": curvature smoothness parameter for roundovers, default 0.75
-//   
+//   .
 //   Function helpers for defining ends, prefixed by "os" for offset_stroke.
-//   
+//   .
 //   os_flat(angle|absangle): specify a flat end either relative to the path or relative to the x-axis
 //   os_pointed(loc,dist): specify a pointed tip where the point is distance `loc` from the centerline (positive is the left direction as for offset), and `dist` is the distance from the path end to the point tip.  The default value for `loc` is zero (the center).  You must specify `dist` when using this option.
 //   os_round(cut,angle|absangle,k).  Rounded ends with the specified cut distance, based on the specified angle or absolute angle.  The `k` parameter is the smoothness parameter for continuous curvature rounding.
-//   
+//   .
 //   Note that `offset_stroke()` will attempt to apply roundovers and angles at the ends even when it means deleting segments of the stroke, unlike round_corners which only works on a segment adjacent to a corner.  If you specify an overly extreme angle it will fail to find an intersection with the stroke and display an error.  When you specify an angle the end segment is rotated around the center of the stroke and the last segment of the stroke one one side is extended to the corner.
-//   
+//   .
 //   The $fn and $fs variables are used to determine the number of segments for rounding, while maxstep is used to determine the segments of `offset`.  If you
 //   get the expected rounding along the path, decrease `maxstep` and if the curves created by `os_round()` are too coarse, adjust $fn or $fs.
 //
@@ -1419,11 +1419,11 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
 //   not be continuous in practice.  A value of 0.92 is a good approximation to a circle.  If you set it very small then the transition
 //   is so gradual that the roundover may be very small.  If you want a very smooth roundover, set the joint parameter as large as possible and
 //   then adjust the k value down as low as gives a sufficiently large roundover.
-//   
+//   .
 //   You can specify the bottom and top polygons by giving two compatible 3d paths.  You can also give 2d paths and a height/length and the
 //   two shapes will be offset in the z direction from each other.  The final option is to specify just the bottom along with a height/length;
 //   in this case the top will be a copy of the bottom, offset in the z direction by the specified height.
-//   
+//   .
 //   You define rounding for all of the top edges, all of the bottom edges, and independently for each of the connecting side edges.
 //   You specify rounding the rounding by giving the joint distance for where the curved section should start.  If the joint distance is 1 then
 //   it means the curved section begins 1 unit away from the edge (in the perpendicular direction).  Typically each joint distance is a scalar
@@ -1433,11 +1433,11 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
 //   outward.  If you give a vector value then a negative value then if joint_top[0] is negative the shape will flare outward, but if
 //   joint_top[1] is negative the shape will flare upward.  At least one value must be non-negative.  The same rules apply for joint_bot.
 //   The joint_sides parameter must be entirely nonnegative.
-//   
+//   .
 //   If you set `debug` to true the module version will display the polyhedron even when it is invalid and it will show the bezier patches at the corners.
 //   This can help troubleshoot problems with your parameters.  With the function form setting debug to true causes it to return [patches,vnf] where
 //   patches is a list of the bezier control points for the corner patches.
-//   
+//   .
 // Arguments:
 //   bottom = 2d or 3d path describing bottom polygon
 //   top = 2d or 3d path describing top polygon (must be the same dimension as bottom)
diff --git a/scripts/docs_gen.py b/scripts/docs_gen.py
index b1131d3..5d38829 100755
--- a/scripts/docs_gen.py
+++ b/scripts/docs_gen.py
@@ -26,68 +26,6 @@ else:
     GIT = "git"
 
 
-def get_header_link(name):
-    refpat = re.compile("[^a-z0-9_ -]")
-    return refpat.sub("", name.lower()).replace(" ", "-")
-
-
-def toc_entry(name, indent, count=None):
-    lname = "{0}{1}".format(
-        ("%d. " % count) if count else "",
-        name
-    )
-    ref = get_header_link(lname)
-    if name.endswith( (")", "}", "]") ):
-        name = "`" + name.replace("\\", "") + "`"
-    return "{0}{1} [{2}](#{3})".format(
-        indent,
-        ("%d." % count) if count else "-",
-        name,
-        ref
-    )
-
-
-def mkdn_esc(txt):
-    out = ""
-    quotpat = re.compile(r'([^`]*)(`[^`]*`)(.*$)');
-    while txt:
-        m = quotpat.match(txt)
-        if m:
-            out += m.group(1).replace(r'_', r'\_')
-            out += m.group(2)
-            txt = m.group(3)
-        else:
-            out += txt.replace(r'_', r'\_')
-            txt = ""
-    return out
-
-
-def get_comment_block(lines, prefix, blanks=1):
-    out = []
-    blankcnt = 0
-    indent = 0
-    while lines:
-        if not lines[0].startswith(prefix+" "):
-            break
-        line = lines.pop(0)[len(prefix):]
-        if not indent:
-            while line.startswith(" "):
-                line = line[1:]
-                indent += 1
-        else:
-            line = line[indent:]
-        if line == "":
-            blankcnt += 1
-            if blankcnt >= blanks:
-                break
-        else:
-            blankcnt = 0
-        if line.rstrip() == '.':
-            line = "\n"
-        out.append(line.rstrip())
-    return (lines, out)
-
-
 def image_compare(file1, file2):
     img1 = Image.open(file1)
     img2 = Image.open(file2)
@@ -329,6 +267,68 @@ class ImageProcessing(object):
 imgprc = ImageProcessing()
 
 
+def get_header_link(name):
+    refpat = re.compile("[^a-z0-9_ -]")
+    return refpat.sub("", name.lower()).replace(" ", "-")
+
+
+def toc_entry(name, indent, count=None):
+    lname = "{0}{1}".format(
+        ("%d. " % count) if count else "",
+        name
+    )
+    ref = get_header_link(lname)
+    if name.endswith( (")", "}", "]") ):
+        name = "`" + name.replace("\\", "") + "`"
+    return "{0}{1} [{2}](#{3})".format(
+        indent,
+        ("%d." % count) if count else "-",
+        name,
+        ref
+    )
+
+
+def mkdn_esc(txt):
+    out = ""
+    quotpat = re.compile(r'([^`]*)(`[^`]*`)(.*$)');
+    while txt:
+        m = quotpat.match(txt)
+        if m:
+            out += m.group(1).replace(r'_', r'\_')
+            out += m.group(2)
+            txt = m.group(3)
+        else:
+            out += txt.replace(r'_', r'\_')
+            txt = ""
+    return out
+
+
+def get_comment_block(lines, prefix, blanks=1):
+    out = []
+    blankcnt = 0
+    indent = 0
+    while lines:
+        if not lines[0].startswith(prefix+" "):
+            break
+        line = lines.pop(0)[len(prefix):]
+        if not indent:
+            while line.startswith(" "):
+                line = line[1:]
+                indent += 1
+        else:
+            line = line[indent:]
+        if line == "":
+            blankcnt += 1
+            if blankcnt >= blanks:
+                break
+        else:
+            blankcnt = 0
+        if line.rstrip() == '.':
+            line = "\n"
+        out.append(line.rstrip())
+    return (lines, out)
+
+
 class LeafNode(object):
     def __init__(self):
         self.name = ""
@@ -374,26 +374,39 @@ class LeafNode(object):
                     break
                 continue
             blankcnt = 0
+
             line = line[len(prefix):]
             if line.startswith("Constant:"):
                 leaftype, title = line.split(":", 1)
                 self.name = title.strip()
                 self.leaftype = leaftype.strip()
+                continue
             if line.startswith("Function&Module:"):
                 leaftype, title = line.split(":", 1)
                 self.name = title.strip()
                 self.leaftype = leaftype.strip()
+                continue
             if line.startswith("Function:"):
                 leaftype, title = line.split(":", 1)
                 self.name = title.strip()
                 self.leaftype = leaftype.strip()
+                continue
             if line.startswith("Module:"):
                 leaftype, title = line.split(":", 1)
                 self.name = title.strip()
                 self.leaftype = leaftype.strip()
+                continue
+
             if line.startswith("Status:"):
                 dummy, status = line.split(":", 1)
                 self.status = status.strip()
+                continue
+            if line.startswith("Usage:"):
+                dummy, title = line.split(":", 1)
+                title = title.strip()
+                lines, block = get_comment_block(lines, prefix)
+                self.usages.append([title, block])
+                continue
             if line.startswith("Description:"):
                 dummy, desc = line.split(":", 1)
                 desc = desc.strip()
@@ -401,50 +414,7 @@ class LeafNode(object):
                     self.description.append(desc)
                 lines, block = get_comment_block(lines, prefix)
                 self.description.extend(block)
-            if line.startswith("Usage:"):
-                dummy, title = line.split(":", 1)
-                title = title.strip()
-                lines, block = get_comment_block(lines, prefix)
-                self.usages.append([title, block])
-            if line.startswith("Arguments:"):
-                lines, block = get_comment_block(lines, prefix)
-                for line in block:
-                    if "=" not in line:
-                        print("Error in {}: Could not parse line in Argument block.  Missing '='.".format(self.name))
-                        print("Line read was:")
-                        print(line)
-                        sys.exit(-2)
-                    argname, argdesc = line.split("=", 1)
-                    argname = argname.strip()
-                    argdesc = argdesc.strip()
-                    self.arguments.append([argname, argdesc])
-            if line.startswith("Extra Anchors:") or line.startswith("Anchors:"):
-                lines, block = get_comment_block(lines, prefix)
-                for line in block:
-                    if "=" not in line:
-                        print("Error: bad anchor line:")
-                        print(line)
-                        sys.exit(-2)
-                    anchorname, anchordesc = line.split("=", 1)
-                    anchorname = anchorname.strip()
-                    anchordesc = anchordesc.strip()
-                    self.anchors.append([anchorname, anchordesc])
-            if line.startswith("Side Effects:"):
-                lines, block = get_comment_block(lines, prefix)
-                self.side_effects.extend(block)
-            m = expat.match(line)
-            if m:  # Example(TYPE):
-                plural = m.group(1) == "Examples"
-                extype = m.group(3)
-                title = m.group(4)
-                lines, block = get_comment_block(lines, prefix)
-                if not extype:
-                    extype = "3D" if self.leaftype in ["Module", "Function&Module"] else "NORENDER"
-                if not plural:
-                    self.add_example(title=title, code=block, extype=extype)
-                else:
-                    for line in block:
-                        self.add_example(title="", code=[line], extype=extype)
+                continue
             m = figpat.match(line)
             if m:  # Figure(TYPE):
                 plural = m.group(1) == "Figures"
@@ -458,6 +428,60 @@ class LeafNode(object):
                 else:
                     for line in block:
                         self.add_figure("", [line], figtype)
+                continue
+            if line.startswith("Arguments:"):
+                lines, block = get_comment_block(lines, prefix)
+                for line in block:
+                    if "=" not in line:
+                        print("Error in {}: Could not parse line in Argument block.  Missing '='.".format(self.name))
+                        print("Line read was:")
+                        print(line)
+                        sys.exit(-2)
+                    argname, argdesc = line.split("=", 1)
+                    argname = argname.strip()
+                    argdesc = argdesc.strip()
+                    self.arguments.append([argname, argdesc])
+                continue
+            if line.startswith("Extra Anchors:") or line.startswith("Anchors:"):
+                lines, block = get_comment_block(lines, prefix)
+                for line in block:
+                    if "=" not in line:
+                        print("Error: bad anchor line:")
+                        print(line)
+                        sys.exit(-2)
+                    anchorname, anchordesc = line.split("=", 1)
+                    anchorname = anchorname.strip()
+                    anchordesc = anchordesc.strip()
+                    self.anchors.append([anchorname, anchordesc])
+                continue
+            if line.startswith("Side Effects:"):
+                lines, block = get_comment_block(lines, prefix)
+                self.side_effects.extend(block)
+                continue
+
+            m = expat.match(line)
+            if m:  # Example(TYPE):
+                plural = m.group(1) == "Examples"
+                extype = m.group(3)
+                title = m.group(4)
+                lines, block = get_comment_block(lines, prefix)
+                if not extype:
+                    extype = "3D" if self.leaftype in ["Module", "Function&Module"] else "NORENDER"
+                if not plural:
+                    self.add_example(title=title, code=block, extype=extype)
+                else:
+                    for line in block:
+                        self.add_example(title="", code=[line], extype=extype)
+                continue
+
+            if ":" not in line:
+                print("Error in {}: Unrecognized block header.  Missing colon?".format(self.name))
+            else:
+                print("Error in {}: Unrecognized block header.".format(self.name))
+            print("Line read was:")
+            print(line)
+            sys.exit(-2)
+
         return lines
 
     def gen_md(self, fileroot, imgroot):
diff --git a/shapes.scad b/shapes.scad
index 16b970d..cb6d4e5 100644
--- a/shapes.scad
+++ b/shapes.scad
@@ -1062,7 +1062,7 @@ module rect_tube(
 
 // Module: torus()
 //
-// Descriptiom:
+// Description:
 //   Creates a torus shape.
 //
 // Usage:
diff --git a/shapes2d.scad b/shapes2d.scad
index 9622ad4..4c13d91 100644
--- a/shapes2d.scad
+++ b/shapes2d.scad
@@ -480,11 +480,11 @@ function _normal_segment(p1,p2) =
 //   origin, pointed along the positive x axis with a movement distance of 1.  By default, `turtle` returns just
 //   the computed turtle path.  If you set `full_state` to true then it instead returns the full turtle state.
 //   You can invoke `turtle` again with this full state to continue the turtle path where you left off.
-//   
+//   .
 //   The turtle state is a list with three entries: the path constructed so far, the current step as a 2-vector, and the current default angle.
-//   
+//   .
 //   For the list below, `dist` is the current movement distance.
-//   
+//   .
 //   Commands     | Arguments          | What it does
 //   ------------ | ------------------ | -------------------------------
 //   "move"       | [dist]             | Move turtle scale*dist units in the turtle direction.  Default dist=1.  
@@ -1754,9 +1754,9 @@ module mask2d_teardrop(r,d,angle=45,excess=0.1,anchor=CENTER,spin=0) {
 //   Since there are a number of shapes that fall under the name ogee, the shape of this mask is given as a pattern.
 //   Patterns are given as TYPE, VALUE pairs.  ie: `["fillet",10, "xstep",2, "step",[5,5], ...]`.  See Patterns below.
 //   If called as a function, this just returns a 2D path of the outline of the mask shape.
-//   
+//   .
 //   ### Patterns
-//   
+//   .
 //   Type     | Argument  | Description
 //   -------- | --------- | ----------------
 //   "step"   | [x,y]     | Makes a line to a point `x` right and `y` down.
diff --git a/skin.scad b/skin.scad
index 0d1c3a0..920f4ed 100644
--- a/skin.scad
+++ b/skin.scad
@@ -26,13 +26,13 @@ include <vnf.scad>
 //   Each profile must rotate in the same clockwise direction.  If called as a function, returns a
 //   [VNF structure](vnf.scad) like `[VERTICES, FACES]`.  If called as a module, creates a polyhedron
 //    of the skinned profiles.
-//   
+//   .
 //   The profiles can be specified either as a list of 3d curves or they can be specified as
 //   2d curves with heights given in the `z` parameter.  It is your responsibility to ensure
 //   that the resulting polyhedron is free from self-intersections, which would make it invalid
 //   and can result in cryptic CGAL errors upon rendering, even though the polyhedron appears
 //   OK during preview.
-//   
+//   .
 //   For this operation to be well-defined, the profiles must all have the same vertex count and
 //   we must assume that profiles are aligned so that vertex `i` links to vertex `i` on all polygons.
 //   Many interesting cases do not comply with this restriction.  Two basic methods can handle
@@ -44,7 +44,7 @@ include <vnf.scad>
 //   a square with 5 points (two of which are identical), so that it can match up to a pentagon.
 //   Such a combination would create a triangular face at the location of the duplicated vertex.
 //   Alternatively, `skin` provides methods (described below) for matching up incompatible paths.
-//   
+//   .
 //   In order for skinned surfaces to look good it is usually necessary to use a fine sampling of
 //   points on all of the profiles, and a large number of extra interpolated slices between the
 //   profiles that you specify.  It is generally best if the triangles forming your polyhedron
@@ -55,7 +55,7 @@ include <vnf.scad>
 //   multiplying the number of points by N, so a profile with 8 points will have 8*N points after
 //   refinement.  Note that when dealing with continuous curves it is always better to adjust the
 //   sampling in your code to generate the desired sampling rather than using the `refine` argument.
-//   
+//   .
 //   Two methods are available for resampling, `"length"` and `"segment"`.  Specify them using
 //   the `sampling` argument.  The length resampling method resamples proportional to length.
 //   The segment method divides each segment of a profile into the same number of points.
@@ -66,7 +66,7 @@ include <vnf.scad>
 //   The available methods are `"distance"`, `"tangent"`, `"direct"` and `"reindex"`.
 //   It is useful to distinguish between continuous curves like a circle and discrete profiles
 //   like a hexagon or star, because the algorithms' suitability depend on this distinction.
-//   
+//   .
 //   The "direct" and "reindex" methods work by resampling the profiles if necessary.  As noted above,
 //   for continuous input curves, it is better to generate your curves directly at the desired sample size,
 //   but for mapping between a discrete profile like a hexagon and a circle, the hexagon must be resampled
@@ -81,7 +81,7 @@ include <vnf.scad>
 //   method which will look for the index choice that will minimize the length of all of the edges
 //   in the polyhedron---in will produce the least twisted possible result.  This algorithm has quadratic
 //   run time so it can be slow with very large profiles.
-//   
+//   .
 //   The "distance" and "tangent" methods are work by duplicating vertices to create
 //   triangular faces.  The "distance" method finds the global minimum distance method for connecting two
 //   profiles.  This algorithm generally produces a good result when both profiles are discrete ones with
@@ -98,7 +98,7 @@ include <vnf.scad>
 //   have no effect.  For best efficiency set `refine=1` and `slices=0`.  When you use refinement with either
 //   of these methods, it is always the "segment" based resampling described above.  This is necessary because
 //   sampling by length will ignore the repeated vertices and break the alignment.
-//   
+//   .
 //   It is possible to specify `method` and `refine` as arrays, but it is important to observe
 //   matching rules when you do this.  If a pair of profiles is connected using "tangent" or "distance"
 //   then the `refine` values for those two profiles must be equal.  If a profile is connected by
@@ -107,7 +107,7 @@ include <vnf.scad>
 //   used for the resampled profiles.  The best way to avoid confusion is to ensure that the
 //   profiles connected by "direct" or "realign" all have the same number of points and at the
 //   transition, the refined number of points matches.
-//   
+//   .
 // Arguments:
 //   profiles = list of 2d or 3d profiles to be skinned.  (If 2d must also give `z`.)
 //   slices = scalar or vector number of slices to insert between each pair of profiles.  Set to zero to use only the profiles you provided.  Recommend starting with a value around 10.
@@ -791,7 +791,7 @@ function associate_vertices(polygons, split, curpoly=0) =
 //   If `closed=true` then the first and last transformation are linked together.
 //   The `caps` parameter controls whether the ends of the shape are closed.
 //   As a function, returns the VNF for the polyhedron.  As a module, computes the polyhedron.
-//   
+//   .
 //   Note that this is a very powerful, general framework for producing polyhedra.  It is important
 //   to ensure that your resulting polyhedron does not include any self-intersections, or it will
 //   be invalid and will generate CGAL errors.  If you get such errors, most likely you have an
@@ -851,15 +851,15 @@ module sweep(shape, transformations, closed=false, caps, convexity=10) {
 //   Takes as input a 2d shape (specified as a point list) and a 2d or 3d path and constructs a polyhedron by sweeping the shape along the path.
 //   When run as a module returns the polyhedron geometry.  When run as a function returns a VNF by default or if you set `transforms=true` then
 //   it returns a list of transformations suitable as input to `sweep`.
-//   
+//   .
 //   The sweep operation has an ambiguity: the shape can rotate around the axis defined by the path.  Several options provide
 //   methods for controlling this rotation.  You can choose from three different methods for selecting the rotation of your shape.
 //   None of these methods will produce good, or even valid, results on all inputs, so it is important to select a suitable method. 
 //   You can also add (or remove) twist to the model.  This twist adjustment is done uniformly in arc length by default, or you
 //   can set `twist_by_length=false` to distribute the twist uniformly over the path point list.
-//   
+//   .
 //   The method is set using the parameter with that name to one of the following:
-//   
+//   .
 //   The "incremental" method (the default) works by adjusting the shape at each step by the minimal rotation that makes the shape normal to the tangent
 //   at the next point.  This method is robust in that it always produces a valid result for well-behaved paths with sufficiently high
 //   sampling.  Unfortunately, it can produce a large amount of undesirable twist.  When constructing a closed shape this algorithm in
@@ -873,7 +873,7 @@ module sweep(shape, transformations, closed=false, caps, convexity=10) {
 //   makes an angle of 45 deg or less with the xy plane and it points BACK if the path makes a higher angle with the XY plane.  You
 //   can also supply `last_normal` which provides an ending orientation constraint.  Be aware that the curve may still exhibit
 //   twisting in the middle.  This method is the default because it is the most robust, not because it generally produces the best result.  
-//   
+//   .
 //   The "natural" method works by computing the Frenet frame at each point on the path.  This is defined by the tangent to the curve and
 //   the normal which lies in the plane defined by the curve at each point.  This normal points in the direction of curvature of the curve.
 //   The result is a very well behaved set of sections without any unexpected twisting---as long as the curvature never falls to zero.  At a
@@ -881,7 +881,7 @@ module sweep(shape, transformations, closed=false, caps, convexity=10) {
 //   you skip over this troublesome point so the normal is defined, it can change direction abruptly when the curvature is zero, leading to
 //   a nasty twist and an invalid model.  A simple example is a circular arc joined to another arc that curves the other direction.  Note
 //   that the X axis of the shape is aligned with the normal from the Frenet frame.
-//   
+//   .
 //   The "manual" method allows you to specify your desired normal either globally with a single vector, or locally with
 //   a list of normal vectors for every path point.  The normal you supply is projected to be orthogonal to the tangent to the
 //   path and the Y direction of your shape will be aligned with the projected normal.  (Note this is different from the "natural" method.)  
@@ -890,7 +890,7 @@ module sweep(shape, transformations, closed=false, caps, convexity=10) {
 //   uses the actual specified normal.  In this case, the tangent is projected to be orthogonal to your supplied normal to define
 //   the cross section orientation.  Specifying a list of normal vectors gives you complete control over the orientation of your
 //   cross sections and can be useful if you want to position your model to be on the surface of some solid.
-//   
+//   .
 //   For any method you can use the `twist` argument to add the specified number of degrees of twist into the model.  
 //   If the model is closed then the twist must be a multiple of 360/symmetry.  The twist is normally spread uniformly along your shape
 //   based on the path length.  If you set `twist_by_length` to false then the twist will be uniform based on the point count of your path.
diff --git a/strings.scad b/strings.scad
index 4786607..cc0713b 100644
--- a/strings.scad
+++ b/strings.scad
@@ -234,13 +234,13 @@ function str_num(str) =
 //   Breaks an input string into substrings using a separator or list of separators.  If keep_nulls is true
 //   then two sequential separator characters produce an empty string in the output list.  If keep_nulls is false
 //   then no empty strings are included in the output list.
-//
+//   .
 //   If sep is a single string then each character in sep is treated as a delimiting character and the input string is
 //   split at every delimiting character.  Empty strings can occur whenever two delimiting characters are sequential.
 //   If sep is a list of strings then the input string is split sequentially using each string from the list in order. 
 //   If keep_nulls is true then the output will have length equal to `len(sep)+1`, possibly with trailing null strings
 //   if the string runs out before the separator list.  
-// Arguments
+// Arguments:
 //   str = String to split.
 //   sep = a string or list of strings to use for the separator
 //   keep_nulls = boolean value indicating whether to keep null strings in the output list.  Default: true
@@ -613,7 +613,7 @@ function is_letter(s) =
 //   - An optional `.` followed by an integer precision length, for specifying how many digits to display in numeric formats.  If not give, 6 digits is assumed.
 //   - An optional letter to indicate the formatting style to use.  If not given, `s` is assumed, which will do it's generic best to format any data type.
 //   - A trailing `}` character to show the end of the placeholder.
-//   
+//   .
 //   Formatting styles, and their effects are as follows:
 //   - `s`: Converts the value to a string with `str()` to display.  This is very generic.
 //   - `i` or `d`: Formats numeric values as integers.
diff --git a/version.scad b/version.scad
index 7efaa86..b6ba49c 100644
--- a/version.scad
+++ b/version.scad
@@ -8,7 +8,7 @@
 //////////////////////////////////////////////////////////////////////
 
 
-BOSL_VERSION = [2,0,398];
+BOSL_VERSION = [2,0,399];
 
 
 // Section: BOSL Library Version Functions
diff --git a/vnf.scad b/vnf.scad
index c9d56c0..b72902e 100644
--- a/vnf.scad
+++ b/vnf.scad
@@ -531,7 +531,7 @@ function vnf_bend(vnf,r,d,axis="Z") =
 //   Each error has the format `[ERR_OR_WARN,CODE,MESG,POINTS,COLOR]`.
 //   When called as a module, echoes the non-manifold errors to the console, and color hilites the
 //   bad edges and vertices, overlaid on a transparent gray polyhedron of the VNF.
-//   
+//   .
 //   Currently checks for these problems:
 //   Type    | Color    | Code         | Message 
 //   ------- | -------- | ------------ | ---------------------------------
@@ -543,7 +543,7 @@ function vnf_bend(vnf,r,d,axis="Z") =
 //   ERROR   | Red      | T_JUNCTION   | Vertex is mid-edge on another Face
 //   ERROR   | Blue     | FACE_ISECT   | Faces intersect
 //   ERROR   | Magenta  | HOLE_EDGE    | Edge bounds Hole
-//   
+//   .
 //   Still to implement:
 //   - Overlapping coplanar faces.
 // Arguments:

From ab57790c272a1e0d75a060ee64d86d55e5bc4e0a Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Tue, 28 Jul 2020 19:02:35 +0100
Subject: [PATCH 09/24] Correction of is_vector and doc text

---
 arrays.scad            | 25 +++++++++++++++++++++++++
 common.scad            |  8 +++++++-
 tests/test_arrays.scad | 16 ++++++++++++++++
 vectors.scad           | 15 +++------------
 4 files changed, 51 insertions(+), 13 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index f19aff6..01c21bb 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -18,6 +18,20 @@
 
 // Section: List Query Operations
 
+// Function: is_simple_list()
+// Description:
+//   Returns true just when all elements of `list` are simple values.
+// Usage:
+//   is_simple_list(list)
+// Arguments:
+//   list = The list to check.
+// Example:
+//   a = is_simple_list([3,4,5,6,7,8,9]);  Returns: true
+//   b = is_simple_list([3,4,5,[6],7,8]);  Returns: false
+function is_simple_list(list) =
+		is_list(list)
+		&& []==[for(e=list) if(is_list(e)) 0];
+
 
 // Function: select()
 // Description:
@@ -1289,6 +1303,17 @@ function array_group(v, cnt=2, dflt=0) = [for (i = [0:cnt:len(v)-1]) [for (j = [
 function flatten(l) = [for (a = l) each a];
 
 
+// Function: full_flatten()
+// Description: 
+//   Collects in a list all elements recursively found in any level of the given list.
+//   The output list is ordered in depth first order.
+// Arguments:
+//   l = List to flatten.
+// Example:
+//   full_flatten([[1,2,3], [4,5,[6,7,8]]]) returns [1,2,3,4,5,6,7,8]
+function full_flatten(l) = [for(a=l) if(is_list(a)) (each full_flatten(a)) else a ];
+
+
 // Internal.  Not exposed.
 function _array_dim_recurse(v) =
     !is_list(v[0])
diff --git a/common.scad b/common.scad
index f3fabd5..387cedd 100644
--- a/common.scad
+++ b/common.scad
@@ -134,9 +134,15 @@ function is_list_of(list,pattern) =
 //   is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]); // Returns true
 //   is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]);   // Returns false
 function is_consistent(list) =
-  is_list(list) && is_list_of(list, list[0]);
+    is_list(list) && is_list_of(list, list[0]);
 
 
+//**
+// is_consistent doesn't ensure the list contains just numbers!
+// for instance, is_consistent([ [1,undef], [2,"a"] ]) is true
+// is_consistent ensures that if we substitute each number in the list by true and any other value by false, 
+// all list items will be equal. The same happens with same_shape().
+
 // Function: same_shape()
 // Usage:
 //   same_shape(a,b)
diff --git a/tests/test_arrays.scad b/tests/test_arrays.scad
index 9af7ebf..f621cd0 100644
--- a/tests/test_arrays.scad
+++ b/tests/test_arrays.scad
@@ -3,6 +3,14 @@ include <../std.scad>
 
 // Section: List Query Operations
 
+module test_is_simple_list() {
+		assert(is_simple_list([1,2,3,4]));
+		assert(is_simple_list([]));
+		assert(!is_simple_list([1,2,[3,4]]));
+} 
+test_is_simple_list();
+
+
 module test_select() {
     l = [3,4,5,6,7,8,9];
     assert(select(l, 5, 6) == [8,9]);
@@ -434,10 +442,18 @@ test_array_group();
 
 module test_flatten() {
     assert(flatten([[1,2,3], [4,5,[6,7,8]]]) == [1,2,3,4,5,[6,7,8]]);
+    assert(flatten([]) == []);
 }
 test_flatten();
 
 
+module test_full_flatten() {
+    assert(full_flatten([[1,2,3], [4,5,[6,[7],8]]]) == [1,2,3,4,5,6,7,8]);
+    assert(full_flatten([]) == []);
+}
+test_full_flatten();
+
+
 module test_array_dim() {
     assert(array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]) == [2,2,3]);
     assert(array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 0) == 2);
diff --git a/vectors.scad b/vectors.scad
index 7b11b91..51f4735 100644
--- a/vectors.scad
+++ b/vectors.scad
@@ -37,8 +37,6 @@ function is_vector(v,length,zero,eps=EPSILON) =
     && (is_undef(length) || len(v)==length)
     && (is_undef(zero) || ((norm(v) >= eps) == !zero));
 
-//***
-// add_scalar() is an array operation: moved to array.scad 
 
 // Function: vang()
 // Usage:
@@ -56,21 +54,19 @@ function vang(v) =
 // Function: vmul()
 // Description:
 //   Element-wise vector multiplication.  Multiplies each element of vector `v1` by
-//   the corresponding element of vector `v2`.  Returns a vector of the products.
+//   the corresponding element of vector `v2`. The vectors should have the same dimension.
+//   Returns a vector of the products.
 // Arguments:
 //   v1 = The first vector.
 //   v2 = The second vector.
 // Example:
 //   vmul([3,4,5], [8,7,6]);  // Returns [24, 28, 30]
 function vmul(v1, v2) = 
+// this thighter check can be done yet because it would break other codes in the library
 //    assert( is_vector(v1) && is_vector(v2,len(v1)), "Incompatible vectors")
     assert( is_vector(v1) && is_vector(v2), "Invalid vector(s)")
     [for (i = [0:1:len(v1)-1]) v1[i]*v2[i]];
     
-//***
-// some other functions seem to rely on the multiplication of vectors with different lengths
-// so, vmul assert cannot check lengths for now
-// when len(v1)>len(v2), undef will be in the output list.
 
 
 // Function: vdiv()
@@ -177,8 +173,6 @@ function vector_angle(v1,v2,v3) =
     // NOTE: constrain() corrects crazy FP rounding errors that exceed acos()'s domain.
     acos(constrain((vecs[0]*vecs[1])/(norm0*norm1), -1, 1));
     
-//***
-// completing input data check
 
 // Function: vector_axis()
 // Usage:
@@ -224,8 +218,5 @@ function vector_axis(v1,v2=undef,v3=undef) =
             ) unit(cross(w1,w3));
 
 
-//***
-// completing input data check and refactoring 
-// Note: vector_angle and vector_axis have the same kind of inputs and two code strategy alternatives
 
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

From 3d226f1ffa5d2c207ab4af6ce35312838e86c232 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Tue, 28 Jul 2020 21:51:45 +0100
Subject: [PATCH 10/24] doc correction

---
 arrays.scad | 101 +++++++++++++---------------------------------------
 common.scad |  11 ++----
 2 files changed, 26 insertions(+), 86 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index 01c21bb..365649a 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -73,9 +73,7 @@ function select(list, start, end=undef) =
             :   concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ;
 
 
-//***
-// 1. avoids undef when the list is void
-// 2. format
+
 
 
 // Function: slice()
@@ -103,13 +101,10 @@ function slice(list,start,end) =
         ) [for (i=[s:1:e-1]) if (e>s) list[i]];
 
 
-//***
-// 1. for sake of consistence, the list argument identifier was changed to list and st to start
-// 2. avoids undef when the list is void
-// 3. checks inputs of start and end
+
 
 // Function: in_list()
-// Description: Returns true if value `val` is in list `list`.
+// Description: Returns true if value `val` is in list `list`. When `val==NAN` the answer will be false for any list.
 // Arguments:
 //   val = The simple value to search for.
 //   list = The list to search.
@@ -118,17 +113,12 @@ function slice(list,start,end) =
 //   in_list("bar", ["foo", "bar", "baz"]);  // Returns true.
 //   in_list("bee", ["foo", "bar", "baz"]);  // Returns false.
 //   in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1);  // Returns true.
-// Note:
-//   When `val==NAN` the answer will be false for any list.
 function in_list(val,list,idx=undef) = 
     let( s = search([val], list, num_returns_per_match=1, index_col_num=idx)[0] )
     s==[] || s[0]==[] ? false
     : is_undef(idx) ? val==list[s] 
     : val==list[s][idx];
     
-//***
-// 1. for sake of consistence, the arguments were changed to val and list
-// 2. in_list(0,[ 1, [0,1],2])) was returning true
 
 
 // Function: min_index()
@@ -147,9 +137,6 @@ function min_index(vals, all=false) =
     assert( is_vector(vals) && len(vals)>0 , "Invalid or empty list of numbers.")
     all ? search(min(vals),vals,0) : search(min(vals), vals)[0];
 
-//***
-// 1. corrected examples
-// 2. input data check
 
 // Function: max_index()
 // Usage:
@@ -167,8 +154,6 @@ function max_index(vals, all=false) =
     assert( is_vector(vals) && len(vals)>0 , "Invalid or empty list of numbers.")
     all ? search(max(vals),vals,0) : search(max(vals), vals)[0];
 
-//***
-// 1. input data check
 
 // Function: list_increasing()
 // Usage:
@@ -223,8 +208,7 @@ function repeat(val, n, i=0) =
     (i>=len(n))? val :
     [for (j=[1:1:n[i]]) repeat(val, n, i+1)];
 
-//***
-// 1. input data check
+
 
 // Function: list_range()
 // Usage:
@@ -263,9 +247,7 @@ function list_range(n=undef, s=0, e=undef, step=undef) =
     :   assert( is_vector([s,step,e]), "Start `s`, step `step` and end `e` must be numbers.")
         [for (v=[s:step:e]) v] ;
     
-//***
-// 1. input data check
-// 2. reworked accordingly
+
 
 
 // Section: List Manipulation
@@ -305,8 +287,6 @@ function list_rotate(list,n=1) =
     assert(is_finite(n), "Invalid number")
     select(list,n,n+len(list)-1);
 
-//***
-// 1. input data check
 
 // Function: deduplicate()
 // Usage:
@@ -334,10 +314,7 @@ function deduplicate(list, closed=false, eps=EPSILON) =
     ? [for (i=[0:1:l-1]) if (i==end || list[i] != list[(i+1)%l]) list[i]]
     : [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]];
 
-//***
-// 1. change Usage, Description, Arguments and add Example of eps=0
-// 2. reworked accordingly
-// 3. when eps==0, doesn't call approx; the list may contain non numerical itens
+
 
 
 // Function: deduplicate_indexed()
@@ -373,10 +350,7 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
         if (i==end || !eq) indices[i]
     ];
 
-//**
-// 1. msg of asserts
-// 2. format
-// 3. eps=0 change
+
 
 
 // Function: repeat_entries()
@@ -417,9 +391,7 @@ function repeat_entries(list, N, exact = true) =
     )
     [for(i=[0:length-1]) each repeat(list[i],reps[i])];
     
-//***
-// 1. Complete asserts
-// 2. Format
+
 
 
 // Function: list_set()
@@ -460,8 +432,6 @@ function list_set(list=[],indices,values,dflt=0,minlen=0) =
           each repeat(dflt, minlen-max(indices))
         ];
       
-//***
-// a full refactoring without sorting; its is quite faster than the original
 
 
 // Function: list_insert()
@@ -495,9 +465,6 @@ function list_insert(list, indices, values, _i=0) =
         ];
 
 
-//***
-// Full refactoring without sorting
-// For sake of consistence, changes `pos` and `elements` to `indices` and `values`
 
 
 // Function: list_remove()
@@ -521,9 +488,7 @@ function list_remove(list, indices) =
         [ for(i=[0:len(list)-1])
             if ( []==search(i,indices,1) ) list[i] ]; 
 
-//***
-// Refactoring without sort
-// For sake of consistence, change `elements` to `indices`
+
 
 
 // Function: list_remove_values()
@@ -594,9 +559,7 @@ function list_bset(indexset, valuelist, dflt=0) =
         repeat(dflt,len(indexset)-max(trueind)-1)  // Add trailing values so length matches indexset
     );
 
-//***
-// search might return false results depending if it identifies `true` with 1.
-// this function failed elsewere when the length of valuelist is different from the length of trueind
+
 
 
 // Section: List Length Manipulation
@@ -610,8 +573,7 @@ function list_shortest(array) =
     assert(is_list(array)||is_string(list), "Invalid input." )
     min([for (v = array) len(v)]);
 
-//***
-// parameter name changed here and in the following for sake of consistence. It was `vecs`
+
 
 // Function: list_longest()
 // Description:
@@ -661,8 +623,7 @@ function list_fit(array, length, fill) =
     l> length ? list_trim(array,length) 
               : list_pad(array,length,fill);
 
-//***
-// format
+
 
 // Section: List Shuffling and Sorting
 
@@ -744,8 +705,7 @@ function _sort_vectors3(arr) =
                     y ]
     ) concat( _sort_vectors3(lesser), equal, _sort_vectors3(greater) );
 
-//***
-// format
+
 
 // Sort a vector of vectors based on the first four entries of each vector
 // Lexicographic order, remaining entries of vector ignored
@@ -778,8 +738,7 @@ function _sort_vectors4(arr) =
                     y ]
     ) concat( _sort_vectors4(lesser), equal, _sort_vectors4(greater) );
     
-//***
-// format
+
 
 function _sort_general(arr, idx=undef) =
     (len(arr)<=1) ? arr :
@@ -815,8 +774,7 @@ function _sort_general(arr, idx=undef) =
     concat(_sort_general(lesser,idx), equal, _sort_general(greater,idx));
 
 
-//***
-// format 
+
 
 
 // Function: sort()
@@ -850,8 +808,7 @@ function sort(list, idx=undef) =
       ) 
     : _sort_general(list);
 
-//***
-// Format and input check
+
 
 // Function: sortidx()
 // Description:
@@ -913,8 +870,7 @@ function sortidx(list, idx=undef) =
     ) :
     // general case
     subindex(_sort_general(aug, idx=list_range(s=1,n=len(aug)-1)), 0);
-//***
-// Format and input check
+
 // sort() does not accept strings but sortidx does; isn't inconsistent ?
 
 
@@ -934,8 +890,7 @@ function unique(arr) =
                 sorted[i]
     ];
 
-//***
-// Format and input check
+
 
 // Function: unique_count()
 // Usage:
@@ -952,8 +907,7 @@ function unique_count(arr) =
       let( ind = [0, for(i=[1:1:len(arr)-1]) if (arr[i]!=arr[i-1]) i] )
       [ select(arr,ind), deltas( concat(ind,[len(arr)]) ) ];
 
-//***
-// format and input check
+
 
 
 // Section: List Iteration Helpers
@@ -1149,8 +1103,7 @@ function set_union(a, b, get_indices=false) =
                 ]
     ) [idxs, nset];
 
-//***
-// format and input check
+
 
 
 // Function: set_difference()
@@ -1171,8 +1124,7 @@ function set_difference(a, b) =
     let( found = search(a, b, num_returns_per_match=1) )
     [ for (i=idx(a)) if(found[i]==[]) a[i] ];
 
-//***
-// format and input check
+
 
 // Function: set_intersection()
 // Usage:
@@ -1192,8 +1144,7 @@ function set_intersection(a, b) =
     let( found = search(a, b, num_returns_per_match=1) )
     [ for (i=idx(a)) if(found[i]!=[]) a[i] ];
 
-//***
-// format and input check
+
 
 
 // Section: Array Manipulation
@@ -1213,8 +1164,7 @@ function set_intersection(a, b) =
 function add_scalar(v,s) = 
     is_finite(s) ? [for (x=v) is_list(x)? add_scalar(x,s) : is_finite(x) ? x+s: x] : v;
 
-//***
-// for sake of consistence, move it to here from vectors.scad
+
 
 // Function: subindex()
 // Description:
@@ -1358,8 +1308,6 @@ function array_dim(v, depth=undef) =
         :  let( dimlist = _array_dim_recurse(v))
            (depth > len(dimlist))? 0 : dimlist[depth-1] ;
 
-//***
-// format
 // This function may return undef!
 
 
@@ -1400,8 +1348,7 @@ function transpose(arr) =
           [ for (j=[0:1:len(arr)-1]) arr[j][i] ] ] 
     :  arr;
 
-//***
-// Input data check and format
+
 
 
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/common.scad b/common.scad
index 387cedd..0d88e52 100644
--- a/common.scad
+++ b/common.scad
@@ -27,8 +27,7 @@ function typeof(x) =
     is_range(x) ? "range" :
     "invalid";
 
-//***
-// included "invalid"
+
 
 // Function: is_type()
 // Usage:
@@ -137,11 +136,7 @@ function is_consistent(list) =
     is_list(list) && is_list_of(list, list[0]);
 
 
-//**
-// is_consistent doesn't ensure the list contains just numbers!
-// for instance, is_consistent([ [1,undef], [2,"a"] ]) is true
-// is_consistent ensures that if we substitute each number in the list by true and any other value by false, 
-// all list items will be equal. The same happens with same_shape().
+
 
 // Function: same_shape()
 // Usage:
@@ -328,8 +323,6 @@ function segs(r) =
     let( r = is_finite(r)? r: 0 ) 
     ceil(max(5, min(360/$fa, abs(r)*2*PI/$fs))) ;
 
-//***
-// avoids undef
 
 
 // Section: Testing Helpers

From 51be74b5ececf27be88b995df5df13a95b7a2f88 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Tue, 28 Jul 2020 22:42:07 +0100
Subject: [PATCH 11/24] input checks, general review, adding new functions

Defines functions:
1. binomial()
2. convolve()
3. all_numeric()
4. is_addable()

Input data check in all functions.

Recoding of:
1. cumsum (a non-recursive version)
2. median()
3. count_true() (more efficient version)
4. polynomial functions so that a zero polynomial is represented by [0] and not by []
---
 math.scad | 662 ++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 449 insertions(+), 213 deletions(-)

diff --git a/math.scad b/math.scad
index 7629cb1..a5b12b6 100644
--- a/math.scad
+++ b/math.scad
@@ -33,7 +33,10 @@ NAN = acos(2);  // The value `nan`, useful for comparisons.
 //   sqr([3,4]); // Returns: [9,16]
 //   sqr([[1,2],[3,4]]);  // Returns [[1,4],[9,16]]
 //   sqr([[1,2],3]);      // Returns [[1,4],9]
-function sqr(x) = is_list(x) ? [for(val=x) sqr(val)] : x*x;
+function sqr(x) = 
+    is_list(x) ? [for(val=x) sqr(val)] : 
+    is_finite(x) ? x*x :
+    assert(is_finite(x) || is_vector(x), "Input is not neither a number nor a list of numbers.");
 
 
 // Function: log2()
@@ -45,8 +48,11 @@ function sqr(x) = is_list(x) ? [for(val=x) sqr(val)] : x*x;
 //   log2(0.125);  // Returns: -3
 //   log2(16);     // Returns: 4
 //   log2(256);    // Returns: 8
-function log2(x) = ln(x)/ln(2);
+function log2(x) = 
+    assert( is_finite(x), "Input is not a number.")
+    ln(x)/ln(2);
 
+// this may return NAN or INF; should it check x>0 ?
 
 // Function: hypot()
 // Usage:
@@ -60,7 +66,9 @@ function log2(x) = ln(x)/ln(2);
 // Example:
 //   l = hypot(3,4);  // Returns: 5
 //   l = hypot(3,4,5);  // Returns: ~7.0710678119
-function hypot(x,y,z=0) = norm([x,y,z]);
+function hypot(x,y,z=0) = 
+    assert( is_vector([x,y,z]), "Improper number(s).")
+    norm([x,y,z]);
 
 
 // Function: factorial()
@@ -76,11 +84,51 @@ function hypot(x,y,z=0) = norm([x,y,z]);
 //   y = factorial(6);  // Returns: 720
 //   z = factorial(9);  // Returns: 362880
 function factorial(n,d=0) =
-    assert(n>=0 && d>=0, "Factorial is not defined for negative numbers")
+    assert(is_int(n) && is_int(d) && n>=0 && d>=0, "Factorial is not defined for negative numbers")
     assert(d<=n, "d cannot be larger than n")
     product([1,for (i=[n:-1:d+1]) i]);
 
 
+// Function: binomial()
+// Usage:
+//   x = binomial(n);
+// Description:
+//   Returns the binomial coefficients of the integer `n`.  
+// Arguments:
+//   n = The integer to get the binomial coefficients of
+// Example:
+//   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]
+function binomial(n) =
+    assert( is_int(n) && n>0, "Input is not an integer greater than 0.")
+    [for( c = 1, i = 0; 
+        i<=n; 
+         c = c*(n-i)/(i+1), i = i+1
+        ) c ] ;
+
+// Function: binomial_coefficient()
+// Usage:
+//   x = binomial_coefficient(n,k);
+// Description:
+//   Returns the k-th binomial coefficient of the integer `n`.  
+// Arguments:
+//   n = The integer to get the binomial coefficient of
+//   k = The binomial coefficient index
+// Example:
+//   x = binomial_coefficient(3,2);  // Returns: 3
+//   y = binomial_coefficient(10,6); // Returns: 210
+function binomial_coefficient(n,k) =
+    assert( is_int(n) && is_int(k), "Some input is not a number.")
+    k < 0 || k > n ? 0 :
+    k ==0 || k ==n ? 1 :
+    let( k = min(k, n-k),
+         b = [for( c = 1, i = 0; 
+                   i<=k; 
+                   c = c*(n-i)/(i+1), i = i+1
+                 ) c] )
+    b[len(b)-1];
+
 // Function: lerp()
 // Usage:
 //   x = lerp(a, b, u);
@@ -91,8 +139,8 @@ function factorial(n,d=0) =
 //   If `u` is 0.0, then the value of `a` is returned.
 //   If `u` is 1.0, then the value of `b` is returned.
 //   If `u` is a range, or list of numbers, returns a list of interpolated values.
-//   It is valid to use a `u` value outside the range 0 to 1.  The result will be a predicted
-//   value along the slope formed by `a` and `b`, but not between those two values.
+//   It is valid to use a `u` value outside the range 0 to 1.  The result will be an extrapolation
+//   along the slope formed by `a` and `b`.
 // Arguments:
 //   a = First value or vector.
 //   b = Second value or vector.
@@ -114,46 +162,86 @@ function factorial(n,d=0) =
 function lerp(a,b,u) =
     assert(same_shape(a,b), "Bad or inconsistent inputs to lerp")
     is_num(u)? (1-u)*a + u*b :
-    assert(!is_undef(u)&&!is_bool(u)&&!is_string(u), "Input u to lerp must be a number, vector, or range.")
+    assert(is_finite(u) || is_vector(u) || is_range(u), "Input u to lerp must be a number, vector, or range.")
     [for (v = u) lerp(a,b,v)];
 
 
 
+// Function: all_numeric()
+// Usage:
+//   x = all_numeric(list);
+// Description:
+//   Returns true if all simple value in `list` is a finite number and the list is not empty. Uses recursion.  
+// Arguments:
+//   list = The list to check
+// Example:
+//   x = all_numeric([1,[2,3,[4]]]);  // Returns: true
+//   y = all_numeric([1,[2,3,[1/0]]]); // Returns: false
+function all_numeric(list) =
+    !is_list(list) ? is_num(0*list)
+    : let(v = list*list[0]) 
+        is_num(0*(v*v))         // true just for vectors and matrices
+        || []==[for(vi=list) if( !all_numeric(vi)) 0] ;
+
+        
+// Function: is_addable()
+// Usage:
+//   x = is_addable(list);
+// Description:
+//   Returns true if `list` is both consistent and numerical. 
+// Arguments:
+//   list = The list to check
+// Example:
+//   x = is_addable([[[1],2],[[0],2]]));    // Returns: true
+//   y = is_addable([[[1],2],[[0],[]]]));   // Returns: false
+function is_addable(list) = // consistent and numerical
+    is_list_of(list,list[0])
+    &&  ( ( let(v = list*list[0]) is_num(0*(v*v)) ) 
+        || []==[for(vi=list) if( !all_numeric(vi)) 0] );
+
+          
+
 // Section: Hyperbolic Trigonometry
 
 // Function: sinh()
 // Description: Takes a value `x`, and returns the hyperbolic sine of it.
 function sinh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     (exp(x)-exp(-x))/2;
 
 
 // Function: cosh()
 // Description: Takes a value `x`, and returns the hyperbolic cosine of it.
 function cosh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     (exp(x)+exp(-x))/2;
 
 
 // Function: tanh()
 // Description: Takes a value `x`, and returns the hyperbolic tangent of it.
 function tanh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     sinh(x)/cosh(x);
 
 
 // Function: asinh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic sine of it.
 function asinh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     ln(x+sqrt(x*x+1));
 
 
 // Function: acosh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic cosine of it.
 function acosh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     ln(x+sqrt(x*x-1));
 
 
 // Function: atanh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic tangent of it.
 function atanh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     ln((1+x)/(1-x))/2;
 
 
@@ -185,8 +273,12 @@ function atanh(x) =
 //   quant([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,12,12,12]
 //   quant([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[12,12,12]]
 function quant(x,y) =
-    is_list(x)? [for (v=x) quant(v,y)] :
-    floor(x/y+0.5)*y;
+    assert(is_int(y), "The multiple must be an integer.")
+    is_list(x)
+    ?   [for (v=x) quant(v,y)]
+    :   assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
+        floor(x/y+0.5)*y;
+
 
 
 // Function: quantdn()
@@ -214,8 +306,11 @@ function quant(x,y) =
 //   quantdn([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,9,9,12]
 //   quantdn([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[9,9,12]]
 function quantdn(x,y) =
-    is_list(x)? [for (v=x) quantdn(v,y)] :
-    floor(x/y)*y;
+    assert(is_int(y), "The multiple must be an integer.")
+    is_list(x)
+    ?    [for (v=x) quantdn(v,y)]
+    :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
+        floor(x/y)*y;
 
 
 // Function: quantup()
@@ -243,8 +338,11 @@ function quantdn(x,y) =
 //   quantup([9,10,10.4,10.5,11,12],3);      // Returns: [9,12,12,12,12,12]
 //   quantup([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,12,12],[12,12,12]]
 function quantup(x,y) =
-    is_list(x)? [for (v=x) quantup(v,y)] :
-    ceil(x/y)*y;
+    assert(is_int(y), "The multiple must be an integer.")
+    is_list(x)
+    ?    [for (v=x) quantup(v,y)]
+    :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
+        ceil(x/y)*y;
 
 
 // Section: Constraints and Modulos
@@ -264,7 +362,9 @@ function quantup(x,y) =
 //   constrain(0.3, -1, 1);  // Returns: 0.3
 //   constrain(9.1, 0, 9);   // Returns: 9
 //   constrain(-0.1, 0, 9);  // Returns: 0
-function constrain(v, minval, maxval) = min(maxval, max(minval, v));
+function constrain(v, minval, maxval) = 
+    assert( is_finite(v+minval+maxval), "Input must be finite number(s).")
+    min(maxval, max(minval, v));
 
 
 // Function: posmod()
@@ -283,7 +383,9 @@ function constrain(v, minval, maxval) = min(maxval, max(minval, v));
 //   posmod(270,360);   // Returns: 270
 //   posmod(700,360);   // Returns: 340
 //   posmod(3,2.5);     // Returns: 0.5
-function posmod(x,m) = (x%m+m)%m;
+function posmod(x,m) = 
+    assert( is_finite(x) && is_int(m), "Input must be finite numbers.")
+    (x%m+m)%m;
 
 
 // Function: modang(x)
@@ -299,6 +401,7 @@ function posmod(x,m) = (x%m+m)%m;
 //   modang(270,360);   // Returns: -90
 //   modang(700,360);   // Returns: -20
 function modang(x) =
+    assert( is_finite(x), "Input must be a finite number.")
     let(xx = posmod(x,360)) xx<180? xx : xx-360;
 
 
@@ -306,7 +409,7 @@ function modang(x) =
 // Usage:
 //   modrange(x, y, m, [step])
 // Description:
-//   Returns a normalized list of values from `x` to `y`, by `step`, modulo `m`.  Wraps if `x` > `y`.
+//   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.
@@ -318,6 +421,7 @@ function modang(x) =
 //   modrange(90,270,360, step=-45);  // Returns: [90,45,0,315,270]
 //   modrange(270,90,360, step=-45);  // Returns: [270,225,180,135,90]
 function modrange(x, y, m, step=1) =
+    assert( is_finite(x+y+step) && is_int(m), "Input must be finite numbers.")
     let(
         a = posmod(x, m),
         b = posmod(y, m),
@@ -330,20 +434,21 @@ function modrange(x, y, m, step=1) =
 
 // Function: rand_int()
 // Usage:
-//   rand_int(min,max,N,[seed]);
+//   rand_int(minval,maxval,N,[seed]);
 // Description:
-//   Return a list of random integers in the range of min to max, inclusive.
+//   Return a list of random integers in the range of minval to maxval, inclusive.
 // Arguments:
-//   min = Minimum integer value to return.
-//   max = Maximum integer value to return.
+//   minval = Minimum integer value to return.
+//   maxval = Maximum integer value to return.
 //   N = Number of random integers to return.
 //   seed = If given, sets the random number seed.
 // Example:
 //   ints = rand_int(0,100,3);
 //   int = rand_int(-10,10,1)[0];
-function rand_int(min, max, N, seed=undef) =
-    assert(max >= min, "Max value cannot be smaller than min")
-    let (rvect = is_def(seed) ? rands(min,max+1,N,seed) : rands(min,max+1,N))
+function rand_int(minval, maxval, N, seed=undef) =
+    assert( is_finite(minval+maxval+N+seed), "Input must be finite numbers.")
+    assert(maxval >= minval, "Max value cannot be smaller than minval")
+    let (rvect = is_def(seed) ? rands(minval,maxval+1,N,seed) : rands(minval,maxval+1,N))
     [for(entry = rvect) floor(entry)];
 
 
@@ -358,6 +463,7 @@ function rand_int(min, max, N, seed=undef) =
 //   N = Number of random numbers to return.  Default: 1
 //   seed = If given, sets the random number seed.
 function gaussian_rands(mean, stddev, N=1, seed=undef) =
+    assert( is_finite(mean+stddev+N+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])];
 
@@ -374,6 +480,7 @@ function gaussian_rands(mean, stddev, N=1, seed=undef) =
 //   N = Number of random numbers to return.  Default: 1
 //   seed = If given, sets the random number seed.
 function log_rands(minval, maxval, factor, N=1, seed=undef) =
+    assert( is_finite(minval+maxval+factor+N+seed), "Input must be finite numbers.")
     assert(maxval >= minval, "maxval cannot be smaller than minval")
     let(
         minv = 1-1/pow(factor,minval),
@@ -395,18 +502,18 @@ function gcd(a,b) =
     b==0 ? abs(a) : gcd(b,a % b);
 
 
-// Computes lcm for two scalars
+// Computes lcm for two integers
 function _lcm(a,b) =
-    assert(is_int(a), "Invalid non-integer parameters to lcm")
-    assert(is_int(b), "Invalid non-integer parameters to lcm")
-    assert(a!=0 && b!=0, "Arguments to lcm must be nonzero")
+    assert(is_int(a) && is_int(b), "Invalid non-integer parameters to lcm")
+    assert(a!=0 && b!=0, "Arguments to lcm must be non zero")
     abs(a*b) / gcd(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(slice(a,0,len(a)-2),[lcm(a[len(a)-2],a[len(a)-1])]));
 
 
 // Function: lcm()
@@ -418,12 +525,11 @@ function _lcmlist(a) =
 //   be non-zero integers.  The output is always a positive integer.  It is an error to pass zero
 //   as an argument.  
 function lcm(a,b=[]) =
-    !is_list(a) && !is_list(b) ? _lcm(a,b) : 
-    let(
-        arglist = concat(force_list(a),force_list(b))
-    )
-    assert(len(arglist)>0,"invalid call to lcm with empty list(s)")
-    _lcmlist(arglist);
+    !is_list(a) && !is_list(b) 
+    ?   _lcm(a,b) 
+    :   let( arglist = concat(force_list(a),force_list(b)) )
+        assert(len(arglist)>0, "Invalid call to lcm with empty list(s)")
+        _lcmlist(arglist);
 
 
 
@@ -441,35 +547,40 @@ function lcm(a,b=[]) =
 //   sum([1,2,3]);  // returns 6.
 //   sum([[1,2,3], [3,4,5], [5,6,7]]);  // returns [9, 12, 15]
 function sum(v, dflt=0) =
-    is_vector(v) ? [for(i=v) 1]*v :
+    is_list(v) && len(v) == 0 ? dflt :
+    is_vector(v) || is_matrix(v)? [for(i=v) 1]*v :
     assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
-    is_vector(v[0]) ? [for(i=v) 1]*v :
-    len(v) == 0 ? dflt :
-                  _sum(v,v[0]*0);
+    _sum(v,v[0]*0);
 
 function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
 
 
 // Function: cumsum()
 // Description:
-//   Returns a list where each item is the cumulative sum of all items up to and including the corresponding entry in the input list.
-//   If passed an array of vectors, returns a list of cumulative vectors sums.
+//   Returns a list where each item is the cumulative sum of all items up to and including the corresponding entry 
+//   in the input list `v`. If passed an array of vectors, returns a list of cumulative vectors sums.
+//   if a compatible value `off` is given, it is added to each item in the output list.
 // Arguments:
-//   v = The list to get the sum of.
+//   v   = The list to get the sum of.
+//   off = An offset to add to each output sum. 
 // Example:
 //   cumsum([1,1,1]);  // returns [1,2,3]
 //   cumsum([2,2,2]);  // returns [2,4,6]
 //   cumsum([1,2,3]);  // returns [1,3,6]
 //   cumsum([[1,2,3], [3,4,5], [5,6,7]]);  // returns [[1,2,3], [4,6,8], [9,12,15]]
-function cumsum(v,_i=0,_acc=[]) =
-    _i==len(v) ? _acc :
-    cumsum(
-        v, _i+1,
-        concat(
-            _acc,
-            [_i==0 ? v[_i] : select(_acc,-1)+v[_i]]
-        )
-    );
+//   cumsum([-2,-1,0,1,2],3);   // returns [1,0,0,1,3] 
+function cumsum(v, off) =
+  assert( is_list(v), "The input is not a list.")
+  v==[] ? [] :
+  let( off = is_undef(off)? 0*v[0]: off )
+  assert( is_consistent(v) , "Improper inputs.")
+  [for( i = 0, 
+        S = v[0]+off; 
+      i<=len(v)-1; 
+        i = i+1, 
+        S = S + v[i] 
+      ) S ];
+
 
 
 // Function: sum_of_squares()
@@ -495,24 +606,29 @@ function sum_of_squares(v) = sum(vmul(v,v));
 // Examples:
 //   v = sum_of_sines(30, [[10,3,0], [5,5.5,60]]);
 function sum_of_sines(a, sines) =
-    sum([
-        for (s = sines) let(
-            ss=point3d(s),
-            v=ss.x*sin(a*ss.y+ss.z)
-        ) v
-    ]);
+    assert( is_finite(a) && is_matrix(sines), "Invalid inputs.")
+    sum([ for (s = sines) 
+            let(
+              ss=point3d(s),
+              v=ss[0]*sin(a*ss[1]+ss[2])
+            ) v
+        ]);
 
 
 // Function: deltas()
 // Description:
 //   Returns a list with the deltas of adjacent entries in the given list.
+//   The list should be a consistent list of numeric components (numbers, vectors, matrix, etc).
 //   Given [a,b,c,d], returns [b-a,c-b,d-c].
 // Arguments:
 //   v = The list to get the deltas of.
 // Example:
 //   deltas([2,5,9,17]);  // returns [3,4,8].
 //   deltas([[1,2,3], [3,6,8], [4,8,11]]);  // returns [[2,4,5], [1,2,3]]
-function deltas(v) = [for (p=pair(v)) p.y-p.x];
+function deltas(v) = 
+    (is_vector(v) || is_matrix(v)) && len(v)>1 ? [for (p=pair(v)) p[1]-p[0]] :
+    all_numeric(v) ? [for (p=pair(v)) p[1]-p[0]] :
+    assert( false, "Inconsistent or non numeric input list.");
 
 
 // Function: product()
@@ -525,7 +641,16 @@ function deltas(v) = [for (p=pair(v)) p.y-p.x];
 // Example:
 //   product([2,3,4]);  // returns 24.
 //   product([[1,2,3], [3,4,5], [5,6,7]]);  // returns [15, 48, 105]
-function product(v, i=0, tot=undef) = i>=len(v)? tot : product(v, i+1, ((tot==undef)? v[i] : is_vector(v[i])? vmul(tot,v[i]) : tot*v[i]));
+function product(v) = 
+    assert( is_consistent(v), "Inconsistent input.")
+    _product(v, 1, v[0]);
+
+function _product(v, i=0, _tot) = 
+    i>=len(v) ? _tot :
+    _product( v, 
+              i+1, 
+              ( is_vector(v[i])? vmul(_tot,v[i]) : _tot*v[i] ) );
+               
 
 
 // Function: outer_product()
@@ -534,21 +659,22 @@ function product(v, i=0, tot=undef) = i>=len(v)? tot : product(v, i+1, ((tot==un
 // Usage:
 //   M = outer_product(u,v);
 function outer_product(u,v) =
-  assert(is_vector(u) && is_vector(v))
-  assert(len(u)==len(v))
-  [for(i=[0:len(u)-1]) [for(j=[0:len(u)-1]) u[i]*v[j]]];
+  assert(is_vector(u) && is_vector(v), "The inputs must be vectors.")
+  [for(ui=u) ui*v];
 
 
 // Function: mean()
 // Description:
-//   Returns the arithmatic mean/average of all entries in the given array.
+//   Returns the arithmetic mean/average of all entries in the given array.
 //   If passed a list of vectors, returns a vector of the mean of each part.
 // Arguments:
 //   v = The list of values to get the mean of.
 // Example:
 //   mean([2,3,4]);  // returns 3.
 //   mean([[1,2,3], [3,4,5], [5,6,7]]);  // returns [3, 4, 5]
-function mean(v) = sum(v)/len(v);
+function mean(v) = 
+    assert(is_list(v) && len(v)>0, "Invalid list.")
+    sum(v)/len(v);
 
 
 // Function: median()
@@ -556,18 +682,35 @@ function mean(v) = sum(v)/len(v);
 //   x = median(v);
 // Description:
 //   Given a list of numbers or vectors, finds the median value or midpoint.
-//   If passed a list of vectors, returns the vector of the median of each part.
+//   If passed a list of vectors, returns the vector of the median of each component.
 function median(v) =
-    assert(is_list(v))
-    assert(len(v)>0)
-    is_vector(v[0])? (
-        assert(is_consistent(v))
-        [
-            for (i=idx(v[0]))
-            let(vals = subindex(v,i))
-            (min(vals)+max(vals))/2
-        ]
-    ) : (min(v)+max(v))/2;
+    is_vector(v) && len(v)>0 ? (min(v)+max(v))/2 :
+    is_matrix(v) && len(v)>0
+    ?    [for(ti=transpose(v))  (min(ti)+max(ti))/2 ]
+    :   assert(false , "Invalid input or empty list.");
+
+// Function: convolve()
+// Usage:
+//   x = convolve(p,q);
+// Description:
+//   Given two vectors, finds the convolution of them.
+//   The length of the returned vector is len(p)+len(q)-1 .
+// Arguments:
+//   p = The first vector.
+//   q = The second vector.
+// Example:
+//   a = convolve([1,1],[1,2,1]); // Returns: [1,3,3,1]
+//   b = convolve([1,2,3],[1,2,1])); // Returns: [1,4,8,8,3]
+function convolve(p,q) =
+    p==[] || q==[] ? [] :
+    assert( is_vector(p) && is_vector(q), "The inputs should be vectors.")
+    let( n = len(p),
+         m = len(q))
+    [for(i=[0:n+m-2], k1 = max(0,i-n+1), k2 = min(i,m-1) )
+       [for(j=[k1:k2]) p[i-j] ] * [for(j=[k1:k2]) q[j] ] 
+    ];
+     
+
 
 
 // Section: Matrix math
@@ -582,7 +725,7 @@ function median(v) =
 //   want to solve Ax=b1 and Ax=b2 that you need to form the matrix transpose([b1,b2]) for the right hand side and then
 //   transpose the returned value.  
 function linear_solve(A,b) =
-    assert(is_matrix(A))
+    assert(is_matrix(A), "Input should be a matrix.")
     let(
         m = len(A),
         n = len(A[0])
@@ -619,6 +762,7 @@ function matrix_inverse(A) =
 // Description:
 //   Returns a submatrix with the specified index ranges or index sets.  
 function submatrix(M,ind1,ind2) =
+    assert( is_matrix(M), "Input must be a matrix." )
     [for(i=ind1) [for(j=ind2) M[i][j] ] ];
 
 
@@ -628,7 +772,7 @@ function submatrix(M,ind1,ind2) =
 //   Calculates the QR factorization of the input matrix A and returns it as the list [Q,R].  This factorization can be
 //   used to solve linear systems of equations.  
 function qr_factor(A) =
-    assert(is_matrix(A))
+    assert(is_matrix(A), "Input must be a matrix." )
     let(
       m = len(A),
       n = len(A[0])
@@ -659,8 +803,8 @@ function _qr_factor(A,Q, column, m, n) =
 // Function: back_substitute()
 // Usage: back_substitute(R, b, [transpose])
 // Description:
-//   Solves the problem Rx=b where R is an upper triangular square matrix.  No check is made that the lower triangular entries
-//   are actually zero.  If transpose==true then instead solve transpose(R)*x=b.
+//   Solves the problem Rx=b where R is an upper triangular square matrix.  The lower triangular entries of R are
+//   ignored.  If transpose==true then instead solve transpose(R)*x=b.
 //   You can supply a compatible matrix b and it will produce the solution for every column of b.  Note that if you want to
 //   solve Rx=b1 and Rx=b2 you must set b to transpose([b1,b2]) and then take the transpose of the result.  If the matrix
 //   is singular (e.g. has a zero on the diagonal) then it returns [].  
@@ -694,7 +838,9 @@ function back_substitute(R, b, x=[],transpose = false) =
 // Example:
 //   M = [ [6,-2], [1,8] ];
 //   det = det2(M);  // Returns: 50
-function det2(M) = M[0][0] * M[1][1] - M[0][1]*M[1][0];
+function det2(M) = 
+    assert( is_matrix(M,2,2), "Matrix should be 2x2." )
+    M[0][0] * M[1][1] - M[0][1]*M[1][0];
 
 
 // Function: det3()
@@ -706,6 +852,7 @@ function det2(M) = M[0][0] * M[1][1] - M[0][1]*M[1][0];
 //   M = [ [6,4,-2], [1,-2,8], [1,5,7] ];
 //   det = det3(M);  // Returns: -334
 function det3(M) =
+    assert( is_matrix(M,3,3), "Matrix should be 3x3." )
     M[0][0] * (M[1][1]*M[2][2]-M[2][1]*M[1][2]) -
     M[1][0] * (M[0][1]*M[2][2]-M[2][1]*M[0][2]) +
     M[2][0] * (M[0][1]*M[1][2]-M[1][1]*M[0][2]);
@@ -720,21 +867,21 @@ function det3(M) =
 //   M = [ [6,4,-2,9], [1,-2,8,3], [1,5,7,6], [4,2,5,1] ];
 //   det = determinant(M);  // Returns: 2267
 function determinant(M) =
-    assert(len(M)==len(M[0]))
+    assert(is_matrix(M,square=true), "Input should be a square matrix." )
     len(M)==1? M[0][0] :
     len(M)==2? det2(M) :
     len(M)==3? det3(M) :
     sum(
         [for (col=[0:1:len(M)-1])
             ((col%2==0)? 1 : -1) *
-            M[col][0] *
-            determinant(
-                [for (r=[1:1:len(M)-1])
-                    [for (c=[0:1:len(M)-1])
-                        if (c!=col) M[c][r]
+                M[col][0] *
+                determinant(
+                    [for (r=[1:1:len(M)-1])
+                        [for (c=[0:1:len(M)-1])
+                            if (c!=col) M[c][r]
+                        ]
                     ]
-                ]
-            )
+                )
         ]
     );
 
@@ -753,8 +900,16 @@ function determinant(M) =
 //   n = optional width of matrix
 //   square = set to true to require a square matrix.  Default: false        
 function is_matrix(A,m,n,square=false) =
-    is_vector(A[0],n) && is_vector(A*(0*A[0]),m) &&
-    (!square || len(A)==len(A[0]));
+    is_vector(A[0],n) 
+    && is_vector(A*(0*A[0]),m)
+    && ( !square || len(A)==len(A[0]));
+    
+function is_matrix(A,m,n,square=false) =
+    is_list(A[0]) 
+    && ( let(v = A*A[0]) is_num(0*(v*v)) ) // a matrix of finite numbers 
+    && (is_undef(n) || len(A[0])==n )
+    && (is_undef(m) || len(A)==m )
+    && ( !square || len(A)==len(A[0]));
 
 
 // Section: Comparisons and Logic
@@ -774,11 +929,13 @@ function is_matrix(A,m,n,square=false) =
 //   approx(0.3333,1/3);          // Returns: false
 //   approx(0.3333,1/3,eps=1e-3);  // Returns: true
 //   approx(PI,3.1415926536);     // Returns: true
-function approx(a,b,eps=EPSILON) =
+function approx(a,b,eps=EPSILON) = 
     a==b? true :
     a*0!=b*0? false :
-    is_list(a)? ([for (i=idx(a)) if(!approx(a[i],b[i],eps=eps)) 1] == []) :
-    (abs(a-b) <= eps);
+    is_list(a)
+    ? ([for (i=idx(a)) if( !approx(a[i],b[i],eps=eps)) 1] == [])
+    : is_num(a) && is_num(b) && (abs(a-b) <= eps);
+    
 
 
 function _type_num(x) =
@@ -796,7 +953,7 @@ function _type_num(x) =
 // Description:
 //   Compares two values.  Lists are compared recursively.
 //   Returns <0 if a<b.  Returns >0 if a>b.  Returns 0 if a==b.
-//   If types are not the same, then undef < bool < num < str < list < range.
+//   If types are not the same, then undef < bool < nan < num < str < list < range.
 // Arguments:
 //   a = First value to compare.
 //   b = Second value to compare.
@@ -820,13 +977,14 @@ function compare_vals(a, b) =
 //   a = First list to compare.
 //   b = Second list to compare.
 function compare_lists(a, b) =
-    a==b? 0 : let(
-        cmps = [
-            for(i=[0:1:min(len(a),len(b))-1]) let(
-                cmp = compare_vals(a[i],b[i])
-            ) if(cmp!=0) cmp
-        ]
-    ) cmps==[]? (len(a)-len(b)) : cmps[0];
+    a==b? 0 
+    :   let(
+          cmps = [ for(i=[0:1:min(len(a),len(b))-1]) 
+                      let( cmp = compare_vals(a[i],b[i]) )
+                      if(cmp!=0) cmp 
+                 ]
+           ) 
+        cmps==[]? (len(a)-len(b)) : cmps[0];
 
 
 // Function: any()
@@ -843,12 +1001,11 @@ function compare_lists(a, b) =
 //   any([[0,0], [1,0]]);   // Returns true.
 function any(l, i=0, succ=false) =
     (i>=len(l) || succ)? succ :
-    any(
-        l, i=i+1, succ=(
-            is_list(l[i])? any(l[i]) :
-            !(!l[i])
-        )
-    );
+    any( l, 
+         i+1, 
+         succ = is_list(l[i]) ? any(l[i]) : !(!l[i])
+        );
+
 
 
 // Function: all()
@@ -865,13 +1022,12 @@ function any(l, i=0, succ=false) =
 //   all([[0,0], [1,0]]);   // Returns false.
 //   all([[1,1], [1,1]]);   // Returns true.
 function all(l, i=0, fail=false) =
-    (i>=len(l) || fail)? (!fail) :
-    all(
-        l, i=i+1, fail=(
-            is_list(l[i])? !all(l[i]) :
-            !l[i]
-        )
-    );
+    (i>=len(l) || fail)? !fail :
+    all( l, 
+         i+1,
+         fail = is_list(l[i]) ? !all(l[i]) : !l[i]
+        ) ;
+
 
 
 // Function: count_true()
@@ -904,6 +1060,21 @@ function count_true(l, nmax=undef, i=0, cnt=0) =
     );
 
 
+function count_true(l, nmax) = 
+    !is_list(l) ? !(!l) ? 1: 0 :
+    let( c = [for( i = 0,
+                   n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
+                   c = !is_undef(n)? n : count_true(l[i], nmax),
+                   s = c;
+                 i<len(l) && (is_undef(nmax) || s<nmax);
+                   i = i+1,
+                   n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
+                   c = !is_undef(n) || (i==len(l))? n : count_true(l[i], nmax-s),
+                   s = s+c
+                 )  s ] )
+    len(c)<len(l)? nmax: c[len(c)-1];
+
+
 
 // Section: Calculus
 
@@ -921,42 +1092,49 @@ function count_true(l, nmax=undef, i=0, cnt=0) =
 //   between data[i+1] and data[i], and the data values will be linearly resampled at each corner
 //   to produce a uniform spacing for the derivative estimate.  At the endpoints a single point method
 //   is used: f'(t) = (f(t+h)-f(t))/h.  
+// Arguments:
+//   data = the list of the elements to compute the derivative of.
+//   h = the parametric sampling of the data.
+//   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv(data, h=1, closed=false) =
+    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
+    assert( len(data)>=2, "Input `data` should have at least 2 elements.") 
+    assert( is_finite(h) || is_vector(h), "The sampling `h` must be a number or a list of numbers." )
+    assert( is_num(h) || len(h) == len(data)-(closed?0:1),
+            str("Vector valued `h` must have length ",len(data)-(closed?0:1)))
     is_vector(h) ? _deriv_nonuniform(data, h, closed=closed) :
     let( L = len(data) )
-    closed? [
+    closed
+    ? [
         for(i=[0:1:L-1])
         (data[(i+1)%L]-data[(L+i-1)%L])/2/h
-    ] :
-    let(
-        first =
-            L<3? data[1]-data[0] : 
-            3*(data[1]-data[0]) - (data[2]-data[1]),
-        last =
-            L<3? data[L-1]-data[L-2]:
-            (data[L-3]-data[L-2])-3*(data[L-2]-data[L-1])
-    ) [
+      ]
+    : let(
+        first = L<3 ? data[1]-data[0] : 
+                3*(data[1]-data[0]) - (data[2]-data[1]),
+        last = L<3 ? data[L-1]-data[L-2]:
+               (data[L-3]-data[L-2])-3*(data[L-2]-data[L-1])
+         ) 
+      [
         first/2/h,
         for(i=[1:1:L-2]) (data[i+1]-data[i-1])/2/h,
         last/2/h
-    ];
+      ];
 
 
 function _dnu_calc(f1,fc,f2,h1,h2) =
     let(
         f1 = h2<h1 ? lerp(fc,f1,h2/h1) : f1 , 
         f2 = h1<h2 ? lerp(fc,f2,h1/h2) : f2
-    )
-    (f2-f1) / 2 / min([h1,h2]);
+       )
+    (f2-f1) / 2 / min(h1,h2);
 
 
 function _deriv_nonuniform(data, h, closed) =
-    assert(len(h) == len(data)-(closed?0:1),str("Vector valued h must be length ",len(data)-(closed?0:1)))
-    let(
-      L = len(data)
-    )
-    closed? [for(i=[0:1:L-1])
-    	        _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
+    let( L = len(data) )
+    closed
+    ? [for(i=[0:1:L-1])
+          _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
     : [
         (data[1]-data[0])/h[0],
         for(i=[1:1:L-2]) _dnu_calc(data[i-1],data[i],data[i+1], h[i-1],h[i]),
@@ -967,15 +1145,23 @@ function _deriv_nonuniform(data, h, closed) =
 // Function: deriv2()
 // Usage: deriv2(data, [h], [closed])
 // Description:
-//   Computes a numerical esimate of the second derivative of the data, which may be scalar or vector valued.
+//   Computes a numerical estimate of the second derivative of the data, which may be scalar or vector valued.
 //   The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. 
 //   If the `closed` parameter is true the data is assumed to be defined on a loop with data[0] adjacent to
 //   data[len(data)-1].  For internal points this function uses the approximation 
-//   f''(t) = (f(t-h)-2*f(t)+f(t+h))/h^2.  For the endpoints (when closed=false) the algorithm
-//   when sufficient points are available the method is either the four point expression
-//   f''(t) = (2*f(t) - 5*f(t+h) + 4*f(t+2*h) - f(t+3*h))/h^2 or if five points are available
+//   f''(t) = (f(t-h)-2*f(t)+f(t+h))/h^2.  For the endpoints (when closed=false),
+//   when sufficient points are available, the method is either the four point expression
+//   f''(t) = (2*f(t) - 5*f(t+h) + 4*f(t+2*h) - f(t+3*h))/h^2 or 
 //   f''(t) = (35*f(t) - 104*f(t+h) + 114*f(t+2*h) - 56*f(t+3*h) + 11*f(t+4*h)) / 12h^2
+//   if five points are available.
+// Arguments:
+//   data = the list of the elements to compute the derivative of.
+//   h = the constant parametric sampling of the data.
+//   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv2(data, h=1, closed=false) =
+    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
+    assert( len(data)>=3, "Input list has less than 3 elements.") 
+    assert( is_finite(h), "The sampling `h` must be a number." )
     let( L = len(data) )
     closed? [
         for(i=[0:1:L-1])
@@ -1003,16 +1189,19 @@ function deriv2(data, h=1, closed=false) =
 //   Computes a numerical third derivative estimate of the data, which may be scalar or vector valued.
 //   The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. 
 //   If the `closed` parameter is true the data is assumed to be defined on a loop with data[0] adjacent to
-//   data[len(data)-1].  This function uses a five point derivative estimate, so the input must include five points:
+//   data[len(data)-1].  This function uses a five point derivative estimate, so the input data must include 
+//   at least five points:
 //   f'''(t) = (-f(t-2*h)+2*f(t-h)-2*f(t+h)+f(t+2*h)) / 2h^3.  At the first and second points from the end
 //   the estimates are f'''(t) = (-5*f(t)+18*f(t+h)-24*f(t+2*h)+14*f(t+3*h)-3*f(t+4*h)) / 2h^3 and
 //   f'''(t) = (-3*f(t-h)+10*f(t)-12*f(t+h)+6*f(t+2*h)-f(t+3*h)) / 2h^3.
 function deriv3(data, h=1, closed=false) =
+    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
+    assert( len(data)>=5, "Input list has less than 5 elements.") 
+    assert( is_finite(h), "The sampling `h` must be a number." )
     let(
         L = len(data),
         h3 = h*h*h
     )
-    assert(L>=5, "Need five points for 3rd derivative estimate")
     closed? [
         for(i=[0:1:L-1])
         (-data[(L+i-2)%L]+2*data[(L+i-1)%L]-2*data[(i+1)%L]+data[(i+2)%L])/2/h3
@@ -1036,16 +1225,22 @@ function deriv3(data, h=1, closed=false) =
 // Function: C_times()
 // Usage: C_times(z1,z2)
 // Description:
-//   Multiplies two complex numbers.  
-function C_times(z1,z2) = [z1.x*z2.x-z1.y*z2.y,z1.x*z2.y+z1.y*z2.x];
+//   Multiplies two complex numbers represented by 2D vectors.  
+function C_times(z1,z2) = 
+    assert( is_vector(z1+z2,2), "Complex numbers should be represented by 2D vectors." )
+    [ z1.x*z2.x - z1.y*z2.y, z1.x*z2.y + z1.y*z2.x ];
 
 // Function: C_div()
 // Usage: C_div(z1,z2)
 // Description:
-//   Divides z1 by z2.  
-function C_div(z1,z2) = let(den = z2.x*z2.x + z2.y*z2.y)
-   [(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x-z1.x*z2.y)/den];
+//   Divides two complex numbers represented by 2D vectors.  
+function C_div(z1,z2) = 
+    assert( is_vector(z1,2) && is_vector(z2), "Complex numbers should be represented by 2D vectors." )
+    assert( !approx(z2,0), "The divisor `z2` cannot be zero." ) 
+    let(den = z2.x*z2.x + z2.y*z2.y)
+    [(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x - z1.x*z2.y)/den];
 
+// For the sake of consistence with Q_mul and vmul, C_times should be called C_mul
 
 // Section: Polynomials
 
@@ -1056,38 +1251,70 @@ function C_div(z1,z2) = let(den = z2.x*z2.x + z2.y*z2.y)
 //   Evaluates specified real polynomial, p, at the complex or real input value, z.
 //   The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]
 //   where a_n is the z^n coefficient.  Polynomial coefficients are real.
+//   The result is a number if `z` is a number and a complex number otherwise.
 
 // Note: this should probably be recoded to use division by [1,-z], which is more accurate
 // and avoids overflow with large coefficients, but requires poly_div to support complex coefficients.  
-function polynomial(p, z, k, zk, total) =
-   is_undef(k) ? polynomial(p, z, len(p)-1, is_num(z)? 1 : [1,0], is_num(z) ? 0 : [0,0]) :
-   k==-1 ? total :
-   polynomial(p, z, k-1, is_num(z) ? zk*z : C_times(zk,z), total+zk*p[k]);
+function polynomial(p, z, _k, _zk, _total) =
+    is_undef(_k)  
+    ?   assert( is_vector(p), "Input polynomial coefficients must be a vector." )
+        let(p = _poly_trim(p))
+        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
+        polynomial( p, 
+                    z, 
+                    len(p)-1, 
+                    is_num(z)? 1 : [1,0], 
+                    is_num(z) ? 0 : [0,0]) 
+    :   _k==0 
+        ? _total + +_zk*p[0]
+        : polynomial( p, 
+                      z, 
+                      _k-1, 
+                      is_num(z) ? _zk*z : C_times(_zk,z), 
+                      _total+_zk*p[_k]);
+
 
 
 // Function: poly_mult()
 // Usage
 //   polymult(p,q)
 //   polymult([p1,p2,p3,...])
-// Descriptoin:
+// Description:
 //   Given a list of polynomials represented as real coefficient lists, with the highest degree coefficient first, 
 //   computes the coefficient list of the product polynomial.  
 function poly_mult(p,q) = 
-  is_undef(q) ?
-     assert(is_list(p) && (is_vector(p[0]) || p[0]==[]), "Invalid arguments to poly_mult")
-     len(p)==2 ? poly_mult(p[0],p[1]) 
-               : poly_mult(p[0], poly_mult(select(p,1,-1)))
-  :
-  _poly_trim(
-  [
-  for(n = [len(p)+len(q)-2:-1:0])
-      sum( [for(i=[0:1:len(p)-1])
-           let(j = len(p)+len(q)- 2 - n - i)
-           if (j>=0 && j<len(q)) p[i]*q[j]
-               ])
-   ]);        
-
+    is_undef(q) ?
+       assert( is_list(p) 
+               && []==[for(pi=p) if( !is_vector(pi) && pi!=[]) 0], 
+               "Invalid arguments to poly_mult")
+       len(p)==2 ? poly_mult(p[0],p[1]) 
+                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
+    :
+    _poly_trim(
+    [
+    for(n = [len(p)+len(q)-2:-1:0])
+        sum( [for(i=[0:1:len(p)-1])
+             let(j = len(p)+len(q)- 2 - n - i)
+             if (j>=0 && j<len(q)) p[i]*q[j]
+                 ])
+     ]);        
+     
+function poly_mult(p,q) = 
+    is_undef(q) ?
+       len(p)==2 ? poly_mult(p[0],p[1]) 
+                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
+    :
+    assert( is_vector(p) && is_vector(q),"Invalid arguments to poly_mult")
+    _poly_trim(
+               [
+                  for(n = [len(p)+len(q)-2:-1:0])
+                      sum( [for(i=[0:1:len(p)-1])
+                           let(j = len(p)+len(q)- 2 - n - i)
+                           if (j>=0 && j<len(q)) p[i]*q[j]
+                               ])
+                   ]);
 
+    
 // Function: poly_div()
 // Usage:
 //    [quotient,remainder] = poly_div(n,d)
@@ -1096,15 +1323,19 @@ function poly_mult(p,q) =
 //    a list of two polynomials, [quotient, remainder].  If the division has no remainder then
 //    the zero polynomial [] is returned for the remainder.  Similarly if the quotient is zero
 //    the returned quotient will be [].  
-function poly_div(n,d,q=[]) =
-    assert(len(d)>0 && d[0]!=0 , "Denominator is zero or has leading zero coefficient")
-    len(n)<len(d) ? [q,_poly_trim(n)] : 
-    let(
-      t = n[0] / d[0],
-      newq = concat(q,[t]),
-      newn =  [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
-    )  
-    poly_div(newn,d,newq);
+function poly_div(n,d,q) =
+    is_undef(q) 
+    ?   assert( is_vector(n) && is_vector(d) , "Invalid polynomials." )
+        let( d = _poly_trim(d) )
+        assert( d!=[0] , "Denominator cannot be a zero polynomial." )
+        poly_div(n,d,q=[])
+    :   len(n)<len(d) ? [q,_poly_trim(n)] : 
+        let(
+          t = n[0] / d[0], 
+          newq = concat(q,[t]),
+          newn = [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
+        )  
+        poly_div(newn,d,newq);
 
 
 // Internal Function: _poly_trim()
@@ -1114,8 +1345,8 @@ function poly_div(n,d,q=[]) =
 //    Removes leading zero terms of a polynomial.  By default zeros must be exact,
 //    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 ? [] : select(p,nz[0],-1);
+    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);
 
 
 // Function: poly_add()
@@ -1124,12 +1355,13 @@ function _poly_trim(p,eps=0) =
 // Description:
 //    Computes the sum of two polynomials.  
 function poly_add(p,q) = 
-  let(  plen = len(p),
-        qlen = len(q),
-        long = plen>qlen ? p : q,
-        short = plen>qlen ? q : p
-     )
-   _poly_trim(long + concat(repeat(0,len(long)-len(short)),short));
+    assert( is_vector(p) && is_vector(q), "Invalid input polynomial(s)." )
+    let(  plen = len(p),
+          qlen = len(q),
+          long = plen>qlen ? p : q,
+          short = plen>qlen ? q : p
+       )
+     _poly_trim(long + concat(repeat(0,len(long)-len(short)),short));
 
 
 // Function: poly_roots()
@@ -1150,38 +1382,38 @@ function poly_add(p,q) =
 //
 // Dario Bini. "Numerical computation of polynomial zeros by means of Aberth's Method", Numerical Algorithms, Feb 1996.
 // https://www.researchgate.net/publication/225654837_Numerical_computation_of_polynomial_zeros_by_means_of_Aberth's_method
-
 function poly_roots(p,tol=1e-14,error_bound=false) =
-  assert(p!=[], "Input polynomial must have a nonzero coefficient")
-  assert(is_vector(p), "Input must be a vector")
-  p[0] == 0 ? poly_roots(slice(p,1,-1),tol=tol,error_bound=error_bound) :    // Strip leading zero coefficients
-  p[len(p)-1] == 0 ?                                       // Strip trailing zero coefficients
-      let( solutions = poly_roots(select(p,0,-2),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
-  len(p)==2 ? let( solution = [[-p[1]/p[0],0]])            // Linear case needs special handling
-              (error_bound ? [solution,[0]] : solution)
-  : 
-  let(
-      n = len(p)-1,   // polynomial degree
-      pderiv = [for(i=[0:n-1]) p[i]*(n-i)],
-         
-      s = [for(i=[0:1:n]) abs(p[i])*(4*(n-i)+1)],  // Error bound polynomial from Bini
+    assert( is_vector(p), "Invalid polynomial." )
+    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))
+        (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
+    len(p)==2 ? let( solution = [[-p[1]/p[0],0]])            // Linear case needs special handling
+                (error_bound ? [solution,[0]] : solution)
+    : 
+    let(
+        n = len(p)-1,   // polynomial degree
+        pderiv = [for(i=[0:n-1]) p[i]*(n-i)],
+           
+        s = [for(i=[0:1:n]) abs(p[i])*(4*(n-i)+1)],  // Error bound polynomial from Bini
 
-      // Using method from: http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/0915-24.pdf
-      beta = -p[1]/p[0]/n,
-      r = 1+pow(abs(polynomial(p,beta)/p[0]),1/n),
-      init = [for(i=[0:1:n-1])                // Initial guess for roots       
-               let(angle = 360*i/n+270/n/PI)
-               [beta,0]+r*[cos(angle),sin(angle)]
-             ],
-      roots = _poly_roots(p,pderiv,s,init,tol=tol),
-      error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) /
-                                abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0
-    )
-    error_bound ? [roots, error] : roots;
+        // Using method from: http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/0915-24.pdf
+        beta = -p[1]/p[0]/n,
+        r = 1+pow(abs(polynomial(p,beta)/p[0]),1/n),
+        init = [for(i=[0:1:n-1])                // Initial guess for roots       
+                 let(angle = 360*i/n+270/n/PI)
+                 [beta,0]+r*[cos(angle),sin(angle)]
+               ],
+        roots = _poly_roots(p,pderiv,s,init,tol=tol),
+        error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) /
+                                  abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0
+      )
+      error_bound ? [roots, error] : roots;
 
+// Internal function
 // p = polynomial
 // pderiv = derivative polynomial of p
 // z = current guess for the roots
@@ -1222,12 +1454,16 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) =
 //   tol = tolerance for the complex polynomial root finder
 
 function real_roots(p,eps=undef,tol=1e-14) =
-   let( 
+    assert( is_vector(p), "Invalid polynomial." )
+    let( p = _poly_trim(p,eps=0) )
+    assert( p!=[0], "Input polynomial cannot be zero." )
+    let( 
        roots_err = poly_roots(p,error_bound=true),
        roots = roots_err[0],
        err = roots_err[1]
-   )
-   is_def(eps) ? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
-               : [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
+    )
+    is_def(eps) 
+    ? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
+    : [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
 
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

From d45f289a9526de09db18ba248dab9b9e9f45b4d0 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Tue, 28 Jul 2020 22:55:21 +0100
Subject: [PATCH 12/24] update

---
 math.scad            |  2 +-
 shapes2d.scad        |  1 +
 tests/test_all.scad  | 29 +++++++++++++++++++
 tests/test_math.scad | 69 ++++++++++++++++++++++++++++++++++++++------
 4 files changed, 91 insertions(+), 10 deletions(-)
 create mode 100644 tests/test_all.scad

diff --git a/math.scad b/math.scad
index a5b12b6..23a8174 100644
--- a/math.scad
+++ b/math.scad
@@ -1244,7 +1244,7 @@ function C_div(z1,z2) =
 
 // Section: Polynomials
 
-// Function: polynomial()
+// Function: polynomial() 
 // Usage:
 //   polynomial(p, z)
 // Description:
diff --git a/shapes2d.scad b/shapes2d.scad
index 9622ad4..f328952 100644
--- a/shapes2d.scad
+++ b/shapes2d.scad
@@ -787,6 +787,7 @@ function rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) =
         size = is_num(size)? [size,size] : point2d(size),
         anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT),
         complex = rounding!=0 || chamfer!=0
+				, xxx = echo(anchor=anchor)echo(size=size)
     )
     (rounding==0 && chamfer==0)? let(
         path = [
diff --git a/tests/test_all.scad b/tests/test_all.scad
new file mode 100644
index 0000000..c9c89ac
--- /dev/null
+++ b/tests/test_all.scad
@@ -0,0 +1,29 @@
+//include<hull.scad>
+include<polyhedra.scad>
+include<test_affine.scad>
+include<test_arrays.scad>
+include<test_common.scad>
+include<test_coords.scad>
+include<test_cubetruss.scad>
+include<test_debug.scad>
+include<test_edges.scad>
+include<test_errors.scad>
+include<test_geometry.scad>
+include<test_linear_bearings.scad>
+include<test_math.scad>
+include<test_mutators.scad>
+include<test_primitives.scad>
+include<test_quaternions.scad>
+include<test_queues.scad>
+include<test_shapes.scad>
+include<test_shapes2d.scad>
+include<test_skin.scad>
+include<test_stacks.scad>
+include<test_strings.scad>
+include<test_structs.scad>
+include<test_transforms.scad>
+include<test_vectors.scad>
+include<test_version.scad>
+include<test_vnf.scad>
+
+
diff --git a/tests/test_math.scad b/tests/test_math.scad
index 3233e6f..eefb18d 100644
--- a/tests/test_math.scad
+++ b/tests/test_math.scad
@@ -1,5 +1,17 @@
 include <../std.scad>
 
+module test_all_numeric() {
+    assert( all_numeric(1));
+    assert( all_numeric([1,2,[3,4]]));
+    assert( all_numeric([]));
+    assert(!all_numeric([1,2,[3,"a"]]));
+    assert(!all_numeric([1,2,[3,true]]));
+    assert(!all_numeric([1,2,[3,NAN]]));
+    assert(!all_numeric([1,2,[3,INF]]));
+    assert( all_numeric([1,2,[3,[]]]));
+}
+test_all_numeric();
+
 // Simple Calculations
 
 module test_quant() {
@@ -110,6 +122,8 @@ module test_approx() {
     assert_equal(approx(1/3, 0.3333333333), true);
     assert_equal(approx(-1/3, -0.3333333333), true);
     assert_equal(approx(10*[cos(30),sin(30)], 10*[sqrt(3)/2, 1/2]), true);
+    assert_equal(approx([1,[1,undef]], [1+1e-12,[1,true]]), false);
+    assert_equal(approx([1,[1,undef]], [1+1e-12,[1,undef]]), true);
 }
 test_approx();
 
@@ -342,7 +356,9 @@ module test_cumsum() {
     assert_equal(cumsum([2,2,2]), [2,4,6]);
     assert_equal(cumsum([1,2,3]), [1,3,6]);
     assert_equal(cumsum([-2,-1,0,1,2]), [-2,-3,-3,-2,0]);
+    assert_equal(cumsum([-2,-1,0,1,2],3), [1,0,0,1,3]);
     assert_equal(cumsum([[1,2,3], [3,4,5], [5,6,7]]), [[1,2,3],[4,6,8],[9,12,15]]);
+    assert_equal(cumsum([[1,2,3], [3,4,5], [5,6,7]], [1,0,0]), [[2,2,3],[5,6,8],[10,12,15]]);
 }
 test_cumsum();
 
@@ -389,7 +405,6 @@ module test_mean() {
 }
 test_mean();
 
-
 module test_median() {
     assert_equal(median([2,3,7]), 4.5);
     assert_equal(median([[1,2,3], [3,4,5], [8,9,10]]), [4.5,5.5,6.5]);
@@ -397,6 +412,16 @@ module test_median() {
 test_median();
 
 
+module test_convolve() {
+    assert_equal(convolve([],[1,2,1]), []);
+    assert_equal(convolve([1,1],[]), []);
+    assert_equal(convolve([1,1],[1,2,1]), [1,3,3,1]);
+    assert_equal(convolve([1,2,3],[1,2,1]), [1,4,8,8,3]);
+}
+test_convolve();
+
+
+
 module test_matrix_inverse() {
     assert_approx(matrix_inverse(rot([20,30,40])), [[0.663413948169,0.556670399226,-0.5,0],[-0.47302145844,0.829769465589,0.296198132726,0],[0.579769465589,0.0400087565481,0.813797681349,0],[0,0,0,1]]);
 }
@@ -583,6 +608,24 @@ module test_factorial() {
 }
 test_factorial();
 
+module test_binomial() {
+    assert_equal(binomial(1), [1,1]);
+    assert_equal(binomial(2), [1,2,1]);
+    assert_equal(binomial(3), [1,3,3,1]);
+    assert_equal(binomial(5), [1,5,10,10,5,1]);
+}
+test_binomial();
+
+module test_binomial_coefficient() {
+    assert_equal(binomial_coefficient(2,1), 2);
+    assert_equal(binomial_coefficient(3,2), 3);
+    assert_equal(binomial_coefficient(4,2), 6);
+    assert_equal(binomial_coefficient(10,7), 120);
+    assert_equal(binomial_coefficient(10,7), binomial(10)[7]);
+    assert_equal(binomial_coefficient(15,4), binomial(15)[4]);
+}
+test_binomial_coefficient();
+
 
 module test_gcd() {
     assert_equal(gcd(15,25), 5);
@@ -682,6 +725,7 @@ test_linear_solve();
 
 module test_outer_product(){
   assert_equal(outer_product([1,2,3],[4,5,6]), [[4,5,6],[8,10,12],[12,15,18]]);
+  assert_equal(outer_product([1,2],[4,5,6]), [[4,5,6],[8,10,12]]);
   assert_equal(outer_product([9],[7]), [[63]]);
 }
 test_outer_product();
@@ -782,8 +826,10 @@ test_deriv3();
 
 
 module test_polynomial(){
-  assert_equal(polynomial([],12),0);
-  assert_equal(polynomial([],[12,4]),[0,0]);
+  assert_equal(polynomial([0],12),0);
+  assert_equal(polynomial([0],[12,4]),[0,0]);
+//  assert_equal(polynomial([],12),0);
+//  assert_equal(polynomial([],[12,4]),[0,0]);
   assert_equal(polynomial([1,2,3,4],3),58);
   assert_equal(polynomial([1,2,3,4],[3,-1]),[47,-41]);
   assert_equal(polynomial([0,0,2],4),2);
@@ -879,16 +925,20 @@ test_qr_factor();
 
 module test_poly_mult(){
   assert_equal(poly_mult([3,2,1],[4,5,6,7]),[12,23,32,38,20,7]);
-  assert_equal(poly_mult([3,2,1],[]),[]);
+  assert_equal(poly_mult([3,2,1],[0]),[0]);
+//  assert_equal(poly_mult([3,2,1],[]),[]);
   assert_equal(poly_mult([[1,2],[3,4],[5,6]]), [15,68,100,48]);
-  assert_equal(poly_mult([[1,2],[],[5,6]]), []);
-  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[]);
+  assert_equal(poly_mult([[1,2],[0],[5,6]]), [0]);
+//  assert_equal(poly_mult([[1,2],[],[5,6]]), []);
+  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[0]);
+//  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[]);
 }
 test_poly_mult();
 
- 
+
 module test_poly_div(){
-  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[]]);
+  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[0]]);
+//  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[]]);
   assert_equal(poly_div([1,2,3,4],[1,2,3,4,5]), [[], [1,2,3,4]]);
   assert_equal(poly_div(poly_add(poly_mult([1,2,3,4],[2,0,2]), [1,1,2]), [1,2,3,4]), [[2,0,2],[1,1,2]]);
   assert_equal(poly_div([1,2,3,4], [1,-3]), [[1,5,18],[58]]);
@@ -899,7 +949,8 @@ test_poly_div();
 module test_poly_add(){
   assert_equal(poly_add([2,3,4],[3,4,5,6]),[3,6,8,10]);
   assert_equal(poly_add([1,2,3,4],[-1,-2,3,4]), [6,8]);
-  assert_equal(poly_add([1,2,3],-[1,2,3]),[]);
+  assert_equal(poly_add([1,2,3],-[1,2,3]),[0]);
+//  assert_equal(poly_add([1,2,3],-[1,2,3]),[]);
 }
 test_poly_add();
 

From ea604f967215a0de46881c90706cc65fc356338e Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Tue, 28 Jul 2020 23:10:42 +0100
Subject: [PATCH 13/24] restore polyhedra, shapes and shapes3d

---
 polyhedra.scad |  785 --------------------
 shapes.scad    | 1755 ---------------------------------------------
 shapes2d.scad  | 1866 ------------------------------------------------
 3 files changed, 4406 deletions(-)
 delete mode 100644 polyhedra.scad
 delete mode 100644 shapes.scad
 delete mode 100644 shapes2d.scad

diff --git a/polyhedra.scad b/polyhedra.scad
deleted file mode 100644
index 3902ecf..0000000
--- a/polyhedra.scad
+++ /dev/null
@@ -1,785 +0,0 @@
-//////////////////////////////////////////////////////////////////////
-// LibFile: polyhedra.scad
-//   Useful platonic, archimedian, and catalan polyhedra.
-//   To use, add the following lines to the beginning of your file:
-//   ```
-//   include <BOSL2/std.scad>
-//   include <BOSL2/polyhedra.scad>
-//   ```
-//////////////////////////////////////////////////////////////////////
-
-
-include <hull.scad>
-
-
-// CommonCode:
-//   $fn=96;
-
-
-// Section: Polyhedra
-
-
-// Groups entries in "arr" into groups of equal values and returns index lists of those groups
-
-function _unique_groups(m) = [
-    for (i=[0:1:len(m)-1]) let(
-        s = search([m[i]], m, 0)[0]
-    ) if (s[0]==i) s
-];
-
-
-// TODO
-//
-// Use volume info?
-// Support choosing a face number down
-// Support multiple inspheres/outspheres when appropriate?
-// face order for children?
-// orient faces so an edge is parallel to the x-axis
-//
-
-
-// Module: regular_polyhedron()
-//
-// Description:
-//   Creates a regular polyhedron with optional rounding.  Children are placed on the polyhedron's faces.
-//   
-//   **Selecting the polyhedron:**
-//   You constrain the polyhedra list by specifying different characteristics, that must all be met
-//   * `name`: e.g. `"dodecahedron"` or `"pentagonal icositetrahedron"`
-//   * `type`: Options are `"platonic"`, `"archimedean"` and `"catalan"`
-//   * `faces`: The required number of faces
-//   * `facetype`: The required face type(s).  List of vertex counts for the faces.  Exactly the listed types of faces must appear:
-//     * `facetype = 3`: polyhedron with all triangular faces.
-//     * `facetype = [5,6]`: polyhedron with only pentagons and hexagons. (Must have both!)
-//   * hasfaces: The list of vertex counts for faces; at least one listed type must appear:
-//     * `hasfaces = 3`: polygon has at least one triangular face
-//     * `hasfaces = [5,6]`: polygon has a hexagonal or a pentagonal face
-//   
-//   The result is a list of selected polyhedra.  You then specify `index` to choose which one of the
-//   remaining polyhedra you want.  If you don't give `index` the first one on the list is created.
-//   Two examples:
-//   * `faces=12, index=2`:  Creates the 3rd solid with 12 faces
-//   * `type="archimedean", faces=14`: Creates the first archimedean solid with 14 faces (there are 3)
-//   
-//   **Choosing the size of your polyhedron:**
-//   The default is to create a polyhedron whose smallest edge has length 1.  You can specify the
-//   smallest edge length with the size option.  Alternatively you can specify the size of the
-//   inscribed sphere, midscribed sphere, or circumscribed sphere using `ir`, `mr` and `cr` respectively.
-//   If you specify `cr=3` then the outermost points of the polyhedron will be 3 units from the center.
-//   If you specify `ir=3` then the innermost faces of the polyhedron will be 3 units from the center.
-//   For the platonic solids every face meets the inscribed sphere and every corner touches the
-//   circumscribed sphere.  For the Archimedean solids the inscribed sphere will touch only some of
-//   the faces and for the Catalan solids the circumscribed sphere meets only some of the corners.
-//   
-//   **Orientation:**
-//   Orientation is controled by the facedown parameter.  Set this to false to get the canonical orientation.
-//   Set it to true to get the largest face oriented down.  If you set it to a number the module searches for
-//   a face with the specified number of vertices and orients that face down.
-//   
-//   **Rounding:**
-//   If you specify the rounding parameter the module makes a rounded polyhedron by first creating an
-//   undersized model and then expanding it with `minkowski()`.  This only produces the correct result
-//   if the in-sphere contacts all of the faces of the polyhedron, which is true for the platonic, the
-//   catalan solids and the trapezohedra but false for the archimedean solids.
-//   
-//   **Children:**
-//   The module places children on the faces of the polyhedron.  The child coordinate system is
-//   positioned so that the origin is the center of the face.  If `rotate_children` is true (default)
-//   then the coordinate system is oriented so the z axis is normal to the face, which lies in the xy
-//   plane.  If you give `repeat=true` (default) the children are cycled through to cover all faces.
-//   With `repeat=false` each child is used once.  You can specify `draw=false` to suppress drawing of
-//   the polyhedron, e.g. to use for `difference()` operations.  The module sets various parameters
-//   you can use in your children (see the side effects list below).
-//   
-//   **Stellation:**
-//   Technically stellation is an operation of shifting the polyhedron's faces to produce a new shape
-//   that may have self-intersecting faces.  OpenSCAD cannot handle self-intersecting faces, so we
-//   instead erect a pyramid on each face, a process technically referred to as augmentation.  The
-//   height of the pyramid is given by the `stellate` argument.  If `stellate` is `false` or `0` then
-//   no stellation is performed.  Otherwise stellate gives the pyramid height as a multiple of the
-//   edge length.  A negative pyramid height can be used to perform excavation, where a pyramid is
-//   removed from each face.
-//   
-//   **Special Polyhedra:**
-//   These can be selected only by name and may require different parameters, or ignore some standard
-//   parameters.
-//   * Trapezohedron: a family of solids with an even number of kite shaped sides.
-//     One example of a trapezohedron is the d10 die, which is a 10 face trapezohedron.
-//     You must specify exactly two of `side`, `longside`, `h`, and `r` (or `d`).
-//     You cannot create trapezohedron shapes using `mr`, `ir`, or `or`.
-//     * `side`: Length of the short side.
-//     * `longside`: Length of the long side that extends to the apex.
-//     * `h`: Distance from the center to the apex.
-//     * `r`: Radius of the polygon that defines the equatorial vertices.
-//     * `d`: Diameter of the polygon that defines the equatorial vertices.
-//   
-//   * Named stellations: various polyhedra such as the Kepler-Poinsot solids are stellations with
-//    specific pyramid heights.  To make them easier to generate you can specify them by name.
-//    This is equivalent to giving the name of the appropriate base solid and the magic stellate
-//    parameter needed to produce that shape.  The supported solids are:
-//     * `"great dodecahedron"`
-//     * `"small stellated dodecahedron"`
-//     * `"great stellated dodecahedron"`
-//
-// Arguments:
-//   name = Name of polyhedron to create.
-//   index = Index to select from polyhedron list.  Default: 0.
-//   type = Type of polyhedron: "platonic", "archimedean", "catalan".
-//   faces = Number of faces.
-//   facetype = Scalar or vector listing required type of faces as vertex count.  Polyhedron must have faces of every type listed and no other types.
-//   hasfaces = Scalar of vector list face vertex counts.  Polyhedron must have at least one of the listed types of face.
-//   side = Length of the smallest edge of the polyhedron.  Default: 1.
-//   ir = inner radius.  Polyhedron is scaled so it has the specified inner radius. Overrides side.
-//   mr = middle radius.  Polyhedron is scaled so it has the specified middle radius.  Overrides side.
-//   or = outer radius.   Polyhedron is scaled so it has the specified outer radius.  Overrides side.
-//   r = outer radius.  Overrides or.
-//   d = outer diameter.  Overrides or.
-//   anchor = Side of the origin to anchor to.  The bounding box of the polyhedron is aligned as specified.  Use directional constants from `constants.scad`.  Default: `CENTER`
-//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP+BACK+RIGHT`.
-//   facedown = If false display the solid in native orientation.  If true orient it with a largest face down.  If set to a vertex count, orient it so a face with the specified number of vertices is down.  Default: true.
-//   rounding = Specify a rounding radius for the shape.  Note that depending on $fn the dimensions of the shape may have small dimensional errors.
-//   repeat = If true then repeat the children to fill all the faces.  If false use only the available children and stop.  Default: true.
-//   draw = If true then draw the polyhedron.  If false, draw the children but not the polyhedron.  Default: true.
-//   rotate_children = If true then orient children normal to their associated face.  If false orient children to the parent coordinate system.  Default: true.
-//   stellate = Set to a number to erect a pyramid on every face of your polyhedron with the specified height.  The height is a multiple of the side length.  Default: false.
-//   longside = Specify the long side length for a trapezohedron.  Ignored for other shapes.
-//   h = Specify the height of the apex for a trapezohedron.  Ignored for other shapes.
-//
-// Side Effects:
-//   `$faceindex` - Index number of the face
-//   `$face` - Coordinates of the face (2d if rotate_children==true, 3d if not)
-//   `$center` - Polyhedron center in the child coordinate system
-//
-// Examples: All of the available polyhedra by name in their native orientation
-//   regular_polyhedron("tetrahedron", facedown=false);
-//   regular_polyhedron("cube", facedown=false);
-//   regular_polyhedron("octahedron", facedown=false);
-//   regular_polyhedron("dodecahedron", facedown=false);
-//   regular_polyhedron("icosahedron", facedown=false);
-//   regular_polyhedron("truncated tetrahedron", facedown=false);
-//   regular_polyhedron("truncated octahedron", facedown=false);
-//   regular_polyhedron("truncated cube", facedown=false);
-//   regular_polyhedron("truncated icosahedron", facedown=false);
-//   regular_polyhedron("truncated dodecahedron", facedown=false);
-//   regular_polyhedron("cuboctahedron", facedown=false);
-//   regular_polyhedron("icosidodecahedron", facedown=false);
-//   regular_polyhedron("rhombicuboctahedron", facedown=false);
-//   regular_polyhedron("rhombicosidodecahedron", facedown=false);
-//   regular_polyhedron("truncated cuboctahedron", facedown=false);
-//   regular_polyhedron("truncated icosidodecahedron", facedown=false);
-//   regular_polyhedron("snub cube", facedown=false);
-//   regular_polyhedron("snub dodecahedron", facedown=false);
-//   regular_polyhedron("triakis tetrahedron", facedown=false);
-//   regular_polyhedron("tetrakis hexahedron", facedown=false);
-//   regular_polyhedron("triakis octahedron", facedown=false);
-//   regular_polyhedron("pentakis dodecahedron", facedown=false);
-//   regular_polyhedron("triakis icosahedron", facedown=false);
-//   regular_polyhedron("rhombic dodecahedron", facedown=false);
-//   regular_polyhedron("rhombic triacontahedron", facedown=false);
-//   regular_polyhedron("deltoidal icositetrahedron", facedown=false);
-//   regular_polyhedron("deltoidal hexecontahedron", facedown=false);
-//   regular_polyhedron("disdyakis dodecahedron", facedown=false);
-//   regular_polyhedron("disdyakis triacontahedron", facedown=false);
-//   regular_polyhedron("pentagonal icositetrahedron", facedown=false);
-//   regular_polyhedron("pentagonal hexecontahedron", facedown=false);
-//   regular_polyhedron("trapezohedron",faces=10, side=1, longside=2.25, facedown=false);
-//   regular_polyhedron("great dodecahedron");
-//   regular_polyhedron("small stellated dodecahedron");
-//   regular_polyhedron("great stellated dodecahedron");
-// Example: Third Archimedean solid
-//   regular_polyhedron(type="archimedean", index=2);
-// Example(Med): Solids that have 8 or 10 vertex faces
-//   N = len(regular_polyhedron_info("index set", hasfaces=[8,10]));
-//   for(i=[0:N-1]) right(3*i)
-//     regular_polyhedron(hasfaces=[8,10], index=i, mr=1);
-// Example(Big): Solids that include a quadrilateral face
-//   N = len(regular_polyhedron_info("index set", hasfaces=4));
-//   for(i=[0:N-1]) right(3*i)
-//     regular_polyhedron(hasfaces=4, index=i, mr=1);
-// Example(Med): Solids with only quadrilateral faces
-//   N = len(regular_polyhedron_info("index set", facetype=4));
-//   for(i=[0:N-1]) right(3*i)
-//     regular_polyhedron(facetype=4, index=i, mr=1);
-// Example: Solids that have both pentagons and hexagons and no other face types
-//   N = len(regular_polyhedron_info("index set", facetype=[5,6]));
-//   for(i=[0:N-1]) right(3*i)
-//     regular_polyhedron(facetype=[5,6], index=i, mr=1);
-// Example: Rounded octahedron
-//   regular_polyhedron("octahedron", side=1, rounding=.2);
-// Example: Rounded catalon solid
-//   regular_polyhedron("rhombic dodecahedron", side=1, rounding=0.2);
-// Example(Med): Rounded Archimedean solid compared to unrounded version.  The small faces are shifted back from their correct position.
-//   %regular_polyhedron(type="archimedean", mr=1, rounding=0);
-//   regular_polyhedron(type="archimedean", mr=1, rounding=0.3);
-// Example: Two children are distributed arbitrarily over the faces
-//   regular_polyhedron(faces=12,index=2,repeat=true) {
-//     color("red") sphere(r=.1);
-//     color("green") sphere(r=.1);
-//   }
-// Example(FlatSpin): Difference the children from the polyhedron; children depend on $faceindex
-//   difference(){
-//     regular_polyhedron("tetrahedron", side=25);
-//     regular_polyhedron("tetrahedron", side=25,draw=false)
-//       down(.3) linear_extrude(height=1)
-//         text(str($faceindex),halign="center",valign="center");
-//   }
-// Example(Big): With `rotate_children` you can control direction of the children.
-//   regular_polyhedron(name="tetrahedron", anchor=UP, rotate_children=true)
-//     cylinder(r=.1, h=.5);
-//   right(2) regular_polyhedron(name="tetrahedron", anchor=UP, rotate_children=false)
-//     cylinder(r=.1, h=.5);
-// Example(FlatSpin,Med): Using `$face` you can have full control of the construction of your children.  This example constructs the Great Icosahedron.
-//   module makestar(pts) {    // Make a star from a point list
-//       polygon(
-//         [
-//           for(i=[0:len(pts)-1]) let(
-//             p0=select(pts,i),
-//             p1=select(pts,i+1),
-//             center=(p0+p1)/2,
-//             v=sqrt(7/4-PHI)*(p1-p0)
-//           ) each [p0, [v.y+center.x, -v.x+center.y]]
-//         ]
-//       );
-//   }
-//   regular_polyhedron("dodecahedron", side=1, repeat=true)
-//   linear_extrude(scale=0, height=sqrt((5+2*sqrt(5))/5)) makestar($face);
-// Example(Med): The spheres are all radius 1 and the octahedra are sized to match the in-sphere, mid-sphere and out-sphere.  The sphere size is slightly adjusted for the in-sphere and out-sphere so you can see the relationship: the sphere is tangent to the faces for the former and the corners poke out for the latter.  Note also the difference in the size of the three octahedra.
-//   sphere(r=1.005);
-//   %regular_polyhedron("octahedron", ir=1, facedown=false);
-//   right(3.5) {
-//     sphere(r=1);
-//     %regular_polyhedron("octahedron", mr=1, facedown=false);
-//   }
-//   right(6.5) {
-//     %sphere(r=.95);  // Slightly undersized sphere means the points poke out a bit
-//     regular_polyhedron("octahedron", or=1,facedown=false);
-//   }
-// Example(Med): For the Archimdean solids the in-sphere does not touch all of the faces, as shown by this example, but the circumscribed sphere meets every vertex.  (This explains the problem for rounding over these solids because the rounding method uses the in-sphere.)
-//   sphere(r=1.005);
-//   %regular_polyhedron("snub dodecahedron", ir=1, facedown=false);
-//   right(3) {
-//     sphere(r=1);
-//     %regular_polyhedron("snub dodecahedron", mr=1, facedown=false);
-//   }
-//   right(6) {
-//     %sphere(r=.99);
-//     regular_polyhedron("snub dodecahedron", or=1,facedown=false);
-//   }
-// Example(Med): For a Catalan solid the in-sphere touches every face but the circumscribed sphere only touches some vertices.
-//   sphere(r=1.002);
-//   %regular_polyhedron("pentagonal hexecontahedron", ir=1, facedown=false);
-//   right(3) {
-//     sphere(r=1);
-//     %regular_polyhedron("pentagonal hexecontahedron", mr=1, facedown=false);
-//   }
-//   right(6) {
-//     %sphere(r=.98);
-//     regular_polyhedron("pentagonal hexecontahedron", or=1,facedown=false);
-//   }
-module regular_polyhedron(
-    name=undef,
-    index=undef,
-    type=undef,
-    faces=undef,
-    facetype=undef,
-    hasfaces=undef,
-    side=1,
-    ir=undef,
-    mr=undef,
-    or=undef,
-    r=undef,
-    d=undef,
-    anchor=[0,0,0],
-    center=undef,
-    rounding=0,
-    repeat=true,
-    facedown=true,
-    draw=true,
-    rotate_children=true,
-    stellate = false,
-    longside=undef, // special parameter for trapezohedron
-    h=undef         // special parameter for trapezohedron
-) {
-    assert(rounding>=0, "'rounding' must be nonnegative");
-    entry = regular_polyhedron_info(
-        "fullentry", name=name, index=index,
-        type=type, faces=faces, facetype=facetype,
-        hasfaces=hasfaces, side=side,
-        ir=ir, mr=mr, or=or,
-        r=r, d=d,
-        anchor=anchor, center=center,
-        facedown=facedown,
-        stellate=stellate,
-        longside=longside, h=h
-    );
-    scaled_points = entry[0];
-    translation = entry[1];
-    face_triangles = entry[2];
-    faces = entry[3];
-    face_normals = entry[4];
-    in_radius = entry[5];
-    if (draw){
-        if (rounding==0)
-            polyhedron(move(translation, p=scaled_points), faces = face_triangles);
-        else {
-            fn = segs(rounding);
-            rounding = rounding/cos(180/fn);
-            adjusted_scale = 1 - rounding / in_radius;
-            minkowski(){
-                sphere(r=rounding, $fn=fn);
-                polyhedron(move(translation,p=adjusted_scale*scaled_points), faces = face_triangles);
-            }
-        }
-    }
-    if ($children>0) {
-        maxrange = repeat ? len(faces)-1 : $children-1;
-        for(i=[0:1:maxrange]) {
-            // Would like to orient so an edge (longest edge?) is parallel to x axis
-            facepts = move(translation, p=select(scaled_points, faces[i]));
-            center = mean(facepts);
-            rotatedface = rot(from=face_normals[i], to=[0,0,1], p=move(-center, p=facepts));
-            clockwise = sortidx([for(pt=rotatedface) -atan2(pt.y,pt.x)]);
-            $face = rotate_children?
-                        path2d(select(rotatedface,clockwise)) :
-                        select(move(-center,p=facepts), clockwise);
-            $faceindex = i;
-            $center = -translation-center;
-            translate(center)
-            if (rotate_children) {
-                rot(from=[0,0,1], to=face_normals[i])
-                children(i % $children);
-            } else {
-                children(i % $children);
-            }
-        }
-    }
-}
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Some internal functions used to generate polyhedra data
-//
-// All permutations and even permutations of three items
-//
-function _even_perms(v) = [v, [v[2], v[0], v[1]], [v[1],v[2],v[0]]];
-function _all_perms(v) = [v, [v[2], v[0], v[1]], [v[1],v[2],v[0]], [v[1],v[0],v[2]],[v[2],v[1],v[0]],[v[0],v[2],v[1]]];
-//
-// Point reflections across all planes.    In the unconstrained case, this means one point becomes 8 points.
-//
-// sign=="even" means an even number of minus signs (odd number of plus signs)
-// sign=="odd" means an odd number of minus signs (even number of plus signs)
-//
-function _point_ref(points, sign="both") =
-    unique([
-        for(i=[-1,1],j=[-1,1],k=[-1,1])
-            if (sign=="both" || sign=="even" && i*j*k>0 || sign=="odd" && i*j*k<0)
-                each [for(point=points) vmul(point,[i,j,k])]
-    ]);
-//
-_tribonacci=(1+4*cosh(acosh(2+3/8)/3))/3;
-//
-/////////////////////////////////////////////////////////////////////////////
-//
-// Polyhedra data table.
-// The polyhedra information is from Wikipedia and http://dmccooey.com/polyhedra/
-//
-_polyhedra_ = [
-    // Platonic Solids
-
-    ["tetrahedron", "platonic", 4,[3], 2*sqrt(2), sqrt(6)/12, sqrt(2)/4, sqrt(6)/4, 1/6/sqrt(2),
-            _point_ref([[1,1,1]], sign="even")],
-    ["cube", "platonic", 6, [4], 2, 1/2, 1/sqrt(2), sqrt(3)/2, 1,
-            _point_ref([[1,1,1]])],
-    ["octahedron", "platonic", 8, [3], sqrt(2), sqrt(6)/6, 1/2, sqrt(2)/2, sqrt(2)/3,
-            _point_ref(_even_perms([1,0,0]))],
-    ["dodecahedron", "platonic", 12, [5], 2/PHI, sqrt(5/2+11*sqrt(5)/10)/2, (3+sqrt(5))/4, sqrt(3)*PHI/2, (15+7*sqrt(5))/4,
-            _point_ref(concat([[1,1,1]],_even_perms([0,PHI,1/PHI])))],
-    ["icosahedron", "platonic", 20, [3], 2, PHI*PHI/2/sqrt(3), cos(36), sin(72), 5*(3+sqrt(5))/12,
-            _point_ref(_even_perms([0,1,PHI]))],
-
-    // Archimedian Solids, listed in order by Wenniger number, W6-W18
-
-    ["truncated tetrahedron", "archimedean", 8,[6,3], sqrt(8), sqrt(6)/4, 3*sqrt(2)/4, sqrt(11/8), 23*sqrt(2)/12,
-            _point_ref(_all_perms([1,1,3]),sign="even")],
-    ["truncated octahedron", "archimedean", 14, [6,4], sqrt(2), sqrt(6)/2, 1.5, sqrt(10)/2, 8*sqrt(2),
-            _point_ref(_all_perms([0,1,2]))],
-    ["truncated cube", "archimedean", 14, [8,3], 2*(sqrt(2)-1), (1+sqrt(2))/2, 1+sqrt(2)/2, sqrt(7+4*sqrt(2))/2, 7+14*sqrt(2)/3,
-            _point_ref(_all_perms([1,1,sqrt(2)-1]))],
-    ["truncated icosahedron", "archimedean", 32, [6, 5], 2, (3*sqrt(3)+sqrt(15))/4, 3*PHI/2, sqrt(58+18*sqrt(5))/4, (125+43*sqrt(5))/4,
-            _point_ref(concat(
-                _even_perms([0,1,3*PHI]),
-                _even_perms([1,2+PHI,2*PHI]),
-                _even_perms([PHI,2,PHI*PHI*PHI])
-            ))],
-    ["truncated dodecahedron", "archimedean", 32, [10, 3], 2*PHI-2, sqrt(7+11*PHI)/2, (3*PHI+1)/2,sqrt(11+PHI*15)/2, 5*(99+47*sqrt(5))/12,
-            _point_ref(concat(
-                _even_perms([0,1/PHI, 2+PHI]),
-                _even_perms([1/PHI,PHI,2*PHI]),
-                _even_perms([PHI,2,PHI+1])
-            ))],
-    ["cuboctahedron", "archimedean", 14, [4,3], sqrt(2), sqrt(2)/2, sqrt(3)/2, 1, 5*sqrt(2)/3,
-            _point_ref(_all_perms([1,1,0]))],
-    ["icosidodecahedron", "archimedean", 32, [5,3], 1, sqrt(5*(5+2*sqrt(5)))/5,sqrt(5+2*sqrt(5))/2, PHI, (14+17*PHI)/3,
-            _point_ref(concat(_even_perms([0,0,PHI]),_even_perms([1/2,PHI/2,PHI*PHI/2])))],
-    ["rhombicuboctahedron", "archimedean", 26, [4, 3], 2, (1+sqrt(2))/2, sqrt(2*(2+sqrt(2)))/2, sqrt(5+2*sqrt(2))/2, 4+10*sqrt(2)/3,
-            _point_ref(_even_perms([1,1,1+sqrt(2)]))],
-    ["rhombicosidodecahedron", "archimedean", 62, [5,4,3], 2, 3/10*sqrt(15+20*PHI), sqrt(3/2+2*PHI), sqrt(8*PHI+7)/2, (31+58*PHI)/3,
-            _point_ref(concat(
-                _even_perms([1,1,PHI*PHI*PHI]),
-                _even_perms([PHI*PHI,PHI,2*PHI]),
-                _even_perms([2+PHI,0,PHI*PHI])
-            ))],
-    ["truncated cuboctahedron", "archimedean", 26, [8, 6, 4], 2, (1+2*sqrt(2))/2, sqrt(6*(2+sqrt(2)))/2, sqrt(13+6*sqrt(2))/2, (22+14*sqrt(2)),
-            _point_ref(_all_perms([1,1+sqrt(2), 1+2*sqrt(2)]))],
-    ["truncated icosidodecahedron", "archimedean", 62, [10,6,4], 2*PHI - 2, sqrt(15/4+5*PHI),sqrt(9/2+6*PHI),sqrt(19/4+6*PHI), 95+50*sqrt(5),
-            _point_ref(concat(
-                _even_perms([1/PHI,1/PHI,3+PHI]),
-                _even_perms([2/PHI,PHI,1+2*PHI]),
-                _even_perms([1/PHI,PHI*PHI,3*PHI-1]),
-                _even_perms([2*PHI-1,2,2+PHI]),
-                _even_perms([PHI,3,2*PHI])
-            ))],
-    ["snub cube", "archimedean",    38, [4,3], 1.60972,1.14261350892596209,1.24722316799364325, 1.34371337374460170,
-            sqrt((613*_tribonacci+203)/(9*(35*_tribonacci-62))),
-            concat(
-                _point_ref(_even_perms([1,1/_tribonacci,_tribonacci]), sign="odd"),
-                _point_ref(_even_perms([1,_tribonacci,1/_tribonacci]), sign="even")
-            )],
-    ["snub dodecahedron", "archimedean", 92, [5, 3], 1, 1.98091594728184,2.097053835252087,2.155837375115, 37.61664996273336,
-            concat(
-                _point_ref(_even_perms([0.374821658114562,0.330921024729844,2.097053835252088]), sign="odd"),
-                _point_ref(_even_perms([0.192893711352359,1.249503788463027,1.746186440985827]), sign="odd"),
-                _point_ref(_even_perms([1.103156835071754,0.847550046789061,1.646917940690374]), sign="odd"),
-                _point_ref(_even_perms([0.567715369466922,0.643029605914072,1.977838965420219]), sign="even"),
-                _point_ref(_even_perms([1.415265416255982,0.728335176957192,1.454024229338015]), sign="even")
-            )],
-
-    // Catalan Solids, the duals to the Archimedean solids, listed in the corresponding order
-
-    ["triakis tetrahedron","catalan", 12, [3], 9/5, 5*sqrt(22)/44, 5*sqrt(2)/12, 5*sqrt(6)/12, 25*sqrt(2)/36,
-            concat(
-                _point_ref([9*sqrt(2)/20*[1,1,1]],sign="even"),
-                _point_ref([3*sqrt(2)/4*[1,1,1]],sign="odd")
-            )],
-    ["tetrakis hexahedron", "catalan", 24, [3], 1, 2/sqrt(5), 2*sqrt(2)/3, 2/sqrt(3), 32/9,
-            _point_ref(concat([[2/3,2/3,2/3]],_even_perms([1,0,0])))],
-    ["triakis octahedron", "catalan", 24, [3], 2, sqrt(17*(23+16*sqrt(2)))/34, 1/2+sqrt(2)/4,(1+sqrt(2))/2,3/2+sqrt(2),
-            _point_ref(concat([[1,1,1]],_even_perms([1+sqrt(2),0,0])))],
-    ["pentakis dodecahedron", "catalan", 60, [3], 1,sqrt(477/436+97*sqrt(5)/218), sqrt(5)/4+11/12, sqrt(7/4+sqrt(5)/3), 125*sqrt(5)/36+205/36,
-            _point_ref(concat(
-                _even_perms([0,(5-PHI)/6, PHI/2+2/3]),
-                _even_perms([0,(PHI+1)/2,PHI/2]),[(4*PHI-1)/6 * [1,1,1]]
-            ))],
-    ["triakis icosahedron", "catalan", 60, [3], 1, sqrt((139+199*PHI)/244), (8*PHI+1)/10, sqrt(13/8+19/8/sqrt(5)), (13*PHI+3)/2,
-            _point_ref(concat(
-                _even_perms([(PHI+7)/10, 0, (8*PHI+1)/10]),
-                _even_perms([0, 1/2, (PHI+1)/2]),[PHI/2*[1,1,1]]
-            ))],
-    ["rhombic dodecahedron", "catalan", 12, [4], sqrt(3), sqrt(2/3), 2*sqrt(2)/3, 2/sqrt(3), 16*sqrt(3)/9,
-            _point_ref(concat([[1,1,1]], _even_perms([2,0,0])))],
-    ["rhombic triacontahedron", "catalan", 30,[4], 1, sqrt(1+2/sqrt(5)), 1+1/sqrt(5), (1+sqrt(5))/2, 4*sqrt(5+2*sqrt(5)),
-            concat(
-                _point_ref(_even_perms([0,sqrt(1+2/sqrt(5)), sqrt((5+sqrt(5))/10)])),
-                _point_ref(_even_perms([0,sqrt(2/(5+sqrt(5))), sqrt(1+2/sqrt(5))])),
-                _point_ref([sqrt((5+sqrt(5))/10)*[1,1,1]])
-            )],
-    ["deltoidal icositetrahedron", "catalan", 24, [4], 2*sqrt(10-sqrt(2))/7, 7*sqrt((7+4*sqrt(2))/(34 * (10-sqrt(2)))),
-            7*sqrt(2*(2+sqrt(2)))/sqrt(10-sqrt(2))/4, 7*sqrt(2)/sqrt(10-sqrt(2))/2,
-            (14+21*sqrt(2))/sqrt(10-sqrt(2)),
-            _point_ref(concat(
-                _even_perms([0,1,1]), _even_perms([sqrt(2),0,0]),
-                _even_perms((4+sqrt(2))/7*[1,1,1])
-            ))],
-    ["deltoidal hexecontahedron", "catalan", 60, [4], sqrt(5*(85-31*sqrt(5)))/11, sqrt(571/164+1269/164/sqrt(5)), 5/4+13/4/sqrt(5),
-            sqrt(147+65*sqrt(5))/6, sqrt(29530+13204*sqrt(5))/3,
-            _point_ref(concat(
-                _even_perms([0,0,sqrt(5)]),
-                _even_perms([0,(15+sqrt(5))/22, (25+9*sqrt(5))/22]),
-                _even_perms([0,(5+3*sqrt(5))/6, (5+sqrt(5))/6]),
-                _even_perms([(5-sqrt(5))/4, sqrt(5)/2, (5+sqrt(5))/4]),
-                [(5+4*sqrt(5))/11*[1,1,1]]
-            ))],
-    ["disdyakis dodecahedron", "catalan", 48, [3], 1,sqrt(249/194+285/194/sqrt(2)) ,(2+3*sqrt(2))/4, sqrt(183/98+213/98/sqrt(2)),
-            sqrt(6582+4539*sqrt(2))/7,
-            _point_ref(concat(
-                _even_perms([sqrt(183/98+213/98/sqrt(2)),0,0]),
-                _even_perms(sqrt(3+3/sqrt(2))/2 * [1,1,0]),[7/sqrt(6*(10-sqrt(2)))*[1,1,1]]
-            ))],
-    ["disdyakis triacontahedron","catalan", 120, [3], sqrt(15*(85-31*sqrt(5)))/11, sqrt(3477/964+7707/964/sqrt(5)), 5/4+13/4/sqrt(5),
-            sqrt(441+195*sqrt(5))/10,sqrt(17718/5+39612/5/sqrt(5)),
-            _point_ref(concat(
-                _even_perms([0,0,3*(5+4*sqrt(5))/11]),
-                _even_perms([0,(5-sqrt(5))/2,(5+sqrt(5))/2]),
-                _even_perms([0,(15+9*sqrt(5))/10,3*(5+sqrt(5))/10]),
-                _even_perms([3*(15+sqrt(5))/44,3*(5+4*sqrt(5))/22, (75+27*sqrt(5))/44]), [sqrt(5)*[1,1,1]]
-            ))],
-    ["pentagonal icositetrahedron","catalan",24, [5], 0.593465355971, 1.950681331784, 2.1015938932963, 2.29400105368695, 35.6302020120713,
-            concat(
-                _point_ref(_even_perms([0.21879664300048044,0.740183741369857,1.0236561781126901]),sign="even"),
-                _point_ref(_even_perms([0.21879664300048044,1.0236561781126901,0.740183741369857]),sign="odd"),
-                _point_ref(_even_perms([1.3614101519264425,0,0])),
-                _point_ref([0.7401837413698572*[1,1,1]])
-            )],
-    ["pentagonal hexecontahedron", "catalan", 60,[5], 0.58289953474498, 3.499527848905764,3.597624822551189,3.80854772878239, 189.789852066885,
-            concat(
-                _point_ref(_even_perms([0.192893711352359,0.218483370127321,2.097053835252087]), sign="even"),
-                _point_ref(_even_perms([0,0.7554672605165955,1.9778389654202186])),
-                _point_ref(_even_perms([0,1.888445389283669154,1.1671234364753339])),
-                _point_ref(_even_perms([0.56771536946692131,0.824957552676275846,1.8654013108176956657]),sign="odd"),
-                _point_ref(_even_perms([0.37482165811456229,1.13706613386050418,1.746186440985826345]), sign="even"),
-                _point_ref(_even_perms([0.921228888309550,0.95998770139158,1.6469179406903744]),sign="even"),
-                _point_ref(_even_perms([0.7283351769571914773,1.2720962825758121,1.5277030708585051]),sign="odd"),
-                _point_ref([1.222371704903623092*[1,1,1]])
-            )],
-];
-
-
-_stellated_polyhedra_ = [
-    ["great dodecahedron", "icosahedron", -sqrt(5/3-PHI)],
-    ["small stellated dodecahedron", "dodecahedron", sqrt((5+2*sqrt(5))/5)],
-    ["great stellated dodecahedron", "icosahedron", sqrt(2/3+PHI)],
-];
-
-
-// Function: regular_polyhedron_info()
-//
-// Usage: regular_polyhedron_info(info, ....)
-//
-// Description:
-//   Calculate characteristics of regular polyhedra or the selection set for regular_polyhedron().
-//   Invoke with the same arguments used by regular_polyhedron() and use the `info` argument to
-//   request the desired return value. Set `info` to:
-//     * `"vnf"`: vnf for the selected polyhedron
-//     * `"vertices"`: vertex list for the selected polyhedron
-//     * `"faces"`: list of faces for the selected polyhedron, where each entry on the list is a list of point index values to be used with the vertex list
-//     * `"face normals"`: list of normal vectors for each face
-//     * `"in_radius"`: in-sphere radius for the selected polyhedron
-//     * `"mid_radius"`: mid-sphere radius for the selected polyhedron
-//     * `"out_radius"`: circumscribed sphere radius for the selected polyhedron
-//     * `"index set"`: index set selected by your specifications; use its length to determine the valid range for `index`.
-//     * `"face vertices"`: number of vertices on the faces of the selected polyhedron (always a list)
-//     * `"edge length"`: length of the smallest edge of the selected polyhedron
-//     * `"center"`: center for the polyhedron
-//     * `"type"`: polyhedron type, one of "platonic", "archimedean", "catalan", or "trapezohedron"
-//     * `"name"`: name of selected polyhedron
-//
-// Arguments:
-//   name = Name of polyhedron to create.
-//   index = Index to select from polyhedron list.  Default: 0.
-//   type = Type of polyhedron: "platonic", "archimedean", "catalan".
-//   faces = Number of faces.
-//   facetype = Scalar or vector listing required type of faces as vertex count.  Polyhedron must have faces of every type listed and no other types.
-//   hasfaces = Scalar of vector list face vertex counts.  Polyhedron must have at least one of the listed types of face.
-//   side = Length of the smallest edge of the polyhedron.  Default: 1.
-//   ir = inner radius.  Polyhedron is scaled so it has the specified inner radius. Overrides side.
-//   mr = middle radius.  Polyhedron is scaled so it has the specified middle radius.  Overrides side.
-//   or = outer radius.   Polyhedron is scaled so it has the specified outer radius.  Overrides side.
-//   r = outer radius.  Overrides or.
-//   d = outer diameter.  Overrides or.
-//   anchor = Side of the origin to anchor to.  The bounding box of the polyhedron is aligned as specified.  Use directional constants from `constants.scad`.  Default: `CENTER`
-//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP+BACK+RIGHT`.
-//   facedown = If false display the solid in native orientation.  If true orient it with a largest face down.  If set to a vertex count, orient it so a face with the specified number of vertices is down.  Default: true.
-//   rounding = Specify a rounding radius for the shape.  Note that depending on $fn the dimensions of the shape may have small dimensional errors.
-//   repeat = If true then repeat the children to fill all the faces.  If false use only the available children and stop.  Default: true.
-//   draw = If true then draw the polyhedron.  If false, draw the children but not the polyhedron.  Default: true.
-//   rotate_children = If true then orient children normal to their associated face.  If false orient children to the parent coordinate system.  Default: true.
-//   stellate = Set to a number to erect a pyramid on every face of your polyhedron with the specified height.  The height is a multiple of the side length.  Default: false.
-//   longside = Specify the long side length for a trapezohedron.  Ignored for other shapes.
-//   h = Specify the height of the apex for a trapezohedron.  Ignored for other shapes.
-function regular_polyhedron_info(
-    info=undef, name=undef,
-    index=undef, type=undef,
-    faces=undef, facetype=undef,
-    hasfaces=undef, side=1,
-    ir=undef, mr=undef, or=undef,
-    r=undef, d=undef,
-    anchor=[0,0,0], center=undef,
-    facedown=true, stellate=false,
-    longside=undef, h=undef  // special parameters for trapezohedron
-) = let(
-        anchor = !is_undef(center) ? [0,0,0] : anchor,
-        argcount = num_defined([ir,mr,or,r,d])
-    )
-    assert(argcount<=1, "You must specify only one of 'ir', 'mr', 'or', 'r', and 'd'")
-    let(
-        //////////////////////
-        //Index values into the _polyhedra_ array
-        //
-        pname = 0,        // name of polyhedron
-        class = 1,        // class name (e.g. platonic, archimedean)
-        facecount = 2,    // number of faces
-        facevertices = 3, // vertices on the faces, e.g. [3] for all triangles, [3,4] for triangles and squares
-        edgelen = 4,      // length of the edge for the vertex list in the database
-        in_radius = 5,    // in radius for unit polyhedron (shortest side 1)
-        mid_radius = 6,   // mid radius for unit polyhedron
-        out_radius = 7,   // out radius for unit polyhedron
-        volume = 8,       // volume of unit polyhedron (data not validated, not used right now)
-        vertices = 9,     // vertex list (in arbitrary order)
-        //////////////////////
-        r = !is_undef(d) ? d/2 : r,
-        or = !is_undef(r) ? r : or,
-        stellate_index = search([name], _stellated_polyhedra_, 1, 0)[0],
-        name = stellate_index==[] ? name : _stellated_polyhedra_[stellate_index][1],
-        stellate = stellate_index==[] ? stellate : _stellated_polyhedra_[stellate_index][2],
-        indexlist = (
-            name=="trapezohedron" ? [0] : [  // dumy list of one item
-                for(i=[0:1:len(_polyhedra_)-1]) (
-                    if (
-                        (is_undef(name) || _polyhedra_[i][pname]==name) &&
-                        (is_undef(type) || _polyhedra_[i][class]==type) &&
-                        (is_undef(faces) || _polyhedra_[i][facecount]==faces) &&
-                        (
-                            is_undef(facetype) || 0==compare_lists(
-                                is_list(facetype)? reverse(sort(facetype)) : [facetype],
-                                _polyhedra_[i][facevertices]
-                            )
-                        ) &&
-                        (is_undef(hasfaces) || any([for (ft=hasfaces) in_list(ft,_polyhedra_[i][facevertices])]))
-                    ) i
-                )
-            ]
-        )
-    )
-    assert(len(indexlist)>0, "No polyhedra meet your specification")
-    let(validindex = is_undef(index) || (index>=0 && index<len(indexlist)))
-    assert(validindex, str(
-        len(indexlist),
-        " polyhedra meet specifications, so 'index' must be in [0,",
-        len(indexlist)-1,
-        "], but 'index' is ",
-        index
-    ))
-    let(
-        entry = (
-            name == "trapezohedron"? (
-                trapezohedron(faces=faces, side=side, longside=longside, h=h, r=r)
-            ) : (
-                _polyhedra_[!is_undef(index)?
-                    indexlist[index] :
-                    indexlist[0]]
-            )
-        ),
-        valid_facedown = is_bool(facedown) || in_list(facedown, entry[facevertices])
-    )
-    assert(valid_facedown,str("'facedown' set to ",facedown," but selected polygon only has faces with size(s) ",entry[facevertices]))
-    let(
-        scalefactor = (
-            name=="trapezohedron" ? 1 : (
-                argcount == 0? side :
-                !is_undef(ir)? ir/entry[in_radius] :
-                !is_undef(mr)? mr/entry[mid_radius] : or/entry[out_radius]
-            ) / entry[edgelen]
-        ),
-        face_triangles = hull(entry[vertices]),
-        faces_normals_vertices = stellate_faces(
-            entry[edgelen], stellate, entry[vertices],
-            entry[facevertices]==[3]?
-                [face_triangles, [for(face=face_triangles) _facenormal(entry[vertices],face)]] :
-                _full_faces(entry[vertices], face_triangles)
-        ),
-        faces = faces_normals_vertices[0],
-        faces_vertex_count = [for(face=faces) len(face)],
-        facedown = facedown == true ? (stellate==false? entry[facevertices][0] : 3) : facedown,
-        down_direction = facedown == false?  [0,0,-1] :
-            faces_normals_vertices[1][search(facedown, faces_vertex_count)[0]],
-        scaled_points = scalefactor * rot(p=faces_normals_vertices[2], from=down_direction, to=[0,0,-1]),
-        bounds = pointlist_bounds(scaled_points),
-        boundtable = [bounds[0], [0,0,0], bounds[1]],
-        translation = [for(i=[0:2]) -boundtable[1+anchor[i]][i]],
-        face_normals = rot(p=faces_normals_vertices[1], from=down_direction, to=[0,0,-1]),
-        side_length = scalefactor * entry[edgelen]
-    )
-    info == "fullentry" ? [
-        scaled_points,
-        translation,
-        stellate ? faces : face_triangles,
-        faces,
-        face_normals,
-        side_length*entry[in_radius]
-    ] :
-    info == "vnf" ? [move(translation,p=scaled_points), stellate ? faces : face_triangles] : 
-    info == "vertices" ? move(translation,p=scaled_points) :
-    info == "faces" ? faces :
-    info == "face normals" ? face_normals :
-    info == "in_radius" ? side_length * entry[in_radius] :
-    info == "mid_radius" ? side_length * entry[mid_radius] :
-    info == "out_radius" ? side_length * entry[out_radius] :
-    info == "index set" ? indexlist :
-    info == "face vertices" ? (stellate==false? entry[facevertices] : [3]) :
-    info == "edge length" ? scalefactor * entry[edgelen] :
-    info == "center" ? translation :
-    info == "type" ? entry[class] :
-    info == "name" ? entry[pname] :
-    echo_warning(str("Unknown info type '",info,"' requested"));
-
-
-
-/// hull solution fails due to roundoff
-/// either cross product or just rotate to
-///
-function stellate_faces(scalefactor,stellate,vertices,faces_normals) =
-    (stellate == false || stellate == 0)? concat(faces_normals,[vertices]) :
-    let(
-        faces = [for(face=faces_normals[0]) select(face,hull(select(vertices,face)))],
-        direction = [for(i=[0:1:len(faces)-1]) _facenormal(vertices, faces[i])*faces_normals[1][i]>0 ? 1 : -1],
-        maxvertex = len(vertices),
-        newpts = [for(i=[0:1:len(faces)-1]) mean(select(vertices,faces[i]))+stellate*scalefactor*faces_normals[1][i]],
-        newfaces = [for(i=[0:1:len(faces)-1], j=[0:len(faces[i])-1]) concat([i+maxvertex],select(faces[i], [j, j+direction[i]]))],
-        allpts = concat(vertices, newpts),
-        normals = [for(face=newfaces) _facenormal(allpts,face)]
-    ) [newfaces, normals, allpts];
-
-
-function trapezohedron(faces, r, side, longside, h) =
-    assert(faces%2==0, "Number of faces must be even")
-    let(
-        N = faces/2,
-        parmcount = num_defined([r,side,longside,h])
-    )
-    assert(parmcount==2,"Must define exactly two of 'r', 'side', 'longside', and 'height'")
-    let(
-        separation = (
-            !is_undef(h) ? 2*h*sqr(tan(90/N)) :
-            (!is_undef(r) && !is_undef(side))? sqrt(side*side+2*r*r*(cos(180/N)-1)) :
-            (!is_undef(r) && !is_undef(longside))? 2 * sqrt(sqr(longside)-sqr(r)) / (1-sqr(tan(90/N))) * sqr(tan(90/N)) :
-            2*sqr(sin(90/N))*sqrt((sqr(side) + 2*sqr(longside)*(cos(180/N)-1)) / (cos(180/N)-1) / (cos(180/N)+cos(360/N)))
-        )
-    )
-    assert(separation==separation, "Impossible trapezohedron specification")
-    let(
-        h = !is_undef(h) ? h : 0.5*separation / sqr(tan(90/N)),
-        r = (
-            !is_undef(r) ? r :
-            !is_undef(side) ? sqrt((sqr(separation) - sqr(side))/2/(cos(180/N)-1)) :
-            sqrt(sqr(longside) - sqr(h-separation/2))
-        ),
-        top = [for(i=[0:1:N-1]) [r*cos(360/N*i), r*sin(360/N*i),separation/2]],
-        bot = [for(i=[0:1:N-1]) [r*cos(180/N+360/N*i), r*sin(180/N+360/N*i),-separation/2]],
-        vertices = concat([[0,0,h],[0,0,-h]],top,bot)
-    ) [
-        "trapezohedron", "trapezohedron", faces, [4],
-        !is_undef(side)? side : sqrt(sqr(separation)-2*r*(cos(180/N)-1)),  // actual side length
-        h*r/sqrt(r*r+sqr(h+separation/2)),     // in_radius
-        h*r/sqrt(r*r+sqr(h-separation/2)),     // mid_radius
-        max(h,sqrt(r*r+sqr(separation/2))),  // out_radius
-        undef,                               // volume
-        vertices
-    ];
-
-
-function _facenormal(pts, face) = unit(cross(pts[face[2]]-pts[face[0]], pts[face[1]]-pts[face[0]]));
-
-// hull() function returns triangulated faces.    This function identifies the vertices that belong to each face
-// by grouping together the face triangles that share normal vectors.    The output gives the face polygon
-// point indices in arbitrary order (not usable as input to a polygon call) and a normal vector.
-
-function _full_faces(pts,faces) =
-    let(
-        normals = [for(face=faces) quant(_facenormal(pts,face),1e-12)],
-        groups = _unique_groups(normals),
-        faces = [for(entry=groups) unique(flatten(select(faces, entry)))],
-        facenormals = [for(entry=groups) normals[entry[0]]]
-    ) [faces, facenormals];
-
-
-// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/shapes.scad b/shapes.scad
deleted file mode 100644
index 16b970d..0000000
--- a/shapes.scad
+++ /dev/null
@@ -1,1755 +0,0 @@
-//////////////////////////////////////////////////////////////////////
-// LibFile: shapes.scad
-//   Common useful shapes and structured objects.
-//   To use, add the following lines to the beginning of your file:
-//   ```
-//   include <BOSL2/std.scad>
-//   ```
-//////////////////////////////////////////////////////////////////////
-
-
-// Section: Cuboids
-
-
-// Module: cuboid()
-//
-// Description:
-//   Creates a cube or cuboid object, with optional chamfering or rounding.
-//   Negative chamfers and roundings can be applied to create external masks,
-//   but only apply to edges around the top or bottom faces.
-//
-// Arguments:
-//   size = The size of the cube.
-//   chamfer = Size of chamfer, inset from sides.  Default: No chamfering.
-//   rounding = Radius of the edge rounding.  Default: No rounding.
-//   edges = Edges to chamfer/round.  See the docs for [`edges()`](edges.scad#edges) to see acceptable values.  Default: All edges.
-//   except_edges = Edges to explicitly NOT chamfer/round.  See the docs for [`edges()`](edges.scad#edges) to see acceptable values.  Default: No edges.
-//   trimcorners = If true, rounds or chamfers corners where three chamfered/rounded edges meet.  Default: `true`
-//   p1 = Align the cuboid's corner at `p1`, if given.  Forces `anchor=ALLNEG`.
-//   p2 = If given with `p1`, defines the cornerpoints of the cuboid.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards.  See [orient](attachments.scad#orient).  Default: `UP`
-//
-// Example: Simple regular cube.
-//   cuboid(40);
-// Example: Cube with minimum cornerpoint given.
-//   cuboid(20, p1=[10,0,0]);
-// Example: Rectangular cube, with given X, Y, and Z sizes.
-//   cuboid([20,40,50]);
-// Example: Cube by Opposing Corners.
-//   cuboid(p1=[0,10,0], p2=[20,30,30]);
-// Example: Chamferred Edges and Corners.
-//   cuboid([30,40,50], chamfer=5);
-// Example: Chamferred Edges, Untrimmed Corners.
-//   cuboid([30,40,50], chamfer=5, trimcorners=false);
-// Example: Rounded Edges and Corners
-//   cuboid([30,40,50], rounding=10);
-// Example: Rounded Edges, Untrimmed Corners
-//   cuboid([30,40,50], rounding=10, trimcorners=false);
-// Example: Chamferring Selected Edges
-//   cuboid([30,40,50], chamfer=5, edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT], $fn=24);
-// Example: Rounding Selected Edges
-//   cuboid([30,40,50], rounding=5, edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT], $fn=24);
-// Example: Negative Chamferring
-//   cuboid([30,40,50], chamfer=-5, edges=[TOP,BOT], except_edges=RIGHT, $fn=24);
-// Example: Negative Chamferring, Untrimmed Corners
-//   cuboid([30,40,50], chamfer=-5, edges=[TOP,BOT], except_edges=RIGHT, trimcorners=false, $fn=24);
-// Example: Negative Rounding
-//   cuboid([30,40,50], rounding=-5, edges=[TOP,BOT], except_edges=RIGHT, $fn=24);
-// Example: Negative Rounding, Untrimmed Corners
-//   cuboid([30,40,50], rounding=-5, edges=[TOP,BOT], except_edges=RIGHT, trimcorners=false, $fn=24);
-// Example: Standard Connectors
-//   cuboid(40) show_anchors();
-module cuboid(
-    size=[1,1,1],
-    p1=undef, p2=undef,
-    chamfer=undef,
-    rounding=undef,
-    edges=EDGES_ALL,
-    except_edges=[],
-    trimcorners=true,
-    anchor=CENTER,
-    spin=0,
-    orient=UP
-) {
-    size = scalar_vec3(size);
-    edges = edges(edges, except=except_edges);
-    if (!is_undef(p1)) {
-        if (!is_undef(p2)) {
-            translate(pointlist_bounds([p1,p2])[0]) {
-                cuboid(size=vabs(p2-p1), chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=ALLNEG) children();
-            }
-        } else {
-            translate(p1) {
-                cuboid(size=size, chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=ALLNEG) children();
-            }
-        }
-    } else {
-        if (chamfer != undef) {
-            if (any(edges[0])) assert(chamfer <= size.y/2 && chamfer <=size.z/2, "chamfer must be smaller than half the cube length or height.");
-            if (any(edges[1])) assert(chamfer <= size.x/2 && chamfer <=size.z/2, "chamfer must be smaller than half the cube width or height.");
-            if (any(edges[2])) assert(chamfer <= size.x/2 && chamfer <=size.y/2, "chamfer must be smaller than half the cube width or length.");
-        }
-        if (rounding != undef) {
-            if (any(edges[0])) assert(rounding <= size.y/2 && rounding<=size.z/2, "rounding radius must be smaller than half the cube length or height.");
-            if (any(edges[1])) assert(rounding <= size.x/2 && rounding<=size.z/2, "rounding radius must be smaller than half the cube width or height.");
-            if (any(edges[2])) assert(rounding <= size.x/2 && rounding<=size.y/2, "rounding radius must be smaller than half the cube width or length.");
-        }
-        majrots = [[0,90,0], [90,0,0], [0,0,0]];
-        attachable(anchor,spin,orient, size=size) {
-            if (chamfer != undef) {
-                if (edges == EDGES_ALL && trimcorners) {
-                    if (chamfer<0) {
-                        cube(size, center=true) {
-                            attach(TOP) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
-                            attach(BOT) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
-                        }
-                    } else {
-                        isize = [for (v = size) max(0.001, v-2*chamfer)];
-                        hull() {
-                            cube([size.x, isize.y, isize.z], center=true);
-                            cube([isize.x, size.y, isize.z], center=true);
-                            cube([isize.x, isize.y, size.z], center=true);
-                        }
-                    }
-                } else if (chamfer<0) {
-                    ach = abs(chamfer);
-                    cube(size, center=true);
-
-                    // External-Chamfer mask edges
-                    difference() {
-                        union() {
-                            for (i = [0:3], axis=[0:1]) {
-                                if (edges[axis][i]>0) {
-                                    vec = EDGE_OFFSETS[axis][i];
-                                    translate(vmul(vec/2, size+[ach,ach,-ach])) {
-                                        rotate(majrots[axis]) {
-                                            cube([ach, ach, size[axis]], center=true);
-                                        }
-                                    }
-                                }
-                            }
-
-                            // Add multi-edge corners.
-                            if (trimcorners) {
-                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
-                                    if (corner_edge_count(edges, [xa,ya,za]) > 1) {
-                                        translate(vmul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
-                                            cube([ach+0.01,ach+0.01,ach], center=true);
-                                        }
-                                    }
-                                }
-                            }
-                        }
-
-                        // Remove bevels from overhangs.
-                        for (i = [0:3], axis=[0:1]) {
-                            if (edges[axis][i]>0) {
-                                vec = EDGE_OFFSETS[axis][i];
-                                translate(vmul(vec/2, size+[2*ach,2*ach,-2*ach])) {
-                                    rotate(majrots[axis]) {
-                                        zrot(45) cube([ach*sqrt(2), ach*sqrt(2), size[axis]+2.1*ach], center=true);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                } else {
-                    difference() {
-                        cube(size, center=true);
-
-                        // Chamfer edges
-                        for (i = [0:3], axis=[0:2]) {
-                            if (edges[axis][i]>0) {
-                                translate(vmul(EDGE_OFFSETS[axis][i], size/2)) {
-                                    rotate(majrots[axis]) {
-                                        zrot(45) cube([chamfer*sqrt(2), chamfer*sqrt(2), size[axis]+0.01], center=true);
-                                    }
-                                }
-                            }
-                        }
-
-                        // Chamfer triple-edge corners.
-                        if (trimcorners) {
-                            for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
-                                if (corner_edge_count(edges, [xa,ya,za]) > 2) {
-                                    translate(vmul([xa,ya,za]/2, size-[1,1,1]*chamfer*4/3)) {
-                                        rot(from=UP, to=[xa,ya,za]) {
-                                            cube(chamfer*3, anchor=BOTTOM);
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            } else if (rounding != undef) {
-                sides = quantup(segs(rounding),4);
-                if (edges == EDGES_ALL) {
-                    if(rounding<0) {
-                        cube(size, center=true);
-                        zflip_copy() {
-                            up(size.z/2) {
-                                difference() {
-                                    down(-rounding/2) cube([size.x-2*rounding, size.y-2*rounding, -rounding], center=true);
-                                    down(-rounding) {
-                                        ycopies(size.y-2*rounding) xcyl(l=size.x-3*rounding, r=-rounding);
-                                        xcopies(size.x-2*rounding) ycyl(l=size.y-3*rounding, r=-rounding);
-                                    }
-                                }
-                            }
-                        }
-                    } else {
-                        isize = [for (v = size) max(0.001, v-2*rounding)];
-                        minkowski() {
-                            cube(isize, center=true);
-                            if (trimcorners) {
-                                spheroid(r=rounding, style="octa", $fn=sides);
-                            } else {
-                                intersection() {
-                                    cyl(r=rounding, h=rounding*2, $fn=sides);
-                                    rotate([90,0,0]) cyl(r=rounding, h=rounding*2, $fn=sides);
-                                    rotate([0,90,0]) cyl(r=rounding, h=rounding*2, $fn=sides);
-                                }
-                            }
-                        }
-                    }
-                } else if (rounding<0) {
-                    ard = abs(rounding);
-                    cube(size, center=true);
-
-                    // External-Chamfer mask edges
-                    difference() {
-                        union() {
-                            for (i = [0:3], axis=[0:1]) {
-                                if (edges[axis][i]>0) {
-                                    vec = EDGE_OFFSETS[axis][i];
-                                    translate(vmul(vec/2, size+[ard,ard,-ard])) {
-                                        rotate(majrots[axis]) {
-                                            cube([ard, ard, size[axis]], center=true);
-                                        }
-                                    }
-                                }
-                            }
-
-                            // Add multi-edge corners.
-                            if (trimcorners) {
-                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
-                                    if (corner_edge_count(edges, [xa,ya,za]) > 1) {
-                                        translate(vmul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
-                                            cube([ard+0.01,ard+0.01,ard], center=true);
-                                        }
-                                    }
-                                }
-                            }
-                        }
-
-                        // Remove roundings from overhangs.
-                        for (i = [0:3], axis=[0:1]) {
-                            if (edges[axis][i]>0) {
-                                vec = EDGE_OFFSETS[axis][i];
-                                translate(vmul(vec/2, size+[2*ard,2*ard,-2*ard])) {
-                                    rotate(majrots[axis]) {
-                                        cyl(l=size[axis]+2.1*ard, r=ard);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                } else {
-                    difference() {
-                        cube(size, center=true);
-
-                        // Round edges.
-                        for (i = [0:3], axis=[0:2]) {
-                            if (edges[axis][i]>0) {
-                                difference() {
-                                    translate(vmul(EDGE_OFFSETS[axis][i], size/2)) {
-                                        rotate(majrots[axis]) cube([rounding*2, rounding*2, size[axis]+0.1], center=true);
-                                    }
-                                    translate(vmul(EDGE_OFFSETS[axis][i], size/2 - [1,1,1]*rounding)) {
-                                        rotate(majrots[axis]) cyl(h=size[axis]+0.2, r=rounding, $fn=sides);
-                                    }
-                                }
-                            }
-                        }
-
-                        // Round triple-edge corners.
-                        if (trimcorners) {
-                            for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
-                                if (corner_edge_count(edges, [xa,ya,za]) > 2) {
-                                    difference() {
-                                        translate(vmul([xa,ya,za], size/2)) {
-                                            cube(rounding*2, center=true);
-                                        }
-                                        translate(vmul([xa,ya,za], size/2-[1,1,1]*rounding)) {
-                                            spheroid(r=rounding, style="octa", $fn=sides);
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            } else {
-                cube(size=size, center=true);
-            }
-            children();
-        }
-    }
-}
-
-
-
-// Section: Prismoids
-
-
-// Function&Module: prismoid()
-//
-// Usage: As Module
-//   prismoid(size1, size2, h|l, [shift], [rounding], [chamfer]);
-//   prismoid(size1, size2, h|l, [shift], [rounding1], [rounding2], [chamfer1], [chamfer2]);
-// Usage: As Function
-//   vnf = prismoid(size1, size2, h|l, [shift], [rounding], [chamfer]);
-//   vnf = prismoid(size1, size2, h|l, [shift], [rounding1], [rounding2], [chamfer1], [chamfer2]);
-//
-// Description:
-//   Creates a rectangular prismoid shape with optional roundovers and chamfering.
-//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
-//   specify rounding and/or chamferring per-edge, and for top and bottom separately.
-//   Note: if using chamfers or rounding, you **must** also include the hull.scad file:
-//   ```
-//   include <BOSL2/hull.scad>
-//   ```
-//
-// Arguments:
-//   size1 = [width, length] of the axis-negative end of the prism.
-//   size2 = [width, length] of the axis-positive end of the prism.
-//   h|l = Height of the prism.
-//   shift = [X,Y] amount to shift the center of the top with respect to the center of the bottom.
-//   rounding = The roundover radius for the edges of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
-//   rounding1 = The roundover radius for the bottom corners of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
-//   rounding2 = The roundover radius for the top corners of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
-//   chamfer = The chamfer size for the edges of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].  Default: 0 (no chamfer)
-//   chamfer1 = The chamfer size for the bottom corners of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
-//   chamfer2 = The chamfer size for the top corners of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-//
-// Example: Rectangular Pyramid
-//   prismoid([40,40], [0,0], h=20);
-// Example: Prism
-//   prismoid(size1=[40,40], size2=[0,40], h=20);
-// Example: Truncated Pyramid
-//   prismoid(size1=[35,50], size2=[20,30], h=20);
-// Example: Wedge
-//   prismoid(size1=[60,35], size2=[30,0], h=30);
-// Example: Truncated Tetrahedron
-//   prismoid(size1=[10,40], size2=[40,10], h=40);
-// Example: Inverted Truncated Pyramid
-//   prismoid(size1=[15,5], size2=[30,20], h=20);
-// Example: Right Prism
-//   prismoid(size1=[30,60], size2=[0,60], shift=[-15,0], h=30);
-// Example(FlatSpin): Shifting/Skewing
-//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]);
-// Example: Rounding
-//   include <BOSL2/hull.scad>
-//   prismoid(100, 80, rounding=10, h=30);
-// Example: Outer Chamfer Only
-//   include <BOSL2/hull.scad>
-//   prismoid(100, 80, chamfer=5, h=30);
-// Example: Gradiant Rounding
-//   include <BOSL2/hull.scad>
-//   prismoid(100, 80, rounding1=10, rounding2=0, h=30);
-// Example: Per Corner Rounding
-//   include <BOSL2/hull.scad>
-//   prismoid(100, 80, rounding=[0,5,10,15], h=30);
-// Example: Per Corner Chamfer
-//   include <BOSL2/hull.scad>
-//   prismoid(100, 80, chamfer=[0,5,10,15], h=30);
-// Example: Mixing Chamfer and Rounding
-//   include <BOSL2/hull.scad>
-//   prismoid(100, 80, chamfer=[0,5,0,10], rounding=[5,0,10,0], h=30);
-// Example: Really Mixing It Up
-//   include <BOSL2/hull.scad>
-//   prismoid(
-//       size1=[100,80], size2=[80,60], h=20,
-//       chamfer1=[0,5,0,10], chamfer2=[5,0,10,0],
-//       rounding1=[5,0,10,0], rounding2=[0,5,0,10]
-//   );
-// Example(Spin): Standard Connectors
-//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5])
-//       show_anchors();
-module prismoid(
-    size1, size2, h, shift=[0,0],
-    rounding=0, rounding1, rounding2,
-    chamfer=0, chamfer1, chamfer2,
-    l, center,
-    anchor, spin=0, orient=UP
-) {
-    assert(is_num(size1) || is_vector(size1,2));
-    assert(is_num(size2) || is_vector(size2,2));
-    assert(is_num(h) || is_num(l));
-    assert(is_vector(shift,2));
-    assert(is_num(rounding) || is_vector(rounding,4), "Bad rounding argument.");
-    assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "Bad rounding1 argument.");
-    assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "Bad rounding2 argument.");
-    assert(is_num(chamfer) || is_vector(chamfer,4), "Bad chamfer argument.");
-    assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "Bad chamfer1 argument.");
-    assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "Bad chamfer2 argument.");
-    eps = pow(2,-14);
-    size1 = is_num(size1)? [size1,size1] : size1;
-    size2 = is_num(size2)? [size2,size2] : size2;
-    s1 = [max(size1.x, eps), max(size1.y, eps)];
-    s2 = [max(size2.x, eps), max(size2.y, eps)];
-    rounding1 = default(rounding1, rounding);
-    rounding2 = default(rounding2, rounding);
-    chamfer1 = default(chamfer1, chamfer);
-    chamfer2 = default(chamfer2, chamfer);
-    anchor = get_anchor(anchor, center, BOT, BOT);
-    vnf = prismoid(
-        size1=size1, size2=size2, h=h, shift=shift,
-        rounding=rounding, rounding1=rounding1, rounding2=rounding2,
-        chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
-        l=l, center=CENTER
-    );
-    attachable(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift) {
-        vnf_polyhedron(vnf, convexity=4);
-        children();
-    }
-}
-
-function prismoid(
-    size1, size2, h, shift=[0,0],
-    rounding=0, rounding1, rounding2,
-    chamfer=0, chamfer1, chamfer2,
-    l, center,
-    anchor=DOWN, spin=0, orient=UP
-) =
-    assert(is_vector(size1,2))
-    assert(is_vector(size2,2))
-    assert(is_num(h) || is_num(l))
-    assert(is_vector(shift,2))
-    assert(is_num(rounding) || is_vector(rounding,4), "Bad rounding argument.")
-    assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "Bad rounding1 argument.")
-    assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "Bad rounding2 argument.")
-    assert(is_num(chamfer) || is_vector(chamfer,4), "Bad chamfer argument.")
-    assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "Bad chamfer1 argument.")
-    assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "Bad chamfer2 argument.")
-    let(
-        eps = pow(2,-14),
-        h = first_defined([h,l,1]),
-        shiftby = point3d(point2d(shift)),
-        s1 = [max(size1.x, eps), max(size1.y, eps)],
-        s2 = [max(size2.x, eps), max(size2.y, eps)],
-        rounding1 = default(rounding1, rounding),
-        rounding2 = default(rounding2, rounding),
-        chamfer1 = default(chamfer1, chamfer),
-        chamfer2 = default(chamfer2, chamfer),
-        anchor = get_anchor(anchor, center, BOT, BOT),
-        vnf = (rounding1==0 && rounding2==0 && chamfer1==0 && chamfer2==0)? (
-            let(
-                corners = [[1,1],[1,-1],[-1,-1],[-1,1]] * 0.5,
-                points = [
-                    for (p=corners) point3d(vmul(s2,p), +h/2) + shiftby,
-                    for (p=corners) point3d(vmul(s1,p), -h/2)
-                ],
-                faces=[
-                    [0,1,2], [0,2,3], [0,4,5], [0,5,1],
-                    [1,5,6], [1,6,2], [2,6,7], [2,7,3],
-                    [3,7,4], [3,4,0], [4,7,6], [4,6,5],
-                ]
-            ) [points, faces]
-        ) : (
-            let(
-                path1 = rect(size1, rounding=rounding1, chamfer=chamfer1, anchor=CTR),
-                path2 = rect(size2, rounding=rounding2, chamfer=chamfer2, anchor=CTR),
-                points = [
-                    each path3d(path1, -h/2),
-                    each path3d(move(shiftby, p=path2), +h/2),
-                ],
-                faces = hull(points)
-            ) [points, faces]
-        )
-    ) reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf);
-
-
-// Module: right_triangle()
-//
-// Usage:
-//   right_triangle(size, [center]);
-//
-// Description:
-//   Creates a 3D right triangular prism with the hypotenuse in the X+Y+ quadrant.
-//
-// Arguments:
-//   size = [width, thickness, height]
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `ALLNEG`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-//
-// Example: Centered
-//   right_triangle([60, 40, 10], center=true);
-// Example: *Non*-Centered
-//   right_triangle([60, 40, 10]);
-// Example: Standard Connectors
-//   right_triangle([60, 40, 15]) show_anchors();
-module right_triangle(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
-{
-    size = scalar_vec3(size);
-    anchor = get_anchor(anchor, center, ALLNEG, ALLNEG);
-    attachable(anchor,spin,orient, size=size) {
-        linear_extrude(height=size.z, convexity=2, center=true) {
-            polygon([[-size.x/2,-size.y/2], [-size.x/2,size.y/2], [size.x/2,-size.y/2]]);
-        }
-        children();
-    }
-}
-
-
-
-// Section: Cylindroids
-
-
-// Module: cyl()
-//
-// Description:
-//   Creates cylinders in various anchors and orientations,
-//   with optional rounding and chamfers. You can use `r` and `l`
-//   interchangably, and all variants allow specifying size
-//   by either `r`|`d`, or `r1`|`d1` and `r2`|`d2`.
-//   Note that that chamfers and rounding cannot cross the
-//   midpoint of the cylinder's length.
-//
-// Usage: Normal Cylinders
-//   cyl(l|h, r|d, [circum], [realign], [center]);
-//   cyl(l|h, r1|d1, r2/d2, [circum], [realign], [center]);
-//
-// Usage: Chamferred Cylinders
-//   cyl(l|h, r|d, chamfer, [chamfang], [from_end], [circum], [realign], [center]);
-//   cyl(l|h, r|d, chamfer1, [chamfang1], [from_end], [circum], [realign], [center]);
-//   cyl(l|h, r|d, chamfer2, [chamfang2], [from_end], [circum], [realign], [center]);
-//   cyl(l|h, r|d, chamfer1, chamfer2, [chamfang1], [chamfang2], [from_end], [circum], [realign], [center]);
-//
-// Usage: Rounded End Cylinders
-//   cyl(l|h, r|d, rounding, [circum], [realign], [center]);
-//   cyl(l|h, r|d, rounding1, [circum], [realign], [center]);
-//   cyl(l|h, r|d, rounding2, [circum], [realign], [center]);
-//   cyl(l|h, r|d, rounding1, rounding2, [circum], [realign], [center]);
-//
-// Arguments:
-//   l / h = Length of cylinder along oriented axis. (Default: 1.0)
-//   r = Radius of cylinder.
-//   r1 = Radius of the negative (X-, Y-, Z-) end of cylinder.
-//   r2 = Radius of the positive (X+, Y+, Z+) end of cylinder.
-//   d = Diameter of cylinder.
-//   d1 = Diameter of the negative (X-, Y-, Z-) end of cylinder.
-//   d2 = Diameter of the positive (X+, Y+, Z+) end of cylinder.
-//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
-//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
-//   chamfer1 = The size of the chamfer on the axis-negative end of the cylinder.  Default: none.
-//   chamfer2 = The size of the chamfer on the axis-positive end of the cylinder.  Default: none.
-//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
-//   chamfang1 = The angle in degrees of the chamfer on the axis-negative end of the cylinder.
-//   chamfang2 = The angle in degrees of the chamfer on the axis-positive end of the cylinder.
-//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
-//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
-//   rounding1 = The radius of the rounding on the axis-negative end of the cylinder.
-//   rounding2 = The radius of the rounding on the axis-positive end of the cylinder.
-//   realign = If true, rotate the cylinder by half the angle of one face.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
-//
-// Example: By Radius
-//   xdistribute(30) {
-//       cyl(l=40, r=10);
-//       cyl(l=40, r1=10, r2=5);
-//   }
-//
-// Example: By Diameter
-//   xdistribute(30) {
-//       cyl(l=40, d=25);
-//       cyl(l=40, d1=25, d2=10);
-//   }
-//
-// Example: Chamferring
-//   xdistribute(60) {
-//       // Shown Left to right.
-//       cyl(l=40, d=40, chamfer=7);  // Default chamfang=45
-//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=false);
-//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=true);
-//   }
-//
-// Example: Rounding
-//   cyl(l=40, d=40, rounding=10);
-//
-// Example: Heterogenous Chamfers and Rounding
-//   ydistribute(80) {
-//       // Shown Front to Back.
-//       cyl(l=40, d=40, rounding1=15, orient=UP);
-//       cyl(l=40, d=40, chamfer2=5, orient=UP);
-//       cyl(l=40, d=40, chamfer1=12, rounding2=10, orient=UP);
-//   }
-//
-// Example: Putting it all together
-//   cyl(l=40, d1=25, d2=15, chamfer1=10, chamfang1=30, from_end=true, rounding2=5);
-//
-// Example: External Chamfers
-//   cyl(l=50, r=30, chamfer=-5, chamfang=30, $fa=1, $fs=1);
-//
-// Example: External Roundings
-//   cyl(l=50, r=30, rounding1=-5, rounding2=5, $fa=1, $fs=1);
-//
-// Example: Standard Connectors
-//   xdistribute(40) {
-//       cyl(l=30, d=25) show_anchors();
-//       cyl(l=30, d1=25, d2=10) show_anchors();
-//   }
-//
-module cyl(
-    l=undef, h=undef,
-    r=undef, r1=undef, r2=undef,
-    d=undef, d1=undef, d2=undef,
-    chamfer=undef, chamfer1=undef, chamfer2=undef,
-    chamfang=undef, chamfang1=undef, chamfang2=undef,
-    rounding=undef, rounding1=undef, rounding2=undef,
-    circum=false, realign=false, from_end=false,
-    center, anchor, spin=0, orient=UP
-) {
-    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
-    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
-    l = first_defined([l, h, 1]);
-    sides = segs(max(r1,r2));
-    sc = circum? 1/cos(180/sides) : 1;
-    phi = atan2(l, r2-r1);
-    anchor = get_anchor(anchor,center,BOT,CENTER);
-    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
-        zrot(realign? 180/sides : 0) {
-            if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])) {
-                cylinder(h=l, r1=r1*sc, r2=r2*sc, center=true, $fn=sides);
-            } else {
-                vang = atan2(l, r1-r2)/2;
-                chang1 = 90-first_defined([chamfang1, chamfang, vang]);
-                chang2 = 90-first_defined([chamfang2, chamfang, 90-vang]);
-                cham1 = first_defined([chamfer1, chamfer]) * (from_end? 1 : tan(chang1));
-                cham2 = first_defined([chamfer2, chamfer]) * (from_end? 1 : tan(chang2));
-                fil1 = first_defined([rounding1, rounding]);
-                fil2 = first_defined([rounding2, rounding]);
-                if (chamfer != undef) {
-                    assert(chamfer <= r1,  "chamfer is larger than the r1 radius of the cylinder.");
-                    assert(chamfer <= r2,  "chamfer is larger than the r2 radius of the cylinder.");
-                }
-                if (cham1 != undef) {
-                    assert(cham1 <= r1,  "chamfer1 is larger than the r1 radius of the cylinder.");
-                }
-                if (cham2 != undef) {
-                    assert(cham2 <= r2,  "chamfer2 is larger than the r2 radius of the cylinder.");
-                }
-                if (rounding != undef) {
-                    assert(rounding <= r1,  "rounding is larger than the r1 radius of the cylinder.");
-                    assert(rounding <= r2,  "rounding is larger than the r2 radius of the cylinder.");
-                }
-                if (fil1 != undef) {
-                    assert(fil1 <= r1,  "rounding1 is larger than the r1 radius of the cylinder.");
-                }
-                if (fil2 != undef) {
-                    assert(fil2 <= r2,  "rounding2 is larger than the r1 radius of the cylinder.");
-                }
-                dy1 = abs(first_defined([cham1, fil1, 0]));
-                dy2 = abs(first_defined([cham2, fil2, 0]));
-                assert(dy1+dy2 <= l, "Sum of fillets and chamfer sizes must be less than the length of the cylinder.");
-
-                path = concat(
-                    [[0,l/2]],
-
-                    !is_undef(cham2)? (
-                        let(
-                            p1 = [r2-cham2/tan(chang2),l/2],
-                            p2 = lerp([r2,l/2],[r1,-l/2],abs(cham2)/l)
-                        ) [p1,p2]
-                    ) : !is_undef(fil2)? (
-                        let(
-                            cn = find_circle_2tangents([r2-fil2,l/2], [r2,l/2], [r1,-l/2], r=abs(fil2)),
-                            ang = fil2<0? phi : phi-180,
-                            steps = ceil(abs(ang)/360*segs(abs(fil2))),
-                            step = ang/steps,
-                            pts = [for (i=[0:1:steps]) let(a=90+i*step) cn[0]+abs(fil2)*[cos(a),sin(a)]]
-                        ) pts
-                    ) : [[r2,l/2]],
-
-                    !is_undef(cham1)? (
-                        let(
-                            p1 = lerp([r1,-l/2],[r2,l/2],abs(cham1)/l),
-                            p2 = [r1-cham1/tan(chang1),-l/2]
-                        ) [p1,p2]
-                    ) : !is_undef(fil1)? (
-                        let(
-                            cn = find_circle_2tangents([r1-fil1,-l/2], [r1,-l/2], [r2,l/2], r=abs(fil1)),
-                            ang = fil1<0? 180-phi : -phi,
-                            steps = ceil(abs(ang)/360*segs(abs(fil1))),
-                            step = ang/steps,
-                            pts = [for (i=[0:1:steps]) let(a=(fil1<0?180:0)+(phi-90)+i*step) cn[0]+abs(fil1)*[cos(a),sin(a)]]
-                        ) pts
-                    ) : [[r1,-l/2]],
-
-                    [[0,-l/2]]
-                );
-                rotate_extrude(convexity=2) {
-                    polygon(path);
-                }
-            }
-        }
-        children();
-    }
-}
-
-
-
-// Module: xcyl()
-//
-// Description:
-//   Creates a cylinder oriented along the X axis.
-//
-// Usage:
-//   xcyl(l|h, r|d, [anchor]);
-//   xcyl(l|h, r1|d1, r2|d2, [anchor]);
-//
-// Arguments:
-//   l / h = Length of cylinder along oriented axis. (Default: `1.0`)
-//   r = Radius of cylinder.
-//   r1 = Optional radius of left (X-) end of cylinder.
-//   r2 = Optional radius of right (X+) end of cylinder.
-//   d = Optional diameter of cylinder. (use instead of `r`)
-//   d1 = Optional diameter of left (X-) end of cylinder.
-//   d2 = Optional diameter of right (X+) end of cylinder.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//
-// Example: By Radius
-//   ydistribute(50) {
-//       xcyl(l=35, r=10);
-//       xcyl(l=35, r1=15, r2=5);
-//   }
-//
-// Example: By Diameter
-//   ydistribute(50) {
-//       xcyl(l=35, d=20);
-//       xcyl(l=35, d1=30, d2=10);
-//   }
-module xcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, anchor=CENTER)
-{
-    anchor = rot(from=RIGHT, to=UP, p=anchor);
-    cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=RIGHT, anchor=anchor) children();
-}
-
-
-
-// Module: ycyl()
-//
-// Description:
-//   Creates a cylinder oriented along the Y axis.
-//
-// Usage:
-//   ycyl(l|h, r|d, [anchor]);
-//   ycyl(l|h, r1|d1, r2|d2, [anchor]);
-//
-// Arguments:
-//   l / h = Length of cylinder along oriented axis. (Default: `1.0`)
-//   r = Radius of cylinder.
-//   r1 = Radius of front (Y-) end of cone.
-//   r2 = Radius of back (Y+) end of one.
-//   d = Diameter of cylinder.
-//   d1 = Diameter of front (Y-) end of one.
-//   d2 = Diameter of back (Y+) end of one.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//
-// Example: By Radius
-//   xdistribute(50) {
-//       ycyl(l=35, r=10);
-//       ycyl(l=35, r1=15, r2=5);
-//   }
-//
-// Example: By Diameter
-//   xdistribute(50) {
-//       ycyl(l=35, d=20);
-//       ycyl(l=35, d1=30, d2=10);
-//   }
-module ycyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, anchor=CENTER)
-{
-    anchor = rot(from=BACK, to=UP, p=anchor);
-    cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=BACK, anchor=anchor) children();
-}
-
-
-
-// Module: zcyl()
-//
-// Description:
-//   Creates a cylinder oriented along the Z axis.
-//
-// Usage:
-//   zcyl(l|h, r|d, [anchor]);
-//   zcyl(l|h, r1|d1, r2|d2, [anchor]);
-//
-// Arguments:
-//   l / h = Length of cylinder along oriented axis. (Default: 1.0)
-//   r = Radius of cylinder.
-//   r1 = Radius of front (Y-) end of cone.
-//   r2 = Radius of back (Y+) end of one.
-//   d = Diameter of cylinder.
-//   d1 = Diameter of front (Y-) end of one.
-//   d2 = Diameter of back (Y+) end of one.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//
-// Example: By Radius
-//   xdistribute(50) {
-//       zcyl(l=35, r=10);
-//       zcyl(l=35, r1=15, r2=5);
-//   }
-//
-// Example: By Diameter
-//   xdistribute(50) {
-//       zcyl(l=35, d=20);
-//       zcyl(l=35, d1=30, d2=10);
-//   }
-module zcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, anchor=CENTER)
-{
-    cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=UP, anchor=anchor) children();
-}
-
-
-
-// Module: tube()
-//
-// Description:
-//   Makes a hollow tube with the given outer size and wall thickness.
-//
-// Usage:
-//   tube(h|l, ir|id, wall, [realign]);
-//   tube(h|l, or|od, wall, [realign]);
-//   tube(h|l, ir|id, or|od, [realign]);
-//   tube(h|l, ir1|id1, ir2|id2, wall, [realign]);
-//   tube(h|l, or1|od1, or2|od2, wall, [realign]);
-//   tube(h|l, ir1|id1, ir2|id2, or1|od1, or2|od2, [realign]);
-//
-// Arguments:
-//   h|l = height of tube. (Default: 1)
-//   or = Outer radius of tube.
-//   or1 = Outer radius of bottom of tube.  (Default: value of r)
-//   or2 = Outer radius of top of tube.  (Default: value of r)
-//   od = Outer diameter of tube.
-//   od1 = Outer diameter of bottom of tube.
-//   od2 = Outer diameter of top of tube.
-//   wall = horizontal thickness of tube wall. (Default 0.5)
-//   ir = Inner radius of tube.
-//   ir1 = Inner radius of bottom of tube.
-//   ir2 = Inner radius of top of tube.
-//   id = Inner diameter of tube.
-//   id1 = Inner diameter of bottom of tube.
-//   id2 = Inner diameter of top of tube.
-//   realign = If true, rotate the tube by half the angle of one face.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-//
-// Example: These all Produce the Same Tube
-//   tube(h=30, or=40, wall=5);
-//   tube(h=30, ir=35, wall=5);
-//   tube(h=30, or=40, ir=35);
-//   tube(h=30, od=80, id=70);
-// Example: These all Produce the Same Conical Tube
-//   tube(h=30, or1=40, or2=25, wall=5);
-//   tube(h=30, ir1=35, or2=20, wall=5);
-//   tube(h=30, or1=40, or2=25, ir1=35, ir2=20);
-// Example: Circular Wedge
-//   tube(h=30, or1=40, or2=30, ir1=20, ir2=30);
-// Example: Standard Connectors
-//   tube(h=30, or=40, wall=5) show_anchors();
-module tube(
-    h, wall=undef,
-    r=undef, r1=undef, r2=undef,
-    d=undef, d1=undef, d2=undef,
-    or=undef, or1=undef, or2=undef,
-    od=undef, od1=undef, od2=undef,
-    ir=undef, id=undef, ir1=undef,
-    ir2=undef, id1=undef, id2=undef,
-    anchor, spin=0, orient=UP,
-    center, realign=false, l
-) {
-    h = first_defined([h,l,1]);
-    r1 = first_defined([or1, od1/2, r1, d1/2, or, od/2, r, d/2, ir1+wall, id1/2+wall, ir+wall, id/2+wall]);
-    r2 = first_defined([or2, od2/2, r2, d2/2, or, od/2, r, d/2, ir2+wall, id2/2+wall, ir+wall, id/2+wall]);
-    ir1 = first_defined([ir1, id1/2, ir, id/2, r1-wall, d1/2-wall, r-wall, d/2-wall]);
-    ir2 = first_defined([ir2, id2/2, ir, id/2, r2-wall, d2/2-wall, r-wall, d/2-wall]);
-    assert(ir1 <= r1, "Inner radius is larger than outer radius.");
-    assert(ir2 <= r2, "Inner radius is larger than outer radius.");
-    sides = segs(max(r1,r2));
-    anchor = get_anchor(anchor, center, BOT, BOT);
-    attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
-        zrot(realign? 180/sides : 0) {
-            difference() {
-                cyl(h=h, r1=r1, r2=r2, $fn=sides) children();
-                cyl(h=h+0.05, r1=ir1, r2=ir2);
-            }
-        }
-        children();
-    }
-}
-
-
-// Module: rect_tube()
-// Usage:
-//   rect_tube(size, wall, h, [center]);
-//   rect_tube(isize, wall, h, [center]);
-//   rect_tube(size, isize, h, [center]);
-//   rect_tube(size1, size2, wall, h, [center]);
-//   rect_tube(isize1, isize2, wall, h, [center]);
-//   rect_tube(size1, size2, isize1, isize2, h, [center]);
-// Description:
-//   Creates a rectangular or prismoid tube with optional roundovers and/or chamfers.
-//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
-//   specify rounding and/or chamferring per-edge, and for top and bottom, inside and
-//   outside  separately.
-//   Note: if using chamfers or rounding, you **must** also include the hull.scad file:
-//   ```
-//   include <BOSL2/hull.scad>
-//   ```
-// Arguments:
-//   size = The outer [X,Y] size of the rectangular tube.
-//   isize = The inner [X,Y] size of the rectangular tube.
-//   h|l = The height or length of the rectangular tube.  Default: 1
-//   wall = The thickness of the rectangular tube wall.
-//   size1 = The [X,Y] side of the outside of the bottom of the rectangular tube.
-//   size2 = The [X,Y] side of the outside of the top of the rectangular tube.
-//   isize1 = The [X,Y] side of the inside of the bottom of the rectangular tube.
-//   isize2 = The [X,Y] side of the inside of the top of the rectangular tube.
-//   rounding = The roundover radius for the outside edges of the rectangular tube.
-//   rounding1 = The roundover radius for the outside bottom corner of the rectangular tube.
-//   rounding2 = The roundover radius for the outside top corner of the rectangular tube.
-//   chamfer = The chamfer size for the outside edges of the rectangular tube.
-//   chamfer1 = The chamfer size for the outside bottom corner of the rectangular tube.
-//   chamfer2 = The chamfer size for the outside top corner of the rectangular tube.
-//   irounding = The roundover radius for the inside edges of the rectangular tube. Default: Same as `rounding`
-//   irounding1 = The roundover radius for the inside bottom corner of the rectangular tube.
-//   irounding2 = The roundover radius for the inside top corner of the rectangular tube.
-//   ichamfer = The chamfer size for the inside edges of the rectangular tube.  Default: Same as `chamfer`
-//   ichamfer1 = The chamfer size for the inside bottom corner of the rectangular tube.
-//   ichamfer2 = The chamfer size for the inside top corner of the rectangular tube.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `BOTTOM`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-// Examples:
-//   rect_tube(size=50, wall=5, h=30);
-//   rect_tube(size=[100,60], wall=5, h=30);
-//   rect_tube(isize=[60,80], wall=5, h=30);
-//   rect_tube(size=[100,60], isize=[90,50], h=30);
-//   rect_tube(size1=[100,60], size2=[70,40], wall=5, h=30);
-//   rect_tube(size1=[100,60], size2=[70,40], isize1=[40,20], isize2=[65,35], h=15);
-// Example: Outer Rounding Only
-//   include <BOSL2/hull.scad>
-//   rect_tube(size=100, wall=5, rounding=10, irounding=0, h=30);
-// Example: Outer Chamfer Only
-//   include <BOSL2/hull.scad>
-//   rect_tube(size=100, wall=5, chamfer=5, ichamfer=0, h=30);
-// Example: Outer Rounding, Inner Chamfer
-//   include <BOSL2/hull.scad>
-//   rect_tube(size=100, wall=5, rounding=10, ichamfer=8, h=30);
-// Example: Inner Rounding, Outer Chamfer
-//   include <BOSL2/hull.scad>
-//   rect_tube(size=100, wall=5, chamfer=10, irounding=8, h=30);
-// Example: Gradiant Rounding
-//   include <BOSL2/hull.scad>
-//   rect_tube(size1=100, size2=80, wall=5, rounding1=10, rounding2=0, irounding1=8, irounding2=0, h=30);
-// Example: Per Corner Rounding
-//   include <BOSL2/hull.scad>
-//   rect_tube(size=100, wall=10, rounding=[0,5,10,15], irounding=0, h=30);
-// Example: Per Corner Chamfer
-//   include <BOSL2/hull.scad>
-//   rect_tube(size=100, wall=10, chamfer=[0,5,10,15], ichamfer=0, h=30);
-// Example: Mixing Chamfer and Rounding
-//   include <BOSL2/hull.scad>
-//   rect_tube(size=100, wall=10, chamfer=[0,5,0,10], ichamfer=0, rounding=[5,0,10,0], irounding=0, h=30);
-// Example: Really Mixing It Up
-//   include <BOSL2/hull.scad>
-//   rect_tube(
-//       size1=[100,80], size2=[80,60],
-//       isize1=[50,30], isize2=[70,50], h=20,
-//       chamfer1=[0,5,0,10], ichamfer1=[0,3,0,8],
-//       chamfer2=[5,0,10,0], ichamfer2=[3,0,8,0],
-//       rounding1=[5,0,10,0], irounding1=[3,0,8,0],
-//       rounding2=[0,5,0,10], irounding2=[0,3,0,8]
-//   );
-module rect_tube(
-    size, isize,
-    h, shift=[0,0], wall,
-    size1, size2,
-    isize1, isize2,
-    rounding=0, rounding1, rounding2,
-    irounding=0, irounding1, irounding2,
-    chamfer=0, chamfer1, chamfer2,
-    ichamfer=0, ichamfer1, ichamfer2,
-    anchor, spin=0, orient=UP,
-    center, l
-) {
-    h = first_defined([h,l,1]);
-    assert(is_num(h), "l or h argument required.");
-    assert(is_vector(shift,2));
-    s1 = is_num(size1)? [size1, size1] :
-        is_vector(size1,2)? size1 :
-        is_num(size)? [size, size] :
-        is_vector(size,2)? size :
-        undef;
-    s2 = is_num(size2)? [size2, size2] :
-        is_vector(size2,2)? size2 :
-        is_num(size)? [size, size] :
-        is_vector(size,2)? size :
-        undef;
-    is1 = is_num(isize1)? [isize1, isize1] :
-        is_vector(isize1,2)? isize1 :
-        is_num(isize)? [isize, isize] :
-        is_vector(isize,2)? isize :
-        undef;
-    is2 = is_num(isize2)? [isize2, isize2] :
-        is_vector(isize2,2)? isize2 :
-        is_num(isize)? [isize, isize] :
-        is_vector(isize,2)? isize :
-        undef;
-    size1 = is_def(s1)? s1 :
-        (is_def(wall) && is_def(is1))? (is1+2*[wall,wall]) :
-        undef;
-    size2 = is_def(s2)? s2 :
-        (is_def(wall) && is_def(is2))? (is2+2*[wall,wall]) :
-        undef;
-    isize1 = is_def(is1)? is1 :
-        (is_def(wall) && is_def(s1))? (s1-2*[wall,wall]) :
-        undef;
-    isize2 = is_def(is2)? is2 :
-        (is_def(wall) && is_def(s2))? (s2-2*[wall,wall]) :
-        undef;
-    assert(wall==undef || is_num(wall));
-    assert(size1!=undef, "Bad size/size1 argument.");
-    assert(size2!=undef, "Bad size/size2 argument.");
-    assert(isize1!=undef, "Bad isize/isize1 argument.");
-    assert(isize2!=undef, "Bad isize/isize2 argument.");
-    assert(isize1.x < size1.x, "Inner size is larger than outer size.");
-    assert(isize1.y < size1.y, "Inner size is larger than outer size.");
-    assert(isize2.x < size2.x, "Inner size is larger than outer size.");
-    assert(isize2.y < size2.y, "Inner size is larger than outer size.");
-    anchor = get_anchor(anchor, center, BOT, BOT);
-    attachable(anchor,spin,orient, size=[each size1, h], size2=size2, shift=shift) {
-        diff("_H_o_L_e_")
-        prismoid(
-            size1, size2, h=h, shift=shift,
-            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
-            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
-            anchor=CTR
-        ) {
-            children();
-            tags("_H_o_L_e_") prismoid(
-                isize1, isize2, h=h+0.05, shift=shift,
-                rounding=irounding, rounding1=irounding1, rounding2=irounding2,
-                chamfer=ichamfer, chamfer1=ichamfer1, chamfer2=ichamfer2,
-                anchor=CTR
-            );
-        }
-        children();
-    }
-}
-
-
-// Module: torus()
-//
-// Descriptiom:
-//   Creates a torus shape.
-//
-// Usage:
-//   torus(r|d, r2|d2);
-//   torus(or|od, ir|id);
-//
-// Arguments:
-//   r  = major radius of torus ring. (use with of 'r2', or 'd2')
-//   r2 = minor radius of torus ring. (use with of 'r', or 'd')
-//   d  = major diameter of torus ring. (use with of 'r2', or 'd2')
-//   d2 = minor diameter of torus ring. (use with of 'r', or 'd')
-//   or = outer radius of the torus. (use with 'ir', or 'id')
-//   ir = inside radius of the torus. (use with 'or', or 'od')
-//   od = outer diameter of the torus. (use with 'ir' or 'id')
-//   id = inside diameter of the torus. (use with 'or' or 'od')
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-//
-// Example:
-//   // These all produce the same torus.
-//   torus(r=22.5, r2=7.5);
-//   torus(d=45, d2=15);
-//   torus(or=30, ir=15);
-//   torus(od=60, id=30);
-// Example: Standard Connectors
-//   torus(od=60, id=30) show_anchors();
-module torus(
-    r=undef,  d=undef,
-    r2=undef, d2=undef,
-    or=undef, od=undef,
-    ir=undef, id=undef,
-    center, anchor, spin=0, orient=UP
-) {
-    orr = get_radius(r=or, d=od, dflt=1.0);
-    irr = get_radius(r=ir, d=id, dflt=0.5);
-    majrad = get_radius(r=r, d=d, dflt=(orr+irr)/2);
-    minrad = get_radius(r=r2, d=d2, dflt=(orr-irr)/2);
-    anchor = get_anchor(anchor, center, BOT, CENTER);
-    attachable(anchor,spin,orient, r=(majrad+minrad), l=minrad*2) {
-        rotate_extrude(convexity=4) {
-            right(majrad) circle(r=minrad);
-        }
-        children();
-    }
-}
-
-
-
-// Section: Spheroid
-
-
-// Function&Module: spheroid()
-// Usage: As Module
-//   spheroid(r|d, [circum], [style])
-// Usage: As Function
-//   vnf = spheroid(r|d, [circum], [style])
-// Description:
-//   Creates a spheroid object, with support for anchoring and attachments.
-//   This is a drop-in replacement for the built-in `sphere()` module.
-//   When called as a function, returns a [VNF](vnf.scad) for a spheroid.
-// Arguments:
-//   r = Radius of the spheroid.
-//   d = Diameter of the spheroid.
-//   circum = If true, the spheroid is made large enough to circumscribe the sphere of the ideal side.  Otherwise inscribes.  Default: false (inscribes)
-//   style = The style of the spheroid's construction. One of "orig", "aligned", "stagger", "octa", or "icosa".  Default: "aligned"
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-// Example: By Radius
-//   spheroid(r=50);
-// Example: By Diameter
-//   spheroid(d=100);
-// Example: style="orig"
-//   spheroid(d=100, style="orig", $fn=10);
-// Example: style="aligned"
-//   spheroid(d=100, style="aligned", $fn=10);
-// Example: style="stagger"
-//   spheroid(d=100, style="stagger", $fn=10);
-// Example: style="octa", octahedral based tesselation.
-//   spheroid(d=100, style="octa", $fn=10);
-//   // In "octa" style, $fn is quantized
-//   //   to the nearest multiple of 4.
-// Example: style="icosa", icosahedral based tesselation.
-//   spheroid(d=100, style="icosa", $fn=10);
-//   // In "icosa" style, $fn is quantized
-//   //   to the nearest multiple of 5.
-// Example: Anchoring
-//   spheroid(d=100, anchor=FRONT);
-// Example: Spin
-//   spheroid(d=100, anchor=FRONT, spin=45);
-// Example: Orientation
-//   spheroid(d=100, anchor=FRONT, spin=45, orient=FWD);
-// Example: Standard Connectors
-//   spheroid(d=50) show_anchors();
-// Example: Called as Function
-//   vnf = spheroid(d=100, style="icosa");
-//   vnf_polyhedron(vnf);
-module spheroid(r, d, circum=false, style="aligned", anchor=CENTER, spin=0, orient=UP)
-{
-    r = get_radius(r=r, d=d, dflt=1);
-    sides = segs(r);
-    attachable(anchor,spin,orient, r=r) {
-        if (style=="orig") {
-            rotate_extrude(convexity=2,$fn=sides) {
-                difference() {
-                    oval(r=r, circum=circum, $fn=sides);
-                    left(r) square(2*r,center=true);
-                }
-            }
-        } else if (style=="aligned") {
-            rotate_extrude(convexity=2,$fn=sides) {
-                difference() {
-                    zrot(180/sides) oval(r=r, circum=circum, $fn=sides);
-                    left(r) square(2*r,center=true);
-                }
-            }
-        } else {
-            vnf = spheroid(r=r, circum=circum, style=style);
-            vnf_polyhedron(vnf, convexity=2);
-        }
-        children();
-    }
-}
-
-
-function spheroid(r, d, circum=false, style="aligned", anchor=CENTER, spin=0, orient=UP) =
-    let(
-        r = get_radius(r=r, d=d, dflt=1),
-        hsides = segs(r),
-        vsides = max(2,ceil(hsides/2)),
-        octa_steps = round(max(4,hsides)/4),
-        icosa_steps = round(max(5,hsides)/5),
-        rr = circum? (r / cos(90/vsides) / cos(180/hsides)) : r,
-        stagger = style=="stagger",
-        verts = style=="orig"? [
-            for (i=[0:1:vsides-1]) let(phi = (i+0.5)*180/(vsides))
-            for (j=[0:1:hsides-1]) let(theta = j*360/hsides)
-            spherical_to_xyz(rr, theta, phi),
-        ] : style=="aligned" || style=="stagger"? [
-            spherical_to_xyz(rr, 0, 0),
-            for (i=[1:1:vsides-1]) let(phi = i*180/vsides)
-                for (j=[0:1:hsides-1]) let(theta = (j+((stagger && i%2!=0)?0.5:0))*360/hsides)
-                    spherical_to_xyz(rr, theta, phi),
-            spherical_to_xyz(rr, 0, 180)
-        ] : style=="octa"? let(
-            meridians = [
-                1,
-                for (i = [1:1:octa_steps]) i*4,
-                for (i = [octa_steps-1:-1:1]) i*4,
-                1,
-            ]
-        ) [
-            for (i=idx(meridians), j=[0:1:meridians[i]-1])
-            spherical_to_xyz(rr, j*360/meridians[i], i*180/(len(meridians)-1))
-        ] : style=="icosa"? [
-            for (tb=[0,1], j=[0,2], i = [0:1:4]) let(
-                theta0 = i*360/5,
-                theta1 = (i-0.5)*360/5,
-                theta2 = (i+0.5)*360/5,
-                phi0 = 180/3 * j,
-                phi1 = 180/3,
-                v0 = spherical_to_xyz(1,theta0,phi0),
-                v1 = spherical_to_xyz(1,theta1,phi1),
-                v2 = spherical_to_xyz(1,theta2,phi1),
-                ax0 = vector_axis(v0, v1),
-                ang0 = vector_angle(v0, v1),
-                ax1 = vector_axis(v0, v2),
-                ang1 = vector_angle(v0, v2)
-            )
-            for (k = [0:1:icosa_steps]) let(
-                u = k/icosa_steps,
-                vv0 = rot(ang0*u, ax0, p=v0),
-                vv1 = rot(ang1*u, ax1, p=v0),
-                ax2 = vector_axis(vv0, vv1),
-                ang2 = vector_angle(vv0, vv1)
-            )
-            for (l = [0:1:k]) let(
-                v = k? l/k : 0,
-                pt = rot(ang2*v, v=ax2, p=vv0) * rr * (tb? -1 : 1)
-            ) pt
-        ] : assert(in_list(style,["orig","aligned","stagger","octa","icosa"])),
-        lv = len(verts),
-        faces = style=="orig"? [
-            [for (i=[0:1:hsides-1]) hsides-i-1],
-            [for (i=[0:1:hsides-1]) lv-hsides+i],
-            for (i=[0:1:vsides-2], j=[0:1:hsides-1]) each [
-                [(i+1)*hsides+j, i*hsides+j, i*hsides+(j+1)%hsides],
-                [(i+1)*hsides+j, i*hsides+(j+1)%hsides, (i+1)*hsides+(j+1)%hsides],
-            ]
-        ] : style=="aligned" || style=="stagger"? [
-            for (i=[0:1:hsides-1]) let(
-                b2 = lv-2-hsides
-            ) each [
-                [i+1, 0, ((i+1)%hsides)+1],
-                [lv-1, b2+i+1, b2+((i+1)%hsides)+1],
-            ],
-            for (i=[0:1:vsides-3], j=[0:1:hsides-1]) let(
-                base = 1 + hsides*i
-            ) each (
-                (stagger && i%2!=0)? [
-                    [base+j, base+hsides+j%hsides, base+hsides+(j+hsides-1)%hsides],
-                    [base+j, base+(j+1)%hsides, base+hsides+j],
-                ] : [
-                    [base+j, base+(j+1)%hsides, base+hsides+(j+1)%hsides],
-                    [base+j, base+hsides+(j+1)%hsides, base+hsides+j],
-                ]
-            )
-        ] : style=="octa"? let(
-            meridians = [
-                0, 1,
-                for (i = [1:1:octa_steps]) i*4,
-                for (i = [octa_steps-1:-1:1]) i*4,
-                1,
-            ],
-            offs = cumsum(meridians),
-            pc = select(offs,-1)-1,
-            os = octa_steps * 2
-        ) [
-            for (i=[0:1:3]) [0, 1+(i+1)%4, 1+i],
-            for (i=[0:1:3]) [pc-0, pc-(1+(i+1)%4), pc-(1+i)],
-            for (i=[1:1:octa_steps-1]) let(
-                m = meridians[i+2]/4
-            )
-            for (j=[0:1:3], k=[0:1:m-1]) let(
-                m1 = meridians[i+1],
-                m2 = meridians[i+2],
-                p1 = offs[i+0] + (j*m1/4 + k+0) % m1,
-                p2 = offs[i+0] + (j*m1/4 + k+1) % m1,
-                p3 = offs[i+1] + (j*m2/4 + k+0) % m2,
-                p4 = offs[i+1] + (j*m2/4 + k+1) % m2,
-                p5 = offs[os-i+0] + (j*m1/4 + k+0) % m1,
-                p6 = offs[os-i+0] + (j*m1/4 + k+1) % m1,
-                p7 = offs[os-i-1] + (j*m2/4 + k+0) % m2,
-                p8 = offs[os-i-1] + (j*m2/4 + k+1) % m2
-            ) each [
-                [p1, p4, p3],
-                if (k<m-1) [p1, p2, p4],
-                [p5, p7, p8],
-                if (k<m-1) [p5, p8, p6],
-            ],
-        ] : style=="icosa"? let(
-            pyr = [for (x=[0:1:icosa_steps+1]) x],
-            tri = sum(pyr),
-            soff = cumsum(pyr)
-        ) [
-            for (tb=[0,1], j=[0,1], i = [0:1:4]) let(
-                base = ((((tb*2) + j) * 5) + i) * tri
-            )
-            for (k = [0:1:icosa_steps-1])
-            for (l = [0:1:k]) let(
-                v1 = base + soff[k] + l,
-                v2 = base + soff[k+1] + l,
-                v3 = base + soff[k+1] + (l + 1),
-                faces = [
-                    if(l>0) [v1-1,v1,v2],
-                    [v1,v3,v2],
-                ],
-                faces2 = (tb+j)%2? [for (f=faces) reverse(f)] : faces
-            ) each faces2
-        ] : []
-    ) [reorient(anchor,spin,orient, r=r, p=verts), faces];
-
-
-
-// Section: 3D Printing Shapes
-
-
-// Module: teardrop()
-//
-// Description:
-//   Makes a teardrop shape in the XZ plane. Useful for 3D printable holes.
-//
-// Usage:
-//   teardrop(r|d, l|h, [ang], [cap_h])
-//
-// Arguments:
-//   r = Radius of circular part of teardrop.  (Default: 1)
-//   d = Diameter of circular portion of bottom. (Use instead of r)
-//   l = Thickness of teardrop. (Default: 1)
-//   ang = Angle of hat walls from the Z axis.  (Default: 45 degrees)
-//   cap_h = If given, height above center where the shape will be truncated.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-//
-// Example: Typical Shape
-//   teardrop(r=30, h=10, ang=30);
-// Example: Crop Cap
-//   teardrop(r=30, h=10, ang=30, cap_h=40);
-// Example: Close Crop
-//   teardrop(r=30, h=10, ang=30, cap_h=20);
-module teardrop(r=undef, d=undef, l=undef, h=undef, ang=45, cap_h=undef, anchor=CENTER, spin=0, orient=UP)
-{
-    r = get_radius(r=r, d=d, dflt=1);
-    l = first_defined([l, h, 1]);
-    size = [r*2,l,r*2];
-    attachable(anchor,spin,orient, size=size) {
-        rot(from=UP,to=FWD) {
-            linear_extrude(height=l, center=true, slices=2) {
-                teardrop2d(r=r, ang=ang, cap_h=cap_h);
-            }
-        }
-        children();
-    }
-}
-
-
-// Module: onion()
-//
-// Description:
-//   Creates a sphere with a conical hat, to make a 3D teardrop.
-//
-// Usage:
-//   onion(r|d, [maxang], [cap_h]);
-//
-// Arguments:
-//   r = radius of spherical portion of the bottom. (Default: 1)
-//   d = diameter of spherical portion of bottom.
-//   cap_h = height above sphere center to truncate teardrop shape.
-//   maxang = angle of cone on top from vertical.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-//
-// Example: Typical Shape
-//   onion(r=30, maxang=30);
-// Example: Crop Cap
-//   onion(r=30, maxang=30, cap_h=40);
-// Example: Close Crop
-//   onion(r=30, maxang=30, cap_h=20);
-// Example: Standard Connectors
-//   onion(r=30, maxang=30, cap_h=40) show_anchors();
-module onion(cap_h=undef, r=undef, d=undef, maxang=45, h=undef, anchor=CENTER, spin=0, orient=UP)
-{
-    r = get_radius(r=r, d=d, dflt=1);
-    h = first_defined([cap_h, h]);
-    maxd = 3*r/tan(maxang);
-    anchors = [
-        ["cap", [0,0,h], UP, 0]
-    ];
-    attachable(anchor,spin,orient, r=r, anchors=anchors) {
-        rotate_extrude(convexity=2) {
-            difference() {
-                teardrop2d(r=r, ang=maxang, cap_h=h);
-                left(r) square(size=[2*r,maxd], center=true);
-            }
-        }
-        children();
-    }
-}
-
-
-
-// Section: Miscellaneous
-
-
-// Module: nil()
-//
-// Description:
-//   Useful when you MUST pass a child to a module, but you want it to be nothing.
-module nil() union(){}
-
-
-// Module: noop()
-//
-// Description:
-//   Passes through the children passed to it, with no action at all.
-//   Useful while debugging when you want to replace a command.
-//
-// Arguments:
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-module noop(spin=0, orient=UP) attachable(CENTER,spin,orient, d=0.01) {nil(); children();}
-
-
-// Module: pie_slice()
-//
-// Description:
-//   Creates a pie slice shape.
-//
-// Usage:
-//   pie_slice(ang, l|h, r|d, [center]);
-//   pie_slice(ang, l|h, r1|d1, r2|d2, [center]);
-//
-// Arguments:
-//   ang = pie slice angle in degrees.
-//   h = height of pie slice.
-//   r = radius of pie slice.
-//   r1 = bottom radius of pie slice.
-//   r2 = top radius of pie slice.
-//   d = diameter of pie slice.
-//   d1 = bottom diameter of pie slice.
-//   d2 = top diameter of pie slice.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
-//
-// Example: Cylindrical Pie Slice
-//   pie_slice(ang=45, l=20, r=30);
-// Example: Conical Pie Slice
-//   pie_slice(ang=60, l=20, d1=50, d2=70);
-module pie_slice(
-    ang=30, l=undef,
-    r=undef, r1=undef, r2=undef,
-    d=undef, d1=undef, d2=undef,
-    h=undef, center,
-    anchor, spin=0, orient=UP
-) {
-    l = first_defined([l, h, 1]);
-    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10);
-    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10);
-    maxd = max(r1,r2)+0.1;
-    anchor = get_anchor(anchor, center, BOT, BOT);
-    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
-        difference() {
-            cyl(r1=r1, r2=r2, h=l);
-            if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
-            difference() {
-                fwd(maxd/2) cube([2*maxd, maxd, l+0.2], center=true);
-                if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
-            }
-        }
-        children();
-    }
-}
-
-
-// Module: interior_fillet()
-//
-// Description:
-//   Creates a shape that can be unioned into a concave joint between two faces, to fillet them.
-//   Center this part along the concave edge to be chamfered and union it in.
-//
-// Usage:
-//   interior_fillet(l, r, [ang], [overlap]);
-//
-// Arguments:
-//   l = length of edge to fillet.
-//   r = radius of fillet.
-//   ang = angle between faces to fillet.
-//   overlap = overlap size for unioning with faces.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `FRONT+LEFT`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-//
-// Example:
-//   union() {
-//       translate([0,2,-4]) cube([20, 4, 24], anchor=BOTTOM);
-//       translate([0,-10,-4]) cube([20, 20, 4], anchor=BOTTOM);
-//       color("green") interior_fillet(l=20, r=10, spin=180, orient=RIGHT);
-//   }
-//
-// Example:
-//   interior_fillet(l=40, r=10, spin=-90);
-//
-// Example: Using with Attachments
-//   cube(50,center=true) {
-//     position(FRONT+LEFT)
-//       interior_fillet(l=50, r=10, spin=-90);
-//     position(BOT+FRONT)
-//       interior_fillet(l=50, r=10, spin=180, orient=RIGHT);
-//   }
-module interior_fillet(l=1.0, r=1.0, ang=90, overlap=0.01, anchor=FRONT+LEFT, spin=0, orient=UP) {
-    dy = r/tan(ang/2);
-    steps = ceil(segs(r)*ang/360);
-    step = ang/steps;
-    attachable(anchor,spin,orient, size=[r,r,l]) {
-        linear_extrude(height=l, convexity=4, center=true) {
-            path = concat(
-                [[0,0]],
-                [for (i=[0:1:steps]) let(a=270-i*step) r*[cos(a),sin(a)]+[dy,r]]
-            );
-            translate(-[r,r]/2) polygon(path);
-        }
-        children();
-    }
-}
-
-
-
-// Module: slot()
-//
-// Description:
-//   Makes a linear slot with rounded ends, appropriate for bolts to slide along.
-//
-// Usage:
-//   slot(h, l, r|d, [center]);
-//   slot(h, p1, p2, r|d, [center]);
-//   slot(h, l, r1|d1, r2|d2, [center]);
-//   slot(h, p1, p2, r1|d1, r2|d2, [center]);
-//
-// Arguments:
-//   p1 = center of starting circle of slot.
-//   p2 = center of ending circle of slot.
-//   l = length of slot along the X axis.
-//   h = height of slot shape. (default: 10)
-//   r = radius of slot circle. (default: 5)
-//   r1 = bottom radius of slot cone.
-//   r2 = top radius of slot cone.
-//   d = diameter of slot circle.
-//   d1 = bottom diameter of slot cone.
-//   d2 = top diameter of slot cone.
-//
-// Example: Between Two Points
-//   slot([0,0,0], [50,50,0], r1=5, r2=10, h=5);
-// Example: By Length
-//   slot(l=50, r1=5, r2=10, h=5);
-module slot(
-    p1=undef, p2=undef, h=10, l=undef,
-    r=undef, r1=undef, r2=undef,
-    d=undef, d1=undef, d2=undef
-) {
-    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=5);
-    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=5);
-    sides = quantup(segs(max(r1, r2)), 4);
-    // TODO: implement orient and anchors.
-    hull() line_of(p1=p1, p2=p2, l=l, n=2) cyl(l=h, r1=r1, r2=r2, center=true, $fn=sides);
-}
-
-
-// Module: arced_slot()
-//
-// Description:
-//   Makes an arced slot, appropriate for bolts to slide along.
-//
-// Usage:
-//   arced_slot(h, r|d, sr|sd, [sa], [ea], [center], [$fn2]);
-//   arced_slot(h, r|d, sr1|sd1, sr2|sd2, [sa], [ea], [center], [$fn2]);
-//
-// Arguments:
-//   cp = Centerpoint of slot arc.  Default: `[0, 0, 0]`
-//   h = Height of slot arc shape.  Default: `1`
-//   r = Radius of slot arc.  Default: `0.5`
-//   d = Diameter of slot arc.  Default: `1`
-//   sr = Radius of slot channel.  Default: `0.5`
-//   sd = Diameter of slot channel.  Default: `0.5`
-//   sr1 = Bottom radius of slot channel cone.  Use instead of `sr`.
-//   sr2 = Top radius of slot channel cone.  Use instead of `sr`.
-//   sd1 = Bottom diameter of slot channel cone.  Use instead of `sd`.
-//   sd2 = Top diameter of slot channel cone.  Use instead of `sd`.
-//   sa = Starting angle.  Default: `0`
-//   ea = Ending angle.  Default: `90`
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
-//   $fn2 = The `$fn` value to use on the small round endcaps.  The major arcs are still based on `$fn`.  Default: `$fn`
-//
-// Example(Med): Typical Arced Slot
-//   arced_slot(d=60, h=5, sd=10, sa=60, ea=280);
-// Example(Med): Conical Arced Slot
-//   arced_slot(r=60, h=5, sd1=10, sd2=15, sa=45, ea=180);
-module arced_slot(
-    r=undef, d=undef, h=1.0,
-    sr=undef, sr1=undef, sr2=undef,
-    sd=undef, sd1=undef, sd2=undef,
-    sa=0, ea=90, cp=[0,0,0],
-    anchor=TOP, spin=0, orient=UP,
-    $fn2 = undef
-) {
-    r = get_radius(r=r, d=d, dflt=2);
-    sr1 = get_radius(r1=sr1, r=sr, d1=sd1, d=sd, dflt=2);
-    sr2 = get_radius(r1=sr2, r=sr, d1=sd2, d=sd, dflt=2);
-    fn_minor = first_defined([$fn2, $fn]);
-    da = ea - sa;
-    attachable(anchor,spin,orient, r1=r+sr1, r2=r+sr2, l=h) {
-        translate(cp) {
-            zrot(sa) {
-                difference() {
-                    pie_slice(ang=da, l=h, r1=r+sr1, r2=r+sr2, orient=UP, anchor=CENTER);
-                    cyl(h=h+0.1, r1=r-sr1, r2=r-sr2);
-                }
-                right(r) cyl(h=h, r1=sr1, r2=sr2, $fn=fn_minor);
-                zrot(da) right(r) cyl(h=h, r1=sr1, r2=sr2, $fn=fn_minor);
-            }
-        }
-        children();
-    }
-}
-
-
-// Module: heightfield()
-// Usage:
-//   heightfield(heightfield, [size], [bottom]);
-// Description:
-//   Given a regular rectangular 2D grid of scalar values, generates a 3D surface where the height at
-//   any given point is the scalar value for that position.
-// Arguments:
-//   heightfield = The 2D rectangular array of heights.
-//   size = The [X,Y] size of the surface to create.  If given as a scalar, use it for both X and Y sizes.
-//   bottom = The Z coordinate for the bottom of the heightfield object to create.  Must be less than the minimum heightfield value.  Default: 0
-//   convexity = Max number of times a line could intersect a wall of the surface being formed.
-// Example:
-//   heightfield(size=[100,100], bottom=-20, heightfield=[
-//       for (x=[-180:4:180]) [for(y=[-180:4:180]) 10*cos(3*norm([x,y]))]
-//   ]);
-// Example:
-//   intersection() {
-//       heightfield(size=[100,100], heightfield=[
-//           for (x=[-180:5:180]) [for(y=[-180:5:180]) 10+5*cos(3*x)*sin(3*y)]
-//       ]);
-//       cylinder(h=50,d=100);
-//   }
-module heightfield(heightfield, size=[100,100], bottom=0, convexity=10)
-{
-    size = is_num(size)? [size,size] : point2d(size);
-    dim = array_dim(heightfield);
-    assert(dim.x!=undef);
-    assert(dim.y!=undef);
-    assert(bottom<min(flatten(heightfield)), "bottom must be less than the minimum heightfield value.");
-    spacing = vdiv(size,dim-[1,1]);
-    vertices = concat(
-        [
-            for (i=[0:1:dim.x-1], j=[0:1:dim.y-1]) let(
-                pos = [i*spacing.x-size.x/2, j*spacing.y-size.y/2, heightfield[i][j]]
-            ) pos
-        ], [
-            for (i=[0:1:dim.x-1]) let(
-                pos = [i*spacing.x-size.x/2, -size.y/2, bottom]
-            ) pos
-        ], [
-            for (i=[0:1:dim.x-1]) let(
-                pos = [i*spacing.x-size.x/2, size.y/2, bottom]
-            ) pos
-        ], [
-            for (j=[0:1:dim.y-1]) let(
-                pos = [-size.x/2, j*spacing.y-size.y/2, bottom]
-            ) pos
-        ], [
-            for (j=[0:1:dim.y-1]) let(
-                pos = [size.x/2, j*spacing.y-size.y/2, bottom]
-            ) pos
-        ]
-    );
-    faces = concat(
-        [
-            for (i=[0:1:dim.x-2], j=[0:1:dim.y-2]) let(
-                idx1 = (i+0)*dim.y + j+0,
-                idx2 = (i+0)*dim.y + j+1,
-                idx3 = (i+1)*dim.y + j+0,
-                idx4 = (i+1)*dim.y + j+1
-            ) each [[idx1, idx2, idx4], [idx1, idx4, idx3]]
-        ], [
-            for (i=[0:1:dim.x-2]) let(
-                idx1 = dim.x*dim.y,
-                idx2 = dim.x*dim.y+dim.x+i,
-                idx3 = idx2+1
-            ) [idx1,idx3,idx2]
-        ], [
-            for (i=[0:1:dim.y-2]) let(
-                idx1 = dim.x*dim.y,
-                idx2 = dim.x*dim.y+dim.x*2+dim.y+i,
-                idx3 = idx2+1
-            ) [idx1,idx2,idx3]
-        ], [
-            for (i=[0:1:dim.x-2]) let(
-                idx1 = (i+0)*dim.y+0,
-                idx2 = (i+1)*dim.y+0,
-                idx3 = dim.x*dim.y+i,
-                idx4 = idx3+1
-            ) each [[idx1, idx2, idx4], [idx1, idx4, idx3]]
-        ], [
-            for (i=[0:1:dim.x-2]) let(
-                idx1 = (i+0)*dim.y+dim.y-1,
-                idx2 = (i+1)*dim.y+dim.y-1,
-                idx3 = dim.x*dim.y+dim.x+i,
-                idx4 = idx3+1
-            ) each [[idx1, idx4, idx2], [idx1, idx3, idx4]]
-        ], [
-            for (j=[0:1:dim.y-2]) let(
-                idx1 = j,
-                idx2 = j+1,
-                idx3 = dim.x*dim.y+dim.x*2+j,
-                idx4 = idx3+1
-            ) each [[idx1, idx4, idx2], [idx1, idx3, idx4]]
-        ], [
-            for (j=[0:1:dim.y-2]) let(
-                idx1 = (dim.x-1)*dim.y+j,
-                idx2 = idx1+1,
-                idx3 = dim.x*dim.y+dim.x*2+dim.y+j,
-                idx4 = idx3+1
-            ) each [[idx1, idx2, idx4], [idx1, idx4, idx3]]
-        ]
-    );
-    polyhedron(points=vertices, faces=faces, convexity=convexity);
-}
-
-
-
-// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/shapes2d.scad b/shapes2d.scad
deleted file mode 100644
index f328952..0000000
--- a/shapes2d.scad
+++ /dev/null
@@ -1,1866 +0,0 @@
-//////////////////////////////////////////////////////////////////////
-// LibFile: shapes2d.scad
-//   Common useful 2D shapes.
-//   To use, add the following lines to the beginning of your file:
-//   ```
-//   include <BOSL2/std.scad>
-//   ```
-//////////////////////////////////////////////////////////////////////
-
-
-// Section: 2D Drawing Helpers
-
-// Module: stroke()
-// Usage:
-//   stroke(path, [width], [closed], [endcaps], [endcap_width], [endcap_length], [endcap_extent], [trim]);
-//   stroke(path, [width], [closed], [endcap1], [endcap2], [endcap_width1], [endcap_width2], [endcap_length1], [endcap_length2], [endcap_extent1], [endcap_extent2], [trim1], [trim2]);
-// Description:
-//   Draws a 2D or 3D path with a given line width.  Endcaps can be specified for each end individually.
-// Figure(2D,Big): Endcap Types
-//   endcaps = [
-//       ["butt", "square", "round", "chisel", "tail", "tail2"],
-//       ["line", "cross", "dot", "diamond", "x", "arrow", "arrow2"]
-//   ];
-//   for (x=idx(endcaps), y=idx(endcaps[x])) {
-//       cap = endcaps[x][y];
-//       right(x*60-60+5) fwd(y*10+15) {
-//           right(28) color("black") text(text=cap, size=5, halign="left", valign="center");
-//           stroke([[0,0], [20,0]], width=3, endcap_width=3, endcap1=false, endcap2=cap);
-//           color("black") stroke([[0,0], [20,0]], width=0.25, endcaps=false);
-//       }
-//   }
-// Arguments:
-//   path = The 2D path to draw along.
-//   width = The width of the line to draw.  If given as a list of widths, (one for each path point), draws the line with varying thickness to each point.
-//   closed = If true, draw an additional line from the end of the path to the start.
-//   endcaps = Specifies the endcap type for both ends of the line.  If a 2D path is given, use that to draw custom endcaps.
-//   endcap1 = Specifies the endcap type for the start of the line.  If a 2D path is given, use that to draw a custom endcap.
-//   endcap2 = Specifies the endcap type for the end of the line.  If a 2D path is given, use that to draw a custom endcap.
-//   endcap_width = Some endcap types are wider than the line.  This specifies the size of endcaps, in multiples of the line width.  Default: 3.5
-//   endcap_width1 = This specifies the size of starting endcap, in multiples of the line width.  Default: 3.5
-//   endcap_width2 = This specifies the size of ending endcap, in multiples of the line width.  Default: 3.5
-//   endcap_length = Length of endcaps, in multiples of the line width.  Default: `endcap_width*0.5`
-//   endcap_length1 = Length of starting endcap, in multiples of the line width.  Default: `endcap_width1*0.5`
-//   endcap_length2 = Length of ending endcap, in multiples of the line width.  Default: `endcap_width2*0.5`
-//   endcap_extent = Extents length of endcaps, in multiples of the line width.  Default: `endcap_width*0.5`
-//   endcap_extent1 = Extents length of starting endcap, in multiples of the line width.  Default: `endcap_width1*0.5`
-//   endcap_extent2 = Extents length of ending endcap, in multiples of the line width.  Default: `endcap_width2*0.5`
-//   endcap_angle = Extra axial rotation given to flat endcaps for 3D paths, in degrees.  If not given, the endcaps are fully spun.  Default: `undef` (Fully spun cap)
-//   endcap_angle1 = Extra axial rotation given to a flat starting endcap for 3D paths, in degrees.  If not given, the endcap is fully spun.  Default: `undef` (Fully spun cap)
-//   endcap_angle2 = Extra axial rotation given to a flat ending endcap for 3D paths, in degrees.  If not given, the endcap is fully spun.  Default: `undef` (Fully spun cap)
-//   trim = Trim the the start and end line segments by this much, to keep them from interfering with custom endcaps.
-//   trim1 = Trim the the starting line segment by this much, to keep it from interfering with a custom endcap.
-//   trim2 = Trim the the ending line segment by this much, to keep it from interfering with a custom endcap.
-//   convexity = Max number of times a line could intersect a wall of an endcap.
-//   hull = If true, use `hull()` to make higher quality joints between segments, at the cost of being much slower.  Default: true
-// Example(2D): Drawing a Path
-//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
-//   stroke(path, width=20);
-// Example(2D): Closing a Path
-//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
-//   stroke(path, width=20, endcaps=true, closed=true);
-// Example(2D): Fancy Arrow Endcaps
-//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
-//   stroke(path, width=10, endcaps="arrow2");
-// Example(2D): Modified Fancy Arrow Endcaps
-//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
-//   stroke(path, width=10, endcaps="arrow2", endcap_width=6, endcap_length=3, endcap_extent=2);
-// Example(2D): Mixed Endcaps
-//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
-//   stroke(path, width=10, endcap1="tail2", endcap2="arrow2");
-// Example(2D): Custom Endcap Shapes
-//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
-//   arrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
-//   stroke(path, width=10, trim=3.5, endcaps=arrow);
-// Example(2D): Variable Line Width
-//   path = circle(d=50,$fn=18);
-//   widths = [for (i=idx(path)) 10*i/len(path)+2];
-//   stroke(path,width=widths,$fa=1,$fs=1);
-// Example: 3D Path with Endcaps
-//   path = rot([15,30,0], p=path3d(pentagon(d=50)));
-//   stroke(path, width=2, endcaps="arrow2", $fn=18);
-// Example: 3D Path with Flat Endcaps
-//   path = rot([15,30,0], p=path3d(pentagon(d=50)));
-//   stroke(path, width=2, endcaps="arrow2", endcap_angle=0, $fn=18);
-// Example: 3D Path with Mixed Endcaps
-//   path = rot([15,30,0], p=path3d(pentagon(d=50)));
-//   stroke(path, width=2, endcap1="arrow2", endcap2="tail", endcap_angle2=0, $fn=18);
-module stroke(
-    path, width=1, closed=false,
-    endcaps, endcap1, endcap2,
-    trim, trim1, trim2,
-    endcap_width, endcap_width1, endcap_width2,
-    endcap_length, endcap_length1, endcap_length2,
-    endcap_extent, endcap_extent1, endcap_extent2,
-    endcap_angle, endcap_angle1, endcap_angle2,
-    convexity=10, hull=true
-) {
-    function _endcap_shape(cap,linewidth,w,l,l2) = (
-        let(sq2=sqrt(2), l3=l-l2)
-        (cap=="round" || cap==true)? circle(d=1, $fn=max(8, segs(w/2))) :
-        cap=="chisel"? [[-0.5,0], [0,0.5], [0.5,0], [0,-0.5]] :
-        cap=="square"? [[-0.5,-0.5], [-0.5,0.5], [0.5,0.5], [0.5,-0.5]] :
-        cap=="diamond"? [[0,w/2], [w/2,0], [0,-w/2], [-w/2,0]] :
-        cap=="dot"?    circle(d=3, $fn=max(12, segs(w*3/2))) :
-        cap=="x"?      [for (a=[0:90:270]) each rot(a,p=[[w+sq2/2,w-sq2/2]/2, [w-sq2/2,w+sq2/2]/2, [0,sq2/2]]) ] :
-        cap=="cross"?  [for (a=[0:90:270]) each rot(a,p=[[1,w]/2, [-1,w]/2, [-1,1]/2]) ] :
-        cap=="line"?   [[w/2,0.5], [w/2,-0.5], [-w/2,-0.5], [-w/2,0.5]] :
-        cap=="arrow"?  [[0,0], [w/2,-l2], [w/2,-l2-l], [0,-l], [-w/2,-l2-l], [-w/2,-l2]] :
-        cap=="arrow2"? [[0,0], [w/2,-l2-l], [0,-l], [-w/2,-l2-l]] :
-        cap=="tail"?   [[0,0], [w/2,l2], [w/2,l2-l], [0,-l], [-w/2,l2-l], [-w/2,l2]] :
-        cap=="tail2"?  [[w/2,0], [w/2,-l], [0,-l-l2], [-w/2,-l], [-w/2,0]] :
-        is_path(cap)? cap :
-        []
-    ) * linewidth;
-
-    assert(is_bool(closed));
-    assert(is_list(path));
-    if (len(path) > 1) {
-        assert(is_path(path,[2,3]), "The path argument must be a list of 2D or 3D points.");
-    }
-    path = deduplicate( closed? close_path(path) : path );
-
-    assert(is_num(width) || (is_vector(width) && len(width)==len(path)));
-    width = is_num(width)? [for (x=path) width] : width;
-
-    endcap1 = first_defined([endcap1, endcaps, "round"]);
-    endcap2 = first_defined([endcap2, endcaps, "round"]);
-    assert(is_bool(endcap1) || is_string(endcap1) || is_path(endcap1));
-    assert(is_bool(endcap2) || is_string(endcap2) || is_path(endcap2));
-
-    endcap_width1 = first_defined([endcap_width1, endcap_width, 3.5]);
-    endcap_width2 = first_defined([endcap_width2, endcap_width, 3.5]);
-    assert(is_num(endcap_width1));
-    assert(is_num(endcap_width2));
-
-    endcap_length1 = first_defined([endcap_length1, endcap_length, endcap_width1*0.5]);
-    endcap_length2 = first_defined([endcap_length2, endcap_length, endcap_width2*0.5]);
-    assert(is_num(endcap_length1));
-    assert(is_num(endcap_length2));
-
-    endcap_extent1 = first_defined([endcap_extent1, endcap_extent, endcap_width1*0.5]);
-    endcap_extent2 = first_defined([endcap_extent2, endcap_extent, endcap_width2*0.5]);
-    assert(is_num(endcap_extent1));
-    assert(is_num(endcap_extent2));
-
-    endcap_angle1 = first_defined([endcap_angle1, endcap_angle]);
-    endcap_angle2 = first_defined([endcap_angle2, endcap_angle]);
-    assert(is_undef(endcap_angle1)||is_num(endcap_angle1));
-    assert(is_undef(endcap_angle2)||is_num(endcap_angle2));
-
-    endcap_shape1 = _endcap_shape(endcap1, select(width,0), endcap_width1, endcap_length1, endcap_extent1);
-    endcap_shape2 = _endcap_shape(endcap2, select(width,-1), endcap_width2, endcap_length2, endcap_extent2);
-
-    trim1 = select(width,0) * first_defined([
-        trim1, trim,
-        (endcap1=="arrow")? endcap_length1-0.01 :
-        (endcap1=="arrow2")? endcap_length1*3/4 :
-        0
-    ]);
-    assert(is_num(trim1));
-
-    trim2 = select(width,-1) * first_defined([
-        trim2, trim,
-        (endcap2=="arrow")? endcap_length2-0.01 :
-        (endcap2=="arrow2")? endcap_length2*3/4 :
-        0
-    ]);
-    assert(is_num(trim2));
-
-    if (len(path) == 1) {
-        if (len(path[0]) == 2) {
-            translate(path[0]) circle(d=width[0]);
-        } else {
-            translate(path[0]) sphere(d=width[0]);
-        }
-    } else {
-        spos = path_pos_from_start(path,trim1,closed=false);
-        epos = path_pos_from_end(path,trim2,closed=false);
-        path2 = path_subselect(path, spos[0], spos[1], epos[0], epos[1]);
-        widths = concat(
-            [lerp(width[spos[0]], width[(spos[0]+1)%len(width)], spos[1])],
-            [for (i = [spos[0]+1:1:epos[0]]) width[i]],
-            [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);
-
-        if (len(path[0]) == 2) {
-            // Straight segments
-            for (i = idx(path2,end=-2)) {
-                seg = select(path2,i,i+1);
-                delt = seg[1] - seg[0];
-                translate(seg[0]) {
-                    rot(from=BACK,to=delt) {
-                        trapezoid(w1=widths[i], w2=widths[i+1], h=norm(delt), anchor=FRONT);
-                    }
-                }
-            }
-
-            // Joints
-            for (i = [1:1:len(path2)-2]) {
-                $fn = quantup(segs(widths[i]/2),4);
-                if (hull) {
-                    hull() {
-                        translate(path2[i]) {
-                            rot(from=BACK, to=path2[i]-path2[i-1])
-                                circle(d=widths[i]);
-                            rot(from=BACK, to=path2[i+1]-path2[i])
-                                circle(d=widths[i]);
-                        }
-                    }
-                } else {
-                    translate(path2[i]) {
-                        rot(from=BACK, to=path2[i]-path2[i-1])
-                            circle(d=widths[i]);
-                        rot(from=BACK, to=path2[i+1]-path2[i])
-                            circle(d=widths[i]);
-                    }
-                }
-            }
-
-            // Endcap1
-            translate(path[0]) {
-                start_vec = select(path,0) - select(path,1);
-                rot(from=BACK, to=start_vec) {
-                    polygon(endcap_shape1);
-                }
-            }
-
-            // Endcap2
-            translate(select(path,-1)) {
-                rot(from=BACK, to=end_vec) {
-                    polygon(endcap_shape2);
-                }
-            }
-        } else {
-            quatsums = Q_Cumulative([
-                for (i = idx(path2,end=-2)) let(
-                    vec1 = i==0? UP : unit(path2[i]-path2[i-1], UP),
-                    vec2 = unit(path2[i+1]-path2[i], UP),
-                    axis = vector_axis(vec1,vec2),
-                    ang = vector_angle(vec1,vec2)
-                ) Quat(axis,ang)
-            ]);
-            rotmats = [for (q=quatsums) Q_Matrix4(q)];
-            sides = [
-                for (i = idx(path2,end=-2))
-                quantup(segs(max(widths[i],widths[i+1])/2),4)
-            ];
-
-            // Straight segments
-            for (i = idx(path2,end=-2)) {
-                dist = norm(path2[i+1] - path2[i]);
-                w1 = widths[i]/2;
-                w2 = widths[i+1]/2;
-                $fn = sides[i];
-                translate(path2[i]) {
-                    multmatrix(rotmats[i]) {
-                        cylinder(r1=w1, r2=w2, h=dist, center=false);
-                    }
-                }
-            }
-
-            // Joints
-            for (i = [1:1:len(path2)-2]) {
-                $fn = sides[i];
-                translate(path2[i]) {
-                    if (hull) {
-                        hull(){
-                            multmatrix(rotmats[i]) {
-                                sphere(d=widths[i]);
-                            }
-                            multmatrix(rotmats[i-1]) {
-                                sphere(d=widths[i]);
-                            }
-                        }
-                    } else {
-                        multmatrix(rotmats[i]) {
-                            sphere(d=widths[i]);
-                        }
-                        multmatrix(rotmats[i-1]) {
-                            sphere(d=widths[i]);
-                        }
-                    }
-                }
-            }
-
-            // Endcap1
-            translate(path[0]) {
-                multmatrix(rotmats[0] * xrot(180)) {
-                    $fn = sides[0];
-                    if (is_undef(endcap_angle1)) {
-                        rotate_extrude(convexity=convexity) {
-                            right_half(planar=true) {
-                                polygon(endcap_shape1);
-                            }
-                        }
-                    } else {
-                        rotate([90,0,endcap_angle1]) {
-                            linear_extrude(height=widths[0], center=true, convexity=convexity) {
-                                polygon(endcap_shape1);
-                            }
-                        }
-                    }
-                }
-            }
-
-            // Endcap2
-            translate(select(path,-1)) {
-                multmatrix(select(rotmats,-1)) {
-                    $fn = select(sides,-1);
-                    if (is_undef(endcap_angle2)) {
-                        rotate_extrude(convexity=convexity) {
-                            right_half(planar=true) {
-                                polygon(endcap_shape2);
-                            }
-                        }
-                    } else {
-                        rotate([90,0,endcap_angle2]) {
-                            linear_extrude(height=select(widths,-1), center=true, convexity=convexity) {
-                                polygon(endcap_shape2);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-
-// Function&Module: arc()
-// Usage: 2D arc from 0º to `angle` degrees.
-//   arc(N, r|d, angle);
-// Usage: 2D arc from START to END degrees.
-//   arc(N, r|d, angle=[START,END])
-// Usage: 2D arc from `start` to `start+angle` degrees.
-//   arc(N, r|d, start, angle)
-// Usage: 2D circle segment by `width` and `thickness`, starting and ending on the X axis.
-//   arc(N, width, thickness)
-// Usage: Shortest 2D or 3D arc around centerpoint `cp`, starting at P0 and ending on the vector pointing from `cp` to `P1`.
-//   arc(N, cp, points=[P0,P1],[long],[cw],[ccw])
-// Usage: 2D or 3D arc, starting at `P0`, passing through `P1` and ending at `P2`.
-//   arc(N, points=[P0,P1,P2])
-// Description:
-//   If called as a function, returns a 2D or 3D path forming an arc.
-//   If called as a module, creates a 2D arc polygon or pie slice shape.
-// Arguments:
-//   N = Number of vertices to form the arc curve from.
-//   r = Radius of the arc.
-//   d = Diameter of the arc.
-//   angle = If a scalar, specifies the end angle in degrees.  If a vector of two scalars, specifies start and end angles.
-//   cp = Centerpoint of arc.
-//   points = Points on the arc.
-//   long = if given with cp and points takes the long arc instead of the default short arc.  Default: false
-//   cw = if given with cp and 2 points takes the arc in the clockwise direction.  Default: false
-//   ccw = if given with cp and 2 points takes the arc in the counter-clockwise direction.  Default: false
-//   width = If given with `thickness`, arc starts and ends on X axis, to make a circle segment.
-//   thickness = If given with `width`, arc starts and ends on X axis, to make a circle segment.
-//   start = Start angle of arc.
-//   wedge = If true, include centerpoint `cp` in output to form pie slice shape.
-// Examples(2D):
-//   arc(N=4, r=30, angle=30, wedge=true);
-//   arc(r=30, angle=30, wedge=true);
-//   arc(d=60, angle=30, wedge=true);
-//   arc(d=60, angle=120);
-//   arc(d=60, angle=120, wedge=true);
-//   arc(r=30, angle=[75,135], wedge=true);
-//   arc(r=30, start=45, angle=75, wedge=true);
-//   arc(width=60, thickness=20);
-//   arc(cp=[-10,5], points=[[20,10],[0,35]], wedge=true);
-//   arc(points=[[30,-5],[20,10],[-10,20]], wedge=true);
-//   arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
-// Example(2D):
-//   path = arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
-//   stroke(closed=true, path);
-// Example(FlatSpin):
-//   path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]);
-//   trace_polyline(path, showpts=true, color="cyan");
-function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false) =
-    // First try for 2D arc specified by width and thickness
-    is_def(width) && is_def(thickness)? (
-                assert(!any_defined([r,cp,points]) && !any([cw,ccw,long]),"Conflicting or invalid parameters to arc")
-                assert(width>0, "Width must be postive")
-                assert(thickness>0, "Thickness must be positive")
-        arc(N,points=[[width/2,0], [0,thickness], [-width/2,0]],wedge=wedge)
-    ) : is_def(angle)? (
-        let(
-            parmok = !any_defined([points,width,thickness]) &&
-                ((is_vector(angle,2) && is_undef(start)) || is_num(angle))
-        )
-        assert(parmok,"Invalid parameters in arc")
-        let(
-            cp = first_defined([cp,[0,0]]),
-            start = is_def(start)? start : is_vector(angle) ? angle[0] : 0,
-            angle = is_vector(angle)? angle[1]-angle[0] : angle,
-            r = get_radius(r=r, d=d)
-                )
-                assert(is_vector(cp,2),"Centerpoint must be a 2d vector")
-                assert(angle!=0, "Arc has zero length")
-                assert(r>0, "Arc radius invalid")
-                let(
-            N = max(3, is_undef(N)? ceil(segs(r)*abs(angle)/360) : N),
-            arcpoints = [for(i=[0:N-1]) let(theta = start + i*angle/(N-1)) r*[cos(theta),sin(theta)]+cp],
-            extra = wedge? [cp] : []
-        )
-        concat(extra,arcpoints)
-    ) :
-          assert(is_path(points,[2,3]),"Point list is invalid")
-        // Arc is 3D, so transform points to 2D and make a recursive call, then remap back to 3D
-         len(points[0])==3? (
-                assert(!(cw || ccw), "(Counter)clockwise isn't meaningful in 3d, so `cw` and `ccw` must be false")
-                assert(is_undef(cp) || is_vector(cp,3),"points are 3d so cp must be 3d")
-        let(
-            thirdpoint = is_def(cp) ? cp : points[2],
-            center2d = is_def(cp) ? project_plane(cp,thirdpoint,points[0],points[1]) : undef,
-            points2d = project_plane(points,thirdpoint,points[0],points[1])
-        )
-        lift_plane(arc(N,cp=center2d,points=points2d,wedge=wedge,long=long),thirdpoint,points[0],points[1])
-    ) : is_def(cp)? (
-        // Arc defined by center plus two points, will have radius defined by center and points[0]
-        // and extent defined by direction of point[1] from the center
-                assert(is_vector(cp,2), "Centerpoint must be a 2d vector")
-                assert(len(points)==2, "When pointlist has length 3 centerpoint is not allowed")
-                assert(points[0]!=points[1], "Arc endpoints are equal")
-                assert(cp!=points[0]&&cp!=points[1], "Centerpoint equals an arc endpoint")
-                assert(count_true([long,cw,ccw])<=1, str("Only one of `long`, `cw` and `ccw` can be true",cw,ccw,long))
-        let(    
-            angle = vector_angle(points[0], cp, points[1]),
-            v1 = points[0]-cp,
-            v2 = points[1]-cp,
-            prelim_dir = sign(det2([v1,v2])),   // z component of cross product
-                        dir = prelim_dir != 0
-                                  ? prelim_dir
-                                  : assert(cw || ccw, "Collinear inputs don't define a unique arc")
-                                    1,
-            r=norm(v1),
-                        final_angle = long || (ccw && dir<0) || (cw && dir>0) ? -dir*(360-angle) : dir*angle
-        )
-        arc(N,cp=cp,r=r,start=atan2(v1.y,v1.x),angle=final_angle,wedge=wedge)
-    ) : (
-        // Final case is arc passing through three points, starting at point[0] and ending at point[3]
-        let(col = collinear(points[0],points[1],points[2]))
-        assert(!col, "Collinear inputs do not define an arc")
-        let(
-            cp = line_intersection(_normal_segment(points[0],points[1]),_normal_segment(points[1],points[2])),
-            // select order to be counterclockwise
-            dir = det2([points[1]-points[0],points[2]-points[1]]) > 0,
-            points = dir? select(points,[0,2]) : select(points,[2,0]),
-            r = norm(points[0]-cp),
-            theta_start = atan2(points[0].y-cp.y, points[0].x-cp.x),
-            theta_end = atan2(points[1].y-cp.y, points[1].x-cp.x),
-            angle = posmod(theta_end-theta_start, 360),
-            arcpts = arc(N,cp=cp,r=r,start=theta_start,angle=angle,wedge=wedge)
-        )
-        dir ? arcpts : reverse(arcpts)
-    );
-
-
-module arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false)
-{
-    path = arc(N=N, r=r, angle=angle, d=d, cp=cp, points=points, width=width, thickness=thickness, start=start, wedge=wedge);
-    polygon(path);
-}
-
-
-function _normal_segment(p1,p2) =
-    let(center = (p1+p2)/2)
-    [center, center + norm(p1-p2)/2 * line_normal(p1,p2)];
-
-
-// Function: turtle()
-// Usage:
-//   turtle(commands, [state], [return_state])
-// Description:
-//   Use a sequence of turtle graphics commands to generate a path.  The parameter `commands` is a list of
-//   turtle commands and optional parameters for each command.  The turtle state has a position, movement direction,
-//   movement distance, and default turn angle.  If you do not give `state` as input then the turtle starts at the
-//   origin, pointed along the positive x axis with a movement distance of 1.  By default, `turtle` returns just
-//   the computed turtle path.  If you set `full_state` to true then it instead returns the full turtle state.
-//   You can invoke `turtle` again with this full state to continue the turtle path where you left off.
-//   
-//   The turtle state is a list with three entries: the path constructed so far, the current step as a 2-vector, and the current default angle.
-//   
-//   For the list below, `dist` is the current movement distance.
-//   
-//   Commands     | Arguments          | What it does
-//   ------------ | ------------------ | -------------------------------
-//   "move"       | [dist]             | Move turtle scale*dist units in the turtle direction.  Default dist=1.  
-//   "xmove"      | [dist]             | Move turtle scale*dist units in the x direction. Default dist=1.  Does not change turtle direction.
-//   "ymove"      | [dist]             | Move turtle scale*dist units in the y direction. Default dist=1.  Does not change turtle direction.
-//   "xymove"     | vector             | Move turtle by the specified vector.  Does not change turtle direction. 
-//   "untilx"     | xtarget            | Move turtle in turtle direction until x==xtarget.  Produces an error if xtarget is not reachable.
-//   "untily"     | ytarget            | Move turtle in turtle direction until y==ytarget.  Produces an error if xtarget is not reachable.
-//   "jump"       | point              | Move the turtle to the specified point
-//   "xjump"      | x                  | Move the turtle's x position to the specified value
-//   "yjump       | y                  | Move the turtle's y position to the specified value
-//   "turn"       | [angle]            | Turn turtle direction by specified angle, or the turtle's default turn angle.  The default angle starts at 90.
-//   "left"       | [angle]            | Same as "turn"
-//   "right"      | [angle]            | Same as "turn", -angle
-//   "angle"      | angle              | Set the default turn angle.
-//   "setdir"     | dir                | Set turtle direction.  The parameter `dir` can be an angle or a vector.
-//   "length"     | length             | Change the turtle move distance to `length`
-//   "scale"      | factor             | Multiply turtle move distance by `factor`
-//   "addlength"  | length             | Add `length` to the turtle move distance
-//   "repeat"     | count, commands    | Repeats a list of commands `count` times.
-//   "arcleft"    | radius, [angle]    | Draw an arc from the current position toward the left at the specified radius and angle.  The turtle turns by `angle`.  A negative angle draws the arc to the right instead of the left, and leaves the turtle facing right.  A negative radius draws the arc to the right but leaves the turtle facing left.  
-//   "arcright"   | radius, [angle]    | Draw an arc from the current position toward the right at the specified radius and angle
-//   "arcleftto"  | radius, angle      | Draw an arc at the given radius turning toward the left until reaching the specified absolute angle.  
-//   "arcrightto" | radius, angle      | Draw an arc at the given radius turning toward the right until reaching the specified absolute angle.  
-//   "arcsteps"   | count              | Specifies the number of segments to use for drawing arcs.  If you set it to zero then the standard `$fn`, `$fa` and `$fs` variables define the number of segments.  
-//
-// Arguments:
-//   commands = List of turtle commands
-//   state = Starting turtle state (from previous call) or starting point.  Default: start at the origin, pointing right.
-//   full_state = If true return the full turtle state for continuing the path in subsequent turtle calls.  Default: false
-//   repeat = Number of times to repeat the command list.  Default: 1
-//
-// Example(2D): Simple rectangle
-//   path = turtle(["xmove",3, "ymove", "xmove",-3, "ymove",-1]);
-//   stroke(path,width=.1);
-// Example(2D): Pentagon
-//   path=turtle(["angle",360/5,"move","turn","move","turn","move","turn","move"]);
-//   stroke(path,width=.1,closed=true);
-// Example(2D): Pentagon using the repeat argument
-//   path=turtle(["move","turn",360/5],repeat=5);
-//   stroke(path,width=.1,closed=true);
-// Example(2D): Pentagon using the repeat turtle command, setting the turn angle
-//   path=turtle(["angle",360/5,"repeat",5,["move","turn"]]);
-//   stroke(path,width=.1,closed=true);
-// Example(2D): Pentagram
-//   path = turtle(["move","left",144], repeat=4);
-//   stroke(path,width=.05,closed=true);
-// Example(2D): Sawtooth path
-//   path = turtle([
-//       "turn", 55,
-//       "untily", 2,
-//       "turn", -55-90,
-//       "untily", 0,
-//       "turn", 55+90,
-//       "untily", 2.5,
-//       "turn", -55-90,
-//       "untily", 0,
-//       "turn", 55+90,
-//       "untily", 3,
-//       "turn", -55-90,
-//       "untily", 0
-//   ]);
-//   stroke(path, width=.1);
-// Example(2D): Simpler way to draw the sawtooth.  The direction of the turtle is preserved when executing "yjump".
-//   path = turtle([
-//       "turn", 55,
-//       "untily", 2,
-//       "yjump", 0,
-//       "untily", 2.5,
-//       "yjump", 0,
-//       "untily", 3,
-//       "yjump", 0,
-//   ]);
-//   stroke(path, width=.1);
-// Example(2DMed): square spiral
-//   path = turtle(["move","left","addlength",1],repeat=50);
-//   stroke(path,width=.2);
-// Example(2DMed): pentagonal spiral
-//   path = turtle(["move","left",360/5,"addlength",1],repeat=50);
-//   stroke(path,width=.2);
-// Example(2DMed): yet another spiral, without using `repeat`
-//   path = turtle(concat(["angle",71],flatten(repeat(["move","left","addlength",1],50))));
-//   stroke(path,width=.2);
-// Example(2DMed): The previous spiral grows linearly and eventually intersects itself.  This one grows geometrically and does not.
-//   path = turtle(["move","left",71,"scale",1.05],repeat=50);
-//   stroke(path,width=.05);
-// Example(2D): Koch Snowflake
-//   function koch_unit(depth) =
-//       depth==0 ? ["move"] :
-//       concat(
-//           koch_unit(depth-1),
-//           ["right"],
-//           koch_unit(depth-1),
-//           ["left","left"],
-//           koch_unit(depth-1),
-//           ["right"],
-//           koch_unit(depth-1)
-//       );
-//   koch=concat(["angle",60,"repeat",3],[concat(koch_unit(3),["left","left"])]);
-//   polygon(turtle(koch));
-function turtle(commands, state=[[[0,0]],[1,0],90,0], full_state=false, repeat=1) =
-    let( state = is_vector(state) ? [[state],[1,0],90,0] : state )
-        repeat == 1?
-            _turtle(commands,state,full_state) :
-            _turtle_repeat(commands, state, full_state, repeat);
-
-function _turtle_repeat(commands, state, full_state, repeat) =
-    repeat==1?
-        _turtle(commands,state,full_state) :
-        _turtle_repeat(commands, _turtle(commands, state, true), full_state, repeat-1);
-
-function _turtle_command_len(commands, index) =
-    let( one_or_two_arg = ["arcleft","arcright", "arcleftto", "arcrightto"] )
-    commands[index] == "repeat"? 3 :   // Repeat command requires 2 args
-    // For these, the first arg is required, second arg is present if it is not a string
-    in_list(commands[index], one_or_two_arg) && len(commands)>index+2 && !is_string(commands[index+2]) ? 3 :  
-    is_string(commands[index+1])? 1 :  // If 2nd item is a string it's must be a new command
-    2;                                 // Otherwise we have command and arg
-
-function _turtle(commands, state, full_state, index=0) =
-    index < len(commands) ?
-    _turtle(commands,
-            _turtle_command(commands[index],commands[index+1],commands[index+2],state,index),
-            full_state,
-            index+_turtle_command_len(commands,index)
-        ) :
-        ( full_state ? state : state[0] );
-
-// Turtle state: state = [path, step_vector, default angle]
-
-function _turtle_command(command, parm, parm2, state, index) =
-    command == "repeat"?
-        assert(is_num(parm),str("\"repeat\" command requires a numeric repeat count at index ",index))
-        assert(is_list(parm2),str("\"repeat\" command requires a command list parameter at index ",index))
-        _turtle_repeat(parm2, state, true, parm) :
-    let(
-        path = 0,
-        step=1,
-        angle=2,
-        arcsteps=3,
-        parm = !is_string(parm) ? parm : undef,
-        parm2 = !is_string(parm2) ? parm2 : undef,
-        needvec = ["jump", "xymove"],
-        neednum = ["untilx","untily","xjump","yjump","angle","length","scale","addlength"],
-        needeither = ["setdir"],
-        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)
-    )
-    assert(chvec,str("\"",command,"\" requires a vector parameter at index ",index))
-    assert(chnum,str("\"",command,"\" requires a numeric parameter at index ",index))
-    assert(vec_or_num,str("\"",command,"\" requires a vector or numeric parameter at index ",index))
-
-    command=="move" ? list_set(state, path, concat(state[path],[default(parm,1)*state[step]+lastpt])) :
-    command=="untilx" ? (
-        let(
-            int = line_intersection([lastpt,lastpt+state[step]], [[parm,0],[parm,1]]),
-            xgood = sign(state[step].x) == sign(int.x-lastpt.x)
-        )
-        assert(xgood,str("\"untilx\" never reaches desired goal at index ",index))
-        list_set(state,path,concat(state[path],[int]))
-    ) :
-    command=="untily" ? (
-        let(
-            int = line_intersection([lastpt,lastpt+state[step]], [[0,parm],[1,parm]]),
-            ygood = is_def(int) && sign(state[step].y) == sign(int.y-lastpt.y)
-        )
-        assert(ygood,str("\"untily\" never reaches desired goal at index ",index))
-        list_set(state,path,concat(state[path],[int]))
-    ) :
-    command=="xmove" ? list_set(state, path, concat(state[path],[default(parm,1)*norm(state[step])*[1,0]+lastpt])):
-    command=="ymove" ? list_set(state, path, concat(state[path],[default(parm,1)*norm(state[step])*[0,1]+lastpt])):
-        command=="xymove" ? list_set(state, path, concat(state[path], [lastpt+parm])):
-    command=="jump" ?  list_set(state, path, concat(state[path],[parm])):
-    command=="xjump" ? list_set(state, path, concat(state[path],[[parm,lastpt.y]])):
-    command=="yjump" ? list_set(state, path, concat(state[path],[[lastpt.x,parm]])):
-    command=="turn" || command=="left" ? list_set(state, step, rot(default(parm,state[angle]),p=state[step],planar=true)) :
-    command=="right" ? list_set(state, step, rot(-default(parm,state[angle]),p=state[step],planar=true)) :
-    command=="angle" ? list_set(state, angle, parm) :
-    command=="setdir" ? (
-        is_vector(parm) ?
-            list_set(state, step, norm(state[step]) * unit(parm)) :
-            list_set(state, step, norm(state[step]) * [cos(parm),sin(parm)])
-    ) :
-    command=="length" ? list_set(state, step, parm*unit(state[step])) :
-    command=="scale" ?  list_set(state, step, parm*state[step]) :
-    command=="addlength" ?  list_set(state, step, state[step]+unit(state[step])*parm) :
-    command=="arcsteps" ? list_set(state, arcsteps, parm) :
-    command=="arcleft" || command=="arcright" ?
-        assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))  
-        let(
-            myangle = default(parm2,state[angle]),
-            lrsign = command=="arcleft" ? 1 : -1,
-            radius = parm*sign(myangle),
-            center = lastpt + lrsign*radius*line_normal([0,0],state[step]),
-            steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps], 
-            arcpath = myangle == 0 || radius == 0 ? [] : arc(
-                steps,
-                points = [
-                    lastpt,
-                    rot(cp=center, p=lastpt, a=sign(parm)*lrsign*myangle/2),
-                    rot(cp=center, p=lastpt, a=sign(parm)*lrsign*myangle)
-                ]
-            )
-        )
-        list_set(
-            state, [path,step], [
-                concat(state[path], slice(arcpath,1,-1)),
-                rot(lrsign * myangle,p=state[step],planar=true)
-            ]
-        ) :
-    command=="arcleftto" || command=="arcrightto" ?
-        assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))
-        assert(is_num(parm2),str("\"",command,"\" command requires a numeric angle value at index ",index))
-        let(
-            radius = parm,
-            lrsign = command=="arcleftto" ? 1 : -1,
-            center = lastpt + lrsign*radius*line_normal([0,0],state[step]),
-            steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps],
-            start_angle = posmod(atan2(state[step].y, state[step].x),360),
-            end_angle = posmod(parm2,360),
-            delta_angle =  -start_angle + (lrsign * end_angle < lrsign*start_angle ? end_angle+lrsign*360 : end_angle),
-            arcpath = delta_angle == 0 || radius==0 ? [] : arc(
-                steps,
-                points = [
-                    lastpt,
-                    rot(cp=center, p=lastpt, a=sign(radius)*delta_angle/2),
-                    rot(cp=center, p=lastpt, a=sign(radius)*delta_angle)
-                ]
-            )
-        )
-        list_set(
-            state, [path,step], [
-                concat(state[path], slice(arcpath,1,-1)),
-                rot(delta_angle,p=state[step],planar=true)
-            ]
-        ) :
-    assert(false,str("Unknown turtle command \"",command,"\" at index",index))
-    [];
-
-
-
-// Section: 2D Primitives
-
-// Function&Module: rect()
-// Usage:
-//   rect(size, [center], [rounding], [chamfer], [anchor], [spin])
-// Description:
-//   When called as a module, creates a 2D rectangle of the given size, with optional rounding or chamfering.
-//   When called as a function, returns a 2D path/list of points for a square/rectangle of the given size.
-// Arguments:
-//   size = The size of the rectangle to create.  If given as a scalar, both X and Y will be the same size.
-//   rounding = The rounding radius for the corners.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
-//   chamfer = The chamfer size for the corners.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].  Default: 0 (no chamfer)
-//   center = If given and true, overrides `anchor` to be `CENTER`.  If given and false, overrides `anchor` to be `FRONT+LEFT`.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-// Example(2D):
-//   rect(40);
-// Example(2D): Centered
-//   rect([40,30], center=true);
-// Example(2D): Anchored
-//   rect([40,30], anchor=FRONT);
-// Example(2D): Spun
-//   rect([40,30], anchor=FRONT, spin=30);
-// Example(2D): Chamferred Rect
-//   rect([40,30], chamfer=5, center=true);
-// Example(2D): Rounded Rect
-//   rect([40,30], rounding=5, center=true);
-// Example(2D): Mixed Chamferring and Rounding
-//   rect([40,30],center=true,rounding=[5,0,10,0],chamfer=[0,8,0,15],$fa=1,$fs=1);
-// Example(2D): Called as Function
-//   path = rect([40,30], chamfer=5, anchor=FRONT, spin=30);
-//   stroke(path, closed=true);
-//   move_copies(path) color("blue") circle(d=2,$fn=8);
-module rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) {
-    size = is_num(size)? [size,size] : point2d(size);
-    anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT);
-    if (rounding==0 && chamfer==0) {
-        attachable(anchor,spin, two_d=true, size=size) {
-            square(size, center=true);
-            children();
-        }
-    } else {
-        pts = rect(size=size, rounding=rounding, chamfer=chamfer, center=true);
-        attachable(anchor,spin, two_d=true, path=pts) {
-            polygon(pts);
-            children();
-        }
-    }
-}
-
-
-function rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) =
-    assert(is_num(size)     || is_vector(size))
-    assert(is_num(chamfer)  || len(chamfer)==4)
-    assert(is_num(rounding) || len(rounding)==4)
-    let(
-        size = is_num(size)? [size,size] : point2d(size),
-        anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT),
-        complex = rounding!=0 || chamfer!=0
-				, xxx = echo(anchor=anchor)echo(size=size)
-    )
-    (rounding==0 && chamfer==0)? let(
-        path = [
-            [ size.x/2, -size.y/2],
-            [-size.x/2, -size.y/2],
-            [-size.x/2,  size.y/2],
-            [ size.x/2,  size.y/2] 
-        ]
-    ) rot(spin, p=move(-vmul(anchor,size/2), p=path)) :
-    let(
-        chamfer = is_list(chamfer)? chamfer : [for (i=[0:3]) chamfer],
-        rounding = is_list(rounding)? rounding : [for (i=[0:3]) rounding],
-        quadorder = [3,2,1,0],
-        quadpos = [[1,1],[-1,1],[-1,-1],[1,-1]],
-        insets = [for (i=[0:3]) chamfer[i]>0? chamfer[i] : rounding[i]>0? rounding[i] : 0],
-        insets_x = max(insets[0]+insets[1],insets[2]+insets[3]),
-        insets_y = max(insets[0]+insets[3],insets[1]+insets[2])
-    )
-    assert(insets_x <= size.x, "Requested roundings and/or chamfers exceed the rect width.")
-    assert(insets_y <= size.y, "Requested roundings and/or chamfers exceed the rect height.")
-    let(
-        path = [
-            for(i = [0:3])
-            let(
-                quad = quadorder[i],
-                inset = insets[quad],
-                cverts = quant(segs(inset),4)/4,
-                cp = vmul(size/2-[inset,inset], quadpos[quad]),
-                step = 90/cverts,
-                angs =
-                    chamfer[quad] > 0?  [0,-90]-90*[i,i] :
-                    rounding[quad] > 0? [for (j=[0:1:cverts]) 360-j*step-i*90] :
-                    [0]
-            )
-            each [for (a = angs) cp + inset*[cos(a),sin(a)]]
-        ]
-    ) complex?
-        reorient(anchor,spin, two_d=true, path=path, p=path) :
-        reorient(anchor,spin, two_d=true, size=size, p=path);
-
-
-// Function&Module: oval()
-// Usage:
-//   oval(r|d, [realign], [circum])
-// Description:
-//   When called as a module, creates a 2D polygon that approximates a circle of the given size.
-//   When called as a function, returns a 2D list of points (path) for a polygon that approximates a circle of the given size.
-// Arguments:
-//   r = Radius of the circle/oval to create.  Can be a scalar, or a list of sizes per axis.
-//   d = Diameter of the circle/oval to create.  Can be a scalar, or a list of sizes per axis.
-//   realign = If true, rotates the polygon that approximates the circle/oval by half of one size.
-//   circum = If true, the polygon that approximates the circle will be upsized slightly to circumscribe the theoretical circle.  If false, it inscribes the theoretical circle.  Default: false
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-// Example(2D): By Radius
-//   oval(r=25);
-// Example(2D): By Diameter
-//   oval(d=50);
-// Example(2D): Anchoring
-//   oval(d=50, anchor=FRONT);
-// Example(2D): Spin
-//   oval(d=50, anchor=FRONT, spin=45);
-// Example(NORENDER): Called as Function
-//   path = oval(d=50, anchor=FRONT, spin=45);
-module oval(r, d, realign=false, circum=false, anchor=CENTER, spin=0) {
-    r = get_radius(r=r, d=d, dflt=1);
-    sides = segs(max(r));
-    sc = circum? (1 / cos(180/sides)) : 1;
-    rx = default(r[0],r) * sc;
-    ry = default(r[1],r) * sc;
-    attachable(anchor,spin, two_d=true, r=[rx,ry]) {
-        if (rx < ry) {
-            xscale(rx/ry) {
-                zrot(realign? 180/sides : 0) {
-                    circle(r=ry, $fn=sides);
-                }
-            }
-        } else {
-            yscale(ry/rx) {
-                zrot(realign? 180/sides : 0) {
-                    circle(r=rx, $fn=sides);
-                }
-            }
-        }
-        children();
-    }
-}
-
-
-function oval(r, d, realign=false, circum=false, anchor=CENTER, spin=0) =
-    let(
-        r = get_radius(r=r, d=d, dflt=1),
-        sides = segs(max(r)),
-        offset = realign? 180/sides : 0,
-        sc = circum? (1 / cos(180/sides)) : 1,
-        rx = default(r[0],r) * sc,
-        ry = default(r[1],r) * sc,
-        pts = [for (i=[0:1:sides-1]) let(a=360-offset-i*360/sides) [rx*cos(a), ry*sin(a)]]
-    ) reorient(anchor,spin, two_d=true, r=[rx,ry], p=pts);
-
-
-
-// Section: 2D N-Gons
-
-// Function&Module: regular_ngon()
-// Usage:
-//   regular_ngon(n, r|d|or|od, [realign]);
-//   regular_ngon(n, ir|id, [realign]);
-//   regular_ngon(n, side, [realign]);
-// Description:
-//   When called as a function, returns a 2D path for a regular N-sided polygon.
-//   When called as a module, creates a 2D regular N-sided polygon.
-// Arguments:
-//   n = The number of sides.
-//   or = Outside radius, at points.
-//   r = Same as or
-//   od = Outside diameter, at points.
-//   d = Same as od
-//   ir = Inside radius, at center of sides.
-//   id = Inside diameter, at center of sides.
-//   side = Length of each side.
-//   rounding = Radius of rounding for the tips of the polygon.  Default: 0 (no rounding)
-//   realign = If false, a tip is aligned with the Y+ axis.  If true, the midpoint of a side is aligned with the Y+ axis.  Default: false
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-// Extra Anchors:
-//   "tip0", "tip1", etc. = Each tip has an anchor, pointing outwards.
-//   "side0", "side1", etc. = The center of each side has an anchor, pointing outwards.
-// Example(2D): by Outer Size
-//   regular_ngon(n=5, or=30);
-//   regular_ngon(n=5, od=60);
-// Example(2D): by Inner Size
-//   regular_ngon(n=5, ir=30);
-//   regular_ngon(n=5, id=60);
-// Example(2D): by Side Length
-//   regular_ngon(n=8, side=20);
-// Example(2D): Realigned
-//   regular_ngon(n=8, side=20, realign=true);
-// Example(2D): Rounded
-//   regular_ngon(n=5, od=100, rounding=20, $fn=20);
-// Example(2D): Called as Function
-//   stroke(closed=true, regular_ngon(n=6, or=30));
-function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) =
-    let(
-        sc = 1/cos(180/n),
-        r = get_radius(r1=ir*sc, r2=or, r=r, d1=id*sc, d2=od, d=d, dflt=side/2/sin(180/n))
-    )
-    assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side.")
-    let(
-        inset = opp_ang_to_hyp(rounding, (180-360/n)/2),
-        path = rounding==0? oval(r=r, realign=realign, $fn=n) : (
-            let(
-                steps = floor(segs(r)/n),
-                step = 360/n/steps,
-                path2 = [
-                    for (i = [0:1:n-1]) let(
-                        a = 360 - i*360/n - (realign? 180/n : 0),
-                        p = polar_to_xy(r-inset, a)
-                    )
-                    each arc(N=steps, cp=p, r=rounding, start=a+180/n, angle=-360/n)
-                ],
-                maxx_idx = max_index(subindex(path2,0)),
-                path3 = polygon_shift(path2,maxx_idx)
-            ) path3
-        ),
-        anchors = !is_string(anchor)? [] : [
-            for (i = [0:1:n-1]) let(
-                a1 = 360 - i*360/n - (realign? 180/n : 0),
-                a2 = a1 - 360/n,
-                p1 = polar_to_xy(r,a1),
-                p2 = polar_to_xy(r,a2),
-                tipp = polar_to_xy(r-inset+rounding,a1),
-                pos = (p1+p2)/2
-            ) each [
-                anchorpt(str("tip",i), tipp, unit(tipp,BACK), 0),
-                anchorpt(str("side",i), pos, unit(pos,BACK), 0),
-            ]
-        ]
-    ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path, anchors=anchors);
-
-
-module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) {
-    sc = 1/cos(180/n);
-    r = get_radius(r1=ir*sc, r2=or, r=r, d1=id*sc, d2=od, d=d, dflt=side/2/sin(180/n));
-    assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side.");
-    path = regular_ngon(n=n, r=r, rounding=rounding, realign=realign);
-    inset = opp_ang_to_hyp(rounding, (180-360/n)/2);
-    anchors = [
-        for (i = [0:1:n-1]) let(
-            a1 = 360 - i*360/n - (realign? 180/n : 0),
-            a2 = a1 - 360/n,
-            p1 = polar_to_xy(r,a1),
-            p2 = polar_to_xy(r,a2),
-            tipp = polar_to_xy(r-inset+rounding,a1),
-            pos = (p1+p2)/2
-        ) each [
-            anchorpt(str("tip",i), tipp, unit(tipp,BACK), 0),
-            anchorpt(str("side",i), pos, unit(pos,BACK), 0),
-        ]
-    ];
-    attachable(anchor,spin, two_d=true, path=path, extent=false, anchors=anchors) {
-        polygon(path);
-        children();
-    }
-}
-
-
-// Function&Module: pentagon()
-// Usage:
-//   pentagon(or|od, [realign]);
-//   pentagon(ir|id, [realign]);
-//   pentagon(side, [realign]);
-// Description:
-//   When called as a function, returns a 2D path for a regular pentagon.
-//   When called as a module, creates a 2D regular pentagon.
-// Arguments:
-//   or = Outside radius, at points.
-//   r = Same as or.
-//   od = Outside diameter, at points.
-//   d = Same as od.
-//   ir = Inside radius, at center of sides.
-//   id = Inside diameter, at center of sides.
-//   side = Length of each side.
-//   rounding = Radius of rounding for the tips of the polygon.  Default: 0 (no rounding)
-//   realign = If false, a tip is aligned with the Y+ axis.  If true, the midpoint of a side is aligned with the Y+ axis.  Default: false
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-// Extra Anchors:
-//   "tip0" ... "tip4" = Each tip has an anchor, pointing outwards.
-//   "side0" ... "side4" = The center of each side has an anchor, pointing outwards.
-// Example(2D): by Outer Size
-//   pentagon(or=30);
-//   pentagon(od=60);
-// Example(2D): by Inner Size
-//   pentagon(ir=30);
-//   pentagon(id=60);
-// Example(2D): by Side Length
-//   pentagon(side=20);
-// Example(2D): Realigned
-//   pentagon(side=20, realign=true);
-// Example(2D): Rounded
-//   pentagon(od=100, rounding=20, $fn=20);
-// Example(2D): Called as Function
-//   stroke(closed=true, pentagon(or=30));
-function pentagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) =
-    regular_ngon(n=5, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin);
-
-
-module pentagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0)
-    regular_ngon(n=5, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin) children();
-
-
-// Function&Module: hexagon()
-// Usage:
-//   hexagon(or, od, ir, id, side);
-// Description:
-//   When called as a function, returns a 2D path for a regular hexagon.
-//   When called as a module, creates a 2D regular hexagon.
-// Arguments:
-//   or = Outside radius, at points.
-//   r = Same as or
-//   od = Outside diameter, at points.
-//   d = Same as od
-//   ir = Inside radius, at center of sides.
-//   id = Inside diameter, at center of sides.
-//   side = Length of each side.
-//   rounding = Radius of rounding for the tips of the polygon.  Default: 0 (no rounding)
-//   realign = If false, a tip is aligned with the Y+ axis.  If true, the midpoint of a side is aligned with the Y+ axis.  Default: false
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-// Extra Anchors:
-//   "tip0" ... "tip5" = Each tip has an anchor, pointing outwards.
-//   "side0" ... "side5" = The center of each side has an anchor, pointing outwards.
-// Example(2D): by Outer Size
-//   hexagon(or=30);
-//   hexagon(od=60);
-// Example(2D): by Inner Size
-//   hexagon(ir=30);
-//   hexagon(id=60);
-// Example(2D): by Side Length
-//   hexagon(side=20);
-// Example(2D): Realigned
-//   hexagon(side=20, realign=true);
-// Example(2D): Rounded
-//   hexagon(od=100, rounding=20, $fn=20);
-// Example(2D): Called as Function
-//   stroke(closed=true, hexagon(or=30));
-function hexagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) =
-    regular_ngon(n=6, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin);
-
-
-module hexagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0)
-    regular_ngon(n=6, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin) children();
-
-
-// Function&Module: octagon()
-// Usage:
-//   octagon(or, od, ir, id, side);
-// Description:
-//   When called as a function, returns a 2D path for a regular octagon.
-//   When called as a module, creates a 2D regular octagon.
-// Arguments:
-//   or = Outside radius, at points.
-//   r = Same as or
-//   od = Outside diameter, at points.
-//   d = Same as od
-//   ir = Inside radius, at center of sides.
-//   id = Inside diameter, at center of sides.
-//   side = Length of each side.
-//   rounding = Radius of rounding for the tips of the polygon.  Default: 0 (no rounding)
-//   realign = If false, a tip is aligned with the Y+ axis.  If true, the midpoint of a side is aligned with the Y+ axis.  Default: false
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-// Extra Anchors:
-//   "tip0" ... "tip7" = Each tip has an anchor, pointing outwards.
-//   "side0" ... "side7" = The center of each side has an anchor, pointing outwards.
-// Example(2D): by Outer Size
-//   octagon(or=30);
-//   octagon(od=60);
-// Example(2D): by Inner Size
-//   octagon(ir=30);
-//   octagon(id=60);
-// Example(2D): by Side Length
-//   octagon(side=20);
-// Example(2D): Realigned
-//   octagon(side=20, realign=true);
-// Example(2D): Rounded
-//   octagon(od=100, rounding=20, $fn=20);
-// Example(2D): Called as Function
-//   stroke(closed=true, octagon(or=30));
-function octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) =
-    regular_ngon(n=8, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin);
-
-
-module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0)
-    regular_ngon(n=8, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin) children();
-
-
-
-// Section: Other 2D Shapes
-
-
-// Function&Module: trapezoid()
-// Usage:
-//   trapezoid(h, w1, w2);
-// Description:
-//   When called as a function, returns a 2D path for a trapezoid with parallel front and back sides.
-//   When called as a module, creates a 2D trapezoid with parallel front and back sides.
-// Arguments:
-//   h = The Y axis height of the trapezoid.
-//   w1 = The X axis width of the front end of the trapezoid.
-//   w2 = The X axis width of the back end of the trapezoid.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-// Examples(2D):
-//   trapezoid(h=30, w1=40, w2=20);
-//   trapezoid(h=25, w1=20, w2=35);
-//   trapezoid(h=20, w1=40, w2=0);
-// Example(2D): Called as Function
-//   stroke(closed=true, trapezoid(h=30, w1=40, w2=20));
-function trapezoid(h, w1, w2, anchor=CENTER, spin=0) =
-    let(
-        path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2,h/2], [w2/2,h/2]]
-    ) reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, p=path);
-
-
-
-module trapezoid(h, w1, w2, anchor=CENTER, spin=0) {
-    path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2,h/2], [w2/2,h/2]];
-    attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2) {
-        polygon(path);
-        children();
-    }
-}
-
-
-// Function&Module: teardrop2d()
-//
-// Description:
-//   Makes a 2D teardrop shape. Useful for extruding into 3D printable holes.
-//
-// Usage:
-//   teardrop2d(r|d, [ang], [cap_h]);
-//
-// Arguments:
-//   r = radius of circular part of teardrop.  (Default: 1)
-//   d = diameter of spherical portion of bottom. (Use instead of r)
-//   ang = angle of hat walls from the Y axis.  (Default: 45 degrees)
-//   cap_h = if given, height above center where the shape will be truncated.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-//
-// Example(2D): Typical Shape
-//   teardrop2d(r=30, ang=30);
-// Example(2D): Crop Cap
-//   teardrop2d(r=30, ang=30, cap_h=40);
-// Example(2D): Close Crop
-//   teardrop2d(r=30, ang=30, cap_h=20);
-module teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0)
-{
-    path = teardrop2d(r=r, d=d, ang=ang, cap_h=cap_h);
-    attachable(anchor,spin, two_d=true, path=path) {
-        polygon(path);
-        children();
-    }
-}
-
-
-function teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0) =
-    let(
-        r = get_radius(r=r, d=d, dflt=1),
-        cord = 2 * r * cos(ang),
-        cord_h = r * sin(ang),
-        tip_y = (cord/2)/tan(ang),
-        cap_h = min((!is_undef(cap_h)? cap_h : tip_y+cord_h), tip_y+cord_h),
-        cap_w = cord * (1 - (cap_h - cord_h)/tip_y),
-        ang = min(ang,asin(cap_h/r)),
-        sa = 180 - ang,
-        ea = 360 + ang,
-        steps = segs(r)*(ea-sa)/360,
-        step = (ea-sa)/steps,
-        path = deduplicate(
-            [
-                [ cap_w/2,cap_h],
-                for (i=[0:1:steps]) let(a=ea-i*step) r*[cos(a),sin(a)],
-                [-cap_w/2,cap_h]
-            ], closed=true
-        ),
-        maxx_idx = max_index(subindex(path,0)),
-        path2 = polygon_shift(path,maxx_idx)
-    ) reorient(anchor,spin, two_d=true, path=path2, p=path2);
-
-
-
-// Function&Module: glued_circles()
-// Usage:
-//   glued_circles(r|d, spread, tangent);
-// Description:
-//   When called as a function, returns a 2D path forming a shape of two circles joined by curved waist.
-//   When called as a module, creates a 2D shape of two circles joined by curved waist.
-// Arguments:
-//   r = The radius of the end circles.
-//   d = The diameter of the end circles.
-//   spread = The distance between the centers of the end circles.
-//   tangent = The angle in degrees of the tangent point for the joining arcs, measured away from the Y axis.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-// Examples(2D):
-//   glued_circles(r=15, spread=40, tangent=45);
-//   glued_circles(d=30, spread=30, tangent=30);
-//   glued_circles(d=30, spread=30, tangent=15);
-//   glued_circles(d=30, spread=30, tangent=-30);
-// Example(2D): Called as Function
-//   stroke(closed=true, glued_circles(r=15, spread=40, tangent=45));
-function glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) =
-    let(
-        r = get_radius(r=r, d=d, dflt=10),
-        r2 = (spread/2 / sin(tangent)) - r,
-        cp1 = [spread/2, 0],
-        cp2 = [0, (r+r2)*cos(tangent)],
-        sa1 = 90-tangent,
-        ea1 = 270+tangent,
-        lobearc = ea1-sa1,
-        lobesegs = floor(segs(r)*lobearc/360),
-        lobestep = lobearc / lobesegs,
-        sa2 = 270-tangent,
-        ea2 = 270+tangent,
-        subarc = ea2-sa2,
-        arcsegs = ceil(segs(r2)*abs(subarc)/360),
-        arcstep = subarc / arcsegs,
-        path = concat(
-            [for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep)     r  * [cos(a),sin(a)] - cp1],
-            tangent==0? [] : [for (i=[0:1:arcsegs])  let(a=ea2-i*arcstep+180)  r2 * [cos(a),sin(a)] - cp2],
-            [for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep+180) r  * [cos(a),sin(a)] + cp1],
-            tangent==0? [] : [for (i=[0:1:arcsegs])  let(a=ea2-i*arcstep)      r2 * [cos(a),sin(a)] + cp2]
-        ),
-        maxx_idx = max_index(subindex(path,0)),
-        path2 = reverse_polygon(polygon_shift(path,maxx_idx))
-    ) reorient(anchor,spin, two_d=true, path=path2, extent=true, p=path2);
-
-
-module glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) {
-    path = glued_circles(r=r, d=d, spread=spread, tangent=tangent);
-    attachable(anchor,spin, two_d=true, path=path, extent=true) {
-        polygon(path);
-        children();
-    }
-}
-
-
-// Function&Module: star()
-// Usage:
-//   star(n, r|d|or|od, ir|id|step, [realign]);
-// Description:
-//   When called as a function, returns the path needed to create a star polygon with N points.
-//   When called as a module, creates a star polygon with N points.
-// Arguments:
-//   n = The number of stellate tips on the star.
-//   r = The radius to the tips of the star.
-//   or = Same as r
-//   d = The diameter to the tips of the star.
-//   od = Same as d
-//   ir = The radius to the inner corners of the star.
-//   id = The diameter to the inner corners of the star.
-//   step = Calculates the radius of the inner star corners by virtually drawing a straight line `step` tips around the star.  2 <= step < n/2
-//   realign = If false, a tip is aligned with the Y+ axis.  If true, an inner corner is aligned with the Y+ axis.  Default: false
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-// Extra Anchors:
-//   "tip0" ... "tip4" = Each tip has an anchor, pointing outwards.
-//   "corner0" ... "corner4" = The inside corner between each tip has an anchor, pointing outwards.
-//   "midpt0" ... "midpt4" = The center-point between each pair or tips has an anchor, pointing outwards.
-// Examples(2D):
-//   star(n=5, r=50, ir=25);
-//   star(n=5, r=50, step=2);
-//   star(n=7, r=50, step=2);
-//   star(n=7, r=50, step=3);
-// Example(2D): Realigned
-//   star(n=7, r=50, step=3, realign=true);
-// Example(2D): Called as Function
-//   stroke(closed=true, star(n=5, r=50, ir=25));
-function star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin=0) =
-    let(
-        r = get_radius(r1=or, d1=od, r=r, d=d),
-        count = num_defined([ir,id,step]),
-        stepOK = is_undef(step) || (step>1 && step<n/2)
-    )
-    assert(is_def(n), "Must specify number of points, n")
-    assert(count==1, "Must specify exactly one of ir, id, step")
-    assert(stepOK, str("Parameter 'step' must be between 2 and ",floor(n/2)," for ",n," point star"))
-    let(
-        stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n),
-        ir = get_radius(r=ir, d=id, dflt=stepr),
-        offset = realign? 180/n : 0,
-        path = [for(i=[2*n:-1:1]) let(theta=180*i/n+offset, radius=(i%2)?ir:r) radius*[cos(theta), sin(theta)]],
-        anchors = !is_string(anchor)? [] : [
-            for (i = [0:1:n-1]) let(
-                a1 = 360 - i*360/n - (realign? 180/n : 0),
-                a2 = a1 - 180/n,
-                a3 = a1 - 360/n,
-                p1 = polar_to_xy(r,a1),
-                p2 = polar_to_xy(ir,a2),
-                p3 = polar_to_xy(r,a3),
-                pos = (p1+p3)/2
-            ) each [
-                anchorpt(str("tip",i), p1, unit(p1,BACK), 0),
-                anchorpt(str("corner",i), p2, unit(p2,BACK), 0),
-                anchorpt(str("midpt",i), pos, unit(pos,BACK), 0),
-            ]
-        ]
-    ) reorient(anchor,spin, two_d=true, path=path, p=path, anchors=anchors);
-
-
-module star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin=0) {
-    r = get_radius(r1=or, d1=od, r=r, d=d, dflt=undef);
-    stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n);
-    ir = get_radius(r=ir, d=id, dflt=stepr);
-    path = star(n=n, r=r, ir=ir, realign=realign);
-    anchors = [
-        for (i = [0:1:n-1]) let(
-            a1 = 360 - i*360/n - (realign? 180/n : 0),
-            a2 = a1 - 180/n,
-            a3 = a1 - 360/n,
-            p1 = polar_to_xy(r,a1),
-            p2 = polar_to_xy(ir,a2),
-            p3 = polar_to_xy(r,a3),
-            pos = (p1+p3)/2
-        ) each [
-            anchorpt(str("tip",i), p1, unit(p1,BACK), 0),
-            anchorpt(str("corner",i), p2, unit(p2,BACK), 0),
-            anchorpt(str("midpt",i), pos, unit(pos,BACK), 0),
-        ]
-    ];
-    attachable(anchor,spin, two_d=true, path=path, anchors=anchors) {
-        polygon(path);
-        children();
-    }
-}
-
-
-function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) =
-    pow(pow(abs(cos(m1*theta/4)/a),n2)+pow(abs(sin(m2*theta/4)/b),n3),-1/n1);
-
-// Function&Module: supershape()
-// Usage:
-//   supershape(step,[m1],[m2],[n1],[n2],[n3],[a],[b],[r|d]);
-// Description:
-//   When called as a function, returns a 2D path for the outline of the [Superformula](https://en.wikipedia.org/wiki/Superformula) shape.
-//   When called as a module, creates a 2D [Superformula](https://en.wikipedia.org/wiki/Superformula) shape.
-// Arguments:
-//   step = The angle step size for sampling the superformula shape.  Smaller steps are slower but more accurate.
-//   m1 = The m1 argument for the superformula. Default: 4.
-//   m2 = The m2 argument for the superformula. Default: m1.
-//   n1 = The n1 argument for the superformula. Default: 1.
-//   n2 = The n2 argument for the superformula. Default: n1.
-//   n3 = The n3 argument for the superformula. Default: n2.
-//   a = The a argument for the superformula.  Default: 1.
-//   b = The b argument for the superformula.  Default: a.
-//   r = Radius of the shape.  Scale shape to fit in a circle of radius r.
-//   d = Diameter of the shape.  Scale shape to fit in a circle of diameter d.
-//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
-//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
-// Example(2D):
-//   supershape(step=0.5,m1=16,m2=16,n1=0.5,n2=0.5,n3=16,r=50);
-// Example(2D): Called as Function
-//   stroke(closed=true, supershape(step=0.5,m1=16,m2=16,n1=0.5,n2=0.5,n3=16,d=100));
-// Examples(2D,Med):
-//   for(n=[2:5]) right(2.5*(n-2)) supershape(m1=4,m2=4,n1=n,a=1,b=2);  // Superellipses
-//   m=[2,3,5,7]; for(i=[0:3]) right(2.5*i) supershape(.5,m1=m[i],n1=1);
-//   m=[6,8,10,12]; for(i=[0:3]) right(2.7*i) supershape(.5,m1=m[i],n1=1,b=1.5);  // m should be even
-//   m=[1,2,3,5]; for(i=[0:3]) fwd(1.5*i) supershape(m1=m[i],n1=0.4);
-//   supershape(m1=5, n1=4, n2=1); right(2.5) supershape(m1=5, n1=40, n2=10);
-//   m=[2,3,5,7]; for(i=[0:3]) right(2.5*i) supershape(m1=m[i], n1=60, n2=55, n3=30);
-//   n=[0.5,0.2,0.1,0.02]; for(i=[0:3]) right(2.5*i) supershape(m1=5,n1=n[i], n2=1.7);
-//   supershape(m1=2, n1=1, n2=4, n3=8);
-//   supershape(m1=7, n1=2, n2=8, n3=4);
-//   supershape(m1=7, n1=3, n2=4, n3=17);
-//   supershape(m1=4, n1=1/2, n2=1/2, n3=4);
-//   supershape(m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9);
-//   for(i=[1:4]) right(3*i) supershape(m1=i, m2=3*i, n1=2);
-//   m=[4,6,10]; for(i=[0:2]) right(i*5) supershape(m1=m[i], n1=12, n2=8, n3=5, a=2.7);
-//   for(i=[-1.5:3:1.5]) right(i*1.5) supershape(m1=2,m2=10,n1=i,n2=1);
-//   for(i=[1:3],j=[-1,1]) translate([3.5*i,1.5*j])supershape(m1=4,m2=6,n1=i*j,n2=1);
-//   for(i=[1:3]) right(2.5*i)supershape(step=.5,m1=88, m2=64, n1=-i*i,n2=1,r=1);
-// Examples:
-//   linear_extrude(height=0.3, scale=0) supershape(step=1, m1=6, n1=0.4, n2=0, n3=6);
-//   linear_extrude(height=5, scale=0) supershape(step=1, b=3, m1=6, n1=3.8, n2=16, n3=10);
-function supershape(step=0.5,m1=4,m2=undef,n1=1,n2=undef,n3=undef,a=1,b=undef,r=undef,d=undef,anchor=CENTER, spin=0) =
-    let(
-        r = get_radius(r=r, d=d, dflt=undef),
-        m2 = is_def(m2) ? m2 : m1,
-        n2 = is_def(n2) ? n2 : n1,
-        n3 = is_def(n3) ? n3 : n2,
-        b = is_def(b) ? b : a,
-        steps = ceil(360/step),
-        step = 360/steps,
-        angs = [for (i = [0:steps]) step*i],
-        rads = [for (theta = angs) _superformula(theta=theta,m1=m1,m2=m2,n1=n1,n2=n2,n3=n3,a=a,b=b)],
-        scale = is_def(r) ? r/max(rads) : 1,
-        path = [for (i = [steps:-1:1]) let(a=angs[i]) scale*rads[i]*[cos(a), sin(a)]]
-    ) reorient(anchor,spin, two_d=true, path=path, p=path);
-
-module supershape(step=0.5,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=undef, d=undef, anchor=CENTER, spin=0) {
-    path = supershape(step=step,m1=m1,m2=m2,n1=n1,n2=n2,n3=n3,a=a,b=b,r=r,d=d);
-    attachable(anchor,spin, two_d=true, path=path) {
-        polygon(path);
-        children();
-    }
-}
-
-
-// Section: 2D Masking Shapes
-
-// Function&Module: mask2d_roundover()
-// Usage:
-//   mask2d_roundover(r|d, [inset], [excess]);
-// Description:
-//   Creates a 2D roundover/bead mask shape that is useful for extruding into a 3D mask for a 90º edge.
-//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
-//   If called as a function, this just returns a 2D path of the outline of the mask shape.
-// Arguments:
-//   r = Radius of the roundover.
-//   d = Diameter of the roundover.
-//   inset = Optional bead inset size.  Default: 0
-//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
-// Example(2D): 2D Roundover Mask
-//   mask2d_roundover(r=10);
-// Example(2D): 2D Bead Mask
-//   mask2d_roundover(r=10,inset=2);
-// Example: Masking by Edge Attachment
-//   diff("mask")
-//   cube([50,60,70],center=true)
-//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
-//           mask2d_roundover(r=10, inset=2);
-module mask2d_roundover(r, d, excess, inset=0, anchor=CENTER,spin=0) {
-    path = mask2d_roundover(r=r,d=d,excess=excess,inset=inset);
-    attachable(anchor,spin, two_d=true, path=path) {
-        polygon(path);
-        children();
-    }
-}
-
-function mask2d_roundover(r, d, excess, inset=0, anchor=CENTER,spin=0) =
-    assert(is_num(r)||is_num(d))
-    assert(is_undef(excess)||is_num(excess))
-    assert(is_num(inset)||(is_vector(inset)&&len(inset)==2))
-    let(
-        inset = is_list(inset)? inset : [inset,inset],
-        excess = default(excess,$overlap),
-        r = get_radius(r=r,d=d,dflt=1),
-        steps = quantup(segs(r),4)/4,
-        step = 90/steps,
-        path = [
-            [r+inset.x,-excess],
-            [-excess,-excess],
-            [-excess, r+inset.y],
-            for (i=[0:1:steps]) [r,r] + inset + polar_to_xy(r,180+i*step)
-        ]
-    ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
-
-
-// Function&Module: mask2d_cove()
-// Usage:
-//   mask2d_cove(r|d, [inset], [excess]);
-// Description:
-//   Creates a 2D cove mask shape that is useful for extruding into a 3D mask for a 90º edge.
-//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
-//   If called as a function, this just returns a 2D path of the outline of the mask shape.
-// Arguments:
-//   r = Radius of the cove.
-//   d = Diameter of the cove.
-//   inset = Optional amount to inset code from corner.  Default: 0
-//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
-// Example(2D): 2D Cove Mask
-//   mask2d_cove(r=10);
-// Example(2D): 2D Inset Cove Mask
-//   mask2d_cove(r=10,inset=3);
-// Example: Masking by Edge Attachment
-//   diff("mask")
-//   cube([50,60,70],center=true)
-//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
-//           mask2d_cove(r=10, inset=2);
-module mask2d_cove(r, d, inset=0, excess, anchor=CENTER,spin=0) {
-    path = mask2d_cove(r=r,d=d,excess=excess,inset=inset);
-    attachable(anchor,spin, two_d=true, path=path) {
-        polygon(path);
-        children();
-    }
-}
-
-function mask2d_cove(r, d, inset=0, excess, anchor=CENTER,spin=0) =
-    assert(is_num(r)||is_num(d))
-    assert(is_undef(excess)||is_num(excess))
-    assert(is_num(inset)||(is_vector(inset)&&len(inset)==2))
-    let(
-        inset = is_list(inset)? inset : [inset,inset],
-        excess = default(excess,$overlap),
-        r = get_radius(r=r,d=d,dflt=1),
-        steps = quantup(segs(r),4)/4,
-        step = 90/steps,
-        path = [
-            [r+inset.x,-excess],
-            [-excess,-excess],
-            [-excess, r+inset.y],
-            for (i=[0:1:steps]) inset + polar_to_xy(r,90-i*step)
-        ]
-    ) reorient(anchor,spin, two_d=true, path=path, p=path);
-
-
-// Function&Module: mask2d_chamfer()
-// Usage:
-//   mask2d_chamfer(x|y|edge, [angle], [inset], [excess]);
-// Description:
-//   Creates a 2D chamfer mask shape that is useful for extruding into a 3D mask for a 90º edge.
-//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
-//   If called as a function, this just returns a 2D path of the outline of the mask shape.
-// Arguments:
-//   x = The width of the chamfer.
-//   y = The height of the chamfer.
-//   edge = The length of the edge of the chamfer.
-//   angle = The angle of the chamfer edge, away from vertical.  Default: 45.
-//   inset = Optional amount to inset code from corner.  Default: 0
-//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
-// Example(2D): 2D Chamfer Mask
-//   mask2d_chamfer(x=10);
-// Example(2D): 2D Chamfer Mask by Width.
-//   mask2d_chamfer(x=10, angle=30);
-// Example(2D): 2D Chamfer Mask by Height.
-//   mask2d_chamfer(y=10, angle=30);
-// Example(2D): 2D Inset Chamfer Mask
-//   mask2d_chamfer(x=10, inset=2);
-// Example: Masking by Edge Attachment
-//   diff("mask")
-//   cube([50,60,70],center=true)
-//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
-//           mask2d_chamfer(x=10, inset=2);
-module mask2d_chamfer(x, y, edge, angle=45, excess, inset=0, anchor=CENTER,spin=0) {
-    path = mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset);
-    attachable(anchor,spin, two_d=true, path=path, extent=true) {
-        polygon(path);
-        children();
-    }
-}
-
-function mask2d_chamfer(x, y, edge, angle=45, excess, inset=0, anchor=CENTER,spin=0) =
-    assert(num_defined([x,y,edge])==1)
-    assert(is_num(first_defined([x,y,edge])))
-    assert(is_num(angle))
-    assert(is_undef(excess)||is_num(excess))
-    assert(is_num(inset)||(is_vector(inset)&&len(inset)==2))
-    let(
-        inset = is_list(inset)? inset : [inset,inset],
-        excess = default(excess,$overlap),
-        x = !is_undef(x)? x :
-            !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) :
-            hyp_ang_to_opp(hyp=edge,ang=angle),
-        y = opp_ang_to_adj(opp=x,ang=angle),
-        path = [
-            [x+inset.x, -excess],
-            [-excess, -excess],
-            [-excess, y+inset.y],
-            [inset.x, y+inset.y],
-            [x+inset.x, inset.y]
-        ]
-    ) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path);
-
-
-// Function&Module: mask2d_rabbet()
-// Usage:
-//   mask2d_rabbet(size, [excess]);
-// Description:
-//   Creates a 2D rabbet mask shape that is useful for extruding into a 3D mask for a 90º edge.
-//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
-//   If called as a function, this just returns a 2D path of the outline of the mask shape.
-// Arguments:
-//   size = The size of the rabbet, either as a scalar or an [X,Y] list.
-//   inset = Optional amount to inset code from corner.  Default: 0
-//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
-// Example(2D): 2D Rabbet Mask
-//   mask2d_rabbet(size=10);
-// Example(2D): 2D Asymmetrical Rabbet Mask
-//   mask2d_rabbet(size=[5,10]);
-// Example: Masking by Edge Attachment
-//   diff("mask")
-//   cube([50,60,70],center=true)
-//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
-//           mask2d_rabbet(size=10);
-module mask2d_rabbet(size, excess, anchor=CENTER,spin=0) {
-    path = mask2d_rabbet(size=size, excess=excess);
-    attachable(anchor,spin, two_d=true, path=path, extent=false) {
-        polygon(path);
-        children();
-    }
-}
-
-function mask2d_rabbet(size, excess, anchor=CENTER,spin=0) =
-    assert(is_num(size)||(is_vector(size)&&len(size)==2))
-    assert(is_undef(excess)||is_num(excess))
-    let(
-        excess = default(excess,$overlap),
-        size = is_list(size)? size : [size,size],
-        path = [
-            [size.x, -excess],
-            [-excess, -excess],
-            [-excess, size.y],
-            size
-        ]
-    ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
-
-
-// Function&Module: mask2d_dovetail()
-// Usage:
-//   mask2d_dovetail(x|y|edge, [angle], [inset], [shelf], [excess]);
-// Description:
-//   Creates a 2D dovetail mask shape that is useful for extruding into a 3D mask for a 90º edge.
-//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
-//   If called as a function, this just returns a 2D path of the outline of the mask shape.
-// Arguments:
-//   x = The width of the dovetail.
-//   y = The height of the dovetail.
-//   edge = The length of the edge of the dovetail.
-//   angle = The angle of the chamfer edge, away from vertical.  Default: 30.
-//   inset = Optional amount to inset code from corner.  Default: 0
-//   shelf = The extra height to add to the inside corner of the dovetail.  Default: 0
-//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
-// Example(2D): 2D Dovetail Mask
-//   mask2d_dovetail(x=10);
-// Example(2D): 2D Dovetail Mask by Width.
-//   mask2d_dovetail(x=10, angle=30);
-// Example(2D): 2D Dovetail Mask by Height.
-//   mask2d_dovetail(y=10, angle=30);
-// Example(2D): 2D Inset Dovetail Mask
-//   mask2d_dovetail(x=10, inset=2);
-// Example: Masking by Edge Attachment
-//   diff("mask")
-//   cube([50,60,70],center=true)
-//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
-//           mask2d_dovetail(x=10, inset=2);
-module mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess, anchor=CENTER, spin=0) {
-    path = mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess);
-    attachable(anchor,spin, two_d=true, path=path) {
-        polygon(path);
-        children();
-    }
-}
-
-function mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess, anchor=CENTER, spin=0) =
-    assert(num_defined([x,y,edge])==1)
-    assert(is_num(first_defined([x,y,edge])))
-    assert(is_num(angle))
-    assert(is_undef(excess)||is_num(excess))
-    assert(is_num(inset)||(is_vector(inset)&&len(inset)==2))
-    let(
-        inset = is_list(inset)? inset : [inset,inset],
-        excess = default(excess,$overlap),
-        x = !is_undef(x)? x :
-            !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) :
-            hyp_ang_to_opp(hyp=edge,ang=angle),
-        y = opp_ang_to_adj(opp=x,ang=angle),
-        path = [
-            [inset.x,0],
-            [-excess, 0],
-            [-excess, y+inset.y+shelf],
-            inset+[x,y+shelf],
-            inset+[x,y],
-            inset
-        ]
-    ) reorient(anchor,spin, two_d=true, path=path, p=path);
-
-
-// Function&Module: mask2d_teardrop()
-// Usage:
-//   mask2d_teardrop(r|d, [angle], [excess]);
-// Description:
-//   Creates a 2D teardrop mask shape that is useful for extruding into a 3D mask for a 90º edge.
-//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
-//   If called as a function, this just returns a 2D path of the outline of the mask shape.
-//   This is particularly useful to make partially rounded bottoms, that don't need support to print.
-// Arguments:
-//   r = Radius of the rounding.
-//   d = Diameter of the rounding.
-//   angle = The maximum angle from vertical.
-//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
-// Example(2D): 2D Teardrop Mask
-//   mask2d_teardrop(r=10);
-// Example(2D): Using a Custom Angle
-//   mask2d_teardrop(r=10,angle=30);
-// Example: Masking by Edge Attachment
-//   diff("mask")
-//   cube([50,60,70],center=true)
-//       edge_profile(BOT)
-//           mask2d_teardrop(r=10, angle=40);
-function mask2d_teardrop(r,d,angle=45,excess=0.1,anchor=CENTER,spin=0) =  
-    assert(is_num(angle))
-    assert(angle>0 && angle<90)
-    assert(is_num(excess))
-    let(
-        r = get_radius(r=r, d=d, dflt=1),
-        n = ceil(segs(r) * angle/360),
-        cp = [r,r],
-        tp = cp + polar_to_xy(r,180+angle),
-        bp = [tp.x+adj_ang_to_opp(tp.y,angle), 0],
-        step = angle/n,
-        path = [
-            bp, bp-[0,excess], [-excess,-excess], [-excess,r],
-            for (i=[0:1:n]) cp+polar_to_xy(r,180+i*step)
-        ]
-    ) reorient(anchor,spin, two_d=true, path=path, p=path);
-
-module mask2d_teardrop(r,d,angle=45,excess=0.1,anchor=CENTER,spin=0) {
-    path = mask2d_teardrop(r=r, d=d, angle=angle, excess=excess);
-    attachable(anchor,spin, two_d=true, path=path) {
-        polygon(path);
-        children();
-    }
-}
-
-// Function&Module: mask2d_ogee()
-// Usage:
-//   mask2d_ogee(pattern, [excess]);
-//
-// Description:
-//   Creates a 2D Ogee mask shape that is useful for extruding into a 3D mask for a 90º edge.
-//   This 2D mask is designed to be `difference()`d  away from the edge of a shape that is in the first (X+Y+) quadrant.
-//   Since there are a number of shapes that fall under the name ogee, the shape of this mask is given as a pattern.
-//   Patterns are given as TYPE, VALUE pairs.  ie: `["fillet",10, "xstep",2, "step",[5,5], ...]`.  See Patterns below.
-//   If called as a function, this just returns a 2D path of the outline of the mask shape.
-//   
-//   ### Patterns
-//   
-//   Type     | Argument  | Description
-//   -------- | --------- | ----------------
-//   "step"   | [x,y]     | Makes a line to a point `x` right and `y` down.
-//   "xstep"  | dist      | Makes a `dist` length line towards X+.
-//   "ystep"  | dist      | Makes a `dist` length line towards Y-.
-//   "round"  | radius    | Makes an arc that will mask a roundover.
-//   "fillet" | radius    | Makes an arc that will mask a fillet.
-//
-// Arguments:
-//   pattern = A list of pattern pieces to describe the Ogee.
-//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
-//
-// Example(2D): 2D Ogee Mask
-//   mask2d_ogee([
-//       "xstep",1,  "ystep",1,  // Starting shoulder.
-//       "fillet",5, "round",5,  // S-curve.
-//       "ystep",1,  "xstep",1   // Ending shoulder.
-//   ]);
-// Example: Masking by Edge Attachment
-//   diff("mask")
-//   cube([50,60,70],center=true)
-//       edge_profile(TOP)
-//           mask2d_ogee([
-//               "xstep",1,  "ystep",1,  // Starting shoulder.
-//               "fillet",5, "round",5,  // S-curve.
-//               "ystep",1,  "xstep",1   // Ending shoulder.
-//           ]);
-module mask2d_ogee(pattern, excess, anchor=CENTER,spin=0) {
-    path = mask2d_ogee(pattern, excess=excess);
-    attachable(anchor,spin, two_d=true, path=path) {
-        polygon(path);
-        children();
-    }
-}
-
-function mask2d_ogee(pattern, excess, anchor=CENTER, spin=0) =
-    assert(is_list(pattern))
-    assert(len(pattern)>0)
-    assert(len(pattern)%2==0,"pattern must be a list of TYPE, VAL pairs.")
-    assert(all([for (i = idx(pattern,step=2)) in_list(pattern[i],["step","xstep","ystep","round","fillet"])]))
-    let(
-        excess = default(excess,$overlap),
-        x = concat([0], cumsum([
-            for (i=idx(pattern,step=2)) let(
-                type = pattern[i],
-                val = pattern[i+1]
-            ) (
-                type=="step"?   val.x :
-                type=="xstep"?  val :
-                type=="round"?  val :
-                type=="fillet"? val :
-                0
-            )
-        ])),
-        y = concat([0], cumsum([
-            for (i=idx(pattern,step=2)) let(
-                type = pattern[i],
-                val = pattern[i+1]
-            ) (
-                type=="step"?   val.y :
-                type=="ystep"?  val :
-                type=="round"?  val :
-                type=="fillet"? val :
-                0
-            )
-        ])),
-        tot_x = select(x,-1),
-        tot_y = select(y,-1),
-        data = [
-            for (i=idx(pattern,step=2)) let(
-                type = pattern[i],
-                val = pattern[i+1],
-                pt = [x[i/2], tot_y-y[i/2]] + (
-                    type=="step"?   [val.x,-val.y] :
-                    type=="xstep"?  [val,0] :
-                    type=="ystep"?  [0,-val] :
-                    type=="round"?  [val,0] :
-                    type=="fillet"? [0,-val] :
-                    [0,0]
-                )
-            ) [type, val, pt]
-        ],
-        path = [
-            [tot_x,-excess],
-            [-excess,-excess],
-            [-excess,tot_y],
-            for (pat = data) each
-                pat[0]=="step"?  [pat[2]] :
-                pat[0]=="xstep"? [pat[2]] :
-                pat[0]=="ystep"? [pat[2]] :
-                let(
-                    r = pat[1],
-                    steps = segs(abs(r)),
-                    step = 90/steps
-                ) [
-                    for (i=[0:1:steps]) let(
-                        a = pat[0]=="round"? (180+i*step) : (90-i*step)
-                    ) pat[2] + abs(r)*[cos(a),sin(a)]
-                ]
-        ],
-        path2 = deduplicate(path)
-    ) reorient(anchor,spin, two_d=true, path=path2, p=path2);
-
-
-
-// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

From 66aba45802c38d3dccdbe05b7e74b773bde2ca61 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Wed, 29 Jul 2020 02:40:59 +0100
Subject: [PATCH 14/24] remove test_all

---
 tests/test_all.scad | 29 -----------------------------
 1 file changed, 29 deletions(-)
 delete mode 100644 tests/test_all.scad

diff --git a/tests/test_all.scad b/tests/test_all.scad
deleted file mode 100644
index c9c89ac..0000000
--- a/tests/test_all.scad
+++ /dev/null
@@ -1,29 +0,0 @@
-//include<hull.scad>
-include<polyhedra.scad>
-include<test_affine.scad>
-include<test_arrays.scad>
-include<test_common.scad>
-include<test_coords.scad>
-include<test_cubetruss.scad>
-include<test_debug.scad>
-include<test_edges.scad>
-include<test_errors.scad>
-include<test_geometry.scad>
-include<test_linear_bearings.scad>
-include<test_math.scad>
-include<test_mutators.scad>
-include<test_primitives.scad>
-include<test_quaternions.scad>
-include<test_queues.scad>
-include<test_shapes.scad>
-include<test_shapes2d.scad>
-include<test_skin.scad>
-include<test_stacks.scad>
-include<test_strings.scad>
-include<test_structs.scad>
-include<test_transforms.scad>
-include<test_vectors.scad>
-include<test_version.scad>
-include<test_vnf.scad>
-
-

From f67226a6dd61105766f0dc3b6e72329e0d5419b1 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Wed, 29 Jul 2020 03:19:01 +0100
Subject: [PATCH 15/24] formating

---
 arrays.scad | 42 +++---------------------------------------
 math.scad   | 52 +++++++++++++++++-----------------------------------
 2 files changed, 20 insertions(+), 74 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index 365649a..aa401bd 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -73,9 +73,6 @@ function select(list, start, end=undef) =
             :   concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ;
 
 
-
-
-
 // Function: slice()
 // Description:
 //   Returns a slice of a list.  The first item is index 0.
@@ -101,8 +98,6 @@ function slice(list,start,end) =
         ) [for (i=[s:1:e-1]) if (e>s) list[i]];
 
 
-
-
 // Function: in_list()
 // Description: Returns true if value `val` is in list `list`. When `val==NAN` the answer will be false for any list.
 // Arguments:
@@ -118,7 +113,6 @@ function in_list(val,list,idx=undef) =
     s==[] || s[0]==[] ? false
     : is_undef(idx) ? val==list[s] 
     : val==list[s][idx];
-    
 
 
 // Function: min_index()
@@ -209,7 +203,6 @@ function repeat(val, n, i=0) =
     [for (j=[1:1:n[i]]) repeat(val, n, i+1)];
 
 
-
 // Function: list_range()
 // Usage:
 //   list_range(n, [s], [e])
@@ -246,8 +239,6 @@ function list_range(n=undef, s=0, e=undef, step=undef) =
         [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] ;
-    
-
 
 
 // Section: List Manipulation
@@ -315,8 +306,6 @@ function deduplicate(list, closed=false, eps=EPSILON) =
     : [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]];
 
 
-
-
 // Function: deduplicate_indexed()
 // Usage:
 //   new_idxs = deduplicate_indexed(list, indices, [closed], [eps]);
@@ -351,8 +340,6 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
     ];
 
 
-
-
 // Function: repeat_entries()
 // Usage:
 //   newlist = repeat_entries(list, N)
@@ -392,8 +379,6 @@ function repeat_entries(list, N, exact = true) =
     [for(i=[0:length-1]) each repeat(list[i],reps[i])];
     
 
-
-
 // Function: list_set()
 // Usage:
 //   list_set(list, indices, values, [dflt], [minlen])
@@ -433,7 +418,6 @@ function list_set(list=[],indices,values,dflt=0,minlen=0) =
         ];
       
 
-
 // Function: list_insert()
 // Usage:
 //   list_insert(list, indices, values);
@@ -465,8 +449,6 @@ function list_insert(list, indices, values, _i=0) =
         ];
 
 
-
-
 // Function: list_remove()
 // Usage:
 //   list_remove(list, indices)
@@ -489,8 +471,6 @@ function list_remove(list, indices) =
             if ( []==search(i,indices,1) ) list[i] ]; 
 
 
-
-
 // Function: list_remove_values()
 // Usage:
 //   list_remove_values(list,values,all=false) =
@@ -560,8 +540,6 @@ function list_bset(indexset, valuelist, dflt=0) =
     );
 
 
-
-
 // Section: List Length Manipulation
 
 // Function: list_shortest()
@@ -574,7 +552,6 @@ function list_shortest(array) =
     min([for (v = array) len(v)]);
 
 
-
 // Function: list_longest()
 // Description:
 //   Returns the length of the longest sublist in a list of lists.
@@ -624,7 +601,6 @@ function list_fit(array, length, fill) =
               : list_pad(array,length,fill);
 
 
-
 // Section: List Shuffling and Sorting
 
 // Function: shuffle()
@@ -679,6 +655,7 @@ function _sort_vectors2(arr) =
     ) 
     concat( _sort_vectors2(lesser), equal, _sort_vectors2(greater) );
 
+
 // Sort a vector of vectors based on the first three entries of each vector
 // Lexicographic order, remaining entries of vector ignored
 function _sort_vectors3(arr) =
@@ -756,6 +733,7 @@ function _sort_general(arr, idx=undef) =
     )
     concat(_sort_general(lesser,idx), equal, _sort_general(greater,idx));
     
+
 function _sort_general(arr, idx=undef) =
     (len(arr)<=1) ? arr :
     let(
@@ -774,9 +752,6 @@ function _sort_general(arr, idx=undef) =
     concat(_sort_general(lesser,idx), equal, _sort_general(greater,idx));
 
 
-
-
-
 // Function: sort()
 // Usage:
 //   sort(list, [idx])
@@ -809,7 +784,6 @@ function sort(list, idx=undef) =
     : _sort_general(list);
 
 
-
 // Function: sortidx()
 // Description:
 //   Given a list, calculates the sort order of the list, and returns
@@ -853,6 +827,7 @@ function sortidx(list, idx=undef) =
     :   // general case
         subindex(_sort_general(aug, idx=list_range(s=1,n=len(aug)-1)), 0);
 
+
 function sortidx(list, idx=undef) =
     list==[] ? [] : let(
         size = array_dim(list),
@@ -891,7 +866,6 @@ function unique(arr) =
     ];
 
 
-
 // Function: unique_count()
 // Usage:
 //   unique_count(arr);
@@ -908,8 +882,6 @@ function unique_count(arr) =
       [ select(arr,ind), deltas( concat(ind,[len(arr)]) ) ];
 
 
-
-
 // Section: List Iteration Helpers
 
 // Function: idx()
@@ -1104,8 +1076,6 @@ function set_union(a, b, get_indices=false) =
     ) [idxs, nset];
 
 
-
-
 // Function: set_difference()
 // Usage:
 //   s = set_difference(a, b);
@@ -1125,7 +1095,6 @@ function set_difference(a, b) =
     [ for (i=idx(a)) if(found[i]==[]) a[i] ];
 
 
-
 // Function: set_intersection()
 // Usage:
 //   s = set_intersection(a, b);
@@ -1145,8 +1114,6 @@ function set_intersection(a, b) =
     [ for (i=idx(a)) if(found[i]!=[]) a[i] ];
 
 
-
-
 // Section: Array Manipulation
 
 // Function: add_scalar()
@@ -1165,7 +1132,6 @@ function add_scalar(v,s) =
     is_finite(s) ? [for (x=v) is_list(x)? add_scalar(x,s) : is_finite(x) ? x+s: x] : v;
 
 
-
 // Function: subindex()
 // Description:
 //   For each array item, return the indexed subitem.
@@ -1349,6 +1315,4 @@ function transpose(arr) =
     :  arr;
 
 
-
-
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/math.scad b/math.scad
index 23a8174..2e915ee 100644
--- a/math.scad
+++ b/math.scad
@@ -107,6 +107,7 @@ function binomial(n) =
          c = c*(n-i)/(i+1), i = i+1
         ) c ] ;
 
+
 // Function: binomial_coefficient()
 // Usage:
 //   x = binomial_coefficient(n,k);
@@ -129,6 +130,7 @@ function binomial_coefficient(n,k) =
                  ) c] )
     b[len(b)-1];
 
+
 // Function: lerp()
 // Usage:
 //   x = lerp(a, b, u);
@@ -166,7 +168,6 @@ function lerp(a,b,u) =
     [for (v = u) lerp(a,b,v)];
 
 
-
 // Function: all_numeric()
 // Usage:
 //   x = all_numeric(list);
@@ -184,22 +185,6 @@ function all_numeric(list) =
         || []==[for(vi=list) if( !all_numeric(vi)) 0] ;
 
         
-// Function: is_addable()
-// Usage:
-//   x = is_addable(list);
-// Description:
-//   Returns true if `list` is both consistent and numerical. 
-// Arguments:
-//   list = The list to check
-// Example:
-//   x = is_addable([[[1],2],[[0],2]]));    // Returns: true
-//   y = is_addable([[[1],2],[[0],[]]]));   // Returns: false
-function is_addable(list) = // consistent and numerical
-    is_list_of(list,list[0])
-    &&  ( ( let(v = list*list[0]) is_num(0*(v*v)) ) 
-        || []==[for(vi=list) if( !all_numeric(vi)) 0] );
-
-          
 
 // Section: Hyperbolic Trigonometry
 
@@ -245,7 +230,6 @@ function atanh(x) =
     ln((1+x)/(1-x))/2;
 
 
-
 // Section: Quantization
 
 // Function: quant()
@@ -273,14 +257,13 @@ function atanh(x) =
 //   quant([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,12,12,12]
 //   quant([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[12,12,12]]
 function quant(x,y) =
-    assert(is_int(y), "The multiple must be an integer.")
+    assert(is_finite(y), "The multiple must be an integer.")
     is_list(x)
     ?   [for (v=x) quant(v,y)]
     :   assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
         floor(x/y+0.5)*y;
 
 
-
 // Function: quantdn()
 // Description:
 //   Quantize a value `x` to an integer multiple of `y`, rounding down to the previous multiple.
@@ -306,7 +289,7 @@ function quant(x,y) =
 //   quantdn([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,9,9,12]
 //   quantdn([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[9,9,12]]
 function quantdn(x,y) =
-    assert(is_int(y), "The multiple must be an integer.")
+    assert(is_finite(y), "The multiple must be a finite number.")
     is_list(x)
     ?    [for (v=x) quantdn(v,y)]
     :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
@@ -338,7 +321,7 @@ function quantdn(x,y) =
 //   quantup([9,10,10.4,10.5,11,12],3);      // Returns: [9,12,12,12,12,12]
 //   quantup([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,12,12],[12,12,12]]
 function quantup(x,y) =
-    assert(is_int(y), "The multiple must be an integer.")
+    assert(is_finite(y), "The multiple must be a number.")
     is_list(x)
     ?    [for (v=x) quantup(v,y)]
     :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
@@ -384,7 +367,7 @@ function constrain(v, minval, maxval) =
 //   posmod(700,360);   // Returns: 340
 //   posmod(3,2.5);     // Returns: 0.5
 function posmod(x,m) = 
-    assert( is_finite(x) && is_int(m), "Input must be finite numbers.")
+    assert( is_finite(x) && is_finite(m), "Input must be finite numbers.")
     (x%m+m)%m;
 
 
@@ -421,7 +404,7 @@ function modang(x) =
 //   modrange(90,270,360, step=-45);  // Returns: [90,45,0,315,270]
 //   modrange(270,90,360, step=-45);  // Returns: [270,225,180,135,90]
 function modrange(x, y, m, step=1) =
-    assert( is_finite(x+y+step) && is_int(m), "Input must be finite numbers.")
+    assert( is_finite(x+y+step+m), "Input must be finite numbers.")
     let(
         a = posmod(x, m),
         b = posmod(y, m),
@@ -582,7 +565,6 @@ function cumsum(v, off) =
       ) S ];
 
 
-
 // Function: sum_of_squares()
 // Description:
 //   Returns the sum of the square of each element of a vector.
@@ -709,7 +691,6 @@ function convolve(p,q) =
     [for(i=[0:n+m-2], k1 = max(0,i-n+1), k2 = min(i,m-1) )
        [for(j=[k1:k2]) p[i-j] ] * [for(j=[k1:k2]) q[j] ] 
     ];
-     
 
 
 
@@ -899,11 +880,6 @@ function determinant(M) =
 //   m = optional height of matrix
 //   n = optional width of matrix
 //   square = set to true to require a square matrix.  Default: false        
-function is_matrix(A,m,n,square=false) =
-    is_vector(A[0],n) 
-    && is_vector(A*(0*A[0]),m)
-    && ( !square || len(A)==len(A[0]));
-    
 function is_matrix(A,m,n,square=false) =
     is_list(A[0]) 
     && ( let(v = A*A[0]) is_num(0*(v*v)) ) // a matrix of finite numbers 
@@ -1097,7 +1073,7 @@ function count_true(l, nmax) =
 //   h = the parametric sampling of the data.
 //   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv(data, h=1, closed=false) =
-    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
+    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
     assert( len(data)>=2, "Input `data` should have at least 2 elements.") 
     assert( is_finite(h) || is_vector(h), "The sampling `h` must be a number or a list of numbers." )
     assert( is_num(h) || len(h) == len(data)-(closed?0:1),
@@ -1159,7 +1135,7 @@ function _deriv_nonuniform(data, h, closed) =
 //   h = the constant parametric sampling of the data.
 //   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv2(data, h=1, closed=false) =
-    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
+    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
     assert( len(data)>=3, "Input list has less than 3 elements.") 
     assert( is_finite(h), "The sampling `h` must be a number." )
     let( L = len(data) )
@@ -1195,7 +1171,7 @@ function deriv2(data, h=1, closed=false) =
 //   the estimates are f'''(t) = (-5*f(t)+18*f(t+h)-24*f(t+2*h)+14*f(t+3*h)-3*f(t+4*h)) / 2h^3 and
 //   f'''(t) = (-3*f(t-h)+10*f(t)-12*f(t+h)+6*f(t+2*h)-f(t+3*h)) / 2h^3.
 function deriv3(data, h=1, closed=false) =
-    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
+    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
     assert( len(data)>=5, "Input list has less than 5 elements.") 
     assert( is_finite(h), "The sampling `h` must be a number." )
     let(
@@ -1273,7 +1249,13 @@ function polynomial(p, z, _k, _zk, _total) =
                       is_num(z) ? _zk*z : C_times(_zk,z), 
                       _total+_zk*p[_k]);
 
-
+function newpoly(p,z,k,total) =
+     is_undef(k)
+   ?    assert( is_vector(p) || p==[], "Input polynomial coefficients must be a vector." )
+        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
+        newpoly(p, z, 0, is_num(z) ? 0 : [0,0])
+   : k==len(p) ? total
+   : newpoly(p,z,k+1, is_num(z) ? total*z+p[k] : C_times(total,z)+[p[k],0]);
 
 // Function: poly_mult()
 // Usage

From d1fe73bb5ed542c1b3f5b3223ff52cd84e9ef6de Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Wed, 29 Jul 2020 06:50:14 +0100
Subject: [PATCH 16/24] Revert "restore polyhedra, shapes and shapes3d"

This reverts commit ea604f967215a0de46881c90706cc65fc356338e.
---
 polyhedra.scad |  785 ++++++++++++++++++++
 shapes.scad    | 1755 +++++++++++++++++++++++++++++++++++++++++++++
 shapes2d.scad  | 1866 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 4406 insertions(+)
 create mode 100644 polyhedra.scad
 create mode 100644 shapes.scad
 create mode 100644 shapes2d.scad

diff --git a/polyhedra.scad b/polyhedra.scad
new file mode 100644
index 0000000..3902ecf
--- /dev/null
+++ b/polyhedra.scad
@@ -0,0 +1,785 @@
+//////////////////////////////////////////////////////////////////////
+// LibFile: polyhedra.scad
+//   Useful platonic, archimedian, and catalan polyhedra.
+//   To use, add the following lines to the beginning of your file:
+//   ```
+//   include <BOSL2/std.scad>
+//   include <BOSL2/polyhedra.scad>
+//   ```
+//////////////////////////////////////////////////////////////////////
+
+
+include <hull.scad>
+
+
+// CommonCode:
+//   $fn=96;
+
+
+// Section: Polyhedra
+
+
+// Groups entries in "arr" into groups of equal values and returns index lists of those groups
+
+function _unique_groups(m) = [
+    for (i=[0:1:len(m)-1]) let(
+        s = search([m[i]], m, 0)[0]
+    ) if (s[0]==i) s
+];
+
+
+// TODO
+//
+// Use volume info?
+// Support choosing a face number down
+// Support multiple inspheres/outspheres when appropriate?
+// face order for children?
+// orient faces so an edge is parallel to the x-axis
+//
+
+
+// Module: regular_polyhedron()
+//
+// Description:
+//   Creates a regular polyhedron with optional rounding.  Children are placed on the polyhedron's faces.
+//   
+//   **Selecting the polyhedron:**
+//   You constrain the polyhedra list by specifying different characteristics, that must all be met
+//   * `name`: e.g. `"dodecahedron"` or `"pentagonal icositetrahedron"`
+//   * `type`: Options are `"platonic"`, `"archimedean"` and `"catalan"`
+//   * `faces`: The required number of faces
+//   * `facetype`: The required face type(s).  List of vertex counts for the faces.  Exactly the listed types of faces must appear:
+//     * `facetype = 3`: polyhedron with all triangular faces.
+//     * `facetype = [5,6]`: polyhedron with only pentagons and hexagons. (Must have both!)
+//   * hasfaces: The list of vertex counts for faces; at least one listed type must appear:
+//     * `hasfaces = 3`: polygon has at least one triangular face
+//     * `hasfaces = [5,6]`: polygon has a hexagonal or a pentagonal face
+//   
+//   The result is a list of selected polyhedra.  You then specify `index` to choose which one of the
+//   remaining polyhedra you want.  If you don't give `index` the first one on the list is created.
+//   Two examples:
+//   * `faces=12, index=2`:  Creates the 3rd solid with 12 faces
+//   * `type="archimedean", faces=14`: Creates the first archimedean solid with 14 faces (there are 3)
+//   
+//   **Choosing the size of your polyhedron:**
+//   The default is to create a polyhedron whose smallest edge has length 1.  You can specify the
+//   smallest edge length with the size option.  Alternatively you can specify the size of the
+//   inscribed sphere, midscribed sphere, or circumscribed sphere using `ir`, `mr` and `cr` respectively.
+//   If you specify `cr=3` then the outermost points of the polyhedron will be 3 units from the center.
+//   If you specify `ir=3` then the innermost faces of the polyhedron will be 3 units from the center.
+//   For the platonic solids every face meets the inscribed sphere and every corner touches the
+//   circumscribed sphere.  For the Archimedean solids the inscribed sphere will touch only some of
+//   the faces and for the Catalan solids the circumscribed sphere meets only some of the corners.
+//   
+//   **Orientation:**
+//   Orientation is controled by the facedown parameter.  Set this to false to get the canonical orientation.
+//   Set it to true to get the largest face oriented down.  If you set it to a number the module searches for
+//   a face with the specified number of vertices and orients that face down.
+//   
+//   **Rounding:**
+//   If you specify the rounding parameter the module makes a rounded polyhedron by first creating an
+//   undersized model and then expanding it with `minkowski()`.  This only produces the correct result
+//   if the in-sphere contacts all of the faces of the polyhedron, which is true for the platonic, the
+//   catalan solids and the trapezohedra but false for the archimedean solids.
+//   
+//   **Children:**
+//   The module places children on the faces of the polyhedron.  The child coordinate system is
+//   positioned so that the origin is the center of the face.  If `rotate_children` is true (default)
+//   then the coordinate system is oriented so the z axis is normal to the face, which lies in the xy
+//   plane.  If you give `repeat=true` (default) the children are cycled through to cover all faces.
+//   With `repeat=false` each child is used once.  You can specify `draw=false` to suppress drawing of
+//   the polyhedron, e.g. to use for `difference()` operations.  The module sets various parameters
+//   you can use in your children (see the side effects list below).
+//   
+//   **Stellation:**
+//   Technically stellation is an operation of shifting the polyhedron's faces to produce a new shape
+//   that may have self-intersecting faces.  OpenSCAD cannot handle self-intersecting faces, so we
+//   instead erect a pyramid on each face, a process technically referred to as augmentation.  The
+//   height of the pyramid is given by the `stellate` argument.  If `stellate` is `false` or `0` then
+//   no stellation is performed.  Otherwise stellate gives the pyramid height as a multiple of the
+//   edge length.  A negative pyramid height can be used to perform excavation, where a pyramid is
+//   removed from each face.
+//   
+//   **Special Polyhedra:**
+//   These can be selected only by name and may require different parameters, or ignore some standard
+//   parameters.
+//   * Trapezohedron: a family of solids with an even number of kite shaped sides.
+//     One example of a trapezohedron is the d10 die, which is a 10 face trapezohedron.
+//     You must specify exactly two of `side`, `longside`, `h`, and `r` (or `d`).
+//     You cannot create trapezohedron shapes using `mr`, `ir`, or `or`.
+//     * `side`: Length of the short side.
+//     * `longside`: Length of the long side that extends to the apex.
+//     * `h`: Distance from the center to the apex.
+//     * `r`: Radius of the polygon that defines the equatorial vertices.
+//     * `d`: Diameter of the polygon that defines the equatorial vertices.
+//   
+//   * Named stellations: various polyhedra such as the Kepler-Poinsot solids are stellations with
+//    specific pyramid heights.  To make them easier to generate you can specify them by name.
+//    This is equivalent to giving the name of the appropriate base solid and the magic stellate
+//    parameter needed to produce that shape.  The supported solids are:
+//     * `"great dodecahedron"`
+//     * `"small stellated dodecahedron"`
+//     * `"great stellated dodecahedron"`
+//
+// Arguments:
+//   name = Name of polyhedron to create.
+//   index = Index to select from polyhedron list.  Default: 0.
+//   type = Type of polyhedron: "platonic", "archimedean", "catalan".
+//   faces = Number of faces.
+//   facetype = Scalar or vector listing required type of faces as vertex count.  Polyhedron must have faces of every type listed and no other types.
+//   hasfaces = Scalar of vector list face vertex counts.  Polyhedron must have at least one of the listed types of face.
+//   side = Length of the smallest edge of the polyhedron.  Default: 1.
+//   ir = inner radius.  Polyhedron is scaled so it has the specified inner radius. Overrides side.
+//   mr = middle radius.  Polyhedron is scaled so it has the specified middle radius.  Overrides side.
+//   or = outer radius.   Polyhedron is scaled so it has the specified outer radius.  Overrides side.
+//   r = outer radius.  Overrides or.
+//   d = outer diameter.  Overrides or.
+//   anchor = Side of the origin to anchor to.  The bounding box of the polyhedron is aligned as specified.  Use directional constants from `constants.scad`.  Default: `CENTER`
+//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP+BACK+RIGHT`.
+//   facedown = If false display the solid in native orientation.  If true orient it with a largest face down.  If set to a vertex count, orient it so a face with the specified number of vertices is down.  Default: true.
+//   rounding = Specify a rounding radius for the shape.  Note that depending on $fn the dimensions of the shape may have small dimensional errors.
+//   repeat = If true then repeat the children to fill all the faces.  If false use only the available children and stop.  Default: true.
+//   draw = If true then draw the polyhedron.  If false, draw the children but not the polyhedron.  Default: true.
+//   rotate_children = If true then orient children normal to their associated face.  If false orient children to the parent coordinate system.  Default: true.
+//   stellate = Set to a number to erect a pyramid on every face of your polyhedron with the specified height.  The height is a multiple of the side length.  Default: false.
+//   longside = Specify the long side length for a trapezohedron.  Ignored for other shapes.
+//   h = Specify the height of the apex for a trapezohedron.  Ignored for other shapes.
+//
+// Side Effects:
+//   `$faceindex` - Index number of the face
+//   `$face` - Coordinates of the face (2d if rotate_children==true, 3d if not)
+//   `$center` - Polyhedron center in the child coordinate system
+//
+// Examples: All of the available polyhedra by name in their native orientation
+//   regular_polyhedron("tetrahedron", facedown=false);
+//   regular_polyhedron("cube", facedown=false);
+//   regular_polyhedron("octahedron", facedown=false);
+//   regular_polyhedron("dodecahedron", facedown=false);
+//   regular_polyhedron("icosahedron", facedown=false);
+//   regular_polyhedron("truncated tetrahedron", facedown=false);
+//   regular_polyhedron("truncated octahedron", facedown=false);
+//   regular_polyhedron("truncated cube", facedown=false);
+//   regular_polyhedron("truncated icosahedron", facedown=false);
+//   regular_polyhedron("truncated dodecahedron", facedown=false);
+//   regular_polyhedron("cuboctahedron", facedown=false);
+//   regular_polyhedron("icosidodecahedron", facedown=false);
+//   regular_polyhedron("rhombicuboctahedron", facedown=false);
+//   regular_polyhedron("rhombicosidodecahedron", facedown=false);
+//   regular_polyhedron("truncated cuboctahedron", facedown=false);
+//   regular_polyhedron("truncated icosidodecahedron", facedown=false);
+//   regular_polyhedron("snub cube", facedown=false);
+//   regular_polyhedron("snub dodecahedron", facedown=false);
+//   regular_polyhedron("triakis tetrahedron", facedown=false);
+//   regular_polyhedron("tetrakis hexahedron", facedown=false);
+//   regular_polyhedron("triakis octahedron", facedown=false);
+//   regular_polyhedron("pentakis dodecahedron", facedown=false);
+//   regular_polyhedron("triakis icosahedron", facedown=false);
+//   regular_polyhedron("rhombic dodecahedron", facedown=false);
+//   regular_polyhedron("rhombic triacontahedron", facedown=false);
+//   regular_polyhedron("deltoidal icositetrahedron", facedown=false);
+//   regular_polyhedron("deltoidal hexecontahedron", facedown=false);
+//   regular_polyhedron("disdyakis dodecahedron", facedown=false);
+//   regular_polyhedron("disdyakis triacontahedron", facedown=false);
+//   regular_polyhedron("pentagonal icositetrahedron", facedown=false);
+//   regular_polyhedron("pentagonal hexecontahedron", facedown=false);
+//   regular_polyhedron("trapezohedron",faces=10, side=1, longside=2.25, facedown=false);
+//   regular_polyhedron("great dodecahedron");
+//   regular_polyhedron("small stellated dodecahedron");
+//   regular_polyhedron("great stellated dodecahedron");
+// Example: Third Archimedean solid
+//   regular_polyhedron(type="archimedean", index=2);
+// Example(Med): Solids that have 8 or 10 vertex faces
+//   N = len(regular_polyhedron_info("index set", hasfaces=[8,10]));
+//   for(i=[0:N-1]) right(3*i)
+//     regular_polyhedron(hasfaces=[8,10], index=i, mr=1);
+// Example(Big): Solids that include a quadrilateral face
+//   N = len(regular_polyhedron_info("index set", hasfaces=4));
+//   for(i=[0:N-1]) right(3*i)
+//     regular_polyhedron(hasfaces=4, index=i, mr=1);
+// Example(Med): Solids with only quadrilateral faces
+//   N = len(regular_polyhedron_info("index set", facetype=4));
+//   for(i=[0:N-1]) right(3*i)
+//     regular_polyhedron(facetype=4, index=i, mr=1);
+// Example: Solids that have both pentagons and hexagons and no other face types
+//   N = len(regular_polyhedron_info("index set", facetype=[5,6]));
+//   for(i=[0:N-1]) right(3*i)
+//     regular_polyhedron(facetype=[5,6], index=i, mr=1);
+// Example: Rounded octahedron
+//   regular_polyhedron("octahedron", side=1, rounding=.2);
+// Example: Rounded catalon solid
+//   regular_polyhedron("rhombic dodecahedron", side=1, rounding=0.2);
+// Example(Med): Rounded Archimedean solid compared to unrounded version.  The small faces are shifted back from their correct position.
+//   %regular_polyhedron(type="archimedean", mr=1, rounding=0);
+//   regular_polyhedron(type="archimedean", mr=1, rounding=0.3);
+// Example: Two children are distributed arbitrarily over the faces
+//   regular_polyhedron(faces=12,index=2,repeat=true) {
+//     color("red") sphere(r=.1);
+//     color("green") sphere(r=.1);
+//   }
+// Example(FlatSpin): Difference the children from the polyhedron; children depend on $faceindex
+//   difference(){
+//     regular_polyhedron("tetrahedron", side=25);
+//     regular_polyhedron("tetrahedron", side=25,draw=false)
+//       down(.3) linear_extrude(height=1)
+//         text(str($faceindex),halign="center",valign="center");
+//   }
+// Example(Big): With `rotate_children` you can control direction of the children.
+//   regular_polyhedron(name="tetrahedron", anchor=UP, rotate_children=true)
+//     cylinder(r=.1, h=.5);
+//   right(2) regular_polyhedron(name="tetrahedron", anchor=UP, rotate_children=false)
+//     cylinder(r=.1, h=.5);
+// Example(FlatSpin,Med): Using `$face` you can have full control of the construction of your children.  This example constructs the Great Icosahedron.
+//   module makestar(pts) {    // Make a star from a point list
+//       polygon(
+//         [
+//           for(i=[0:len(pts)-1]) let(
+//             p0=select(pts,i),
+//             p1=select(pts,i+1),
+//             center=(p0+p1)/2,
+//             v=sqrt(7/4-PHI)*(p1-p0)
+//           ) each [p0, [v.y+center.x, -v.x+center.y]]
+//         ]
+//       );
+//   }
+//   regular_polyhedron("dodecahedron", side=1, repeat=true)
+//   linear_extrude(scale=0, height=sqrt((5+2*sqrt(5))/5)) makestar($face);
+// Example(Med): The spheres are all radius 1 and the octahedra are sized to match the in-sphere, mid-sphere and out-sphere.  The sphere size is slightly adjusted for the in-sphere and out-sphere so you can see the relationship: the sphere is tangent to the faces for the former and the corners poke out for the latter.  Note also the difference in the size of the three octahedra.
+//   sphere(r=1.005);
+//   %regular_polyhedron("octahedron", ir=1, facedown=false);
+//   right(3.5) {
+//     sphere(r=1);
+//     %regular_polyhedron("octahedron", mr=1, facedown=false);
+//   }
+//   right(6.5) {
+//     %sphere(r=.95);  // Slightly undersized sphere means the points poke out a bit
+//     regular_polyhedron("octahedron", or=1,facedown=false);
+//   }
+// Example(Med): For the Archimdean solids the in-sphere does not touch all of the faces, as shown by this example, but the circumscribed sphere meets every vertex.  (This explains the problem for rounding over these solids because the rounding method uses the in-sphere.)
+//   sphere(r=1.005);
+//   %regular_polyhedron("snub dodecahedron", ir=1, facedown=false);
+//   right(3) {
+//     sphere(r=1);
+//     %regular_polyhedron("snub dodecahedron", mr=1, facedown=false);
+//   }
+//   right(6) {
+//     %sphere(r=.99);
+//     regular_polyhedron("snub dodecahedron", or=1,facedown=false);
+//   }
+// Example(Med): For a Catalan solid the in-sphere touches every face but the circumscribed sphere only touches some vertices.
+//   sphere(r=1.002);
+//   %regular_polyhedron("pentagonal hexecontahedron", ir=1, facedown=false);
+//   right(3) {
+//     sphere(r=1);
+//     %regular_polyhedron("pentagonal hexecontahedron", mr=1, facedown=false);
+//   }
+//   right(6) {
+//     %sphere(r=.98);
+//     regular_polyhedron("pentagonal hexecontahedron", or=1,facedown=false);
+//   }
+module regular_polyhedron(
+    name=undef,
+    index=undef,
+    type=undef,
+    faces=undef,
+    facetype=undef,
+    hasfaces=undef,
+    side=1,
+    ir=undef,
+    mr=undef,
+    or=undef,
+    r=undef,
+    d=undef,
+    anchor=[0,0,0],
+    center=undef,
+    rounding=0,
+    repeat=true,
+    facedown=true,
+    draw=true,
+    rotate_children=true,
+    stellate = false,
+    longside=undef, // special parameter for trapezohedron
+    h=undef         // special parameter for trapezohedron
+) {
+    assert(rounding>=0, "'rounding' must be nonnegative");
+    entry = regular_polyhedron_info(
+        "fullentry", name=name, index=index,
+        type=type, faces=faces, facetype=facetype,
+        hasfaces=hasfaces, side=side,
+        ir=ir, mr=mr, or=or,
+        r=r, d=d,
+        anchor=anchor, center=center,
+        facedown=facedown,
+        stellate=stellate,
+        longside=longside, h=h
+    );
+    scaled_points = entry[0];
+    translation = entry[1];
+    face_triangles = entry[2];
+    faces = entry[3];
+    face_normals = entry[4];
+    in_radius = entry[5];
+    if (draw){
+        if (rounding==0)
+            polyhedron(move(translation, p=scaled_points), faces = face_triangles);
+        else {
+            fn = segs(rounding);
+            rounding = rounding/cos(180/fn);
+            adjusted_scale = 1 - rounding / in_radius;
+            minkowski(){
+                sphere(r=rounding, $fn=fn);
+                polyhedron(move(translation,p=adjusted_scale*scaled_points), faces = face_triangles);
+            }
+        }
+    }
+    if ($children>0) {
+        maxrange = repeat ? len(faces)-1 : $children-1;
+        for(i=[0:1:maxrange]) {
+            // Would like to orient so an edge (longest edge?) is parallel to x axis
+            facepts = move(translation, p=select(scaled_points, faces[i]));
+            center = mean(facepts);
+            rotatedface = rot(from=face_normals[i], to=[0,0,1], p=move(-center, p=facepts));
+            clockwise = sortidx([for(pt=rotatedface) -atan2(pt.y,pt.x)]);
+            $face = rotate_children?
+                        path2d(select(rotatedface,clockwise)) :
+                        select(move(-center,p=facepts), clockwise);
+            $faceindex = i;
+            $center = -translation-center;
+            translate(center)
+            if (rotate_children) {
+                rot(from=[0,0,1], to=face_normals[i])
+                children(i % $children);
+            } else {
+                children(i % $children);
+            }
+        }
+    }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Some internal functions used to generate polyhedra data
+//
+// All permutations and even permutations of three items
+//
+function _even_perms(v) = [v, [v[2], v[0], v[1]], [v[1],v[2],v[0]]];
+function _all_perms(v) = [v, [v[2], v[0], v[1]], [v[1],v[2],v[0]], [v[1],v[0],v[2]],[v[2],v[1],v[0]],[v[0],v[2],v[1]]];
+//
+// Point reflections across all planes.    In the unconstrained case, this means one point becomes 8 points.
+//
+// sign=="even" means an even number of minus signs (odd number of plus signs)
+// sign=="odd" means an odd number of minus signs (even number of plus signs)
+//
+function _point_ref(points, sign="both") =
+    unique([
+        for(i=[-1,1],j=[-1,1],k=[-1,1])
+            if (sign=="both" || sign=="even" && i*j*k>0 || sign=="odd" && i*j*k<0)
+                each [for(point=points) vmul(point,[i,j,k])]
+    ]);
+//
+_tribonacci=(1+4*cosh(acosh(2+3/8)/3))/3;
+//
+/////////////////////////////////////////////////////////////////////////////
+//
+// Polyhedra data table.
+// The polyhedra information is from Wikipedia and http://dmccooey.com/polyhedra/
+//
+_polyhedra_ = [
+    // Platonic Solids
+
+    ["tetrahedron", "platonic", 4,[3], 2*sqrt(2), sqrt(6)/12, sqrt(2)/4, sqrt(6)/4, 1/6/sqrt(2),
+            _point_ref([[1,1,1]], sign="even")],
+    ["cube", "platonic", 6, [4], 2, 1/2, 1/sqrt(2), sqrt(3)/2, 1,
+            _point_ref([[1,1,1]])],
+    ["octahedron", "platonic", 8, [3], sqrt(2), sqrt(6)/6, 1/2, sqrt(2)/2, sqrt(2)/3,
+            _point_ref(_even_perms([1,0,0]))],
+    ["dodecahedron", "platonic", 12, [5], 2/PHI, sqrt(5/2+11*sqrt(5)/10)/2, (3+sqrt(5))/4, sqrt(3)*PHI/2, (15+7*sqrt(5))/4,
+            _point_ref(concat([[1,1,1]],_even_perms([0,PHI,1/PHI])))],
+    ["icosahedron", "platonic", 20, [3], 2, PHI*PHI/2/sqrt(3), cos(36), sin(72), 5*(3+sqrt(5))/12,
+            _point_ref(_even_perms([0,1,PHI]))],
+
+    // Archimedian Solids, listed in order by Wenniger number, W6-W18
+
+    ["truncated tetrahedron", "archimedean", 8,[6,3], sqrt(8), sqrt(6)/4, 3*sqrt(2)/4, sqrt(11/8), 23*sqrt(2)/12,
+            _point_ref(_all_perms([1,1,3]),sign="even")],
+    ["truncated octahedron", "archimedean", 14, [6,4], sqrt(2), sqrt(6)/2, 1.5, sqrt(10)/2, 8*sqrt(2),
+            _point_ref(_all_perms([0,1,2]))],
+    ["truncated cube", "archimedean", 14, [8,3], 2*(sqrt(2)-1), (1+sqrt(2))/2, 1+sqrt(2)/2, sqrt(7+4*sqrt(2))/2, 7+14*sqrt(2)/3,
+            _point_ref(_all_perms([1,1,sqrt(2)-1]))],
+    ["truncated icosahedron", "archimedean", 32, [6, 5], 2, (3*sqrt(3)+sqrt(15))/4, 3*PHI/2, sqrt(58+18*sqrt(5))/4, (125+43*sqrt(5))/4,
+            _point_ref(concat(
+                _even_perms([0,1,3*PHI]),
+                _even_perms([1,2+PHI,2*PHI]),
+                _even_perms([PHI,2,PHI*PHI*PHI])
+            ))],
+    ["truncated dodecahedron", "archimedean", 32, [10, 3], 2*PHI-2, sqrt(7+11*PHI)/2, (3*PHI+1)/2,sqrt(11+PHI*15)/2, 5*(99+47*sqrt(5))/12,
+            _point_ref(concat(
+                _even_perms([0,1/PHI, 2+PHI]),
+                _even_perms([1/PHI,PHI,2*PHI]),
+                _even_perms([PHI,2,PHI+1])
+            ))],
+    ["cuboctahedron", "archimedean", 14, [4,3], sqrt(2), sqrt(2)/2, sqrt(3)/2, 1, 5*sqrt(2)/3,
+            _point_ref(_all_perms([1,1,0]))],
+    ["icosidodecahedron", "archimedean", 32, [5,3], 1, sqrt(5*(5+2*sqrt(5)))/5,sqrt(5+2*sqrt(5))/2, PHI, (14+17*PHI)/3,
+            _point_ref(concat(_even_perms([0,0,PHI]),_even_perms([1/2,PHI/2,PHI*PHI/2])))],
+    ["rhombicuboctahedron", "archimedean", 26, [4, 3], 2, (1+sqrt(2))/2, sqrt(2*(2+sqrt(2)))/2, sqrt(5+2*sqrt(2))/2, 4+10*sqrt(2)/3,
+            _point_ref(_even_perms([1,1,1+sqrt(2)]))],
+    ["rhombicosidodecahedron", "archimedean", 62, [5,4,3], 2, 3/10*sqrt(15+20*PHI), sqrt(3/2+2*PHI), sqrt(8*PHI+7)/2, (31+58*PHI)/3,
+            _point_ref(concat(
+                _even_perms([1,1,PHI*PHI*PHI]),
+                _even_perms([PHI*PHI,PHI,2*PHI]),
+                _even_perms([2+PHI,0,PHI*PHI])
+            ))],
+    ["truncated cuboctahedron", "archimedean", 26, [8, 6, 4], 2, (1+2*sqrt(2))/2, sqrt(6*(2+sqrt(2)))/2, sqrt(13+6*sqrt(2))/2, (22+14*sqrt(2)),
+            _point_ref(_all_perms([1,1+sqrt(2), 1+2*sqrt(2)]))],
+    ["truncated icosidodecahedron", "archimedean", 62, [10,6,4], 2*PHI - 2, sqrt(15/4+5*PHI),sqrt(9/2+6*PHI),sqrt(19/4+6*PHI), 95+50*sqrt(5),
+            _point_ref(concat(
+                _even_perms([1/PHI,1/PHI,3+PHI]),
+                _even_perms([2/PHI,PHI,1+2*PHI]),
+                _even_perms([1/PHI,PHI*PHI,3*PHI-1]),
+                _even_perms([2*PHI-1,2,2+PHI]),
+                _even_perms([PHI,3,2*PHI])
+            ))],
+    ["snub cube", "archimedean",    38, [4,3], 1.60972,1.14261350892596209,1.24722316799364325, 1.34371337374460170,
+            sqrt((613*_tribonacci+203)/(9*(35*_tribonacci-62))),
+            concat(
+                _point_ref(_even_perms([1,1/_tribonacci,_tribonacci]), sign="odd"),
+                _point_ref(_even_perms([1,_tribonacci,1/_tribonacci]), sign="even")
+            )],
+    ["snub dodecahedron", "archimedean", 92, [5, 3], 1, 1.98091594728184,2.097053835252087,2.155837375115, 37.61664996273336,
+            concat(
+                _point_ref(_even_perms([0.374821658114562,0.330921024729844,2.097053835252088]), sign="odd"),
+                _point_ref(_even_perms([0.192893711352359,1.249503788463027,1.746186440985827]), sign="odd"),
+                _point_ref(_even_perms([1.103156835071754,0.847550046789061,1.646917940690374]), sign="odd"),
+                _point_ref(_even_perms([0.567715369466922,0.643029605914072,1.977838965420219]), sign="even"),
+                _point_ref(_even_perms([1.415265416255982,0.728335176957192,1.454024229338015]), sign="even")
+            )],
+
+    // Catalan Solids, the duals to the Archimedean solids, listed in the corresponding order
+
+    ["triakis tetrahedron","catalan", 12, [3], 9/5, 5*sqrt(22)/44, 5*sqrt(2)/12, 5*sqrt(6)/12, 25*sqrt(2)/36,
+            concat(
+                _point_ref([9*sqrt(2)/20*[1,1,1]],sign="even"),
+                _point_ref([3*sqrt(2)/4*[1,1,1]],sign="odd")
+            )],
+    ["tetrakis hexahedron", "catalan", 24, [3], 1, 2/sqrt(5), 2*sqrt(2)/3, 2/sqrt(3), 32/9,
+            _point_ref(concat([[2/3,2/3,2/3]],_even_perms([1,0,0])))],
+    ["triakis octahedron", "catalan", 24, [3], 2, sqrt(17*(23+16*sqrt(2)))/34, 1/2+sqrt(2)/4,(1+sqrt(2))/2,3/2+sqrt(2),
+            _point_ref(concat([[1,1,1]],_even_perms([1+sqrt(2),0,0])))],
+    ["pentakis dodecahedron", "catalan", 60, [3], 1,sqrt(477/436+97*sqrt(5)/218), sqrt(5)/4+11/12, sqrt(7/4+sqrt(5)/3), 125*sqrt(5)/36+205/36,
+            _point_ref(concat(
+                _even_perms([0,(5-PHI)/6, PHI/2+2/3]),
+                _even_perms([0,(PHI+1)/2,PHI/2]),[(4*PHI-1)/6 * [1,1,1]]
+            ))],
+    ["triakis icosahedron", "catalan", 60, [3], 1, sqrt((139+199*PHI)/244), (8*PHI+1)/10, sqrt(13/8+19/8/sqrt(5)), (13*PHI+3)/2,
+            _point_ref(concat(
+                _even_perms([(PHI+7)/10, 0, (8*PHI+1)/10]),
+                _even_perms([0, 1/2, (PHI+1)/2]),[PHI/2*[1,1,1]]
+            ))],
+    ["rhombic dodecahedron", "catalan", 12, [4], sqrt(3), sqrt(2/3), 2*sqrt(2)/3, 2/sqrt(3), 16*sqrt(3)/9,
+            _point_ref(concat([[1,1,1]], _even_perms([2,0,0])))],
+    ["rhombic triacontahedron", "catalan", 30,[4], 1, sqrt(1+2/sqrt(5)), 1+1/sqrt(5), (1+sqrt(5))/2, 4*sqrt(5+2*sqrt(5)),
+            concat(
+                _point_ref(_even_perms([0,sqrt(1+2/sqrt(5)), sqrt((5+sqrt(5))/10)])),
+                _point_ref(_even_perms([0,sqrt(2/(5+sqrt(5))), sqrt(1+2/sqrt(5))])),
+                _point_ref([sqrt((5+sqrt(5))/10)*[1,1,1]])
+            )],
+    ["deltoidal icositetrahedron", "catalan", 24, [4], 2*sqrt(10-sqrt(2))/7, 7*sqrt((7+4*sqrt(2))/(34 * (10-sqrt(2)))),
+            7*sqrt(2*(2+sqrt(2)))/sqrt(10-sqrt(2))/4, 7*sqrt(2)/sqrt(10-sqrt(2))/2,
+            (14+21*sqrt(2))/sqrt(10-sqrt(2)),
+            _point_ref(concat(
+                _even_perms([0,1,1]), _even_perms([sqrt(2),0,0]),
+                _even_perms((4+sqrt(2))/7*[1,1,1])
+            ))],
+    ["deltoidal hexecontahedron", "catalan", 60, [4], sqrt(5*(85-31*sqrt(5)))/11, sqrt(571/164+1269/164/sqrt(5)), 5/4+13/4/sqrt(5),
+            sqrt(147+65*sqrt(5))/6, sqrt(29530+13204*sqrt(5))/3,
+            _point_ref(concat(
+                _even_perms([0,0,sqrt(5)]),
+                _even_perms([0,(15+sqrt(5))/22, (25+9*sqrt(5))/22]),
+                _even_perms([0,(5+3*sqrt(5))/6, (5+sqrt(5))/6]),
+                _even_perms([(5-sqrt(5))/4, sqrt(5)/2, (5+sqrt(5))/4]),
+                [(5+4*sqrt(5))/11*[1,1,1]]
+            ))],
+    ["disdyakis dodecahedron", "catalan", 48, [3], 1,sqrt(249/194+285/194/sqrt(2)) ,(2+3*sqrt(2))/4, sqrt(183/98+213/98/sqrt(2)),
+            sqrt(6582+4539*sqrt(2))/7,
+            _point_ref(concat(
+                _even_perms([sqrt(183/98+213/98/sqrt(2)),0,0]),
+                _even_perms(sqrt(3+3/sqrt(2))/2 * [1,1,0]),[7/sqrt(6*(10-sqrt(2)))*[1,1,1]]
+            ))],
+    ["disdyakis triacontahedron","catalan", 120, [3], sqrt(15*(85-31*sqrt(5)))/11, sqrt(3477/964+7707/964/sqrt(5)), 5/4+13/4/sqrt(5),
+            sqrt(441+195*sqrt(5))/10,sqrt(17718/5+39612/5/sqrt(5)),
+            _point_ref(concat(
+                _even_perms([0,0,3*(5+4*sqrt(5))/11]),
+                _even_perms([0,(5-sqrt(5))/2,(5+sqrt(5))/2]),
+                _even_perms([0,(15+9*sqrt(5))/10,3*(5+sqrt(5))/10]),
+                _even_perms([3*(15+sqrt(5))/44,3*(5+4*sqrt(5))/22, (75+27*sqrt(5))/44]), [sqrt(5)*[1,1,1]]
+            ))],
+    ["pentagonal icositetrahedron","catalan",24, [5], 0.593465355971, 1.950681331784, 2.1015938932963, 2.29400105368695, 35.6302020120713,
+            concat(
+                _point_ref(_even_perms([0.21879664300048044,0.740183741369857,1.0236561781126901]),sign="even"),
+                _point_ref(_even_perms([0.21879664300048044,1.0236561781126901,0.740183741369857]),sign="odd"),
+                _point_ref(_even_perms([1.3614101519264425,0,0])),
+                _point_ref([0.7401837413698572*[1,1,1]])
+            )],
+    ["pentagonal hexecontahedron", "catalan", 60,[5], 0.58289953474498, 3.499527848905764,3.597624822551189,3.80854772878239, 189.789852066885,
+            concat(
+                _point_ref(_even_perms([0.192893711352359,0.218483370127321,2.097053835252087]), sign="even"),
+                _point_ref(_even_perms([0,0.7554672605165955,1.9778389654202186])),
+                _point_ref(_even_perms([0,1.888445389283669154,1.1671234364753339])),
+                _point_ref(_even_perms([0.56771536946692131,0.824957552676275846,1.8654013108176956657]),sign="odd"),
+                _point_ref(_even_perms([0.37482165811456229,1.13706613386050418,1.746186440985826345]), sign="even"),
+                _point_ref(_even_perms([0.921228888309550,0.95998770139158,1.6469179406903744]),sign="even"),
+                _point_ref(_even_perms([0.7283351769571914773,1.2720962825758121,1.5277030708585051]),sign="odd"),
+                _point_ref([1.222371704903623092*[1,1,1]])
+            )],
+];
+
+
+_stellated_polyhedra_ = [
+    ["great dodecahedron", "icosahedron", -sqrt(5/3-PHI)],
+    ["small stellated dodecahedron", "dodecahedron", sqrt((5+2*sqrt(5))/5)],
+    ["great stellated dodecahedron", "icosahedron", sqrt(2/3+PHI)],
+];
+
+
+// Function: regular_polyhedron_info()
+//
+// Usage: regular_polyhedron_info(info, ....)
+//
+// Description:
+//   Calculate characteristics of regular polyhedra or the selection set for regular_polyhedron().
+//   Invoke with the same arguments used by regular_polyhedron() and use the `info` argument to
+//   request the desired return value. Set `info` to:
+//     * `"vnf"`: vnf for the selected polyhedron
+//     * `"vertices"`: vertex list for the selected polyhedron
+//     * `"faces"`: list of faces for the selected polyhedron, where each entry on the list is a list of point index values to be used with the vertex list
+//     * `"face normals"`: list of normal vectors for each face
+//     * `"in_radius"`: in-sphere radius for the selected polyhedron
+//     * `"mid_radius"`: mid-sphere radius for the selected polyhedron
+//     * `"out_radius"`: circumscribed sphere radius for the selected polyhedron
+//     * `"index set"`: index set selected by your specifications; use its length to determine the valid range for `index`.
+//     * `"face vertices"`: number of vertices on the faces of the selected polyhedron (always a list)
+//     * `"edge length"`: length of the smallest edge of the selected polyhedron
+//     * `"center"`: center for the polyhedron
+//     * `"type"`: polyhedron type, one of "platonic", "archimedean", "catalan", or "trapezohedron"
+//     * `"name"`: name of selected polyhedron
+//
+// Arguments:
+//   name = Name of polyhedron to create.
+//   index = Index to select from polyhedron list.  Default: 0.
+//   type = Type of polyhedron: "platonic", "archimedean", "catalan".
+//   faces = Number of faces.
+//   facetype = Scalar or vector listing required type of faces as vertex count.  Polyhedron must have faces of every type listed and no other types.
+//   hasfaces = Scalar of vector list face vertex counts.  Polyhedron must have at least one of the listed types of face.
+//   side = Length of the smallest edge of the polyhedron.  Default: 1.
+//   ir = inner radius.  Polyhedron is scaled so it has the specified inner radius. Overrides side.
+//   mr = middle radius.  Polyhedron is scaled so it has the specified middle radius.  Overrides side.
+//   or = outer radius.   Polyhedron is scaled so it has the specified outer radius.  Overrides side.
+//   r = outer radius.  Overrides or.
+//   d = outer diameter.  Overrides or.
+//   anchor = Side of the origin to anchor to.  The bounding box of the polyhedron is aligned as specified.  Use directional constants from `constants.scad`.  Default: `CENTER`
+//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP+BACK+RIGHT`.
+//   facedown = If false display the solid in native orientation.  If true orient it with a largest face down.  If set to a vertex count, orient it so a face with the specified number of vertices is down.  Default: true.
+//   rounding = Specify a rounding radius for the shape.  Note that depending on $fn the dimensions of the shape may have small dimensional errors.
+//   repeat = If true then repeat the children to fill all the faces.  If false use only the available children and stop.  Default: true.
+//   draw = If true then draw the polyhedron.  If false, draw the children but not the polyhedron.  Default: true.
+//   rotate_children = If true then orient children normal to their associated face.  If false orient children to the parent coordinate system.  Default: true.
+//   stellate = Set to a number to erect a pyramid on every face of your polyhedron with the specified height.  The height is a multiple of the side length.  Default: false.
+//   longside = Specify the long side length for a trapezohedron.  Ignored for other shapes.
+//   h = Specify the height of the apex for a trapezohedron.  Ignored for other shapes.
+function regular_polyhedron_info(
+    info=undef, name=undef,
+    index=undef, type=undef,
+    faces=undef, facetype=undef,
+    hasfaces=undef, side=1,
+    ir=undef, mr=undef, or=undef,
+    r=undef, d=undef,
+    anchor=[0,0,0], center=undef,
+    facedown=true, stellate=false,
+    longside=undef, h=undef  // special parameters for trapezohedron
+) = let(
+        anchor = !is_undef(center) ? [0,0,0] : anchor,
+        argcount = num_defined([ir,mr,or,r,d])
+    )
+    assert(argcount<=1, "You must specify only one of 'ir', 'mr', 'or', 'r', and 'd'")
+    let(
+        //////////////////////
+        //Index values into the _polyhedra_ array
+        //
+        pname = 0,        // name of polyhedron
+        class = 1,        // class name (e.g. platonic, archimedean)
+        facecount = 2,    // number of faces
+        facevertices = 3, // vertices on the faces, e.g. [3] for all triangles, [3,4] for triangles and squares
+        edgelen = 4,      // length of the edge for the vertex list in the database
+        in_radius = 5,    // in radius for unit polyhedron (shortest side 1)
+        mid_radius = 6,   // mid radius for unit polyhedron
+        out_radius = 7,   // out radius for unit polyhedron
+        volume = 8,       // volume of unit polyhedron (data not validated, not used right now)
+        vertices = 9,     // vertex list (in arbitrary order)
+        //////////////////////
+        r = !is_undef(d) ? d/2 : r,
+        or = !is_undef(r) ? r : or,
+        stellate_index = search([name], _stellated_polyhedra_, 1, 0)[0],
+        name = stellate_index==[] ? name : _stellated_polyhedra_[stellate_index][1],
+        stellate = stellate_index==[] ? stellate : _stellated_polyhedra_[stellate_index][2],
+        indexlist = (
+            name=="trapezohedron" ? [0] : [  // dumy list of one item
+                for(i=[0:1:len(_polyhedra_)-1]) (
+                    if (
+                        (is_undef(name) || _polyhedra_[i][pname]==name) &&
+                        (is_undef(type) || _polyhedra_[i][class]==type) &&
+                        (is_undef(faces) || _polyhedra_[i][facecount]==faces) &&
+                        (
+                            is_undef(facetype) || 0==compare_lists(
+                                is_list(facetype)? reverse(sort(facetype)) : [facetype],
+                                _polyhedra_[i][facevertices]
+                            )
+                        ) &&
+                        (is_undef(hasfaces) || any([for (ft=hasfaces) in_list(ft,_polyhedra_[i][facevertices])]))
+                    ) i
+                )
+            ]
+        )
+    )
+    assert(len(indexlist)>0, "No polyhedra meet your specification")
+    let(validindex = is_undef(index) || (index>=0 && index<len(indexlist)))
+    assert(validindex, str(
+        len(indexlist),
+        " polyhedra meet specifications, so 'index' must be in [0,",
+        len(indexlist)-1,
+        "], but 'index' is ",
+        index
+    ))
+    let(
+        entry = (
+            name == "trapezohedron"? (
+                trapezohedron(faces=faces, side=side, longside=longside, h=h, r=r)
+            ) : (
+                _polyhedra_[!is_undef(index)?
+                    indexlist[index] :
+                    indexlist[0]]
+            )
+        ),
+        valid_facedown = is_bool(facedown) || in_list(facedown, entry[facevertices])
+    )
+    assert(valid_facedown,str("'facedown' set to ",facedown," but selected polygon only has faces with size(s) ",entry[facevertices]))
+    let(
+        scalefactor = (
+            name=="trapezohedron" ? 1 : (
+                argcount == 0? side :
+                !is_undef(ir)? ir/entry[in_radius] :
+                !is_undef(mr)? mr/entry[mid_radius] : or/entry[out_radius]
+            ) / entry[edgelen]
+        ),
+        face_triangles = hull(entry[vertices]),
+        faces_normals_vertices = stellate_faces(
+            entry[edgelen], stellate, entry[vertices],
+            entry[facevertices]==[3]?
+                [face_triangles, [for(face=face_triangles) _facenormal(entry[vertices],face)]] :
+                _full_faces(entry[vertices], face_triangles)
+        ),
+        faces = faces_normals_vertices[0],
+        faces_vertex_count = [for(face=faces) len(face)],
+        facedown = facedown == true ? (stellate==false? entry[facevertices][0] : 3) : facedown,
+        down_direction = facedown == false?  [0,0,-1] :
+            faces_normals_vertices[1][search(facedown, faces_vertex_count)[0]],
+        scaled_points = scalefactor * rot(p=faces_normals_vertices[2], from=down_direction, to=[0,0,-1]),
+        bounds = pointlist_bounds(scaled_points),
+        boundtable = [bounds[0], [0,0,0], bounds[1]],
+        translation = [for(i=[0:2]) -boundtable[1+anchor[i]][i]],
+        face_normals = rot(p=faces_normals_vertices[1], from=down_direction, to=[0,0,-1]),
+        side_length = scalefactor * entry[edgelen]
+    )
+    info == "fullentry" ? [
+        scaled_points,
+        translation,
+        stellate ? faces : face_triangles,
+        faces,
+        face_normals,
+        side_length*entry[in_radius]
+    ] :
+    info == "vnf" ? [move(translation,p=scaled_points), stellate ? faces : face_triangles] : 
+    info == "vertices" ? move(translation,p=scaled_points) :
+    info == "faces" ? faces :
+    info == "face normals" ? face_normals :
+    info == "in_radius" ? side_length * entry[in_radius] :
+    info == "mid_radius" ? side_length * entry[mid_radius] :
+    info == "out_radius" ? side_length * entry[out_radius] :
+    info == "index set" ? indexlist :
+    info == "face vertices" ? (stellate==false? entry[facevertices] : [3]) :
+    info == "edge length" ? scalefactor * entry[edgelen] :
+    info == "center" ? translation :
+    info == "type" ? entry[class] :
+    info == "name" ? entry[pname] :
+    echo_warning(str("Unknown info type '",info,"' requested"));
+
+
+
+/// hull solution fails due to roundoff
+/// either cross product or just rotate to
+///
+function stellate_faces(scalefactor,stellate,vertices,faces_normals) =
+    (stellate == false || stellate == 0)? concat(faces_normals,[vertices]) :
+    let(
+        faces = [for(face=faces_normals[0]) select(face,hull(select(vertices,face)))],
+        direction = [for(i=[0:1:len(faces)-1]) _facenormal(vertices, faces[i])*faces_normals[1][i]>0 ? 1 : -1],
+        maxvertex = len(vertices),
+        newpts = [for(i=[0:1:len(faces)-1]) mean(select(vertices,faces[i]))+stellate*scalefactor*faces_normals[1][i]],
+        newfaces = [for(i=[0:1:len(faces)-1], j=[0:len(faces[i])-1]) concat([i+maxvertex],select(faces[i], [j, j+direction[i]]))],
+        allpts = concat(vertices, newpts),
+        normals = [for(face=newfaces) _facenormal(allpts,face)]
+    ) [newfaces, normals, allpts];
+
+
+function trapezohedron(faces, r, side, longside, h) =
+    assert(faces%2==0, "Number of faces must be even")
+    let(
+        N = faces/2,
+        parmcount = num_defined([r,side,longside,h])
+    )
+    assert(parmcount==2,"Must define exactly two of 'r', 'side', 'longside', and 'height'")
+    let(
+        separation = (
+            !is_undef(h) ? 2*h*sqr(tan(90/N)) :
+            (!is_undef(r) && !is_undef(side))? sqrt(side*side+2*r*r*(cos(180/N)-1)) :
+            (!is_undef(r) && !is_undef(longside))? 2 * sqrt(sqr(longside)-sqr(r)) / (1-sqr(tan(90/N))) * sqr(tan(90/N)) :
+            2*sqr(sin(90/N))*sqrt((sqr(side) + 2*sqr(longside)*(cos(180/N)-1)) / (cos(180/N)-1) / (cos(180/N)+cos(360/N)))
+        )
+    )
+    assert(separation==separation, "Impossible trapezohedron specification")
+    let(
+        h = !is_undef(h) ? h : 0.5*separation / sqr(tan(90/N)),
+        r = (
+            !is_undef(r) ? r :
+            !is_undef(side) ? sqrt((sqr(separation) - sqr(side))/2/(cos(180/N)-1)) :
+            sqrt(sqr(longside) - sqr(h-separation/2))
+        ),
+        top = [for(i=[0:1:N-1]) [r*cos(360/N*i), r*sin(360/N*i),separation/2]],
+        bot = [for(i=[0:1:N-1]) [r*cos(180/N+360/N*i), r*sin(180/N+360/N*i),-separation/2]],
+        vertices = concat([[0,0,h],[0,0,-h]],top,bot)
+    ) [
+        "trapezohedron", "trapezohedron", faces, [4],
+        !is_undef(side)? side : sqrt(sqr(separation)-2*r*(cos(180/N)-1)),  // actual side length
+        h*r/sqrt(r*r+sqr(h+separation/2)),     // in_radius
+        h*r/sqrt(r*r+sqr(h-separation/2)),     // mid_radius
+        max(h,sqrt(r*r+sqr(separation/2))),  // out_radius
+        undef,                               // volume
+        vertices
+    ];
+
+
+function _facenormal(pts, face) = unit(cross(pts[face[2]]-pts[face[0]], pts[face[1]]-pts[face[0]]));
+
+// hull() function returns triangulated faces.    This function identifies the vertices that belong to each face
+// by grouping together the face triangles that share normal vectors.    The output gives the face polygon
+// point indices in arbitrary order (not usable as input to a polygon call) and a normal vector.
+
+function _full_faces(pts,faces) =
+    let(
+        normals = [for(face=faces) quant(_facenormal(pts,face),1e-12)],
+        groups = _unique_groups(normals),
+        faces = [for(entry=groups) unique(flatten(select(faces, entry)))],
+        facenormals = [for(entry=groups) normals[entry[0]]]
+    ) [faces, facenormals];
+
+
+// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/shapes.scad b/shapes.scad
new file mode 100644
index 0000000..16b970d
--- /dev/null
+++ b/shapes.scad
@@ -0,0 +1,1755 @@
+//////////////////////////////////////////////////////////////////////
+// LibFile: shapes.scad
+//   Common useful shapes and structured objects.
+//   To use, add the following lines to the beginning of your file:
+//   ```
+//   include <BOSL2/std.scad>
+//   ```
+//////////////////////////////////////////////////////////////////////
+
+
+// Section: Cuboids
+
+
+// Module: cuboid()
+//
+// Description:
+//   Creates a cube or cuboid object, with optional chamfering or rounding.
+//   Negative chamfers and roundings can be applied to create external masks,
+//   but only apply to edges around the top or bottom faces.
+//
+// Arguments:
+//   size = The size of the cube.
+//   chamfer = Size of chamfer, inset from sides.  Default: No chamfering.
+//   rounding = Radius of the edge rounding.  Default: No rounding.
+//   edges = Edges to chamfer/round.  See the docs for [`edges()`](edges.scad#edges) to see acceptable values.  Default: All edges.
+//   except_edges = Edges to explicitly NOT chamfer/round.  See the docs for [`edges()`](edges.scad#edges) to see acceptable values.  Default: No edges.
+//   trimcorners = If true, rounds or chamfers corners where three chamfered/rounded edges meet.  Default: `true`
+//   p1 = Align the cuboid's corner at `p1`, if given.  Forces `anchor=ALLNEG`.
+//   p2 = If given with `p1`, defines the cornerpoints of the cuboid.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards.  See [orient](attachments.scad#orient).  Default: `UP`
+//
+// Example: Simple regular cube.
+//   cuboid(40);
+// Example: Cube with minimum cornerpoint given.
+//   cuboid(20, p1=[10,0,0]);
+// Example: Rectangular cube, with given X, Y, and Z sizes.
+//   cuboid([20,40,50]);
+// Example: Cube by Opposing Corners.
+//   cuboid(p1=[0,10,0], p2=[20,30,30]);
+// Example: Chamferred Edges and Corners.
+//   cuboid([30,40,50], chamfer=5);
+// Example: Chamferred Edges, Untrimmed Corners.
+//   cuboid([30,40,50], chamfer=5, trimcorners=false);
+// Example: Rounded Edges and Corners
+//   cuboid([30,40,50], rounding=10);
+// Example: Rounded Edges, Untrimmed Corners
+//   cuboid([30,40,50], rounding=10, trimcorners=false);
+// Example: Chamferring Selected Edges
+//   cuboid([30,40,50], chamfer=5, edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT], $fn=24);
+// Example: Rounding Selected Edges
+//   cuboid([30,40,50], rounding=5, edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT], $fn=24);
+// Example: Negative Chamferring
+//   cuboid([30,40,50], chamfer=-5, edges=[TOP,BOT], except_edges=RIGHT, $fn=24);
+// Example: Negative Chamferring, Untrimmed Corners
+//   cuboid([30,40,50], chamfer=-5, edges=[TOP,BOT], except_edges=RIGHT, trimcorners=false, $fn=24);
+// Example: Negative Rounding
+//   cuboid([30,40,50], rounding=-5, edges=[TOP,BOT], except_edges=RIGHT, $fn=24);
+// Example: Negative Rounding, Untrimmed Corners
+//   cuboid([30,40,50], rounding=-5, edges=[TOP,BOT], except_edges=RIGHT, trimcorners=false, $fn=24);
+// Example: Standard Connectors
+//   cuboid(40) show_anchors();
+module cuboid(
+    size=[1,1,1],
+    p1=undef, p2=undef,
+    chamfer=undef,
+    rounding=undef,
+    edges=EDGES_ALL,
+    except_edges=[],
+    trimcorners=true,
+    anchor=CENTER,
+    spin=0,
+    orient=UP
+) {
+    size = scalar_vec3(size);
+    edges = edges(edges, except=except_edges);
+    if (!is_undef(p1)) {
+        if (!is_undef(p2)) {
+            translate(pointlist_bounds([p1,p2])[0]) {
+                cuboid(size=vabs(p2-p1), chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=ALLNEG) children();
+            }
+        } else {
+            translate(p1) {
+                cuboid(size=size, chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=ALLNEG) children();
+            }
+        }
+    } else {
+        if (chamfer != undef) {
+            if (any(edges[0])) assert(chamfer <= size.y/2 && chamfer <=size.z/2, "chamfer must be smaller than half the cube length or height.");
+            if (any(edges[1])) assert(chamfer <= size.x/2 && chamfer <=size.z/2, "chamfer must be smaller than half the cube width or height.");
+            if (any(edges[2])) assert(chamfer <= size.x/2 && chamfer <=size.y/2, "chamfer must be smaller than half the cube width or length.");
+        }
+        if (rounding != undef) {
+            if (any(edges[0])) assert(rounding <= size.y/2 && rounding<=size.z/2, "rounding radius must be smaller than half the cube length or height.");
+            if (any(edges[1])) assert(rounding <= size.x/2 && rounding<=size.z/2, "rounding radius must be smaller than half the cube width or height.");
+            if (any(edges[2])) assert(rounding <= size.x/2 && rounding<=size.y/2, "rounding radius must be smaller than half the cube width or length.");
+        }
+        majrots = [[0,90,0], [90,0,0], [0,0,0]];
+        attachable(anchor,spin,orient, size=size) {
+            if (chamfer != undef) {
+                if (edges == EDGES_ALL && trimcorners) {
+                    if (chamfer<0) {
+                        cube(size, center=true) {
+                            attach(TOP) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
+                            attach(BOT) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
+                        }
+                    } else {
+                        isize = [for (v = size) max(0.001, v-2*chamfer)];
+                        hull() {
+                            cube([size.x, isize.y, isize.z], center=true);
+                            cube([isize.x, size.y, isize.z], center=true);
+                            cube([isize.x, isize.y, size.z], center=true);
+                        }
+                    }
+                } else if (chamfer<0) {
+                    ach = abs(chamfer);
+                    cube(size, center=true);
+
+                    // External-Chamfer mask edges
+                    difference() {
+                        union() {
+                            for (i = [0:3], axis=[0:1]) {
+                                if (edges[axis][i]>0) {
+                                    vec = EDGE_OFFSETS[axis][i];
+                                    translate(vmul(vec/2, size+[ach,ach,-ach])) {
+                                        rotate(majrots[axis]) {
+                                            cube([ach, ach, size[axis]], center=true);
+                                        }
+                                    }
+                                }
+                            }
+
+                            // Add multi-edge corners.
+                            if (trimcorners) {
+                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
+                                    if (corner_edge_count(edges, [xa,ya,za]) > 1) {
+                                        translate(vmul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
+                                            cube([ach+0.01,ach+0.01,ach], center=true);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                        // Remove bevels from overhangs.
+                        for (i = [0:3], axis=[0:1]) {
+                            if (edges[axis][i]>0) {
+                                vec = EDGE_OFFSETS[axis][i];
+                                translate(vmul(vec/2, size+[2*ach,2*ach,-2*ach])) {
+                                    rotate(majrots[axis]) {
+                                        zrot(45) cube([ach*sqrt(2), ach*sqrt(2), size[axis]+2.1*ach], center=true);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    difference() {
+                        cube(size, center=true);
+
+                        // Chamfer edges
+                        for (i = [0:3], axis=[0:2]) {
+                            if (edges[axis][i]>0) {
+                                translate(vmul(EDGE_OFFSETS[axis][i], size/2)) {
+                                    rotate(majrots[axis]) {
+                                        zrot(45) cube([chamfer*sqrt(2), chamfer*sqrt(2), size[axis]+0.01], center=true);
+                                    }
+                                }
+                            }
+                        }
+
+                        // Chamfer triple-edge corners.
+                        if (trimcorners) {
+                            for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
+                                if (corner_edge_count(edges, [xa,ya,za]) > 2) {
+                                    translate(vmul([xa,ya,za]/2, size-[1,1,1]*chamfer*4/3)) {
+                                        rot(from=UP, to=[xa,ya,za]) {
+                                            cube(chamfer*3, anchor=BOTTOM);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            } else if (rounding != undef) {
+                sides = quantup(segs(rounding),4);
+                if (edges == EDGES_ALL) {
+                    if(rounding<0) {
+                        cube(size, center=true);
+                        zflip_copy() {
+                            up(size.z/2) {
+                                difference() {
+                                    down(-rounding/2) cube([size.x-2*rounding, size.y-2*rounding, -rounding], center=true);
+                                    down(-rounding) {
+                                        ycopies(size.y-2*rounding) xcyl(l=size.x-3*rounding, r=-rounding);
+                                        xcopies(size.x-2*rounding) ycyl(l=size.y-3*rounding, r=-rounding);
+                                    }
+                                }
+                            }
+                        }
+                    } else {
+                        isize = [for (v = size) max(0.001, v-2*rounding)];
+                        minkowski() {
+                            cube(isize, center=true);
+                            if (trimcorners) {
+                                spheroid(r=rounding, style="octa", $fn=sides);
+                            } else {
+                                intersection() {
+                                    cyl(r=rounding, h=rounding*2, $fn=sides);
+                                    rotate([90,0,0]) cyl(r=rounding, h=rounding*2, $fn=sides);
+                                    rotate([0,90,0]) cyl(r=rounding, h=rounding*2, $fn=sides);
+                                }
+                            }
+                        }
+                    }
+                } else if (rounding<0) {
+                    ard = abs(rounding);
+                    cube(size, center=true);
+
+                    // External-Chamfer mask edges
+                    difference() {
+                        union() {
+                            for (i = [0:3], axis=[0:1]) {
+                                if (edges[axis][i]>0) {
+                                    vec = EDGE_OFFSETS[axis][i];
+                                    translate(vmul(vec/2, size+[ard,ard,-ard])) {
+                                        rotate(majrots[axis]) {
+                                            cube([ard, ard, size[axis]], center=true);
+                                        }
+                                    }
+                                }
+                            }
+
+                            // Add multi-edge corners.
+                            if (trimcorners) {
+                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
+                                    if (corner_edge_count(edges, [xa,ya,za]) > 1) {
+                                        translate(vmul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
+                                            cube([ard+0.01,ard+0.01,ard], center=true);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                        // Remove roundings from overhangs.
+                        for (i = [0:3], axis=[0:1]) {
+                            if (edges[axis][i]>0) {
+                                vec = EDGE_OFFSETS[axis][i];
+                                translate(vmul(vec/2, size+[2*ard,2*ard,-2*ard])) {
+                                    rotate(majrots[axis]) {
+                                        cyl(l=size[axis]+2.1*ard, r=ard);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    difference() {
+                        cube(size, center=true);
+
+                        // Round edges.
+                        for (i = [0:3], axis=[0:2]) {
+                            if (edges[axis][i]>0) {
+                                difference() {
+                                    translate(vmul(EDGE_OFFSETS[axis][i], size/2)) {
+                                        rotate(majrots[axis]) cube([rounding*2, rounding*2, size[axis]+0.1], center=true);
+                                    }
+                                    translate(vmul(EDGE_OFFSETS[axis][i], size/2 - [1,1,1]*rounding)) {
+                                        rotate(majrots[axis]) cyl(h=size[axis]+0.2, r=rounding, $fn=sides);
+                                    }
+                                }
+                            }
+                        }
+
+                        // Round triple-edge corners.
+                        if (trimcorners) {
+                            for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
+                                if (corner_edge_count(edges, [xa,ya,za]) > 2) {
+                                    difference() {
+                                        translate(vmul([xa,ya,za], size/2)) {
+                                            cube(rounding*2, center=true);
+                                        }
+                                        translate(vmul([xa,ya,za], size/2-[1,1,1]*rounding)) {
+                                            spheroid(r=rounding, style="octa", $fn=sides);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            } else {
+                cube(size=size, center=true);
+            }
+            children();
+        }
+    }
+}
+
+
+
+// Section: Prismoids
+
+
+// Function&Module: prismoid()
+//
+// Usage: As Module
+//   prismoid(size1, size2, h|l, [shift], [rounding], [chamfer]);
+//   prismoid(size1, size2, h|l, [shift], [rounding1], [rounding2], [chamfer1], [chamfer2]);
+// Usage: As Function
+//   vnf = prismoid(size1, size2, h|l, [shift], [rounding], [chamfer]);
+//   vnf = prismoid(size1, size2, h|l, [shift], [rounding1], [rounding2], [chamfer1], [chamfer2]);
+//
+// Description:
+//   Creates a rectangular prismoid shape with optional roundovers and chamfering.
+//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
+//   specify rounding and/or chamferring per-edge, and for top and bottom separately.
+//   Note: if using chamfers or rounding, you **must** also include the hull.scad file:
+//   ```
+//   include <BOSL2/hull.scad>
+//   ```
+//
+// Arguments:
+//   size1 = [width, length] of the axis-negative end of the prism.
+//   size2 = [width, length] of the axis-positive end of the prism.
+//   h|l = Height of the prism.
+//   shift = [X,Y] amount to shift the center of the top with respect to the center of the bottom.
+//   rounding = The roundover radius for the edges of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
+//   rounding1 = The roundover radius for the bottom corners of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
+//   rounding2 = The roundover radius for the top corners of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
+//   chamfer = The chamfer size for the edges of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].  Default: 0 (no chamfer)
+//   chamfer1 = The chamfer size for the bottom corners of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
+//   chamfer2 = The chamfer size for the top corners of the prismoid.  Requires including hull.scad.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+//
+// Example: Rectangular Pyramid
+//   prismoid([40,40], [0,0], h=20);
+// Example: Prism
+//   prismoid(size1=[40,40], size2=[0,40], h=20);
+// Example: Truncated Pyramid
+//   prismoid(size1=[35,50], size2=[20,30], h=20);
+// Example: Wedge
+//   prismoid(size1=[60,35], size2=[30,0], h=30);
+// Example: Truncated Tetrahedron
+//   prismoid(size1=[10,40], size2=[40,10], h=40);
+// Example: Inverted Truncated Pyramid
+//   prismoid(size1=[15,5], size2=[30,20], h=20);
+// Example: Right Prism
+//   prismoid(size1=[30,60], size2=[0,60], shift=[-15,0], h=30);
+// Example(FlatSpin): Shifting/Skewing
+//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]);
+// Example: Rounding
+//   include <BOSL2/hull.scad>
+//   prismoid(100, 80, rounding=10, h=30);
+// Example: Outer Chamfer Only
+//   include <BOSL2/hull.scad>
+//   prismoid(100, 80, chamfer=5, h=30);
+// Example: Gradiant Rounding
+//   include <BOSL2/hull.scad>
+//   prismoid(100, 80, rounding1=10, rounding2=0, h=30);
+// Example: Per Corner Rounding
+//   include <BOSL2/hull.scad>
+//   prismoid(100, 80, rounding=[0,5,10,15], h=30);
+// Example: Per Corner Chamfer
+//   include <BOSL2/hull.scad>
+//   prismoid(100, 80, chamfer=[0,5,10,15], h=30);
+// Example: Mixing Chamfer and Rounding
+//   include <BOSL2/hull.scad>
+//   prismoid(100, 80, chamfer=[0,5,0,10], rounding=[5,0,10,0], h=30);
+// Example: Really Mixing It Up
+//   include <BOSL2/hull.scad>
+//   prismoid(
+//       size1=[100,80], size2=[80,60], h=20,
+//       chamfer1=[0,5,0,10], chamfer2=[5,0,10,0],
+//       rounding1=[5,0,10,0], rounding2=[0,5,0,10]
+//   );
+// Example(Spin): Standard Connectors
+//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5])
+//       show_anchors();
+module prismoid(
+    size1, size2, h, shift=[0,0],
+    rounding=0, rounding1, rounding2,
+    chamfer=0, chamfer1, chamfer2,
+    l, center,
+    anchor, spin=0, orient=UP
+) {
+    assert(is_num(size1) || is_vector(size1,2));
+    assert(is_num(size2) || is_vector(size2,2));
+    assert(is_num(h) || is_num(l));
+    assert(is_vector(shift,2));
+    assert(is_num(rounding) || is_vector(rounding,4), "Bad rounding argument.");
+    assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "Bad rounding1 argument.");
+    assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "Bad rounding2 argument.");
+    assert(is_num(chamfer) || is_vector(chamfer,4), "Bad chamfer argument.");
+    assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "Bad chamfer1 argument.");
+    assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "Bad chamfer2 argument.");
+    eps = pow(2,-14);
+    size1 = is_num(size1)? [size1,size1] : size1;
+    size2 = is_num(size2)? [size2,size2] : size2;
+    s1 = [max(size1.x, eps), max(size1.y, eps)];
+    s2 = [max(size2.x, eps), max(size2.y, eps)];
+    rounding1 = default(rounding1, rounding);
+    rounding2 = default(rounding2, rounding);
+    chamfer1 = default(chamfer1, chamfer);
+    chamfer2 = default(chamfer2, chamfer);
+    anchor = get_anchor(anchor, center, BOT, BOT);
+    vnf = prismoid(
+        size1=size1, size2=size2, h=h, shift=shift,
+        rounding=rounding, rounding1=rounding1, rounding2=rounding2,
+        chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
+        l=l, center=CENTER
+    );
+    attachable(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift) {
+        vnf_polyhedron(vnf, convexity=4);
+        children();
+    }
+}
+
+function prismoid(
+    size1, size2, h, shift=[0,0],
+    rounding=0, rounding1, rounding2,
+    chamfer=0, chamfer1, chamfer2,
+    l, center,
+    anchor=DOWN, spin=0, orient=UP
+) =
+    assert(is_vector(size1,2))
+    assert(is_vector(size2,2))
+    assert(is_num(h) || is_num(l))
+    assert(is_vector(shift,2))
+    assert(is_num(rounding) || is_vector(rounding,4), "Bad rounding argument.")
+    assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "Bad rounding1 argument.")
+    assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "Bad rounding2 argument.")
+    assert(is_num(chamfer) || is_vector(chamfer,4), "Bad chamfer argument.")
+    assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "Bad chamfer1 argument.")
+    assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "Bad chamfer2 argument.")
+    let(
+        eps = pow(2,-14),
+        h = first_defined([h,l,1]),
+        shiftby = point3d(point2d(shift)),
+        s1 = [max(size1.x, eps), max(size1.y, eps)],
+        s2 = [max(size2.x, eps), max(size2.y, eps)],
+        rounding1 = default(rounding1, rounding),
+        rounding2 = default(rounding2, rounding),
+        chamfer1 = default(chamfer1, chamfer),
+        chamfer2 = default(chamfer2, chamfer),
+        anchor = get_anchor(anchor, center, BOT, BOT),
+        vnf = (rounding1==0 && rounding2==0 && chamfer1==0 && chamfer2==0)? (
+            let(
+                corners = [[1,1],[1,-1],[-1,-1],[-1,1]] * 0.5,
+                points = [
+                    for (p=corners) point3d(vmul(s2,p), +h/2) + shiftby,
+                    for (p=corners) point3d(vmul(s1,p), -h/2)
+                ],
+                faces=[
+                    [0,1,2], [0,2,3], [0,4,5], [0,5,1],
+                    [1,5,6], [1,6,2], [2,6,7], [2,7,3],
+                    [3,7,4], [3,4,0], [4,7,6], [4,6,5],
+                ]
+            ) [points, faces]
+        ) : (
+            let(
+                path1 = rect(size1, rounding=rounding1, chamfer=chamfer1, anchor=CTR),
+                path2 = rect(size2, rounding=rounding2, chamfer=chamfer2, anchor=CTR),
+                points = [
+                    each path3d(path1, -h/2),
+                    each path3d(move(shiftby, p=path2), +h/2),
+                ],
+                faces = hull(points)
+            ) [points, faces]
+        )
+    ) reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf);
+
+
+// Module: right_triangle()
+//
+// Usage:
+//   right_triangle(size, [center]);
+//
+// Description:
+//   Creates a 3D right triangular prism with the hypotenuse in the X+Y+ quadrant.
+//
+// Arguments:
+//   size = [width, thickness, height]
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `ALLNEG`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+//
+// Example: Centered
+//   right_triangle([60, 40, 10], center=true);
+// Example: *Non*-Centered
+//   right_triangle([60, 40, 10]);
+// Example: Standard Connectors
+//   right_triangle([60, 40, 15]) show_anchors();
+module right_triangle(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
+{
+    size = scalar_vec3(size);
+    anchor = get_anchor(anchor, center, ALLNEG, ALLNEG);
+    attachable(anchor,spin,orient, size=size) {
+        linear_extrude(height=size.z, convexity=2, center=true) {
+            polygon([[-size.x/2,-size.y/2], [-size.x/2,size.y/2], [size.x/2,-size.y/2]]);
+        }
+        children();
+    }
+}
+
+
+
+// Section: Cylindroids
+
+
+// Module: cyl()
+//
+// Description:
+//   Creates cylinders in various anchors and orientations,
+//   with optional rounding and chamfers. You can use `r` and `l`
+//   interchangably, and all variants allow specifying size
+//   by either `r`|`d`, or `r1`|`d1` and `r2`|`d2`.
+//   Note that that chamfers and rounding cannot cross the
+//   midpoint of the cylinder's length.
+//
+// Usage: Normal Cylinders
+//   cyl(l|h, r|d, [circum], [realign], [center]);
+//   cyl(l|h, r1|d1, r2/d2, [circum], [realign], [center]);
+//
+// Usage: Chamferred Cylinders
+//   cyl(l|h, r|d, chamfer, [chamfang], [from_end], [circum], [realign], [center]);
+//   cyl(l|h, r|d, chamfer1, [chamfang1], [from_end], [circum], [realign], [center]);
+//   cyl(l|h, r|d, chamfer2, [chamfang2], [from_end], [circum], [realign], [center]);
+//   cyl(l|h, r|d, chamfer1, chamfer2, [chamfang1], [chamfang2], [from_end], [circum], [realign], [center]);
+//
+// Usage: Rounded End Cylinders
+//   cyl(l|h, r|d, rounding, [circum], [realign], [center]);
+//   cyl(l|h, r|d, rounding1, [circum], [realign], [center]);
+//   cyl(l|h, r|d, rounding2, [circum], [realign], [center]);
+//   cyl(l|h, r|d, rounding1, rounding2, [circum], [realign], [center]);
+//
+// Arguments:
+//   l / h = Length of cylinder along oriented axis. (Default: 1.0)
+//   r = Radius of cylinder.
+//   r1 = Radius of the negative (X-, Y-, Z-) end of cylinder.
+//   r2 = Radius of the positive (X+, Y+, Z+) end of cylinder.
+//   d = Diameter of cylinder.
+//   d1 = Diameter of the negative (X-, Y-, Z-) end of cylinder.
+//   d2 = Diameter of the positive (X+, Y+, Z+) end of cylinder.
+//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
+//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
+//   chamfer1 = The size of the chamfer on the axis-negative end of the cylinder.  Default: none.
+//   chamfer2 = The size of the chamfer on the axis-positive end of the cylinder.  Default: none.
+//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
+//   chamfang1 = The angle in degrees of the chamfer on the axis-negative end of the cylinder.
+//   chamfang2 = The angle in degrees of the chamfer on the axis-positive end of the cylinder.
+//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
+//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
+//   rounding1 = The radius of the rounding on the axis-negative end of the cylinder.
+//   rounding2 = The radius of the rounding on the axis-positive end of the cylinder.
+//   realign = If true, rotate the cylinder by half the angle of one face.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
+//
+// Example: By Radius
+//   xdistribute(30) {
+//       cyl(l=40, r=10);
+//       cyl(l=40, r1=10, r2=5);
+//   }
+//
+// Example: By Diameter
+//   xdistribute(30) {
+//       cyl(l=40, d=25);
+//       cyl(l=40, d1=25, d2=10);
+//   }
+//
+// Example: Chamferring
+//   xdistribute(60) {
+//       // Shown Left to right.
+//       cyl(l=40, d=40, chamfer=7);  // Default chamfang=45
+//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=false);
+//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=true);
+//   }
+//
+// Example: Rounding
+//   cyl(l=40, d=40, rounding=10);
+//
+// Example: Heterogenous Chamfers and Rounding
+//   ydistribute(80) {
+//       // Shown Front to Back.
+//       cyl(l=40, d=40, rounding1=15, orient=UP);
+//       cyl(l=40, d=40, chamfer2=5, orient=UP);
+//       cyl(l=40, d=40, chamfer1=12, rounding2=10, orient=UP);
+//   }
+//
+// Example: Putting it all together
+//   cyl(l=40, d1=25, d2=15, chamfer1=10, chamfang1=30, from_end=true, rounding2=5);
+//
+// Example: External Chamfers
+//   cyl(l=50, r=30, chamfer=-5, chamfang=30, $fa=1, $fs=1);
+//
+// Example: External Roundings
+//   cyl(l=50, r=30, rounding1=-5, rounding2=5, $fa=1, $fs=1);
+//
+// Example: Standard Connectors
+//   xdistribute(40) {
+//       cyl(l=30, d=25) show_anchors();
+//       cyl(l=30, d1=25, d2=10) show_anchors();
+//   }
+//
+module cyl(
+    l=undef, h=undef,
+    r=undef, r1=undef, r2=undef,
+    d=undef, d1=undef, d2=undef,
+    chamfer=undef, chamfer1=undef, chamfer2=undef,
+    chamfang=undef, chamfang1=undef, chamfang2=undef,
+    rounding=undef, rounding1=undef, rounding2=undef,
+    circum=false, realign=false, from_end=false,
+    center, anchor, spin=0, orient=UP
+) {
+    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
+    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
+    l = first_defined([l, h, 1]);
+    sides = segs(max(r1,r2));
+    sc = circum? 1/cos(180/sides) : 1;
+    phi = atan2(l, r2-r1);
+    anchor = get_anchor(anchor,center,BOT,CENTER);
+    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
+        zrot(realign? 180/sides : 0) {
+            if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])) {
+                cylinder(h=l, r1=r1*sc, r2=r2*sc, center=true, $fn=sides);
+            } else {
+                vang = atan2(l, r1-r2)/2;
+                chang1 = 90-first_defined([chamfang1, chamfang, vang]);
+                chang2 = 90-first_defined([chamfang2, chamfang, 90-vang]);
+                cham1 = first_defined([chamfer1, chamfer]) * (from_end? 1 : tan(chang1));
+                cham2 = first_defined([chamfer2, chamfer]) * (from_end? 1 : tan(chang2));
+                fil1 = first_defined([rounding1, rounding]);
+                fil2 = first_defined([rounding2, rounding]);
+                if (chamfer != undef) {
+                    assert(chamfer <= r1,  "chamfer is larger than the r1 radius of the cylinder.");
+                    assert(chamfer <= r2,  "chamfer is larger than the r2 radius of the cylinder.");
+                }
+                if (cham1 != undef) {
+                    assert(cham1 <= r1,  "chamfer1 is larger than the r1 radius of the cylinder.");
+                }
+                if (cham2 != undef) {
+                    assert(cham2 <= r2,  "chamfer2 is larger than the r2 radius of the cylinder.");
+                }
+                if (rounding != undef) {
+                    assert(rounding <= r1,  "rounding is larger than the r1 radius of the cylinder.");
+                    assert(rounding <= r2,  "rounding is larger than the r2 radius of the cylinder.");
+                }
+                if (fil1 != undef) {
+                    assert(fil1 <= r1,  "rounding1 is larger than the r1 radius of the cylinder.");
+                }
+                if (fil2 != undef) {
+                    assert(fil2 <= r2,  "rounding2 is larger than the r1 radius of the cylinder.");
+                }
+                dy1 = abs(first_defined([cham1, fil1, 0]));
+                dy2 = abs(first_defined([cham2, fil2, 0]));
+                assert(dy1+dy2 <= l, "Sum of fillets and chamfer sizes must be less than the length of the cylinder.");
+
+                path = concat(
+                    [[0,l/2]],
+
+                    !is_undef(cham2)? (
+                        let(
+                            p1 = [r2-cham2/tan(chang2),l/2],
+                            p2 = lerp([r2,l/2],[r1,-l/2],abs(cham2)/l)
+                        ) [p1,p2]
+                    ) : !is_undef(fil2)? (
+                        let(
+                            cn = find_circle_2tangents([r2-fil2,l/2], [r2,l/2], [r1,-l/2], r=abs(fil2)),
+                            ang = fil2<0? phi : phi-180,
+                            steps = ceil(abs(ang)/360*segs(abs(fil2))),
+                            step = ang/steps,
+                            pts = [for (i=[0:1:steps]) let(a=90+i*step) cn[0]+abs(fil2)*[cos(a),sin(a)]]
+                        ) pts
+                    ) : [[r2,l/2]],
+
+                    !is_undef(cham1)? (
+                        let(
+                            p1 = lerp([r1,-l/2],[r2,l/2],abs(cham1)/l),
+                            p2 = [r1-cham1/tan(chang1),-l/2]
+                        ) [p1,p2]
+                    ) : !is_undef(fil1)? (
+                        let(
+                            cn = find_circle_2tangents([r1-fil1,-l/2], [r1,-l/2], [r2,l/2], r=abs(fil1)),
+                            ang = fil1<0? 180-phi : -phi,
+                            steps = ceil(abs(ang)/360*segs(abs(fil1))),
+                            step = ang/steps,
+                            pts = [for (i=[0:1:steps]) let(a=(fil1<0?180:0)+(phi-90)+i*step) cn[0]+abs(fil1)*[cos(a),sin(a)]]
+                        ) pts
+                    ) : [[r1,-l/2]],
+
+                    [[0,-l/2]]
+                );
+                rotate_extrude(convexity=2) {
+                    polygon(path);
+                }
+            }
+        }
+        children();
+    }
+}
+
+
+
+// Module: xcyl()
+//
+// Description:
+//   Creates a cylinder oriented along the X axis.
+//
+// Usage:
+//   xcyl(l|h, r|d, [anchor]);
+//   xcyl(l|h, r1|d1, r2|d2, [anchor]);
+//
+// Arguments:
+//   l / h = Length of cylinder along oriented axis. (Default: `1.0`)
+//   r = Radius of cylinder.
+//   r1 = Optional radius of left (X-) end of cylinder.
+//   r2 = Optional radius of right (X+) end of cylinder.
+//   d = Optional diameter of cylinder. (use instead of `r`)
+//   d1 = Optional diameter of left (X-) end of cylinder.
+//   d2 = Optional diameter of right (X+) end of cylinder.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//
+// Example: By Radius
+//   ydistribute(50) {
+//       xcyl(l=35, r=10);
+//       xcyl(l=35, r1=15, r2=5);
+//   }
+//
+// Example: By Diameter
+//   ydistribute(50) {
+//       xcyl(l=35, d=20);
+//       xcyl(l=35, d1=30, d2=10);
+//   }
+module xcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, anchor=CENTER)
+{
+    anchor = rot(from=RIGHT, to=UP, p=anchor);
+    cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=RIGHT, anchor=anchor) children();
+}
+
+
+
+// Module: ycyl()
+//
+// Description:
+//   Creates a cylinder oriented along the Y axis.
+//
+// Usage:
+//   ycyl(l|h, r|d, [anchor]);
+//   ycyl(l|h, r1|d1, r2|d2, [anchor]);
+//
+// Arguments:
+//   l / h = Length of cylinder along oriented axis. (Default: `1.0`)
+//   r = Radius of cylinder.
+//   r1 = Radius of front (Y-) end of cone.
+//   r2 = Radius of back (Y+) end of one.
+//   d = Diameter of cylinder.
+//   d1 = Diameter of front (Y-) end of one.
+//   d2 = Diameter of back (Y+) end of one.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//
+// Example: By Radius
+//   xdistribute(50) {
+//       ycyl(l=35, r=10);
+//       ycyl(l=35, r1=15, r2=5);
+//   }
+//
+// Example: By Diameter
+//   xdistribute(50) {
+//       ycyl(l=35, d=20);
+//       ycyl(l=35, d1=30, d2=10);
+//   }
+module ycyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, anchor=CENTER)
+{
+    anchor = rot(from=BACK, to=UP, p=anchor);
+    cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=BACK, anchor=anchor) children();
+}
+
+
+
+// Module: zcyl()
+//
+// Description:
+//   Creates a cylinder oriented along the Z axis.
+//
+// Usage:
+//   zcyl(l|h, r|d, [anchor]);
+//   zcyl(l|h, r1|d1, r2|d2, [anchor]);
+//
+// Arguments:
+//   l / h = Length of cylinder along oriented axis. (Default: 1.0)
+//   r = Radius of cylinder.
+//   r1 = Radius of front (Y-) end of cone.
+//   r2 = Radius of back (Y+) end of one.
+//   d = Diameter of cylinder.
+//   d1 = Diameter of front (Y-) end of one.
+//   d2 = Diameter of back (Y+) end of one.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//
+// Example: By Radius
+//   xdistribute(50) {
+//       zcyl(l=35, r=10);
+//       zcyl(l=35, r1=15, r2=5);
+//   }
+//
+// Example: By Diameter
+//   xdistribute(50) {
+//       zcyl(l=35, d=20);
+//       zcyl(l=35, d1=30, d2=10);
+//   }
+module zcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, anchor=CENTER)
+{
+    cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=UP, anchor=anchor) children();
+}
+
+
+
+// Module: tube()
+//
+// Description:
+//   Makes a hollow tube with the given outer size and wall thickness.
+//
+// Usage:
+//   tube(h|l, ir|id, wall, [realign]);
+//   tube(h|l, or|od, wall, [realign]);
+//   tube(h|l, ir|id, or|od, [realign]);
+//   tube(h|l, ir1|id1, ir2|id2, wall, [realign]);
+//   tube(h|l, or1|od1, or2|od2, wall, [realign]);
+//   tube(h|l, ir1|id1, ir2|id2, or1|od1, or2|od2, [realign]);
+//
+// Arguments:
+//   h|l = height of tube. (Default: 1)
+//   or = Outer radius of tube.
+//   or1 = Outer radius of bottom of tube.  (Default: value of r)
+//   or2 = Outer radius of top of tube.  (Default: value of r)
+//   od = Outer diameter of tube.
+//   od1 = Outer diameter of bottom of tube.
+//   od2 = Outer diameter of top of tube.
+//   wall = horizontal thickness of tube wall. (Default 0.5)
+//   ir = Inner radius of tube.
+//   ir1 = Inner radius of bottom of tube.
+//   ir2 = Inner radius of top of tube.
+//   id = Inner diameter of tube.
+//   id1 = Inner diameter of bottom of tube.
+//   id2 = Inner diameter of top of tube.
+//   realign = If true, rotate the tube by half the angle of one face.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+//
+// Example: These all Produce the Same Tube
+//   tube(h=30, or=40, wall=5);
+//   tube(h=30, ir=35, wall=5);
+//   tube(h=30, or=40, ir=35);
+//   tube(h=30, od=80, id=70);
+// Example: These all Produce the Same Conical Tube
+//   tube(h=30, or1=40, or2=25, wall=5);
+//   tube(h=30, ir1=35, or2=20, wall=5);
+//   tube(h=30, or1=40, or2=25, ir1=35, ir2=20);
+// Example: Circular Wedge
+//   tube(h=30, or1=40, or2=30, ir1=20, ir2=30);
+// Example: Standard Connectors
+//   tube(h=30, or=40, wall=5) show_anchors();
+module tube(
+    h, wall=undef,
+    r=undef, r1=undef, r2=undef,
+    d=undef, d1=undef, d2=undef,
+    or=undef, or1=undef, or2=undef,
+    od=undef, od1=undef, od2=undef,
+    ir=undef, id=undef, ir1=undef,
+    ir2=undef, id1=undef, id2=undef,
+    anchor, spin=0, orient=UP,
+    center, realign=false, l
+) {
+    h = first_defined([h,l,1]);
+    r1 = first_defined([or1, od1/2, r1, d1/2, or, od/2, r, d/2, ir1+wall, id1/2+wall, ir+wall, id/2+wall]);
+    r2 = first_defined([or2, od2/2, r2, d2/2, or, od/2, r, d/2, ir2+wall, id2/2+wall, ir+wall, id/2+wall]);
+    ir1 = first_defined([ir1, id1/2, ir, id/2, r1-wall, d1/2-wall, r-wall, d/2-wall]);
+    ir2 = first_defined([ir2, id2/2, ir, id/2, r2-wall, d2/2-wall, r-wall, d/2-wall]);
+    assert(ir1 <= r1, "Inner radius is larger than outer radius.");
+    assert(ir2 <= r2, "Inner radius is larger than outer radius.");
+    sides = segs(max(r1,r2));
+    anchor = get_anchor(anchor, center, BOT, BOT);
+    attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
+        zrot(realign? 180/sides : 0) {
+            difference() {
+                cyl(h=h, r1=r1, r2=r2, $fn=sides) children();
+                cyl(h=h+0.05, r1=ir1, r2=ir2);
+            }
+        }
+        children();
+    }
+}
+
+
+// Module: rect_tube()
+// Usage:
+//   rect_tube(size, wall, h, [center]);
+//   rect_tube(isize, wall, h, [center]);
+//   rect_tube(size, isize, h, [center]);
+//   rect_tube(size1, size2, wall, h, [center]);
+//   rect_tube(isize1, isize2, wall, h, [center]);
+//   rect_tube(size1, size2, isize1, isize2, h, [center]);
+// Description:
+//   Creates a rectangular or prismoid tube with optional roundovers and/or chamfers.
+//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
+//   specify rounding and/or chamferring per-edge, and for top and bottom, inside and
+//   outside  separately.
+//   Note: if using chamfers or rounding, you **must** also include the hull.scad file:
+//   ```
+//   include <BOSL2/hull.scad>
+//   ```
+// Arguments:
+//   size = The outer [X,Y] size of the rectangular tube.
+//   isize = The inner [X,Y] size of the rectangular tube.
+//   h|l = The height or length of the rectangular tube.  Default: 1
+//   wall = The thickness of the rectangular tube wall.
+//   size1 = The [X,Y] side of the outside of the bottom of the rectangular tube.
+//   size2 = The [X,Y] side of the outside of the top of the rectangular tube.
+//   isize1 = The [X,Y] side of the inside of the bottom of the rectangular tube.
+//   isize2 = The [X,Y] side of the inside of the top of the rectangular tube.
+//   rounding = The roundover radius for the outside edges of the rectangular tube.
+//   rounding1 = The roundover radius for the outside bottom corner of the rectangular tube.
+//   rounding2 = The roundover radius for the outside top corner of the rectangular tube.
+//   chamfer = The chamfer size for the outside edges of the rectangular tube.
+//   chamfer1 = The chamfer size for the outside bottom corner of the rectangular tube.
+//   chamfer2 = The chamfer size for the outside top corner of the rectangular tube.
+//   irounding = The roundover radius for the inside edges of the rectangular tube. Default: Same as `rounding`
+//   irounding1 = The roundover radius for the inside bottom corner of the rectangular tube.
+//   irounding2 = The roundover radius for the inside top corner of the rectangular tube.
+//   ichamfer = The chamfer size for the inside edges of the rectangular tube.  Default: Same as `chamfer`
+//   ichamfer1 = The chamfer size for the inside bottom corner of the rectangular tube.
+//   ichamfer2 = The chamfer size for the inside top corner of the rectangular tube.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `BOTTOM`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+// Examples:
+//   rect_tube(size=50, wall=5, h=30);
+//   rect_tube(size=[100,60], wall=5, h=30);
+//   rect_tube(isize=[60,80], wall=5, h=30);
+//   rect_tube(size=[100,60], isize=[90,50], h=30);
+//   rect_tube(size1=[100,60], size2=[70,40], wall=5, h=30);
+//   rect_tube(size1=[100,60], size2=[70,40], isize1=[40,20], isize2=[65,35], h=15);
+// Example: Outer Rounding Only
+//   include <BOSL2/hull.scad>
+//   rect_tube(size=100, wall=5, rounding=10, irounding=0, h=30);
+// Example: Outer Chamfer Only
+//   include <BOSL2/hull.scad>
+//   rect_tube(size=100, wall=5, chamfer=5, ichamfer=0, h=30);
+// Example: Outer Rounding, Inner Chamfer
+//   include <BOSL2/hull.scad>
+//   rect_tube(size=100, wall=5, rounding=10, ichamfer=8, h=30);
+// Example: Inner Rounding, Outer Chamfer
+//   include <BOSL2/hull.scad>
+//   rect_tube(size=100, wall=5, chamfer=10, irounding=8, h=30);
+// Example: Gradiant Rounding
+//   include <BOSL2/hull.scad>
+//   rect_tube(size1=100, size2=80, wall=5, rounding1=10, rounding2=0, irounding1=8, irounding2=0, h=30);
+// Example: Per Corner Rounding
+//   include <BOSL2/hull.scad>
+//   rect_tube(size=100, wall=10, rounding=[0,5,10,15], irounding=0, h=30);
+// Example: Per Corner Chamfer
+//   include <BOSL2/hull.scad>
+//   rect_tube(size=100, wall=10, chamfer=[0,5,10,15], ichamfer=0, h=30);
+// Example: Mixing Chamfer and Rounding
+//   include <BOSL2/hull.scad>
+//   rect_tube(size=100, wall=10, chamfer=[0,5,0,10], ichamfer=0, rounding=[5,0,10,0], irounding=0, h=30);
+// Example: Really Mixing It Up
+//   include <BOSL2/hull.scad>
+//   rect_tube(
+//       size1=[100,80], size2=[80,60],
+//       isize1=[50,30], isize2=[70,50], h=20,
+//       chamfer1=[0,5,0,10], ichamfer1=[0,3,0,8],
+//       chamfer2=[5,0,10,0], ichamfer2=[3,0,8,0],
+//       rounding1=[5,0,10,0], irounding1=[3,0,8,0],
+//       rounding2=[0,5,0,10], irounding2=[0,3,0,8]
+//   );
+module rect_tube(
+    size, isize,
+    h, shift=[0,0], wall,
+    size1, size2,
+    isize1, isize2,
+    rounding=0, rounding1, rounding2,
+    irounding=0, irounding1, irounding2,
+    chamfer=0, chamfer1, chamfer2,
+    ichamfer=0, ichamfer1, ichamfer2,
+    anchor, spin=0, orient=UP,
+    center, l
+) {
+    h = first_defined([h,l,1]);
+    assert(is_num(h), "l or h argument required.");
+    assert(is_vector(shift,2));
+    s1 = is_num(size1)? [size1, size1] :
+        is_vector(size1,2)? size1 :
+        is_num(size)? [size, size] :
+        is_vector(size,2)? size :
+        undef;
+    s2 = is_num(size2)? [size2, size2] :
+        is_vector(size2,2)? size2 :
+        is_num(size)? [size, size] :
+        is_vector(size,2)? size :
+        undef;
+    is1 = is_num(isize1)? [isize1, isize1] :
+        is_vector(isize1,2)? isize1 :
+        is_num(isize)? [isize, isize] :
+        is_vector(isize,2)? isize :
+        undef;
+    is2 = is_num(isize2)? [isize2, isize2] :
+        is_vector(isize2,2)? isize2 :
+        is_num(isize)? [isize, isize] :
+        is_vector(isize,2)? isize :
+        undef;
+    size1 = is_def(s1)? s1 :
+        (is_def(wall) && is_def(is1))? (is1+2*[wall,wall]) :
+        undef;
+    size2 = is_def(s2)? s2 :
+        (is_def(wall) && is_def(is2))? (is2+2*[wall,wall]) :
+        undef;
+    isize1 = is_def(is1)? is1 :
+        (is_def(wall) && is_def(s1))? (s1-2*[wall,wall]) :
+        undef;
+    isize2 = is_def(is2)? is2 :
+        (is_def(wall) && is_def(s2))? (s2-2*[wall,wall]) :
+        undef;
+    assert(wall==undef || is_num(wall));
+    assert(size1!=undef, "Bad size/size1 argument.");
+    assert(size2!=undef, "Bad size/size2 argument.");
+    assert(isize1!=undef, "Bad isize/isize1 argument.");
+    assert(isize2!=undef, "Bad isize/isize2 argument.");
+    assert(isize1.x < size1.x, "Inner size is larger than outer size.");
+    assert(isize1.y < size1.y, "Inner size is larger than outer size.");
+    assert(isize2.x < size2.x, "Inner size is larger than outer size.");
+    assert(isize2.y < size2.y, "Inner size is larger than outer size.");
+    anchor = get_anchor(anchor, center, BOT, BOT);
+    attachable(anchor,spin,orient, size=[each size1, h], size2=size2, shift=shift) {
+        diff("_H_o_L_e_")
+        prismoid(
+            size1, size2, h=h, shift=shift,
+            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
+            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
+            anchor=CTR
+        ) {
+            children();
+            tags("_H_o_L_e_") prismoid(
+                isize1, isize2, h=h+0.05, shift=shift,
+                rounding=irounding, rounding1=irounding1, rounding2=irounding2,
+                chamfer=ichamfer, chamfer1=ichamfer1, chamfer2=ichamfer2,
+                anchor=CTR
+            );
+        }
+        children();
+    }
+}
+
+
+// Module: torus()
+//
+// Descriptiom:
+//   Creates a torus shape.
+//
+// Usage:
+//   torus(r|d, r2|d2);
+//   torus(or|od, ir|id);
+//
+// Arguments:
+//   r  = major radius of torus ring. (use with of 'r2', or 'd2')
+//   r2 = minor radius of torus ring. (use with of 'r', or 'd')
+//   d  = major diameter of torus ring. (use with of 'r2', or 'd2')
+//   d2 = minor diameter of torus ring. (use with of 'r', or 'd')
+//   or = outer radius of the torus. (use with 'ir', or 'id')
+//   ir = inside radius of the torus. (use with 'or', or 'od')
+//   od = outer diameter of the torus. (use with 'ir' or 'id')
+//   id = inside diameter of the torus. (use with 'or' or 'od')
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+//
+// Example:
+//   // These all produce the same torus.
+//   torus(r=22.5, r2=7.5);
+//   torus(d=45, d2=15);
+//   torus(or=30, ir=15);
+//   torus(od=60, id=30);
+// Example: Standard Connectors
+//   torus(od=60, id=30) show_anchors();
+module torus(
+    r=undef,  d=undef,
+    r2=undef, d2=undef,
+    or=undef, od=undef,
+    ir=undef, id=undef,
+    center, anchor, spin=0, orient=UP
+) {
+    orr = get_radius(r=or, d=od, dflt=1.0);
+    irr = get_radius(r=ir, d=id, dflt=0.5);
+    majrad = get_radius(r=r, d=d, dflt=(orr+irr)/2);
+    minrad = get_radius(r=r2, d=d2, dflt=(orr-irr)/2);
+    anchor = get_anchor(anchor, center, BOT, CENTER);
+    attachable(anchor,spin,orient, r=(majrad+minrad), l=minrad*2) {
+        rotate_extrude(convexity=4) {
+            right(majrad) circle(r=minrad);
+        }
+        children();
+    }
+}
+
+
+
+// Section: Spheroid
+
+
+// Function&Module: spheroid()
+// Usage: As Module
+//   spheroid(r|d, [circum], [style])
+// Usage: As Function
+//   vnf = spheroid(r|d, [circum], [style])
+// Description:
+//   Creates a spheroid object, with support for anchoring and attachments.
+//   This is a drop-in replacement for the built-in `sphere()` module.
+//   When called as a function, returns a [VNF](vnf.scad) for a spheroid.
+// Arguments:
+//   r = Radius of the spheroid.
+//   d = Diameter of the spheroid.
+//   circum = If true, the spheroid is made large enough to circumscribe the sphere of the ideal side.  Otherwise inscribes.  Default: false (inscribes)
+//   style = The style of the spheroid's construction. One of "orig", "aligned", "stagger", "octa", or "icosa".  Default: "aligned"
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+// Example: By Radius
+//   spheroid(r=50);
+// Example: By Diameter
+//   spheroid(d=100);
+// Example: style="orig"
+//   spheroid(d=100, style="orig", $fn=10);
+// Example: style="aligned"
+//   spheroid(d=100, style="aligned", $fn=10);
+// Example: style="stagger"
+//   spheroid(d=100, style="stagger", $fn=10);
+// Example: style="octa", octahedral based tesselation.
+//   spheroid(d=100, style="octa", $fn=10);
+//   // In "octa" style, $fn is quantized
+//   //   to the nearest multiple of 4.
+// Example: style="icosa", icosahedral based tesselation.
+//   spheroid(d=100, style="icosa", $fn=10);
+//   // In "icosa" style, $fn is quantized
+//   //   to the nearest multiple of 5.
+// Example: Anchoring
+//   spheroid(d=100, anchor=FRONT);
+// Example: Spin
+//   spheroid(d=100, anchor=FRONT, spin=45);
+// Example: Orientation
+//   spheroid(d=100, anchor=FRONT, spin=45, orient=FWD);
+// Example: Standard Connectors
+//   spheroid(d=50) show_anchors();
+// Example: Called as Function
+//   vnf = spheroid(d=100, style="icosa");
+//   vnf_polyhedron(vnf);
+module spheroid(r, d, circum=false, style="aligned", anchor=CENTER, spin=0, orient=UP)
+{
+    r = get_radius(r=r, d=d, dflt=1);
+    sides = segs(r);
+    attachable(anchor,spin,orient, r=r) {
+        if (style=="orig") {
+            rotate_extrude(convexity=2,$fn=sides) {
+                difference() {
+                    oval(r=r, circum=circum, $fn=sides);
+                    left(r) square(2*r,center=true);
+                }
+            }
+        } else if (style=="aligned") {
+            rotate_extrude(convexity=2,$fn=sides) {
+                difference() {
+                    zrot(180/sides) oval(r=r, circum=circum, $fn=sides);
+                    left(r) square(2*r,center=true);
+                }
+            }
+        } else {
+            vnf = spheroid(r=r, circum=circum, style=style);
+            vnf_polyhedron(vnf, convexity=2);
+        }
+        children();
+    }
+}
+
+
+function spheroid(r, d, circum=false, style="aligned", anchor=CENTER, spin=0, orient=UP) =
+    let(
+        r = get_radius(r=r, d=d, dflt=1),
+        hsides = segs(r),
+        vsides = max(2,ceil(hsides/2)),
+        octa_steps = round(max(4,hsides)/4),
+        icosa_steps = round(max(5,hsides)/5),
+        rr = circum? (r / cos(90/vsides) / cos(180/hsides)) : r,
+        stagger = style=="stagger",
+        verts = style=="orig"? [
+            for (i=[0:1:vsides-1]) let(phi = (i+0.5)*180/(vsides))
+            for (j=[0:1:hsides-1]) let(theta = j*360/hsides)
+            spherical_to_xyz(rr, theta, phi),
+        ] : style=="aligned" || style=="stagger"? [
+            spherical_to_xyz(rr, 0, 0),
+            for (i=[1:1:vsides-1]) let(phi = i*180/vsides)
+                for (j=[0:1:hsides-1]) let(theta = (j+((stagger && i%2!=0)?0.5:0))*360/hsides)
+                    spherical_to_xyz(rr, theta, phi),
+            spherical_to_xyz(rr, 0, 180)
+        ] : style=="octa"? let(
+            meridians = [
+                1,
+                for (i = [1:1:octa_steps]) i*4,
+                for (i = [octa_steps-1:-1:1]) i*4,
+                1,
+            ]
+        ) [
+            for (i=idx(meridians), j=[0:1:meridians[i]-1])
+            spherical_to_xyz(rr, j*360/meridians[i], i*180/(len(meridians)-1))
+        ] : style=="icosa"? [
+            for (tb=[0,1], j=[0,2], i = [0:1:4]) let(
+                theta0 = i*360/5,
+                theta1 = (i-0.5)*360/5,
+                theta2 = (i+0.5)*360/5,
+                phi0 = 180/3 * j,
+                phi1 = 180/3,
+                v0 = spherical_to_xyz(1,theta0,phi0),
+                v1 = spherical_to_xyz(1,theta1,phi1),
+                v2 = spherical_to_xyz(1,theta2,phi1),
+                ax0 = vector_axis(v0, v1),
+                ang0 = vector_angle(v0, v1),
+                ax1 = vector_axis(v0, v2),
+                ang1 = vector_angle(v0, v2)
+            )
+            for (k = [0:1:icosa_steps]) let(
+                u = k/icosa_steps,
+                vv0 = rot(ang0*u, ax0, p=v0),
+                vv1 = rot(ang1*u, ax1, p=v0),
+                ax2 = vector_axis(vv0, vv1),
+                ang2 = vector_angle(vv0, vv1)
+            )
+            for (l = [0:1:k]) let(
+                v = k? l/k : 0,
+                pt = rot(ang2*v, v=ax2, p=vv0) * rr * (tb? -1 : 1)
+            ) pt
+        ] : assert(in_list(style,["orig","aligned","stagger","octa","icosa"])),
+        lv = len(verts),
+        faces = style=="orig"? [
+            [for (i=[0:1:hsides-1]) hsides-i-1],
+            [for (i=[0:1:hsides-1]) lv-hsides+i],
+            for (i=[0:1:vsides-2], j=[0:1:hsides-1]) each [
+                [(i+1)*hsides+j, i*hsides+j, i*hsides+(j+1)%hsides],
+                [(i+1)*hsides+j, i*hsides+(j+1)%hsides, (i+1)*hsides+(j+1)%hsides],
+            ]
+        ] : style=="aligned" || style=="stagger"? [
+            for (i=[0:1:hsides-1]) let(
+                b2 = lv-2-hsides
+            ) each [
+                [i+1, 0, ((i+1)%hsides)+1],
+                [lv-1, b2+i+1, b2+((i+1)%hsides)+1],
+            ],
+            for (i=[0:1:vsides-3], j=[0:1:hsides-1]) let(
+                base = 1 + hsides*i
+            ) each (
+                (stagger && i%2!=0)? [
+                    [base+j, base+hsides+j%hsides, base+hsides+(j+hsides-1)%hsides],
+                    [base+j, base+(j+1)%hsides, base+hsides+j],
+                ] : [
+                    [base+j, base+(j+1)%hsides, base+hsides+(j+1)%hsides],
+                    [base+j, base+hsides+(j+1)%hsides, base+hsides+j],
+                ]
+            )
+        ] : style=="octa"? let(
+            meridians = [
+                0, 1,
+                for (i = [1:1:octa_steps]) i*4,
+                for (i = [octa_steps-1:-1:1]) i*4,
+                1,
+            ],
+            offs = cumsum(meridians),
+            pc = select(offs,-1)-1,
+            os = octa_steps * 2
+        ) [
+            for (i=[0:1:3]) [0, 1+(i+1)%4, 1+i],
+            for (i=[0:1:3]) [pc-0, pc-(1+(i+1)%4), pc-(1+i)],
+            for (i=[1:1:octa_steps-1]) let(
+                m = meridians[i+2]/4
+            )
+            for (j=[0:1:3], k=[0:1:m-1]) let(
+                m1 = meridians[i+1],
+                m2 = meridians[i+2],
+                p1 = offs[i+0] + (j*m1/4 + k+0) % m1,
+                p2 = offs[i+0] + (j*m1/4 + k+1) % m1,
+                p3 = offs[i+1] + (j*m2/4 + k+0) % m2,
+                p4 = offs[i+1] + (j*m2/4 + k+1) % m2,
+                p5 = offs[os-i+0] + (j*m1/4 + k+0) % m1,
+                p6 = offs[os-i+0] + (j*m1/4 + k+1) % m1,
+                p7 = offs[os-i-1] + (j*m2/4 + k+0) % m2,
+                p8 = offs[os-i-1] + (j*m2/4 + k+1) % m2
+            ) each [
+                [p1, p4, p3],
+                if (k<m-1) [p1, p2, p4],
+                [p5, p7, p8],
+                if (k<m-1) [p5, p8, p6],
+            ],
+        ] : style=="icosa"? let(
+            pyr = [for (x=[0:1:icosa_steps+1]) x],
+            tri = sum(pyr),
+            soff = cumsum(pyr)
+        ) [
+            for (tb=[0,1], j=[0,1], i = [0:1:4]) let(
+                base = ((((tb*2) + j) * 5) + i) * tri
+            )
+            for (k = [0:1:icosa_steps-1])
+            for (l = [0:1:k]) let(
+                v1 = base + soff[k] + l,
+                v2 = base + soff[k+1] + l,
+                v3 = base + soff[k+1] + (l + 1),
+                faces = [
+                    if(l>0) [v1-1,v1,v2],
+                    [v1,v3,v2],
+                ],
+                faces2 = (tb+j)%2? [for (f=faces) reverse(f)] : faces
+            ) each faces2
+        ] : []
+    ) [reorient(anchor,spin,orient, r=r, p=verts), faces];
+
+
+
+// Section: 3D Printing Shapes
+
+
+// Module: teardrop()
+//
+// Description:
+//   Makes a teardrop shape in the XZ plane. Useful for 3D printable holes.
+//
+// Usage:
+//   teardrop(r|d, l|h, [ang], [cap_h])
+//
+// Arguments:
+//   r = Radius of circular part of teardrop.  (Default: 1)
+//   d = Diameter of circular portion of bottom. (Use instead of r)
+//   l = Thickness of teardrop. (Default: 1)
+//   ang = Angle of hat walls from the Z axis.  (Default: 45 degrees)
+//   cap_h = If given, height above center where the shape will be truncated.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+//
+// Example: Typical Shape
+//   teardrop(r=30, h=10, ang=30);
+// Example: Crop Cap
+//   teardrop(r=30, h=10, ang=30, cap_h=40);
+// Example: Close Crop
+//   teardrop(r=30, h=10, ang=30, cap_h=20);
+module teardrop(r=undef, d=undef, l=undef, h=undef, ang=45, cap_h=undef, anchor=CENTER, spin=0, orient=UP)
+{
+    r = get_radius(r=r, d=d, dflt=1);
+    l = first_defined([l, h, 1]);
+    size = [r*2,l,r*2];
+    attachable(anchor,spin,orient, size=size) {
+        rot(from=UP,to=FWD) {
+            linear_extrude(height=l, center=true, slices=2) {
+                teardrop2d(r=r, ang=ang, cap_h=cap_h);
+            }
+        }
+        children();
+    }
+}
+
+
+// Module: onion()
+//
+// Description:
+//   Creates a sphere with a conical hat, to make a 3D teardrop.
+//
+// Usage:
+//   onion(r|d, [maxang], [cap_h]);
+//
+// Arguments:
+//   r = radius of spherical portion of the bottom. (Default: 1)
+//   d = diameter of spherical portion of bottom.
+//   cap_h = height above sphere center to truncate teardrop shape.
+//   maxang = angle of cone on top from vertical.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+//
+// Example: Typical Shape
+//   onion(r=30, maxang=30);
+// Example: Crop Cap
+//   onion(r=30, maxang=30, cap_h=40);
+// Example: Close Crop
+//   onion(r=30, maxang=30, cap_h=20);
+// Example: Standard Connectors
+//   onion(r=30, maxang=30, cap_h=40) show_anchors();
+module onion(cap_h=undef, r=undef, d=undef, maxang=45, h=undef, anchor=CENTER, spin=0, orient=UP)
+{
+    r = get_radius(r=r, d=d, dflt=1);
+    h = first_defined([cap_h, h]);
+    maxd = 3*r/tan(maxang);
+    anchors = [
+        ["cap", [0,0,h], UP, 0]
+    ];
+    attachable(anchor,spin,orient, r=r, anchors=anchors) {
+        rotate_extrude(convexity=2) {
+            difference() {
+                teardrop2d(r=r, ang=maxang, cap_h=h);
+                left(r) square(size=[2*r,maxd], center=true);
+            }
+        }
+        children();
+    }
+}
+
+
+
+// Section: Miscellaneous
+
+
+// Module: nil()
+//
+// Description:
+//   Useful when you MUST pass a child to a module, but you want it to be nothing.
+module nil() union(){}
+
+
+// Module: noop()
+//
+// Description:
+//   Passes through the children passed to it, with no action at all.
+//   Useful while debugging when you want to replace a command.
+//
+// Arguments:
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+module noop(spin=0, orient=UP) attachable(CENTER,spin,orient, d=0.01) {nil(); children();}
+
+
+// Module: pie_slice()
+//
+// Description:
+//   Creates a pie slice shape.
+//
+// Usage:
+//   pie_slice(ang, l|h, r|d, [center]);
+//   pie_slice(ang, l|h, r1|d1, r2|d2, [center]);
+//
+// Arguments:
+//   ang = pie slice angle in degrees.
+//   h = height of pie slice.
+//   r = radius of pie slice.
+//   r1 = bottom radius of pie slice.
+//   r2 = top radius of pie slice.
+//   d = diameter of pie slice.
+//   d1 = bottom diameter of pie slice.
+//   d2 = top diameter of pie slice.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
+//
+// Example: Cylindrical Pie Slice
+//   pie_slice(ang=45, l=20, r=30);
+// Example: Conical Pie Slice
+//   pie_slice(ang=60, l=20, d1=50, d2=70);
+module pie_slice(
+    ang=30, l=undef,
+    r=undef, r1=undef, r2=undef,
+    d=undef, d1=undef, d2=undef,
+    h=undef, center,
+    anchor, spin=0, orient=UP
+) {
+    l = first_defined([l, h, 1]);
+    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10);
+    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10);
+    maxd = max(r1,r2)+0.1;
+    anchor = get_anchor(anchor, center, BOT, BOT);
+    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
+        difference() {
+            cyl(r1=r1, r2=r2, h=l);
+            if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
+            difference() {
+                fwd(maxd/2) cube([2*maxd, maxd, l+0.2], center=true);
+                if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
+            }
+        }
+        children();
+    }
+}
+
+
+// Module: interior_fillet()
+//
+// Description:
+//   Creates a shape that can be unioned into a concave joint between two faces, to fillet them.
+//   Center this part along the concave edge to be chamfered and union it in.
+//
+// Usage:
+//   interior_fillet(l, r, [ang], [overlap]);
+//
+// Arguments:
+//   l = length of edge to fillet.
+//   r = radius of fillet.
+//   ang = angle between faces to fillet.
+//   overlap = overlap size for unioning with faces.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `FRONT+LEFT`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+//
+// Example:
+//   union() {
+//       translate([0,2,-4]) cube([20, 4, 24], anchor=BOTTOM);
+//       translate([0,-10,-4]) cube([20, 20, 4], anchor=BOTTOM);
+//       color("green") interior_fillet(l=20, r=10, spin=180, orient=RIGHT);
+//   }
+//
+// Example:
+//   interior_fillet(l=40, r=10, spin=-90);
+//
+// Example: Using with Attachments
+//   cube(50,center=true) {
+//     position(FRONT+LEFT)
+//       interior_fillet(l=50, r=10, spin=-90);
+//     position(BOT+FRONT)
+//       interior_fillet(l=50, r=10, spin=180, orient=RIGHT);
+//   }
+module interior_fillet(l=1.0, r=1.0, ang=90, overlap=0.01, anchor=FRONT+LEFT, spin=0, orient=UP) {
+    dy = r/tan(ang/2);
+    steps = ceil(segs(r)*ang/360);
+    step = ang/steps;
+    attachable(anchor,spin,orient, size=[r,r,l]) {
+        linear_extrude(height=l, convexity=4, center=true) {
+            path = concat(
+                [[0,0]],
+                [for (i=[0:1:steps]) let(a=270-i*step) r*[cos(a),sin(a)]+[dy,r]]
+            );
+            translate(-[r,r]/2) polygon(path);
+        }
+        children();
+    }
+}
+
+
+
+// Module: slot()
+//
+// Description:
+//   Makes a linear slot with rounded ends, appropriate for bolts to slide along.
+//
+// Usage:
+//   slot(h, l, r|d, [center]);
+//   slot(h, p1, p2, r|d, [center]);
+//   slot(h, l, r1|d1, r2|d2, [center]);
+//   slot(h, p1, p2, r1|d1, r2|d2, [center]);
+//
+// Arguments:
+//   p1 = center of starting circle of slot.
+//   p2 = center of ending circle of slot.
+//   l = length of slot along the X axis.
+//   h = height of slot shape. (default: 10)
+//   r = radius of slot circle. (default: 5)
+//   r1 = bottom radius of slot cone.
+//   r2 = top radius of slot cone.
+//   d = diameter of slot circle.
+//   d1 = bottom diameter of slot cone.
+//   d2 = top diameter of slot cone.
+//
+// Example: Between Two Points
+//   slot([0,0,0], [50,50,0], r1=5, r2=10, h=5);
+// Example: By Length
+//   slot(l=50, r1=5, r2=10, h=5);
+module slot(
+    p1=undef, p2=undef, h=10, l=undef,
+    r=undef, r1=undef, r2=undef,
+    d=undef, d1=undef, d2=undef
+) {
+    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=5);
+    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=5);
+    sides = quantup(segs(max(r1, r2)), 4);
+    // TODO: implement orient and anchors.
+    hull() line_of(p1=p1, p2=p2, l=l, n=2) cyl(l=h, r1=r1, r2=r2, center=true, $fn=sides);
+}
+
+
+// Module: arced_slot()
+//
+// Description:
+//   Makes an arced slot, appropriate for bolts to slide along.
+//
+// Usage:
+//   arced_slot(h, r|d, sr|sd, [sa], [ea], [center], [$fn2]);
+//   arced_slot(h, r|d, sr1|sd1, sr2|sd2, [sa], [ea], [center], [$fn2]);
+//
+// Arguments:
+//   cp = Centerpoint of slot arc.  Default: `[0, 0, 0]`
+//   h = Height of slot arc shape.  Default: `1`
+//   r = Radius of slot arc.  Default: `0.5`
+//   d = Diameter of slot arc.  Default: `1`
+//   sr = Radius of slot channel.  Default: `0.5`
+//   sd = Diameter of slot channel.  Default: `0.5`
+//   sr1 = Bottom radius of slot channel cone.  Use instead of `sr`.
+//   sr2 = Top radius of slot channel cone.  Use instead of `sr`.
+//   sd1 = Bottom diameter of slot channel cone.  Use instead of `sd`.
+//   sd2 = Top diameter of slot channel cone.  Use instead of `sd`.
+//   sa = Starting angle.  Default: `0`
+//   ea = Ending angle.  Default: `90`
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
+//   $fn2 = The `$fn` value to use on the small round endcaps.  The major arcs are still based on `$fn`.  Default: `$fn`
+//
+// Example(Med): Typical Arced Slot
+//   arced_slot(d=60, h=5, sd=10, sa=60, ea=280);
+// Example(Med): Conical Arced Slot
+//   arced_slot(r=60, h=5, sd1=10, sd2=15, sa=45, ea=180);
+module arced_slot(
+    r=undef, d=undef, h=1.0,
+    sr=undef, sr1=undef, sr2=undef,
+    sd=undef, sd1=undef, sd2=undef,
+    sa=0, ea=90, cp=[0,0,0],
+    anchor=TOP, spin=0, orient=UP,
+    $fn2 = undef
+) {
+    r = get_radius(r=r, d=d, dflt=2);
+    sr1 = get_radius(r1=sr1, r=sr, d1=sd1, d=sd, dflt=2);
+    sr2 = get_radius(r1=sr2, r=sr, d1=sd2, d=sd, dflt=2);
+    fn_minor = first_defined([$fn2, $fn]);
+    da = ea - sa;
+    attachable(anchor,spin,orient, r1=r+sr1, r2=r+sr2, l=h) {
+        translate(cp) {
+            zrot(sa) {
+                difference() {
+                    pie_slice(ang=da, l=h, r1=r+sr1, r2=r+sr2, orient=UP, anchor=CENTER);
+                    cyl(h=h+0.1, r1=r-sr1, r2=r-sr2);
+                }
+                right(r) cyl(h=h, r1=sr1, r2=sr2, $fn=fn_minor);
+                zrot(da) right(r) cyl(h=h, r1=sr1, r2=sr2, $fn=fn_minor);
+            }
+        }
+        children();
+    }
+}
+
+
+// Module: heightfield()
+// Usage:
+//   heightfield(heightfield, [size], [bottom]);
+// Description:
+//   Given a regular rectangular 2D grid of scalar values, generates a 3D surface where the height at
+//   any given point is the scalar value for that position.
+// Arguments:
+//   heightfield = The 2D rectangular array of heights.
+//   size = The [X,Y] size of the surface to create.  If given as a scalar, use it for both X and Y sizes.
+//   bottom = The Z coordinate for the bottom of the heightfield object to create.  Must be less than the minimum heightfield value.  Default: 0
+//   convexity = Max number of times a line could intersect a wall of the surface being formed.
+// Example:
+//   heightfield(size=[100,100], bottom=-20, heightfield=[
+//       for (x=[-180:4:180]) [for(y=[-180:4:180]) 10*cos(3*norm([x,y]))]
+//   ]);
+// Example:
+//   intersection() {
+//       heightfield(size=[100,100], heightfield=[
+//           for (x=[-180:5:180]) [for(y=[-180:5:180]) 10+5*cos(3*x)*sin(3*y)]
+//       ]);
+//       cylinder(h=50,d=100);
+//   }
+module heightfield(heightfield, size=[100,100], bottom=0, convexity=10)
+{
+    size = is_num(size)? [size,size] : point2d(size);
+    dim = array_dim(heightfield);
+    assert(dim.x!=undef);
+    assert(dim.y!=undef);
+    assert(bottom<min(flatten(heightfield)), "bottom must be less than the minimum heightfield value.");
+    spacing = vdiv(size,dim-[1,1]);
+    vertices = concat(
+        [
+            for (i=[0:1:dim.x-1], j=[0:1:dim.y-1]) let(
+                pos = [i*spacing.x-size.x/2, j*spacing.y-size.y/2, heightfield[i][j]]
+            ) pos
+        ], [
+            for (i=[0:1:dim.x-1]) let(
+                pos = [i*spacing.x-size.x/2, -size.y/2, bottom]
+            ) pos
+        ], [
+            for (i=[0:1:dim.x-1]) let(
+                pos = [i*spacing.x-size.x/2, size.y/2, bottom]
+            ) pos
+        ], [
+            for (j=[0:1:dim.y-1]) let(
+                pos = [-size.x/2, j*spacing.y-size.y/2, bottom]
+            ) pos
+        ], [
+            for (j=[0:1:dim.y-1]) let(
+                pos = [size.x/2, j*spacing.y-size.y/2, bottom]
+            ) pos
+        ]
+    );
+    faces = concat(
+        [
+            for (i=[0:1:dim.x-2], j=[0:1:dim.y-2]) let(
+                idx1 = (i+0)*dim.y + j+0,
+                idx2 = (i+0)*dim.y + j+1,
+                idx3 = (i+1)*dim.y + j+0,
+                idx4 = (i+1)*dim.y + j+1
+            ) each [[idx1, idx2, idx4], [idx1, idx4, idx3]]
+        ], [
+            for (i=[0:1:dim.x-2]) let(
+                idx1 = dim.x*dim.y,
+                idx2 = dim.x*dim.y+dim.x+i,
+                idx3 = idx2+1
+            ) [idx1,idx3,idx2]
+        ], [
+            for (i=[0:1:dim.y-2]) let(
+                idx1 = dim.x*dim.y,
+                idx2 = dim.x*dim.y+dim.x*2+dim.y+i,
+                idx3 = idx2+1
+            ) [idx1,idx2,idx3]
+        ], [
+            for (i=[0:1:dim.x-2]) let(
+                idx1 = (i+0)*dim.y+0,
+                idx2 = (i+1)*dim.y+0,
+                idx3 = dim.x*dim.y+i,
+                idx4 = idx3+1
+            ) each [[idx1, idx2, idx4], [idx1, idx4, idx3]]
+        ], [
+            for (i=[0:1:dim.x-2]) let(
+                idx1 = (i+0)*dim.y+dim.y-1,
+                idx2 = (i+1)*dim.y+dim.y-1,
+                idx3 = dim.x*dim.y+dim.x+i,
+                idx4 = idx3+1
+            ) each [[idx1, idx4, idx2], [idx1, idx3, idx4]]
+        ], [
+            for (j=[0:1:dim.y-2]) let(
+                idx1 = j,
+                idx2 = j+1,
+                idx3 = dim.x*dim.y+dim.x*2+j,
+                idx4 = idx3+1
+            ) each [[idx1, idx4, idx2], [idx1, idx3, idx4]]
+        ], [
+            for (j=[0:1:dim.y-2]) let(
+                idx1 = (dim.x-1)*dim.y+j,
+                idx2 = idx1+1,
+                idx3 = dim.x*dim.y+dim.x*2+dim.y+j,
+                idx4 = idx3+1
+            ) each [[idx1, idx2, idx4], [idx1, idx4, idx3]]
+        ]
+    );
+    polyhedron(points=vertices, faces=faces, convexity=convexity);
+}
+
+
+
+// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/shapes2d.scad b/shapes2d.scad
new file mode 100644
index 0000000..f328952
--- /dev/null
+++ b/shapes2d.scad
@@ -0,0 +1,1866 @@
+//////////////////////////////////////////////////////////////////////
+// LibFile: shapes2d.scad
+//   Common useful 2D shapes.
+//   To use, add the following lines to the beginning of your file:
+//   ```
+//   include <BOSL2/std.scad>
+//   ```
+//////////////////////////////////////////////////////////////////////
+
+
+// Section: 2D Drawing Helpers
+
+// Module: stroke()
+// Usage:
+//   stroke(path, [width], [closed], [endcaps], [endcap_width], [endcap_length], [endcap_extent], [trim]);
+//   stroke(path, [width], [closed], [endcap1], [endcap2], [endcap_width1], [endcap_width2], [endcap_length1], [endcap_length2], [endcap_extent1], [endcap_extent2], [trim1], [trim2]);
+// Description:
+//   Draws a 2D or 3D path with a given line width.  Endcaps can be specified for each end individually.
+// Figure(2D,Big): Endcap Types
+//   endcaps = [
+//       ["butt", "square", "round", "chisel", "tail", "tail2"],
+//       ["line", "cross", "dot", "diamond", "x", "arrow", "arrow2"]
+//   ];
+//   for (x=idx(endcaps), y=idx(endcaps[x])) {
+//       cap = endcaps[x][y];
+//       right(x*60-60+5) fwd(y*10+15) {
+//           right(28) color("black") text(text=cap, size=5, halign="left", valign="center");
+//           stroke([[0,0], [20,0]], width=3, endcap_width=3, endcap1=false, endcap2=cap);
+//           color("black") stroke([[0,0], [20,0]], width=0.25, endcaps=false);
+//       }
+//   }
+// Arguments:
+//   path = The 2D path to draw along.
+//   width = The width of the line to draw.  If given as a list of widths, (one for each path point), draws the line with varying thickness to each point.
+//   closed = If true, draw an additional line from the end of the path to the start.
+//   endcaps = Specifies the endcap type for both ends of the line.  If a 2D path is given, use that to draw custom endcaps.
+//   endcap1 = Specifies the endcap type for the start of the line.  If a 2D path is given, use that to draw a custom endcap.
+//   endcap2 = Specifies the endcap type for the end of the line.  If a 2D path is given, use that to draw a custom endcap.
+//   endcap_width = Some endcap types are wider than the line.  This specifies the size of endcaps, in multiples of the line width.  Default: 3.5
+//   endcap_width1 = This specifies the size of starting endcap, in multiples of the line width.  Default: 3.5
+//   endcap_width2 = This specifies the size of ending endcap, in multiples of the line width.  Default: 3.5
+//   endcap_length = Length of endcaps, in multiples of the line width.  Default: `endcap_width*0.5`
+//   endcap_length1 = Length of starting endcap, in multiples of the line width.  Default: `endcap_width1*0.5`
+//   endcap_length2 = Length of ending endcap, in multiples of the line width.  Default: `endcap_width2*0.5`
+//   endcap_extent = Extents length of endcaps, in multiples of the line width.  Default: `endcap_width*0.5`
+//   endcap_extent1 = Extents length of starting endcap, in multiples of the line width.  Default: `endcap_width1*0.5`
+//   endcap_extent2 = Extents length of ending endcap, in multiples of the line width.  Default: `endcap_width2*0.5`
+//   endcap_angle = Extra axial rotation given to flat endcaps for 3D paths, in degrees.  If not given, the endcaps are fully spun.  Default: `undef` (Fully spun cap)
+//   endcap_angle1 = Extra axial rotation given to a flat starting endcap for 3D paths, in degrees.  If not given, the endcap is fully spun.  Default: `undef` (Fully spun cap)
+//   endcap_angle2 = Extra axial rotation given to a flat ending endcap for 3D paths, in degrees.  If not given, the endcap is fully spun.  Default: `undef` (Fully spun cap)
+//   trim = Trim the the start and end line segments by this much, to keep them from interfering with custom endcaps.
+//   trim1 = Trim the the starting line segment by this much, to keep it from interfering with a custom endcap.
+//   trim2 = Trim the the ending line segment by this much, to keep it from interfering with a custom endcap.
+//   convexity = Max number of times a line could intersect a wall of an endcap.
+//   hull = If true, use `hull()` to make higher quality joints between segments, at the cost of being much slower.  Default: true
+// Example(2D): Drawing a Path
+//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
+//   stroke(path, width=20);
+// Example(2D): Closing a Path
+//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
+//   stroke(path, width=20, endcaps=true, closed=true);
+// Example(2D): Fancy Arrow Endcaps
+//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
+//   stroke(path, width=10, endcaps="arrow2");
+// Example(2D): Modified Fancy Arrow Endcaps
+//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
+//   stroke(path, width=10, endcaps="arrow2", endcap_width=6, endcap_length=3, endcap_extent=2);
+// Example(2D): Mixed Endcaps
+//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
+//   stroke(path, width=10, endcap1="tail2", endcap2="arrow2");
+// Example(2D): Custom Endcap Shapes
+//   path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
+//   arrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
+//   stroke(path, width=10, trim=3.5, endcaps=arrow);
+// Example(2D): Variable Line Width
+//   path = circle(d=50,$fn=18);
+//   widths = [for (i=idx(path)) 10*i/len(path)+2];
+//   stroke(path,width=widths,$fa=1,$fs=1);
+// Example: 3D Path with Endcaps
+//   path = rot([15,30,0], p=path3d(pentagon(d=50)));
+//   stroke(path, width=2, endcaps="arrow2", $fn=18);
+// Example: 3D Path with Flat Endcaps
+//   path = rot([15,30,0], p=path3d(pentagon(d=50)));
+//   stroke(path, width=2, endcaps="arrow2", endcap_angle=0, $fn=18);
+// Example: 3D Path with Mixed Endcaps
+//   path = rot([15,30,0], p=path3d(pentagon(d=50)));
+//   stroke(path, width=2, endcap1="arrow2", endcap2="tail", endcap_angle2=0, $fn=18);
+module stroke(
+    path, width=1, closed=false,
+    endcaps, endcap1, endcap2,
+    trim, trim1, trim2,
+    endcap_width, endcap_width1, endcap_width2,
+    endcap_length, endcap_length1, endcap_length2,
+    endcap_extent, endcap_extent1, endcap_extent2,
+    endcap_angle, endcap_angle1, endcap_angle2,
+    convexity=10, hull=true
+) {
+    function _endcap_shape(cap,linewidth,w,l,l2) = (
+        let(sq2=sqrt(2), l3=l-l2)
+        (cap=="round" || cap==true)? circle(d=1, $fn=max(8, segs(w/2))) :
+        cap=="chisel"? [[-0.5,0], [0,0.5], [0.5,0], [0,-0.5]] :
+        cap=="square"? [[-0.5,-0.5], [-0.5,0.5], [0.5,0.5], [0.5,-0.5]] :
+        cap=="diamond"? [[0,w/2], [w/2,0], [0,-w/2], [-w/2,0]] :
+        cap=="dot"?    circle(d=3, $fn=max(12, segs(w*3/2))) :
+        cap=="x"?      [for (a=[0:90:270]) each rot(a,p=[[w+sq2/2,w-sq2/2]/2, [w-sq2/2,w+sq2/2]/2, [0,sq2/2]]) ] :
+        cap=="cross"?  [for (a=[0:90:270]) each rot(a,p=[[1,w]/2, [-1,w]/2, [-1,1]/2]) ] :
+        cap=="line"?   [[w/2,0.5], [w/2,-0.5], [-w/2,-0.5], [-w/2,0.5]] :
+        cap=="arrow"?  [[0,0], [w/2,-l2], [w/2,-l2-l], [0,-l], [-w/2,-l2-l], [-w/2,-l2]] :
+        cap=="arrow2"? [[0,0], [w/2,-l2-l], [0,-l], [-w/2,-l2-l]] :
+        cap=="tail"?   [[0,0], [w/2,l2], [w/2,l2-l], [0,-l], [-w/2,l2-l], [-w/2,l2]] :
+        cap=="tail2"?  [[w/2,0], [w/2,-l], [0,-l-l2], [-w/2,-l], [-w/2,0]] :
+        is_path(cap)? cap :
+        []
+    ) * linewidth;
+
+    assert(is_bool(closed));
+    assert(is_list(path));
+    if (len(path) > 1) {
+        assert(is_path(path,[2,3]), "The path argument must be a list of 2D or 3D points.");
+    }
+    path = deduplicate( closed? close_path(path) : path );
+
+    assert(is_num(width) || (is_vector(width) && len(width)==len(path)));
+    width = is_num(width)? [for (x=path) width] : width;
+
+    endcap1 = first_defined([endcap1, endcaps, "round"]);
+    endcap2 = first_defined([endcap2, endcaps, "round"]);
+    assert(is_bool(endcap1) || is_string(endcap1) || is_path(endcap1));
+    assert(is_bool(endcap2) || is_string(endcap2) || is_path(endcap2));
+
+    endcap_width1 = first_defined([endcap_width1, endcap_width, 3.5]);
+    endcap_width2 = first_defined([endcap_width2, endcap_width, 3.5]);
+    assert(is_num(endcap_width1));
+    assert(is_num(endcap_width2));
+
+    endcap_length1 = first_defined([endcap_length1, endcap_length, endcap_width1*0.5]);
+    endcap_length2 = first_defined([endcap_length2, endcap_length, endcap_width2*0.5]);
+    assert(is_num(endcap_length1));
+    assert(is_num(endcap_length2));
+
+    endcap_extent1 = first_defined([endcap_extent1, endcap_extent, endcap_width1*0.5]);
+    endcap_extent2 = first_defined([endcap_extent2, endcap_extent, endcap_width2*0.5]);
+    assert(is_num(endcap_extent1));
+    assert(is_num(endcap_extent2));
+
+    endcap_angle1 = first_defined([endcap_angle1, endcap_angle]);
+    endcap_angle2 = first_defined([endcap_angle2, endcap_angle]);
+    assert(is_undef(endcap_angle1)||is_num(endcap_angle1));
+    assert(is_undef(endcap_angle2)||is_num(endcap_angle2));
+
+    endcap_shape1 = _endcap_shape(endcap1, select(width,0), endcap_width1, endcap_length1, endcap_extent1);
+    endcap_shape2 = _endcap_shape(endcap2, select(width,-1), endcap_width2, endcap_length2, endcap_extent2);
+
+    trim1 = select(width,0) * first_defined([
+        trim1, trim,
+        (endcap1=="arrow")? endcap_length1-0.01 :
+        (endcap1=="arrow2")? endcap_length1*3/4 :
+        0
+    ]);
+    assert(is_num(trim1));
+
+    trim2 = select(width,-1) * first_defined([
+        trim2, trim,
+        (endcap2=="arrow")? endcap_length2-0.01 :
+        (endcap2=="arrow2")? endcap_length2*3/4 :
+        0
+    ]);
+    assert(is_num(trim2));
+
+    if (len(path) == 1) {
+        if (len(path[0]) == 2) {
+            translate(path[0]) circle(d=width[0]);
+        } else {
+            translate(path[0]) sphere(d=width[0]);
+        }
+    } else {
+        spos = path_pos_from_start(path,trim1,closed=false);
+        epos = path_pos_from_end(path,trim2,closed=false);
+        path2 = path_subselect(path, spos[0], spos[1], epos[0], epos[1]);
+        widths = concat(
+            [lerp(width[spos[0]], width[(spos[0]+1)%len(width)], spos[1])],
+            [for (i = [spos[0]+1:1:epos[0]]) width[i]],
+            [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);
+
+        if (len(path[0]) == 2) {
+            // Straight segments
+            for (i = idx(path2,end=-2)) {
+                seg = select(path2,i,i+1);
+                delt = seg[1] - seg[0];
+                translate(seg[0]) {
+                    rot(from=BACK,to=delt) {
+                        trapezoid(w1=widths[i], w2=widths[i+1], h=norm(delt), anchor=FRONT);
+                    }
+                }
+            }
+
+            // Joints
+            for (i = [1:1:len(path2)-2]) {
+                $fn = quantup(segs(widths[i]/2),4);
+                if (hull) {
+                    hull() {
+                        translate(path2[i]) {
+                            rot(from=BACK, to=path2[i]-path2[i-1])
+                                circle(d=widths[i]);
+                            rot(from=BACK, to=path2[i+1]-path2[i])
+                                circle(d=widths[i]);
+                        }
+                    }
+                } else {
+                    translate(path2[i]) {
+                        rot(from=BACK, to=path2[i]-path2[i-1])
+                            circle(d=widths[i]);
+                        rot(from=BACK, to=path2[i+1]-path2[i])
+                            circle(d=widths[i]);
+                    }
+                }
+            }
+
+            // Endcap1
+            translate(path[0]) {
+                start_vec = select(path,0) - select(path,1);
+                rot(from=BACK, to=start_vec) {
+                    polygon(endcap_shape1);
+                }
+            }
+
+            // Endcap2
+            translate(select(path,-1)) {
+                rot(from=BACK, to=end_vec) {
+                    polygon(endcap_shape2);
+                }
+            }
+        } else {
+            quatsums = Q_Cumulative([
+                for (i = idx(path2,end=-2)) let(
+                    vec1 = i==0? UP : unit(path2[i]-path2[i-1], UP),
+                    vec2 = unit(path2[i+1]-path2[i], UP),
+                    axis = vector_axis(vec1,vec2),
+                    ang = vector_angle(vec1,vec2)
+                ) Quat(axis,ang)
+            ]);
+            rotmats = [for (q=quatsums) Q_Matrix4(q)];
+            sides = [
+                for (i = idx(path2,end=-2))
+                quantup(segs(max(widths[i],widths[i+1])/2),4)
+            ];
+
+            // Straight segments
+            for (i = idx(path2,end=-2)) {
+                dist = norm(path2[i+1] - path2[i]);
+                w1 = widths[i]/2;
+                w2 = widths[i+1]/2;
+                $fn = sides[i];
+                translate(path2[i]) {
+                    multmatrix(rotmats[i]) {
+                        cylinder(r1=w1, r2=w2, h=dist, center=false);
+                    }
+                }
+            }
+
+            // Joints
+            for (i = [1:1:len(path2)-2]) {
+                $fn = sides[i];
+                translate(path2[i]) {
+                    if (hull) {
+                        hull(){
+                            multmatrix(rotmats[i]) {
+                                sphere(d=widths[i]);
+                            }
+                            multmatrix(rotmats[i-1]) {
+                                sphere(d=widths[i]);
+                            }
+                        }
+                    } else {
+                        multmatrix(rotmats[i]) {
+                            sphere(d=widths[i]);
+                        }
+                        multmatrix(rotmats[i-1]) {
+                            sphere(d=widths[i]);
+                        }
+                    }
+                }
+            }
+
+            // Endcap1
+            translate(path[0]) {
+                multmatrix(rotmats[0] * xrot(180)) {
+                    $fn = sides[0];
+                    if (is_undef(endcap_angle1)) {
+                        rotate_extrude(convexity=convexity) {
+                            right_half(planar=true) {
+                                polygon(endcap_shape1);
+                            }
+                        }
+                    } else {
+                        rotate([90,0,endcap_angle1]) {
+                            linear_extrude(height=widths[0], center=true, convexity=convexity) {
+                                polygon(endcap_shape1);
+                            }
+                        }
+                    }
+                }
+            }
+
+            // Endcap2
+            translate(select(path,-1)) {
+                multmatrix(select(rotmats,-1)) {
+                    $fn = select(sides,-1);
+                    if (is_undef(endcap_angle2)) {
+                        rotate_extrude(convexity=convexity) {
+                            right_half(planar=true) {
+                                polygon(endcap_shape2);
+                            }
+                        }
+                    } else {
+                        rotate([90,0,endcap_angle2]) {
+                            linear_extrude(height=select(widths,-1), center=true, convexity=convexity) {
+                                polygon(endcap_shape2);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+// Function&Module: arc()
+// Usage: 2D arc from 0º to `angle` degrees.
+//   arc(N, r|d, angle);
+// Usage: 2D arc from START to END degrees.
+//   arc(N, r|d, angle=[START,END])
+// Usage: 2D arc from `start` to `start+angle` degrees.
+//   arc(N, r|d, start, angle)
+// Usage: 2D circle segment by `width` and `thickness`, starting and ending on the X axis.
+//   arc(N, width, thickness)
+// Usage: Shortest 2D or 3D arc around centerpoint `cp`, starting at P0 and ending on the vector pointing from `cp` to `P1`.
+//   arc(N, cp, points=[P0,P1],[long],[cw],[ccw])
+// Usage: 2D or 3D arc, starting at `P0`, passing through `P1` and ending at `P2`.
+//   arc(N, points=[P0,P1,P2])
+// Description:
+//   If called as a function, returns a 2D or 3D path forming an arc.
+//   If called as a module, creates a 2D arc polygon or pie slice shape.
+// Arguments:
+//   N = Number of vertices to form the arc curve from.
+//   r = Radius of the arc.
+//   d = Diameter of the arc.
+//   angle = If a scalar, specifies the end angle in degrees.  If a vector of two scalars, specifies start and end angles.
+//   cp = Centerpoint of arc.
+//   points = Points on the arc.
+//   long = if given with cp and points takes the long arc instead of the default short arc.  Default: false
+//   cw = if given with cp and 2 points takes the arc in the clockwise direction.  Default: false
+//   ccw = if given with cp and 2 points takes the arc in the counter-clockwise direction.  Default: false
+//   width = If given with `thickness`, arc starts and ends on X axis, to make a circle segment.
+//   thickness = If given with `width`, arc starts and ends on X axis, to make a circle segment.
+//   start = Start angle of arc.
+//   wedge = If true, include centerpoint `cp` in output to form pie slice shape.
+// Examples(2D):
+//   arc(N=4, r=30, angle=30, wedge=true);
+//   arc(r=30, angle=30, wedge=true);
+//   arc(d=60, angle=30, wedge=true);
+//   arc(d=60, angle=120);
+//   arc(d=60, angle=120, wedge=true);
+//   arc(r=30, angle=[75,135], wedge=true);
+//   arc(r=30, start=45, angle=75, wedge=true);
+//   arc(width=60, thickness=20);
+//   arc(cp=[-10,5], points=[[20,10],[0,35]], wedge=true);
+//   arc(points=[[30,-5],[20,10],[-10,20]], wedge=true);
+//   arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
+// Example(2D):
+//   path = arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
+//   stroke(closed=true, path);
+// Example(FlatSpin):
+//   path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]);
+//   trace_polyline(path, showpts=true, color="cyan");
+function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false) =
+    // First try for 2D arc specified by width and thickness
+    is_def(width) && is_def(thickness)? (
+                assert(!any_defined([r,cp,points]) && !any([cw,ccw,long]),"Conflicting or invalid parameters to arc")
+                assert(width>0, "Width must be postive")
+                assert(thickness>0, "Thickness must be positive")
+        arc(N,points=[[width/2,0], [0,thickness], [-width/2,0]],wedge=wedge)
+    ) : is_def(angle)? (
+        let(
+            parmok = !any_defined([points,width,thickness]) &&
+                ((is_vector(angle,2) && is_undef(start)) || is_num(angle))
+        )
+        assert(parmok,"Invalid parameters in arc")
+        let(
+            cp = first_defined([cp,[0,0]]),
+            start = is_def(start)? start : is_vector(angle) ? angle[0] : 0,
+            angle = is_vector(angle)? angle[1]-angle[0] : angle,
+            r = get_radius(r=r, d=d)
+                )
+                assert(is_vector(cp,2),"Centerpoint must be a 2d vector")
+                assert(angle!=0, "Arc has zero length")
+                assert(r>0, "Arc radius invalid")
+                let(
+            N = max(3, is_undef(N)? ceil(segs(r)*abs(angle)/360) : N),
+            arcpoints = [for(i=[0:N-1]) let(theta = start + i*angle/(N-1)) r*[cos(theta),sin(theta)]+cp],
+            extra = wedge? [cp] : []
+        )
+        concat(extra,arcpoints)
+    ) :
+          assert(is_path(points,[2,3]),"Point list is invalid")
+        // Arc is 3D, so transform points to 2D and make a recursive call, then remap back to 3D
+         len(points[0])==3? (
+                assert(!(cw || ccw), "(Counter)clockwise isn't meaningful in 3d, so `cw` and `ccw` must be false")
+                assert(is_undef(cp) || is_vector(cp,3),"points are 3d so cp must be 3d")
+        let(
+            thirdpoint = is_def(cp) ? cp : points[2],
+            center2d = is_def(cp) ? project_plane(cp,thirdpoint,points[0],points[1]) : undef,
+            points2d = project_plane(points,thirdpoint,points[0],points[1])
+        )
+        lift_plane(arc(N,cp=center2d,points=points2d,wedge=wedge,long=long),thirdpoint,points[0],points[1])
+    ) : is_def(cp)? (
+        // Arc defined by center plus two points, will have radius defined by center and points[0]
+        // and extent defined by direction of point[1] from the center
+                assert(is_vector(cp,2), "Centerpoint must be a 2d vector")
+                assert(len(points)==2, "When pointlist has length 3 centerpoint is not allowed")
+                assert(points[0]!=points[1], "Arc endpoints are equal")
+                assert(cp!=points[0]&&cp!=points[1], "Centerpoint equals an arc endpoint")
+                assert(count_true([long,cw,ccw])<=1, str("Only one of `long`, `cw` and `ccw` can be true",cw,ccw,long))
+        let(    
+            angle = vector_angle(points[0], cp, points[1]),
+            v1 = points[0]-cp,
+            v2 = points[1]-cp,
+            prelim_dir = sign(det2([v1,v2])),   // z component of cross product
+                        dir = prelim_dir != 0
+                                  ? prelim_dir
+                                  : assert(cw || ccw, "Collinear inputs don't define a unique arc")
+                                    1,
+            r=norm(v1),
+                        final_angle = long || (ccw && dir<0) || (cw && dir>0) ? -dir*(360-angle) : dir*angle
+        )
+        arc(N,cp=cp,r=r,start=atan2(v1.y,v1.x),angle=final_angle,wedge=wedge)
+    ) : (
+        // Final case is arc passing through three points, starting at point[0] and ending at point[3]
+        let(col = collinear(points[0],points[1],points[2]))
+        assert(!col, "Collinear inputs do not define an arc")
+        let(
+            cp = line_intersection(_normal_segment(points[0],points[1]),_normal_segment(points[1],points[2])),
+            // select order to be counterclockwise
+            dir = det2([points[1]-points[0],points[2]-points[1]]) > 0,
+            points = dir? select(points,[0,2]) : select(points,[2,0]),
+            r = norm(points[0]-cp),
+            theta_start = atan2(points[0].y-cp.y, points[0].x-cp.x),
+            theta_end = atan2(points[1].y-cp.y, points[1].x-cp.x),
+            angle = posmod(theta_end-theta_start, 360),
+            arcpts = arc(N,cp=cp,r=r,start=theta_start,angle=angle,wedge=wedge)
+        )
+        dir ? arcpts : reverse(arcpts)
+    );
+
+
+module arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false)
+{
+    path = arc(N=N, r=r, angle=angle, d=d, cp=cp, points=points, width=width, thickness=thickness, start=start, wedge=wedge);
+    polygon(path);
+}
+
+
+function _normal_segment(p1,p2) =
+    let(center = (p1+p2)/2)
+    [center, center + norm(p1-p2)/2 * line_normal(p1,p2)];
+
+
+// Function: turtle()
+// Usage:
+//   turtle(commands, [state], [return_state])
+// Description:
+//   Use a sequence of turtle graphics commands to generate a path.  The parameter `commands` is a list of
+//   turtle commands and optional parameters for each command.  The turtle state has a position, movement direction,
+//   movement distance, and default turn angle.  If you do not give `state` as input then the turtle starts at the
+//   origin, pointed along the positive x axis with a movement distance of 1.  By default, `turtle` returns just
+//   the computed turtle path.  If you set `full_state` to true then it instead returns the full turtle state.
+//   You can invoke `turtle` again with this full state to continue the turtle path where you left off.
+//   
+//   The turtle state is a list with three entries: the path constructed so far, the current step as a 2-vector, and the current default angle.
+//   
+//   For the list below, `dist` is the current movement distance.
+//   
+//   Commands     | Arguments          | What it does
+//   ------------ | ------------------ | -------------------------------
+//   "move"       | [dist]             | Move turtle scale*dist units in the turtle direction.  Default dist=1.  
+//   "xmove"      | [dist]             | Move turtle scale*dist units in the x direction. Default dist=1.  Does not change turtle direction.
+//   "ymove"      | [dist]             | Move turtle scale*dist units in the y direction. Default dist=1.  Does not change turtle direction.
+//   "xymove"     | vector             | Move turtle by the specified vector.  Does not change turtle direction. 
+//   "untilx"     | xtarget            | Move turtle in turtle direction until x==xtarget.  Produces an error if xtarget is not reachable.
+//   "untily"     | ytarget            | Move turtle in turtle direction until y==ytarget.  Produces an error if xtarget is not reachable.
+//   "jump"       | point              | Move the turtle to the specified point
+//   "xjump"      | x                  | Move the turtle's x position to the specified value
+//   "yjump       | y                  | Move the turtle's y position to the specified value
+//   "turn"       | [angle]            | Turn turtle direction by specified angle, or the turtle's default turn angle.  The default angle starts at 90.
+//   "left"       | [angle]            | Same as "turn"
+//   "right"      | [angle]            | Same as "turn", -angle
+//   "angle"      | angle              | Set the default turn angle.
+//   "setdir"     | dir                | Set turtle direction.  The parameter `dir` can be an angle or a vector.
+//   "length"     | length             | Change the turtle move distance to `length`
+//   "scale"      | factor             | Multiply turtle move distance by `factor`
+//   "addlength"  | length             | Add `length` to the turtle move distance
+//   "repeat"     | count, commands    | Repeats a list of commands `count` times.
+//   "arcleft"    | radius, [angle]    | Draw an arc from the current position toward the left at the specified radius and angle.  The turtle turns by `angle`.  A negative angle draws the arc to the right instead of the left, and leaves the turtle facing right.  A negative radius draws the arc to the right but leaves the turtle facing left.  
+//   "arcright"   | radius, [angle]    | Draw an arc from the current position toward the right at the specified radius and angle
+//   "arcleftto"  | radius, angle      | Draw an arc at the given radius turning toward the left until reaching the specified absolute angle.  
+//   "arcrightto" | radius, angle      | Draw an arc at the given radius turning toward the right until reaching the specified absolute angle.  
+//   "arcsteps"   | count              | Specifies the number of segments to use for drawing arcs.  If you set it to zero then the standard `$fn`, `$fa` and `$fs` variables define the number of segments.  
+//
+// Arguments:
+//   commands = List of turtle commands
+//   state = Starting turtle state (from previous call) or starting point.  Default: start at the origin, pointing right.
+//   full_state = If true return the full turtle state for continuing the path in subsequent turtle calls.  Default: false
+//   repeat = Number of times to repeat the command list.  Default: 1
+//
+// Example(2D): Simple rectangle
+//   path = turtle(["xmove",3, "ymove", "xmove",-3, "ymove",-1]);
+//   stroke(path,width=.1);
+// Example(2D): Pentagon
+//   path=turtle(["angle",360/5,"move","turn","move","turn","move","turn","move"]);
+//   stroke(path,width=.1,closed=true);
+// Example(2D): Pentagon using the repeat argument
+//   path=turtle(["move","turn",360/5],repeat=5);
+//   stroke(path,width=.1,closed=true);
+// Example(2D): Pentagon using the repeat turtle command, setting the turn angle
+//   path=turtle(["angle",360/5,"repeat",5,["move","turn"]]);
+//   stroke(path,width=.1,closed=true);
+// Example(2D): Pentagram
+//   path = turtle(["move","left",144], repeat=4);
+//   stroke(path,width=.05,closed=true);
+// Example(2D): Sawtooth path
+//   path = turtle([
+//       "turn", 55,
+//       "untily", 2,
+//       "turn", -55-90,
+//       "untily", 0,
+//       "turn", 55+90,
+//       "untily", 2.5,
+//       "turn", -55-90,
+//       "untily", 0,
+//       "turn", 55+90,
+//       "untily", 3,
+//       "turn", -55-90,
+//       "untily", 0
+//   ]);
+//   stroke(path, width=.1);
+// Example(2D): Simpler way to draw the sawtooth.  The direction of the turtle is preserved when executing "yjump".
+//   path = turtle([
+//       "turn", 55,
+//       "untily", 2,
+//       "yjump", 0,
+//       "untily", 2.5,
+//       "yjump", 0,
+//       "untily", 3,
+//       "yjump", 0,
+//   ]);
+//   stroke(path, width=.1);
+// Example(2DMed): square spiral
+//   path = turtle(["move","left","addlength",1],repeat=50);
+//   stroke(path,width=.2);
+// Example(2DMed): pentagonal spiral
+//   path = turtle(["move","left",360/5,"addlength",1],repeat=50);
+//   stroke(path,width=.2);
+// Example(2DMed): yet another spiral, without using `repeat`
+//   path = turtle(concat(["angle",71],flatten(repeat(["move","left","addlength",1],50))));
+//   stroke(path,width=.2);
+// Example(2DMed): The previous spiral grows linearly and eventually intersects itself.  This one grows geometrically and does not.
+//   path = turtle(["move","left",71,"scale",1.05],repeat=50);
+//   stroke(path,width=.05);
+// Example(2D): Koch Snowflake
+//   function koch_unit(depth) =
+//       depth==0 ? ["move"] :
+//       concat(
+//           koch_unit(depth-1),
+//           ["right"],
+//           koch_unit(depth-1),
+//           ["left","left"],
+//           koch_unit(depth-1),
+//           ["right"],
+//           koch_unit(depth-1)
+//       );
+//   koch=concat(["angle",60,"repeat",3],[concat(koch_unit(3),["left","left"])]);
+//   polygon(turtle(koch));
+function turtle(commands, state=[[[0,0]],[1,0],90,0], full_state=false, repeat=1) =
+    let( state = is_vector(state) ? [[state],[1,0],90,0] : state )
+        repeat == 1?
+            _turtle(commands,state,full_state) :
+            _turtle_repeat(commands, state, full_state, repeat);
+
+function _turtle_repeat(commands, state, full_state, repeat) =
+    repeat==1?
+        _turtle(commands,state,full_state) :
+        _turtle_repeat(commands, _turtle(commands, state, true), full_state, repeat-1);
+
+function _turtle_command_len(commands, index) =
+    let( one_or_two_arg = ["arcleft","arcright", "arcleftto", "arcrightto"] )
+    commands[index] == "repeat"? 3 :   // Repeat command requires 2 args
+    // For these, the first arg is required, second arg is present if it is not a string
+    in_list(commands[index], one_or_two_arg) && len(commands)>index+2 && !is_string(commands[index+2]) ? 3 :  
+    is_string(commands[index+1])? 1 :  // If 2nd item is a string it's must be a new command
+    2;                                 // Otherwise we have command and arg
+
+function _turtle(commands, state, full_state, index=0) =
+    index < len(commands) ?
+    _turtle(commands,
+            _turtle_command(commands[index],commands[index+1],commands[index+2],state,index),
+            full_state,
+            index+_turtle_command_len(commands,index)
+        ) :
+        ( full_state ? state : state[0] );
+
+// Turtle state: state = [path, step_vector, default angle]
+
+function _turtle_command(command, parm, parm2, state, index) =
+    command == "repeat"?
+        assert(is_num(parm),str("\"repeat\" command requires a numeric repeat count at index ",index))
+        assert(is_list(parm2),str("\"repeat\" command requires a command list parameter at index ",index))
+        _turtle_repeat(parm2, state, true, parm) :
+    let(
+        path = 0,
+        step=1,
+        angle=2,
+        arcsteps=3,
+        parm = !is_string(parm) ? parm : undef,
+        parm2 = !is_string(parm2) ? parm2 : undef,
+        needvec = ["jump", "xymove"],
+        neednum = ["untilx","untily","xjump","yjump","angle","length","scale","addlength"],
+        needeither = ["setdir"],
+        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)
+    )
+    assert(chvec,str("\"",command,"\" requires a vector parameter at index ",index))
+    assert(chnum,str("\"",command,"\" requires a numeric parameter at index ",index))
+    assert(vec_or_num,str("\"",command,"\" requires a vector or numeric parameter at index ",index))
+
+    command=="move" ? list_set(state, path, concat(state[path],[default(parm,1)*state[step]+lastpt])) :
+    command=="untilx" ? (
+        let(
+            int = line_intersection([lastpt,lastpt+state[step]], [[parm,0],[parm,1]]),
+            xgood = sign(state[step].x) == sign(int.x-lastpt.x)
+        )
+        assert(xgood,str("\"untilx\" never reaches desired goal at index ",index))
+        list_set(state,path,concat(state[path],[int]))
+    ) :
+    command=="untily" ? (
+        let(
+            int = line_intersection([lastpt,lastpt+state[step]], [[0,parm],[1,parm]]),
+            ygood = is_def(int) && sign(state[step].y) == sign(int.y-lastpt.y)
+        )
+        assert(ygood,str("\"untily\" never reaches desired goal at index ",index))
+        list_set(state,path,concat(state[path],[int]))
+    ) :
+    command=="xmove" ? list_set(state, path, concat(state[path],[default(parm,1)*norm(state[step])*[1,0]+lastpt])):
+    command=="ymove" ? list_set(state, path, concat(state[path],[default(parm,1)*norm(state[step])*[0,1]+lastpt])):
+        command=="xymove" ? list_set(state, path, concat(state[path], [lastpt+parm])):
+    command=="jump" ?  list_set(state, path, concat(state[path],[parm])):
+    command=="xjump" ? list_set(state, path, concat(state[path],[[parm,lastpt.y]])):
+    command=="yjump" ? list_set(state, path, concat(state[path],[[lastpt.x,parm]])):
+    command=="turn" || command=="left" ? list_set(state, step, rot(default(parm,state[angle]),p=state[step],planar=true)) :
+    command=="right" ? list_set(state, step, rot(-default(parm,state[angle]),p=state[step],planar=true)) :
+    command=="angle" ? list_set(state, angle, parm) :
+    command=="setdir" ? (
+        is_vector(parm) ?
+            list_set(state, step, norm(state[step]) * unit(parm)) :
+            list_set(state, step, norm(state[step]) * [cos(parm),sin(parm)])
+    ) :
+    command=="length" ? list_set(state, step, parm*unit(state[step])) :
+    command=="scale" ?  list_set(state, step, parm*state[step]) :
+    command=="addlength" ?  list_set(state, step, state[step]+unit(state[step])*parm) :
+    command=="arcsteps" ? list_set(state, arcsteps, parm) :
+    command=="arcleft" || command=="arcright" ?
+        assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))  
+        let(
+            myangle = default(parm2,state[angle]),
+            lrsign = command=="arcleft" ? 1 : -1,
+            radius = parm*sign(myangle),
+            center = lastpt + lrsign*radius*line_normal([0,0],state[step]),
+            steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps], 
+            arcpath = myangle == 0 || radius == 0 ? [] : arc(
+                steps,
+                points = [
+                    lastpt,
+                    rot(cp=center, p=lastpt, a=sign(parm)*lrsign*myangle/2),
+                    rot(cp=center, p=lastpt, a=sign(parm)*lrsign*myangle)
+                ]
+            )
+        )
+        list_set(
+            state, [path,step], [
+                concat(state[path], slice(arcpath,1,-1)),
+                rot(lrsign * myangle,p=state[step],planar=true)
+            ]
+        ) :
+    command=="arcleftto" || command=="arcrightto" ?
+        assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))
+        assert(is_num(parm2),str("\"",command,"\" command requires a numeric angle value at index ",index))
+        let(
+            radius = parm,
+            lrsign = command=="arcleftto" ? 1 : -1,
+            center = lastpt + lrsign*radius*line_normal([0,0],state[step]),
+            steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps],
+            start_angle = posmod(atan2(state[step].y, state[step].x),360),
+            end_angle = posmod(parm2,360),
+            delta_angle =  -start_angle + (lrsign * end_angle < lrsign*start_angle ? end_angle+lrsign*360 : end_angle),
+            arcpath = delta_angle == 0 || radius==0 ? [] : arc(
+                steps,
+                points = [
+                    lastpt,
+                    rot(cp=center, p=lastpt, a=sign(radius)*delta_angle/2),
+                    rot(cp=center, p=lastpt, a=sign(radius)*delta_angle)
+                ]
+            )
+        )
+        list_set(
+            state, [path,step], [
+                concat(state[path], slice(arcpath,1,-1)),
+                rot(delta_angle,p=state[step],planar=true)
+            ]
+        ) :
+    assert(false,str("Unknown turtle command \"",command,"\" at index",index))
+    [];
+
+
+
+// Section: 2D Primitives
+
+// Function&Module: rect()
+// Usage:
+//   rect(size, [center], [rounding], [chamfer], [anchor], [spin])
+// Description:
+//   When called as a module, creates a 2D rectangle of the given size, with optional rounding or chamfering.
+//   When called as a function, returns a 2D path/list of points for a square/rectangle of the given size.
+// Arguments:
+//   size = The size of the rectangle to create.  If given as a scalar, both X and Y will be the same size.
+//   rounding = The rounding radius for the corners.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
+//   chamfer = The chamfer size for the corners.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].  Default: 0 (no chamfer)
+//   center = If given and true, overrides `anchor` to be `CENTER`.  If given and false, overrides `anchor` to be `FRONT+LEFT`.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+// Example(2D):
+//   rect(40);
+// Example(2D): Centered
+//   rect([40,30], center=true);
+// Example(2D): Anchored
+//   rect([40,30], anchor=FRONT);
+// Example(2D): Spun
+//   rect([40,30], anchor=FRONT, spin=30);
+// Example(2D): Chamferred Rect
+//   rect([40,30], chamfer=5, center=true);
+// Example(2D): Rounded Rect
+//   rect([40,30], rounding=5, center=true);
+// Example(2D): Mixed Chamferring and Rounding
+//   rect([40,30],center=true,rounding=[5,0,10,0],chamfer=[0,8,0,15],$fa=1,$fs=1);
+// Example(2D): Called as Function
+//   path = rect([40,30], chamfer=5, anchor=FRONT, spin=30);
+//   stroke(path, closed=true);
+//   move_copies(path) color("blue") circle(d=2,$fn=8);
+module rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) {
+    size = is_num(size)? [size,size] : point2d(size);
+    anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT);
+    if (rounding==0 && chamfer==0) {
+        attachable(anchor,spin, two_d=true, size=size) {
+            square(size, center=true);
+            children();
+        }
+    } else {
+        pts = rect(size=size, rounding=rounding, chamfer=chamfer, center=true);
+        attachable(anchor,spin, two_d=true, path=pts) {
+            polygon(pts);
+            children();
+        }
+    }
+}
+
+
+function rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) =
+    assert(is_num(size)     || is_vector(size))
+    assert(is_num(chamfer)  || len(chamfer)==4)
+    assert(is_num(rounding) || len(rounding)==4)
+    let(
+        size = is_num(size)? [size,size] : point2d(size),
+        anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT),
+        complex = rounding!=0 || chamfer!=0
+				, xxx = echo(anchor=anchor)echo(size=size)
+    )
+    (rounding==0 && chamfer==0)? let(
+        path = [
+            [ size.x/2, -size.y/2],
+            [-size.x/2, -size.y/2],
+            [-size.x/2,  size.y/2],
+            [ size.x/2,  size.y/2] 
+        ]
+    ) rot(spin, p=move(-vmul(anchor,size/2), p=path)) :
+    let(
+        chamfer = is_list(chamfer)? chamfer : [for (i=[0:3]) chamfer],
+        rounding = is_list(rounding)? rounding : [for (i=[0:3]) rounding],
+        quadorder = [3,2,1,0],
+        quadpos = [[1,1],[-1,1],[-1,-1],[1,-1]],
+        insets = [for (i=[0:3]) chamfer[i]>0? chamfer[i] : rounding[i]>0? rounding[i] : 0],
+        insets_x = max(insets[0]+insets[1],insets[2]+insets[3]),
+        insets_y = max(insets[0]+insets[3],insets[1]+insets[2])
+    )
+    assert(insets_x <= size.x, "Requested roundings and/or chamfers exceed the rect width.")
+    assert(insets_y <= size.y, "Requested roundings and/or chamfers exceed the rect height.")
+    let(
+        path = [
+            for(i = [0:3])
+            let(
+                quad = quadorder[i],
+                inset = insets[quad],
+                cverts = quant(segs(inset),4)/4,
+                cp = vmul(size/2-[inset,inset], quadpos[quad]),
+                step = 90/cverts,
+                angs =
+                    chamfer[quad] > 0?  [0,-90]-90*[i,i] :
+                    rounding[quad] > 0? [for (j=[0:1:cverts]) 360-j*step-i*90] :
+                    [0]
+            )
+            each [for (a = angs) cp + inset*[cos(a),sin(a)]]
+        ]
+    ) complex?
+        reorient(anchor,spin, two_d=true, path=path, p=path) :
+        reorient(anchor,spin, two_d=true, size=size, p=path);
+
+
+// Function&Module: oval()
+// Usage:
+//   oval(r|d, [realign], [circum])
+// Description:
+//   When called as a module, creates a 2D polygon that approximates a circle of the given size.
+//   When called as a function, returns a 2D list of points (path) for a polygon that approximates a circle of the given size.
+// Arguments:
+//   r = Radius of the circle/oval to create.  Can be a scalar, or a list of sizes per axis.
+//   d = Diameter of the circle/oval to create.  Can be a scalar, or a list of sizes per axis.
+//   realign = If true, rotates the polygon that approximates the circle/oval by half of one size.
+//   circum = If true, the polygon that approximates the circle will be upsized slightly to circumscribe the theoretical circle.  If false, it inscribes the theoretical circle.  Default: false
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+// Example(2D): By Radius
+//   oval(r=25);
+// Example(2D): By Diameter
+//   oval(d=50);
+// Example(2D): Anchoring
+//   oval(d=50, anchor=FRONT);
+// Example(2D): Spin
+//   oval(d=50, anchor=FRONT, spin=45);
+// Example(NORENDER): Called as Function
+//   path = oval(d=50, anchor=FRONT, spin=45);
+module oval(r, d, realign=false, circum=false, anchor=CENTER, spin=0) {
+    r = get_radius(r=r, d=d, dflt=1);
+    sides = segs(max(r));
+    sc = circum? (1 / cos(180/sides)) : 1;
+    rx = default(r[0],r) * sc;
+    ry = default(r[1],r) * sc;
+    attachable(anchor,spin, two_d=true, r=[rx,ry]) {
+        if (rx < ry) {
+            xscale(rx/ry) {
+                zrot(realign? 180/sides : 0) {
+                    circle(r=ry, $fn=sides);
+                }
+            }
+        } else {
+            yscale(ry/rx) {
+                zrot(realign? 180/sides : 0) {
+                    circle(r=rx, $fn=sides);
+                }
+            }
+        }
+        children();
+    }
+}
+
+
+function oval(r, d, realign=false, circum=false, anchor=CENTER, spin=0) =
+    let(
+        r = get_radius(r=r, d=d, dflt=1),
+        sides = segs(max(r)),
+        offset = realign? 180/sides : 0,
+        sc = circum? (1 / cos(180/sides)) : 1,
+        rx = default(r[0],r) * sc,
+        ry = default(r[1],r) * sc,
+        pts = [for (i=[0:1:sides-1]) let(a=360-offset-i*360/sides) [rx*cos(a), ry*sin(a)]]
+    ) reorient(anchor,spin, two_d=true, r=[rx,ry], p=pts);
+
+
+
+// Section: 2D N-Gons
+
+// Function&Module: regular_ngon()
+// Usage:
+//   regular_ngon(n, r|d|or|od, [realign]);
+//   regular_ngon(n, ir|id, [realign]);
+//   regular_ngon(n, side, [realign]);
+// Description:
+//   When called as a function, returns a 2D path for a regular N-sided polygon.
+//   When called as a module, creates a 2D regular N-sided polygon.
+// Arguments:
+//   n = The number of sides.
+//   or = Outside radius, at points.
+//   r = Same as or
+//   od = Outside diameter, at points.
+//   d = Same as od
+//   ir = Inside radius, at center of sides.
+//   id = Inside diameter, at center of sides.
+//   side = Length of each side.
+//   rounding = Radius of rounding for the tips of the polygon.  Default: 0 (no rounding)
+//   realign = If false, a tip is aligned with the Y+ axis.  If true, the midpoint of a side is aligned with the Y+ axis.  Default: false
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+// Extra Anchors:
+//   "tip0", "tip1", etc. = Each tip has an anchor, pointing outwards.
+//   "side0", "side1", etc. = The center of each side has an anchor, pointing outwards.
+// Example(2D): by Outer Size
+//   regular_ngon(n=5, or=30);
+//   regular_ngon(n=5, od=60);
+// Example(2D): by Inner Size
+//   regular_ngon(n=5, ir=30);
+//   regular_ngon(n=5, id=60);
+// Example(2D): by Side Length
+//   regular_ngon(n=8, side=20);
+// Example(2D): Realigned
+//   regular_ngon(n=8, side=20, realign=true);
+// Example(2D): Rounded
+//   regular_ngon(n=5, od=100, rounding=20, $fn=20);
+// Example(2D): Called as Function
+//   stroke(closed=true, regular_ngon(n=6, or=30));
+function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) =
+    let(
+        sc = 1/cos(180/n),
+        r = get_radius(r1=ir*sc, r2=or, r=r, d1=id*sc, d2=od, d=d, dflt=side/2/sin(180/n))
+    )
+    assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side.")
+    let(
+        inset = opp_ang_to_hyp(rounding, (180-360/n)/2),
+        path = rounding==0? oval(r=r, realign=realign, $fn=n) : (
+            let(
+                steps = floor(segs(r)/n),
+                step = 360/n/steps,
+                path2 = [
+                    for (i = [0:1:n-1]) let(
+                        a = 360 - i*360/n - (realign? 180/n : 0),
+                        p = polar_to_xy(r-inset, a)
+                    )
+                    each arc(N=steps, cp=p, r=rounding, start=a+180/n, angle=-360/n)
+                ],
+                maxx_idx = max_index(subindex(path2,0)),
+                path3 = polygon_shift(path2,maxx_idx)
+            ) path3
+        ),
+        anchors = !is_string(anchor)? [] : [
+            for (i = [0:1:n-1]) let(
+                a1 = 360 - i*360/n - (realign? 180/n : 0),
+                a2 = a1 - 360/n,
+                p1 = polar_to_xy(r,a1),
+                p2 = polar_to_xy(r,a2),
+                tipp = polar_to_xy(r-inset+rounding,a1),
+                pos = (p1+p2)/2
+            ) each [
+                anchorpt(str("tip",i), tipp, unit(tipp,BACK), 0),
+                anchorpt(str("side",i), pos, unit(pos,BACK), 0),
+            ]
+        ]
+    ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path, anchors=anchors);
+
+
+module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) {
+    sc = 1/cos(180/n);
+    r = get_radius(r1=ir*sc, r2=or, r=r, d1=id*sc, d2=od, d=d, dflt=side/2/sin(180/n));
+    assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side.");
+    path = regular_ngon(n=n, r=r, rounding=rounding, realign=realign);
+    inset = opp_ang_to_hyp(rounding, (180-360/n)/2);
+    anchors = [
+        for (i = [0:1:n-1]) let(
+            a1 = 360 - i*360/n - (realign? 180/n : 0),
+            a2 = a1 - 360/n,
+            p1 = polar_to_xy(r,a1),
+            p2 = polar_to_xy(r,a2),
+            tipp = polar_to_xy(r-inset+rounding,a1),
+            pos = (p1+p2)/2
+        ) each [
+            anchorpt(str("tip",i), tipp, unit(tipp,BACK), 0),
+            anchorpt(str("side",i), pos, unit(pos,BACK), 0),
+        ]
+    ];
+    attachable(anchor,spin, two_d=true, path=path, extent=false, anchors=anchors) {
+        polygon(path);
+        children();
+    }
+}
+
+
+// Function&Module: pentagon()
+// Usage:
+//   pentagon(or|od, [realign]);
+//   pentagon(ir|id, [realign]);
+//   pentagon(side, [realign]);
+// Description:
+//   When called as a function, returns a 2D path for a regular pentagon.
+//   When called as a module, creates a 2D regular pentagon.
+// Arguments:
+//   or = Outside radius, at points.
+//   r = Same as or.
+//   od = Outside diameter, at points.
+//   d = Same as od.
+//   ir = Inside radius, at center of sides.
+//   id = Inside diameter, at center of sides.
+//   side = Length of each side.
+//   rounding = Radius of rounding for the tips of the polygon.  Default: 0 (no rounding)
+//   realign = If false, a tip is aligned with the Y+ axis.  If true, the midpoint of a side is aligned with the Y+ axis.  Default: false
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+// Extra Anchors:
+//   "tip0" ... "tip4" = Each tip has an anchor, pointing outwards.
+//   "side0" ... "side4" = The center of each side has an anchor, pointing outwards.
+// Example(2D): by Outer Size
+//   pentagon(or=30);
+//   pentagon(od=60);
+// Example(2D): by Inner Size
+//   pentagon(ir=30);
+//   pentagon(id=60);
+// Example(2D): by Side Length
+//   pentagon(side=20);
+// Example(2D): Realigned
+//   pentagon(side=20, realign=true);
+// Example(2D): Rounded
+//   pentagon(od=100, rounding=20, $fn=20);
+// Example(2D): Called as Function
+//   stroke(closed=true, pentagon(or=30));
+function pentagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) =
+    regular_ngon(n=5, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin);
+
+
+module pentagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0)
+    regular_ngon(n=5, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin) children();
+
+
+// Function&Module: hexagon()
+// Usage:
+//   hexagon(or, od, ir, id, side);
+// Description:
+//   When called as a function, returns a 2D path for a regular hexagon.
+//   When called as a module, creates a 2D regular hexagon.
+// Arguments:
+//   or = Outside radius, at points.
+//   r = Same as or
+//   od = Outside diameter, at points.
+//   d = Same as od
+//   ir = Inside radius, at center of sides.
+//   id = Inside diameter, at center of sides.
+//   side = Length of each side.
+//   rounding = Radius of rounding for the tips of the polygon.  Default: 0 (no rounding)
+//   realign = If false, a tip is aligned with the Y+ axis.  If true, the midpoint of a side is aligned with the Y+ axis.  Default: false
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+// Extra Anchors:
+//   "tip0" ... "tip5" = Each tip has an anchor, pointing outwards.
+//   "side0" ... "side5" = The center of each side has an anchor, pointing outwards.
+// Example(2D): by Outer Size
+//   hexagon(or=30);
+//   hexagon(od=60);
+// Example(2D): by Inner Size
+//   hexagon(ir=30);
+//   hexagon(id=60);
+// Example(2D): by Side Length
+//   hexagon(side=20);
+// Example(2D): Realigned
+//   hexagon(side=20, realign=true);
+// Example(2D): Rounded
+//   hexagon(od=100, rounding=20, $fn=20);
+// Example(2D): Called as Function
+//   stroke(closed=true, hexagon(or=30));
+function hexagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) =
+    regular_ngon(n=6, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin);
+
+
+module hexagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0)
+    regular_ngon(n=6, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin) children();
+
+
+// Function&Module: octagon()
+// Usage:
+//   octagon(or, od, ir, id, side);
+// Description:
+//   When called as a function, returns a 2D path for a regular octagon.
+//   When called as a module, creates a 2D regular octagon.
+// Arguments:
+//   or = Outside radius, at points.
+//   r = Same as or
+//   od = Outside diameter, at points.
+//   d = Same as od
+//   ir = Inside radius, at center of sides.
+//   id = Inside diameter, at center of sides.
+//   side = Length of each side.
+//   rounding = Radius of rounding for the tips of the polygon.  Default: 0 (no rounding)
+//   realign = If false, a tip is aligned with the Y+ axis.  If true, the midpoint of a side is aligned with the Y+ axis.  Default: false
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+// Extra Anchors:
+//   "tip0" ... "tip7" = Each tip has an anchor, pointing outwards.
+//   "side0" ... "side7" = The center of each side has an anchor, pointing outwards.
+// Example(2D): by Outer Size
+//   octagon(or=30);
+//   octagon(od=60);
+// Example(2D): by Inner Size
+//   octagon(ir=30);
+//   octagon(id=60);
+// Example(2D): by Side Length
+//   octagon(side=20);
+// Example(2D): Realigned
+//   octagon(side=20, realign=true);
+// Example(2D): Rounded
+//   octagon(od=100, rounding=20, $fn=20);
+// Example(2D): Called as Function
+//   stroke(closed=true, octagon(or=30));
+function octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) =
+    regular_ngon(n=8, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin);
+
+
+module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0)
+    regular_ngon(n=8, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, anchor=anchor, spin=spin) children();
+
+
+
+// Section: Other 2D Shapes
+
+
+// Function&Module: trapezoid()
+// Usage:
+//   trapezoid(h, w1, w2);
+// Description:
+//   When called as a function, returns a 2D path for a trapezoid with parallel front and back sides.
+//   When called as a module, creates a 2D trapezoid with parallel front and back sides.
+// Arguments:
+//   h = The Y axis height of the trapezoid.
+//   w1 = The X axis width of the front end of the trapezoid.
+//   w2 = The X axis width of the back end of the trapezoid.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+// Examples(2D):
+//   trapezoid(h=30, w1=40, w2=20);
+//   trapezoid(h=25, w1=20, w2=35);
+//   trapezoid(h=20, w1=40, w2=0);
+// Example(2D): Called as Function
+//   stroke(closed=true, trapezoid(h=30, w1=40, w2=20));
+function trapezoid(h, w1, w2, anchor=CENTER, spin=0) =
+    let(
+        path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2,h/2], [w2/2,h/2]]
+    ) reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, p=path);
+
+
+
+module trapezoid(h, w1, w2, anchor=CENTER, spin=0) {
+    path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2,h/2], [w2/2,h/2]];
+    attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2) {
+        polygon(path);
+        children();
+    }
+}
+
+
+// Function&Module: teardrop2d()
+//
+// Description:
+//   Makes a 2D teardrop shape. Useful for extruding into 3D printable holes.
+//
+// Usage:
+//   teardrop2d(r|d, [ang], [cap_h]);
+//
+// Arguments:
+//   r = radius of circular part of teardrop.  (Default: 1)
+//   d = diameter of spherical portion of bottom. (Use instead of r)
+//   ang = angle of hat walls from the Y axis.  (Default: 45 degrees)
+//   cap_h = if given, height above center where the shape will be truncated.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+//
+// Example(2D): Typical Shape
+//   teardrop2d(r=30, ang=30);
+// Example(2D): Crop Cap
+//   teardrop2d(r=30, ang=30, cap_h=40);
+// Example(2D): Close Crop
+//   teardrop2d(r=30, ang=30, cap_h=20);
+module teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0)
+{
+    path = teardrop2d(r=r, d=d, ang=ang, cap_h=cap_h);
+    attachable(anchor,spin, two_d=true, path=path) {
+        polygon(path);
+        children();
+    }
+}
+
+
+function teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0) =
+    let(
+        r = get_radius(r=r, d=d, dflt=1),
+        cord = 2 * r * cos(ang),
+        cord_h = r * sin(ang),
+        tip_y = (cord/2)/tan(ang),
+        cap_h = min((!is_undef(cap_h)? cap_h : tip_y+cord_h), tip_y+cord_h),
+        cap_w = cord * (1 - (cap_h - cord_h)/tip_y),
+        ang = min(ang,asin(cap_h/r)),
+        sa = 180 - ang,
+        ea = 360 + ang,
+        steps = segs(r)*(ea-sa)/360,
+        step = (ea-sa)/steps,
+        path = deduplicate(
+            [
+                [ cap_w/2,cap_h],
+                for (i=[0:1:steps]) let(a=ea-i*step) r*[cos(a),sin(a)],
+                [-cap_w/2,cap_h]
+            ], closed=true
+        ),
+        maxx_idx = max_index(subindex(path,0)),
+        path2 = polygon_shift(path,maxx_idx)
+    ) reorient(anchor,spin, two_d=true, path=path2, p=path2);
+
+
+
+// Function&Module: glued_circles()
+// Usage:
+//   glued_circles(r|d, spread, tangent);
+// Description:
+//   When called as a function, returns a 2D path forming a shape of two circles joined by curved waist.
+//   When called as a module, creates a 2D shape of two circles joined by curved waist.
+// Arguments:
+//   r = The radius of the end circles.
+//   d = The diameter of the end circles.
+//   spread = The distance between the centers of the end circles.
+//   tangent = The angle in degrees of the tangent point for the joining arcs, measured away from the Y axis.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+// Examples(2D):
+//   glued_circles(r=15, spread=40, tangent=45);
+//   glued_circles(d=30, spread=30, tangent=30);
+//   glued_circles(d=30, spread=30, tangent=15);
+//   glued_circles(d=30, spread=30, tangent=-30);
+// Example(2D): Called as Function
+//   stroke(closed=true, glued_circles(r=15, spread=40, tangent=45));
+function glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) =
+    let(
+        r = get_radius(r=r, d=d, dflt=10),
+        r2 = (spread/2 / sin(tangent)) - r,
+        cp1 = [spread/2, 0],
+        cp2 = [0, (r+r2)*cos(tangent)],
+        sa1 = 90-tangent,
+        ea1 = 270+tangent,
+        lobearc = ea1-sa1,
+        lobesegs = floor(segs(r)*lobearc/360),
+        lobestep = lobearc / lobesegs,
+        sa2 = 270-tangent,
+        ea2 = 270+tangent,
+        subarc = ea2-sa2,
+        arcsegs = ceil(segs(r2)*abs(subarc)/360),
+        arcstep = subarc / arcsegs,
+        path = concat(
+            [for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep)     r  * [cos(a),sin(a)] - cp1],
+            tangent==0? [] : [for (i=[0:1:arcsegs])  let(a=ea2-i*arcstep+180)  r2 * [cos(a),sin(a)] - cp2],
+            [for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep+180) r  * [cos(a),sin(a)] + cp1],
+            tangent==0? [] : [for (i=[0:1:arcsegs])  let(a=ea2-i*arcstep)      r2 * [cos(a),sin(a)] + cp2]
+        ),
+        maxx_idx = max_index(subindex(path,0)),
+        path2 = reverse_polygon(polygon_shift(path,maxx_idx))
+    ) reorient(anchor,spin, two_d=true, path=path2, extent=true, p=path2);
+
+
+module glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) {
+    path = glued_circles(r=r, d=d, spread=spread, tangent=tangent);
+    attachable(anchor,spin, two_d=true, path=path, extent=true) {
+        polygon(path);
+        children();
+    }
+}
+
+
+// Function&Module: star()
+// Usage:
+//   star(n, r|d|or|od, ir|id|step, [realign]);
+// Description:
+//   When called as a function, returns the path needed to create a star polygon with N points.
+//   When called as a module, creates a star polygon with N points.
+// Arguments:
+//   n = The number of stellate tips on the star.
+//   r = The radius to the tips of the star.
+//   or = Same as r
+//   d = The diameter to the tips of the star.
+//   od = Same as d
+//   ir = The radius to the inner corners of the star.
+//   id = The diameter to the inner corners of the star.
+//   step = Calculates the radius of the inner star corners by virtually drawing a straight line `step` tips around the star.  2 <= step < n/2
+//   realign = If false, a tip is aligned with the Y+ axis.  If true, an inner corner is aligned with the Y+ axis.  Default: false
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+// Extra Anchors:
+//   "tip0" ... "tip4" = Each tip has an anchor, pointing outwards.
+//   "corner0" ... "corner4" = The inside corner between each tip has an anchor, pointing outwards.
+//   "midpt0" ... "midpt4" = The center-point between each pair or tips has an anchor, pointing outwards.
+// Examples(2D):
+//   star(n=5, r=50, ir=25);
+//   star(n=5, r=50, step=2);
+//   star(n=7, r=50, step=2);
+//   star(n=7, r=50, step=3);
+// Example(2D): Realigned
+//   star(n=7, r=50, step=3, realign=true);
+// Example(2D): Called as Function
+//   stroke(closed=true, star(n=5, r=50, ir=25));
+function star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin=0) =
+    let(
+        r = get_radius(r1=or, d1=od, r=r, d=d),
+        count = num_defined([ir,id,step]),
+        stepOK = is_undef(step) || (step>1 && step<n/2)
+    )
+    assert(is_def(n), "Must specify number of points, n")
+    assert(count==1, "Must specify exactly one of ir, id, step")
+    assert(stepOK, str("Parameter 'step' must be between 2 and ",floor(n/2)," for ",n," point star"))
+    let(
+        stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n),
+        ir = get_radius(r=ir, d=id, dflt=stepr),
+        offset = realign? 180/n : 0,
+        path = [for(i=[2*n:-1:1]) let(theta=180*i/n+offset, radius=(i%2)?ir:r) radius*[cos(theta), sin(theta)]],
+        anchors = !is_string(anchor)? [] : [
+            for (i = [0:1:n-1]) let(
+                a1 = 360 - i*360/n - (realign? 180/n : 0),
+                a2 = a1 - 180/n,
+                a3 = a1 - 360/n,
+                p1 = polar_to_xy(r,a1),
+                p2 = polar_to_xy(ir,a2),
+                p3 = polar_to_xy(r,a3),
+                pos = (p1+p3)/2
+            ) each [
+                anchorpt(str("tip",i), p1, unit(p1,BACK), 0),
+                anchorpt(str("corner",i), p2, unit(p2,BACK), 0),
+                anchorpt(str("midpt",i), pos, unit(pos,BACK), 0),
+            ]
+        ]
+    ) reorient(anchor,spin, two_d=true, path=path, p=path, anchors=anchors);
+
+
+module star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin=0) {
+    r = get_radius(r1=or, d1=od, r=r, d=d, dflt=undef);
+    stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n);
+    ir = get_radius(r=ir, d=id, dflt=stepr);
+    path = star(n=n, r=r, ir=ir, realign=realign);
+    anchors = [
+        for (i = [0:1:n-1]) let(
+            a1 = 360 - i*360/n - (realign? 180/n : 0),
+            a2 = a1 - 180/n,
+            a3 = a1 - 360/n,
+            p1 = polar_to_xy(r,a1),
+            p2 = polar_to_xy(ir,a2),
+            p3 = polar_to_xy(r,a3),
+            pos = (p1+p3)/2
+        ) each [
+            anchorpt(str("tip",i), p1, unit(p1,BACK), 0),
+            anchorpt(str("corner",i), p2, unit(p2,BACK), 0),
+            anchorpt(str("midpt",i), pos, unit(pos,BACK), 0),
+        ]
+    ];
+    attachable(anchor,spin, two_d=true, path=path, anchors=anchors) {
+        polygon(path);
+        children();
+    }
+}
+
+
+function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) =
+    pow(pow(abs(cos(m1*theta/4)/a),n2)+pow(abs(sin(m2*theta/4)/b),n3),-1/n1);
+
+// Function&Module: supershape()
+// Usage:
+//   supershape(step,[m1],[m2],[n1],[n2],[n3],[a],[b],[r|d]);
+// Description:
+//   When called as a function, returns a 2D path for the outline of the [Superformula](https://en.wikipedia.org/wiki/Superformula) shape.
+//   When called as a module, creates a 2D [Superformula](https://en.wikipedia.org/wiki/Superformula) shape.
+// Arguments:
+//   step = The angle step size for sampling the superformula shape.  Smaller steps are slower but more accurate.
+//   m1 = The m1 argument for the superformula. Default: 4.
+//   m2 = The m2 argument for the superformula. Default: m1.
+//   n1 = The n1 argument for the superformula. Default: 1.
+//   n2 = The n2 argument for the superformula. Default: n1.
+//   n3 = The n3 argument for the superformula. Default: n2.
+//   a = The a argument for the superformula.  Default: 1.
+//   b = The b argument for the superformula.  Default: a.
+//   r = Radius of the shape.  Scale shape to fit in a circle of radius r.
+//   d = Diameter of the shape.  Scale shape to fit in a circle of diameter d.
+//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
+//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
+// Example(2D):
+//   supershape(step=0.5,m1=16,m2=16,n1=0.5,n2=0.5,n3=16,r=50);
+// Example(2D): Called as Function
+//   stroke(closed=true, supershape(step=0.5,m1=16,m2=16,n1=0.5,n2=0.5,n3=16,d=100));
+// Examples(2D,Med):
+//   for(n=[2:5]) right(2.5*(n-2)) supershape(m1=4,m2=4,n1=n,a=1,b=2);  // Superellipses
+//   m=[2,3,5,7]; for(i=[0:3]) right(2.5*i) supershape(.5,m1=m[i],n1=1);
+//   m=[6,8,10,12]; for(i=[0:3]) right(2.7*i) supershape(.5,m1=m[i],n1=1,b=1.5);  // m should be even
+//   m=[1,2,3,5]; for(i=[0:3]) fwd(1.5*i) supershape(m1=m[i],n1=0.4);
+//   supershape(m1=5, n1=4, n2=1); right(2.5) supershape(m1=5, n1=40, n2=10);
+//   m=[2,3,5,7]; for(i=[0:3]) right(2.5*i) supershape(m1=m[i], n1=60, n2=55, n3=30);
+//   n=[0.5,0.2,0.1,0.02]; for(i=[0:3]) right(2.5*i) supershape(m1=5,n1=n[i], n2=1.7);
+//   supershape(m1=2, n1=1, n2=4, n3=8);
+//   supershape(m1=7, n1=2, n2=8, n3=4);
+//   supershape(m1=7, n1=3, n2=4, n3=17);
+//   supershape(m1=4, n1=1/2, n2=1/2, n3=4);
+//   supershape(m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9);
+//   for(i=[1:4]) right(3*i) supershape(m1=i, m2=3*i, n1=2);
+//   m=[4,6,10]; for(i=[0:2]) right(i*5) supershape(m1=m[i], n1=12, n2=8, n3=5, a=2.7);
+//   for(i=[-1.5:3:1.5]) right(i*1.5) supershape(m1=2,m2=10,n1=i,n2=1);
+//   for(i=[1:3],j=[-1,1]) translate([3.5*i,1.5*j])supershape(m1=4,m2=6,n1=i*j,n2=1);
+//   for(i=[1:3]) right(2.5*i)supershape(step=.5,m1=88, m2=64, n1=-i*i,n2=1,r=1);
+// Examples:
+//   linear_extrude(height=0.3, scale=0) supershape(step=1, m1=6, n1=0.4, n2=0, n3=6);
+//   linear_extrude(height=5, scale=0) supershape(step=1, b=3, m1=6, n1=3.8, n2=16, n3=10);
+function supershape(step=0.5,m1=4,m2=undef,n1=1,n2=undef,n3=undef,a=1,b=undef,r=undef,d=undef,anchor=CENTER, spin=0) =
+    let(
+        r = get_radius(r=r, d=d, dflt=undef),
+        m2 = is_def(m2) ? m2 : m1,
+        n2 = is_def(n2) ? n2 : n1,
+        n3 = is_def(n3) ? n3 : n2,
+        b = is_def(b) ? b : a,
+        steps = ceil(360/step),
+        step = 360/steps,
+        angs = [for (i = [0:steps]) step*i],
+        rads = [for (theta = angs) _superformula(theta=theta,m1=m1,m2=m2,n1=n1,n2=n2,n3=n3,a=a,b=b)],
+        scale = is_def(r) ? r/max(rads) : 1,
+        path = [for (i = [steps:-1:1]) let(a=angs[i]) scale*rads[i]*[cos(a), sin(a)]]
+    ) reorient(anchor,spin, two_d=true, path=path, p=path);
+
+module supershape(step=0.5,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=undef, d=undef, anchor=CENTER, spin=0) {
+    path = supershape(step=step,m1=m1,m2=m2,n1=n1,n2=n2,n3=n3,a=a,b=b,r=r,d=d);
+    attachable(anchor,spin, two_d=true, path=path) {
+        polygon(path);
+        children();
+    }
+}
+
+
+// Section: 2D Masking Shapes
+
+// Function&Module: mask2d_roundover()
+// Usage:
+//   mask2d_roundover(r|d, [inset], [excess]);
+// Description:
+//   Creates a 2D roundover/bead mask shape that is useful for extruding into a 3D mask for a 90º edge.
+//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
+//   If called as a function, this just returns a 2D path of the outline of the mask shape.
+// Arguments:
+//   r = Radius of the roundover.
+//   d = Diameter of the roundover.
+//   inset = Optional bead inset size.  Default: 0
+//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
+// Example(2D): 2D Roundover Mask
+//   mask2d_roundover(r=10);
+// Example(2D): 2D Bead Mask
+//   mask2d_roundover(r=10,inset=2);
+// Example: Masking by Edge Attachment
+//   diff("mask")
+//   cube([50,60,70],center=true)
+//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
+//           mask2d_roundover(r=10, inset=2);
+module mask2d_roundover(r, d, excess, inset=0, anchor=CENTER,spin=0) {
+    path = mask2d_roundover(r=r,d=d,excess=excess,inset=inset);
+    attachable(anchor,spin, two_d=true, path=path) {
+        polygon(path);
+        children();
+    }
+}
+
+function mask2d_roundover(r, d, excess, inset=0, anchor=CENTER,spin=0) =
+    assert(is_num(r)||is_num(d))
+    assert(is_undef(excess)||is_num(excess))
+    assert(is_num(inset)||(is_vector(inset)&&len(inset)==2))
+    let(
+        inset = is_list(inset)? inset : [inset,inset],
+        excess = default(excess,$overlap),
+        r = get_radius(r=r,d=d,dflt=1),
+        steps = quantup(segs(r),4)/4,
+        step = 90/steps,
+        path = [
+            [r+inset.x,-excess],
+            [-excess,-excess],
+            [-excess, r+inset.y],
+            for (i=[0:1:steps]) [r,r] + inset + polar_to_xy(r,180+i*step)
+        ]
+    ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
+
+
+// Function&Module: mask2d_cove()
+// Usage:
+//   mask2d_cove(r|d, [inset], [excess]);
+// Description:
+//   Creates a 2D cove mask shape that is useful for extruding into a 3D mask for a 90º edge.
+//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
+//   If called as a function, this just returns a 2D path of the outline of the mask shape.
+// Arguments:
+//   r = Radius of the cove.
+//   d = Diameter of the cove.
+//   inset = Optional amount to inset code from corner.  Default: 0
+//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
+// Example(2D): 2D Cove Mask
+//   mask2d_cove(r=10);
+// Example(2D): 2D Inset Cove Mask
+//   mask2d_cove(r=10,inset=3);
+// Example: Masking by Edge Attachment
+//   diff("mask")
+//   cube([50,60,70],center=true)
+//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
+//           mask2d_cove(r=10, inset=2);
+module mask2d_cove(r, d, inset=0, excess, anchor=CENTER,spin=0) {
+    path = mask2d_cove(r=r,d=d,excess=excess,inset=inset);
+    attachable(anchor,spin, two_d=true, path=path) {
+        polygon(path);
+        children();
+    }
+}
+
+function mask2d_cove(r, d, inset=0, excess, anchor=CENTER,spin=0) =
+    assert(is_num(r)||is_num(d))
+    assert(is_undef(excess)||is_num(excess))
+    assert(is_num(inset)||(is_vector(inset)&&len(inset)==2))
+    let(
+        inset = is_list(inset)? inset : [inset,inset],
+        excess = default(excess,$overlap),
+        r = get_radius(r=r,d=d,dflt=1),
+        steps = quantup(segs(r),4)/4,
+        step = 90/steps,
+        path = [
+            [r+inset.x,-excess],
+            [-excess,-excess],
+            [-excess, r+inset.y],
+            for (i=[0:1:steps]) inset + polar_to_xy(r,90-i*step)
+        ]
+    ) reorient(anchor,spin, two_d=true, path=path, p=path);
+
+
+// Function&Module: mask2d_chamfer()
+// Usage:
+//   mask2d_chamfer(x|y|edge, [angle], [inset], [excess]);
+// Description:
+//   Creates a 2D chamfer mask shape that is useful for extruding into a 3D mask for a 90º edge.
+//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
+//   If called as a function, this just returns a 2D path of the outline of the mask shape.
+// Arguments:
+//   x = The width of the chamfer.
+//   y = The height of the chamfer.
+//   edge = The length of the edge of the chamfer.
+//   angle = The angle of the chamfer edge, away from vertical.  Default: 45.
+//   inset = Optional amount to inset code from corner.  Default: 0
+//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
+// Example(2D): 2D Chamfer Mask
+//   mask2d_chamfer(x=10);
+// Example(2D): 2D Chamfer Mask by Width.
+//   mask2d_chamfer(x=10, angle=30);
+// Example(2D): 2D Chamfer Mask by Height.
+//   mask2d_chamfer(y=10, angle=30);
+// Example(2D): 2D Inset Chamfer Mask
+//   mask2d_chamfer(x=10, inset=2);
+// Example: Masking by Edge Attachment
+//   diff("mask")
+//   cube([50,60,70],center=true)
+//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
+//           mask2d_chamfer(x=10, inset=2);
+module mask2d_chamfer(x, y, edge, angle=45, excess, inset=0, anchor=CENTER,spin=0) {
+    path = mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset);
+    attachable(anchor,spin, two_d=true, path=path, extent=true) {
+        polygon(path);
+        children();
+    }
+}
+
+function mask2d_chamfer(x, y, edge, angle=45, excess, inset=0, anchor=CENTER,spin=0) =
+    assert(num_defined([x,y,edge])==1)
+    assert(is_num(first_defined([x,y,edge])))
+    assert(is_num(angle))
+    assert(is_undef(excess)||is_num(excess))
+    assert(is_num(inset)||(is_vector(inset)&&len(inset)==2))
+    let(
+        inset = is_list(inset)? inset : [inset,inset],
+        excess = default(excess,$overlap),
+        x = !is_undef(x)? x :
+            !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) :
+            hyp_ang_to_opp(hyp=edge,ang=angle),
+        y = opp_ang_to_adj(opp=x,ang=angle),
+        path = [
+            [x+inset.x, -excess],
+            [-excess, -excess],
+            [-excess, y+inset.y],
+            [inset.x, y+inset.y],
+            [x+inset.x, inset.y]
+        ]
+    ) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path);
+
+
+// Function&Module: mask2d_rabbet()
+// Usage:
+//   mask2d_rabbet(size, [excess]);
+// Description:
+//   Creates a 2D rabbet mask shape that is useful for extruding into a 3D mask for a 90º edge.
+//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
+//   If called as a function, this just returns a 2D path of the outline of the mask shape.
+// Arguments:
+//   size = The size of the rabbet, either as a scalar or an [X,Y] list.
+//   inset = Optional amount to inset code from corner.  Default: 0
+//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
+// Example(2D): 2D Rabbet Mask
+//   mask2d_rabbet(size=10);
+// Example(2D): 2D Asymmetrical Rabbet Mask
+//   mask2d_rabbet(size=[5,10]);
+// Example: Masking by Edge Attachment
+//   diff("mask")
+//   cube([50,60,70],center=true)
+//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
+//           mask2d_rabbet(size=10);
+module mask2d_rabbet(size, excess, anchor=CENTER,spin=0) {
+    path = mask2d_rabbet(size=size, excess=excess);
+    attachable(anchor,spin, two_d=true, path=path, extent=false) {
+        polygon(path);
+        children();
+    }
+}
+
+function mask2d_rabbet(size, excess, anchor=CENTER,spin=0) =
+    assert(is_num(size)||(is_vector(size)&&len(size)==2))
+    assert(is_undef(excess)||is_num(excess))
+    let(
+        excess = default(excess,$overlap),
+        size = is_list(size)? size : [size,size],
+        path = [
+            [size.x, -excess],
+            [-excess, -excess],
+            [-excess, size.y],
+            size
+        ]
+    ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
+
+
+// Function&Module: mask2d_dovetail()
+// Usage:
+//   mask2d_dovetail(x|y|edge, [angle], [inset], [shelf], [excess]);
+// Description:
+//   Creates a 2D dovetail mask shape that is useful for extruding into a 3D mask for a 90º edge.
+//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
+//   If called as a function, this just returns a 2D path of the outline of the mask shape.
+// Arguments:
+//   x = The width of the dovetail.
+//   y = The height of the dovetail.
+//   edge = The length of the edge of the dovetail.
+//   angle = The angle of the chamfer edge, away from vertical.  Default: 30.
+//   inset = Optional amount to inset code from corner.  Default: 0
+//   shelf = The extra height to add to the inside corner of the dovetail.  Default: 0
+//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
+// Example(2D): 2D Dovetail Mask
+//   mask2d_dovetail(x=10);
+// Example(2D): 2D Dovetail Mask by Width.
+//   mask2d_dovetail(x=10, angle=30);
+// Example(2D): 2D Dovetail Mask by Height.
+//   mask2d_dovetail(y=10, angle=30);
+// Example(2D): 2D Inset Dovetail Mask
+//   mask2d_dovetail(x=10, inset=2);
+// Example: Masking by Edge Attachment
+//   diff("mask")
+//   cube([50,60,70],center=true)
+//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
+//           mask2d_dovetail(x=10, inset=2);
+module mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess, anchor=CENTER, spin=0) {
+    path = mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess);
+    attachable(anchor,spin, two_d=true, path=path) {
+        polygon(path);
+        children();
+    }
+}
+
+function mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess, anchor=CENTER, spin=0) =
+    assert(num_defined([x,y,edge])==1)
+    assert(is_num(first_defined([x,y,edge])))
+    assert(is_num(angle))
+    assert(is_undef(excess)||is_num(excess))
+    assert(is_num(inset)||(is_vector(inset)&&len(inset)==2))
+    let(
+        inset = is_list(inset)? inset : [inset,inset],
+        excess = default(excess,$overlap),
+        x = !is_undef(x)? x :
+            !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) :
+            hyp_ang_to_opp(hyp=edge,ang=angle),
+        y = opp_ang_to_adj(opp=x,ang=angle),
+        path = [
+            [inset.x,0],
+            [-excess, 0],
+            [-excess, y+inset.y+shelf],
+            inset+[x,y+shelf],
+            inset+[x,y],
+            inset
+        ]
+    ) reorient(anchor,spin, two_d=true, path=path, p=path);
+
+
+// Function&Module: mask2d_teardrop()
+// Usage:
+//   mask2d_teardrop(r|d, [angle], [excess]);
+// Description:
+//   Creates a 2D teardrop mask shape that is useful for extruding into a 3D mask for a 90º edge.
+//   This 2D mask is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant.
+//   If called as a function, this just returns a 2D path of the outline of the mask shape.
+//   This is particularly useful to make partially rounded bottoms, that don't need support to print.
+// Arguments:
+//   r = Radius of the rounding.
+//   d = Diameter of the rounding.
+//   angle = The maximum angle from vertical.
+//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
+// Example(2D): 2D Teardrop Mask
+//   mask2d_teardrop(r=10);
+// Example(2D): Using a Custom Angle
+//   mask2d_teardrop(r=10,angle=30);
+// Example: Masking by Edge Attachment
+//   diff("mask")
+//   cube([50,60,70],center=true)
+//       edge_profile(BOT)
+//           mask2d_teardrop(r=10, angle=40);
+function mask2d_teardrop(r,d,angle=45,excess=0.1,anchor=CENTER,spin=0) =  
+    assert(is_num(angle))
+    assert(angle>0 && angle<90)
+    assert(is_num(excess))
+    let(
+        r = get_radius(r=r, d=d, dflt=1),
+        n = ceil(segs(r) * angle/360),
+        cp = [r,r],
+        tp = cp + polar_to_xy(r,180+angle),
+        bp = [tp.x+adj_ang_to_opp(tp.y,angle), 0],
+        step = angle/n,
+        path = [
+            bp, bp-[0,excess], [-excess,-excess], [-excess,r],
+            for (i=[0:1:n]) cp+polar_to_xy(r,180+i*step)
+        ]
+    ) reorient(anchor,spin, two_d=true, path=path, p=path);
+
+module mask2d_teardrop(r,d,angle=45,excess=0.1,anchor=CENTER,spin=0) {
+    path = mask2d_teardrop(r=r, d=d, angle=angle, excess=excess);
+    attachable(anchor,spin, two_d=true, path=path) {
+        polygon(path);
+        children();
+    }
+}
+
+// Function&Module: mask2d_ogee()
+// Usage:
+//   mask2d_ogee(pattern, [excess]);
+//
+// Description:
+//   Creates a 2D Ogee mask shape that is useful for extruding into a 3D mask for a 90º edge.
+//   This 2D mask is designed to be `difference()`d  away from the edge of a shape that is in the first (X+Y+) quadrant.
+//   Since there are a number of shapes that fall under the name ogee, the shape of this mask is given as a pattern.
+//   Patterns are given as TYPE, VALUE pairs.  ie: `["fillet",10, "xstep",2, "step",[5,5], ...]`.  See Patterns below.
+//   If called as a function, this just returns a 2D path of the outline of the mask shape.
+//   
+//   ### Patterns
+//   
+//   Type     | Argument  | Description
+//   -------- | --------- | ----------------
+//   "step"   | [x,y]     | Makes a line to a point `x` right and `y` down.
+//   "xstep"  | dist      | Makes a `dist` length line towards X+.
+//   "ystep"  | dist      | Makes a `dist` length line towards Y-.
+//   "round"  | radius    | Makes an arc that will mask a roundover.
+//   "fillet" | radius    | Makes an arc that will mask a fillet.
+//
+// Arguments:
+//   pattern = A list of pattern pieces to describe the Ogee.
+//   excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape.
+//
+// Example(2D): 2D Ogee Mask
+//   mask2d_ogee([
+//       "xstep",1,  "ystep",1,  // Starting shoulder.
+//       "fillet",5, "round",5,  // S-curve.
+//       "ystep",1,  "xstep",1   // Ending shoulder.
+//   ]);
+// Example: Masking by Edge Attachment
+//   diff("mask")
+//   cube([50,60,70],center=true)
+//       edge_profile(TOP)
+//           mask2d_ogee([
+//               "xstep",1,  "ystep",1,  // Starting shoulder.
+//               "fillet",5, "round",5,  // S-curve.
+//               "ystep",1,  "xstep",1   // Ending shoulder.
+//           ]);
+module mask2d_ogee(pattern, excess, anchor=CENTER,spin=0) {
+    path = mask2d_ogee(pattern, excess=excess);
+    attachable(anchor,spin, two_d=true, path=path) {
+        polygon(path);
+        children();
+    }
+}
+
+function mask2d_ogee(pattern, excess, anchor=CENTER, spin=0) =
+    assert(is_list(pattern))
+    assert(len(pattern)>0)
+    assert(len(pattern)%2==0,"pattern must be a list of TYPE, VAL pairs.")
+    assert(all([for (i = idx(pattern,step=2)) in_list(pattern[i],["step","xstep","ystep","round","fillet"])]))
+    let(
+        excess = default(excess,$overlap),
+        x = concat([0], cumsum([
+            for (i=idx(pattern,step=2)) let(
+                type = pattern[i],
+                val = pattern[i+1]
+            ) (
+                type=="step"?   val.x :
+                type=="xstep"?  val :
+                type=="round"?  val :
+                type=="fillet"? val :
+                0
+            )
+        ])),
+        y = concat([0], cumsum([
+            for (i=idx(pattern,step=2)) let(
+                type = pattern[i],
+                val = pattern[i+1]
+            ) (
+                type=="step"?   val.y :
+                type=="ystep"?  val :
+                type=="round"?  val :
+                type=="fillet"? val :
+                0
+            )
+        ])),
+        tot_x = select(x,-1),
+        tot_y = select(y,-1),
+        data = [
+            for (i=idx(pattern,step=2)) let(
+                type = pattern[i],
+                val = pattern[i+1],
+                pt = [x[i/2], tot_y-y[i/2]] + (
+                    type=="step"?   [val.x,-val.y] :
+                    type=="xstep"?  [val,0] :
+                    type=="ystep"?  [0,-val] :
+                    type=="round"?  [val,0] :
+                    type=="fillet"? [0,-val] :
+                    [0,0]
+                )
+            ) [type, val, pt]
+        ],
+        path = [
+            [tot_x,-excess],
+            [-excess,-excess],
+            [-excess,tot_y],
+            for (pat = data) each
+                pat[0]=="step"?  [pat[2]] :
+                pat[0]=="xstep"? [pat[2]] :
+                pat[0]=="ystep"? [pat[2]] :
+                let(
+                    r = pat[1],
+                    steps = segs(abs(r)),
+                    step = 90/steps
+                ) [
+                    for (i=[0:1:steps]) let(
+                        a = pat[0]=="round"? (180+i*step) : (90-i*step)
+                    ) pat[2] + abs(r)*[cos(a),sin(a)]
+                ]
+        ],
+        path2 = deduplicate(path)
+    ) reorient(anchor,spin, two_d=true, path=path2, p=path2);
+
+
+
+// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

From 62629b6eb6c89b1a2f6bc879ad7600493310f92e Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Wed, 29 Jul 2020 06:50:42 +0100
Subject: [PATCH 17/24] Revert "remove test_all"

This reverts commit 66aba45802c38d3dccdbe05b7e74b773bde2ca61.
---
 tests/test_all.scad | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)
 create mode 100644 tests/test_all.scad

diff --git a/tests/test_all.scad b/tests/test_all.scad
new file mode 100644
index 0000000..c9c89ac
--- /dev/null
+++ b/tests/test_all.scad
@@ -0,0 +1,29 @@
+//include<hull.scad>
+include<polyhedra.scad>
+include<test_affine.scad>
+include<test_arrays.scad>
+include<test_common.scad>
+include<test_coords.scad>
+include<test_cubetruss.scad>
+include<test_debug.scad>
+include<test_edges.scad>
+include<test_errors.scad>
+include<test_geometry.scad>
+include<test_linear_bearings.scad>
+include<test_math.scad>
+include<test_mutators.scad>
+include<test_primitives.scad>
+include<test_quaternions.scad>
+include<test_queues.scad>
+include<test_shapes.scad>
+include<test_shapes2d.scad>
+include<test_skin.scad>
+include<test_stacks.scad>
+include<test_strings.scad>
+include<test_structs.scad>
+include<test_transforms.scad>
+include<test_vectors.scad>
+include<test_version.scad>
+include<test_vnf.scad>
+
+

From 014eea601d5bd16b7a84f7660415cd77cbb654fa Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Wed, 29 Jul 2020 06:52:12 +0100
Subject: [PATCH 18/24] Revert "formating"

This reverts commit f67226a6dd61105766f0dc3b6e72329e0d5419b1.
---
 arrays.scad | 42 +++++++++++++++++++++++++++++++++++++++---
 math.scad   | 52 +++++++++++++++++++++++++++++++++++-----------------
 2 files changed, 74 insertions(+), 20 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index aa401bd..365649a 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -73,6 +73,9 @@ function select(list, start, end=undef) =
             :   concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ;
 
 
+
+
+
 // Function: slice()
 // Description:
 //   Returns a slice of a list.  The first item is index 0.
@@ -98,6 +101,8 @@ function slice(list,start,end) =
         ) [for (i=[s:1:e-1]) if (e>s) list[i]];
 
 
+
+
 // Function: in_list()
 // Description: Returns true if value `val` is in list `list`. When `val==NAN` the answer will be false for any list.
 // Arguments:
@@ -113,6 +118,7 @@ function in_list(val,list,idx=undef) =
     s==[] || s[0]==[] ? false
     : is_undef(idx) ? val==list[s] 
     : val==list[s][idx];
+    
 
 
 // Function: min_index()
@@ -203,6 +209,7 @@ function repeat(val, n, i=0) =
     [for (j=[1:1:n[i]]) repeat(val, n, i+1)];
 
 
+
 // Function: list_range()
 // Usage:
 //   list_range(n, [s], [e])
@@ -239,6 +246,8 @@ function list_range(n=undef, s=0, e=undef, step=undef) =
         [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] ;
+    
+
 
 
 // Section: List Manipulation
@@ -306,6 +315,8 @@ function deduplicate(list, closed=false, eps=EPSILON) =
     : [for (i=[0:1:l-1]) if (i==end || !approx(list[i], list[(i+1)%l], eps)) list[i]];
 
 
+
+
 // Function: deduplicate_indexed()
 // Usage:
 //   new_idxs = deduplicate_indexed(list, indices, [closed], [eps]);
@@ -340,6 +351,8 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
     ];
 
 
+
+
 // Function: repeat_entries()
 // Usage:
 //   newlist = repeat_entries(list, N)
@@ -379,6 +392,8 @@ function repeat_entries(list, N, exact = true) =
     [for(i=[0:length-1]) each repeat(list[i],reps[i])];
     
 
+
+
 // Function: list_set()
 // Usage:
 //   list_set(list, indices, values, [dflt], [minlen])
@@ -418,6 +433,7 @@ function list_set(list=[],indices,values,dflt=0,minlen=0) =
         ];
       
 
+
 // Function: list_insert()
 // Usage:
 //   list_insert(list, indices, values);
@@ -449,6 +465,8 @@ function list_insert(list, indices, values, _i=0) =
         ];
 
 
+
+
 // Function: list_remove()
 // Usage:
 //   list_remove(list, indices)
@@ -471,6 +489,8 @@ function list_remove(list, indices) =
             if ( []==search(i,indices,1) ) list[i] ]; 
 
 
+
+
 // Function: list_remove_values()
 // Usage:
 //   list_remove_values(list,values,all=false) =
@@ -540,6 +560,8 @@ function list_bset(indexset, valuelist, dflt=0) =
     );
 
 
+
+
 // Section: List Length Manipulation
 
 // Function: list_shortest()
@@ -552,6 +574,7 @@ function list_shortest(array) =
     min([for (v = array) len(v)]);
 
 
+
 // Function: list_longest()
 // Description:
 //   Returns the length of the longest sublist in a list of lists.
@@ -601,6 +624,7 @@ function list_fit(array, length, fill) =
               : list_pad(array,length,fill);
 
 
+
 // Section: List Shuffling and Sorting
 
 // Function: shuffle()
@@ -655,7 +679,6 @@ function _sort_vectors2(arr) =
     ) 
     concat( _sort_vectors2(lesser), equal, _sort_vectors2(greater) );
 
-
 // Sort a vector of vectors based on the first three entries of each vector
 // Lexicographic order, remaining entries of vector ignored
 function _sort_vectors3(arr) =
@@ -733,7 +756,6 @@ function _sort_general(arr, idx=undef) =
     )
     concat(_sort_general(lesser,idx), equal, _sort_general(greater,idx));
     
-
 function _sort_general(arr, idx=undef) =
     (len(arr)<=1) ? arr :
     let(
@@ -752,6 +774,9 @@ function _sort_general(arr, idx=undef) =
     concat(_sort_general(lesser,idx), equal, _sort_general(greater,idx));
 
 
+
+
+
 // Function: sort()
 // Usage:
 //   sort(list, [idx])
@@ -784,6 +809,7 @@ function sort(list, idx=undef) =
     : _sort_general(list);
 
 
+
 // Function: sortidx()
 // Description:
 //   Given a list, calculates the sort order of the list, and returns
@@ -827,7 +853,6 @@ function sortidx(list, idx=undef) =
     :   // general case
         subindex(_sort_general(aug, idx=list_range(s=1,n=len(aug)-1)), 0);
 
-
 function sortidx(list, idx=undef) =
     list==[] ? [] : let(
         size = array_dim(list),
@@ -866,6 +891,7 @@ function unique(arr) =
     ];
 
 
+
 // Function: unique_count()
 // Usage:
 //   unique_count(arr);
@@ -882,6 +908,8 @@ function unique_count(arr) =
       [ select(arr,ind), deltas( concat(ind,[len(arr)]) ) ];
 
 
+
+
 // Section: List Iteration Helpers
 
 // Function: idx()
@@ -1076,6 +1104,8 @@ function set_union(a, b, get_indices=false) =
     ) [idxs, nset];
 
 
+
+
 // Function: set_difference()
 // Usage:
 //   s = set_difference(a, b);
@@ -1095,6 +1125,7 @@ function set_difference(a, b) =
     [ for (i=idx(a)) if(found[i]==[]) a[i] ];
 
 
+
 // Function: set_intersection()
 // Usage:
 //   s = set_intersection(a, b);
@@ -1114,6 +1145,8 @@ function set_intersection(a, b) =
     [ for (i=idx(a)) if(found[i]!=[]) a[i] ];
 
 
+
+
 // Section: Array Manipulation
 
 // Function: add_scalar()
@@ -1132,6 +1165,7 @@ function add_scalar(v,s) =
     is_finite(s) ? [for (x=v) is_list(x)? add_scalar(x,s) : is_finite(x) ? x+s: x] : v;
 
 
+
 // Function: subindex()
 // Description:
 //   For each array item, return the indexed subitem.
@@ -1315,4 +1349,6 @@ function transpose(arr) =
     :  arr;
 
 
+
+
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/math.scad b/math.scad
index 2e915ee..23a8174 100644
--- a/math.scad
+++ b/math.scad
@@ -107,7 +107,6 @@ function binomial(n) =
          c = c*(n-i)/(i+1), i = i+1
         ) c ] ;
 
-
 // Function: binomial_coefficient()
 // Usage:
 //   x = binomial_coefficient(n,k);
@@ -130,7 +129,6 @@ function binomial_coefficient(n,k) =
                  ) c] )
     b[len(b)-1];
 
-
 // Function: lerp()
 // Usage:
 //   x = lerp(a, b, u);
@@ -168,6 +166,7 @@ function lerp(a,b,u) =
     [for (v = u) lerp(a,b,v)];
 
 
+
 // Function: all_numeric()
 // Usage:
 //   x = all_numeric(list);
@@ -185,6 +184,22 @@ function all_numeric(list) =
         || []==[for(vi=list) if( !all_numeric(vi)) 0] ;
 
         
+// Function: is_addable()
+// Usage:
+//   x = is_addable(list);
+// Description:
+//   Returns true if `list` is both consistent and numerical. 
+// Arguments:
+//   list = The list to check
+// Example:
+//   x = is_addable([[[1],2],[[0],2]]));    // Returns: true
+//   y = is_addable([[[1],2],[[0],[]]]));   // Returns: false
+function is_addable(list) = // consistent and numerical
+    is_list_of(list,list[0])
+    &&  ( ( let(v = list*list[0]) is_num(0*(v*v)) ) 
+        || []==[for(vi=list) if( !all_numeric(vi)) 0] );
+
+          
 
 // Section: Hyperbolic Trigonometry
 
@@ -230,6 +245,7 @@ function atanh(x) =
     ln((1+x)/(1-x))/2;
 
 
+
 // Section: Quantization
 
 // Function: quant()
@@ -257,13 +273,14 @@ function atanh(x) =
 //   quant([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,12,12,12]
 //   quant([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[12,12,12]]
 function quant(x,y) =
-    assert(is_finite(y), "The multiple must be an integer.")
+    assert(is_int(y), "The multiple must be an integer.")
     is_list(x)
     ?   [for (v=x) quant(v,y)]
     :   assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
         floor(x/y+0.5)*y;
 
 
+
 // Function: quantdn()
 // Description:
 //   Quantize a value `x` to an integer multiple of `y`, rounding down to the previous multiple.
@@ -289,7 +306,7 @@ function quant(x,y) =
 //   quantdn([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,9,9,12]
 //   quantdn([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[9,9,12]]
 function quantdn(x,y) =
-    assert(is_finite(y), "The multiple must be a finite number.")
+    assert(is_int(y), "The multiple must be an integer.")
     is_list(x)
     ?    [for (v=x) quantdn(v,y)]
     :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
@@ -321,7 +338,7 @@ function quantdn(x,y) =
 //   quantup([9,10,10.4,10.5,11,12],3);      // Returns: [9,12,12,12,12,12]
 //   quantup([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,12,12],[12,12,12]]
 function quantup(x,y) =
-    assert(is_finite(y), "The multiple must be a number.")
+    assert(is_int(y), "The multiple must be an integer.")
     is_list(x)
     ?    [for (v=x) quantup(v,y)]
     :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
@@ -367,7 +384,7 @@ function constrain(v, minval, maxval) =
 //   posmod(700,360);   // Returns: 340
 //   posmod(3,2.5);     // Returns: 0.5
 function posmod(x,m) = 
-    assert( is_finite(x) && is_finite(m), "Input must be finite numbers.")
+    assert( is_finite(x) && is_int(m), "Input must be finite numbers.")
     (x%m+m)%m;
 
 
@@ -404,7 +421,7 @@ function modang(x) =
 //   modrange(90,270,360, step=-45);  // Returns: [90,45,0,315,270]
 //   modrange(270,90,360, step=-45);  // Returns: [270,225,180,135,90]
 function modrange(x, y, m, step=1) =
-    assert( is_finite(x+y+step+m), "Input must be finite numbers.")
+    assert( is_finite(x+y+step) && is_int(m), "Input must be finite numbers.")
     let(
         a = posmod(x, m),
         b = posmod(y, m),
@@ -565,6 +582,7 @@ function cumsum(v, off) =
       ) S ];
 
 
+
 // Function: sum_of_squares()
 // Description:
 //   Returns the sum of the square of each element of a vector.
@@ -691,6 +709,7 @@ function convolve(p,q) =
     [for(i=[0:n+m-2], k1 = max(0,i-n+1), k2 = min(i,m-1) )
        [for(j=[k1:k2]) p[i-j] ] * [for(j=[k1:k2]) q[j] ] 
     ];
+     
 
 
 
@@ -880,6 +899,11 @@ function determinant(M) =
 //   m = optional height of matrix
 //   n = optional width of matrix
 //   square = set to true to require a square matrix.  Default: false        
+function is_matrix(A,m,n,square=false) =
+    is_vector(A[0],n) 
+    && is_vector(A*(0*A[0]),m)
+    && ( !square || len(A)==len(A[0]));
+    
 function is_matrix(A,m,n,square=false) =
     is_list(A[0]) 
     && ( let(v = A*A[0]) is_num(0*(v*v)) ) // a matrix of finite numbers 
@@ -1073,7 +1097,7 @@ function count_true(l, nmax) =
 //   h = the parametric sampling of the data.
 //   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv(data, h=1, closed=false) =
-    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
+    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
     assert( len(data)>=2, "Input `data` should have at least 2 elements.") 
     assert( is_finite(h) || is_vector(h), "The sampling `h` must be a number or a list of numbers." )
     assert( is_num(h) || len(h) == len(data)-(closed?0:1),
@@ -1135,7 +1159,7 @@ function _deriv_nonuniform(data, h, closed) =
 //   h = the constant parametric sampling of the data.
 //   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv2(data, h=1, closed=false) =
-    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
+    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
     assert( len(data)>=3, "Input list has less than 3 elements.") 
     assert( is_finite(h), "The sampling `h` must be a number." )
     let( L = len(data) )
@@ -1171,7 +1195,7 @@ function deriv2(data, h=1, closed=false) =
 //   the estimates are f'''(t) = (-5*f(t)+18*f(t+h)-24*f(t+2*h)+14*f(t+3*h)-3*f(t+4*h)) / 2h^3 and
 //   f'''(t) = (-3*f(t-h)+10*f(t)-12*f(t+h)+6*f(t+2*h)-f(t+3*h)) / 2h^3.
 function deriv3(data, h=1, closed=false) =
-    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
+    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
     assert( len(data)>=5, "Input list has less than 5 elements.") 
     assert( is_finite(h), "The sampling `h` must be a number." )
     let(
@@ -1249,13 +1273,7 @@ function polynomial(p, z, _k, _zk, _total) =
                       is_num(z) ? _zk*z : C_times(_zk,z), 
                       _total+_zk*p[_k]);
 
-function newpoly(p,z,k,total) =
-     is_undef(k)
-   ?    assert( is_vector(p) || p==[], "Input polynomial coefficients must be a vector." )
-        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
-        newpoly(p, z, 0, is_num(z) ? 0 : [0,0])
-   : k==len(p) ? total
-   : newpoly(p,z,k+1, is_num(z) ? total*z+p[k] : C_times(total,z)+[p[k],0]);
+
 
 // Function: poly_mult()
 // Usage

From 281d945ed7d7244182998062764be340230e4494 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Wed, 29 Jul 2020 15:30:35 +0100
Subject: [PATCH 19/24] Revert "update"

This reverts commit d45f289a9526de09db18ba248dab9b9e9f45b4d0.
---
 math.scad            |  2 +-
 shapes2d.scad        |  1 -
 tests/test_all.scad  | 29 -------------------
 tests/test_math.scad | 69 ++++++--------------------------------------
 4 files changed, 10 insertions(+), 91 deletions(-)
 delete mode 100644 tests/test_all.scad

diff --git a/math.scad b/math.scad
index 23a8174..a5b12b6 100644
--- a/math.scad
+++ b/math.scad
@@ -1244,7 +1244,7 @@ function C_div(z1,z2) =
 
 // Section: Polynomials
 
-// Function: polynomial() 
+// Function: polynomial()
 // Usage:
 //   polynomial(p, z)
 // Description:
diff --git a/shapes2d.scad b/shapes2d.scad
index f328952..9622ad4 100644
--- a/shapes2d.scad
+++ b/shapes2d.scad
@@ -787,7 +787,6 @@ function rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) =
         size = is_num(size)? [size,size] : point2d(size),
         anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT),
         complex = rounding!=0 || chamfer!=0
-				, xxx = echo(anchor=anchor)echo(size=size)
     )
     (rounding==0 && chamfer==0)? let(
         path = [
diff --git a/tests/test_all.scad b/tests/test_all.scad
deleted file mode 100644
index c9c89ac..0000000
--- a/tests/test_all.scad
+++ /dev/null
@@ -1,29 +0,0 @@
-//include<hull.scad>
-include<polyhedra.scad>
-include<test_affine.scad>
-include<test_arrays.scad>
-include<test_common.scad>
-include<test_coords.scad>
-include<test_cubetruss.scad>
-include<test_debug.scad>
-include<test_edges.scad>
-include<test_errors.scad>
-include<test_geometry.scad>
-include<test_linear_bearings.scad>
-include<test_math.scad>
-include<test_mutators.scad>
-include<test_primitives.scad>
-include<test_quaternions.scad>
-include<test_queues.scad>
-include<test_shapes.scad>
-include<test_shapes2d.scad>
-include<test_skin.scad>
-include<test_stacks.scad>
-include<test_strings.scad>
-include<test_structs.scad>
-include<test_transforms.scad>
-include<test_vectors.scad>
-include<test_version.scad>
-include<test_vnf.scad>
-
-
diff --git a/tests/test_math.scad b/tests/test_math.scad
index eefb18d..3233e6f 100644
--- a/tests/test_math.scad
+++ b/tests/test_math.scad
@@ -1,17 +1,5 @@
 include <../std.scad>
 
-module test_all_numeric() {
-    assert( all_numeric(1));
-    assert( all_numeric([1,2,[3,4]]));
-    assert( all_numeric([]));
-    assert(!all_numeric([1,2,[3,"a"]]));
-    assert(!all_numeric([1,2,[3,true]]));
-    assert(!all_numeric([1,2,[3,NAN]]));
-    assert(!all_numeric([1,2,[3,INF]]));
-    assert( all_numeric([1,2,[3,[]]]));
-}
-test_all_numeric();
-
 // Simple Calculations
 
 module test_quant() {
@@ -122,8 +110,6 @@ module test_approx() {
     assert_equal(approx(1/3, 0.3333333333), true);
     assert_equal(approx(-1/3, -0.3333333333), true);
     assert_equal(approx(10*[cos(30),sin(30)], 10*[sqrt(3)/2, 1/2]), true);
-    assert_equal(approx([1,[1,undef]], [1+1e-12,[1,true]]), false);
-    assert_equal(approx([1,[1,undef]], [1+1e-12,[1,undef]]), true);
 }
 test_approx();
 
@@ -356,9 +342,7 @@ module test_cumsum() {
     assert_equal(cumsum([2,2,2]), [2,4,6]);
     assert_equal(cumsum([1,2,3]), [1,3,6]);
     assert_equal(cumsum([-2,-1,0,1,2]), [-2,-3,-3,-2,0]);
-    assert_equal(cumsum([-2,-1,0,1,2],3), [1,0,0,1,3]);
     assert_equal(cumsum([[1,2,3], [3,4,5], [5,6,7]]), [[1,2,3],[4,6,8],[9,12,15]]);
-    assert_equal(cumsum([[1,2,3], [3,4,5], [5,6,7]], [1,0,0]), [[2,2,3],[5,6,8],[10,12,15]]);
 }
 test_cumsum();
 
@@ -405,6 +389,7 @@ module test_mean() {
 }
 test_mean();
 
+
 module test_median() {
     assert_equal(median([2,3,7]), 4.5);
     assert_equal(median([[1,2,3], [3,4,5], [8,9,10]]), [4.5,5.5,6.5]);
@@ -412,16 +397,6 @@ module test_median() {
 test_median();
 
 
-module test_convolve() {
-    assert_equal(convolve([],[1,2,1]), []);
-    assert_equal(convolve([1,1],[]), []);
-    assert_equal(convolve([1,1],[1,2,1]), [1,3,3,1]);
-    assert_equal(convolve([1,2,3],[1,2,1]), [1,4,8,8,3]);
-}
-test_convolve();
-
-
-
 module test_matrix_inverse() {
     assert_approx(matrix_inverse(rot([20,30,40])), [[0.663413948169,0.556670399226,-0.5,0],[-0.47302145844,0.829769465589,0.296198132726,0],[0.579769465589,0.0400087565481,0.813797681349,0],[0,0,0,1]]);
 }
@@ -608,24 +583,6 @@ module test_factorial() {
 }
 test_factorial();
 
-module test_binomial() {
-    assert_equal(binomial(1), [1,1]);
-    assert_equal(binomial(2), [1,2,1]);
-    assert_equal(binomial(3), [1,3,3,1]);
-    assert_equal(binomial(5), [1,5,10,10,5,1]);
-}
-test_binomial();
-
-module test_binomial_coefficient() {
-    assert_equal(binomial_coefficient(2,1), 2);
-    assert_equal(binomial_coefficient(3,2), 3);
-    assert_equal(binomial_coefficient(4,2), 6);
-    assert_equal(binomial_coefficient(10,7), 120);
-    assert_equal(binomial_coefficient(10,7), binomial(10)[7]);
-    assert_equal(binomial_coefficient(15,4), binomial(15)[4]);
-}
-test_binomial_coefficient();
-
 
 module test_gcd() {
     assert_equal(gcd(15,25), 5);
@@ -725,7 +682,6 @@ test_linear_solve();
 
 module test_outer_product(){
   assert_equal(outer_product([1,2,3],[4,5,6]), [[4,5,6],[8,10,12],[12,15,18]]);
-  assert_equal(outer_product([1,2],[4,5,6]), [[4,5,6],[8,10,12]]);
   assert_equal(outer_product([9],[7]), [[63]]);
 }
 test_outer_product();
@@ -826,10 +782,8 @@ test_deriv3();
 
 
 module test_polynomial(){
-  assert_equal(polynomial([0],12),0);
-  assert_equal(polynomial([0],[12,4]),[0,0]);
-//  assert_equal(polynomial([],12),0);
-//  assert_equal(polynomial([],[12,4]),[0,0]);
+  assert_equal(polynomial([],12),0);
+  assert_equal(polynomial([],[12,4]),[0,0]);
   assert_equal(polynomial([1,2,3,4],3),58);
   assert_equal(polynomial([1,2,3,4],[3,-1]),[47,-41]);
   assert_equal(polynomial([0,0,2],4),2);
@@ -925,20 +879,16 @@ test_qr_factor();
 
 module test_poly_mult(){
   assert_equal(poly_mult([3,2,1],[4,5,6,7]),[12,23,32,38,20,7]);
-  assert_equal(poly_mult([3,2,1],[0]),[0]);
-//  assert_equal(poly_mult([3,2,1],[]),[]);
+  assert_equal(poly_mult([3,2,1],[]),[]);
   assert_equal(poly_mult([[1,2],[3,4],[5,6]]), [15,68,100,48]);
-  assert_equal(poly_mult([[1,2],[0],[5,6]]), [0]);
-//  assert_equal(poly_mult([[1,2],[],[5,6]]), []);
-  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[0]);
-//  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[]);
+  assert_equal(poly_mult([[1,2],[],[5,6]]), []);
+  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[]);
 }
 test_poly_mult();
 
-
+ 
 module test_poly_div(){
-  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[0]]);
-//  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[]]);
+  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[]]);
   assert_equal(poly_div([1,2,3,4],[1,2,3,4,5]), [[], [1,2,3,4]]);
   assert_equal(poly_div(poly_add(poly_mult([1,2,3,4],[2,0,2]), [1,1,2]), [1,2,3,4]), [[2,0,2],[1,1,2]]);
   assert_equal(poly_div([1,2,3,4], [1,-3]), [[1,5,18],[58]]);
@@ -949,8 +899,7 @@ test_poly_div();
 module test_poly_add(){
   assert_equal(poly_add([2,3,4],[3,4,5,6]),[3,6,8,10]);
   assert_equal(poly_add([1,2,3,4],[-1,-2,3,4]), [6,8]);
-  assert_equal(poly_add([1,2,3],-[1,2,3]),[0]);
-//  assert_equal(poly_add([1,2,3],-[1,2,3]),[]);
+  assert_equal(poly_add([1,2,3],-[1,2,3]),[]);
 }
 test_poly_add();
 

From ec4d960ecbf3de3b04b84d768028eac9d1cc087c Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Wed, 29 Jul 2020 15:30:57 +0100
Subject: [PATCH 20/24] Revert "input checks, general review, adding new
 functions"

This reverts commit 51be74b5ececf27be88b995df5df13a95b7a2f88.
---
 math.scad | 662 ++++++++++++++++++------------------------------------
 1 file changed, 213 insertions(+), 449 deletions(-)

diff --git a/math.scad b/math.scad
index a5b12b6..7629cb1 100644
--- a/math.scad
+++ b/math.scad
@@ -33,10 +33,7 @@ NAN = acos(2);  // The value `nan`, useful for comparisons.
 //   sqr([3,4]); // Returns: [9,16]
 //   sqr([[1,2],[3,4]]);  // Returns [[1,4],[9,16]]
 //   sqr([[1,2],3]);      // Returns [[1,4],9]
-function sqr(x) = 
-    is_list(x) ? [for(val=x) sqr(val)] : 
-    is_finite(x) ? x*x :
-    assert(is_finite(x) || is_vector(x), "Input is not neither a number nor a list of numbers.");
+function sqr(x) = is_list(x) ? [for(val=x) sqr(val)] : x*x;
 
 
 // Function: log2()
@@ -48,11 +45,8 @@ function sqr(x) =
 //   log2(0.125);  // Returns: -3
 //   log2(16);     // Returns: 4
 //   log2(256);    // Returns: 8
-function log2(x) = 
-    assert( is_finite(x), "Input is not a number.")
-    ln(x)/ln(2);
+function log2(x) = ln(x)/ln(2);
 
-// this may return NAN or INF; should it check x>0 ?
 
 // Function: hypot()
 // Usage:
@@ -66,9 +60,7 @@ function log2(x) =
 // Example:
 //   l = hypot(3,4);  // Returns: 5
 //   l = hypot(3,4,5);  // Returns: ~7.0710678119
-function hypot(x,y,z=0) = 
-    assert( is_vector([x,y,z]), "Improper number(s).")
-    norm([x,y,z]);
+function hypot(x,y,z=0) = norm([x,y,z]);
 
 
 // Function: factorial()
@@ -84,51 +76,11 @@ function hypot(x,y,z=0) =
 //   y = factorial(6);  // Returns: 720
 //   z = factorial(9);  // Returns: 362880
 function factorial(n,d=0) =
-    assert(is_int(n) && is_int(d) && n>=0 && d>=0, "Factorial is not defined for negative numbers")
+    assert(n>=0 && d>=0, "Factorial is not defined for negative numbers")
     assert(d<=n, "d cannot be larger than n")
     product([1,for (i=[n:-1:d+1]) i]);
 
 
-// Function: binomial()
-// Usage:
-//   x = binomial(n);
-// Description:
-//   Returns the binomial coefficients of the integer `n`.  
-// Arguments:
-//   n = The integer to get the binomial coefficients of
-// Example:
-//   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]
-function binomial(n) =
-    assert( is_int(n) && n>0, "Input is not an integer greater than 0.")
-    [for( c = 1, i = 0; 
-        i<=n; 
-         c = c*(n-i)/(i+1), i = i+1
-        ) c ] ;
-
-// Function: binomial_coefficient()
-// Usage:
-//   x = binomial_coefficient(n,k);
-// Description:
-//   Returns the k-th binomial coefficient of the integer `n`.  
-// Arguments:
-//   n = The integer to get the binomial coefficient of
-//   k = The binomial coefficient index
-// Example:
-//   x = binomial_coefficient(3,2);  // Returns: 3
-//   y = binomial_coefficient(10,6); // Returns: 210
-function binomial_coefficient(n,k) =
-    assert( is_int(n) && is_int(k), "Some input is not a number.")
-    k < 0 || k > n ? 0 :
-    k ==0 || k ==n ? 1 :
-    let( k = min(k, n-k),
-         b = [for( c = 1, i = 0; 
-                   i<=k; 
-                   c = c*(n-i)/(i+1), i = i+1
-                 ) c] )
-    b[len(b)-1];
-
 // Function: lerp()
 // Usage:
 //   x = lerp(a, b, u);
@@ -139,8 +91,8 @@ function binomial_coefficient(n,k) =
 //   If `u` is 0.0, then the value of `a` is returned.
 //   If `u` is 1.0, then the value of `b` is returned.
 //   If `u` is a range, or list of numbers, returns a list of interpolated values.
-//   It is valid to use a `u` value outside the range 0 to 1.  The result will be an extrapolation
-//   along the slope formed by `a` and `b`.
+//   It is valid to use a `u` value outside the range 0 to 1.  The result will be a predicted
+//   value along the slope formed by `a` and `b`, but not between those two values.
 // Arguments:
 //   a = First value or vector.
 //   b = Second value or vector.
@@ -162,86 +114,46 @@ function binomial_coefficient(n,k) =
 function lerp(a,b,u) =
     assert(same_shape(a,b), "Bad or inconsistent inputs to lerp")
     is_num(u)? (1-u)*a + u*b :
-    assert(is_finite(u) || is_vector(u) || is_range(u), "Input u to lerp must be a number, vector, or range.")
+    assert(!is_undef(u)&&!is_bool(u)&&!is_string(u), "Input u to lerp must be a number, vector, or range.")
     [for (v = u) lerp(a,b,v)];
 
 
 
-// Function: all_numeric()
-// Usage:
-//   x = all_numeric(list);
-// Description:
-//   Returns true if all simple value in `list` is a finite number and the list is not empty. Uses recursion.  
-// Arguments:
-//   list = The list to check
-// Example:
-//   x = all_numeric([1,[2,3,[4]]]);  // Returns: true
-//   y = all_numeric([1,[2,3,[1/0]]]); // Returns: false
-function all_numeric(list) =
-    !is_list(list) ? is_num(0*list)
-    : let(v = list*list[0]) 
-        is_num(0*(v*v))         // true just for vectors and matrices
-        || []==[for(vi=list) if( !all_numeric(vi)) 0] ;
-
-        
-// Function: is_addable()
-// Usage:
-//   x = is_addable(list);
-// Description:
-//   Returns true if `list` is both consistent and numerical. 
-// Arguments:
-//   list = The list to check
-// Example:
-//   x = is_addable([[[1],2],[[0],2]]));    // Returns: true
-//   y = is_addable([[[1],2],[[0],[]]]));   // Returns: false
-function is_addable(list) = // consistent and numerical
-    is_list_of(list,list[0])
-    &&  ( ( let(v = list*list[0]) is_num(0*(v*v)) ) 
-        || []==[for(vi=list) if( !all_numeric(vi)) 0] );
-
-          
-
 // Section: Hyperbolic Trigonometry
 
 // Function: sinh()
 // Description: Takes a value `x`, and returns the hyperbolic sine of it.
 function sinh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     (exp(x)-exp(-x))/2;
 
 
 // Function: cosh()
 // Description: Takes a value `x`, and returns the hyperbolic cosine of it.
 function cosh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     (exp(x)+exp(-x))/2;
 
 
 // Function: tanh()
 // Description: Takes a value `x`, and returns the hyperbolic tangent of it.
 function tanh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     sinh(x)/cosh(x);
 
 
 // Function: asinh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic sine of it.
 function asinh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     ln(x+sqrt(x*x+1));
 
 
 // Function: acosh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic cosine of it.
 function acosh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     ln(x+sqrt(x*x-1));
 
 
 // Function: atanh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic tangent of it.
 function atanh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     ln((1+x)/(1-x))/2;
 
 
@@ -273,12 +185,8 @@ function atanh(x) =
 //   quant([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,12,12,12]
 //   quant([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[12,12,12]]
 function quant(x,y) =
-    assert(is_int(y), "The multiple must be an integer.")
-    is_list(x)
-    ?   [for (v=x) quant(v,y)]
-    :   assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
-        floor(x/y+0.5)*y;
-
+    is_list(x)? [for (v=x) quant(v,y)] :
+    floor(x/y+0.5)*y;
 
 
 // Function: quantdn()
@@ -306,11 +214,8 @@ function quant(x,y) =
 //   quantdn([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,9,9,12]
 //   quantdn([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[9,9,12]]
 function quantdn(x,y) =
-    assert(is_int(y), "The multiple must be an integer.")
-    is_list(x)
-    ?    [for (v=x) quantdn(v,y)]
-    :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
-        floor(x/y)*y;
+    is_list(x)? [for (v=x) quantdn(v,y)] :
+    floor(x/y)*y;
 
 
 // Function: quantup()
@@ -338,11 +243,8 @@ function quantdn(x,y) =
 //   quantup([9,10,10.4,10.5,11,12],3);      // Returns: [9,12,12,12,12,12]
 //   quantup([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,12,12],[12,12,12]]
 function quantup(x,y) =
-    assert(is_int(y), "The multiple must be an integer.")
-    is_list(x)
-    ?    [for (v=x) quantup(v,y)]
-    :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
-        ceil(x/y)*y;
+    is_list(x)? [for (v=x) quantup(v,y)] :
+    ceil(x/y)*y;
 
 
 // Section: Constraints and Modulos
@@ -362,9 +264,7 @@ function quantup(x,y) =
 //   constrain(0.3, -1, 1);  // Returns: 0.3
 //   constrain(9.1, 0, 9);   // Returns: 9
 //   constrain(-0.1, 0, 9);  // Returns: 0
-function constrain(v, minval, maxval) = 
-    assert( is_finite(v+minval+maxval), "Input must be finite number(s).")
-    min(maxval, max(minval, v));
+function constrain(v, minval, maxval) = min(maxval, max(minval, v));
 
 
 // Function: posmod()
@@ -383,9 +283,7 @@ function constrain(v, minval, maxval) =
 //   posmod(270,360);   // Returns: 270
 //   posmod(700,360);   // Returns: 340
 //   posmod(3,2.5);     // Returns: 0.5
-function posmod(x,m) = 
-    assert( is_finite(x) && is_int(m), "Input must be finite numbers.")
-    (x%m+m)%m;
+function posmod(x,m) = (x%m+m)%m;
 
 
 // Function: modang(x)
@@ -401,7 +299,6 @@ function posmod(x,m) =
 //   modang(270,360);   // Returns: -90
 //   modang(700,360);   // Returns: -20
 function modang(x) =
-    assert( is_finite(x), "Input must be a finite number.")
     let(xx = posmod(x,360)) xx<180? xx : xx-360;
 
 
@@ -409,7 +306,7 @@ function modang(x) =
 // Usage:
 //   modrange(x, y, m, [step])
 // Description:
-//   Returns a normalized list of numbers from `x` to `y`, by `step`, modulo `m`.  Wraps if `x` > `y`.
+//   Returns a normalized list of values 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.
@@ -421,7 +318,6 @@ function modang(x) =
 //   modrange(90,270,360, step=-45);  // Returns: [90,45,0,315,270]
 //   modrange(270,90,360, step=-45);  // Returns: [270,225,180,135,90]
 function modrange(x, y, m, step=1) =
-    assert( is_finite(x+y+step) && is_int(m), "Input must be finite numbers.")
     let(
         a = posmod(x, m),
         b = posmod(y, m),
@@ -434,21 +330,20 @@ function modrange(x, y, m, step=1) =
 
 // Function: rand_int()
 // Usage:
-//   rand_int(minval,maxval,N,[seed]);
+//   rand_int(min,max,N,[seed]);
 // Description:
-//   Return a list of random integers in the range of minval to maxval, inclusive.
+//   Return a list of random integers in the range of min to max, inclusive.
 // Arguments:
-//   minval = Minimum integer value to return.
-//   maxval = Maximum integer value to return.
+//   min = Minimum integer value to return.
+//   max = Maximum integer value to return.
 //   N = Number of random integers to return.
 //   seed = If given, sets the random number seed.
 // Example:
 //   ints = rand_int(0,100,3);
 //   int = rand_int(-10,10,1)[0];
-function rand_int(minval, maxval, N, seed=undef) =
-    assert( is_finite(minval+maxval+N+seed), "Input must be finite numbers.")
-    assert(maxval >= minval, "Max value cannot be smaller than minval")
-    let (rvect = is_def(seed) ? rands(minval,maxval+1,N,seed) : rands(minval,maxval+1,N))
+function rand_int(min, max, N, seed=undef) =
+    assert(max >= min, "Max value cannot be smaller than min")
+    let (rvect = is_def(seed) ? rands(min,max+1,N,seed) : rands(min,max+1,N))
     [for(entry = rvect) floor(entry)];
 
 
@@ -463,7 +358,6 @@ function rand_int(minval, maxval, N, seed=undef) =
 //   N = Number of random numbers to return.  Default: 1
 //   seed = If given, sets the random number seed.
 function gaussian_rands(mean, stddev, N=1, seed=undef) =
-    assert( is_finite(mean+stddev+N+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])];
 
@@ -480,7 +374,6 @@ function gaussian_rands(mean, stddev, N=1, seed=undef) =
 //   N = Number of random numbers to return.  Default: 1
 //   seed = If given, sets the random number seed.
 function log_rands(minval, maxval, factor, N=1, seed=undef) =
-    assert( is_finite(minval+maxval+factor+N+seed), "Input must be finite numbers.")
     assert(maxval >= minval, "maxval cannot be smaller than minval")
     let(
         minv = 1-1/pow(factor,minval),
@@ -502,18 +395,18 @@ function gcd(a,b) =
     b==0 ? abs(a) : gcd(b,a % b);
 
 
-// Computes lcm for two integers
+// Computes lcm for two scalars
 function _lcm(a,b) =
-    assert(is_int(a) && is_int(b), "Invalid non-integer parameters to lcm")
-    assert(a!=0 && b!=0, "Arguments to lcm must be non zero")
+    assert(is_int(a), "Invalid non-integer parameters to lcm")
+    assert(is_int(b), "Invalid non-integer parameters to lcm")
+    assert(a!=0 && b!=0, "Arguments to lcm must be nonzero")
     abs(a*b) / gcd(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(slice(a,0,len(a)-2),[lcm(a[len(a)-2],a[len(a)-1])]));
 
 
 // Function: lcm()
@@ -525,11 +418,12 @@ function _lcmlist(a) =
 //   be non-zero integers.  The output is always a positive integer.  It is an error to pass zero
 //   as an argument.  
 function lcm(a,b=[]) =
-    !is_list(a) && !is_list(b) 
-    ?   _lcm(a,b) 
-    :   let( arglist = concat(force_list(a),force_list(b)) )
-        assert(len(arglist)>0, "Invalid call to lcm with empty list(s)")
-        _lcmlist(arglist);
+    !is_list(a) && !is_list(b) ? _lcm(a,b) : 
+    let(
+        arglist = concat(force_list(a),force_list(b))
+    )
+    assert(len(arglist)>0,"invalid call to lcm with empty list(s)")
+    _lcmlist(arglist);
 
 
 
@@ -547,40 +441,35 @@ function lcm(a,b=[]) =
 //   sum([1,2,3]);  // returns 6.
 //   sum([[1,2,3], [3,4,5], [5,6,7]]);  // returns [9, 12, 15]
 function sum(v, dflt=0) =
-    is_list(v) && len(v) == 0 ? dflt :
-    is_vector(v) || is_matrix(v)? [for(i=v) 1]*v :
+    is_vector(v) ? [for(i=v) 1]*v :
     assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
-    _sum(v,v[0]*0);
+    is_vector(v[0]) ? [for(i=v) 1]*v :
+    len(v) == 0 ? dflt :
+                  _sum(v,v[0]*0);
 
 function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
 
 
 // Function: cumsum()
 // Description:
-//   Returns a list where each item is the cumulative sum of all items up to and including the corresponding entry 
-//   in the input list `v`. If passed an array of vectors, returns a list of cumulative vectors sums.
-//   if a compatible value `off` is given, it is added to each item in the output list.
+//   Returns a list where each item is the cumulative sum of all items up to and including the corresponding entry in the input list.
+//   If passed an array of vectors, returns a list of cumulative vectors sums.
 // Arguments:
-//   v   = The list to get the sum of.
-//   off = An offset to add to each output sum. 
+//   v = The list to get the sum of.
 // Example:
 //   cumsum([1,1,1]);  // returns [1,2,3]
 //   cumsum([2,2,2]);  // returns [2,4,6]
 //   cumsum([1,2,3]);  // returns [1,3,6]
 //   cumsum([[1,2,3], [3,4,5], [5,6,7]]);  // returns [[1,2,3], [4,6,8], [9,12,15]]
-//   cumsum([-2,-1,0,1,2],3);   // returns [1,0,0,1,3] 
-function cumsum(v, off) =
-  assert( is_list(v), "The input is not a list.")
-  v==[] ? [] :
-  let( off = is_undef(off)? 0*v[0]: off )
-  assert( is_consistent(v) , "Improper inputs.")
-  [for( i = 0, 
-        S = v[0]+off; 
-      i<=len(v)-1; 
-        i = i+1, 
-        S = S + v[i] 
-      ) S ];
-
+function cumsum(v,_i=0,_acc=[]) =
+    _i==len(v) ? _acc :
+    cumsum(
+        v, _i+1,
+        concat(
+            _acc,
+            [_i==0 ? v[_i] : select(_acc,-1)+v[_i]]
+        )
+    );
 
 
 // Function: sum_of_squares()
@@ -606,29 +495,24 @@ function sum_of_squares(v) = sum(vmul(v,v));
 // Examples:
 //   v = sum_of_sines(30, [[10,3,0], [5,5.5,60]]);
 function sum_of_sines(a, sines) =
-    assert( is_finite(a) && is_matrix(sines), "Invalid inputs.")
-    sum([ for (s = sines) 
-            let(
-              ss=point3d(s),
-              v=ss[0]*sin(a*ss[1]+ss[2])
-            ) v
-        ]);
+    sum([
+        for (s = sines) let(
+            ss=point3d(s),
+            v=ss.x*sin(a*ss.y+ss.z)
+        ) v
+    ]);
 
 
 // Function: deltas()
 // Description:
 //   Returns a list with the deltas of adjacent entries in the given list.
-//   The list should be a consistent list of numeric components (numbers, vectors, matrix, etc).
 //   Given [a,b,c,d], returns [b-a,c-b,d-c].
 // Arguments:
 //   v = The list to get the deltas of.
 // Example:
 //   deltas([2,5,9,17]);  // returns [3,4,8].
 //   deltas([[1,2,3], [3,6,8], [4,8,11]]);  // returns [[2,4,5], [1,2,3]]
-function deltas(v) = 
-    (is_vector(v) || is_matrix(v)) && len(v)>1 ? [for (p=pair(v)) p[1]-p[0]] :
-    all_numeric(v) ? [for (p=pair(v)) p[1]-p[0]] :
-    assert( false, "Inconsistent or non numeric input list.");
+function deltas(v) = [for (p=pair(v)) p.y-p.x];
 
 
 // Function: product()
@@ -641,16 +525,7 @@ function deltas(v) =
 // Example:
 //   product([2,3,4]);  // returns 24.
 //   product([[1,2,3], [3,4,5], [5,6,7]]);  // returns [15, 48, 105]
-function product(v) = 
-    assert( is_consistent(v), "Inconsistent input.")
-    _product(v, 1, v[0]);
-
-function _product(v, i=0, _tot) = 
-    i>=len(v) ? _tot :
-    _product( v, 
-              i+1, 
-              ( is_vector(v[i])? vmul(_tot,v[i]) : _tot*v[i] ) );
-               
+function product(v, i=0, tot=undef) = i>=len(v)? tot : product(v, i+1, ((tot==undef)? v[i] : is_vector(v[i])? vmul(tot,v[i]) : tot*v[i]));
 
 
 // Function: outer_product()
@@ -659,22 +534,21 @@ function _product(v, i=0, _tot) =
 // Usage:
 //   M = outer_product(u,v);
 function outer_product(u,v) =
-  assert(is_vector(u) && is_vector(v), "The inputs must be vectors.")
-  [for(ui=u) ui*v];
+  assert(is_vector(u) && is_vector(v))
+  assert(len(u)==len(v))
+  [for(i=[0:len(u)-1]) [for(j=[0:len(u)-1]) u[i]*v[j]]];
 
 
 // Function: mean()
 // Description:
-//   Returns the arithmetic mean/average of all entries in the given array.
+//   Returns the arithmatic mean/average of all entries in the given array.
 //   If passed a list of vectors, returns a vector of the mean of each part.
 // Arguments:
 //   v = The list of values to get the mean of.
 // Example:
 //   mean([2,3,4]);  // returns 3.
 //   mean([[1,2,3], [3,4,5], [5,6,7]]);  // returns [3, 4, 5]
-function mean(v) = 
-    assert(is_list(v) && len(v)>0, "Invalid list.")
-    sum(v)/len(v);
+function mean(v) = sum(v)/len(v);
 
 
 // Function: median()
@@ -682,35 +556,18 @@ function mean(v) =
 //   x = median(v);
 // Description:
 //   Given a list of numbers or vectors, finds the median value or midpoint.
-//   If passed a list of vectors, returns the vector of the median of each component.
+//   If passed a list of vectors, returns the vector of the median of each part.
 function median(v) =
-    is_vector(v) && len(v)>0 ? (min(v)+max(v))/2 :
-    is_matrix(v) && len(v)>0
-    ?    [for(ti=transpose(v))  (min(ti)+max(ti))/2 ]
-    :   assert(false , "Invalid input or empty list.");
-
-// Function: convolve()
-// Usage:
-//   x = convolve(p,q);
-// Description:
-//   Given two vectors, finds the convolution of them.
-//   The length of the returned vector is len(p)+len(q)-1 .
-// Arguments:
-//   p = The first vector.
-//   q = The second vector.
-// Example:
-//   a = convolve([1,1],[1,2,1]); // Returns: [1,3,3,1]
-//   b = convolve([1,2,3],[1,2,1])); // Returns: [1,4,8,8,3]
-function convolve(p,q) =
-    p==[] || q==[] ? [] :
-    assert( is_vector(p) && is_vector(q), "The inputs should be vectors.")
-    let( n = len(p),
-         m = len(q))
-    [for(i=[0:n+m-2], k1 = max(0,i-n+1), k2 = min(i,m-1) )
-       [for(j=[k1:k2]) p[i-j] ] * [for(j=[k1:k2]) q[j] ] 
-    ];
-     
-
+    assert(is_list(v))
+    assert(len(v)>0)
+    is_vector(v[0])? (
+        assert(is_consistent(v))
+        [
+            for (i=idx(v[0]))
+            let(vals = subindex(v,i))
+            (min(vals)+max(vals))/2
+        ]
+    ) : (min(v)+max(v))/2;
 
 
 // Section: Matrix math
@@ -725,7 +582,7 @@ function convolve(p,q) =
 //   want to solve Ax=b1 and Ax=b2 that you need to form the matrix transpose([b1,b2]) for the right hand side and then
 //   transpose the returned value.  
 function linear_solve(A,b) =
-    assert(is_matrix(A), "Input should be a matrix.")
+    assert(is_matrix(A))
     let(
         m = len(A),
         n = len(A[0])
@@ -762,7 +619,6 @@ function matrix_inverse(A) =
 // Description:
 //   Returns a submatrix with the specified index ranges or index sets.  
 function submatrix(M,ind1,ind2) =
-    assert( is_matrix(M), "Input must be a matrix." )
     [for(i=ind1) [for(j=ind2) M[i][j] ] ];
 
 
@@ -772,7 +628,7 @@ function submatrix(M,ind1,ind2) =
 //   Calculates the QR factorization of the input matrix A and returns it as the list [Q,R].  This factorization can be
 //   used to solve linear systems of equations.  
 function qr_factor(A) =
-    assert(is_matrix(A), "Input must be a matrix." )
+    assert(is_matrix(A))
     let(
       m = len(A),
       n = len(A[0])
@@ -803,8 +659,8 @@ function _qr_factor(A,Q, column, m, n) =
 // Function: back_substitute()
 // Usage: back_substitute(R, b, [transpose])
 // Description:
-//   Solves the problem Rx=b where R is an upper triangular square matrix.  The lower triangular entries of R are
-//   ignored.  If transpose==true then instead solve transpose(R)*x=b.
+//   Solves the problem Rx=b where R is an upper triangular square matrix.  No check is made that the lower triangular entries
+//   are actually zero.  If transpose==true then instead solve transpose(R)*x=b.
 //   You can supply a compatible matrix b and it will produce the solution for every column of b.  Note that if you want to
 //   solve Rx=b1 and Rx=b2 you must set b to transpose([b1,b2]) and then take the transpose of the result.  If the matrix
 //   is singular (e.g. has a zero on the diagonal) then it returns [].  
@@ -838,9 +694,7 @@ function back_substitute(R, b, x=[],transpose = false) =
 // Example:
 //   M = [ [6,-2], [1,8] ];
 //   det = det2(M);  // Returns: 50
-function det2(M) = 
-    assert( is_matrix(M,2,2), "Matrix should be 2x2." )
-    M[0][0] * M[1][1] - M[0][1]*M[1][0];
+function det2(M) = M[0][0] * M[1][1] - M[0][1]*M[1][0];
 
 
 // Function: det3()
@@ -852,7 +706,6 @@ function det2(M) =
 //   M = [ [6,4,-2], [1,-2,8], [1,5,7] ];
 //   det = det3(M);  // Returns: -334
 function det3(M) =
-    assert( is_matrix(M,3,3), "Matrix should be 3x3." )
     M[0][0] * (M[1][1]*M[2][2]-M[2][1]*M[1][2]) -
     M[1][0] * (M[0][1]*M[2][2]-M[2][1]*M[0][2]) +
     M[2][0] * (M[0][1]*M[1][2]-M[1][1]*M[0][2]);
@@ -867,21 +720,21 @@ function det3(M) =
 //   M = [ [6,4,-2,9], [1,-2,8,3], [1,5,7,6], [4,2,5,1] ];
 //   det = determinant(M);  // Returns: 2267
 function determinant(M) =
-    assert(is_matrix(M,square=true), "Input should be a square matrix." )
+    assert(len(M)==len(M[0]))
     len(M)==1? M[0][0] :
     len(M)==2? det2(M) :
     len(M)==3? det3(M) :
     sum(
         [for (col=[0:1:len(M)-1])
             ((col%2==0)? 1 : -1) *
-                M[col][0] *
-                determinant(
-                    [for (r=[1:1:len(M)-1])
-                        [for (c=[0:1:len(M)-1])
-                            if (c!=col) M[c][r]
-                        ]
+            M[col][0] *
+            determinant(
+                [for (r=[1:1:len(M)-1])
+                    [for (c=[0:1:len(M)-1])
+                        if (c!=col) M[c][r]
                     ]
-                )
+                ]
+            )
         ]
     );
 
@@ -900,16 +753,8 @@ function determinant(M) =
 //   n = optional width of matrix
 //   square = set to true to require a square matrix.  Default: false        
 function is_matrix(A,m,n,square=false) =
-    is_vector(A[0],n) 
-    && is_vector(A*(0*A[0]),m)
-    && ( !square || len(A)==len(A[0]));
-    
-function is_matrix(A,m,n,square=false) =
-    is_list(A[0]) 
-    && ( let(v = A*A[0]) is_num(0*(v*v)) ) // a matrix of finite numbers 
-    && (is_undef(n) || len(A[0])==n )
-    && (is_undef(m) || len(A)==m )
-    && ( !square || len(A)==len(A[0]));
+    is_vector(A[0],n) && is_vector(A*(0*A[0]),m) &&
+    (!square || len(A)==len(A[0]));
 
 
 // Section: Comparisons and Logic
@@ -929,13 +774,11 @@ function is_matrix(A,m,n,square=false) =
 //   approx(0.3333,1/3);          // Returns: false
 //   approx(0.3333,1/3,eps=1e-3);  // Returns: true
 //   approx(PI,3.1415926536);     // Returns: true
-function approx(a,b,eps=EPSILON) = 
+function approx(a,b,eps=EPSILON) =
     a==b? true :
     a*0!=b*0? false :
-    is_list(a)
-    ? ([for (i=idx(a)) if( !approx(a[i],b[i],eps=eps)) 1] == [])
-    : is_num(a) && is_num(b) && (abs(a-b) <= eps);
-    
+    is_list(a)? ([for (i=idx(a)) if(!approx(a[i],b[i],eps=eps)) 1] == []) :
+    (abs(a-b) <= eps);
 
 
 function _type_num(x) =
@@ -953,7 +796,7 @@ function _type_num(x) =
 // Description:
 //   Compares two values.  Lists are compared recursively.
 //   Returns <0 if a<b.  Returns >0 if a>b.  Returns 0 if a==b.
-//   If types are not the same, then undef < bool < nan < num < str < list < range.
+//   If types are not the same, then undef < bool < num < str < list < range.
 // Arguments:
 //   a = First value to compare.
 //   b = Second value to compare.
@@ -977,14 +820,13 @@ function compare_vals(a, b) =
 //   a = First list to compare.
 //   b = Second list to compare.
 function compare_lists(a, b) =
-    a==b? 0 
-    :   let(
-          cmps = [ for(i=[0:1:min(len(a),len(b))-1]) 
-                      let( cmp = compare_vals(a[i],b[i]) )
-                      if(cmp!=0) cmp 
-                 ]
-           ) 
-        cmps==[]? (len(a)-len(b)) : cmps[0];
+    a==b? 0 : let(
+        cmps = [
+            for(i=[0:1:min(len(a),len(b))-1]) let(
+                cmp = compare_vals(a[i],b[i])
+            ) if(cmp!=0) cmp
+        ]
+    ) cmps==[]? (len(a)-len(b)) : cmps[0];
 
 
 // Function: any()
@@ -1001,11 +843,12 @@ function compare_lists(a, b) =
 //   any([[0,0], [1,0]]);   // Returns true.
 function any(l, i=0, succ=false) =
     (i>=len(l) || succ)? succ :
-    any( l, 
-         i+1, 
-         succ = is_list(l[i]) ? any(l[i]) : !(!l[i])
-        );
-
+    any(
+        l, i=i+1, succ=(
+            is_list(l[i])? any(l[i]) :
+            !(!l[i])
+        )
+    );
 
 
 // Function: all()
@@ -1022,12 +865,13 @@ function any(l, i=0, succ=false) =
 //   all([[0,0], [1,0]]);   // Returns false.
 //   all([[1,1], [1,1]]);   // Returns true.
 function all(l, i=0, fail=false) =
-    (i>=len(l) || fail)? !fail :
-    all( l, 
-         i+1,
-         fail = is_list(l[i]) ? !all(l[i]) : !l[i]
-        ) ;
-
+    (i>=len(l) || fail)? (!fail) :
+    all(
+        l, i=i+1, fail=(
+            is_list(l[i])? !all(l[i]) :
+            !l[i]
+        )
+    );
 
 
 // Function: count_true()
@@ -1060,21 +904,6 @@ function count_true(l, nmax=undef, i=0, cnt=0) =
     );
 
 
-function count_true(l, nmax) = 
-    !is_list(l) ? !(!l) ? 1: 0 :
-    let( c = [for( i = 0,
-                   n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
-                   c = !is_undef(n)? n : count_true(l[i], nmax),
-                   s = c;
-                 i<len(l) && (is_undef(nmax) || s<nmax);
-                   i = i+1,
-                   n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
-                   c = !is_undef(n) || (i==len(l))? n : count_true(l[i], nmax-s),
-                   s = s+c
-                 )  s ] )
-    len(c)<len(l)? nmax: c[len(c)-1];
-
-
 
 // Section: Calculus
 
@@ -1092,49 +921,42 @@ function count_true(l, nmax) =
 //   between data[i+1] and data[i], and the data values will be linearly resampled at each corner
 //   to produce a uniform spacing for the derivative estimate.  At the endpoints a single point method
 //   is used: f'(t) = (f(t+h)-f(t))/h.  
-// Arguments:
-//   data = the list of the elements to compute the derivative of.
-//   h = the parametric sampling of the data.
-//   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv(data, h=1, closed=false) =
-    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
-    assert( len(data)>=2, "Input `data` should have at least 2 elements.") 
-    assert( is_finite(h) || is_vector(h), "The sampling `h` must be a number or a list of numbers." )
-    assert( is_num(h) || len(h) == len(data)-(closed?0:1),
-            str("Vector valued `h` must have length ",len(data)-(closed?0:1)))
     is_vector(h) ? _deriv_nonuniform(data, h, closed=closed) :
     let( L = len(data) )
-    closed
-    ? [
+    closed? [
         for(i=[0:1:L-1])
         (data[(i+1)%L]-data[(L+i-1)%L])/2/h
-      ]
-    : let(
-        first = L<3 ? data[1]-data[0] : 
-                3*(data[1]-data[0]) - (data[2]-data[1]),
-        last = L<3 ? data[L-1]-data[L-2]:
-               (data[L-3]-data[L-2])-3*(data[L-2]-data[L-1])
-         ) 
-      [
+    ] :
+    let(
+        first =
+            L<3? data[1]-data[0] : 
+            3*(data[1]-data[0]) - (data[2]-data[1]),
+        last =
+            L<3? data[L-1]-data[L-2]:
+            (data[L-3]-data[L-2])-3*(data[L-2]-data[L-1])
+    ) [
         first/2/h,
         for(i=[1:1:L-2]) (data[i+1]-data[i-1])/2/h,
         last/2/h
-      ];
+    ];
 
 
 function _dnu_calc(f1,fc,f2,h1,h2) =
     let(
         f1 = h2<h1 ? lerp(fc,f1,h2/h1) : f1 , 
         f2 = h1<h2 ? lerp(fc,f2,h1/h2) : f2
-       )
-    (f2-f1) / 2 / min(h1,h2);
+    )
+    (f2-f1) / 2 / min([h1,h2]);
 
 
 function _deriv_nonuniform(data, h, closed) =
-    let( L = len(data) )
-    closed
-    ? [for(i=[0:1:L-1])
-          _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
+    assert(len(h) == len(data)-(closed?0:1),str("Vector valued h must be length ",len(data)-(closed?0:1)))
+    let(
+      L = len(data)
+    )
+    closed? [for(i=[0:1:L-1])
+    	        _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
     : [
         (data[1]-data[0])/h[0],
         for(i=[1:1:L-2]) _dnu_calc(data[i-1],data[i],data[i+1], h[i-1],h[i]),
@@ -1145,23 +967,15 @@ function _deriv_nonuniform(data, h, closed) =
 // Function: deriv2()
 // Usage: deriv2(data, [h], [closed])
 // Description:
-//   Computes a numerical estimate of the second derivative of the data, which may be scalar or vector valued.
+//   Computes a numerical esimate of the second derivative of the data, which may be scalar or vector valued.
 //   The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. 
 //   If the `closed` parameter is true the data is assumed to be defined on a loop with data[0] adjacent to
 //   data[len(data)-1].  For internal points this function uses the approximation 
-//   f''(t) = (f(t-h)-2*f(t)+f(t+h))/h^2.  For the endpoints (when closed=false),
-//   when sufficient points are available, the method is either the four point expression
-//   f''(t) = (2*f(t) - 5*f(t+h) + 4*f(t+2*h) - f(t+3*h))/h^2 or 
+//   f''(t) = (f(t-h)-2*f(t)+f(t+h))/h^2.  For the endpoints (when closed=false) the algorithm
+//   when sufficient points are available the method is either the four point expression
+//   f''(t) = (2*f(t) - 5*f(t+h) + 4*f(t+2*h) - f(t+3*h))/h^2 or if five points are available
 //   f''(t) = (35*f(t) - 104*f(t+h) + 114*f(t+2*h) - 56*f(t+3*h) + 11*f(t+4*h)) / 12h^2
-//   if five points are available.
-// Arguments:
-//   data = the list of the elements to compute the derivative of.
-//   h = the constant parametric sampling of the data.
-//   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv2(data, h=1, closed=false) =
-    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
-    assert( len(data)>=3, "Input list has less than 3 elements.") 
-    assert( is_finite(h), "The sampling `h` must be a number." )
     let( L = len(data) )
     closed? [
         for(i=[0:1:L-1])
@@ -1189,19 +1003,16 @@ function deriv2(data, h=1, closed=false) =
 //   Computes a numerical third derivative estimate of the data, which may be scalar or vector valued.
 //   The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. 
 //   If the `closed` parameter is true the data is assumed to be defined on a loop with data[0] adjacent to
-//   data[len(data)-1].  This function uses a five point derivative estimate, so the input data must include 
-//   at least five points:
+//   data[len(data)-1].  This function uses a five point derivative estimate, so the input must include five points:
 //   f'''(t) = (-f(t-2*h)+2*f(t-h)-2*f(t+h)+f(t+2*h)) / 2h^3.  At the first and second points from the end
 //   the estimates are f'''(t) = (-5*f(t)+18*f(t+h)-24*f(t+2*h)+14*f(t+3*h)-3*f(t+4*h)) / 2h^3 and
 //   f'''(t) = (-3*f(t-h)+10*f(t)-12*f(t+h)+6*f(t+2*h)-f(t+3*h)) / 2h^3.
 function deriv3(data, h=1, closed=false) =
-    assert( is_addable(data) , "Input list is not consistent or not numerical.") 
-    assert( len(data)>=5, "Input list has less than 5 elements.") 
-    assert( is_finite(h), "The sampling `h` must be a number." )
     let(
         L = len(data),
         h3 = h*h*h
     )
+    assert(L>=5, "Need five points for 3rd derivative estimate")
     closed? [
         for(i=[0:1:L-1])
         (-data[(L+i-2)%L]+2*data[(L+i-1)%L]-2*data[(i+1)%L]+data[(i+2)%L])/2/h3
@@ -1225,22 +1036,16 @@ function deriv3(data, h=1, closed=false) =
 // Function: C_times()
 // Usage: C_times(z1,z2)
 // Description:
-//   Multiplies two complex numbers represented by 2D vectors.  
-function C_times(z1,z2) = 
-    assert( is_vector(z1+z2,2), "Complex numbers should be represented by 2D vectors." )
-    [ z1.x*z2.x - z1.y*z2.y, z1.x*z2.y + z1.y*z2.x ];
+//   Multiplies two complex numbers.  
+function C_times(z1,z2) = [z1.x*z2.x-z1.y*z2.y,z1.x*z2.y+z1.y*z2.x];
 
 // Function: C_div()
 // Usage: C_div(z1,z2)
 // Description:
-//   Divides two complex numbers represented by 2D vectors.  
-function C_div(z1,z2) = 
-    assert( is_vector(z1,2) && is_vector(z2), "Complex numbers should be represented by 2D vectors." )
-    assert( !approx(z2,0), "The divisor `z2` cannot be zero." ) 
-    let(den = z2.x*z2.x + z2.y*z2.y)
-    [(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x - z1.x*z2.y)/den];
+//   Divides z1 by z2.  
+function C_div(z1,z2) = let(den = z2.x*z2.x + z2.y*z2.y)
+   [(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x-z1.x*z2.y)/den];
 
-// For the sake of consistence with Q_mul and vmul, C_times should be called C_mul
 
 // Section: Polynomials
 
@@ -1251,70 +1056,38 @@ function C_div(z1,z2) =
 //   Evaluates specified real polynomial, p, at the complex or real input value, z.
 //   The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]
 //   where a_n is the z^n coefficient.  Polynomial coefficients are real.
-//   The result is a number if `z` is a number and a complex number otherwise.
 
 // Note: this should probably be recoded to use division by [1,-z], which is more accurate
 // and avoids overflow with large coefficients, but requires poly_div to support complex coefficients.  
-function polynomial(p, z, _k, _zk, _total) =
-    is_undef(_k)  
-    ?   assert( is_vector(p), "Input polynomial coefficients must be a vector." )
-        let(p = _poly_trim(p))
-        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
-        polynomial( p, 
-                    z, 
-                    len(p)-1, 
-                    is_num(z)? 1 : [1,0], 
-                    is_num(z) ? 0 : [0,0]) 
-    :   _k==0 
-        ? _total + +_zk*p[0]
-        : polynomial( p, 
-                      z, 
-                      _k-1, 
-                      is_num(z) ? _zk*z : C_times(_zk,z), 
-                      _total+_zk*p[_k]);
-
+function polynomial(p, z, k, zk, total) =
+   is_undef(k) ? polynomial(p, z, len(p)-1, is_num(z)? 1 : [1,0], is_num(z) ? 0 : [0,0]) :
+   k==-1 ? total :
+   polynomial(p, z, k-1, is_num(z) ? zk*z : C_times(zk,z), total+zk*p[k]);
 
 
 // Function: poly_mult()
 // Usage
 //   polymult(p,q)
 //   polymult([p1,p2,p3,...])
-// Description:
+// Descriptoin:
 //   Given a list of polynomials represented as real coefficient lists, with the highest degree coefficient first, 
 //   computes the coefficient list of the product polynomial.  
 function poly_mult(p,q) = 
-    is_undef(q) ?
-       assert( is_list(p) 
-               && []==[for(pi=p) if( !is_vector(pi) && pi!=[]) 0], 
-               "Invalid arguments to poly_mult")
-       len(p)==2 ? poly_mult(p[0],p[1]) 
-                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
-    :
-    _poly_trim(
-    [
-    for(n = [len(p)+len(q)-2:-1:0])
-        sum( [for(i=[0:1:len(p)-1])
-             let(j = len(p)+len(q)- 2 - n - i)
-             if (j>=0 && j<len(q)) p[i]*q[j]
-                 ])
-     ]);        
-     
-function poly_mult(p,q) = 
-    is_undef(q) ?
-       len(p)==2 ? poly_mult(p[0],p[1]) 
-                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
-    :
-    assert( is_vector(p) && is_vector(q),"Invalid arguments to poly_mult")
-    _poly_trim(
-               [
-                  for(n = [len(p)+len(q)-2:-1:0])
-                      sum( [for(i=[0:1:len(p)-1])
-                           let(j = len(p)+len(q)- 2 - n - i)
-                           if (j>=0 && j<len(q)) p[i]*q[j]
-                               ])
-                   ]);
+  is_undef(q) ?
+     assert(is_list(p) && (is_vector(p[0]) || p[0]==[]), "Invalid arguments to poly_mult")
+     len(p)==2 ? poly_mult(p[0],p[1]) 
+               : poly_mult(p[0], poly_mult(select(p,1,-1)))
+  :
+  _poly_trim(
+  [
+  for(n = [len(p)+len(q)-2:-1:0])
+      sum( [for(i=[0:1:len(p)-1])
+           let(j = len(p)+len(q)- 2 - n - i)
+           if (j>=0 && j<len(q)) p[i]*q[j]
+               ])
+   ]);        
+
 
-    
 // Function: poly_div()
 // Usage:
 //    [quotient,remainder] = poly_div(n,d)
@@ -1323,19 +1096,15 @@ function poly_mult(p,q) =
 //    a list of two polynomials, [quotient, remainder].  If the division has no remainder then
 //    the zero polynomial [] is returned for the remainder.  Similarly if the quotient is zero
 //    the returned quotient will be [].  
-function poly_div(n,d,q) =
-    is_undef(q) 
-    ?   assert( is_vector(n) && is_vector(d) , "Invalid polynomials." )
-        let( d = _poly_trim(d) )
-        assert( d!=[0] , "Denominator cannot be a zero polynomial." )
-        poly_div(n,d,q=[])
-    :   len(n)<len(d) ? [q,_poly_trim(n)] : 
-        let(
-          t = n[0] / d[0], 
-          newq = concat(q,[t]),
-          newn = [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
-        )  
-        poly_div(newn,d,newq);
+function poly_div(n,d,q=[]) =
+    assert(len(d)>0 && d[0]!=0 , "Denominator is zero or has leading zero coefficient")
+    len(n)<len(d) ? [q,_poly_trim(n)] : 
+    let(
+      t = n[0] / d[0],
+      newq = concat(q,[t]),
+      newn =  [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
+    )  
+    poly_div(newn,d,newq);
 
 
 // Internal Function: _poly_trim()
@@ -1345,8 +1114,8 @@ function poly_div(n,d,q) =
 //    Removes leading zero terms of a polynomial.  By default zeros must be exact,
 //    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);
+  let(  nz = [for(i=[0:1:len(p)-1]) if (!approx(p[i],0,eps)) i])
+  len(nz)==0 ? [] : select(p,nz[0],-1);
 
 
 // Function: poly_add()
@@ -1355,13 +1124,12 @@ function _poly_trim(p,eps=0) =
 // Description:
 //    Computes the sum of two polynomials.  
 function poly_add(p,q) = 
-    assert( is_vector(p) && is_vector(q), "Invalid input polynomial(s)." )
-    let(  plen = len(p),
-          qlen = len(q),
-          long = plen>qlen ? p : q,
-          short = plen>qlen ? q : p
-       )
-     _poly_trim(long + concat(repeat(0,len(long)-len(short)),short));
+  let(  plen = len(p),
+        qlen = len(q),
+        long = plen>qlen ? p : q,
+        short = plen>qlen ? q : p
+     )
+   _poly_trim(long + concat(repeat(0,len(long)-len(short)),short));
 
 
 // Function: poly_roots()
@@ -1382,38 +1150,38 @@ function poly_add(p,q) =
 //
 // Dario Bini. "Numerical computation of polynomial zeros by means of Aberth's Method", Numerical Algorithms, Feb 1996.
 // https://www.researchgate.net/publication/225654837_Numerical_computation_of_polynomial_zeros_by_means_of_Aberth's_method
+
 function poly_roots(p,tol=1e-14,error_bound=false) =
-    assert( is_vector(p), "Invalid polynomial." )
-    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))
-        (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
-    len(p)==2 ? let( solution = [[-p[1]/p[0],0]])            // Linear case needs special handling
-                (error_bound ? [solution,[0]] : solution)
-    : 
-    let(
-        n = len(p)-1,   // polynomial degree
-        pderiv = [for(i=[0:n-1]) p[i]*(n-i)],
-           
-        s = [for(i=[0:1:n]) abs(p[i])*(4*(n-i)+1)],  // Error bound polynomial from Bini
+  assert(p!=[], "Input polynomial must have a nonzero coefficient")
+  assert(is_vector(p), "Input must be a vector")
+  p[0] == 0 ? poly_roots(slice(p,1,-1),tol=tol,error_bound=error_bound) :    // Strip leading zero coefficients
+  p[len(p)-1] == 0 ?                                       // Strip trailing zero coefficients
+      let( solutions = poly_roots(select(p,0,-2),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
+  len(p)==2 ? let( solution = [[-p[1]/p[0],0]])            // Linear case needs special handling
+              (error_bound ? [solution,[0]] : solution)
+  : 
+  let(
+      n = len(p)-1,   // polynomial degree
+      pderiv = [for(i=[0:n-1]) p[i]*(n-i)],
+         
+      s = [for(i=[0:1:n]) abs(p[i])*(4*(n-i)+1)],  // Error bound polynomial from Bini
 
-        // Using method from: http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/0915-24.pdf
-        beta = -p[1]/p[0]/n,
-        r = 1+pow(abs(polynomial(p,beta)/p[0]),1/n),
-        init = [for(i=[0:1:n-1])                // Initial guess for roots       
-                 let(angle = 360*i/n+270/n/PI)
-                 [beta,0]+r*[cos(angle),sin(angle)]
-               ],
-        roots = _poly_roots(p,pderiv,s,init,tol=tol),
-        error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) /
-                                  abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0
-      )
-      error_bound ? [roots, error] : roots;
+      // Using method from: http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/0915-24.pdf
+      beta = -p[1]/p[0]/n,
+      r = 1+pow(abs(polynomial(p,beta)/p[0]),1/n),
+      init = [for(i=[0:1:n-1])                // Initial guess for roots       
+               let(angle = 360*i/n+270/n/PI)
+               [beta,0]+r*[cos(angle),sin(angle)]
+             ],
+      roots = _poly_roots(p,pderiv,s,init,tol=tol),
+      error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) /
+                                abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0
+    )
+    error_bound ? [roots, error] : roots;
 
-// Internal function
 // p = polynomial
 // pderiv = derivative polynomial of p
 // z = current guess for the roots
@@ -1454,16 +1222,12 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) =
 //   tol = tolerance for the complex polynomial root finder
 
 function real_roots(p,eps=undef,tol=1e-14) =
-    assert( is_vector(p), "Invalid polynomial." )
-    let( p = _poly_trim(p,eps=0) )
-    assert( p!=[0], "Input polynomial cannot be zero." )
-    let( 
+   let( 
        roots_err = poly_roots(p,error_bound=true),
        roots = roots_err[0],
        err = roots_err[1]
-    )
-    is_def(eps) 
-    ? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
-    : [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
+   )
+   is_def(eps) ? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
+               : [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
 
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

From 19e5a9504a5c1cb9cb58753e4633f263ad8b0014 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Wed, 29 Jul 2020 21:49:15 +0100
Subject: [PATCH 21/24] input checks in math and new function definitions

---
 common.scad            |  23 +-
 math.scad              | 627 +++++++++++++++++++++++++++--------------
 tests/test_common.scad |  25 +-
 tests/test_math.scad   |  16 +-
 4 files changed, 462 insertions(+), 229 deletions(-)

diff --git a/common.scad b/common.scad
index 0d88e52..2624da5 100644
--- a/common.scad
+++ b/common.scad
@@ -99,6 +99,14 @@ function is_finite(v) = is_num(0*v);
 function is_range(x) = !is_list(x) && is_finite(x[0]+x[1]+x[2]) ;
 
 
+// Function: valid_range()
+// Description:
+//   Returns true if its argument is a valid range (deprecated range is excluded).
+function valid_range(ind) = 
+    is_range(ind) 
+		&& ( ( ind[1]>0 && ind[0]<=ind[2]) || (ind[1]<0 && ind[0]>=ind[2]) );
+
+
 // Function: is_list_of()
 // Usage:
 //   is_list_of(list, pattern)
@@ -133,10 +141,15 @@ function is_list_of(list,pattern) =
 //   is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]); // Returns true
 //   is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]);   // Returns false
 function is_consistent(list) =
-    is_list(list) && is_list_of(list, list[0]);
-
-
-
+    is_list_of(list, _list_pattern(list[0]));
+		
+//Internal function
+//Creates a list with the same structure of `list` with each of its elements substituted by 0.
+// `list` must be a list
+function _list_pattern(list) = 
+    is_list(list)
+    ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0] 
+    : 0;
 
 // Function: same_shape()
 // Usage:
@@ -146,7 +159,7 @@ function is_consistent(list) =
 // Example:
 //   same_shape([3,[4,5]],[7,[3,4]]);   // Returns true
 //   same_shape([3,4,5], [7,[3,4]]);    // Returns false
-function same_shape(a,b) = a*0 == b*0;
+function same_shape(a,b) = _list_pattern(a) == b*0;
 
 
 // Section: Handling `undef`s.
diff --git a/math.scad b/math.scad
index 7629cb1..150a769 100644
--- a/math.scad
+++ b/math.scad
@@ -33,7 +33,10 @@ NAN = acos(2);  // The value `nan`, useful for comparisons.
 //   sqr([3,4]); // Returns: [9,16]
 //   sqr([[1,2],[3,4]]);  // Returns [[1,4],[9,16]]
 //   sqr([[1,2],3]);      // Returns [[1,4],9]
-function sqr(x) = is_list(x) ? [for(val=x) sqr(val)] : x*x;
+function sqr(x) = 
+    is_list(x) ? [for(val=x) sqr(val)] : 
+    is_finite(x) ? x*x :
+    assert(is_finite(x) || is_vector(x), "Input is not neither a number nor a list of numbers.");
 
 
 // Function: log2()
@@ -45,8 +48,11 @@ function sqr(x) = is_list(x) ? [for(val=x) sqr(val)] : x*x;
 //   log2(0.125);  // Returns: -3
 //   log2(16);     // Returns: 4
 //   log2(256);    // Returns: 8
-function log2(x) = ln(x)/ln(2);
+function log2(x) = 
+    assert( is_finite(x), "Input is not a number.")
+    ln(x)/ln(2);
 
+// this may return NAN or INF; should it check x>0 ?
 
 // Function: hypot()
 // Usage:
@@ -60,7 +66,9 @@ function log2(x) = ln(x)/ln(2);
 // Example:
 //   l = hypot(3,4);  // Returns: 5
 //   l = hypot(3,4,5);  // Returns: ~7.0710678119
-function hypot(x,y,z=0) = norm([x,y,z]);
+function hypot(x,y,z=0) = 
+    assert( is_vector([x,y,z]), "Improper number(s).")
+    norm([x,y,z]);
 
 
 // Function: factorial()
@@ -76,11 +84,53 @@ function hypot(x,y,z=0) = norm([x,y,z]);
 //   y = factorial(6);  // Returns: 720
 //   z = factorial(9);  // Returns: 362880
 function factorial(n,d=0) =
-    assert(n>=0 && d>=0, "Factorial is not defined for negative numbers")
+    assert(is_int(n) && is_int(d) && n>=0 && d>=0, "Factorial is not defined for negative numbers")
     assert(d<=n, "d cannot be larger than n")
     product([1,for (i=[n:-1:d+1]) i]);
 
 
+// Function: binomial()
+// Usage:
+//   x = binomial(n);
+// Description:
+//   Returns the binomial coefficients of the integer `n`.  
+// Arguments:
+//   n = The integer to get the binomial coefficients of
+// Example:
+//   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]
+function binomial(n) =
+    assert( is_int(n) && n>0, "Input is not an integer greater than 0.")
+    [for( c = 1, i = 0; 
+        i<=n; 
+         c = c*(n-i)/(i+1), i = i+1
+        ) c ] ;
+
+
+// Function: binomial_coefficient()
+// Usage:
+//   x = binomial_coefficient(n,k);
+// Description:
+//   Returns the k-th binomial coefficient of the integer `n`.  
+// Arguments:
+//   n = The integer to get the binomial coefficient of
+//   k = The binomial coefficient index
+// Example:
+//   x = binomial_coefficient(3,2);  // Returns: 3
+//   y = binomial_coefficient(10,6); // Returns: 210
+function binomial_coefficient(n,k) =
+    assert( is_int(n) && is_int(k), "Some input is not a number.")
+    k < 0 || k > n ? 0 :
+    k ==0 || k ==n ? 1 :
+    let( k = min(k, n-k),
+         b = [for( c = 1, i = 0; 
+                   i<=k; 
+                   c = c*(n-i)/(i+1), i = i+1
+                 ) c] )
+    b[len(b)-1];
+
+
 // Function: lerp()
 // Usage:
 //   x = lerp(a, b, u);
@@ -91,8 +141,8 @@ function factorial(n,d=0) =
 //   If `u` is 0.0, then the value of `a` is returned.
 //   If `u` is 1.0, then the value of `b` is returned.
 //   If `u` is a range, or list of numbers, returns a list of interpolated values.
-//   It is valid to use a `u` value outside the range 0 to 1.  The result will be a predicted
-//   value along the slope formed by `a` and `b`, but not between those two values.
+//   It is valid to use a `u` value outside the range 0 to 1.  The result will be an extrapolation
+//   along the slope formed by `a` and `b`.
 // Arguments:
 //   a = First value or vector.
 //   b = Second value or vector.
@@ -113,9 +163,9 @@ function factorial(n,d=0) =
 //   rainbow(pts) translate($item) circle(d=3,$fn=8);
 function lerp(a,b,u) =
     assert(same_shape(a,b), "Bad or inconsistent inputs to lerp")
-    is_num(u)? (1-u)*a + u*b :
-    assert(!is_undef(u)&&!is_bool(u)&&!is_string(u), "Input u to lerp must be a number, vector, or range.")
-    [for (v = u) lerp(a,b,v)];
+    is_finite(u)? (1-u)*a + u*b :
+    assert(is_finite(u) || is_vector(u) || valid_range(u), "Input u to lerp must be a number, vector, or range.")
+    [for (v = u) (1-v)*a + v*b ];
 
 
 
@@ -124,40 +174,45 @@ function lerp(a,b,u) =
 // Function: sinh()
 // Description: Takes a value `x`, and returns the hyperbolic sine of it.
 function sinh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     (exp(x)-exp(-x))/2;
 
 
 // Function: cosh()
 // Description: Takes a value `x`, and returns the hyperbolic cosine of it.
 function cosh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     (exp(x)+exp(-x))/2;
 
 
 // Function: tanh()
 // Description: Takes a value `x`, and returns the hyperbolic tangent of it.
 function tanh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     sinh(x)/cosh(x);
 
 
 // Function: asinh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic sine of it.
 function asinh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     ln(x+sqrt(x*x+1));
 
 
 // Function: acosh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic cosine of it.
 function acosh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     ln(x+sqrt(x*x-1));
 
 
 // Function: atanh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic tangent of it.
 function atanh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     ln((1+x)/(1-x))/2;
 
 
-
 // Section: Quantization
 
 // Function: quant()
@@ -185,8 +240,11 @@ function atanh(x) =
 //   quant([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,12,12,12]
 //   quant([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[12,12,12]]
 function quant(x,y) =
-    is_list(x)? [for (v=x) quant(v,y)] :
-    floor(x/y+0.5)*y;
+    assert(is_finite(y) && y>0, "The multiple must be a non zero integer.")
+    is_list(x)
+    ?   [for (v=x) quant(v,y)]
+    :   assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
+        floor(x/y+0.5)*y;
 
 
 // Function: quantdn()
@@ -214,8 +272,11 @@ function quant(x,y) =
 //   quantdn([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,9,9,12]
 //   quantdn([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[9,9,12]]
 function quantdn(x,y) =
-    is_list(x)? [for (v=x) quantdn(v,y)] :
-    floor(x/y)*y;
+    assert(is_finite(y) && !approx(y,0), "The multiple must be a non zero integer.")
+    is_list(x)
+    ?    [for (v=x) quantdn(v,y)]
+    :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
+        floor(x/y)*y;
 
 
 // Function: quantup()
@@ -243,8 +304,11 @@ function quantdn(x,y) =
 //   quantup([9,10,10.4,10.5,11,12],3);      // Returns: [9,12,12,12,12,12]
 //   quantup([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,12,12],[12,12,12]]
 function quantup(x,y) =
-    is_list(x)? [for (v=x) quantup(v,y)] :
-    ceil(x/y)*y;
+    assert(is_finite(y) && !approx(y,0), "The multiple must be a non zero integer.")
+    is_list(x)
+    ?    [for (v=x) quantup(v,y)]
+    :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
+        ceil(x/y)*y;
 
 
 // Section: Constraints and Modulos
@@ -264,7 +328,9 @@ function quantup(x,y) =
 //   constrain(0.3, -1, 1);  // Returns: 0.3
 //   constrain(9.1, 0, 9);   // Returns: 9
 //   constrain(-0.1, 0, 9);  // Returns: 0
-function constrain(v, minval, maxval) = min(maxval, max(minval, v));
+function constrain(v, minval, maxval) = 
+    assert( is_finite(v+minval+maxval), "Input must be finite number(s).")
+    min(maxval, max(minval, v));
 
 
 // Function: posmod()
@@ -283,7 +349,9 @@ function constrain(v, minval, maxval) = min(maxval, max(minval, v));
 //   posmod(270,360);   // Returns: 270
 //   posmod(700,360);   // Returns: 340
 //   posmod(3,2.5);     // Returns: 0.5
-function posmod(x,m) = (x%m+m)%m;
+function posmod(x,m) = 
+    assert( is_finite(x) && is_finite(m) && !approx(m,0) , "Input must be finite numbers. The divisor cannot be zero.")
+    (x%m+m)%m;
 
 
 // Function: modang(x)
@@ -299,6 +367,7 @@ function posmod(x,m) = (x%m+m)%m;
 //   modang(270,360);   // Returns: -90
 //   modang(700,360);   // Returns: -20
 function modang(x) =
+    assert( is_finite(x), "Input must be a finite number.")
     let(xx = posmod(x,360)) xx<180? xx : xx-360;
 
 
@@ -306,7 +375,7 @@ function modang(x) =
 // Usage:
 //   modrange(x, y, m, [step])
 // Description:
-//   Returns a normalized list of values from `x` to `y`, by `step`, modulo `m`.  Wraps if `x` > `y`.
+//   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.
@@ -318,6 +387,7 @@ function modang(x) =
 //   modrange(90,270,360, step=-45);  // Returns: [90,45,0,315,270]
 //   modrange(270,90,360, step=-45);  // Returns: [270,225,180,135,90]
 function modrange(x, y, m, step=1) =
+    assert( is_finite(x+y+step+m) && !approx(m,0), "Input must be finite numbers. The module value cannot be zero.")
     let(
         a = posmod(x, m),
         b = posmod(y, m),
@@ -330,20 +400,21 @@ function modrange(x, y, m, step=1) =
 
 // Function: rand_int()
 // Usage:
-//   rand_int(min,max,N,[seed]);
+//   rand_int(minval,maxval,N,[seed]);
 // Description:
-//   Return a list of random integers in the range of min to max, inclusive.
+//   Return a list of random integers in the range of minval to maxval, inclusive.
 // Arguments:
-//   min = Minimum integer value to return.
-//   max = Maximum integer value to return.
+//   minval = Minimum integer value to return.
+//   maxval = Maximum integer value to return.
 //   N = Number of random integers to return.
 //   seed = If given, sets the random number seed.
 // Example:
 //   ints = rand_int(0,100,3);
 //   int = rand_int(-10,10,1)[0];
-function rand_int(min, max, N, seed=undef) =
-    assert(max >= min, "Max value cannot be smaller than min")
-    let (rvect = is_def(seed) ? rands(min,max+1,N,seed) : rands(min,max+1,N))
+function rand_int(minval, maxval, N, seed=undef) =
+    assert( is_finite(minval+maxval+N) && (is_undef(seed) || is_finite(seed) ), "Input must be finite numbers.")
+    assert(maxval >= minval, "Max value cannot be smaller than minval")
+    let (rvect = is_def(seed) ? rands(minval,maxval+1,N,seed) : rands(minval,maxval+1,N))
     [for(entry = rvect) floor(entry)];
 
 
@@ -358,6 +429,7 @@ function rand_int(min, max, N, seed=undef) =
 //   N = Number of random numbers to return.  Default: 1
 //   seed = If given, sets the random number seed.
 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])];
 
@@ -374,6 +446,10 @@ function gaussian_rands(mean, stddev, N=1, seed=undef) =
 //   N = Number of random numbers to return.  Default: 1
 //   seed = If given, sets the random number seed.
 function log_rands(minval, maxval, factor, N=1, seed=undef) =
+    assert( is_finite(minval+maxval+N) 
+		        && (is_undef(seed) || is_finite(seed) )
+						&& factor>0, 
+						"Input must be finite numbers. `factor` should be greater than zero.")
     assert(maxval >= minval, "maxval cannot be smaller than minval")
     let(
         minv = 1-1/pow(factor,minval),
@@ -395,18 +471,18 @@ function gcd(a,b) =
     b==0 ? abs(a) : gcd(b,a % b);
 
 
-// Computes lcm for two scalars
+// Computes lcm for two integers
 function _lcm(a,b) =
-    assert(is_int(a), "Invalid non-integer parameters to lcm")
-    assert(is_int(b), "Invalid non-integer parameters to lcm")
-    assert(a!=0 && b!=0, "Arguments to lcm must be nonzero")
+    assert(is_int(a) && is_int(b), "Invalid non-integer parameters to lcm")
+    assert(a!=0 && b!=0, "Arguments to lcm must be non zero")
     abs(a*b) / gcd(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(slice(a,0,len(a)-2),[lcm(a[len(a)-2],a[len(a)-1])]));
 
 
 // Function: lcm()
@@ -418,12 +494,11 @@ function _lcmlist(a) =
 //   be non-zero integers.  The output is always a positive integer.  It is an error to pass zero
 //   as an argument.  
 function lcm(a,b=[]) =
-    !is_list(a) && !is_list(b) ? _lcm(a,b) : 
-    let(
-        arglist = concat(force_list(a),force_list(b))
-    )
-    assert(len(arglist)>0,"invalid call to lcm with empty list(s)")
-    _lcmlist(arglist);
+    !is_list(a) && !is_list(b) 
+    ?   _lcm(a,b) 
+    :   let( arglist = concat(force_list(a),force_list(b)) )
+        assert(len(arglist)>0, "Invalid call to lcm with empty list(s)")
+        _lcmlist(arglist);
 
 
 
@@ -431,8 +506,9 @@ function lcm(a,b=[]) =
 
 // Function: sum()
 // Description:
-//   Returns the sum of all entries in the given list.
-//   If passed an array of vectors, returns a vector of sums of each part.
+//   Returns the sum of all entries in the given consistent list.
+//   If passed an array of vectors, returns the sum the vectors.
+//   If passed an array of matrices, returns the sum of the matrices.
 //   If passed an empty list, the value of `dflt` will be returned.
 // Arguments:
 //   v = The list to get the sum of.
@@ -441,11 +517,10 @@ function lcm(a,b=[]) =
 //   sum([1,2,3]);  // returns 6.
 //   sum([[1,2,3], [3,4,5], [5,6,7]]);  // returns [9, 12, 15]
 function sum(v, dflt=0) =
-    is_vector(v) ? [for(i=v) 1]*v :
+    is_list(v) && len(v) == 0 ? dflt :
+    is_vector(v) || is_matrix(v)? [for(i=v) 1]*v :
     assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
-    is_vector(v[0]) ? [for(i=v) 1]*v :
-    len(v) == 0 ? dflt :
-                  _sum(v,v[0]*0);
+    _sum(v,v[0]*0);
 
 function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
 
@@ -495,37 +570,51 @@ function sum_of_squares(v) = sum(vmul(v,v));
 // Examples:
 //   v = sum_of_sines(30, [[10,3,0], [5,5.5,60]]);
 function sum_of_sines(a, sines) =
-    sum([
-        for (s = sines) let(
-            ss=point3d(s),
-            v=ss.x*sin(a*ss.y+ss.z)
-        ) v
-    ]);
+    assert( is_finite(a) && is_matrix(sines,undef,3), "Invalid input.")
+    sum([ for (s = sines) 
+            let(
+              ss=point3d(s),
+              v=ss[0]*sin(a*ss[1]+ss[2])
+            ) v
+        ]);
 
 
 // Function: deltas()
 // Description:
 //   Returns a list with the deltas of adjacent entries in the given list.
+//   The list should be a consistent list of numeric components (numbers, vectors, matrix, etc).
 //   Given [a,b,c,d], returns [b-a,c-b,d-c].
 // Arguments:
 //   v = The list to get the deltas of.
 // Example:
 //   deltas([2,5,9,17]);  // returns [3,4,8].
 //   deltas([[1,2,3], [3,6,8], [4,8,11]]);  // returns [[2,4,5], [1,2,3]]
-function deltas(v) = [for (p=pair(v)) p.y-p.x];
+function deltas(v) = 
+    assert( is_consistent(v) && len(v)>1 , "Inconsistent list or with length<=1.")
+    [for (p=pair(v)) p[1]-p[0]] ;
 
 
 // Function: product()
 // Description:
 //   Returns the product of all entries in the given list.
-//   If passed an array of vectors, returns a vector of products of each part.
-//   If passed an array of matrices, returns a the resulting product matrix.
+//   If passed a list of vectors of same dimension, returns a vector of products of each part.
+//   If passed a list of square matrices, returns a the resulting product matrix.
 // Arguments:
 //   v = The list to get the product of.
 // Example:
 //   product([2,3,4]);  // returns 24.
 //   product([[1,2,3], [3,4,5], [5,6,7]]);  // returns [15, 48, 105]
-function product(v, i=0, tot=undef) = i>=len(v)? tot : product(v, i+1, ((tot==undef)? v[i] : is_vector(v[i])? vmul(tot,v[i]) : tot*v[i]));
+function product(v) = 
+    assert( is_vector(v) || is_matrix(v) || ( is_matrix(v[0],square=true) && is_consistent(v)), 
+		        "Invalid input.")
+    _product(v, 1, v[0]);
+
+function _product(v, i=0, _tot) = 
+    i>=len(v) ? _tot :
+    _product( v, 
+              i+1, 
+              ( is_vector(v[i])? vmul(_tot,v[i]) : _tot*v[i] ) );
+               
 
 
 // Function: outer_product()
@@ -534,21 +623,22 @@ function product(v, i=0, tot=undef) = i>=len(v)? tot : product(v, i+1, ((tot==un
 // Usage:
 //   M = outer_product(u,v);
 function outer_product(u,v) =
-  assert(is_vector(u) && is_vector(v))
-  assert(len(u)==len(v))
-  [for(i=[0:len(u)-1]) [for(j=[0:len(u)-1]) u[i]*v[j]]];
+  assert(is_vector(u) && is_vector(v), "The inputs must be vectors.")
+  [for(ui=u) ui*v];
 
 
 // Function: mean()
 // Description:
-//   Returns the arithmatic mean/average of all entries in the given array.
+//   Returns the arithmetic mean/average of all entries in the given array.
 //   If passed a list of vectors, returns a vector of the mean of each part.
 // Arguments:
 //   v = The list of values to get the mean of.
 // Example:
 //   mean([2,3,4]);  // returns 3.
 //   mean([[1,2,3], [3,4,5], [5,6,7]]);  // returns [3, 4, 5]
-function mean(v) = sum(v)/len(v);
+function mean(v) = 
+    assert(is_list(v) && len(v)>0, "Invalid list.")
+    sum(v)/len(v);
 
 
 // Function: median()
@@ -556,18 +646,33 @@ function mean(v) = sum(v)/len(v);
 //   x = median(v);
 // Description:
 //   Given a list of numbers or vectors, finds the median value or midpoint.
-//   If passed a list of vectors, returns the vector of the median of each part.
+//   If passed a list of vectors, returns the vector of the median of each component.
 function median(v) =
-    assert(is_list(v))
-    assert(len(v)>0)
-    is_vector(v[0])? (
-        assert(is_consistent(v))
-        [
-            for (i=idx(v[0]))
-            let(vals = subindex(v,i))
-            (min(vals)+max(vals))/2
-        ]
-    ) : (min(v)+max(v))/2;
+    is_vector(v) ? (min(v)+max(v))/2 :
+    is_matrix(v) ? [for(ti=transpose(v))  (min(ti)+max(ti))/2 ]
+    :   assert(false , "Invalid input.");
+
+// Function: convolve()
+// Usage:
+//   x = convolve(p,q);
+// Description:
+//   Given two vectors, finds the convolution of them.
+//   The length of the returned vector is len(p)+len(q)-1 .
+// Arguments:
+//   p = The first vector.
+//   q = The second vector.
+// Example:
+//   a = convolve([1,1],[1,2,1]); // Returns: [1,3,3,1]
+//   b = convolve([1,2,3],[1,2,1])); // Returns: [1,4,8,8,3]
+function convolve(p,q) =
+    p==[] || q==[] ? [] :
+    assert( is_vector(p) && is_vector(q), "The inputs should be vectors.")
+    let( n = len(p),
+         m = len(q))
+    [for(i=[0:n+m-2], k1 = max(0,i-n+1), k2 = min(i,m-1) )
+       [for(j=[k1:k2]) p[i-j] ] * [for(j=[k1:k2]) q[j] ] 
+    ];
+
 
 
 // Section: Matrix math
@@ -582,7 +687,7 @@ function median(v) =
 //   want to solve Ax=b1 and Ax=b2 that you need to form the matrix transpose([b1,b2]) for the right hand side and then
 //   transpose the returned value.  
 function linear_solve(A,b) =
-    assert(is_matrix(A))
+    assert(is_matrix(A), "Input should be a matrix.")
     let(
         m = len(A),
         n = len(A[0])
@@ -619,8 +724,12 @@ function matrix_inverse(A) =
 // Description:
 //   Returns a submatrix with the specified index ranges or index sets.  
 function submatrix(M,ind1,ind2) =
-    [for(i=ind1) [for(j=ind2) M[i][j] ] ];
-
+    assert( is_matrix(M), "Input must be a matrix." )
+		[for(i=ind1) 
+		    [for(j=ind2) 
+		        assert( ! is_undef(M[i][j]), "Invalid indexing." )
+		        M[i][j] ] ];
+		
 
 // Function: qr_factor()
 // Usage: qr = qr_factor(A)
@@ -628,7 +737,7 @@ function submatrix(M,ind1,ind2) =
 //   Calculates the QR factorization of the input matrix A and returns it as the list [Q,R].  This factorization can be
 //   used to solve linear systems of equations.  
 function qr_factor(A) =
-    assert(is_matrix(A))
+    assert(is_matrix(A), "Input must be a matrix." )
     let(
       m = len(A),
       n = len(A[0])
@@ -659,8 +768,8 @@ function _qr_factor(A,Q, column, m, n) =
 // Function: back_substitute()
 // Usage: back_substitute(R, b, [transpose])
 // Description:
-//   Solves the problem Rx=b where R is an upper triangular square matrix.  No check is made that the lower triangular entries
-//   are actually zero.  If transpose==true then instead solve transpose(R)*x=b.
+//   Solves the problem Rx=b where R is an upper triangular square matrix.  The lower triangular entries of R are
+//   ignored.  If transpose==true then instead solve transpose(R)*x=b.
 //   You can supply a compatible matrix b and it will produce the solution for every column of b.  Note that if you want to
 //   solve Rx=b1 and Rx=b2 you must set b to transpose([b1,b2]) and then take the transpose of the result.  If the matrix
 //   is singular (e.g. has a zero on the diagonal) then it returns [].  
@@ -694,7 +803,9 @@ function back_substitute(R, b, x=[],transpose = false) =
 // Example:
 //   M = [ [6,-2], [1,8] ];
 //   det = det2(M);  // Returns: 50
-function det2(M) = M[0][0] * M[1][1] - M[0][1]*M[1][0];
+function det2(M) = 
+    assert( is_matrix(M,2,2), "Matrix should be 2x2." )
+    M[0][0] * M[1][1] - M[0][1]*M[1][0];
 
 
 // Function: det3()
@@ -706,6 +817,7 @@ function det2(M) = M[0][0] * M[1][1] - M[0][1]*M[1][0];
 //   M = [ [6,4,-2], [1,-2,8], [1,5,7] ];
 //   det = det3(M);  // Returns: -334
 function det3(M) =
+    assert( is_matrix(M,3,3), "Matrix should be 3x3." )
     M[0][0] * (M[1][1]*M[2][2]-M[2][1]*M[1][2]) -
     M[1][0] * (M[0][1]*M[2][2]-M[2][1]*M[0][2]) +
     M[2][0] * (M[0][1]*M[1][2]-M[1][1]*M[0][2]);
@@ -720,21 +832,21 @@ function det3(M) =
 //   M = [ [6,4,-2,9], [1,-2,8,3], [1,5,7,6], [4,2,5,1] ];
 //   det = determinant(M);  // Returns: 2267
 function determinant(M) =
-    assert(len(M)==len(M[0]))
+    assert(is_matrix(M,square=true), "Input should be a square matrix." )
     len(M)==1? M[0][0] :
     len(M)==2? det2(M) :
     len(M)==3? det3(M) :
     sum(
         [for (col=[0:1:len(M)-1])
             ((col%2==0)? 1 : -1) *
-            M[col][0] *
-            determinant(
-                [for (r=[1:1:len(M)-1])
-                    [for (c=[0:1:len(M)-1])
-                        if (c!=col) M[c][r]
+                M[col][0] *
+                determinant(
+                    [for (r=[1:1:len(M)-1])
+                        [for (c=[0:1:len(M)-1])
+                            if (c!=col) M[c][r]
+                        ]
                     ]
-                ]
-            )
+                )
         ]
     );
 
@@ -753,8 +865,11 @@ function determinant(M) =
 //   n = optional width of matrix
 //   square = set to true to require a square matrix.  Default: false        
 function is_matrix(A,m,n,square=false) =
-    is_vector(A[0],n) && is_vector(A*(0*A[0]),m) &&
-    (!square || len(A)==len(A[0]));
+    is_list(A[0]) 
+    && ( let(v = A*A[0]) is_num(0*(v*v)) ) // a matrix of finite numbers 
+    && (is_undef(n) || len(A[0])==n )
+    && (is_undef(m) || len(A)==m )
+    && ( !square || len(A)==len(A[0]));
 
 
 // Section: Comparisons and Logic
@@ -774,11 +889,13 @@ function is_matrix(A,m,n,square=false) =
 //   approx(0.3333,1/3);          // Returns: false
 //   approx(0.3333,1/3,eps=1e-3);  // Returns: true
 //   approx(PI,3.1415926536);     // Returns: true
-function approx(a,b,eps=EPSILON) =
+function approx(a,b,eps=EPSILON) = 
     a==b? true :
     a*0!=b*0? false :
-    is_list(a)? ([for (i=idx(a)) if(!approx(a[i],b[i],eps=eps)) 1] == []) :
-    (abs(a-b) <= eps);
+    is_list(a)
+    ? ([for (i=idx(a)) if( !approx(a[i],b[i],eps=eps)) 1] == [])
+    : is_num(a) && is_num(b) && (abs(a-b) <= eps);
+    
 
 
 function _type_num(x) =
@@ -796,7 +913,7 @@ function _type_num(x) =
 // Description:
 //   Compares two values.  Lists are compared recursively.
 //   Returns <0 if a<b.  Returns >0 if a>b.  Returns 0 if a==b.
-//   If types are not the same, then undef < bool < num < str < list < range.
+//   If types are not the same, then undef < bool < nan < num < str < list < range.
 // Arguments:
 //   a = First value to compare.
 //   b = Second value to compare.
@@ -820,13 +937,14 @@ function compare_vals(a, b) =
 //   a = First list to compare.
 //   b = Second list to compare.
 function compare_lists(a, b) =
-    a==b? 0 : let(
-        cmps = [
-            for(i=[0:1:min(len(a),len(b))-1]) let(
-                cmp = compare_vals(a[i],b[i])
-            ) if(cmp!=0) cmp
-        ]
-    ) cmps==[]? (len(a)-len(b)) : cmps[0];
+    a==b? 0 
+    :   let(
+          cmps = [ for(i=[0:1:min(len(a),len(b))-1]) 
+                      let( cmp = compare_vals(a[i],b[i]) )
+                      if(cmp!=0) cmp 
+                 ]
+           ) 
+        cmps==[]? (len(a)-len(b)) : cmps[0];
 
 
 // Function: any()
@@ -843,12 +961,11 @@ function compare_lists(a, b) =
 //   any([[0,0], [1,0]]);   // Returns true.
 function any(l, i=0, succ=false) =
     (i>=len(l) || succ)? succ :
-    any(
-        l, i=i+1, succ=(
-            is_list(l[i])? any(l[i]) :
-            !(!l[i])
-        )
-    );
+    any( l, 
+         i+1, 
+         succ = is_list(l[i]) ? any(l[i]) : !(!l[i])
+        );
+
 
 
 // Function: all()
@@ -865,13 +982,12 @@ function any(l, i=0, succ=false) =
 //   all([[0,0], [1,0]]);   // Returns false.
 //   all([[1,1], [1,1]]);   // Returns true.
 function all(l, i=0, fail=false) =
-    (i>=len(l) || fail)? (!fail) :
-    all(
-        l, i=i+1, fail=(
-            is_list(l[i])? !all(l[i]) :
-            !l[i]
-        )
-    );
+    (i>=len(l) || fail)? !fail :
+    all( l, 
+         i+1,
+         fail = is_list(l[i]) ? !all(l[i]) : !l[i]
+        ) ;
+
 
 
 // Function: count_true()
@@ -904,6 +1020,21 @@ function count_true(l, nmax=undef, i=0, cnt=0) =
     );
 
 
+function count_true(l, nmax) = 
+    !is_list(l) ? !(!l) ? 1: 0 :
+    let( c = [for( i = 0,
+                   n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
+                   c = !is_undef(n)? n : count_true(l[i], nmax),
+                   s = c;
+                 i<len(l) && (is_undef(nmax) || s<nmax);
+                   i = i+1,
+                   n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
+                   c = !is_undef(n) || (i==len(l))? n : count_true(l[i], nmax-s),
+                   s = s+c
+                 )  s ] )
+    len(c)<len(l)? nmax: c[len(c)-1];
+
+
 
 // Section: Calculus
 
@@ -921,42 +1052,49 @@ function count_true(l, nmax=undef, i=0, cnt=0) =
 //   between data[i+1] and data[i], and the data values will be linearly resampled at each corner
 //   to produce a uniform spacing for the derivative estimate.  At the endpoints a single point method
 //   is used: f'(t) = (f(t+h)-f(t))/h.  
+// Arguments:
+//   data = the list of the elements to compute the derivative of.
+//   h = the parametric sampling of the data.
+//   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv(data, h=1, closed=false) =
+    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
+    assert( len(data)>=2, "Input `data` should have at least 2 elements.") 
+    assert( is_finite(h) || is_vector(h), "The sampling `h` must be a number or a list of numbers." )
+    assert( is_num(h) || len(h) == len(data)-(closed?0:1),
+            str("Vector valued `h` must have length ",len(data)-(closed?0:1)))
     is_vector(h) ? _deriv_nonuniform(data, h, closed=closed) :
     let( L = len(data) )
-    closed? [
+    closed
+    ? [
         for(i=[0:1:L-1])
         (data[(i+1)%L]-data[(L+i-1)%L])/2/h
-    ] :
-    let(
-        first =
-            L<3? data[1]-data[0] : 
-            3*(data[1]-data[0]) - (data[2]-data[1]),
-        last =
-            L<3? data[L-1]-data[L-2]:
-            (data[L-3]-data[L-2])-3*(data[L-2]-data[L-1])
-    ) [
+      ]
+    : let(
+        first = L<3 ? data[1]-data[0] : 
+                3*(data[1]-data[0]) - (data[2]-data[1]),
+        last = L<3 ? data[L-1]-data[L-2]:
+               (data[L-3]-data[L-2])-3*(data[L-2]-data[L-1])
+         ) 
+      [
         first/2/h,
         for(i=[1:1:L-2]) (data[i+1]-data[i-1])/2/h,
         last/2/h
-    ];
+      ];
 
 
 function _dnu_calc(f1,fc,f2,h1,h2) =
     let(
         f1 = h2<h1 ? lerp(fc,f1,h2/h1) : f1 , 
         f2 = h1<h2 ? lerp(fc,f2,h1/h2) : f2
-    )
-    (f2-f1) / 2 / min([h1,h2]);
+       )
+    (f2-f1) / 2 / min(h1,h2);
 
 
 function _deriv_nonuniform(data, h, closed) =
-    assert(len(h) == len(data)-(closed?0:1),str("Vector valued h must be length ",len(data)-(closed?0:1)))
-    let(
-      L = len(data)
-    )
-    closed? [for(i=[0:1:L-1])
-    	        _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
+    let( L = len(data) )
+    closed
+    ? [for(i=[0:1:L-1])
+          _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
     : [
         (data[1]-data[0])/h[0],
         for(i=[1:1:L-2]) _dnu_calc(data[i-1],data[i],data[i+1], h[i-1],h[i]),
@@ -967,15 +1105,23 @@ function _deriv_nonuniform(data, h, closed) =
 // Function: deriv2()
 // Usage: deriv2(data, [h], [closed])
 // Description:
-//   Computes a numerical esimate of the second derivative of the data, which may be scalar or vector valued.
+//   Computes a numerical estimate of the second derivative of the data, which may be scalar or vector valued.
 //   The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. 
 //   If the `closed` parameter is true the data is assumed to be defined on a loop with data[0] adjacent to
 //   data[len(data)-1].  For internal points this function uses the approximation 
-//   f''(t) = (f(t-h)-2*f(t)+f(t+h))/h^2.  For the endpoints (when closed=false) the algorithm
-//   when sufficient points are available the method is either the four point expression
-//   f''(t) = (2*f(t) - 5*f(t+h) + 4*f(t+2*h) - f(t+3*h))/h^2 or if five points are available
+//   f''(t) = (f(t-h)-2*f(t)+f(t+h))/h^2.  For the endpoints (when closed=false),
+//   when sufficient points are available, the method is either the four point expression
+//   f''(t) = (2*f(t) - 5*f(t+h) + 4*f(t+2*h) - f(t+3*h))/h^2 or 
 //   f''(t) = (35*f(t) - 104*f(t+h) + 114*f(t+2*h) - 56*f(t+3*h) + 11*f(t+4*h)) / 12h^2
+//   if five points are available.
+// Arguments:
+//   data = the list of the elements to compute the derivative of.
+//   h = the constant parametric sampling of the data.
+//   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv2(data, h=1, closed=false) =
+    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
+    assert( len(data)>=3, "Input list has less than 3 elements.") 
+    assert( is_finite(h), "The sampling `h` must be a number." )
     let( L = len(data) )
     closed? [
         for(i=[0:1:L-1])
@@ -1003,16 +1149,19 @@ function deriv2(data, h=1, closed=false) =
 //   Computes a numerical third derivative estimate of the data, which may be scalar or vector valued.
 //   The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. 
 //   If the `closed` parameter is true the data is assumed to be defined on a loop with data[0] adjacent to
-//   data[len(data)-1].  This function uses a five point derivative estimate, so the input must include five points:
+//   data[len(data)-1].  This function uses a five point derivative estimate, so the input data must include 
+//   at least five points:
 //   f'''(t) = (-f(t-2*h)+2*f(t-h)-2*f(t+h)+f(t+2*h)) / 2h^3.  At the first and second points from the end
 //   the estimates are f'''(t) = (-5*f(t)+18*f(t+h)-24*f(t+2*h)+14*f(t+3*h)-3*f(t+4*h)) / 2h^3 and
 //   f'''(t) = (-3*f(t-h)+10*f(t)-12*f(t+h)+6*f(t+2*h)-f(t+3*h)) / 2h^3.
 function deriv3(data, h=1, closed=false) =
+    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
+    assert( len(data)>=5, "Input list has less than 5 elements.") 
+    assert( is_finite(h), "The sampling `h` must be a number." )
     let(
         L = len(data),
         h3 = h*h*h
     )
-    assert(L>=5, "Need five points for 3rd derivative estimate")
     closed? [
         for(i=[0:1:L-1])
         (-data[(L+i-2)%L]+2*data[(L+i-1)%L]-2*data[(i+1)%L]+data[(i+2)%L])/2/h3
@@ -1036,75 +1185,122 @@ function deriv3(data, h=1, closed=false) =
 // Function: C_times()
 // Usage: C_times(z1,z2)
 // Description:
-//   Multiplies two complex numbers.  
-function C_times(z1,z2) = [z1.x*z2.x-z1.y*z2.y,z1.x*z2.y+z1.y*z2.x];
+//   Multiplies two complex numbers represented by 2D vectors.  
+function C_times(z1,z2) = 
+    assert( is_vector(z1+z2,2), "Complex numbers should be represented by 2D vectors." )
+    [ z1.x*z2.x - z1.y*z2.y, z1.x*z2.y + z1.y*z2.x ];
 
 // Function: C_div()
 // Usage: C_div(z1,z2)
 // Description:
-//   Divides z1 by z2.  
-function C_div(z1,z2) = let(den = z2.x*z2.x + z2.y*z2.y)
-   [(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x-z1.x*z2.y)/den];
+//   Divides two complex numbers represented by 2D vectors.  
+function C_div(z1,z2) = 
+    assert( is_vector(z1,2) && is_vector(z2), "Complex numbers should be represented by 2D vectors." )
+    assert( !approx(z2,0), "The divisor `z2` cannot be zero." ) 
+    let(den = z2.x*z2.x + z2.y*z2.y)
+    [(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x - z1.x*z2.y)/den];
 
+// For the sake of consistence with Q_mul and vmul, C_times should be called C_mul
 
 // Section: Polynomials
 
-// Function: polynomial()
+// Function: polynomial() 
 // Usage:
 //   polynomial(p, z)
 // Description:
 //   Evaluates specified real polynomial, p, at the complex or real input value, z.
 //   The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]
 //   where a_n is the z^n coefficient.  Polynomial coefficients are real.
+//   The result is a number if `z` is a number and a complex number otherwise.
 
 // Note: this should probably be recoded to use division by [1,-z], which is more accurate
 // and avoids overflow with large coefficients, but requires poly_div to support complex coefficients.  
-function polynomial(p, z, k, zk, total) =
-   is_undef(k) ? polynomial(p, z, len(p)-1, is_num(z)? 1 : [1,0], is_num(z) ? 0 : [0,0]) :
-   k==-1 ? total :
-   polynomial(p, z, k-1, is_num(z) ? zk*z : C_times(zk,z), total+zk*p[k]);
+function polynomial(p, z, _k, _zk, _total) =
+    is_undef(_k)  
+    ?   echo(poly=p) assert( is_vector(p), "Input polynomial coefficients must be a vector." )
+        let(p = _poly_trim(p))
+        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
+        polynomial( p, 
+                    z, 
+                    len(p)-1, 
+                    is_num(z)? 1 : [1,0], 
+                    is_num(z) ? 0 : [0,0]) 
+    :   _k==0 
+        ? _total + +_zk*p[0]
+        : polynomial( p, 
+                      z, 
+                      _k-1, 
+                      is_num(z) ? _zk*z : C_times(_zk,z), 
+                      _total+_zk*p[_k]);
 
+function polynomial(p,z,k,total) =
+     is_undef(k)
+   ?    assert( is_vector(p) , "Input polynomial coefficients must be a vector." )
+        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
+        polynomial( _poly_trim(p), z, 0, is_num(z) ? 0 : [0,0])
+   : k==len(p) ? total
+   : polynomial(p,z,k+1, is_num(z) ? total*z+p[k] : C_times(total,z)+[p[k],0]);
 
 // Function: poly_mult()
 // Usage
 //   polymult(p,q)
 //   polymult([p1,p2,p3,...])
-// Descriptoin:
+// Description:
 //   Given a list of polynomials represented as real coefficient lists, with the highest degree coefficient first, 
 //   computes the coefficient list of the product polynomial.  
 function poly_mult(p,q) = 
-  is_undef(q) ?
-     assert(is_list(p) && (is_vector(p[0]) || p[0]==[]), "Invalid arguments to poly_mult")
-     len(p)==2 ? poly_mult(p[0],p[1]) 
-               : poly_mult(p[0], poly_mult(select(p,1,-1)))
-  :
-  _poly_trim(
-  [
-  for(n = [len(p)+len(q)-2:-1:0])
-      sum( [for(i=[0:1:len(p)-1])
-           let(j = len(p)+len(q)- 2 - n - i)
-           if (j>=0 && j<len(q)) p[i]*q[j]
-               ])
-   ]);        
-
+    is_undef(q) ?
+       assert( is_list(p) 
+               && []==[for(pi=p) if( !is_vector(pi) && pi!=[]) 0], 
+               "Invalid arguments to poly_mult")
+       len(p)==2 ? poly_mult(p[0],p[1]) 
+                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
+    :
+    _poly_trim(
+    [
+    for(n = [len(p)+len(q)-2:-1:0])
+        sum( [for(i=[0:1:len(p)-1])
+             let(j = len(p)+len(q)- 2 - n - i)
+             if (j>=0 && j<len(q)) p[i]*q[j]
+                 ])
+     ]);        
+     
+function poly_mult(p,q) = 
+    is_undef(q) ?
+       len(p)==2 ? poly_mult(p[0],p[1]) 
+                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
+    :
+    assert( is_vector(p) && is_vector(q),"Invalid arguments to poly_mult")
+    _poly_trim( [
+                  for(n = [len(p)+len(q)-2:-1:0])
+                      sum( [for(i=[0:1:len(p)-1])
+                           let(j = len(p)+len(q)- 2 - n - i)
+                           if (j>=0 && j<len(q)) p[i]*q[j]
+                               ])
+                   ]);
 
+    
 // Function: poly_div()
 // Usage:
 //    [quotient,remainder] = poly_div(n,d)
 // Description:
 //    Computes division of the numerator polynomial by the denominator polynomial and returns
 //    a list of two polynomials, [quotient, remainder].  If the division has no remainder then
-//    the zero polynomial [] is returned for the remainder.  Similarly if the quotient is zero
-//    the returned quotient will be [].  
-function poly_div(n,d,q=[]) =
-    assert(len(d)>0 && d[0]!=0 , "Denominator is zero or has leading zero coefficient")
-    len(n)<len(d) ? [q,_poly_trim(n)] : 
-    let(
-      t = n[0] / d[0],
-      newq = concat(q,[t]),
-      newn =  [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
-    )  
-    poly_div(newn,d,newq);
+//    the zero polynomial [0] is returned for the remainder.  Similarly if the quotient is zero
+//    the returned quotient will be [0].  
+function poly_div(n,d,q) =
+    is_undef(q) 
+    ?   assert( is_vector(n) && is_vector(d) , "Invalid polynomials." )
+        let( d = _poly_trim(d) )
+        assert( d!=[0] , "Denominator cannot be a zero polynomial." )
+        poly_div(n,d,q=[])
+    :   len(n)<len(d) ? [q==[]? [0]: q,_poly_trim(n)] : 
+        let(
+          t = n[0] / d[0], 
+          newq = concat(q,[t]),
+          newn = [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
+        )  
+        poly_div(newn,d,newq);
 
 
 // Internal Function: _poly_trim()
@@ -1114,8 +1310,8 @@ function poly_div(n,d,q=[]) =
 //    Removes leading zero terms of a polynomial.  By default zeros must be exact,
 //    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 ? [] : select(p,nz[0],-1);
+    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);
 
 
 // Function: poly_add()
@@ -1124,12 +1320,13 @@ function _poly_trim(p,eps=0) =
 // Description:
 //    Computes the sum of two polynomials.  
 function poly_add(p,q) = 
-  let(  plen = len(p),
-        qlen = len(q),
-        long = plen>qlen ? p : q,
-        short = plen>qlen ? q : p
-     )
-   _poly_trim(long + concat(repeat(0,len(long)-len(short)),short));
+    assert( is_vector(p) && is_vector(q), "Invalid input polynomial(s)." )
+    let(  plen = len(p),
+          qlen = len(q),
+          long = plen>qlen ? p : q,
+          short = plen>qlen ? q : p
+       )
+     _poly_trim(long + concat(repeat(0,len(long)-len(short)),short));
 
 
 // Function: poly_roots()
@@ -1150,38 +1347,38 @@ function poly_add(p,q) =
 //
 // Dario Bini. "Numerical computation of polynomial zeros by means of Aberth's Method", Numerical Algorithms, Feb 1996.
 // https://www.researchgate.net/publication/225654837_Numerical_computation_of_polynomial_zeros_by_means_of_Aberth's_method
-
 function poly_roots(p,tol=1e-14,error_bound=false) =
-  assert(p!=[], "Input polynomial must have a nonzero coefficient")
-  assert(is_vector(p), "Input must be a vector")
-  p[0] == 0 ? poly_roots(slice(p,1,-1),tol=tol,error_bound=error_bound) :    // Strip leading zero coefficients
-  p[len(p)-1] == 0 ?                                       // Strip trailing zero coefficients
-      let( solutions = poly_roots(select(p,0,-2),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
-  len(p)==2 ? let( solution = [[-p[1]/p[0],0]])            // Linear case needs special handling
-              (error_bound ? [solution,[0]] : solution)
-  : 
-  let(
-      n = len(p)-1,   // polynomial degree
-      pderiv = [for(i=[0:n-1]) p[i]*(n-i)],
-         
-      s = [for(i=[0:1:n]) abs(p[i])*(4*(n-i)+1)],  // Error bound polynomial from Bini
+    assert( is_vector(p), "Invalid polynomial." )
+    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))
+        (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
+    len(p)==2 ? let( solution = [[-p[1]/p[0],0]])            // Linear case needs special handling
+                (error_bound ? [solution,[0]] : solution)
+    : 
+    let(
+        n = len(p)-1,   // polynomial degree
+        pderiv = [for(i=[0:n-1]) p[i]*(n-i)],
+           
+        s = [for(i=[0:1:n]) abs(p[i])*(4*(n-i)+1)],  // Error bound polynomial from Bini
 
-      // Using method from: http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/0915-24.pdf
-      beta = -p[1]/p[0]/n,
-      r = 1+pow(abs(polynomial(p,beta)/p[0]),1/n),
-      init = [for(i=[0:1:n-1])                // Initial guess for roots       
-               let(angle = 360*i/n+270/n/PI)
-               [beta,0]+r*[cos(angle),sin(angle)]
-             ],
-      roots = _poly_roots(p,pderiv,s,init,tol=tol),
-      error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) /
-                                abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0
-    )
-    error_bound ? [roots, error] : roots;
+        // Using method from: http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/0915-24.pdf
+        beta = -p[1]/p[0]/n,
+        r = 1+pow(abs(polynomial(p,beta)/p[0]),1/n),
+        init = [for(i=[0:1:n-1])                // Initial guess for roots       
+                 let(angle = 360*i/n+270/n/PI)
+                 [beta,0]+r*[cos(angle),sin(angle)]
+               ],
+        roots = _poly_roots(p,pderiv,s,init,tol=tol),
+        error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) /
+                                  abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0
+      )
+      error_bound ? [roots, error] : roots;
 
+// Internal function
 // p = polynomial
 // pderiv = derivative polynomial of p
 // z = current guess for the roots
@@ -1222,12 +1419,16 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) =
 //   tol = tolerance for the complex polynomial root finder
 
 function real_roots(p,eps=undef,tol=1e-14) =
-   let( 
+    assert( is_vector(p), "Invalid polynomial." )
+    let( p = _poly_trim(p,eps=0) )
+    assert( p!=[0], "Input polynomial cannot be zero." )
+    let( 
        roots_err = poly_roots(p,error_bound=true),
        roots = roots_err[0],
        err = roots_err[1]
-   )
-   is_def(eps) ? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
-               : [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
+    )
+    is_def(eps) 
+    ? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
+    : [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
 
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/tests/test_common.scad b/tests/test_common.scad
index 1cad5e3..808b9c4 100644
--- a/tests/test_common.scad
+++ b/tests/test_common.scad
@@ -169,7 +169,6 @@ module test_is_range() {
     assert(!is_range(5));
     assert(!is_range(INF));
     assert(!is_range(-INF));
-    assert(!is_nan(NAN));
     assert(!is_range(""));
     assert(!is_range("foo"));
     assert(!is_range([]));
@@ -179,7 +178,23 @@ module test_is_range() {
     assert(!is_range([3:4:"a"]));
     assert(is_range([3:1:5]));
 }
-test_is_nan();
+test_is_range();
+
+
+module test_valid_range() {
+    assert(valid_range([0:0]));
+    assert(valid_range([0:1:0]));
+    assert(valid_range([0:1:10]));
+    assert(valid_range([0.1:1.1:2.1]));
+    assert(valid_range([0:-1:0]));
+    assert(valid_range([10:-1:0]));
+    assert(valid_range([2.1:-1.1:0.1]));
+    assert(!valid_range([10:1:0]));
+    assert(!valid_range([2.1:1.1:0.1]));
+    assert(!valid_range([0:-1:10]));
+    assert(!valid_range([0.1:-1.1:2.1]));
+}
+test_valid_range();
 
 
 module test_is_list_of() {
@@ -192,10 +207,14 @@ module test_is_list_of() {
 }
 test_is_list_of();
 
-
 module test_is_consistent() {
+    assert(is_consistent([]));
+    assert(is_consistent([[],[]]));
     assert(is_consistent([3,4,5]));
     assert(is_consistent([[3,4],[4,5],[6,7]]));
+    assert(is_consistent([[[3],4],[[4],5]]));
+    assert(!is_consistent(5));
+    assert(!is_consistent(undef));
     assert(!is_consistent([[3,4,5],[3,4]]));
     assert(is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]));
     assert(!is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]));
diff --git a/tests/test_math.scad b/tests/test_math.scad
index 3233e6f..f659d34 100644
--- a/tests/test_math.scad
+++ b/tests/test_math.scad
@@ -782,8 +782,8 @@ test_deriv3();
 
 
 module test_polynomial(){
-  assert_equal(polynomial([],12),0);
-  assert_equal(polynomial([],[12,4]),[0,0]);
+  assert_equal(polynomial([0],12),0);
+  assert_equal(polynomial([0],[12,4]),[0,0]);
   assert_equal(polynomial([1,2,3,4],3),58);
   assert_equal(polynomial([1,2,3,4],[3,-1]),[47,-41]);
   assert_equal(polynomial([0,0,2],4),2);
@@ -879,17 +879,17 @@ test_qr_factor();
 
 module test_poly_mult(){
   assert_equal(poly_mult([3,2,1],[4,5,6,7]),[12,23,32,38,20,7]);
-  assert_equal(poly_mult([3,2,1],[]),[]);
+  assert_equal(poly_mult([3,2,1],[0]),[0]);
   assert_equal(poly_mult([[1,2],[3,4],[5,6]]), [15,68,100,48]);
-  assert_equal(poly_mult([[1,2],[],[5,6]]), []);
-  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[]);
+  assert_equal(poly_mult([[1,2],[0],[5,6]]), [0]);
+  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[0]);
 }
 test_poly_mult();
 
  
 module test_poly_div(){
-  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[]]);
-  assert_equal(poly_div([1,2,3,4],[1,2,3,4,5]), [[], [1,2,3,4]]);
+  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[0]]);
+  assert_equal(poly_div([1,2,3,4],[1,2,3,4,5]), [[0], [1,2,3,4]]);
   assert_equal(poly_div(poly_add(poly_mult([1,2,3,4],[2,0,2]), [1,1,2]), [1,2,3,4]), [[2,0,2],[1,1,2]]);
   assert_equal(poly_div([1,2,3,4], [1,-3]), [[1,5,18],[58]]);
 }
@@ -899,7 +899,7 @@ test_poly_div();
 module test_poly_add(){
   assert_equal(poly_add([2,3,4],[3,4,5,6]),[3,6,8,10]);
   assert_equal(poly_add([1,2,3,4],[-1,-2,3,4]), [6,8]);
-  assert_equal(poly_add([1,2,3],-[1,2,3]),[]);
+  assert_equal(poly_add([1,2,3],-[1,2,3]),[0]);
 }
 test_poly_add();
 

From babc10d60dae0f6b5c9eb43c4c7ec47c7971b2e8 Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Wed, 29 Jul 2020 21:50:22 +0100
Subject: [PATCH 22/24] Revert "input checks in math and new function
 definitions"

This reverts commit 19e5a9504a5c1cb9cb58753e4633f263ad8b0014.
---
 common.scad            |  23 +-
 math.scad              | 627 ++++++++++++++---------------------------
 tests/test_common.scad |  25 +-
 tests/test_math.scad   |  16 +-
 4 files changed, 229 insertions(+), 462 deletions(-)

diff --git a/common.scad b/common.scad
index 2624da5..0d88e52 100644
--- a/common.scad
+++ b/common.scad
@@ -99,14 +99,6 @@ function is_finite(v) = is_num(0*v);
 function is_range(x) = !is_list(x) && is_finite(x[0]+x[1]+x[2]) ;
 
 
-// Function: valid_range()
-// Description:
-//   Returns true if its argument is a valid range (deprecated range is excluded).
-function valid_range(ind) = 
-    is_range(ind) 
-		&& ( ( ind[1]>0 && ind[0]<=ind[2]) || (ind[1]<0 && ind[0]>=ind[2]) );
-
-
 // Function: is_list_of()
 // Usage:
 //   is_list_of(list, pattern)
@@ -141,15 +133,10 @@ function is_list_of(list,pattern) =
 //   is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]); // Returns true
 //   is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]);   // Returns false
 function is_consistent(list) =
-    is_list_of(list, _list_pattern(list[0]));
-		
-//Internal function
-//Creates a list with the same structure of `list` with each of its elements substituted by 0.
-// `list` must be a list
-function _list_pattern(list) = 
-    is_list(list)
-    ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0] 
-    : 0;
+    is_list(list) && is_list_of(list, list[0]);
+
+
+
 
 // Function: same_shape()
 // Usage:
@@ -159,7 +146,7 @@ function _list_pattern(list) =
 // Example:
 //   same_shape([3,[4,5]],[7,[3,4]]);   // Returns true
 //   same_shape([3,4,5], [7,[3,4]]);    // Returns false
-function same_shape(a,b) = _list_pattern(a) == b*0;
+function same_shape(a,b) = a*0 == b*0;
 
 
 // Section: Handling `undef`s.
diff --git a/math.scad b/math.scad
index 150a769..7629cb1 100644
--- a/math.scad
+++ b/math.scad
@@ -33,10 +33,7 @@ NAN = acos(2);  // The value `nan`, useful for comparisons.
 //   sqr([3,4]); // Returns: [9,16]
 //   sqr([[1,2],[3,4]]);  // Returns [[1,4],[9,16]]
 //   sqr([[1,2],3]);      // Returns [[1,4],9]
-function sqr(x) = 
-    is_list(x) ? [for(val=x) sqr(val)] : 
-    is_finite(x) ? x*x :
-    assert(is_finite(x) || is_vector(x), "Input is not neither a number nor a list of numbers.");
+function sqr(x) = is_list(x) ? [for(val=x) sqr(val)] : x*x;
 
 
 // Function: log2()
@@ -48,11 +45,8 @@ function sqr(x) =
 //   log2(0.125);  // Returns: -3
 //   log2(16);     // Returns: 4
 //   log2(256);    // Returns: 8
-function log2(x) = 
-    assert( is_finite(x), "Input is not a number.")
-    ln(x)/ln(2);
+function log2(x) = ln(x)/ln(2);
 
-// this may return NAN or INF; should it check x>0 ?
 
 // Function: hypot()
 // Usage:
@@ -66,9 +60,7 @@ function log2(x) =
 // Example:
 //   l = hypot(3,4);  // Returns: 5
 //   l = hypot(3,4,5);  // Returns: ~7.0710678119
-function hypot(x,y,z=0) = 
-    assert( is_vector([x,y,z]), "Improper number(s).")
-    norm([x,y,z]);
+function hypot(x,y,z=0) = norm([x,y,z]);
 
 
 // Function: factorial()
@@ -84,53 +76,11 @@ function hypot(x,y,z=0) =
 //   y = factorial(6);  // Returns: 720
 //   z = factorial(9);  // Returns: 362880
 function factorial(n,d=0) =
-    assert(is_int(n) && is_int(d) && n>=0 && d>=0, "Factorial is not defined for negative numbers")
+    assert(n>=0 && d>=0, "Factorial is not defined for negative numbers")
     assert(d<=n, "d cannot be larger than n")
     product([1,for (i=[n:-1:d+1]) i]);
 
 
-// Function: binomial()
-// Usage:
-//   x = binomial(n);
-// Description:
-//   Returns the binomial coefficients of the integer `n`.  
-// Arguments:
-//   n = The integer to get the binomial coefficients of
-// Example:
-//   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]
-function binomial(n) =
-    assert( is_int(n) && n>0, "Input is not an integer greater than 0.")
-    [for( c = 1, i = 0; 
-        i<=n; 
-         c = c*(n-i)/(i+1), i = i+1
-        ) c ] ;
-
-
-// Function: binomial_coefficient()
-// Usage:
-//   x = binomial_coefficient(n,k);
-// Description:
-//   Returns the k-th binomial coefficient of the integer `n`.  
-// Arguments:
-//   n = The integer to get the binomial coefficient of
-//   k = The binomial coefficient index
-// Example:
-//   x = binomial_coefficient(3,2);  // Returns: 3
-//   y = binomial_coefficient(10,6); // Returns: 210
-function binomial_coefficient(n,k) =
-    assert( is_int(n) && is_int(k), "Some input is not a number.")
-    k < 0 || k > n ? 0 :
-    k ==0 || k ==n ? 1 :
-    let( k = min(k, n-k),
-         b = [for( c = 1, i = 0; 
-                   i<=k; 
-                   c = c*(n-i)/(i+1), i = i+1
-                 ) c] )
-    b[len(b)-1];
-
-
 // Function: lerp()
 // Usage:
 //   x = lerp(a, b, u);
@@ -141,8 +91,8 @@ function binomial_coefficient(n,k) =
 //   If `u` is 0.0, then the value of `a` is returned.
 //   If `u` is 1.0, then the value of `b` is returned.
 //   If `u` is a range, or list of numbers, returns a list of interpolated values.
-//   It is valid to use a `u` value outside the range 0 to 1.  The result will be an extrapolation
-//   along the slope formed by `a` and `b`.
+//   It is valid to use a `u` value outside the range 0 to 1.  The result will be a predicted
+//   value along the slope formed by `a` and `b`, but not between those two values.
 // Arguments:
 //   a = First value or vector.
 //   b = Second value or vector.
@@ -163,9 +113,9 @@ function binomial_coefficient(n,k) =
 //   rainbow(pts) translate($item) circle(d=3,$fn=8);
 function lerp(a,b,u) =
     assert(same_shape(a,b), "Bad or inconsistent inputs to lerp")
-    is_finite(u)? (1-u)*a + u*b :
-    assert(is_finite(u) || is_vector(u) || valid_range(u), "Input u to lerp must be a number, vector, or range.")
-    [for (v = u) (1-v)*a + v*b ];
+    is_num(u)? (1-u)*a + u*b :
+    assert(!is_undef(u)&&!is_bool(u)&&!is_string(u), "Input u to lerp must be a number, vector, or range.")
+    [for (v = u) lerp(a,b,v)];
 
 
 
@@ -174,45 +124,40 @@ function lerp(a,b,u) =
 // Function: sinh()
 // Description: Takes a value `x`, and returns the hyperbolic sine of it.
 function sinh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     (exp(x)-exp(-x))/2;
 
 
 // Function: cosh()
 // Description: Takes a value `x`, and returns the hyperbolic cosine of it.
 function cosh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     (exp(x)+exp(-x))/2;
 
 
 // Function: tanh()
 // Description: Takes a value `x`, and returns the hyperbolic tangent of it.
 function tanh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     sinh(x)/cosh(x);
 
 
 // Function: asinh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic sine of it.
 function asinh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     ln(x+sqrt(x*x+1));
 
 
 // Function: acosh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic cosine of it.
 function acosh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     ln(x+sqrt(x*x-1));
 
 
 // Function: atanh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic tangent of it.
 function atanh(x) =
-    assert(is_finite(x), "The input must be a finite number.")
     ln((1+x)/(1-x))/2;
 
 
+
 // Section: Quantization
 
 // Function: quant()
@@ -240,11 +185,8 @@ function atanh(x) =
 //   quant([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,12,12,12]
 //   quant([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[12,12,12]]
 function quant(x,y) =
-    assert(is_finite(y) && y>0, "The multiple must be a non zero integer.")
-    is_list(x)
-    ?   [for (v=x) quant(v,y)]
-    :   assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
-        floor(x/y+0.5)*y;
+    is_list(x)? [for (v=x) quant(v,y)] :
+    floor(x/y+0.5)*y;
 
 
 // Function: quantdn()
@@ -272,11 +214,8 @@ function quant(x,y) =
 //   quantdn([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,9,9,12]
 //   quantdn([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[9,9,12]]
 function quantdn(x,y) =
-    assert(is_finite(y) && !approx(y,0), "The multiple must be a non zero integer.")
-    is_list(x)
-    ?    [for (v=x) quantdn(v,y)]
-    :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
-        floor(x/y)*y;
+    is_list(x)? [for (v=x) quantdn(v,y)] :
+    floor(x/y)*y;
 
 
 // Function: quantup()
@@ -304,11 +243,8 @@ function quantdn(x,y) =
 //   quantup([9,10,10.4,10.5,11,12],3);      // Returns: [9,12,12,12,12,12]
 //   quantup([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,12,12],[12,12,12]]
 function quantup(x,y) =
-    assert(is_finite(y) && !approx(y,0), "The multiple must be a non zero integer.")
-    is_list(x)
-    ?    [for (v=x) quantup(v,y)]
-    :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
-        ceil(x/y)*y;
+    is_list(x)? [for (v=x) quantup(v,y)] :
+    ceil(x/y)*y;
 
 
 // Section: Constraints and Modulos
@@ -328,9 +264,7 @@ function quantup(x,y) =
 //   constrain(0.3, -1, 1);  // Returns: 0.3
 //   constrain(9.1, 0, 9);   // Returns: 9
 //   constrain(-0.1, 0, 9);  // Returns: 0
-function constrain(v, minval, maxval) = 
-    assert( is_finite(v+minval+maxval), "Input must be finite number(s).")
-    min(maxval, max(minval, v));
+function constrain(v, minval, maxval) = min(maxval, max(minval, v));
 
 
 // Function: posmod()
@@ -349,9 +283,7 @@ function constrain(v, minval, maxval) =
 //   posmod(270,360);   // Returns: 270
 //   posmod(700,360);   // Returns: 340
 //   posmod(3,2.5);     // Returns: 0.5
-function posmod(x,m) = 
-    assert( is_finite(x) && is_finite(m) && !approx(m,0) , "Input must be finite numbers. The divisor cannot be zero.")
-    (x%m+m)%m;
+function posmod(x,m) = (x%m+m)%m;
 
 
 // Function: modang(x)
@@ -367,7 +299,6 @@ function posmod(x,m) =
 //   modang(270,360);   // Returns: -90
 //   modang(700,360);   // Returns: -20
 function modang(x) =
-    assert( is_finite(x), "Input must be a finite number.")
     let(xx = posmod(x,360)) xx<180? xx : xx-360;
 
 
@@ -375,7 +306,7 @@ function modang(x) =
 // Usage:
 //   modrange(x, y, m, [step])
 // Description:
-//   Returns a normalized list of numbers from `x` to `y`, by `step`, modulo `m`.  Wraps if `x` > `y`.
+//   Returns a normalized list of values 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.
@@ -387,7 +318,6 @@ function modang(x) =
 //   modrange(90,270,360, step=-45);  // Returns: [90,45,0,315,270]
 //   modrange(270,90,360, step=-45);  // Returns: [270,225,180,135,90]
 function modrange(x, y, m, step=1) =
-    assert( is_finite(x+y+step+m) && !approx(m,0), "Input must be finite numbers. The module value cannot be zero.")
     let(
         a = posmod(x, m),
         b = posmod(y, m),
@@ -400,21 +330,20 @@ function modrange(x, y, m, step=1) =
 
 // Function: rand_int()
 // Usage:
-//   rand_int(minval,maxval,N,[seed]);
+//   rand_int(min,max,N,[seed]);
 // Description:
-//   Return a list of random integers in the range of minval to maxval, inclusive.
+//   Return a list of random integers in the range of min to max, inclusive.
 // Arguments:
-//   minval = Minimum integer value to return.
-//   maxval = Maximum integer value to return.
+//   min = Minimum integer value to return.
+//   max = Maximum integer value to return.
 //   N = Number of random integers to return.
 //   seed = If given, sets the random number seed.
 // Example:
 //   ints = rand_int(0,100,3);
 //   int = rand_int(-10,10,1)[0];
-function rand_int(minval, maxval, N, seed=undef) =
-    assert( is_finite(minval+maxval+N) && (is_undef(seed) || is_finite(seed) ), "Input must be finite numbers.")
-    assert(maxval >= minval, "Max value cannot be smaller than minval")
-    let (rvect = is_def(seed) ? rands(minval,maxval+1,N,seed) : rands(minval,maxval+1,N))
+function rand_int(min, max, N, seed=undef) =
+    assert(max >= min, "Max value cannot be smaller than min")
+    let (rvect = is_def(seed) ? rands(min,max+1,N,seed) : rands(min,max+1,N))
     [for(entry = rvect) floor(entry)];
 
 
@@ -429,7 +358,6 @@ function rand_int(minval, maxval, N, seed=undef) =
 //   N = Number of random numbers to return.  Default: 1
 //   seed = If given, sets the random number seed.
 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])];
 
@@ -446,10 +374,6 @@ function gaussian_rands(mean, stddev, N=1, seed=undef) =
 //   N = Number of random numbers to return.  Default: 1
 //   seed = If given, sets the random number seed.
 function log_rands(minval, maxval, factor, N=1, seed=undef) =
-    assert( is_finite(minval+maxval+N) 
-		        && (is_undef(seed) || is_finite(seed) )
-						&& factor>0, 
-						"Input must be finite numbers. `factor` should be greater than zero.")
     assert(maxval >= minval, "maxval cannot be smaller than minval")
     let(
         minv = 1-1/pow(factor,minval),
@@ -471,18 +395,18 @@ function gcd(a,b) =
     b==0 ? abs(a) : gcd(b,a % b);
 
 
-// Computes lcm for two integers
+// Computes lcm for two scalars
 function _lcm(a,b) =
-    assert(is_int(a) && is_int(b), "Invalid non-integer parameters to lcm")
-    assert(a!=0 && b!=0, "Arguments to lcm must be non zero")
+    assert(is_int(a), "Invalid non-integer parameters to lcm")
+    assert(is_int(b), "Invalid non-integer parameters to lcm")
+    assert(a!=0 && b!=0, "Arguments to lcm must be nonzero")
     abs(a*b) / gcd(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(slice(a,0,len(a)-2),[lcm(a[len(a)-2],a[len(a)-1])]));
 
 
 // Function: lcm()
@@ -494,11 +418,12 @@ function _lcmlist(a) =
 //   be non-zero integers.  The output is always a positive integer.  It is an error to pass zero
 //   as an argument.  
 function lcm(a,b=[]) =
-    !is_list(a) && !is_list(b) 
-    ?   _lcm(a,b) 
-    :   let( arglist = concat(force_list(a),force_list(b)) )
-        assert(len(arglist)>0, "Invalid call to lcm with empty list(s)")
-        _lcmlist(arglist);
+    !is_list(a) && !is_list(b) ? _lcm(a,b) : 
+    let(
+        arglist = concat(force_list(a),force_list(b))
+    )
+    assert(len(arglist)>0,"invalid call to lcm with empty list(s)")
+    _lcmlist(arglist);
 
 
 
@@ -506,9 +431,8 @@ function lcm(a,b=[]) =
 
 // Function: sum()
 // Description:
-//   Returns the sum of all entries in the given consistent list.
-//   If passed an array of vectors, returns the sum the vectors.
-//   If passed an array of matrices, returns the sum of the matrices.
+//   Returns the sum of all entries in the given list.
+//   If passed an array of vectors, returns a vector of sums of each part.
 //   If passed an empty list, the value of `dflt` will be returned.
 // Arguments:
 //   v = The list to get the sum of.
@@ -517,10 +441,11 @@ function lcm(a,b=[]) =
 //   sum([1,2,3]);  // returns 6.
 //   sum([[1,2,3], [3,4,5], [5,6,7]]);  // returns [9, 12, 15]
 function sum(v, dflt=0) =
-    is_list(v) && len(v) == 0 ? dflt :
-    is_vector(v) || is_matrix(v)? [for(i=v) 1]*v :
+    is_vector(v) ? [for(i=v) 1]*v :
     assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
-    _sum(v,v[0]*0);
+    is_vector(v[0]) ? [for(i=v) 1]*v :
+    len(v) == 0 ? dflt :
+                  _sum(v,v[0]*0);
 
 function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
 
@@ -570,51 +495,37 @@ function sum_of_squares(v) = sum(vmul(v,v));
 // Examples:
 //   v = sum_of_sines(30, [[10,3,0], [5,5.5,60]]);
 function sum_of_sines(a, sines) =
-    assert( is_finite(a) && is_matrix(sines,undef,3), "Invalid input.")
-    sum([ for (s = sines) 
-            let(
-              ss=point3d(s),
-              v=ss[0]*sin(a*ss[1]+ss[2])
-            ) v
-        ]);
+    sum([
+        for (s = sines) let(
+            ss=point3d(s),
+            v=ss.x*sin(a*ss.y+ss.z)
+        ) v
+    ]);
 
 
 // Function: deltas()
 // Description:
 //   Returns a list with the deltas of adjacent entries in the given list.
-//   The list should be a consistent list of numeric components (numbers, vectors, matrix, etc).
 //   Given [a,b,c,d], returns [b-a,c-b,d-c].
 // Arguments:
 //   v = The list to get the deltas of.
 // Example:
 //   deltas([2,5,9,17]);  // returns [3,4,8].
 //   deltas([[1,2,3], [3,6,8], [4,8,11]]);  // returns [[2,4,5], [1,2,3]]
-function deltas(v) = 
-    assert( is_consistent(v) && len(v)>1 , "Inconsistent list or with length<=1.")
-    [for (p=pair(v)) p[1]-p[0]] ;
+function deltas(v) = [for (p=pair(v)) p.y-p.x];
 
 
 // Function: product()
 // Description:
 //   Returns the product of all entries in the given list.
-//   If passed a list of vectors of same dimension, returns a vector of products of each part.
-//   If passed a list of square matrices, returns a the resulting product matrix.
+//   If passed an array of vectors, returns a vector of products of each part.
+//   If passed an array of matrices, returns a the resulting product matrix.
 // Arguments:
 //   v = The list to get the product of.
 // Example:
 //   product([2,3,4]);  // returns 24.
 //   product([[1,2,3], [3,4,5], [5,6,7]]);  // returns [15, 48, 105]
-function product(v) = 
-    assert( is_vector(v) || is_matrix(v) || ( is_matrix(v[0],square=true) && is_consistent(v)), 
-		        "Invalid input.")
-    _product(v, 1, v[0]);
-
-function _product(v, i=0, _tot) = 
-    i>=len(v) ? _tot :
-    _product( v, 
-              i+1, 
-              ( is_vector(v[i])? vmul(_tot,v[i]) : _tot*v[i] ) );
-               
+function product(v, i=0, tot=undef) = i>=len(v)? tot : product(v, i+1, ((tot==undef)? v[i] : is_vector(v[i])? vmul(tot,v[i]) : tot*v[i]));
 
 
 // Function: outer_product()
@@ -623,22 +534,21 @@ function _product(v, i=0, _tot) =
 // Usage:
 //   M = outer_product(u,v);
 function outer_product(u,v) =
-  assert(is_vector(u) && is_vector(v), "The inputs must be vectors.")
-  [for(ui=u) ui*v];
+  assert(is_vector(u) && is_vector(v))
+  assert(len(u)==len(v))
+  [for(i=[0:len(u)-1]) [for(j=[0:len(u)-1]) u[i]*v[j]]];
 
 
 // Function: mean()
 // Description:
-//   Returns the arithmetic mean/average of all entries in the given array.
+//   Returns the arithmatic mean/average of all entries in the given array.
 //   If passed a list of vectors, returns a vector of the mean of each part.
 // Arguments:
 //   v = The list of values to get the mean of.
 // Example:
 //   mean([2,3,4]);  // returns 3.
 //   mean([[1,2,3], [3,4,5], [5,6,7]]);  // returns [3, 4, 5]
-function mean(v) = 
-    assert(is_list(v) && len(v)>0, "Invalid list.")
-    sum(v)/len(v);
+function mean(v) = sum(v)/len(v);
 
 
 // Function: median()
@@ -646,33 +556,18 @@ function mean(v) =
 //   x = median(v);
 // Description:
 //   Given a list of numbers or vectors, finds the median value or midpoint.
-//   If passed a list of vectors, returns the vector of the median of each component.
+//   If passed a list of vectors, returns the vector of the median of each part.
 function median(v) =
-    is_vector(v) ? (min(v)+max(v))/2 :
-    is_matrix(v) ? [for(ti=transpose(v))  (min(ti)+max(ti))/2 ]
-    :   assert(false , "Invalid input.");
-
-// Function: convolve()
-// Usage:
-//   x = convolve(p,q);
-// Description:
-//   Given two vectors, finds the convolution of them.
-//   The length of the returned vector is len(p)+len(q)-1 .
-// Arguments:
-//   p = The first vector.
-//   q = The second vector.
-// Example:
-//   a = convolve([1,1],[1,2,1]); // Returns: [1,3,3,1]
-//   b = convolve([1,2,3],[1,2,1])); // Returns: [1,4,8,8,3]
-function convolve(p,q) =
-    p==[] || q==[] ? [] :
-    assert( is_vector(p) && is_vector(q), "The inputs should be vectors.")
-    let( n = len(p),
-         m = len(q))
-    [for(i=[0:n+m-2], k1 = max(0,i-n+1), k2 = min(i,m-1) )
-       [for(j=[k1:k2]) p[i-j] ] * [for(j=[k1:k2]) q[j] ] 
-    ];
-
+    assert(is_list(v))
+    assert(len(v)>0)
+    is_vector(v[0])? (
+        assert(is_consistent(v))
+        [
+            for (i=idx(v[0]))
+            let(vals = subindex(v,i))
+            (min(vals)+max(vals))/2
+        ]
+    ) : (min(v)+max(v))/2;
 
 
 // Section: Matrix math
@@ -687,7 +582,7 @@ function convolve(p,q) =
 //   want to solve Ax=b1 and Ax=b2 that you need to form the matrix transpose([b1,b2]) for the right hand side and then
 //   transpose the returned value.  
 function linear_solve(A,b) =
-    assert(is_matrix(A), "Input should be a matrix.")
+    assert(is_matrix(A))
     let(
         m = len(A),
         n = len(A[0])
@@ -724,12 +619,8 @@ function matrix_inverse(A) =
 // Description:
 //   Returns a submatrix with the specified index ranges or index sets.  
 function submatrix(M,ind1,ind2) =
-    assert( is_matrix(M), "Input must be a matrix." )
-		[for(i=ind1) 
-		    [for(j=ind2) 
-		        assert( ! is_undef(M[i][j]), "Invalid indexing." )
-		        M[i][j] ] ];
-		
+    [for(i=ind1) [for(j=ind2) M[i][j] ] ];
+
 
 // Function: qr_factor()
 // Usage: qr = qr_factor(A)
@@ -737,7 +628,7 @@ function submatrix(M,ind1,ind2) =
 //   Calculates the QR factorization of the input matrix A and returns it as the list [Q,R].  This factorization can be
 //   used to solve linear systems of equations.  
 function qr_factor(A) =
-    assert(is_matrix(A), "Input must be a matrix." )
+    assert(is_matrix(A))
     let(
       m = len(A),
       n = len(A[0])
@@ -768,8 +659,8 @@ function _qr_factor(A,Q, column, m, n) =
 // Function: back_substitute()
 // Usage: back_substitute(R, b, [transpose])
 // Description:
-//   Solves the problem Rx=b where R is an upper triangular square matrix.  The lower triangular entries of R are
-//   ignored.  If transpose==true then instead solve transpose(R)*x=b.
+//   Solves the problem Rx=b where R is an upper triangular square matrix.  No check is made that the lower triangular entries
+//   are actually zero.  If transpose==true then instead solve transpose(R)*x=b.
 //   You can supply a compatible matrix b and it will produce the solution for every column of b.  Note that if you want to
 //   solve Rx=b1 and Rx=b2 you must set b to transpose([b1,b2]) and then take the transpose of the result.  If the matrix
 //   is singular (e.g. has a zero on the diagonal) then it returns [].  
@@ -803,9 +694,7 @@ function back_substitute(R, b, x=[],transpose = false) =
 // Example:
 //   M = [ [6,-2], [1,8] ];
 //   det = det2(M);  // Returns: 50
-function det2(M) = 
-    assert( is_matrix(M,2,2), "Matrix should be 2x2." )
-    M[0][0] * M[1][1] - M[0][1]*M[1][0];
+function det2(M) = M[0][0] * M[1][1] - M[0][1]*M[1][0];
 
 
 // Function: det3()
@@ -817,7 +706,6 @@ function det2(M) =
 //   M = [ [6,4,-2], [1,-2,8], [1,5,7] ];
 //   det = det3(M);  // Returns: -334
 function det3(M) =
-    assert( is_matrix(M,3,3), "Matrix should be 3x3." )
     M[0][0] * (M[1][1]*M[2][2]-M[2][1]*M[1][2]) -
     M[1][0] * (M[0][1]*M[2][2]-M[2][1]*M[0][2]) +
     M[2][0] * (M[0][1]*M[1][2]-M[1][1]*M[0][2]);
@@ -832,21 +720,21 @@ function det3(M) =
 //   M = [ [6,4,-2,9], [1,-2,8,3], [1,5,7,6], [4,2,5,1] ];
 //   det = determinant(M);  // Returns: 2267
 function determinant(M) =
-    assert(is_matrix(M,square=true), "Input should be a square matrix." )
+    assert(len(M)==len(M[0]))
     len(M)==1? M[0][0] :
     len(M)==2? det2(M) :
     len(M)==3? det3(M) :
     sum(
         [for (col=[0:1:len(M)-1])
             ((col%2==0)? 1 : -1) *
-                M[col][0] *
-                determinant(
-                    [for (r=[1:1:len(M)-1])
-                        [for (c=[0:1:len(M)-1])
-                            if (c!=col) M[c][r]
-                        ]
+            M[col][0] *
+            determinant(
+                [for (r=[1:1:len(M)-1])
+                    [for (c=[0:1:len(M)-1])
+                        if (c!=col) M[c][r]
                     ]
-                )
+                ]
+            )
         ]
     );
 
@@ -865,11 +753,8 @@ function determinant(M) =
 //   n = optional width of matrix
 //   square = set to true to require a square matrix.  Default: false        
 function is_matrix(A,m,n,square=false) =
-    is_list(A[0]) 
-    && ( let(v = A*A[0]) is_num(0*(v*v)) ) // a matrix of finite numbers 
-    && (is_undef(n) || len(A[0])==n )
-    && (is_undef(m) || len(A)==m )
-    && ( !square || len(A)==len(A[0]));
+    is_vector(A[0],n) && is_vector(A*(0*A[0]),m) &&
+    (!square || len(A)==len(A[0]));
 
 
 // Section: Comparisons and Logic
@@ -889,13 +774,11 @@ function is_matrix(A,m,n,square=false) =
 //   approx(0.3333,1/3);          // Returns: false
 //   approx(0.3333,1/3,eps=1e-3);  // Returns: true
 //   approx(PI,3.1415926536);     // Returns: true
-function approx(a,b,eps=EPSILON) = 
+function approx(a,b,eps=EPSILON) =
     a==b? true :
     a*0!=b*0? false :
-    is_list(a)
-    ? ([for (i=idx(a)) if( !approx(a[i],b[i],eps=eps)) 1] == [])
-    : is_num(a) && is_num(b) && (abs(a-b) <= eps);
-    
+    is_list(a)? ([for (i=idx(a)) if(!approx(a[i],b[i],eps=eps)) 1] == []) :
+    (abs(a-b) <= eps);
 
 
 function _type_num(x) =
@@ -913,7 +796,7 @@ function _type_num(x) =
 // Description:
 //   Compares two values.  Lists are compared recursively.
 //   Returns <0 if a<b.  Returns >0 if a>b.  Returns 0 if a==b.
-//   If types are not the same, then undef < bool < nan < num < str < list < range.
+//   If types are not the same, then undef < bool < num < str < list < range.
 // Arguments:
 //   a = First value to compare.
 //   b = Second value to compare.
@@ -937,14 +820,13 @@ function compare_vals(a, b) =
 //   a = First list to compare.
 //   b = Second list to compare.
 function compare_lists(a, b) =
-    a==b? 0 
-    :   let(
-          cmps = [ for(i=[0:1:min(len(a),len(b))-1]) 
-                      let( cmp = compare_vals(a[i],b[i]) )
-                      if(cmp!=0) cmp 
-                 ]
-           ) 
-        cmps==[]? (len(a)-len(b)) : cmps[0];
+    a==b? 0 : let(
+        cmps = [
+            for(i=[0:1:min(len(a),len(b))-1]) let(
+                cmp = compare_vals(a[i],b[i])
+            ) if(cmp!=0) cmp
+        ]
+    ) cmps==[]? (len(a)-len(b)) : cmps[0];
 
 
 // Function: any()
@@ -961,11 +843,12 @@ function compare_lists(a, b) =
 //   any([[0,0], [1,0]]);   // Returns true.
 function any(l, i=0, succ=false) =
     (i>=len(l) || succ)? succ :
-    any( l, 
-         i+1, 
-         succ = is_list(l[i]) ? any(l[i]) : !(!l[i])
-        );
-
+    any(
+        l, i=i+1, succ=(
+            is_list(l[i])? any(l[i]) :
+            !(!l[i])
+        )
+    );
 
 
 // Function: all()
@@ -982,12 +865,13 @@ function any(l, i=0, succ=false) =
 //   all([[0,0], [1,0]]);   // Returns false.
 //   all([[1,1], [1,1]]);   // Returns true.
 function all(l, i=0, fail=false) =
-    (i>=len(l) || fail)? !fail :
-    all( l, 
-         i+1,
-         fail = is_list(l[i]) ? !all(l[i]) : !l[i]
-        ) ;
-
+    (i>=len(l) || fail)? (!fail) :
+    all(
+        l, i=i+1, fail=(
+            is_list(l[i])? !all(l[i]) :
+            !l[i]
+        )
+    );
 
 
 // Function: count_true()
@@ -1020,21 +904,6 @@ function count_true(l, nmax=undef, i=0, cnt=0) =
     );
 
 
-function count_true(l, nmax) = 
-    !is_list(l) ? !(!l) ? 1: 0 :
-    let( c = [for( i = 0,
-                   n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
-                   c = !is_undef(n)? n : count_true(l[i], nmax),
-                   s = c;
-                 i<len(l) && (is_undef(nmax) || s<nmax);
-                   i = i+1,
-                   n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
-                   c = !is_undef(n) || (i==len(l))? n : count_true(l[i], nmax-s),
-                   s = s+c
-                 )  s ] )
-    len(c)<len(l)? nmax: c[len(c)-1];
-
-
 
 // Section: Calculus
 
@@ -1052,49 +921,42 @@ function count_true(l, nmax) =
 //   between data[i+1] and data[i], and the data values will be linearly resampled at each corner
 //   to produce a uniform spacing for the derivative estimate.  At the endpoints a single point method
 //   is used: f'(t) = (f(t+h)-f(t))/h.  
-// Arguments:
-//   data = the list of the elements to compute the derivative of.
-//   h = the parametric sampling of the data.
-//   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv(data, h=1, closed=false) =
-    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
-    assert( len(data)>=2, "Input `data` should have at least 2 elements.") 
-    assert( is_finite(h) || is_vector(h), "The sampling `h` must be a number or a list of numbers." )
-    assert( is_num(h) || len(h) == len(data)-(closed?0:1),
-            str("Vector valued `h` must have length ",len(data)-(closed?0:1)))
     is_vector(h) ? _deriv_nonuniform(data, h, closed=closed) :
     let( L = len(data) )
-    closed
-    ? [
+    closed? [
         for(i=[0:1:L-1])
         (data[(i+1)%L]-data[(L+i-1)%L])/2/h
-      ]
-    : let(
-        first = L<3 ? data[1]-data[0] : 
-                3*(data[1]-data[0]) - (data[2]-data[1]),
-        last = L<3 ? data[L-1]-data[L-2]:
-               (data[L-3]-data[L-2])-3*(data[L-2]-data[L-1])
-         ) 
-      [
+    ] :
+    let(
+        first =
+            L<3? data[1]-data[0] : 
+            3*(data[1]-data[0]) - (data[2]-data[1]),
+        last =
+            L<3? data[L-1]-data[L-2]:
+            (data[L-3]-data[L-2])-3*(data[L-2]-data[L-1])
+    ) [
         first/2/h,
         for(i=[1:1:L-2]) (data[i+1]-data[i-1])/2/h,
         last/2/h
-      ];
+    ];
 
 
 function _dnu_calc(f1,fc,f2,h1,h2) =
     let(
         f1 = h2<h1 ? lerp(fc,f1,h2/h1) : f1 , 
         f2 = h1<h2 ? lerp(fc,f2,h1/h2) : f2
-       )
-    (f2-f1) / 2 / min(h1,h2);
+    )
+    (f2-f1) / 2 / min([h1,h2]);
 
 
 function _deriv_nonuniform(data, h, closed) =
-    let( L = len(data) )
-    closed
-    ? [for(i=[0:1:L-1])
-          _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
+    assert(len(h) == len(data)-(closed?0:1),str("Vector valued h must be length ",len(data)-(closed?0:1)))
+    let(
+      L = len(data)
+    )
+    closed? [for(i=[0:1:L-1])
+    	        _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
     : [
         (data[1]-data[0])/h[0],
         for(i=[1:1:L-2]) _dnu_calc(data[i-1],data[i],data[i+1], h[i-1],h[i]),
@@ -1105,23 +967,15 @@ function _deriv_nonuniform(data, h, closed) =
 // Function: deriv2()
 // Usage: deriv2(data, [h], [closed])
 // Description:
-//   Computes a numerical estimate of the second derivative of the data, which may be scalar or vector valued.
+//   Computes a numerical esimate of the second derivative of the data, which may be scalar or vector valued.
 //   The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. 
 //   If the `closed` parameter is true the data is assumed to be defined on a loop with data[0] adjacent to
 //   data[len(data)-1].  For internal points this function uses the approximation 
-//   f''(t) = (f(t-h)-2*f(t)+f(t+h))/h^2.  For the endpoints (when closed=false),
-//   when sufficient points are available, the method is either the four point expression
-//   f''(t) = (2*f(t) - 5*f(t+h) + 4*f(t+2*h) - f(t+3*h))/h^2 or 
+//   f''(t) = (f(t-h)-2*f(t)+f(t+h))/h^2.  For the endpoints (when closed=false) the algorithm
+//   when sufficient points are available the method is either the four point expression
+//   f''(t) = (2*f(t) - 5*f(t+h) + 4*f(t+2*h) - f(t+3*h))/h^2 or if five points are available
 //   f''(t) = (35*f(t) - 104*f(t+h) + 114*f(t+2*h) - 56*f(t+3*h) + 11*f(t+4*h)) / 12h^2
-//   if five points are available.
-// Arguments:
-//   data = the list of the elements to compute the derivative of.
-//   h = the constant parametric sampling of the data.
-//   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv2(data, h=1, closed=false) =
-    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
-    assert( len(data)>=3, "Input list has less than 3 elements.") 
-    assert( is_finite(h), "The sampling `h` must be a number." )
     let( L = len(data) )
     closed? [
         for(i=[0:1:L-1])
@@ -1149,19 +1003,16 @@ function deriv2(data, h=1, closed=false) =
 //   Computes a numerical third derivative estimate of the data, which may be scalar or vector valued.
 //   The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. 
 //   If the `closed` parameter is true the data is assumed to be defined on a loop with data[0] adjacent to
-//   data[len(data)-1].  This function uses a five point derivative estimate, so the input data must include 
-//   at least five points:
+//   data[len(data)-1].  This function uses a five point derivative estimate, so the input must include five points:
 //   f'''(t) = (-f(t-2*h)+2*f(t-h)-2*f(t+h)+f(t+2*h)) / 2h^3.  At the first and second points from the end
 //   the estimates are f'''(t) = (-5*f(t)+18*f(t+h)-24*f(t+2*h)+14*f(t+3*h)-3*f(t+4*h)) / 2h^3 and
 //   f'''(t) = (-3*f(t-h)+10*f(t)-12*f(t+h)+6*f(t+2*h)-f(t+3*h)) / 2h^3.
 function deriv3(data, h=1, closed=false) =
-    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
-    assert( len(data)>=5, "Input list has less than 5 elements.") 
-    assert( is_finite(h), "The sampling `h` must be a number." )
     let(
         L = len(data),
         h3 = h*h*h
     )
+    assert(L>=5, "Need five points for 3rd derivative estimate")
     closed? [
         for(i=[0:1:L-1])
         (-data[(L+i-2)%L]+2*data[(L+i-1)%L]-2*data[(i+1)%L]+data[(i+2)%L])/2/h3
@@ -1185,122 +1036,75 @@ function deriv3(data, h=1, closed=false) =
 // Function: C_times()
 // Usage: C_times(z1,z2)
 // Description:
-//   Multiplies two complex numbers represented by 2D vectors.  
-function C_times(z1,z2) = 
-    assert( is_vector(z1+z2,2), "Complex numbers should be represented by 2D vectors." )
-    [ z1.x*z2.x - z1.y*z2.y, z1.x*z2.y + z1.y*z2.x ];
+//   Multiplies two complex numbers.  
+function C_times(z1,z2) = [z1.x*z2.x-z1.y*z2.y,z1.x*z2.y+z1.y*z2.x];
 
 // Function: C_div()
 // Usage: C_div(z1,z2)
 // Description:
-//   Divides two complex numbers represented by 2D vectors.  
-function C_div(z1,z2) = 
-    assert( is_vector(z1,2) && is_vector(z2), "Complex numbers should be represented by 2D vectors." )
-    assert( !approx(z2,0), "The divisor `z2` cannot be zero." ) 
-    let(den = z2.x*z2.x + z2.y*z2.y)
-    [(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x - z1.x*z2.y)/den];
+//   Divides z1 by z2.  
+function C_div(z1,z2) = let(den = z2.x*z2.x + z2.y*z2.y)
+   [(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x-z1.x*z2.y)/den];
 
-// For the sake of consistence with Q_mul and vmul, C_times should be called C_mul
 
 // Section: Polynomials
 
-// Function: polynomial() 
+// Function: polynomial()
 // Usage:
 //   polynomial(p, z)
 // Description:
 //   Evaluates specified real polynomial, p, at the complex or real input value, z.
 //   The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]
 //   where a_n is the z^n coefficient.  Polynomial coefficients are real.
-//   The result is a number if `z` is a number and a complex number otherwise.
 
 // Note: this should probably be recoded to use division by [1,-z], which is more accurate
 // and avoids overflow with large coefficients, but requires poly_div to support complex coefficients.  
-function polynomial(p, z, _k, _zk, _total) =
-    is_undef(_k)  
-    ?   echo(poly=p) assert( is_vector(p), "Input polynomial coefficients must be a vector." )
-        let(p = _poly_trim(p))
-        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
-        polynomial( p, 
-                    z, 
-                    len(p)-1, 
-                    is_num(z)? 1 : [1,0], 
-                    is_num(z) ? 0 : [0,0]) 
-    :   _k==0 
-        ? _total + +_zk*p[0]
-        : polynomial( p, 
-                      z, 
-                      _k-1, 
-                      is_num(z) ? _zk*z : C_times(_zk,z), 
-                      _total+_zk*p[_k]);
+function polynomial(p, z, k, zk, total) =
+   is_undef(k) ? polynomial(p, z, len(p)-1, is_num(z)? 1 : [1,0], is_num(z) ? 0 : [0,0]) :
+   k==-1 ? total :
+   polynomial(p, z, k-1, is_num(z) ? zk*z : C_times(zk,z), total+zk*p[k]);
 
-function polynomial(p,z,k,total) =
-     is_undef(k)
-   ?    assert( is_vector(p) , "Input polynomial coefficients must be a vector." )
-        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
-        polynomial( _poly_trim(p), z, 0, is_num(z) ? 0 : [0,0])
-   : k==len(p) ? total
-   : polynomial(p,z,k+1, is_num(z) ? total*z+p[k] : C_times(total,z)+[p[k],0]);
 
 // Function: poly_mult()
 // Usage
 //   polymult(p,q)
 //   polymult([p1,p2,p3,...])
-// Description:
+// Descriptoin:
 //   Given a list of polynomials represented as real coefficient lists, with the highest degree coefficient first, 
 //   computes the coefficient list of the product polynomial.  
 function poly_mult(p,q) = 
-    is_undef(q) ?
-       assert( is_list(p) 
-               && []==[for(pi=p) if( !is_vector(pi) && pi!=[]) 0], 
-               "Invalid arguments to poly_mult")
-       len(p)==2 ? poly_mult(p[0],p[1]) 
-                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
-    :
-    _poly_trim(
-    [
-    for(n = [len(p)+len(q)-2:-1:0])
-        sum( [for(i=[0:1:len(p)-1])
-             let(j = len(p)+len(q)- 2 - n - i)
-             if (j>=0 && j<len(q)) p[i]*q[j]
-                 ])
-     ]);        
-     
-function poly_mult(p,q) = 
-    is_undef(q) ?
-       len(p)==2 ? poly_mult(p[0],p[1]) 
-                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
-    :
-    assert( is_vector(p) && is_vector(q),"Invalid arguments to poly_mult")
-    _poly_trim( [
-                  for(n = [len(p)+len(q)-2:-1:0])
-                      sum( [for(i=[0:1:len(p)-1])
-                           let(j = len(p)+len(q)- 2 - n - i)
-                           if (j>=0 && j<len(q)) p[i]*q[j]
-                               ])
-                   ]);
+  is_undef(q) ?
+     assert(is_list(p) && (is_vector(p[0]) || p[0]==[]), "Invalid arguments to poly_mult")
+     len(p)==2 ? poly_mult(p[0],p[1]) 
+               : poly_mult(p[0], poly_mult(select(p,1,-1)))
+  :
+  _poly_trim(
+  [
+  for(n = [len(p)+len(q)-2:-1:0])
+      sum( [for(i=[0:1:len(p)-1])
+           let(j = len(p)+len(q)- 2 - n - i)
+           if (j>=0 && j<len(q)) p[i]*q[j]
+               ])
+   ]);        
+
 
-    
 // Function: poly_div()
 // Usage:
 //    [quotient,remainder] = poly_div(n,d)
 // Description:
 //    Computes division of the numerator polynomial by the denominator polynomial and returns
 //    a list of two polynomials, [quotient, remainder].  If the division has no remainder then
-//    the zero polynomial [0] is returned for the remainder.  Similarly if the quotient is zero
-//    the returned quotient will be [0].  
-function poly_div(n,d,q) =
-    is_undef(q) 
-    ?   assert( is_vector(n) && is_vector(d) , "Invalid polynomials." )
-        let( d = _poly_trim(d) )
-        assert( d!=[0] , "Denominator cannot be a zero polynomial." )
-        poly_div(n,d,q=[])
-    :   len(n)<len(d) ? [q==[]? [0]: q,_poly_trim(n)] : 
-        let(
-          t = n[0] / d[0], 
-          newq = concat(q,[t]),
-          newn = [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
-        )  
-        poly_div(newn,d,newq);
+//    the zero polynomial [] is returned for the remainder.  Similarly if the quotient is zero
+//    the returned quotient will be [].  
+function poly_div(n,d,q=[]) =
+    assert(len(d)>0 && d[0]!=0 , "Denominator is zero or has leading zero coefficient")
+    len(n)<len(d) ? [q,_poly_trim(n)] : 
+    let(
+      t = n[0] / d[0],
+      newq = concat(q,[t]),
+      newn =  [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
+    )  
+    poly_div(newn,d,newq);
 
 
 // Internal Function: _poly_trim()
@@ -1310,8 +1114,8 @@ function poly_div(n,d,q) =
 //    Removes leading zero terms of a polynomial.  By default zeros must be exact,
 //    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);
+  let(  nz = [for(i=[0:1:len(p)-1]) if (!approx(p[i],0,eps)) i])
+  len(nz)==0 ? [] : select(p,nz[0],-1);
 
 
 // Function: poly_add()
@@ -1320,13 +1124,12 @@ function _poly_trim(p,eps=0) =
 // Description:
 //    Computes the sum of two polynomials.  
 function poly_add(p,q) = 
-    assert( is_vector(p) && is_vector(q), "Invalid input polynomial(s)." )
-    let(  plen = len(p),
-          qlen = len(q),
-          long = plen>qlen ? p : q,
-          short = plen>qlen ? q : p
-       )
-     _poly_trim(long + concat(repeat(0,len(long)-len(short)),short));
+  let(  plen = len(p),
+        qlen = len(q),
+        long = plen>qlen ? p : q,
+        short = plen>qlen ? q : p
+     )
+   _poly_trim(long + concat(repeat(0,len(long)-len(short)),short));
 
 
 // Function: poly_roots()
@@ -1347,38 +1150,38 @@ function poly_add(p,q) =
 //
 // Dario Bini. "Numerical computation of polynomial zeros by means of Aberth's Method", Numerical Algorithms, Feb 1996.
 // https://www.researchgate.net/publication/225654837_Numerical_computation_of_polynomial_zeros_by_means_of_Aberth's_method
+
 function poly_roots(p,tol=1e-14,error_bound=false) =
-    assert( is_vector(p), "Invalid polynomial." )
-    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))
-        (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
-    len(p)==2 ? let( solution = [[-p[1]/p[0],0]])            // Linear case needs special handling
-                (error_bound ? [solution,[0]] : solution)
-    : 
-    let(
-        n = len(p)-1,   // polynomial degree
-        pderiv = [for(i=[0:n-1]) p[i]*(n-i)],
-           
-        s = [for(i=[0:1:n]) abs(p[i])*(4*(n-i)+1)],  // Error bound polynomial from Bini
+  assert(p!=[], "Input polynomial must have a nonzero coefficient")
+  assert(is_vector(p), "Input must be a vector")
+  p[0] == 0 ? poly_roots(slice(p,1,-1),tol=tol,error_bound=error_bound) :    // Strip leading zero coefficients
+  p[len(p)-1] == 0 ?                                       // Strip trailing zero coefficients
+      let( solutions = poly_roots(select(p,0,-2),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
+  len(p)==2 ? let( solution = [[-p[1]/p[0],0]])            // Linear case needs special handling
+              (error_bound ? [solution,[0]] : solution)
+  : 
+  let(
+      n = len(p)-1,   // polynomial degree
+      pderiv = [for(i=[0:n-1]) p[i]*(n-i)],
+         
+      s = [for(i=[0:1:n]) abs(p[i])*(4*(n-i)+1)],  // Error bound polynomial from Bini
 
-        // Using method from: http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/0915-24.pdf
-        beta = -p[1]/p[0]/n,
-        r = 1+pow(abs(polynomial(p,beta)/p[0]),1/n),
-        init = [for(i=[0:1:n-1])                // Initial guess for roots       
-                 let(angle = 360*i/n+270/n/PI)
-                 [beta,0]+r*[cos(angle),sin(angle)]
-               ],
-        roots = _poly_roots(p,pderiv,s,init,tol=tol),
-        error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) /
-                                  abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0
-      )
-      error_bound ? [roots, error] : roots;
+      // Using method from: http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/0915-24.pdf
+      beta = -p[1]/p[0]/n,
+      r = 1+pow(abs(polynomial(p,beta)/p[0]),1/n),
+      init = [for(i=[0:1:n-1])                // Initial guess for roots       
+               let(angle = 360*i/n+270/n/PI)
+               [beta,0]+r*[cos(angle),sin(angle)]
+             ],
+      roots = _poly_roots(p,pderiv,s,init,tol=tol),
+      error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) /
+                                abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0
+    )
+    error_bound ? [roots, error] : roots;
 
-// Internal function
 // p = polynomial
 // pderiv = derivative polynomial of p
 // z = current guess for the roots
@@ -1419,16 +1222,12 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) =
 //   tol = tolerance for the complex polynomial root finder
 
 function real_roots(p,eps=undef,tol=1e-14) =
-    assert( is_vector(p), "Invalid polynomial." )
-    let( p = _poly_trim(p,eps=0) )
-    assert( p!=[0], "Input polynomial cannot be zero." )
-    let( 
+   let( 
        roots_err = poly_roots(p,error_bound=true),
        roots = roots_err[0],
        err = roots_err[1]
-    )
-    is_def(eps) 
-    ? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
-    : [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
+   )
+   is_def(eps) ? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
+               : [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
 
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/tests/test_common.scad b/tests/test_common.scad
index 808b9c4..1cad5e3 100644
--- a/tests/test_common.scad
+++ b/tests/test_common.scad
@@ -169,6 +169,7 @@ module test_is_range() {
     assert(!is_range(5));
     assert(!is_range(INF));
     assert(!is_range(-INF));
+    assert(!is_nan(NAN));
     assert(!is_range(""));
     assert(!is_range("foo"));
     assert(!is_range([]));
@@ -178,23 +179,7 @@ module test_is_range() {
     assert(!is_range([3:4:"a"]));
     assert(is_range([3:1:5]));
 }
-test_is_range();
-
-
-module test_valid_range() {
-    assert(valid_range([0:0]));
-    assert(valid_range([0:1:0]));
-    assert(valid_range([0:1:10]));
-    assert(valid_range([0.1:1.1:2.1]));
-    assert(valid_range([0:-1:0]));
-    assert(valid_range([10:-1:0]));
-    assert(valid_range([2.1:-1.1:0.1]));
-    assert(!valid_range([10:1:0]));
-    assert(!valid_range([2.1:1.1:0.1]));
-    assert(!valid_range([0:-1:10]));
-    assert(!valid_range([0.1:-1.1:2.1]));
-}
-test_valid_range();
+test_is_nan();
 
 
 module test_is_list_of() {
@@ -207,14 +192,10 @@ module test_is_list_of() {
 }
 test_is_list_of();
 
+
 module test_is_consistent() {
-    assert(is_consistent([]));
-    assert(is_consistent([[],[]]));
     assert(is_consistent([3,4,5]));
     assert(is_consistent([[3,4],[4,5],[6,7]]));
-    assert(is_consistent([[[3],4],[[4],5]]));
-    assert(!is_consistent(5));
-    assert(!is_consistent(undef));
     assert(!is_consistent([[3,4,5],[3,4]]));
     assert(is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]));
     assert(!is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]));
diff --git a/tests/test_math.scad b/tests/test_math.scad
index f659d34..3233e6f 100644
--- a/tests/test_math.scad
+++ b/tests/test_math.scad
@@ -782,8 +782,8 @@ test_deriv3();
 
 
 module test_polynomial(){
-  assert_equal(polynomial([0],12),0);
-  assert_equal(polynomial([0],[12,4]),[0,0]);
+  assert_equal(polynomial([],12),0);
+  assert_equal(polynomial([],[12,4]),[0,0]);
   assert_equal(polynomial([1,2,3,4],3),58);
   assert_equal(polynomial([1,2,3,4],[3,-1]),[47,-41]);
   assert_equal(polynomial([0,0,2],4),2);
@@ -879,17 +879,17 @@ test_qr_factor();
 
 module test_poly_mult(){
   assert_equal(poly_mult([3,2,1],[4,5,6,7]),[12,23,32,38,20,7]);
-  assert_equal(poly_mult([3,2,1],[0]),[0]);
+  assert_equal(poly_mult([3,2,1],[]),[]);
   assert_equal(poly_mult([[1,2],[3,4],[5,6]]), [15,68,100,48]);
-  assert_equal(poly_mult([[1,2],[0],[5,6]]), [0]);
-  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[0]);
+  assert_equal(poly_mult([[1,2],[],[5,6]]), []);
+  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[]);
 }
 test_poly_mult();
 
  
 module test_poly_div(){
-  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[0]]);
-  assert_equal(poly_div([1,2,3,4],[1,2,3,4,5]), [[0], [1,2,3,4]]);
+  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[]]);
+  assert_equal(poly_div([1,2,3,4],[1,2,3,4,5]), [[], [1,2,3,4]]);
   assert_equal(poly_div(poly_add(poly_mult([1,2,3,4],[2,0,2]), [1,1,2]), [1,2,3,4]), [[2,0,2],[1,1,2]]);
   assert_equal(poly_div([1,2,3,4], [1,-3]), [[1,5,18],[58]]);
 }
@@ -899,7 +899,7 @@ test_poly_div();
 module test_poly_add(){
   assert_equal(poly_add([2,3,4],[3,4,5,6]),[3,6,8,10]);
   assert_equal(poly_add([1,2,3,4],[-1,-2,3,4]), [6,8]);
-  assert_equal(poly_add([1,2,3],-[1,2,3]),[0]);
+  assert_equal(poly_add([1,2,3],-[1,2,3]),[]);
 }
 test_poly_add();
 

From 8a25764744cf260d775b7c81540b8449c6a1382d Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Wed, 29 Jul 2020 22:41:02 +0100
Subject: [PATCH 23/24] Overall review, input data check, format, new function
 definitions

New function definitions in commom.scad:
1. valid_range;
2. _list_pattern

New function definitions in math.scad:
1. binomial;
2. binomial_coefficient;
3. convolve;

Code review in math:
1. sum;
2. median;
3. is_matrix;
4. approx;
5. count_true;
6. doc of deriv2;
7. polynomial;
8. poly_mult;
9; poly_div;
10. _poly_trim

Code change in test_common:
1. new test_valid_range;
2. test_is_consistent

Code change in test_math:
1. test_approx;
2. new test_convolve;
3. new test_binomial;
4. new test_binomial_coefficient;
5. test_outer_product;
6. test_polynomial;
7. test_poly_mult;
8. test_poly_div;
9. test_poly_add;
---
 common.scad            |  20 +-
 math.scad              | 623 +++++++++++++++++++++++++++--------------
 tests/test_common.scad |  24 +-
 tests/test_math.scad   |  55 +++-
 4 files changed, 497 insertions(+), 225 deletions(-)

diff --git a/common.scad b/common.scad
index 0d88e52..37ba4af 100644
--- a/common.scad
+++ b/common.scad
@@ -99,6 +99,16 @@ function is_finite(v) = is_num(0*v);
 function is_range(x) = !is_list(x) && is_finite(x[0]+x[1]+x[2]) ;
 
 
+// Function: valid_range()
+// Description:
+//   Returns true if its argument is a valid range (deprecated ranges excluded).
+function valid_range(x) = 
+    is_range(x) 
+    && ( x[1]>0 
+         ? x[0]<=x[2]
+         : ( x[1]<0 && x[0]>=x[2] ) );
+
+
 // Function: is_list_of()
 // Usage:
 //   is_list_of(list, pattern)
@@ -133,9 +143,15 @@ function is_list_of(list,pattern) =
 //   is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]); // Returns true
 //   is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]);   // Returns false
 function is_consistent(list) =
-    is_list(list) && is_list_of(list, list[0]);
+  is_list(list) && is_list_of(list, _list_pattern(list[0]));
 
 
+//Internal function
+//Creates a list with the same structure of `list` with each of its elements substituted by 0.
+function _list_pattern(list) =
+  is_list(list) 
+  ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0]
+  : 0;
 
 
 // Function: same_shape()
@@ -146,7 +162,7 @@ function is_consistent(list) =
 // Example:
 //   same_shape([3,[4,5]],[7,[3,4]]);   // Returns true
 //   same_shape([3,4,5], [7,[3,4]]);    // Returns false
-function same_shape(a,b) = a*0 == b*0;
+function same_shape(a,b) = _list_pattern(a) == b*0;
 
 
 // Section: Handling `undef`s.
diff --git a/math.scad b/math.scad
index 7629cb1..2262cac 100644
--- a/math.scad
+++ b/math.scad
@@ -33,7 +33,10 @@ NAN = acos(2);  // The value `nan`, useful for comparisons.
 //   sqr([3,4]); // Returns: [9,16]
 //   sqr([[1,2],[3,4]]);  // Returns [[1,4],[9,16]]
 //   sqr([[1,2],3]);      // Returns [[1,4],9]
-function sqr(x) = is_list(x) ? [for(val=x) sqr(val)] : x*x;
+function sqr(x) = 
+    is_list(x) ? [for(val=x) sqr(val)] : 
+    is_finite(x) ? x*x :
+    assert(is_finite(x) || is_vector(x), "Input is not neither a number nor a list of numbers.");
 
 
 // Function: log2()
@@ -45,8 +48,11 @@ function sqr(x) = is_list(x) ? [for(val=x) sqr(val)] : x*x;
 //   log2(0.125);  // Returns: -3
 //   log2(16);     // Returns: 4
 //   log2(256);    // Returns: 8
-function log2(x) = ln(x)/ln(2);
+function log2(x) = 
+    assert( is_finite(x), "Input is not a number.")
+    ln(x)/ln(2);
 
+// this may return NAN or INF; should it check x>0 ?
 
 // Function: hypot()
 // Usage:
@@ -60,7 +66,9 @@ function log2(x) = ln(x)/ln(2);
 // Example:
 //   l = hypot(3,4);  // Returns: 5
 //   l = hypot(3,4,5);  // Returns: ~7.0710678119
-function hypot(x,y,z=0) = norm([x,y,z]);
+function hypot(x,y,z=0) = 
+    assert( is_vector([x,y,z]), "Improper number(s).")
+    norm([x,y,z]);
 
 
 // Function: factorial()
@@ -76,11 +84,53 @@ function hypot(x,y,z=0) = norm([x,y,z]);
 //   y = factorial(6);  // Returns: 720
 //   z = factorial(9);  // Returns: 362880
 function factorial(n,d=0) =
-    assert(n>=0 && d>=0, "Factorial is not defined for negative numbers")
+    assert(is_int(n) && is_int(d) && n>=0 && d>=0, "Factorial is not defined for negative numbers")
     assert(d<=n, "d cannot be larger than n")
     product([1,for (i=[n:-1:d+1]) i]);
 
 
+// Function: binomial()
+// Usage:
+//   x = binomial(n);
+// Description:
+//   Returns the binomial coefficients of the integer `n`.  
+// Arguments:
+//   n = The integer to get the binomial coefficients of
+// Example:
+//   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]
+function binomial(n) =
+    assert( is_int(n) && n>0, "Input is not an integer greater than 0.")
+    [for( c = 1, i = 0; 
+        i<=n; 
+         c = c*(n-i)/(i+1), i = i+1
+        ) c ] ;
+
+
+// Function: binomial_coefficient()
+// Usage:
+//   x = binomial_coefficient(n,k);
+// Description:
+//   Returns the k-th binomial coefficient of the integer `n`.  
+// Arguments:
+//   n = The integer to get the binomial coefficient of
+//   k = The binomial coefficient index
+// Example:
+//   x = binomial_coefficient(3,2);  // Returns: 3
+//   y = binomial_coefficient(10,6); // Returns: 210
+function binomial_coefficient(n,k) =
+    assert( is_int(n) && is_int(k), "Some input is not a number.")
+    k < 0 || k > n ? 0 :
+    k ==0 || k ==n ? 1 :
+    let( k = min(k, n-k),
+         b = [for( c = 1, i = 0; 
+                   i<=k; 
+                   c = c*(n-i)/(i+1), i = i+1
+                 ) c] )
+    b[len(b)-1];
+
+
 // Function: lerp()
 // Usage:
 //   x = lerp(a, b, u);
@@ -91,8 +141,8 @@ function factorial(n,d=0) =
 //   If `u` is 0.0, then the value of `a` is returned.
 //   If `u` is 1.0, then the value of `b` is returned.
 //   If `u` is a range, or list of numbers, returns a list of interpolated values.
-//   It is valid to use a `u` value outside the range 0 to 1.  The result will be a predicted
-//   value along the slope formed by `a` and `b`, but not between those two values.
+//   It is valid to use a `u` value outside the range 0 to 1.  The result will be an extrapolation
+//   along the slope formed by `a` and `b`.
 // Arguments:
 //   a = First value or vector.
 //   b = Second value or vector.
@@ -113,9 +163,9 @@ function factorial(n,d=0) =
 //   rainbow(pts) translate($item) circle(d=3,$fn=8);
 function lerp(a,b,u) =
     assert(same_shape(a,b), "Bad or inconsistent inputs to lerp")
-    is_num(u)? (1-u)*a + u*b :
-    assert(!is_undef(u)&&!is_bool(u)&&!is_string(u), "Input u to lerp must be a number, vector, or range.")
-    [for (v = u) lerp(a,b,v)];
+    is_finite(u)? (1-u)*a + u*b :
+    assert(is_finite(u) || is_vector(u) || valid_range(u), "Input u to lerp must be a number, vector, or range.")
+    [for (v = u) (1-v)*a + v*b ];
 
 
 
@@ -124,40 +174,45 @@ function lerp(a,b,u) =
 // Function: sinh()
 // Description: Takes a value `x`, and returns the hyperbolic sine of it.
 function sinh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     (exp(x)-exp(-x))/2;
 
 
 // Function: cosh()
 // Description: Takes a value `x`, and returns the hyperbolic cosine of it.
 function cosh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     (exp(x)+exp(-x))/2;
 
 
 // Function: tanh()
 // Description: Takes a value `x`, and returns the hyperbolic tangent of it.
 function tanh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     sinh(x)/cosh(x);
 
 
 // Function: asinh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic sine of it.
 function asinh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     ln(x+sqrt(x*x+1));
 
 
 // Function: acosh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic cosine of it.
 function acosh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     ln(x+sqrt(x*x-1));
 
 
 // Function: atanh()
 // Description: Takes a value `x`, and returns the inverse hyperbolic tangent of it.
 function atanh(x) =
+    assert(is_finite(x), "The input must be a finite number.")
     ln((1+x)/(1-x))/2;
 
 
-
 // Section: Quantization
 
 // Function: quant()
@@ -185,8 +240,11 @@ function atanh(x) =
 //   quant([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,12,12,12]
 //   quant([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[12,12,12]]
 function quant(x,y) =
-    is_list(x)? [for (v=x) quant(v,y)] :
-    floor(x/y+0.5)*y;
+    assert(is_finite(y) && !approx(y,0,eps=1e-24), "The multiple must be a non zero integer.")
+    is_list(x)
+    ?   [for (v=x) quant(v,y)]
+    :   assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
+        floor(x/y+0.5)*y;
 
 
 // Function: quantdn()
@@ -214,8 +272,11 @@ function quant(x,y) =
 //   quantdn([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,9,9,12]
 //   quantdn([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[9,9,12]]
 function quantdn(x,y) =
-    is_list(x)? [for (v=x) quantdn(v,y)] :
-    floor(x/y)*y;
+    assert(is_finite(y) && !approx(y,0,eps=1e-24), "The multiple must be a non zero integer.")
+    is_list(x)
+    ?    [for (v=x) quantdn(v,y)]
+    :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
+        floor(x/y)*y;
 
 
 // Function: quantup()
@@ -243,8 +304,11 @@ function quantdn(x,y) =
 //   quantup([9,10,10.4,10.5,11,12],3);      // Returns: [9,12,12,12,12,12]
 //   quantup([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,12,12],[12,12,12]]
 function quantup(x,y) =
-    is_list(x)? [for (v=x) quantup(v,y)] :
-    ceil(x/y)*y;
+    assert(is_finite(y) && !approx(y,0,eps=1e-24), "The multiple must be a non zero integer.")
+    is_list(x)
+    ?    [for (v=x) quantup(v,y)]
+    :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
+        ceil(x/y)*y;
 
 
 // Section: Constraints and Modulos
@@ -264,7 +328,9 @@ function quantup(x,y) =
 //   constrain(0.3, -1, 1);  // Returns: 0.3
 //   constrain(9.1, 0, 9);   // Returns: 9
 //   constrain(-0.1, 0, 9);  // Returns: 0
-function constrain(v, minval, maxval) = min(maxval, max(minval, v));
+function constrain(v, minval, maxval) = 
+    assert( is_finite(v+minval+maxval), "Input must be finite number(s).")
+    min(maxval, max(minval, v));
 
 
 // Function: posmod()
@@ -283,7 +349,9 @@ function constrain(v, minval, maxval) = min(maxval, max(minval, v));
 //   posmod(270,360);   // Returns: 270
 //   posmod(700,360);   // Returns: 340
 //   posmod(3,2.5);     // Returns: 0.5
-function posmod(x,m) = (x%m+m)%m;
+function posmod(x,m) = 
+    assert( is_finite(x) && is_finite(m) && !approx(m,0) , "Input must be finite numbers. The divisor cannot be zero.")
+    (x%m+m)%m;
 
 
 // Function: modang(x)
@@ -299,6 +367,7 @@ function posmod(x,m) = (x%m+m)%m;
 //   modang(270,360);   // Returns: -90
 //   modang(700,360);   // Returns: -20
 function modang(x) =
+    assert( is_finite(x), "Input must be a finite number.")
     let(xx = posmod(x,360)) xx<180? xx : xx-360;
 
 
@@ -306,7 +375,7 @@ function modang(x) =
 // Usage:
 //   modrange(x, y, m, [step])
 // Description:
-//   Returns a normalized list of values from `x` to `y`, by `step`, modulo `m`.  Wraps if `x` > `y`.
+//   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.
@@ -318,6 +387,7 @@ function modang(x) =
 //   modrange(90,270,360, step=-45);  // Returns: [90,45,0,315,270]
 //   modrange(270,90,360, step=-45);  // Returns: [270,225,180,135,90]
 function modrange(x, y, m, step=1) =
+    assert( is_finite(x+y+step+m) && !approx(m,0), "Input must be finite numbers. The module value cannot be zero.")
     let(
         a = posmod(x, m),
         b = posmod(y, m),
@@ -330,20 +400,21 @@ function modrange(x, y, m, step=1) =
 
 // Function: rand_int()
 // Usage:
-//   rand_int(min,max,N,[seed]);
+//   rand_int(minval,maxval,N,[seed]);
 // Description:
-//   Return a list of random integers in the range of min to max, inclusive.
+//   Return a list of random integers in the range of minval to maxval, inclusive.
 // Arguments:
-//   min = Minimum integer value to return.
-//   max = Maximum integer value to return.
+//   minval = Minimum integer value to return.
+//   maxval = Maximum integer value to return.
 //   N = Number of random integers to return.
 //   seed = If given, sets the random number seed.
 // Example:
 //   ints = rand_int(0,100,3);
 //   int = rand_int(-10,10,1)[0];
-function rand_int(min, max, N, seed=undef) =
-    assert(max >= min, "Max value cannot be smaller than min")
-    let (rvect = is_def(seed) ? rands(min,max+1,N,seed) : rands(min,max+1,N))
+function rand_int(minval, maxval, N, seed=undef) =
+    assert( is_finite(minval+maxval+N) && (is_undef(seed) || is_finite(seed) ), "Input must be finite numbers.")
+    assert(maxval >= minval, "Max value cannot be smaller than minval")
+    let (rvect = is_def(seed) ? rands(minval,maxval+1,N,seed) : rands(minval,maxval+1,N))
     [for(entry = rvect) floor(entry)];
 
 
@@ -358,6 +429,7 @@ function rand_int(min, max, N, seed=undef) =
 //   N = Number of random numbers to return.  Default: 1
 //   seed = If given, sets the random number seed.
 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])];
 
@@ -374,6 +446,10 @@ function gaussian_rands(mean, stddev, N=1, seed=undef) =
 //   N = Number of random numbers to return.  Default: 1
 //   seed = If given, sets the random number seed.
 function log_rands(minval, maxval, factor, N=1, seed=undef) =
+    assert( is_finite(minval+maxval+N) 
+            && (is_undef(seed) || is_finite(seed) )
+            && factor>0, 
+            "Input must be finite numbers. `factor` should be greater than zero.")
     assert(maxval >= minval, "maxval cannot be smaller than minval")
     let(
         minv = 1-1/pow(factor,minval),
@@ -395,18 +471,18 @@ function gcd(a,b) =
     b==0 ? abs(a) : gcd(b,a % b);
 
 
-// Computes lcm for two scalars
+// Computes lcm for two integers
 function _lcm(a,b) =
-    assert(is_int(a), "Invalid non-integer parameters to lcm")
-    assert(is_int(b), "Invalid non-integer parameters to lcm")
-    assert(a!=0 && b!=0, "Arguments to lcm must be nonzero")
+    assert(is_int(a) && is_int(b), "Invalid non-integer parameters to lcm")
+    assert(a!=0 && b!=0, "Arguments to lcm must be non zero")
     abs(a*b) / gcd(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(slice(a,0,len(a)-2),[lcm(a[len(a)-2],a[len(a)-1])]));
 
 
 // Function: lcm()
@@ -418,12 +494,11 @@ function _lcmlist(a) =
 //   be non-zero integers.  The output is always a positive integer.  It is an error to pass zero
 //   as an argument.  
 function lcm(a,b=[]) =
-    !is_list(a) && !is_list(b) ? _lcm(a,b) : 
-    let(
-        arglist = concat(force_list(a),force_list(b))
-    )
-    assert(len(arglist)>0,"invalid call to lcm with empty list(s)")
-    _lcmlist(arglist);
+    !is_list(a) && !is_list(b) 
+    ?   _lcm(a,b) 
+    :   let( arglist = concat(force_list(a),force_list(b)) )
+        assert(len(arglist)>0, "Invalid call to lcm with empty list(s)")
+        _lcmlist(arglist);
 
 
 
@@ -431,8 +506,9 @@ function lcm(a,b=[]) =
 
 // Function: sum()
 // Description:
-//   Returns the sum of all entries in the given list.
-//   If passed an array of vectors, returns a vector of sums of each part.
+//   Returns the sum of all entries in the given consistent list.
+//   If passed an array of vectors, returns the sum the vectors.
+//   If passed an array of matrices, returns the sum of the matrices.
 //   If passed an empty list, the value of `dflt` will be returned.
 // Arguments:
 //   v = The list to get the sum of.
@@ -441,11 +517,10 @@ function lcm(a,b=[]) =
 //   sum([1,2,3]);  // returns 6.
 //   sum([[1,2,3], [3,4,5], [5,6,7]]);  // returns [9, 12, 15]
 function sum(v, dflt=0) =
-    is_vector(v) ? [for(i=v) 1]*v :
+    is_list(v) && len(v) == 0 ? dflt :
+    is_vector(v) || is_matrix(v)? [for(i=v) 1]*v :
     assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
-    is_vector(v[0]) ? [for(i=v) 1]*v :
-    len(v) == 0 ? dflt :
-                  _sum(v,v[0]*0);
+    _sum(v,v[0]*0);
 
 function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
 
@@ -495,37 +570,51 @@ function sum_of_squares(v) = sum(vmul(v,v));
 // Examples:
 //   v = sum_of_sines(30, [[10,3,0], [5,5.5,60]]);
 function sum_of_sines(a, sines) =
-    sum([
-        for (s = sines) let(
-            ss=point3d(s),
-            v=ss.x*sin(a*ss.y+ss.z)
-        ) v
-    ]);
+    assert( is_finite(a) && is_matrix(sines,undef,3), "Invalid input.")
+    sum([ for (s = sines) 
+            let(
+              ss=point3d(s),
+              v=ss[0]*sin(a*ss[1]+ss[2])
+            ) v
+        ]);
 
 
 // Function: deltas()
 // Description:
 //   Returns a list with the deltas of adjacent entries in the given list.
+//   The list should be a consistent list of numeric components (numbers, vectors, matrix, etc).
 //   Given [a,b,c,d], returns [b-a,c-b,d-c].
 // Arguments:
 //   v = The list to get the deltas of.
 // Example:
 //   deltas([2,5,9,17]);  // returns [3,4,8].
 //   deltas([[1,2,3], [3,6,8], [4,8,11]]);  // returns [[2,4,5], [1,2,3]]
-function deltas(v) = [for (p=pair(v)) p.y-p.x];
+function deltas(v) = 
+    assert( is_consistent(v) && len(v)>1 , "Inconsistent list or with length<=1.")
+    [for (p=pair(v)) p[1]-p[0]] ;
 
 
 // Function: product()
 // Description:
 //   Returns the product of all entries in the given list.
-//   If passed an array of vectors, returns a vector of products of each part.
-//   If passed an array of matrices, returns a the resulting product matrix.
+//   If passed a list of vectors of same dimension, returns a vector of products of each part.
+//   If passed a list of square matrices, returns a the resulting product matrix.
 // Arguments:
 //   v = The list to get the product of.
 // Example:
 //   product([2,3,4]);  // returns 24.
 //   product([[1,2,3], [3,4,5], [5,6,7]]);  // returns [15, 48, 105]
-function product(v, i=0, tot=undef) = i>=len(v)? tot : product(v, i+1, ((tot==undef)? v[i] : is_vector(v[i])? vmul(tot,v[i]) : tot*v[i]));
+function product(v) = 
+    assert( is_vector(v) || is_matrix(v) || ( is_matrix(v[0],square=true) && is_consistent(v)), 
+            "Invalid input.")
+    _product(v, 1, v[0]);
+
+function _product(v, i=0, _tot) = 
+    i>=len(v) ? _tot :
+    _product( v, 
+              i+1, 
+              ( is_vector(v[i])? vmul(_tot,v[i]) : _tot*v[i] ) );
+               
 
 
 // Function: outer_product()
@@ -534,21 +623,22 @@ function product(v, i=0, tot=undef) = i>=len(v)? tot : product(v, i+1, ((tot==un
 // Usage:
 //   M = outer_product(u,v);
 function outer_product(u,v) =
-  assert(is_vector(u) && is_vector(v))
-  assert(len(u)==len(v))
-  [for(i=[0:len(u)-1]) [for(j=[0:len(u)-1]) u[i]*v[j]]];
+  assert(is_vector(u) && is_vector(v), "The inputs must be vectors.")
+  [for(ui=u) ui*v];
 
 
 // Function: mean()
 // Description:
-//   Returns the arithmatic mean/average of all entries in the given array.
+//   Returns the arithmetic mean/average of all entries in the given array.
 //   If passed a list of vectors, returns a vector of the mean of each part.
 // Arguments:
 //   v = The list of values to get the mean of.
 // Example:
 //   mean([2,3,4]);  // returns 3.
 //   mean([[1,2,3], [3,4,5], [5,6,7]]);  // returns [3, 4, 5]
-function mean(v) = sum(v)/len(v);
+function mean(v) = 
+    assert(is_list(v) && len(v)>0, "Invalid list.")
+    sum(v)/len(v);
 
 
 // Function: median()
@@ -556,18 +646,33 @@ function mean(v) = sum(v)/len(v);
 //   x = median(v);
 // Description:
 //   Given a list of numbers or vectors, finds the median value or midpoint.
-//   If passed a list of vectors, returns the vector of the median of each part.
+//   If passed a list of vectors, returns the vector of the median of each component.
 function median(v) =
-    assert(is_list(v))
-    assert(len(v)>0)
-    is_vector(v[0])? (
-        assert(is_consistent(v))
-        [
-            for (i=idx(v[0]))
-            let(vals = subindex(v,i))
-            (min(vals)+max(vals))/2
-        ]
-    ) : (min(v)+max(v))/2;
+    is_vector(v) ? (min(v)+max(v))/2 :
+    is_matrix(v) ? [for(ti=transpose(v))  (min(ti)+max(ti))/2 ]
+    :   assert(false , "Invalid input.");
+
+// Function: convolve()
+// Usage:
+//   x = convolve(p,q);
+// Description:
+//   Given two vectors, finds the convolution of them.
+//   The length of the returned vector is len(p)+len(q)-1 .
+// Arguments:
+//   p = The first vector.
+//   q = The second vector.
+// Example:
+//   a = convolve([1,1],[1,2,1]); // Returns: [1,3,3,1]
+//   b = convolve([1,2,3],[1,2,1])); // Returns: [1,4,8,8,3]
+function convolve(p,q) =
+    p==[] || q==[] ? [] :
+    assert( is_vector(p) && is_vector(q), "The inputs should be vectors.")
+    let( n = len(p),
+         m = len(q))
+    [for(i=[0:n+m-2], k1 = max(0,i-n+1), k2 = min(i,m-1) )
+       [for(j=[k1:k2]) p[i-j] ] * [for(j=[k1:k2]) q[j] ] 
+    ];
+
 
 
 // Section: Matrix math
@@ -582,7 +687,7 @@ function median(v) =
 //   want to solve Ax=b1 and Ax=b2 that you need to form the matrix transpose([b1,b2]) for the right hand side and then
 //   transpose the returned value.  
 function linear_solve(A,b) =
-    assert(is_matrix(A))
+    assert(is_matrix(A), "Input should be a matrix.")
     let(
         m = len(A),
         n = len(A[0])
@@ -619,8 +724,12 @@ function matrix_inverse(A) =
 // Description:
 //   Returns a submatrix with the specified index ranges or index sets.  
 function submatrix(M,ind1,ind2) =
-    [for(i=ind1) [for(j=ind2) M[i][j] ] ];
-
+    assert( is_matrix(M), "Input must be a matrix." )
+    [for(i=ind1) 
+        [for(j=ind2) 
+            assert( ! is_undef(M[i][j]), "Invalid indexing." )
+            M[i][j] ] ];
+    
 
 // Function: qr_factor()
 // Usage: qr = qr_factor(A)
@@ -628,7 +737,7 @@ function submatrix(M,ind1,ind2) =
 //   Calculates the QR factorization of the input matrix A and returns it as the list [Q,R].  This factorization can be
 //   used to solve linear systems of equations.  
 function qr_factor(A) =
-    assert(is_matrix(A))
+    assert(is_matrix(A), "Input must be a matrix." )
     let(
       m = len(A),
       n = len(A[0])
@@ -659,8 +768,8 @@ function _qr_factor(A,Q, column, m, n) =
 // Function: back_substitute()
 // Usage: back_substitute(R, b, [transpose])
 // Description:
-//   Solves the problem Rx=b where R is an upper triangular square matrix.  No check is made that the lower triangular entries
-//   are actually zero.  If transpose==true then instead solve transpose(R)*x=b.
+//   Solves the problem Rx=b where R is an upper triangular square matrix.  The lower triangular entries of R are
+//   ignored.  If transpose==true then instead solve transpose(R)*x=b.
 //   You can supply a compatible matrix b and it will produce the solution for every column of b.  Note that if you want to
 //   solve Rx=b1 and Rx=b2 you must set b to transpose([b1,b2]) and then take the transpose of the result.  If the matrix
 //   is singular (e.g. has a zero on the diagonal) then it returns [].  
@@ -694,7 +803,9 @@ function back_substitute(R, b, x=[],transpose = false) =
 // Example:
 //   M = [ [6,-2], [1,8] ];
 //   det = det2(M);  // Returns: 50
-function det2(M) = M[0][0] * M[1][1] - M[0][1]*M[1][0];
+function det2(M) = 
+    assert( is_matrix(M,2,2), "Matrix should be 2x2." )
+    M[0][0] * M[1][1] - M[0][1]*M[1][0];
 
 
 // Function: det3()
@@ -706,6 +817,7 @@ function det2(M) = M[0][0] * M[1][1] - M[0][1]*M[1][0];
 //   M = [ [6,4,-2], [1,-2,8], [1,5,7] ];
 //   det = det3(M);  // Returns: -334
 function det3(M) =
+    assert( is_matrix(M,3,3), "Matrix should be 3x3." )
     M[0][0] * (M[1][1]*M[2][2]-M[2][1]*M[1][2]) -
     M[1][0] * (M[0][1]*M[2][2]-M[2][1]*M[0][2]) +
     M[2][0] * (M[0][1]*M[1][2]-M[1][1]*M[0][2]);
@@ -720,21 +832,21 @@ function det3(M) =
 //   M = [ [6,4,-2,9], [1,-2,8,3], [1,5,7,6], [4,2,5,1] ];
 //   det = determinant(M);  // Returns: 2267
 function determinant(M) =
-    assert(len(M)==len(M[0]))
+    assert(is_matrix(M,square=true), "Input should be a square matrix." )
     len(M)==1? M[0][0] :
     len(M)==2? det2(M) :
     len(M)==3? det3(M) :
     sum(
         [for (col=[0:1:len(M)-1])
             ((col%2==0)? 1 : -1) *
-            M[col][0] *
-            determinant(
-                [for (r=[1:1:len(M)-1])
-                    [for (c=[0:1:len(M)-1])
-                        if (c!=col) M[c][r]
+                M[col][0] *
+                determinant(
+                    [for (r=[1:1:len(M)-1])
+                        [for (c=[0:1:len(M)-1])
+                            if (c!=col) M[c][r]
+                        ]
                     ]
-                ]
-            )
+                )
         ]
     );
 
@@ -753,8 +865,11 @@ function determinant(M) =
 //   n = optional width of matrix
 //   square = set to true to require a square matrix.  Default: false        
 function is_matrix(A,m,n,square=false) =
-    is_vector(A[0],n) && is_vector(A*(0*A[0]),m) &&
-    (!square || len(A)==len(A[0]));
+    is_list(A[0]) 
+    && ( let(v = A*A[0]) is_num(0*(v*v)) ) // a matrix of finite numbers 
+    && (is_undef(n) || len(A[0])==n )
+    && (is_undef(m) || len(A)==m )
+    && ( !square || len(A)==len(A[0]));
 
 
 // Section: Comparisons and Logic
@@ -774,11 +889,13 @@ function is_matrix(A,m,n,square=false) =
 //   approx(0.3333,1/3);          // Returns: false
 //   approx(0.3333,1/3,eps=1e-3);  // Returns: true
 //   approx(PI,3.1415926536);     // Returns: true
-function approx(a,b,eps=EPSILON) =
+function approx(a,b,eps=EPSILON) = 
     a==b? true :
     a*0!=b*0? false :
-    is_list(a)? ([for (i=idx(a)) if(!approx(a[i],b[i],eps=eps)) 1] == []) :
-    (abs(a-b) <= eps);
+    is_list(a)
+    ? ([for (i=idx(a)) if( !approx(a[i],b[i],eps=eps)) 1] == [])
+    : is_num(a) && is_num(b) && (abs(a-b) <= eps);
+    
 
 
 function _type_num(x) =
@@ -796,7 +913,7 @@ function _type_num(x) =
 // Description:
 //   Compares two values.  Lists are compared recursively.
 //   Returns <0 if a<b.  Returns >0 if a>b.  Returns 0 if a==b.
-//   If types are not the same, then undef < bool < num < str < list < range.
+//   If types are not the same, then undef < bool < nan < num < str < list < range.
 // Arguments:
 //   a = First value to compare.
 //   b = Second value to compare.
@@ -820,13 +937,14 @@ function compare_vals(a, b) =
 //   a = First list to compare.
 //   b = Second list to compare.
 function compare_lists(a, b) =
-    a==b? 0 : let(
-        cmps = [
-            for(i=[0:1:min(len(a),len(b))-1]) let(
-                cmp = compare_vals(a[i],b[i])
-            ) if(cmp!=0) cmp
-        ]
-    ) cmps==[]? (len(a)-len(b)) : cmps[0];
+    a==b? 0 
+    :   let(
+          cmps = [ for(i=[0:1:min(len(a),len(b))-1]) 
+                      let( cmp = compare_vals(a[i],b[i]) )
+                      if(cmp!=0) cmp 
+                 ]
+           ) 
+        cmps==[]? (len(a)-len(b)) : cmps[0];
 
 
 // Function: any()
@@ -843,12 +961,11 @@ function compare_lists(a, b) =
 //   any([[0,0], [1,0]]);   // Returns true.
 function any(l, i=0, succ=false) =
     (i>=len(l) || succ)? succ :
-    any(
-        l, i=i+1, succ=(
-            is_list(l[i])? any(l[i]) :
-            !(!l[i])
-        )
-    );
+    any( l, 
+         i+1, 
+         succ = is_list(l[i]) ? any(l[i]) : !(!l[i])
+        );
+
 
 
 // Function: all()
@@ -865,13 +982,12 @@ function any(l, i=0, succ=false) =
 //   all([[0,0], [1,0]]);   // Returns false.
 //   all([[1,1], [1,1]]);   // Returns true.
 function all(l, i=0, fail=false) =
-    (i>=len(l) || fail)? (!fail) :
-    all(
-        l, i=i+1, fail=(
-            is_list(l[i])? !all(l[i]) :
-            !l[i]
-        )
-    );
+    (i>=len(l) || fail)? !fail :
+    all( l, 
+         i+1,
+         fail = is_list(l[i]) ? !all(l[i]) : !l[i]
+        ) ;
+
 
 
 // Function: count_true()
@@ -904,6 +1020,21 @@ function count_true(l, nmax=undef, i=0, cnt=0) =
     );
 
 
+function count_true(l, nmax) = 
+    !is_list(l) ? !(!l) ? 1: 0 :
+    let( c = [for( i = 0,
+                   n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
+                   c = !is_undef(n)? n : count_true(l[i], nmax),
+                   s = c;
+                 i<len(l) && (is_undef(nmax) || s<nmax);
+                   i = i+1,
+                   n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
+                   c = !is_undef(n) || (i==len(l))? n : count_true(l[i], nmax-s),
+                   s = s+c
+                 )  s ] )
+    len(c)<len(l)? nmax: c[len(c)-1];
+
+
 
 // Section: Calculus
 
@@ -921,42 +1052,49 @@ function count_true(l, nmax=undef, i=0, cnt=0) =
 //   between data[i+1] and data[i], and the data values will be linearly resampled at each corner
 //   to produce a uniform spacing for the derivative estimate.  At the endpoints a single point method
 //   is used: f'(t) = (f(t+h)-f(t))/h.  
+// Arguments:
+//   data = the list of the elements to compute the derivative of.
+//   h = the parametric sampling of the data.
+//   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv(data, h=1, closed=false) =
+    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
+    assert( len(data)>=2, "Input `data` should have at least 2 elements.") 
+    assert( is_finite(h) || is_vector(h), "The sampling `h` must be a number or a list of numbers." )
+    assert( is_num(h) || len(h) == len(data)-(closed?0:1),
+            str("Vector valued `h` must have length ",len(data)-(closed?0:1)))
     is_vector(h) ? _deriv_nonuniform(data, h, closed=closed) :
     let( L = len(data) )
-    closed? [
+    closed
+    ? [
         for(i=[0:1:L-1])
         (data[(i+1)%L]-data[(L+i-1)%L])/2/h
-    ] :
-    let(
-        first =
-            L<3? data[1]-data[0] : 
-            3*(data[1]-data[0]) - (data[2]-data[1]),
-        last =
-            L<3? data[L-1]-data[L-2]:
-            (data[L-3]-data[L-2])-3*(data[L-2]-data[L-1])
-    ) [
+      ]
+    : let(
+        first = L<3 ? data[1]-data[0] : 
+                3*(data[1]-data[0]) - (data[2]-data[1]),
+        last = L<3 ? data[L-1]-data[L-2]:
+               (data[L-3]-data[L-2])-3*(data[L-2]-data[L-1])
+         ) 
+      [
         first/2/h,
         for(i=[1:1:L-2]) (data[i+1]-data[i-1])/2/h,
         last/2/h
-    ];
+      ];
 
 
 function _dnu_calc(f1,fc,f2,h1,h2) =
     let(
         f1 = h2<h1 ? lerp(fc,f1,h2/h1) : f1 , 
         f2 = h1<h2 ? lerp(fc,f2,h1/h2) : f2
-    )
-    (f2-f1) / 2 / min([h1,h2]);
+       )
+    (f2-f1) / 2 / min(h1,h2);
 
 
 function _deriv_nonuniform(data, h, closed) =
-    assert(len(h) == len(data)-(closed?0:1),str("Vector valued h must be length ",len(data)-(closed?0:1)))
-    let(
-      L = len(data)
-    )
-    closed? [for(i=[0:1:L-1])
-    	        _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
+    let( L = len(data) )
+    closed
+    ? [for(i=[0:1:L-1])
+          _dnu_calc(data[(L+i-1)%L], data[i], data[(i+1)%L], select(h,i-1), h[i]) ]
     : [
         (data[1]-data[0])/h[0],
         for(i=[1:1:L-2]) _dnu_calc(data[i-1],data[i],data[i+1], h[i-1],h[i]),
@@ -967,15 +1105,23 @@ function _deriv_nonuniform(data, h, closed) =
 // Function: deriv2()
 // Usage: deriv2(data, [h], [closed])
 // Description:
-//   Computes a numerical esimate of the second derivative of the data, which may be scalar or vector valued.
+//   Computes a numerical estimate of the second derivative of the data, which may be scalar or vector valued.
 //   The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. 
 //   If the `closed` parameter is true the data is assumed to be defined on a loop with data[0] adjacent to
 //   data[len(data)-1].  For internal points this function uses the approximation 
-//   f''(t) = (f(t-h)-2*f(t)+f(t+h))/h^2.  For the endpoints (when closed=false) the algorithm
-//   when sufficient points are available the method is either the four point expression
-//   f''(t) = (2*f(t) - 5*f(t+h) + 4*f(t+2*h) - f(t+3*h))/h^2 or if five points are available
+//   f''(t) = (f(t-h)-2*f(t)+f(t+h))/h^2.  For the endpoints (when closed=false),
+//   when sufficient points are available, the method is either the four point expression
+//   f''(t) = (2*f(t) - 5*f(t+h) + 4*f(t+2*h) - f(t+3*h))/h^2 or 
 //   f''(t) = (35*f(t) - 104*f(t+h) + 114*f(t+2*h) - 56*f(t+3*h) + 11*f(t+4*h)) / 12h^2
+//   if five points are available.
+// Arguments:
+//   data = the list of the elements to compute the derivative of.
+//   h = the constant parametric sampling of the data.
+//   closed = boolean to indicate if the data set should be wrapped around from the end to the start.
 function deriv2(data, h=1, closed=false) =
+    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
+    assert( len(data)>=3, "Input list has less than 3 elements.") 
+    assert( is_finite(h), "The sampling `h` must be a number." )
     let( L = len(data) )
     closed? [
         for(i=[0:1:L-1])
@@ -1003,16 +1149,19 @@ function deriv2(data, h=1, closed=false) =
 //   Computes a numerical third derivative estimate of the data, which may be scalar or vector valued.
 //   The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. 
 //   If the `closed` parameter is true the data is assumed to be defined on a loop with data[0] adjacent to
-//   data[len(data)-1].  This function uses a five point derivative estimate, so the input must include five points:
+//   data[len(data)-1].  This function uses a five point derivative estimate, so the input data must include 
+//   at least five points:
 //   f'''(t) = (-f(t-2*h)+2*f(t-h)-2*f(t+h)+f(t+2*h)) / 2h^3.  At the first and second points from the end
 //   the estimates are f'''(t) = (-5*f(t)+18*f(t+h)-24*f(t+2*h)+14*f(t+3*h)-3*f(t+4*h)) / 2h^3 and
 //   f'''(t) = (-3*f(t-h)+10*f(t)-12*f(t+h)+6*f(t+2*h)-f(t+3*h)) / 2h^3.
 function deriv3(data, h=1, closed=false) =
+    assert( is_consistent(data) , "Input list is not consistent or not numerical.") 
+    assert( len(data)>=5, "Input list has less than 5 elements.") 
+    assert( is_finite(h), "The sampling `h` must be a number." )
     let(
         L = len(data),
         h3 = h*h*h
     )
-    assert(L>=5, "Need five points for 3rd derivative estimate")
     closed? [
         for(i=[0:1:L-1])
         (-data[(L+i-2)%L]+2*data[(L+i-1)%L]-2*data[(i+1)%L]+data[(i+2)%L])/2/h3
@@ -1036,58 +1185,101 @@ function deriv3(data, h=1, closed=false) =
 // Function: C_times()
 // Usage: C_times(z1,z2)
 // Description:
-//   Multiplies two complex numbers.  
-function C_times(z1,z2) = [z1.x*z2.x-z1.y*z2.y,z1.x*z2.y+z1.y*z2.x];
+//   Multiplies two complex numbers represented by 2D vectors.  
+function C_times(z1,z2) = 
+    assert( is_vector(z1+z2,2), "Complex numbers should be represented by 2D vectors." )
+    [ z1.x*z2.x - z1.y*z2.y, z1.x*z2.y + z1.y*z2.x ];
 
 // Function: C_div()
 // Usage: C_div(z1,z2)
 // Description:
-//   Divides z1 by z2.  
-function C_div(z1,z2) = let(den = z2.x*z2.x + z2.y*z2.y)
-   [(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x-z1.x*z2.y)/den];
+//   Divides two complex numbers represented by 2D vectors.  
+function C_div(z1,z2) = 
+    assert( is_vector(z1,2) && is_vector(z2), "Complex numbers should be represented by 2D vectors." )
+    assert( !approx(z2,0), "The divisor `z2` cannot be zero." ) 
+    let(den = z2.x*z2.x + z2.y*z2.y)
+    [(z1.x*z2.x + z1.y*z2.y)/den, (z1.y*z2.x - z1.x*z2.y)/den];
 
+// For the sake of consistence with Q_mul and vmul, C_times should be called C_mul
 
 // Section: Polynomials
 
-// Function: polynomial()
+// Function: polynomial() 
 // Usage:
 //   polynomial(p, z)
 // Description:
 //   Evaluates specified real polynomial, p, at the complex or real input value, z.
 //   The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]
 //   where a_n is the z^n coefficient.  Polynomial coefficients are real.
+//   The result is a number if `z` is a number and a complex number otherwise.
 
 // Note: this should probably be recoded to use division by [1,-z], which is more accurate
 // and avoids overflow with large coefficients, but requires poly_div to support complex coefficients.  
-function polynomial(p, z, k, zk, total) =
-   is_undef(k) ? polynomial(p, z, len(p)-1, is_num(z)? 1 : [1,0], is_num(z) ? 0 : [0,0]) :
-   k==-1 ? total :
-   polynomial(p, z, k-1, is_num(z) ? zk*z : C_times(zk,z), total+zk*p[k]);
+function polynomial(p, z, _k, _zk, _total) =
+    is_undef(_k)  
+    ?   assert( is_vector(p), "Input polynomial coefficients must be a vector." )
+        let(p = _poly_trim(p))
+        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
+        polynomial( p, 
+                    z, 
+                    len(p)-1, 
+                    is_num(z)? 1 : [1,0], 
+                    is_num(z) ? 0 : [0,0]) 
+    :   _k==0 
+        ? _total + +_zk*p[0]
+        : polynomial( p, 
+                      z, 
+                      _k-1, 
+                      is_num(z) ? _zk*z : C_times(_zk,z), 
+                      _total+_zk*p[_k]);
 
+function polynomial(p,z,k,total) =
+     is_undef(k)
+   ?    assert( is_vector(p) , "Input polynomial coefficients must be a vector." )
+        assert( is_finite(z) || is_vector(z,2), "The value of `z` must be a real or a complex number." )
+        polynomial( _poly_trim(p), z, 0, is_num(z) ? 0 : [0,0])
+   : k==len(p) ? total
+   : polynomial(p,z,k+1, is_num(z) ? total*z+p[k] : C_times(total,z)+[p[k],0]);
 
 // Function: poly_mult()
 // Usage
 //   polymult(p,q)
 //   polymult([p1,p2,p3,...])
-// Descriptoin:
+// Description:
 //   Given a list of polynomials represented as real coefficient lists, with the highest degree coefficient first, 
 //   computes the coefficient list of the product polynomial.  
 function poly_mult(p,q) = 
-  is_undef(q) ?
-     assert(is_list(p) && (is_vector(p[0]) || p[0]==[]), "Invalid arguments to poly_mult")
-     len(p)==2 ? poly_mult(p[0],p[1]) 
-               : poly_mult(p[0], poly_mult(select(p,1,-1)))
-  :
-  _poly_trim(
-  [
-  for(n = [len(p)+len(q)-2:-1:0])
-      sum( [for(i=[0:1:len(p)-1])
-           let(j = len(p)+len(q)- 2 - n - i)
-           if (j>=0 && j<len(q)) p[i]*q[j]
-               ])
-   ]);        
-
+    is_undef(q) ?
+       assert( is_list(p) 
+               && []==[for(pi=p) if( !is_vector(pi) && pi!=[]) 0], 
+               "Invalid arguments to poly_mult")
+       len(p)==2 ? poly_mult(p[0],p[1]) 
+                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
+    :
+    _poly_trim(
+    [
+    for(n = [len(p)+len(q)-2:-1:0])
+        sum( [for(i=[0:1:len(p)-1])
+             let(j = len(p)+len(q)- 2 - n - i)
+             if (j>=0 && j<len(q)) p[i]*q[j]
+                 ])
+     ]);        
+     
+function poly_mult(p,q) = 
+    is_undef(q) ?
+       len(p)==2 ? poly_mult(p[0],p[1]) 
+                 : poly_mult(p[0], poly_mult(select(p,1,-1)))
+    :
+    assert( is_vector(p) && is_vector(q),"Invalid arguments to poly_mult")
+    _poly_trim( [
+                  for(n = [len(p)+len(q)-2:-1:0])
+                      sum( [for(i=[0:1:len(p)-1])
+                           let(j = len(p)+len(q)- 2 - n - i)
+                           if (j>=0 && j<len(q)) p[i]*q[j]
+                               ])
+                   ]);
 
+    
 // Function: poly_div()
 // Usage:
 //    [quotient,remainder] = poly_div(n,d)
@@ -1096,15 +1288,19 @@ function poly_mult(p,q) =
 //    a list of two polynomials, [quotient, remainder].  If the division has no remainder then
 //    the zero polynomial [] is returned for the remainder.  Similarly if the quotient is zero
 //    the returned quotient will be [].  
-function poly_div(n,d,q=[]) =
-    assert(len(d)>0 && d[0]!=0 , "Denominator is zero or has leading zero coefficient")
-    len(n)<len(d) ? [q,_poly_trim(n)] : 
-    let(
-      t = n[0] / d[0],
-      newq = concat(q,[t]),
-      newn =  [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
-    )  
-    poly_div(newn,d,newq);
+function poly_div(n,d,q) =
+    is_undef(q) 
+    ?   assert( is_vector(n) && is_vector(d) , "Invalid polynomials." )
+        let( d = _poly_trim(d) )
+        assert( d!=[0] , "Denominator cannot be a zero polynomial." )
+        poly_div(n,d,q=[])
+    :   len(n)<len(d) ? [q,_poly_trim(n)] : 
+        let(
+          t = n[0] / d[0], 
+          newq = concat(q,[t]),
+          newn = [for(i=[1:1:len(n)-1]) i<len(d) ? n[i] - t*d[i] : n[i]]
+        )  
+        poly_div(newn,d,newq);
 
 
 // Internal Function: _poly_trim()
@@ -1114,8 +1310,8 @@ function poly_div(n,d,q=[]) =
 //    Removes leading zero terms of a polynomial.  By default zeros must be exact,
 //    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 ? [] : select(p,nz[0],-1);
+    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);
 
 
 // Function: poly_add()
@@ -1124,12 +1320,13 @@ function _poly_trim(p,eps=0) =
 // Description:
 //    Computes the sum of two polynomials.  
 function poly_add(p,q) = 
-  let(  plen = len(p),
-        qlen = len(q),
-        long = plen>qlen ? p : q,
-        short = plen>qlen ? q : p
-     )
-   _poly_trim(long + concat(repeat(0,len(long)-len(short)),short));
+    assert( is_vector(p) && is_vector(q), "Invalid input polynomial(s)." )
+    let(  plen = len(p),
+          qlen = len(q),
+          long = plen>qlen ? p : q,
+          short = plen>qlen ? q : p
+       )
+     _poly_trim(long + concat(repeat(0,len(long)-len(short)),short));
 
 
 // Function: poly_roots()
@@ -1150,38 +1347,38 @@ function poly_add(p,q) =
 //
 // Dario Bini. "Numerical computation of polynomial zeros by means of Aberth's Method", Numerical Algorithms, Feb 1996.
 // https://www.researchgate.net/publication/225654837_Numerical_computation_of_polynomial_zeros_by_means_of_Aberth's_method
-
 function poly_roots(p,tol=1e-14,error_bound=false) =
-  assert(p!=[], "Input polynomial must have a nonzero coefficient")
-  assert(is_vector(p), "Input must be a vector")
-  p[0] == 0 ? poly_roots(slice(p,1,-1),tol=tol,error_bound=error_bound) :    // Strip leading zero coefficients
-  p[len(p)-1] == 0 ?                                       // Strip trailing zero coefficients
-      let( solutions = poly_roots(select(p,0,-2),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
-  len(p)==2 ? let( solution = [[-p[1]/p[0],0]])            // Linear case needs special handling
-              (error_bound ? [solution,[0]] : solution)
-  : 
-  let(
-      n = len(p)-1,   // polynomial degree
-      pderiv = [for(i=[0:n-1]) p[i]*(n-i)],
-         
-      s = [for(i=[0:1:n]) abs(p[i])*(4*(n-i)+1)],  // Error bound polynomial from Bini
+    assert( is_vector(p), "Invalid polynomial." )
+    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))
+        (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
+    len(p)==2 ? let( solution = [[-p[1]/p[0],0]])            // Linear case needs special handling
+                (error_bound ? [solution,[0]] : solution)
+    : 
+    let(
+        n = len(p)-1,   // polynomial degree
+        pderiv = [for(i=[0:n-1]) p[i]*(n-i)],
+           
+        s = [for(i=[0:1:n]) abs(p[i])*(4*(n-i)+1)],  // Error bound polynomial from Bini
 
-      // Using method from: http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/0915-24.pdf
-      beta = -p[1]/p[0]/n,
-      r = 1+pow(abs(polynomial(p,beta)/p[0]),1/n),
-      init = [for(i=[0:1:n-1])                // Initial guess for roots       
-               let(angle = 360*i/n+270/n/PI)
-               [beta,0]+r*[cos(angle),sin(angle)]
-             ],
-      roots = _poly_roots(p,pderiv,s,init,tol=tol),
-      error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) /
-                                abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0
-    )
-    error_bound ? [roots, error] : roots;
+        // Using method from: http://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/0915-24.pdf
+        beta = -p[1]/p[0]/n,
+        r = 1+pow(abs(polynomial(p,beta)/p[0]),1/n),
+        init = [for(i=[0:1:n-1])                // Initial guess for roots       
+                 let(angle = 360*i/n+270/n/PI)
+                 [beta,0]+r*[cos(angle),sin(angle)]
+               ],
+        roots = _poly_roots(p,pderiv,s,init,tol=tol),
+        error = error_bound ? [for(xi=roots) n * (norm(polynomial(p,xi))+tol*polynomial(s,norm(xi))) /
+                                  abs(norm(polynomial(pderiv,xi))-tol*polynomial(s,norm(xi)))] : 0
+      )
+      error_bound ? [roots, error] : roots;
 
+// Internal function
 // p = polynomial
 // pderiv = derivative polynomial of p
 // z = current guess for the roots
@@ -1222,12 +1419,16 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) =
 //   tol = tolerance for the complex polynomial root finder
 
 function real_roots(p,eps=undef,tol=1e-14) =
-   let( 
+    assert( is_vector(p), "Invalid polynomial." )
+    let( p = _poly_trim(p,eps=0) )
+    assert( p!=[0], "Input polynomial cannot be zero." )
+    let( 
        roots_err = poly_roots(p,error_bound=true),
        roots = roots_err[0],
        err = roots_err[1]
-   )
-   is_def(eps) ? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
-               : [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
+    )
+    is_def(eps) 
+    ? [for(z=roots) if (abs(z.y)/(1+norm(z))<eps) z.x]
+    : [for(i=idx(roots)) if (abs(roots[i].y)<=err[i]) roots[i].x];
 
 // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
diff --git a/tests/test_common.scad b/tests/test_common.scad
index 1cad5e3..871eac2 100644
--- a/tests/test_common.scad
+++ b/tests/test_common.scad
@@ -169,7 +169,6 @@ module test_is_range() {
     assert(!is_range(5));
     assert(!is_range(INF));
     assert(!is_range(-INF));
-    assert(!is_nan(NAN));
     assert(!is_range(""));
     assert(!is_range("foo"));
     assert(!is_range([]));
@@ -179,9 +178,24 @@ module test_is_range() {
     assert(!is_range([3:4:"a"]));
     assert(is_range([3:1:5]));
 }
-test_is_nan();
+test_is_range();
 
 
+module test_valid_range() {
+    assert(valid_range([0:0]));
+    assert(valid_range([0:1:0]));
+    assert(valid_range([0:1:10]));
+    assert(valid_range([0.1:1.1:2.1]));
+    assert(valid_range([0:-1:0]));
+    assert(valid_range([10:-1:0]));
+    assert(valid_range([2.1:-1.1:0.1]));
+    assert(!valid_range([10:1:0]));
+    assert(!valid_range([2.1:1.1:0.1]));
+    assert(!valid_range([0:-1:10]));
+    assert(!valid_range([0.1:-1.1:2.1]));
+}
+test_valid_range();
+
 module test_is_list_of() {
     assert(is_list_of([3,4,5], 0));
     assert(!is_list_of([3,4,undef], 0));
@@ -192,10 +206,14 @@ module test_is_list_of() {
 }
 test_is_list_of();
 
-
 module test_is_consistent() {
+    assert(is_consistent([]));
+    assert(is_consistent([[],[]]));
     assert(is_consistent([3,4,5]));
     assert(is_consistent([[3,4],[4,5],[6,7]]));
+    assert(is_consistent([[[3],4],[[4],5]]));
+    assert(!is_consistent(5));
+    assert(!is_consistent(undef));
     assert(!is_consistent([[3,4,5],[3,4]]));
     assert(is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]));
     assert(!is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]));
diff --git a/tests/test_math.scad b/tests/test_math.scad
index 3233e6f..f1b36b9 100644
--- a/tests/test_math.scad
+++ b/tests/test_math.scad
@@ -110,6 +110,8 @@ module test_approx() {
     assert_equal(approx(1/3, 0.3333333333), true);
     assert_equal(approx(-1/3, -0.3333333333), true);
     assert_equal(approx(10*[cos(30),sin(30)], 10*[sqrt(3)/2, 1/2]), true);
+    assert_equal(approx([1,[1,undef]], [1+1e-12,[1,true]]), false);
+    assert_equal(approx([1,[1,undef]], [1+1e-12,[1,undef]]), true);
 }
 test_approx();
 
@@ -389,7 +391,6 @@ module test_mean() {
 }
 test_mean();
 
-
 module test_median() {
     assert_equal(median([2,3,7]), 4.5);
     assert_equal(median([[1,2,3], [3,4,5], [8,9,10]]), [4.5,5.5,6.5]);
@@ -397,6 +398,16 @@ module test_median() {
 test_median();
 
 
+module test_convolve() {
+    assert_equal(convolve([],[1,2,1]), []);
+    assert_equal(convolve([1,1],[]), []);
+    assert_equal(convolve([1,1],[1,2,1]), [1,3,3,1]);
+    assert_equal(convolve([1,2,3],[1,2,1]), [1,4,8,8,3]);
+}
+test_convolve();
+
+
+
 module test_matrix_inverse() {
     assert_approx(matrix_inverse(rot([20,30,40])), [[0.663413948169,0.556670399226,-0.5,0],[-0.47302145844,0.829769465589,0.296198132726,0],[0.579769465589,0.0400087565481,0.813797681349,0],[0,0,0,1]]);
 }
@@ -583,6 +594,24 @@ module test_factorial() {
 }
 test_factorial();
 
+module test_binomial() {
+    assert_equal(binomial(1), [1,1]);
+    assert_equal(binomial(2), [1,2,1]);
+    assert_equal(binomial(3), [1,3,3,1]);
+    assert_equal(binomial(5), [1,5,10,10,5,1]);
+}
+test_binomial();
+
+module test_binomial_coefficient() {
+    assert_equal(binomial_coefficient(2,1), 2);
+    assert_equal(binomial_coefficient(3,2), 3);
+    assert_equal(binomial_coefficient(4,2), 6);
+    assert_equal(binomial_coefficient(10,7), 120);
+    assert_equal(binomial_coefficient(10,7), binomial(10)[7]);
+    assert_equal(binomial_coefficient(15,4), binomial(15)[4]);
+}
+test_binomial_coefficient();
+
 
 module test_gcd() {
     assert_equal(gcd(15,25), 5);
@@ -682,6 +711,7 @@ test_linear_solve();
 
 module test_outer_product(){
   assert_equal(outer_product([1,2,3],[4,5,6]), [[4,5,6],[8,10,12],[12,15,18]]);
+  assert_equal(outer_product([1,2],[4,5,6]), [[4,5,6],[8,10,12]]);
   assert_equal(outer_product([9],[7]), [[63]]);
 }
 test_outer_product();
@@ -782,8 +812,10 @@ test_deriv3();
 
 
 module test_polynomial(){
-  assert_equal(polynomial([],12),0);
-  assert_equal(polynomial([],[12,4]),[0,0]);
+  assert_equal(polynomial([0],12),0);
+  assert_equal(polynomial([0],[12,4]),[0,0]);
+//  assert_equal(polynomial([],12),0);
+//  assert_equal(polynomial([],[12,4]),[0,0]);
   assert_equal(polynomial([1,2,3,4],3),58);
   assert_equal(polynomial([1,2,3,4],[3,-1]),[47,-41]);
   assert_equal(polynomial([0,0,2],4),2);
@@ -879,16 +911,20 @@ test_qr_factor();
 
 module test_poly_mult(){
   assert_equal(poly_mult([3,2,1],[4,5,6,7]),[12,23,32,38,20,7]);
-  assert_equal(poly_mult([3,2,1],[]),[]);
+  assert_equal(poly_mult([3,2,1],[0]),[0]);
+//  assert_equal(poly_mult([3,2,1],[]),[]);
   assert_equal(poly_mult([[1,2],[3,4],[5,6]]), [15,68,100,48]);
-  assert_equal(poly_mult([[1,2],[],[5,6]]), []);
-  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[]);
+  assert_equal(poly_mult([[1,2],[0],[5,6]]), [0]);
+//  assert_equal(poly_mult([[1,2],[],[5,6]]), []);
+  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[0]);
+//  assert_equal(poly_mult([[3,4,5],[0,0,0]]),[]);
 }
 test_poly_mult();
 
- 
+
 module test_poly_div(){
-  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[]]);
+  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[0]]);
+//  assert_equal(poly_div(poly_mult([4,3,3,2],[2,1,3]), [2,1,3]),[[4,3,3,2],[]]);
   assert_equal(poly_div([1,2,3,4],[1,2,3,4,5]), [[], [1,2,3,4]]);
   assert_equal(poly_div(poly_add(poly_mult([1,2,3,4],[2,0,2]), [1,1,2]), [1,2,3,4]), [[2,0,2],[1,1,2]]);
   assert_equal(poly_div([1,2,3,4], [1,-3]), [[1,5,18],[58]]);
@@ -899,7 +935,8 @@ test_poly_div();
 module test_poly_add(){
   assert_equal(poly_add([2,3,4],[3,4,5,6]),[3,6,8,10]);
   assert_equal(poly_add([1,2,3,4],[-1,-2,3,4]), [6,8]);
-  assert_equal(poly_add([1,2,3],-[1,2,3]),[]);
+  assert_equal(poly_add([1,2,3],-[1,2,3]),[0]);
+//  assert_equal(poly_add([1,2,3],-[1,2,3]),[]);
 }
 test_poly_add();
 

From aa0e77b1f492fd28c22556b54f43bbd266ca22d7 Mon Sep 17 00:00:00 2001
From: Garth Minette <gminette@gmail.com>
Date: Wed, 29 Jul 2020 21:58:12 -0700
Subject: [PATCH 24/24] Minor fixes for PR #216

---
 arrays.scad  | 4 ++--
 math.scad    | 6 +++---
 vectors.scad | 2 ++
 version.scad | 2 +-
 4 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/arrays.scad b/arrays.scad
index 5068d3b..5154407 100644
--- a/arrays.scad
+++ b/arrays.scad
@@ -66,8 +66,8 @@ function select(list, start, end=undef) =
                 list[ (start%l+l)%l ]
             :   assert( is_list(start) || is_range(start), "Invalid start parameter")
                 [for (i=start) list[ (i%l+l)%l ] ]
-        :   assert(is_num(start), "Invalid start parameter.")
-            assert(is_num(end), "Invalid end parameter.")
+        :   assert(is_finite(start), "Invalid start parameter.")
+            assert(is_finite(end), "Invalid end parameter.")
             let( s = (start%l+l)%l, e = (end%l+l)%l )
             (s <= e)? [for (i = [s:1:e]) list[i]]
             :   concat([for (i = [s:1:l-1]) list[i]], [for (i = [0:1:e]) list[i]]) ;
diff --git a/math.scad b/math.scad
index 3ff62d7..55e662b 100644
--- a/math.scad
+++ b/math.scad
@@ -240,7 +240,7 @@ function atanh(x) =
 //   quant([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,12,12,12]
 //   quant([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[12,12,12]]
 function quant(x,y) =
-    assert(is_finite(y) && !approx(y,0,eps=1e-24), "The multiple must be a non zero integer.")
+    assert(is_finite(y) && !approx(y,0,eps=1e-24), "The multiple must be a non zero number.")
     is_list(x)
     ?   [for (v=x) quant(v,y)]
     :   assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
@@ -272,7 +272,7 @@ function quant(x,y) =
 //   quantdn([9,10,10.4,10.5,11,12],3);      // Returns: [9,9,9,9,9,12]
 //   quantdn([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,9,9],[9,9,12]]
 function quantdn(x,y) =
-    assert(is_finite(y) && !approx(y,0,eps=1e-24), "The multiple must be a non zero integer.")
+    assert(is_finite(y) && !approx(y,0,eps=1e-24), "The multiple must be a non zero number.")
     is_list(x)
     ?    [for (v=x) quantdn(v,y)]
     :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
@@ -304,7 +304,7 @@ function quantdn(x,y) =
 //   quantup([9,10,10.4,10.5,11,12],3);      // Returns: [9,12,12,12,12,12]
 //   quantup([[9,10,10.4],[10.5,11,12]],3);  // Returns: [[9,12,12],[12,12,12]]
 function quantup(x,y) =
-    assert(is_finite(y) && !approx(y,0,eps=1e-24), "The multiple must be a non zero integer.")
+    assert(is_finite(y) && !approx(y,0,eps=1e-24), "The multiple must be a non zero number.")
     is_list(x)
     ?    [for (v=x) quantup(v,y)]
     :    assert( is_finite(x), "The input to quantize must be a number or a list of numbers.")
diff --git a/vectors.scad b/vectors.scad
index 51f4735..80909b2 100644
--- a/vectors.scad
+++ b/vectors.scad
@@ -19,6 +19,8 @@
 // Arguments:
 //   v = The value to test to see if it is a vector.
 //   length = If given, make sure the vector is `length` items long.
+//   zero = If false, require that the length of the vector is not approximately zero.  If true, require the length of the vector to be approximately zero-length.  Default: `undef` (don't check vector length.)
+//   eps = The minimum vector length that is considered non-zero.  Default: `EPSILON` (`1e-9`)
 // Example:
 //   is_vector(4);                          // Returns false
 //   is_vector([4,true,false]);             // Returns false
diff --git a/version.scad b/version.scad
index b6ba49c..483c726 100644
--- a/version.scad
+++ b/version.scad
@@ -8,7 +8,7 @@
 //////////////////////////////////////////////////////////////////////
 
 
-BOSL_VERSION = [2,0,399];
+BOSL_VERSION = [2,0,400];
 
 
 // Section: BOSL Library Version Functions