diff --git a/src/geo/base.js b/src/geo/base.js index 2f5aeaeb..957b56da 100644 --- a/src/geo/base.js +++ b/src/geo/base.js @@ -479,7 +479,8 @@ // default # of decimal places in generated gcode gcode_decimals : 4, // heal disjoint polygons in slicing (experimental) - bridgeLineGapDistance : 0.5, + bridgeLineGapDistance : 0.05, + bridgeLineGapDistanceMax : 2, // Bounds default margin nearTo // Polygon.offset mindist2 offset precision precision_offset : 0.05, diff --git a/src/geo/polygon.js b/src/geo/polygon.js index dbfe2d9d..e9185511 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -1411,6 +1411,18 @@ return this.clean(true, undefined, Math.min(CONF.clipper / 10, CONF.clipperClean * 5)); }; + // does not work with nested polys + PRO.simplify = function(parent) { + if (this.length === 0 || this.open) return [ this ]; + let clib = self.ClipperLib, + clip = clib.Clipper, + ctyp = clib.ClipType, + p2cl = this.toClipper(), + clean = clip.SimplifyPolygon(p2cl[0], ctyp.pftPositive), + z = this.getZ(); + return clean.map(array => fromClipperPath(array, z)); + }; + /** * simplify and merge collinear. only works for single * non-nested polygons. used primarily in slicer/connectLines. diff --git a/src/kiri-mode/fdm/slice.js b/src/kiri-mode/fdm/slice.js index 587b4fa8..023d8df4 100644 --- a/src/kiri-mode/fdm/slice.js +++ b/src/kiri-mode/fdm/slice.js @@ -726,15 +726,13 @@ slice.lines.forEach((line, i) => { const group = i % dash; const color = [ 0xff0000, 0x00aa00, 0x0000ff, 0xaaaa00, 0xff00ff ][group]; - slice.output().setLayer(`xl-${group}`, color).addLine(line.p1, line.p2); + slice.output().setLayer(`l${group}`, color).addLine(line.p1, line.p2); }); } if (slice.groups) POLY.nest(slice.groups).forEach((poly, i) => { slice.addTop(poly); - const group = i % 5; - const color = [ 0xff0000, 0x00aa00, 0x0000ff, 0xaaaa00, 0xff00ff ][group]; - slice.output().setLayer(`xg-${group}`, color).addPoly(poly); + slice.output().setLayer(`g${i}`, 0x888888).addPoly(poly); }); } diff --git a/src/kiri/slicer.js b/src/kiri/slicer.js index 85aafcac..78164a1f 100644 --- a/src/kiri/slicer.js +++ b/src/kiri/slicer.js @@ -518,7 +518,10 @@ // de-dup and group lines lines = removeDuplicateLines(lines); - let groups = connectLines(lines, z, options.debug); + let groups = connectLines(lines, z, { + debug: options.debug, + union: options.union + }); // simplistic healing of bad meshes if (options.union) { @@ -549,7 +552,9 @@ * @param {number} [index] * @returns {Array} */ - function connectLines(input, z, debug) { + function connectLines(input, z, opt = {}) { + let { debug, union } = opt; + // map points to all other points they're connected to let CONF = BASE.config, pmap = {}, @@ -559,6 +564,7 @@ search = 1, nextMod = 1, bridge = CONF.bridgeLineGapDistance, + bridgeMax = CONF.bridgeLineGapDistanceMax, p1, p2; function cachedPoint(p) { @@ -575,9 +581,10 @@ } function perimeter(array) { - let p = BASE.newPolygon().addPoints(array); - // console.log(p); - return p.perimeter(); + if (!array.perimeter) { + array.perimeter = BASE.newPolygon().addPoints(array).perimeter(); + } + return array.perimeter; } /** @@ -617,30 +624,36 @@ // allow point re-use in other path searches for (let p of rpath) p.del = false; } + // flatten and sort in ascending perimeter let flat = branches.map(b => b.flat()).sort((a,b) => { return perimeter(b) - perimeter(a); - return b.length - a.length; }); let npath = flat[0]; for (let p of npath) p.del = true; // if (debug) console.log({root, branches, flat, path, npath}); - return root ? npath : path; + return emitPath(root ? npath : path); } else { - path.open = point.group.length === 1; + // dangling end + path.open = point.group.length <= 1; // choose next (unused) point point = links[0]; } // hit an open end or branch if (!point || point.del) { - // console.log({open: path.open, path: path.slice()}); - return path; + return emitPath(path); } } } // emit a polygon if it can be cleaned and still have 2 or more points - function emit(poly) { + function emit(poly, recurse = true) { + // if (recurse && union) { + // for (let simple of poly.simplify()) { + // emit(simple, false); + // } + // return; + // } poly = poly.clean(); if (poly.length > 2) output.push(poly); if (debug) console.log('xray',poly.xray()); @@ -650,12 +663,14 @@ // eliminating points from the paths as they are emitted // shorter paths any point eliminated are eliminated as candidates. function emitPath(path) { - if (path.open) { - // if (debug) console.log('emit OPEN', path.length); - connect.push(path); - } else { - // if (debug) console.log('emit CLOSED', path.length); + let closed = path[0].group.indexOf(path.peek()) >= 0; + if (closed && path.length > 2) { + if (debug) console.log(`CLOSED [${path.length}]`); emit(BASE.newPolygon().addPoints(path), debug); + } else if (path.length > 1) { + let gap = path[0].distTo2D(path.peek()).round(4); + if (debug) console.log(`OPEN [${path.length}] ${gap}`); + connect.push(path); } } @@ -669,12 +684,11 @@ // console.log({points, forks: points.filter(p => p.group.length !== 2)}); - // first trace paths starting at undeleted points with connections + // for each unused point, find the longest non-intersecting path for (let point of points) { // must not have been used and be a dangling end if (!point.del && point.group.length) { - let path = findNextPath(point); - if (path) emitPath(path); + findNextPath(point); } } @@ -684,65 +698,118 @@ }); // return true if points are deemed "close enough" close a polygon - function close(p1,p2) { - return p1.distToSq2D(p2) <= 0.01; + function close(p1,p2,dist) { + return p1.distToSq2D(p2) <= dist || 0.01; } if (debug && connect.length) console.log({connect}); - // reconnect dangling/open polygons to closest endpoint - for (let i=0; i 0 && bridge && bridge < bridgeMax && mingap < bridgeMax); + + for (let array of connect) { + if (array.delete) continue; + + let first = array[0]; + let last = array.peek(); + let dist = first.distToSq2D(last); + if (dist <= bridge) { + // tail meets head (close) + emit(BASE.newPolygon().addPoints(array)); + } else { + // tail meets head (far) + // console.log({dist}); + emit(BASE.newPolygon().addPoints(array) + .setOpen() + ); + } } return output;