From 9f249d9cf349c9e7b0b676f87232548e5fd6721a Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 13 Mar 2021 20:33:03 -0500 Subject: [PATCH 1/3] fast sum for matrices too --- math.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/math.scad b/math.scad index 79d5ffa..720d0e6 100644 --- a/math.scad +++ b/math.scad @@ -585,7 +585,7 @@ function lcm(a,b=[]) = function sum(v, dflt=0) = v==[]? dflt : assert(is_consistent(v), "Input to sum is non-numeric or inconsistent") - is_vector(v) ? [for(i=[1:len(v)]) 1]*v : + is_vector(v) || is_matrix(v) ? [for(i=[1:len(v)]) 1]*v : _sum(v,v[0]*0); function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1); From 35c2d7df42edf6139caaefbd02b23ab0fb490284 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 14 Mar 2021 10:25:22 -0400 Subject: [PATCH 2/3] doc tweaks for skin bug fixes for is_matrix, is_vector, and new tests for same --- math.scad | 1 + skin.scad | 46 +++++++++++++++++++++++++++-------------- tests/test_math.scad | 6 ++++++ tests/test_vectors.scad | 4 ++++ vectors.scad | 3 ++- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/math.scad b/math.scad index 720d0e6..bf676e4 100644 --- a/math.scad +++ b/math.scad @@ -990,6 +990,7 @@ function is_matrix(A,m,n,square=false) = && is_list(A[0]) && (( is_undef(n) && len(A[0]) ) || len(A[0])==n) && (!square || len(A) == len(A[0])) + && is_vector(A[0]) && is_consistent(A); diff --git a/skin.scad b/skin.scad index 92536aa..79cd244 100644 --- a/skin.scad +++ b/skin.scad @@ -33,14 +33,18 @@ // For this operation to be well-defined, the profiles must all have the same vertex count and // we must assume that profiles are aligned so that vertex `i` links to vertex `i` on all polygons. // Many interesting cases do not comply with this restriction. Two basic methods can handle -// these cases: either add points to edges (resample) so that the profiles are compatible, -// or repeat vertices. Repeating vertices allows two edges to terminate at the same point, creating -// triangular faces. You can adjust non-matching profiles yourself +// these cases: either subdivide edges (insert additional points along edges) +// or duplicate vertcies (insert edges of length 0) so that both polygons have +// the same number of points. +// Duplicating vertices allows two distinct points in one polygon to connect to a single point +// in the other one, creating +// triangular faces. You can adjust non-matching polygons yourself // either by resampling them using `subdivide_path` or by duplicating vertices using -// `repeat_entries`. It is OK to pass a profile that has the same vertex repeated, such as +// `repeat_entries`. It is OK to pass a polygon that has the same vertex repeated, such as // a square with 5 points (two of which are identical), so that it can match up to a pentagon. // Such a combination would create a triangular face at the location of the duplicated vertex. -// Alternatively, `skin` provides methods (described below) for matching up incompatible paths. +// Alternatively, `skin` provides methods (described below) for inserting additional vertices +// automatically to make incompatible paths match. // . // In order for skinned surfaces to look good it is usually necessary to use a fine sampling of // points on all of the profiles, and a large number of extra interpolated slices between the @@ -77,7 +81,7 @@ // in the polyhedron---in will produce the least twisted possible result. This algorithm has quadratic // run time so it can be slow with very large profiles. // . -// When the profiles are incommensurate, the "direct" and "reindex" resampling them to match. As noted above, +// When the profiles are incommensurate, the "direct" and "reindex" resample them to match. As noted above, // for continuous input curves, it is better to generate your curves directly at the desired sample size, // but for mapping between a discrete profile like a hexagon and a circle, the hexagon must be resampled // to match the circle. When you use "direct" or "reindex" the default `sampling` value is @@ -88,23 +92,35 @@ // the segments of your input profiles have unequal length. // . // The "distance", "fast_distance" and "tangent" methods work by duplicating vertices to create -// triangular faces. The "distance" method finds the global minimum distance method for connecting two -// profiles. This algorithm generally produces a good result when both profiles are discrete ones with +// triangular faces. In the skined object created by two polygons, every vertex of a polygon must +// have an edge that connects to some vertex on the other one. If you connect two squares this can be +// accomplished with four edges, but if you want to connect a square to a pentagon you must add a +// fifth edge for the "extra" vertex on the pentagon. You must now decide which vertex on the square to +// connect the "extra" edge to. How do you decide where to put that fifth edge? The "distance" method answers this +// question by using an optimization: it minimizes the total length of all the edges connecting +// the two polygons. This algorithm generally produces a good result when both profiles are discrete ones with // a small number of vertices. It is computationally intensive (O(N^3)) and may be // slow on large inputs. The resulting surfaces generally have curved faces, so be // sure to select a sufficiently large value for `slices` and `refine`. Note that for // this method, `sampling` must be set to `"segment"`, and hence this is the default setting. // Using sampling by length would ignore the repeated vertices and ruin the alignment. -// The "fast_distance" method is similar to "distance", but it makes the assumption that an edge should -// connect the first vertices of the two polygons. This reduces the run time to O(N^2) and makes +// The "fast_distance" method restricts the optimization by assuming that an edge should connect +// vertex 0 of the two polygons. This reduces the run time to O(N^2) and makes // the method usable on profiles with more points if you take care to index the inputs to match. // . // The `"tangent"` method generally produces good results when -// connecting a discrete polygon to a convex, finely sampled curve. It works by finding -// a plane that passed through each edge of the polygon that is tangent to -// the curve. It may fail if the curved profile is non-convex, or doesn't have enough points to distinguish -// all of the tangent points from each other. It connects all of the points of the curve to the corners of the discrete -// polygon using triangular faces. Using `refine` with this method will have little effect on the model, so +// connecting a discrete polygon to a convex, finely sampled curve. Given a polygon and a curve, consider one edge +// on the polygon. Find a plane passing through the edge that is tangent to the curve. The endpoints of the edge and +// the point of tangency define a triangular face in the output polyhedron. If you work your way around the polygon +// edges, you can establish a series of triangular faces in this way, with edges linking the polygon to the curve. +// You can then complete the edge assignment by connecting all the edges in between the triangular faces together, +// with many edges meeting at each polygon vertex. The result is an alternation of flat triangular faces with conical +// curves joining them. Another way to think about it is that it splits the points on the curve up into groups and +// connects all the points in one group to the same vertex on the polygon. +// . +// The "tangent" method may fail if the curved profile is non-convex, or doesn't have enough points to distinguish +// all of the tangent points from each other. The algorithm treats whichever input profile has fewer points as the polygon +// and the other one as the curve. Using `refine` with this method will have little effect on the model, so // you should do it only for agreement with other profiles, and these models are linear, so extra slices also // have no effect. For best efficiency set `refine=1` and `slices=0`. As with the "distance" method, refinement // must be done using the "segment" sampling scheme to preserve alignment across duplicated points. diff --git a/tests/test_math.scad b/tests/test_math.scad index ae9801d..103be26 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -83,6 +83,12 @@ module test_is_matrix() { assert(!is_matrix([[2,3,4],[5,6,7]],n=5)); assert(!is_matrix([[2,3],[5,6],[8,9]],m=2,n=3)); assert(!is_matrix([[2,3,4],[5,6,7]],m=3,n=2)); + assert(!is_matrix([ [2,[3,4]], + [4,[5,6]]])); + assert(!is_matrix([[3,4],[undef,3]])); + assert(!is_matrix([[3,4],[3,"foo"]])); + assert(!is_matrix([[3,4],[3,3,2]])); + assert(!is_matrix([ [3,4],6])); assert(!is_matrix(undef)); assert(!is_matrix(NAN)); assert(!is_matrix(INF)); diff --git a/tests/test_vectors.scad b/tests/test_vectors.scad index ce8dea0..b23ccb0 100644 --- a/tests/test_vectors.scad +++ b/tests/test_vectors.scad @@ -11,6 +11,10 @@ module test_is_vector() { assert(is_vector(1) == false); assert(is_vector("foo") == false); assert(is_vector(true) == false); + assert(is_vector([3,4,"foo"]) == false); + assert(is_vector([3,4,[4,5]]) == false); + assert(is_vector([3,4,undef]) == false); + assert(is_vector(["foo","bar"]) == false); assert(is_vector([0,0,0],zero=true) == true); assert(is_vector([0,0,0],zero=false) == false); diff --git a/vectors.scad b/vectors.scad index cd45624..8a6ca60 100644 --- a/vectors.scad +++ b/vectors.scad @@ -36,7 +36,8 @@ // is_vector([1,1,1],all_nonzero=false); // Returns true // is_vector([],zero=false); // Returns false function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) = - is_list(v) && is_num(v[0]) && is_num(0*(v*v)) + is_list_of(v,1) // is_list(v) && is_num(v[0]) && is_num(0*(v*v)) + && len(v)>0 && (is_undef(length) || len(v)==length) && (is_undef(zero) || ((norm(v) >= eps) == !zero)) && (!all_nonzero || all_nonzero(v)) ; From 737baed34c8cb635843c053c3bab299016a30063 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 14 Mar 2021 12:08:20 -0400 Subject: [PATCH 3/3] further type checking fixes --- common.scad | 49 +++++++++++++++++------------------------- skin.scad | 4 ++-- tests/test_common.scad | 17 ++++++--------- turtle3d.scad | 4 ++-- vectors.scad | 3 +-- 5 files changed, 32 insertions(+), 45 deletions(-) diff --git a/common.scad b/common.scad index 44f1513..ab24380 100644 --- a/common.scad +++ b/common.scad @@ -185,49 +185,40 @@ function valid_range(x) = : ( x[1]<0 && x[0]>=x[2] ) ); -// Function: is_list_of() -// Usage: -// bool = is_list_of(list, pattern); -// Topics: Type Checking -// See Also: typeof(), is_type(), is_str(), is_def(), is_int(), is_range() -// Description: -// Tests whether the input is a list whose entries are all numeric lists that have the same -// list shape as the pattern. -// Example: -// 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]]); // 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=0*list) if (entry != pattern) entry]; - - // Function: is_consistent() // Usage: -// bool = is_consistent(list); +// bool = is_consistent(list, ); // Topics: Type Checking // See Also: typeof(), is_type(), is_str(), is_def(), is_int(), is_range(), is_homogeneous() // Description: // Tests whether input is a list of entries which all have the same list structure -// and are filled with finite numerical data. It returns `true`for the empty list. +// and are filled with finite numerical data. You can optionally specify a required +// list structure with the pattern argument. It returns `true` for the empty list. +// Arguments: +// list = list to check +// pattern = optional pattern required to match // Example: // is_consistent([3,4,5]); // Returns true // is_consistent([[3,4],[4,5],[6,7]]); // Returns true // is_consistent([[3,4,5],[3,4]]); // Returns false // 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_pattern(list[0])); - +// is_consistent([3,4,5], 0); // Returns true +// is_consistent([3,4,undef], 0); // Returns false +// is_consistent([[3,4],[4,5]], [1,1]); // Returns true +// is_consistent([[3,"a"],[4,true]], [1,undef]); // Returns true +// is_consistent([[3,4], 6, [4,5]], [1,1]); // Returns false +// is_consistent([[1,[3,4]], [4,[5,6]]], [1,[2,3]]); // Returns true +// is_consistent([[1,[3,INF]], [4,[5,6]]], [1,[2,3]]); // Returns false +// is_consistent([], [1,[2,3]]); // Returns true +function is_consistent(list, pattern) = + is_list(list) + && (len(list)==0 + || (let(pattern = is_undef(pattern) ? _list_pattern(list[0]): _list_pattern(pattern) ) + []==[for(entry=0*list) if (entry != pattern) entry])); //Internal function -//Creates a list with the same structure of `list` with each of its elements substituted by 0. +//Creates a list with the same structure of `list` with each of its elements replaced by 0. function _list_pattern(list) = is_list(list) ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0] diff --git a/skin.scad b/skin.scad index 79cd244..40237fb 100644 --- a/skin.scad +++ b/skin.scad @@ -236,7 +236,7 @@ // interior = regular_ngon(n=len(base), d=60); // right_half() // skin([ sub_base, base, base, sub_base, interior], z=[0,2,height, height, 2], slices=0, refine=1, method="reindex"); -// Example: Connecting a pentagon and circle with the "tangent" method produces triangular faces. +// Example: Connecting a pentagon and circle with the "tangent" method produces large triangular faces and cone shaped corners. // skin([pentagon(4), circle($fn=80,r=2)], z=[0,3], slices=10, method="tangent"); // Example: rounding corners of a square. Note that `$fn` makes the number of points constant, and avoiding the `rounding=0` case keeps everything simple. In this case, the connections between profiles are linear, so there is no benefit to setting `slices` bigger than zero. // shapes = [for(i=[.01:.045:2])zrot(-i*180/2,cp=[-8,0,0],p=xrot(90,p=path3d(regular_ngon(n=4, side=4, rounding=i, $fn=64))))]; @@ -921,7 +921,7 @@ function associate_vertices(polygons, split, curpoly=0) = // sweep(shape, concat(outside,inside)); function sweep(shape, transforms, closed=false, caps) = - assert(is_list_of(transforms, ident(4)), "Input transforms must be a list of numeric 4x4 matrices in sweep") + assert(is_consistent(transforms, ident(4)), "Input transforms must be a list of numeric 4x4 matrices in sweep") assert(is_path(shape,2) || is_region(shape), "Input shape must be a 2d path or a region.") let( caps = is_def(caps) ? caps : diff --git a/tests/test_common.scad b/tests/test_common.scad index 3568440..3d150d7 100644 --- a/tests/test_common.scad +++ b/tests/test_common.scad @@ -217,16 +217,6 @@ module test_valid_range() { } test_valid_range(); -module test_is_list_of() { - assert(is_list_of([3,4,5], 0)); - assert(!is_list_of([3,4,undef], 0)); - assert(is_list_of([[3,4],[4,5]], [1,1])); - assert(!is_list_of([[3,4], 6, [4,5]], [1,1])); - assert(is_list_of([[1,[3,4]], [4,[5,6]]], [1,[2,3]])); - assert(!is_list_of([[1,[3,INF]], [4,[5,6]]], [1,[2,3]])); -} -test_is_list_of(); - module test_is_consistent() { assert(is_consistent([])); assert(is_consistent([[],[]])); @@ -238,6 +228,13 @@ module test_is_consistent() { 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]]])); + + assert(is_consistent([3,4,5], 0)); + assert(!is_consistent([3,4,undef], 0)); + assert(is_consistent([[3,4],[4,5]], [1,1])); + assert(!is_consistent([[3,4], 6, [4,5]], [1,1])); + assert(is_consistent([[1,[3,4]], [4,[5,6]]], [1,[2,3]])); + assert(!is_consistent([[1,[3,INF]], [4,[5,6]]], [1,[2,3]])); } test_is_consistent(); diff --git a/turtle3d.scad b/turtle3d.scad index 8b510b0..8f9176d 100644 --- a/turtle3d.scad +++ b/turtle3d.scad @@ -421,8 +421,8 @@ turtle state: sequence of transformations ("path") so far function _turtle3d_state_valid(state) = is_list(state) - && is_list_of(state[0],ident(4)) - && is_list_of(state[1],ident(4)) + && is_consistent(state[0],ident(4)) + && is_consistent(state[1],ident(4)) && is_num(state[2]) && is_num(state[3]) && is_num(state[4]); diff --git a/vectors.scad b/vectors.scad index 8a6ca60..f8bc2f5 100644 --- a/vectors.scad +++ b/vectors.scad @@ -36,8 +36,7 @@ // is_vector([1,1,1],all_nonzero=false); // Returns true // is_vector([],zero=false); // Returns false function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) = - is_list_of(v,1) // is_list(v) && is_num(v[0]) && is_num(0*(v*v)) - && len(v)>0 + is_list(v) && len(v)>0 && []==[for(vi=v) if(!is_num(vi)) 0] && (is_undef(length) || len(v)==length) && (is_undef(zero) || ((norm(v) >= eps) == !zero)) && (!all_nonzero || all_nonzero(v)) ;