diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 2e00ddd6..f827b901 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -844,6 +844,39 @@ 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() ? * @@ -858,12 +891,12 @@ } let mid, - midcheck, + midcheck = CONF.precision_midpoint_check_dist, exit = true; this.forEachSegment(function(prev, next) { - // check midpoint on long lines - if (prev.distTo2D(next) > CONF.precision_midpoint_check_dist) { + // check midpoint on long lines (TODO: should be distToSq2D()?) + if (prev.distTo2D(next) > midcheck) { mid = prev.midPointTo(next); if (!(mid.inPolygon(poly) || mid.nearPolygon(poly, tolerance || CONF.precision_close_to_poly_sq))) { exit = false; @@ -1130,9 +1163,13 @@ if (recurse) { let i, ai = this.inner, bi = poly.inner; 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++) { - if (!ai[i].isEquivalent(bi[i])) return false; + if (!ai[i].isEquivalent(bi[i])) { + return false; + } } } } @@ -1146,7 +1183,9 @@ pointok = false; poly.forEachSegment(function(i1p1, i1p2) { // 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 min = Math.min(min, dist); }); diff --git a/src/kiri/print.js b/src/kiri/print.js index 8aca82f1..8cc1c4f2 100644 --- a/src/kiri/print.js +++ b/src/kiri/print.js @@ -60,7 +60,6 @@ PRO.constReplace = constReplace; PRO.poly2polyEmit = poly2polyEmit; PRO.addPrintPoints = addPrintPoints; - PRO.poly2polyDepthFirstEmit = poly2polyDepthFirstEmit; PRO.parseSVG = function(code, offset) { let scope = this, @@ -987,8 +986,10 @@ * to be more like outputOrderClosest() and have the option to account for * depth in determining distance */ - function poly2polyEmit(array, startPoint, emitter, mark, weight) { - let mindist, dist, found, count = 0, marker = mark || 'delete'; + function poly2polyEmit(array, startPoint, emitter, options) { + let opt = options || {}; + let marker = opt.mark || 'delete'; + let mindist, dist, found, count = 0; for (;;) { found = null; mindist = Infinity; @@ -1012,7 +1013,7 @@ } let area = poly.area(); poly.forEachPoint(function(point, index) { - dist = weight ? + dist = opt.weight ? startPoint.distTo3D(point) * area * area : startPoint.distTo2D(point); if (dist < mindist) { @@ -1021,12 +1022,11 @@ } }); }); - if (found) { - found.poly[marker] = true; - startPoint = emitter(found.poly, found.index, ++count, startPoint) || found.point; - } else { + if (!found || opt.term) { break; } + found.poly[marker] = true; + startPoint = emitter(found.poly, found.index, ++count, startPoint) || found.point; } // undo delete marks @@ -1035,117 +1035,6 @@ 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 { - 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 * diff --git a/src/mode/cam/prepare.js b/src/mode/cam/prepare.js index 04ebaa96..c5448ce5 100644 --- a/src/mode/cam/prepare.js +++ b/src/mode/cam/prepare.js @@ -76,7 +76,6 @@ addOutput = print.addOutput, tip2tipEmit = print.tip2tipEmit, poly2polyEmit = print.poly2polyEmit, - poly2polyDepthFirstEmit = print.poly2polyDepthFirstEmit, maxToolDiam = widget.maxToolDiam, terrain = widget.terrain.map(data => { return { @@ -213,7 +212,7 @@ isMove = !cut; // drop points too close together if (deltaXY < 0.001 && point.z === lastPoint.z) { - console.trace(["drop dup",lastPoint,point]); + console.log(["drop dup",lastPoint,point]); return; } if (isMove && deltaXY <= toolDiamMove) { @@ -352,7 +351,7 @@ let polys = [], t = [], c = []; POLY.flatten(top.traces, top.inner || []).forEach(function (poly) { let child = poly.parent; - if (depthFirst) poly = poly.clone(true); + if (depthFirst) poly = poly.clone(); if (child) c.push(poly); else t.push(poly); poly.layer = depthData.layer; polys.push(poly); @@ -425,6 +424,7 @@ }, fromPoint); } else { poly.forEachPoint(function(point, pidx, points, offset) { + last = point; camOut(point.clone(), offset !== 0); }, poly.isClosed(), index); } @@ -432,7 +432,7 @@ return last; } - function polyLevelEmitter(start, depth, levels, tops, emitter, fit) { + function depthRoughPath(start, depth, levels, tops, emitter, fit) { let level = levels[depth]; if (!level) { return start; @@ -443,22 +443,39 @@ top.level_emit = true; let inside = level.filter(poly => poly.isInside(top)); 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; } - function polyArrayEmitter(levels, printPoint, emitter, info, fit) { - let tops = levels.map(level => { - return POLY.nest(level.filter(poly => poly.depth === 0).clone()); + function depthOutlinePath(start, depth, levels, radius, emitter, clr) { + let bottm = depth < levels.length - 1 ? levels[levels.length - 1] : null; + 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 - // printPoint = levels[0] - // .filter(p => p.depth) - // .sort((a,b) => { return a.area() - b.area() }) - // .shift() - // .average(); - return polyLevelEmitter(printPoint, 0, levels, tops, emitter); + // omit polys that match bottom level polys unless level above is cleared + start = poly2polyEmit(level, start, (poly, index, count, fromPoint) => { + poly.level_emit = true; + fromPoint = polyEmit(poly, index, count, fromPoint); + fromPoint = depthOutlinePath(fromPoint, depth + 1, levels, radius, emitter, clr); + return fromPoint; + }, {weight: true}); + start = depthOutlinePath(start, depth + 1, levels, radius, emitter, clr); + return start; } // act on accumulated layer data @@ -468,16 +485,26 @@ lastMode = PRO.ROUGH; setTool(process.camRoughTool, process.camRoughSpeed, process.camRoughPlunge); 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 if (depthData.outline.length > 0) { lastMode = PRO.OUTLINE; setTool(process.camOutlineTool, process.camOutlineSpeed, process.camOutlinePlunge); spindle = Math.min(spindleMax, process.camOutlineSpindle); - printPoint = poly2polyDepthFirstEmit( - depthData.outline, printPoint, polyEmit, - depthData.outlineDiam * 0.01); + let flatLevels = depthData.outline.map(level => { + return POLY.flatten(level.clone(true), [], true).filter(p => !(p.depth = 0)); + }) + // 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 if (process.camContourCurves) { diff --git a/src/mode/cam/slice.js b/src/mode/cam/slice.js index 10a1f0e2..25115154 100644 --- a/src/mode/cam/slice.js +++ b/src/mode/cam/slice.js @@ -205,6 +205,7 @@ slice.tops[0].traces = offset; slice.index = sliceIndex++; }); + sliceAll.appendAll(slices); } @@ -278,6 +279,7 @@ addCutoutTabs(slice, outlineToolDiam, tabWidth, proc.camTabsCount, proc.camTabsAngle); } }); + sliceAll.appendAll(slices); } diff --git a/src/mode/fdm/prepare.js b/src/mode/fdm/prepare.js index acc9a78a..7286fe90 100644 --- a/src/mode/fdm/prepare.js +++ b/src/mode/fdm/prepare.js @@ -146,7 +146,7 @@ } } }); - }, null, true); + }); print.addPrintPoints(preout, layerout, null);