From 14804421b7f4b1a55e9ef0a25d869d56cf3f3a1a Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 30 Oct 2021 11:59:59 -0400 Subject: [PATCH 01/11] Fix docs for path functions with 1-regions, change is_path_region to is_1region --- paths.scad | 166 +++++++++++++++++++++++++++------------------------ regions.scad | 6 +- 2 files changed, 90 insertions(+), 82 deletions(-) diff --git a/paths.scad b/paths.scad index fe7fd86..480050c 100644 --- a/paths.scad +++ b/paths.scad @@ -1,14 +1,16 @@ ////////////////////////////////////////////////////////////////////// // LibFile: paths.scad -// Support for polygons and paths. +// A `path` is a list of points of the same dimensions, usually 2D or 3D, that can +// be connected together to form a sequence of line segments or a polygon. +// The functions in this file work on paths and also 1-regions, which are regions +// that include exactly one path. Capabilities include computing length of paths, computing +// path tangents and normals, resampling of paths, and cutting paths up into smaller paths. // Includes: // include ////////////////////////////////////////////////////////////////////// - // Section: Utility Functions - // Function: is_path() // Usage: // is_path(list, [dim], [fast]) @@ -16,7 +18,8 @@ // Returns true if `list` is a path. A path is a list of two or more numeric vectors (AKA points). // All vectors must of the same size, and may only contain numbers that are not inf or nan. // By default the vectors in a path must be 2d or 3d. Set the `dim` parameter to specify a list -// of allowed dimensions, or set it to `undef` to allow any dimension. +// of allowed dimensions, or set it to `undef` to allow any dimension. (Note that this function +// returns `false` on 1-regions.) // Example: // bool1 = is_path([[3,4],[5,6]]); // Returns true // bool2 = is_path([[3,4]]); // Returns false @@ -45,18 +48,17 @@ function is_path(list, dim=[2,3], fast=false) = && len(list[0])>0 && (is_undef(dim) || in_list(len(list[0]), force_list(dim))); -// Function: is_path_region() +// Function: is_1region() // Usage: -// bool = is_path_region(path, [name]) +// bool = is_1region(path, [name]) // Description: -// If `path` is a region with one component then return true. If path is a region with more components +// If `path` is a region with one component (a 1-region) then return true. If path is a region with more components // then display an error message about the parameter `name` requiring a path or a single component region. If the input -// is not a region then return false. This function helps accept singleton regions in functions that -// operate on a path. +// is not a region then return false. This function helps path functions accept 1-regions. // Arguments: // path = input to process // name = name of parameter to use in error message. Default: "path" -function is_path_region(path, name="path") = +function is_1region(path, name="path") = !is_region(path)? false :assert(len(path)==1,str("Parameter \"",name,"\" must be a path or singleton region, but is a multicomponent region")) true; @@ -65,10 +67,9 @@ function is_path_region(path, name="path") = // Usage: // outpath = force_path(path, [name]) // Description: -// If `path` is a region with one component then return that component as a path. If path is a region with more components +// If `path` is a region with one component (a 1-region) then return that component as a path. If path is a region with more components // then display an error message about the parameter `name` requiring a path or a single component region. If the input -// is not a region then return the input without any checks. This function helps accept singleton regions in functions that -// operate on a path. +// is not a region then return the input without any checks. This function helps path functions accept 1-regions. // Arguments: // path = input to process // name = name of parameter to use in error message. Default: "path" @@ -142,11 +143,11 @@ function _path_select(path, s1, u1, s2, u2, closed=false) = // Usage: // path_merge_collinear(path, [eps]) // Arguments: -// path = A list of path points of any dimension. +// path = A path of any dimension or a 1-region // closed = treat as closed polygon. Default: false // eps = Largest positional variance allowed. Default: `EPSILON` (1-e9) function path_merge_collinear(path, closed, eps=EPSILON) = - is_path_region(path) ? path_merge_collinear(path[0], default(closed,true), eps) : + is_1region(path) ? path_merge_collinear(path[0], default(closed,true), eps) : let(closed=default(closed,false)) assert(is_bool(closed)) assert( is_path(path), "Invalid path in path_merge_collinear." ) @@ -172,13 +173,13 @@ function path_merge_collinear(path, closed, eps=EPSILON) = // Description: // Returns the length of the path. // Arguments: -// path = The list of points of the path to measure. +// path = Path of any dimension or 1-region. // closed = true if the path is closed. Default: false // Example: // path = [[0,0], [5,35], [60,-25], [80,0]]; // echo(path_length(path)); function path_length(path,closed) = - is_path_region(path) ? path_length(path[0], default(closed,true)) : + is_1region(path) ? path_length(path[0], default(closed,true)) : assert(is_path(path), "Invalid path in path_length") let(closed=default(closed,false)) assert(is_bool(closed)) @@ -192,10 +193,10 @@ function path_length(path,closed) = // Description: // Returns list of the length of each segment in a path // Arguments: -// path = path to measure +// path = path in any dimension or 1-region // closed = true if the path is closed. Default: false function path_segment_lengths(path, closed) = - is_path_region(path) ? path_segment_lengths(path[0], default(closed,true)) : + is_1region(path) ? path_segment_lengths(path[0], default(closed,true)) : let(closed=default(closed,false)) assert(is_path(path),"Invalid path in path_segment_lengths.") assert(is_bool(closed)) @@ -214,10 +215,10 @@ function path_segment_lengths(path, closed) = // will have one extra point because of the final connecting segment that connects the last // point of the path to the first point. // Arguments: -// path = path to operate on +// path = path in any dimension or a 1-region // closed = set to true if path is closed. Default: false function path_length_fractions(path, closed) = - is_path_region(path) ? path_length_fractions(path[0], default(closed,true)): + is_1region(path) ? path_length_fractions(path[0], default(closed,true)): let(closed=default(closed, false)) assert(is_path(path)) assert(is_bool(closed)) @@ -337,7 +338,7 @@ function _sum_preserving_round(data, index=0) = // a closed polygon the total number of points will be sum(N). Note that with an open // path there is an extra point at the end, so the number of points will be sum(N)+1. // Arguments: -// path = path to subdivide +// path = path in any dimension or a 1-region // N = scalar total number of points desired or with `method="segment"` can be a vector requesting `N[i]-1` points on segment i. // refine = number of points to add each segment. // closed = set to false if the path is open. Default: True @@ -423,7 +424,7 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length // Description: // Evenly subdivides long `path` segments until they are all shorter than `maxlen`. // Arguments: -// path = The path to subdivide. +// path = path in any dimension or a 1-region // maxlen = The maximum allowed path segment length. // --- // closed = If true, treat path like a closed polygon. Default: true @@ -459,7 +460,7 @@ function subdivide_long_segments(path, maxlen, closed=true) = // Note that because this function operates on a discrete input path the quality of the output depends on // the sampling of the input. If you want very accurate output, use a lot of points for the input. // Arguments: -// path = path to resample +// path = path in any dimension or a 1-region // N = Number of points in output // spacing = Approximate spacing desired // closed = set to true if path is closed. Default: true @@ -493,11 +494,11 @@ function resample_path(path, N, spacing, closed=true) = // still be simple. // If closed is set to true then treat the path as a polygon. // Arguments: -// path = path to check +// path = 2D path or 1-region // closed = set to true to treat path as a polygon. Default: false // eps = Epsilon error value used for determine if points coincide. Default: `EPSILON` (1e-9) function is_path_simple(path, closed, eps=EPSILON) = - is_path_region(path) ? is_path_simple(path[0], default(closed,true), eps) : + is_1region(path) ? is_path_simple(path[0], default(closed,true), eps) : let(closed=default(closed,false)) assert(is_path(path, 2),"Must give a 2D path") assert(is_bool(closed)) @@ -551,7 +552,7 @@ function path_closest_point(path, pt, closed=true) = // assumed to be non-uniform and the derivative is computed with adjustments to produce corrected // values. // Arguments: -// path = path to find the tagent vectors for +// path = path of any dimension or a 1-region // closed = set to true of the path is closed. Default: false // uniform = set to false to correct for non-uniform sampling. Default: true // Example(2D): A shape with non-uniform sampling gives distorted derivatives that may be undesirable. Note that derivatives tilt towards the long edges of the rectangle. @@ -569,7 +570,7 @@ function path_closest_point(path, pt, closed=true) = // for(i=[0:len(tangents)-1]) // stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.25, endcap2="arrow2"); function path_tangents(path, closed, uniform=true) = - is_path_region(path) ? path_tangents(path[0], default(closed,true), uniform) : + is_1region(path) ? path_tangents(path[0], default(closed,true), uniform) : let(closed=default(closed,false)) assert(is_bool(closed)) assert(is_path(path)) @@ -592,11 +593,11 @@ function path_tangents(path, closed, uniform=true) = // For 2d paths the plane is always defined so the normal fails to exist only // when the derivative is zero (in the case of repeated points). // Arguments: -// path = path to compute the normals to +// path = 2D or 3D path or a 1-region // tangents = path tangents optionally supplied // closed = if true path is treated as a polygon. Default: false function path_normals(path, tangents, closed) = - is_path_region(path) ? path_normals(path[0], tangents, default(closed,true)) : + is_1region(path) ? path_normals(path[0], tangents, default(closed,true)) : let(closed=default(closed,false)) assert(is_path(path,[2,3])) assert(is_bool(closed)) @@ -623,9 +624,12 @@ function path_normals(path, tangents, closed) = // Usage: // curvs = path_curvature(path, [closed]); // Description: -// Numerically estimate the curvature of the path (in any dimension). +// Numerically estimate the curvature of the path (in any dimension). +// Arguments: +// path = path in any dimension or a 1-region +// closed = if true then treat the path as a polygon. Default: false function path_curvature(path, closed) = - is_path_region(path) ? path_curvature(path[0], default(closed,true)) : + is_1region(path) ? path_curvature(path[0], default(closed,true)) : let(closed=default(closed,false)) assert(is_bool(closed)) assert(is_path(path)) @@ -643,9 +647,12 @@ function path_curvature(path, closed) = // Function: path_torsion() // Usage: -// tortions = path_torsion(path, [closed]); +// torsions = path_torsion(path, [closed]); // Description: -// Numerically estimate the torsion of a 3d path. +// Numerically estimate the torsion of a 3d path. +// Arguments: +// path = 3D path +// closed = if true then treat path as a polygon. Default: false function path_torsion(path, closed=false) = assert(is_path(path,3), "Input path must be a 3d path") assert(is_bool(closed)) @@ -939,19 +946,19 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) = // Topics: Paths // See Also: split_path_at_self_crossings() // Usage: -// path_list = path_cut(path, cutdist, [closed=]); +// path_list = path_cut(path, cutdist, [closed=]); // Description: -// Given a list of distances in `cutdist`, cut the path into -// subpaths at those lengths, returning a list of paths. -// If the input path is closed then the final path will include the -// original starting point. The list of cut distances must be -// in ascending order and should not include the endpoints: 0 -// or len(path). If you repeat a distance you will get an -// empty list in that position in the output. If you give an -// empty cutdist array you will get the input path as output -// (without the final vertex doubled in the case of a closed path). +// Given a list of distances in `cutdist`, cut the path into +// subpaths at those lengths, returning a list of paths. +// If the input path is closed then the final path will include the +// original starting point. The list of cut distances must be +// in ascending order and should not include the endpoints: 0 +// or len(path). If you repeat a distance you will get an +// empty list in that position in the output. If you give an +// empty cutdist array you will get the input path as output +// (without the final vertex doubled in the case of a closed path). // Arguments: -// path = The original path to split. +// path = path of any dimension or a 1-region // cutdist = Distance or list of distances where path is cut // closed = If true, treat the path as a closed polygon. Default: false // Example(2D,NoAxes): @@ -960,7 +967,7 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) = // rainbow(segs) stroke($item, endcaps="butt", width=3); function path_cut(path,cutdist,closed) = is_num(cutdist) ? path_cut(path,[cutdist],closed) : - is_path_region(path) ? path_cut(path[0], cutdist, default(closed,true)): + is_1region(path) ? path_cut(path[0], cutdist, default(closed,true)): let(closed=default(closed,false)) assert(is_bool(closed)) assert(is_vector(cutdist)) @@ -1017,10 +1024,11 @@ function _cut_to_seg_u_form(pathcut, path, closed) = // Usage: // paths = split_path_at_self_crossings(path, [closed], [eps]); // Description: -// Splits a path into sub-paths wherever the original path crosses itself. +// Splits a 2D path into sub-paths wherever the original path crosses itself. // Splits may occur mid-segment, so new vertices will be created at the intersection points. +// Returns a list of the resulting subpaths. // Arguments: -// path = The path to split up. +// path = A 2D path or a 1-region. // closed = If true, treat path as a closed polygon. Default: true // eps = Acceptable variance. Default: `EPSILON` (1e-9) // Example(2D,NoAxes): @@ -1081,22 +1089,22 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) = // Function: polygon_parts() // Usage: -// splitpaths = polygon_parts(path, [nonzero], [eps]); +// splitpolys = polygon_parts(poly, [nonzero], [eps]); // Description: -// Given a possibly self-intersecting polygon, constructs a representation of the original polygon as a list of -// non-intersecting simple polygons. If nonzero is set to true then it uses the nonzero method for defining polygon membership, which -// means it will produce the outer perimeter. +// Given a possibly self-intersecting 2d polygon, constructs a representation of the original polygon as a list of +// non-intersecting simple polygons. If nonzero is set to true then it uses the nonzero method for defining polygon membership. +// For simple cases, such as the pentagram, this will produce the outer perimeter of a self-intersecting polygon. // Arguments: -// path = The path to split up. +// poly = a 2D polygon or 1-region // nonzero = If true use the nonzero method for checking if a point is in a polygon. Otherwise use the even-odd method. Default: false // eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9) // Example(2D,NoAxes): This cross-crossing polygon breaks up into its 3 components (regardless of the value of nonzero). -// path = [ +// poly = [ // [-100,100], [0,-50], [100,100], // [100,-100], [0,50], [-100,-100] // ]; -// splitpaths = polygon_parts(path); -// rainbow(splitpaths) stroke($item, closed=true, width=3); +// splitpolys = polygon_parts(poly); +// rainbow(splitpolys) stroke($item, closed=true, width=3); // Example(2D,NoAxes): With nonzero=false you get even-odd mode which matches OpenSCAD, so the pentagram breaks apart into its five points. // pentagram = turtle(["move",100,"left",144], repeat=4); // left(100)polygon(pentagram); @@ -1112,39 +1120,39 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) = // N=12; // ang=360/N; // sr=10; -// path = turtle(["angle", 90+ang/2, +// poly = turtle(["angle", 90+ang/2, // "move", sr, "left", // "move", 2*sr*sin(ang/2), "left", // "repeat", 4, // ["move", 2*sr, "left", // "move", 2*sr*sin(ang/2), "left"], // "move", sr]); -// stroke(path, width=.3); -// right(20)rainbow(polygon_parts(path)) polygon($item); -// Example(2D,NoAxes): overlapping path segments disappear -// path = [[0,0], [10,0], [10,10], [0,10],[0,20], [20,10],[10,10], [0,10],[0,0]]; -// stroke(path,width=0.3); -// right(22)stroke(polygon_parts(path)[0], width=0.3, closed=true); -// Example(2D,NoAxes): Path segments disappear outside as well -// path = turtle(["repeat", 3, ["move", 17, "left", "move", 10, "left", "move", 7, "left", "move", 10, "left"]]); -// back(2)stroke(path,width=.5); -// fwd(12)rainbow(polygon_parts(path)) stroke($item, closed=true, width=0.5); +// stroke(poly, width=.3); +// right(20)rainbow(polygon_parts(poly)) polygon($item); +// Example(2D,NoAxes): overlapping poly segments disappear +// poly = [[0,0], [10,0], [10,10], [0,10],[0,20], [20,10],[10,10], [0,10],[0,0]]; +// stroke(poly,width=0.3); +// right(22)stroke(polygon_parts(poly)[0], width=0.3, closed=true); +// Example(2D,NoAxes): Poly segments disappear outside as well +// poly = turtle(["repeat", 3, ["move", 17, "left", "move", 10, "left", "move", 7, "left", "move", 10, "left"]]); +// back(2)stroke(poly,width=.5); +// fwd(12)rainbow(polygon_parts(poly)) stroke($item, closed=true, width=0.5); // Example(2D,NoAxes): This shape has six components -// path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 17, "left"]]); -// polygon(path); -// right(22)rainbow(polygon_parts(path)) polygon($item); +// poly = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 17, "left"]]); +// polygon(poly); +// right(22)rainbow(polygon_parts(poly)) polygon($item); // Example(2D,NoAxes): When the loops of the shape overlap then nonzero gives a different result than the even-odd method. -// path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 10, "left"]]); -// polygon(path); -// right(27)rainbow(polygon_parts(path)) polygon($item); -// move([16,-14])rainbow(polygon_parts(path,nonzero=true)) polygon($item); -function polygon_parts(path, nonzero=false, eps=EPSILON) = - let(path = force_path(path)) - assert(is_path(path,2), "Must give 2D path") +// poly = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 10, "left"]]); +// polygon(poly); +// right(27)rainbow(polygon_parts(poly)) polygon($item); +// move([16,-14])rainbow(polygon_parts(poly,nonzero=true)) polygon($item); +function polygon_parts(poly, nonzero=false, eps=EPSILON) = + let(poly = force_path(poly)) + assert(is_path(poly,2), "Must give 2D polygon") assert(is_bool(nonzero)) let( - path = cleanup_path(path, eps=eps), - tagged = _tag_self_crossing_subpaths(path, nonzero=nonzero, closed=true, eps=eps), + poly = cleanup_path(poly, eps=eps), + tagged = _tag_self_crossing_subpaths(poly, nonzero=nonzero, closed=true, eps=eps), kept = [for (sub = tagged) if(sub[0] == "O") sub[1]], outregion = _assemble_path_fragments(kept, eps=eps) ) outregion; diff --git a/regions.scad b/regions.scad index 662d860..5380d47 100644 --- a/regions.scad +++ b/regions.scad @@ -3,8 +3,7 @@ // This file provides 2D boolean geometry operations on paths, where you can // compute the intersection or union of the shape defined by point lists, producing // a new point list. Of course, boolean operations may produce shapes with multiple -// components. To handle that, we use "regions" which are defined by sets of -// multiple paths. +// components. To handle that, we use "regions" which are defined as lists of paths. // Includes: // include ////////////////////////////////////////////////////////////////////// @@ -23,7 +22,8 @@ // Checking that the polygons on a list are simple and non-crossing can be a time consuming test, // so it is not done automatically. It is your responsibility to ensure that your regions are // compliant. You can construct regions by making a list of polygons, or by using -// boolean function operations such as union() or difference(). And if you must you +// boolean function operations such as union() or difference(), which all except paths, as +// well as regions, as their inputs. And if you must you // can clean up an ill-formed region using sanitize_region(). From 934b3c7b04e74166519b64d2af30128e0e416e24 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 30 Oct 2021 17:47:17 -0400 Subject: [PATCH 02/11] make all_zero, etc, non-recursive, just work on vectors --- comparisons.scad | 110 ++++++++++++++++++------------------ tests/test_comparisons.scad | 6 +- tests/test_linalg.scad | 2 +- 3 files changed, 58 insertions(+), 60 deletions(-) diff --git a/comparisons.scad b/comparisons.scad index e78d189..bd2a25c 100644 --- a/comparisons.scad +++ b/comparisons.scad @@ -6,7 +6,7 @@ ////////////////////////////////////////////////////////////////////// -// Section: Comparing lists to zero +// Section: List comparison operations // Function: approx() // Usage: @@ -34,7 +34,7 @@ function approx(a,b,eps=EPSILON) = // x = all_zero(x, [eps]); // Description: // Returns true if the finite number passed to it is approximately zero, to within `eps`. -// If passed a list, recursively checks if all items in the list are approximately zero. +// If passed a list returns true if all its entries are approximately zero. // Otherwise, returns false. // Arguments: // x = The value to check. @@ -45,17 +45,16 @@ function approx(a,b,eps=EPSILON) = // c = all_zero([0,0,0]); // Returns: true. // d = all_zero([0,0,1e-3]); // Returns: false. function all_zero(x, eps=EPSILON) = - is_finite(x)? approx(x,eps) : - is_list(x)? (x != [] && [for (xx=x) if(!all_zero(xx,eps=eps)) 1] == []) : - false; + is_finite(x)? abs(x)eps) 1] == []; // Function: all_nonzero() // Usage: // test = all_nonzero(x, [eps]); // Description: -// Returns true if the finite number passed to it is not almost zero, to within `eps`. -// If passed a list, recursively checks if all items in the list are not almost zero. +// Returns true if the finite number passed to it is different from zero by `eps`. +// If passed a list returns true if all the entries of the list are different from zero by `eps`. // Otherwise, returns false. // Arguments: // x = The value to check. @@ -67,20 +66,20 @@ function all_zero(x, eps=EPSILON) = // d = all_nonzero([0,0,1e-3]); // Returns: false. // e = all_nonzero([1e-3,1e-3,1e-3]); // Returns: true. function all_nonzero(x, eps=EPSILON) = - is_finite(x)? !approx(x,eps) : - is_list(x)? (x != [] && [for (xx=x) if(!all_nonzero(xx,eps=eps)) 1] == []) : - false; + is_finite(x)? abs(x)>eps : + is_vector(x) && [for (xx=x) if(abs(xx)0 : - is_list(x)? (x != [] && [for (xx=x) if(!all_positive(xx)) 1] == []) : - false; +function all_positive(x,eps=0) = + is_num(x)? x>eps : + is_vector(x) && [for (xx=x) if(xx<=0) 1] == []; // Function: all_negative() // Usage: -// test = all_negative(x); +// test = all_negative(x, [eps]); // Description: // Returns true if the finite number passed to it is less than zero. // If passed a list, recursively checks if all items in the list are negative. // Otherwise, returns false. // Arguments: // x = The value to check. +// eps = tolerance. Default: 0 // Example: // a = all_negative(-2); // Returns: true. // b = all_negative(0); // Returns: false. @@ -113,21 +112,21 @@ function all_positive(x) = // f = all_negative([3,1,2]); // Returns: false. // g = all_negative([3,-1,2]); // Returns: false. // h = all_negative([-3,-1,-2]); // Returns: true. -function all_negative(x) = - is_num(x)? x<0 : - is_list(x)? (x != [] && [for (xx=x) if(!all_negative(xx)) 1] == []) : - false; +function all_negative(x, eps=0) = + is_num(x)? x<-eps : + is_vector(x) && [for (xx=x) if(xx>=-eps) 1] == []; // Function: all_nonpositive() // Usage: -// all_nonpositive(x); +// all_nonpositive(x, [eps]); // Description: // Returns true if the finite number passed to it is less than or equal to zero. // If passed a list, recursively checks if all items in the list are nonpositive. -// Otherwise, returns false. +// Otherwise, returns false. // Arguments: // x = The value to check. +// eps = tolerance. Default: 0 // Example: // a = all_nonpositive(-2); // Returns: true. // b = all_nonpositive(0); // Returns: true. @@ -137,21 +136,21 @@ function all_negative(x) = // f = all_nonpositive([3,1,2]); // Returns: false. // g = all_nonpositive([3,-1,2]); // Returns: false. // h = all_nonpositive([-3,-1,-2]); // Returns: true. -function all_nonpositive(x) = - is_num(x)? x<=0 : - is_list(x)? (x != [] && [for (xx=x) if(!all_nonpositive(xx)) 1] == []) : - false; +function all_nonpositive(x,eps=0) = + is_num(x)? x<=eps : + is_vector(x) && [for (xx=x) if(xx>eps) 1] == []; // Function: all_nonnegative() // Usage: -// all_nonnegative(x); +// all_nonnegative(x, [eps]); // Description: // Returns true if the finite number passed to it is greater than or equal to zero. // If passed a list, recursively checks if all items in the list are nonnegative. // Otherwise, returns false. // Arguments: // x = The value to check. +// eps = tolerance. Default: 0 // Example: // a = all_nonnegative(-2); // Returns: false. // b = all_nonnegative(0); // Returns: true. @@ -162,10 +161,9 @@ function all_nonpositive(x) = // g = all_nonnegative([3,1,2]); // Returns: true. // h = all_nonnegative([3,-1,2]); // Returns: false. // i = all_nonnegative([-3,-1,-2]); // Returns: false. -function all_nonnegative(x) = - is_num(x)? x>=0 : - is_list(x)? (x != [] && [for (xx=x) if(!all_nonnegative(xx)) 1] == []) : - false; +function all_nonnegative(x,eps=0) = + is_num(x)? x>=-eps : + is_vector(x) && [for (xx=x) if(xx<-eps) 1] == []; // Function: all_equal() @@ -280,30 +278,6 @@ function compare_lists(a, b) = cmps==[]? (len(a)-len(b)) : cmps[0]; -// Function: list_smallest() -// Usage: -// small = list_smallest(list, k) -// Description: -// Returns a set of the k smallest items in list in arbitrary order. The items must be -// mutually comparable with native OpenSCAD comparison operations. You will get "undefined operation" -// errors if you provide invalid input. -// Arguments: -// list = list to process -// k = number of items to return -function list_smallest(list, k) = - assert(is_list(list)) - assert(is_finite(k) && k>=0, "k must be nonnegative") - let( - v = list[rand_int(0,len(list)-1,1)[0]], - smaller = [for(li=list) if(li= k ? [ each smaller, for(i=[1:k-len(smaller)]) v ] : - len(smaller) > k ? list_smallest(smaller, k) : - let( bigger = [for(li=list) if(li>v) li ] ) - concat(smaller, equal, list_smallest(bigger, k-len(smaller) -len(equal))); - // Section: Dealing with duplicate list entries @@ -780,3 +754,27 @@ function group_data(groups, values) = ]; +// Function: list_smallest() +// Usage: +// small = list_smallest(list, k) +// Description: +// Returns a set of the k smallest items in list in arbitrary order. The items must be +// mutually comparable with native OpenSCAD comparison operations. You will get "undefined operation" +// errors if you provide invalid input. +// Arguments: +// list = list to process +// k = number of items to return +function list_smallest(list, k) = + assert(is_list(list)) + assert(is_finite(k) && k>=0, "k must be nonnegative") + let( + v = list[rand_int(0,len(list)-1,1)[0]], + smaller = [for(li=list) if(li= k ? [ each smaller, for(i=[1:k-len(smaller)]) v ] : + len(smaller) > k ? list_smallest(smaller, k) : + let( bigger = [for(li=list) if(li>v) li ] ) + concat(smaller, equal, list_smallest(bigger, k-len(smaller) -len(equal))); + diff --git a/tests/test_comparisons.scad b/tests/test_comparisons.scad index 68a6357..a38b4f1 100644 --- a/tests/test_comparisons.scad +++ b/tests/test_comparisons.scad @@ -145,7 +145,7 @@ test_deduplicate_indexed(); module test_all_zero() { assert(all_zero(0)); assert(all_zero([0,0,0])); - assert(all_zero([[0,0,0],[0,0]])); + assert(!all_zero([[0,0,0],[0,0]])); assert(all_zero([EPSILON/2,EPSILON/2,EPSILON/2])); assert(!all_zero(1e-3)); assert(!all_zero([0,0,1e-3])); @@ -215,7 +215,7 @@ module test_all_negative() { assert(!all_negative([3,-1,2])); assert(all_negative([-3,-1,-2])); assert(!all_negative([-3,1,-2])); - assert(all_negative([[-5,-7],[-3,-1,-2]])); + assert(!all_negative([[-5,-7],[-3,-1,-2]])); assert(!all_negative([[-5,-7],[-3,1,-2]])); assert(!all_negative([])); assert(!all_negative(true)); @@ -256,7 +256,7 @@ module test_all_nonnegative() { assert(!all_nonnegative([[-5,-7],[-3,-1,-2]])); assert(!all_nonnegative([[-5,-7],[-3,1,-2]])); assert(!all_nonnegative([[5,7],[3,-1,2]])); - assert(all_nonnegative([[5,7],[3,1,2]])); + assert(!all_nonnegative([[5,7],[3,1,2]])); assert(!all_nonnegative([])); assert(!all_nonnegative(true)); assert(!all_nonnegative(false)); diff --git a/tests/test_linalg.scad b/tests/test_linalg.scad index 08f2060..2582eef 100644 --- a/tests/test_linalg.scad +++ b/tests/test_linalg.scad @@ -136,7 +136,7 @@ module test_null_space(){ function nullcheck(A,dim) = let(v=null_space(A)) - len(v)==dim && all_zero(A*transpose(v),eps=1e-12); + len(v)==dim && all_zero(flatten(A*transpose(v)),eps=1e-12); A = [[-1, 2, -5, 2],[-3,-1,3,-3],[5,0,5,0],[3,-4,11,-4]]; assert(nullcheck(A,1)); From b202f8526b7609f943de731e1c142456613290bb Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 30 Oct 2021 17:51:32 -0400 Subject: [PATCH 03/11] doc tweak --- paths.scad | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/paths.scad b/paths.scad index 480050c..4404c07 100644 --- a/paths.scad +++ b/paths.scad @@ -3,7 +3,9 @@ // A `path` is a list of points of the same dimensions, usually 2D or 3D, that can // be connected together to form a sequence of line segments or a polygon. // The functions in this file work on paths and also 1-regions, which are regions -// that include exactly one path. Capabilities include computing length of paths, computing +// that include exactly one path. When you pass a 1-region to a function, the default +// value for `closed` is always `true` because regions represent polygons. +// Capabilities include computing length of paths, computing // path tangents and normals, resampling of paths, and cutting paths up into smaller paths. // Includes: // include From 5153cb20da35532e714c2002f03c09c59a34544e Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 30 Oct 2021 18:07:43 -0400 Subject: [PATCH 04/11] doc tweak --- paths.scad | 1 + shapes2d.scad | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/paths.scad b/paths.scad index 4404c07..9b41982 100644 --- a/paths.scad +++ b/paths.scad @@ -65,6 +65,7 @@ function is_1region(path, name="path") = :assert(len(path)==1,str("Parameter \"",name,"\" must be a path or singleton region, but is a multicomponent region")) true; + // Function: force_path() // Usage: // outpath = force_path(path, [name]) diff --git a/shapes2d.scad b/shapes2d.scad index 6936ba3..35769e4 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -602,7 +602,7 @@ module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip, regular_ngon(n=8, r=r, d=d, or=or, od=od, ir=ir, id=id, side=side, rounding=rounding, realign=realign, align_tip=align_tip, align_side=align_side, anchor=anchor, spin=spin) children(); -// Function&Module right_triangle() +// Function&Module: right_triangle() // Usage: As Module // right_triangle(size, [center], ...); // Usage: With Attachments From f72d89fae78048efa945a936e065477b072e49ac Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 30 Oct 2021 18:34:42 -0400 Subject: [PATCH 05/11] doc fix --- geometry.scad | 4 ++-- paths.scad | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/geometry.scad b/geometry.scad index 37326e1..2ee3b7e 100644 --- a/geometry.scad +++ b/geometry.scad @@ -253,8 +253,8 @@ function line_intersection(line1, line2, bounded1, bounded2, bounded, eps=EPSILO // pt = line_closest_point(line, pt, [bounded]); // Topics: Geometry, Lines, Distance // Description: -// Returns the point on the given 2D or 3D line, segment or ray that is closest to the given point `pt`. -// The inputs `line` and `pt` args should either both be 2D or both 3D. The parameter bounded indicates +// Returns the point on the given line, segment or ray that is closest to the given point `pt`. +// The inputs `line` and `pt` args should be of the same dimension. The parameter bounded indicates // whether the points of `line` should be treated as endpoints. // Arguments: // line = A list of two points that are on the unbounded line. diff --git a/paths.scad b/paths.scad index 9b41982..a13612a 100644 --- a/paths.scad +++ b/paths.scad @@ -523,8 +523,8 @@ function is_path_simple(path, closed, eps=EPSILON) = // Finds the closest path segment, and point on that segment to the given point. // Returns `[SEGNUM, POINT]` // Arguments: -// path = The path to find the closest point on. -// pt = the point to find the closest point to. +// path = path of any dimension or a 1-region +// pt = the point to find the closest point to // closed = // Example(2D): // path = circle(d=100,$fn=6); @@ -535,7 +535,7 @@ function is_path_simple(path, closed, eps=EPSILON) = // color("red") translate(closest[1]) circle(d=3, $fn=12); function path_closest_point(path, pt, closed=true) = let(path = force_path(path)) - assert(is_path(path,[2,3]), "Must give 2D or 3D path.") + assert(is_path(path), "Input must be a path") assert(is_vector(pt, len(path[0])), "Input pt must be a compatible vector") assert(is_bool(closed)) let( From 12b8e7438c3fd7259512a6f6f4a1e2159c080253 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sat, 30 Oct 2021 23:15:59 -0400 Subject: [PATCH 06/11] fix path_extrude2d bug --- mutators.scad | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/mutators.scad b/mutators.scad index c69fcb7..d1f67fc 100644 --- a/mutators.scad +++ b/mutators.scad @@ -475,12 +475,13 @@ module chain_hull() // Module: path_extrude2d() // Usage: -// path_extrude2d(path, [caps]) {...} +// path_extrude2d(path, [caps], [closed]) {...} // Description: -// Extrudes 2D children along the given 2D path, with optional rounded endcaps. +// Extrudes 2D children along the given 2D path, with optional rounded endcaps that work only if the children are symmetric across the y axis. // Arguments: // path = The 2D path to extrude the geometry along. -// caps = If true, caps each end of the path with a `rotate_extrude()`d copy of the children. This may interact oddly when given asymmetric profile children. +// caps = If true, caps each end of the path with a `rotate_extrude()`d copy of the children. This may interact oddly when given asymmetric profile children. Default: false +// closed = If true, connect the starting point of the path to the ending point. Default: false // Example: // path = [ // each right(50, p=arc(d=100,angle=[90,180])), @@ -491,7 +492,7 @@ module chain_hull() // fwd(6) square([10,5],center=true); // } // Example: -// path_extrude2d(arc(d=100,angle=[180,270])) +// path_extrude2d(arc(d=100,angle=[180,270]),caps=true) // trapezoid(w1=10, w2=5, h=10, anchor=BACK); // Example: // include @@ -500,13 +501,14 @@ module chain_hull() // ]); // path_extrude2d(path, caps=false) // trapezoid(w1=10, w2=1, h=5, anchor=BACK); -module path_extrude2d(path, caps=true) { +module path_extrude2d(path, caps=false, closed=false) { + assert(caps==false || closed==false thin = 0.01; path = deduplicate(path); - for (p=pair(path)) { + for (p=pair(path,wrap=closed)) { delt = p[1]-p[0]; translate(p[0]) { - rot(from=BACK,to=delt) { + frame_map(y=point3d(delt),z=UP){ minkowski() { cube([thin,norm(delt),thin], anchor=FRONT); rotate([90,0,0]) linear_extrude(height=thin,center=true) children(); @@ -514,23 +516,24 @@ module path_extrude2d(path, caps=true) { } } } - for (t=triplet(path)) { + for (t=triplet(path,wrap=closed)) { ang = v_theta(t[2]-t[1]) - v_theta(t[1]-t[0]); - delt = t[2] - t[1]; + delt = point3d(t[2] - t[1]); translate(t[1]) { minkowski() { cube(thin,center=true); if (ang >= 0) { rotate(90-ang) - rot(from=LEFT,to=delt) + frame_map(x=-delt, z=UP) rotate_extrude(angle=ang+0.01) right_half(planar=true) children(); } else { - rotate(-90) - rot(from=RIGHT,to=delt) + rotate(-90) + frame_map(x=delt, z=UP) rotate_extrude(angle=-ang+0.01) left_half(planar=true) children(); } + } } } From 5934705dcd010dab537dcf06e9ad5c1245938ddd Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 31 Oct 2021 00:36:51 -0400 Subject: [PATCH 07/11] further extrude2d fixes --- mutators.scad | 48 ++++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/mutators.scad b/mutators.scad index d1f67fc..45861dc 100644 --- a/mutators.scad +++ b/mutators.scad @@ -477,7 +477,9 @@ module chain_hull() // Usage: // path_extrude2d(path, [caps], [closed]) {...} // Description: -// Extrudes 2D children along the given 2D path, with optional rounded endcaps that work only if the children are symmetric across the y axis. +// Extrudes 2D children along the given 2D path, with optional rounded endcaps. This module works properly in general only if the given +// children are symmetric across the Y axis. It works by constructing flat sections corresponding to each segment of the path and +// inserting rounded joints at each corner. // Arguments: // path = The 2D path to extrude the geometry along. // caps = If true, caps each end of the path with a `rotate_extrude()`d copy of the children. This may interact oddly when given asymmetric profile children. Default: false @@ -502,40 +504,30 @@ module chain_hull() // path_extrude2d(path, caps=false) // trapezoid(w1=10, w2=1, h=5, anchor=BACK); module path_extrude2d(path, caps=false, closed=false) { - assert(caps==false || closed==false + assert(caps==false || closed==false, "Cannot have caps on a closed extrusion"); thin = 0.01; path = deduplicate(path); - for (p=pair(path,wrap=closed)) { - delt = p[1]-p[0]; - translate(p[0]) { - frame_map(y=point3d(delt),z=UP){ - minkowski() { - cube([thin,norm(delt),thin], anchor=FRONT); - rotate([90,0,0]) linear_extrude(height=thin,center=true) children(); - } - } - } - } + for (p=pair(path,wrap=closed)) + extrude_from_to(p[0],p[1]) xflip()rot(-90)children(); for (t=triplet(path,wrap=closed)) { - ang = v_theta(t[2]-t[1]) - v_theta(t[1]-t[0]); + ang = 180-vector_angle(t); + rightside = _point_left_of_line2d(t[2],[t[0],t[1]])>0; delt = point3d(t[2] - t[1]); + if (ang>0) translate(t[1]) { - minkowski() { - cube(thin,center=true); - if (ang >= 0) { - rotate(90-ang) - frame_map(x=-delt, z=UP) - rotate_extrude(angle=ang+0.01) - right_half(planar=true) children(); - } else { - rotate(-90) - frame_map(x=delt, z=UP) - rotate_extrude(angle=-ang+0.01) - left_half(planar=true) children(); - } - + if (rightside){ //ang >= 0) { + rotate(90-ang) + frame_map(x=-delt, z=UP) + rotate_extrude(angle=ang) + right_half(planar=true) children(); + } else { + rotate(-(90-ang)) + frame_map(x=delt, z=UP) + rotate_extrude(angle=-ang) + left_half(planar=true) children(); } } + } if (caps) { move_copies([path[0],last(path)]) From 662f6c458d13c65cf1564e9149b3a237025af3e3 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 31 Oct 2021 00:54:00 -0400 Subject: [PATCH 08/11] further fixes to path_extrude2d --- mutators.scad | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/mutators.scad b/mutators.scad index 45861dc..a406106 100644 --- a/mutators.scad +++ b/mutators.scad @@ -478,7 +478,7 @@ module chain_hull() // path_extrude2d(path, [caps], [closed]) {...} // Description: // Extrudes 2D children along the given 2D path, with optional rounded endcaps. This module works properly in general only if the given -// children are symmetric across the Y axis. It works by constructing flat sections corresponding to each segment of the path and +// children are convex and symmetric across the Y axis. It works by constructing flat sections corresponding to each segment of the path and // inserting rounded joints at each corner. // Arguments: // path = The 2D path to extrude the geometry along. @@ -510,23 +510,17 @@ module path_extrude2d(path, caps=false, closed=false) { for (p=pair(path,wrap=closed)) extrude_from_to(p[0],p[1]) xflip()rot(-90)children(); for (t=triplet(path,wrap=closed)) { - ang = 180-vector_angle(t); - rightside = _point_left_of_line2d(t[2],[t[0],t[1]])>0; + ang = -(180-vector_angle(t)) * sign(_point_left_of_line2d(t[2],[t[0],t[1]])); delt = point3d(t[2] - t[1]); - if (ang>0) - translate(t[1]) { - if (rightside){ //ang >= 0) { - rotate(90-ang) - frame_map(x=-delt, z=UP) - rotate_extrude(angle=ang) + if (ang!=0) + translate(t[1]) { + frame_map(y=delt, z=UP) + rotate_extrude(angle=ang) + if (ang<0) right_half(planar=true) children(); - } else { - rotate(-(90-ang)) - frame_map(x=delt, z=UP) - rotate_extrude(angle=-ang) - left_half(planar=true) children(); + else + left_half(planar=true) children(); } - } } if (caps) { From 3a367c3fafeb32762ecd06611568e35a6ba25a97 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 31 Oct 2021 15:35:45 -0400 Subject: [PATCH 09/11] add path.scad tests, shift tests to correct files path_extrude2d bugfix --- math.scad | 2 +- mutators.scad | 17 +-- paths.scad | 73 +++++++----- tests/test_affine.scad | 90 -------------- tests/test_comparisons.scad | 111 +++++++++++++++++ tests/test_drawing.scad | 10 ++ tests/test_geometry.scad | 34 ++++++ tests/test_linalg.scad | 46 +++++++ tests/test_lists.scad | 9 -- tests/test_math.scad | 171 -------------------------- tests/test_paths.scad | 231 +++++++++++++++++++++++++++++++++++- tests/test_shapes2d.scad | 7 -- tests/test_transforms.scad | 57 +++++++++ tests/test_utility.scad | 9 ++ tests/test_vectors.scad | 34 ++++++ 15 files changed, 586 insertions(+), 315 deletions(-) diff --git a/math.scad b/math.scad index c675a04..7d2f006 100644 --- a/math.scad +++ b/math.scad @@ -192,7 +192,7 @@ function lerp(a,b,u) = // l = lerpn(0,1,6); // Returns: [0, 0.2, 0.4, 0.6, 0.8, 1] // l = lerpn(0,1,5,false); // Returns: [0, 0.2, 0.4, 0.6, 0.8] function lerpn(a,b,n,endpoint=true) = - assert(same_shape(a,b), "Bad or inconsistent inputs to lerp") + assert(same_shape(a,b), "Bad or inconsistent inputs to lerpn") assert(is_int(n)) assert(is_bool(endpoint)) let( d = n - (endpoint? 1 : 0) ) diff --git a/mutators.scad b/mutators.scad index a406106..63e22c4 100644 --- a/mutators.scad +++ b/mutators.scad @@ -504,8 +504,8 @@ module chain_hull() // path_extrude2d(path, caps=false) // trapezoid(w1=10, w2=1, h=5, anchor=BACK); module path_extrude2d(path, caps=false, closed=false) { + extra_ang = 0.1; // Extra angle for overlap of joints assert(caps==false || closed==false, "Cannot have caps on a closed extrusion"); - thin = 0.01; path = deduplicate(path); for (p=pair(path,wrap=closed)) extrude_from_to(p[0],p[1]) xflip()rot(-90)children(); @@ -514,16 +514,17 @@ module path_extrude2d(path, caps=false, closed=false) { delt = point3d(t[2] - t[1]); if (ang!=0) translate(t[1]) { - frame_map(y=delt, z=UP) - rotate_extrude(angle=ang) - if (ang<0) - right_half(planar=true) children(); - else - left_half(planar=true) children(); + frame_map(y=delt, z=UP) + rotate(-sign(ang)*extra_ang/2) + rotate_extrude(angle=ang+sign(ang)*extra_ang) + if (ang<0) + right_half(planar=true) children(); + else + left_half(planar=true) children(); } } - if (caps) { + if (false && caps) { move_copies([path[0],last(path)]) rotate_extrude() right_half(planar=true) children(); diff --git a/paths.scad b/paths.scad index a13612a..bfe0772 100644 --- a/paths.scad +++ b/paths.scad @@ -290,10 +290,8 @@ function _path_self_intersections(path, closed=true, eps=EPSILON) = isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps) ) if (isect -// && isect[1]> (i==0 && !closed? -eps: 0) // Apparently too strict && isect[1]>=-eps && isect[1]<= 1+eps -// && isect[2]> 0 && isect[2]>= -eps && isect[2]<= 1+eps) [isect[0], i, isect[1], j, isect[2]] @@ -390,32 +388,28 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length assert((is_num(N) && N>0) || is_vector(N),"Parameter N to subdivide_path must be postive number or vector") let( count = len(path) - (closed?0:1), - add_guess = method=="segment"? ( - is_list(N)? ( - assert(len(N)==count,"Vector parameter N to subdivide_path has the wrong length") - add_scalar(N,-1) - ) : repeat((N-len(path)) / count, count) - ) : // method=="length" - assert(is_num(N),"Parameter N to subdivide path must be a number when method=\"length\"") - let( - path_lens = concat( - [ for (i = [0:1:len(path)-2]) norm(path[i+1]-path[i]) ], - closed? [norm(path[len(path)-1]-path[0])] : [] - ), - add_density = (N - len(path)) / sum(path_lens) - ) - path_lens * add_density, - add = exact? _sum_preserving_round(add_guess) : - [for (val=add_guess) round(val)] - ) concat( - [ - for (i=[0:1:count]) each [ - for(j=[0:1:add[i]]) - lerp(path[i],select(path,i+1), j/(add[i]+1)) - ] - ], - closed? [] : [last(path)] - ); + add_guess = method=="segment"? + ( + is_list(N) + ? assert(len(N)==count,"Vector parameter N to subdivide_path has the wrong length") + add_scalar(N,-1) + : repeat((N-len(path)) / count, count) + ) + : // method=="length" + assert(is_num(N),"Parameter N to subdivide path must be a number when method=\"length\"") + let( + path_lens = path_segment_lengths(path,closed), + add_density = (N - len(path)) / sum(path_lens) + ) + path_lens * add_density, + add = exact? _sum_preserving_round(add_guess) + : [for (val=add_guess) round(val)] + ) + [ + for (i=[0:1:count-1]) + each lerpn(path[i],select(path,i+1), 1+add[i],endpoint=false), + if (!closed) last(path) + ]; @@ -465,8 +459,31 @@ function subdivide_long_segments(path, maxlen, closed=true) = // Arguments: // path = path in any dimension or a 1-region // N = Number of points in output +// --- // spacing = Approximate spacing desired // closed = set to true if path is closed. Default: true +// Example(2D): Subsampling lots of points from a smooth curve +// path = xscale(2,circle($fn=250, r=10)); +// sampled = resample_path(path, 16); +// stroke(path); +// color("red")move_copies(sampled) circle($fn=16); +// Example(2D): Specified spacing is rounded to make a uniform sampling +// path = xscale(2,circle($fn=250, r=10)); +// sampled = resample_path(path, spacing=17); +// stroke(path); +// color("red")move_copies(sampled) circle($fn=16); +// Example(2D): Notice that the corners are excluded +// path = square(20); +// sampled = resample_path(path, spacing=6); +// stroke(path,closed=true); +// color("red")move_copies(sampled) circle($fn=16); +// Example(2D): Closed set to false +// path = square(20); +// sampled = resample_path(path, spacing=6,closed=false); +// stroke(path); +// color("red")move_copies(sampled) circle($fn=16); + + function resample_path(path, N, spacing, closed=true) = let(path = force_path(path)) assert(is_path(path)) diff --git a/tests/test_affine.scad b/tests/test_affine.scad index 819d7e1..7e90ebd 100644 --- a/tests/test_affine.scad +++ b/tests/test_affine.scad @@ -1,37 +1,6 @@ include <../std.scad> -module test_ident() { - assert(ident(3) == [[1,0,0],[0,1,0],[0,0,1]]); - assert(ident(4) == [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]); -} -test_ident(); - - -module test_is_2d_transform() { - assert(!is_2d_transform(affine2d_identity())); - assert(!is_2d_transform(affine2d_translate([5,8]))); - assert(!is_2d_transform(affine2d_scale([3,4]))); - assert(!is_2d_transform(affine2d_zrot(30))); - assert(!is_2d_transform(affine2d_mirror([-1,1]))); - assert(!is_2d_transform(affine2d_skew(30,15))); - - assert(is_2d_transform(affine3d_identity())); - assert(is_2d_transform(affine3d_translate([30,40,0]))); - assert(!is_2d_transform(affine3d_translate([30,40,50]))); - assert(is_2d_transform(affine3d_scale([3,4,1]))); - assert(!is_2d_transform(affine3d_xrot(30))); - assert(!is_2d_transform(affine3d_yrot(30))); - assert(is_2d_transform(affine3d_zrot(30))); - assert(is_2d_transform(affine3d_skew(sxy=2))); - assert(is_2d_transform(affine3d_skew(syx=2))); - assert(!is_2d_transform(affine3d_skew(szx=2))); - assert(!is_2d_transform(affine3d_skew(szy=2))); -} -test_is_2d_transform(); - - - // 2D module test_affine2d_identity() { @@ -197,64 +166,5 @@ test_affine3d_skew_yz(); //////////////////////////// -module test_apply() { - assert(approx(apply(affine3d_xrot(90),2*UP),2*FRONT)); - assert(approx(apply(affine3d_yrot(90),2*UP),2*RIGHT)); - assert(approx(apply(affine3d_zrot(90),2*UP),2*UP)); - assert(approx(apply(affine3d_zrot(90),2*RIGHT),2*BACK)); - assert(approx(apply(affine3d_zrot(90),2*BACK+2*RIGHT),2*BACK+2*LEFT)); - assert(approx(apply(affine3d_xrot(135),2*BACK+2*UP),2*sqrt(2)*FWD)); - assert(approx(apply(affine3d_yrot(135),2*RIGHT+2*UP),2*sqrt(2)*DOWN)); - assert(approx(apply(affine3d_zrot(45),2*BACK+2*RIGHT),2*sqrt(2)*BACK)); - - module check_path_apply(mat,path) - assert_approx(apply(mat,path),path3d([for (p=path) mat*concat(p,1)])); - - check_path_apply(xrot(45), path3d(rect(100,center=true))); - check_path_apply(yrot(45), path3d(rect(100,center=true))); - check_path_apply(zrot(45), path3d(rect(100,center=true))); - check_path_apply(rot([20,30,40])*scale([0.9,1.1,1])*move([10,20,30]), path3d(rect(100,center=true))); - - module check_patch_apply(mat,patch) - assert_approx(apply(mat,patch), [for (path=patch) path3d([for (p=path) mat*concat(p,1)])]); - - flat = [for (x=[-50:25:50]) [for (y=[-50:25:50]) [x,y,0]]]; - check_patch_apply(xrot(45), flat); - check_patch_apply(yrot(45), flat); - check_patch_apply(zrot(45), flat); - check_patch_apply(rot([20,30,40])*scale([0.9,1.1,1])*move([10,20,30]), flat); -} -test_apply(); - - -module test_rot_decode() { - Tlist = [ - rot(37), - xrot(49), - yrot(88), - rot(37,v=[1,3,3]), - rot(41,v=[2,-3,4]), - rot(180), - xrot(180), - yrot(180), - rot(180, v=[3,2,-5], cp=[3,5,18]), - rot(0.1, v=[1,2,3]), - rot(-47,v=[3,4,5],cp=[9,3,4]), - rot(197,v=[13,4,5],cp=[9,-3,4]), - move([3,4,5]), - move([3,4,5]) * rot(a=56, v=[5,3,-3], cp=[2,3,4]), - ident(4) - ]; - errlist = [for(T = Tlist) - let( - parm = rot_decode(T), - restore = move(parm[3])*rot(a=parm[0],v=parm[1],cp=parm[2]) - ) - norm_fro(restore-T)]; - assert(max(errlist)<1e-13); -} -test_rot_decode(); - - // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/tests/test_comparisons.scad b/tests/test_comparisons.scad index a38b4f1..e78f336 100644 --- a/tests/test_comparisons.scad +++ b/tests/test_comparisons.scad @@ -280,3 +280,114 @@ module test_approx() { assert_equal(approx([1,[1,undef]], [1+1e-12,[1,undef]]), true); } test_approx(); + + + +module test_group_data() { + assert_equal(group_data([1,2,0], ["A","B","C"]), [["C"],["A"],["B"]]); + assert_equal(group_data([1,3,0], ["A","B","C"]), [["C"],["A"],[],["B"]]); + assert_equal(group_data([5,3,1], ["A","B","C"]), [[],["C"],[],["B"],[],["A"]]); + assert_equal(group_data([1,3,1], ["A","B","C"]), [[],["A","C"],[],["B"]]); +} +test_group_data(); + + +module test_compare_vals() { + assert(compare_vals(-10,0) < 0); + assert(compare_vals(10,0) > 0); + assert(compare_vals(10,10) == 0); + + assert(compare_vals("abc","abcd") < 0); + assert(compare_vals("abcd","abc") > 0); + assert(compare_vals("abcd","abcd") == 0); + + assert(compare_vals(false,false) == 0); + assert(compare_vals(true,false) > 0); + assert(compare_vals(false,true) < 0); + assert(compare_vals(true,true) == 0); + + assert(compare_vals([2,3,4], [2,3,4,5]) < 0); + assert(compare_vals([2,3,4,5], [2,3,4,5]) == 0); + assert(compare_vals([2,3,4,5], [2,3,4]) > 0); + assert(compare_vals([2,3,4,5], [2,3,5,5]) < 0); + assert(compare_vals([[2,3,4,5]], [[2,3,5,5]]) < 0); + + assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5]]) == 0); + assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4,5], [3,4,5]]) < 0); + assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5,6]]) < 0); + assert(compare_vals([[2,3,4,5],[3,4,5]], [[2,3,4], [3,4,5]]) > 0); + assert(compare_vals([[2,3,4],[3,4,5,6]], [[2,3,4], [3,4,5]]) > 0); + assert(compare_vals([[2,3,4],[3,5,5]], [[2,3,4], [3,4,5]]) > 0); + assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4], [3,5,5]]) < 0); + + assert(compare_vals(undef, undef) == 0); + assert(compare_vals(undef, true) < 0); + assert(compare_vals(undef, 0) < 0); + assert(compare_vals(undef, "foo") < 0); + assert(compare_vals(undef, [2,3,4]) < 0); + assert(compare_vals(undef, [0:3]) < 0); + + assert(compare_vals(true, undef) > 0); + assert(compare_vals(true, true) == 0); + assert(compare_vals(true, 0) < 0); + assert(compare_vals(true, "foo") < 0); + assert(compare_vals(true, [2,3,4]) < 0); + assert(compare_vals(true, [0:3]) < 0); + + assert(compare_vals(0, undef) > 0); + assert(compare_vals(0, true) > 0); + assert(compare_vals(0, 0) == 0); + assert(compare_vals(0, "foo") < 0); + assert(compare_vals(0, [2,3,4]) < 0); + assert(compare_vals(0, [0:3]) < 0); + + assert(compare_vals(1, undef) > 0); + assert(compare_vals(1, true) > 0); + assert(compare_vals(1, 1) == 0); + assert(compare_vals(1, "foo") < 0); + assert(compare_vals(1, [2,3,4]) < 0); + assert(compare_vals(1, [0:3]) < 0); + + assert(compare_vals("foo", undef) > 0); + assert(compare_vals("foo", true) > 0); + assert(compare_vals("foo", 1) > 0); + assert(compare_vals("foo", "foo") == 0); + assert(compare_vals("foo", [2,3,4]) < 0); + assert(compare_vals("foo", [0:3]) < 0); + + assert(compare_vals([2,3,4], undef) > 0); + assert(compare_vals([2,3,4], true) > 0); + assert(compare_vals([2,3,4], 1) > 0); + assert(compare_vals([2,3,4], "foo") > 0); + assert(compare_vals([2,3,4], [2,3,4]) == 0); + assert(compare_vals([2,3,4], [0:3]) < 0); + + assert(compare_vals([0:3], undef) > 0); + assert(compare_vals([0:3], true) > 0); + assert(compare_vals([0:3], 1) > 0); + assert(compare_vals([0:3], "foo") > 0); + assert(compare_vals([0:3], [2,3,4]) > 0); + assert(compare_vals([0:3], [0:3]) == 0); +} +test_compare_vals(); + + +module test_compare_lists() { + assert(compare_lists([2,3,4], [2,3,4,5]) < 0); + assert(compare_lists([2,3,4,5], [2,3,4,5]) == 0); + assert(compare_lists([2,3,4,5], [2,3,4]) > 0); + assert(compare_lists([2,3,4,5], [2,3,5,5]) < 0); + + assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5]]) == 0); + assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4,5], [3,4,5]]) < 0); + assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5,6]]) < 0); + assert(compare_lists([[2,3,4,5],[3,4,5]], [[2,3,4], [3,4,5]]) > 0); + assert(compare_lists([[2,3,4],[3,4,5,6]], [[2,3,4], [3,4,5]]) > 0); + assert(compare_lists([[2,3,4],[3,5,5]], [[2,3,4], [3,4,5]]) > 0); + assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4], [3,5,5]]) < 0); + + assert(compare_lists("cat", "bat") > 0); + assert(compare_lists(["cat"], ["bat"]) > 0); +} +test_compare_lists(); + diff --git a/tests/test_drawing.scad b/tests/test_drawing.scad index a7e5fa1..a45dc99 100644 --- a/tests/test_drawing.scad +++ b/tests/test_drawing.scad @@ -49,3 +49,13 @@ module test_arc() { assert_approx(arc($fn=24, cp=[10,10], points=[[45,45],[-25,45]], long=true), [[45,45],[53.2421021636,34.0856928585],[58.1827254512,21.3324740498],[59.4446596304,7.71403542491],[56.9315576496,-5.72987274525],[50.8352916125,-17.9728253654],[41.6213035891,-28.0800887515],[29.9930697126,-35.2799863457],[16.8383906815,-39.0228152281],[3.16160931847,-39.0228152281],[-9.9930697126,-35.2799863457],[-21.6213035891,-28.0800887515],[-30.8352916125,-17.9728253654],[-36.9315576496,-5.72987274525],[-39.4446596304,7.71403542491],[-38.1827254512,21.3324740498],[-33.2421021636,34.0856928585],[-25,45]]); } test_arc(); + + + +module test_dashed_stroke() { + segs = dashed_stroke([[0,0],[10,0]], dashpat=[3,2], closed=false); + assert_equal(segs,[[[0,0],[3,0]], [[5,0],[8,0]]]); +} +test_dashed_stroke(); + + diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad index 7eff2bc..a751f9c 100644 --- a/tests/test_geometry.scad +++ b/tests/test_geometry.scad @@ -55,6 +55,7 @@ test_ccw_polygon(); test_reverse_polygon(); test_polygon_normal(); +test_rot_decode(); //tests to migrate to other files test_convex_distance(); @@ -966,4 +967,37 @@ module test_convex_collision() { } *test_convex_distance(); + + +module test_rot_decode() { + Tlist = [ + rot(37), + xrot(49), + yrot(88), + rot(37,v=[1,3,3]), + rot(41,v=[2,-3,4]), + rot(180), + xrot(180), + yrot(180), + rot(180, v=[3,2,-5], cp=[3,5,18]), + rot(0.1, v=[1,2,3]), + rot(-47,v=[3,4,5],cp=[9,3,4]), + rot(197,v=[13,4,5],cp=[9,-3,4]), + move([3,4,5]), + move([3,4,5]) * rot(a=56, v=[5,3,-3], cp=[2,3,4]), + ident(4) + ]; + errlist = [for(T = Tlist) + let( + parm = rot_decode(T), + restore = move(parm[3])*rot(a=parm[0],v=parm[1],cp=parm[2]) + ) + norm_fro(restore-T)]; + assert(max(errlist)<1e-13); +} +*test_rot_decode(); + + + + // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/tests/test_linalg.scad b/tests/test_linalg.scad index 2582eef..c482ab1 100644 --- a/tests/test_linalg.scad +++ b/tests/test_linalg.scad @@ -1,5 +1,51 @@ include <../std.scad> + +module test_is_matrix() { + assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]])); + assert(is_matrix([[2,3],[5,6],[8,9]],3,2)); + assert(is_matrix([[2,3],[5,6],[8,9]],m=3,n=2)); + assert(is_matrix([[2,3,4],[5,6,7]],m=2,n=3)); + assert(is_matrix([[2,3,4],[5,6,7]],2,3)); + assert(is_matrix([[2,3,4],[5,6,7]],m=2)); + assert(is_matrix([[2,3,4],[5,6,7]],2)); + assert(is_matrix([[2,3,4],[5,6,7]],n=3)); + assert(!is_matrix([[2,3,4],[5,6,7]],m=4)); + 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)); + assert(!is_matrix(-5)); + assert(!is_matrix(0)); + assert(!is_matrix(5)); + assert(!is_matrix("")); + assert(!is_matrix("foo")); + assert(!is_matrix([3,4,5])); + assert(!is_matrix([])); +} +test_is_matrix(); + + + + +module test_ident() { + assert(ident(3) == [[1,0,0],[0,1,0],[0,0,1]]); + assert(ident(4) == [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]); +} +test_ident(); + + + + + module test_qr_factor() { // Check that R is upper triangular function is_ut(R) = diff --git a/tests/test_lists.scad b/tests/test_lists.scad index b2618cc..bb8b140 100644 --- a/tests/test_lists.scad +++ b/tests/test_lists.scad @@ -401,15 +401,6 @@ module test_list_to_matrix() { test_list_to_matrix(); -module test_group_data() { - assert_equal(group_data([1,2,0], ["A","B","C"]), [["C"],["A"],["B"]]); - assert_equal(group_data([1,3,0], ["A","B","C"]), [["C"],["A"],[],["B"]]); - assert_equal(group_data([5,3,1], ["A","B","C"]), [[],["C"],[],["B"],[],["A"]]); - assert_equal(group_data([1,3,1], ["A","B","C"]), [[],["A","C"],[],["B"]]); -} -test_group_data(); - - module test_flatten() { assert(flatten([[1,2,3], [4,5,[6,7,8]]]) == [1,2,3,4,5,[6,7,8]]); assert(flatten([]) == []); diff --git a/tests/test_math.scad b/tests/test_math.scad index 710762d..cdb8ebe 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -76,40 +76,6 @@ module test_constrain() { test_constrain(); -module test_is_matrix() { - assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]])); - assert(is_matrix([[2,3],[5,6],[8,9]],3,2)); - assert(is_matrix([[2,3],[5,6],[8,9]],m=3,n=2)); - assert(is_matrix([[2,3,4],[5,6,7]],m=2,n=3)); - assert(is_matrix([[2,3,4],[5,6,7]],2,3)); - assert(is_matrix([[2,3,4],[5,6,7]],m=2)); - assert(is_matrix([[2,3,4],[5,6,7]],2)); - assert(is_matrix([[2,3,4],[5,6,7]],n=3)); - assert(!is_matrix([[2,3,4],[5,6,7]],m=4)); - 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)); - assert(!is_matrix(-5)); - assert(!is_matrix(0)); - assert(!is_matrix(5)); - assert(!is_matrix("")); - assert(!is_matrix("foo")); - assert(!is_matrix([3,4,5])); - assert(!is_matrix([])); -} -test_is_matrix(); - - - module test_all_integer() { assert(!all_integer(undef)); @@ -133,37 +99,6 @@ test_all_integer(); -module test_min_index() { - vals = rands(-100,100,100,seed=75); - minval = min(vals); - minidx = min_index(vals); - assert_equal(vals[minidx], minval); - assert_equal(min_index([3,4,5,6]), 0); - assert_equal(min_index([4,3,5,6]), 1); - assert_equal(min_index([4,5,3,6]), 2); - assert_equal(min_index([4,5,6,3]), 3); - assert_equal(min_index([6,5,4,3]), 3); - assert_equal(min_index([6,3,4,5]), 1); - assert_equal(min_index([-56,72,-874,5]), 2); -} -test_min_index(); - - -module test_max_index() { - vals = rands(-100,100,100,seed=97); - maxval = max(vals); - maxidx = max_index(vals); - assert_equal(vals[maxidx], maxval); - assert_equal(max_index([3,4,5,6]), 3); - assert_equal(max_index([3,4,6,5]), 2); - assert_equal(max_index([3,6,4,5]), 1); - assert_equal(max_index([6,3,4,5]), 0); - assert_equal(max_index([5,6,4,3]), 1); - assert_equal(max_index([-56,72,-874,5]), 1); -} -test_max_index(); - - module test_posmod() { assert_equal(posmod(-5,3), 1); assert_equal(posmod(-4,3), 2); @@ -248,13 +183,6 @@ test_gaussian_rands(); -module test_segs() { - assert_equal(segs(50,$fn=8), 8); - assert_equal(segs(50,$fa=2,$fs=2), 158); -} -test_segs(); - - module test_lerp() { assert_equal(lerp(-20,20,0), -20); assert_equal(lerp(-20,20,0.25), -10); @@ -486,105 +414,6 @@ test_convolve(); // Logic -module test_compare_vals() { - assert(compare_vals(-10,0) < 0); - assert(compare_vals(10,0) > 0); - assert(compare_vals(10,10) == 0); - - assert(compare_vals("abc","abcd") < 0); - assert(compare_vals("abcd","abc") > 0); - assert(compare_vals("abcd","abcd") == 0); - - assert(compare_vals(false,false) == 0); - assert(compare_vals(true,false) > 0); - assert(compare_vals(false,true) < 0); - assert(compare_vals(true,true) == 0); - - assert(compare_vals([2,3,4], [2,3,4,5]) < 0); - assert(compare_vals([2,3,4,5], [2,3,4,5]) == 0); - assert(compare_vals([2,3,4,5], [2,3,4]) > 0); - assert(compare_vals([2,3,4,5], [2,3,5,5]) < 0); - assert(compare_vals([[2,3,4,5]], [[2,3,5,5]]) < 0); - - assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5]]) == 0); - assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4,5], [3,4,5]]) < 0); - assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5,6]]) < 0); - assert(compare_vals([[2,3,4,5],[3,4,5]], [[2,3,4], [3,4,5]]) > 0); - assert(compare_vals([[2,3,4],[3,4,5,6]], [[2,3,4], [3,4,5]]) > 0); - assert(compare_vals([[2,3,4],[3,5,5]], [[2,3,4], [3,4,5]]) > 0); - assert(compare_vals([[2,3,4],[3,4,5]], [[2,3,4], [3,5,5]]) < 0); - - assert(compare_vals(undef, undef) == 0); - assert(compare_vals(undef, true) < 0); - assert(compare_vals(undef, 0) < 0); - assert(compare_vals(undef, "foo") < 0); - assert(compare_vals(undef, [2,3,4]) < 0); - assert(compare_vals(undef, [0:3]) < 0); - - assert(compare_vals(true, undef) > 0); - assert(compare_vals(true, true) == 0); - assert(compare_vals(true, 0) < 0); - assert(compare_vals(true, "foo") < 0); - assert(compare_vals(true, [2,3,4]) < 0); - assert(compare_vals(true, [0:3]) < 0); - - assert(compare_vals(0, undef) > 0); - assert(compare_vals(0, true) > 0); - assert(compare_vals(0, 0) == 0); - assert(compare_vals(0, "foo") < 0); - assert(compare_vals(0, [2,3,4]) < 0); - assert(compare_vals(0, [0:3]) < 0); - - assert(compare_vals(1, undef) > 0); - assert(compare_vals(1, true) > 0); - assert(compare_vals(1, 1) == 0); - assert(compare_vals(1, "foo") < 0); - assert(compare_vals(1, [2,3,4]) < 0); - assert(compare_vals(1, [0:3]) < 0); - - assert(compare_vals("foo", undef) > 0); - assert(compare_vals("foo", true) > 0); - assert(compare_vals("foo", 1) > 0); - assert(compare_vals("foo", "foo") == 0); - assert(compare_vals("foo", [2,3,4]) < 0); - assert(compare_vals("foo", [0:3]) < 0); - - assert(compare_vals([2,3,4], undef) > 0); - assert(compare_vals([2,3,4], true) > 0); - assert(compare_vals([2,3,4], 1) > 0); - assert(compare_vals([2,3,4], "foo") > 0); - assert(compare_vals([2,3,4], [2,3,4]) == 0); - assert(compare_vals([2,3,4], [0:3]) < 0); - - assert(compare_vals([0:3], undef) > 0); - assert(compare_vals([0:3], true) > 0); - assert(compare_vals([0:3], 1) > 0); - assert(compare_vals([0:3], "foo") > 0); - assert(compare_vals([0:3], [2,3,4]) > 0); - assert(compare_vals([0:3], [0:3]) == 0); -} -test_compare_vals(); - - -module test_compare_lists() { - assert(compare_lists([2,3,4], [2,3,4,5]) < 0); - assert(compare_lists([2,3,4,5], [2,3,4,5]) == 0); - assert(compare_lists([2,3,4,5], [2,3,4]) > 0); - assert(compare_lists([2,3,4,5], [2,3,5,5]) < 0); - - assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5]]) == 0); - assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4,5], [3,4,5]]) < 0); - assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4], [3,4,5,6]]) < 0); - assert(compare_lists([[2,3,4,5],[3,4,5]], [[2,3,4], [3,4,5]]) > 0); - assert(compare_lists([[2,3,4],[3,4,5,6]], [[2,3,4], [3,4,5]]) > 0); - assert(compare_lists([[2,3,4],[3,5,5]], [[2,3,4], [3,4,5]]) > 0); - assert(compare_lists([[2,3,4],[3,4,5]], [[2,3,4], [3,5,5]]) < 0); - - assert(compare_lists("cat", "bat") > 0); - assert(compare_lists(["cat"], ["bat"]) > 0); -} -test_compare_lists(); - module test_any() { assert_equal(any([0,false,undef]), false); diff --git a/tests/test_paths.scad b/tests/test_paths.scad index 317b4be..6bd3252 100644 --- a/tests/test_paths.scad +++ b/tests/test_paths.scad @@ -1,5 +1,6 @@ include<../std.scad> + module test_is_path() { assert(is_path([[1,2,3],[4,5,6]])); assert(is_path([[1,2,3],[4,5,6],[7,8,9]])); @@ -15,6 +16,23 @@ module test_is_path() { test_is_path(); +module test_is_1region() { + assert(!is_1region([[3,4],[5,6],[7,8]])); + assert(is_1region([[[3,4],[5,6],[7,8]]])); +} +test_is_1region(); + + +module force_path() { + assert_equal(force_path([[3,4],[5,6],[7,8]]), [[3,4],[5,6],[7,8]]); + assert_equal(force_path([[[3,4],[5,6],[7,8]]]), [[3,4],[5,6],[7,8]]); + assert_equal(force_path("abc"), "abc"); + assert_equal(force_path(13), 13); +} +test_is_1region(); + + + module test_is_closed_path() { assert(!is_closed_path([[1,2,3],[4,5,6],[1,8,9]])); assert(is_closed_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]])); @@ -39,8 +57,219 @@ test_cleanup_path(); module test_path_merge_collinear() { path = [[-20,-20], [-10,-20], [0,-10], [10,0], [20,10], [20,20], [15,30]]; assert(path_merge_collinear(path) == [[-20,-20], [-10,-20], [20,10], [20,20], [15,30]]); - assert(path_merge_collinear([path]) == [[-20,-20], [-10,-20], [20,10], [20,20], [15,30]]); + assert(path_merge_collinear([path]) == [[-20,-20], [-10,-20], [20,10], [20,20], [15,30]]); + sq=square(10); + assert_equal(path_merge_collinear(subdivide_path(square(10), refine=25),closed=true), sq); } test_path_merge_collinear(); +module test_path_length(){ + sq = square(10); + assert_equal(path_length(sq),30); + assert_equal(path_length(sq,true),40); + c = circle($fn=1000, r=1); + assert(approx(path_length(c,closed=true), 2*PI,eps=.0001)); +} +test_path_length(); + + +module test_path_segment_lengths(){ + sq = square(10); + assert_equal(path_segment_lengths(sq), [10,10,10]); + assert_equal(path_segment_lengths(sq,true), [10,10,10,10]); + c = circle($fn=1000, r=1); + assert(approx(path_segment_lengths(c,closed=true), repeat(2*PI/1000,1000),eps=1e-7)); +} +test_path_segment_lengths(); + + +module test_path_length_fractions(){ + sq = square(10); + assert_approx(path_length_fractions(sq), [0,1/3, 2/3, 1]); + assert_approx(path_length_fractions(sq,true), [0,1/4, 2/4,3/4, 1]); +} +test_path_length_fractions(); + + + +module test_subdivide_path(){ + assert(approx(subdivide_path(square([2,2],center=true), 12), [[1, -1], [1/3, -1], [-1/3, -1], [-1, -1], [-1, -1/3], [-1, 1/3], [-1, 1], [-1/3, 1], [1/3, 1], [1, 1], [1, 1/3], [1, -1/3]])); + assert_equal(subdivide_path(square([8,2],center=true), 12), [[4, -1], [2, -1], [0, -1], [-2, -1], [-4, -1], [-4, 0], [-4, 1], [-2, 1], [0, 1], [2, 1], [4, 1], [4, 0]]); + assert_approx(subdivide_path(square([8,2],center=true), 12, method="segment"), [[4, -1], [4/3, -1], [-4/3, -1], [-4, -1], [-4, -1/3], [-4, 1/3], [-4, 1], [-4/3, 1], [4/3, 1], [4, 1], [4, 1/3], [4, -1/3]]); + assert_approx(subdivide_path(square([2,2],center=true), 17, closed=false), [[1, -1], [0.6, -1], [0.2, -1], [-0.2, -1], [-0.6, -1], [-1, -1], [-1, -2/3], [-1, -1/3], [-1, 0], [-1, 1/3], [-1, 2/3], [-1, 1], [-0.6, 1], [-0.2, 1], [0.2, 1], [0.6, 1], [1, 1]]); + assert_approx(subdivide_path(hexagon(side=2), [2,3,4,5,6,7], method="segment"), + [[2, 0], [1.5, -0.866025403784], [1, -1.73205080757], + [0.333333333333, -1.73205080757], [-0.333333333333, + -1.73205080757], [-1, -1.73205080757], [-1.25, + -1.29903810568], [-1.5, -0.866025403784], [-1.75, + -0.433012701892], [-2, 0], [-1.8, 0.346410161514], + [-1.6, 0.692820323028], [-1.4, 1.03923048454], [-1.2, + 1.38564064606], [-1, 1.73205080757], [-0.666666666667, + 1.73205080757], [-0.333333333333, 1.73205080757], [0, + 1.73205080757], [0.333333333333, 1.73205080757], + [0.666666666667, 1.73205080757], [1, 1.73205080757], + [1.14285714286, 1.48461497792], [1.28571428571, + 1.23717914826], [1.42857142857, 0.989743318611], + [1.57142857143, 0.742307488958], [1.71428571429, + 0.494871659305], [1.85714285714, 0.247435829653]]); + assert_approx(subdivide_path(pentagon(side=2), [3,4,3,4], method="segment", closed=false), + [[1.7013016167, 0], [1.30944478184, -0.539344662917], + [0.917587946981, -1.07868932583], [0.525731112119, + -1.61803398875], [0.0502028539716, -1.46352549156], + [-0.425325404176, -1.30901699437], [-0.900853662324, + -1.15450849719], [-1.37638192047, -1], [-1.37638192047, + -0.333333333333], [-1.37638192047, 0.333333333333], + [-1.37638192047, 1], [-0.900853662324, 1.15450849719], + [-0.425325404176, 1.30901699437], [0.0502028539716, + 1.46352549156], [0.525731112119, 1.61803398875]]); + assert_approx(subdivide_path(pentagon(side=2), 17), + [[1.7013016167, 0], [1.30944478184, + -0.539344662917], [0.917587946981, -1.07868932583], + [0.525731112119, -1.61803398875], [0.0502028539716, + -1.46352549156], [-0.425325404176, -1.30901699437], + [-0.900853662324, -1.15450849719], [-1.37638192047, + -1], [-1.37638192047, -0.333333333333], + [-1.37638192047, 0.333333333333], [-1.37638192047, + 1], [-0.900853662324, 1.15450849719], + [-0.425325404176, 1.30901699437], [0.0502028539716, + 1.46352549156], [0.525731112119, 1.61803398875], + [0.917587946981, 1.07868932583], [1.30944478184, + 0.539344662917]]); + assert_approx(subdivide_path(pentagon(side=2), 17, exact=false), + [[1.7013016167, 0], [1.30944478184, + -0.539344662917], [0.917587946981, -1.07868932583], + [0.525731112119, -1.61803398875], [-0.108306565411, + -1.41202265917], [-0.742344242941, -1.20601132958], + [-1.37638192047, -1], [-1.37638192047, + -0.333333333333], [-1.37638192047, 0.333333333333], + [-1.37638192047, 1], [-0.742344242941, + 1.20601132958], [-0.108306565411, 1.41202265917], + [0.525731112119, 1.61803398875], [0.917587946981, + 1.07868932583], [1.30944478184, 0.539344662917]]); + assert_approx(subdivide_path(pentagon(side=2), 18, exact=false), + [[1.7013016167, 0], [1.40740899056, + -0.404508497187], [1.11351636441, + -0.809016994375], [0.819623738265, + -1.21352549156], [0.525731112119, -1.61803398875], + [0.0502028539716, -1.46352549156], + [-0.425325404176, -1.30901699437], + [-0.900853662324, -1.15450849719], + [-1.37638192047, -1], [-1.37638192047, -0.5], + [-1.37638192047, 0], [-1.37638192047, 0.5], + [-1.37638192047, 1], [-0.900853662324, + 1.15450849719], [-0.425325404176, 1.30901699437], + [0.0502028539716, 1.46352549156], [0.525731112119, + 1.61803398875], [0.819623738265, 1.21352549156], + [1.11351636441, 0.809016994375], [1.40740899056, + 0.404508497187]]); + assert_approx(subdivide_path([[0,0,0],[2,0,1],[2,3,2]], 12), + [[0, 0, 0], [2/3, 0, 1/3], [4/3, 0, 2/3], [2, 0, 1], [2, 0.75, 1.25], [2, 1.5, 1.5], [2, 2.25, 1.75], [2, 3, 2], [1.6, 2.4, 1.6], [1.2, 1.8, 1.2], [0.8, 1.2, 0.8], [0.4, 0.6, 0.4]]); +} +test_subdivide_path(); + + +module test_subdivide_long_segments(){ + path = pentagon(d=100); + spath = subdivide_long_segments(path, 10, closed=true); + assert_approx(spath, + [[50, 0], [44.2418082865, -7.92547096913], [38.4836165729, + -15.8509419383], [32.7254248594, -23.7764129074], [26.9672331458, + -31.7018838765], [21.2090414323, -39.6273548456], [15.4508497187, + -47.5528258148], [6.1338998125, -44.5255652814], [-3.18305009375, + -41.498304748], [-12.5, -38.4710442147], [-21.8169499062, + -35.4437836813], [-31.1338998125, -32.416523148], [-40.4508497187, + -29.3892626146], [-40.4508497187, -19.5928417431], [-40.4508497187, + -9.79642087154], [-40.4508497187, 0], [-40.4508497187, 9.79642087154], + [-40.4508497187, 19.5928417431], [-40.4508497187, 29.3892626146], + [-31.1338998125, 32.416523148], [-21.8169499062, 35.4437836813], + [-12.5, 38.4710442147], [-3.18305009375, 41.498304748], [6.1338998125, + 44.5255652814], [15.4508497187, 47.5528258148], [21.2090414323, + 39.6273548456], [26.9672331458, 31.7018838765], [32.7254248594, + 23.7764129074], [38.4836165729, 15.8509419383], [44.2418082865, + 7.92547096913]]); +} +test_subdivide_long_segments(); + + +module test_resample_path(){ + path = xscale(2,circle($fn=250, r=10)); + sampled = resample_path(path, 16); + assert_approx(sampled, + [[20, 0], [17.1657142861, -5.13020769642], + [11.8890531315, -8.04075246881], [6.03095737128, + -9.53380030092], [1.72917236085e-14, -9.99921044204], + [-6.03095737128, -9.53380030092], [-11.8890531315, + -8.04075246881], [-17.1657142861, -5.13020769642], [-20, + -3.19176120946e-14], [-17.1657142861, 5.13020769642], + [-11.8890531315, 8.04075246881], [-6.03095737128, + 9.53380030092], [-4.20219414821e-14, 9.99921044204], + [6.03095737128, 9.53380030092], [11.8890531315, + 8.04075246881], [17.1657142861, 5.13020769642]]); + path2 = square(20); + assert_approx(resample_path(path2, spacing=6), + [[20, 0], [13.8461538462, 0], [7.69230769231, 0], [1.53846153846, 0], + [0, 4.61538461538], [0, 10.7692307692], [0, 16.9230769231], [3.07692307692, 20], + [9.23076923077, 20], [15.3846153846, 20], [20, 18.4615384615], [20, 12.3076923077], [20, 6.15384615385]]); + assert_equal(resample_path(path2, spacing=6,closed=false),[[20, 0], [14, 0], [8, 0], [2, 0], [0, 4], [0, 10], [0, 16], [2, 20], [8, 20], [14, 20], [20, 20]]); + assert_approx(resample_path(path, spacing=17), + [[20, 0], [8.01443073309, -9.16170407964], + [-8.01443073309, -9.16170407964], [-20, + -1.59309060367e-14], [-8.01443073309, 9.16170407964], + [8.01443073309, 9.16170407964]]); +} +test_resample_path(); + + +module test_path_closest_point(){ + path = circle(d=100,$fn=6); + pt = [20,10]; + closest = path_closest_point(path, pt); + assert_approx(closest, [5, [38.1698729811, 20.4903810568]]); +} +test_path_closest_point(); + + + +module test_path_tangents(){ + path = circle(r=1, $fn=200); + path_t = path_tangents(path,closed=true); + assert_approx(path_t, hstack(column(path,1), -column(path,0))); + rect = square([10,3]); + tr1 = path_tangents(rect,closed=true); + tr2 = path_tangents(rect,closed=true,uniform=false); + tr3 = path_tangents(rect,closed=false); + tr4 = path_tangents(rect,closed=false,uniform=false); + assert_approx(tr1, [[-0.957826285221, -0.287347885566], [-0.957826285221, 0.287347885566], [0.957826285221, 0.287347885566], [0.957826285221, -0.287347885566]]); + assert_approx(tr2, [[-0.707106781187, -0.707106781187], [-0.707106781187, 0.707106781187], [0.707106781187, 0.707106781187], [0.707106781187, -0.707106781187]]); + assert_approx(tr3, [[-0.99503719021, -0.099503719021], [-0.957826285221, 0.287347885566], [0.957826285221, 0.287347885566], [0.99503719021, -0.099503719021]]); + assert_approx(tr4, [[-1, 0], [-0.707106781187, 0.707106781187], [0.707106781187, 0.707106781187], [1, 0]]); +} +test_path_tangents(); + + + +module test_path_curvature(){ + c8 = path3d(circle(r=8, $fn=100)); + c28 = path3d(circle(r=28, $fn=100)); + assert(approx(path_curvature(c8,closed=true), repeat(1/8, 100), 4e-4)); + assert(approx(path_curvature(c28,closed=true), repeat(1/28, 100), 4e-4)); +} +test_path_curvature(); + + +module test_path_torsion(){ + c = path3d(circle(r=1, $fn=100)); + tc = path_torsion(c, closed=true); + assert(all_zero(tc)); + a=3;b=7; + helix = [for(t=[0:1:20]) [a*cos(t), a*sin(t), b*t*PI/180]]; + th = path_torsion(helix, closed=false); + assert(approx(th[5], b/(a*a+b*b), 1e-5)); +} +test_path_torsion(); + + + +//echo(fmt_float(sampled)); + diff --git a/tests/test_shapes2d.scad b/tests/test_shapes2d.scad index 1db2c02..42a178b 100644 --- a/tests/test_shapes2d.scad +++ b/tests/test_shapes2d.scad @@ -216,11 +216,4 @@ module test_mask2d_ogee() { test_mask2d_ogee(); -module test_dashed_stroke() { - segs = dashed_stroke([[0,0],[10,0]], dashpat=[3,2], closed=false); - assert_equal(segs,[[[0,0],[3,0]], [[5,0],[8,0]]]); -} -test_dashed_stroke(); - - // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/tests/test_transforms.scad b/tests/test_transforms.scad index 947e695..ec338a1 100644 --- a/tests/test_transforms.scad +++ b/tests/test_transforms.scad @@ -395,4 +395,61 @@ module test_skew() { test_skew(); +module test_apply() { + assert(approx(apply(affine3d_xrot(90),2*UP),2*FRONT)); + assert(approx(apply(affine3d_yrot(90),2*UP),2*RIGHT)); + assert(approx(apply(affine3d_zrot(90),2*UP),2*UP)); + assert(approx(apply(affine3d_zrot(90),2*RIGHT),2*BACK)); + assert(approx(apply(affine3d_zrot(90),2*BACK+2*RIGHT),2*BACK+2*LEFT)); + assert(approx(apply(affine3d_xrot(135),2*BACK+2*UP),2*sqrt(2)*FWD)); + assert(approx(apply(affine3d_yrot(135),2*RIGHT+2*UP),2*sqrt(2)*DOWN)); + assert(approx(apply(affine3d_zrot(45),2*BACK+2*RIGHT),2*sqrt(2)*BACK)); + + module check_path_apply(mat,path) + assert_approx(apply(mat,path),path3d([for (p=path) mat*concat(p,1)])); + + check_path_apply(xrot(45), path3d(rect(100,center=true))); + check_path_apply(yrot(45), path3d(rect(100,center=true))); + check_path_apply(zrot(45), path3d(rect(100,center=true))); + check_path_apply(rot([20,30,40])*scale([0.9,1.1,1])*move([10,20,30]), path3d(rect(100,center=true))); + + module check_patch_apply(mat,patch) + assert_approx(apply(mat,patch), [for (path=patch) path3d([for (p=path) mat*concat(p,1)])]); + + flat = [for (x=[-50:25:50]) [for (y=[-50:25:50]) [x,y,0]]]; + check_patch_apply(xrot(45), flat); + check_patch_apply(yrot(45), flat); + check_patch_apply(zrot(45), flat); + check_patch_apply(rot([20,30,40])*scale([0.9,1.1,1])*move([10,20,30]), flat); +} +test_apply(); + + +module test_is_2d_transform() { + assert(!is_2d_transform(affine2d_identity())); + assert(!is_2d_transform(affine2d_translate([5,8]))); + assert(!is_2d_transform(affine2d_scale([3,4]))); + assert(!is_2d_transform(affine2d_zrot(30))); + assert(!is_2d_transform(affine2d_mirror([-1,1]))); + assert(!is_2d_transform(affine2d_skew(30,15))); + + assert(is_2d_transform(affine3d_identity())); + assert(is_2d_transform(affine3d_translate([30,40,0]))); + assert(!is_2d_transform(affine3d_translate([30,40,50]))); + assert(is_2d_transform(affine3d_scale([3,4,1]))); + assert(!is_2d_transform(affine3d_xrot(30))); + assert(!is_2d_transform(affine3d_yrot(30))); + assert(is_2d_transform(affine3d_zrot(30))); + assert(is_2d_transform(affine3d_skew(sxy=2))); + assert(is_2d_transform(affine3d_skew(syx=2))); + assert(!is_2d_transform(affine3d_skew(szx=2))); + assert(!is_2d_transform(affine3d_skew(szy=2))); +} +test_is_2d_transform(); + + + + + + // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/tests/test_utility.scad b/tests/test_utility.scad index 3d150d7..0ceddb0 100644 --- a/tests/test_utility.scad +++ b/tests/test_utility.scad @@ -76,6 +76,15 @@ module test_is_def() { test_is_def(); +module test_segs() { + assert_equal(segs(50,$fn=8), 8); + assert_equal(segs(50,$fa=2,$fs=2), 158); +} +test_segs(); + + + + module test_is_str() { assert(!is_str(undef)); assert(!is_str(true)); diff --git a/tests/test_vectors.scad b/tests/test_vectors.scad index 8b287f1..4c6408a 100644 --- a/tests/test_vectors.scad +++ b/tests/test_vectors.scad @@ -113,6 +113,40 @@ module test_v_theta() { test_v_theta(); +module test_min_index() { + vals = rands(-100,100,100,seed=75); + minval = min(vals); + minidx = min_index(vals); + assert_equal(vals[minidx], minval); + assert_equal(min_index([3,4,5,6]), 0); + assert_equal(min_index([4,3,5,6]), 1); + assert_equal(min_index([4,5,3,6]), 2); + assert_equal(min_index([4,5,6,3]), 3); + assert_equal(min_index([6,5,4,3]), 3); + assert_equal(min_index([6,3,4,5]), 1); + assert_equal(min_index([-56,72,-874,5]), 2); +} +test_min_index(); + + +module test_max_index() { + vals = rands(-100,100,100,seed=97); + maxval = max(vals); + maxidx = max_index(vals); + assert_equal(vals[maxidx], maxval); + assert_equal(max_index([3,4,5,6]), 3); + assert_equal(max_index([3,4,6,5]), 2); + assert_equal(max_index([3,6,4,5]), 1); + assert_equal(max_index([6,3,4,5]), 0); + assert_equal(max_index([5,6,4,3]), 1); + assert_equal(max_index([-56,72,-874,5]), 1); +} +test_max_index(); + + + + + module test_unit() { assert(unit([10,0,0]) == [1,0,0]); assert(unit([0,10,0]) == [0,1,0]); From 7bac9a484da53ee0498d2d9153faf4d9affa7d20 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Mon, 1 Nov 2021 17:34:18 -0400 Subject: [PATCH 10/11] point_in_polygon optimization --- geometry.scad | 60 ++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/geometry.scad b/geometry.scad index 2ee3b7e..0dd5502 100644 --- a/geometry.scad +++ b/geometry.scad @@ -1583,6 +1583,7 @@ function _point_above_below_segment(point, edge) = ? (edge[1].y > 0 && cross(edge[0], edge[1]-edge[0]) > 0) ? 1 : 0 : (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0; + function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) = // Original algorithms from http://geomalgorithms.com/a03-_inclusion.html assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2, @@ -1597,39 +1598,44 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) = : // Does the point lie on any edges? If so return 0. let( - on_brd = [ - for (i = [0:1:len(poly)-1]) - let( seg = select(poly,i,i+1) ) - if (!approx(seg[0],seg[1],eps) ) - _is_point_on_line(point, seg, SEGMENT, eps=eps)? 1:0 - ] + segs = pair(poly,true), + on_border = [for (seg=segs) + if (norm(seg[0]-seg[1])>eps && _is_point_on_line(point, seg, SEGMENT, eps=eps)) 1] ) - sum(on_brd) > 0? 0 : - nonzero - ? // Compute winding number and return 1 for interior, -1 for exterior - let( - windchk = [ - for(i=[0:1:len(poly)-1]) - let( seg=select(poly,i,i+1) ) - if (!approx(seg[0],seg[1],eps=eps)) - _point_above_below_segment(point, seg) + on_border != [] ? 0 : + nonzero // Compute winding number and return 1 for interior, -1 for exterior + ? let( + winding = [ + for(seg=segs) + let( + p0=seg[0]-point, + p1=seg[1]-point + ) + if (norm(p0-p1)>eps) + p0.y <=0 + ? p1.y > 0 && cross(p0,p1-p0)>0 ? 1 : 0 + : p1.y <=0 && cross(p0,p1-p0)<0 ? -1: 0 ] - ) sum(windchk) != 0 ? 1 : -1 + ) + sum(winding) != 0 ? 1 : -1 : // or compute the crossings with the ray [point, point+[1,0]] let( - n = len(poly), cross = [ - for(i=[0:n-1]) - let( - p0 = poly[i]-point, - p1 = poly[(i+1)%n]-point - ) - if ( - ( (p1.y>eps && p0.y<=eps) || (p1.y<=eps && p0.y>eps) ) - && -eps < p0.x - p0.y *(p1.x - p0.x)/(p1.y - p0.y) - ) 1 + for(seg=segs) + let( + p0 = seg[0]-point, + p1 = seg[1]-point + ) + if ( + ( (p1.y>eps && p0.y<=eps) || (p1.y<=eps && p0.y>eps) ) + && -eps < p0.x - p0.y *(p1.x - p0.x)/(p1.y - p0.y) + ) + 1 ] - ) 2*(len(cross)%2)-1; + ) + 2*(len(cross)%2)-1; + + // Function: polygon_triangulate() From 530f3b3449a795676380c185fb0aa6d063e96c62 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Mon, 1 Nov 2021 18:14:31 -0400 Subject: [PATCH 11/11] triplet fix --- geometry.scad | 4 ++-- lists.scad | 24 +++++++++++++++++------- mutators.scad | 2 +- paths.scad | 8 ++++---- tests/test_lists.scad | 7 +++---- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/geometry.scad b/geometry.scad index 0dd5502..498be1c 100644 --- a/geometry.scad +++ b/geometry.scad @@ -1839,7 +1839,7 @@ function is_polygon_clockwise(poly) = // poly = The list of 2D path points for the perimeter of the polygon. function clockwise_polygon(poly) = assert(is_path(poly,dim=2), "Input should be a 2d polygon") - polygon_area(poly, signed=true)<0 ? poly : reverse_polygon(poly); + is_polygon_clockwise(poly) ? poly : reverse_polygon(poly); // Function: ccw_polygon() @@ -1853,7 +1853,7 @@ function clockwise_polygon(poly) = // poly = The list of 2D path points for the perimeter of the polygon. function ccw_polygon(poly) = assert(is_path(poly,dim=2), "Input should be a 2d polygon") - polygon_area(poly, signed=true)<0 ? reverse_polygon(poly) : poly; + is_polygon_clockwise(poly) ? reverse_polygon(poly) : poly; // Function: reverse_polygon() diff --git a/lists.scad b/lists.scad index 07356e9..8947b7f 100644 --- a/lists.scad +++ b/lists.scad @@ -970,9 +970,17 @@ function pair(list, wrap=false) = // See Also: idx(), enumerate(), pair(), combinations(), permutations() // Description: // Takes a list, and returns a list of adjacent triplets from it, optionally wrapping back to the front. +// If you set `wrap` to true then the first triplet is the one centered on the first list element, so it includes +// the last element and the first two elements. If the list has fewer than three elements then the empty list is returned. +// Arguments: +// list = list to produce triplets from +// wrap = if true, wrap triplets around the list. Default: false // Example: -// l = ["A","B","C","D","E"]; -// echo([for (p=triplet(l)) str(p.z,p.y,p.x)]); // Outputs: ["CBA", "DCB", "EDC"] +// list = [0,1,2,3,4]; +// a = triplet(list); // Returns [[0,1,2],[1,2,3],[2,3,4]] +// b = triplet(list,wrap=true); // Returns [[4,0,1],[0,1,2],[1,2,3],[2,3,4],[3,4,0]] +// letters = ["A","B","C","D","E"]; +// [for (p=triplet(letters)) str(p.z,p.y,p.x)]; // Returns: ["CBA", "DCB", "EDC"] // Example(2D): // path = [for (i=[0:24]) polar_to_xy(i*2, i*360/12)]; // for (t = triplet(path)) { @@ -984,11 +992,13 @@ function pair(list, wrap=false) = function triplet(list, wrap=false) = assert(is_list(list)||is_string(list), "Invalid input." ) assert(is_bool(wrap)) - let( - ll = len(list) - ) wrap - ? [for (i=[0:1:ll-1]) [ list[i], list[(i+1)%ll], list[(i+2)%ll] ]] - : [for (i=[0:1:ll-3]) [ list[i], list[i+1], list[i+2] ]]; + let(L=len(list)) + [ + if(wrap) [list[L-1], list[0], list[1]], + for (i=[0:1:L-3]) [list[i],list[i+1],list[i+2]], + if(wrap) [list[L-2], list[L-1], list[0]] + ]; + // Function: combinations() diff --git a/mutators.scad b/mutators.scad index 63e22c4..88836a2 100644 --- a/mutators.scad +++ b/mutators.scad @@ -524,7 +524,7 @@ module path_extrude2d(path, caps=false, closed=false) { } } - if (false && caps) { + if (caps) { move_copies([path[0],last(path)]) rotate_extrude() right_half(planar=true) children(); diff --git a/paths.scad b/paths.scad index bfe0772..15349dd 100644 --- a/paths.scad +++ b/paths.scad @@ -228,12 +228,12 @@ function path_length_fractions(path, closed) = let( lengths = [ 0, - for (i=[0:1:len(path)-(closed?1:2)]) - norm(select(path,i+1)-path[i]) + each path_segment_lengths(path,closed) ], partial_len = cumsum(lengths), total_len = last(partial_len) - ) partial_len / total_len; + ) + partial_len / total_len; @@ -556,7 +556,7 @@ function path_closest_point(path, pt, closed=true) = assert(is_vector(pt, len(path[0])), "Input pt must be a compatible vector") assert(is_bool(closed)) let( - pts = [for (seg=[0:1:len(path)-(closed?1:2)]) line_closest_point(select(path,seg,seg+1),pt,SEGMENT)], + pts = [for (seg=pair(path,closed)) line_closest_point(seg,pt,SEGMENT)], dists = [for (p=pts) norm(p-pt)], min_seg = min_index(dists) ) [min_seg, pts[min_seg]]; diff --git a/tests/test_lists.scad b/tests/test_lists.scad index bb8b140..3672802 100644 --- a/tests/test_lists.scad +++ b/tests/test_lists.scad @@ -358,10 +358,9 @@ test_pair(); module test_triplet() { assert(triplet([3,4,5,6,7]) == [[3,4,5], [4,5,6], [5,6,7]]); assert(triplet("ABCDE") == [["A","B","C"], ["B","C","D"], ["C","D","E"]]); - assert(triplet([3,4,5,6],true) == [[3,4,5], [4,5,6], [5,6,3], [6,3,4]]); - assert(triplet("ABCD",true) == [["A","B","C"], ["B","C","D"], ["C","D","A"], ["D","A","B"]]); - assert(triplet([3,4,5,6],wrap=true) == [[3,4,5], [4,5,6], [5,6,3], [6,3,4]]); - assert(triplet("ABCD",wrap=true) == [["A","B","C"], ["B","C","D"], ["C","D","A"], ["D","A","B"]]); + assert(triplet([3,4,5,6],true) == [[6,3,4],[3,4,5], [4,5,6], [5,6,3]]); + assert(triplet("ABCD",true) == [["D","A","B"],["A","B","C"], ["B","C","D"], ["C","D","A"]]); + assert(triplet("ABCD",wrap=true) == [["D","A","B"],["A","B","C"], ["B","C","D"], ["C","D","A"]]); } test_triplet();