refactor slice progress tracking, comment out ununsed support var, tighten slice shadow range for fdm

This commit is contained in:
Stewart Allen 2026-01-12 19:14:08 -05:00
commit 69fdf1a60b
4 changed files with 95 additions and 50 deletions

View file

@ -345,7 +345,7 @@ export const conf = {
sliceSupportNozzle: 0, sliceSupportNozzle: 0,
sliceSupportOffset: 1.0, sliceSupportOffset: 1.0,
sliceSupportOutline: true, sliceSupportOutline: true,
sliceSupportSpan: 5, // sliceSupportSpan: 5,
sliceSupportType: "disabled", sliceSupportType: "disabled",
sliceSupportTree: false, sliceSupportTree: false,
sliceTopLayers: 3, sliceTopLayers: 3,

View file

@ -786,7 +786,7 @@ class Widget {
return this.cache.shadow = stack; return this.cache.shadow = stack;
} }
async computeShadowStack(zlist, progress, pocket) { async computeShadowStack(zlist, progress, pocket, up = 1) {
let shadow_stack = this.cache.shadow_stack; let shadow_stack = this.cache.shadow_stack;
if (!shadow_stack) { if (!shadow_stack) {
shadow_stack = this.cache.shadow_stack = {}; shadow_stack = this.cache.shadow_stack = {};
@ -803,7 +803,7 @@ class Widget {
let p = work.minions.queueAsync({ let p = work.minions.queueAsync({
cmd: 'cam_shadow_z', cmd: 'cam_shadow_z',
z: z - 0.005, z: z - 0.005,
t: z + 1 t: z + up
}).then(reply => { }).then(reply => {
shadow_stack[z - 0.005] = decode(reply.data); shadow_stack[z - 0.005] = decode(reply.data);
pval += pinc; pval += pinc;

View file

@ -124,9 +124,10 @@ export function menu() {
sliceSupportGap: newInput(LANG.sp_gaps_s, {title:LANG.sp_gaps_l, convert:toInt, bound:bound(0,5)}), sliceSupportGap: newInput(LANG.sp_gaps_s, {title:LANG.sp_gaps_l, convert:toInt, bound:bound(0,5)}),
sliceSupportOffset: newInput(LANG.sp_offs_s, {title:LANG.sp_offs_l, convert:toFloat, bound:bound(0.0,200.0)}), sliceSupportOffset: newInput(LANG.sp_offs_s, {title:LANG.sp_offs_l, convert:toFloat, bound:bound(0.0,200.0)}),
sliceSupportExtra: newInput(LANG.sp_xpnd_s, {title:LANG.sp_xpnd_l, convert:toFloat, bound:bound(0.0,10.0)}), sliceSupportExtra: newInput(LANG.sp_xpnd_s, {title:LANG.sp_xpnd_l, convert:toFloat, bound:bound(0.0,10.0)}),
sliceSupportSpan: newInput(LANG.sp_span_s, {title:LANG.sp_span_l, convert:toFloat, bound:bound(0.0,200.0), show:() => ui.sliceSupportEnable.checked }), // sliceSupportSpan: newInput(LANG.sp_span_s, {title:LANG.sp_span_l, convert:toFloat, bound:bound(0.0,200.0), show:() => ui.sliceSupportEnable.checked }),
separator: newBlank({ class:"set-sep", driven }), separator: newBlank({ class:"set-sep", driven }),
sliceSupportOutline: newBoolean(LANG.sp_outl_s, onBooleanClick, {title:LANG.sp_outl_l, xshow: () => !isTree() }), sliceSupportOutline: newBoolean(LANG.sp_outl_s, onBooleanClick, {title:LANG.sp_outl_l, xshow: () => !isTree() }),
sliceSupportTree: newBoolean(LANG.sp_tree_s, onBooleanClick, {title:LANG.sp_tree_l }),
separator: newBlank({ class:"set-sep", driven, show:manualSupport }), separator: newBlank({ class:"set-sep", driven, show:manualSupport }),
sliceSupportManual: newRow([ sliceSupportManual: newRow([
(ui.ssmAdd = newButton(undefined, onButtonClick, {icon:'<i class="fas fa-plus"></i>'})), (ui.ssmAdd = newButton(undefined, onButtonClick, {icon:'<i class="fas fa-plus"></i>'})),

View file

@ -173,6 +173,29 @@ function vopt(opt, ctx) {
return opt; return opt;
} }
/**
* return percentage values broken into ranges
*
* @param {number} plo 0.0-1.0 percentage value
* @param {number} phi 0.0-1.0 high percentage value
* @param {Array} pcts [{ lo, hi }, ...]
*/
function divide(plo, phi, pcts) {
let sum = 0;
let lo = plo;
let rval = pcts.map(pct => {
sum += pct;
let diff = (phi - plo) * pct;
let rval = { lo, hi: lo + diff };
lo += diff;
return rval;
});
if (Math.abs(1 - sum) > 0.001) {
console.log('SUM FAIL', { rval, sum });
}
return rval;
}
/** /**
* DRIVER SLICE CONTRACT * DRIVER SLICE CONTRACT
* *
@ -441,17 +464,18 @@ export function sliceOne(settings, widget, onupdate, ondone) {
}).filter(s => s); }).filter(s => s);
} }
// slicing is the first 50% of the update "time"
function trackupdate(pct, from, to, msg) {
// console.log(from.round(2), to.round(2), msg);
onupdate(0.5 + (from + (pct * (to - from))) * 0.5, msg);
}
// calculate % complete and call onupdate() // calculate % complete and call onupdate()
function doupdate(index, from, to, msg) { function doupdate(index, from, to, msg) {
trackupdate(index / slices.length, from, to, msg); trackupdate(index / slices.length, from, to, msg);
} }
// slicing is the first 50% of the update "time" // for each slice, perform a function and call doupdate()
function trackupdate(pct, from, to, msg) {
onupdate(0.5 + (from + (pct * (to - from))) * 0.5, msg);
}
// for each slice, performe a function and call doupdate()
function forSlices(from, to, fn, msg) { function forSlices(from, to, fn, msg) {
slices.forEach(slice => { slices.forEach(slice => {
fn(slice); fn(slice);
@ -462,11 +486,12 @@ export function sliceOne(settings, widget, onupdate, ondone) {
/** /**
* Process automatic and manual shadow-based support generation * Process automatic and manual shadow-based support generation
*/ */
async function processSupports() { async function processSupports(plo, phi) {
if (process.sliceSupportType === 'disabled') { if (process.sliceSupportType === 'disabled') {
return; return;
} }
let div = divide(plo, phi, [ 0.5, 0.5 ]);
let stack = slices.slice(); let stack = slices.slice();
let indices = stack.map(s => s.z); let indices = stack.map(s => s.z);
let zAngNorm = Math.sin(process.sliceSupportAngle * Math.PI / 180); let zAngNorm = Math.sin(process.sliceSupportAngle * Math.PI / 180);
@ -517,8 +542,8 @@ export function sliceOne(settings, widget, onupdate, ondone) {
// create automatic shadows supports when on manual paint // create automatic shadows supports when on manual paint
if (!manual) { if (!manual) {
await widget.computeShadowStack(indices, progress => { await widget.computeShadowStack(indices, progress => {
trackupdate(progress, 0.05, 0.10, "shadow"); trackupdate(progress, div[0].lo, div[0].hi, "shadow");
}, zAngNorm); }, zAngNorm, sliceHeight);
for (let slice of stack) { for (let slice of stack) {
slice.shadow = await widget.shadowAt(slice.z, true); slice.shadow = await widget.shadowAt(slice.z, true);
@ -532,9 +557,15 @@ export function sliceOne(settings, widget, onupdate, ondone) {
let length = stack.length; let length = stack.length;
let count = 0; let count = 0;
// convert shadows to trees, when specified
if (process.sliceSupportTree) {
// console.log('TREE OUTPUT');
}
// perform accumulation top down // perform accumulation top down
for (let slice of stack.reverse()) { for (let slice of stack.reverse()) {
let shadow = slice.shadow ?? []; let shadow = slice.shadow ?? [];
if (devel) slice.output().setLayer("shadow", 0xff0000).addPolys(shadow.clone(true));
if (process.sliceSupportExtra) { if (process.sliceSupportExtra) {
shadow = POLY.offset(shadow, process.sliceSupportExtra); shadow = POLY.offset(shadow, process.sliceSupportExtra);
} }
@ -545,11 +576,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
// subtract slice.clips areas (widget boundaries) from shadow projection // subtract slice.clips areas (widget boundaries) from shadow projection
if (true) { if (true) {
let rem = []; let rem = [];
let clips = [ let clips = [ slice.clips ].filter(v => v).flat();
slice.up?.clips,
slice.clips,
slice.down?.clips
].filter(v => v).flat();
clips = POLY.union(clips, minArea, true); clips = POLY.union(clips, minArea, true);
POLY.subtract(shadow, clips, rem, null, slice.z, minArea, { wasm: false }); POLY.subtract(shadow, clips, rem, null, slice.z, minArea, { wasm: false });
shadow = rem; shadow = rem;
@ -573,8 +600,22 @@ export function sliceOne(settings, widget, onupdate, ondone) {
if (devel) slice.output().setLayer("belt clip", 0xffff00).addPolys([ clip ]); if (devel) slice.output().setLayer("belt clip", 0xffff00).addPolys([ clip ]);
} }
slice.supports = shadow; slice.supports = shadow;
if (devel) slice.output().setLayer("shadow", 0xff0000).addPolys(shadow); // if (devel) slice.output().setLayer("shadow", 0xff0000).addPolys(shadow);
trackupdate((++count/length), 0.10, 0.15, "support"); trackupdate((++count/length), div[1].lo, div[1].hi, "support");
}
// clip shadow to layers above/below clip areas (for part separation)
for (let slice of stack.reverse()) {
let clips = [
slice.up?.clips,
slice.down?.clips
].filter(v => v).flat();
if (clips.length) {
let rem = [];
clips = POLY.union(clips, minArea, true);
POLY.subtract(slice.supports, clips, rem, null, slice.z, minArea, { wasm: false });
slice.supports = rem;
}
} }
} }
@ -582,8 +623,8 @@ export function sliceOne(settings, widget, onupdate, ondone) {
* Process top and bottom layers or any other * Process top and bottom layers or any other
* layers detected and marked for solid fill * layers detected and marked for solid fill
*/ */
async function processSolidLayers() { async function processSolidLayers(plo, phi) {
forSlices(0.15, 0.2, slice => { forSlices(plo, phi, slice => {
let range = slice.params; let range = slice.params;
let isBottom = slice.index < bottomLayers; let isBottom = slice.index < bottomLayers;
let isTop = topLayers && slice.index > slices.length - topLayers - 1; let isTop = topLayers && slice.index > slices.length - topLayers - 1;
@ -604,10 +645,11 @@ export function sliceOne(settings, widget, onupdate, ondone) {
/** /**
* Process layer diffs and project solid areas * Process layer diffs and project solid areas
*/ */
async function processLayerDiffs() { async function processLayerDiffs(plo, phi) {
let div = divide(plo, phi, [ 0.9, 0.05, 0.05 ]);
// boolean diff layers to detect bridges and flats // boolean diff layers to detect bridges and flats
profileStart("delta"); profileStart("delta");
forSlices(0.2, 0.33, slice => { forSlices(div[0].lo, div[1].hi, slice => {
let params = slice.params || process; let params = slice.params || process;
let solidMinArea = params.sliceSolidMinArea; let solidMinArea = params.sliceSolidMinArea;
let sliceMinThick = params.sliceSolidMinThick; let sliceMinThick = params.sliceSolidMinThick;
@ -617,7 +659,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
profileEnd(); profileEnd();
// project bridges and flats up and down into part // project bridges and flats up and down into part
profileStart("delta-project"); profileStart("delta-project");
forSlices(0.33, 0.34, slice => { forSlices(div[1].lo, div[1].hi, slice => {
let params = slice.params || process; let params = slice.params || process;
topLayers = params.sliceTopLayers || 0; topLayers = params.sliceTopLayers || 0;
bottomLayers = params.sliceBottomLayers || 0; bottomLayers = params.sliceBottomLayers || 0;
@ -628,7 +670,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
profileEnd(); profileEnd();
// union solid areas // union solid areas
profileStart("solid-union"); profileStart("solid-union");
forSlices(0.34, 0.35, slice => { forSlices(div[2].lo, div[2].hi, slice => {
if (slice.solids) { if (slice.solids) {
slice.solids = POLY.union(slice.solids, 0, true); slice.solids = POLY.union(slice.solids, 0, true);
} }
@ -639,10 +681,11 @@ export function sliceOne(settings, widget, onupdate, ondone) {
/** /**
* Process solid fill patterns * Process solid fill patterns
*/ */
async function processSolidFills() { async function processSolidFills(plo, phi) {
profileStart("solid-fill") profileStart("solid-fill")
let promises = isConcurrent ? [] : undefined; let promises = isConcurrent ? [] : undefined;
forSlices(0.35, promises ? 0.4 : 0.5, slice => { let div = divide(plo, phi, promises ? [ 0.8, 0.2 ] : [ 1 ]);
forSlices(div[0].lo, div[0].hi, slice => {
let params = slice.params || process; let params = slice.params || process;
let solidWidth = params.sliceFillWidth || 1; let solidWidth = params.sliceFillWidth || 1;
let fillSpace = fillSpacing * solidWidth; let fillSpace = fillSpacing * solidWidth;
@ -654,7 +697,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
slices.last().finishSolids = true slices.last().finishSolids = true
if (promises) { if (promises) {
await tracker(promises, (i, t) => { await tracker(promises, (i, t) => {
trackupdate(i / t, 0.4, 0.5); trackupdate(i / t, div[1].lo, div[1].hi);
}); });
} }
profileEnd(); profileEnd();
@ -663,10 +706,11 @@ export function sliceOne(settings, widget, onupdate, ondone) {
/** /**
* Process sparse infill patterns * Process sparse infill patterns
*/ */
async function processSparseInfill() { async function processSparseInfill(plo, phi) {
let lastType; let lastType;
let promises = isConcurrent ? [] : undefined; let promises = isConcurrent ? [] : undefined;
forSlices(0.5, promises ? 0.55 : 0.7, slice => { let div = divide(plo, phi, promises ? [ 0.8, 0.2 ] : [ 1 ]);
forSlices(div[0].lo, div[0].hi, slice => {
let params = slice.params || process; let params = slice.params || process;
if (!params.sliceFillSparse) { if (!params.sliceFillSparse) {
return; return;
@ -689,7 +733,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
}, "infill"); }, "infill");
if (promises) { if (promises) {
await tracker(promises, (i, t) => { await tracker(promises, (i, t) => {
trackupdate(i / t, 0.55, 0.7); trackupdate(i / t, div[1].lo, div[1].hi);
}); });
} }
// filter out tiny fill points less than nozzle diameter // filter out tiny fill points less than nozzle diameter
@ -715,10 +759,11 @@ export function sliceOne(settings, widget, onupdate, ondone) {
/** /**
* Process support structure fills * Process support structure fills
*/ */
async function processSupportFills() { async function processSupportFills(plo, phi) {
profileStart("support-fill"); profileStart("support-fill");
let promises = false && isConcurrent ? [] : undefined; let promises = false && isConcurrent ? [] : undefined;
forSlices(0.8, promises ? 0.88 : 0.9, slice => { let div = divide(plo, phi, promises ? [ 0.8, 0.2 ] : [ 1 ]);
forSlices(div[0].lo, div[0].hi, slice => {
let params = slice.params || process; let params = slice.params || process;
let density = params.sliceSupportDensity; let density = params.sliceSupportDensity;
layerSupportFill({ layerSupportFill({
@ -733,7 +778,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
}, "support fill"); }, "support fill");
if (promises) { if (promises) {
await tracker(promises, (i, t) => { await tracker(promises, (i, t) => {
trackupdate(i / t, 0.88, 0.9); trackupdate(i / t, div[1].lo, div[1].hi);
}); });
} }
profileEnd(); profileEnd();
@ -788,8 +833,8 @@ export function sliceOne(settings, widget, onupdate, ondone) {
slices.forEach((s,i) => s.index = i); slices.forEach((s,i) => s.index = i);
} }
async function renderSlices() { async function renderSlices(plo, phi) {
forSlices(0.9, 1.0, slice => { forSlices(plo, phi, slice => {
let params = slice.params || process; let params = slice.params || process;
layerRender(slice, params, { layerRender(slice, params, {
dark: controller.dark, dark: controller.dark,
@ -850,7 +895,7 @@ export function sliceOne(settings, widget, onupdate, ondone) {
} }
// process solid layers (top/bottom) // process solid layers (top/bottom)
await processSolidLayers(); await processSolidLayers(0.10, 0.20);
// 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) {
@ -883,10 +928,6 @@ export function sliceOne(settings, widget, onupdate, ondone) {
}); });
} }
// support generation using either
// enclosed shadow or manual painted supports
await processSupports();
// calculations only relevant when solid layers are used // calculations only relevant when solid layers are used
// layer boolean diffs need to be computed to find flat areas to fill // layer boolean diffs need to be computed to find flat areas to fill
// and overhangs that need to be supported. these are stored in flats // and overhangs that need to be supported. these are stored in flats
@ -894,24 +935,27 @@ export function sliceOne(settings, widget, onupdate, ondone) {
// for "real" objects, fill the remaining voids with sparse fill // for "real" objects, fill the remaining voids with sparse fill
// sparse layers only present when non-vase mode and sparse % > 0 // sparse layers only present when non-vase mode and sparse % > 0
if (!vaseMode) { if (!vaseMode) {
await processLayerDiffs(); await processLayerDiffs(0.2, 0.4);
await processSolidFills(); // support generation using either
await processSparseInfill(); // enclosed shadow or manual painted supports
await processSupports(0.4, 0.5);
await processSolidFills(0.5, 0.6);
await processSparseInfill(0.6, 0.8);
} }
// fill all supports (auto and manual) // fill all supports (auto and manual)
if (supportDensity) { if (supportDensity) {
await processSupportFills(); await processSupportFills(0.8, 0.84);
} }
// brick/interleave mode processing // brick/interleave mode processing
if (isBrick) { if (isBrick) {
await processBrickMode(); await processBrickMode(0.84, 0.85);
} }
// render if not explicitly disabled // render if not explicitly disabled
if (render) { if (render) {
await renderSlices(); await renderSlices(0.85, 1.0);
} }
if (isBelt) { if (isBelt) {
@ -1384,10 +1428,10 @@ export function layerDiff(slice, options = {}) {
newFlats = newFlats.filter(p => p.areaDeep() >= area && p.thickness(true) >= thick); newFlats = newFlats.filter(p => p.areaDeep() >= area && p.thickness(true) >= thick);
if (grow > 0 && newBridges.length) { if (grow > 0 && newBridges.length) {
newBridges = POLY.offset(newBridges, grow); newBridges = POLY.offset(newBridges, grow, { z: slice.z });
} }
if (grow > 0 && newFlats.length) { if (grow > 0 && newFlats.length) {
newFlats = POLY.offset(newFlats, grow); newFlats = POLY.offset(newFlats, grow, { z: slice.z });
} }
bridges.appendAll(newBridges); bridges.appendAll(newBridges);