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 01/21] 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 02/21] 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 03/21] 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 04/21] 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 05/21] 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 06/21] 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 ab57790c272a1e0d75a060ee64d86d55e5bc4e0a Mon Sep 17 00:00:00 2001
From: RonaldoCMP <rcmpersiano@gmail.com>
Date: Tue, 28 Jul 2020 19:02:35 +0100
Subject: [PATCH 07/21] 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 08/21] 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 09/21] 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 10/21] 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 11/21] 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 12/21] 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 13/21] 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 14/21] 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 15/21] 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 16/21] 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 17/21] 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 18/21] 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 19/21] 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 20/21] 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 21/21] 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();