re-attach support to fdm prepare pipeline
This commit is contained in:
parent
9a463881d1
commit
6bbc67d727
2 changed files with 84 additions and 142 deletions
|
|
@ -87,7 +87,7 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
});
|
});
|
||||||
// collect support polygons
|
// collect support polygons
|
||||||
if (slice.supports)
|
if (slice.supports)
|
||||||
slice.supports.forEach(function(support) {
|
slice.supports.forEach((support) => {
|
||||||
tops.push(support.clone());
|
tops.push(support.clone());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
tops = POLY.union(tops,1,true);
|
tops = POLY.union(tops,1,true);
|
||||||
}
|
}
|
||||||
// nest and offset tops
|
// nest and offset tops
|
||||||
POLY.nest(tops).forEach(function(poly) {
|
POLY.nest(tops).forEach((poly) => {
|
||||||
let off = poly.offset(-offset + nozzle / 2);
|
let off = poly.offset(-offset + nozzle / 2);
|
||||||
if (off) off.forEach(function(brim) {
|
if (off) off.forEach(function(brim) {
|
||||||
brim.move(widget.track.pos);
|
brim.move(widget.track.pos);
|
||||||
|
|
@ -541,7 +541,7 @@ export async function fdm_prepare(widgets, settings, update) {
|
||||||
},
|
},
|
||||||
routeAround: process.outputAvoidGaps,
|
routeAround: process.outputAvoidGaps,
|
||||||
seedPoint: printPoint.sub(offset),
|
seedPoint: printPoint.sub(offset),
|
||||||
support: slice.widget.support,
|
support: slice.supports,
|
||||||
zmax,
|
zmax,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -451,22 +451,49 @@ export function sliceOne(settings, widget, onupdate, ondone) {
|
||||||
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);
|
||||||
|
|
||||||
await widget.computeShadowStack(indices, progress => {}, zAngNorm);
|
await widget.computeShadowStack(indices, progress => {
|
||||||
|
trackupdate(progress, 0.05, 0.15, "support gen");
|
||||||
|
}, zAngNorm);
|
||||||
|
|
||||||
if (process.sliceSupportTree) {
|
stack.sort((a,b) => a.z - b.z); // deltas only
|
||||||
stack.sort((a,b) => a.z - b.z); // deltas only
|
|
||||||
} else {
|
|
||||||
stack.sort((a,b) => b.z - a.z); // accumulate deltas
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. accumulate / union shadow coverage top down
|
// 1. accumulate / union shadow coverage top down
|
||||||
// 2. trim to area outside slice.clips
|
// 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);
|
slice.shadow = await widget.shadowAt(slice.z, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let minArea = lineWidth;
|
||||||
|
let shadowSum;
|
||||||
|
for (let slice of stack.reverse()) {
|
||||||
|
let { shadow } = slice;
|
||||||
if (process.sliceSupportExtra) {
|
if (process.sliceSupportExtra) {
|
||||||
shadow = POLY.offset(shadow, process.sliceSupportExtra);
|
shadow = POLY.offset(shadow, process.sliceSupportExtra);
|
||||||
}
|
}
|
||||||
|
// shadows accumulate down
|
||||||
|
if (shadowSum) {
|
||||||
|
shadow = POLY.union([...shadow, ...shadowSum], minArea, true);
|
||||||
|
}
|
||||||
|
// trim to slice.clips
|
||||||
|
if (true) {
|
||||||
|
let rem = [];
|
||||||
|
let clips = [
|
||||||
|
slice.up?.clips,
|
||||||
|
slice.clips,
|
||||||
|
slice.down?.clips
|
||||||
|
].filter(v => v).flat();
|
||||||
|
clips = POLY.union(clips, minArea, true);
|
||||||
|
POLY.subtract(shadow, clips, rem, null, slice.z, minArea, { wasm: false });
|
||||||
|
shadow = rem;
|
||||||
|
}
|
||||||
|
// pump shadow to clean to clean it up
|
||||||
|
if (true) {
|
||||||
|
let bump = lineWidth * 2;
|
||||||
|
shadow = POLY.offset(shadow, bump, { z: slice.z });
|
||||||
|
shadow = POLY.offset(shadow, -bump, { z: slice.z });
|
||||||
|
}
|
||||||
|
shadowSum = shadow;
|
||||||
slice.supports = shadow;
|
slice.supports = shadow;
|
||||||
slice.output().setLayer("shadow", 0xff0000).addPolys(shadow);
|
slice.output().setLayer("shadow", 0xff0000).addPolys(shadow);
|
||||||
}
|
}
|
||||||
|
|
@ -606,15 +633,13 @@ export function sliceOne(settings, widget, onupdate, ondone) {
|
||||||
layerSupportFill({
|
layerSupportFill({
|
||||||
angle: process.sliceSupportFill,
|
angle: process.sliceSupportFill,
|
||||||
density,
|
density,
|
||||||
gap: process.sliceSupportGap,
|
|
||||||
isBelt,
|
isBelt,
|
||||||
lineWidth,
|
lineWidth,
|
||||||
minArea: 0,
|
|
||||||
outline: process.sliceSupportOutline !== false,
|
outline: process.sliceSupportOutline !== false,
|
||||||
promises,
|
promises,
|
||||||
slice,
|
slice,
|
||||||
});
|
});
|
||||||
}, "support");
|
}, "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, 0.88, 0.9);
|
||||||
|
|
@ -689,9 +714,6 @@ export function sliceOne(settings, widget, onupdate, ondone) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// new auto support demonstrator using cast shadow
|
|
||||||
await processAutoSupport();
|
|
||||||
|
|
||||||
// alert non-manifold parts
|
// alert non-manifold parts
|
||||||
if (healed) {
|
if (healed) {
|
||||||
onupdate(null, null, "part may not be manifold");
|
onupdate(null, null, "part may not be manifold");
|
||||||
|
|
@ -728,11 +750,14 @@ export function sliceOne(settings, widget, onupdate, ondone) {
|
||||||
|
|
||||||
// reset for solids, support projections
|
// reset for solids, support projections
|
||||||
// and other annotations
|
// and other annotations
|
||||||
slices.forEach(slice => {
|
for (let slice of slices) {
|
||||||
slice.widget = widget;
|
slice.widget = widget;
|
||||||
slice.extruder = extruder;
|
slice.extruder = extruder;
|
||||||
slice.solids = [];
|
slice.solids = [];
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// new auto support demonstrator using cast shadow
|
||||||
|
await processAutoSupport();
|
||||||
|
|
||||||
// process solid layers (top/bottom)
|
// process solid layers (top/bottom)
|
||||||
processSolidLayers();
|
processSolidLayers();
|
||||||
|
|
@ -1434,87 +1459,47 @@ function areaFill({ promises: fillQ, polys, angle, spacing, output, minLen, maxL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate support structure fill patterns for a slice
|
* Generate support structure fill patterns for a slice
|
||||||
*
|
*
|
||||||
* This function processes support polygons by:
|
* @param {SupportFillOptions} args - Configuration object
|
||||||
* 1. Unioning all support polygons to eliminate overlaps
|
* @param {Promise[]} [args.promises] - Array for async fill operations (concurrent mode)
|
||||||
* 2. Clipping supports to avoid collision with the part (using clip offsets)
|
* @param {Slice} args.slice - Current slice to process
|
||||||
* 3. Applying optional gap spacing from upper/lower layers
|
* @param {number} args.lineWidth - Extrusion width in mm (typically nozzle diameter)
|
||||||
* 4. Generating hatching fill patterns based on density
|
* @param {number} args.density - Fill density from 0.0 (0%) to 1.0 (100%)
|
||||||
* 5. Optionally connecting fill lines to reduce travel moves
|
* Typical support density is 0.10-0.25 (10-25%)
|
||||||
*
|
* @param {boolean} args.isBelt - True for belt printer mode (affects auto-angle calculation)
|
||||||
* Process flow:
|
* @param {number} [args.angle] - Fill angle in degrees. Special values:
|
||||||
* - Union: Merge overlapping support regions into unified polygons
|
* - undefined/null: Auto-calculate based on polygon shape
|
||||||
* - Clip: Subtract part geometry (slice.clips) to prevent support/part collision
|
* - >= 1000: Auto-calculate (1000=0° for tall, 1090=90° for wide)
|
||||||
* - Gap: If gap specified, also subtract clips from adjacent layers for clearance
|
* - 0-360: Use specified angle
|
||||||
* - Fill: Generate hatching pattern with auto or specified angle
|
* @param {boolean} args.outline - Include support perimeter outline in output
|
||||||
* - Connect: Optionally link nearby fill line endpoints to reduce travels
|
* If false, only interior fill lines are generated
|
||||||
*
|
*
|
||||||
* @param {SupportFillOptions} args - Configuration object
|
* @modifies {Slice} slice.supports - Updated with filled support polygons (array of Polygon)
|
||||||
* @param {Promise[]} [args.promises] - Array for async fill operations (concurrent mode)
|
* Each polygon has a .fill property with fill lines
|
||||||
* @param {Slice} args.slice - Current slice to process
|
*
|
||||||
* @param {number} args.lineWidth - Extrusion width in mm (typically nozzle diameter)
|
* @see supportPolyFill - Called internally to generate fill patterns
|
||||||
* @param {number} args.density - Fill density from 0.0 (0%) to 1.0 (100%)
|
* @see CONSTANTS.SUPPORT_INSET_RATIO - Inset ratio (1/3 of line width)
|
||||||
* Typical support density is 0.10-0.25 (10-25%)
|
* @see CONSTANTS.SUPPORT_AUTO_ANGLE_WIDE - Auto angle for wide polygons (1090 = 90°)
|
||||||
* @param {number} args.minArea - Minimum polygon area in mm² to keep (default: 0.1)
|
* @see CONSTANTS.SUPPORT_AUTO_ANGLE_TALL - Auto angle for tall polygons (1000 = 0°)
|
||||||
* Smaller polygons are filtered out as noise
|
* @see CONSTANTS.SUPPORT_CONNECT_DISTANCE_MULT - Distance multiplier for line connection (2x)
|
||||||
* @param {boolean} args.isBelt - True for belt printer mode (affects auto-angle calculation)
|
*/
|
||||||
* @param {number} [args.angle] - Fill angle in degrees. Special values:
|
function layerSupportFill({ promises, slice, lineWidth, density, isBelt, angle, outline, gap }) {
|
||||||
* - undefined/null: Auto-calculate based on polygon shape
|
let polys = slice.supports;
|
||||||
* - >= 1000: Auto-calculate (1000=0° for tall, 1090=90° for wide)
|
if (polys) {
|
||||||
* - 0-360: Use specified angle
|
|
||||||
* @param {boolean} args.outline - Include support perimeter outline in output
|
|
||||||
* If false, only interior fill lines are generated
|
|
||||||
* @param {number} [args.gap] - Gap distance in mm between support and part surfaces
|
|
||||||
* When specified, subtracts adjacent layer clips for clearance
|
|
||||||
*
|
|
||||||
* @modifies {Slice} slice.supports - Updated with filled support polygons (array of Polygon)
|
|
||||||
* Each polygon has a .fill property with fill lines
|
|
||||||
* @modifies {Slice} slice.supportOutline - Stores support outline polygons
|
|
||||||
*
|
|
||||||
* @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 layerSupportFill({ promises, slice, lineWidth, density, minArea, isBelt, angle, outline, gap }) {
|
|
||||||
let supports = slice.supports,
|
|
||||||
min = minArea || 0.1,
|
|
||||||
sub;
|
|
||||||
|
|
||||||
if (!supports) return;
|
|
||||||
|
|
||||||
// union supports
|
|
||||||
supports = POLY.setZ(POLY.union(supports, undefined, true, { wasm: false }), slice.z);
|
|
||||||
|
|
||||||
// clip supports to slice clip offset (or shell if none)
|
|
||||||
POLY.subtract(supports, slice.clips, sub = [], null, slice.z, min, { wasm: false });
|
|
||||||
supports = sub;
|
|
||||||
|
|
||||||
// trim to upper offsets, if they exist
|
|
||||||
if (gap && slice?.up?.clips) {
|
|
||||||
POLY.subtract(supports, slice.up.clips, sub = [], null, slice.z, min, { wasm: false });
|
|
||||||
supports = sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
// trim to lower offsets, if they exist
|
|
||||||
if (gap && slice?.down?.clips) {
|
|
||||||
POLY.subtract(supports, slice.down.clips, sub = [], null, slice.z, min, { wasm: false });
|
|
||||||
supports = sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supports) {
|
|
||||||
supportPolyFill({
|
supportPolyFill({
|
||||||
promises, polys: supports, lineWidth, density, z: slice.z, isBelt, angle, outline
|
angle,
|
||||||
|
density,
|
||||||
|
isBelt,
|
||||||
|
lineWidth,
|
||||||
|
outline,
|
||||||
|
polys,
|
||||||
|
promises,
|
||||||
|
z: slice.z
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// re-assign new supports back to slice
|
|
||||||
slice.supportOutline = supports;
|
|
||||||
slice.supports = supports;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate fill patterns for individual support polygons
|
* Generate fill patterns for individual support polygons
|
||||||
|
|
@ -1525,33 +1510,6 @@ function areaFill({ promises: fillQ, polys, angle, spacing, output, minLen, maxL
|
||||||
* - Intelligent inset to prevent perimeter over-extrusion
|
* - Intelligent inset to prevent perimeter over-extrusion
|
||||||
* - Optional line connection to reduce travel moves when no outline is needed
|
* - Optional line connection to reduce travel moves when no outline is needed
|
||||||
*
|
*
|
||||||
* ## Algorithm Details
|
|
||||||
*
|
|
||||||
* 1. **Spacing Calculation**: Converts density percentage to actual spacing distance
|
|
||||||
* - Formula: spacing = lineWidth * (1 / density)
|
|
||||||
* - Example: 0.4mm line × (1 / 0.15) = 2.67mm spacing for 15% density
|
|
||||||
*
|
|
||||||
* 2. **Auto-Angle Selection**: Chooses fill direction based on polygon shape
|
|
||||||
* - Wide polygons (width/height > 1): Use 1090 (90° horizontal lines)
|
|
||||||
* - Tall polygons (width/height ≤ 1): Use 1000 (0° vertical lines)
|
|
||||||
* - Belt mode: Always use 1090 (90°) regardless of aspect ratio
|
|
||||||
* - Rationale: Aligns fill lines with longest dimension for structural strength
|
|
||||||
*
|
|
||||||
* 3. **Inset Operation**: Shrinks polygon before filling
|
|
||||||
* - Inset distance: lineWidth × (1/3) = 0.33× line width
|
|
||||||
* - Purpose: Prevents over-extrusion where fill lines meet perimeter
|
|
||||||
* - Creates ~0.13mm gap for typical 0.4mm nozzle
|
|
||||||
*
|
|
||||||
* 4. **Fill Generation**: Creates parallel hatching lines at calculated angle
|
|
||||||
* - Uses doFillArea() to generate scan lines through inset polygon
|
|
||||||
* - Stores result in poly.fill array
|
|
||||||
*
|
|
||||||
* 5. **Line Connection** (optional): Reduces travel moves
|
|
||||||
* - Only when outline=false (no support perimeter needed)
|
|
||||||
* - Connects nearby line endpoints within 2× spacing distance
|
|
||||||
* - Converts many short lines into fewer longer continuous paths
|
|
||||||
* - Reduces print time and stringing
|
|
||||||
*
|
|
||||||
* @param {Object} args - Configuration object
|
* @param {Object} args - Configuration object
|
||||||
* @param {Promise[]} [args.promises] - Array for async fill operations (concurrent mode)
|
* @param {Promise[]} [args.promises] - Array for async fill operations (concurrent mode)
|
||||||
* Pass same array through all calls for parallelism
|
* Pass same array through all calls for parallelism
|
||||||
|
|
@ -1585,13 +1543,14 @@ function areaFill({ promises: fillQ, polys, angle, spacing, output, minLen, maxL
|
||||||
* @see CONSTANTS.SUPPORT_INSET_RATIO - Inset ratio for fill area (1/3)
|
* @see CONSTANTS.SUPPORT_INSET_RATIO - Inset ratio for fill area (1/3)
|
||||||
* @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 supportPolyFill({ 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) {
|
||||||
// calculate angle based on width/height ratio
|
// calculate angle based on width/height ratio
|
||||||
let auto = isBelt || (poly.bounds.width() / poly.bounds.height() > 1) ? CONSTANTS.SUPPORT_AUTO_ANGLE_WIDE : CONSTANTS.SUPPORT_AUTO_ANGLE_TALL;
|
let auto = isBelt || (poly.bounds.width() / poly.bounds.height() > 1) ?
|
||||||
|
CONSTANTS.SUPPORT_AUTO_ANGLE_WIDE :
|
||||||
|
CONSTANTS.SUPPORT_AUTO_ANGLE_TALL;
|
||||||
// inset support poly for fill lines
|
// inset support poly for fill lines
|
||||||
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
|
||||||
|
|
@ -1650,23 +1609,6 @@ function projectSolid({ slice, polys, count, up, first }) {
|
||||||
slice: up ? slice.up : slice.down,
|
slice: up ? slice.up : slice.down,
|
||||||
up
|
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
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue