complete stacked laser output. add shapert tool support for stacked laser svg
This commit is contained in:
parent
2e92d9d25c
commit
badbca4b10
6 changed files with 119 additions and 51 deletions
|
|
@ -410,7 +410,7 @@ CAM.export = function(print, online) {
|
|||
point.y *= scale.y;
|
||||
point.z *= scale.z;
|
||||
}
|
||||
if (scale.a) {
|
||||
if (point.a && scale.a) {
|
||||
point.a *= scale.a;
|
||||
}
|
||||
if (spro.outputInvertX) point.x = -point.x;
|
||||
|
|
|
|||
|
|
@ -46,10 +46,9 @@ function polyLabel(poly, label) {
|
|||
|
||||
function sliceEmitObjects(print, slice, groups, opt = { }) {
|
||||
let process = print.settings.process,
|
||||
marked = process.ctOutMark,
|
||||
stacked = process.ctOutStack,
|
||||
grouped = marked || process.ctOutGroup,
|
||||
label = false && process.outputLaserLabel,
|
||||
{ ctOutMark, ctOutStack, ctOutGroup, ctOutShaper, outputLaserLabel } = process,
|
||||
grouped = ctOutMark || ctOutGroup,
|
||||
label = false && outputLaserLabel,
|
||||
simple = opt.simple || false,
|
||||
emit = { in: [], out: [], mark: [] },
|
||||
lastEmit = opt.lastEmit,
|
||||
|
|
@ -72,13 +71,17 @@ function sliceEmitObjects(print, slice, groups, opt = { }) {
|
|||
let lbl = index !== undefined ? `${slice.index}-${index}` : `${slice.index}`;
|
||||
polyLabel(poly, lbl);
|
||||
}
|
||||
if (simple) pathOpt.simple = true;
|
||||
if (poly.open) pathOpt.open = true;
|
||||
if (simple) {
|
||||
pathOpt.simple = true;
|
||||
}
|
||||
if (poly.open) {
|
||||
pathOpt.open = true;
|
||||
}
|
||||
emit[type].push(poly);
|
||||
print.PPP(poly, group, pathOpt);
|
||||
// when stacking (top down widget slices), if the last layer is fully contained
|
||||
// by the current layer, then it is "marked" onto the current layer in a different color
|
||||
if (marked && type === "out" && lastEmit) {
|
||||
if (ctOutMark && type === "out" && lastEmit) {
|
||||
for (let out of lastEmit.out) {
|
||||
if (out.isInside(poly)) {
|
||||
polyOut(out, group, "mark");
|
||||
|
|
@ -90,9 +93,10 @@ function sliceEmitObjects(print, slice, groups, opt = { }) {
|
|||
|
||||
if (grouped) {
|
||||
let group = [];
|
||||
group.thick = slice.thick;
|
||||
let outer = slice.offset;
|
||||
let inner = outer.map(poly => poly.inner || []).flat();
|
||||
// preserve slice thickness in group
|
||||
group.thick = slice.thick;
|
||||
// cut inside before outside
|
||||
polyOut(inner, group, "in", outer.length > 1);
|
||||
polyOut(outer, group, "out", outer.length > 1);
|
||||
|
|
@ -264,16 +268,18 @@ async function prepare(widgets, settings, update) {
|
|||
print = self.worker.print = kiri.newPrint(settings, widgets),
|
||||
isWire = self.worker.mode === 'WEDM',
|
||||
knifeOn = self.worker.mode === 'DRAG',
|
||||
knifeTipOff = process.ctOutKnifeTip,
|
||||
output = print.output = [],
|
||||
totalSlices = 0,
|
||||
slices = 0,
|
||||
maxZ = 0,
|
||||
{ bedWidth, bedDepth } = device,
|
||||
{ ctOutLayer, ctOutTileSpacing } = process,
|
||||
{ ctOriginCenter, ctOriginBounds } = process,
|
||||
{ ctOriginOffX, ctOriginOffY } = process;
|
||||
{ ctOriginOffX, ctOriginOffY } = process,
|
||||
{ ctOutStack, ctOutMerged, ctOutKnifeTip, ctOutShaper } = process;
|
||||
|
||||
// shim to adapt older code -- should be refactored
|
||||
let pppo = [];
|
||||
// let pppo = [];
|
||||
let pppg = [];
|
||||
let tile = [];
|
||||
let tiles = [];
|
||||
|
|
@ -283,54 +289,62 @@ async function prepare(widgets, settings, update) {
|
|||
tiles.push(tile = []);
|
||||
tile.num = tiles.length;
|
||||
}
|
||||
pppo.push({ poly, group, options });
|
||||
// pppo.push({ poly, group, options });
|
||||
tile.push({ poly, group, options });
|
||||
};
|
||||
|
||||
// filter ignored widgets
|
||||
widgets = widgets.filter(w => !w.track.ignore && !w.meta.disabled);
|
||||
|
||||
// find max layers (for updates)
|
||||
// find max layers (for updates) and Z (for shaper)
|
||||
for (let widget of widgets) {
|
||||
totalSlices += widget.slices.length;
|
||||
maxZ = Math.max(maxZ, widget.bounds.max.z);
|
||||
}
|
||||
|
||||
// emit objects from each slice into output array
|
||||
let layers = [];
|
||||
for (let widget of widgets) {
|
||||
if (process.ctOutStack) {
|
||||
if (ctOutStack) {
|
||||
// 3d stack output, no merging or layout
|
||||
for (let slice of widget.slices) {
|
||||
let group = [];
|
||||
for (let poly of slice.offset) {
|
||||
print.PPP(poly, group, {
|
||||
extrude: 1,
|
||||
rate: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (process.ctOutMerged) {
|
||||
} else if (ctOutMerged) {
|
||||
// slice stack merging, no layout
|
||||
// there are no inner vs outer polys
|
||||
// merged/same polys "increase" color/weight
|
||||
// merged polys "increase" their color / weight
|
||||
let merged = [];
|
||||
for (let slice of widget.slices) {
|
||||
let polys = [];
|
||||
slice.offset.clone(true).forEach(p => p.flattenTo(polys));
|
||||
polys.forEach(p => {
|
||||
for (let poly of polys) {
|
||||
let match = false;
|
||||
for (let i=0; i<merged.length; i++) {
|
||||
let mp = merged[i];
|
||||
if (p.isEquivalent(mp)) {
|
||||
if (poly.isEquivalent(mp)) {
|
||||
// increase weight
|
||||
match = true;
|
||||
mp.depth++;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
p.depth = 1;
|
||||
merged.push(p);
|
||||
poly.depth = 1;
|
||||
merged.push(poly);
|
||||
}
|
||||
});
|
||||
}
|
||||
update((slices++ / totalSlices) * 0.5, "prepare");
|
||||
}
|
||||
let gather = [];
|
||||
for (let poly of merged) {
|
||||
if (knifeOn) {
|
||||
addKnifeRadii(poly, knifeTipOff);
|
||||
addKnifeRadii(poly, ctOutKnifeTip);
|
||||
}
|
||||
print.PPP(poly, gather, {
|
||||
extrude: poly.depth,
|
||||
|
|
@ -342,12 +356,12 @@ async function prepare(widgets, settings, update) {
|
|||
// output a layer for each slice
|
||||
if (knifeOn) {
|
||||
for (let slice of widget.slices) {
|
||||
slice.offset.forEach(poly => {
|
||||
addKnifeRadii(poly, knifeTipOff);
|
||||
for (let poly of slice.offset) {
|
||||
addKnifeRadii(poly, ctOutKnifeTip);
|
||||
if (poly.inner) poly.inner.forEach(ip => {
|
||||
addKnifeRadii(ip, knifeTipOff);
|
||||
addKnifeRadii(ip, ctOutKnifeTip);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let lastEmit;
|
||||
|
|
@ -361,8 +375,8 @@ async function prepare(widgets, settings, update) {
|
|||
// for tile layout packing
|
||||
let dw = device.bedWidth / 2,
|
||||
dh = device.bedDepth / 2,
|
||||
sort = !process.ctOutLayer,
|
||||
spacing = process.ctOutTileSpacing;
|
||||
sort = !ctOutLayer,
|
||||
spacing = ctOutTileSpacing;
|
||||
|
||||
// compute tile bounds
|
||||
for (let tile of tiles) {
|
||||
|
|
@ -378,7 +392,8 @@ async function prepare(widgets, settings, update) {
|
|||
return packer.rescale(1.1, 1.1);
|
||||
});
|
||||
|
||||
// reposition tiles into their packed locations
|
||||
// reposition tiles into their packed locations (unless 3d stack)
|
||||
if (!ctOutStack)
|
||||
for (let tile of tiles) {
|
||||
let { fit, bounds } = tile;
|
||||
for (let { poly } of tile) {
|
||||
|
|
@ -397,13 +412,18 @@ async function prepare(widgets, settings, update) {
|
|||
// set starting point based on origin type
|
||||
let currentPos =
|
||||
ctOriginCenter ? newPoint(0,0,0) :
|
||||
ctOriginBounds ? newPoint(- tp.max.w/2 - ctOriginOffX, - tp.max.h/2 - ctOriginOffY, 0) :
|
||||
ctOriginBounds ? newPoint(
|
||||
- tp.max.w/2 - (ctOriginOffX || 0),
|
||||
- tp.max.h/2 - (ctOriginOffY || 0), 0
|
||||
) :
|
||||
newPoint(-dw, -dh, 0);
|
||||
|
||||
// output tiles starting with closest to origin
|
||||
let remain = tiles.slice();
|
||||
while (remain.length) {
|
||||
let closest = { dist: Infinity };
|
||||
let closest = ctOutShaper ?
|
||||
{ dist: 0, tile: remain[0] } :
|
||||
{ dist: Infinity };
|
||||
for (let tile of remain) {
|
||||
for (let { poly } of tile) {
|
||||
for (let p of poly.points) {
|
||||
|
|
@ -418,7 +438,9 @@ async function prepare(widgets, settings, update) {
|
|||
if (!closest.tile) throw "no closest found";
|
||||
for (let { poly, group, options } of closest.tile) {
|
||||
currentPos = print.polyPrintPath(poly, currentPos, group, options);
|
||||
if (!output.contains(group)) output.push(group);
|
||||
if (!output.contains(group)) {
|
||||
output.push(group);
|
||||
}
|
||||
}
|
||||
remain.remove(closest.tile);
|
||||
}
|
||||
|
|
@ -431,8 +453,15 @@ async function prepare(widgets, settings, update) {
|
|||
}
|
||||
|
||||
return kiri.render.path(output, (progress, layer) => {
|
||||
if (ctOutShaper) {
|
||||
for (let layer of output) {
|
||||
for (let out of layer) {
|
||||
out.point.zi = maxZ - out.point.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
update(0.5 + progress * 0.5, "render", layer);
|
||||
}, { thin: true, z: 0, action: "cut", moves: process.knifeOn });
|
||||
}, { thin: true, z: ctOutStack ? undefined : 0, action: "cut", moves: process.knifeOn });
|
||||
};
|
||||
|
||||
function exportLaser(print, online, ondone) {
|
||||
|
|
@ -448,6 +477,7 @@ function exportElements(settings, output, onpre, onpoly, onpost, onpoint, onlaye
|
|||
{ ctOriginCenter, ctOriginBounds } = process,
|
||||
{ outputInvertX, outputInvertY } = process,
|
||||
{ ctOriginOffX, ctOriginOffY } = process,
|
||||
{ ctOutPower, ctOutSpeed, maxZ } = process,
|
||||
last,
|
||||
point,
|
||||
poly = [],
|
||||
|
|
@ -492,8 +522,8 @@ function exportElements(settings, output, onpre, onpoly, onpost, onpoint, onlaye
|
|||
for (let layer of output) {
|
||||
for (let out of layer) {
|
||||
point = out.point;
|
||||
point.x = point.x - min.x + ctOriginOffX;
|
||||
point.y = point.y - min.y + ctOriginOffY;
|
||||
point.x = point.x - min.x + off.x;
|
||||
point.y = point.y - min.y + off.y;
|
||||
bounds.update(point);
|
||||
}
|
||||
}
|
||||
|
|
@ -520,7 +550,7 @@ function exportElements(settings, output, onpre, onpoly, onpost, onpoint, onlaye
|
|||
min.y = 0;
|
||||
}
|
||||
|
||||
onpre(min, max, process.ctOutPower, process.ctOutSpeed);
|
||||
onpre(min, max, ctOutPower, ctOutSpeed);
|
||||
|
||||
// output each layer with moves between them
|
||||
output.forEach((layer, index) => {
|
||||
|
|
@ -644,8 +674,9 @@ function exportGCode(settings, data) {
|
|||
*/
|
||||
function exportSVG(settings, data, cut_color) {
|
||||
let { process } = settings;
|
||||
let { ctOutInches, ctOutStack, ctOutShaper } = process;
|
||||
let swidth = "0.1mm";
|
||||
let zcolor = process.ctOutZColor ? 1 : 0;
|
||||
let zstack = process.ctOutMark;
|
||||
let lines = [], dx = 0, dy = 0, my, z = 0;
|
||||
let colors = [
|
||||
"black",
|
||||
|
|
@ -670,21 +701,52 @@ function exportSVG(settings, data, cut_color) {
|
|||
my = max.y;
|
||||
lines.push('<?xml version="1.0" standalone="no"?>');
|
||||
lines.push('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">');
|
||||
lines.push(`<svg width="${width}mm" height="${height}mm" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" version="1.1">`);
|
||||
if (ctOutInches) {
|
||||
width = (width / 25.4).round(2);
|
||||
height = (height / 25.4).round(2);
|
||||
lines.push(`<svg width="${width}in" height="${height}in" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" version="1.1">`);
|
||||
} else {
|
||||
lines.push(`<svg width="${width}mm" height="${height}mm" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" version="1.1">`);
|
||||
}
|
||||
},
|
||||
function(poly, color, thick) {
|
||||
let cout = zcolor || colors[((color-1) % colors.length)];
|
||||
let def = ["polyline"];
|
||||
if (z !== undefined) def.push(`z="${z}"`);
|
||||
if (thick !== undefined) def.push(`h="${thick}"`);
|
||||
lines.push(`<${def.join(' ')} points="${poly.join(' ')}" fill="none" stroke="${cout}" stroke-width="0.1mm" />`);
|
||||
if (ctOutStack) {
|
||||
let def = ["path"];
|
||||
if (z !== undefined && ctOutShaper) {
|
||||
if (ctOutInches) {
|
||||
def.push(`shaper:cutDepth="${(z / 25.4).toFixed(2)}in"`);
|
||||
} else {
|
||||
def.push(`shaper:cutDepth="${(z).toFixed(2)}mm"`);
|
||||
}
|
||||
def.push(`shaper:cutType="online"`);
|
||||
// def.push(`shaper:cutOffset="0in"`);
|
||||
// def.push(`shaper:toolDia="0in"`);
|
||||
} else if (z !== undefined) {
|
||||
def.push(`z="${z}"`);
|
||||
}
|
||||
let path = poly.map((xy, i) => i > 0 ? `L${xy}` : `M${xy}`);
|
||||
lines.push(`<${def.join(' ')} B="${path.join(' ')}" fill="none" stroke="${cout}" stroke-width="${swidth}" />`);
|
||||
} else {
|
||||
let def = ["polyline"];
|
||||
if (z !== undefined) def.push(`z="${z}"`);
|
||||
if (thick !== undefined) def.push(`h="${thick}"`);
|
||||
lines.push(`<${def.join(' ')} points="${poly.join(' ')}" fill="none" stroke="${cout}" stroke-width="${swidth}" />`);
|
||||
}
|
||||
},
|
||||
function() {
|
||||
lines.push("</svg>");
|
||||
},
|
||||
function(point) {
|
||||
z = point.z;
|
||||
return base.util.round(point.x - dx, 3) + "," + base.util.round(my - point.y - dy, 3);
|
||||
z = point.zi ?? point.z;
|
||||
let px = point.x - dx;
|
||||
let py = my - point.y - dy;
|
||||
// convert to imperial for stacked
|
||||
if (ctOutInches) {
|
||||
px /= 25.4;
|
||||
py /= 25.4;
|
||||
}
|
||||
return base.util.round(px, 3) + "," + base.util.round(py, 3);
|
||||
},
|
||||
function(layer, z, thick, layers) {
|
||||
if (zcolor) {
|
||||
|
|
|
|||
|
|
@ -626,7 +626,9 @@ const conf = exports({
|
|||
outputInvertY: false,
|
||||
ctOutKnifeDepth: 1,
|
||||
ctOutKnifePasses: 1,
|
||||
ctOutKnifeTip: 2
|
||||
ctOutKnifeTip: 2,
|
||||
ctOutInches: false,
|
||||
ctOutShaper: false
|
||||
}
|
||||
},
|
||||
drag: {
|
||||
|
|
|
|||
|
|
@ -138,8 +138,6 @@ function exportLaserDialog(data, names) {
|
|||
$('print-svg').onclick = download_svg;
|
||||
$('print-dxf').onclick = download_dxf;
|
||||
$('print-lg').onclick = download_gcode;
|
||||
|
||||
console.log('laser export', { fileroot, filename });
|
||||
}
|
||||
|
||||
function bindField(field, varname) {
|
||||
|
|
|
|||
|
|
@ -1319,8 +1319,10 @@ gapp.register("kiri.init", (root, exports) => {
|
|||
ctOutZColor: newBoolean(LANG.ou_layo_s, onBooleanClick, { title:LANG.ou_layo_l, modes:TWONED, show:() => !ui.ctOutMerged.checked }),
|
||||
ctOutLayer: newBoolean(LANG.ou_layr_s, onBooleanClick, { title:LANG.ou_layr_l, modes:TWONED, show:() => !ui.ctOutStack.checked }),
|
||||
ctOutMark: newBoolean(LANG.ou_lays_s, onBooleanClick, { title:LANG.ou_lays_l, modes:TWONED, show:() => !ui.ctOutStack.checked }),
|
||||
separator: newBlank({ class:"set-sep", driven, modes:TWOD }),
|
||||
ctOutStack: newBoolean(LANG.ou_stak_s, onBooleanClick, { title:LANG.ou_stak_l, modes:TWOD }),
|
||||
separator: newBlank({ class:"set-sep", driven, modes:LASER }),
|
||||
ctOutInches: newBoolean(LANG.ou_inch_s, onBooleanClick, { title:LANG.ou_inch_l, modes:LASER }),
|
||||
ctOutStack: newBoolean(LANG.ou_stak_s, onBooleanClick, { title:LANG.ou_stak_l, modes:LASER }),
|
||||
ctOutShaper: newBoolean(LANG.ou_shap_s, onBooleanClick, { title:LANG.ou_shap_l, modes:LASER, show:() => ui.ctOutStack.checked }),
|
||||
|
||||
/** SLA SETTINGS */
|
||||
|
||||
|
|
|
|||
|
|
@ -696,10 +696,14 @@ self.kiri.lang['en-us'] = {
|
|||
ou_lays_l: ["mark layers for stacking. the layer above will be output in a different color which the laser can mark with a lower power. turns on layer grouping."],
|
||||
ou_drkn_s: "drag knife",
|
||||
ou_drkn_l: ["enable drag knife","output in gcode","cut radii are added","to corners with","cut down passes"],
|
||||
ou_stak_s: "fixed",
|
||||
ou_stak_s: "3d stack",
|
||||
ou_stak_l: ["output is a 3D stack of 2D paths rather than packing layers flat in 2D"],
|
||||
ou_maxp_s: "max power",
|
||||
ou_maxp_l: ["max power value in gcode. power % from settings will scale from 0 to this value"],
|
||||
ou_inch_s: "imperial",
|
||||
ou_inch_l: ["output values in inches"],
|
||||
ou_shap_s: "shaper tool",
|
||||
ou_shap_l: ["annotate SVG with Shaper Tool custom attributes"],
|
||||
|
||||
// OUTPUT FDM
|
||||
ou_nozl_s: "nozzle temp",
|
||||
|
|
|
|||
Loading…
Reference in a new issue