reflow / rename / document fdm slicing

This commit is contained in:
Stewart Allen 2026-01-06 19:16:11 -05:00
commit 9a463881d1
4 changed files with 178 additions and 160 deletions

View file

@ -9,14 +9,17 @@ import { slicer } from '../../../geo/slicer.js';
import { Point } from '../../../geo/point.js';
import { Slope } from '../../../geo/slope.js';
slicer.slicePost.FDM = slicePost;
// tell the slicer what to do in FDM mode
// with slices (post processing). the post
// processor may depend on worker / minion context
slicer.slicePost.FDM = slicerPostProcessor;
/**
* 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.
*/
export function slicePost(data, options) {
function slicerPostProcessor(data, options) {
const { groups, z } = data;
const { post_args, useAssembly, zIndexes } = options;
const { process, vaseMode } = post_args;
@ -81,7 +84,7 @@ export function slicePost(data, options) {
const nutops = [];
// co-locate shell processing with top generation in slicer
for (let top of data.tops) {
nutops.push(doTopShells(z, top, count, offset/2, offset, fillOff, {
nutops.push(layerProcessTop(z, top, count, offset/2, offset, fillOff, {
thinType,
vaseMode,
useAssembly
@ -687,7 +690,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) {
* may run in minion or worker context. performs shell offsetting
* including thin wall detection when enabled
*/
export function doTopShells(z, top, count, offset1, offsetN, fillOffset, opt = {}) {
export function layerProcessTop(z, top, count, offset1, offsetN, fillOffset, opt = {}) {
// pretend we're a top object in minions
if (!top.poly) {
top = { poly: top };

View file

@ -1,7 +1,6 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { base, util } from '../../../geo/base.js';
import { doTopShells } from './post.js';
import { layerProcessTop } from './post.js';
import { fill, fill_fixed } from './fill.js';
import { generateBeltAnchor, embossBeltPooch, finalizeBeltBounds } from './belt.js';
import { getRangeParameters } from './driver.js';
@ -10,8 +9,7 @@ import { newPolygon } from '../../../geo/polygon.js';
import { newSlice } from '../../core/slice.js';
import { polygons as POLY } from '../../../geo/polygons.js';
import { slice, sliceZ } from '../../../geo/slicer.js';
const { config } = base;
import { util } from '../../../geo/base.js';
const CONSTANTS = {
// Support fill
@ -174,55 +172,6 @@ function vopt(opt, ctx) {
return opt;
}
/**
* DRIVER SLICE CONTRACT
*
* global slice operation run after all individual widget slice() complete
*/
export function slicePost(settings, onupdate) {
// future home of brim and anchor generation
let widgets = Object.values(self.kiri_worker.cache)
.filter(w => !w.meta.disabled)
.sort((a,b) => {
return a.slices[0].z - b.slices[0].z
});
// assign grid_id which can be embedded in gcode and
// used by the controller to cancel objects during print
let { bounds } = settings;
for (let widget of widgets) {
let { pos, box } = widget.track;
// calculate top/left coordinate for widget
// relative to bounding box for all widgets
let tl = {
x: Math.round((pos.x - box.w/2 - bounds.min.x) / 10) + 1,
y: Math.round((pos.y - box.h/2 - bounds.min.y) / 10) + 1
};
widget.track.grid_id = tl.x * 100 + tl.y;
}
// count extruders used
let ext = [];
for (let w of widgets) {
if (w.anno && w.anno.extruder >= 0) {
let e = w.anno.extruder;
if (ext.indexOf(e) < 0) {
ext.push(e);
}
}
}
// sort widgets by first slice Z
widgets.sort((a,b) => {
return a.slices[0].z - b.slices[0].z;
});
// give first widget a pass since it should have the anchor
widgets.shift();
// remove anchor slices from other widgets (only with multi-material)
if (ext.length > 1) {
for (let w of widgets) {
w.slices = w.slices.filter(s => s.index >= 0);
}
}
};
/**
* DRIVER SLICE CONTRACT
*
@ -258,7 +207,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
shellOffset = lineWidth,
fillSpacing = lineWidth,
fillOffset = lineWidth * fillOffsetMult,
clipOffset = process.sliceSupportOffset,
clipOffset = process.sliceSupportOffset + lineWidth / 2,
sliceFillAngle = process.sliceFillAngle,
supportDensity = process.sliceSupportDensity,
{ sliceCompInner, sliceCompOuter } = process;
@ -344,6 +293,8 @@ export function sliceOne(settings, widget, onupdate, ondone) {
}
}
// create Slice objects for specified list of Z heights
// zGen() produces the list (or empty for slicer auto-detected)
slice(points, {
strict: true,
debug: process.xray,
@ -372,7 +323,13 @@ export function sliceOne(settings, widget, onupdate, ondone) {
onupdate(update) {
return onupdate(0.0 + update * 0.5)
}
}).then((output) => {
})
.then(decodeSlices)
.then(processSlices)
.then(ondone);
// turn slicer raw output into Slice objects
function decodeSlices(output) {
// post process slices and re-incorporate missing meta-data
return output.slices.map(data => {
let { z, clip, lines, groups, changes } = data;
@ -397,9 +354,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
}
return slice;
}).filter(s => s);
}).then(slices => {
return onSliceDone(slices);
}).then(ondone);
}
// z index generator
function zGen(zopt) {
@ -483,9 +438,9 @@ export function sliceOne(settings, widget, onupdate, ondone) {
return zi;
}
async function onSliceDone(slices) {
async function processSlices(slices) {
/**
* Process automatic shadow-based support generation
* Process shadow-based support generation
*/
async function processAutoSupport() {
if (process.sliceSupportType !== 'automatic') {
@ -504,17 +459,22 @@ export function sliceOne(settings, widget, onupdate, ondone) {
stack.sort((a,b) => b.z - a.z); // accumulate deltas
}
// 1. accumulate / union shadow coverage top down
// 2. trim to area outside slice.clips
for (let slice of stack) {
let shadow = await widget.shadowAt(slice.z, true);
if (process.sliceSupportExtra) {
shadow = POLY.offset(shadow, process.sliceSupportExtra);
}
slice.supports = shadow;
slice.output().setLayer("shadow", 0xff0000).addPolys(shadow);
}
}
/**
* Process solid layer fills (top/bottom layers)
* Process top and bottom layers or any other
* layers detected and marked for solid fill
*/
function processSolidLayers() {
forSlices(0.15, 0.2, slice => {
@ -527,7 +487,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
let solidWidth = isSolid ? range.sliceFillWidth || 1 : 0;
if (solidWidth) {
let fillSpace = fillSpacing * solidWidth;
doSolidLayerFill({ slice, spacing: fillSpace, angle: sliceFillAngle });
layerMakeSolid({ slice, spacing: fillSpace, angle: sliceFillAngle });
}
if (slice.index === slices.length - 1) {
slice.isFlatsLayer = true;
@ -544,15 +504,16 @@ export function sliceOne(settings, widget, onupdate, ondone) {
* Process layer diffs and project solid areas
*/
function processLayerDiffs() {
// boolean diff layers to detect bridges and flats
profileStart("delta");
forSlices(0.2, 0.34, slice => {
let params = slice.params || process;
let solidMinArea = params.sliceSolidMinArea;
let sliceFillGrow = params.sliceFillGrow;
doDiff(slice, { min: solidMinArea, grow: sliceFillGrow });
layerDiff(slice, { min: solidMinArea, grow: sliceFillGrow });
}, "layer deltas");
profileEnd();
// project bridges and flats up and down into part
profileStart("delta-project");
forSlices(0.34, 0.35, slice => {
let params = slice.params || process;
@ -560,6 +521,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
bottomLayers = params.sliceBottomLayers || 0;
if (topLayers) projectFlats(slice, topLayers);
if (bottomLayers) projectBridges(slice, bottomLayers);
if (slice.flats) POLY.setZ(slice.flats, slice.z);
}, "layer deltas");
profileEnd();
}
@ -572,11 +534,10 @@ export function sliceOne(settings, widget, onupdate, ondone) {
let promises = isConcurrent ? [] : undefined;
forSlices(0.35, promises ? 0.4 : 0.5, slice => {
let params = slice.params || process;
let first = slice.index === 0;
let solidWidth = params.sliceFillWidth || 1;
let fillSpace = fillSpacing * solidWidth;
let solidMinArea = params.sliceSolidMinArea;
doSolidsFill({ slice, spacing: fillSpace, angle: sliceFillAngle, minArea: solidMinArea, promises });
layerFillSolids({ slice, spacing: fillSpace, angle: sliceFillAngle, minArea: solidMinArea, promises });
sliceFillAngle = (sliceFillAngle + 90.0) % 360;
}, "fill solids");
// very last layer (top) is set to finish solid rate
@ -601,7 +562,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
return;
}
let newType = params.sliceFillType;
doSparseLayerFill(slice, {
layerSparseFill(slice, {
settings,
process,
device,
@ -642,7 +603,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
forSlices(0.8, promises ? 0.88 : 0.9, slice => {
let params = slice.params || process;
let density = params.sliceSupportDensity;
doSupportFill({
layerSupportFill({
angle: process.sliceSupportFill,
density,
gap: process.sliceSupportGap,
@ -765,9 +726,6 @@ export function sliceOne(settings, widget, onupdate, ondone) {
slice.params = getRangeParameters(process, slice.index);
}
// do not hint polygon fill longer than a max span length
config.hint_len_max = util.sqr(process.sliceBridgeMax);
// reset for solids, support projections
// and other annotations
slices.forEach(slice => {
@ -781,8 +739,6 @@ export function sliceOne(settings, widget, onupdate, ondone) {
// add lead in anchor when specified in belt mode (but not for synths)
if (isBelt) {
let { slope } = widget.belt;
// Generate belt anchor
generateBeltAnchor({
slices,
@ -841,7 +797,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
if (render) {
forSlices(0.9, 1.0, slice => {
let params = slice.params || process;
doRender(slice, params, {
layerRender(slice, params, {
dark: controller.dark,
devel: controller.devel,
renderContext
@ -899,7 +855,7 @@ function bound(v,min,max) {
* @param {boolean} opt.devel - Enable development visualization
* @param {RenderContext} opt.renderContext - Rendering context
*/
function doRender(slice, params, opt = {}) {
function layerRender(slice, params, opt = {}) {
const { dark, devel, renderContext: ctx } = opt;
const Color = dark ? COLOR_DARK : COLOR;
const output = slice.output();
@ -961,15 +917,15 @@ function doRender(slice, params, opt = {}) {
});
if (isThin && devel) {
if (slice.solids && slice.solids.length) output
if (slice.solids?.length) output
.setLayer("solids", { face: 0xbbbb00, check: 0xbbbb00 })
.addAreas(slice.solids);
if (slice.bridges && slice.bridges.length) output
if (slice.bridges?.length) output
.setLayer("bridges", { face: 0x00cccc, line: 0x00cccc, check: 0x00cccc })
.addAreas(slice.bridges);
if (slice.flats && slice.flats.length) output
if (slice.flats?.length) output
.setLayer("flats", { face: 0xaa00aa, line: 0xaa00aa, check: 0xaa00aa })
.addAreas(slice.flats);
}
@ -1004,6 +960,55 @@ function doRender(slice, params, opt = {}) {
// console.log(slice.index, slice.render.stats);
}
/**
* DRIVER SLICE CONTRACT
*
* global slice operation run after all individual widget slice() complete
*/
export function slicePost(settings, onupdate) {
// future home of brim and anchor generation
let widgets = Object.values(self.kiri_worker.cache)
.filter(w => !w.meta.disabled)
.sort((a,b) => {
return a.slices[0].z - b.slices[0].z
});
// assign grid_id which can be embedded in gcode and
// used by the controller to cancel objects during print
let { bounds } = settings;
for (let widget of widgets) {
let { pos, box } = widget.track;
// calculate top/left coordinate for widget
// relative to bounding box for all widgets
let tl = {
x: Math.round((pos.x - box.w/2 - bounds.min.x) / 10) + 1,
y: Math.round((pos.y - box.h/2 - bounds.min.y) / 10) + 1
};
widget.track.grid_id = tl.x * 100 + tl.y;
}
// count extruders used
let ext = [];
for (let w of widgets) {
if (w.anno && w.anno.extruder >= 0) {
let e = w.anno.extruder;
if (ext.indexOf(e) < 0) {
ext.push(e);
}
}
}
// sort widgets by first slice Z
widgets.sort((a,b) => {
return a.slices[0].z - b.slices[0].z;
});
// give first widget a pass since it should have the anchor
widgets.shift();
// remove anchor slices from other widgets (only with multi-material)
if (ext.length > 1) {
for (let w of widgets) {
w.slices = w.slices.filter(s => s.index >= 0);
}
}
}
/**
* Compute offset shell polygons. For FDM, the first offset is usually half
* of the nozzle width. Each subsequent offset is a full nozzle width. User
@ -1018,9 +1023,9 @@ function doRender(slice, params, opt = {}) {
* @param {number} fillOffset
* @param {Obejct} options
*/
export function doShells(slice, count, offset1, offsetN, fillOffset, opt = {}) {
export function layerProcessTops(slice, count, offset1, offsetN, fillOffset, opt = {}) {
for (let top of slice.tops) {
doTopShells(slice.z, top, count, offset1, offsetN, fillOffset, opt);
layerProcessTop(slice.z, top, count, offset1, offsetN, fillOffset, opt);
}
}
@ -1039,9 +1044,7 @@ export function doShells(slice, count, offset1, offsetN, fillOffset, opt = {}) {
* @param {number} args.spacing - Fill line spacing in mm
* @param {number} args.angle - Fill angle in degrees
*/
function doSolidLayerFill(args) {
const { slice, spacing, angle } = args;
function layerMakeSolid({ slice, spacing, angle }) {
if (slice.xray) {
return;
}
@ -1064,7 +1067,7 @@ function doSolidLayerFill(args) {
* Take output from pluggable sparse infill algorithm and clip to
* the bounds of the top polygons and their inner solid areas.
*/
function doSparseLayerFill(slice, options = {}) {
function layerSparseFill(slice, options = {}) {
if (slice.xray || options.type === 'none') {
return;
}
@ -1239,7 +1242,7 @@ function doSparseLayerFill(slice, options = {}) {
* Used to calculate bridges, flats and then solid projections.
* 'expand' is used for top offsets in SLA mode
*/
export function doDiff(slice, options = {}) {
export function layerDiff(slice, options = {}) {
const { sla, fakedown, grow, min } = options;
if ((slice.index <= 0 && !fakedown) || slice.xray) {
return;
@ -1277,47 +1280,7 @@ export function doDiff(slice, options = {}) {
bridges.appendAll(newBridges);
flats.appendAll(newFlats);
};
/**
*
*
* @param {Polygon[]} polys
*/
function addSolidFills(slice, polys) {
if (slice.solids) {
slice.solids.appendAll(polys);
} else if (polys && polys.length) {
console.log({no_solids_in: slice, for: polys})
}
};
/**
* project bottom flats down
*/
export function projectFlats(slice, count, expand) {
if (!slice.down || !slice.flats) return;
// these flats are marked for finishing print speed
if (slice.flats?.length) {
slice.finishSolids = true;
slice.isFlatsLayer = true;
const flats = expand ? POLY.expand(slice.flats, expand) : slice.flats;
projectSolid({ slice, polys: flats, count, up: false, first: true });
}
};
/**
* project top bridges up
*/
export function projectBridges(slice, count) {
if (!slice.up || !slice.bridges) return;
// these flats are marked for finishing print speed
if (slice.bridges?.length) {
slice.finishSolids = true;
slice.isBridgeLayer = true;
}
projectSolid({ slice, polys: slice.bridges, count, up: true, first: true });
};
}
/**
* Fill projected solid areas and store line data
@ -1330,8 +1293,7 @@ export function projectBridges(slice, count) {
* @param {Promise[]} [args.promises] - Array for async operations
* @return {boolean} true if filled, false if not
*/
function doSolidsFill(args) {
const { slice, spacing, angle, minArea, promises: fillQ } = args;
function layerFillSolids({ slice, spacing, angle, minArea, promises: fillQ }) {
let minarea = minArea || 1,
tops = slice.tops,
solids = slice.solids;
@ -1406,7 +1368,7 @@ function doSolidsFill(args) {
for (let top of tops) {
top.solids = [];
}
doSolidLayerFill({ slice, spacing, angle });
layerMakeSolid({ slice, spacing, angle });
return;
}
@ -1435,12 +1397,12 @@ function doSolidsFill(args) {
}
}
if (tofill.length > 0) {
doFillArea({ promises: fillQ, polys: tofill, angle, spacing, output: newfill });
areaFill({ promises: fillQ, polys: tofill, angle, spacing, output: newfill });
}
if (angfill.length > 0) {
top.fill_lines_ang = {spacing:spacing,list:[],poly:[]};
for (let af of angfill) {
doFillArea({
areaFill({
promises: fillQ,
polys: [af],
angle: af.fillang.angle + CONSTANTS.ANGLED_FILL_ROTATION,
@ -1464,8 +1426,7 @@ function doSolidsFill(args) {
* @param {number} [args.minLen] - Minimum line length
* @param {number} [args.maxLen] - Maximum line length
*/
function doFillArea(args) {
const { promises: fillQ, polys, angle, spacing, output, minLen, maxLen } = args;
function areaFill({ promises: fillQ, polys, angle, spacing, output, minLen, maxLen }) {
if (fillQ) {
fillQ.push(self.kiri_worker.minions.fill(polys, angle, spacing, output, minLen, maxLen));
} else {
@ -1512,14 +1473,13 @@ function doFillArea(args) {
* Each polygon has a .fill property with fill lines
* @modifies {Slice} slice.supportOutline - Stores support outline polygons
*
* @see fillSupportPolys - Called internally to generate fill patterns
* @see supportPolyFill - Called internally to generate fill patterns
* @see CONSTANTS.SUPPORT_INSET_RATIO - Inset ratio (1/3 of line width)
* @see CONSTANTS.SUPPORT_AUTO_ANGLE_WIDE - Auto angle for wide polygons (1090 = 90°)
* @see CONSTANTS.SUPPORT_AUTO_ANGLE_TALL - Auto angle for tall polygons (1000 = 0°)
* @see CONSTANTS.SUPPORT_CONNECT_DISTANCE_MULT - Distance multiplier for line connection (2x)
*/
function doSupportFill(args) {
const { promises, slice, lineWidth, density, minArea, isBelt, angle, outline, gap } = args;
function layerSupportFill({ promises, slice, lineWidth, density, minArea, isBelt, angle, outline, gap }) {
let supports = slice.supports,
min = minArea || 0.1,
sub;
@ -1546,7 +1506,7 @@ function doFillArea(args) {
}
if (supports) {
fillSupportPolys({
supportPolyFill({
promises, polys: supports, lineWidth, density, z: slice.z, isBelt, angle, outline
});
}
@ -1617,8 +1577,8 @@ function doFillArea(args) {
* - Lines are either individual segments (outline=true) or
* connected paths (outline=false) for travel reduction
*
* @see doSupportFill - Parent function that calls this after clipping/union
* @see doFillArea - Lower-level function that generates the actual scan lines
* @see layerSupportFill - Parent function that calls this after clipping/union
* @see areaFill - Lower-level function that generates the actual scan lines
* @see connect_lines - Utility that connects nearby line segments
* @see CONSTANTS.SUPPORT_AUTO_ANGLE_WIDE - Auto angle for wide polygons (1090 = 90°)
* @see CONSTANTS.SUPPORT_AUTO_ANGLE_TALL - Auto angle for tall polygons (1000 = 0°)
@ -1626,7 +1586,7 @@ function doFillArea(args) {
* @see CONSTANTS.SUPPORT_CONNECT_DISTANCE_MULT - Distance multiplier for line connection (2x)
*/
function fillSupportPolys({ promises, polys, lineWidth, density, z, isBelt, angle, outline }) {
function supportPolyFill({ promises, polys, lineWidth, density, z, isBelt, angle, outline }) {
// calculate fill density
let spacing = lineWidth * (1 / density);
for (let poly of polys) {
@ -1636,7 +1596,7 @@ function fillSupportPolys({ promises, polys, lineWidth, density, z, isBelt, angl
let inset = POLY.offset([poly], -lineWidth * CONSTANTS.SUPPORT_INSET_RATIO, {flat: true, z, wasm: true});
// do the fill
if (inset && inset.length > 0) {
doFillArea({
areaFill({
angle: angle || auto,
output: poly.fill = [],
polys: inset,
@ -1660,24 +1620,79 @@ function fillSupportPolys({ promises, polys, lineWidth, density, z, isBelt, angl
* @param {boolean} args.up - Project upward (true) or downward (false)
* @param {boolean} args.first - First projection (hint fill angles)
*/
function projectSolid(args) {
const { slice, polys, count, up, first } = args;
function projectSolid({ slice, polys, count, up, first }) {
if (slice && !up && count === 1) {
slice.isBridgeLayer = true;
}
if (!slice || count <= 0) {
return;
}
let clones = polys.clone(true);
if (first) {
clones.forEach(function(p) {
p.hintFillAngle();
});
for (let clone of clones) {
clone.hintFillAngle();
}
}
addSolidFills(slice, clones);
if (slice.solids) {
slice.solids.appendAll(clones);
} else if (clones && clones.length) {
console.log({no_solids_in: slice, for: clones})
}
if (count > 0) {
if (up) projectSolid({ slice: slice.up, polys, count: count-1, up: true, first: false });
else projectSolid({ slice: slice.down, polys, count: count-1, up: false, first: false });
projectSolid({
count: count - 1,
first: false,
polys,
slice: up ? slice.up : slice.down,
up
});
// if (up) {
// projectSolid({
// count: count-1,
// first: false,
// polys,
// slice: slice.up,
// up: true
// });
// } else {
// projectSolid({
// count: count-1,
// first: false,
// polys,
// slice: slice.down,
// up: false
// });
// }
}
}
/**
* project bottom flats down into part
*/
export function projectFlats(slice, count, expand) {
if (!slice.down || !slice.flats) return;
// these flats are marked for finishing print speed
if (slice.flats?.length) {
slice.finishSolids = true;
slice.isFlatsLayer = true;
const flats = expand ? POLY.expand(slice.flats, expand) : slice.flats;
projectSolid({ slice, polys: flats, count, up: false, first: true });
}
}
/**
* project top bridges up into part
*/
export function projectBridges(slice, count) {
if (!slice.up || !slice.bridges) return;
// these flats are marked for finishing print speed
if (slice.bridges?.length) {
slice.finishSolids = true;
slice.isBridgeLayer = true;
}
projectSolid({ slice, polys: slice.bridges, count, up: true, first: true });
}

View file

@ -5,7 +5,7 @@ import { slicer } from '../../../geo/slicer.js';
import { newPolygon } from '../../../geo/polygon.js';
import { polygons as POLY } from '../../../geo/polygons.js';
import { newSlice, newTop } from '../../core/slice.js';
import { doShells, doDiff, projectFlats, projectBridges } from '../fdm/slice.js';
import { layerProcessTops, layerDiff, projectFlats, projectBridges } from '../fdm/slice.js';
import { PNG } from '../../../ext/pngjs.esm.js';
import { SLA } from './driver.js';
@ -149,14 +149,14 @@ export function sla_slice(settings, widget, onupdate, ondone) {
work_remain = work_total;
forSlices(slices, 5, (slice,index) => {
if (process.slaShell) {
doShells(slice, 2, 0, process.slaShell);
layerProcessTops(slice, 2, 0, process.slaShell);
} else {
doShells(slice, 1, 0);
layerProcessTops(slice, 1, 0);
}
}, "shells");
forSlices(slices, 10, (slice) => {
if (slice.synth) return;
doDiff(slice, 0.000001, {
layerDiff(slice, 0.000001, {
sla: true,
fakedown: !process.slaOpenBase
});

View file

@ -8,7 +8,7 @@ import '../../add/three.js';
import { base } from '../../geo/base.js';
import { codec, encode, encodePointArray } from '../core/codec.js';
import { doTopShells } from '../mode/fdm/post.js';
import { layerProcessTop } from '../mode/fdm/post.js';
import { newPoint } from '../../geo/point.js';
import { newWidget } from '../core/widget.js';
import { polygons as POLY } from '../../geo/polygons.js';
@ -92,7 +92,7 @@ const funcs = self.minion = {
topShells(data, seq) {
let top = codec.decode(data.top, {full: true});
let {z, count, offset1, offsetN, fillOffset, opt} = data;
doTopShells(z, top, count, offset1, offsetN, fillOffset, opt);
layerProcessTop(z, top, count, offset1, offsetN, fillOffset, opt);
let state = { zeros: [] };
reply({ seq, top: codec.encode(top, {full: true}) }, state.zeros);
},