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 { Point } from '../../../geo/point.js';
import { Slope } from '../../../geo/slope.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 * may run in minion or worker context. do not create objects
* that will not quickly encode in threaded mode. add to existing * that will not quickly encode in threaded mode. add to existing
* data object. return is ignored. * data object. return is ignored.
*/ */
export function slicePost(data, options) { function slicerPostProcessor(data, options) {
const { groups, z } = data; const { groups, z } = data;
const { post_args, useAssembly, zIndexes } = options; const { post_args, useAssembly, zIndexes } = options;
const { process, vaseMode } = post_args; const { process, vaseMode } = post_args;
@ -81,7 +84,7 @@ export function slicePost(data, options) {
const nutops = []; const nutops = [];
// co-locate shell processing with top generation in slicer // co-locate shell processing with top generation in slicer
for (let top of data.tops) { 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, thinType,
vaseMode, vaseMode,
useAssembly useAssembly
@ -687,7 +690,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) {
* may run in minion or worker context. performs shell offsetting * may run in minion or worker context. performs shell offsetting
* including thin wall detection when enabled * 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 // pretend we're a top object in minions
if (!top.poly) { if (!top.poly) {
top = { poly: top }; top = { poly: top };

View file

@ -1,7 +1,6 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */ /** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { base, util } from '../../../geo/base.js'; import { layerProcessTop } from './post.js';
import { doTopShells } from './post.js';
import { fill, fill_fixed } from './fill.js'; import { fill, fill_fixed } from './fill.js';
import { generateBeltAnchor, embossBeltPooch, finalizeBeltBounds } from './belt.js'; import { generateBeltAnchor, embossBeltPooch, finalizeBeltBounds } from './belt.js';
import { getRangeParameters } from './driver.js'; import { getRangeParameters } from './driver.js';
@ -10,8 +9,7 @@ import { newPolygon } from '../../../geo/polygon.js';
import { newSlice } from '../../core/slice.js'; import { newSlice } from '../../core/slice.js';
import { polygons as POLY } from '../../../geo/polygons.js'; import { polygons as POLY } from '../../../geo/polygons.js';
import { slice, sliceZ } from '../../../geo/slicer.js'; import { slice, sliceZ } from '../../../geo/slicer.js';
import { util } from '../../../geo/base.js';
const { config } = base;
const CONSTANTS = { const CONSTANTS = {
// Support fill // Support fill
@ -174,55 +172,6 @@ function vopt(opt, ctx) {
return opt; 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 * DRIVER SLICE CONTRACT
* *
@ -258,7 +207,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
shellOffset = lineWidth, shellOffset = lineWidth,
fillSpacing = lineWidth, fillSpacing = lineWidth,
fillOffset = lineWidth * fillOffsetMult, fillOffset = lineWidth * fillOffsetMult,
clipOffset = process.sliceSupportOffset, clipOffset = process.sliceSupportOffset + lineWidth / 2,
sliceFillAngle = process.sliceFillAngle, sliceFillAngle = process.sliceFillAngle,
supportDensity = process.sliceSupportDensity, supportDensity = process.sliceSupportDensity,
{ sliceCompInner, sliceCompOuter } = process; { 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, { slice(points, {
strict: true, strict: true,
debug: process.xray, debug: process.xray,
@ -372,7 +323,13 @@ export function sliceOne(settings, widget, onupdate, ondone) {
onupdate(update) { onupdate(update) {
return onupdate(0.0 + update * 0.5) 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 // post process slices and re-incorporate missing meta-data
return output.slices.map(data => { return output.slices.map(data => {
let { z, clip, lines, groups, changes } = data; let { z, clip, lines, groups, changes } = data;
@ -397,9 +354,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
} }
return slice; return slice;
}).filter(s => s); }).filter(s => s);
}).then(slices => { }
return onSliceDone(slices);
}).then(ondone);
// z index generator // z index generator
function zGen(zopt) { function zGen(zopt) {
@ -483,9 +438,9 @@ export function sliceOne(settings, widget, onupdate, ondone) {
return zi; return zi;
} }
async function onSliceDone(slices) { async function processSlices(slices) {
/** /**
* Process automatic shadow-based support generation * Process shadow-based support generation
*/ */
async function processAutoSupport() { async function processAutoSupport() {
if (process.sliceSupportType !== 'automatic') { 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 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) { for (let slice of stack) {
let shadow = await widget.shadowAt(slice.z, true); let shadow = await widget.shadowAt(slice.z, true);
if (process.sliceSupportExtra) { if (process.sliceSupportExtra) {
shadow = POLY.offset(shadow, process.sliceSupportExtra); shadow = POLY.offset(shadow, process.sliceSupportExtra);
} }
slice.supports = shadow; 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() { function processSolidLayers() {
forSlices(0.15, 0.2, slice => { forSlices(0.15, 0.2, slice => {
@ -527,7 +487,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
let solidWidth = isSolid ? range.sliceFillWidth || 1 : 0; let solidWidth = isSolid ? range.sliceFillWidth || 1 : 0;
if (solidWidth) { if (solidWidth) {
let fillSpace = fillSpacing * solidWidth; let fillSpace = fillSpacing * solidWidth;
doSolidLayerFill({ slice, spacing: fillSpace, angle: sliceFillAngle }); layerMakeSolid({ slice, spacing: fillSpace, angle: sliceFillAngle });
} }
if (slice.index === slices.length - 1) { if (slice.index === slices.length - 1) {
slice.isFlatsLayer = true; slice.isFlatsLayer = true;
@ -544,15 +504,16 @@ export function sliceOne(settings, widget, onupdate, ondone) {
* Process layer diffs and project solid areas * Process layer diffs and project solid areas
*/ */
function processLayerDiffs() { function processLayerDiffs() {
// boolean diff layers to detect bridges and flats
profileStart("delta"); profileStart("delta");
forSlices(0.2, 0.34, slice => { forSlices(0.2, 0.34, slice => {
let params = slice.params || process; let params = slice.params || process;
let solidMinArea = params.sliceSolidMinArea; let solidMinArea = params.sliceSolidMinArea;
let sliceFillGrow = params.sliceFillGrow; let sliceFillGrow = params.sliceFillGrow;
doDiff(slice, { min: solidMinArea, grow: sliceFillGrow }); layerDiff(slice, { min: solidMinArea, grow: sliceFillGrow });
}, "layer deltas"); }, "layer deltas");
profileEnd(); profileEnd();
// project bridges and flats up and down into part
profileStart("delta-project"); profileStart("delta-project");
forSlices(0.34, 0.35, slice => { forSlices(0.34, 0.35, slice => {
let params = slice.params || process; let params = slice.params || process;
@ -560,6 +521,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
bottomLayers = params.sliceBottomLayers || 0; bottomLayers = params.sliceBottomLayers || 0;
if (topLayers) projectFlats(slice, topLayers); if (topLayers) projectFlats(slice, topLayers);
if (bottomLayers) projectBridges(slice, bottomLayers); if (bottomLayers) projectBridges(slice, bottomLayers);
if (slice.flats) POLY.setZ(slice.flats, slice.z);
}, "layer deltas"); }, "layer deltas");
profileEnd(); profileEnd();
} }
@ -572,11 +534,10 @@ export function sliceOne(settings, widget, onupdate, ondone) {
let promises = isConcurrent ? [] : undefined; let promises = isConcurrent ? [] : undefined;
forSlices(0.35, promises ? 0.4 : 0.5, slice => { forSlices(0.35, promises ? 0.4 : 0.5, slice => {
let params = slice.params || process; let params = slice.params || process;
let first = slice.index === 0;
let solidWidth = params.sliceFillWidth || 1; let solidWidth = params.sliceFillWidth || 1;
let fillSpace = fillSpacing * solidWidth; let fillSpace = fillSpacing * solidWidth;
let solidMinArea = params.sliceSolidMinArea; 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; sliceFillAngle = (sliceFillAngle + 90.0) % 360;
}, "fill solids"); }, "fill solids");
// very last layer (top) is set to finish solid rate // very last layer (top) is set to finish solid rate
@ -601,7 +562,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
return; return;
} }
let newType = params.sliceFillType; let newType = params.sliceFillType;
doSparseLayerFill(slice, { layerSparseFill(slice, {
settings, settings,
process, process,
device, device,
@ -642,7 +603,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
forSlices(0.8, promises ? 0.88 : 0.9, slice => { forSlices(0.8, promises ? 0.88 : 0.9, slice => {
let params = slice.params || process; let params = slice.params || process;
let density = params.sliceSupportDensity; let density = params.sliceSupportDensity;
doSupportFill({ layerSupportFill({
angle: process.sliceSupportFill, angle: process.sliceSupportFill,
density, density,
gap: process.sliceSupportGap, gap: process.sliceSupportGap,
@ -765,9 +726,6 @@ export function sliceOne(settings, widget, onupdate, ondone) {
slice.params = getRangeParameters(process, slice.index); 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 // reset for solids, support projections
// and other annotations // and other annotations
slices.forEach(slice => { 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) // add lead in anchor when specified in belt mode (but not for synths)
if (isBelt) { if (isBelt) {
let { slope } = widget.belt;
// Generate belt anchor // Generate belt anchor
generateBeltAnchor({ generateBeltAnchor({
slices, slices,
@ -841,7 +797,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
if (render) { if (render) {
forSlices(0.9, 1.0, slice => { forSlices(0.9, 1.0, slice => {
let params = slice.params || process; let params = slice.params || process;
doRender(slice, params, { layerRender(slice, params, {
dark: controller.dark, dark: controller.dark,
devel: controller.devel, devel: controller.devel,
renderContext renderContext
@ -899,7 +855,7 @@ function bound(v,min,max) {
* @param {boolean} opt.devel - Enable development visualization * @param {boolean} opt.devel - Enable development visualization
* @param {RenderContext} opt.renderContext - Rendering context * @param {RenderContext} opt.renderContext - Rendering context
*/ */
function doRender(slice, params, opt = {}) { function layerRender(slice, params, opt = {}) {
const { dark, devel, renderContext: ctx } = opt; const { dark, devel, renderContext: ctx } = opt;
const Color = dark ? COLOR_DARK : COLOR; const Color = dark ? COLOR_DARK : COLOR;
const output = slice.output(); const output = slice.output();
@ -961,15 +917,15 @@ function doRender(slice, params, opt = {}) {
}); });
if (isThin && devel) { if (isThin && devel) {
if (slice.solids && slice.solids.length) output if (slice.solids?.length) output
.setLayer("solids", { face: 0xbbbb00, check: 0xbbbb00 }) .setLayer("solids", { face: 0xbbbb00, check: 0xbbbb00 })
.addAreas(slice.solids); .addAreas(slice.solids);
if (slice.bridges && slice.bridges.length) output if (slice.bridges?.length) output
.setLayer("bridges", { face: 0x00cccc, line: 0x00cccc, check: 0x00cccc }) .setLayer("bridges", { face: 0x00cccc, line: 0x00cccc, check: 0x00cccc })
.addAreas(slice.bridges); .addAreas(slice.bridges);
if (slice.flats && slice.flats.length) output if (slice.flats?.length) output
.setLayer("flats", { face: 0xaa00aa, line: 0xaa00aa, check: 0xaa00aa }) .setLayer("flats", { face: 0xaa00aa, line: 0xaa00aa, check: 0xaa00aa })
.addAreas(slice.flats); .addAreas(slice.flats);
} }
@ -1004,6 +960,55 @@ function doRender(slice, params, opt = {}) {
// console.log(slice.index, slice.render.stats); // 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 * 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 * 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 {number} fillOffset
* @param {Obejct} options * @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) { 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.spacing - Fill line spacing in mm
* @param {number} args.angle - Fill angle in degrees * @param {number} args.angle - Fill angle in degrees
*/ */
function doSolidLayerFill(args) { function layerMakeSolid({ slice, spacing, angle }) {
const { slice, spacing, angle } = args;
if (slice.xray) { if (slice.xray) {
return; return;
} }
@ -1064,7 +1067,7 @@ function doSolidLayerFill(args) {
* Take output from pluggable sparse infill algorithm and clip to * Take output from pluggable sparse infill algorithm and clip to
* the bounds of the top polygons and their inner solid areas. * 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') { if (slice.xray || options.type === 'none') {
return; return;
} }
@ -1239,7 +1242,7 @@ function doSparseLayerFill(slice, options = {}) {
* Used to calculate bridges, flats and then solid projections. * Used to calculate bridges, flats and then solid projections.
* 'expand' is used for top offsets in SLA mode * '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; const { sla, fakedown, grow, min } = options;
if ((slice.index <= 0 && !fakedown) || slice.xray) { if ((slice.index <= 0 && !fakedown) || slice.xray) {
return; return;
@ -1277,47 +1280,7 @@ export function doDiff(slice, options = {}) {
bridges.appendAll(newBridges); bridges.appendAll(newBridges);
flats.appendAll(newFlats); 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 * 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 * @param {Promise[]} [args.promises] - Array for async operations
* @return {boolean} true if filled, false if not * @return {boolean} true if filled, false if not
*/ */
function doSolidsFill(args) { function layerFillSolids({ slice, spacing, angle, minArea, promises: fillQ }) {
const { slice, spacing, angle, minArea, promises: fillQ } = args;
let minarea = minArea || 1, let minarea = minArea || 1,
tops = slice.tops, tops = slice.tops,
solids = slice.solids; solids = slice.solids;
@ -1406,7 +1368,7 @@ function doSolidsFill(args) {
for (let top of tops) { for (let top of tops) {
top.solids = []; top.solids = [];
} }
doSolidLayerFill({ slice, spacing, angle }); layerMakeSolid({ slice, spacing, angle });
return; return;
} }
@ -1435,12 +1397,12 @@ function doSolidsFill(args) {
} }
} }
if (tofill.length > 0) { 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) { if (angfill.length > 0) {
top.fill_lines_ang = {spacing:spacing,list:[],poly:[]}; top.fill_lines_ang = {spacing:spacing,list:[],poly:[]};
for (let af of angfill) { for (let af of angfill) {
doFillArea({ areaFill({
promises: fillQ, promises: fillQ,
polys: [af], polys: [af],
angle: af.fillang.angle + CONSTANTS.ANGLED_FILL_ROTATION, 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.minLen] - Minimum line length
* @param {number} [args.maxLen] - Maximum line length * @param {number} [args.maxLen] - Maximum line length
*/ */
function doFillArea(args) { function areaFill({ promises: fillQ, polys, angle, spacing, output, minLen, maxLen }) {
const { promises: fillQ, polys, angle, spacing, output, minLen, maxLen } = args;
if (fillQ) { if (fillQ) {
fillQ.push(self.kiri_worker.minions.fill(polys, angle, spacing, output, minLen, maxLen)); fillQ.push(self.kiri_worker.minions.fill(polys, angle, spacing, output, minLen, maxLen));
} else { } else {
@ -1512,14 +1473,13 @@ function doFillArea(args) {
* Each polygon has a .fill property with fill lines * Each polygon has a .fill property with fill lines
* @modifies {Slice} slice.supportOutline - Stores support outline polygons * @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_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_WIDE - Auto angle for wide polygons (1090 = 90°)
* @see CONSTANTS.SUPPORT_AUTO_ANGLE_TALL - Auto angle for tall polygons (1000 = 0°) * @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) * @see CONSTANTS.SUPPORT_CONNECT_DISTANCE_MULT - Distance multiplier for line connection (2x)
*/ */
function doSupportFill(args) { function layerSupportFill({ promises, slice, lineWidth, density, minArea, isBelt, angle, outline, gap }) {
const { promises, slice, lineWidth, density, minArea, isBelt, angle, outline, gap } = args;
let supports = slice.supports, let supports = slice.supports,
min = minArea || 0.1, min = minArea || 0.1,
sub; sub;
@ -1546,7 +1506,7 @@ function doFillArea(args) {
} }
if (supports) { if (supports) {
fillSupportPolys({ supportPolyFill({
promises, polys: supports, lineWidth, density, z: slice.z, isBelt, angle, outline 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 * - Lines are either individual segments (outline=true) or
* connected paths (outline=false) for travel reduction * connected paths (outline=false) for travel reduction
* *
* @see doSupportFill - Parent function that calls this after clipping/union * @see layerSupportFill - Parent function that calls this after clipping/union
* @see doFillArea - Lower-level function that generates the actual scan lines * @see areaFill - Lower-level function that generates the actual scan lines
* @see connect_lines - Utility that connects nearby line segments * @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_WIDE - Auto angle for wide polygons (1090 = 90°)
* @see CONSTANTS.SUPPORT_AUTO_ANGLE_TALL - Auto angle for tall polygons (1000 = 0°) * @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) * @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 // calculate fill density
let spacing = lineWidth * (1 / density); let spacing = lineWidth * (1 / density);
for (let poly of polys) { 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}); let inset = POLY.offset([poly], -lineWidth * CONSTANTS.SUPPORT_INSET_RATIO, {flat: true, z, wasm: true});
// do the fill // do the fill
if (inset && inset.length > 0) { if (inset && inset.length > 0) {
doFillArea({ areaFill({
angle: angle || auto, angle: angle || auto,
output: poly.fill = [], output: poly.fill = [],
polys: inset, 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.up - Project upward (true) or downward (false)
* @param {boolean} args.first - First projection (hint fill angles) * @param {boolean} args.first - First projection (hint fill angles)
*/ */
function projectSolid(args) { function projectSolid({ slice, polys, count, up, first }) {
const { slice, polys, count, up, first } = args;
if (slice && !up && count === 1) { if (slice && !up && count === 1) {
slice.isBridgeLayer = true; slice.isBridgeLayer = true;
} }
if (!slice || count <= 0) { if (!slice || count <= 0) {
return; return;
} }
let clones = polys.clone(true); let clones = polys.clone(true);
if (first) { if (first) {
clones.forEach(function(p) { for (let clone of clones) {
p.hintFillAngle(); 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 (count > 0) {
if (up) projectSolid({ slice: slice.up, polys, count: count-1, up: true, first: false }); projectSolid({
else projectSolid({ slice: slice.down, polys, count: count-1, up: false, first: false }); 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 { newPolygon } from '../../../geo/polygon.js';
import { polygons as POLY } from '../../../geo/polygons.js'; import { polygons as POLY } from '../../../geo/polygons.js';
import { newSlice, newTop } from '../../core/slice.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 { PNG } from '../../../ext/pngjs.esm.js';
import { SLA } from './driver.js'; import { SLA } from './driver.js';
@ -149,14 +149,14 @@ export function sla_slice(settings, widget, onupdate, ondone) {
work_remain = work_total; work_remain = work_total;
forSlices(slices, 5, (slice,index) => { forSlices(slices, 5, (slice,index) => {
if (process.slaShell) { if (process.slaShell) {
doShells(slice, 2, 0, process.slaShell); layerProcessTops(slice, 2, 0, process.slaShell);
} else { } else {
doShells(slice, 1, 0); layerProcessTops(slice, 1, 0);
} }
}, "shells"); }, "shells");
forSlices(slices, 10, (slice) => { forSlices(slices, 10, (slice) => {
if (slice.synth) return; if (slice.synth) return;
doDiff(slice, 0.000001, { layerDiff(slice, 0.000001, {
sla: true, sla: true,
fakedown: !process.slaOpenBase fakedown: !process.slaOpenBase
}); });

View file

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