add laser stacking stl reconstruction option
This commit is contained in:
parent
a7fccd7636
commit
84d6cdcf2c
7 changed files with 98 additions and 6 deletions
|
|
@ -2403,6 +2403,7 @@ export class Polygon {
|
|||
}
|
||||
|
||||
// extrude poly (with inner voids) into 3d mesh
|
||||
// todo: zadd broken when poly Z is not 0
|
||||
extrude(z = 1, opt = {}) {
|
||||
let earcut = this.earcut(); // array of 3-point polygons
|
||||
|
||||
|
|
|
|||
|
|
@ -647,7 +647,11 @@ export const conf = {
|
|||
ctSliceHeight: 1,
|
||||
ctSliceHeightMin: 0,
|
||||
ctSliceSingle: true,
|
||||
ctOmitInner: false,
|
||||
ctOutTileSpacing: 1,
|
||||
ctOutClean: false,
|
||||
ctOutFilter: 1,
|
||||
ctOutSmooth: 0.1,
|
||||
ctOutPower: 100,
|
||||
ctOutSpeed: 1000,
|
||||
ctOutGroup: true,
|
||||
|
|
@ -690,6 +694,7 @@ export const conf = {
|
|||
ctSliceHeight: 1,
|
||||
ctSliceHeightMin: 0,
|
||||
ctSliceSingle: true,
|
||||
ctOmitInner: false,
|
||||
ctOutTileSpacing: 1,
|
||||
ctOutPower: 100,
|
||||
ctOutSpeed: 1000,
|
||||
|
|
@ -734,6 +739,7 @@ export const conf = {
|
|||
ctSliceHeight: 1,
|
||||
ctSliceHeightMin: 0,
|
||||
ctSliceSingle: true,
|
||||
ctOmitInner: false,
|
||||
ctOutTileSpacing: 1,
|
||||
ctOutPower: 100,
|
||||
ctOutSpeed: 1000,
|
||||
|
|
|
|||
|
|
@ -99,6 +99,12 @@ function exportLaserDialog(data, names) {
|
|||
const filename = `${fileroot}-${(printSeq.toString().padStart(3,"0"))}`;
|
||||
const settings = api.conf.get();
|
||||
const driver = laser_driver;
|
||||
const { process } = settings;
|
||||
if (process.ctOutStack) {
|
||||
$('print-stl').classList.remove('hide');
|
||||
} else {
|
||||
$('print-stl').classList.add('hide');
|
||||
}
|
||||
|
||||
function download_svg() {
|
||||
api.util.download(
|
||||
|
|
@ -114,6 +120,13 @@ function exportLaserDialog(data, names) {
|
|||
);
|
||||
}
|
||||
|
||||
function download_stl() {
|
||||
api.util.download(
|
||||
driver.exportSTL(settings, data),
|
||||
$('print-filename-laser').value + ".stl"
|
||||
);
|
||||
}
|
||||
|
||||
function download_gcode() {
|
||||
api.util.download(
|
||||
driver.exportGCode(settings, data),
|
||||
|
|
@ -130,6 +143,7 @@ function exportLaserDialog(data, names) {
|
|||
$('print-lines').value = util.comma(segments);
|
||||
$('print-svg').onclick = download_svg;
|
||||
$('print-dxf').onclick = download_dxf;
|
||||
$('print-stl').onclick = download_stl;
|
||||
$('print-lg').onclick = download_gcode;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -872,8 +872,9 @@ class Widget {
|
|||
return shadows[z] = POLY.setZ(shadow, z);
|
||||
}
|
||||
|
||||
// create a stack of faces in 1mm increments
|
||||
// the stacks are then used to produce shadowlines
|
||||
#ensureShadowCache() {
|
||||
// cache faces with normals up
|
||||
if (this.cache.shadow) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -881,7 +882,6 @@ class Widget {
|
|||
const length = geo.length;
|
||||
const bounds = this.getBoundingBox();
|
||||
const stack = {};
|
||||
// const faces = [];
|
||||
for (let i = Math.floor(bounds.min.z); i <= Math.ceil(bounds.max.z); i++) {
|
||||
stack[i] = [];
|
||||
}
|
||||
|
|
@ -892,7 +892,6 @@ class Widget {
|
|||
const n = THREE.computeFaceNormal(a, b, c);
|
||||
if (n.z < 0.001) {
|
||||
continue;
|
||||
// faces.push(a, b, c);
|
||||
}
|
||||
const minZ = Math.floor(Math.min(a.z, b.z, c.z));
|
||||
const maxZ = Math.ceil(Math.max(a.z, b.z, c.z));
|
||||
|
|
@ -904,6 +903,7 @@ class Widget {
|
|||
}
|
||||
|
||||
// union triangles > z (opt cap < ztop) into polygon(s)
|
||||
// slice the triangle stack matchingg z then union the results
|
||||
#computeShadowAt(z, ztop) {
|
||||
this.#ensureShadowCache();
|
||||
const found = [];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import { polygons as POLY } from '../../../geo/polygons.js';
|
|||
import { render } from '../../core/render.js';
|
||||
import { slicer } from '../../../geo/slicer.js';
|
||||
import { newSlice } from '../../core/slice.js';
|
||||
import { newPolygon } from '../../../geo/polygon.js';
|
||||
import { STL } from '../../../load/stl.js';
|
||||
|
||||
export const TYPE = {
|
||||
LASER: 0,
|
||||
|
|
@ -228,9 +230,28 @@ function laser_slice(settings, widget, onupdate, ondone) {
|
|||
widget.slices = output.slices.map(data => {
|
||||
let { z, lines, groups } = data;
|
||||
let tops = POLY.nest(groups);
|
||||
let { ctOutClean, ctOutFilter, ctOutSmooth } = proc;
|
||||
// optional cleaning, smoothing, filtering steps
|
||||
// if we're going to re-export an stl
|
||||
if (ctOutClean) {
|
||||
tops.map(top => top.clean(true));
|
||||
POLY.setZ(tops, z);
|
||||
}
|
||||
// running this here vs at output time seems to cause manifold issues ?!?
|
||||
// if (ctOutSmooth) {
|
||||
// console.log({ ctOutSmooth });
|
||||
// tops = POLY.offset(tops, ctOutSmooth);
|
||||
// tops = POLY.offset(tops, -ctOutSmooth);
|
||||
// POLY.setZ(tops, z);
|
||||
// }
|
||||
if (ctOutFilter) {
|
||||
tops = tops.filter(top => top.area() > ctOutFilter);
|
||||
}
|
||||
// for WireEDM, we commonly omit inner polys because we can't
|
||||
// navigate to cut them separately unlike laser, water, drag
|
||||
if (ctOmitInner) tops.forEach(top => delete top.inner);
|
||||
if (ctOmitInner) {
|
||||
tops.forEach(top => delete top.inner);
|
||||
}
|
||||
let slice = newSlice(z).addTops(tops);
|
||||
slice.index = indices.indexOf(z);
|
||||
// create top offsets (even if 0)
|
||||
|
|
@ -269,6 +290,7 @@ async function laser_prepare(widgets, settings, update) {
|
|||
{ ctOutStack, ctOutMerged, ctOutKnifeTip, ctOutShaper } = process;
|
||||
|
||||
ctOutStack = ctOutStack && isLaser;
|
||||
print.widgets = widgets;
|
||||
|
||||
// shim to adapt older code -- should be refactored
|
||||
// let pppo = [];
|
||||
|
|
@ -588,6 +610,49 @@ function exportElements(settings, output, onpre, onpoly, onpost, onpoint, onlaye
|
|||
onpost();
|
||||
};
|
||||
|
||||
function exportSTL(settings, data) {
|
||||
let { ctSliceHeight, ctOutSmooth } = settings.process;
|
||||
let polys = [];
|
||||
for (let array of data) {
|
||||
let poly;
|
||||
for (let el of array) {
|
||||
let { emit, point } = el;
|
||||
if (emit === 0) {
|
||||
poly = newPolygon().addObj(point);
|
||||
polys.push(poly);
|
||||
} else {
|
||||
poly.addObj(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctOutSmooth) {
|
||||
polys = polys.map(poly => {
|
||||
let pump = POLY.offset([ poly ], ctOutSmooth);
|
||||
pump = POLY.offset(pump, -ctOutSmooth);
|
||||
return POLY.setZ(pump, poly.getZ());
|
||||
}).flat().filter(v => v);
|
||||
}
|
||||
let layers = {};
|
||||
let mesh = [];
|
||||
for (let poly of polys) {
|
||||
let z = poly.getZ();
|
||||
let layer = layers[z];
|
||||
if (!layer) {
|
||||
layer = layers[z] = [];
|
||||
}
|
||||
layer.push(poly);
|
||||
}
|
||||
for (let rec of Object.entries(layers)) {
|
||||
let [ z, polys ] = rec;
|
||||
polys = POLY.nest(polys);
|
||||
for (let poly of polys) {
|
||||
let ext = poly.setZ(0).extrude(ctSliceHeight, { zadd: parseFloat(z) });
|
||||
mesh = mesh.concat(ext);
|
||||
}
|
||||
}
|
||||
return new STL().encode(mesh);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
@ -867,5 +932,6 @@ export const LASER = {
|
|||
export: laser_export,
|
||||
exportGCode,
|
||||
exportDXF,
|
||||
exportSVG
|
||||
exportSVG,
|
||||
exportSTL
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export function menu() {
|
|||
ctSliceHeightMin: newInput(LANG.ls_lahm_s, {title:LANG.ls_lahm_l, convert:toFloat, show:() => ui.ctSliceHeight.value == 0 && !ui.ctSliceSingle.checked }),
|
||||
separator: newBlank({ class:"set-sep", driven }),
|
||||
ctSliceSingle: newBoolean(LANG.ls_sngl_s, onBooleanClick, {title:LANG.ls_sngl_l}),
|
||||
ctOmitInner: newBoolean(LANG.we_omit_s, onBooleanClick, {title:LANG.we_omit_l, modes:WEDM}),
|
||||
ctOmitInner: newBoolean(LANG.we_omit_s, onBooleanClick, {title:LANG.we_omit_l}),
|
||||
_____: newGroup('surfaces', $('lzr-surface'), { modes:[-1], driven, separator }),
|
||||
ctSurfaces: newRow([
|
||||
(ui.faceAdd = newButton(undefined, onButtonClick, {icon:'<i class="fas fa-plus"></i>'})),
|
||||
|
|
@ -68,6 +68,10 @@ export function menu() {
|
|||
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 }),
|
||||
separator: newBlank({ class:"set-sep", driven, modes:LASER, show: () => ui.ctOutStack.checked }),
|
||||
ctOutClean: newBoolean('clean', onBooleanClick, { title:'clean', modes:LASER, show:() => ui.ctOutStack.checked }),
|
||||
ctOutFilter: newInput('filter', { title:'filter', modes:LASER, convert:toFloat, show:() => ui.ctOutStack.checked }),
|
||||
ctOutSmooth: newInput('smooth', { title:'smooth', modes:LASER, convert:toFloat, show:() => ui.ctOutStack.checked }),
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -691,6 +691,7 @@
|
|||
<div class="f-row box">
|
||||
<button id="print-svg" class="grow">svg</button>
|
||||
<button id="print-dxf" class="grow">dxf</button>
|
||||
<button id="print-stl" class="grow">stl</button>
|
||||
<button id="print-lg" class="grow">gcode</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue