mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
10604cd20b
23 changed files with 992 additions and 568 deletions
110
comparisons.scad
110
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 :
|
||||
is_vector(x) && [for (xx=x) if(abs(xx)>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)<eps) 1] == [];
|
||||
|
||||
|
||||
// Function: all_positive()
|
||||
// Usage:
|
||||
// test = all_positive(x);
|
||||
// test = all_positive(x,[eps]);
|
||||
// Description:
|
||||
// Returns true if the finite number passed to it is greater than zero.
|
||||
// If passed a list, recursively checks if all items in the list are positive.
|
||||
// If passed a list returns true if all the entries are positive.
|
||||
// Otherwise, returns false.
|
||||
// Arguments:
|
||||
// x = The value to check.
|
||||
// eps = Tolerance. Default: 0
|
||||
// Example:
|
||||
// a = all_positive(-2); // Returns: false.
|
||||
// b = all_positive(0); // Returns: false.
|
||||
|
@ -89,21 +88,21 @@ function all_nonzero(x, eps=EPSILON) =
|
|||
// e = all_positive([0,1,2]); // Returns: false.
|
||||
// f = all_positive([3,1,2]); // Returns: true.
|
||||
// g = all_positive([3,-1,2]); // Returns: false.
|
||||
function all_positive(x) =
|
||||
is_num(x)? x>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<v) li ],
|
||||
equal = [for(li=list) if(li==v) li ]
|
||||
)
|
||||
len(smaller) == k ? smaller :
|
||||
len(smaller)<k && len(smaller)+len(equal) >= 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<v) li ],
|
||||
equal = [for(li=list) if(li==v) li ]
|
||||
)
|
||||
len(smaller) == k ? smaller :
|
||||
len(smaller)<k && len(smaller)+len(equal) >= 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)));
|
||||
|
||||
|
|
|
@ -554,6 +554,7 @@ function dashed_stroke(path, dashpat=[3,3], closed=false) =
|
|||
|
||||
|
||||
module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) {
|
||||
no_children($children);
|
||||
segs = dashed_stroke(path, dashpat=dashpat*width, closed=closed);
|
||||
for (seg = segs)
|
||||
stroke(seg, width=width, endcaps=false);
|
||||
|
@ -731,6 +732,7 @@ module arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false)
|
|||
// stroke(helix(turns=-2.5, h=100, r=50), dots=true, dots_color="blue");
|
||||
// Example(3D): Flat helix (note points are still 3d)
|
||||
// stroke(helix(h=0,r1=50,r2=25,l=0, turns=4));
|
||||
module helix(l,h,turns,angle, r, r1, r2, d, d1, d2) {no_module();}
|
||||
function helix(l,h,turns,angle, r, r1, r2, d, d1, d2)=
|
||||
let(
|
||||
r1=get_radius(r=r,r1=r1,d=d,d1=d1,dflt=1),
|
||||
|
|
|
@ -310,8 +310,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.
|
||||
|
@ -1496,7 +1496,7 @@ function _region_centroid(region,eps=EPSILON) =
|
|||
total[0]/total[1];
|
||||
|
||||
|
||||
/// Function: _polygon_centroid()
|
||||
/// Internal Function: _polygon_centroid()
|
||||
/// Usage:
|
||||
/// cpt = _polygon_centroid(poly);
|
||||
/// Topics: Geometry, Polygons, Centroid
|
||||
|
@ -1640,6 +1640,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,
|
||||
|
@ -1654,39 +1655,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()
|
||||
|
@ -1912,7 +1918,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()
|
||||
|
@ -1926,7 +1932,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()
|
||||
|
|
37
lists.scad
37
lists.scad
|
@ -955,11 +955,13 @@ function enumerate(l,idx=undef) =
|
|||
function pair(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]]]
|
||||
: [for (i=[0:1:ll-2]) [list[i], list[i+1]]];
|
||||
let( L = len(list)-1)
|
||||
L<1 ? [] :
|
||||
[
|
||||
for (i=[0:1:L-1]) [list[i], list[i+1]],
|
||||
if(wrap) [list[L], list[0]]
|
||||
];
|
||||
|
||||
|
||||
|
||||
// Function: triplet()
|
||||
|
@ -970,9 +972,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 +994,14 @@ 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))
|
||||
L<3 ? [] :
|
||||
[
|
||||
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()
|
||||
|
|
|
@ -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) )
|
||||
|
|
109
mutators.scad
109
mutators.scad
|
@ -475,12 +475,15 @@ 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. This module works properly in general only if the given
|
||||
// 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.
|
||||
// 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 +494,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 <BOSL2/beziers.scad>
|
||||
|
@ -500,39 +503,77 @@ module chain_hull()
|
|||
// ]);
|
||||
// path_extrude2d(path, caps=false)
|
||||
// trapezoid(w1=10, w2=1, h=5, anchor=BACK);
|
||||
module path_extrude2d(path, caps=true) {
|
||||
thin = 0.01;
|
||||
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");
|
||||
path = deduplicate(path);
|
||||
for (p=pair(path)) {
|
||||
delt = p[1]-p[0];
|
||||
translate(p[0]) {
|
||||
rot(from=BACK,to=delt) {
|
||||
minkowski() {
|
||||
cube([thin,norm(delt),thin], anchor=FRONT);
|
||||
rotate([90,0,0]) linear_extrude(height=thin,center=true) children();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (t=triplet(path)) {
|
||||
ang = v_theta(t[2]-t[1]) - v_theta(t[1]-t[0]);
|
||||
delt = t[2] - t[1];
|
||||
translate(t[1]) {
|
||||
minkowski() {
|
||||
cube(thin,center=true);
|
||||
if (ang >= 0) {
|
||||
rotate(90-ang)
|
||||
rot(from=LEFT,to=delt)
|
||||
rotate_extrude(angle=ang+0.01)
|
||||
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)) * sign(_point_left_of_line2d(t[2],[t[0],t[1]]));
|
||||
delt = point3d(t[2] - t[1]);
|
||||
if (ang!=0)
|
||||
translate(t[1]) {
|
||||
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 {
|
||||
rotate(-90)
|
||||
rot(from=RIGHT,to=delt)
|
||||
rotate_extrude(angle=-ang+0.01)
|
||||
left_half(planar=true) children();
|
||||
}
|
||||
else
|
||||
left_half(planar=true) children();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (caps) {
|
||||
move_copies([path[0],last(path)])
|
||||
rotate_extrude()
|
||||
right_half(planar=true) children();
|
||||
}
|
||||
}
|
||||
module new_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");
|
||||
path = deduplicate(path);
|
||||
|
||||
|
||||
for (i=[0:1:len(path)-(closed?1:2)]){
|
||||
// for (i=[0:1:1]){
|
||||
difference(){
|
||||
extrude_from_to(path[i],select(path,i+1)) xflip()rot(-90)children();
|
||||
# for(t = [select(path,i-1,i+1)]){ //, select(path,i,i+2)]){
|
||||
ang = -(180-vector_angle(t)) * sign(_point_left_of_line2d(t[2],[t[0],t[1]]));
|
||||
echo(ang=ang);
|
||||
delt = point3d(t[2] - t[1]);
|
||||
if (ang!=0)
|
||||
translate(t[1]) {
|
||||
frame_map(y=delt, z=UP)
|
||||
rotate(-sign(ang)*extra_ang/2)
|
||||
rotate_extrude(angle=ang+sign(ang)*extra_ang)
|
||||
if (ang<0)
|
||||
left_half(planar=true) children();
|
||||
else
|
||||
right_half(planar=true) children();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (t=triplet(path,wrap=closed)) {
|
||||
ang = -(180-vector_angle(t)) * sign(_point_left_of_line2d(t[2],[t[0],t[1]]));
|
||||
echo(oang=ang);
|
||||
delt = point3d(t[2] - t[1]);
|
||||
if (ang!=0)
|
||||
translate(t[1]) {
|
||||
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) {
|
||||
move_copies([path[0],last(path)])
|
||||
|
|
256
paths.scad
256
paths.scad
|
@ -1,14 +1,18 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
// 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. 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 <BOSL2/std.scad>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// Section: Utility Functions
|
||||
|
||||
|
||||
// Function: is_path()
|
||||
// Usage:
|
||||
// is_path(list, [dim], [fast])
|
||||
|
@ -16,7 +20,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,30 +50,29 @@ 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;
|
||||
|
||||
|
||||
// Function: force_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 +146,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 +176,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 +196,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,22 +218,22 @@ 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))
|
||||
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;
|
||||
|
||||
|
||||
|
||||
|
@ -286,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]]
|
||||
|
@ -337,7 +339,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
|
||||
|
@ -386,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)
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
@ -423,7 +421,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,10 +457,33 @@ 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
|
||||
// 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))
|
||||
|
@ -493,11 +514,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))
|
||||
|
@ -519,8 +540,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);
|
||||
|
@ -531,11 +552,11 @@ 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(
|
||||
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]];
|
||||
|
@ -551,7 +572,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 +590,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 +613,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 +644,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 +667,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 +966,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 +987,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 +1044,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 +1109,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 +1140,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;
|
||||
|
|
129
regions.scad
129
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 <BOSL2/std.scad>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
@ -23,29 +22,100 @@
|
|||
// 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
|
||||
// can clean up an ill-formed region using sanitize_region().
|
||||
// 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 make_region().
|
||||
|
||||
|
||||
// Function: is_region()
|
||||
// Usage:
|
||||
// is_region(x);
|
||||
// Description:
|
||||
// Returns true if the given item looks like a region. A region is defined as a list of zero or more paths.
|
||||
// Returns true if the given item looks like a region. A region is a list of non-crossing simple paths. This test just checks
|
||||
// that the argument is a list whose first entry is a path.
|
||||
function is_region(x) = is_list(x) && is_path(x.x);
|
||||
|
||||
|
||||
// Function: force_region()
|
||||
// Function: is_valid_region()
|
||||
// Usage:
|
||||
// region = force_region(path)
|
||||
// bool = is_valid_region(region, [eps]);
|
||||
// Description:
|
||||
// If the input is a path then return it as a region. Otherwise return it unaltered.
|
||||
function force_region(path) = is_path(path) ? [path] : path;
|
||||
// Returns true if the input is a valid region, meaning that it is a list of simple paths whose segments do not cross each other.
|
||||
// This test can be time consuming with regions that contain many points.
|
||||
// It differs from `is_region()` which simply checks that the object appears to be a list of paths
|
||||
// because it searches all the region paths for any self-intersections or intersections with each other.
|
||||
// Will also return true if given a single simple path. Use {{make_region()}} to convert sets of self-intersecting polygons into
|
||||
// a region.
|
||||
// Arguments:
|
||||
// region = region to check
|
||||
// eps = tolerance for geometric comparisons. Default: `EPSILON` = 1e-9
|
||||
// Example(2D,noaxes): Nested squares form a region
|
||||
// region = [for(i=[3:2:10]) square(i,center=true)];
|
||||
// rainbow(region)stroke($item, width=.1,closed=true);
|
||||
// back(6)text(is_valid_region(region) ? "region" : "non-region", size=2,halign="center");
|
||||
// Example(2D,noaxes): Two non-intersecting squares make a valid region:
|
||||
// region = [square(10), right(11,square(8))];
|
||||
// rainbow(region)stroke($item, width=.1,closed=true);
|
||||
// back(12)text(is_valid_region(region) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): Not a region due to a self-intersecting (non-simple) hourglass path
|
||||
// object = [move([-2,-2],square(14)), [[0,0],[10,0],[0,10],[10,10]]];
|
||||
// rainbow(object)stroke($item, width=.1,closed=true);
|
||||
// move([-1.5,13])text(is_valid_region(object) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): Breaking hourglass in half fixes it. Now it's a region:
|
||||
// region = [move([-2,-2],square(14)), [[0,0],[10,0],[5,5]], [[5,5],[0,10],[10,10]]];
|
||||
// rainbow(region)stroke($item, width=.1,closed=true);
|
||||
// move([1,13])text(is_valid_region(region) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): As with the "broken" hourglass, Touching at corners is OK. This is a region.
|
||||
// region = [square(10), move([10,10], square(8))];
|
||||
// rainbow(region)stroke($item, width=.1,closed=true);
|
||||
// back(12)text(is_valid_region(region) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): The squares cross each other, so not a region
|
||||
// object = [square(10), move([8,8], square(8))];
|
||||
// rainbow(object)stroke($item, width=.1,closed=true);
|
||||
// back(17)text(is_valid_region(object) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): A union is one way to fix the above example and get a region. (Note that union is run here on two simple paths, which are valid regions themselves and hence acceptable inputs to union.
|
||||
// region = union([square(10), move([8,8], square(8))]);
|
||||
// rainbow(region)stroke($item, width=.1,closed=true);
|
||||
// back(12)text(is_valid_region(region) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): These two squares share part of an edge, hence not a region
|
||||
// object = [square(10), move([10,2], square(7))];
|
||||
// stroke(object[0], width=0.1,closed=true);
|
||||
// color("red")dashed_stroke(object[1], width=0.1,closed=true);
|
||||
// back(12)text(is_valid_region(object) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): These two squares share a full edge, hence not a region
|
||||
// object = [square(10), right(10, square(10))];
|
||||
// stroke(object[0], width=0.1,closed=true);
|
||||
// color("red")dashed_stroke(object[1], width=0.1,closed=true);
|
||||
// back(12)text(is_valid_region(object) ? "region" : "non-region", size=2);
|
||||
// Example(2D,noaxes): Sharing on edge on the inside, also not a regionn
|
||||
// object = [square(10), [[0,0], [2,2],[2,8],[0,10]]];
|
||||
// stroke(object[0], width=0.1,closed=true);
|
||||
// color("red")dashed_stroke(object[1], width=0.1,closed=true);
|
||||
// back(12)text(is_valid_region(object) ? "region" : "non-region", size=2);
|
||||
function is_valid_region(region, eps=EPSILON) =
|
||||
let(region=force_region(region))
|
||||
assert(is_region(region), "Input is not a region")
|
||||
[for(p=region) if (!is_path_simple(p,closed=true,eps=eps)) 1] == []
|
||||
&&
|
||||
[for(i=[0:1:len(region)-2])
|
||||
|
||||
let( isect = _region_region_intersections([region[i]], list_tail(region,i+1), eps=eps))
|
||||
each [
|
||||
// check for intersection points not at the end of a segment
|
||||
for(pts=flatten(isect[0])) if (pts[2]!=0 && pts[2]!=1) 1,
|
||||
// check for full segment
|
||||
for(seg=pair(flatten(isect[0])))
|
||||
if (seg[0][0]==seg[1][0] // same path
|
||||
&& seg[0][1]==seg[1][1] // same segment
|
||||
&& seg[0][2]==0 && seg[1][2]==1) // both ends
|
||||
1]
|
||||
] ==[];
|
||||
|
||||
|
||||
// Function: sanitize_region()
|
||||
|
||||
// Function: make_region()
|
||||
// Usage:
|
||||
// r_fixed = sanitize_region(r, [nonzero], [eps]);
|
||||
// r_fixed = make_region(r, [nonzero], [eps]);
|
||||
// Description:
|
||||
// Takes a malformed input region that contains self-intersecting polygons or polygons
|
||||
// that cross each other and converts it into a properly defined region without
|
||||
|
@ -56,7 +126,7 @@ function force_region(path) = is_path(path) ? [path] : path;
|
|||
// eps = Epsilon for geometric comparisons. Default: `EPSILON` (1e-9)
|
||||
// Examples:
|
||||
//
|
||||
function sanitize_region(r,nonzero=false,eps=EPSILON) =
|
||||
function make_region(r,nonzero=false,eps=EPSILON) =
|
||||
let(r=force_region(r))
|
||||
assert(is_region(r), "Input is not a region")
|
||||
exclusive_or(
|
||||
|
@ -64,6 +134,17 @@ function sanitize_region(r,nonzero=false,eps=EPSILON) =
|
|||
eps=eps);
|
||||
|
||||
|
||||
|
||||
// Function: force_region()
|
||||
// Usage:
|
||||
// region = force_region(path)
|
||||
// Description:
|
||||
// If the input is a path then return it as a region. Otherwise return it unaltered.
|
||||
function force_region(path) = is_path(path) ? [path] : path;
|
||||
|
||||
|
||||
// Section: Turning a region into geometry
|
||||
|
||||
// Module: region()
|
||||
// Usage:
|
||||
// region(r);
|
||||
|
@ -93,6 +174,8 @@ module region(r)
|
|||
|
||||
|
||||
|
||||
// Section: Gometrical calculations with region
|
||||
|
||||
// Function: point_in_region()
|
||||
// Usage:
|
||||
// check = point_in_region(point, region, [eps]);
|
||||
|
@ -128,23 +211,6 @@ function region_area(region) =
|
|||
-sum([for(R=parts, poly=R) polygon_area(poly,signed=true)]);
|
||||
|
||||
|
||||
// Function: is_region_simple()
|
||||
// Usage:
|
||||
// bool = is_region_simple(region, [eps]);
|
||||
// Description:
|
||||
// Returns true if the region is entirely non-self-intersecting, meaning that it is
|
||||
// formed from a list of simple polygons that do not intersect each other.
|
||||
// Arguments:
|
||||
// region = region to check
|
||||
// eps = tolerance for geometric comparisons. Default: `EPSILON` = 1e-9
|
||||
function is_region_simple(region, eps=EPSILON) =
|
||||
let(region=force_region(region))
|
||||
assert(is_region(region), "Input is not a region")
|
||||
[for(p=region) if (!is_path_simple(p,closed=true,eps)) 1] == []
|
||||
&&
|
||||
[for(i=[0:1:len(region)-2])
|
||||
if (_region_region_intersections([region[i]], list_tail(region,i+1), eps=eps)[0][0] != []) 1
|
||||
] ==[];
|
||||
|
||||
function _clockwise_region(r) = [for(p=r) clockwise_polygon(p)];
|
||||
|
||||
|
@ -182,7 +248,7 @@ function __are_regions_equal(region1, region2, i) =
|
|||
/// Returns a pair of sorted lists such that risect[0] is a list of intersection
|
||||
/// points for every path in region1, and similarly risect[1] is a list of intersection
|
||||
/// points for the paths in region2. For each path the intersection list is
|
||||
/// a sorted list of the form [SEGMENT, U]. You can specify that the paths in either
|
||||
/// a sorted list of the form [PATHIND, SEGMENT, U]. You can specify that the paths in either
|
||||
/// region be regarded as open paths if desired. Default is to treat them as
|
||||
/// regions and hence the paths as closed polygons.
|
||||
/// .
|
||||
|
@ -252,6 +318,9 @@ function _region_region_intersections(region1, region2, closed1=true,closed2=tru
|
|||
[for(i=[0:1]) [for(j=counts[i]) _sort_vectors(select(risect[i],pathind[i][j]))]];
|
||||
|
||||
|
||||
// Section: Breaking up regions into subregions
|
||||
|
||||
|
||||
// Function: split_region_at_region_crossings()
|
||||
// Usage:
|
||||
// split_region = split_region_at_region_crossings(region1, region2, [closed1], [closed2], [eps])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) =
|
||||
|
@ -136,7 +182,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));
|
||||
|
|
|
@ -351,6 +351,12 @@ module test_pair() {
|
|||
assert(pair("ABCD",true) == [["A","B"], ["B","C"], ["C","D"], ["D","A"]]);
|
||||
assert(pair([3,4,5,6],wrap=true) == [[3,4], [4,5], [5,6], [6,3]]);
|
||||
assert(pair("ABCD",wrap=true) == [["A","B"], ["B","C"], ["C","D"], ["D","A"]]);
|
||||
assert_equal(pair([],wrap=true),[]);
|
||||
assert_equal(pair([],wrap=false),[]);
|
||||
assert_equal(pair([1],wrap=true),[]);
|
||||
assert_equal(pair([1],wrap=false),[]);
|
||||
assert_equal(pair([1,2],wrap=false),[[1,2]]);
|
||||
assert_equal(pair([1,2],wrap=true),[[1,2],[2,1]]);
|
||||
}
|
||||
test_pair();
|
||||
|
||||
|
@ -358,10 +364,17 @@ 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"]]);
|
||||
assert_equal(triplet([],wrap=true),[]);
|
||||
assert_equal(triplet([],wrap=false),[]);
|
||||
assert_equal(triplet([1],wrap=true),[]);
|
||||
assert_equal(triplet([1],wrap=false),[]);
|
||||
assert_equal(triplet([1,2],wrap=true),[]);
|
||||
assert_equal(triplet([1,2],wrap=false),[]);
|
||||
assert_equal(triplet([1,2,3],wrap=true),[[3,1,2],[1,2,3],[2,3,1]]);
|
||||
assert_equal(triplet([1,2,3],wrap=false),[[1,2,3]]);
|
||||
}
|
||||
test_triplet();
|
||||
|
||||
|
@ -401,15 +414,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([]) == []);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -427,6 +427,7 @@ function _turtle3d_state_valid(state) =
|
|||
&& is_num(state[3])
|
||||
&& is_num(state[4]);
|
||||
|
||||
module turtle3d(commands, state=RIGHT, transforms=false, full_state=false, repeat=1) {no_module();}
|
||||
function turtle3d(commands, state=RIGHT, transforms=false, full_state=false, repeat=1) =
|
||||
assert(is_bool(transforms))
|
||||
let(
|
||||
|
|
4
vnf.scad
4
vnf.scad
|
@ -737,7 +737,7 @@ function _split_2dpolygons_at_each_x(polys, xs, _i=0) =
|
|||
], xs, _i=_i+1
|
||||
);
|
||||
|
||||
/// Function: _slice_3dpolygons()
|
||||
/// Internal Function: _slice_3dpolygons()
|
||||
/// Usage:
|
||||
/// splitpolys = _slice_3dpolygons(polys, dir, cuts);
|
||||
/// Topics: Geometry, Polygons, Intersections
|
||||
|
@ -877,7 +877,7 @@ function vnf_area(vnf) =
|
|||
sum([for(face=vnf[1]) polygon_area(select(verts,face))]);
|
||||
|
||||
|
||||
/// Function: _vnf_centroid()
|
||||
/// Internal Function: _vnf_centroid()
|
||||
/// Usage:
|
||||
/// vol = _vnf_centroid(vnf);
|
||||
/// Description:
|
||||
|
|
Loading…
Reference in a new issue