complete refactor of thin wall detection

This commit is contained in:
Stewart Allen 2020-11-01 21:11:26 -05:00
commit de46926d4c
8 changed files with 197 additions and 165 deletions

View file

@ -25,8 +25,8 @@
fillArea : fillArea,
subtract : subtract,
flatten : flatten,
offset : offset,
trimTo : trimTo,
expand2 : expand2,
expand : expand,
expand_lines: expand_lines,
union : union,
@ -46,6 +46,10 @@
* Polygon array utility functions
******************************************************************* */
function numOrDefault(num, def) {
return num !== undefined ? num : def;
}
function toClipper(polys,debug) {
let out = [];
polys.forEach(function(poly) { poly.toClipper(out,debug) });
@ -62,9 +66,9 @@
};
function fromClipperTree(tnode, z, tops, parent, minarea) {
let polys = tops || [],
poly,
min = minarea || 0.1;
let poly,
polys = tops || [],
min = numOrDefault(minarea, 0.1);
tnode.m_Childs.forEach(function(child) {
poly = fromClipperNode(child, z);
@ -473,75 +477,79 @@
}
/**
* by "over" expanding then contracting, this causes shells that are too
* close together to merge and cancel out. it's a more expensive operation
* but prevents shells that are too close together to extrude properly.
*
* @param {Polygon[]} polys
* @param {number} dist1 first offset distance
* @param {number} dist2 2nd thru last offset distance
* @param {Polygon[]} out optional collector
* @param {number} count offset passes (0 == until no space left)
* @param {Function} collector receives output of each pass
* @param {Function} thins receives output of each pass
* @param {number} [z] defaults to 0
* @returns {Polygon[]} last offset
* offset an array of polygons by distance with options to recurse
* and return resulting gaps from offsets for thin wall detection in
* in FDM mode and uncleared areas in CAM mode.
*/
function expand2(polys, dist1, dist2, out, count, collector, thins, z) {
// prepare alignments for clipper lib
function offset(polys, dist, opt) {
// cause inner / outer polys to be reversed from each other
alignWindings(polys);
polys.forEach(function(poly) {
if (poly.inner) setWinding(poly.inner, !poly.isClockwise());
if (poly.inner) {
setWinding(poly.inner, !poly.isClockwise());
}
});
let fact = CONF.clipper,
clib = self.ClipperLib,
clip = clib.Clipper,
cpft = clib.PolyFillType,
cjnt = clib.JoinType,
cety = clib.EndType,
coff = new clib.ClipperOffset(),
ctre = new clib.PolyTree(),
orig = polys,
over = dist1 * 0.45;
let orig = polys,
opts = opt || {},
depth = numOrDefault(opt.depth, 0),
clean = opts.clean !== false,
simple = opts.simple !== false,
fill = opts.fill || ClipperLib.PolyFillType.pftNonZero,
join = opts.join || ClipperLib.JoinType.jtMiter,
type = opts.type || ClipperLib.EndType.etClosedPolygon,
coff = new ClipperLib.ClipperOffset(),
ctre = new ClipperLib.PolyTree(),
// if dist is array with values, shift out next offset
offs = Array.isArray(dist) ? (dist.length > 1 ? dist.shift() : dist[0]) : dist,
mina = numOrDefault(opts.minArea, 0.1),
zed = opt.z || 0;
// inset
// setup offset
polys.forEach(function(poly) {
let clean = clip.CleanPolygons(poly.toClipper(), CONF.clipperClean);
let simple = clip.SimplifyPolygons(clean, cpft.pftNonZero);
coff.AddPaths(simple, cjnt.jtMiter, cety.etClosedPolygon);
// convert to clipper format
poly = poly.toClipper();
if (clean) poly = ClipperLib.Clipper.CleanPolygons(poly, CONF.clipperClean);
if (simple) poly = ClipperLib.Clipper.SimplifyPolygons(poly, fill);
coff.AddPaths(poly, join, type);
});
// perform offset
coff.Execute(ctre, offs * CONF.clipper);
// convert back from clipper output format
polys = fromClipperTree(ctre, zed, null, null, mina);
coff.Execute(ctre, (dist1 + over) * fact);
polys = fromClipperTree(ctre, z);
// if specified, perform offset gap analysis
if (opt.gaps && polys.length) {
let negoff = offset(polys, -offs, {
fill: opt.fill, join: opt.join, type: opt.type, z: opt.z, minArea: mina
});
let suba = [];
let diff = subtract(orig, negoff, suba, null, zed);
opt.gaps.push(suba);
}
// outset
coff = new clib.ClipperOffset();
ctre = new clib.PolyTree();
polys.forEach(function(poly) {
let clean = clip.CleanPolygons(poly.toClipper(), CONF.clipperClean);
let simple = clip.SimplifyPolygons(clean, cpft.pftNonZero);
coff.AddPaths(simple, cjnt.jtMiter, cety.etClosedPolygon);
});
coff.Execute(ctre, -over * fact);
polys = fromClipperTree(ctre, z);
if (opt.gaps && !polys.length) {
opt.gaps.push(orig);
}
// detect possible thin walls
if (thins) {
let circ1 = sumCirc(orig),
circ2 = sumCirc(polys),
diff = Math.abs(1 - (circ1 / circ2));
// if circularity of original and offset differ, possible wall gaps
if (diff > 0.2) {
thins(orig, out.length ? polys : null, diff, -over);
// if specified, perform up to *count* successive offsets
if (polys.length) {
// ensure opts has offset accumulator array
opt.outs = opt.outs || [];
// store polys in accumulator
opt.outs.push(polys);
if (opt.count > 1) {
// decrement count, increment depth
opt.count--;
opt.depth = depth + 1;
// call next offset
offset(polys, dist, opt);
}
}
// process
if (out) out.appendAll(polys);
if (collector) collector(polys, count);
if ((count === 0 || count > 1) && polys.length > 0) {
expand2(polys, dist2 || dist1, dist2, out, count > 0 ? count-1 : 0, collector, thins, z);
// if the polys fail to offset at the top level, consider them gaps
if (opt.gaps && polys.length === 0 && depth === 0) {
opt.gaps.push(orig);
}
return polys;