mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
Make most path functions accept singleton regions
Replace check_and_fix_path with force_path
This commit is contained in:
parent
9cf991bb29
commit
052200433b
6 changed files with 150 additions and 74 deletions
|
@ -1860,6 +1860,7 @@ function ccw_polygon(poly) =
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// poly = The list of the path points for the perimeter of the polygon.
|
// poly = The list of the path points for the perimeter of the polygon.
|
||||||
function reverse_polygon(poly) =
|
function reverse_polygon(poly) =
|
||||||
|
let(poly=force_path(poly,"poly"))
|
||||||
assert(is_path(poly), "Input should be a polygon")
|
assert(is_path(poly), "Input should be a polygon")
|
||||||
[ poly[0], for(i=[len(poly)-1:-1:1]) poly[i] ];
|
[ poly[0], for(i=[len(poly)-1:-1:1]) poly[i] ];
|
||||||
|
|
||||||
|
@ -1878,6 +1879,7 @@ function reverse_polygon(poly) =
|
||||||
// Example:
|
// Example:
|
||||||
// polygon_shift([[3,4], [8,2], [0,2], [-4,0]], 2); // Returns [[0,2], [-4,0], [3,4], [8,2]]
|
// polygon_shift([[3,4], [8,2], [0,2], [-4,0]], 2); // Returns [[0,2], [-4,0], [3,4], [8,2]]
|
||||||
function polygon_shift(poly, i) =
|
function polygon_shift(poly, i) =
|
||||||
|
let(poly=force_path(poly,"poly"))
|
||||||
assert(is_path(poly), "Invalid polygon." )
|
assert(is_path(poly), "Invalid polygon." )
|
||||||
list_rotate(cleanup_path(poly), i);
|
list_rotate(cleanup_path(poly), i);
|
||||||
|
|
||||||
|
@ -1895,7 +1897,7 @@ function polygon_shift(poly, i) =
|
||||||
// makes the total sum over all pairs as small as possible. Returns the reindexed polygon. Note
|
// makes the total sum over all pairs as small as possible. Returns the reindexed polygon. Note
|
||||||
// that the geometry of the polygon is not changed by this operation, just the labeling of its
|
// that the geometry of the polygon is not changed by this operation, just the labeling of its
|
||||||
// vertices. If the input polygon is 2d and is oriented opposite the reference then its point order is
|
// vertices. If the input polygon is 2d and is oriented opposite the reference then its point order is
|
||||||
// flipped.
|
// reversed.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// reference = reference polygon path
|
// reference = reference polygon path
|
||||||
// poly = input polygon to reindex
|
// poly = input polygon to reindex
|
||||||
|
@ -1913,7 +1915,9 @@ function polygon_shift(poly, i) =
|
||||||
// move_copies(concat(circ,pent)) circle(r=.1,$fn=32);
|
// move_copies(concat(circ,pent)) circle(r=.1,$fn=32);
|
||||||
// color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
|
// color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
|
||||||
// color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
|
// color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
|
||||||
function reindex_polygon(reference, poly, return_error=false) =
|
function reindex_polygon(reference, poly, return_error=false) =
|
||||||
|
let(reference=force_path(reference,"reference"),
|
||||||
|
poly=force_path(poly,"poly"))
|
||||||
assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
|
assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
|
||||||
"Invalid polygon(s) or incompatible dimensions. " )
|
"Invalid polygon(s) or incompatible dimensions. " )
|
||||||
assert(len(reference)==len(poly), "The polygons must have the same length.")
|
assert(len(reference)==len(poly), "The polygons must have the same length.")
|
||||||
|
@ -1971,6 +1975,8 @@ function polygon_shift(poly, i) =
|
||||||
// stroke(ellipse, width=.5, closed=true);
|
// stroke(ellipse, width=.5, closed=true);
|
||||||
// color("blue")stroke(aligned,width=.5,closed=true);
|
// color("blue")stroke(aligned,width=.5,closed=true);
|
||||||
function align_polygon(reference, poly, angles, cp, trans, return_ind=false) =
|
function align_polygon(reference, poly, angles, cp, trans, return_ind=false) =
|
||||||
|
let(reference=force_path(reference,"reference"),
|
||||||
|
poly=force_path(poly,"poly"))
|
||||||
assert(is_undef(trans) || (is_undef(angles) && is_undef(cp)), "Cannot give both angles/cp and trans as input")
|
assert(is_undef(trans) || (is_undef(angles) && is_undef(cp)), "Cannot give both angles/cp and trans as input")
|
||||||
let(
|
let(
|
||||||
trans = is_def(trans) ? trans :
|
trans = is_def(trans) ? trans :
|
||||||
|
@ -1978,8 +1984,8 @@ function align_polygon(reference, poly, angles, cp, trans, return_ind=false) =
|
||||||
"The `angle` parameter must be a range or a non void list of numbers.")
|
"The `angle` parameter must be a range or a non void list of numbers.")
|
||||||
[for(angle=angles) zrot(angle,cp=cp)]
|
[for(angle=angles) zrot(angle,cp=cp)]
|
||||||
)
|
)
|
||||||
assert(is_path(reference,dim=2) && is_path(poly,dim=2),
|
assert(is_path(reference,dim=2), "reference must be a 2D polygon")
|
||||||
"Invalid polygon(s). " )
|
assert(is_path(poly,dim=2), "poly must be a 2D polygon")
|
||||||
assert(len(reference)==len(poly), "The polygons must have the same length.")
|
assert(len(reference)==len(poly), "The polygons must have the same length.")
|
||||||
let( // alignments is a vector of entries of the form: [polygon, error]
|
let( // alignments is a vector of entries of the form: [polygon, error]
|
||||||
alignments = [
|
alignments = [
|
||||||
|
|
115
paths.scad
115
paths.scad
|
@ -45,6 +45,39 @@ function is_path(list, dim=[2,3], fast=false) =
|
||||||
&& len(list[0])>0
|
&& len(list[0])>0
|
||||||
&& (is_undef(dim) || in_list(len(list[0]), force_list(dim)));
|
&& (is_undef(dim) || in_list(len(list[0]), force_list(dim)));
|
||||||
|
|
||||||
|
// Function: is_path_region()
|
||||||
|
// Usage:
|
||||||
|
// bool = is_path_region(path, [name])
|
||||||
|
// Description:
|
||||||
|
// If `path` is a region with one component 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.
|
||||||
|
// Arguments:
|
||||||
|
// path = input to process
|
||||||
|
// name = name of parameter to use in error message. Default: "path"
|
||||||
|
function is_path_region(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
|
||||||
|
// 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.
|
||||||
|
// Arguments:
|
||||||
|
// path = input to process
|
||||||
|
// name = name of parameter to use in error message. Default: "path"
|
||||||
|
function force_path(path, name="path") =
|
||||||
|
is_region(path) ?
|
||||||
|
assert(len(path)==1, str("Parameter \"",name,"\" must be a path or singleton region, but is a multicomponent region"))
|
||||||
|
path[0]
|
||||||
|
: path;
|
||||||
|
|
||||||
|
|
||||||
// Function: is_closed_path()
|
// Function: is_closed_path()
|
||||||
// Usage:
|
// Usage:
|
||||||
|
@ -102,6 +135,7 @@ function _path_select(path, s1, u1, s2, u2, closed=false) =
|
||||||
) pathout;
|
) pathout;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Function: path_merge_collinear()
|
// Function: path_merge_collinear()
|
||||||
// Description:
|
// Description:
|
||||||
// Takes a path and removes unnecessary sequential collinear points.
|
// Takes a path and removes unnecessary sequential collinear points.
|
||||||
|
@ -111,8 +145,11 @@ function _path_select(path, s1, u1, s2, u2, closed=false) =
|
||||||
// path = A list of path points of any dimension.
|
// path = A list of path points of any dimension.
|
||||||
// closed = treat as closed polygon. Default: false
|
// closed = treat as closed polygon. Default: false
|
||||||
// eps = Largest positional variance allowed. Default: `EPSILON` (1-e9)
|
// eps = Largest positional variance allowed. Default: `EPSILON` (1-e9)
|
||||||
function path_merge_collinear(path, closed=false, eps=EPSILON) =
|
function path_merge_collinear(path, closed, eps=EPSILON) =
|
||||||
assert( is_path(path), "Invalid path." )
|
is_path_region(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." )
|
||||||
assert( is_undef(eps) || (is_finite(eps) && (eps>=0) ), "Invalid tolerance." )
|
assert( is_undef(eps) || (is_finite(eps) && (eps>=0) ), "Invalid tolerance." )
|
||||||
len(path)<=2 ? path :
|
len(path)<=2 ? path :
|
||||||
let(
|
let(
|
||||||
|
@ -140,7 +177,11 @@ function path_merge_collinear(path, closed=false, eps=EPSILON) =
|
||||||
// Example:
|
// Example:
|
||||||
// path = [[0,0], [5,35], [60,-25], [80,0]];
|
// path = [[0,0], [5,35], [60,-25], [80,0]];
|
||||||
// echo(path_length(path));
|
// echo(path_length(path));
|
||||||
function path_length(path,closed=false) =
|
function path_length(path,closed) =
|
||||||
|
is_path_region(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))
|
||||||
len(path)<2? 0 :
|
len(path)<2? 0 :
|
||||||
sum([for (i = [0:1:len(path)-2]) norm(path[i+1]-path[i])])+(closed?norm(path[len(path)-1]-path[0]):0);
|
sum([for (i = [0:1:len(path)-2]) norm(path[i+1]-path[i])])+(closed?norm(path[len(path)-1]-path[0]):0);
|
||||||
|
|
||||||
|
@ -153,7 +194,11 @@ function path_length(path,closed=false) =
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// path = path to measure
|
// path = path to measure
|
||||||
// closed = true if the path is closed. Default: false
|
// closed = true if the path is closed. Default: false
|
||||||
function path_segment_lengths(path, closed=false) =
|
function path_segment_lengths(path, closed) =
|
||||||
|
is_path_region(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))
|
||||||
[
|
[
|
||||||
for (i=[0:1:len(path)-2]) norm(path[i+1]-path[i]),
|
for (i=[0:1:len(path)-2]) norm(path[i+1]-path[i]),
|
||||||
if (closed) norm(path[0]-last(path))
|
if (closed) norm(path[0]-last(path))
|
||||||
|
@ -171,7 +216,9 @@ function path_segment_lengths(path, closed=false) =
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// path = path to operate on
|
// path = path to operate on
|
||||||
// closed = set to true if path is closed. Default: false
|
// closed = set to true if path is closed. Default: false
|
||||||
function path_length_fractions(path, closed=false) =
|
function path_length_fractions(path, closed) =
|
||||||
|
is_path_region(path) ? path_length_fractions(path[0], default(closed,true)):
|
||||||
|
let(closed=default(closed, false))
|
||||||
assert(is_path(path))
|
assert(is_path(path))
|
||||||
assert(is_bool(closed))
|
assert(is_bool(closed))
|
||||||
let(
|
let(
|
||||||
|
@ -327,6 +374,7 @@ function _sum_preserving_round(data, index=0) =
|
||||||
// mypath = subdivide_path([[0,0,0],[2,0,1],[2,3,2]], 12);
|
// mypath = subdivide_path([[0,0,0],[2,0,1],[2,3,2]], 12);
|
||||||
// move_copies(mypath)sphere(r=.1,$fn=32);
|
// move_copies(mypath)sphere(r=.1,$fn=32);
|
||||||
function subdivide_path(path, N, refine, closed=true, exact=true, method="length") =
|
function subdivide_path(path, N, refine, closed=true, exact=true, method="length") =
|
||||||
|
let(path = force_path(path))
|
||||||
assert(is_path(path))
|
assert(is_path(path))
|
||||||
assert(method=="length" || method=="segment")
|
assert(method=="length" || method=="segment")
|
||||||
assert(num_defined([N,refine]),"Must give exactly one of N and refine")
|
assert(num_defined([N,refine]),"Must give exactly one of N and refine")
|
||||||
|
@ -385,7 +433,8 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length
|
||||||
// stroke(path,width=2,closed=true);
|
// stroke(path,width=2,closed=true);
|
||||||
// color("red") move_copies(path) circle(d=9,$fn=12);
|
// color("red") move_copies(path) circle(d=9,$fn=12);
|
||||||
// color("blue") move_copies(spath) circle(d=5,$fn=12);
|
// color("blue") move_copies(spath) circle(d=5,$fn=12);
|
||||||
function subdivide_long_segments(path, maxlen, closed=false) =
|
function subdivide_long_segments(path, maxlen, closed=true) =
|
||||||
|
let(path=force_path(path))
|
||||||
assert(is_path(path))
|
assert(is_path(path))
|
||||||
assert(is_finite(maxlen))
|
assert(is_finite(maxlen))
|
||||||
assert(is_bool(closed))
|
assert(is_bool(closed))
|
||||||
|
@ -413,8 +462,9 @@ function subdivide_long_segments(path, maxlen, closed=false) =
|
||||||
// path = path to resample
|
// path = path to resample
|
||||||
// N = Number of points in output
|
// N = Number of points in output
|
||||||
// spacing = Approximate spacing desired
|
// spacing = Approximate spacing desired
|
||||||
// closed = set to true if path is closed. Default: false
|
// closed = set to true if path is closed. Default: true
|
||||||
function resample_path(path, N, spacing, closed=false) =
|
function resample_path(path, N, spacing, closed=true) =
|
||||||
|
let(path = force_path(path))
|
||||||
assert(is_path(path))
|
assert(is_path(path))
|
||||||
assert(num_defined([N,spacing])==1,"Must define exactly one of N and spacing")
|
assert(num_defined([N,spacing])==1,"Must define exactly one of N and spacing")
|
||||||
assert(is_bool(closed))
|
assert(is_bool(closed))
|
||||||
|
@ -432,9 +482,6 @@ function resample_path(path, N, spacing, closed=false) =
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Section: Path Geometry
|
// Section: Path Geometry
|
||||||
|
|
||||||
// Function: is_path_simple()
|
// Function: is_path_simple()
|
||||||
|
@ -449,8 +496,11 @@ function resample_path(path, N, spacing, closed=false) =
|
||||||
// path = path to check
|
// path = path to check
|
||||||
// closed = set to true to treat path as a polygon. Default: false
|
// 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)
|
// eps = Epsilon error value used for determine if points coincide. Default: `EPSILON` (1e-9)
|
||||||
function is_path_simple(path, closed=false, eps=EPSILON) =
|
function is_path_simple(path, closed, eps=EPSILON) =
|
||||||
|
is_path_region(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_path(path, 2),"Must give a 2D path")
|
||||||
|
assert(is_bool(closed))
|
||||||
[for(i=[0:1:len(path)-(closed?2:3)])
|
[for(i=[0:1:len(path)-(closed?2:3)])
|
||||||
let(v1=path[i+1]-path[i],
|
let(v1=path[i+1]-path[i],
|
||||||
v2=select(path,i+2)-path[i+1],
|
v2=select(path,i+2)-path[i+1],
|
||||||
|
@ -471,6 +521,7 @@ function is_path_simple(path, closed=false, eps=EPSILON) =
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// path = The path to find the closest point on.
|
// path = The path to find the closest point on.
|
||||||
// pt = the point to find the closest point to.
|
// pt = the point to find the closest point to.
|
||||||
|
// closed =
|
||||||
// Example(2D):
|
// Example(2D):
|
||||||
// path = circle(d=100,$fn=6);
|
// path = circle(d=100,$fn=6);
|
||||||
// pt = [20,10];
|
// pt = [20,10];
|
||||||
|
@ -478,9 +529,13 @@ function is_path_simple(path, closed=false, eps=EPSILON) =
|
||||||
// stroke(path, closed=true);
|
// stroke(path, closed=true);
|
||||||
// color("blue") translate(pt) circle(d=3, $fn=12);
|
// color("blue") translate(pt) circle(d=3, $fn=12);
|
||||||
// color("red") translate(closest[1]) circle(d=3, $fn=12);
|
// color("red") translate(closest[1]) circle(d=3, $fn=12);
|
||||||
function path_closest_point(path, pt) =
|
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_vector(pt, len(path[0])), "Input pt must be a compatible vector")
|
||||||
|
assert(is_bool(closed))
|
||||||
let(
|
let(
|
||||||
pts = [for (seg=idx(path)) line_closest_point(select(path,seg,seg+1),pt,SEGMENT)],
|
pts = [for (seg=[0:1:len(path)-(closed?1:2)]) line_closest_point(select(path,seg,seg+1),pt,SEGMENT)],
|
||||||
dists = [for (p=pts) norm(p-pt)],
|
dists = [for (p=pts) norm(p-pt)],
|
||||||
min_seg = min_index(dists)
|
min_seg = min_index(dists)
|
||||||
) [min_seg, pts[min_seg]];
|
) [min_seg, pts[min_seg]];
|
||||||
|
@ -513,7 +568,10 @@ function path_closest_point(path, pt) =
|
||||||
// color("purple")
|
// color("purple")
|
||||||
// for(i=[0:len(tangents)-1])
|
// for(i=[0:len(tangents)-1])
|
||||||
// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.25, endcap2="arrow2");
|
// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.25, endcap2="arrow2");
|
||||||
function path_tangents(path, closed=false, uniform=true) =
|
function path_tangents(path, closed, uniform=true) =
|
||||||
|
is_path_region(path) ? path_tangents(path[0], default(closed,true), uniform) :
|
||||||
|
let(closed=default(closed,false))
|
||||||
|
assert(is_bool(closed))
|
||||||
assert(is_path(path))
|
assert(is_path(path))
|
||||||
!uniform ? [for(t=deriv(path,closed=closed, h=path_segment_lengths(path,closed))) unit(t)]
|
!uniform ? [for(t=deriv(path,closed=closed, h=path_segment_lengths(path,closed))) unit(t)]
|
||||||
: [for(t=deriv(path,closed=closed)) unit(t)];
|
: [for(t=deriv(path,closed=closed)) unit(t)];
|
||||||
|
@ -533,7 +591,13 @@ function path_tangents(path, closed=false, uniform=true) =
|
||||||
// normal is not uniquely defined. In this case the function issues an error.
|
// normal is not uniquely defined. In this case the function issues an error.
|
||||||
// For 2d paths the plane is always defined so the normal fails to exist only
|
// 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).
|
// when the derivative is zero (in the case of repeated points).
|
||||||
function path_normals(path, tangents, closed=false) =
|
// Arguments:
|
||||||
|
// path = path to compute the normals to
|
||||||
|
// 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)) :
|
||||||
|
let(closed=default(closed,false))
|
||||||
assert(is_path(path,[2,3]))
|
assert(is_path(path,[2,3]))
|
||||||
assert(is_bool(closed))
|
assert(is_bool(closed))
|
||||||
let(
|
let(
|
||||||
|
@ -560,7 +624,11 @@ function path_normals(path, tangents, closed=false) =
|
||||||
// curvs = path_curvature(path, [closed]);
|
// curvs = path_curvature(path, [closed]);
|
||||||
// Description:
|
// Description:
|
||||||
// Numerically estimate the curvature of the path (in any dimension).
|
// Numerically estimate the curvature of the path (in any dimension).
|
||||||
function path_curvature(path, closed=false) =
|
function path_curvature(path, closed) =
|
||||||
|
is_path_region(path) ? path_curvature(path[0], default(closed,true)) :
|
||||||
|
let(closed=default(closed,false))
|
||||||
|
assert(is_bool(closed))
|
||||||
|
assert(is_path(path))
|
||||||
let(
|
let(
|
||||||
d1 = deriv(path, closed=closed),
|
d1 = deriv(path, closed=closed),
|
||||||
d2 = deriv2(path, closed=closed)
|
d2 = deriv2(path, closed=closed)
|
||||||
|
@ -579,6 +647,8 @@ function path_curvature(path, closed=false) =
|
||||||
// Description:
|
// Description:
|
||||||
// Numerically estimate the torsion of a 3d path.
|
// Numerically estimate the torsion of a 3d path.
|
||||||
function path_torsion(path, closed=false) =
|
function path_torsion(path, closed=false) =
|
||||||
|
assert(is_path(path,3), "Input path must be a 3d path")
|
||||||
|
assert(is_bool(closed))
|
||||||
let(
|
let(
|
||||||
d1 = deriv(path,closed=closed),
|
d1 = deriv(path,closed=closed),
|
||||||
d2 = deriv2(path,closed=closed),
|
d2 = deriv2(path,closed=closed),
|
||||||
|
@ -883,13 +953,16 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) =
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// path = The original path to split.
|
// path = The original path to split.
|
||||||
// cutdist = Distance or list of distances where path is cut
|
// cutdist = Distance or list of distances where path is cut
|
||||||
// closed = If true, treat the path as a closed polygon.
|
// closed = If true, treat the path as a closed polygon. Default: false
|
||||||
// Example(2D,NoAxes):
|
// Example(2D,NoAxes):
|
||||||
// path = circle(d=100);
|
// path = circle(d=100);
|
||||||
// segs = path_cut(path, [50, 200], closed=true);
|
// segs = path_cut(path, [50, 200], closed=true);
|
||||||
// rainbow(segs) stroke($item, endcaps="butt", width=3);
|
// rainbow(segs) stroke($item, endcaps="butt", width=3);
|
||||||
function path_cut(path,cutdist,closed) =
|
function path_cut(path,cutdist,closed) =
|
||||||
is_num(cutdist) ? 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)):
|
||||||
|
let(closed=default(closed,false))
|
||||||
|
assert(is_bool(closed))
|
||||||
assert(is_vector(cutdist))
|
assert(is_vector(cutdist))
|
||||||
assert(last(cutdist)<path_length(path,closed=closed),"Cut distances must be smaller than the path length")
|
assert(last(cutdist)<path_length(path,closed=closed),"Cut distances must be smaller than the path length")
|
||||||
assert(cutdist[0]>0, "Cut distances must be strictly positive")
|
assert(cutdist[0]>0, "Cut distances must be strictly positive")
|
||||||
|
@ -955,6 +1028,9 @@ function _cut_to_seg_u_form(pathcut, path, closed) =
|
||||||
// paths = split_path_at_self_crossings(path);
|
// paths = split_path_at_self_crossings(path);
|
||||||
// rainbow(paths) stroke($item, closed=false, width=3);
|
// rainbow(paths) stroke($item, closed=false, width=3);
|
||||||
function split_path_at_self_crossings(path, closed=true, eps=EPSILON) =
|
function split_path_at_self_crossings(path, closed=true, eps=EPSILON) =
|
||||||
|
let(path = force_path(path))
|
||||||
|
assert(is_path(path,2), "Must give a 2D path")
|
||||||
|
assert(is_bool(closed))
|
||||||
let(
|
let(
|
||||||
path = cleanup_path(path, eps=eps),
|
path = cleanup_path(path, eps=eps),
|
||||||
isects = deduplicate(
|
isects = deduplicate(
|
||||||
|
@ -1063,6 +1139,9 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
|
||||||
// right(27)rainbow(polygon_parts(path)) polygon($item);
|
// right(27)rainbow(polygon_parts(path)) polygon($item);
|
||||||
// move([16,-14])rainbow(polygon_parts(path,nonzero=true)) polygon($item);
|
// move([16,-14])rainbow(polygon_parts(path,nonzero=true)) polygon($item);
|
||||||
function polygon_parts(path, nonzero=false, eps=EPSILON) =
|
function polygon_parts(path, nonzero=false, eps=EPSILON) =
|
||||||
|
let(path = force_path(path))
|
||||||
|
assert(is_path(path,2), "Must give 2D path")
|
||||||
|
assert(is_bool(nonzero))
|
||||||
let(
|
let(
|
||||||
path = cleanup_path(path, eps=eps),
|
path = cleanup_path(path, eps=eps),
|
||||||
tagged = _tag_self_crossing_subpaths(path, nonzero=nonzero, closed=true, eps=eps),
|
tagged = _tag_self_crossing_subpaths(path, nonzero=nonzero, closed=true, eps=eps),
|
||||||
|
|
50
regions.scad
50
regions.scad
|
@ -43,42 +43,6 @@ function is_region(x) = is_list(x) && is_path(x.x);
|
||||||
function force_region(path) = is_path(path) ? [path] : path;
|
function force_region(path) = is_path(path) ? [path] : path;
|
||||||
|
|
||||||
|
|
||||||
// Function: check_and_fix_path()
|
|
||||||
// Usage:
|
|
||||||
// check_and_fix_path(path, [valid_dim], [closed], [name])
|
|
||||||
// Description:
|
|
||||||
// Checks that the input is a path. If it is a region with one component, converts it to a path.
|
|
||||||
// Note that arbitrary paths must have at least two points, but closed paths need at least 3 points.
|
|
||||||
// valid_dim specfies the allowed dimension of the points in the path.
|
|
||||||
// If the path is closed, removes duplicate endpoint if present.
|
|
||||||
// Arguments:
|
|
||||||
// path = path to process
|
|
||||||
// valid_dim = list of allowed dimensions for the points in the path, e.g. [2,3] to require 2 or 3 dimensional input. If left undefined do not perform this check. Default: undef
|
|
||||||
// closed = set to true if the path is closed, which enables a check for endpoint duplication
|
|
||||||
// name = parameter name to use for reporting errors. Default: "path"
|
|
||||||
function check_and_fix_path(path, valid_dim=undef, closed=false, name="path") =
|
|
||||||
let(
|
|
||||||
path =
|
|
||||||
is_region(path)?
|
|
||||||
assert(len(path)==1,str("Region ",name," supplied as path does not have exactly one component"))
|
|
||||||
path[0]
|
|
||||||
:
|
|
||||||
assert(is_path(path), str("Input ",name," is not a path"))
|
|
||||||
path
|
|
||||||
)
|
|
||||||
assert(len(path)>(closed?2:1),closed?str("Closed path ",name," must have at least 3 points")
|
|
||||||
:str("Path ",name," must have at least 2 points"))
|
|
||||||
let(valid=is_undef(valid_dim) || in_list(len(path[0]),force_list(valid_dim)))
|
|
||||||
assert(
|
|
||||||
valid, str(
|
|
||||||
"Input ",name," must has dimension ", len(path[0])," but dimension must be ",
|
|
||||||
is_list(valid_dim) ? str("one of ",valid_dim) : valid_dim
|
|
||||||
)
|
|
||||||
)
|
|
||||||
closed && approx(path[0], last(path))? list_head(path) : path;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Function: sanitize_region()
|
// Function: sanitize_region()
|
||||||
// Usage:
|
// Usage:
|
||||||
// r_fixed = sanitize_region(r, [nonzero], [eps]);
|
// r_fixed = sanitize_region(r, [nonzero], [eps]);
|
||||||
|
@ -150,6 +114,20 @@ function point_in_region(point, region, eps=EPSILON, _i=0, _cnt=0) =
|
||||||
: point_in_region(point, region, eps=eps, _i=_i+1, _cnt = _cnt + (pip>0? 1 : 0));
|
: point_in_region(point, region, eps=eps, _i=_i+1, _cnt = _cnt + (pip>0? 1 : 0));
|
||||||
|
|
||||||
|
|
||||||
|
// Function: region_area()
|
||||||
|
// Usage:
|
||||||
|
// area=region_area(region);
|
||||||
|
// Description:
|
||||||
|
// Computes the area of the specified valid region. (If the region is invalid and has self intersections
|
||||||
|
// the result is meaningless.)
|
||||||
|
function region_area(region) =
|
||||||
|
assert(is_region(region), "Input must be a region")
|
||||||
|
let(
|
||||||
|
parts = region_parts(region)
|
||||||
|
)
|
||||||
|
-sum([for(R=parts, poly=R) polygon_area(poly,signed=true)]);
|
||||||
|
|
||||||
|
|
||||||
// Function: is_region_simple()
|
// Function: is_region_simple()
|
||||||
// Usage:
|
// Usage:
|
||||||
// bool = is_region_simple(region, [eps]);
|
// bool = is_region_simple(region, [eps]);
|
||||||
|
|
|
@ -227,9 +227,7 @@ function round_corners(path, method="circle", radius, cut, joint, k, closed=true
|
||||||
let(
|
let(
|
||||||
default_k = 0.5,
|
default_k = 0.5,
|
||||||
size=one_defined([radius, cut, joint], "radius,cut,joint"),
|
size=one_defined([radius, cut, joint], "radius,cut,joint"),
|
||||||
path = is_region(path)?
|
path = force_path(path),
|
||||||
assert(len(path)==1, "Region supplied as path does not have exactly one component")
|
|
||||||
path[0] : path,
|
|
||||||
size_ok = is_num(size) || len(size)==len(path) || (!closed && len(size)==len(path)-2),
|
size_ok = is_num(size) || len(size)==len(path) || (!closed && len(size)==len(path)-2),
|
||||||
k_ok = is_undef(k) || (method=="smooth" && (is_num(k) || len(k)==len(path) || (!closed && len(k)==len(path)-2))),
|
k_ok = is_undef(k) || (method=="smooth" && (is_num(k) || len(k)==len(path) || (!closed && len(k)==len(path)-2))),
|
||||||
measure = is_def(radius) ? "radius" :
|
measure = is_def(radius) ? "radius" :
|
||||||
|
@ -611,6 +609,7 @@ module path_join(paths,joint=0,k=0.5,relocate=true,closed=false) { no_module();}
|
||||||
function path_join(paths,joint=0,k=0.5,relocate=true,closed=false)=
|
function path_join(paths,joint=0,k=0.5,relocate=true,closed=false)=
|
||||||
assert(is_list(paths),"Input paths must be a list of paths")
|
assert(is_list(paths),"Input paths must be a list of paths")
|
||||||
let(
|
let(
|
||||||
|
paths = [for(i=idx(paths)) force_path(paths[i],str("paths[",i,"]"))],
|
||||||
badpath = [for(j=idx(paths)) if (!is_path(paths[j])) j]
|
badpath = [for(j=idx(paths)) if (!is_path(paths[j])) j]
|
||||||
)
|
)
|
||||||
assert(badpath==[], str("Entries in paths are not valid paths: ",badpath))
|
assert(badpath==[], str("Entries in paths are not valid paths: ",badpath))
|
||||||
|
@ -963,7 +962,10 @@ function offset_sweep(
|
||||||
["k", k],
|
["k", k],
|
||||||
["points", []],
|
["points", []],
|
||||||
],
|
],
|
||||||
path = check_and_fix_path(path, [2], closed=true),
|
path = force_path(path)
|
||||||
|
)
|
||||||
|
assert(is_path(path,2), "Input path must be a 2D path")
|
||||||
|
let(
|
||||||
clockwise = is_polygon_clockwise(path),
|
clockwise = is_polygon_clockwise(path),
|
||||||
dummy1 = _struct_valid(top,"offset_sweep","top"),
|
dummy1 = _struct_valid(top,"offset_sweep","top"),
|
||||||
dummy2 = _struct_valid(bottom,"offset_sweep","bottom"),
|
dummy2 = _struct_valid(bottom,"offset_sweep","bottom"),
|
||||||
|
@ -1456,6 +1458,7 @@ function _remove_undefined_vals(list) =
|
||||||
// right(12)
|
// right(12)
|
||||||
// offset_stroke(path, width=1, closed=true);
|
// offset_stroke(path, width=1, closed=true);
|
||||||
function offset_stroke(path, width=1, rounded=true, start="flat", end="flat", check_valid=true, quality=1, chamfer=false, closed=false) =
|
function offset_stroke(path, width=1, rounded=true, start="flat", end="flat", check_valid=true, quality=1, chamfer=false, closed=false) =
|
||||||
|
let(path = force_path(path))
|
||||||
assert(is_path(path,2),"path is not a 2d path")
|
assert(is_path(path,2),"path is not a 2d path")
|
||||||
let(closedok = !closed || (is_undef(start) && is_undef(end)))
|
let(closedok = !closed || (is_undef(start) && is_undef(end)))
|
||||||
assert(closedok, "Parameters `start` and `end` not allowed with closed path")
|
assert(closedok, "Parameters `start` and `end` not allowed with closed path")
|
||||||
|
@ -1832,7 +1835,11 @@ module rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_bot
|
||||||
|
|
||||||
function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_bot, k_top, k_sides, k=0.5, splinesteps=16,
|
function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_bot, k_top, k_sides, k=0.5, splinesteps=16,
|
||||||
h, length, l, height, debug=false) =
|
h, length, l, height, debug=false) =
|
||||||
assert(is_path(bottom) && len(bottom)>=3)
|
let(
|
||||||
|
bottom = force_path(bottom,"bottom"),
|
||||||
|
top = force_path(top,"top")
|
||||||
|
)
|
||||||
|
assert(is_path(bottom,[2,3]) && len(bottom)>=3, "bottom must be a 2D or 3D path")
|
||||||
assert(is_num(k) && k>=0 && k<=1, "Curvature parameter k must be in interval [0,1]")
|
assert(is_num(k) && k>=0 && k<=1, "Curvature parameter k must be in interval [0,1]")
|
||||||
let(
|
let(
|
||||||
N=len(bottom),
|
N=len(bottom),
|
||||||
|
@ -2155,21 +2162,22 @@ module bent_cutout_mask(r, thickness, path, radius, convexity=10)
|
||||||
{
|
{
|
||||||
no_children($children);
|
no_children($children);
|
||||||
r = get_radius(r1=r, r2=radius);
|
r = get_radius(r1=r, r2=radius);
|
||||||
dummy=assert(is_def(r) && r>0,"Radius of the cylinder to bend around must be positive");
|
dummy1=assert(is_def(r) && r>0,"Radius of the cylinder to bend around must be positive");
|
||||||
assert(is_path(path,2),"Input path must be a 2d path");
|
path2 = force_path(path);
|
||||||
|
dummy2=assert(is_path(path2,2),"Input path must be a 2D path");
|
||||||
assert(r-thickness>0, "Thickness too large for radius");
|
assert(r-thickness>0, "Thickness too large for radius");
|
||||||
assert(thickness>0, "Thickness must be positive");
|
assert(thickness>0, "Thickness must be positive");
|
||||||
path = clockwise_polygon(path);
|
fixpath = clockwise_polygon(path2);
|
||||||
curvepoints = arc(d=thickness, angle = [-180,0]);
|
curvepoints = arc(d=thickness, angle = [-180,0]);
|
||||||
profiles = [for(pt=curvepoints) _cyl_hole(r+pt.x,apply(xscale((r+pt.x)/r), offset(path,delta=thickness/2+pt.y,check_valid=false,closed=true)))];
|
profiles = [for(pt=curvepoints) _cyl_hole(r+pt.x,apply(xscale((r+pt.x)/r), offset(fixpath,delta=thickness/2+pt.y,check_valid=false,closed=true)))];
|
||||||
pathx = column(path,0);
|
pathx = column(fixpath,0);
|
||||||
minangle = (min(pathx)-thickness/2)*360/(2*PI*r);
|
minangle = (min(pathx)-thickness/2)*360/(2*PI*r);
|
||||||
maxangle = (max(pathx)+thickness/2)*360/(2*PI*r);
|
maxangle = (max(pathx)+thickness/2)*360/(2*PI*r);
|
||||||
mindist = (r+thickness/2)/cos((maxangle-minangle)/2);
|
mindist = (r+thickness/2)/cos((maxangle-minangle)/2);
|
||||||
assert(maxangle-minangle<180,"Cutout angle span is too large. Must be smaller than 180.");
|
assert(maxangle-minangle<180,"Cutout angle span is too large. Must be smaller than 180.");
|
||||||
zmean = mean(column(path,1));
|
zmean = mean(column(fixpath,1));
|
||||||
innerzero = repeat([0,0,zmean], len(path));
|
innerzero = repeat([0,0,zmean], len(fixpath));
|
||||||
outerpt = repeat( [1.5*mindist*cos((maxangle+minangle)/2),1.5*mindist*sin((maxangle+minangle)/2),zmean], len(path));
|
outerpt = repeat( [1.5*mindist*cos((maxangle+minangle)/2),1.5*mindist*sin((maxangle+minangle)/2),zmean], len(fixpath));
|
||||||
vnf_polyhedron(vnf_vertex_array([innerzero, each profiles, outerpt],col_wrap=true),convexity=convexity);
|
vnf_polyhedron(vnf_vertex_array([innerzero, each profiles, outerpt],col_wrap=true),convexity=convexity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
skin.scad
10
skin.scad
|
@ -829,8 +829,8 @@ function path_sweep(shape, path, method="incremental", normal, closed=false, twi
|
||||||
assert(!closed || twist % (360/symmetry)==0, str("For a closed sweep, twist must be a multiple of 360/symmetry = ",360/symmetry))
|
assert(!closed || twist % (360/symmetry)==0, str("For a closed sweep, twist must be a multiple of 360/symmetry = ",360/symmetry))
|
||||||
assert(closed || symmetry==1, "symmetry must be 1 when closed is false")
|
assert(closed || symmetry==1, "symmetry must be 1 when closed is false")
|
||||||
assert(is_integer(symmetry) && symmetry>0, "symmetry must be a positive integer")
|
assert(is_integer(symmetry) && symmetry>0, "symmetry must be a positive integer")
|
||||||
// let(shape = check_and_fix_path(shape,valid_dim=2,closed=true,name="shape"))
|
let(path = force_path(path))
|
||||||
assert(is_path(path), "input path is not a path")
|
assert(is_path(path,[2,3]), "input path is not a 2D or 3D path")
|
||||||
assert(!closed || !approx(path[0],last(path)), "Closed path includes start point at the end")
|
assert(!closed || !approx(path[0],last(path)), "Closed path includes start point at the end")
|
||||||
let(
|
let(
|
||||||
path = path3d(path),
|
path = path3d(path),
|
||||||
|
@ -973,8 +973,11 @@ function path_sweep2d(shape, path, closed=false, caps, quality=1, style="min_edg
|
||||||
: closed ? false : true,
|
: closed ? false : true,
|
||||||
capsOK = is_bool(caps) || is_bool_list(caps,2),
|
capsOK = is_bool(caps) || is_bool_list(caps,2),
|
||||||
fullcaps = is_bool(caps) ? [caps,caps] : caps,
|
fullcaps = is_bool(caps) ? [caps,caps] : caps,
|
||||||
shape = check_and_fix_path(shape,valid_dim=2,closed=true,name="shape")
|
shape = force_path(shape,"shape"),
|
||||||
|
path = force_path(path)
|
||||||
)
|
)
|
||||||
|
assert(is_path(shape,2), "shape must be a 2D path")
|
||||||
|
assert(is_path(path,2), "path must be a 2D path")
|
||||||
assert(capsOK, "caps must be boolean or a list of two booleans")
|
assert(capsOK, "caps must be boolean or a list of two booleans")
|
||||||
assert(!closed || !caps, "Cannot make closed shape with caps")
|
assert(!closed || !caps, "Cannot make closed shape with caps")
|
||||||
let(
|
let(
|
||||||
|
@ -1222,6 +1225,7 @@ function _smooth(data,len,closed=false,angle=false) =
|
||||||
)
|
)
|
||||||
result;
|
result;
|
||||||
|
|
||||||
|
|
||||||
// Function: rot_resample()
|
// Function: rot_resample()
|
||||||
// Usage:
|
// Usage:
|
||||||
// rlist = rot_resample(rotlist, N, [method], [twist], [scale], [smoothlen], [long], [turns], [closed])
|
// rlist = rot_resample(rotlist, N, [method], [twist], [scale], [smoothlen], [long], [turns], [closed])
|
||||||
|
|
|
@ -39,6 +39,7 @@ test_cleanup_path();
|
||||||
module test_path_merge_collinear() {
|
module test_path_merge_collinear() {
|
||||||
path = [[-20,-20], [-10,-20], [0,-10], [10,0], [20,10], [20,20], [15,30]];
|
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]]);
|
||||||
}
|
}
|
||||||
test_path_merge_collinear();
|
test_path_merge_collinear();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue