add new outline depth-first strategy. fix depth-first cloning bug
This commit is contained in:
parent
24dc8898f5
commit
44cfa53a25
5 changed files with 102 additions and 145 deletions
|
|
@ -844,6 +844,39 @@
|
||||||
return exit;
|
return exit;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns true if any point on this polygon
|
||||||
|
* is within radius of a point on the target
|
||||||
|
*/
|
||||||
|
PRO.isNear = function(poly, radius, cache) {
|
||||||
|
const midcheck = CONF.precision_midpoint_check_dist;
|
||||||
|
const dist = radius || CONF.precision_close_to_poly_sq;
|
||||||
|
let near = false;
|
||||||
|
let mem = cache ? this.cacheNear = this.cacheNear || {} : undefined;
|
||||||
|
|
||||||
|
if (mem && mem[poly.id] !== undefined) {
|
||||||
|
return mem[poly.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.forEachSegment(function(prev, next) {
|
||||||
|
// check midpoint on long lines
|
||||||
|
if (prev.distToSq2D(next) > midcheck) {
|
||||||
|
if (prev.midPointTo(next).nearPolygon(poly, dist)) {
|
||||||
|
return near = true; // stops iteration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (next.nearPolygon(poly, dist)) {
|
||||||
|
return near = true; // stops iteration
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mem) {
|
||||||
|
mem[poly.id] = near;
|
||||||
|
}
|
||||||
|
|
||||||
|
return near;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO replace isNested() with isInside() ?
|
* TODO replace isNested() with isInside() ?
|
||||||
*
|
*
|
||||||
|
|
@ -858,12 +891,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let mid,
|
let mid,
|
||||||
midcheck,
|
midcheck = CONF.precision_midpoint_check_dist,
|
||||||
exit = true;
|
exit = true;
|
||||||
|
|
||||||
this.forEachSegment(function(prev, next) {
|
this.forEachSegment(function(prev, next) {
|
||||||
// check midpoint on long lines
|
// check midpoint on long lines (TODO: should be distToSq2D()?)
|
||||||
if (prev.distTo2D(next) > CONF.precision_midpoint_check_dist) {
|
if (prev.distTo2D(next) > midcheck) {
|
||||||
mid = prev.midPointTo(next);
|
mid = prev.midPointTo(next);
|
||||||
if (!(mid.inPolygon(poly) || mid.nearPolygon(poly, tolerance || CONF.precision_close_to_poly_sq))) {
|
if (!(mid.inPolygon(poly) || mid.nearPolygon(poly, tolerance || CONF.precision_close_to_poly_sq))) {
|
||||||
exit = false;
|
exit = false;
|
||||||
|
|
@ -1130,9 +1163,13 @@
|
||||||
if (recurse) {
|
if (recurse) {
|
||||||
let i, ai = this.inner, bi = poly.inner;
|
let i, ai = this.inner, bi = poly.inner;
|
||||||
if (ai !== bi) {
|
if (ai !== bi) {
|
||||||
if (ai === null || bi === null || ai.length != bi.length) return false;
|
if (ai === null || bi === null || ai.length != bi.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (i=0; i < ai.length; i++) {
|
for (i=0; i < ai.length; i++) {
|
||||||
if (!ai[i].isEquivalent(bi[i])) return false;
|
if (!ai[i].isEquivalent(bi[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1146,7 +1183,9 @@
|
||||||
pointok = false;
|
pointok = false;
|
||||||
poly.forEachSegment(function(i1p1, i1p2) {
|
poly.forEachSegment(function(i1p1, i1p2) {
|
||||||
// if point is close to poly, terminate search, go to next point
|
// if point is close to poly, terminate search, go to next point
|
||||||
if ((dist = i2p.distToLine(i1p1, i1p2)) < CONF.precision_poly_merge) return pointok = true;
|
if ((dist = i2p.distToLine(i1p1, i1p2)) < CONF.precision_poly_merge) {
|
||||||
|
return pointok = true;
|
||||||
|
}
|
||||||
// otherwise track min and keep searching
|
// otherwise track min and keep searching
|
||||||
min = Math.min(min, dist);
|
min = Math.min(min, dist);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,6 @@
|
||||||
PRO.constReplace = constReplace;
|
PRO.constReplace = constReplace;
|
||||||
PRO.poly2polyEmit = poly2polyEmit;
|
PRO.poly2polyEmit = poly2polyEmit;
|
||||||
PRO.addPrintPoints = addPrintPoints;
|
PRO.addPrintPoints = addPrintPoints;
|
||||||
PRO.poly2polyDepthFirstEmit = poly2polyDepthFirstEmit;
|
|
||||||
|
|
||||||
PRO.parseSVG = function(code, offset) {
|
PRO.parseSVG = function(code, offset) {
|
||||||
let scope = this,
|
let scope = this,
|
||||||
|
|
@ -987,8 +986,10 @@
|
||||||
* to be more like outputOrderClosest() and have the option to account for
|
* to be more like outputOrderClosest() and have the option to account for
|
||||||
* depth in determining distance
|
* depth in determining distance
|
||||||
*/
|
*/
|
||||||
function poly2polyEmit(array, startPoint, emitter, mark, weight) {
|
function poly2polyEmit(array, startPoint, emitter, options) {
|
||||||
let mindist, dist, found, count = 0, marker = mark || 'delete';
|
let opt = options || {};
|
||||||
|
let marker = opt.mark || 'delete';
|
||||||
|
let mindist, dist, found, count = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
found = null;
|
found = null;
|
||||||
mindist = Infinity;
|
mindist = Infinity;
|
||||||
|
|
@ -1012,7 +1013,7 @@
|
||||||
}
|
}
|
||||||
let area = poly.area();
|
let area = poly.area();
|
||||||
poly.forEachPoint(function(point, index) {
|
poly.forEachPoint(function(point, index) {
|
||||||
dist = weight ?
|
dist = opt.weight ?
|
||||||
startPoint.distTo3D(point) * area * area :
|
startPoint.distTo3D(point) * area * area :
|
||||||
startPoint.distTo2D(point);
|
startPoint.distTo2D(point);
|
||||||
if (dist < mindist) {
|
if (dist < mindist) {
|
||||||
|
|
@ -1021,12 +1022,11 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (found) {
|
if (!found || opt.term) {
|
||||||
found.poly[marker] = true;
|
|
||||||
startPoint = emitter(found.poly, found.index, ++count, startPoint) || found.point;
|
|
||||||
} else {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
found.poly[marker] = true;
|
||||||
|
startPoint = emitter(found.poly, found.index, ++count, startPoint) || found.point;
|
||||||
}
|
}
|
||||||
|
|
||||||
// undo delete marks
|
// undo delete marks
|
||||||
|
|
@ -1035,117 +1035,6 @@
|
||||||
return startPoint;
|
return startPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Polygon[][]} array of array of polygons representing each layer (top down)
|
|
||||||
* @param {Point} startPoint entry point for algorithm
|
|
||||||
* @param {Function} emitter called to emit each polygon
|
|
||||||
* @param {number} offset tool diameter used for this depth-first cut
|
|
||||||
*
|
|
||||||
* used for CAM depth first layer output. included here for convenience
|
|
||||||
* because it relies heavily on other functions in the print library.
|
|
||||||
*/
|
|
||||||
function poly2polyDepthFirstEmit(array, startPoint, emitter, offset) {
|
|
||||||
let layers = [],
|
|
||||||
pools;
|
|
||||||
|
|
||||||
array.forEach(function(layerPolys, layerIndex) {
|
|
||||||
pools = [];
|
|
||||||
layers.push(pools);
|
|
||||||
|
|
||||||
// flattening but preserving inner relationships
|
|
||||||
// allows iterating over all layer polys to determine
|
|
||||||
// if they deserve their own pool
|
|
||||||
flattenPolygons(POLY.nest(layerPolys, true, true)).sort(function(p1,p2) {
|
|
||||||
// sort by area descending
|
|
||||||
return p2.area() - p1.area();
|
|
||||||
}).forEach(function (poly) {
|
|
||||||
// a polygon should be made into a pool if any is true:
|
|
||||||
// - it is open
|
|
||||||
// - it has more than one sibling
|
|
||||||
// - it has no parent (top/outer most)
|
|
||||||
// - it is offset from its parent by more than diameter
|
|
||||||
// if (poly.isOpen() || !poly.parent || poly.parent.innerCount() > 1 || !polygonWithinOffset(poly, poly.parent, offset)) {
|
|
||||||
if (poly.depth === 0) {
|
|
||||||
pools.push(poly);
|
|
||||||
poly.pool = [];
|
|
||||||
poly.poolsDown = [];
|
|
||||||
} else {
|
|
||||||
// otherwise search the poly's parents (same layer) to find a pool to join
|
|
||||||
let search = poly.parent;
|
|
||||||
// walk up until pool found
|
|
||||||
while (search && !search.pool) {
|
|
||||||
search = search.parent;
|
|
||||||
}
|
|
||||||
// open polygons can be unparented and without a pool
|
|
||||||
if (!search) {
|
|
||||||
console.log({orphan:poly});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// add to pool
|
|
||||||
search.pool.push(poly);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// sort pools increasing in size to aid fitting from below
|
|
||||||
pools.sort(function (p1, p2) {
|
|
||||||
return p1.area() - p2.area();
|
|
||||||
});
|
|
||||||
|
|
||||||
// add add pools to smallest enclosing pool in layer above
|
|
||||||
const poolsAbove = layers[layerIndex - 1];
|
|
||||||
|
|
||||||
if (layerIndex > 0)
|
|
||||||
pools.forEach(function(pool) {
|
|
||||||
for (let i=0; i<poolsAbove.length; i++) {
|
|
||||||
const above = poolsAbove[i];
|
|
||||||
// can only add open polys to open polys
|
|
||||||
if (above.isOpen() && pool.isClosed()) {
|
|
||||||
// console.log({skip_open_above:above});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// if pool fits into smallest above pool, add it and break
|
|
||||||
if (polygonFitsIn(pool, above, 0.1)) {
|
|
||||||
above.poolsDown.push(pool);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const emitPool = function(poolPoly) {
|
|
||||||
if (poolPoly.mark) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
poolPoly.mark = true;
|
|
||||||
const polys = poolPoly.pool.slice().append(poolPoly);
|
|
||||||
startPoint = poly2polyEmit(polys, startPoint, emitter, null, false);
|
|
||||||
startPoint = poly2polyEmit(poolPoly.poolsDown, startPoint, emitPool, "del_pdown", false);
|
|
||||||
return startPoint;
|
|
||||||
};
|
|
||||||
|
|
||||||
// from the top layer, iterate and descend through all connected pools
|
|
||||||
// pools are sorted smallest to largest. pools are polygons with an
|
|
||||||
// attached 'pool' array of polygons
|
|
||||||
layers.forEach(function(pools) {
|
|
||||||
pools.forEach(ppoly => {
|
|
||||||
if (!(ppoly.pool && ppoly.pool.length)) {
|
|
||||||
return ppoly.smallest = ppoly;
|
|
||||||
}
|
|
||||||
ppoly.pool.sort((a, b) => {
|
|
||||||
return a.perimeter() - b.perimeter();
|
|
||||||
});
|
|
||||||
ppoly.smallest = ppoly.pool[0];
|
|
||||||
});
|
|
||||||
pools.sort((a, b) => {
|
|
||||||
return a.smallest.perimeter() - b.smallest.perimeter();
|
|
||||||
}).forEach((ppoly, count) => {
|
|
||||||
startPoint = emitPool(ppoly) || startPoint;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
return startPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* flatten deeply nested polygons preserving inner arrays
|
* flatten deeply nested polygons preserving inner arrays
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,6 @@
|
||||||
addOutput = print.addOutput,
|
addOutput = print.addOutput,
|
||||||
tip2tipEmit = print.tip2tipEmit,
|
tip2tipEmit = print.tip2tipEmit,
|
||||||
poly2polyEmit = print.poly2polyEmit,
|
poly2polyEmit = print.poly2polyEmit,
|
||||||
poly2polyDepthFirstEmit = print.poly2polyDepthFirstEmit,
|
|
||||||
maxToolDiam = widget.maxToolDiam,
|
maxToolDiam = widget.maxToolDiam,
|
||||||
terrain = widget.terrain.map(data => {
|
terrain = widget.terrain.map(data => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -213,7 +212,7 @@
|
||||||
isMove = !cut;
|
isMove = !cut;
|
||||||
// drop points too close together
|
// drop points too close together
|
||||||
if (deltaXY < 0.001 && point.z === lastPoint.z) {
|
if (deltaXY < 0.001 && point.z === lastPoint.z) {
|
||||||
console.trace(["drop dup",lastPoint,point]);
|
console.log(["drop dup",lastPoint,point]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isMove && deltaXY <= toolDiamMove) {
|
if (isMove && deltaXY <= toolDiamMove) {
|
||||||
|
|
@ -352,7 +351,7 @@
|
||||||
let polys = [], t = [], c = [];
|
let polys = [], t = [], c = [];
|
||||||
POLY.flatten(top.traces, top.inner || []).forEach(function (poly) {
|
POLY.flatten(top.traces, top.inner || []).forEach(function (poly) {
|
||||||
let child = poly.parent;
|
let child = poly.parent;
|
||||||
if (depthFirst) poly = poly.clone(true);
|
if (depthFirst) poly = poly.clone();
|
||||||
if (child) c.push(poly); else t.push(poly);
|
if (child) c.push(poly); else t.push(poly);
|
||||||
poly.layer = depthData.layer;
|
poly.layer = depthData.layer;
|
||||||
polys.push(poly);
|
polys.push(poly);
|
||||||
|
|
@ -425,6 +424,7 @@
|
||||||
}, fromPoint);
|
}, fromPoint);
|
||||||
} else {
|
} else {
|
||||||
poly.forEachPoint(function(point, pidx, points, offset) {
|
poly.forEachPoint(function(point, pidx, points, offset) {
|
||||||
|
last = point;
|
||||||
camOut(point.clone(), offset !== 0);
|
camOut(point.clone(), offset !== 0);
|
||||||
}, poly.isClosed(), index);
|
}, poly.isClosed(), index);
|
||||||
}
|
}
|
||||||
|
|
@ -432,7 +432,7 @@
|
||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
function polyLevelEmitter(start, depth, levels, tops, emitter, fit) {
|
function depthRoughPath(start, depth, levels, tops, emitter, fit) {
|
||||||
let level = levels[depth];
|
let level = levels[depth];
|
||||||
if (!level) {
|
if (!level) {
|
||||||
return start;
|
return start;
|
||||||
|
|
@ -443,22 +443,39 @@
|
||||||
top.level_emit = true;
|
top.level_emit = true;
|
||||||
let inside = level.filter(poly => poly.isInside(top));
|
let inside = level.filter(poly => poly.isInside(top));
|
||||||
start = poly2polyEmit(inside, start, emitter);
|
start = poly2polyEmit(inside, start, emitter);
|
||||||
start = polyLevelEmitter(start, depth + 1, levels, tops, emitter, top);
|
start = depthRoughPath(start, depth + 1, levels, tops, emitter, top);
|
||||||
});
|
});
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
function polyArrayEmitter(levels, printPoint, emitter, info, fit) {
|
function depthOutlinePath(start, depth, levels, radius, emitter, clr) {
|
||||||
let tops = levels.map(level => {
|
let bottm = depth < levels.length - 1 ? levels[levels.length - 1] : null;
|
||||||
return POLY.nest(level.filter(poly => poly.depth === 0).clone());
|
let above = levels[depth-1];
|
||||||
|
let level = levels[depth];
|
||||||
|
if (!level) {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
if (above) {
|
||||||
|
level = level.filter(lp => {
|
||||||
|
return above.filter(ap => !ap.level_emit && lp.isNear(ap, radius, true)).length === 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
level = level.filter(lp => {
|
||||||
|
if (lp.level_emit) return false;
|
||||||
|
if (bottm && !clr) {
|
||||||
|
return bottm.filter(bp => lp.isEquivalent(bp)).length === 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
// start with the smallest polygon on the top
|
// omit polys that match bottom level polys unless level above is cleared
|
||||||
// printPoint = levels[0]
|
start = poly2polyEmit(level, start, (poly, index, count, fromPoint) => {
|
||||||
// .filter(p => p.depth)
|
poly.level_emit = true;
|
||||||
// .sort((a,b) => { return a.area() - b.area() })
|
fromPoint = polyEmit(poly, index, count, fromPoint);
|
||||||
// .shift()
|
fromPoint = depthOutlinePath(fromPoint, depth + 1, levels, radius, emitter, clr);
|
||||||
// .average();
|
return fromPoint;
|
||||||
return polyLevelEmitter(printPoint, 0, levels, tops, emitter);
|
}, {weight: true});
|
||||||
|
start = depthOutlinePath(start, depth + 1, levels, radius, emitter, clr);
|
||||||
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
// act on accumulated layer data
|
// act on accumulated layer data
|
||||||
|
|
@ -468,16 +485,26 @@
|
||||||
lastMode = PRO.ROUGH;
|
lastMode = PRO.ROUGH;
|
||||||
setTool(process.camRoughTool, process.camRoughSpeed, process.camRoughPlunge);
|
setTool(process.camRoughTool, process.camRoughSpeed, process.camRoughPlunge);
|
||||||
spindle = Math.min(spindleMax, process.camRoughSpindle);
|
spindle = Math.min(spindleMax, process.camRoughSpindle);
|
||||||
printPoint = polyArrayEmitter(depthData.rough, printPoint, polyEmit);
|
let tops = depthData.rough.map(level => {
|
||||||
|
return POLY.nest(level.filter(poly => poly.depth === 0).clone());
|
||||||
|
});
|
||||||
|
printPoint = depthRoughPath(printPoint, 0, depthData.rough, tops, polyEmit);
|
||||||
}
|
}
|
||||||
// outline depth first
|
// outline depth first
|
||||||
if (depthData.outline.length > 0) {
|
if (depthData.outline.length > 0) {
|
||||||
lastMode = PRO.OUTLINE;
|
lastMode = PRO.OUTLINE;
|
||||||
setTool(process.camOutlineTool, process.camOutlineSpeed, process.camOutlinePlunge);
|
setTool(process.camOutlineTool, process.camOutlineSpeed, process.camOutlinePlunge);
|
||||||
spindle = Math.min(spindleMax, process.camOutlineSpindle);
|
spindle = Math.min(spindleMax, process.camOutlineSpindle);
|
||||||
printPoint = poly2polyDepthFirstEmit(
|
let flatLevels = depthData.outline.map(level => {
|
||||||
depthData.outline, printPoint, polyEmit,
|
return POLY.flatten(level.clone(true), [], true).filter(p => !(p.depth = 0));
|
||||||
depthData.outlineDiam * 0.01);
|
})
|
||||||
|
// start with the smallest polygon on the top
|
||||||
|
printPoint = flatLevels[0]
|
||||||
|
.sort((a,b) => { return a.area() - b.area() })
|
||||||
|
.shift()
|
||||||
|
.average();
|
||||||
|
printPoint = depthOutlinePath(printPoint, 0, flatLevels, toolDiam, polyEmit, false);
|
||||||
|
printPoint = depthOutlinePath(printPoint, 0, flatLevels, toolDiam, polyEmit, true);
|
||||||
}
|
}
|
||||||
// two modes for deferred outlining: x then y or combined
|
// two modes for deferred outlining: x then y or combined
|
||||||
if (process.camContourCurves) {
|
if (process.camContourCurves) {
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,7 @@
|
||||||
slice.tops[0].traces = offset;
|
slice.tops[0].traces = offset;
|
||||||
slice.index = sliceIndex++;
|
slice.index = sliceIndex++;
|
||||||
});
|
});
|
||||||
|
|
||||||
sliceAll.appendAll(slices);
|
sliceAll.appendAll(slices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,6 +279,7 @@
|
||||||
addCutoutTabs(slice, outlineToolDiam, tabWidth, proc.camTabsCount, proc.camTabsAngle);
|
addCutoutTabs(slice, outlineToolDiam, tabWidth, proc.camTabsCount, proc.camTabsAngle);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
sliceAll.appendAll(slices);
|
sliceAll.appendAll(slices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, null, true);
|
});
|
||||||
|
|
||||||
print.addPrintPoints(preout, layerout, null);
|
print.addPrintPoints(preout, layerout, null);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue