add missing support slice height, add comments for critical code paths
This commit is contained in:
parent
397f527239
commit
38d040b190
1 changed files with 145 additions and 34 deletions
|
|
@ -368,25 +368,64 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
let purgedFirst = false;
|
let purgedFirst = false;
|
||||||
let lastPurgeTool;
|
let lastPurgeTool;
|
||||||
|
|
||||||
// generate purge block for given nozzle
|
/**
|
||||||
|
* purge() - Generate purge tower block output for a given tool
|
||||||
|
*
|
||||||
|
* Called in two contexts:
|
||||||
|
* 1. Real purge: When switching tools during printing (using=undefined)
|
||||||
|
* 2. Fill-in: At layer end to maintain unused blocks (using=actual tool index)
|
||||||
|
*
|
||||||
|
* The function consumes one purge block from the track array and generates:
|
||||||
|
* - Outline perimeter (with extrusion)
|
||||||
|
* - Fill pattern (dense for real purges, sparse for fill-ins)
|
||||||
|
* - Retract
|
||||||
|
* - Optional wipe move (outline without extrusion)
|
||||||
|
*
|
||||||
|
* @param {number} nozzle - The extruder index that needs purging/filling
|
||||||
|
* @param {Array} track - Array of available purge block records (mutated via shift())
|
||||||
|
* @param {Array} layer - Output layer array to append print moves to
|
||||||
|
* @param {Point} start - Current print head position
|
||||||
|
* @param {number} z - Z height for this layer
|
||||||
|
* @param {number} using - Optional override tool index:
|
||||||
|
* - undefined/negative: real purge (use nozzle param)
|
||||||
|
* - >= 0: fill-in mode (use this tool instead)
|
||||||
|
* @param {Object} offset - Position offset for belt printers
|
||||||
|
* @returns {Point} Updated print head position after purge block
|
||||||
|
*
|
||||||
|
* Fill pattern selection:
|
||||||
|
* - Dense fill (rec.full): Used for first layer or real tool changes
|
||||||
|
* - Sparse fill (rec.sparse): Used for fill-ins or consecutive same-tool purges
|
||||||
|
*/
|
||||||
function purge(nozzle, track, layer, start, z, using, offset) {
|
function purge(nozzle, track, layer, start, z, using, offset) {
|
||||||
if (!outputPurgeTower || extcount < 2) {
|
if (!outputPurgeTower || extcount < 2) {
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
// Get next available purge block (consumes from track array)
|
||||||
let rec = track.shift();
|
let rec = track.shift();
|
||||||
|
|
||||||
|
// Use sparse fill if: filling someone else's block OR same tool as last purge
|
||||||
let thin = using >= 0 || lastPurgeTool === nozzle;
|
let thin = using >= 0 || lastPurgeTool === nozzle;
|
||||||
|
|
||||||
|
// Actual tool to use: override tool for fill-ins, otherwise the nozzle being purged
|
||||||
let tool = using >= 0 ? using : nozzle;
|
let tool = using >= 0 ? using : nozzle;
|
||||||
|
|
||||||
|
// Detect first layer for special handling
|
||||||
let first = isBelt ? !purgedFirst && layer.slice.index >= 0 : layer.slice.index === 0;
|
let first = isBelt ? !purgedFirst && layer.slice.index >= 0 : layer.slice.index === 0;
|
||||||
let rate = first ? process.firstLayerRate : process.outputFeedrate;
|
let rate = first ? process.firstLayerRate : process.outputFeedrate;
|
||||||
let wipe = true;
|
let wipe = true;
|
||||||
lastPurgeTool = tool;
|
lastPurgeTool = tool;
|
||||||
|
|
||||||
if (rec) {
|
if (rec) {
|
||||||
print.setType('purge tower');
|
print.setType('purge tower');
|
||||||
if (layer.last()) {
|
if (layer.last()) {
|
||||||
layer.last().retract = true;
|
layer.last().retract = true;
|
||||||
}
|
}
|
||||||
purgedFirst = purgedFirst || first;
|
purgedFirst = purgedFirst || first;
|
||||||
|
|
||||||
|
// Use dense fill for first layer or real purges (when thin=false)
|
||||||
let purgeOn = first || !thin;
|
let purgeOn = first || !thin;
|
||||||
|
|
||||||
|
// Belt printer: scale tower size for negative Z (before belt contact)
|
||||||
if (isBelt && z < 0) {
|
if (isBelt && z < 0) {
|
||||||
let scale = 1 - ((z / zmin));
|
let scale = 1 - ((z / zmin));
|
||||||
mkblok(blokw * scale, blokh);
|
mkblok(blokw * scale, blokh);
|
||||||
|
|
@ -394,9 +433,12 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
rate = (process.outputFeedrate - process.firstLayerRate) * scale + process.firstLayerRate;
|
rate = (process.outputFeedrate - process.firstLayerRate) * scale + process.firstLayerRate;
|
||||||
wipe = false;
|
wipe = false;
|
||||||
}
|
}
|
||||||
|
// Prepare geometry: outline, pause point, and fill pattern (dense or sparse)
|
||||||
let box = rec.rect.clone().setZ(z);
|
let box = rec.rect.clone().setZ(z);
|
||||||
let pause = rec.pause.clone().setZ(z);
|
let pause = rec.pause.clone().setZ(z);
|
||||||
let fill = (z >= 0 && purgeOn ? rec.full : rec.sparse).clone().setZ(z);
|
let fill = (z >= 0 && purgeOn ? rec.full : rec.sparse).clone().setZ(z);
|
||||||
|
|
||||||
|
// Belt printer: apply Y/Z coordinate transformations
|
||||||
if (isBelt) {
|
if (isBelt) {
|
||||||
let bmove = {x:0, y:z, z:0};
|
let bmove = {x:0, y:z, z:0};
|
||||||
box.move(bmove);
|
box.move(bmove);
|
||||||
|
|
@ -411,6 +453,8 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
if (fill) fill.move(bo);
|
if (fill) fill.move(bo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Output sequence: 1. Outline perimeter (with extrusion)
|
||||||
start = print.polyPrintPath(box, start, layer, {
|
start = print.polyPrintPath(box, start, layer, {
|
||||||
tool,
|
tool,
|
||||||
rate,
|
rate,
|
||||||
|
|
@ -418,8 +462,9 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
open: false,
|
open: false,
|
||||||
onfirstout: (out => out.overate = (isBelt ? rate : 0))
|
onfirstout: (out => out.overate = (isBelt ? rate : 0))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Output sequence: 2. Fill pattern (with extrusion, dense or sparse)
|
||||||
if (fill && fill.length) {
|
if (fill && fill.length) {
|
||||||
// for pings, split path at 20mm
|
|
||||||
start = print.polyPrintPath(fill, start, layer, {
|
start = print.polyPrintPath(fill, start, layer, {
|
||||||
tool,
|
tool,
|
||||||
rate,
|
rate,
|
||||||
|
|
@ -428,8 +473,11 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
onfirst: (point) => { point.purgeOn = purgeOn ? pause : undefined }
|
onfirst: (point) => { point.purgeOn = purgeOn ? pause : undefined }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Output sequence: 3. Retract after fill
|
||||||
layer.last().retract = true;
|
layer.last().retract = true;
|
||||||
// experimental post-retract wipe
|
|
||||||
|
// Output sequence: 4. Post-retract wipe (outline without extrusion)
|
||||||
if (wipe) start = print.polyPrintPath(box, start, layer, {
|
if (wipe) start = print.polyPrintPath(box, start, layer, {
|
||||||
tool,
|
tool,
|
||||||
rate,
|
rate,
|
||||||
|
|
@ -468,6 +516,7 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
for (let slice of widget.slices) {
|
for (let slice of widget.slices) {
|
||||||
if (slice.supports) {
|
if (slice.supports) {
|
||||||
let nslice = newSlice(slice.z);
|
let nslice = newSlice(slice.z);
|
||||||
|
nslice.height = slice.height;
|
||||||
nslice.index = slice.index;
|
nslice.index = slice.index;
|
||||||
nslice.supports = slice.supports;
|
nslice.supports = slice.supports;
|
||||||
nslice.extruder = sliceSupportNozzle;
|
nslice.extruder = sliceSupportNozzle;
|
||||||
|
|
@ -501,26 +550,55 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
return a.z - b.z;
|
return a.z - b.z;
|
||||||
});
|
});
|
||||||
|
|
||||||
let firstTool;
|
let firstTool; // First extruder used (determines starting extruder)
|
||||||
let lastWidget;
|
let lastWidget; // Last widget printed (for retraction decisions)
|
||||||
let lastExt;
|
let lastExt; // Last extruder used (for minimizing tool changes)
|
||||||
let lastOut;
|
let lastOut; // Last output slice (for tracking state)
|
||||||
|
|
||||||
print.zmax = zmax;
|
print.zmax = zmax;
|
||||||
print.total_time = 0;
|
print.total_time = 0;
|
||||||
|
|
||||||
// walk cake layers bottom up
|
/**
|
||||||
|
* MAIN PRINT LOOP: Walk cake layers bottom up
|
||||||
|
*
|
||||||
|
* The "cake" is a sorted array of layers where each layer contains all slices
|
||||||
|
* at that Z height from all widgets. This loop processes layers in Z order.
|
||||||
|
*
|
||||||
|
* For each layer:
|
||||||
|
* 1. Process all slices using greedy nearest-neighbor with extruder penalties
|
||||||
|
* 2. Generate purge tower blocks on extruder changes
|
||||||
|
* 3. Add optional draft shield around perimeter
|
||||||
|
* 4. Fill any unused purge blocks to maintain tower structure
|
||||||
|
* 5. Append completed layer to output and update progress
|
||||||
|
*
|
||||||
|
* Key optimization: Minimize travel and extruder changes by:
|
||||||
|
* - Prioritizing slices using the same extruder (10000x distance penalty for changes)
|
||||||
|
* - Choosing nearest slice within same-extruder group
|
||||||
|
* - Starting first object with extruder 0 when possible
|
||||||
|
*/
|
||||||
for (let layer of cake) {
|
for (let layer of cake) {
|
||||||
// track purge blocks generated for each layer
|
// Clone towers array to track available purge blocks for this layer
|
||||||
|
// Each purge block can only be used once per layer
|
||||||
let track = towers.slice();
|
let track = towers.slice();
|
||||||
|
|
||||||
// iterate over layer slices, find closest widget, print, eliminate
|
/**
|
||||||
|
* INNER LOOP: Process all slices in this layer
|
||||||
|
*
|
||||||
|
* Uses greedy nearest-neighbor algorithm with extruder awareness:
|
||||||
|
* - Builds a priority queue of unprinted slices
|
||||||
|
* - Heavily penalizes extruder changes (10000x distance multiplier)
|
||||||
|
* - Selects closest slice with preferred extruder
|
||||||
|
* - Marks as prep'd and removes from consideration
|
||||||
|
* - Repeats until all slices processed (order.length === 0)
|
||||||
|
*/
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
// Build priority queue of unprinted slices with weighted distances
|
||||||
let order = [];
|
let order = [];
|
||||||
// select slices of the same extruder type first then distance
|
|
||||||
|
// Evaluate each unprinted slice in this layer
|
||||||
for (let slice of layer.slices) {
|
for (let slice of layer.slices) {
|
||||||
if (slice.prep) {
|
if (slice.prep) {
|
||||||
continue;
|
continue; // Skip already processed slices
|
||||||
}
|
}
|
||||||
let offset = lastOffset = slice.widget.offset;
|
let offset = lastOffset = slice.widget.offset;
|
||||||
let find = slice.findClosestPointTo(printPoint.sub(offset));
|
let find = slice.findClosestPointTo(printPoint.sub(offset));
|
||||||
|
|
@ -528,69 +606,91 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
let ext = slice.extruder;
|
let ext = slice.extruder;
|
||||||
let lex = lastExt;
|
let lex = lastExt;
|
||||||
let dst = Math.abs(find.distance);
|
let dst = Math.abs(find.distance);
|
||||||
// penalize extruder swaps
|
|
||||||
|
// CRITICAL: Heavily penalize extruder changes to minimize purging
|
||||||
|
// 10000x multiplier makes any same-extruder slice preferred over a swap
|
||||||
if (ext !== lex) {
|
if (ext !== lex) {
|
||||||
dst *= 10000;
|
dst *= 10000;
|
||||||
}
|
}
|
||||||
// for first object, penalize extruders other than first
|
|
||||||
|
// CRITICAL: For first slice in print, prefer extruder 0
|
||||||
|
// Ensures print starts with primary extruder when possible
|
||||||
if (lastExt === undefined && ext > 0) {
|
if (lastExt === undefined && ext > 0) {
|
||||||
dst *= 10000;
|
dst *= 10000;
|
||||||
}
|
}
|
||||||
|
|
||||||
order.push({dst, slice, offset, z: layer.z});
|
order.push({dst, slice, offset, z: layer.z});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All slices processed for this layer - exit inner loop
|
||||||
if (order.length === 0) {
|
if (order.length === 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort by weighted distance (extruder penalty + travel distance)
|
||||||
order.sort((a,b) => {
|
order.sort((a,b) => {
|
||||||
return a.dst - b.dst;
|
return a.dst - b.dst;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Select nearest slice (considering extruder penalties)
|
||||||
let { z, slice, offset } = order[0];
|
let { z, slice, offset } = order[0];
|
||||||
|
// Track the first tool used to determine print starting extruder
|
||||||
if (firstTool === undefined) {
|
if (firstTool === undefined) {
|
||||||
firstTool = slice.extruder;
|
firstTool = slice.extruder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when layers switch between widgets, force retraction
|
// Force retraction when switching between different widgets
|
||||||
|
// Prevents stringing during travel moves between objects
|
||||||
let forceRetract = lastOut && lastOut.widget !== slice.widget;
|
let forceRetract = lastOut && lastOut.widget !== slice.widget;
|
||||||
if (forceRetract && output.length) {
|
if (forceRetract && output.length) {
|
||||||
// selecet last output of last layer
|
|
||||||
output.last().last().retract = true;
|
output.last().last().retract = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get any layer-specific parameter overrides for this slice
|
||||||
let params = getRangeParameters(process, slice.index);
|
let params = getRangeParameters(process, slice.index);
|
||||||
|
|
||||||
|
// Mark slice as processed so it won't be selected again
|
||||||
slice.prep = true;
|
slice.prep = true;
|
||||||
// retract between widgets or layers (when set)
|
|
||||||
|
// Retract between widgets within the same layer
|
||||||
if (layerout.length && slice.widget !== lastWidget) {
|
if (layerout.length && slice.widget !== lastWidget) {
|
||||||
layerout.last().retract = true;
|
layerout.last().retract = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update layer metadata for this slice
|
||||||
lastWidget = slice.widget;
|
lastWidget = slice.widget;
|
||||||
layerout.z = z + slice.height / 2;
|
layerout.z = z + slice.height / 2;
|
||||||
layerout.height = layerout.height || slice.height;
|
layerout.height = layerout.height || slice.height;
|
||||||
layerout.slice = slice;
|
layerout.slice = slice;
|
||||||
layerout.params = params;
|
layerout.params = params;
|
||||||
// mark layer as anchor if slice is belt and flag set
|
layerout.anchor = slice.belt && slice.belt.anchor; // Belt anchor flag
|
||||||
layerout.anchor = slice.belt && slice.belt.anchor;
|
|
||||||
// detect extruder change and print purge block
|
// PURGE TOWER: Generate purge block on extruder change
|
||||||
|
// Only purge when switching to a different extruder
|
||||||
if (!lastOut || lastOut.extruder !== slice.extruder) {
|
if (!lastOut || lastOut.extruder !== slice.extruder) {
|
||||||
if (slice.extruder >= 0)
|
if (slice.extruder >= 0)
|
||||||
printPoint = purge(slice.extruder, track, layerout, printPoint, slice.z, undefined, offset);
|
printPoint = purge(slice.extruder, track, layerout, printPoint, slice.z, undefined, offset);
|
||||||
}
|
}
|
||||||
let wtb = slice.widget.track.box;
|
let wtb = slice.widget.track.box;
|
||||||
let beltStart = slice.belt && slice.belt.touch;// && (widgets.length === 1);
|
let beltStart = slice.belt && slice.belt.touch;
|
||||||
// output seek to start point between mesh slices if previous data
|
|
||||||
|
// PRINT THIS SLICE: Generate all toolpaths (shells, infill, etc.)
|
||||||
print.setType('layer');
|
print.setType('layer');
|
||||||
print.setWidget(lastWidget);
|
print.setWidget(lastWidget);
|
||||||
printPoint = slicePrintPath(
|
printPoint = slicePrintPath(
|
||||||
print,
|
print,
|
||||||
slice,
|
slice,
|
||||||
|
// Belt prints start from far corner, others from current position
|
||||||
beltStart ? newPoint(-5000, 5000, 0) : printPoint.sub(offset),
|
beltStart ? newPoint(-5000, 5000, 0) : printPoint.sub(offset),
|
||||||
offset,
|
offset,
|
||||||
layerout,
|
layerout,
|
||||||
{
|
{
|
||||||
first: slice.index === 0,
|
first: slice.index === 0,
|
||||||
onBelt: beltStart,
|
onBelt: beltStart,
|
||||||
params, // range parameters
|
params, // layer-specific parameter overrides
|
||||||
pretract: (wipeDist) => {
|
pretract: (wipeDist) => {
|
||||||
|
// Optional wipe move before retraction
|
||||||
if (!(lastLayer && lastLayer.length)) {
|
if (!(lastLayer && lastLayer.length)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -610,32 +710,42 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
zmax,
|
zmax,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Update print time statistics
|
||||||
layerout.print_time = printPoint.layer_time;
|
layerout.print_time = printPoint.layer_time;
|
||||||
print.total_time += printPoint.layer_time;
|
print.total_time += printPoint.layer_time;
|
||||||
print.setWidget(null);
|
print.setWidget(null);
|
||||||
|
|
||||||
|
// Update state for next iteration
|
||||||
lastOut = slice;
|
lastOut = slice;
|
||||||
lastExt = lastOut.extruder;
|
lastExt = lastOut.extruder;
|
||||||
lastPoly = slice.lastPoly;
|
lastPoly = slice.lastPoly;
|
||||||
lastLayer = layerout;
|
lastLayer = layerout;
|
||||||
|
|
||||||
|
// Optional retraction after every layer
|
||||||
if (params.outputLayerRetract && layerout.length) {
|
if (params.outputLayerRetract && layerout.length) {
|
||||||
layerout.last().retract = true;
|
layerout.last().retract = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// End of inner loop - all slices in this layer have been processed
|
||||||
|
|
||||||
// clear slice.prep so it can be re-previewed in a different mode
|
/**
|
||||||
|
* POST-PROCESSING: Layer finalization steps
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Clear prep flags to allow re-preview in different modes
|
||||||
|
// Only clear real widget slices, not synthesized support widgets
|
||||||
for (let widget of widgets) {
|
for (let widget of widgets) {
|
||||||
// skip synthesized support widget(s)
|
|
||||||
if (!widget.mesh) {
|
if (!widget.mesh) {
|
||||||
continue;
|
continue; // Skip synthesized support widgets
|
||||||
}
|
}
|
||||||
for (let slice of widget.slices) {
|
for (let slice of widget.slices) {
|
||||||
slice.prep = false;
|
slice.prep = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// draft shield
|
// DRAFT SHIELD: Print perimeter around all objects (layers 1+)
|
||||||
|
// Helps contain heat and protects from drafts for better print quality
|
||||||
if (layerno > 0 && shield && outputDraftShield) {
|
if (layerno > 0 && shield && outputDraftShield) {
|
||||||
print.setType('shield');
|
print.setType('shield');
|
||||||
shield = POLY.setZ(shield.clone(), printPoint.z);
|
shield = POLY.setZ(shield.clone(), printPoint.z);
|
||||||
|
|
@ -644,7 +754,6 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
return print.polyPrintPath(poly, startPoint, preout, {
|
return print.polyPrintPath(poly, startPoint, preout, {
|
||||||
onfirst: function(point) {
|
onfirst: function(point) {
|
||||||
if (preout.length && point.distTo2D(startPoint) > 2) {
|
if (preout.length && point.distTo2D(startPoint) > 2) {
|
||||||
// retract between part and shield
|
|
||||||
preout.last().retract = true;
|
preout.last().retract = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -654,27 +763,29 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
layerout.appendAll(preout);
|
layerout.appendAll(preout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if a declared extruder isn't used in a layer, use selected
|
// FILL UNUSED PURGE BLOCKS: Maintain structural integrity of purge tower
|
||||||
// extruder to fill the relevant purge blocks for later support
|
// Any purge blocks not used for real tool changes get filled with sparse
|
||||||
|
// infill using the current extruder. This keeps the tower solid for support.
|
||||||
if (lastOut) track.slice().forEach(ext => {
|
if (lastOut) track.slice().forEach(ext => {
|
||||||
|
// using=lastExt means "fill with current tool" (sparse fill mode)
|
||||||
printPoint = purge(ext.extruder, track, layerout, printPoint, lastOut.z, lastExt, lastOffset);
|
printPoint = purge(ext.extruder, track, layerout, printPoint, lastOut.z, lastExt, lastOffset);
|
||||||
});
|
});
|
||||||
|
|
||||||
// if layer produced output, append to output array
|
// Append completed layer to output array
|
||||||
if (layerout.length) {
|
if (layerout.length) {
|
||||||
output.append(layerout);
|
output.append(layerout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// retract after last layer
|
// Final retraction after last layer of print
|
||||||
if (layerno === cake.length - 1 && layerout.length) {
|
if (layerno === cake.length - 1 && layerout.length) {
|
||||||
layerout.last().retract = true;
|
layerout.last().retract = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify progress
|
// Update progress callback (prepare phase is 0-50%)
|
||||||
layerout.layer = layerno++;
|
layerout.layer = layerno++;
|
||||||
update((layerno / cake.length) * 0.5, "prepare");
|
update((layerno / cake.length) * 0.5, "prepare");
|
||||||
|
|
||||||
// clear for next layer
|
// Reset for next layer
|
||||||
layerout = [];
|
layerout = [];
|
||||||
layerout.height = sliceHeight;
|
layerout.height = sliceHeight;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue