/** Copyright Stewart Allen -- All Rights Reserved */ "use strict"; // dep: geo.base // dep: geo.polygons gapp.register("kiri.slice", [], (root, exports) => { const { base, kiri } = root; const { key, polygons } = base; const { util } = base; const POLY = base.polygons; const NOKEY = key.NONE; /** * Object encapsulates a z-slice from an object. This code is shared by the * client and the worker thread. As such, the view layers are ignored in the * worker code paths. */ class Slice { constructor (z, view) { this.z = z; // z-index this.index = 0; // slice index this.lines = null; // slice raw this.groups = null; // grouped lines this.up = null; // slice above (linked list) this.down = null; // slice below (linked list) this.tops = []; // array of Top objects this.view = view; // for rendering this slice this.finger = null; // cached fingerprint this.layers = null; // will replace most of the layer output data } /** * return Layers object for this slice. creates it if necessary. */ output() { if (this.layers) return this.layers; return this.layers = new kiri.Layers(); }; /** * returns a cloned slice the option of a deep clone on the top polys */ clone(deep) { const from = this, slice = newSlice(from.z, from.view); from.tops.forEach(function(top) { slice.addTop(top.poly.clone(deep)); }); return slice; }; topPolys() { return this.tops.map(top => top.poly); }; topSimples() { return this.tops.map(top => top.simple); }; // FDM top intersect optimization topPolysFlat() { if (this.topFlatPolys) { return this.topFlatPolys; } return this.topFlatPolys = POLY.flatten(this.topPolys().clone(true), [], true); }; // FDM retract path routing using first shell topRouteFlat() { if (this.topFlatRoutes) { return this.topFlatRoutes; } let topShells0 = this.tops.map(top => top.shells[0]).filter(p => p); return this.topFlatRoutes = POLY.flatten(topShells0.clone(true), [], true); } // CAM only topPolyInners() { return this.tops.map(top => top.poly.inner).flat().filter(poly => poly); }; // FDM / SLA only topInners() { return this.tops.map(top => top.last).flat().filter(poly => poly); }; // FDM / SLA only topFillOff() { return this.tops.map(top => top.fill_off).flat().filter(poly => poly); }; // FDM only topFill() { return this.tops.map(top => top.fill_lines).flat().filter(poly => poly); }; // FDM only topShells() { return this.tops.map(top => top.shells).flat().filter(poly => poly); }; /** * produces a fingerprint for a slice that should be the same for * layers that are identical. this happens in parts with unchanging * vertical wall regions. this allows us to eliminate expensive diffs * and infill computation when we detect the layers are the same. */ fingerprint() { if (this.finger) { return this.finger; } return this.finger = POLY.fingerprint(this.topPolys()); }; /** * returns true if the layers' fingerprints are the same */ fingerprintSame(slice) { return slice ? POLY.fingerprintCompare(this.fingerprint(), slice.fingerprint()) : false; }; addTops(polys) { polys.forEach(p => { this.addTop(p); }); return this; } addTop(poly) { if (poly.length) { // standard legacy polygon let top = new Top(poly); this.tops.push(top); top.simple = poly; return top; } else { // frop top object passed back by slicePost() let top = new Top(poly.poly); top.thin_fill = poly.thin_fill ? poly.thin_fill.map(p => base.newPoint(p.x,p.y,p.z)) : undefined; top.fill_lines = poly.fill_lines; top.fill_off = poly.fill_off; top.last = poly.last; top.shells = poly.shells; top.simple = poly.simple; this.tops.push(top); return top; } console.log('top', poly); throw "invalid top"; }; findClosestPointTo(target) { let min, find; if (this.tops && this.tops.length) { this.tops.forEach(function(top) { find = top.poly.findClosestPointTo(target); if (!min || find.distance < min.distance) { min = find; } }); } else if (this.supports) { this.supports.forEach(function(poly) { find = poly.findClosestPointTo(target); if (!min || find.distance < min.distance) { min = find; } }); } return min; }; // xray(dash = 3) { // // console.log('xray', this); // this.output().setLayer(`xp`, 0x888800).addPolys(this.topPolys()); // this.lines.forEach((line, i) => { // const group = i % dash; // const color = [ 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff ][group]; // this.output().setLayer(`xl-${group}`, color).addLine(line.p1, line.p2); // }); // } } /** * Represents a top-level (outer) polygon in a slice. Slices may contain * multiple tops each with nested structures. Top objects contain cached * and computed objects for quick access for rendering and dependent computations. */ class Top { constructor(polygon) { this.poly = polygon; // outline poly } clone(deep) { let top = new Top(this.poly.clone(deep)); return top; } /** * return innermost traces under a given top. for FDM, this represents * the outline shell that the fill touches. used by Print, Laser */ innerShells() { let shells = this.shells, array = []; if (shells) shells.forEach(function(p) { if (p.inner) array.appendAll(p.inner); }); return array; } /** * Returns shell polygons of a given depth * used by Print (FDM) * * @param {Polygon[]} out array to populate * @returns {Polygon[]} array of top polygons */ shellsAtDepth(depth) { return this.shells ? this.shells.filter(poly => poly.depth === depth) : []; } } function newTop(poly) { return new Top(poly); } function newSlice(z, view) { return new Slice(z, view); } gapp.overlay(kiri, { Top, Slice, newTop, newSlice }); });