mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-01-16 13:50:23 +01:00
same_shape bugfix (fails if b==undef)
check for collinear points in round_corners plus other fixes fix path_cut to work correctly when closed==true, and change it to fail with error when cut is too long instead of returning undef. Add path_cut_segs.
This commit is contained in:
parent
863c410404
commit
f736ef98f7
@ -244,7 +244,7 @@ function _list_pattern(list) =
|
||||
// Example:
|
||||
// same_shape([3,[4,5]],[7,[3,4]]); // Returns true
|
||||
// same_shape([3,4,5], [7,[3,4]]); // Returns false
|
||||
function same_shape(a,b) = _list_pattern(a) == b*0;
|
||||
function same_shape(a,b) = is_def(b) && _list_pattern(a) == b*0;
|
||||
|
||||
|
||||
// Function: is_bool_list()
|
||||
|
96
paths.scad
96
paths.scad
@ -1225,11 +1225,17 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals
|
||||
// Cuts a path at a list of distances from the first point in the path. Returns a list of the cut
|
||||
// points and indices of the next point in the path after that point. So for example, a return
|
||||
// value entry of [[2,3], 5] means that the cut point was [2,3] and the next point on the path after
|
||||
// this point is path[5]. If the path is too short then path_cut returns undef. If you set
|
||||
// this point is path[5]. If the path is too short then path_cut fails with an error. If you set
|
||||
// `direction` to true then `path_cut` will also return the tangent vector to the path and a normal
|
||||
// vector to the path. It tries to find a normal vector that is coplanar to the path near the cut
|
||||
// point. If this fails it will return a normal vector parallel to the xy plane. The output with
|
||||
// direction vectors will be `[point, next_index, tangent, normal]`.
|
||||
// .
|
||||
// If you give the very last point of the path as a cut point then the returned index will be
|
||||
// one larger than the last index (so it will not be a valid index). If you use the closed
|
||||
// option then the returned index will be equal to the path length for cuts along the closing
|
||||
// path segment, and if you give a point equal to the path length you will get an
|
||||
// index of len(path)+1 for the index.
|
||||
//
|
||||
// Arguments:
|
||||
// path = path to cut
|
||||
@ -1246,8 +1252,10 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals
|
||||
function path_cut(path, dists, closed=false, direction=false) =
|
||||
let(long_enough = len(path) >= (closed ? 3 : 2))
|
||||
assert(long_enough,len(path)<2 ? "Two points needed to define a path" : "Closed path must include three points")
|
||||
!is_list(dists)? path_cut(path, [dists],closed, direction)[0]
|
||||
: let(cuts = _path_cut(path,dists,closed))
|
||||
is_num(dists) ? path_cut(path, [dists],closed, direction)[0] :
|
||||
assert(is_vector(dists))
|
||||
assert(list_increasing(dists), "Cut distances must be an increasing list")
|
||||
let(cuts = _path_cut(path,dists,closed))
|
||||
!direction
|
||||
? cuts
|
||||
: let(
|
||||
@ -1260,20 +1268,23 @@ function path_cut(path, dists, closed=false, direction=false) =
|
||||
function _path_cut(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[]) =
|
||||
dind == len(dists) ? result :
|
||||
let(
|
||||
lastpt = len(result)>0? select(result,-1)[0] : [],
|
||||
dpartial = len(result)==0? 0 : norm(lastpt-path[pind]),
|
||||
nextpoint = dpartial > dists[dind]-dtotal?
|
||||
[lerp(lastpt,path[pind], (dists[dind]-dtotal)/dpartial),pind] :
|
||||
_path_cut_single(path, dists[dind]-dtotal-dpartial, closed, pind)
|
||||
) is_undef(nextpoint)?
|
||||
concat(result, repeat(undef,len(dists)-dind)) :
|
||||
_path_cut(path, dists, closed, nextpoint[1], dists[dind],dind+1, concat(result, [nextpoint]));
|
||||
lastpt = len(result)==0? [] : select(result,-1)[0], // location of last cut point
|
||||
dpartial = len(result)==0? 0 : norm(lastpt-select(path,pind)), // remaining length in segment
|
||||
nextpoint = dists[dind] <= dpartial+dtotal // Do we have enough length left on the current segment?
|
||||
? [lerp(lastpt,select(path,pind),(dists[dind]-dtotal)/dpartial),pind]
|
||||
: _path_cut_single(path, dists[dind]-dtotal-dpartial, closed, pind)
|
||||
)
|
||||
_path_cut(path, dists, closed, nextpoint[1], dists[dind],dind+1, concat(result, [nextpoint]));
|
||||
|
||||
|
||||
// Search for a single cut point in the path
|
||||
function _path_cut_single(path, dist, closed=false, ind=0, eps=1e-7) =
|
||||
ind>=len(path)? undef :
|
||||
ind==len(path)-1 && !closed? (dist<eps? [path[ind],ind+1] : undef) :
|
||||
let(d = norm(path[ind]-select(path,ind+1))) d > dist ?
|
||||
// If we get to the very end of the path (ind is last point or wraparound for closed case) then
|
||||
// check if we are within epsilon of the final path point. If not we're out of path, so we fail
|
||||
ind==len(path)-(closed?0:1) ?
|
||||
assert(dist<eps,"Path is too short for specified cut distance")
|
||||
[select(path,ind),ind+1]
|
||||
:let(d = norm(path[ind]-select(path,ind+1))) d > dist ?
|
||||
[lerp(path[ind],select(path,ind+1),dist/d), ind+1] :
|
||||
_path_cut_single(path, dist-d,closed, ind+1, eps);
|
||||
|
||||
@ -1307,18 +1318,61 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) =
|
||||
zeros = path[0]*0,
|
||||
nextind = cuts[ind][1],
|
||||
nextpath = unit(select(path, nextind+1)-select(path, nextind),zeros),
|
||||
thispath = unit(select(path, nextind) - path[nextind-1],zeros),
|
||||
lastpath = unit(path[nextind-1] - select(path, nextind-2),zeros),
|
||||
thispath = unit(select(path, nextind) - select(path,nextind-1),zeros),
|
||||
lastpath = unit(select(path,nextind-1) - select(path, nextind-2),zeros),
|
||||
nextdir =
|
||||
nextind==len(path) && !closed? lastpath :
|
||||
(nextind<=len(path)-2 || closed) && approx(cuts[ind][0], path[nextind],eps)?
|
||||
unit(nextpath+thispath) :
|
||||
(nextind>1 || closed) && approx(cuts[ind][0],path[nextind-1],eps)?
|
||||
unit(thispath+lastpath) :
|
||||
thispath
|
||||
(nextind<=len(path)-2 || closed) && approx(cuts[ind][0], path[nextind],eps)
|
||||
? unit(nextpath+thispath)
|
||||
: (nextind>1 || closed) && approx(cuts[ind][0],select(path,nextind-1),eps)
|
||||
? unit(thispath+lastpath)
|
||||
: thispath
|
||||
) nextdir
|
||||
];
|
||||
|
||||
|
||||
// Function: path_cut_segs()
|
||||
// Usage:
|
||||
// path_list = path_cut_segs(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. If you repeat a distance you will get an
|
||||
// empty list in that position in the output.
|
||||
// Arguments:
|
||||
// path = path to cut
|
||||
// cutdist = distance or list of distances where path is cut
|
||||
// closed = set to true for a closed path. Default: false
|
||||
function path_cut_segs(path,cutdist,closed) =
|
||||
is_num(cutdist) ? path_cut_segs(path,[cutdist],closed) :
|
||||
assert(is_vector(cutdist))
|
||||
assert(select(cutdist,-1)<path_length(path,closed=closed),"Cut distances must be smaller than the path length")
|
||||
assert(cutdist[0]>0, "Cut distances must be strictly positive")
|
||||
let(
|
||||
cutlist = path_cut(path,cutdist,closed=closed),
|
||||
cuts = len(cutlist)
|
||||
)
|
||||
[
|
||||
[ each slice(path,0,cutlist[0][1]),
|
||||
if (!approx(cutlist[0][0], path[cutlist[0][1]-1])) cutlist[0][0]
|
||||
],
|
||||
for(i=[0:1:cuts-2])
|
||||
cutlist[i][0]==cutlist[i+1][0] ? []
|
||||
:
|
||||
[ if (!approx(cutlist[i][0], select(path,cutlist[i][1]))) cutlist[i][0],
|
||||
each slice(path,cutlist[i][1], cutlist[i+1][1]),
|
||||
if (!approx(cutlist[i+1][0], select(path,cutlist[i+1][1]-1))) cutlist[i+1][0],
|
||||
],
|
||||
[
|
||||
if (!approx(cutlist[cuts-1][0], select(path,cutlist[cuts-1][1]))) cutlist[cuts-1][0],
|
||||
each select(path,cutlist[cuts-1][1],closed ? 0 : -1)
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
|
||||
// Input `data` is a list that sums to an integer.
|
||||
// Returns rounded version of input data so that every
|
||||
// entry is rounded to an integer and the sum is the same as
|
||||
|
@ -60,7 +60,8 @@ include <structs.scad>
|
||||
// or you can specify a list that has length len(path)-2, omitting the two dummy values.
|
||||
// .
|
||||
// If your input path includes collinear points you must use a cut or radius value of zero for those "corners". You can
|
||||
// choose a nonzero joint parameter, which will cause extra points to be inserted.
|
||||
// choose a nonzero joint parameter when the collinear points form a 180 degree angle. This will cause extra points to be inserted.
|
||||
// If the collinear points form a spike (0 degree angle) then round_corners will fail.
|
||||
// .
|
||||
// Examples:
|
||||
// * `method="circle", radius=2`:
|
||||
@ -75,7 +76,8 @@ include <structs.scad>
|
||||
// ignored. Note that $fn is interpreted as the number of points on the roundover curve, which is
|
||||
// not equivalent to its meaning for rounding circles because roundovers are usually small fractions
|
||||
// of a circular arc. When doing continuous curvature rounding be sure to use lots of segments or the effect
|
||||
// will be hidden by the discretization.
|
||||
// will be hidden by the discretization. Note that if you use $fn then $fn with "smooth" then $fn points are added at each corner, even
|
||||
// if the "corner" is flat, with collinear points, so this guarantees a specific output length.
|
||||
//
|
||||
// Figure(2D,Med):
|
||||
// h = 18;
|
||||
@ -260,10 +262,16 @@ function round_corners(path, method="circle", radius, cut, joint, k, closed=true
|
||||
dk = [
|
||||
for(i=[0:1:len(path)-1])
|
||||
let(
|
||||
angle = vector_angle(select(path,i-1,i+1))/2
|
||||
pathbit = select(path,i-1,i+1),
|
||||
angle = approx(pathbit[0],pathbit[1]) || approx(pathbit[1],pathbit[2]) ? undef
|
||||
: vector_angle(select(path,i-1,i+1))/2,
|
||||
f=echo(angle=angle)
|
||||
)
|
||||
(!closed && (i==0 || i==len(path)-1)) ? [0] : // Force zeros at ends for non-closed
|
||||
parm[i]==0 ? [0] : // If no rounding requested then don't try to compute parameters
|
||||
assert(is_def(angle), str("Repeated point in path at index ",i," with nonzero rounding"))
|
||||
assert(!approx(angle,0), closed && i==0 ? "Closing the path causes it to turn back on itself at the end" :
|
||||
str("Path turns back on itself at index ",i," with nonzero rounding"))
|
||||
(method=="chamfer" && measure=="joint")? [parm[i]] :
|
||||
(method=="chamfer" && measure=="cut") ? [parm[i]/cos(angle)] :
|
||||
(method=="smooth" && measure=="joint") ? [parm[i],k[i]] :
|
||||
@ -277,10 +285,11 @@ function round_corners(path, method="circle", radius, cut, joint, k, closed=true
|
||||
lengths = [for(i=[0:1:len(path)]) norm(select(path,i)-select(path,i-1))],
|
||||
scalefactors = [
|
||||
for(i=[0:1:len(path)-1])
|
||||
min(
|
||||
if (closed || (i!=0 && i!=len(path)-1))
|
||||
min(
|
||||
lengths[i]/(select(dk,i-1)[0]+dk[i][0]),
|
||||
lengths[i+1]/(dk[i][0]+select(dk,i+1)[0])
|
||||
)
|
||||
)
|
||||
],
|
||||
dummy = verbose ? echo("Roundover scale factors:",scalefactors) : 0
|
||||
)
|
||||
@ -639,12 +648,12 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
|
||||
d_next = is_vector(joint[i]) ? joint[i][1] : joint[i]
|
||||
)
|
||||
assert(d_first>=0 && d_next>=0, str("Joint value negative when adding path ",i+1))
|
||||
assert(d_first<path_length(revresult),str("Path ",i," is too short for specified cut distance ",d_first))
|
||||
assert(d_next<path_length(nextpath), str("Path ",i+1," is too short for specified cut distance ",d_next))
|
||||
let(
|
||||
firstcut = path_cut(revresult, d_first, direction=true),
|
||||
nextcut = path_cut(nextpath, d_next, direction=true)
|
||||
)
|
||||
assert(is_def(firstcut),str("Path ",i," is too short for specified cut distance ",d_first))
|
||||
assert(is_def(nextcut),str("Path ",i+1," is too short for specified cut distance ",d_next))
|
||||
assert(!loop || nextcut[1] < len(revresult)-1-firstcut[1], "Path is too short to close the loop")
|
||||
let(
|
||||
first_dir=firstcut[2],
|
||||
|
@ -245,6 +245,12 @@ test_is_consistent();
|
||||
module test_same_shape() {
|
||||
assert(same_shape([3,[4,5]],[7,[3,4]]));
|
||||
assert(!same_shape([3,4,5], [7,[3,4]]));
|
||||
assert(!same_shape([3,4,5],undef));
|
||||
assert(!same_shape([5,3],3));
|
||||
assert(!same_shape(undef,[3,4]));
|
||||
assert(same_shape(4,5));
|
||||
assert(!same_shape(5,undef));
|
||||
|
||||
}
|
||||
test_same_shape();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user