Update paths.scad

This commit is contained in:
Richard Milewski 2025-12-14 15:24:27 -08:00
parent 5a06450320
commit 270cc2fe62

View file

@ -15,6 +15,10 @@
// FileFootnotes: STD=Included in std.scad
//////////////////////////////////////////////////////////////////////
_BOSL2_PATHS = is_undef(_BOSL2_STD) && (is_undef(BOSL2_NO_STD_WARNING) || !BOSL2_NO_STD_WARNING) ?
echo("Warning: paths.scad included without std.scad; dependencies may be missing\nSet BOSL2_NO_STD_WARNING = true to mute this warning.") true : true;
// Section: Utility Functions
// Definitions:
// Point|Points = A list of numbers, also called a vector. Usually has length 2 or 3 to represent points in the place on points in space.
@ -146,8 +150,8 @@ function _path_select(path, s1, u1, s2, u2, closed=false) =
// Arguments:
// 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) =
// eps = Largest positional variance allowed. Default: `_EPSILON` (1-e9)
function path_merge_collinear(path, closed, eps=_EPSILON) =
is_1region(path) ? path_merge_collinear(path[0], default(closed,true), eps) :
let(closed=default(closed,false))
assert(is_bool(closed))
@ -258,7 +262,7 @@ function path_length_fractions(path, closed) =
/// Arguments:
/// path = The path to find self intersections of.
/// closed = If true, treat path like a closed polygon. Default: true
/// eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9)
/// eps = The epsilon error value to determine whether two points coincide. Default: `_EPSILON` (1e-9)
/// Example(2D):
/// path = [
/// [-100,100], [0,-50], [100,100], [100,-100], [0,50], [-100,-100]
@ -267,7 +271,7 @@ function path_length_fractions(path, closed) =
/// // isects == [[[-33.3333, 0], 0, 0.666667, 4, 0.333333], [[33.3333, 0], 1, 0.333333, 3, 0.666667]]
/// stroke(path, closed=true, width=1);
/// for (isect=isects) translate(isect[0]) color("blue") sphere(d=10);
function _path_self_intersections(path, closed=true, eps=EPSILON) =
function _path_self_intersections(path, closed=true, eps=_EPSILON) =
let(
path = closed ? list_wrap(path,eps=eps) : path,
plen = len(path)
@ -687,8 +691,8 @@ function _err_resample(path, maxerr, n, i1=0, i2=2, resultidx=[0], iter=0) =
// Arguments:
// 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) =
// eps = Epsilon error value used for determine if points coincide. Default: `_EPSILON` (1e-9)
function is_path_simple(path, closed, eps=_EPSILON) =
is_1region(path) ? is_path_simple(path[0], default(closed,true), eps) :
let(closed=default(closed,false))
assert(is_path(path, 2),"\nMust give a 2D path.")
@ -820,7 +824,7 @@ function path_normals(path, tangents, closed) =
)
dim == 2 ? [tangents[i].y,-tangents[i].x]
: let( v=cross(cross(pts[1]-pts[0], pts[2]-pts[0]),tangents[i]))
assert(norm(v)>EPSILON, "\n3D path contains collinear points.")
assert(norm(v)>_EPSILON, "\n3D path contains collinear points.")
unit(v)
];
@ -939,8 +943,8 @@ function path_cut(path,cutdist,closed) =
let(closed=default(closed,false))
assert(is_bool(closed))
assert(is_vector(cutdist))
assert(last(cutdist)<path_length(path,closed=closed)-EPSILON,"\nCut distances must be smaller than the path length.")
assert(cutdist[0]>EPSILON, "\nCut distances must be strictly positive.")
assert(last(cutdist)<path_length(path,closed=closed)-_EPSILON,"\nCut distances must be smaller than the path length.")
assert(cutdist[0]>_EPSILON, "\nCut distances must be strictly positive.")
let(
cutlist = path_cut_points(path,cutdist,closed=closed)
)
@ -1122,12 +1126,12 @@ function _cut_to_seg_u_form(pathcut, path, closed) =
// Arguments:
// 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)
// eps = Acceptable variance. Default: `_EPSILON` (1e-9)
// Example(2D,NoAxes):
// path = [ [-100,100], [0,-50], [100,100], [100,-100], [0,50], [-100,-100] ];
// paths = split_path_at_self_crossings(path);
// 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), "\nMust give a 2D path.")
assert(is_bool(closed))
@ -1160,7 +1164,7 @@ function split_path_at_self_crossings(path, closed=true, eps=EPSILON) =
];
function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=_EPSILON) =
let(
subpaths = split_path_at_self_crossings(
path, closed=true, eps=eps
@ -1193,7 +1197,7 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
// Arguments:
// 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)
// 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).
// poly = [
// [-100,100], [0,-50], [100,100],
@ -1242,7 +1246,7 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
// 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) =
function polygon_parts(poly, nonzero=false, eps=_EPSILON) =
let(poly = force_path(poly))
assert(is_path(poly,2), "\nMust give 2D polygon.")
assert(is_bool(nonzero))
@ -1254,7 +1258,7 @@ function polygon_parts(poly, nonzero=false, eps=EPSILON) =
) outregion;
function _extreme_angle_fragment(seg, fragments, rightmost=true, eps=EPSILON) =
function _extreme_angle_fragment(seg, fragments, rightmost=true, eps=_EPSILON) =
!fragments? [undef, []] :
let(
delta = seg[1] - seg[0],
@ -1298,8 +1302,8 @@ function _extreme_angle_fragment(seg, fragments, rightmost=true, eps=EPSILON) =
/// fragments = List of paths to be assembled into complete polygons.
/// rightmost = If true, assemble paths using rightmost turns. Leftmost if false.
/// startfrag = The fragment to start with. Default: 0
/// eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9)
function _assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0, eps=EPSILON) =
/// eps = The epsilon error value to determine whether two points coincide. Default: `_EPSILON` (1e-9)
function _assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0, eps=_EPSILON) =
len(fragments)==0? [[],[]] :
len(fragments)==1? [fragments[0],[]] :
let(
@ -1353,8 +1357,8 @@ function _assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0,
/// Polygons with area < eps are discarded and not returned.
/// Arguments:
/// fragments = List of paths to be assembled into complete polygons.
/// eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9)
function _assemble_path_fragments(fragments, eps=EPSILON, _finished=[]) =
/// eps = The epsilon error value to determine whether two points coincide. Default: `_EPSILON` (1e-9)
function _assemble_path_fragments(fragments, eps=_EPSILON, _finished=[]) =
len(fragments)==0? _finished :
let(
minxidx = min_index([