/** Copyright Stewart Allen -- All Rights Reserved */ "use strict"; // dep: geo.base // dep: geo.polygons // dep: kiri-mode.fdm.driver gapp.register("kiri-mode.fdm.post", [], (root, exports) => { const { base, kiri } = root; const { driver } = kiri; const { polygons } = base; const { fillArea } = polygons; const { FDM } = driver; const POLY = polygons; /** * may run in minion or worker context. do not create objects * that will not quickly encode in threaded mode. add to existing * data object. return is ignored. */ base.slicePost.FDM = function(data, options) { const { z, lines, groups } = data; const { useAssembly, post_args, zIndexes } = options; const { process, isSynth, vaseMode } = post_args; const { shellOffset, fillOffset, clipOffset } = post_args; data.tops = POLY.nest(groups); if (isSynth) { const process = post_args.process; if (process.sliceSupportGrow > 0) { // expand synth supports here so they can be clipped later data.tops = POLY.expand(data.tops, process.sliceSupportGrow, data.z, []); } // do not shell synth widgets because // they will be clipped against peers later // which requires shelling post-clip // but we still need to generate tops delete data.groups; return; } const index = zIndexes.indexOf(z); const range = FDM.getRangeParameters(process, index); // calculate fractional shells let shellFrac = (range.sliceShells - (range.sliceShells | 0)); let sliceShells = range.sliceShells | 0; if (shellFrac) { let v1 = shellFrac > 0.5 ? 1 - shellFrac : shellFrac; let v2 = 1 - v1; let parts = Math.round(v2/v1) + 1; let rem = index % parts; let trg = shellFrac > 0.5 ? 1 : parts - 1; sliceShells += rem >= trg ? 1 : 0; } const isFirst = index === 0; const height = process.sliceHeight; const spaceMult = isFirst ? process.firstLayerLineMult || 1 : 1; const count = isSynth ? 1 : sliceShells; const offset = shellOffset * spaceMult; const fillOff = fillOffset * spaceMult; const thinType = isSynth ? undefined : process.sliceDetectThin; const nutops = []; // co-locate shell processing with top generation in slicer for (let top of data.tops) { nutops.push(FDM.doTopShells(z, top, count, offset/2, offset, fillOff, { thinType, vaseMode, useAssembly })); } data.clip = clipOffset ? POLY.offset(nutops.map(t => t.simple), clipOffset) : undefined; data.tops = nutops; delete data.groups; }; function offset_default(params) { let { z, top, count, top_poly, offset1, offsetN, wasm, last, gaps } = params; // standard wall offsetting strategy POLY.offset( top_poly, [-offset1, -offsetN], { z, wasm, count, outs: top.shells, flat: true, call: (polys, onCount) => { last = polys; // mark each poly with depth (offset #) starting at 0 for (let p of polys) { p.depth = count - onCount; if (p.fill_off) p.fill_off.forEach(function(pi) { // use negative offset for inners pi.depth = -(count - onCount); }); // mark inner depth to match parent if (p.inner) { for (let pi of p.inner) { pi.depth = p.depth; } } } } } ); return { last, gaps }; } function thin_type_1(params) { let { z, top, count, top_poly, offsetN, last, gaps } = params; top.thin_fill = []; top.fill_sparse = []; let layers = POLY.inset(top_poly, offsetN, count, z, true); last = layers.last().mid; top.shells = layers.map(r => r.mid).flat(); top.gaps = layers.map(r => r.gap).flat(); let off = offsetN; let min = off * 0.75; let max = off * 4; for (let poly of layers.map(r => r.gap).flat()) { let centers = poly.centers(off/2, z, min, max, {lines:false}); top.fill_sparse.appendAll(centers); } return { last, gaps }; } function thin_type_2(params) { let { z, top, count, top_poly, offset1, offsetN, last, gaps, wasm } = params; top.gaps = gaps; top.thin_fill = []; let oso = {z, count, gaps: [], outs: [], minArea: 0.05, wasm}; POLY.offset(top_poly, [-offset1, -offsetN], oso); oso.outs.forEach((polys, i) => { polys.forEach(p => { p.depth = i; if (p.fill_off) { p.fill_off.forEach(pi => pi.depth = i); } if (p.inner) { for (let pi of p.inner) { pi.depth = p.depth; } } top.shells.push(p); }); last = polys; }); // slice.solids.trimmed = slice.solids.trimmed || []; oso.gaps.forEach((polys, i) => { let off = (i == 0 ? offset1 : offsetN); polys = POLY.offset(polys, -off * 0.8, {z, minArea: 0, wasm}); top.thin_fill.appendAll(cullIntersections( fillArea(polys, 45, off/2, [], 0.01, off*2), fillArea(polys, 135, off/2, [], 0.01, off*2), )); gaps.push(...polys); }); return { last, gaps }; } /** * may run in minion or worker context. performs shell offsetting * including thin wall detection when enabled */ FDM.doTopShells = function(z, top, count, offset1, offsetN, fillOffset, opt = {}) { // pretend we're a top object in minions if (!top.poly) { top = { poly: top }; } // add simple (low rez poly) where less accuracy is OK top.simple = top.poly.simple(); let wasm = opt.useAssembly; let top_poly = [ top.poly ]; if (opt.vaseMode) { // remove top poly inners in vase mode top.poly = top.poly.clone(false); } top.shells = []; top.fill_off = []; top.fill_lines = []; let last = [], gaps = []; if (count) { // permit offset of 0 for laser and drag knife if (offset1 === 0 && count === 1) { last = top_poly.clone(true); top.shells = last; } else { // heal top open polygons if the ends are close (benchy tilt test) top_poly.forEach(p => { if (p.open) { let dist = p.first().distTo2D(p.last()); if (dist < 1) p.open = false; } }); let ret = { last, gaps }; switch (opt.thinType) { case "type 1": ret = thin_type_1({ z, top, count, top_poly, offsetN, fillOffset }); break; case "type 2": ret = thin_type_2({ z, top, count, top_poly, offset1, offsetN, fillOffset, gaps, wasm }); break; default: ret = offset_default({ z, top, count, top_poly, offset1, offsetN, wasm }); break; } last = ret.last || last; gaps = ret.gaps || gaps; } } else { // no shells, just infill, is permitted last = [top.poly]; } // generate fill offset poly set from last offset to top.fill_off if (fillOffset && last.length > 0) { // if gaps present, remove that area from fill inset if (gaps.length) { let nulast = []; POLY.subtract(last, gaps, nulast, null, z); last = nulast; } last.forEach(function(inner) { POLY.offset([inner], -fillOffset, {outs: top.fill_off, flat: true, z}); }); } // for diffing top.last = last; // top.last_simple = last.map(p => p.clean(true, undefined, config.clipper / 10)); return top; } /** * given an array of arrays of points (lines), eliminate intersections * between groups, then return a unified array of shortest non-intersects. */ function cullIntersections() { function toLines(pts) { let lns = []; for (let i=0, il=pts.length; i t); if (aOa.length < 1) return; let aa = toLines(aOa.shift()); while (aOa.length) { let bb = toLines(aOa.shift()); loop: for (let i=0, il=aa.length; i !l.del).concat(bb.filter(l => !l.del)); } let good = []; for (let i=0, il=aa.length; i 2 ? good : []; } });