checkpoint
This commit is contained in:
parent
ed4b5dba7a
commit
ab8a86b2a8
4 changed files with 145 additions and 67 deletions
|
|
@ -479,7 +479,8 @@
|
||||||
// default # of decimal places in generated gcode
|
// default # of decimal places in generated gcode
|
||||||
gcode_decimals : 4,
|
gcode_decimals : 4,
|
||||||
// heal disjoint polygons in slicing (experimental)
|
// heal disjoint polygons in slicing (experimental)
|
||||||
bridgeLineGapDistance : 0.5,
|
bridgeLineGapDistance : 0.05,
|
||||||
|
bridgeLineGapDistanceMax : 2,
|
||||||
// Bounds default margin nearTo
|
// Bounds default margin nearTo
|
||||||
// Polygon.offset mindist2 offset precision
|
// Polygon.offset mindist2 offset precision
|
||||||
precision_offset : 0.05,
|
precision_offset : 0.05,
|
||||||
|
|
|
||||||
|
|
@ -1411,6 +1411,18 @@
|
||||||
return this.clean(true, undefined, Math.min(CONF.clipper / 10, CONF.clipperClean * 5));
|
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
|
* simplify and merge collinear. only works for single
|
||||||
* non-nested polygons. used primarily in slicer/connectLines.
|
* non-nested polygons. used primarily in slicer/connectLines.
|
||||||
|
|
|
||||||
|
|
@ -726,15 +726,13 @@
|
||||||
slice.lines.forEach((line, i) => {
|
slice.lines.forEach((line, i) => {
|
||||||
const group = i % dash;
|
const group = i % dash;
|
||||||
const color = [ 0xff0000, 0x00aa00, 0x0000ff, 0xaaaa00, 0xff00ff ][group];
|
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)
|
if (slice.groups)
|
||||||
POLY.nest(slice.groups).forEach((poly, i) => {
|
POLY.nest(slice.groups).forEach((poly, i) => {
|
||||||
slice.addTop(poly);
|
slice.addTop(poly);
|
||||||
const group = i % 5;
|
slice.output().setLayer(`g${i}`, 0x888888).addPoly(poly);
|
||||||
const color = [ 0xff0000, 0x00aa00, 0x0000ff, 0xaaaa00, 0xff00ff ][group];
|
|
||||||
slice.output().setLayer(`xg-${group}`, color).addPoly(poly);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -518,7 +518,10 @@
|
||||||
|
|
||||||
// de-dup and group lines
|
// de-dup and group lines
|
||||||
lines = removeDuplicateLines(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
|
// simplistic healing of bad meshes
|
||||||
if (options.union) {
|
if (options.union) {
|
||||||
|
|
@ -549,7 +552,9 @@
|
||||||
* @param {number} [index]
|
* @param {number} [index]
|
||||||
* @returns {Array}
|
* @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
|
// map points to all other points they're connected to
|
||||||
let CONF = BASE.config,
|
let CONF = BASE.config,
|
||||||
pmap = {},
|
pmap = {},
|
||||||
|
|
@ -559,6 +564,7 @@
|
||||||
search = 1,
|
search = 1,
|
||||||
nextMod = 1,
|
nextMod = 1,
|
||||||
bridge = CONF.bridgeLineGapDistance,
|
bridge = CONF.bridgeLineGapDistance,
|
||||||
|
bridgeMax = CONF.bridgeLineGapDistanceMax,
|
||||||
p1, p2;
|
p1, p2;
|
||||||
|
|
||||||
function cachedPoint(p) {
|
function cachedPoint(p) {
|
||||||
|
|
@ -575,9 +581,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function perimeter(array) {
|
function perimeter(array) {
|
||||||
let p = BASE.newPolygon().addPoints(array);
|
if (!array.perimeter) {
|
||||||
// console.log(p);
|
array.perimeter = BASE.newPolygon().addPoints(array).perimeter();
|
||||||
return p.perimeter();
|
}
|
||||||
|
return array.perimeter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -617,30 +624,36 @@
|
||||||
// allow point re-use in other path searches
|
// allow point re-use in other path searches
|
||||||
for (let p of rpath) p.del = false;
|
for (let p of rpath) p.del = false;
|
||||||
}
|
}
|
||||||
|
// flatten and sort in ascending perimeter
|
||||||
let flat = branches.map(b => b.flat()).sort((a,b) => {
|
let flat = branches.map(b => b.flat()).sort((a,b) => {
|
||||||
return perimeter(b) - perimeter(a);
|
return perimeter(b) - perimeter(a);
|
||||||
return b.length - a.length;
|
|
||||||
});
|
});
|
||||||
let npath = flat[0];
|
let npath = flat[0];
|
||||||
for (let p of npath) p.del = true;
|
for (let p of npath) p.del = true;
|
||||||
// if (debug) console.log({root, branches, flat, path, npath});
|
// if (debug) console.log({root, branches, flat, path, npath});
|
||||||
return root ? npath : path;
|
return emitPath(root ? npath : path);
|
||||||
} else {
|
} else {
|
||||||
path.open = point.group.length === 1;
|
// dangling end
|
||||||
|
path.open = point.group.length <= 1;
|
||||||
// choose next (unused) point
|
// choose next (unused) point
|
||||||
point = links[0];
|
point = links[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// hit an open end or branch
|
// hit an open end or branch
|
||||||
if (!point || point.del) {
|
if (!point || point.del) {
|
||||||
// console.log({open: path.open, path: path.slice()});
|
return emitPath(path);
|
||||||
return path;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// emit a polygon if it can be cleaned and still have 2 or more points
|
// 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();
|
poly = poly.clean();
|
||||||
if (poly.length > 2) output.push(poly);
|
if (poly.length > 2) output.push(poly);
|
||||||
if (debug) console.log('xray',poly.xray());
|
if (debug) console.log('xray',poly.xray());
|
||||||
|
|
@ -650,12 +663,14 @@
|
||||||
// eliminating points from the paths as they are emitted
|
// eliminating points from the paths as they are emitted
|
||||||
// shorter paths any point eliminated are eliminated as candidates.
|
// shorter paths any point eliminated are eliminated as candidates.
|
||||||
function emitPath(path) {
|
function emitPath(path) {
|
||||||
if (path.open) {
|
let closed = path[0].group.indexOf(path.peek()) >= 0;
|
||||||
// if (debug) console.log('emit OPEN', path.length);
|
if (closed && path.length > 2) {
|
||||||
connect.push(path);
|
if (debug) console.log(`CLOSED [${path.length}]`);
|
||||||
} else {
|
|
||||||
// if (debug) console.log('emit CLOSED', path.length);
|
|
||||||
emit(BASE.newPolygon().addPoints(path), debug);
|
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)});
|
// 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) {
|
for (let point of points) {
|
||||||
// must not have been used and be a dangling end
|
// must not have been used and be a dangling end
|
||||||
if (!point.del && point.group.length) {
|
if (!point.del && point.group.length) {
|
||||||
let path = findNextPath(point);
|
findNextPath(point);
|
||||||
if (path) emitPath(path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -684,65 +698,118 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// return true if points are deemed "close enough" close a polygon
|
// return true if points are deemed "close enough" close a polygon
|
||||||
function close(p1,p2) {
|
function close(p1,p2,dist) {
|
||||||
return p1.distToSq2D(p2) <= 0.01;
|
return p1.distToSq2D(p2) <= dist || 0.01;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debug && connect.length) console.log({connect});
|
if (debug && connect.length) console.log({connect});
|
||||||
|
|
||||||
// reconnect dangling/open polygons to closest endpoint
|
// progressively connect open polygons within a bridge distance
|
||||||
for (let i=0; i<connect.length; i++) {
|
let iter = 10;
|
||||||
|
let mingap;
|
||||||
|
do {
|
||||||
|
mingap = Infinity;
|
||||||
|
|
||||||
let array = connect[i],
|
for (let i=0; i<connect.length; i++) {
|
||||||
last = array[array.length-1],
|
if (!bridge) {
|
||||||
tmp, dist, j;
|
emit(BASE.newPolygon().addPoints(root).setOpen());
|
||||||
|
continue;
|
||||||
if (!bridge) {
|
|
||||||
emit(BASE.newPolygon().addPoints(array).setOpen());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (array.delete) continue;
|
|
||||||
|
|
||||||
loop: for (let merged=0;;) {
|
|
||||||
let closest = { dist:Infinity };
|
|
||||||
for (j=i+1; j<connect.length; j++) {
|
|
||||||
tmp = connect[j];
|
|
||||||
if (tmp.delete) continue;
|
|
||||||
dist = last.distToSq2D(tmp[0]);
|
|
||||||
if (dist < closest.dist && dist <= bridge) {
|
|
||||||
closest = {
|
|
||||||
dist: dist,
|
|
||||||
array: tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dist = last.distToSq2D(tmp[tmp.length-1]);
|
|
||||||
if (dist < closest.dist && dist <= bridge) {
|
|
||||||
closest = {
|
|
||||||
dist: dist,
|
|
||||||
array: tmp,
|
|
||||||
reverse: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tmp = closest.array) {
|
// rollup root with arrays after until no more ends match
|
||||||
if (closest.reverse) tmp.reverse();
|
loop: while (true) {
|
||||||
tmp.delete = true;
|
let root = connect[i];
|
||||||
array.appendAll(tmp);
|
|
||||||
last = array[array.length-1];
|
if (root.delete) break;
|
||||||
merged++;
|
|
||||||
// tail meets head (closed)
|
let rfirst = root[0],
|
||||||
if (close(array[0], last)) {
|
rlast = root.peek(),
|
||||||
emit(BASE.newPolygon().addPoints(array));
|
dist = Infinity,
|
||||||
|
closest = { dist };
|
||||||
|
|
||||||
|
for (let j=i+1; j<connect.length; j++) {
|
||||||
|
let next = connect[j];
|
||||||
|
|
||||||
|
if (next.delete) continue;
|
||||||
|
|
||||||
|
let nfirst = next[0];
|
||||||
|
let nlast = next.peek();
|
||||||
|
|
||||||
|
// test last to next first
|
||||||
|
dist = rlast.distToSq2D(nfirst);
|
||||||
|
mingap = Math.min(mingap, dist);
|
||||||
|
if (dist < closest.dist && dist <= bridge) {
|
||||||
|
closest = { dist, next }
|
||||||
|
}
|
||||||
|
|
||||||
|
// test last to next last
|
||||||
|
dist = rlast.distToSq2D(nlast);
|
||||||
|
mingap = Math.min(mingap, dist);
|
||||||
|
if (dist < closest.dist && dist <= bridge) {
|
||||||
|
closest = { dist, next, reverse: next };
|
||||||
|
}
|
||||||
|
|
||||||
|
// test first to next first
|
||||||
|
dist = rfirst.distToSq2D(nfirst);
|
||||||
|
mingap = Math.min(mingap, dist);
|
||||||
|
if (dist < closest.dist && dist <= bridge) {
|
||||||
|
closest = { dist, next, reverse: root };
|
||||||
|
}
|
||||||
|
|
||||||
|
// test last to next last
|
||||||
|
dist = rfirst.distToSq2D(nlast);
|
||||||
|
mingap = Math.min(mingap, dist);
|
||||||
|
if (dist < closest.dist && dist <= bridge) {
|
||||||
|
closest = { dist, next, swap: j };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let { next, reverse, swap } = closest;
|
||||||
|
|
||||||
|
if (next && closest.dist < bridge) {
|
||||||
|
if (reverse) {
|
||||||
|
reverse.reverse();
|
||||||
|
}
|
||||||
|
if (swap) {
|
||||||
|
next.perimeter += root.perimeter;
|
||||||
|
next.appendAll(root);
|
||||||
|
connect[i] = next;
|
||||||
|
connect[swap] = root;
|
||||||
|
root.delete = true;
|
||||||
|
} else {
|
||||||
|
root.perimeter += next.perimeter;
|
||||||
|
root.appendAll(next);
|
||||||
|
next.delete = true;
|
||||||
|
}
|
||||||
|
if (debug) console.log({rollup: root, next, same: root == next});
|
||||||
|
} else {
|
||||||
|
// no more closest polys (open set)
|
||||||
|
// emit(BASE.newPolygon().addPoints(root));
|
||||||
break loop;
|
break loop;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// no more closest polys (open set)
|
|
||||||
emit(BASE.newPolygon().addPoints(array));
|
|
||||||
break loop;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bridge = mingap < Infinity ? Math.max(mingap + 0.01, bridge + 0.1) : bridge + 0.1;
|
||||||
|
|
||||||
|
} while (iter-- > 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;
|
return output;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue