diff --git a/app.js b/app.js
index fa1b22ef..f2e0d625 100644
--- a/app.js
+++ b/app.js
@@ -410,6 +410,7 @@ const script = {
"geo/slicer",
"geo/mesh",
// "moto/broker",
+ "kiri/consts",
"kiri/pack",
"kiri/utils",
"kiri/slice",
@@ -459,6 +460,7 @@ const script = {
"geo/polygon",
"geo/gyroid",
"geo/slicer",
+ "kiri/consts",
"kiri-mode/fdm/driver",
"kiri-mode/fdm/slice",
"kiri/utils",
diff --git a/src/kiri-mode/fdm/client.js b/src/kiri-mode/fdm/client.js
index 75e8ced7..d7991531 100644
--- a/src/kiri-mode/fdm/client.js
+++ b/src/kiri-mode/fdm/client.js
@@ -4,683 +4,682 @@
(function() {
- let KIRI = self.kiri,
- BASE = self.base,
- UTIL = BASE.util,
- FDM = KIRI.driver.FDM,
- SPACE, API, VIEWS, LANG, PROC, UI, UC,
- nextID = Date.now(),
- p1, p2, iw,
- lastBox,
- lastMode, lastView,
- lastPillar, fromPillar,
- isFdmMode = false,
- addingSupports = false,
- alert = [],
- boxes = {},
- func = {};
+const { Quaternion, Matrix4, Vector3, BufferAttribute, BufferGeometryUtils } = THREE;
+const { Mesh, BoxGeometry, MeshPhongMaterial, Raycaster, Euler } = THREE;
+const { base, kiri, moto } = self;
+const { util } = base;
+const { api, consts, lang } = kiri;
+const { FDM } = kiri.driver;
+const { VIEWS } = consts;
+const { space } = moto;
+const LANG = lang.current;
- FDM.init = function(kiri, api) {
- UI = api.ui;
- UC = api.uc;
- API = api;
- SPACE = kiri.space;
- VIEWS = kiri.consts.VIEWS;
- LANG = KIRI.lang.current;
- PROC = Object.keys(kiri.conf.defaults.fdm.p);
+let lastBox, lastMode, lastView, lastPillar, fromPillar,
+ addingSupports = false,
+ isFdmMode = false,
+ nextID = Date.now(),
+ p1, p2, iw,
+ alert = [],
+ boxes = {},
+ func = {};
- let rangeVars = {
- // slice
- "sliceShells": LANG.sl_shel_s,
- "sliceFillType": LANG.fi_type,
- "sliceFillWidth": LANG.fi_wdth_s,
- "sliceFillSparse": LANG.fi_pcnt_s,
- "sliceSolidMinArea": LANG.ad_msol_s,
- "firstLayerRate": LANG.fl_rate_s,
- "firstLayerPrintMult": LANG.fl_mult_s,
- // prepare
- "sliceShellOrder": LANG.sl_ordr_s,
- "sliceFillOverlap": LANG.fi_over_s,
- "outputFeedrate": LANG.ou_feed_s,
- "outputFinishrate": LANG.ou_fini_s,
- "outputShellMult": LANG.ou_shml_s,
- "outputFillMult": LANG.ou_flml_s,
- "outputSparseMult": LANG.ou_spml_s,
- "outputRetractWipe": LANG.ad_wpln_s,
- "outputShortPoly": LANG.ad_spol_s,
- "outputMinSpeed": LANG.ad_mins_s,
- "outputCoastDist": LANG.ad_scst_s,
- "outputLoops": LANG.ag_loop_s,
- "sliceSupportDensity": LANG.sp_dens_s,
- "sliceSupportOffset": LANG.sp_offs_s,
- "sliceLayerStart": LANG.sl_strt_s,
- "firstLayerBrim": LANG.fl_brim_s,
- "firstLayerBrimIn": LANG.fl_brin_s,
- "firstLayerBrimTrig": LANG.fl_brmn_s,
- "firstLayerBrimGap": LANG.fl_brgp_s,
- // export
- "zHopDistance": LANG.ad_zhop_s,
- "arcTolerance": LANG.ad_zhop_s,
- "antiBacklash": LANG.ad_abkl_s,
- "outputTemp": LANG.ou_nozl_s,
- "outputBedTemp": LANG.ou_bedd_s,
- "outputFanSpeed": LANG.ou_fans_s,
- "outputRetractDist": LANG.ad_rdst_s,
- "outputRetractSpeed": LANG.outputRetractSpeed,
- "outputRetractDwell": LANG.ad_rdwl_s,
- };
+FDM.init = function(kiri, api) {
+ const proc_keys = Object.keys(kiri.conf.defaults.fdm.p);
+ const { ui, uc } = api;
+ const rangeVars = {
+ // slice
+ "sliceShells": LANG.sl_shel_s,
+ "sliceFillType": LANG.fi_type,
+ "sliceFillWidth": LANG.fi_wdth_s,
+ "sliceFillSparse": LANG.fi_pcnt_s,
+ "sliceSolidMinArea": LANG.ad_msol_s,
+ "firstLayerRate": LANG.fl_rate_s,
+ "firstLayerPrintMult": LANG.fl_mult_s,
+ // prepare
+ "sliceShellOrder": LANG.sl_ordr_s,
+ "sliceFillOverlap": LANG.fi_over_s,
+ "outputFeedrate": LANG.ou_feed_s,
+ "outputFinishrate": LANG.ou_fini_s,
+ "outputShellMult": LANG.ou_shml_s,
+ "outputFillMult": LANG.ou_flml_s,
+ "outputSparseMult": LANG.ou_spml_s,
+ "outputRetractWipe": LANG.ad_wpln_s,
+ "outputShortPoly": LANG.ad_spol_s,
+ "outputMinSpeed": LANG.ad_mins_s,
+ "outputCoastDist": LANG.ad_scst_s,
+ "outputLoops": LANG.ag_loop_s,
+ "sliceSupportDensity": LANG.sp_dens_s,
+ "sliceSupportOffset": LANG.sp_offs_s,
+ "sliceLayerStart": LANG.sl_strt_s,
+ "firstLayerBrim": LANG.fl_brim_s,
+ "firstLayerBrimIn": LANG.fl_brin_s,
+ "firstLayerBrimTrig": LANG.fl_brmn_s,
+ "firstLayerBrimGap": LANG.fl_brgp_s,
+ // export
+ "zHopDistance": LANG.ad_zhop_s,
+ "arcTolerance": LANG.ad_zhop_s,
+ "antiBacklash": LANG.ad_abkl_s,
+ "outputTemp": LANG.ou_nozl_s,
+ "outputBedTemp": LANG.ou_bedd_s,
+ "outputFanSpeed": LANG.ou_fans_s,
+ "outputRetractDist": LANG.ad_rdst_s,
+ "outputRetractSpeed": LANG.outputRetractSpeed,
+ "outputRetractDwell": LANG.ad_rdwl_s,
+ };
- for (let key of Object.keys(rangeVars)) {
- if (UI[key]) {
- UI[key].range = true;
+ for (let key of Object.keys(rangeVars)) {
+ if (ui[key]) {
+ ui[key].range = true;
+ }
+ }
+
+ function filterSynth() {
+ api.widgets.filter((widget) => {
+ if (widget.track.synth) {
+ kiri.space.world.remove(widget.mesh);
+ kiri.Widget.Groups.remove(widget);
+ }
+ return !widget.track.synth
+ });
+ }
+
+ function clearRanges() {
+ api.conf.get().process.ranges = [];
+ ui.rangeGroup.firstElementChild.innerHTML = '';
+ }
+
+ function updateRanges(ranges = []) {
+ ui.rangeGroup.style.display = isFdmMode && ranges && ranges.length ? 'flex' : 'none';
+ let html = [];
+ let bind = [];
+ let now = Date.now();
+ let sorted = ranges.sort((a,b) => b.lo - a.lo);
+ for (let range of sorted) {
+ let id = (now++).toString(36);
+ let rows = Object.entries(range.fields).map(a => `
`).join('');
+ let hover = `${rows}
`;
+ let info = ``;
+ html.appendAll([
+ `${hover}${info}
`
+ ]);
+ bind.push({id, range});
+ }
+ ui.rangeGroup.firstElementChild.innerHTML = html.join('');
+ for (let rec of bind) {
+ $(`sel_${rec.id}`).onclick = () => {
+ api.show.layer(rec.range.hi, rec.range.lo);
+ };
+ $(`del_${rec.id}`).onclick = () => {
+ let io = ranges.indexOf(rec.range);
+ ranges.splice(io,1);
+ updateRanges(ranges);
+ };
+ }
+ }
+
+ api.event.on("boolean.click", func.updateSupportButtons = () => {
+ if (!isFdmMode) {
+ return;
+ }
+ for (let btn of [
+ ui.ssaGen,
+ ui.ssmAdd,
+ ui.ssmDun,
+ ui.ssmClr
+ ]) {
+ btn.disabled = ui.sliceSupportEnable.checked;
+ }
+ if (ui.sliceSupportEnable.checked) {
+ func.sclear();
+ }
+ });
+
+ api.event.on("function.animate", (mode) => {
+ if (!isFdmMode) {
+ return;
+ }
+ if (!api.conf.get().controller.danger) {
+ return;
+ }
+ let pct = 0;
+ let int = setInterval(() => {
+ if (pct <= 1) {
+ api.const.STACKS.setFraction(pct);
+ pct += 0.05;
+ } else {
+ api.const.STACKS.setFraction(1);
+ clearTimeout(int);
+ }
+ }, 50);
+ // let slider = $('top-slider');
+ // slider.style.display = 'flex';
+ // slider.oninput = slider.onchange = (ev) => {
+ // api.const.STACKS.setFraction(parseInt(ev.target.value)/100);
+ // };
+ });
+ api.event.on("mode.set", mode => {
+ isFdmMode = mode === 'FDM';
+ lastMode = mode;
+ updateVisiblity();
+ });
+ api.event.on("view.set", view => {
+ lastView = view;
+ updateVisiblity();
+ filterSynth();
+ func.sdone();
+ // let ranges = api.conf.get().process.ranges;
+ if (isFdmMode) {
+ if (lastView === VIEWS.SLICE) {
+ for (let key of proc_keys) {
+ if (ui[key] && !ui[key].range) {
+ ui[key].disabled = true;
+ }
+ }
+ } else {
+ for (let key of proc_keys) {
+ if (ui[key]) ui[key].disabled = false;
+ }
}
}
-
- function filterSynth() {
- api.widgets.filter((widget) => {
- if (widget.track.synth) {
- kiri.space.world.remove(widget.mesh);
- kiri.Widget.Groups.remove(widget);
+ // ui.rangeGroup.style.display = ranges && ranges.length ? 'flex' : 'none';
+ });
+ api.event.on("range.updates", updateRanges);
+ api.event.on("settings.load", (settings) => {
+ if (settings.mode !== 'FDM') return;
+ settings.process.outputOriginCenter = (settings.device.originCenter || false);
+ restoreSupports(api.widgets.all());
+ updateRanges(settings.process.ranges);
+ });
+ api.event.on("settings.saved", (settings) => {
+ updateRanges(settings.process.ranges);
+ func.updateSupportButtons();
+ // let ranges = settings.process.ranges;
+ // ui.rangeGroup.style.display = isFdmMode && ranges && ranges.length ? 'flex' : 'none';
+ });
+ api.event.on("button.click", target => {
+ switch (target) {
+ case api.ui.ssaGen: return func.sgen();
+ case api.ui.ssmAdd: return func.sadd();
+ case api.ui.ssmDun: return func.sdone();
+ case api.ui.ssmClr:
+ // return api.uc.confirm("clear supports?").then(ok => {
+ // if (ok) func.sclear();
+ // });
+ func.sclear();
+ }
+ });
+ api.event.on("fdm.supports.detect", func.sgen = () => {
+ let alerts = [];
+ let { process, device } = api.conf.get();
+ if (!device.bedBelt) {
+ if (process.sliceSupportAngle < 10) {
+ alerts.push(api.show.alert("angles below 10 degrees may fail"));
+ }
+ }
+ alerts.push(api.show.alert("analyzing part(s)...", 1000));
+ FDM.support_generate(array => {
+ func.sclear();
+ api.hide.alert(undefined,alerts);
+ for (let rec of array) {
+ let { widget, supports } = rec;
+ let wa = api.widgets.annotate(widget.id);
+ let ws = wa.support || [];
+ for (let support of supports) {
+ let { from, to, mid } = support;
+ let dw = api.conf.get().process.sliceSupportSize / 2;
+ let dh = from.z - to.z;
+ let rec = {
+ x: mid.x,
+ y: mid.y,
+ z: mid.z,
+ dw,
+ dh,
+ id: Math.random() * 0xffffffffff
+ };
+ addWidgetSupport(widget, rec);
+ ws.push(Object.clone(rec));
}
- return !widget.track.synth
+ wa.support = ws;
+ }
+ api.event.emit("fdm.supports.detected");
+ });
+ });
+ api.event.on("fdm.supports.add", func.sadd = () => {
+ alert = api.show.alert("[esc] key cancels support editing");
+ api.feature.hover = addingSupports = true;
+ });
+ api.event.on("fdm.supports.done", func.sdone = () => {
+ delbox('intZ');
+ delbox('intW');
+ delbox('supp');
+ api.hide.alert(alert);
+ api.feature.hover = addingSupports = false;
+ fromPillar = undefined;
+ });
+ api.event.on("fdm.supports.clear", func.sclear = () => {
+ func.sdone();
+ if (clearAllWidgetSupports()) {
+ api.conf.save();
+ }
+ });
+ api.event.on("slice.begin", () => {
+ if (!isFdmMode) {
+ return;
+ }
+ func.sdone();
+ updateVisiblity();
+
+ // synth support widget for each widget group
+ let synth = [];
+ for (let group of kiri.Widget.Groups.list()) {
+ let merge = group.filter(w => w.sups).map(w => Object.values(w.sups)).flat();
+ if (!merge.length) {
+ continue;
+ }
+ let boxen = merge.map(m => {
+ let geo = m.box.geometry.clone();
+ if (geo.index) geo = geo.toNonIndexed();
+ return geo.translate(m.x, m.y, m.z);
});
+ for (let box of boxen) {
+ // eliminate / normalize uv to allow other widget merge
+ box.setAttribute('uv',new BufferAttribute(new Float32Array(0), 3));
+ }
+ let bbg = BufferGeometryUtils.mergeBufferGeometries(boxen);
+ let sw = kiri.newWidget(null, group);
+ let fwp = group[0].track.pos;
+ sw.loadGeometry(bbg);
+ sw._move(fwp.x, fwp.y, fwp.z);
+ api.widgets.add(sw);
+ sw.track.synth = true;
+ kiri.space.world.add(sw.mesh);
}
-
- function clearRanges() {
- API.conf.get().process.ranges = [];
- UI.rangeGroup.firstElementChild.innerHTML = '';
+ });
+ api.event.on("slice.end", () => {
+ if (!isFdmMode) {
+ return;
}
-
- function updateRanges(ranges = []) {
- UI.rangeGroup.style.display = isFdmMode && ranges && ranges.length ? 'flex' : 'none';
- let html = [];
- let bind = [];
- let now = Date.now();
- let sorted = ranges.sort((a,b) => b.lo - a.lo);
- for (let range of sorted) {
- let id = (now++).toString(36);
- let rows = Object.entries(range.fields).map(a => `
`).join('');
- let hover = `${rows}
`;
- let info = ``;
- html.appendAll([
- `${hover}${info}
`
- ]);
- bind.push({id, range});
- }
- UI.rangeGroup.firstElementChild.innerHTML = html.join('');
- for (let rec of bind) {
- $(`sel_${rec.id}`).onclick = () => {
- api.show.layer(rec.range.hi, rec.range.lo);
- };
- $(`del_${rec.id}`).onclick = () => {
- let io = ranges.indexOf(rec.range);
- ranges.splice(io,1);
- updateRanges(ranges);
- };
- }
+ });
+ api.event.on("key.esc", () => {
+ if (!isFdmMode) {
+ return;
}
-
- api.event.on("boolean.click", func.updateSupportButtons = () => {
- if (!isFdmMode) {
- return;
- }
- for (let btn of [
- UI.ssaGen,
- UI.ssmAdd,
- UI.ssmDun,
- UI.ssmClr
- ]) {
- btn.disabled = UI.sliceSupportEnable.checked;
- }
- if (UI.sliceSupportEnable.checked) {
- func.sclear();
+ func.sdone()
+ });
+ api.event.on("selection.scale", () => {
+ if (isFdmMode) {
+ clearRanges();
+ func.sclear();
+ }
+ });
+ api.event.on("widget.delete", widget => {
+ if (isFdmMode) {
+ clearRanges();
+ return;
+ }
+ });
+ api.event.on("widget.duplicate", (widget, oldwidget) => {
+ if (!isFdmMode) {
+ return;
+ }
+ let ann = api.widgets.annotate(widget.id);
+ if (ann.support) {
+ for (let supp of Object.values(ann.support)) {
+ supp.id = Math.abs(Math.random() * 0xffffffffffff);
}
+ restoreSupports([widget]);
+ }
+ });
+ api.event.on("widget.mirror", widget => {
+ if (!isFdmMode) {
+ return;
+ }
+ let ann = api.widgets.annotate(widget.id);
+ let sups = ann.support || [];
+ sups.forEach(sup => {
+ let wsup = widget.sups[sup.id];
+ wsup.box.position.x = wsup.x = sup.x = -sup.x;
});
-
- api.event.on("function.animate", (mode) => {
- if (!isFdmMode) {
- return;
- }
- if (!API.conf.get().controller.danger) {
- return;
- }
- let pct = 0;
- let int = setInterval(function() {
- if (pct <= 1) {
- api.const.STACKS.setFraction(pct);
- pct += 0.05;
- } else {
- api.const.STACKS.setFraction(1);
- clearTimeout(int);
- }
- }, 50);
- // let slider = $('top-slider');
- // slider.style.display = 'flex';
- // slider.oninput = slider.onchange = (ev) => {
- // api.const.STACKS.setFraction(parseInt(ev.target.value)/100);
- // };
- });
- api.event.on("mode.set", mode => {
- isFdmMode = mode === 'FDM';
- lastMode = mode;
- updateVisiblity();
- });
- api.event.on("view.set", view => {
- lastView = view;
- updateVisiblity();
- filterSynth();
- func.sdone();
- // let ranges = API.conf.get().process.ranges;
- if (isFdmMode) {
- if (lastView === VIEWS.SLICE) {
- for (let key of PROC) {
- if (UI[key] && !UI[key].range) {
- UI[key].disabled = true;
- }
- }
- } else {
- for (let key of PROC) {
- if (UI[key]) UI[key].disabled = false;
- }
- }
- }
- // UI.rangeGroup.style.display = ranges && ranges.length ? 'flex' : 'none';
- });
- api.event.on("range.updates", updateRanges);
- api.event.on("settings.load", (settings) => {
- if (settings.mode !== 'FDM') return;
- settings.process.outputOriginCenter = (settings.device.originCenter || false);
- restoreSupports(api.widgets.all());
- updateRanges(settings.process.ranges);
- });
- api.event.on("settings.saved", (settings) => {
- updateRanges(settings.process.ranges);
- func.updateSupportButtons();
- // let ranges = settings.process.ranges;
- // UI.rangeGroup.style.display = isFdmMode && ranges && ranges.length ? 'flex' : 'none';
- });
- api.event.on("button.click", target => {
- switch (target) {
- case api.ui.ssaGen: return func.sgen();
- case api.ui.ssmAdd: return func.sadd();
- case api.ui.ssmDun: return func.sdone();
- case api.ui.ssmClr:
- // return api.uc.confirm("clear supports?").then(ok => {
- // if (ok) func.sclear();
- // });
- func.sclear();
- }
- });
- api.event.on("fdm.supports.detect", func.sgen = () => {
- let alerts = [];
- let { process, device } = api.conf.get();
- if (!device.bedBelt) {
- if (process.sliceSupportAngle < 10) {
- alerts.push(api.show.alert("angles below 10 degrees may fail"));
- }
- }
- alerts.push(api.show.alert("analyzing part(s)...", 1000));
- FDM.support_generate(array => {
- func.sclear();
- api.hide.alert(undefined,alerts);
- for (let rec of array) {
- let { widget, supports } = rec;
- let wa = API.widgets.annotate(widget.id);
- let ws = wa.support || [];
- for (let support of supports) {
- let { from, to, mid } = support;
- let dw = api.conf.get().process.sliceSupportSize / 2;
- let dh = from.z - to.z;
- let rec = {
- x: mid.x,
- y: mid.y,
- z: mid.z,
- dw,
- dh,
- id: Math.random() * 0xffffffffff
- };
- addWidgetSupport(widget, rec);
- ws.push(Object.clone(rec));
- }
- wa.support = ws;
- }
- api.event.emit("fdm.supports.detected");
- });
- });
- api.event.on("fdm.supports.add", func.sadd = () => {
- alert = api.show.alert("[esc] key cancels support editing");
- api.feature.hover = addingSupports = true;
- });
- api.event.on("fdm.supports.done", func.sdone = () => {
- delbox('intZ');
- delbox('intW');
- delbox('supp');
- api.hide.alert(alert);
- api.feature.hover = addingSupports = false;
- fromPillar = undefined;
- });
- api.event.on("fdm.supports.clear", func.sclear = () => {
- func.sdone();
- if (clearAllWidgetSupports()) {
- API.conf.save();
- }
- });
- api.event.on("slice.begin", () => {
- if (!isFdmMode) {
- return;
- }
- func.sdone();
- updateVisiblity();
-
- // synth support widget for each widget group
- let synth = [];
- for (let group of kiri.Widget.Groups.list()) {
- let merge = group.filter(w => w.sups).map(w => Object.values(w.sups)).flat();
- if (!merge.length) {
- continue;
- }
- let boxen = merge.map(m => {
- let geo = m.box.geometry.clone();
- if (geo.index) geo = geo.toNonIndexed();
- return geo.translate(m.x, m.y, m.z);
- });
- for (let box of boxen) {
- // eliminate / normalize uv to allow other widget merge
- box.setAttribute('uv',new THREE.BufferAttribute(new Float32Array(0), 3));
- }
- let bbg = THREE.BufferGeometryUtils.mergeBufferGeometries(boxen);
- let sw = kiri.newWidget(null, group);
- let fwp = group[0].track.pos;
- sw.loadGeometry(bbg);
- sw._move(fwp.x, fwp.y, fwp.z);
- api.widgets.add(sw);
- sw.track.synth = true;
- KIRI.space.world.add(sw.mesh);
- }
- });
- api.event.on("slice.end", () => {
- if (!isFdmMode) {
- return;
- }
- });
- api.event.on("key.esc", () => {
- if (!isFdmMode) {
- return;
- }
- func.sdone()
- });
- api.event.on("selection.scale", () => {
- if (isFdmMode) {
- clearRanges();
- func.sclear();
- }
- });
- api.event.on("widget.delete", widget => {
- if (isFdmMode) {
- clearRanges();
- return;
- }
- });
- api.event.on("widget.duplicate", (widget, oldwidget) => {
- if (!isFdmMode) {
- return;
- }
- let ann = API.widgets.annotate(widget.id);
- if (ann.support) {
- for (let supp of Object.values(ann.support)) {
- supp.id = Math.abs(Math.random() * 0xffffffffffff);
- }
- restoreSupports([widget]);
- }
- });
- api.event.on("widget.mirror", widget => {
- if (!isFdmMode) {
- return;
- }
- let ann = API.widgets.annotate(widget.id);
+ });
+ api.event.on("widget.rotate", rot => {
+ if (!isFdmMode) {
+ return;
+ }
+ let {widget, x, y, z} = rot;
+ if (x || y) {
+ clearWidgetSupports(widget);
+ } else {
+ let ann = api.widgets.annotate(widget.id);
let sups = ann.support || [];
sups.forEach(sup => {
let wsup = widget.sups[sup.id];
- wsup.box.position.x = wsup.x = sup.x = -sup.x;
+ let vc = new Vector3(sup.x, sup.y, sup.z);
+ let m4 = new Matrix4();
+ m4 = m4.makeRotationFromEuler(new Euler(x || 0, y || 0, z || 0));
+ vc.applyMatrix4(m4);
+ wsup.box.position.x = wsup.x = sup.x = vc.x;
+ wsup.box.position.y = wsup.y = sup.y = vc.y;
+ wsup.box.position.z = wsup.z = sup.z = vc.z;
});
- });
- api.event.on("widget.rotate", rot => {
- if (!isFdmMode) {
- return;
+ }
+ });
+ api.event.on("mouse.hover.up", func.hoverup = (on = {}) => {
+ let { event } = on;
+ if (!isFdmMode) {
+ return;
+ }
+ if (!addingSupports) {
+ return;
+ }
+ delbox('supp');
+ if (lastPillar) {
+ removeWidgetSupport(lastPillar.widget, lastPillar);
+ return;
+ }
+ if (!iw) return;
+ let { point, dim } = lastBox;
+ p1.y = Math.max(0, p1.y);
+ p2.y = Math.max(0, p2.y);
+ let rz = dim.rz;
+ let dh = dim.z;
+ let dw = api.conf.get().process.sliceSupportSize / 2;
+ let ip = iw.track.pos;
+ let wa = api.widgets.annotate(iw.id);
+ let ws = (wa.support = wa.support || []);
+ let id = ++nextID;
+ let rec = {x: point.x - ip.x, y: -point.z - ip.y, z: point.y, rz, dw, dh, id};
+ if (fromPillar && event && event.shiftKey) {
+ let targets = api.widgets.meshes().append(space.internals().platform);
+ let from = fromPillar,
+ d = {
+ x: rec.x - from.x,
+ y: rec.y - from.y,
+ z: rec.z - from.z
+ };
+ let dist = Math.sqrt(d.x*d.x + d.y*d.y + d.z*d.z);
+ let lerp = util.lerp(0,dist,dw).map(v => v/dist);
+ let angle = rec.rz = Math.atan2(d.y,d.x);
+ let fromrec = ws.filter(r => r.id === from.id)[0];
+ if (fromrec) {
+ from.rz = fromrec.rz = angle;
+ removeWidgetSupport(iw, fromPillar);
+ ws.push(Object.clone(fromrec));
+ addWidgetSupport(iw, fromrec);
}
- let {widget, x, y, z} = rot;
- if (x || y) {
- clearWidgetSupports(widget);
- } else {
- let ann = API.widgets.annotate(widget.id);
- let sups = ann.support || [];
- sups.forEach(sup => {
- let wsup = widget.sups[sup.id];
- let vc = new THREE.Vector3(sup.x, sup.y, sup.z);
- let m4 = new THREE.Matrix4();
- m4 = m4.makeRotationFromEuler(new THREE.Euler(x || 0, y || 0, z || 0));
- vc.applyMatrix4(m4);
- wsup.box.position.x = wsup.x = sup.x = vc.x;
- wsup.box.position.y = wsup.y = sup.y = vc.y;
- wsup.box.position.z = wsup.z = sup.z = vc.z;
- });
+ lerp.pop();
+ let seed = Math.random();
+ for (let pct of lerp) {
+ let point = new Vector3(from.x + d.x * pct, from.z + d.z * pct, from.y + d.y * pct);
+ addSupportAtPoint(targets, point, ip, angle, seed++);
}
- });
- api.event.on("mouse.hover.up", func.hoverup = (on = {}) => {
- let { event } = on;
- if (!isFdmMode) {
- return;
- }
- if (!addingSupports) {
- return;
- }
- delbox('supp');
- if (lastPillar) {
- removeWidgetSupport(lastPillar.widget, lastPillar);
- return;
- }
- if (!iw) return;
- let { point, dim } = lastBox;
- p1.y = Math.max(0, p1.y);
- p2.y = Math.max(0, p2.y);
- let rz = dim.rz;
- let dh = dim.z;
- let dw = api.conf.get().process.sliceSupportSize / 2;
- let ip = iw.track.pos;
- let wa = api.widgets.annotate(iw.id);
- let ws = (wa.support = wa.support || []);
- let id = ++nextID;
- let rec = {x: point.x - ip.x, y: -point.z - ip.y, z: point.y, rz, dw, dh, id};
- if (fromPillar && event && event.shiftKey) {
- let targets = api.widgets.meshes().append(SPACE.internals().platform);
- let from = fromPillar,
- d = {
- x: rec.x - from.x,
- y: rec.y - from.y,
- z: rec.z - from.z
- };
- let dist = Math.sqrt(d.x*d.x + d.y*d.y + d.z*d.z);
- let lerp = UTIL.lerp(0,dist,dw).map(v => v/dist);
- let angle = rec.rz = Math.atan2(d.y,d.x);
- let fromrec = ws.filter(r => r.id === from.id)[0];
- if (fromrec) {
- from.rz = fromrec.rz = angle;
- removeWidgetSupport(iw, fromPillar);
- ws.push(Object.clone(fromrec));
- addWidgetSupport(iw, fromrec);
- }
- lerp.pop();
- let seed = Math.random();
- for (let pct of lerp) {
- let point = new THREE.Vector3(from.x + d.x * pct, from.z + d.z * pct, from.y + d.y * pct);
- addSupportAtPoint(targets, point, ip, angle, seed++);
- }
- }
- ws.push(Object.clone(rec));
- fromPillar = addWidgetSupport(iw, rec);
- API.conf.save();
- });
- function addSupportAtPoint(targets, point, ip, angle, id) {
- let up = new THREE.Vector3(0,1,0)
- let dn = new THREE.Vector3(0,-1,0)
- let rp = new THREE.Vector3(point.x + ip.x, point.y, -point.z - ip.y);
- let iup = new THREE.Raycaster(rp, up)
+ }
+ ws.push(Object.clone(rec));
+ fromPillar = addWidgetSupport(iw, rec);
+ api.conf.save();
+ });
+ function addSupportAtPoint(targets, point, ip, angle, id) {
+ let up = new Vector3(0,1,0)
+ let dn = new Vector3(0,-1,0)
+ let rp = new Vector3(point.x + ip.x, point.y, -point.z - ip.y);
+ let iup = new Raycaster(rp, up)
+ .intersectObjects(targets, false)
+ .filter(t => !t.object.pillar);
+ if (iup.length && iup.length % 2 === 0) {
+ let idn = new Raycaster(rp, dn)
.intersectObjects(targets, false)
.filter(t => !t.object.pillar);
- if (iup.length && iup.length % 2 === 0) {
- let idn = new THREE.Raycaster(rp, dn)
- .intersectObjects(targets, false)
- .filter(t => !t.object.pillar);
- if (idn.length && idn.length % 2 === 0) {
- iw = iup[0].object.widget || iw;
- let wa = api.widgets.annotate(iw.id);
- let ws = (wa.support = wa.support || []);
- let phi = iup[0].point;
- let plo = idn[0].point;
- let ply = Math.max(0, plo.y);
- let mp = (phi.y + ply) / 2;
- let dy = Math.abs(phi.y - ply);
- let dw = api.conf.get().process.sliceSupportSize / 2;
- let rec = { rz:angle, x:point.x, y:point.z, z:mp, dw, dh:dy, id };
- ws.push(Object.clone(rec));
- fromPillar = addWidgetSupport(iw, rec);
- }
+ if (idn.length && idn.length % 2 === 0) {
+ iw = iup[0].object.widget || iw;
+ let wa = api.widgets.annotate(iw.id);
+ let ws = (wa.support = wa.support || []);
+ let phi = iup[0].point;
+ let plo = idn[0].point;
+ let ply = Math.max(0, plo.y);
+ let mp = (phi.y + ply) / 2;
+ let dy = Math.abs(phi.y - ply);
+ let dw = api.conf.get().process.sliceSupportSize / 2;
+ let rec = { rz:angle, x:point.x, y:point.z, z:mp, dw, dh:dy, id };
+ ws.push(Object.clone(rec));
+ fromPillar = addWidgetSupport(iw, rec);
}
}
- api.event.on("mouse.hover", data => {
- if (!isFdmMode) {
- return;
- }
- if (!addingSupports) {
- return;
- }
- delbox('supp');
- const { int, type, point, event } = data;
- const pillar = int ? int.object.pillar : undefined;
- if (lastPillar) {
- lastPillar.box.material.color.r = 0;
- lastPillar = null;
- }
- if (pillar) {
- pillar.box.material.color.r = 0.5;
- lastPillar = pillar;
- if (event && (event.metaKey || event.ctrlKey)) {
- return func.hoverup();
- }
- return;
- }
- if (int && type === 'widget') {
- iw = int.object.widget || iw;
- } else {
- iw = null;
- }
- p1 = point;
- let dir = new THREE.Vector3(0,1,0)
- let ray = new THREE.Raycaster(point, dir);
- let rz = int && int.face ? Math.atan2(int.face.normal.y, int.face.normal.x) : 0;
- // when on object, project down on downward faces
- if (int && int.face && int.face.normal.z < -0.1) {
- dir.y = -1;
- }
- let targets = api.widgets.meshes().append(SPACE.internals().platform);
- let i2 = ray.intersectObjects(targets, false)
- .filter(t => !t.object.pillar) // eliminate other pillars
- .filter(i => i.distance > 0.1); // false matches close to origin of ray
- if (i2.length && i2.length % 2 === 0) {
- p2 = i2[0].point;
- iw = i2[0].object.widget || iw;
- let p1y = Math.max(0, p1.y);
- let p2y = Math.max(0, p2.y);
- let hy = (p1y + p2y) / 2;
- let dy = Math.abs(p1y - p2y);
- let dw = api.conf.get().process.sliceSupportSize / 2;
- addbox({x:p1.x, y:hy, z:p1.z}, 0x0000dd, 'supp', {
- x:dw, y:dw, z:dy, rz
- });
- }
- if (event && event.altKey) {
+ }
+ api.event.on("mouse.hover", data => {
+ if (!isFdmMode) {
+ return;
+ }
+ if (!addingSupports) {
+ return;
+ }
+ delbox('supp');
+ const { int, type, point, event } = data;
+ const pillar = int ? int.object.pillar : undefined;
+ if (lastPillar) {
+ lastPillar.box.material.color.r = 0;
+ lastPillar = null;
+ }
+ if (pillar) {
+ pillar.box.material.color.r = 0.5;
+ lastPillar = pillar;
+ if (event && (event.metaKey || event.ctrlKey)) {
return func.hoverup();
}
- });
- api.event.on("export.debug", msg => {
- if (msg.arcQ && msg.arcQ.length > 2) {
- if (xpdebug) {
- xpdebug.destroy();
- xpdebug = undefined;
- }
- let poly = BASE.newPolygon().setOpen();
- for (let rec of msg.arcQ) {
- rec.z += 1;
- poly.addObj(rec);
- }
-
- let layers = new KIRI.Layers();
- let layer = layers.setLayer("arcq", { line: 0xff00ff, face: 0xff00ff, opacity: 1 });
- layer.addPoly(poly, {thin:true});
-
- layer = layers.setLayer("arcc", { line: 0xff0000, face: 0xff0000, opacity: 1 });
- for (let center of msg.arcQ.center) {
- center.z += 0.5;
- layer.addPoly(BASE.newPolygon().centerCircle(
- center, center.r, 20
- ));
- }
-
- xpdebug = new KIRI.Stack(SPACE.world, false);
- xpdebug.addLayers(layers);
- xpdebug.setVisible(0,Infinity);
- xpdebug.show();
-
- SPACE.refresh();
-
- console.log(msg, poly);
- }
- });
- }
-
- let xpdebug;
- let supsave = {};
- let suptimer;
-
- function scheduleSupportSave(widget) {
- clearTimeout(suptimer);
- supsave[widget.id] = widget;
- suptimer = setTimeout(() => {
- for (let w of Object.values(supsave)) {
- w.saveState();
- }
- }, 50);
- }
-
- function activeSupports() {
- const active = [];
- API.widgets.all().forEach(widget => {
- Object.values(widget.sups || {}).forEach(support => {
- active.push(support.box);
- support.box.support = true;
- });
- });
- return active;
- }
-
- function restoreSupports(widgets) {
- widgets.forEach(widget => {
- const supports = API.widgets.annotate(widget.id).support || [];
- supports.forEach(pos => {
- addWidgetSupport(widget, pos);
- });
- });
- }
-
- function addWidgetSupport(widget, pos) {
- const { x, y, z, rz, dw, dh, id } = pos;
- const sups = widget.sups = (widget.sups || {});
- // prevent duplicate restore from repeated settings load calls
- if (!sups[id]) {
- pos.box = addbox(
- { x, y, z }, 0x0000dd, id,
- { x:dw, y:dw, z:dh, rz }, { group: widget.mesh }
- );
- pos.box.pillar = Object.assign({widget}, pos);
- sups[id] = pos;
- widget.adds.push(pos.box);
- scheduleSupportSave(widget);
+ return;
}
- return sups[id];
- }
-
- function removeWidgetSupport(widget, rec) {
- const {box, id} = rec;
- widget.adds.remove(box);
- widget.mesh.remove(box);
- delete widget.sups[id];
- let sa = API.widgets.annotate(widget.id).support;
- let ix = 0;
- sa.forEach((rec,i) => {
- if (rec.id === id) {
- ix = i;
- }
- });
- sa.splice(ix,1);
- API.conf.save();
- scheduleSupportSave(widget);
- fromPillar = undefined;
- }
-
- function updateVisiblity() {
- API.widgets.all().forEach(w => {
- setSupportVisiblity(w, lastMode === 'FDM' && lastView === VIEWS.ARRANGE);
- });
- }
-
- function setSupportVisiblity(widget, bool) {
- Object.values(widget.sups || {}).forEach(support => {
- support.box.visible = bool;
- });
- }
-
- function clearAllWidgetSupports() {
- let cleared = 0;
- API.widgets.all().forEach(widget => {
- cleared += clearWidgetSupports(widget);
- });
- return cleared;
- }
-
- function clearWidgetSupports(widget) {
- let cleared = 0;
- Object.values(widget.sups || {}).forEach(support => {
- widget.adds.remove(support.box);
- widget.mesh.remove(support.box);
- cleared++;
- });
- widget.sups = {};
- delete API.widgets.annotate(widget.id).support;
- scheduleSupportSave(widget);
- return cleared;
- }
-
- function delbox(name) {
- const old = boxes[name];
- if (old) {
- old.groupTo.remove(old);
+ if (int && type === 'widget') {
+ iw = int.object.widget || iw;
+ } else {
+ iw = null;
}
- }
+ p1 = point;
+ let dir = new Vector3(0,1,0)
+ let ray = new Raycaster(point, dir);
+ let rz = int && int.face ? Math.atan2(int.face.normal.y, int.face.normal.x) : 0;
+ // when on object, project down on downward faces
+ if (int && int.face && int.face.normal.z < -0.1) {
+ dir.y = -1;
+ }
+ let targets = api.widgets.meshes().append(space.internals().platform);
+ let i2 = ray.intersectObjects(targets, false)
+ .filter(t => !t.object.pillar) // eliminate other pillars
+ .filter(i => i.distance > 0.1); // false matches close to origin of ray
+ if (i2.length && i2.length % 2 === 0) {
+ p2 = i2[0].point;
+ iw = i2[0].object.widget || iw;
+ let p1y = Math.max(0, p1.y);
+ let p2y = Math.max(0, p2.y);
+ let hy = (p1y + p2y) / 2;
+ let dy = Math.abs(p1y - p2y);
+ let dw = api.conf.get().process.sliceSupportSize / 2;
+ addbox({x:p1.x, y:hy, z:p1.z}, 0x0000dd, 'supp', {
+ x:dw, y:dw, z:dy, rz
+ });
+ }
+ if (event && event.altKey) {
+ return func.hoverup();
+ }
+ });
+ api.event.on("export.debug", msg => {
+ if (msg.arcQ && msg.arcQ.length > 2) {
+ if (xpdebug) {
+ xpdebug.destroy();
+ xpdebug = undefined;
+ }
+ let poly = base.newPolygon().setOpen();
+ for (let rec of msg.arcQ) {
+ rec.z += 1;
+ poly.addObj(rec);
+ }
- function addbox(point, color, name, dim = {x:1,y:1,z:1,rz:0}, opt = {}) {
- delbox(name);
- const box = boxes[name] = new THREE.Mesh(
- new THREE.BoxGeometry(dim.x, dim.y, dim.z),
- new THREE.MeshPhongMaterial({
- transparent: true,
- opacity: 0.5,
- color
- })
+ let layers = new kiri.Layers();
+ let layer = layers.setLayer("arcq", { line: 0xff00ff, face: 0xff00ff, opacity: 1 });
+ layer.addPoly(poly, {thin:true});
+
+ layer = layers.setLayer("arcc", { line: 0xff0000, face: 0xff0000, opacity: 1 });
+ for (let center of msg.arcQ.center) {
+ center.z += 0.5;
+ layer.addPoly(base.newPolygon().centerCircle(
+ center, center.r, 20
+ ));
+ }
+
+ xpdebug = new kiri.Stack(space.world, false);
+ xpdebug.addLayers(layers);
+ xpdebug.setVisible(0,Infinity);
+ xpdebug.show();
+
+ space.refresh();
+
+ console.log(msg, poly);
+ }
+ });
+}
+
+let xpdebug;
+let supsave = {};
+let suptimer;
+
+function scheduleSupportSave(widget) {
+ clearTimeout(suptimer);
+ supsave[widget.id] = widget;
+ suptimer = setTimeout(() => {
+ for (let w of Object.values(supsave)) {
+ w.saveState();
+ }
+ }, 50);
+}
+
+function activeSupports() {
+ const active = [];
+ api.widgets.all().forEach(widget => {
+ Object.values(widget.sups || {}).forEach(support => {
+ active.push(support.box);
+ support.box.support = true;
+ });
+ });
+ return active;
+}
+
+function restoreSupports(widgets) {
+ widgets.forEach(widget => {
+ const supports = api.widgets.annotate(widget.id).support || [];
+ supports.forEach(pos => {
+ addWidgetSupport(widget, pos);
+ });
+ });
+}
+
+function addWidgetSupport(widget, pos) {
+ const { x, y, z, rz, dw, dh, id } = pos;
+ const sups = widget.sups = (widget.sups || {});
+ // prevent duplicate restore from repeated settings load calls
+ if (!sups[id]) {
+ pos.box = addbox(
+ { x, y, z }, 0x0000dd, id,
+ { x:dw, y:dw, z:dh, rz }, { group: widget.mesh }
);
- box.position.x = point.x;
- box.position.y = point.y;
- box.position.z = point.z;
+ pos.box.pillar = Object.assign({widget}, pos);
+ sups[id] = pos;
+ widget.adds.push(pos.box);
+ scheduleSupportSave(widget);
+ }
+ return sups[id];
+}
- lastBox = {point, dim};
+function removeWidgetSupport(widget, rec) {
+ const { box, id } = rec;
+ widget.adds.remove(box);
+ widget.mesh.remove(box);
+ delete widget.sups[id];
+ let sa = api.widgets.annotate(widget.id).support;
+ let ix = 0;
+ sa.forEach((rec,i) => {
+ if (rec.id === id) {
+ ix = i;
+ }
+ });
+ sa.splice(ix,1);
+ api.conf.save();
+ scheduleSupportSave(widget);
+ fromPillar = undefined;
+}
- const group = opt.group || SPACE.scene
- group.add(box);
- box.groupTo = group;
+function updateVisiblity() {
+ api.widgets.all().forEach(w => {
+ setSupportVisiblity(w, lastMode === 'FDM' && lastView === VIEWS.ARRANGE);
+ });
+}
- if (dim.rz) {
- opt.rotate = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), dim.rz);
- }
- if (opt.rotate) {
- opt.matrix = new THREE.Matrix4().makeRotationFromQuaternion(opt.rotate);
- }
- if (opt.matrix) {
- box.geometry.applyMatrix4(opt.matrix);
- }
- return box;
+function setSupportVisiblity(widget, bool) {
+ Object.values(widget.sups || {}).forEach(support => {
+ support.box.visible = bool;
+ });
+}
+
+function clearAllWidgetSupports() {
+ let cleared = 0;
+ api.widgets.all().forEach(widget => {
+ cleared += clearWidgetSupports(widget);
+ });
+ return cleared;
+}
+
+function clearWidgetSupports(widget) {
+ let cleared = 0;
+ Object.values(widget.sups || {}).forEach(support => {
+ widget.adds.remove(support.box);
+ widget.mesh.remove(support.box);
+ cleared++;
+ });
+ widget.sups = {};
+ delete api.widgets.annotate(widget.id).support;
+ scheduleSupportSave(widget);
+ return cleared;
+}
+
+function delbox(name) {
+ const old = boxes[name];
+ if (old) {
+ old.groupTo.remove(old);
+ }
+}
+
+function addbox(point, color, name, dim = {x:1,y:1,z:1,rz:0}, opt = {}) {
+ delbox(name);
+
+ const box = boxes[name] = new Mesh(
+ new BoxGeometry(dim.x, dim.y, dim.z),
+ new MeshPhongMaterial({
+ transparent: true,
+ opacity: 0.5,
+ color
+ })
+ );
+ box.position.x = point.x;
+ box.position.y = point.y;
+ box.position.z = point.z;
+
+ lastBox = {point, dim};
+
+ const group = opt.group || space.scene
+ group.add(box);
+ box.groupTo = group;
+
+ if (dim.rz) {
+ opt.rotate = new Quaternion().setFromAxisAngle(new Vector3(0,0,1), dim.rz);
+ }
+ if (opt.rotate) {
+ opt.matrix = new Matrix4().makeRotationFromQuaternion(opt.rotate);
+ }
+ if (opt.matrix) {
+ box.geometry.applyMatrix4(opt.matrix);
}
- FDM.delbox = delbox;
- FDM.addbox = addbox;
- FDM.restoreSupports = restoreSupports;
+ return box;
+}
+
+FDM.delbox = delbox;
+FDM.addbox = addbox;
+FDM.restoreSupports = restoreSupports;
})();
diff --git a/src/kiri-mode/fdm/driver.js b/src/kiri-mode/fdm/driver.js
index e3eff5d5..d248b63f 100644
--- a/src/kiri-mode/fdm/driver.js
+++ b/src/kiri-mode/fdm/driver.js
@@ -4,69 +4,66 @@
(function() {
- const KIRI = self.kiri,
- BASE = self.base,
- UTIL = BASE.util,
- POLY = BASE.polygons,
- FDM = KIRI.driver.FDM = {
- // init, // src/mode/fdm/client.js
- // slice, // src/mode/fdm/slice.js
- // prepare, // src/mode/fdm/prepare.js
- // export, // src/mode/fdm/export.js
- getRangeParameters
- };
+const { kiri } = self;
+const FDM = kiri.driver.FDM = { getRangeParameters };
- function getRangeParameters(process, index) {
- if (index === undefined || index === null || index < 0) {
- return process;
- }
- let ranges = process.ranges;
- if (!(ranges && ranges.length)) {
- return process;
- }
- let params = Object.clone(process);
- for (let range of ranges) {
- if (index >= range.lo && index <= range.hi) {
- for (let [key,value] of Object.entries(range.fields)) {
- params[key] = value;
- params._range = true;
- }
+// shared by client and worker contexts
+function getRangeParameters(process, index) {
+ if (index === undefined || index === null || index < 0) {
+ return process;
+ }
+ let ranges = process.ranges;
+ if (!(ranges && ranges.length)) {
+ return process;
+ }
+ let params = Object.clone(process);
+ for (let range of ranges) {
+ if (index >= range.lo && index <= range.hi) {
+ for (let [key, value] of Object.entries(range.fields)) {
+ params[key] = value;
+ params._range = true;
}
}
- return params;
}
+ return params;
+}
- // defer loading until KIRI.client and KIRI.worker exist
- KIRI.load(function(API) {
+// defer loading until client and worker exist
+kiri.load(function(api) {
- if (KIRI.client)
- // FDM.support_generate = KIRI.client.fdm_support_generate = function(ondone) {
+ const { client, worker } = kiri;
+
+ if (client) {
FDM.support_generate = function(ondone) {
- KIRI.client.clear();
- KIRI.client.sync();
- let settings = API.conf.get();
- let widgets = API.widgets.map();
- KIRI.client.send("fdm_support_generate", { settings }, (gen) => {
+ client.clear();
+ client.sync();
+ const settings = api.conf.get();
+ const widgets = api.widgets.map();
+ client.send("fdm_support_generate", { settings }, (gen) => {
if (gen && gen.error) {
- API.show.alert('support generation canceled');
+ api.show.alert('support generation canceled');
return ondone([]);
}
- for (let g of gen) g.widget = widgets[g.id];
+ for (let g of gen) {
+ g.widget = widgets[g.id];
+ }
ondone(gen);
});
};
+ }
- if (KIRI.worker)
- KIRI.worker.fdm_support_generate = function(data, send) {
+ if (worker) {
+ worker.fdm_support_generate = function(data, send) {
const { settings } = data;
const widgets = Object.values(wcache);
const fresh = widgets.filter(widget => FDM.supports(settings, widget));
- send.done(KIRI.codec.encode(fresh.map(widget => { return {
+ send.done(kiri.codec.encode(fresh.map(widget => { return {
id: widget.id,
supports: widget.supports,
} } )));
};
+ }
- });
+});
})();
diff --git a/src/kiri-mode/fdm/export.js b/src/kiri-mode/fdm/export.js
index 3539c265..80184290 100644
--- a/src/kiri-mode/fdm/export.js
+++ b/src/kiri-mode/fdm/export.js
@@ -4,945 +4,940 @@
(function() {
- let KIRI = self.kiri,
- BASE = self.base,
- UTIL = BASE.util,
- FDM = KIRI.driver.FDM,
- debug = false;
+const { base, kiri } = self;
+const { util } = base;
+const { FDM } = kiri.driver;
+const { getRangeParameters } = FDM;
- /**
- * @returns {Array} gcode lines
- */
- FDM.export = function(print, online, ondone, ondebug) {
- let layers = print.output,
- settings = print.settings,
- controller = settings.controller,
- thumbnails = controller.exportThumb,
- getRangeParameters = FDM.getRangeParameters,
- device = settings.device,
- extras = device.extras || {},
- extruders = device.extruders,
- extused = Object.keys(print.extruders).map(v => parseInt(v)),
- gcodeFan = device.gcodeFan,
- gcodeLayer = device.gcodeLayer,
- gcodeTrack = device.gcodeTrack,
- zMoveMax = device.deviceZMax || 0,
- tool = 0,
- fwRetract = device.fwRetract,
- isDanger = controller.danger,
- isBelt = device.bedBelt,
- bedType = isBelt ? "belt" : "fixed",
- extruder = extruders[tool],
- offset_x = extruder.extOffsetX,
- offset_y = extruder.extOffsetY,
- extrudeAbs = device.extrudeAbs || false,
- extrudeSet = false,
- time = 0,
- layer = 0,
- layerno = 0,
- pause = [],
- pauseCmd = device.gcodePause,
- output = [],
- outputLength = 0, // absolute extruder position
- lastProgress = 0,
- decimals = BASE.config.gcode_decimals || 4,
- progress = 0,
- distance = 0,
- emitted = 0,
- retracted = 0,
- pos = {x:0, y:0, z:0, f:0},
- lout = {x:0, y:0, z:0},
- last = null,
- zpos = 0,
- bmax = 0,
- blastz = 0,
- process = settings.process,
- belt_add_y = (process.firstLayerYOffset || 0) - (print.belty || 0),
- oloops = process.outputLoops || 0,
- zhop = process.zHopDistance || 0, // range
- lineWidth = process.sliceLineWidth || 0,
- seekMMM = process.outputSeekrate * 60,
- retDist = process.outputRetractDist || 0, // range
- retSpeed = process.outputRetractSpeed * 60 || 1, // range
- retDwell = process.outputRetractDwell || 0, // range
- timeDwell = retDwell / 1000,
- arcDist = isBelt || !isDanger ? 0 : (process.arcTolerance || 0),
- arcMin = 1,
- arcRes = 20,
- arcDev = 0.5,
- arcMax = 40,
- originCenter = process.outputOriginCenter || device.bedRound,
- offset = originCenter ? {
- x: 0,
- y: 0,
- z: 0
- } : {
- x: device.bedWidth/2,
- y: isBelt ? 0 : device.bedDepth/2,
- z: 0
- },
- nozzleTemp = process.firstLayerNozzleTemp || process.outputTemp,
- bedTemp = process.firstLayerBedTemp || process.outputBedTemp,
- fanSpeed = undefined,
- lastType = undefined,
- lastNozzleTemp = nozzleTemp,
- lastBedTemp = bedTemp,
- lastFanSpeed = fanSpeed,
- bounds = settings.bounds,
- subst = {
- minx: bounds.min.x + offset.x,
- maxx: bounds.max.x + offset.x,
- miny: bounds.min.y + offset.y,
- maxy: bounds.max.y + offset.y,
- travel_speed: seekMMM,
- retract_speed: retSpeed,
- retract_distance: retDist,
- temp: nozzleTemp,
- temp_bed: bedTemp,
- bed_temp: bedTemp,
- fan_speed: fanSpeed,
- speed: fanSpeed, // legacy
- top: offset.y ? device.bedDepth : device.bedDepth/2,
- left: offset.x ? 0 : -device.bedWidth/2,
- right: offset.x ? device.bedWidth : device.bedWidth/2,
- bottom: offset.y ? 0 : -device.bedDepth/2,
- z_max: device.maxHeight,
- layers: layers.length,
- progress: 0,
- nozzle: 0,
- tool: 0
- },
- pidx, path, out, speedMMM, emitMM, emitPerMM, lastp, laste, dist,
- lines = 0,
- bytes = 0,
- bcos = Math.cos(Math.PI/4),
- icos = 1 / bcos,
- inloops = 0,
- arcQ = [],
- minz = { x: Infinity, y: Infinity, z: Infinity },
- // lenghts of each filament (by nozzle) consumed
- segments = [],
- // palette & ping data
- isPalette = device.filamentSource === 'palette3',
- paletteInfo = extras.palette || {},
- palettePingStart = Math.max(paletteInfo.ping, paletteInfo.feed, paletteInfo.push) || 500,
- palettePingSpace = paletteInfo.ping || 0,
- // track purges for palette3 pings
- purgePos,
- purgeOn = 0,
- purgeOff = 0;
+FDM.export = function(print, online, ondone, ondebug) {
+ const { settings, belty, tools } = print;
+ const { controller, device } = settings;
+ const { extruders } = device;
- let tools_used = Object.keys(print.tools);
- for (let tool of tools_used) {
- subst[`tool_used_${tool}`] = true;
- }
- subst.tool_count = tools_used.length;
+ let layers = print.output,
+ thumbnails = controller.exportThumb,
+ extras = device.extras || {},
+ extused = Object.keys(print.extruders).map(v => parseInt(v)),
+ gcodeFan = device.gcodeFan,
+ gcodeLayer = device.gcodeLayer,
+ gcodeTrack = device.gcodeTrack,
+ zMoveMax = device.deviceZMax || 0,
+ tool = 0,
+ fwRetract = device.fwRetract,
+ isDanger = controller.danger,
+ isBelt = device.bedBelt,
+ bedType = isBelt ? "belt" : "fixed",
+ extruder = extruders[tool],
+ offset_x = extruder.extOffsetX,
+ offset_y = extruder.extOffsetY,
+ extrudeAbs = device.extrudeAbs || false,
+ extrudeSet = false,
+ time = 0,
+ layer = 0,
+ layerno = 0,
+ pause = [],
+ pauseCmd = device.gcodePause,
+ output = [],
+ outputLength = 0, // absolute extruder position
+ lastProgress = 0,
+ decimals = base.config.gcode_decimals || 4,
+ progress = 0,
+ distance = 0,
+ emitted = 0,
+ retracted = 0,
+ pos = {x:0, y:0, z:0, f:0},
+ lout = {x:0, y:0, z:0},
+ last = null,
+ zpos = 0,
+ bmax = 0,
+ blastz = 0,
+ process = settings.process,
+ belt_add_y = (process.firstLayerYOffset || 0) - (belty || 0),
+ oloops = process.outputLoops || 0,
+ zhop = process.zHopDistance || 0, // range
+ lineWidth = process.sliceLineWidth || 0,
+ seekMMM = process.outputSeekrate * 60,
+ retDist = process.outputRetractDist || 0, // range
+ retSpeed = process.outputRetractSpeed * 60 || 1, // range
+ retDwell = process.outputRetractDwell || 0, // range
+ timeDwell = retDwell / 1000,
+ arcDist = isBelt || !isDanger ? 0 : (process.arcTolerance || 0),
+ arcMin = 1,
+ arcRes = 20,
+ arcDev = 0.5,
+ arcMax = 40,
+ originCenter = process.outputOriginCenter || device.bedRound,
+ offset = originCenter ? {
+ x: 0,
+ y: 0,
+ z: 0
+ } : {
+ x: device.bedWidth/2,
+ y: isBelt ? 0 : device.bedDepth/2,
+ z: 0
+ },
+ nozzleTemp = process.firstLayerNozzleTemp || process.outputTemp,
+ bedTemp = process.firstLayerBedTemp || process.outputBedTemp,
+ fanSpeed = undefined,
+ lastType = undefined,
+ lastNozzleTemp = nozzleTemp,
+ lastBedTemp = bedTemp,
+ lastFanSpeed = fanSpeed,
+ bounds = settings.bounds,
+ subst = {
+ minx: bounds.min.x + offset.x,
+ maxx: bounds.max.x + offset.x,
+ miny: bounds.min.y + offset.y,
+ maxy: bounds.max.y + offset.y,
+ travel_speed: seekMMM,
+ retract_speed: retSpeed,
+ retract_distance: retDist,
+ temp: nozzleTemp,
+ temp_bed: bedTemp,
+ bed_temp: bedTemp,
+ fan_speed: fanSpeed,
+ speed: fanSpeed, // legacy
+ top: offset.y ? device.bedDepth : device.bedDepth/2,
+ left: offset.x ? 0 : -device.bedWidth/2,
+ right: offset.x ? device.bedWidth : device.bedWidth/2,
+ bottom: offset.y ? 0 : -device.bedDepth/2,
+ z_max: device.maxHeight,
+ layers: layers.length,
+ progress: 0,
+ nozzle: 0,
+ tool: 0
+ },
+ pidx, path, out, speedMMM, emitMM, emitPerMM, lastp, laste, dist,
+ lines = 0,
+ bytes = 0,
+ bcos = Math.cos(Math.PI/4),
+ icos = 1 / bcos,
+ inloops = 0,
+ arcQ = [],
+ minz = { x: Infinity, y: Infinity, z: Infinity },
+ // lenghts of each filament (by nozzle) consumed
+ segments = [],
+ // palette & ping data
+ isPalette = device.filamentSource === 'palette3',
+ paletteInfo = extras.palette || {},
+ palettePingStart = Math.max(paletteInfo.ping, paletteInfo.feed, paletteInfo.push) || 500,
+ palettePingSpace = paletteInfo.ping || 0,
+ // track purges for palette3 pings
+ purgePos,
+ purgeOn = 0,
+ purgeOff = 0;
- // smallish band-aid. refactor above to remove redundancy
- function updateParams(layer) {
- let params = getRangeParameters(process, layer);
- zhop = params.zHopDistance || 0; // range
- retDist = params.outputRetractDist || 0; // range
- retSpeed = params.outputRetractSpeed * 60 || 1; // range
- retDwell = params.outputRetractDwell || 0; // range
- timeDwell = retDwell / 1000;
- nozzleTemp = layer === 0 ?
- params.firstLayerNozzleTemp || params.outputTemp :
- params.outputTemp || params.firstLayerNozzleTemp;
- bedTemp = layer === 0 ?
- params.firstLayerBedTemp || params.outputBedTemp :
- params.outputBedTemp || params.firstLayerBedTemp;
- fanSpeed = layer === 0 ?
- params.firstLayerFanSpeed || 0 :
- params.outputFanSpeed || 0;
- Object.assign(subst, {
- temp_bed: bedTemp,
- bed_temp: bedTemp,
- fan_speed: fanSpeed,
- speed: fanSpeed, // legacy
- retract_speed: retSpeed,
- retract_distance: retDist,
- temp: params.outputTemp, // range
- temp_bed: params.outputBedTemp, // range
- bed_temp: params.outputBedTemp, // range
- });
- }
+ let tools_used = Object.keys(tools);
+ for (let tool of tools_used) {
+ subst[`tool_used_${tool}`] = true;
+ }
+ subst.tool_count = tools_used.length;
- let rloops = [];
- if (oloops > 0) {
- // if oloops set, loop entire part
- rloops.push({
- start: layers[0].slice.index,
- end: Infinity,
- iter: oloops - 1
- });
- }
- if (process.ranges) {
- // collect loops from ranges and synth range array
- for (let range of process.ranges) {
- if (range.fields.outputLoops) {
- rloops.push({
- start: range.lo,
- end: range.hi,
- iter: range.fields.outputLoops - 1
- });
- }
- }
- }
- let loops = isBelt && rloops.length ? rloops : undefined;
-
- function append(line) {
- if (line) {
- lines++;
- bytes += line.length;
- output.append(line);
- }
- // batch gcode output to free memory in worker
- // consider alternate transfer schemes like indexeddb
- // since this transfers the burden to the main thread
- if (!line || output.length > 1000) {
- online(output.join("\n"));
- output = [];
- }
- };
-
- function appendSubPad(line, pad) {
- appendSub(line, true);
- }
-
- let subon = true;
- let ifhit = false;
-
- // eval() based var substitution (from print)
- // with logic flow IF / ELIF / ELSE / END
- // IF / IF is valid but will not nest
- function appendSub(line, pad) {
- if (line.indexOf(';; IF ') === 0) {
- line = line.substring(6).trim();
- let evil = print.constReplace(line, subst, 0, 666);
- subon = evil;
- ifhit = subon;
- } else if (line.indexOf(';; ELIF ') === 0) {
- line = line.substring(6).trim();
- let evil = print.constReplace(line, subst, 0, 666);
- subon = !subon && evil;
- ifhit = ifhit || subon;
- } else if (line.indexOf(';; ELSE') === 0) {
- subon = !ifhit && !subon;
- } else if (line.indexOf(';; END') === 0) {
- subon = true;
- ifhit = false;
- } else if (subon) {
- append(print.constReplace(line, subst, 0, pad));
- }
- }
-
- function appendAll(arr) {
- if (!arr) return;
- if (!Array.isArray(arr)) arr = [ arr ];
- arr.forEach(function(line) { append(line) });
- }
-
- function appendAllSub(arr, pad) {
- if (!arr || arr.length === 0) return;
- if (!Array.isArray(arr)) arr = [ arr ];
- arr.forEach(function(line) { appendSub(line, pad) });
- }
-
- function appendTok() {
- let arg = [...arguments].map(v => {
- return typeof(v) === 'object' ? JSON.stringify(v) : v;
- });
- append(arg.join(''));
- }
-
- append(`; Generated by Kiri:Moto ${KIRI.version}`);
- append(`; ${new Date().toString()}`);
- appendSub("; Bed left:{left} right:{right} top:{top} bottom:{bottom}");
- append(`; Bed type: ${bedType}`);
- append(`; Target: ${settings.filter[settings.mode]}`);
- // inject thumbnail preview
- if (thumbnails && worker.snap) {
- let { width, height, url } = worker.snap;
- let data = url.substring(url.indexOf(',') + 1);
- append(`; thumbnail begin ${width} ${height} ${data.length}`);
- for (let i=0; i 0) {
- line.split(";")[0].split(' ').forEach(function (tok) {
- // use max E position from gcode-preamble
- if (tok[0] == 'E') {
- if (extrudeAbs) {
- outputLength = Math.max(outputLength, parseFloat(tok.substring(1)) || 0);
- emitted = outputLength;
- } else {
- emitted += parseFloat(tok.substring(1) || 0);
- }
- }
- });
- }
- if (line.indexOf("{tool}") > 0 && extused.length > 0) {
- for (let i of extused) {
- subst.tool = i;
- appendSubPad(line);
- }
- subst.tool = 0;
- } else {
- appendSubPad(line);
- }
- }
-
- function dwell(ms) {
- time += ms/1000;
- // break up dwell times over 4s because of
- // limitations in some firmwares
- while (ms > 0) {
- let wait = Math.min(4000, ms);
- append(`G4 P${wait}`);
- ms -= wait;
- if (ms) {
- append('G1');
- }
- }
- }
-
- function retract(zhop) {
- if (retracted) {
- // console.log({double_retract: zhop});
- return;
- }
- retracted = retDist;
- if (fwRetract) {
- append('G10');
- } else {
- moveTo({e:-retracted}, retSpeed, `e-retract ${retDist}`);
- }
- if (zhop) moveTo({z:zpos + zhop}, seekMMM, "z-hop start");
- time += (retDist / retSpeed) * 60 * 2; // retraction time
- }
-
- function unretract() {
- if (!retracted) {
- return;
- }
- drainQ();
- // console.log({engage:zhop});
- // when enabled, resume previous Z
- if (zhop && pos.z != zpos) moveTo({z:zpos}, seekMMM, "z-hop end");
- // re-engage retracted filament
- if (fwRetract) {
- append('G11');
- } else {
- moveTo({e:retracted}, retSpeed, `e-engage ${retracted}`);
- }
- retracted = 0;
- // optional dwell after re-engaging filament to allow pressure to build
- if (retDwell) dwell(retDwell);
- time += (retDist / retSpeed) * 60 * 2; // retraction time
- }
-
- let taxis = new THREE.Vector3( 1, 0, 0 );
- let tcent = new THREE.Vector2( 0, 0 );
- let angle = -Math.PI / 4;
- let savePos = pos;
-
- function pushPos(newpos, rate, comment) {
- savePos = Object.clone(pos);
- moveTo({
- x: newpos.x + offset.x,
- y: newpos.y + offset.y,
- z: newpos.z + offset.z
- }, rate, comment);
- }
-
- function popPos(rate, comment) {
- moveTo(savePos, rate, comment);
- }
-
- function moveTo(newpos, rate, comment) {
- if (pingRemain) {
- if (newpos.e) {
- if (pingRemain - newpos.e < -0.4) {
- // split move if ping over-extrudes? complicates emitted calc.
- // console.log({over_ping: pingRemain - newpos.e});
- }
- pingRemain -= newpos.e;
- }
- }
- let o = [!rate && !newpos.e ? 'G0' : 'G1'];
- let emit = { x: false, y: false, z: false };
- if (typeof newpos.x === 'number' && newpos.x !== pos.x) {
- pos.x = newpos.x;
- emit.x = true;
- }
- if (typeof newpos.y === 'number' && newpos.y !== pos.y) {
- pos.y = newpos.y;
- emit.y = true;
- if (isBelt) emit.z = true;
- }
- if (typeof newpos.z === 'number' && newpos.z !== pos.z) {
- pos.z = newpos.z;
- emit.z = true;
- if (isBelt) emit.y = true;
- }
- let epos = isBelt ? { x: pos.x, y: pos.y, z: pos.z } : pos;
- if (isBelt) {
- let zheight = path ? path.height || 0 : 0;
- epos.x = originCenter ? -pos.x : device.bedWidth - pos.x;
- epos.z = blastz = pos.z * icos;
- epos.y = -pos.y + epos.z * bcos + belt_add_y;
- lout = epos;
- }
- if (emit.x) o.append(" X").append(epos.x.toFixed(decimals));
- if (emit.y) o.append(" Y").append(epos.y.toFixed(decimals));
- if (emit.z) o.append(" Z").append(epos.z.toFixed(decimals));
- if (debug) {
- if (emit.x) minz.x = Math.min(minz.x, epos.x);
- if (emit.y) minz.y = Math.min(minz.y, epos.y);
- if (emit.z) minz.z = Math.min(minz.z, epos.z);
- }
- if (typeof newpos.e === 'number') {
- outputLength += newpos.e;
- if (extrudeAbs) {
- // for cumulative (absolute) extruder positions
- o.append(" E").append(outputLength.toFixed(decimals));
- } else {
- o.append(" E").append(newpos.e.toFixed(decimals));
- }
- }
- if (zMoveMax && emit.z) {
- rate = Math.min(zMoveMax, rate) * 60;
- }
- if (rate && rate != pos.f) {
- o.append(" F").append(Math.round(rate));
- pos.f = rate
- }
- if (comment) {
- o.append(` ; ${comment}`);
- }
- if (o.length === 1) {
- // console.trace({no_move: o, out, newpos, pos, lastp, emit});
- return;
- }
- let line = o.join('');
- if (last == line) {
- // console.log({dup:line});
- return;
- }
- last = line;
- append(line);
- }
-
- // calc total distance traveled by head as proxy for progress
- let allout = [];
- let totaldistance = 0;
- layers.forEach(function(outs) {
- allout.appendAll(outs);
+ // smallish band-aid. refactor above to remove redundancy
+ function updateParams(layer) {
+ let params = getRangeParameters(process, layer);
+ zhop = params.zHopDistance || 0; // range
+ retDist = params.outputRetractDist || 0; // range
+ retSpeed = params.outputRetractSpeed * 60 || 1; // range
+ retDwell = params.outputRetractDwell || 0; // range
+ timeDwell = retDwell / 1000;
+ nozzleTemp = layer === 0 ?
+ params.firstLayerNozzleTemp || params.outputTemp :
+ params.outputTemp || params.firstLayerNozzleTemp;
+ bedTemp = layer === 0 ?
+ params.firstLayerBedTemp || params.outputBedTemp :
+ params.outputBedTemp || params.firstLayerBedTemp;
+ fanSpeed = layer === 0 ?
+ params.firstLayerFanSpeed || 0 :
+ params.outputFanSpeed || 0;
+ Object.assign(subst, {
+ temp_bed: bedTemp,
+ bed_temp: bedTemp,
+ fan_speed: fanSpeed,
+ speed: fanSpeed, // legacy
+ retract_speed: retSpeed,
+ retract_distance: retDist,
+ temp: params.outputTemp, // range
+ temp_bed: params.outputBedTemp, // range
+ bed_temp: params.outputBedTemp, // range
});
- allout.forEachPair(function (o1, o2) {
- totaldistance += o1.point.distTo2D(o2.point);
- }, 1);
+ }
- // for palette pings, amount of extrusion left
- let pingRemain = 0;
-
- // retract before first move
- retract();
-
- while (layer < layers.length) {
- path = layers[layer];
- layerno = path.slice.index;
-
- // range overrides
- if (path.layer >= 0) {
- updateParams(path.layer);
+ let rloops = [];
+ if (oloops > 0) {
+ // if oloops set, loop entire part
+ rloops.push({
+ start: layers[0].slice.index,
+ end: Infinity,
+ iter: oloops - 1
+ });
+ }
+ if (process.ranges) {
+ // collect loops from ranges and synth range array
+ for (let range of process.ranges) {
+ if (range.fields.outputLoops) {
+ rloops.push({
+ start: range.lo,
+ end: range.hi,
+ iter: range.fields.outputLoops - 1
+ });
}
-
- emitPerMM = print.extrudePerMM(
- lineWidth || extruder.extNozzle,
- extruder.extFilament,
- path.layer === 0 ?
- (process.firstSliceHeight || process.sliceHeight) : path.height);
-
- zpos = path.z || zpos;
- bmax = Math.max(bmax, pos.z * icos);
- subst.z = subst.Z = zpos.round(3);
- subst.e = subst.E = outputLength;
- subst.layer = layer;
- subst.height = path.height;
-
- if (isBelt) {
- pos.z = zpos;
- }
-
- if (pauseCmd && pause.indexOf(layer) >= 0) {
- appendAllSub(pauseCmd)
- }
-
- let endloop = 0;
- if (loops) {
- for (let loop of loops) {
- if (layerno === loop.start && !loop.started) {
- loop.started = true;
- append(`M808 L${loop.iter}`);
- if (extrudeAbs) {
- append(`G92 Z${lout.z.round(decimals)} E${outputLength.round(decimals)}`);
- } else {
- append(`G92 Z${lout.z.round(decimals)}`);
- }
- inloops++;
- }
- if (layerno === loop.end && !loop.ended) {
- loop.ended = true;
- endloop++;
- inloops--;
- }
- }
- }
-
- if (gcodeLayer && gcodeLayer.length) {
- appendAllSub(gcodeLayer);
- } else {
- append(`;; --- layer ${layer} (${subst.height.toFixed(3)} @ ${subst.z.round(3)}) ---`);
- }
-
- // layer temp and fan overrides at layer changes
- if (fanSpeed !== lastFanSpeed) {
- appendAllSub(gcodeFan);
- lastFanSpeed = fanSpeed;
- }
- if (bedTemp !== lastBedTemp) {
- append(`M140 S${bedTemp} T0`);
- lastBedTemp = bedTemp;
- }
- if (nozzleTemp !== lastNozzleTemp) {
- if (t0) append(`M104 S${nozzleTemp} T0`);
- if (t1) append(`M104 S${nozzleTemp} T1`);
- if (!(t0 || t1)) append(`M104 S${nozzleTemp} T${tool}`);
- lastNozzleTemp = nozzleTemp;
- }
-
- // move Z to layer height
- if (layer > 0 || !isBelt) {
- moveTo({z:zpos}, seekMMM);
- }
-
- // iterate through layer outputs
- for (pidx=0; pidx= palettePingStart) {
- if (purgeOn === 0 && out.point.purgeOn && emitted - purgeOff >= palettePingSpace) {
- retract();
- pushPos(out.point.purgeOn);
- // shorter pause accounts for retract/move
- dwell(12750);
- popPos();
- unretract();
- purgeOn = emitted;
- purgePos = out.point.purgeOn;
- pingRemain = 20;
- }
- if (purgeOn && pingRemain <= 0) {
- retract();
- pushPos(purgePos);
- // shorter pause accounts for retract/move
- dwell(6750);
- popPos();
- unretract();
- if (!print.purges) {
- print.purges = [];
- }
- print.purges.push({
- length: purgeOn,
- extrusion: emitted - purgeOn
- });
- purgeOff = emitted;
- purgeOn = 0;
- pingRemain = 0;
- }
- }
-
- // emit comment on output type chage
- if (last && out.type !== last.type) {
- append(`; feature ${out.type}`);
- lastType = out.type;
- }
-
- // look for extruder change, run scripts, recalc emit factor
- if (out.tool !== undefined && out.tool != tool) {
- segments.push({emitted, tool});
- appendAllSub(extruder.extDeselect);
- tool = out.tool;
- subst.nozzle = subst.tool = tool;
- extruder = extruders[tool];
- offset_x = extruder.extOffsetX;
- offset_y = extruder.extOffsetY;
- emitPerMM = print.extrudePerMM(
- lineWidth || extruder.extNozzle,
- extruder.extFilament,
- path.layer === 0 ?
- (process.firstSliceHeight || process.sliceHeight) : path.height);
- // do not run extruder swapping code when source is Palette3
- if (!isPalette) {
- appendAllSub(extruder.extSelect);
- }
- }
-
- // if no point in output, it's a dwell command
- if (!out.point) {
- dwell(out.speed);
- continue;
- }
-
- let x = out.point.x + offset_x,
- y = out.point.y + offset_y,
- z = out.point.z;
-
- // adjust for inversions and origin offsets
- if (process.outputInvertX) x = -x;
- if (process.outputInvertY) y = -y;
- if (offset) {
- x += offset.x;
- y += offset.y;
- }
-
- dist = lastp ? lastp.distTo2D(out.point) : 0;
-
- // re-engage post-retraction before new extrusion
- if (out.emit && retracted) {
- unretract();
- }
-
- if (lastp && out.emit) {
- if (arcDist) {
- let rec = {e:out.emit, x, y, z, dist, emitPerMM, speedMMM};
- arcQ.push(rec);
- let deem = false; // do arcQ[0] and rec have differing emit values?
- let depm = false; // do arcQ[0] and rec have differing emit speeds?
- let desp = false; // do arcQ[0] and rec have differing move speeds?
- if (arcQ.length > 1) {
- let el = arcQ.length;
- deem = arcQ[0].e !== rec.e;
- depm = arcQ[0].emitPerMM !== rec.emitPerMM;
- desp = arcQ[0].speedMMM !== rec.speedMMM;
- }
- // ondebug({arcQ});
- if (arcQ.length > 2) {
- let el = arcQ.length;
- let e1 = arcQ[0]; // first in arcQ
- let e2 = arcQ[Math.floor(el/2)]; // mid in arcQ
- let e3 = arcQ[el-1]; // last in arcQ
- let e4 = arcQ[el-2]; // second last in arcQ
- let e5 = arcQ[el-3]; // third last in arcQ
- let cc = BASE.util.center2d(e1, e2, e3, 1); // find center
- let lr = BASE.util.center2d(e3, e4, e5, 1); // find local radius
- let dc = 0;
-
- let radFault = false;
- if (lr) {
- let angle = 2 * Math.asin(dist/(2*lr.r));
- radFault = Math.abs(angle) > Math.PI * 2 / arcRes; // enforce arcRes(olution)
- // if (arcQ.center) {
- // arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 );
- // let avg = arcQ.rSum / arcQ.center.length;
- // radFault = radFault || Math.abs(avg - lr.r) / avg > arcDev; // eliminate sharps and flats when local rad is out of arcDev(iation)
- // }
- } else {
- radFault = true;
- }
-
- if (cc) {
- if ([cc.x,cc.y,cc.z,cc.r].hasNaN()) {
- console.log({cc, e1, e2, e3});
- }
- if (arcQ.length === 3) {
- arcQ.center = [ cc ];
- arcQ.xSum = cc.x;
- arcQ.ySum = cc.y;
- arcQ.rSum = cc.r;
- } else {
- // check center point delta
- arcQ.xSum = arcQ.center.reduce( function (t, v) { return t + v.x }, 0 );
- arcQ.ySum = arcQ.center.reduce( function (t, v) { return t + v.y }, 0 );
- arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 );
- let dx = cc.x - arcQ.xSum / arcQ.center.length;
- let dy = cc.y - arcQ.ySum / arcQ.center.length;
- dc = Math.sqrt(dx * dx + dy * dy);
- }
-
- // if new point is off the arc
- // if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) {
- if (deem || depm || desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault || !arcValid()) {
- // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault];
- if (arcQ.length === 4) {
- // not enough points for an arc, drop first point and recalc center
- emitQrec(arcQ.shift());
- let tc = BASE.util.center2d(arcQ[0], arcQ[1], arcQ[2], 1);
- // the new center is invalid as well. drop the first point
- if (!tc) {
- emitQrec(arcQ.shift());
- } else {
- arcQ.center = [ tc ];
- let angle = 2 * Math.asin(arcQ[1].dist/(2*tc.r));
- if (Math.abs(angle) > Math.PI * 2 / arcRes) { // enforce arcRes on initial angle
- emitQrec(arcQ.shift());
- }
- }
- } else {
- // enough to consider an arc, emit and start new arc
- let defer = arcQ.pop();
- drainQ();
- // re-add point that was off the last arc
- arcQ.push(defer);
- }
- } else {
- // new point is on the arc
- arcQ.center.push(cc);
- }
- } else {
- // drainQ on invalid center
- drainQ();
- }
- }
- } else {
- emitMM = emitPerMM * out.emit * dist;
- moveTo({x:x, y:y, e:emitMM}, speedMMM);
- emitted += emitMM;
- }
- } else {
- drainQ();
- moveTo({x:x, y:y}, seekMMM);
- // TODO disabling out of plane z moves until a better mechanism
- // can be built that doesn't rely on computed zpos from layer heights...
- // when making z moves (like polishing) allow slowdown vs fast seek
- // let moveSpeed = (lastp && lastp.z !== z) ? speedMMM : seekMMM;
- // moveTo({x:x, y:y, z:z}, moveSpeed);
- }
-
- // retract filament if point retract flag set
- if (out.retract) {
- drainQ();
- retract(zhop);
- }
-
- // update time and distance (should calc in moveTo() instead)
- time += (dist / speedMMM) * 60 * 1.5;
- distance += dist;
- subst.progress = progress = Math.round((distance / totaldistance) * 100);
-
- // emit tracked progress
- if (gcodeTrack && progress != lastProgress) {
- appendAllSub(gcodeTrack);
- lastProgress = progress;
- }
-
- lastp = out.point;
- laste = out.emit;
- }
- layer++;
-
- // end open loop when detected
- while (endloop-- > 0) {
- append(`M808`);
- }
- drainQ();
}
+ }
+ let loops = isBelt && rloops.length ? rloops : undefined;
- function emitQrec(rec) {
- let {e, x, y, dist, emitPerMM, speedMMM} = rec;
- emitMM = emitPerMM * e * dist;
- moveTo({x:x, y:y, e:emitMM}, speedMMM);
- emitted += emitMM;
+ function append(line) {
+ if (line) {
+ lines++;
+ bytes += line.length;
+ output.append(line);
}
-
- function drainQ() {
- if (!arcDist) {
- return;
- }
- if (arcQ.length > 4) {
- // ondebug({arcQ});
- let vec1 = new THREE.Vector2(arcQ[1].x - arcQ[0].x, arcQ[1].y - arcQ[0].y);
- let vec2 = new THREE.Vector2(arcQ.center[0].x - arcQ[0].x, arcQ.center[0].y - arcQ[0].y);
- let gc = vec1.cross(vec2) < 0 ? 'G2' : 'G3';
- let from = arcQ[0];
- let to = arcQ.peek();
- arcQ.xSum = arcQ.center.reduce( function (t, v) { return t + v.x }, 0 );
- arcQ.ySum = arcQ.center.reduce( function (t, v) { return t + v.y }, 0 );
- arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 );
- let cl = arcQ.center.length;
- let cc;
-
- let angle = BASE.util.thetaDiff(
- Math.atan2((from.y - arcQ.ySum / cl), (from.x - arcQ.xSum / cl)),
- Math.atan2((to.y - arcQ.ySum / cl), (to.x - arcQ.xSum / cl)),
- gc === "G2"
- );
-
- if (Math.abs(angle) <= 3 * Math.PI / 4) {
- cc = BASE.util.center2pr(from, to, arcQ.rSum / cl, gc === "G3");
- }
-
- if (!cc) {
- cc = {x:arcQ.xSum/cl, y:arcQ.ySum/cl, z:arcQ[0].z, r:arcQ.rSum/cl};
- }
-
- // first arc point
- emitQrec(from);
- // console.log(arcQ.slice(), arcQ.center);
- // console.log({first: from, last: arcQ.peek(), center: cc});
- // rest of arc to final point
- let dist = arcQ.slice(1).map(v => v.dist).reduce((a,v) => a+v);
- let emit = from.e;//arcQ.slice(1).map(v => v.e).reduce((a,v) => a+v);
- emit = (from.emitPerMM * emit * dist);
- outputLength += emit;
- emitted += emit;
- if (extrudeAbs) {
- emit = outputLength;
- }
- // XYR form
- // let pre = `${gc} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} R${cc.r.toFixed(decimals)} E${emit.toFixed(decimals)}`;
- // XYIJ form
- let pre = `${gc} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} I${(cc.x - pos.x).toFixed(decimals)} J${(cc.y - pos.y).toFixed(decimals)} E${emit.toFixed(decimals)}`;
- let add = pos.f !== from.speedMMM ? ` E${from.speedMMM}` : '';
- append(`${pre}${add} ; merged=${cl-1} len=${dist.toFixed(decimals)} cp=${cc.x.round(2)},${cc.y.round(2)}`);
- pos.x = to.x;
- pos.y = to.y;
- pos.z = to.z;
- } else {
- for (let rec of arcQ) {
- emitQrec(rec);
- }
- }
- arcQ.length = 0;
- arcQ.center = undefined;
- }
-
- // comprehensive arc validator
- function arcValid() {
- if (arcQ.length < 3) {
- return false;
- }
-
- let globalCenters = []; // see how a point first the curve within the context of the arc's end points.
-
- for (let i = 0; i < arcQ.length - 2; i++) {
- let cc = UTIL.center2d(arcQ[0], arcQ[i+1], arcQ[arcQ.length - 1], 1);
- if (!cc) {
- return false;
- }
- globalCenters.push(cc);
- }
-
- let ac = { // average center
- x:globalCenters.reduce( (t,v) => t + v.x , 0) / (globalCenters.length),
- y:globalCenters.reduce( (t,v) => t + v.y , 0) / (globalCenters.length),
- z:globalCenters.reduce( (t,v) => t + v.z , 0) / (globalCenters.length),
- r:globalCenters.reduce( (t,v) => t + v.r , 0) / (globalCenters.length)
- };
-
- // make sure centers are within specified tolerance
- for (let cc of globalCenters) {
- let dc = Math.sqrt(Math.pow(cc.x - ac.x, 2) + Math.pow(cc.y - ac.y, 2));
- if (dc / ac.r > arcDist) {
- return false;
- }
- }
-
- // make sure radii are within specified tolerance
- for (let point of arcQ) {
- let rad = Math.sqrt(Math.pow(point.x - ac.x, 2) + Math.pow(point.y - ac.y, 2));
- if (Math.abs(rad - ac.r) > ac.r * arcDist) {
- return false;
- }
- }
-
- // enforce arcRes(olution)
- for (let i = 1; i < arcQ.length; i++) {
- let angle = 2 * Math.asin(arcQ[i].dist/(2*ac.r));
- if (Math.abs(angle) > Math.PI * 2 / arcRes) {
- return false;
- }
- }
-
- // check points in the context of neighbors
- for (let i = 0; i < arcQ.length - 2; i++) {
- let cc = UTIL.center2d(arcQ[i], arcQ[i+1], arcQ[i+2], 1);
- if (!cc || Math.abs((cc.r - ac.r) / cc.r) > arcDev) {
- return false;
- }
- }
-
- return true;
-
- }
-
- if (inloops) {
- append(`M808`);
- }
-
- segments.push({emitted, tool});
- subst.material = UTIL.round(emitted,2);
- subst.time = UTIL.round(time,2);
- subst['print_time'] = subst.time;
-
- append("; --- shutdown ---");
- appendAllSub(device.gcodePost);
- append(`; --- filament used: ${subst.material} mm ---`);
- append(`; --- print time: ${time.toFixed(0)}s ---`);
-
- // force emit of buffer
- append();
- // console.log({segments, emitted, outputLength});
- print.segments = isPalette ? segments : undefined;
- print.distance = emitted;
- print.lines = lines;
- print.bytes = bytes + lines - 1;
- print.time = time;
-
- if (debug) {
- console.log('segments', segments);
- console.log('minz', minz);
+ // batch gcode output to free memory in worker
+ // consider alternate transfer schemes like indexeddb
+ // since this transfers the burden to the main thread
+ if (!line || output.length > 1000) {
+ online(output.join("\n"));
+ output = [];
}
};
+ function appendSubPad(line, pad) {
+ appendSub(line, true);
+ }
+
+ let subon = true;
+ let ifhit = false;
+
+ // eval() based var substitution (from print)
+ // with logic flow IF / ELIF / ELSE / END
+ // IF / IF is valid but will not nest
+ function appendSub(line, pad) {
+ if (line.indexOf(';; IF ') === 0) {
+ line = line.substring(6).trim();
+ let evil = print.constReplace(line, subst, 0, 666);
+ subon = evil;
+ ifhit = subon;
+ } else if (line.indexOf(';; ELIF ') === 0) {
+ line = line.substring(6).trim();
+ let evil = print.constReplace(line, subst, 0, 666);
+ subon = !subon && evil;
+ ifhit = ifhit || subon;
+ } else if (line.indexOf(';; ELSE') === 0) {
+ subon = !ifhit && !subon;
+ } else if (line.indexOf(';; END') === 0) {
+ subon = true;
+ ifhit = false;
+ } else if (subon) {
+ append(print.constReplace(line, subst, 0, pad));
+ }
+ }
+
+ function appendAll(arr) {
+ if (!arr) return;
+ if (!Array.isArray(arr)) arr = [ arr ];
+ arr.forEach(function(line) { append(line) });
+ }
+
+ function appendAllSub(arr, pad) {
+ if (!arr || arr.length === 0) return;
+ if (!Array.isArray(arr)) arr = [ arr ];
+ arr.forEach(function(line) { appendSub(line, pad) });
+ }
+
+ function appendTok() {
+ let arg = [...arguments].map(v => {
+ return typeof(v) === 'object' ? JSON.stringify(v) : v;
+ });
+ append(arg.join(''));
+ }
+
+ append(`; Generated by Kiri:Moto ${kiri.version}`);
+ append(`; ${new Date().toString()}`);
+ appendSub("; Bed left:{left} right:{right} top:{top} bottom:{bottom}");
+ append(`; Bed type: ${bedType}`);
+ append(`; Target: ${settings.filter[settings.mode]}`);
+ // inject thumbnail preview
+ if (thumbnails && worker.snap) {
+ let { width, height, url } = worker.snap;
+ let data = url.substring(url.indexOf(',') + 1);
+ append(`; thumbnail begin ${width} ${height} ${data.length}`);
+ for (let i=0; i 0) {
+ line.split(";")[0].split(' ').forEach(function (tok) {
+ // use max E position from gcode-preamble
+ if (tok[0] == 'E') {
+ if (extrudeAbs) {
+ outputLength = Math.max(outputLength, parseFloat(tok.substring(1)) || 0);
+ emitted = outputLength;
+ } else {
+ emitted += parseFloat(tok.substring(1) || 0);
+ }
+ }
+ });
+ }
+ if (line.indexOf("{tool}") > 0 && extused.length > 0) {
+ for (let i of extused) {
+ subst.tool = i;
+ appendSubPad(line);
+ }
+ subst.tool = 0;
+ } else {
+ appendSubPad(line);
+ }
+ }
+
+ function dwell(ms) {
+ time += ms/1000;
+ // break up dwell times over 4s because of
+ // limitations in some firmwares
+ while (ms > 0) {
+ let wait = Math.min(4000, ms);
+ append(`G4 P${wait}`);
+ ms -= wait;
+ if (ms) {
+ append('G1');
+ }
+ }
+ }
+
+ function retract(zhop) {
+ if (retracted) {
+ // console.log({double_retract: zhop});
+ return;
+ }
+ retracted = retDist;
+ if (fwRetract) {
+ append('G10');
+ } else {
+ moveTo({e:-retracted}, retSpeed, `e-retract ${retDist}`);
+ }
+ if (zhop) moveTo({z:zpos + zhop}, seekMMM, "z-hop start");
+ time += (retDist / retSpeed) * 60 * 2; // retraction time
+ }
+
+ function unretract() {
+ if (!retracted) {
+ return;
+ }
+ drainQ();
+ // console.log({engage:zhop});
+ // when enabled, resume previous Z
+ if (zhop && pos.z != zpos) moveTo({z:zpos}, seekMMM, "z-hop end");
+ // re-engage retracted filament
+ if (fwRetract) {
+ append('G11');
+ } else {
+ moveTo({e:retracted}, retSpeed, `e-engage ${retracted}`);
+ }
+ retracted = 0;
+ // optional dwell after re-engaging filament to allow pressure to build
+ if (retDwell) dwell(retDwell);
+ time += (retDist / retSpeed) * 60 * 2; // retraction time
+ }
+
+ let taxis = new THREE.Vector3( 1, 0, 0 );
+ let tcent = new THREE.Vector2( 0, 0 );
+ let angle = -Math.PI / 4;
+ let savePos = pos;
+
+ function pushPos(newpos, rate, comment) {
+ savePos = Object.clone(pos);
+ moveTo({
+ x: newpos.x + offset.x,
+ y: newpos.y + offset.y,
+ z: newpos.z + offset.z
+ }, rate, comment);
+ }
+
+ function popPos(rate, comment) {
+ moveTo(savePos, rate, comment);
+ }
+
+ function moveTo(newpos, rate, comment) {
+ if (pingRemain) {
+ if (newpos.e) {
+ if (pingRemain - newpos.e < -0.4) {
+ // split move if ping over-extrudes? complicates emitted calc.
+ // console.log({over_ping: pingRemain - newpos.e});
+ }
+ pingRemain -= newpos.e;
+ }
+ }
+ let o = [!rate && !newpos.e ? 'G0' : 'G1'];
+ let emit = { x: false, y: false, z: false };
+ if (typeof newpos.x === 'number' && newpos.x !== pos.x) {
+ pos.x = newpos.x;
+ emit.x = true;
+ }
+ if (typeof newpos.y === 'number' && newpos.y !== pos.y) {
+ pos.y = newpos.y;
+ emit.y = true;
+ if (isBelt) emit.z = true;
+ }
+ if (typeof newpos.z === 'number' && newpos.z !== pos.z) {
+ pos.z = newpos.z;
+ emit.z = true;
+ if (isBelt) emit.y = true;
+ }
+ let epos = isBelt ? { x: pos.x, y: pos.y, z: pos.z } : pos;
+ if (isBelt) {
+ let zheight = path ? path.height || 0 : 0;
+ epos.x = originCenter ? -pos.x : device.bedWidth - pos.x;
+ epos.z = blastz = pos.z * icos;
+ epos.y = -pos.y + epos.z * bcos + belt_add_y;
+ lout = epos;
+ }
+ if (emit.x) o.append(" X").append(epos.x.toFixed(decimals));
+ if (emit.y) o.append(" Y").append(epos.y.toFixed(decimals));
+ if (emit.z) o.append(" Z").append(epos.z.toFixed(decimals));
+ if (debug) {
+ if (emit.x) minz.x = Math.min(minz.x, epos.x);
+ if (emit.y) minz.y = Math.min(minz.y, epos.y);
+ if (emit.z) minz.z = Math.min(minz.z, epos.z);
+ }
+ if (typeof newpos.e === 'number') {
+ outputLength += newpos.e;
+ if (extrudeAbs) {
+ // for cumulative (absolute) extruder positions
+ o.append(" E").append(outputLength.toFixed(decimals));
+ } else {
+ o.append(" E").append(newpos.e.toFixed(decimals));
+ }
+ }
+ if (zMoveMax && emit.z) {
+ rate = Math.min(zMoveMax, rate) * 60;
+ }
+ if (rate && rate != pos.f) {
+ o.append(" F").append(Math.round(rate));
+ pos.f = rate
+ }
+ if (comment) {
+ o.append(` ; ${comment}`);
+ }
+ if (o.length === 1) {
+ // console.trace({no_move: o, out, newpos, pos, lastp, emit});
+ return;
+ }
+ let line = o.join('');
+ if (last == line) {
+ // console.log({dup:line});
+ return;
+ }
+ last = line;
+ append(line);
+ }
+
+ // calc total distance traveled by head as proxy for progress
+ let allout = [];
+ let totaldistance = 0;
+ layers.forEach(function(outs) {
+ allout.appendAll(outs);
+ });
+ allout.forEachPair(function (o1, o2) {
+ totaldistance += o1.point.distTo2D(o2.point);
+ }, 1);
+
+ // for palette pings, amount of extrusion left
+ let pingRemain = 0;
+
+ // retract before first move
+ retract();
+
+ while (layer < layers.length) {
+ path = layers[layer];
+ layerno = path.slice.index;
+
+ // range overrides
+ if (path.layer >= 0) {
+ updateParams(path.layer);
+ }
+
+ emitPerMM = print.extrudePerMM(
+ lineWidth || extruder.extNozzle,
+ extruder.extFilament,
+ path.layer === 0 ?
+ (process.firstSliceHeight || process.sliceHeight) : path.height);
+
+ zpos = path.z || zpos;
+ bmax = Math.max(bmax, pos.z * icos);
+ subst.z = subst.Z = zpos.round(3);
+ subst.e = subst.E = outputLength;
+ subst.layer = layer;
+ subst.height = path.height;
+
+ if (isBelt) {
+ pos.z = zpos;
+ }
+
+ if (pauseCmd && pause.indexOf(layer) >= 0) {
+ appendAllSub(pauseCmd)
+ }
+
+ let endloop = 0;
+ if (loops) {
+ for (let loop of loops) {
+ if (layerno === loop.start && !loop.started) {
+ loop.started = true;
+ append(`M808 L${loop.iter}`);
+ if (extrudeAbs) {
+ append(`G92 Z${lout.z.round(decimals)} E${outputLength.round(decimals)}`);
+ } else {
+ append(`G92 Z${lout.z.round(decimals)}`);
+ }
+ inloops++;
+ }
+ if (layerno === loop.end && !loop.ended) {
+ loop.ended = true;
+ endloop++;
+ inloops--;
+ }
+ }
+ }
+
+ if (gcodeLayer && gcodeLayer.length) {
+ appendAllSub(gcodeLayer);
+ } else {
+ append(`;; --- layer ${layer} (${subst.height.toFixed(3)} @ ${subst.z.round(3)}) ---`);
+ }
+
+ // layer temp and fan overrides at layer changes
+ if (fanSpeed !== lastFanSpeed) {
+ appendAllSub(gcodeFan);
+ lastFanSpeed = fanSpeed;
+ }
+ if (bedTemp !== lastBedTemp) {
+ append(`M140 S${bedTemp} T0`);
+ lastBedTemp = bedTemp;
+ }
+ if (nozzleTemp !== lastNozzleTemp) {
+ if (t0) append(`M104 S${nozzleTemp} T0`);
+ if (t1) append(`M104 S${nozzleTemp} T1`);
+ if (!(t0 || t1)) append(`M104 S${nozzleTemp} T${tool}`);
+ lastNozzleTemp = nozzleTemp;
+ }
+
+ // move Z to layer height
+ if (layer > 0 || !isBelt) {
+ moveTo({z:zpos}, seekMMM);
+ }
+
+ // iterate through layer outputs
+ for (pidx=0; pidx= palettePingStart) {
+ if (purgeOn === 0 && out.point.purgeOn && emitted - purgeOff >= palettePingSpace) {
+ retract();
+ pushPos(out.point.purgeOn);
+ // shorter pause accounts for retract/move
+ dwell(12750);
+ popPos();
+ unretract();
+ purgeOn = emitted;
+ purgePos = out.point.purgeOn;
+ pingRemain = 20;
+ }
+ if (purgeOn && pingRemain <= 0) {
+ retract();
+ pushPos(purgePos);
+ // shorter pause accounts for retract/move
+ dwell(6750);
+ popPos();
+ unretract();
+ if (!print.purges) {
+ print.purges = [];
+ }
+ print.purges.push({
+ length: purgeOn,
+ extrusion: emitted - purgeOn
+ });
+ purgeOff = emitted;
+ purgeOn = 0;
+ pingRemain = 0;
+ }
+ }
+
+ // emit comment on output type chage
+ if (last && out.type !== last.type) {
+ append(`; feature ${out.type}`);
+ lastType = out.type;
+ }
+
+ // look for extruder change, run scripts, recalc emit factor
+ if (out.tool !== undefined && out.tool != tool) {
+ segments.push({emitted, tool});
+ appendAllSub(extruder.extDeselect);
+ tool = out.tool;
+ subst.nozzle = subst.tool = tool;
+ extruder = extruders[tool];
+ offset_x = extruder.extOffsetX;
+ offset_y = extruder.extOffsetY;
+ emitPerMM = print.extrudePerMM(
+ lineWidth || extruder.extNozzle,
+ extruder.extFilament,
+ path.layer === 0 ?
+ (process.firstSliceHeight || process.sliceHeight) : path.height);
+ // do not run extruder swapping code when source is Palette3
+ if (!isPalette) {
+ appendAllSub(extruder.extSelect);
+ }
+ }
+
+ // if no point in output, it's a dwell command
+ if (!out.point) {
+ dwell(out.speed);
+ continue;
+ }
+
+ let x = out.point.x + offset_x,
+ y = out.point.y + offset_y,
+ z = out.point.z;
+
+ // adjust for inversions and origin offsets
+ if (process.outputInvertX) x = -x;
+ if (process.outputInvertY) y = -y;
+ if (offset) {
+ x += offset.x;
+ y += offset.y;
+ }
+
+ dist = lastp ? lastp.distTo2D(out.point) : 0;
+
+ // re-engage post-retraction before new extrusion
+ if (out.emit && retracted) {
+ unretract();
+ }
+
+ if (lastp && out.emit) {
+ if (arcDist) {
+ let rec = {e:out.emit, x, y, z, dist, emitPerMM, speedMMM};
+ arcQ.push(rec);
+ let deem = false; // do arcQ[0] and rec have differing emit values?
+ let depm = false; // do arcQ[0] and rec have differing emit speeds?
+ let desp = false; // do arcQ[0] and rec have differing move speeds?
+ if (arcQ.length > 1) {
+ let el = arcQ.length;
+ deem = arcQ[0].e !== rec.e;
+ depm = arcQ[0].emitPerMM !== rec.emitPerMM;
+ desp = arcQ[0].speedMMM !== rec.speedMMM;
+ }
+ // ondebug({arcQ});
+ if (arcQ.length > 2) {
+ let el = arcQ.length;
+ let e1 = arcQ[0]; // first in arcQ
+ let e2 = arcQ[Math.floor(el/2)]; // mid in arcQ
+ let e3 = arcQ[el-1]; // last in arcQ
+ let e4 = arcQ[el-2]; // second last in arcQ
+ let e5 = arcQ[el-3]; // third last in arcQ
+ let cc = util.center2d(e1, e2, e3, 1); // find center
+ let lr = util.center2d(e3, e4, e5, 1); // find local radius
+ let dc = 0;
+
+ let radFault = false;
+ if (lr) {
+ let angle = 2 * Math.asin(dist/(2*lr.r));
+ radFault = Math.abs(angle) > Math.PI * 2 / arcRes; // enforce arcRes(olution)
+ // if (arcQ.center) {
+ // arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 );
+ // let avg = arcQ.rSum / arcQ.center.length;
+ // radFault = radFault || Math.abs(avg - lr.r) / avg > arcDev; // eliminate sharps and flats when local rad is out of arcDev(iation)
+ // }
+ } else {
+ radFault = true;
+ }
+
+ if (cc) {
+ if ([cc.x,cc.y,cc.z,cc.r].hasNaN()) {
+ console.log({cc, e1, e2, e3});
+ }
+ if (arcQ.length === 3) {
+ arcQ.center = [ cc ];
+ arcQ.xSum = cc.x;
+ arcQ.ySum = cc.y;
+ arcQ.rSum = cc.r;
+ } else {
+ // check center point delta
+ arcQ.xSum = arcQ.center.reduce( function (t, v) { return t + v.x }, 0 );
+ arcQ.ySum = arcQ.center.reduce( function (t, v) { return t + v.y }, 0 );
+ arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 );
+ let dx = cc.x - arcQ.xSum / arcQ.center.length;
+ let dy = cc.y - arcQ.ySum / arcQ.center.length;
+ dc = Math.sqrt(dx * dx + dy * dy);
+ }
+
+ // if new point is off the arc
+ // if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) {
+ if (deem || depm || desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault || !arcValid()) {
+ // let debug = [deem, depm, desp, dc * arcQ.center.length / arcQ.rSum > arcDist, dist > cc.r, cc.r > arcMax, radFault];
+ if (arcQ.length === 4) {
+ // not enough points for an arc, drop first point and recalc center
+ emitQrec(arcQ.shift());
+ let tc = util.center2d(arcQ[0], arcQ[1], arcQ[2], 1);
+ // the new center is invalid as well. drop the first point
+ if (!tc) {
+ emitQrec(arcQ.shift());
+ } else {
+ arcQ.center = [ tc ];
+ let angle = 2 * Math.asin(arcQ[1].dist/(2*tc.r));
+ if (Math.abs(angle) > Math.PI * 2 / arcRes) { // enforce arcRes on initial angle
+ emitQrec(arcQ.shift());
+ }
+ }
+ } else {
+ // enough to consider an arc, emit and start new arc
+ let defer = arcQ.pop();
+ drainQ();
+ // re-add point that was off the last arc
+ arcQ.push(defer);
+ }
+ } else {
+ // new point is on the arc
+ arcQ.center.push(cc);
+ }
+ } else {
+ // drainQ on invalid center
+ drainQ();
+ }
+ }
+ } else {
+ emitMM = emitPerMM * out.emit * dist;
+ moveTo({x:x, y:y, e:emitMM}, speedMMM);
+ emitted += emitMM;
+ }
+ } else {
+ drainQ();
+ moveTo({x:x, y:y}, seekMMM);
+ // TODO disabling out of plane z moves until a better mechanism
+ // can be built that doesn't rely on computed zpos from layer heights...
+ // when making z moves (like polishing) allow slowdown vs fast seek
+ // let moveSpeed = (lastp && lastp.z !== z) ? speedMMM : seekMMM;
+ // moveTo({x:x, y:y, z:z}, moveSpeed);
+ }
+
+ // retract filament if point retract flag set
+ if (out.retract) {
+ drainQ();
+ retract(zhop);
+ }
+
+ // update time and distance (should calc in moveTo() instead)
+ time += (dist / speedMMM) * 60 * 1.5;
+ distance += dist;
+ subst.progress = progress = Math.round((distance / totaldistance) * 100);
+
+ // emit tracked progress
+ if (gcodeTrack && progress != lastProgress) {
+ appendAllSub(gcodeTrack);
+ lastProgress = progress;
+ }
+
+ lastp = out.point;
+ laste = out.emit;
+ }
+ layer++;
+
+ // end open loop when detected
+ while (endloop-- > 0) {
+ append(`M808`);
+ }
+ drainQ();
+ }
+
+ function emitQrec(rec) {
+ let {e, x, y, dist, emitPerMM, speedMMM} = rec;
+ emitMM = emitPerMM * e * dist;
+ moveTo({x:x, y:y, e:emitMM}, speedMMM);
+ emitted += emitMM;
+ }
+
+ function drainQ() {
+ if (!arcDist) {
+ return;
+ }
+ if (arcQ.length > 4) {
+ // ondebug({arcQ});
+ let vec1 = new THREE.Vector2(arcQ[1].x - arcQ[0].x, arcQ[1].y - arcQ[0].y);
+ let vec2 = new THREE.Vector2(arcQ.center[0].x - arcQ[0].x, arcQ.center[0].y - arcQ[0].y);
+ let gc = vec1.cross(vec2) < 0 ? 'G2' : 'G3';
+ let from = arcQ[0];
+ let to = arcQ.peek();
+ arcQ.xSum = arcQ.center.reduce( function (t, v) { return t + v.x }, 0 );
+ arcQ.ySum = arcQ.center.reduce( function (t, v) { return t + v.y }, 0 );
+ arcQ.rSum = arcQ.center.reduce( function (t, v) { return t + v.r }, 0 );
+ let cl = arcQ.center.length;
+ let cc;
+
+ let angle = util.thetaDiff(
+ Math.atan2((from.y - arcQ.ySum / cl), (from.x - arcQ.xSum / cl)),
+ Math.atan2((to.y - arcQ.ySum / cl), (to.x - arcQ.xSum / cl)),
+ gc === "G2"
+ );
+
+ if (Math.abs(angle) <= 3 * Math.PI / 4) {
+ cc = util.center2pr(from, to, arcQ.rSum / cl, gc === "G3");
+ }
+
+ if (!cc) {
+ cc = {x:arcQ.xSum/cl, y:arcQ.ySum/cl, z:arcQ[0].z, r:arcQ.rSum/cl};
+ }
+
+ // first arc point
+ emitQrec(from);
+ // console.log(arcQ.slice(), arcQ.center);
+ // console.log({first: from, last: arcQ.peek(), center: cc});
+ // rest of arc to final point
+ let dist = arcQ.slice(1).map(v => v.dist).reduce((a,v) => a+v);
+ let emit = from.e;//arcQ.slice(1).map(v => v.e).reduce((a,v) => a+v);
+ emit = (from.emitPerMM * emit * dist);
+ outputLength += emit;
+ emitted += emit;
+ if (extrudeAbs) {
+ emit = outputLength;
+ }
+ // XYR form
+ // let pre = `${gc} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} R${cc.r.toFixed(decimals)} E${emit.toFixed(decimals)}`;
+ // XYIJ form
+ let pre = `${gc} X${to.x.toFixed(decimals)} Y${to.y.toFixed(decimals)} I${(cc.x - pos.x).toFixed(decimals)} J${(cc.y - pos.y).toFixed(decimals)} E${emit.toFixed(decimals)}`;
+ let add = pos.f !== from.speedMMM ? ` E${from.speedMMM}` : '';
+ append(`${pre}${add} ; merged=${cl-1} len=${dist.toFixed(decimals)} cp=${cc.x.round(2)},${cc.y.round(2)}`);
+ pos.x = to.x;
+ pos.y = to.y;
+ pos.z = to.z;
+ } else {
+ for (let rec of arcQ) {
+ emitQrec(rec);
+ }
+ }
+ arcQ.length = 0;
+ arcQ.center = undefined;
+ }
+
+ // comprehensive arc validator
+ function arcValid() {
+ if (arcQ.length < 3) {
+ return false;
+ }
+
+ let globalCenters = []; // see how a point first the curve within the context of the arc's end points.
+
+ for (let i = 0; i < arcQ.length - 2; i++) {
+ let cc = util.center2d(arcQ[0], arcQ[i+1], arcQ[arcQ.length - 1], 1);
+ if (!cc) {
+ return false;
+ }
+ globalCenters.push(cc);
+ }
+
+ let ac = { // average center
+ x:globalCenters.reduce( (t,v) => t + v.x , 0) / (globalCenters.length),
+ y:globalCenters.reduce( (t,v) => t + v.y , 0) / (globalCenters.length),
+ z:globalCenters.reduce( (t,v) => t + v.z , 0) / (globalCenters.length),
+ r:globalCenters.reduce( (t,v) => t + v.r , 0) / (globalCenters.length)
+ };
+
+ // make sure centers are within specified tolerance
+ for (let cc of globalCenters) {
+ let dc = Math.sqrt(Math.pow(cc.x - ac.x, 2) + Math.pow(cc.y - ac.y, 2));
+ if (dc / ac.r > arcDist) {
+ return false;
+ }
+ }
+
+ // make sure radii are within specified tolerance
+ for (let point of arcQ) {
+ let rad = Math.sqrt(Math.pow(point.x - ac.x, 2) + Math.pow(point.y - ac.y, 2));
+ if (Math.abs(rad - ac.r) > ac.r * arcDist) {
+ return false;
+ }
+ }
+
+ // enforce arcRes(olution)
+ for (let i = 1; i < arcQ.length; i++) {
+ let angle = 2 * Math.asin(arcQ[i].dist/(2*ac.r));
+ if (Math.abs(angle) > Math.PI * 2 / arcRes) {
+ return false;
+ }
+ }
+
+ // check points in the context of neighbors
+ for (let i = 0; i < arcQ.length - 2; i++) {
+ let cc = util.center2d(arcQ[i], arcQ[i+1], arcQ[i+2], 1);
+ if (!cc || Math.abs((cc.r - ac.r) / cc.r) > arcDev) {
+ return false;
+ }
+ }
+
+ return true;
+
+ }
+
+ if (inloops) {
+ append(`M808`);
+ }
+
+ segments.push({emitted, tool});
+ subst.material = util.round(emitted,2);
+ subst.time = util.round(time,2);
+ subst['print_time'] = subst.time;
+
+ append("; --- shutdown ---");
+ appendAllSub(device.gcodePost);
+ append(`; --- filament used: ${subst.material} mm ---`);
+ append(`; --- print time: ${time.toFixed(0)}s ---`);
+
+ // force emit of buffer
+ append();
+ // console.log({segments, emitted, outputLength});
+ print.segments = isPalette ? segments : undefined;
+ print.distance = emitted;
+ print.lines = lines;
+ print.bytes = bytes + lines - 1;
+ print.time = time;
+
+ if (debug) {
+ console.log('segments', segments);
+ console.log('minz', minz);
+ }
+};
+
})();
diff --git a/src/kiri-mode/fdm/fill.js b/src/kiri-mode/fdm/fill.js
index 5c6bec19..5614f053 100644
--- a/src/kiri-mode/fdm/fill.js
+++ b/src/kiri-mode/fdm/fill.js
@@ -4,317 +4,317 @@
(function() {
- if (self.kiri.fill) return;
+if (self.kiri.fill) return;
- const KIRI = self.kiri,
- BASE = self.base,
- UTIL = BASE.util,
- ROUND = UTIL.round,
- DEG2RAD = Math.PI / 180,
- FILL = self.kiri.fill = {
- hex: fillHexFull,
- grid: fillGrid,
- gyroid: fillGyroid,
- triangle: fillTriangle,
- linear: fillLinear,
- cubic: fillCubic
- },
- CACHE = self.kiri.fill_fixed = {
- hex: fillHexFull,
- grid: fillGrid,
- triangle: fillTriangle
- };
+const KIRI = self.kiri,
+ BASE = self.base,
+ UTIL = BASE.util,
+ ROUND = UTIL.round,
+ DEG2RAD = Math.PI / 180,
+ FILL = self.kiri.fill = {
+ hex: fillHexFull,
+ grid: fillGrid,
+ gyroid: fillGyroid,
+ triangle: fillTriangle,
+ linear: fillLinear,
+ cubic: fillCubic
+ },
+ CACHE = self.kiri.fill_fixed = {
+ hex: fillHexFull,
+ grid: fillGrid,
+ triangle: fillTriangle
+ };
- function fillHexFull(target) {
- fillHex(target, true);
- }
+function fillHexFull(target) {
+ fillHex(target, true);
+}
- /**
- * emitter creates a hex infill pattern and sends to target
- *
- * @param {Object} target
- * @param {boolean} full continuous walls
- */
- function fillHex(target, full) {
- // compute segment lengths (vert/horiz and 45)
- let spacing = target.offset();
- // let vhlen = (1 / target.density()) * (target.lineWidth() + spacing);
- let vhlen = (1 / target.density()) * target.lineWidth() * 0.5;
- let anxlen = ROUND(Math.cos(30 * DEG2RAD) * vhlen, 7);
- let anylen = ROUND(Math.sin(30 * DEG2RAD) * vhlen, 7);
- let bounds = target.bounds();
- let even = true;
- let evenZ = target.zIndex() % 2 === 0;
- let maxy = bounds.max.y + (vhlen + anylen * 2);
- let x, y;
+/**
+ * emitter creates a hex infill pattern and sends to target
+ *
+ * @param {Object} target
+ * @param {boolean} full continuous walls
+ */
+function fillHex(target, full) {
+ // compute segment lengths (vert/horiz and 45)
+ let spacing = target.offset();
+ // let vhlen = (1 / target.density()) * (target.lineWidth() + spacing);
+ let vhlen = (1 / target.density()) * target.lineWidth() * 0.5;
+ let anxlen = ROUND(Math.cos(30 * DEG2RAD) * vhlen, 7);
+ let anylen = ROUND(Math.sin(30 * DEG2RAD) * vhlen, 7);
+ let bounds = target.bounds();
+ let even = true;
+ let evenZ = target.zIndex() % 2 === 0;
+ let maxy = bounds.max.y + (vhlen + anylen * 2);
+ let x, y;
- if (full || evenZ) {
- x = bounds.min.x;
- for (;;) {
- if (even && x > bounds.max.x) break;
- if (!even && x > bounds.max.x + anxlen + spacing) break;
- y = bounds.min.y;
- target.newline();
- while (y <= maxy) {
- target.emit(x,y);
- y += vhlen;
- target.emit(x,y);
- if (even) x += anxlen; else x -= anxlen;
- y += anylen;
- target.emit(x,y);
- y += vhlen;
- target.emit(x,y);
- if (even) x -= anxlen; else x += anxlen;
- y += anylen;
- }
- x += spacing;
- if (even) x += (anxlen * 2);
- even = !even;
- target.newline();
- }
- } else {
- y = bounds.min.y + vhlen;
- for (;;) {
- if (even && y > bounds.max.y) break;
- if (!even && y > bounds.max.y + anylen) break;
- x = bounds.min.x;
- target.newline();
- while (x < bounds.max.x) {
- target.emit(x,y);
- if (even) y += anylen; else y -= anylen;
- x += anxlen;
- target.emit(x,y);
- x += spacing;
- target.emit(x,y);
- if (even) y -= anylen; else y += anylen;
- x += anxlen;
- target.emit(x,y);
- x += spacing;
- }
- y += vhlen;
- if (even) y += (anylen * 2);
- even = !even;
- target.newline();
- }
- }
- }
-
- function fillGyroid(target) {
- let bounds = target.bounds();
- let height = target.zHeight();
- let span_x = bounds.max.x - bounds.min.x;
- let span_y = bounds.max.y - bounds.min.y;
- let density = target.density();
- let tile = 1 + (1 - density) * 15;
- let tile_x = span_x / tile;
- let tile_y = span_y / tile;
- let tile_z = 1 / tile;
- let gyroid = BASE.gyroid.slice(target.zValue() * tile_z, (1 - density) * 500);
-
- // gyroid.polys.forEach(poly => {
- // for (let tx=0; tx<=tile_x; tx++) {
- // for (let ty=0; ty<=tile_y; ty++) {
- // target.newline();
- // let bx = tx * tile + bounds.min.x;
- // let by = ty * tile + bounds.min.y;
- // poly.forEach(point => {
- // target.emit(bx + point.x * tile, by + point.y * tile);
- // });
- // }
- // }
- // });
-
- let polys = [];
- for (let tx=0; tx<=tile_x; tx++) {
- for (let ty=0; ty<=tile_y; ty++) {
- for (let poly of gyroid.polys) {
- target.newline();
- let points = poly.map(el => {
- return {
- x: el.x * tile + tx * tile + bounds.min.x,
- y: el.y * tile + ty * tile + bounds.min.y,
- z: 0
- }
- });
- polys.push(BASE.newPolygon().setOpen(true).addObj(points));
- }
- }
- }
- polys = connectOpenPolys(polys);
- for (let poly of polys.filter(p => p.perimeter() > 2)) {
+ if (full || evenZ) {
+ x = bounds.min.x;
+ for (;;) {
+ if (even && x > bounds.max.x) break;
+ if (!even && x > bounds.max.x + anxlen + spacing) break;
+ y = bounds.min.y;
target.newline();
- for (let point of poly.points) {
- target.emit(point.x, point.y);
+ while (y <= maxy) {
+ target.emit(x,y);
+ y += vhlen;
+ target.emit(x,y);
+ if (even) x += anxlen; else x -= anxlen;
+ y += anylen;
+ target.emit(x,y);
+ y += vhlen;
+ target.emit(x,y);
+ if (even) x -= anxlen; else x += anxlen;
+ y += anylen;
+ }
+ x += spacing;
+ if (even) x += (anxlen * 2);
+ even = !even;
+ target.newline();
+ }
+ } else {
+ y = bounds.min.y + vhlen;
+ for (;;) {
+ if (even && y > bounds.max.y) break;
+ if (!even && y > bounds.max.y + anylen) break;
+ x = bounds.min.x;
+ target.newline();
+ while (x < bounds.max.x) {
+ target.emit(x,y);
+ if (even) y += anylen; else y -= anylen;
+ x += anxlen;
+ target.emit(x,y);
+ x += spacing;
+ target.emit(x,y);
+ if (even) y -= anylen; else y += anylen;
+ x += anxlen;
+ target.emit(x,y);
+ x += spacing;
+ }
+ y += vhlen;
+ if (even) y += (anylen * 2);
+ even = !even;
+ target.newline();
+ }
+ }
+}
+
+function fillGyroid(target) {
+ let bounds = target.bounds();
+ let height = target.zHeight();
+ let span_x = bounds.max.x - bounds.min.x;
+ let span_y = bounds.max.y - bounds.min.y;
+ let density = target.density();
+ let tile = 1 + (1 - density) * 15;
+ let tile_x = span_x / tile;
+ let tile_y = span_y / tile;
+ let tile_z = 1 / tile;
+ let gyroid = BASE.gyroid.slice(target.zValue() * tile_z, (1 - density) * 500);
+
+ // gyroid.polys.forEach(poly => {
+ // for (let tx=0; tx<=tile_x; tx++) {
+ // for (let ty=0; ty<=tile_y; ty++) {
+ // target.newline();
+ // let bx = tx * tile + bounds.min.x;
+ // let by = ty * tile + bounds.min.y;
+ // poly.forEach(point => {
+ // target.emit(bx + point.x * tile, by + point.y * tile);
+ // });
+ // }
+ // }
+ // });
+
+ let polys = [];
+ for (let tx=0; tx<=tile_x; tx++) {
+ for (let ty=0; ty<=tile_y; ty++) {
+ for (let poly of gyroid.polys) {
+ target.newline();
+ let points = poly.map(el => {
+ return {
+ x: el.x * tile + tx * tile + bounds.min.x,
+ y: el.y * tile + ty * tile + bounds.min.y,
+ z: 0
+ }
+ });
+ polys.push(BASE.newPolygon().setOpen(true).addObj(points));
}
}
}
+ polys = connectOpenPolys(polys);
+ for (let poly of polys.filter(p => p.perimeter() > 2)) {
+ target.newline();
+ for (let point of poly.points) {
+ target.emit(point.x, point.y);
+ }
+ }
+}
- function connectOpenPolys(noff, dist = 0.1) {
- if (noff.length <= 1) {
- return noff;
- }
- let heal = 0;
- // heal/rejoin open segments that have close endpoints
- outer: for(;; heal++) {
- let ntmp = noff, tlen = ntmp.length;
- for (let i=0; i 0) {
- // cull nulls
- noff = noff.filter(o => o);
- }
+function connectOpenPolys(noff, dist = 0.1) {
+ if (noff.length <= 1) {
return noff;
}
-
- function fillGrid(target) {
- let bounds = target.bounds();
- let height = target.zHeight();
- let span_x = bounds.max.x - bounds.min.x;
- let span_y = bounds.max.y - bounds.min.y;
- let offset = target.offset() / 2;
- let tile = (1 / target.density()) * target.lineWidth();
- let tile_x = tile + offset;
- let tile_xc = span_x / tile_x;
- let tile_yc = span_y / tile;
-
- for (let tx=0; tx<=tile_xc; tx++) {
- target.newline();
- for (let ty=0; ty<=tile_yc; ty++) {
- let bx = tx * tile_x + bounds.min.x;
- let by = ty * tile + bounds.min.y;
- if ((tx + ty) % 2) {
- target.emit(bx, by);
- target.emit(bx + tile_x - offset, by + tile);
- } else {
- target.emit(bx + tile_x - offset, by);
- target.emit(bx, by + tile);
+ let heal = 0;
+ // heal/rejoin open segments that have close endpoints
+ outer: for(;; heal++) {
+ let ntmp = noff, tlen = ntmp.length;
+ for (let i=0; i 0) {
+ // cull nulls
+ noff = noff.filter(o => o);
}
+ return noff;
+}
- function fillLinear(target) {
- let bounds = target.bounds();
- let span = Math.max(
- bounds.max.x - bounds.min.x,
- bounds.max.y - bounds.min.y
- );
- let steps = Math.floor((span / target.lineWidth()) * target.density());
- let step = span / steps;
- let ztype = Math.floor(target.zIndex() / target.repeat()) % 2;
- if (ztype === 1) {
- for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
- target.newline();
- target.emit(tx, bounds.min.y);
- target.emit(tx, bounds.max.y);
- }
- } else if (ztype === 0) {
- for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
- target.newline();
- target.emit(bounds.min.x, ty);
- target.emit(bounds.max.x, ty);
- }
- }
- }
+function fillGrid(target) {
+ let bounds = target.bounds();
+ let height = target.zHeight();
+ let span_x = bounds.max.x - bounds.min.x;
+ let span_y = bounds.max.y - bounds.min.y;
+ let offset = target.offset() / 2;
+ let tile = (1 / target.density()) * target.lineWidth();
+ let tile_x = tile + offset;
+ let tile_xc = span_x / tile_x;
+ let tile_yc = span_y / tile;
- function fillTriangle(target) {
- let bounds = target.bounds();
- let span_x = bounds.max.x - bounds.min.x;
- let span_y = bounds.max.y - bounds.min.y;
- let offset = target.offset();
- let line_w = target.lineWidth() / 2;
- let tile = (1 / target.density()) * (target.lineWidth() * 1.25);
- let tile_x = tile + offset*2 + line_w;
- let tile_xc = span_x / tile_x;
- let tile_yc = span_y / tile;
-
- for (let tx=0; tx<=tile_xc; tx++) {
- target.newline();
- for (let ty=0; ty<=tile_yc; ty++) {
- let bx = tx * tile_x + bounds.min.x;
- let by = ty * tile + bounds.min.y;
- if ((tx + ty) % 2) {
- target.emit(bx, by);
- target.emit(bx + tile_x - offset - line_w, by + tile);
- } else {
- target.emit(bx + tile_x - offset - line_w, by);
- target.emit(bx, by + tile);
- }
- }
- }
-
- for (let tx=0; tx<=tile_xc; tx++) {
+ for (let tx=0; tx<=tile_xc; tx++) {
+ target.newline();
+ for (let ty=0; ty<=tile_yc; ty++) {
let bx = tx * tile_x + bounds.min.x;
- let xp = bx + tile_x - line_w/2 - offset/2;
- target.newline();
- target.emit(xp, bounds.min.y);
- target.emit(xp, bounds.max.y);
+ let by = ty * tile + bounds.min.y;
+ if ((tx + ty) % 2) {
+ target.emit(bx, by);
+ target.emit(bx + tile_x - offset, by + tile);
+ } else {
+ target.emit(bx + tile_x - offset, by);
+ target.emit(bx, by + tile);
+ }
}
}
+}
+
+function fillCubic(target) {
+ let bounds = target.bounds();
+ let span = Math.max(
+ bounds.max.x - bounds.min.x,
+ bounds.max.y - bounds.min.y
+ );
+ let steps = Math.floor((span / target.lineWidth()) * target.density());
+ let step = span / steps;
+ let ztype = Math.floor(target.zIndex() / target.repeat()) % 3;
+ if (ztype === 1) {
+ for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
+ target.newline();
+ target.emit(tx, bounds.min.y);
+ target.emit(tx, bounds.max.y);
+ }
+ } else if (ztype === 0) {
+ for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
+ target.newline();
+ target.emit(bounds.min.x, ty);
+ target.emit(bounds.max.x, ty);
+ }
+ } else {
+ step *- Math.sqrt(2);
+ for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
+ target.newline();
+ target.emit(tx, bounds.min.y);
+ target.emit(tx + 1000, bounds.max.y + 1000);
+ }
+ for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
+ target.newline();
+ target.emit(bounds.min.x, ty);
+ target.emit(bounds.min.x + 1000, ty + 1000);
+ }
+ }
+}
+
+function fillLinear(target) {
+ let bounds = target.bounds();
+ let span = Math.max(
+ bounds.max.x - bounds.min.x,
+ bounds.max.y - bounds.min.y
+ );
+ let steps = Math.floor((span / target.lineWidth()) * target.density());
+ let step = span / steps;
+ let ztype = Math.floor(target.zIndex() / target.repeat()) % 2;
+ if (ztype === 1) {
+ for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
+ target.newline();
+ target.emit(tx, bounds.min.y);
+ target.emit(tx, bounds.max.y);
+ }
+ } else if (ztype === 0) {
+ for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
+ target.newline();
+ target.emit(bounds.min.x, ty);
+ target.emit(bounds.max.x, ty);
+ }
+ }
+}
+
+function fillTriangle(target) {
+ let bounds = target.bounds();
+ let span_x = bounds.max.x - bounds.min.x;
+ let span_y = bounds.max.y - bounds.min.y;
+ let offset = target.offset();
+ let line_w = target.lineWidth() / 2;
+ let tile = (1 / target.density()) * (target.lineWidth() * 1.25);
+ let tile_x = tile + offset*2 + line_w;
+ let tile_xc = span_x / tile_x;
+ let tile_yc = span_y / tile;
+
+ for (let tx=0; tx<=tile_xc; tx++) {
+ target.newline();
+ for (let ty=0; ty<=tile_yc; ty++) {
+ let bx = tx * tile_x + bounds.min.x;
+ let by = ty * tile + bounds.min.y;
+ if ((tx + ty) % 2) {
+ target.emit(bx, by);
+ target.emit(bx + tile_x - offset - line_w, by + tile);
+ } else {
+ target.emit(bx + tile_x - offset - line_w, by);
+ target.emit(bx, by + tile);
+ }
+ }
+ }
+
+ for (let tx=0; tx<=tile_xc; tx++) {
+ let bx = tx * tile_x + bounds.min.x;
+ let xp = bx + tile_x - line_w/2 - offset/2;
+ target.newline();
+ target.emit(xp, bounds.min.y);
+ target.emit(xp, bounds.max.y);
+ }
+}
})();
diff --git a/src/kiri-mode/fdm/prepare.js b/src/kiri-mode/fdm/prepare.js
index 0d0a98dc..55b466c5 100644
--- a/src/kiri-mode/fdm/prepare.js
+++ b/src/kiri-mode/fdm/prepare.js
@@ -4,1133 +4,1742 @@
(function() {
- let KIRI = self.kiri,
- BASE = self.base,
- UTIL = BASE.util,
- POLY = BASE.polygons,
- PATH = BASE.paths,
- FDM = KIRI.driver.FDM,
- { newPoint, newPolygon } = BASE,
- { getRangeParameters } = FDM,
- { poly2polyEmit } = PATH,
- debug = false;
+const { base, kiri, noop } = self;
+const { consts, driver, newSlice, utils } = kiri;
+const { config, polygons, paths, util, newPoint, newPolygon, Polygon } = base;
+const { poly2polyEmit, tip2tipEmit } = paths;
+const { numOrDefault } = util;
+const { fillArea } = polygons;
+const { beltfact } = consts;
+const { FDM } = driver;
+const { getRangeParameters } = FDM;
+const POLY = polygons;
- /**
- * DRIVER PRINT CONTRACT
- *
- * @param {Function} update progress callback
- * @returns {Object[]} returns array of render objects
- */
- FDM.prepare = function(widgets, settings, update) {
- // filter ignored widgets
- widgets = widgets.filter(w => !w.track.ignore && !w.meta.disabled);
+/**
+ * DRIVER PRINT CONTRACT
+ *
+ * @param {Function} update progress callback
+ * @returns {Object[]} returns array of render objects
+ */
+FDM.prepare = function(widgets, settings, update) {
+ // filter ignored widgets
+ widgets = widgets.filter(w => !w.track.ignore && !w.meta.disabled);
- let render = settings.render !== false,
- { device, process, controller, bounds, mode } = settings,
- { bedWidth, bedDepth } = device,
- output = [],
- printPoint = newPoint(0,0,0),
- nozzle = device.extruders[0].extNozzle,
- isBelt = device.bedBelt,
- isThin = controller.lineType === "line",
- isFlat = controller.lineType === "flat",
- isDanger = controller.danger || false,
- isPalette = device.filamentSource === 'palette3',
- firstLayerHeight = isBelt ? process.sliceHeight : process.firstSliceHeight || process.sliceHeight,
- firstLayerSeek = process.outputSeekrate,
- firstLayerRate = process.firstLayerRate,
- firstLayerMult = process.firstLayerPrintMult,
- purgeTower = process.outputPurgeTower || 0,
- layerRetract = process.outputLayerRetract,
- draftShield = process.outputDraftShield,
- layerno = 0,
- zoff = 0,
- zmin = 0,
- shield,
- layerout = [],
- print = self.worker.print = KIRI.newPrint(settings, widgets),
- beltfact = Math.cos(Math.PI/4);
+ let { device, process, controller, bounds, mode } = settings,
+ { sliceHeight, firstSliceHeight, firstLayerRate } = process,
+ { outputSeekrate, outputLayerRetract, outputDraftShield, outputPurgeTower } = process,
+ { bedWidth, bedDepth, filamentSource } = device,
+ { lineType, danger } = controller,
+ printPoint = newPoint(0,0,0),
+ print = self.worker.print = kiri.newPrint(settings, widgets),
+ nozzle = device.extruders[0].extNozzle,
+ isBelt = device.bedBelt,
+ isThin = lineType === "line",
+ isFlat = lineType === "flat",
+ isDanger = danger || false,
+ isPalette = filamentSource === 'palette3',
+ firstLayerHeight = isBelt ? sliceHeight : firstSliceHeight || sliceHeight,
+ layerno = 0,
+ zoff = 0,
+ zmin = 0,
+ shield,
+ output = [],
+ layerout = [];
- // compute bounds if missing
- if (!bounds) {
- bounds = new THREE.Box3();
- for (let widget of widgets) {
- let wp = widget.track.pos;
- let wb = widget.bounds.clone();
- wb.min.x += wp.x;
- wb.max.x += wp.x;
- wb.min.y += wp.y;
- wb.max.y += wp.y;
- bounds.union(wb);
+ // compute bounds if missing
+ if (!bounds) {
+ bounds = new THREE.Box3();
+ for (let widget of widgets) {
+ let wp = widget.track.pos;
+ let wb = widget.bounds.clone();
+ wb.min.x += wp.x;
+ wb.max.x += wp.x;
+ wb.min.y += wp.y;
+ wb.max.y += wp.y;
+ bounds.union(wb);
+ }
+ settings.bounds = bounds;
+ }
+
+ // TODO pick a widget with a slice on the first layer and use that nozzle
+ // create brim, skirt, raft if specificed in FDM mode (code shared by laser)
+ if (!isBelt && (process.outputBrimCount || process.outputRaft)) {
+ let brims = [],
+ offset = process.outputBrimOffset || (process.outputRaft ? 4 : 0);
+
+ // compute first brim
+ widgets.filter(w => w.slices.length).forEach(function(widget) {
+ let tops = [];
+ let slices = [];
+ if (outputDraftShield) {
+ slices.appendAll(widget.slices);
+ } else {
+ slices.push(widget.slices[0]);
}
- settings.bounds = bounds;
+ for (let slice of slices) {
+ // collect top outer polygons
+ if (slice.tops)
+ slice.tops.forEach(function(top) {
+ tops.push(top.poly.clone());
+ });
+ // collect support polygons
+ if (slice.supports)
+ slice.supports.forEach(function(support) {
+ tops.push(support.clone());
+ });
+ }
+ if (outputDraftShield) {
+ tops = POLY.union(tops,1,true);
+ }
+ // nest and offset tops
+ POLY.nest(tops).forEach(function(poly) {
+ let off = poly.offset(-offset + nozzle / 2);
+ if (off) off.forEach(function(brim) {
+ brim.move(widget.track.pos);
+ brims.push(brim);
+ });
+ });
+ });
+
+ // merge brims
+ brims = POLY.union(brims, undefined, true);
+
+ // if brim is offset, over-expand then shrink to induce brims to merge
+ if (brims.length) {
+ let extra = process.sliceSupportExtra + 2;
+ let zheight = brims[0].getZ();
+ brims = POLY.expand(brims, extra, zheight, null, 1);
+ brims = POLY.expand(brims, -extra, zheight, null, 1);
}
- // TODO pick a widget with a slice on the first layer and use that nozzle
- // create brim, skirt, raft if specificed in FDM mode (code shared by laser)
- if (!isBelt && (process.outputBrimCount || process.outputRaft)) {
- let brims = [],
- offset = process.outputBrimOffset || (process.outputRaft ? 4 : 0);
+ // if raft is specified
+ if (process.outputRaft) {
+ let offset = newPoint(0,0,0),
+ height = nozzle,
+ rafts = process.outputBrimCount ?
+ POLY.expand(brims, nozzle * 4, 0, null, 1) : brims;
- // compute first brim
- widgets.filter(w => w.slices.length).forEach(function(widget) {
- let tops = [];
- let slices = [];
- if (draftShield) {
- slices.appendAll(widget.slices);
- } else {
- slices.push(widget.slices[0]);
- }
- for (let slice of slices) {
- // collect top outer polygons
- if (slice.tops)
- slice.tops.forEach(function(top) {
- tops.push(top.poly.clone());
- });
- // collect support polygons
- if (slice.supports)
- slice.supports.forEach(function(support) {
- tops.push(support.clone());
- });
- }
- if (draftShield) {
- tops = POLY.union(tops,1,true);
- }
- // nest and offset tops
- POLY.nest(tops).forEach(function(poly) {
- let off = poly.offset(-offset + nozzle / 2);
- if (off) off.forEach(function(brim) {
- brim.move(widget.track.pos);
- brims.push(brim);
- });
+
+ // cause first point of raft to be used
+ printPoint = null;
+
+ let raft = function(height, angle, spacing, speed, extrude) {
+ let slice = kiri.newSlice(zoff + height / 2);
+ rafts.forEach(function(brim) {
+ // use first point of first brim as start point
+ if (printPoint === null) printPoint = brim.first();
+ let t = slice.addTop(brim);
+ t.traces = [ brim ];
+ t.inner = POLY.expand(t.traces, -nozzle * 0.5, 0, null, 1);
+ // tweak bounds for fill to induce an offset
+ t.inner[0].bounds.minx -= nozzle/2;
+ t.inner[0].bounds.maxx += nozzle/2;
+ t.fill_lines = POLY.fillArea(t.inner, angle, spacing, []);
+ })
+ offset.z = slice.z;
+ printPoint = slicePrintPath(print, slice, printPoint, offset, layerout, {
+ speed,
+ mult: extrude,
+ });
+ layerout.z = zoff + height;
+ layerout.height = height;
+ output.append(layerout);
+
+ layerout = [];
+ zoff += height;
+ };
+
+ raft(nozzle/1, process.sliceFillAngle + 0 , nozzle * 5.0, firstLayerRate / 3, 4);
+ raft(nozzle/1, process.sliceFillAngle + 0 , nozzle * 5.0, firstLayerRate / 2, 4);
+ raft(nozzle/2, process.sliceFillAngle + 90, nozzle * 3.0, process.outputFeedrate, 2.5);
+ raft(nozzle/2, process.sliceFillAngle + 0 , nozzle * 1.0, process.outputFeedrate, 1.5);
+ raft(nozzle/2, process.sliceFillAngle + 90 , nozzle * 0.7, process.outputFeedrate, 0.75);
+
+ // raise first layer off raft slightly to lessen adhesion
+ firstLayerHeight += process.outputRaftSpacing || 0;
+ zoff += process.outputRaftSpacing || 0;
+
+ // retract after last raft layer
+ output.last().last().retract = true;
+ }
+
+ // if using brim or skirt
+ if (process.outputBrimCount) {
+ let polys = [],
+ preout = [];
+
+ // offset specified # of brims
+ POLY.offset(brims, nozzle, {
+ outs: polys,
+ flat: true,
+ count: process.outputBrimCount,
+ z: firstLayerHeight / 2
+ });
+
+ print.setType('brim');
+ shield = POLY.nest(polys.clone(), true).clone();
+
+ // output brim points
+ let brimStart = offset < nozzle * 2 ? newPoint(-bedWidth, -bedDepth, 0) : printPoint;
+ printPoint = poly2polyEmit(polys, brimStart, (poly, index, count, startPoint) => {
+ return print.polyPrintPath(poly, startPoint, preout, {
+ rate: firstLayerRate,
+ onfirst: function(point) {
+ if (preout.length && point.distTo2D(startPoint) > 2) {
+ // retract between brims
+ preout.last().retract = true;
+ }
+ }
});
});
- // merge brims
- brims = POLY.union(brims, undefined, true);
+ print.addPrintPoints(preout, layerout, null);
- // if brim is offset, over-expand then shrink to induce brims to merge
- if (brims.length) {
- let extra = process.sliceSupportExtra + 2;
- let zheight = brims[0].getZ();
- brims = POLY.expand(brims, extra, zheight, null, 1);
- brims = POLY.expand(brims, -extra, zheight, null, 1);
+ if (preout.length) {
+ // retract between brims and print
+ preout.last().retract = true;
+ }
+ }
+ // recompute bounds for purge block offsets
+ let bbounds = base.newBounds();
+ brims.forEach(brim => {
+ bbounds.merge(brim.bounds);
+ });
+ bounds.min.x = Math.min(bounds.min.x, bbounds.minx);
+ bounds.min.y = Math.min(bounds.min.y, bbounds.miny);
+ bounds.max.x = Math.max(bounds.max.x, bbounds.maxx);
+ bounds.max.y = Math.max(bounds.max.y, bbounds.maxy);
+ }
+
+ // synthesize support widgets when needed
+ // so that they can use a separate extruder
+ // compute zmin for belt purge towers
+ for (let widget of widgets.slice()) {
+ let sslices = [];
+ if (!widget.slices) {
+ console.log('invalid widget', widget);
+ continue;
+ }
+ for (let slice of widget.slices) {
+ zmin = Math.min(zmin, slice.z);
+ if (!slice.supports) {
+ continue;
+ }
+ let sslice = kiri.newSlice(slice.z);
+ sslice.extruder = process.sliceSupportNozzle;
+ sslice.supports = slice.supports.slice();
+ sslice.height = slice.height;
+ sslices.push(sslice);
+ }
+ if (sslices.length) {
+ let swidget = kiri.newWidget(null,widget.group);
+ swidget.slices = sslices;
+ swidget.support = true;
+ swidget.rotinfo = widget.rotinfo;
+ swidget.belt = widget.belt;
+ swidget.track = Object.clone(widget.track);
+ swidget.mesh = { widget: swidget, position: swidget.track.pos };
+ widget.anno.extruder = process.sliceSupportNozzle;
+ widgets.push(swidget);
+ }
+ }
+
+ let lastPoly;
+ let lastLayer;
+ let lastOffset;
+ let extruders = print.extruders = [];
+ let extcount = 0;
+
+ // find max layers (for updates)
+ // generate list of used extruders for purge blocks
+ for (let widget of widgets) {
+ let extruder = widget.anno.extruder || 0;
+ if (!extruders[extruder]) {
+ extruders[extruder] = {};
+ extcount++;
+ }
+ }
+
+ // determine size/location of purge blocks
+ let blokw = Math.sqrt(outputPurgeTower || 0);
+ let blokh = blokw;
+ let blokpos, walkpos, blok;
+
+ function mkblok(w,h) {
+ let count = extcount - 1;
+ let gap = nozzle;
+ if (isBelt) {
+ let step = w + gap;
+ let mp = (bounds.max.y + bounds.min.y) / 2; // part y midpoint
+ let sp = (bounds.max.y - bounds.min.y); // part y span
+ let ts = (w * count) + (gap * (count - 1)); // tower y span
+ let ty = -ts + w/2 - gap/2; // tower start y
+ blokpos = { y:ty, x: bounds.max.x + 2 + h / 2}; // first block pos
+ walkpos = { y:step, x:0 };
+ blok = { x:h, y:w };
+ } else if (bounds.min.x < bounds.min.y) {
+ let step = h + gap;
+ let mp = (bounds.max.x + bounds.min.x) / 2; // part y midpoint
+ let sp = (bounds.max.x - bounds.min.x); // part y span
+ let ts = (h * count) + (gap * (count - 1)); // tower y span
+ let tx = mp - ts / 2 + step / 2; // tower start y
+ blokpos = { x:tx, y: bounds.max.y + 2 + w / 2}; // first block pos
+ walkpos = { x:step, y:0 };
+ blok = { x:w, y:h };
+ } else {
+ let step = w + gap;
+ let mp = (bounds.max.y + bounds.min.y) / 2; // part y midpoint
+ let sp = (bounds.max.y - bounds.min.y); // part y span
+ let ts = (w * count) + (gap * (count - 1)); // tower y span
+ let ty = mp - ts / 2 + step / 2; // tower start y
+ blokpos = { y:ty, x: bounds.max.x + 2 + h / 2}; // first block pos
+ walkpos = { y:step, x:0 };
+ blok = { x:w, y:h };
+ }
+ }
+
+ function mkrec(i, angle = 45, thin = 6) {
+ let exi = device.extruders[i],
+ noz = exi.extNozzle,
+ pos = {x:blokpos.x + walkpos.x * i, y:blokpos.y + walkpos.y * i, z:0},
+ rect = newPolygon().centerRectangle(pos, blok.x, blok.y),
+ full = linesToPoly(POLY.fillArea([
+ newPolygon().centerRectangle(pos, blok.x - noz, blok.y - noz)
+ ], angle, noz)),
+ sparse = rect.area() > 10 ? linesToPoly(POLY.fillArea([
+ newPolygon().centerRectangle(pos, blok.x - noz, blok.y - noz)
+ ], angle + 90, noz * thin)) : newPolygon(),
+ rec = {
+ extruder: i,
+ diameter: exi.extNozzle,
+ rect,
+ full,
+ sparse,
+ pause: newPoint(pos.x + walkpos.y, pos.y + walkpos.x, pos.z)
+ };
+ return rec;
+ }
+
+ mkblok(blokw, blokh);
+
+ // allocate tower space for extruders-1 locations
+ let towers = extruders.slice(1).map((ext,i) => {
+ return ext ? mkrec(i) : ext;
+ });
+
+ function linesToPoly(points) {
+ let poly = newPolygon().setOpen();
+ let ping = 0;
+ for (let i=0; i= 0 || lastPurgeTool === nozzle;
+ let tool = using >= 0 ? using : nozzle;
+ let first = isBelt ? !purgedFirst && layer.slice.index >= 0 : layer.slice.index === 0;
+ let rate = first ? process.firstLayerRate : process.outputFeedrate;
+ let wipe = true;
+ lastPurgeTool = tool;
+ if (rec) {
+ print.setType('purge tower');
+ if (layer.last()) {
+ layer.last().retract = true;
+ }
+ purgedFirst = purgedFirst || first;
+ let purgeOn = first || !thin;
+ if (isBelt && z < 0) {
+ let scale = 1 - ((z / zmin));
+ mkblok(blokw * scale, blokh);
+ rec = mkrec(rec.extruder, 0, 10);
+ rate = (process.outputFeedrate - process.firstLayerRate) * scale + process.firstLayerRate;
+ wipe = false;
+ }
+ let box = rec.rect.clone().setZ(z);
+ let pause = rec.pause.clone().setZ(z);
+ let fill = (z >= 0 && purgeOn ? rec.full : rec.sparse).clone().setZ(z);
+ if (isBelt) {
+ let bmove = {x:0, y:z, z:0};
+ box.move(bmove);
+ pause.move(bmove);
+ if (fill) {
+ fill.move(bmove);
+ }
+ if (offset) {
+ let bo = { x:0, y:offset.y, z:offset.z };
+ box.move(bo);
+ pause.move(bo);
+ if (fill) fill.move(bo);
+ }
+ }
+ start = print.polyPrintPath(box, start, layer, {
+ tool,
+ rate,
+ simple: true,
+ open: false,
+ onfirstout: (out => out.overate = (isBelt ? rate : 0))
+ });
+ if (fill && fill.length) {
+ // for pings, split path at 20mm
+ start = print.polyPrintPath(fill, start, layer, {
+ tool,
+ rate,
+ simple: true,
+ open: true,
+ onfirst: (point) => { point.purgeOn = purgeOn ? pause : undefined }
+ });
+ }
+ layer.last().retract = true;
+ // experimental post-retract wipe
+ if (wipe) start = print.polyPrintPath(box, start, layer, {
+ tool,
+ rate,
+ simple: true,
+ open: false,
+ extrude: 0
+ });
+ layer.last().overate = 0;
+ return start;
+ } else {
+ console.log({no_purge_tower_for: nozzle, using, track, layer});
+ return start;
+ }
+ }
+
+ // establish offsets
+ for (let widget of widgets) {
+ let { rotinfo, belt } = widget;
+ let offset = Object.clone(widget.track.pos);
+ if (isBelt) {
+ let o = rotinfo.ypos * beltfact;
+ offset = {
+ x: rotinfo.xpos,
+ y: o,
+ z: o
+ };
+ } else {
+ // when rafts used this is non-zero
+ offset.z = zoff;
+ }
+ widget.offset = offset;
+ }
+
+ // create shuffled slice cake by z offset (slice.z + offset.z)
+ let cake = [];
+ let zrec = {};
+ for (let widget of widgets) {
+ // skip synthesized support widget(s)
+ if (!widget.mesh) {
+ continue;
+ }
+ for (let slice of widget.slices) {
+ slice.widget = widget;
+ let z = (slice.z + widget.offset.z).round(2);
+ let rec = zrec[z] = zrec[z] || {z, slices:[]};
+ if (rec.slices.length === 0) {
+ cake.push(rec);
+ }
+ rec.slices.push(slice);
+ }
+ }
+ cake.sort((a, b) => {
+ return a.z - b.z;
+ });
+
+ let firstExt;
+ let lastWidget;
+ let lastExt;
+ let lastOut;
+
+ // walk cake layers bottom up
+ for (let layer of cake) {
+ // track purge blocks generated for each layer
+ let track = towers.slice();
+
+ // iterate over layer slices, find closest widget, print, eliminate
+ for (;;) {
+ let order = [];
+ // select slices of the same extruder type first then distance
+ for (let slice of layer.slices) {
+ if (slice.prep) {
+ continue;
+ }
+ let offset = lastOffset = slice.widget.offset;
+ let find = slice.findClosestPointTo(printPoint.sub(offset));
+ if (find) {
+ let ext = slice.extruder;
+ let lex = lastExt;
+ let dst = Math.abs(find.distance);
+ // penalize extruder swaps
+ if (ext !== lex) {
+ dst *= 10000;
+ }
+ // for first object, penalize extruders other than first
+ if (lastExt === undefined && ext > 0) {
+ dst *= 10000;
+ }
+ order.push({dst, slice, offset, z: layer.z});
+ }
+ }
+ if (order.length === 0) {
+ break;
+ }
+ order.sort((a,b) => {
+ return a.dst - b.dst;
+ });
+ let { z, slice, offset } = order[0];
+ if (firstExt === undefined) {
+ firstExt = slice.extruder;
}
- // if raft is specified
- if (process.outputRaft) {
- let offset = newPoint(0,0,0),
- height = nozzle,
- rafts = process.outputBrimCount ?
- POLY.expand(brims, nozzle * 4, 0, null, 1) : brims;
-
-
- // cause first point of raft to be used
- printPoint = null;
-
- let raft = function(height, angle, spacing, speed, extrude) {
- let slice = KIRI.newSlice(zoff + height / 2);
- rafts.forEach(function(brim) {
- // use first point of first brim as start point
- if (printPoint === null) printPoint = brim.first();
- let t = slice.addTop(brim);
- t.traces = [ brim ];
- t.inner = POLY.expand(t.traces, -nozzle * 0.5, 0, null, 1);
- // tweak bounds for fill to induce an offset
- t.inner[0].bounds.minx -= nozzle/2;
- t.inner[0].bounds.maxx += nozzle/2;
- t.fill_lines = POLY.fillArea(t.inner, angle, spacing, []);
- })
- offset.z = slice.z;
- printPoint = print.slicePrintPath(slice, printPoint, offset, layerout, {
- speed: speed,
- mult: extrude,
- });
- layerout.z = zoff + height;
- layerout.height = height;
- output.append(layerout);
-
- layerout = [];
- zoff += height;
- };
-
- raft(nozzle/1, process.sliceFillAngle + 0 , nozzle * 5.0, firstLayerRate / 3, 4);
- raft(nozzle/1, process.sliceFillAngle + 0 , nozzle * 5.0, firstLayerRate / 2, 4);
- raft(nozzle/2, process.sliceFillAngle + 90, nozzle * 3.0, process.outputFeedrate, 2.5);
- raft(nozzle/2, process.sliceFillAngle + 0 , nozzle * 1.0, process.outputFeedrate, 1.5);
- raft(nozzle/2, process.sliceFillAngle + 90 , nozzle * 0.7, process.outputFeedrate, 0.75);
-
- // raise first layer off raft slightly to lessen adhesion
- firstLayerHeight += process.outputRaftSpacing || 0;
- zoff += process.outputRaftSpacing || 0;
-
- // retract after last raft layer
+ // when layers switch between widgets, force retraction
+ let forceRetract = lastOut && lastOut.widget !== slice.widget;
+ if (forceRetract && output.length) {
+ // selecet last output of last layer
output.last().last().retract = true;
}
- // if using brim or skirt
- if (process.outputBrimCount) {
- let polys = [],
- preout = [];
-
- // offset specified # of brims
- POLY.offset(brims, nozzle, {
- outs: polys,
- flat: true,
- count: process.outputBrimCount,
- z: firstLayerHeight / 2
- });
-
- print.setType('brim');
- shield = POLY.nest(polys.clone(), true).clone();
-
- // output brim points
- let brimStart = offset < nozzle * 2 ? newPoint(-bedWidth, -bedDepth, 0) : printPoint;
- printPoint = poly2polyEmit(polys, brimStart, (poly, index, count, startPoint) => {
- return print.polyPrintPath(poly, startPoint, preout, {
- rate: firstLayerRate,
- onfirst: function(point) {
- if (preout.length && point.distTo2D(startPoint) > 2) {
- // retract between brims
- preout.last().retract = true;
+ let params = getRangeParameters(process, slice.index);
+ slice.prep = true;
+ // retract between widgets or layers (when set)
+ if (layerout.length && slice.widget !== lastWidget) {
+ layerout.last().retract = true;
+ }
+ lastWidget = slice.widget;
+ layerout.z = z + slice.height / 2;
+ layerout.height = layerout.height || slice.height;
+ layerout.slice = slice;
+ // mark layer as anchor if slice is belt and flag set
+ layerout.anchor = slice.belt && slice.belt.anchor;
+ // detect extruder change and print purge block
+ if (!lastOut || lastOut.extruder !== slice.extruder) {
+ if (slice.extruder >= 0)
+ printPoint = purge(slice.extruder, track, layerout, printPoint, slice.z, undefined, offset);
+ }
+ let wtb = slice.widget.track.box;
+ let beltStart = slice.belt && slice.belt.touch;// && (widgets.length === 1);
+ // output seek to start point between mesh slices if previous data
+ print.setType('layer');
+ printPoint = slicePrintPath(
+ print,
+ slice,
+ beltStart ? newPoint(-5000, 5000, 0) : printPoint.sub(offset),
+ offset,
+ layerout,
+ {
+ routeAround: process.outputAvoidGaps,
+ seedPoint: printPoint.sub(offset),
+ danger: isDanger,
+ params, // range parameters
+ first: slice.index === 0,
+ support: slice.widget.support,
+ onBelt: beltStart,
+ pretract: (wipeDist) => {
+ if (lastLayer && lastLayer.length) {
+ let lastOut = lastLayer.last();
+ lastOut.retract = true;
+ if (wipeDist && lastPoly && lastOut.point) {
+ let endpoint = lastOut.point.followTo(lastPoly.center(true).add(offset), wipeDist);
+ if (endpoint.inPolygon(lastPoly)) {
+ print.addOutput(lastLayer, endpoint);
+ }
}
}
- });
- });
-
- print.addPrintPoints(preout, layerout, null);
-
- if (preout.length) {
- // retract between brims and print
- preout.last().retract = true;
- }
- }
- // recompute bounds for purge block offsets
- let bbounds = BASE.newBounds();
- brims.forEach(brim => {
- bbounds.merge(brim.bounds);
- });
- bounds.min.x = Math.min(bounds.min.x, bbounds.minx);
- bounds.min.y = Math.min(bounds.min.y, bbounds.miny);
- bounds.max.x = Math.max(bounds.max.x, bbounds.maxx);
- bounds.max.y = Math.max(bounds.max.y, bbounds.maxy);
- }
-
- // synthesize support widgets when needed
- // so that they can use a separate extruder
- // compute zmin for belt purge towers
- for (let widget of widgets.slice()) {
- let sslices = [];
- if (!widget.slices) {
- console.log('invalid widget', widget);
- continue;
- }
- for (let slice of widget.slices) {
- zmin = Math.min(zmin, slice.z);
- if (!slice.supports) {
- continue;
- }
- let sslice = KIRI.newSlice(slice.z);
- sslice.extruder = process.sliceSupportNozzle;
- sslice.supports = slice.supports.slice();
- sslice.height = slice.height;
- sslices.push(sslice);
- }
- if (sslices.length) {
- let swidget = KIRI.newWidget(null,widget.group);
- swidget.slices = sslices;
- swidget.support = true;
- swidget.rotinfo = widget.rotinfo;
- swidget.belt = widget.belt;
- swidget.track = Object.clone(widget.track);
- swidget.mesh = { widget: swidget, position: swidget.track.pos };
- widget.anno.extruder = process.sliceSupportNozzle;
- widgets.push(swidget);
- }
- }
-
- let lastPoly;
- let lastLayer;
- let lastOffset;
- let extruders = print.extruders = [];
- let extcount = 0;
-
- // find max layers (for updates)
- // generate list of used extruders for purge blocks
- for (let widget of widgets) {
- let extruder = widget.anno.extruder || 0;
- if (!extruders[extruder]) {
- extruders[extruder] = {};
- extcount++;
- }
- }
-
- // determine size/location of purge blocks
- let blokw = Math.sqrt(purgeTower);
- let blokh = blokw;
- let blokpos, walkpos, blok;
-
- function mkblok(w,h) {
- let count = extcount - 1;
- let gap = nozzle;
- if (isBelt) {
- let step = w + gap;
- let mp = (bounds.max.y + bounds.min.y) / 2; // part y midpoint
- let sp = (bounds.max.y - bounds.min.y); // part y span
- let ts = (w * count) + (gap * (count - 1)); // tower y span
- let ty = -ts + w/2 - gap/2; // tower start y
- blokpos = { y:ty, x: bounds.max.x + 2 + h / 2}; // first block pos
- walkpos = { y:step, x:0 };
- blok = { x:h, y:w };
- } else if (bounds.min.x < bounds.min.y) {
- let step = h + gap;
- let mp = (bounds.max.x + bounds.min.x) / 2; // part y midpoint
- let sp = (bounds.max.x - bounds.min.x); // part y span
- let ts = (h * count) + (gap * (count - 1)); // tower y span
- let tx = mp - ts / 2 + step / 2; // tower start y
- blokpos = { x:tx, y: bounds.max.y + 2 + w / 2}; // first block pos
- walkpos = { x:step, y:0 };
- blok = { x:w, y:h };
- } else {
- let step = w + gap;
- let mp = (bounds.max.y + bounds.min.y) / 2; // part y midpoint
- let sp = (bounds.max.y - bounds.min.y); // part y span
- let ts = (w * count) + (gap * (count - 1)); // tower y span
- let ty = mp - ts / 2 + step / 2; // tower start y
- blokpos = { y:ty, x: bounds.max.x + 2 + h / 2}; // first block pos
- walkpos = { y:step, x:0 };
- blok = { x:w, y:h };
- }
- }
-
- function mkrec(i, angle = 45, thin = 6) {
- let exi = device.extruders[i],
- noz = exi.extNozzle,
- pos = {x:blokpos.x + walkpos.x * i, y:blokpos.y + walkpos.y * i, z:0},
- rect = newPolygon().centerRectangle(pos, blok.x, blok.y),
- full = linesToPoly(POLY.fillArea([
- newPolygon().centerRectangle(pos, blok.x - noz, blok.y - noz)
- ], angle, noz)),
- sparse = rect.area() > 10 ? linesToPoly(POLY.fillArea([
- newPolygon().centerRectangle(pos, blok.x - noz, blok.y - noz)
- ], angle + 90, noz * thin)) : newPolygon(),
- rec = {
- extruder: i,
- diameter: exi.extNozzle,
- rect,
- full,
- sparse,
- pause: newPoint(pos.x + walkpos.y, pos.y + walkpos.x, pos.z)
- };
- return rec;
- }
-
- mkblok(blokw, blokh);
-
- // allocate tower space for extruders-1 locations
- let towers = extruders.slice(1).map((ext,i) => {
- return ext ? mkrec(i) : ext;
- });
-
- function linesToPoly(points) {
- let poly = newPolygon().setOpen();
- let ping = 0;
- for (let i=0; i= 0 || lastPurgeTool === nozzle;
- let tool = using >= 0 ? using : nozzle;
- let first = isBelt ? !purgedFirst && layer.slice.index >= 0 : layer.slice.index === 0;
- let rate = first ? process.firstLayerRate : process.outputFeedrate;
- let wipe = true;
- lastPurgeTool = tool;
- if (rec) {
- print.setType('purge tower');
- if (layer.last()) {
- layer.last().retract = true;
- }
- purgedFirst = purgedFirst || first;
- let purgeOn = first || !thin;
- if (isBelt && z < 0) {
- let scale = 1 - ((z / zmin));
- mkblok(blokw * scale, blokh);
- rec = mkrec(rec.extruder, 0, 10);
- rate = (process.outputFeedrate - process.firstLayerRate) * scale + process.firstLayerRate;
- wipe = false;
- }
- let box = rec.rect.clone().setZ(z);
- let pause = rec.pause.clone().setZ(z);
- let fill = (z >= 0 && purgeOn ? rec.full : rec.sparse).clone().setZ(z);
- if (isBelt) {
- let bmove = {x:0, y:z, z:0};
- box.move(bmove);
- pause.move(bmove);
- if (fill) {
- fill.move(bmove);
- }
- if (offset) {
- let bo = { x:0, y:offset.y, z:offset.z };
- box.move(bo);
- pause.move(bo);
- if (fill) fill.move(bo);
}
}
- start = print.polyPrintPath(box, start, layer, {
- tool,
- rate,
- simple: true,
- open: false,
- onfirstout: (out => out.overate = (isBelt ? rate : 0))
- });
- if (fill && fill.length) {
- // for pings, split path at 20mm
- start = print.polyPrintPath(fill, start, layer, {
- tool,
- rate,
- simple: true,
- open: true,
- onfirst: (point) => { point.purgeOn = purgeOn ? pause : undefined }
- });
- }
- layer.last().retract = true;
- // experimental post-retract wipe
- if (wipe) start = print.polyPrintPath(box, start, layer, {
- tool,
- rate,
- simple: true,
- open: false,
- extrude: 0
- });
- layer.last().overate = 0;
- return start;
- } else {
- console.log({no_purge_tower_for: nozzle, using, track, layer});
- return start;
+ );
+
+ lastOut = slice;
+ lastExt = lastOut.extruder;
+ lastPoly = slice.lastPoly;
+ lastLayer = layerout;
+
+ if (outputLayerRetract && layerout.length) {
+ layerout.last().retract = true;
}
}
- // establish offsets
- for (let widget of widgets) {
- let { rotinfo, belt } = widget;
- let offset = Object.clone(widget.track.pos);
- if (isBelt) {
- let o = rotinfo.ypos * beltfact;
- offset = {
- x: rotinfo.xpos,
- y: o,
- z: o
- };
- } else {
- // when rafts used this is non-zero
- offset.z = zoff;
- }
- widget.offset = offset;
- }
-
- // create shuffled slice cake by z offset (slice.z + offset.z)
- let cake = [];
- let zrec = {};
+ // clear slice.prep so it can be re-previewed in a different mode
for (let widget of widgets) {
// skip synthesized support widget(s)
if (!widget.mesh) {
continue;
}
for (let slice of widget.slices) {
- slice.widget = widget;
- let z = (slice.z + widget.offset.z).round(2);
- let rec = zrec[z] = zrec[z] || {z, slices:[]};
- if (rec.slices.length === 0) {
- cake.push(rec);
- }
- rec.slices.push(slice);
+ slice.prep = false;
}
}
- cake.sort((a, b) => {
- return a.z - b.z;
+
+ // draft shield
+ if (layerno > 0 && shield && outputDraftShield) {
+ print.setType('shield');
+ shield = POLY.setZ(shield.clone(), printPoint.z);
+ let preout = [];
+ printPoint = poly2polyEmit(shield, printPoint, (poly, index, count, startPoint) => {
+ return print.polyPrintPath(poly, startPoint, preout, {
+ onfirst: function(point) {
+ if (preout.length && point.distTo2D(startPoint) > 2) {
+ // retract between part and shield
+ preout.last().retract = true;
+ }
+ }
+ });
+ });
+ preout.last().retract = true;
+ layerout.appendAll(preout);
+ }
+
+ // if a declared extruder isn't used in a layer, use selected
+ // extruder to fill the relevant purge blocks for later support
+ track.slice().forEach(ext => {
+ printPoint = purge(ext.extruder, track, layerout, printPoint, lastOut.z, lastExt, lastOffset);
});
- let firstExt;
- let lastWidget;
- let lastExt;
- let lastOut;
+ // if layer produced output, append to output array
+ if (layerout.length) {
+ output.append(layerout);
+ }
- // walk cake layers bottom up
- for (let layer of cake) {
- // track purge blocks generated for each layer
- let track = towers.slice();
+ // retract after last layer
+ if (layerno === cake.length - 1 && layerout.length) {
+ layerout.last().retract = true;
+ }
- // iterate over layer slices, find closest widget, print, eliminate
- for (;;) {
- let order = [];
- // select slices of the same extruder type first then distance
- for (let slice of layer.slices) {
- if (slice.prep) {
- continue;
- }
- let offset = lastOffset = slice.widget.offset;
- let find = slice.findClosestPointTo(printPoint.sub(offset));
- if (find) {
- let ext = slice.extruder;
- let lex = lastExt;
- let dst = Math.abs(find.distance);
- // penalize extruder swaps
- if (ext !== lex) {
- dst *= 10000;
- }
- // for first object, penalize extruders other than first
- if (lastExt === undefined && ext > 0) {
- dst *= 10000;
- }
- order.push({dst, slice, offset, z: layer.z});
+ // notify progress
+ layerout.layer = layerno++;
+ update((layerno / cake.length) * 0.5, "prepare");
+
+ layerout = [];
+ }
+
+ print.output = output;
+
+ // post-process for base extrusions (touching the bed)
+ if (isBelt) {
+ // tune base threshold
+ let thresh = Infinity;
+ for (let layer of output) {
+ for (let rec of layer) {
+ let point = rec.point;
+ thresh = Math.min(thresh, point.z - point.y);
+ }
+ }
+ // store this offset to be removed from Y values in export
+ print.belty = thresh;
+ thresh = thresh + firstLayerHeight * 0.25;
+ // iterate over layers, find extrusion on belt and
+ // apply corrections and add brim when specified
+ for (let layer of output) {
+ let params = getRangeParameters(process, layer.layer || 0);
+ let brimHalf = params.firstLayerBrim < 0;
+ let firstLayerBrim = Math.abs(params.firstLayerBrim);
+ let firstLayerBrimIn = params.firstLayerBrimIn || 0;
+ let firstLayerBrimTrig = params.firstLayerBrimTrig || 0;
+ let firstLayerBrimGap = params.firstLayerBrimGap || 0;
+ let lastout, first = false;
+ let minz = Infinity, maxy = -Infinity, minx = Infinity, maxx = -Infinity;
+ let mins = Infinity;
+ let miny = Infinity;
+ let pads = [];
+ let overate = 0;
+
+ for (let rec of layer) {
+ overate = rec.overate >= 0 ? rec.overate : overate;
+ let brate = params.firstLayerRate || firstLayerRate;
+ let bmult = params.firstLayerPrintMult || params.firstLayerPrintMult;
+ let point = rec.point;
+ let belty = rec.belty = -point.y + point.z;
+ let lowrate = belty <= thresh ? brate : overate || brate;
+ miny = Math.min(miny, belty);
+ if (layer.anchor) {
+ // apply base rate to entire anchor (including bump)
+ rec.speed = Math.min(rec.speed, lowrate);
+ }
+ if (rec.emit && belty <= thresh && lastout && Math.abs(lastout.belty - belty) < 0.005) {
+ // apply base speed to segments touching belt
+ rec.speed = Math.min(rec.speed, lowrate);
+ rec.emit *= bmult;
+ minx = Math.min(minx, point.x, lastout.point.x);
+ maxx = Math.max(maxx, point.x, lastout.point.x);
+ maxy = Math.max(maxy, point.y);
+ minz = Math.min(minz, point.z);
+ first = rec;
+ // find length of shortest bed-facing segment
+ mins = Math.min(mins, lastout.point.distTo2D(rec.point));
+ // add to pads list if > 1mm long
+ if (point.x - lastout.point.x > 1) {
+ pads.push([lastout.point.x, point.x]);
}
}
- if (order.length === 0) {
+ lastout = rec;
+ }
+ // do not add brims to anchor layers
+ if (!first || layer.anchor) {
+ continue;
+ }
+ let tmpout = [];
+ let trigmet = firstLayerBrimTrig === 0 || (firstLayerBrimTrig && mins <= firstLayerBrimTrig);
+ let brimax = Math.max(firstLayerBrim, firstLayerBrimIn);
+ // add brim when all conditions met
+ if (brimax && trigmet) {
+ let { emit, tool } = first;
+ let y = maxy;
+ let z = minz;
+ let g = firstLayerBrimGap || 0;
+ let b = Math.max(firstLayerBrim, 1) + g;
+ let bi = Math.max(firstLayerBrimIn, 1) + g;
+ layer.last().retract = true;
+ // outside brim
+ if (firstLayerBrim && !brimHalf) {
+ print.addOutput(tmpout, newPoint(maxx + b, y, z), 0, outputSeekrate, tool);
+ print.addOutput(tmpout, newPoint(maxx + g, y, z), emit, firstLayerRate, tool).retract = true;
+ }
+ // inside brim
+ if (firstLayerBrimIn && pads.length > 1) {
+ let gaps = [];
+ let lpad = pads[0];
+ for (let pad of pads.slice(1)) {
+ let x0 = lpad[1];
+ let x1 = pad[0];
+ lpad = pad;
+ if (x1 - x0 > bi * 2) {
+ // over 2x brim so emit two segments
+ print.addOutput(tmpout, newPoint(x1 - g, y, z), 0, outputSeekrate, tool);
+ print.addOutput(tmpout, newPoint(x1 - bi, y, z), emit, firstLayerRate, tool).retract = true;
+ print.addOutput(tmpout, newPoint(x0 + bi, y, z), 0, outputSeekrate, tool);
+ print.addOutput(tmpout, newPoint(x0 + g, y, z), emit, firstLayerRate, tool).retract = true;
+ } else if (x1 - x0 > bi / 3) {
+ // over 1/3rd brim length emit single segment
+ print.addOutput(tmpout, newPoint(x1 - g, y, z), 0, outputSeekrate, tool);
+ print.addOutput(tmpout, newPoint(x0 + g, y, z), emit, firstLayerRate, tool).retract = true;
+ }
+ }
+ }
+ // outside brim
+ if (firstLayerBrim) {
+ print.addOutput(tmpout, newPoint(minx - b, y, z), 0, outputSeekrate, tool);
+ print.addOutput(tmpout, newPoint(minx - g, y, z), emit, firstLayerRate, tool).retract = false;
+ }
+ } else {
+ // for any layer touching belt, ensure start point is nearest origin
+ // print.addOutput(tmpout, newPoint(minx, maxy, minz), 0, outputSeekrate, first.tool);
+ // print.lastPoint = newPoint(minx, maxy, minz);
+ }
+ layer.splice(0,0,...tmpout);
+ }
+ }
+
+ // render if not explicitly disabled
+ if (settings.render !== false) {
+ print.render = FDM.prepareRender(output, (progress, layer) => {
+ update(0.5 + progress * 0.5, "render", layer);
+ }, {
+ toolMode: settings.pmode === 2,
+ tools: device.extruders,
+ thin: isThin,
+ flat: isFlat,
+ fdm: true
+ });
+ }
+
+ return print.render;
+};
+
+// fdm only
+function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
+ const { settings } = print;
+ const { device } = settings;
+
+ let preout = [],
+ process = opt.params || settings.process,
+ extruder = slice.extruder || 0,
+ nozzleSize = device.extruders[extruder].extNozzle,
+ firstLayer = opt.first || false,
+ thinWall = nozzleSize * (opt.thinWall || 1.75),
+ retractDist = opt.retractOver || 2,
+ solidWidth = process.sliceFillWidth || 1,
+ fillMult = opt.mult || process.outputFillMult,
+ shellMult = opt.mult || process.outputShellMult || (process.laserSliceHeight >= 0 ? 1 : 0),
+ shellOrder = {"out-in":-1,"in-out":1}[process.sliceShellOrder] || -1,
+ sparseMult = process.outputSparseMult,
+ coastDist = process.outputCoastDist || 0,
+ finishSpeed = opt.speed || process.outputFinishrate,
+ firstShellSpeed = process.firstLayerRate,
+ firstFillSpeed = process.firstLayerFillRate,
+ firstPrintMult = process.firstLayerPrintMult,
+ printSpeed = opt.speed || (firstLayer ? firstShellSpeed : process.outputFeedrate),
+ fillSpeed = opt.speed || opt.fillSpeed || (firstLayer ? firstFillSpeed || firstShellSpeed : process.outputFeedrate),
+ infillSpeed = process.sliceFillRate || opt.infillSpeed || fillSpeed || printSpeed,
+ moveSpeed = process.outputSeekrate,
+ origin = startPoint.add(offset),
+ zhop = process.zHopDistance || 0,
+ antiBacklash = process.antiBacklash,
+ wipeDist = process.outputRetractWipe || 0,
+ isBelt = device.bedBelt,
+ beltFirst = process.outputBeltFirst || false,
+ startClone = startPoint.clone(),
+ seedPoint = opt.seedPoint || startPoint,
+ z = slice.z,
+ lastPoly;
+
+ // apply first layer extrusion multipliers
+ if (firstLayer) {
+ fillMult *= firstPrintMult;
+ shellMult *= firstPrintMult;
+ sparseMult *= firstPrintMult;
+ }
+
+ function retract() {
+ let array = preout.length ? preout : output;
+ if (array.length) {
+ let last = array.last();
+ last.retract = true;
+ if (wipeDist && lastPoly && last.point) {
+ let endpoint = last.point.followTo(lastPoly.center(true), wipeDist);
+ if (endpoint.inPolygon(lastPoly)) {
+ print.addOutput(array, endpoint);
+ }
+ }
+ } else if (opt.pretract) {
+ opt.pretract(wipeDist);
+ } else {
+ console.log('unable to retract. no preout or output');
+ }
+ }
+
+ function intersectsTop(p1, p2) {
+ if (slice.index < 0) {
+ return false;
+ }
+ let int = false;
+ slice.topPolysFlat().forEach((poly) => {
+ if (!int) poly.forEachSegment((s1, s2) => {
+ if (util.intersect(p1,p2,s1,s2,base.key.SEGINT)) {
+ return int = true;
+ }
+ });
+ });
+ // if intersecting, look for a route around
+ if (int && opt.routeAround) {
+ return !routeAround(p1, p2);
+ }
+ return int;
+ }
+
+ // returns true if routed around or no retract requried
+ function routeAround(p1, p2) {
+ const dbug = false;
+ if (dbug === slice.index) console.log(slice.index, {p1, p2, d: p1.distTo2D(p2)});
+
+ let ints = [];
+ let tops = slice.topRouteFlat();
+ for (let poly of tops) {
+ poly.forEachSegment((s1, s2) => {
+ let ip = util.intersect(p1,p2,s1,s2,base.key.SEGINT);
+ if (ip) {
+ ints.push({ip, poly});
+ }
+ });
+ }
+
+ // no intersections
+ if (ints.length === 0) {
+ if (dbug === slice.index) console.log(slice.index, 'no ints');
+ return false;
+ }
+
+ // odd # of intersections ?!? do retraction
+ if (ints.length && ints.length % 2 !== 0) {
+ if (dbug === slice.index) console.log(slice.index, {odd_intersects: ints});
+ return false;
+ }
+
+ // sort by distance
+ ints.sort((a, b) => {
+ return a.ip.dist - b.ip.dist;
+ });
+
+ if (dbug === slice.index) console.log(slice.index, {ints});
+
+ // check pairs. eliminate too close points.
+ // pairs must intersect same poly or retract.
+ for (let i=0; i i);
+
+ if (ints.length > 2) {
+ if (dbug === slice.index) console.log(slice.index, {complex_route: ints.length});
+ return false;
+ }
+
+ if (ints.length === 2) {
+ // can route around intersected top polys
+ for (let i=0; ii.ip.dist),
+ i1, i2, same: i1.poly === i2.poly,
+ route,
+ p1, p2, dist: p1.distTo2D(p2),
+ r1, r1d, r1s, r1e,
+ r2, r2d, r2s, r2e,
+ isCW});
+
+ for (let p of route) {
+ print.addOutput(preout, p, 0, moveSpeed, extruder);
+ }
+
+ // output last point
+ print.addOutput(preout, i2.ip, 0, moveSpeed, extruder);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ function outputTraces(poly, opt = {}) {
+ if (!poly) return;
+ if (Array.isArray(poly)) {
+ if (opt.sort) {
+ let polys = poly.slice().sort((a,b) => {
+ return (a.perimeter() - b.perimeter()) * opt.sort;
+ });
+ let debug = polys.length > 3;
+ let last;
+ while (polys.length) {
+ let next;
+ for (let p of polys) {
+ if (!last) {
+ next = p;
+ break;
+ }
+ if (opt.sort > 0) {
+ // in-out
+ if (last.isInside(p)) {
+ next = p;
+ break;
+ }
+ } else {
+ // out-in
+ if (p.isInside(last)) {
+ next = p;
+ break;
+ }
+ }
+ }
+ if (next) {
+ last = next;
+ polys.remove(next);
+ outputTraces(next, opt);
+ } else {
+ last = null;
+ }
+ }
+ } else {
+ outputOrderClosest(poly, function(next) {
+ outputTraces(next, opt);
+ }, null);
+ }
+ } else {
+ let finishShell = poly.depth === 0 && !firstLayer;
+ startPoint = print.polyPrintPath(poly, startPoint, preout, {
+ tool: extruder,
+ rate: finishShell ? finishSpeed : printSpeed,
+ accel: finishShell,
+ wipe: process.outputWipeDistance || 0,
+ coast: firstLayer ? 0 : coastDist,
+ extrude: numOrDefault(opt.extrude, shellMult),
+ onfirst: function(firstPoint) {
+ let from = seedPoint || startPoint;
+ if (from.distTo2D(firstPoint) > retractDist) {
+ if (intersectsTop(from, firstPoint)) {
+ retract();
+ }
+ }
+ seedPoint = null;
+ }
+ });
+ lastPoly = slice.lastPoly = poly;
+ }
+ }
+
+ /**
+ * @param {Polygon[]} polys
+ */
+ function outputSparse(polys, extrude, speed) {
+ if (!polys) return;
+ let proxy = polys.map((poly) => {
+ return {poly: poly, first: poly.first(), last: poly.last()};
+ });
+ let lp = startPoint;
+ startPoint = tip2tipEmit(proxy, startPoint, (el, point, count) => {
+ let poly = el.poly;
+ if (poly.last() === point) {
+ poly.reverse();
+ }
+ poly.forEachPoint((p, i) => {
+ let dist = lp.distTo2D(p);
+ let rdst = dist > retractDist;
+ let itop = rdst && intersectsTop(lp,p);
+ let emit = extrude;
+ // retract if dist trigger and crosses a slice top polygon
+ if (i === 0) {
+ if (itop) {
+ retract();
+ emit = 0;
+ } else if (dist > nozzleSize) {
+ emit = 0;
+ }
+ }
+ // let emit = i === 0 ? 0 : extrude;
+ // handle shallow cloned infill
+ if (poly.z !== undefined) {
+ p = p.clone().setZ(poly.z);
+ }
+ print.addOutput(preout, p, emit, speed || printSpeed, extruder);
+ lp = p;
+ }, !poly.open);
+ return lp;
+ });
+ }
+
+ function outputThin(lines) {
+ if (!lines) {
+ return;
+ }
+ let points = lines.group(2).map(grp => {
+ let [ p1, p2 ] = grp;
+ return newPoint(
+ (p1.x + p2.x) / 2,
+ (p1.y + p2.y) / 2,
+ (p1.z + p2.z) / 2
+ )
+ });
+ let order = util.orderClosest(points, (p1, p2) => p1.distTo2D(p2));
+ if (order.length === 0) {
+ return;
+ }
+ let first = points[0];
+ let last = first;
+ print.addOutput(preout, first, 0, moveSpeed, extruder);
+ for (let p of order) {
+ let dist = last ? last.distTo2D(p) : 0;
+ if (dist > thinWall) {
+ retract();
+ print.addOutput(preout, p, 0, moveSpeed, extruder);
+ }
+ print.addOutput(preout, p, 1, fillSpeed, extruder);
+ last = p;
+ }
+ // close a circle
+ if (last && last.distTo2D(first) <= thinWall) {
+ print.addOutput(preout, first, 1, fillSpeed, extruder);
+ }
+ }
+
+ function outputFills(lines, opt = {}) {
+ if (!lines || lines.length === 0) {
+ return;
+ }
+ let p, p1, p2, dist, len, found, group, mindist, t1, t2,
+ marked = 0,
+ start = 0,
+ skip = false,
+ lastIndex = -1,
+ flow = opt.flow || 1,
+ near = opt.near || false,
+ fast = opt.fast || false,
+ fill = (opt.fill >= 0 ? opt.fill : fillMult) * flow,
+ thinDist = near ? thinWall : thinWall;
+
+ while (lines && marked < lines.length) {
+ group = null;
+ found = false;
+ mindist = Infinity;
+
+ // use next nearest line strategy
+ if (near)
+ for (i=0; i lastIndex) {
+ group = p.index;
+ }
+ if (group !== null) {
+ if (p.index !== group) {
+ break;
+ }
+ if (p.index % 2 === 0) {
+ t1 = lines[i];
+ t2 = lines[i+1];
+ } else {
+ t2 = lines[i];
+ t1 = lines[i+1];
+ }
+ dist = Math.min(t1.distTo2D(startPoint), t2.distTo2D(startPoint));
+ if (dist < mindist) {
+ p1 = t1;
+ p2 = t2;
+ mindist = dist;
+ }
+ start = i;
+ found = true;
+ }
+ }
+
+ // go back to start and try again
+ if (!near && !found) {
+ if (start === 0 && lastIndex === -1) {
+ console.log('infinite loop', lines, {
+ marked, i, group, start, lastIndex,
+ points: lines.map(p => p.index).join(', ')
+ });
break;
}
- order.sort((a,b) => {
- return a.dst - b.dst;
- });
- let { z, slice, offset } = order[0];
- if (firstExt === undefined) {
- firstExt = slice.extruder;
- }
-
- // when layers switch between widgets, force retraction
- let forceRetract = lastOut && lastOut.widget !== slice.widget;
- if (forceRetract && output.length) {
- // selecet last output of last layer
- output.last().last().retract = true;
- }
-
- let params = getRangeParameters(process, slice.index);
- slice.prep = true;
- // retract between widgets or layers (when set)
- if (layerout.length && slice.widget !== lastWidget) {
- layerout.last().retract = true;
- }
- lastWidget = slice.widget;
- layerout.z = z + slice.height / 2;
- layerout.height = layerout.height || slice.height;
- layerout.slice = slice;
- // mark layer as anchor if slice is belt and flag set
- layerout.anchor = slice.belt && slice.belt.anchor;
- // detect extruder change and print purge block
- if (!lastOut || lastOut.extruder !== slice.extruder) {
- if (slice.extruder >= 0)
- printPoint = purge(slice.extruder, track, layerout, printPoint, slice.z, undefined, offset);
- }
- let wtb = slice.widget.track.box;
- let beltStart = slice.belt && slice.belt.touch;// && (widgets.length === 1);
- // output seek to start point between mesh slices if previous data
- print.setType('layer');
- printPoint = print.slicePrintPath(
- slice,
- beltStart ? newPoint(-5000, 5000, 0) : printPoint.sub(offset),
- offset,
- layerout,
- {
- routeAround: process.outputAvoidGaps,
- seedPoint: printPoint.sub(offset),
- danger: isDanger,
- params, // range parameters
- first: slice.index === 0,
- support: slice.widget.support,
- onBelt: beltStart,
- pretract: (wipeDist) => {
- if (lastLayer && lastLayer.length) {
- let lastOut = lastLayer.last();
- lastOut.retract = true;
- if (wipeDist && lastPoly && lastOut.point) {
- let endpoint = lastOut.point.followTo(lastPoly.center(true).add(offset), wipeDist);
- if (endpoint.inPolygon(lastPoly)) {
- print.addOutput(lastLayer, endpoint);
- }
- }
- }
- }
- }
- );
-
- lastOut = slice;
- lastExt = lastOut.extruder;
- lastPoly = slice.lastPoly;
- lastLayer = layerout;
-
- if (layerRetract && layerout.length) {
- layerout.last().retract = true;
- }
+ start = 0;
+ lastIndex = -1;
+ continue;
}
- // clear slice.prep so it can be re-previewed in a different mode
- for (let widget of widgets) {
- // skip synthesized support widget(s)
- if (!widget.mesh) {
- continue;
- }
- for (let slice of widget.slices) {
- slice.prep = false;
- }
+ dist = startPoint.distToSq2D(p1);
+ len = p1.distToSq2D(p2);
+
+ // go back to start when dist > retractDist
+ if (!near && !fast && !skip && dist > retractDist) {
+ skip = true;
+ start = 0;
+ lastIndex = -1;
+ continue;
}
+ skip = false;
- // draft shield
- if (layerno > 0 && shield && draftShield) {
- print.setType('shield');
- shield = POLY.setZ(shield.clone(), printPoint.z);
- let preout = [];
- printPoint = poly2polyEmit(shield, printPoint, (poly, index, count, startPoint) => {
- return print.polyPrintPath(poly, startPoint, preout, {
- onfirst: function(point) {
- if (preout.length && point.distTo2D(startPoint) > 2) {
- // retract between part and shield
- preout.last().retract = true;
- }
- }
- });
- });
- preout.last().retract = true;
- layerout.appendAll(preout);
- }
+ // mark as used (temporarily)
+ p1.del = true;
+ p2.del = true;
+ marked += 2;
+ lastIndex = p1.index;
- // if a declared extruder isn't used in a layer, use selected
- // extruder to fill the relevant purge blocks for later support
- track.slice().forEach(ext => {
- printPoint = purge(ext.extruder, track, layerout, printPoint, lastOut.z, lastExt, lastOffset);
- });
-
- // if layer produced output, append to output array
- if (layerout.length) {
- output.append(layerout);
- }
-
- // retract after last layer
- if (layerno === cake.length - 1 && layerout.length) {
- layerout.last().retract = true;
- }
-
- // notify progress
- layerout.layer = layerno++;
- update((layerno / cake.length) * 0.5, "prepare");
-
- layerout = [];
- }
-
- print.output = output;
-
- // post-process for base extrusions (touching the bed)
- if (isBelt) {
- // tune base threshold
- let thresh = Infinity;
- for (let layer of output) {
- for (let rec of layer) {
- let point = rec.point;
- thresh = Math.min(thresh, point.z - point.y);
+ // if dist to new segment is less than thinWall
+ // and segment length is less than thinWall then
+ // just extrude to midpoint of next segment. this is
+ // to avoid shaking the printer to death.
+ if (dist <= thinDist && len <= thinDist) {
+ p2 = p1.midPointTo(p2);
+ // this.addOutput(preout, p2, fill * (dist / thinWall), fillSpeed, extruder);
+ print.addOutput(preout, p2, fill, fillSpeed, extruder);
+ } else {
+ // retract if dist trigger or crosses a slice top polygon
+ if (!fast && dist > retractDist && (zhop || intersectsTop(startPoint, p1))) {
+ retract();
}
- }
- // store this offset to be removed from Y values in export
- print.belty = thresh;
- thresh = thresh + firstLayerHeight * 0.25;
- // iterate over layers, find extrusion on belt and
- // apply corrections and add brim when specified
- for (let layer of output) {
- let params = getRangeParameters(process, layer.layer || 0);
- let brimHalf = params.firstLayerBrim < 0;
- let firstLayerBrim = Math.abs(params.firstLayerBrim);
- let firstLayerBrimIn = params.firstLayerBrimIn || 0;
- let firstLayerBrimTrig = params.firstLayerBrimTrig || 0;
- let firstLayerBrimGap = params.firstLayerBrimGap || 0;
- let lastout, first = false;
- let minz = Infinity, maxy = -Infinity, minx = Infinity, maxx = -Infinity;
- let mins = Infinity;
- let miny = Infinity;
- let pads = [];
- let overate = 0;
- for (let rec of layer) {
- overate = rec.overate >= 0 ? rec.overate : overate;
- let brate = params.firstLayerRate || firstLayerRate;
- let bmult = params.firstLayerPrintMult || firstLayerMult;
- let point = rec.point;
- let belty = rec.belty = -point.y + point.z;
- let lowrate = belty <= thresh ? brate : overate || brate;
- miny = Math.min(miny, belty);
- if (layer.anchor) {
- // apply base rate to entire anchor (including bump)
- rec.speed = Math.min(rec.speed, lowrate);
- }
- if (rec.emit && belty <= thresh && lastout && Math.abs(lastout.belty - belty) < 0.005) {
- // apply base speed to segments touching belt
- rec.speed = Math.min(rec.speed, lowrate);
- rec.emit *= bmult;
- minx = Math.min(minx, point.x, lastout.point.x);
- maxx = Math.max(maxx, point.x, lastout.point.x);
- maxy = Math.max(maxy, point.y);
- minz = Math.min(minz, point.z);
- first = rec;
- // find length of shortest bed-facing segment
- mins = Math.min(mins, lastout.point.distTo2D(rec.point));
- // add to pads list if > 1mm long
- if (point.x - lastout.point.x > 1) {
- pads.push([lastout.point.x, point.x]);
- }
- }
- lastout = rec;
+ // anti-backlash on longer move
+ if (!fast && antiBacklash && dist > retractDist) {
+ print.addOutput(preout, p1.add({x:antiBacklash,y:-antiBacklash,z:0}), 0, moveSpeed, extruder);
}
- // do not add brims to anchor layers
- if (!first || layer.anchor) {
- continue;
- }
- let tmpout = [];
- let trigmet = firstLayerBrimTrig === 0 || (firstLayerBrimTrig && mins <= firstLayerBrimTrig);
- let brimax = Math.max(firstLayerBrim, firstLayerBrimIn);
- // add brim when all conditions met
- if (brimax && trigmet) {
- let { emit, tool } = first;
- let y = maxy;
- let z = minz;
- let g = firstLayerBrimGap || 0;
- let b = Math.max(firstLayerBrim, 1) + g;
- let bi = Math.max(firstLayerBrimIn, 1) + g;
- layer.last().retract = true;
- // outside brim
- if (firstLayerBrim && !brimHalf) {
- print.addOutput(tmpout, newPoint(maxx + b, y, z), 0, firstLayerSeek, tool);
- print.addOutput(tmpout, newPoint(maxx + g, y, z), emit, firstLayerRate, tool).retract = true;
- }
- // inside brim
- if (firstLayerBrimIn && pads.length > 1) {
- let gaps = [];
- let lpad = pads[0];
- for (let pad of pads.slice(1)) {
- let x0 = lpad[1];
- let x1 = pad[0];
- lpad = pad;
- if (x1 - x0 > bi * 2) {
- // over 2x brim so emit two segments
- print.addOutput(tmpout, newPoint(x1 - g, y, z), 0, firstLayerSeek, tool);
- print.addOutput(tmpout, newPoint(x1 - bi, y, z), emit, firstLayerRate, tool).retract = true;
- print.addOutput(tmpout, newPoint(x0 + bi, y, z), 0, firstLayerSeek, tool);
- print.addOutput(tmpout, newPoint(x0 + g, y, z), emit, firstLayerRate, tool).retract = true;
- } else if (x1 - x0 > bi / 3) {
- // over 1/3rd brim length emit single segment
- print.addOutput(tmpout, newPoint(x1 - g, y, z), 0, firstLayerSeek, tool);
- print.addOutput(tmpout, newPoint(x0 + g, y, z), emit, firstLayerRate, tool).retract = true;
- }
- }
- }
- // outside brim
- if (firstLayerBrim) {
- print.addOutput(tmpout, newPoint(minx - b, y, z), 0, firstLayerSeek, tool);
- print.addOutput(tmpout, newPoint(minx - g, y, z), emit, firstLayerRate, tool).retract = false;
- }
+
+ // bridge ends of fill when they're close together
+ if (dist < thinDist) {
+ print.addOutput(preout, p1, fill, fillSpeed, extruder);
} else {
- // for any layer touching belt, ensure start point is nearest origin
- // print.addOutput(tmpout, newPoint(minx, maxy, minz), 0, firstLayerSeek, first.tool);
- // print.lastPoint = newPoint(minx, maxy, minz);
+ print.addOutput(preout, p1, 0, moveSpeed, extruder);
}
- layer.splice(0,0,...tmpout);
+
+ print.addOutput(preout, p2, fill, fillSpeed, extruder);
}
+
+ startPoint = p2;
}
- // render if not explicitly disabled
- if (render) {
- print.render = FDM.prepareRender(output, (progress, layer) => {
- update(0.5 + progress * 0.5, "render", layer);
- }, {
- toolMode: settings.pmode === 2,
- tools: device.extruders,
- thin: isThin,
- flat: isFlat,
- fdm: true
+ // clear delete marks so we can re-print later
+ if (lines) lines.forEach(p => { p.del = false });
+ }
+
+ /**
+ * given array of polygons, emit them in next closest order with
+ * the special exception that depth is considered into distance
+ * so that inner polygons are emitted first.
+ *
+ * @param {Array} array of Polygons or Polygon wrappers (tops)
+ * @param {Function} fn call to emit next candidate
+ * @param {Function} fnp convert 'next' object into a Polygon for closeness
+ */
+ function outputOrderClosest(array, fn, fnp) {
+ if (array.length === 1) {
+ return fn(array[0]);
+ }
+ array = array.slice();
+ let closest, find, next, order, poly, lastDepth = 0;
+ for (;;) {
+ order = [];
+ closest = null;
+ for (let i=0; i {
+ return a.d - b.d;
});
- }
-
- return print.render;
- };
-
- class Counter {
- constructor() {
- this.map = {};
- this.total = 0;
- }
- put(key) {
- const map = this.map;
- const kp = key || 'bad';
- map[kp] = (map[kp] || 0) + 1;
- this.total++;
- }
- get() {
- return { map: this.map, total: this.total };
+ array[order[0].i] = null;
+ fn(order[0].n);
}
}
- FDM.isDark = function() {
- return current.print.settings.controller.dark ? true : false;
+ let out = [];
+ if (slice.tops) {
+ out.appendAll(slice.tops);
};
+ if (opt.support && slice.supports) {
+ out.appendAll(slice.supports);
+ }
- FDM.rateToColor = function(rate, max) {
- return FDM.isDark() ?
- darkColorFunction(rate/max, 1, 0.85) :
- currentColorFunction(rate/max, 1, 0.85);
- };
+ let lastTop = null;
+ outputOrderClosest(out, function(next) {
+ if (next instanceof Polygon) {
+ print.setType('support');
+ // support polygon
+ next.setZ(z);
+ outputTraces([next].appendAll(next.inner || []));
+ if (next.fill) {
+ next.fill.forEach(p => { p.z = z });
+ outputFills(next.fill, {fast: true});
+ }
+ } else {
+ print.setType('shells');
+ if (lastTop && lastTop !== next) {
+ retract();
+ }
- FDM.prepareRender = function(levels, update, opts = {}) {
- levels = levels.filter(level => level.length);
- if (levels.length === 0) {
- self.worker.print.maxSpeed = 0;
- return [];
- }
+ // control of layer start point
+ switch (process.sliceLayerStart) {
+ case "center":
+ startPoint = newPoint(0,0,startPoint.z);
+ break;
+ case "origin":
+ startPoint = origin.clone();
+ break;
+ }
- const dark = FDM.isDark();
- const tools = opts.tools || {};
- const flat = opts.flat;
- const thin = opts.thin && !flat;
- const ckspeed = opts.speed !== false;
- const headColor = 0x888888;
- const moveColor = opts.move >= 0 ? opts.move : 0xaaaaaa;
- const printColor = opts.print >= 0 ? opts.print : 0x777700;
- const arrowAll = true;
- const arrowSize = arrowAll ? 0.2 : 0.4;
- const layers = [];
- const toolMode = opts.toolMode;
-
- const moveOpt = {
- face: moveColor,
- line: flat ? 1 : moveColor,
- opacity: flat ? 0.5 : 1
- };
- const printOpt = {
- face: printColor,
- line: flat ? 1 : printColor,
- opacity: flat ? 0.5 : 1
- };
-
- let minspd = Infinity;
- let maxspd = opts.maxspeed || 0;
- let maxtool = [];
-
- for (let level of levels) {
- for (let o of level) {
- if (o.speed) {
- minspd = Math.min(minspd, o.speed);
- maxspd = Math.max(maxspd, o.speed);
+ // optimize start point on belt for tops touching belt
+ // and enforce optimal shell order (outer first)
+ if (isBelt && opt.onBelt) {
+ startPoint = startClone;
+ if (beltFirst) {
+ shellOrder = -1;
}
- if (toolMode && o.tool !== undefined) {
- if (maxtool.indexOf(o.tool) < 0) {
- maxtool.push(o.tool);
- }
+ }
+
+ // innermost shells
+ let inner = next.innerShells() || [];
+
+ // output inner polygons
+ if (shellOrder === 1) outputTraces(inner, { sort: shellOrder });
+
+ outputTraces(next.shells, { sort: shellOrder });
+
+ // output outer polygons
+ if (shellOrder === -1) outputTraces(inner, { sort: shellOrder });
+
+ // output thin fill
+ print.setType('thin fill');
+ outputThin(next.thin_fill);
+
+ // then output solid and sparse fill
+ print.setType('solid fill');
+ outputFills(next.fill_lines, {flow: solidWidth});
+
+ print.setType('sparse infill');
+ outputSparse(next.fill_sparse, sparseMult, infillSpeed);
+
+ lastTop = next;
+ }
+ }, function(obj) {
+ // for tops
+ return obj instanceof Polygon ? obj : obj.poly;
+ });
+
+ // produce polishing paths when present
+ if (slice.tops.length && slice.tops[0].polish) {
+ let {x,y} = slice.tops[0].polish;
+ if (x) {
+ outputSparse(x, 0, process.polishSpeed);
+ }
+ if (y) {
+ outputSparse(y, 0, process.polishSpeed);
+ }
+ }
+
+ // offset print points
+ for (let i=0; i level.length);
+ if (levels.length === 0) {
+ self.worker.print.maxSpeed = 0;
+ return [];
+ }
+
+ const dark = FDM.isDark();
+ const tools = opts.tools || {};
+ const flat = opts.flat;
+ const thin = opts.thin && !flat;
+ const ckspeed = opts.speed !== false;
+ const headColor = 0x888888;
+ const moveColor = opts.move >= 0 ? opts.move : 0xaaaaaa;
+ const printColor = opts.print >= 0 ? opts.print : 0x777700;
+ const arrowAll = true;
+ const arrowSize = arrowAll ? 0.2 : 0.4;
+ const layers = [];
+ const toolMode = opts.toolMode;
+
+ const moveOpt = {
+ face: moveColor,
+ line: flat ? 1 : moveColor,
+ opacity: flat ? 0.5 : 1
+ };
+ const printOpt = {
+ face: printColor,
+ line: flat ? 1 : printColor,
+ opacity: flat ? 0.5 : 1
+ };
+
+ let minspd = Infinity;
+ let maxspd = opts.maxspeed || 0;
+ let maxtool = [];
+
+ for (let level of levels) {
+ for (let o of level) {
+ if (o.speed) {
+ minspd = Math.min(minspd, o.speed);
+ maxspd = Math.max(maxspd, o.speed);
+ }
+ if (toolMode && o.tool !== undefined) {
+ if (maxtool.indexOf(o.tool) < 0) {
+ maxtool.push(o.tool);
}
}
}
+ }
- // const maxspd = levels.map(level => {
- // return level.map(o => o.speed || 0).reduce((a, v) => Math.max(a,v));
- // }).reduce((a, v) => Math.max(a, v)) + 1;
+ // const maxspd = levels.map(level => {
+ // return level.map(o => o.speed || 0).reduce((a, v) => Math.max(a,v));
+ // }).reduce((a, v) => Math.max(a, v)) + 1;
- // for reporting
- self.worker.print.minSpeed = minspd;
- self.worker.print.maxSpeed = maxspd;
- self.worker.print.thinColor = thin;
- self.worker.print.flatColor = flat;
+ // for reporting
+ self.worker.print.minSpeed = minspd;
+ self.worker.print.maxSpeed = maxspd;
+ self.worker.print.thinColor = thin;
+ self.worker.print.flatColor = flat;
- let lastTool = null;
- let lastEnd = null;
- let lastOut = null;
- let current = null;
- let retracted = false;
- let retractz = 0;
+ let lastTool = null;
+ let lastEnd = null;
+ let lastOut = null;
+ let current = null;
+ let retracted = false;
+ let retractz = 0;
- function color(point) {
- if (toolMode) {
- return FDM.rateToColor(maxtool.indexOf(point.tool), maxtool.length);
- } else {
- return FDM.rateToColor(point.speed, maxspd);
- }
+ function color(point) {
+ if (toolMode) {
+ return FDM.rateToColor(maxtool.indexOf(point.tool), maxtool.length);
+ } else {
+ return FDM.rateToColor(point.speed, maxspd);
}
+ }
- levels.forEach((level, index) => {
- const prints = {};
- const moves = [];
- const heads = [];
- const changes = [];
- const retracts = [];
- const engages = [];
- const output = new KIRI.Layers();
- layers.push(output);
+ levels.forEach((level, index) => {
+ const prints = {};
+ const moves = [];
+ const heads = [];
+ const changes = [];
+ const retracts = [];
+ const engages = [];
+ const output = new kiri.Layers();
+ layers.push(output);
- const pushPrint = (toolid, poly) => {
- toolid = toolid || 0;
- const array = prints[toolid] = prints[toolid] || [];
- const tool = tools[toolid] || {};
- array.width = (tool.extNozzle || 1) / 2;
- array.push(poly);
- emits++;
- };
+ const pushPrint = (toolid, poly) => {
+ toolid = toolid || 0;
+ const array = prints[toolid] = prints[toolid] || [];
+ const tool = tools[toolid] || {};
+ array.width = (tool.extNozzle || 1) / 2;
+ array.push(poly);
+ emits++;
+ };
- let height = level.height / 2;
- let width = 1;
- let emits = 0;
+ let height = level.height / 2;
+ let width = 1;
+ let emits = 0;
- level.forEach((out,oi) => {
- if (retracted && out.emit) {
- retracted = false;
- engages.push(lastOut.point);
+ level.forEach((out,oi) => {
+ if (retracted && out.emit) {
+ retracted = false;
+ engages.push(lastOut.point);
+ }
+ if (out.tool !== lastTool) {
+ lastTool = out.tool;
+ changes.push(out.point);
+ }
+ if (out.retract) {
+ retracts.push(out.point);
+ retracted = true;
+ retractz++;
+ }
+ if (!out.point) {
+ // in cam mode, these are drilling or dwell ops
+ return;
+ }
+
+ if (lastOut) {
+ if (arrowAll || lastOut.emit !== out.emit) {
+ heads.push({p1: lastOut.point, p2: out.point});
}
- if (out.tool !== lastTool) {
- lastTool = out.tool;
- changes.push(out.point);
- }
- if (out.retract) {
- retracts.push(out.point);
- retracted = true;
- retractz++;
- }
- if (!out.point) {
- // in cam mode, these are drilling or dwell ops
- return;
- }
-
- if (lastOut) {
- if (arrowAll || lastOut.emit !== out.emit) {
- heads.push({p1: lastOut.point, p2: out.point});
- }
- const op = out.point, lp = lastOut.point;
- // const moved = Math.max(
- // Math.abs(op.x - lp.x),
- // Math.abs(op.y - lp.y),
- // Math.abs(op.z - lp.z));
- // if (moved < 0.0001) return;
- if (out.emit) {
- if (!lastOut.emit || (ckspeed && out.speed !== lastOut.speed) || lastEnd) {
- current = newPolygon().setOpen();
- current.push(lastOut.point);
- current.color = color(out);
- pushPrint(out.tool, current);
- }
- current.push(out.point);
- } else {
- if (lastOut.emit || lastEnd) {
- current = newPolygon().setOpen();
- current.push(lastOut.point);
- moves.push(current);
- }
- current.push(out.point);
- }
- lastEnd = null;
- } else {
- current = newPolygon().setOpen();
- current.push(out.point);
- if (out.emit) {
+ const op = out.point, lp = lastOut.point;
+ // const moved = Math.max(
+ // Math.abs(op.x - lp.x),
+ // Math.abs(op.y - lp.y),
+ // Math.abs(op.z - lp.z));
+ // if (moved < 0.0001) return;
+ if (out.emit) {
+ if (!lastOut.emit || (ckspeed && out.speed !== lastOut.speed) || lastEnd) {
+ current = newPolygon().setOpen();
+ current.push(lastOut.point);
current.color = color(out);
pushPrint(out.tool, current);
- } else {
+ }
+ current.push(out.point);
+ } else {
+ if (lastOut.emit || lastEnd) {
+ current = newPolygon().setOpen();
+ current.push(lastOut.point);
moves.push(current);
}
+ current.push(out.point);
+ }
+ lastEnd = null;
+ } else {
+ current = newPolygon().setOpen();
+ current.push(out.point);
+ if (out.emit) {
+ current.color = color(out);
+ pushPrint(out.tool, current);
+ } else {
+ moves.push(current);
}
- lastOut = out;
- });
- // all moves with an emit at the very end (common in contouring)
- if (lastOut.emit && !emits) {
- pushPrint(lastOut.tool, current)
}
- lastEnd = lastOut;
- if (changes.length) {
- output
- .setLayer('tool', { line: 0x000055, face: 0x0000ff, opacity: 0.5 }, true)
- .addAreas(changes.map(point => {
- return newPolygon().centerCircle(point, 0.2, 4).setZ(point.z + 0.03);
- }), { outline: true });
- }
- if (retracts.length) {
- output
- .setLayer('retract', { line: 0x550000, face: 0xff0000, opacity: 0.5 }, true)
- .addAreas(retracts.map(point => {
- return newPolygon().centerCircle(point, 0.2, 5).setZ(point.z + 0.01);
- }), { outline: true });
- }
- if (engages.length) {
- output
- .setLayer('engage', { line: 0x005500, face: 0x00ff00, opacity: 0.5 }, true)
- .addAreas(engages.map(point => {
- return newPolygon().centerCircle(point, 0.2, 7).setZ(point.z + 0.02);
- }), { outline: true });
- }
- if (heads.length) {
- let line = dark ? 0xffffff : 0x112233;
- output
- .setLayer('arrows', { face: headColor, line, opacity: 0.75 }, true)
- .addAreas(heads.map(points => {
- const {p1, p2} = points;
- const slope = p2.slopeTo(p1);
- const s1 = BASE.newSlopeFromAngle(slope.angle + 20);
- const s2 = BASE.newSlopeFromAngle(slope.angle - 20);
- const p3 = points.p2.projectOnSlope(s1, arrowSize);
- const p4 = points.p2.projectOnSlope(s2, arrowSize);
- return newPolygon().addPoints([p2,p3,p4]).setZ(p2.z + 0.01);
- }), { thin: true, outline: true });
- }
- output
- .setLayer(opts.other || 'move', moveOpt, opts.moves !== true)
- .addPolys(moves, { thin: true, z: opts.z });
- // force level when present
- let pz = level.z ? level.z - height : opts.z;
- Object.values(prints).forEach(array => {
- array.forEach(poly => {
- if (flat && poly.appearsClosed()) {
- poly.setClosed();
- poly.points.pop();
- }
- output
- .setLayer(opts.action || 'print', printOpt)
- .addPolys([ poly ],
- thin ? { thin, z: opts.z, color: poly.color } :
- flat ? {
- flat, z: pz, color: poly.color,
- outline: true, offset: array.width, open: poly.open } :
- {
- offset: array.width, height, z: pz,
- color: { face: poly.color, line: poly.color }
- })
- });
- });
-
- update(index / levels.length, output);
+ lastOut = out;
});
- // console.log({retractz});
- return layers;
- }
-
- const colorFunctions = FDM.colorFunctions = {
- default: hsv2rgb.bind({ seg: 4, fn: color4d }),
- simple: hsv2rgb.bind({ seg: 3, fn: color4 }),
- dark: hsv2rgb.bind({ seg: 4, fn: color4d })
- };
-
- let currentColorFunction = colorFunctions.default;
- let darkColorFunction = colorFunctions.dark;
-
- // hsv values all = 0 to 1
- function hsv2rgb(h, s, v) {
- const div = this.seg;
- const ss = 1 / div;
- const seg = Math.floor(h / ss);
- const rem = h - (seg * ss);
- const inc = (rem / ss);
- const dec = (1 - inc);
- const rgb = {r: 0, g: 0, b: 0};
- this.fn(rgb, inc, seg);
- rgb.r = ((rgb.r * 255 * v) & 0xff) << 16;
- rgb.g = ((rgb.g * 255 * v) & 0xff) << 8;
- rgb.b = ((rgb.b * 255 * v) & 0xff);
- return rgb.r | rgb.g | rgb.b;
- }
-
- function color5(rgb, inc, seg) {
- const dec = 1 - inc;
- switch (seg) {
- case 0:
- rgb.r = 1;
- rgb.g = inc;
- rgb.b = 0;
- break;
- case 1:
- rgb.r = dec;
- rgb.g = 1;
- rgb.b = 0;
- break;
- case 2:
- rgb.r = 0;
- rgb.g = dec;
- rgb.b = inc;
- break;
- case 3:
- rgb.r = inc;
- rgb.g = 0;
- rgb.b = 1;
- break;
- case 4:
- rgb.r = dec;
- rgb.g = 0;
- rgb.b = dec;
- break;
+ // all moves with an emit at the very end (common in contouring)
+ if (lastOut.emit && !emits) {
+ pushPrint(lastOut.tool, current)
}
- }
-
- function color4d(rgb, inc, seg) {
- const dec = 1 - inc;
- switch (seg) {
- case 0:
- rgb.r = 1;
- rgb.g = inc;
- rgb.b = 0;
- break;
- case 1:
- rgb.r = dec;
- rgb.g = 1;
- rgb.b = 0;
- break;
- case 2:
- rgb.r = 0;
- rgb.g = dec;
- rgb.b = inc;
- break;
- case 3:
- rgb.r = inc * 0.85;
- rgb.g = 0;
- rgb.b = 1;
- break;
- case 4:
- rgb.r = 0.85;
- rgb.g = 0;
- rgb.b = 1;
- break;
+ lastEnd = lastOut;
+ if (changes.length) {
+ output
+ .setLayer('tool', { line: 0x000055, face: 0x0000ff, opacity: 0.5 }, true)
+ .addAreas(changes.map(point => {
+ return newPolygon().centerCircle(point, 0.2, 4).setZ(point.z + 0.03);
+ }), { outline: true });
}
- }
-
-
- function color4(rgb, inc, seg) {
- const dec = 1 - inc;
- switch (seg) {
- case 0:
- rgb.r = 0;
- rgb.g = inc;
- rgb.b = 1;
- break;
- case 1:
- rgb.r = inc;
- rgb.g = 1;
- rgb.b = 0;
- break;
- case 2:
- rgb.r = 1;
- rgb.g = dec;
- rgb.b = 0;
- break;
- case 3:
- rgb.r = dec;
- rgb.g = 0;
- rgb.b = 0;
- break;
+ if (retracts.length) {
+ output
+ .setLayer('retract', { line: 0x550000, face: 0xff0000, opacity: 0.5 }, true)
+ .addAreas(retracts.map(point => {
+ return newPolygon().centerCircle(point, 0.2, 5).setZ(point.z + 0.01);
+ }), { outline: true });
}
+ if (engages.length) {
+ output
+ .setLayer('engage', { line: 0x005500, face: 0x00ff00, opacity: 0.5 }, true)
+ .addAreas(engages.map(point => {
+ return newPolygon().centerCircle(point, 0.2, 7).setZ(point.z + 0.02);
+ }), { outline: true });
+ }
+ if (heads.length) {
+ let line = dark ? 0xffffff : 0x112233;
+ output
+ .setLayer('arrows', { face: headColor, line, opacity: 0.75 }, true)
+ .addAreas(heads.map(points => {
+ const {p1, p2} = points;
+ const slope = p2.slopeTo(p1);
+ const s1 = base.newSlopeFromAngle(slope.angle + 20);
+ const s2 = base.newSlopeFromAngle(slope.angle - 20);
+ const p3 = points.p2.projectOnSlope(s1, arrowSize);
+ const p4 = points.p2.projectOnSlope(s2, arrowSize);
+ return newPolygon().addPoints([p2,p3,p4]).setZ(p2.z + 0.01);
+ }), { thin: true, outline: true });
+ }
+ output
+ .setLayer(opts.other || 'move', moveOpt, opts.moves !== true)
+ .addPolys(moves, { thin: true, z: opts.z });
+ // force level when present
+ let pz = level.z ? level.z - height : opts.z;
+ Object.values(prints).forEach(array => {
+ array.forEach(poly => {
+ if (flat && poly.appearsClosed()) {
+ poly.setClosed();
+ poly.points.pop();
+ }
+ output
+ .setLayer(opts.action || 'print', printOpt)
+ .addPolys([ poly ],
+ thin ? { thin, z: opts.z, color: poly.color } :
+ flat ? {
+ flat, z: pz, color: poly.color,
+ outline: true, offset: array.width, open: poly.open } :
+ {
+ offset: array.width, height, z: pz,
+ color: { face: poly.color, line: poly.color }
+ })
+ });
+ });
+
+ update(index / levels.length, output);
+ });
+ // console.log({retractz});
+ return layers;
+}
+
+const colorFunctions = FDM.colorFunctions = {
+ default: hsv2rgb.bind({ seg: 4, fn: color4d }),
+ simple: hsv2rgb.bind({ seg: 3, fn: color4 }),
+ dark: hsv2rgb.bind({ seg: 4, fn: color4d })
+};
+
+let currentColorFunction = colorFunctions.default;
+let darkColorFunction = colorFunctions.dark;
+
+// hsv values all = 0 to 1
+function hsv2rgb(h, s, v) {
+ const div = this.seg;
+ const ss = 1 / div;
+ const seg = Math.floor(h / ss);
+ const rem = h - (seg * ss);
+ const inc = (rem / ss);
+ const dec = (1 - inc);
+ const rgb = {r: 0, g: 0, b: 0};
+ this.fn(rgb, inc, seg);
+ rgb.r = ((rgb.r * 255 * v) & 0xff) << 16;
+ rgb.g = ((rgb.g * 255 * v) & 0xff) << 8;
+ rgb.b = ((rgb.b * 255 * v) & 0xff);
+ return rgb.r | rgb.g | rgb.b;
+}
+
+function color5(rgb, inc, seg) {
+ const dec = 1 - inc;
+ switch (seg) {
+ case 0:
+ rgb.r = 1;
+ rgb.g = inc;
+ rgb.b = 0;
+ break;
+ case 1:
+ rgb.r = dec;
+ rgb.g = 1;
+ rgb.b = 0;
+ break;
+ case 2:
+ rgb.r = 0;
+ rgb.g = dec;
+ rgb.b = inc;
+ break;
+ case 3:
+ rgb.r = inc;
+ rgb.g = 0;
+ rgb.b = 1;
+ break;
+ case 4:
+ rgb.r = dec;
+ rgb.g = 0;
+ rgb.b = dec;
+ break;
}
+}
+
+function color4d(rgb, inc, seg) {
+ const dec = 1 - inc;
+ switch (seg) {
+ case 0:
+ rgb.r = 1;
+ rgb.g = inc;
+ rgb.b = 0;
+ break;
+ case 1:
+ rgb.r = dec;
+ rgb.g = 1;
+ rgb.b = 0;
+ break;
+ case 2:
+ rgb.r = 0;
+ rgb.g = dec;
+ rgb.b = inc;
+ break;
+ case 3:
+ rgb.r = inc * 0.85;
+ rgb.g = 0;
+ rgb.b = 1;
+ break;
+ case 4:
+ rgb.r = 0.85;
+ rgb.g = 0;
+ rgb.b = 1;
+ break;
+ }
+}
+
+
+function color4(rgb, inc, seg) {
+ const dec = 1 - inc;
+ switch (seg) {
+ case 0:
+ rgb.r = 0;
+ rgb.g = inc;
+ rgb.b = 1;
+ break;
+ case 1:
+ rgb.r = inc;
+ rgb.g = 1;
+ rgb.b = 0;
+ break;
+ case 2:
+ rgb.r = 1;
+ rgb.g = dec;
+ rgb.b = 0;
+ break;
+ case 3:
+ rgb.r = dec;
+ rgb.g = 0;
+ rgb.b = 0;
+ break;
+ }
+}
})();
diff --git a/src/kiri-mode/fdm/slice.js b/src/kiri-mode/fdm/slice.js
index 71687428..6a9ae320 100644
--- a/src/kiri-mode/fdm/slice.js
+++ b/src/kiri-mode/fdm/slice.js
@@ -4,1897 +4,1896 @@
(function() {
- const { base, kiri, noop } = self;
- const { driver, fill, fill_fixed, newSlice, utils } = kiri;
- const { config, polygons, util, newPoint } = base;
- const { fillArea } = polygons;
- const { FDM } = driver;
+const { base, kiri, noop } = self;
+const { consts, driver, fill, fill_fixed, newSlice, utils } = kiri;
+const { config, polygons, util, newPoint } = base;
+const { fillArea } = polygons;
+const { beltfact } = consts;
+const { FDM } = driver;
+const { getRangeParameters } = FDM;
- const POLY = polygons,
- tracker = util.pwait,
- lopacity = 0.6,
- opacity = 1,
- COLOR = {
- anchor: { check: 0x999933, face: 0x999933, line: 0x999933, opacity, lopacity },
- shell: { check: 0x0077bb, face: 0x0077bb, line: 0x0077bb, opacity, lopacity },
- fill: { check: 0x00bb77, face: 0x00bb77, line: 0x00bb77, opacity, lopacity },
- infill: { check: 0x3322bb, face: 0x3322bb, line: 0x3322bb, opacity, lopacity },
- support: { check: 0xaa5533, face: 0xaa5533, line: 0xaa5533, opacity, lopacity },
- gaps: { check: 0xaa3366, face: 0xaa3366, line: 0xaa3366, opacity, lopacity }
- },
- PROTO = Object.clone(COLOR),
- getRangeParameters = FDM.getRangeParameters,
- profile = false,
- profileStart = profile ? console.profile : noop,
- profileEnd = profile ? console.profileEnd : noop,
- debug = false;
+const POLY = polygons,
+ tracker = util.pwait,
+ lopacity = 0.6,
+ opacity = 1,
+ COLOR = {
+ anchor: { check: 0x999933, face: 0x999933, line: 0x999933, opacity, lopacity },
+ shell: { check: 0x0077bb, face: 0x0077bb, line: 0x0077bb, opacity, lopacity },
+ fill: { check: 0x00bb77, face: 0x00bb77, line: 0x00bb77, opacity, lopacity },
+ infill: { check: 0x3322bb, face: 0x3322bb, line: 0x3322bb, opacity, lopacity },
+ support: { check: 0xaa5533, face: 0xaa5533, line: 0xaa5533, opacity, lopacity },
+ gaps: { check: 0xaa3366, face: 0xaa3366, line: 0xaa3366, opacity, lopacity }
+ },
+ PROTO = Object.clone(COLOR),
+ profile = false,
+ profileStart = profile ? console.profile : noop,
+ profileEnd = profile ? console.profileEnd : noop,
+ debug = false;
- let isThin = false, // force line rendering
- isFlat = false, // force flat rendering
- offset = 0; // poly line generation offsets
+let isThin = false, // force line rendering
+ isFlat = false, // force flat rendering
+ offset = 0; // poly line generation offsets
- function vopt(opt) {
- if (opt) {
- if (isFlat) {
- opt.flat = true;
- opt.outline = true;
- return opt;
- }
- if (isThin) return null;
+function vopt(opt) {
+ if (opt) {
+ if (isFlat) {
+ opt.flat = true;
+ opt.outline = true;
+ return opt;
}
- return opt;
+ if (isThin) return null;
+ }
+ return opt;
+}
+
+/**
+ * may run in minion or worker context. do not create objects
+ * that will not quickly encode in threaded mode. add to existing
+ * data object. return is ignored.
+ */
+base.slicePost.FDM = function(data, options) {
+ const { z, lines, groups } = data;
+ const { useAssembly, post_args, zIndexes } = options;
+ const { process, isSynth, isDanger, vaseMode } = post_args;
+ const { shellOffset, fillOffset, clipOffset } = post_args;
+ data.tops = POLY.nest(groups);
+ if (isSynth) {
+ // do not shell synth widgets because
+ // they will be clipped against peers later
+ // which requires shelling post-clip
+ // but we still need to generate tops
+ delete data.groups;
+ return;
+ }
+ const index = zIndexes.indexOf(z);
+ const range = getRangeParameters(process, index);
+ // calculate fractional shells
+ let shellFrac = (range.sliceShells - (range.sliceShells | 0));
+ let sliceShells = range.sliceShells | 0;
+ if (shellFrac) {
+ let v1 = shellFrac > 0.5 ? 1 - shellFrac : shellFrac;
+ let v2 = 1 - v1;
+ let parts = Math.round(v2/v1) + 1;
+ let rem = index % parts;
+ let trg = shellFrac > 0.5 ? 1 : parts - 1;
+ sliceShells += rem >= trg ? 1 : 0;
+ }
+ const isFirst = index === 0;
+ const height = process.sliceHeight;
+ const spaceMult = isFirst ? process.firstLayerLineMult || 1 : 1;
+ const count = isSynth ? 1 : sliceShells;
+ const offset = shellOffset * spaceMult;
+ const fillOff = fillOffset * spaceMult;
+ const nutops = [];
+ // co-locate shell processing with top generation in slicer
+ for (let top of data.tops) {
+ nutops.push(FDM.share.doTopShells(z, top, count, offset/2, offset, fillOff, {
+ vase: vaseMode,
+ thin: process.detectThinWalls && !isSynth,
+ wasm: useAssembly,
+ danger: isDanger
+ }));
+ }
+ data.clip = clipOffset ? POLY.offset(nutops.map(t => t.simple), clipOffset) : undefined;
+ data.tops = nutops;
+ delete data.groups;
+};
+
+FDM.sliceAll = function(settings, onupdate) {
+ // future home of brim and anchor generation
+ let widgets = Object.values(kiri.worker.cache)
+ .filter(w => !w.meta.disabled)
+ .sort((a,b) => {
+ return a.slices[0].z - b.slices[0].z
+ });
+ // ignore first widget
+ widgets.shift();
+ // count extruders used
+ let ext = [];
+ for (let w of widgets) {
+ if (w.anno && w.anno.extruder >= 0) {
+ let e = w.anno.extruder;
+ if (ext.indexOf(e) < 0) {
+ ext.push(e);
+ }
+ }
+ }
+ // remove anchor slices from other widgets (only with multi-material)
+ if (ext.length > 1)
+ for (let w of widgets) {
+ w.slices = w.slices.filter(s => s.index >= 0);
+ }
+};
+
+/**
+ * DRIVER SLICE CONTRACT
+ *
+ * Given a widget and settings object, call functions necessary to produce
+ * slices and then the computations using those slices. This function is
+ * designed to run client or server-side and provides all output via
+ * callback functions.
+ *
+ * @param {Object} settings
+ * @param {Widget} Widget
+ * @param {Function} onupdate (called with % complete and optional message)
+ * @param {Function} ondone (called when complete with an array of Slice objects)
+ */
+FDM.slice = function(settings, widget, onupdate, ondone) {
+ let render = settings.render !== false,
+ { process, device, controller } = settings,
+ isBelt = device.bedBelt,
+ isSynth = widget.track.synth,
+ isDanger = controller.danger,
+ useAssembly = controller.assembly,
+ isConcurrent = controller.threaded && kiri.minions.concurrent,
+ solidMinArea = process.sliceSolidMinArea,
+ solidLayers = process.sliceSolidLayers || 0,
+ vaseMode = process.sliceFillType === 'vase' && !isSynth,
+ metadata = widget.anno,
+ extruder = parseInt(isSynth ? process.sliceSupportNozzle : metadata.extruder || 0),
+ sliceHeight = process.sliceHeight,
+ sliceHeightBase = (isBelt ? sliceHeight : process.firstSliceHeight) || sliceHeight,
+ nozzleSize = device.extruders[extruder].extNozzle,
+ lineWidth = process.sliceLineWidth || nozzleSize,
+ fillOffsetMult = 1.0 - bound(process.sliceFillOverlap, 0, 0.8),
+ shellOffset = lineWidth,
+ fillSpacing = lineWidth,
+ fillOffset = lineWidth * fillOffsetMult,
+ clipOffset = process.sliceSupportOffset,
+ sliceFillAngle = process.sliceFillAngle,
+ supportDensity = process.sliceSupportDensity;
+
+ // override globals used by vopt()
+ isFlat = controller.lineType === "flat";
+ isThin = !isFlat && controller.lineType === "line";
+ offset = lineWidth / 2;
+
+ if (isFlat) {
+ Object.values(COLOR).forEach(color => {
+ color.flat = true;
+ color.line = 1
+ color.opacity = 1;
+ });
+ } else {
+ Object.keys(COLOR).forEach(key => {
+ const color = COLOR[key];
+ const proto = PROTO[key]
+ color.flat = proto.flat;
+ color.line = proto.line;
+ color.opacity = proto.opacity;
+ });
}
- /**
- * may run in minion or worker context. do not create objects
- * that will not quickly encode in threaded mode. add to existing
- * data object. return is ignored.
- */
- base.slicePost.FDM = function(data, options) {
- const { z, lines, groups } = data;
- const { useAssembly, post_args, zIndexes } = options;
- const { process, isSynth, isDanger, vaseMode } = post_args;
- const { shellOffset, fillOffset, clipOffset } = post_args;
- const tops = POLY.nest(groups);
- if (isSynth) {
- // do not shell synth widgets because
- // they will be clipped against peers later
- // which requires shelling post-clip
- // but we still need to generate tops
- data.tops = tops;
- delete data.groups;
+ if (!(sliceHeight > 0 && sliceHeight < 100)) {
+ return ondone("invalid slice height");
+ }
+ if (!(nozzleSize >= 0.01 && nozzleSize < 100)) {
+ return ondone("invalid nozzle size");
+ }
+
+ const sliceMinHeight = process.sliceAdaptive && process.sliceMinHeight > 0 ?
+ Math.min(process.sliceMinHeight, sliceHeight) : 0;
+
+ if (sliceHeightBase <= 0) {
+ console.log("invalid first layer height < slice height");
+ console.log("reverting to min valid slice height");
+ sliceHeightBase = sliceMinHeight || sliceHeight;
+ }
+
+ let bounds = widget.getBoundingBox();
+ let points = widget.getPoints();
+ let indices = [];
+ let heights = [];
+
+ // handle z cutting (floor method) and base flattening
+ let zPress = isBelt ? process.firstLayerFlatten || 0 : 0;
+ let zCut = widget.track.zcut || 0;
+ if (zCut || zPress) {
+ for (let p of points) {
+ if (!p._z) {
+ p._z = p.z;
+ if (zPress) {
+ if (isBelt) {
+ let zb = (p.z - p.y) * beltfact;
+ if (zb > 0 && zb <= zPress) {
+ p.y += zb * beltfact;
+ p.z -= zb * beltfact;
+ }
+ } else {
+ if (p.z <= zPress) p.z = 0;
+ }
+ }
+ if (zCut && !isBelt) {
+ p.z -= zCut;
+ }
+ }
+ }
+ }
+
+ base.slice(points, {
+ debug: process.xray,
+ xray: process.xray,
+ zMin: bounds.min.z,
+ zMax: bounds.max.z - zCut,
+ // support/synth usually has overlapping boxes
+ union: controller.healMesh || isSynth,
+ indices: process.indices || process.xray,
+ useAssembly,
+ post: 'FDM',
+ post_args: {
+ shellOffset,
+ fillOffset,
+ clipOffset,
+ lineWidth,
+ vaseMode,
+ isSynth,
+ process,
+ isDanger,
+ },
+ // z index generator
+ zGen(zopt) {
+ if (process.xray) {
+ return zopt.zIndexes;
+ }
+ let { zMin, zMax } = zopt;
+ let h1 = sliceHeight;
+ let h0 = sliceHeightBase || h1;
+ let hm = sliceMinHeight || 0;
+ let h = h0;
+ let z = h0;
+ let zi = indices; // indices
+ let zh = heights; // heights
+ if (hm) {
+ // adaptive increments based on z indices (var map to legacy code)
+ let zIncFirst = h0;
+ let zInc = h1;
+ let zIncMin = hm;
+ let zHeights = heights;
+ let zIndexes = indices;
+ let zOrdered = Object.values(zopt.zIndexes).map(v => parseFloat(v));
+ // console.log('adaptive slicing', zIncMin, ':', zInc, 'from', zMin, 'to', zMax);
+ let zPos = zIncFirst,
+ zOI = 0,
+ zDelta,
+ zDivMin,
+ zDivMax,
+ zStep,
+ nextZ,
+ lzp = zPos;
+ // adaptive slice height
+ // first slice/height is fixed from base
+ zHeights.push(zIncFirst);
+ zIndexes.push(zIncFirst);
+ // console.log({zIncFirst, zOrdered})
+ while (zPos < zMax && zOI < zOrdered.length) {
+ nextZ = zOrdered[zOI++];
+ if (zPos >= nextZ) {
+ // console.log('skip',{zPos},'>=',{nextZ});
+ continue;
+ }
+ zDelta = nextZ - zPos;
+ if (zDelta < zIncMin) {
+ // console.log('skip',{zDelta},'<',{zIncMin});
+ continue;
+ }
+ zDivMin = Math.floor(zDelta / zIncMin);
+ zDivMax = Math.floor(zDelta / zInc);
+ if (zDivMax && zDivMax <= zDivMin) {
+ if (zDelta % zInc > 0.01) zDivMax++;
+ zStep = zDelta / zDivMax;
+ // console.log(`--- zDivMax <= zDivMin ---`, zStep, zDelta % zInc)
+ } else {
+ zStep = zDelta;
+ }
+ // console.log({nextZ, zPos, zDelta, zStep, zDivMin, zDivMax})
+ while (zPos < nextZ) {
+ zHeights.push(zStep);
+ zIndexes.push(zPos + zStep);
+ zPos += zStep;
+ // console.log({D: zPos - lzp, zPos})
+ // lzp = zPos;
+ }
+ }
+ // console.log({zIndexes, zHeights});
+ } else {
+ // simple based + fixed increment
+ while (z <= zMax) {
+ zh.push(h);
+ zi.push(z);
+ h = h1;
+ z += h;
+ }
+ }
+ // reduce slice position by half height
+ for (let i=0; i {
+ // post process slices and re-incorporate missing meta-data
+ return output.slices.map(data => {
+ let { z, clip, lines, groups } = data;
+ if (!data.tops) return null;
+ let slice = newSlice(z).addTops(data.tops);
+ slice.index = indices.indexOf(z);
+ slice.height = heights[slice.index];
+ slice.clips = clip;
+ if (process.xray) {
+ slice.lines = lines;
+ slice.groups = groups;
+ slice.xray = process.xray;
+ }
+ return slice;
+ }).filter(s => s);
+ }).then(slices => {
+ return onSliceDone(slices);
+ }).then(ondone);
+
+ async function doShadow(slices) {
+ if (widget.shadow) {
return;
}
- const index = zIndexes.indexOf(z);
- const range = FDM.getRangeParameters(process, index);
- // calculate fractional shells
- let shellFrac = (range.sliceShells - (range.sliceShells | 0));
- let sliceShells = range.sliceShells | 0;
- if (shellFrac) {
- let v1 = shellFrac > 0.5 ? 1 - shellFrac : shellFrac;
- let v2 = 1 - v1;
- let parts = Math.round(v2/v1) + 1;
- let rem = index % parts;
- let trg = shellFrac > 0.5 ? 1 : parts - 1;
- sliceShells += rem >= trg ? 1 : 0;
+ let root = widget.group[0];
+ if (root.shadow) {
+ widget.shadow = root.shadow;
+ return;
}
- const isFirst = index === 0;
- const height = process.sliceHeight;
- const spaceMult = isFirst ? process.firstLayerLineMult || 1 : 1;
- const count = isSynth ? 1 : sliceShells;
- const offset = shellOffset * spaceMult;
- const fillOff = fillOffset * spaceMult;
- const nutops = [];
- // co-locate shell processing with top generation in slicer
- for (let top of tops) {
- nutops.push(FDM.share.doTopShells(z, top, count, offset/2, offset, fillOff, {
- vase: vaseMode,
- thin: process.detectThinWalls && !isSynth,
- wasm: useAssembly,
- danger: isDanger
- }));
+ // create shadow for clipping supports
+ let alltops = widget.group
+ .filter(w => !w.track.synth) // no supports in shadow
+ .map(w => w.slices).flat()
+ .map(s => s.tops).flat().map(t => t.simple);
+ let shadow = isConcurrent ?
+ await kiri.minions.union(alltops, 0.1) :
+ POLY.union(alltops, 0.1, true);
+ // expand shadow when requested (support clipping)
+ if (process.sliceSupportExtra) {
+ shadow = POLY.offset(shadow, process.sliceSupportExtra);
}
- data.clip = clipOffset ? POLY.offset(nutops.map(t => t.simple), clipOffset) : undefined;
- data.tops = nutops;
- delete data.groups;
- };
-
- FDM.sliceAll = function(settings, onupdate) {
- // future home of brim and anchor generation
- let widgets = Object.values(kiri.worker.cache)
- .filter(w => !w.meta.disabled)
- .sort((a,b) => {
- return a.slices[0].z - b.slices[0].z
- });
- // ignore first widget
- widgets.shift();
- // count extruders used
- let ext = [];
- for (let w of widgets) {
- if (w.anno && w.anno.extruder >= 0) {
- let e = w.anno.extruder;
- if (ext.indexOf(e) < 0) {
- ext.push(e);
- }
- }
- }
- // remove anchor slices from other widgets (only with multi-material)
- if (ext.length > 1)
- for (let w of widgets) {
- w.slices = w.slices.filter(s => s.index >= 0);
- }
- };
-
- /**
- * DRIVER SLICE CONTRACT
- *
- * Given a widget and settings object, call functions necessary to produce
- * slices and then the computations using those slices. This function is
- * designed to run client or server-side and provides all output via
- * callback functions.
- *
- * @param {Object} settings
- * @param {Widget} Widget
- * @param {Function} onupdate (called with % complete and optional message)
- * @param {Function} ondone (called when complete with an array of Slice objects)
- */
- FDM.slice = function(settings, widget, onupdate, ondone) {
- let render = settings.render !== false,
- { process, device, controller } = settings,
- isBelt = device.bedBelt,
- isSynth = widget.track.synth,
- isDanger = controller.danger,
- useAssembly = controller.assembly,
- isConcurrent = controller.threaded && kiri.minions.concurrent,
- solidMinArea = process.sliceSolidMinArea,
- solidLayers = process.sliceSolidLayers || 0,
- vaseMode = process.sliceFillType === 'vase' && !isSynth,
- metadata = widget.anno,
- extruder = parseInt(isSynth ? process.sliceSupportNozzle : metadata.extruder || 0),
- sliceHeight = process.sliceHeight,
- sliceHeightBase = (isBelt ? sliceHeight : process.firstSliceHeight) || sliceHeight,
- nozzleSize = device.extruders[extruder].extNozzle,
- lineWidth = process.sliceLineWidth || nozzleSize,
- fillOffsetMult = 1.0 - bound(process.sliceFillOverlap, 0, 0.8),
- shellOffset = lineWidth,
- fillSpacing = lineWidth,
- fillOffset = lineWidth * fillOffsetMult,
- clipOffset = process.sliceSupportOffset,
- sliceFillAngle = process.sliceFillAngle,
- supportDensity = process.sliceSupportDensity,
- beltfact = Math.cos(Math.PI/4);
-
- // override globals used by vopt()
- isFlat = controller.lineType === "flat";
- isThin = !isFlat && controller.lineType === "line";
- offset = lineWidth / 2;
-
- if (isFlat) {
- Object.values(COLOR).forEach(color => {
- color.flat = true;
- color.line = 1
- color.opacity = 1;
- });
- } else {
- Object.keys(COLOR).forEach(key => {
- const color = COLOR[key];
- const proto = PROTO[key]
- color.flat = proto.flat;
- color.line = proto.line;
- color.opacity = proto.opacity;
- });
- }
-
- if (!(sliceHeight > 0 && sliceHeight < 100)) {
- return ondone("invalid slice height");
- }
- if (!(nozzleSize >= 0.01 && nozzleSize < 100)) {
- return ondone("invalid nozzle size");
- }
-
- const sliceMinHeight = process.sliceAdaptive && process.sliceMinHeight > 0 ?
- Math.min(process.sliceMinHeight, sliceHeight) : 0;
-
- if (sliceHeightBase <= 0) {
- console.log("invalid first layer height < slice height");
- console.log("reverting to min valid slice height");
- sliceHeightBase = sliceMinHeight || sliceHeight;
- }
-
- let bounds = widget.getBoundingBox();
- let points = widget.getPoints();
- let indices = [];
- let heights = [];
-
- // handle z cutting (floor method) and base flattening
- let zPress = isBelt ? process.firstLayerFlatten || 0 : 0;
- let zCut = widget.track.zcut || 0;
- if (zCut || zPress) {
- for (let p of points) {
- if (!p._z) {
- p._z = p.z;
- if (zPress) {
- if (isBelt) {
- let zb = (p.z - p.y) * beltfact;
- if (zb > 0 && zb <= zPress) {
- p.y += zb * beltfact;
- p.z -= zb * beltfact;
- }
- } else {
- if (p.z <= zPress) p.z = 0;
- }
- }
- if (zCut && !isBelt) {
- p.z -= zCut;
- }
- }
- }
- }
-
- base.slice(points, {
- debug: process.xray,
- xray: process.xray,
- zMin: bounds.min.z,
- zMax: bounds.max.z - zCut,
- // support/synth usually has overlapping boxes
- union: controller.healMesh || isSynth,
- indices: process.indices || process.xray,
- useAssembly,
- post: 'FDM',
- post_args: {
- shellOffset,
- fillOffset,
- clipOffset,
- lineWidth,
- vaseMode,
- isSynth,
- process,
- isDanger,
- },
- // z index generator
- zGen(zopt) {
- if (process.xray) {
- return zopt.zIndexes;
- }
- let { zMin, zMax } = zopt;
- let h1 = sliceHeight;
- let h0 = sliceHeightBase || h1;
- let hm = sliceMinHeight || 0;
- let h = h0;
- let z = h0;
- let zi = indices; // indices
- let zh = heights; // heights
- if (hm) {
- // adaptive increments based on z indices (var map to legacy code)
- let zIncFirst = h0;
- let zInc = h1;
- let zIncMin = hm;
- let zHeights = heights;
- let zIndexes = indices;
- let zOrdered = Object.values(zopt.zIndexes).map(v => parseFloat(v));
- // console.log('adaptive slicing', zIncMin, ':', zInc, 'from', zMin, 'to', zMax);
- let zPos = zIncFirst,
- zOI = 0,
- zDelta,
- zDivMin,
- zDivMax,
- zStep,
- nextZ,
- lzp = zPos;
- // adaptive slice height
- // first slice/height is fixed from base
- zHeights.push(zIncFirst);
- zIndexes.push(zIncFirst);
- // console.log({zIncFirst, zOrdered})
- while (zPos < zMax && zOI < zOrdered.length) {
- nextZ = zOrdered[zOI++];
- if (zPos >= nextZ) {
- // console.log('skip',{zPos},'>=',{nextZ});
- continue;
- }
- zDelta = nextZ - zPos;
- if (zDelta < zIncMin) {
- // console.log('skip',{zDelta},'<',{zIncMin});
- continue;
- }
- zDivMin = Math.floor(zDelta / zIncMin);
- zDivMax = Math.floor(zDelta / zInc);
- if (zDivMax && zDivMax <= zDivMin) {
- if (zDelta % zInc > 0.01) zDivMax++;
- zStep = zDelta / zDivMax;
- // console.log(`--- zDivMax <= zDivMin ---`, zStep, zDelta % zInc)
- } else {
- zStep = zDelta;
- }
- // console.log({nextZ, zPos, zDelta, zStep, zDivMin, zDivMax})
- while (zPos < nextZ) {
- zHeights.push(zStep);
- zIndexes.push(zPos + zStep);
- zPos += zStep;
- // console.log({D: zPos - lzp, zPos})
- // lzp = zPos;
- }
- }
- // console.log({zIndexes, zHeights});
- } else {
- // simple based + fixed increment
- while (z <= zMax) {
- zh.push(h);
- zi.push(z);
- h = h1;
- z += h;
- }
- }
- // reduce slice position by half height
- for (let i=0; i {
- // post process slices and re-incorporate missing meta-data
- return output.slices.map(data => {
- let { z, clip, lines, groups } = data;
- if (!data.tops) return null;
- let slice = newSlice(z).addTops(data.tops);
- slice.index = indices.indexOf(z);
- slice.height = heights[slice.index];
- slice.clips = clip;
- if (process.xray) {
- slice.lines = lines;
- slice.groups = groups;
- slice.xray = process.xray;
- }
- return slice;
- }).filter(s => s);
- }).then(slices => {
- return onSliceDone(slices);
- }).then(ondone);
-
- async function doShadow(slices) {
- if (widget.shadow) {
- return;
- }
- let root = widget.group[0];
- if (root.shadow) {
- widget.shadow = root.shadow;
- return;
- }
- // create shadow for clipping supports
- let alltops = widget.group
- .filter(w => !w.track.synth) // no supports in shadow
- .map(w => w.slices).flat()
- .map(s => s.tops).flat().map(t => t.simple);
- let shadow = isConcurrent ?
- await kiri.minions.union(alltops, 0.1) :
- POLY.union(alltops, 0.1, true);
- // expand shadow when requested (support clipping)
- if (process.sliceSupportExtra) {
- shadow = POLY.offset(shadow, process.sliceSupportExtra);
- }
- widget.shadow = root.shadow = POLY.setZ(shadow, 0);
- // slices[0].output()
- // .setLayer('shadow', { line: 0xff0000, check: 0xff0000 })
- // .addPolys(shadow);
- }
-
- async function onSliceDone(slices) {
- // remove all empty slices above part but leave below
- // for multi-part (multi-extruder) setups where the void is ok
- // also reverse because slicing occurs bottom-up
- let found = false;
- slices = slices.reverse().filter(slice => {
- if (slice.tops.length) {
- return found = true;
- } else {
- return found;
- }
- }).reverse();
-
- // connect slices into linked list for island/bridge projections
- for (let i=1; i 0.1) {
- continue;
- }
- let ntops = [];
- POLY.subtract(tops, pslice.clips, ntops, null, slice.z, 0);
- tops = ntops;
- }
- // trim to group's shadow if not in belt mode
- if (!isBelt) {
- tops = POLY.setZ(POLY.trimTo(tops, widget.shadow), slice.z);
- }
- }
- slice.tops = [];
- for (let t of tops) {
- slice.addTop(t);
- }
- doShells(slice, 1, shellOffset / 2);
- }
- }
-
- // calculate % complete and call onupdate()
- function doupdate(index, from, to, msg) {
- trackupdate(index / slices.length, from, to, msg);
- }
-
- 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) {
- slices.forEach(slice => {
- fn(slice);
- doupdate(slice.index, from, to, msg)
- });
- }
-
- // do not hint polygon fill longer than a max span length
- config.hint_len_max = util.sqr(process.sliceBridgeMax);
-
- // reset for solids, support projections
- // and other annotations
- slices.forEach(slice => {
- slice.widget = widget;
- slice.extruder = extruder;
- slice.solids = [];
- });
-
- // just the top/bottom special solid layers or range defined solid layers
- forSlices(0.15, 0.2, slice => {
- let range = slice.params;
- let spaceMult = slice.index === 0 ? process.firstLayerLineMult || 1 : 1;
- let isBottom = slice.index < process.sliceBottomLayers;
- let isTop = slice.index > slices.length - process.sliceTopLayers - 1;
- let isDense = range.sliceFillSparse > 0.995;
- let isSolid = (isBottom || ((isTop || isDense) && !vaseMode)) && !isSynth;
- let solidWidth = isSolid ? range.sliceFillWidth || 1 : 0;
- if (solidWidth) {
- let fillSpace = fillSpacing * spaceMult * solidWidth;
- doSolidLayerFill(slice, fillSpace, sliceFillAngle);
- }
- sliceFillAngle += 90.0;
- }, "solid layers");
-
- // add lead in anchor when specified in belt mode (but not for synths)
- if (isBelt && !isSynth) {
- // find adjusted zero point from slices
- let smin = Infinity;
- for (let slice of slices) {
- let miny = Infinity;
- for (let poly of slice.topPolys()) {
- let y = poly.bounds.maxy;
- let z = slice.z;
- let by = z - y;
- if (by < miny) miny = by;
- if (by < smin) smin = by;
- }
- slice.belt = { miny, touch: false };
- }
- // mark slices with tops touching belt
- // also find max width of first 5 layers
- let start;
- let minx = Infinity, maxx = -Infinity;
- let peek = 0;
- for (let slice of slices) {
- if (slice.tops.length && peek++ < 5) {
- for (let poly of slice.topPolys()) {
- minx = Math.min(minx, poly.bounds.minx);
- maxx = Math.max(maxx, poly.bounds.maxx);
- }
- }
- // mark slice as touching belt if near miny
- // if (Math.abs(slice.belt.miny - smin) < 0.01) {
- if (Math.abs(slice.belt.miny) < 0.01) {
- slice.belt.touch = true;
- if (!start) start = slice;
- }
- }
- // ensure we start against a layer with shells
- while (start && start.up && start.topShells().length === 0) {
- start = start.up;
- }
- // if a brim applies, add that width to anchor
- let brim = getRangeParameters(process, 0).firstLayerBrim || 0;
- if (brim) {
- minx -= brim;
- maxx += brim;
- }
- // array of added top.fill_sparse arrays
- let adds = [];
- let anchorlen = (process.beltAnchor || process.firstLayerBeltLead) * beltfact;
- while (anchorlen && start && anchorlen >= sliceHeight) {
- let addto = start.down;
- if (!addto) {
- addto = newSlice(start.z - sliceHeight);
- addto.extruder = extruder;
- addto.belt = { };
- addto.height = start.height;
- addto.up = start;
- start.down = addto;
- slices.splice(0,0,addto);
- } else if (!addto.belt) {
- console.log({addto_missing_belt: addto});
- addto.belt = {};
- }
- addto.index = -1;
- addto.belt.anchor = true;
- // this allows the anchor to print bi-directionally
- // by removing the forced start-point in print.js
- addto.belt.touch = false;
- let z = addto.z;
- let y = z - smin - (nozzleSize / 2);
- let splat = base.newPolygon().add(minx, y, z).add(maxx, y, z).setOpen();
- let snew = addto.addTop(splat).fill_sparse = [ splat ];
- adds.push(snew);
- start = addto;
- anchorlen -= sliceHeight;
- }
- // add anchor bump
- let bump = process.firstLayerBeltBump;
- if (bump) {
- adds = adds.reverse().slice(1, adds.length - 1);
- let count = 1;
- for (let add of adds) {
- let poly = add[0];
- let y = count++ * -start.height * 2;
- if (-y > bump) {
- count--;
- // break;
- }
- let first = poly.first();
- // add up/over/down to anchor line (close = down)
- // which completes the bump perimeter
- poly.push(poly.last().add({x:0, y, z:0}));
- poly.push(poly.first().add({x:0, y, z:0}));
- poly.setClosed();
- if (count > 2 && maxx - minx > 10) {
- // add vertical hatch lines insibe bump shell
- let mp = (maxx + minx) / 2;
- let dx = (maxx - minx - 2);
- dx = (Math.floor(dx / 3) * 3) / 2;
- let fy = first.y;
- let fz = first.z;
- let n2 = nozzleSize / 2;
- for (let x = mp - dx; x <= mp + dx ; x += 3) {
- add.push( base.newPolygon().add(x, fy - n2, fz).add(x, fy + y + n2, fz).setOpen() );
- }
- }
- }
- }
- }
-
- // calculations only relevant when solid layers are used
- if (solidLayers && !vaseMode && !isSynth) {
- profileStart("delta");
- forSlices(0.2, 0.34, slice => {
- if (slice.index > 0) doDiff(slice, solidMinArea);
- }, "layer deltas");
- profileEnd();
- profileStart("delta-project");
- forSlices(0.34, 0.35, slice => {
- projectFlats(slice, solidLayers);
- projectBridges(slice, solidLayers);
- }, "layer deltas");
- profileEnd();
- profileStart("solid-fill")
- let promises = isConcurrent ? [] : undefined;
- forSlices(0.35, promises ? 0.4 : 0.5, slice => {
- let params = slice.params || process;
- let first = slice.index === 0;
- let solidWidth = params.sliceFillWidth || 1;
- let spaceMult = first ? params.firstLayerLineMult || 1 : 1;
- let fillSpace = fillSpacing * spaceMult * solidWidth;
- doSolidsFill(slice, fillSpace, sliceFillAngle, solidMinArea, promises);
- sliceFillAngle += 90.0;
- }, "fill solids");
- if (promises) {
- await tracker(promises, (i, t) => {
- trackupdate(i / t, 0.4, 0.5);
- });
- }
- profileEnd();
- }
-
- if (!isSynth && !vaseMode) {
- // sparse layers only present when non-vase mose and sparse % > 0
- let lastType;
- let promises = isConcurrent ? [] : undefined;
- forSlices(0.5, promises ? 0.55 : 0.7, slice => {
- let params = slice.params || process;
- if (!params.sliceFillSparse) {
- return;
- }
- let newType = params.sliceFillType;
- doSparseLayerFill(slice, {
- settings,
- process,
- device,
- lineWidth,
- spacing: fillOffset,
- density: params.sliceFillSparse,
- bounds: widget.getBoundingBox(),
- height: sliceHeight,
- type: newType,
- cache: params._range !== true && lastType === newType,
- promises
- });
- lastType = newType;
- }, "infill");
- if (promises) {
- await tracker(promises, (i, t) => {
- trackupdate(i / t, 0.55, 0.7);
- });
- }
- // back-fill slices marked for infill cloning
- for (let slice of slices) {
- if (slice._clone_sparse) {
- let tops = slice.tops;
- let down = slice.down.tops;
- for (let i=0; i p.cloneZ(slice.z));
- }
- }
- }
- } else if (isSynth) {
- // fill manual supports differently
- let outline = process.sliceSupportOutline || false;
- let promises = isConcurrent ? [] : undefined;
- let resolve = [];
- forSlices(0.5, promises ? 0.6 : 0.7, slice => {
- let params = slice.params || process;
- let density = params.sliceSupportDensity;
- if (density)
- for (let top of slice.tops) {
- if (!outline) {
- let offset = top.shells;
- fillSupportPolys(promises, offset, lineWidth, density, slice.z, isBelt);
- resolve.push({top, offset});
- } else {
- let offset = [];
- POLY.expand(top.shells || [], -nozzleSize/4, slice.z, offset);
- fillSupportPolys(promises, offset, lineWidth, density, slice.z, isBelt);
- resolve.push({top, offset});
- }
- }
- }, "infill");
- if (promises) {
- await tracker(promises, (i, t) => {
- trackupdate(i / t, 0.6, 0.7);
- });
- }
- for (let rec of resolve) {
- let lines = rec.top.fill_lines = rec.offset.map(o => o.fill).flat().filter(v => v);
- // if copying simply's support type, eliminate shells
- // and zig/zag lines connectd by shell segments
- if (!outline) {
- let newlines = [];
- let op2;
- let eo = 0;
- let idx = 1;
- for (let i=0; i {
- promises.push(doSupport(slice, process, widget.shadow, { exp: isDanger }));
- }, "support");
- await tracker(promises, (i, t) => {
- trackupdate(i / t, 0.75, 0.8);
- });
- profileEnd();
- profileStart("support-fill");
- promises = false && isConcurrent ? [] : undefined;
- forSlices(0.8, promises ? 0.88 : 0.9, slice => {
- doSupportFill(promises, slice, lineWidth, supportDensity, process.sliceSupportArea, isBelt);
- }, "support");
- if (promises) {
- await tracker(promises, (i, t) => {
- trackupdate(i / t, 0.88, 0.9);
- });
- }
- profileEnd();
- }
-
- // render if not explicitly disabled
- if (render) {
- forSlices(0.9, 1.0, slice => {
- let params = slice.params || process;
- doRender(slice, isSynth, params, controller.devel);
- }, "render");
- }
-
- if (isBelt) {
- let bounds = base.newBounds();
- for (let top of slices[0].tops) {
- bounds.merge(top.poly.bounds);
- }
- widget.belt.miny = -bounds.miny;
- widget.belt.midy = (bounds.miny + bounds.maxy) / 2;
- }
- }
-
+ widget.shadow = root.shadow = POLY.setZ(shadow, 0);
+ // slices[0].output()
+ // .setLayer('shadow', { line: 0xff0000, check: 0xff0000 })
+ // .addPolys(shadow);
}
- function bound(v,min,max) {
- return Math.max(min,Math.min(max,v));
- }
-
- function doRender(slice, isSynth, params, devel) {
- const output = slice.output();
- const height = slice.height / 2;
- const solidWidth = params.sliceFillWidth || 1;
-
- slice.tops.forEach(top => {
- if (isThin) output
- .setLayer('part', { line: 0x333333, check: 0x333333 })
- .addPolys(top.poly);
-
- output
- .setLayer("shells", isSynth ? COLOR.support : COLOR.shell)
- .addPolys(top.shells || [], vopt({ offset, height, clean: true }));
-
- output
- .setLayer("solid fill", isSynth ? COLOR.support : COLOR.fill)
- .addLines(top.fill_lines || [], vopt({ offset: offset * solidWidth, height }));
-
- if (!(slice.belt && slice.belt.anchor)) output
- .setLayer("sparse fill", COLOR.infill)
- .addPolys(top.fill_sparse || [], vopt({ offset, height, outline: true, trace:true }))
-
- if (slice.belt && slice.belt.anchor) output
- .setLayer("anchor", COLOR.anchor)
- .addPolys(top.fill_sparse || [], vopt({ offset, height, outline: true, trace:true }))
-
- if (top.thin_fill) output
- .setLayer("thin fill", COLOR.fill)
- .addLines(top.thin_fill, vopt({ offset, height }));
-
- if (top.gaps) output
- .setLayer("gaps", COLOR.gaps)
- .addPolys(top.gaps, vopt({ offset, height, thin: true }));
-
- if (isThin && devel && top.fill_off && top.fill_off.length) {
- slice.output()
- .setLayer('fill inset', { face: 0, line: 0xaaaaaa, check: 0xaaaaaa })
- .addPolys(top.fill_off);
- // .setLayer('last', { face: 0, line: 0x008888, check: 0x008888 })
- // .addPolys(top.last);
- }
- });
-
- if (isThin && devel) {
- if (slice.solids && slice.solids.length) output
- .setLayer("solids", { face: 0xbbbb00, check: 0xbbbb00 })
- .addAreas(slice.solids);
-
- if (slice.bridges && slice.bridges.length) output
- .setLayer("bridges", { face: 0x00cccc, line: 0x00cccc, check: 0x00cccc })
- .addAreas(slice.bridges);
-
- if (slice.flats && slice.flats.length) output
- .setLayer("flats", { face: 0xaa00aa, line: 0xaa00aa, check: 0xaa00aa })
- .addAreas(slice.flats);
- }
-
- if (slice.supports) output
- .setLayer("support", COLOR.support)
- .addPolys(slice.supports, vopt({ offset, height }));
-
- if (slice.supports) slice.supports.forEach(poly => {
- if (poly.fill) output
- .setLayer("support", COLOR.support)
- .addLines(poly.fill, vopt({ offset, height }));
- });
-
- if (slice.xray) {
- const color = [ 0xff0000, 0x00aa00, 0x0000ff, 0xaaaa00, 0xff00ff ];
- if (slice.lines) {
- slice.lines.forEach((line, i) => {
- const group = i % 5;
- slice.output().setLayer(`l${group}`, color[group]).addLine(line.p1, line.p2);
- });
- }
- if (slice.groups)
- POLY.nest(slice.groups).forEach((poly, i) => {
- const group = i % 5;
- slice.addTop(poly);
- // slice.output().setLayer(`g${i}`, 0x888888).addPoly(poly);
- slice.output().setLayer(`g${i}`, color[group]).addPoly(poly);
- });
- }
-
- // console.log(slice.index, slice.render.stats);
- }
-
- // shared with SLA driver and minions
- FDM.share = {
- doShells,
- doTopShells,
- doDiff,
- projectFlats,
- projectBridges
- };
-
- /**
- * Compute offset shell polygons. For FDM, the first offset is usually half
- * of the nozzle width. Each subsequent offset is a full nozzle width. User
- * parameters control tweaks to these numbers to allow for better shell bonding.
- * The last shell generated is a "fillOffset" shell. Fill lines are clipped to
- * this polygon. Adjusting fillOffset controls bonding of infill to the shells.
- *
- * Most of this is done in slicePost() in FDM mode. now this is used by SLA, Laser
- *
- * @param {number} count
- * @param {number} offsetN
- * @param {number} fillOffset
- * @param {Obejct} options
- */
- function doShells(slice, count, offset1, offsetN, fillOffset, opt = {}) {
- for (let top of slice.tops) {
- doTopShells(slice.z, top, count, offset1, offsetN, fillOffset, opt);
- }
- }
-
- function doTopShells(z, top, count, offset1, offsetN, fillOffset, opt = {}) {
- // pretend we're a top object in minions
- if (!top.poly) {
- top = { poly: top };
- }
-
- // add simple (low rez poly) where less accuracy is OK
- top.simple = top.poly.simple();
-
- let wasm = opt.wasm;
- let top_poly = [ top.poly ];
-
- if (opt.vase) {
- // remove top poly inners in vase mode
- top.poly = top.poly.clone(false);
- }
-
- top.shells = [];
- top.fill_off = [];
- top.fill_lines = [];
-
- let last = [],
- gaps = [];
-
- if (count) {
- // permit offset of 0 for laser and drag knife
- if (offset1 === 0 && count === 1) {
- last = top_poly.clone(true);
- top.shells = last;
+ async function onSliceDone(slices) {
+ // remove all empty slices above part but leave below
+ // for multi-part (multi-extruder) setups where the void is ok
+ // also reverse because slicing occurs bottom-up
+ let found = false;
+ slices = slices.reverse().filter(slice => {
+ if (slice.tops.length) {
+ return found = true;
} else {
- // heal top open polygons if the ends are close (benchy tilt test)
- top_poly.forEach(p => { if (p.open) {
- let dist = p.first().distTo2D(p.last());
- if (dist < 1) p.open = false;
- } });
- if (opt.danger && opt.thin) {
- top.thin_fill = [];
- top.fill_sparse = [];
- let layers = POLY.inset(top_poly, offsetN, count, z, true);
- last = layers.last().mid;
- top.shells = layers.map(r => r.mid).flat();
- top.gaps = layers.map(r => r.gap).flat();
- let off = offsetN;
- let min = off * 0.75;
- let max = off * 4;
- for (let poly of layers.map(r => r.gap).flat()) {
- let centers = poly.centers(off/2, z, min, max, {lines:false});
- top.fill_sparse.appendAll(centers);
- // top.fill_lines.appendAll(centers);
+ return found;
+ }
+ }).reverse();
+
+ // connect slices into linked list for island/bridge projections
+ for (let i=1; i 0.1) {
+ continue;
+ }
+ let ntops = [];
+ POLY.subtract(tops, pslice.clips, ntops, null, slice.z, 0);
+ tops = ntops;
+ }
+ // trim to group's shadow if not in belt mode
+ if (!isBelt) {
+ tops = POLY.setZ(POLY.trimTo(tops, widget.shadow), slice.z);
+ }
+ }
+ slice.tops = [];
+ for (let t of tops) {
+ slice.addTop(t);
+ }
+ doShells(slice, 1, shellOffset / 2);
+ }
+ }
- oso.outs.forEach((polys, i) => {
- polys.forEach(p => {
- p.depth = i;
- if (p.fill_off) {
- p.fill_off.forEach(pi => pi.depth = i);
- }
- if (p.inner) {
- for (let pi of p.inner) {
- pi.depth = p.depth;
- }
- }
- top.shells.push(p);
- });
- last = polys;
- });
+ // calculate % complete and call onupdate()
+ function doupdate(index, from, to, msg) {
+ trackupdate(index / slices.length, from, to, msg);
+ }
- // slice.solids.trimmed = slice.solids.trimmed || [];
- oso.gaps.forEach((polys, i) => {
- let off = (i == 0 ? offset1 : offsetN);
- polys = POLY.offset(polys, -off * 0.8, {z, minArea: 0, wasm});
- top.thin_fill.appendAll(cullIntersections(
- fillArea(polys, 45, off/2, [], 0.01, off*2),
- fillArea(polys, 135, off/2, [], 0.01, off*2),
- ));
- gaps = polys;
+ 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) {
+ slices.forEach(slice => {
+ fn(slice);
+ doupdate(slice.index, from, to, msg)
+ });
+ }
+
+ // do not hint polygon fill longer than a max span length
+ config.hint_len_max = util.sqr(process.sliceBridgeMax);
+
+ // reset for solids, support projections
+ // and other annotations
+ slices.forEach(slice => {
+ slice.widget = widget;
+ slice.extruder = extruder;
+ slice.solids = [];
+ });
+
+ // just the top/bottom special solid layers or range defined solid layers
+ forSlices(0.15, 0.2, slice => {
+ let range = slice.params;
+ let spaceMult = slice.index === 0 ? process.firstLayerLineMult || 1 : 1;
+ let isBottom = slice.index < process.sliceBottomLayers;
+ let isTop = slice.index > slices.length - process.sliceTopLayers - 1;
+ let isDense = range.sliceFillSparse > 0.995;
+ let isSolid = (isBottom || ((isTop || isDense) && !vaseMode)) && !isSynth;
+ let solidWidth = isSolid ? range.sliceFillWidth || 1 : 0;
+ if (solidWidth) {
+ let fillSpace = fillSpacing * spaceMult * solidWidth;
+ doSolidLayerFill(slice, fillSpace, sliceFillAngle);
+ }
+ sliceFillAngle += 90.0;
+ }, "solid layers");
+
+ // add lead in anchor when specified in belt mode (but not for synths)
+ if (isBelt && !isSynth) {
+ // find adjusted zero point from slices
+ let smin = Infinity;
+ for (let slice of slices) {
+ let miny = Infinity;
+ for (let poly of slice.topPolys()) {
+ let y = poly.bounds.maxy;
+ let z = slice.z;
+ let by = z - y;
+ if (by < miny) miny = by;
+ if (by < smin) smin = by;
+ }
+ slice.belt = { miny, touch: false };
+ }
+ // mark slices with tops touching belt
+ // also find max width of first 5 layers
+ let start;
+ let minx = Infinity, maxx = -Infinity;
+ let peek = 0;
+ for (let slice of slices) {
+ if (slice.tops.length && peek++ < 5) {
+ for (let poly of slice.topPolys()) {
+ minx = Math.min(minx, poly.bounds.minx);
+ maxx = Math.max(maxx, poly.bounds.maxx);
+ }
+ }
+ // mark slice as touching belt if near miny
+ // if (Math.abs(slice.belt.miny - smin) < 0.01) {
+ if (Math.abs(slice.belt.miny) < 0.01) {
+ slice.belt.touch = true;
+ if (!start) start = slice;
+ }
+ }
+ // ensure we start against a layer with shells
+ while (start && start.up && start.topShells().length === 0) {
+ start = start.up;
+ }
+ // if a brim applies, add that width to anchor
+ let brim = getRangeParameters(process, 0).firstLayerBrim || 0;
+ if (brim) {
+ minx -= brim;
+ maxx += brim;
+ }
+ // array of added top.fill_sparse arrays
+ let adds = [];
+ let anchorlen = (process.beltAnchor || process.firstLayerBeltLead) * beltfact;
+ while (anchorlen && start && anchorlen >= sliceHeight) {
+ let addto = start.down;
+ if (!addto) {
+ addto = newSlice(start.z - sliceHeight);
+ addto.extruder = extruder;
+ addto.belt = { };
+ addto.height = start.height;
+ addto.up = start;
+ start.down = addto;
+ slices.splice(0,0,addto);
+ } else if (!addto.belt) {
+ console.log({addto_missing_belt: addto});
+ addto.belt = {};
+ }
+ addto.index = -1;
+ addto.belt.anchor = true;
+ // this allows the anchor to print bi-directionally
+ // by removing the forced start-point in print.js
+ addto.belt.touch = false;
+ let z = addto.z;
+ let y = z - smin - (nozzleSize / 2);
+ let splat = base.newPolygon().add(minx, y, z).add(maxx, y, z).setOpen();
+ let snew = addto.addTop(splat).fill_sparse = [ splat ];
+ adds.push(snew);
+ start = addto;
+ anchorlen -= sliceHeight;
+ }
+ // add anchor bump
+ let bump = process.firstLayerBeltBump;
+ if (bump) {
+ adds = adds.reverse().slice(1, adds.length - 1);
+ let count = 1;
+ for (let add of adds) {
+ let poly = add[0];
+ let y = count++ * -start.height * 2;
+ if (-y > bump) {
+ count--;
+ // break;
+ }
+ let first = poly.first();
+ // add up/over/down to anchor line (close = down)
+ // which completes the bump perimeter
+ poly.push(poly.last().add({x:0, y, z:0}));
+ poly.push(poly.first().add({x:0, y, z:0}));
+ poly.setClosed();
+ if (count > 2 && maxx - minx > 10) {
+ // add vertical hatch lines insibe bump shell
+ let mp = (maxx + minx) / 2;
+ let dx = (maxx - minx - 2);
+ dx = (Math.floor(dx / 3) * 3) / 2;
+ let fy = first.y;
+ let fz = first.z;
+ let n2 = nozzleSize / 2;
+ for (let x = mp - dx; x <= mp + dx ; x += 3) {
+ add.push( base.newPolygon().add(x, fy - n2, fz).add(x, fy + y + n2, fz).setOpen() );
+ }
+ }
+ }
+ }
+ }
+
+ // calculations only relevant when solid layers are used
+ if (solidLayers && !vaseMode && !isSynth) {
+ profileStart("delta");
+ forSlices(0.2, 0.34, slice => {
+ if (slice.index > 0) doDiff(slice, solidMinArea);
+ }, "layer deltas");
+ profileEnd();
+ profileStart("delta-project");
+ forSlices(0.34, 0.35, slice => {
+ projectFlats(slice, solidLayers);
+ projectBridges(slice, solidLayers);
+ }, "layer deltas");
+ profileEnd();
+ profileStart("solid-fill")
+ let promises = isConcurrent ? [] : undefined;
+ forSlices(0.35, promises ? 0.4 : 0.5, slice => {
+ let params = slice.params || process;
+ let first = slice.index === 0;
+ let solidWidth = params.sliceFillWidth || 1;
+ let spaceMult = first ? params.firstLayerLineMult || 1 : 1;
+ let fillSpace = fillSpacing * spaceMult * solidWidth;
+ doSolidsFill(slice, fillSpace, sliceFillAngle, solidMinArea, promises);
+ sliceFillAngle += 90.0;
+ }, "fill solids");
+ if (promises) {
+ await tracker(promises, (i, t) => {
+ trackupdate(i / t, 0.4, 0.5);
+ });
+ }
+ profileEnd();
+ }
+
+ if (!isSynth && !vaseMode) {
+ // sparse layers only present when non-vase mose and sparse % > 0
+ let lastType;
+ let promises = isConcurrent ? [] : undefined;
+ forSlices(0.5, promises ? 0.55 : 0.7, slice => {
+ let params = slice.params || process;
+ if (!params.sliceFillSparse) {
+ return;
+ }
+ let newType = params.sliceFillType;
+ doSparseLayerFill(slice, {
+ settings,
+ process,
+ device,
+ lineWidth,
+ spacing: fillOffset,
+ density: params.sliceFillSparse,
+ bounds: widget.getBoundingBox(),
+ height: sliceHeight,
+ type: newType,
+ cache: params._range !== true && lastType === newType,
+ promises
+ });
+ lastType = newType;
+ }, "infill");
+ if (promises) {
+ await tracker(promises, (i, t) => {
+ trackupdate(i / t, 0.55, 0.7);
+ });
+ }
+ // back-fill slices marked for infill cloning
+ for (let slice of slices) {
+ if (slice._clone_sparse) {
+ let tops = slice.tops;
+ let down = slice.down.tops;
+ for (let i=0; i p.cloneZ(slice.z));
+ }
+ }
+ }
+ } else if (isSynth) {
+ // fill manual supports differently
+ let outline = process.sliceSupportOutline || false;
+ let promises = isConcurrent ? [] : undefined;
+ let resolve = [];
+ forSlices(0.5, promises ? 0.6 : 0.7, slice => {
+ let params = slice.params || process;
+ let density = params.sliceSupportDensity;
+ if (density)
+ for (let top of slice.tops) {
+ if (!outline) {
+ let offset = top.shells;
+ fillSupportPolys(promises, offset, lineWidth, density, slice.z, isBelt);
+ resolve.push({top, offset});
+ } else {
+ let offset = [];
+ POLY.expand(top.shells || [], -nozzleSize/4, slice.z, offset);
+ fillSupportPolys(promises, offset, lineWidth, density, slice.z, isBelt);
+ resolve.push({top, offset});
+ }
+ }
+ }, "infill");
+ if (promises) {
+ await tracker(promises, (i, t) => {
+ trackupdate(i / t, 0.6, 0.7);
+ });
+ }
+ for (let rec of resolve) {
+ let lines = rec.top.fill_lines = rec.offset.map(o => o.fill).flat().filter(v => v);
+ // if copying simply's support type, eliminate shells
+ // and zig/zag lines connectd by shell segments
+ if (!outline) {
+ let newlines = [];
+ let op2;
+ let eo = 0;
+ let idx = 1;
+ for (let i=0; i {
+ promises.push(doSupport(slice, process, widget.shadow, { exp: isDanger }));
+ }, "support");
+ await tracker(promises, (i, t) => {
+ trackupdate(i / t, 0.75, 0.8);
+ });
+ profileEnd();
+ profileStart("support-fill");
+ promises = false && isConcurrent ? [] : undefined;
+ forSlices(0.8, promises ? 0.88 : 0.9, slice => {
+ doSupportFill(promises, slice, lineWidth, supportDensity, process.sliceSupportArea, isBelt);
+ }, "support");
+ if (promises) {
+ await tracker(promises, (i, t) => {
+ trackupdate(i / t, 0.88, 0.9);
+ });
+ }
+ profileEnd();
+ }
+
+ // render if not explicitly disabled
+ if (render) {
+ forSlices(0.9, 1.0, slice => {
+ let params = slice.params || process;
+ doRender(slice, isSynth, params, controller.devel);
+ }, "render");
+ }
+
+ if (isBelt) {
+ let bounds = base.newBounds();
+ for (let top of slices[0].tops) {
+ bounds.merge(top.poly.bounds);
+ }
+ widget.belt.miny = -bounds.miny;
+ widget.belt.midy = (bounds.miny + bounds.maxy) / 2;
+ }
+ }
+
+}
+
+function bound(v,min,max) {
+ return Math.max(min,Math.min(max,v));
+}
+
+function doRender(slice, isSynth, params, devel) {
+ const output = slice.output();
+ const height = slice.height / 2;
+ const solidWidth = params.sliceFillWidth || 1;
+
+ slice.tops.forEach(top => {
+ if (isThin) output
+ .setLayer('part', { line: 0x333333, check: 0x333333 })
+ .addPolys(top.poly);
+
+ output
+ .setLayer("shells", isSynth ? COLOR.support : COLOR.shell)
+ .addPolys(top.shells || [], vopt({ offset, height, clean: true }));
+
+ output
+ .setLayer("solid fill", isSynth ? COLOR.support : COLOR.fill)
+ .addLines(top.fill_lines || [], vopt({ offset: offset * solidWidth, height }));
+
+ if (!(slice.belt && slice.belt.anchor)) output
+ .setLayer("sparse fill", COLOR.infill)
+ .addPolys(top.fill_sparse || [], vopt({ offset, height, outline: true, trace:true }))
+
+ if (slice.belt && slice.belt.anchor) output
+ .setLayer("anchor", COLOR.anchor)
+ .addPolys(top.fill_sparse || [], vopt({ offset, height, outline: true, trace:true }))
+
+ if (top.thin_fill) output
+ .setLayer("thin fill", COLOR.fill)
+ .addLines(top.thin_fill, vopt({ offset, height }));
+
+ if (top.gaps) output
+ .setLayer("gaps", COLOR.gaps)
+ .addPolys(top.gaps, vopt({ offset, height, thin: true }));
+
+ if (isThin && devel && top.fill_off && top.fill_off.length) {
+ slice.output()
+ .setLayer('fill inset', { face: 0, line: 0xaaaaaa, check: 0xaaaaaa })
+ .addPolys(top.fill_off);
+ // .setLayer('last', { face: 0, line: 0x008888, check: 0x008888 })
+ // .addPolys(top.last);
+ }
+ });
+
+ if (isThin && devel) {
+ if (slice.solids && slice.solids.length) output
+ .setLayer("solids", { face: 0xbbbb00, check: 0xbbbb00 })
+ .addAreas(slice.solids);
+
+ if (slice.bridges && slice.bridges.length) output
+ .setLayer("bridges", { face: 0x00cccc, line: 0x00cccc, check: 0x00cccc })
+ .addAreas(slice.bridges);
+
+ if (slice.flats && slice.flats.length) output
+ .setLayer("flats", { face: 0xaa00aa, line: 0xaa00aa, check: 0xaa00aa })
+ .addAreas(slice.flats);
+ }
+
+ if (slice.supports) output
+ .setLayer("support", COLOR.support)
+ .addPolys(slice.supports, vopt({ offset, height }));
+
+ if (slice.supports) slice.supports.forEach(poly => {
+ if (poly.fill) output
+ .setLayer("support", COLOR.support)
+ .addLines(poly.fill, vopt({ offset, height }));
+ });
+
+ if (slice.xray) {
+ const color = [ 0xff0000, 0x00aa00, 0x0000ff, 0xaaaa00, 0xff00ff ];
+ if (slice.lines) {
+ slice.lines.forEach((line, i) => {
+ const group = i % 5;
+ slice.output().setLayer(`l${group}`, color[group]).addLine(line.p1, line.p2);
+ });
+ }
+ if (slice.groups)
+ POLY.nest(slice.groups).forEach((poly, i) => {
+ const group = i % 5;
+ slice.addTop(poly);
+ // slice.output().setLayer(`g${i}`, 0x888888).addPoly(poly);
+ slice.output().setLayer(`g${i}`, color[group]).addPoly(poly);
+ });
+ }
+
+ // console.log(slice.index, slice.render.stats);
+}
+
+// shared with SLA driver and minions
+FDM.share = {
+ doShells,
+ doTopShells,
+ doDiff,
+ projectFlats,
+ projectBridges
+};
+
+/**
+ * Compute offset shell polygons. For FDM, the first offset is usually half
+ * of the nozzle width. Each subsequent offset is a full nozzle width. User
+ * parameters control tweaks to these numbers to allow for better shell bonding.
+ * The last shell generated is a "fillOffset" shell. Fill lines are clipped to
+ * this polygon. Adjusting fillOffset controls bonding of infill to the shells.
+ *
+ * Most of this is done in slicePost() in FDM mode. now this is used by SLA, Laser
+ *
+ * @param {number} count
+ * @param {number} offsetN
+ * @param {number} fillOffset
+ * @param {Obejct} options
+ */
+function doShells(slice, count, offset1, offsetN, fillOffset, opt = {}) {
+ for (let top of slice.tops) {
+ doTopShells(slice.z, top, count, offset1, offsetN, fillOffset, opt);
+ }
+}
+
+function doTopShells(z, top, count, offset1, offsetN, fillOffset, opt = {}) {
+ // pretend we're a top object in minions
+ if (!top.poly) {
+ top = { poly: top };
+ }
+
+ // add simple (low rez poly) where less accuracy is OK
+ top.simple = top.poly.simple();
+
+ let wasm = opt.wasm;
+ let top_poly = [ top.poly ];
+
+ if (opt.vase) {
+ // remove top poly inners in vase mode
+ top.poly = top.poly.clone(false);
+ }
+
+ top.shells = [];
+ top.fill_off = [];
+ top.fill_lines = [];
+
+ let last = [],
+ gaps = [];
+
+ if (count) {
+ // permit offset of 0 for laser and drag knife
+ if (offset1 === 0 && count === 1) {
+ last = top_poly.clone(true);
+ top.shells = last;
+ } else {
+ // heal top open polygons if the ends are close (benchy tilt test)
+ top_poly.forEach(p => { if (p.open) {
+ let dist = p.first().distTo2D(p.last());
+ if (dist < 1) p.open = false;
+ } });
+ if (opt.danger && opt.thin) {
+ top.thin_fill = [];
+ top.fill_sparse = [];
+ let layers = POLY.inset(top_poly, offsetN, count, z, true);
+ last = layers.last().mid;
+ top.shells = layers.map(r => r.mid).flat();
+ top.gaps = layers.map(r => r.gap).flat();
+ let off = offsetN;
+ let min = off * 0.75;
+ let max = off * 4;
+ for (let poly of layers.map(r => r.gap).flat()) {
+ let centers = poly.centers(off/2, z, min, max, {lines:false});
+ top.fill_sparse.appendAll(centers);
+ // top.fill_lines.appendAll(centers);
+ }
+ } else if (opt.thin) {
+ top.thin_fill = [];
+ let oso = {z, count, gaps: [], outs: [], minArea: 0.05, wasm};
+ POLY.offset(top_poly, [-offset1, -offsetN], oso);
+
+ oso.outs.forEach((polys, i) => {
+ polys.forEach(p => {
+ p.depth = i;
+ if (p.fill_off) {
+ p.fill_off.forEach(pi => pi.depth = i);
+ }
+ if (p.inner) {
+ for (let pi of p.inner) {
+ pi.depth = p.depth;
+ }
+ }
+ top.shells.push(p);
});
- } else {
- // standard wall offsetting strategy
- POLY.offset(
- top_poly,
- [-offset1, -offsetN],
- {
- z,
- wasm,
- count,
- outs: top.shells,
- flat: true,
- call: (polys, onCount) => {
- last = polys;
- // mark each poly with depth (offset #) starting at 0
- for (let p of polys) {
- p.depth = count - onCount;
- if (p.fill_off) p.fill_off.forEach(function(pi) {
- // use negative offset for inners
- pi.depth = -(count - onCount);
- });
- // mark inner depth to match parent
- if (p.inner) {
- for (let pi of p.inner) {
- pi.depth = p.depth;
- }
+ last = polys;
+ });
+
+ // slice.solids.trimmed = slice.solids.trimmed || [];
+ oso.gaps.forEach((polys, i) => {
+ let off = (i == 0 ? offset1 : offsetN);
+ polys = POLY.offset(polys, -off * 0.8, {z, minArea: 0, wasm});
+ top.thin_fill.appendAll(cullIntersections(
+ fillArea(polys, 45, off/2, [], 0.01, off*2),
+ fillArea(polys, 135, off/2, [], 0.01, off*2),
+ ));
+ gaps = polys;
+ });
+ } else {
+ // standard wall offsetting strategy
+ POLY.offset(
+ top_poly,
+ [-offset1, -offsetN],
+ {
+ z,
+ wasm,
+ count,
+ outs: top.shells,
+ flat: true,
+ call: (polys, onCount) => {
+ last = polys;
+ // mark each poly with depth (offset #) starting at 0
+ for (let p of polys) {
+ p.depth = count - onCount;
+ if (p.fill_off) p.fill_off.forEach(function(pi) {
+ // use negative offset for inners
+ pi.depth = -(count - onCount);
+ });
+ // mark inner depth to match parent
+ if (p.inner) {
+ for (let pi of p.inner) {
+ pi.depth = p.depth;
}
}
}
}
- );
- }
+ }
+ );
}
- } else {
- // no shells, just infill, is permitted
- last = [top.poly];
}
-
- // generate fill offset poly set from last offset to top.fill_off
- if (fillOffset && last.length > 0) {
- // if gaps present, remove that area from fill inset
- if (gaps.length) {
- let nulast = [];
- POLY.subtract(last, gaps, nulast, null, z);
- last = nulast;
- }
- last.forEach(function(inner) {
- POLY.offset([inner], -fillOffset, {outs: top.fill_off, flat: true, z});
- });
- }
-
- // for diffing
- top.last = last;
- // top.last_simple = last.map(p => p.clean(true, undefined, config.clipper / 10));
-
- return top;
+ } else {
+ // no shells, just infill, is permitted
+ last = [top.poly];
}
- /**
- * Create an entirely solid layer by filling all top polygons
- * with an alternating pattern.
- *
- * @param {number} linewidth
- * @param {number} angle
- * @param {number} density
- */
- function doSolidLayerFill(slice, spacing, angle) {
- if (slice.tops.length === 0 || typeof(angle) != 'number') {
- slice.isSolidLayer = false;
- return;
+ // generate fill offset poly set from last offset to top.fill_off
+ if (fillOffset && last.length > 0) {
+ // if gaps present, remove that area from fill inset
+ if (gaps.length) {
+ let nulast = [];
+ POLY.subtract(last, gaps, nulast, null, z);
+ last = nulast;
}
-
- slice.tops.forEach(function(top) {
- let lines = fillArea(top.fill_off, angle, spacing, null);
- top.fill_lines.appendAll(lines);
- });
-
- slice.isSolidLayer = true;
- };
-
- /**
- * Take output from pluggable sparse infill algorithm and clip to
- * the bounds of the top polygons and their inner solid areas.
- */
- function doSparseLayerFill(slice, options = {}) {
- let process = options.process,
- spacing = options.spacing, // spacing space between fill lines
- density = options.density, // density of infill 0.0 - 1.0
- bounds = options.bounds, // bounding box of widget
- height = options.height, // z layer height
- cache = !(options.cache === false),
- type = options.type || 'hex';
-
- if (slice.tops.length === 0 || density === 0.0 || slice.isSolidLayer || slice.index < 0) {
- slice.isSparseFill = false;
- return;
- }
-
- let tops = slice.tops,
- down = slice.down,
- clib = self.ClipperLib,
- ctyp = clib.ClipType,
- ptyp = clib.PolyType,
- cfil = clib.PolyFillType,
- clip = new clib.Clipper(),
- ctre = new clib.PolyTree(),
- poly,
- polys = [],
- lines = [],
- line = [],
- solids = [],
- // callback passed to pluggable infill algorithm
- target = {
- // slice and slice property access
- slice: function() { return slice },
- zIndex: function() { return slice.index },
- zValue: function() { return slice.z },
- // various option map access
- options: function() { return options },
- lineWidth: function() { return options.lineWidth },
- bounds: function() { return bounds },
- zHeight: function() { return height },
- offset: function() { return spacing },
- density: function() { return density },
- repeat: function() { return process.sliceFillRepeat },
- // output functions
- emit: function(x,y) {
- if (isNaN(x)) {
- solids.push(x);
- } else {
- line.push(newPoint(x, y, slice.z));
- slice.isSparseFill = true;
- }
- },
- newline: function() {
- if (line.length > 0) {
- lines.push(line);
- line = [];
- }
- }
- };
-
- // use specified fill type
- if (type && fill[type]) {
- fill[type](target);
- } else {
- console.log({missing_infill: type});
- return;
- }
-
- // force emit of last line
- target.newline();
-
- // prepare top infill structure
- for (let top of tops) {
- top.fill_sparse = top.fill_sparse || [];
- polys.appendAll(top.fill_off);
- polys.appendAll(top.solids);
- }
-
- // update fill fingerprint for this slice
- slice._fill_finger = POLY.fingerprint(polys);
-
- let skippable = cache && fill_fixed[type] ? true : false;
- let miss = false;
- // if the layer below has the same fingerprint,
- // we may be able to clone the infill instead of regenerating it
- if (skippable && slice.fingerprintSame(down)) {
- // the fill fingerprint can slightly different because of solid projections
- if (down._fill_finger && POLY.fingerprintCompare(slice._fill_finger, down._fill_finger)) {
- for (let i=0; i {
- top.fill_lines.push(p1, p2);
- });
- }
- }
- if (fillable.length) {
- let lines = POLY.fillArea(fillable, angl, options.lineWidth);
- top.fill_lines.appendAll(lines);
- }
- }
- }
- }
-
- // if only solids were added and no lines to clip
- if (!sparse_clip) {
- return;
- }
-
- if (options.promises) {
- options.promises.push(kiri.minions.clip(slice, polys, lines));
- return;
- }
-
- lines = lines.map(a => a.map(p => p.toClipper()));
- clip.AddPaths(lines, ptyp.ptSubject, false);
- clip.AddPaths(POLY.toClipper(polys), ptyp.ptClip, true);
-
- if (clip.Execute(ctyp.ctIntersection, ctre, cfil.pftNonZero, cfil.pftEvenOdd)) {
- for (let node of ctre.m_AllPolys) {
- poly = POLY.fromClipperNode(node, slice.z);
- for (let top of tops) {
- // use only polygons inside this top
- if (poly.isInside(top.poly)) {
- top.fill_sparse.push(poly);
- }
- }
- }
- }
- };
-
- /**
- * Find difference between fill inset poly on two adjacent layers.
- * Used to calculate bridges, flats and then solid projections.
- * 'expand' is used for top offsets in SLA mode
- */
- function doDiff(slice, minArea, options = {}) {
- const { sla, fakedown } = options;
- if (slice.index === 0 && !fakedown) {
- return;
- }
- const top = slice,
- down = slice.down || (fakedown ? newSlice(-1) : null),
- topInner = sla ? top.topPolys() : top.topInners(),
- downInner = sla ? down.topPolys() : down.topInners(),
- bridges = top.bridges = [],
- flats = down.flats = [];
-
- // skip diffing layers that are identical
- if (slice.fingerprintSame(down)) {
- top.bridges = bridges;
- down.flats = flats;
- return;
- }
-
- POLY.subtract(topInner, downInner, bridges, flats, slice.z, minArea, {
- wasm: true
- });
- };
-
- /**
- *
- *
- * @param {Polygon[]} polys
- */
- function addSolidFills(slice, polys) {
- if (slice.solids) {
- slice.solids.appendAll(polys);
- } else if (polys && polys.length) {
- console.log({no_solids_in: slice, for: polys})
- }
- };
-
- /**
- * project bottom flats down
- */
- function projectFlats(slice, count) {
- if (slice.isSolidLayer || !slice.down || !slice.flats) return;
- projectSolid(slice, slice.flats, count, false, true);
- };
-
- /**
- * project top bridges up
- */
- function projectBridges(slice, count) {
- if (slice.isSolidLayer || !slice.up || !slice.bridges) return;
- projectSolid(slice, slice.bridges, count, true, true);
- };
-
- /**
- * fill projected areas and store line data
- * @return {boolean} true if filled, false if not
- */
- function doSolidsFill(slice, spacing, angle, minArea, fillQ) {
- let minarea = minArea || 1,
- tops = slice.tops,
- solids = slice.solids;
-
- if (!(tops && solids)) {
- return;
- }
-
- let unioned = POLY.union(solids, undefined, true, { wasm: true }).flat(),
- isSLA = (spacing === undefined && angle === undefined);
-
- if (solids.length === 0) return false;
- if (unioned.length === 0) return false;
-
- let trims = [],
- inner = isSLA ? slice.topPolys() : slice.topFillOff();
-
- // trim each solid to the inner bounds
- for (let p of unioned) {
- p.setZ(slice.z);
- for (let i of inner) {
- let masks = p.mask(i);
- if (masks && masks.length > 0) {
- trims.appendAll(masks);
- }
- }
- }
-
- // clear old solids and make array for new
- tops.forEach(top => { top.solids = [] });
-
- // replace solids with merged and trimmed solids
- slice.solids = solids = trims;
-
- // parent each solid polygon inside the smallest bounding top
- for (let solid of solids) {
- for (let top of tops) {
- if (top.poly.overlaps(solid)) {
- if (!solid.parent || solid.parent.area() > top.poly.area()) {
- if (solid.areaDeep() < minarea) {
- // console.log({i:slice.index,cull_solid:solid,area:solid.areaDeep()});
- continue;
- }
- solid.parent = top.poly;
- top.solids.push(solid);
- }
- }
- }
- }
-
- // for SLA to bypass line infill
- if (isSLA) {
- return true;
- }
-
- // create empty filled line array for each top
- for (let top of tops) {
- // synth belt anchor tops don't want fill
- if (!top.fill_lines) {
- continue;
- }
- const tofill = [];
- const angfill = [];
- const newfill = top.fill_lines = [];
- // determine fill orientation from top
- for (let solid of solids) {
- if (solid.parent === top.poly) {
- if (solid.fillang) {
- angfill.push(solid);
- } else {
- tofill.push(solid);
- }
- }
- }
- if (tofill.length > 0) {
- doFillArea(fillQ, tofill, angle, spacing, newfill);
- // top.fill_lines_norm = {angle:angle,spacing:spacing};
- }
- if (angfill.length > 0) {
- top.fill_lines_ang = {spacing:spacing,list:[],poly:[]};
- for (let af of angfill) {
- doFillArea(fillQ, [af], af.fillang.angle + 45, spacing, newfill);
- // top.fill_lines_ang.list.push(af.fillang.angle + 45);
- // top.fill_lines_ang.poly.push(af.clone());
- }
- }
- }
- }
-
- function doFillArea(fillQ, polys, angle, spacing, output, minLen, maxLen) {
- if (fillQ) {
- fillQ.push(kiri.minions.fill(polys, angle, spacing, output, minLen, maxLen));
- } else {
- POLY.fillArea(polys, angle, spacing, output, minLen, maxLen);
- }
- }
-
- /**
- * calculate external overhangs requiring support
- */
- async function doSupport(slice, proc, shadow, opt = {}) {
- let maxBridge = proc.sliceSupportSpan || 5,
- minArea = proc.supportMinArea || 0.1,
- pillarSize = proc.sliceSupportSize,
- offset = proc.sliceSupportOffset || 0,
- gap = proc.sliceSupportGap,
- size = (pillarSize || 1),
- tops = slice.topPolys(),
- trimTo = tops;
-
- let traces = POLY.flatten(slice.topShells().clone(true)),
- fill = slice.topFill(),
- points = [],
- down = slice.down,
- down_tops = down ? down.topPolys() : null,
- down_traces = down ? POLY.flatten(down.topShells().clone(true)) : null;
-
- if (opt.exp && down_tops) {
- let points = down_tops.map(p => p.deepLength).reduce((a,v)=>a+v);
- if (points > 200) {
- // use de-rez'd top shadow instead
- down_tops = down.topSimples();
- // de-rez trace polys because it's not that important for supports
- down_traces = down_traces.map(p => p.clean(true, undefined, config.clipper / 10));
- }
- }
-
- // DEBUG code
- let SDBG = false;
- let cks = SDBG ? [] : undefined;
- let pip = SDBG ? [] : undefined;
- let pcl = SDBG ? [] : undefined;
-
- // check if point is supported by layer below
- function checkPointSupport(point) {
- if (SDBG) cks.push(point); // DEBUG
- // skip points close to other support points
- for (let i=0; i= maxBridge) {
- let slope = p1.slopeTo(p2).factor(1/dist),
- segs = Math.floor(dist / maxBridge) + 1,
- seglen = dist / segs;
- while (i < segs) {
- checkPointSupport(p1.projectOnSlope(slope, i++ * seglen));
- }
- }
- if (poly) checkPointSupport(p2);
- }
-
- let supports = [];
-
- // generate support polys from unsupported points
- if (slice.down) (function() {
- // check trace line support needs
- traces.forEach(function(trace) {
- trace.forEachSegment(function(p1, p2) { checkLineSupport(p1, p2, true) });
- });
-
- // add offset solids to supports (or fill depending)
- fill.forEachPair(function(p1,p2) { checkLineSupport(p1, p2, false) });
-
- // skip the rest if no points or supports
- if (!(points.length || supports.length)) return;
-
- let pillars = [];
-
- // for each point, create a bounding rectangle
- points.forEach(function(point) {
- pillars.push(base.newPolygon().centerRectangle(point, size/2, size/2));
- });
-
- supports.appendAll(POLY.union(pillars, null, true, { wasm: false }));
- // merge pillars and replace with convex hull of outer points (aka smoothing)
- pillars = POLY.union(pillars, null, true, { wasm: false }).forEach(function(pillar) {
- supports.push(base.newPolygon().createConvexHull(pillar.points));
- });
- })();
-
- // DEBUG code
- if (SDBG && down_traces) slice.output()
- .setLayer('cks', { line: 0xee5533, check: 0xee5533 })
- .addPolys(cks.map(p => base.newPolygon().centerRectangle(p, 0.25, 0.25)))
- .setLayer('pip', { line: 0xdd4422, check: 0xdd4422 })
- .addPolys(pip.map(p => base.newPolygon().centerRectangle(p, 0.4, 0.4)))
- .setLayer('pcl', { line: 0xcc3311, check: 0xcc3311 })
- .addPolys(pcl.map(p => base.newPolygon().centerRectangle(p, 0.3, 0.3)))
- .setLayer('pts', { line: 0xdd33dd, check: 0xdd33dd })
- .addPolys(points.map(p => base.newPolygon().centerRectangle(p, 0.8, 0.8)))
- .setLayer('dtr', { line: 0x0, check: 0x0 })
- .addPolys(POLY.setZ(down_traces.clone(true),slice.z));
- ;
-
- if (supports.length === 0) {
- return;
- }
-
- // then union supports
- if (supports.length > 10) {
- supports = await kiri.minions.union(supports);
- } else {
- supports = POLY.union(supports, null, true, { wasm: false });
- }
-
- // clip to top polys
- supports = POLY.trimTo(supports, shadow);
-
- let depth = 0;
- while (down && supports.length > 0) {
- down.supports = down.supports || [];
-
- let trimmed = [], culled = [];
-
- // culled = supports;
- // clip supports to shell offsets
- POLY.subtract(supports, down.topSimples(), trimmed, null, slice.z, minArea, { wasm: false });
-
- // set depth hint on support polys for infill density
- trimmed.forEach(function(trim) {
- if (trim.area() < minArea) return;
- culled.push(trim.setZ(down.z));
- });
-
- // exit when no more support polys exist
- if (culled.length === 0) break;
-
- // new bridge polys for next pass (skip first layer below)
- if (depth >= gap) {
- down.supports.appendAll(culled);
- }
-
- supports = culled;
- down = down.down;
- depth++;
- }
-
- }
-
- function doSupportFill(promises, slice, linewidth, density, minArea, isBelt) {
- let supports = slice.supports,
- nsB = [],
- nsC = [],
- min = minArea || 0.1;
-
- 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, nsB, null, slice.z, min, { wasm: false });
- supports = nsB;
-
- // also trim to lower offsets, if they exist
- if (slice.down && slice.down.clips) {
- POLY.subtract(nsB, slice.down.clips, nsC, null, slice.z, min, { wasm: false });
- supports = nsC;
- }
-
- if (supports) {
- fillSupportPolys(promises, supports, linewidth, density, slice.z, isBelt);
- }
-
- // re-assign new supports back to slice
- slice.supports = supports;
- };
-
- function fillSupportPolys(promises, polys, linewidth, density, z, isBelt) {
- // calculate fill density
- let spacing = linewidth * (1 / density);
- polys.forEach(function (poly) {
- // angle based on width/height ratio
- let angle = isBelt || (poly.bounds.width() / poly.bounds.height() > 1) ? 90 : 0;
- // inset support poly for fill lines 33% of nozzle width
- let inset = POLY.offset([poly], -linewidth/3, {flat: true, z, wasm: true});
- // do the fill
- if (inset && inset.length > 0) {
- doFillArea(promises, inset, angle, spacing, poly.fill = []);
- }
- return true;
+ last.forEach(function(inner) {
+ POLY.offset([inner], -fillOffset, {outs: top.fill_off, flat: true, z});
});
}
- /**
- *
- * @param {Slice} slice
- * @param {Polygon[]} polys
- * @param {number} count
- * @param {boolean} up
- * @param {boolean} first
- * @returns {*}
- */
- function projectSolid(slice, polys, count, up, first) {
- if (!slice || slice.isSolidLayer || count <= 0) {
- return;
- }
- let clones = polys.clone(true);
- if (first) {
- clones.forEach(function(p) {
- p.hintFillAngle();
- });
- }
- addSolidFills(slice, clones);
- if (count > 0) {
- if (up) projectSolid(slice.up, polys, count-1, true, false);
- else projectSolid(slice.down, polys, count-1, false, false);
- }
+ // for diffing
+ top.last = last;
+ // top.last_simple = last.map(p => p.clean(true, undefined, config.clipper / 10));
+
+ return top;
+}
+
+/**
+ * Create an entirely solid layer by filling all top polygons
+ * with an alternating pattern.
+ *
+ * @param {number} linewidth
+ * @param {number} angle
+ * @param {number} density
+ */
+ function doSolidLayerFill(slice, spacing, angle) {
+ if (slice.tops.length === 0 || typeof(angle) != 'number') {
+ slice.isSolidLayer = false;
+ return;
}
- /**
- * given an array of arrays of points (lines), eliminate intersections
- * between groups, then return a unified array of shortest non-intersects.
- *
- * @returns {Point[]}
- */
- function cullIntersections() {
- function toLines(pts) {
- let lns = [];
- for (let i=0, il=pts.length; i t);
- if (aOa.length < 1) return;
- let aa = toLines(aOa.shift());
- while (aOa.length) {
- let bb = toLines(aOa.shift());
- loop: for (let i=0, il=aa.length; i !l.del).concat(bb.filter(l => !l.del));
- }
- let good = [];
- for (let i=0, il=aa.length; i 2 ? good : [];
+ slice.tops.forEach(function(top) {
+ let lines = fillArea(top.fill_off, angle, spacing, null);
+ top.fill_lines.appendAll(lines);
+ });
+
+ slice.isSolidLayer = true;
+};
+
+/**
+ * Take output from pluggable sparse infill algorithm and clip to
+ * the bounds of the top polygons and their inner solid areas.
+ */
+function doSparseLayerFill(slice, options = {}) {
+ let process = options.process,
+ spacing = options.spacing, // spacing space between fill lines
+ density = options.density, // density of infill 0.0 - 1.0
+ bounds = options.bounds, // bounding box of widget
+ height = options.height, // z layer height
+ cache = !(options.cache === false),
+ type = options.type || 'hex';
+
+ if (slice.tops.length === 0 || density === 0.0 || slice.isSolidLayer || slice.index < 0) {
+ slice.isSparseFill = false;
+ return;
}
- FDM.supports = function(settings, widget) {
- let isBelt = settings.device.bedBelt;
- let process = settings.process;
- let size = process.sliceSupportSize;
- let s4 = size / 4;
- let s2 = size * 0.45;
- let min = 0.01;
- let geo = new THREE.BufferGeometry();
- geo.setAttribute('position', new THREE.BufferAttribute(widget.vertices, 3));
- let mat = new THREE.MeshBasicMaterial();
- let rad = (Math.PI / 180);
- let deg = (180 / Math.PI);
- let angle = rad * settings.process.sliceSupportAngle;
- let thresh = -Math.sin(angle);
- let dir = new THREE.Vector3(0,0,-1)
- let add = [];
- let mesh = new THREE.Mesh(geo, mat);
- let platform = new THREE.Mesh(
- new THREE.PlaneGeometry(1000,1000,1), mat
- );
- function pointIn(x, y, p1, p2, p3) {
- let det = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)
- return det * ((p2.x - p1.x) * (y - p1.y) - (p2.y - p1.y) * (x - p1.x)) > 0 &&
- det * ((p3.x - p2.x) * (y - p2.y) - (p3.y - p2.y) * (x - p2.x)) > 0 &&
- det * ((p1.x - p3.x) * (y - p3.y) - (p1.y - p3.y) * (x - p3.x)) > 0
- }
- // first, last, distance
- function fld(arr, key) {
- let first = arr[0];
- let last = arr.last();
- let dist = last[key] - first[key];
- return { first, last, dist }
- }
- // sorted range distance from key
- function rdist(range, key) {
- return range.last[key] - range.first[key];
- }
- // test area
- function ta(p1, p2, p3) {
- let sortx = [p1,p2,p3].sort((a,b) => { return a.x - b.x });
- let sorty = [p1,p2,p3].sort((a,b) => { return a.y - b.y });
- let sortz = [p1,p2,p3].sort((a,b) => { return a.z - b.z });
- let xv = fld(sortx, 'x');
- let yv = fld(sorty, 'y');
- let xa = base.util.lerp(xv.first.x + s4, xv.last.x - s4, s2, true);
- let ya = base.util.lerp(yv.first.y + s4, yv.last.y - s4, s2, true);
- for (let x of xa) {
- for (let y of ya) {
- if (pointIn(x, y, p1, p2, p3)) {
- let z = base.util.zInPlane(p1, p2, p3, x, y);
- tp(new THREE.Vector3(x, y, z));
- }
+ let tops = slice.tops,
+ down = slice.down,
+ clib = self.ClipperLib,
+ ctyp = clib.ClipType,
+ ptyp = clib.PolyType,
+ cfil = clib.PolyFillType,
+ clip = new clib.Clipper(),
+ ctre = new clib.PolyTree(),
+ poly,
+ polys = [],
+ lines = [],
+ line = [],
+ solids = [],
+ // callback passed to pluggable infill algorithm
+ target = {
+ // slice and slice property access
+ slice: function() { return slice },
+ zIndex: function() { return slice.index },
+ zValue: function() { return slice.z },
+ // various option map access
+ options: function() { return options },
+ lineWidth: function() { return options.lineWidth },
+ bounds: function() { return bounds },
+ zHeight: function() { return height },
+ offset: function() { return spacing },
+ density: function() { return density },
+ repeat: function() { return process.sliceFillRepeat },
+ // output functions
+ emit: function(x,y) {
+ if (isNaN(x)) {
+ solids.push(x);
+ } else {
+ line.push(newPoint(x, y, slice.z));
+ slice.isSparseFill = true;
+ }
+ },
+ newline: function() {
+ if (line.length > 0) {
+ lines.push(line);
+ line = [];
}
}
- }
- // test poly
- function tP(poly, face) {
- let bounds = poly.bounds;
- let xa = base.util.lerp(bounds.minx + s4, bounds.maxx - s4, s2, true);
- let ya = base.util.lerp(bounds.miny + s4, bounds.maxy - s4, s2, true);
- for (let x of xa) {
- for (let y of ya) {
- if (base.newPoint(x, y, 0).isInPolygon(poly)) {
- let z = base.util.zInPlane(face[0], face[1], face[2], x, y);
- tp(new THREE.Vector3(x, y, z));
- }
+ };
+
+ // use specified fill type
+ if (type && fill[type]) {
+ fill[type](target);
+ } else {
+ console.log({missing_infill: type});
+ return;
+ }
+
+ // force emit of last line
+ target.newline();
+
+ // prepare top infill structure
+ for (let top of tops) {
+ top.fill_sparse = top.fill_sparse || [];
+ polys.appendAll(top.fill_off);
+ polys.appendAll(top.solids);
+ }
+
+ // update fill fingerprint for this slice
+ slice._fill_finger = POLY.fingerprint(polys);
+
+ let skippable = cache && fill_fixed[type] ? true : false;
+ let miss = false;
+ // if the layer below has the same fingerprint,
+ // we may be able to clone the infill instead of regenerating it
+ if (skippable && slice.fingerprintSame(down)) {
+ // the fill fingerprint can slightly different because of solid projections
+ if (down._fill_finger && POLY.fingerprintCompare(slice._fill_finger, down._fill_finger)) {
+ for (let i=0; i 0.5) {
- let mid = new THREE.Vector3().add(point).add(int[0].point).divideScalar(2);
- add.push({from: point, to: int[0].point, mid});
- point.added = true;
- }
- }
- let filter = isBelt ? (norm) => {
- return norm.z <= thresh && norm.y < 0;
- } : (norm) => {
- return norm.z < thresh;
- };
- let { position } = geo.attributes;
- let { itemSize, count, array } = position;
- let v3cache = new Vector3Cache();
- let coplane = new Coplanars();
- for (let i = 0; i base.newPoint(v.x, v.y, v.z)));
- if (poly.area() < min && poly.perimeter() < size) {
- continue;
- }
- // skip faces on bed
- if (a.z + b.z + c.z < 0.01) {
- continue;
- }
- // match with other attached, coplanar faces
- coplane.put(a, b, c, norm.z);
- }
- let groups = coplane.group(true);
- // console.log({v3cache, coplane, groups});
- // let ptotl = Object.values(groups).flat().flat().length;
- // console.log({ptotl});
- // let pdone = 0;
- for (let group of Object.values(groups)) {
- for (let polys of group) {
- for (let poly of polys) {
- if (poly.area() >= process.sliceSupportArea)
- tP(poly, polys.face);
- // console.log(++pdone / ptotl);
- }
- }
- }
-
- widget.supports = add;
- return add.length > 0;
- };
-
- class Vector3Cache {
- constructor() {
- this.cache = {};
- }
-
- get(x, y, z) {
- let key = [x.round(4),y.round(4),z.round(4)].join(',');
- let val = this.cache[key];
- if (!val) {
- val = new THREE.Vector3(x, y, z);
- this.cache[key] = val;
- }
- return val;
}
}
- class Coplanars {
- constructor() {
- this.cache = {};
- }
+ let sparse_clip = slice.isSparseFill;
- put(a, b, c, norm) {
- let key = norm.round(7).toString();
- let arr = this.cache[key];
- if (!arr) {
- arr = [];
- this.cache[key] = arr;
+ // solid fill areas
+ if (solids.length) {
+ for (let top of tops) {
+ if (!top.fill_off) return;
+ let masks = top.fill_off.slice();
+ if (top.solids) {
+ masks = POLY.subtract(masks, top.solids, [], null, slice.z);
}
- arr.push([a,b,c]);
+ let angl = process.sliceFillAngle * ((slice.index % 2) + 1);
+ for (let solid of solids) {
+ let inter = [],
+ fillable = [];
+ for (let mask of masks) {
+ let p = solid.mask(mask);
+ if (p && p.length) inter.appendAll(p);
+ }
+ // offset fill area to accommodate trace
+ if (inter.length) {
+ POLY.expand(inter, -options.lineWidth/2, slice.z, fillable);
+ }
+ // fill intersected areas
+ if (inter.length) {
+ slice.isSparseFill = true;
+ for (let p of inter) {
+ p.forEachSegment((p1, p2) => {
+ top.fill_lines.push(p1, p2);
+ });
+ }
+ }
+ if (fillable.length) {
+ let lines = POLY.fillArea(fillable, angl, options.lineWidth);
+ top.fill_lines.appendAll(lines);
+ }
+ }
+ }
+ }
+
+ // if only solids were added and no lines to clip
+ if (!sparse_clip) {
+ return;
+ }
+
+ if (options.promises) {
+ options.promises.push(kiri.minions.clip(slice, polys, lines));
+ return;
+ }
+
+ lines = lines.map(a => a.map(p => p.toClipper()));
+ clip.AddPaths(lines, ptyp.ptSubject, false);
+ clip.AddPaths(POLY.toClipper(polys), ptyp.ptClip, true);
+
+ if (clip.Execute(ctyp.ctIntersection, ctre, cfil.pftNonZero, cfil.pftEvenOdd)) {
+ for (let node of ctre.m_AllPolys) {
+ poly = POLY.fromClipperNode(node, slice.z);
+ for (let top of tops) {
+ // use only polygons inside this top
+ if (poly.isInside(top.poly)) {
+ top.fill_sparse.push(poly);
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Find difference between fill inset poly on two adjacent layers.
+ * Used to calculate bridges, flats and then solid projections.
+ * 'expand' is used for top offsets in SLA mode
+ */
+function doDiff(slice, minArea, options = {}) {
+ const { sla, fakedown } = options;
+ if (slice.index === 0 && !fakedown) {
+ return;
+ }
+ const top = slice,
+ down = slice.down || (fakedown ? newSlice(-1) : null),
+ topInner = sla ? top.topPolys() : top.topInners(),
+ downInner = sla ? down.topPolys() : down.topInners(),
+ bridges = top.bridges = [],
+ flats = down.flats = [];
+
+ // skip diffing layers that are identical
+ if (slice.fingerprintSame(down)) {
+ top.bridges = bridges;
+ down.flats = flats;
+ return;
+ }
+
+ POLY.subtract(topInner, downInner, bridges, flats, slice.z, minArea, {
+ wasm: true
+ });
+};
+
+/**
+ *
+ *
+ * @param {Polygon[]} polys
+ */
+function addSolidFills(slice, polys) {
+ if (slice.solids) {
+ slice.solids.appendAll(polys);
+ } else if (polys && polys.length) {
+ console.log({no_solids_in: slice, for: polys})
+ }
+};
+
+/**
+ * project bottom flats down
+ */
+function projectFlats(slice, count) {
+ if (slice.isSolidLayer || !slice.down || !slice.flats) return;
+ projectSolid(slice, slice.flats, count, false, true);
+};
+
+/**
+ * project top bridges up
+ */
+function projectBridges(slice, count) {
+ if (slice.isSolidLayer || !slice.up || !slice.bridges) return;
+ projectSolid(slice, slice.bridges, count, true, true);
+};
+
+/**
+ * fill projected areas and store line data
+ * @return {boolean} true if filled, false if not
+ */
+function doSolidsFill(slice, spacing, angle, minArea, fillQ) {
+ let minarea = minArea || 1,
+ tops = slice.tops,
+ solids = slice.solids;
+
+ if (!(tops && solids)) {
+ return;
+ }
+
+ let unioned = POLY.union(solids, undefined, true, { wasm: true }).flat(),
+ isSLA = (spacing === undefined && angle === undefined);
+
+ if (solids.length === 0) return false;
+ if (unioned.length === 0) return false;
+
+ let trims = [],
+ inner = isSLA ? slice.topPolys() : slice.topFillOff();
+
+ // trim each solid to the inner bounds
+ for (let p of unioned) {
+ p.setZ(slice.z);
+ for (let i of inner) {
+ let masks = p.mask(i);
+ if (masks && masks.length > 0) {
+ trims.appendAll(masks);
+ }
+ }
+ }
+
+ // clear old solids and make array for new
+ tops.forEach(top => { top.solids = [] });
+
+ // replace solids with merged and trimmed solids
+ slice.solids = solids = trims;
+
+ // parent each solid polygon inside the smallest bounding top
+ for (let solid of solids) {
+ for (let top of tops) {
+ if (top.poly.overlaps(solid)) {
+ if (!solid.parent || solid.parent.area() > top.poly.area()) {
+ if (solid.areaDeep() < minarea) {
+ // console.log({i:slice.index,cull_solid:solid,area:solid.areaDeep()});
+ continue;
+ }
+ solid.parent = top.poly;
+ top.solids.push(solid);
+ }
+ }
+ }
+ }
+
+ // for SLA to bypass line infill
+ if (isSLA) {
+ return true;
+ }
+
+ // create empty filled line array for each top
+ for (let top of tops) {
+ // synth belt anchor tops don't want fill
+ if (!top.fill_lines) {
+ continue;
+ }
+ const tofill = [];
+ const angfill = [];
+ const newfill = top.fill_lines = [];
+ // determine fill orientation from top
+ for (let solid of solids) {
+ if (solid.parent === top.poly) {
+ if (solid.fillang) {
+ angfill.push(solid);
+ } else {
+ tofill.push(solid);
+ }
+ }
+ }
+ if (tofill.length > 0) {
+ doFillArea(fillQ, tofill, angle, spacing, newfill);
+ // top.fill_lines_norm = {angle:angle,spacing:spacing};
+ }
+ if (angfill.length > 0) {
+ top.fill_lines_ang = {spacing:spacing,list:[],poly:[]};
+ for (let af of angfill) {
+ doFillArea(fillQ, [af], af.fillang.angle + 45, spacing, newfill);
+ // top.fill_lines_ang.list.push(af.fillang.angle + 45);
+ // top.fill_lines_ang.poly.push(af.clone());
+ }
+ }
+ }
+}
+
+function doFillArea(fillQ, polys, angle, spacing, output, minLen, maxLen) {
+ if (fillQ) {
+ fillQ.push(kiri.minions.fill(polys, angle, spacing, output, minLen, maxLen));
+ } else {
+ POLY.fillArea(polys, angle, spacing, output, minLen, maxLen);
+ }
+}
+
+/**
+ * calculate external overhangs requiring support
+ */
+async function doSupport(slice, proc, shadow, opt = {}) {
+ let maxBridge = proc.sliceSupportSpan || 5,
+ minArea = proc.supportMinArea || 0.1,
+ pillarSize = proc.sliceSupportSize,
+ offset = proc.sliceSupportOffset || 0,
+ gap = proc.sliceSupportGap,
+ size = (pillarSize || 1),
+ tops = slice.topPolys(),
+ trimTo = tops;
+
+ let traces = POLY.flatten(slice.topShells().clone(true)),
+ fill = slice.topFill(),
+ points = [],
+ down = slice.down,
+ down_tops = down ? down.topPolys() : null,
+ down_traces = down ? POLY.flatten(down.topShells().clone(true)) : null;
+
+ if (opt.exp && down_tops) {
+ let points = down_tops.map(p => p.deepLength).reduce((a,v)=>a+v);
+ if (points > 200) {
+ // use de-rez'd top shadow instead
+ down_tops = down.topSimples();
+ // de-rez trace polys because it's not that important for supports
+ down_traces = down_traces.map(p => p.clean(true, undefined, config.clipper / 10));
+ }
+ }
+
+ // DEBUG code
+ let SDBG = false;
+ let cks = SDBG ? [] : undefined;
+ let pip = SDBG ? [] : undefined;
+ let pcl = SDBG ? [] : undefined;
+
+ // check if point is supported by layer below
+ function checkPointSupport(point) {
+ if (SDBG) cks.push(point); // DEBUG
+ // skip points close to other support points
+ for (let i=0; i= maxBridge) {
+ let slope = p1.slopeTo(p2).factor(1/dist),
+ segs = Math.floor(dist / maxBridge) + 1,
+ seglen = dist / segs;
+ while (i < segs) {
+ checkPointSupport(p1.projectOnSlope(slope, i++ * seglen));
+ }
+ }
+ if (poly) checkPointSupport(p2);
+ }
+
+ let supports = [];
+
+ // generate support polys from unsupported points
+ if (slice.down) (function() {
+ // check trace line support needs
+ traces.forEach(function(trace) {
+ trace.forEachSegment(function(p1, p2) { checkLineSupport(p1, p2, true) });
+ });
+
+ // add offset solids to supports (or fill depending)
+ fill.forEachPair(function(p1,p2) { checkLineSupport(p1, p2, false) });
+
+ // skip the rest if no points or supports
+ if (!(points.length || supports.length)) return;
+
+ let pillars = [];
+
+ // for each point, create a bounding rectangle
+ points.forEach(function(point) {
+ pillars.push(base.newPolygon().centerRectangle(point, size/2, size/2));
+ });
+
+ supports.appendAll(POLY.union(pillars, null, true, { wasm: false }));
+ // merge pillars and replace with convex hull of outer points (aka smoothing)
+ pillars = POLY.union(pillars, null, true, { wasm: false }).forEach(function(pillar) {
+ supports.push(base.newPolygon().createConvexHull(pillar.points));
+ });
+ })();
+
+ // DEBUG code
+ if (SDBG && down_traces) slice.output()
+ .setLayer('cks', { line: 0xee5533, check: 0xee5533 })
+ .addPolys(cks.map(p => base.newPolygon().centerRectangle(p, 0.25, 0.25)))
+ .setLayer('pip', { line: 0xdd4422, check: 0xdd4422 })
+ .addPolys(pip.map(p => base.newPolygon().centerRectangle(p, 0.4, 0.4)))
+ .setLayer('pcl', { line: 0xcc3311, check: 0xcc3311 })
+ .addPolys(pcl.map(p => base.newPolygon().centerRectangle(p, 0.3, 0.3)))
+ .setLayer('pts', { line: 0xdd33dd, check: 0xdd33dd })
+ .addPolys(points.map(p => base.newPolygon().centerRectangle(p, 0.8, 0.8)))
+ .setLayer('dtr', { line: 0x0, check: 0x0 })
+ .addPolys(POLY.setZ(down_traces.clone(true),slice.z));
+ ;
+
+ if (supports.length === 0) {
+ return;
+ }
+
+ // then union supports
+ if (supports.length > 10) {
+ supports = await kiri.minions.union(supports);
+ } else {
+ supports = POLY.union(supports, null, true, { wasm: false });
+ }
+
+ // clip to top polys
+ supports = POLY.trimTo(supports, shadow);
+
+ let depth = 0;
+ while (down && supports.length > 0) {
+ down.supports = down.supports || [];
+
+ let trimmed = [], culled = [];
+
+ // culled = supports;
+ // clip supports to shell offsets
+ POLY.subtract(supports, down.topSimples(), trimmed, null, slice.z, minArea, { wasm: false });
+
+ // set depth hint on support polys for infill density
+ trimmed.forEach(function(trim) {
+ if (trim.area() < minArea) return;
+ culled.push(trim.setZ(down.z));
+ });
+
+ // exit when no more support polys exist
+ if (culled.length === 0) break;
+
+ // new bridge polys for next pass (skip first layer below)
+ if (depth >= gap) {
+ down.supports.appendAll(culled);
}
- group(union) {
- let out = {};
- for (let norm in this.cache) {
- let arr = this.cache[norm];
- let groups = [];
- for (let face of arr) {
- let match = undefined;
- // see if face matches vertices in any group
- outer: for (let group of groups) {
- for (let el of group) {
- if (
- el.indexOf(face[0]) >= 0 ||
- el.indexOf(face[1]) >= 0 ||
- el.indexOf(face[2]) >= 0
- ) {
- match = group;
- break outer;
- }
+ supports = culled;
+ down = down.down;
+ depth++;
+ }
+
+}
+
+function doSupportFill(promises, slice, linewidth, density, minArea, isBelt) {
+ let supports = slice.supports,
+ nsB = [],
+ nsC = [],
+ min = minArea || 0.1;
+
+ 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, nsB, null, slice.z, min, { wasm: false });
+ supports = nsB;
+
+ // also trim to lower offsets, if they exist
+ if (slice.down && slice.down.clips) {
+ POLY.subtract(nsB, slice.down.clips, nsC, null, slice.z, min, { wasm: false });
+ supports = nsC;
+ }
+
+ if (supports) {
+ fillSupportPolys(promises, supports, linewidth, density, slice.z, isBelt);
+ }
+
+ // re-assign new supports back to slice
+ slice.supports = supports;
+};
+
+function fillSupportPolys(promises, polys, linewidth, density, z, isBelt) {
+ // calculate fill density
+ let spacing = linewidth * (1 / density);
+ polys.forEach(function (poly) {
+ // angle based on width/height ratio
+ let angle = isBelt || (poly.bounds.width() / poly.bounds.height() > 1) ? 90 : 0;
+ // inset support poly for fill lines 33% of nozzle width
+ let inset = POLY.offset([poly], -linewidth/3, {flat: true, z, wasm: true});
+ // do the fill
+ if (inset && inset.length > 0) {
+ doFillArea(promises, inset, angle, spacing, poly.fill = []);
+ }
+ return true;
+ });
+}
+
+/**
+ *
+ * @param {Slice} slice
+ * @param {Polygon[]} polys
+ * @param {number} count
+ * @param {boolean} up
+ * @param {boolean} first
+ * @returns {*}
+ */
+function projectSolid(slice, polys, count, up, first) {
+ if (!slice || slice.isSolidLayer || count <= 0) {
+ return;
+ }
+ let clones = polys.clone(true);
+ if (first) {
+ clones.forEach(function(p) {
+ p.hintFillAngle();
+ });
+ }
+ addSolidFills(slice, clones);
+ if (count > 0) {
+ if (up) projectSolid(slice.up, polys, count-1, true, false);
+ else projectSolid(slice.down, polys, count-1, false, false);
+ }
+}
+
+/**
+ * given an array of arrays of points (lines), eliminate intersections
+ * between groups, then return a unified array of shortest non-intersects.
+ *
+ * @returns {Point[]}
+ */
+function cullIntersections() {
+ function toLines(pts) {
+ let lns = [];
+ for (let i=0, il=pts.length; i t);
+ if (aOa.length < 1) return;
+ let aa = toLines(aOa.shift());
+ while (aOa.length) {
+ let bb = toLines(aOa.shift());
+ loop: for (let i=0, il=aa.length; i !l.del).concat(bb.filter(l => !l.del));
+ }
+ let good = [];
+ for (let i=0, il=aa.length; i 2 ? good : [];
+}
+
+FDM.supports = function(settings, widget) {
+ let isBelt = settings.device.bedBelt;
+ let process = settings.process;
+ let size = process.sliceSupportSize;
+ let s4 = size / 4;
+ let s2 = size * 0.45;
+ let min = 0.01;
+ let geo = new THREE.BufferGeometry();
+ geo.setAttribute('position', new THREE.BufferAttribute(widget.vertices, 3));
+ let mat = new THREE.MeshBasicMaterial();
+ let rad = (Math.PI / 180);
+ let deg = (180 / Math.PI);
+ let angle = rad * settings.process.sliceSupportAngle;
+ let thresh = -Math.sin(angle);
+ let dir = new THREE.Vector3(0,0,-1)
+ let add = [];
+ let mesh = new THREE.Mesh(geo, mat);
+ let platform = new THREE.Mesh(
+ new THREE.PlaneGeometry(1000,1000,1), mat
+ );
+ function pointIn(x, y, p1, p2, p3) {
+ let det = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)
+ return det * ((p2.x - p1.x) * (y - p1.y) - (p2.y - p1.y) * (x - p1.x)) > 0 &&
+ det * ((p3.x - p2.x) * (y - p2.y) - (p3.y - p2.y) * (x - p2.x)) > 0 &&
+ det * ((p1.x - p3.x) * (y - p3.y) - (p1.y - p3.y) * (x - p3.x)) > 0
+ }
+ // first, last, distance
+ function fld(arr, key) {
+ let first = arr[0];
+ let last = arr.last();
+ let dist = last[key] - first[key];
+ return { first, last, dist }
+ }
+ // sorted range distance from key
+ function rdist(range, key) {
+ return range.last[key] - range.first[key];
+ }
+ // test area
+ function ta(p1, p2, p3) {
+ let sortx = [p1,p2,p3].sort((a,b) => { return a.x - b.x });
+ let sorty = [p1,p2,p3].sort((a,b) => { return a.y - b.y });
+ let sortz = [p1,p2,p3].sort((a,b) => { return a.z - b.z });
+ let xv = fld(sortx, 'x');
+ let yv = fld(sorty, 'y');
+ let xa = base.util.lerp(xv.first.x + s4, xv.last.x - s4, s2, true);
+ let ya = base.util.lerp(yv.first.y + s4, yv.last.y - s4, s2, true);
+ for (let x of xa) {
+ for (let y of ya) {
+ if (pointIn(x, y, p1, p2, p3)) {
+ let z = base.util.zInPlane(p1, p2, p3, x, y);
+ tp(new THREE.Vector3(x, y, z));
+ }
+ }
+ }
+ }
+ // test poly
+ function tP(poly, face) {
+ let bounds = poly.bounds;
+ let xa = base.util.lerp(bounds.minx + s4, bounds.maxx - s4, s2, true);
+ let ya = base.util.lerp(bounds.miny + s4, bounds.maxy - s4, s2, true);
+ for (let x of xa) {
+ for (let y of ya) {
+ if (base.newPoint(x, y, 0).isInPolygon(poly)) {
+ let z = base.util.zInPlane(face[0], face[1], face[2], x, y);
+ tp(new THREE.Vector3(x, y, z));
+ }
+ }
+ }
+ }
+ // test point
+ function tp(point) {
+ if (point.added) {
+ return;
+ }
+ // omit pillars close to existing pillars
+ for (let added of add) {
+ let p2 = new THREE.Vector2(point.x, point.y);
+ let pm = new THREE.Vector2(added.mid.x, added.mid.y);
+ if (Math.abs(point.z - added.from.z) < s2 && p2.distanceTo(pm) < s4) {
+ return;
+ }
+ }
+ let ray = new THREE.Raycaster(point, dir);
+ let int = ray.intersectObjects([ mesh, platform ], false);
+ if (int && int.length && int[0].distance > 0.5) {
+ let mid = new THREE.Vector3().add(point).add(int[0].point).divideScalar(2);
+ add.push({from: point, to: int[0].point, mid});
+ point.added = true;
+ }
+ }
+ let filter = isBelt ? (norm) => {
+ return norm.z <= thresh && norm.y < 0;
+ } : (norm) => {
+ return norm.z < thresh;
+ };
+ let { position } = geo.attributes;
+ let { itemSize, count, array } = position;
+ let v3cache = new Vector3Cache();
+ let coplane = new Coplanars();
+ for (let i = 0; i base.newPoint(v.x, v.y, v.z)));
+ if (poly.area() < min && poly.perimeter() < size) {
+ continue;
+ }
+ // skip faces on bed
+ if (a.z + b.z + c.z < 0.01) {
+ continue;
+ }
+ // match with other attached, coplanar faces
+ coplane.put(a, b, c, norm.z);
+ }
+ let groups = coplane.group(true);
+ // console.log({v3cache, coplane, groups});
+ // let ptotl = Object.values(groups).flat().flat().length;
+ // console.log({ptotl});
+ // let pdone = 0;
+ for (let group of Object.values(groups)) {
+ for (let polys of group) {
+ for (let poly of polys) {
+ if (poly.area() >= process.sliceSupportArea)
+ tP(poly, polys.face);
+ // console.log(++pdone / ptotl);
+ }
+ }
+ }
+
+ widget.supports = add;
+ return add.length > 0;
+};
+
+class Vector3Cache {
+ constructor() {
+ this.cache = {};
+ }
+
+ get(x, y, z) {
+ let key = [x.round(4),y.round(4),z.round(4)].join(',');
+ let val = this.cache[key];
+ if (!val) {
+ val = new THREE.Vector3(x, y, z);
+ this.cache[key] = val;
+ }
+ return val;
+ }
+}
+
+class Coplanars {
+ constructor() {
+ this.cache = {};
+ }
+
+ put(a, b, c, norm) {
+ let key = norm.round(7).toString();
+ let arr = this.cache[key];
+ if (!arr) {
+ arr = [];
+ this.cache[key] = arr;
+ }
+ arr.push([a,b,c]);
+ }
+
+ group(union) {
+ let out = {};
+ for (let norm in this.cache) {
+ let arr = this.cache[norm];
+ let groups = [];
+ for (let face of arr) {
+ let match = undefined;
+ // see if face matches vertices in any group
+ outer: for (let group of groups) {
+ for (let el of group) {
+ if (
+ el.indexOf(face[0]) >= 0 ||
+ el.indexOf(face[1]) >= 0 ||
+ el.indexOf(face[2]) >= 0
+ ) {
+ match = group;
+ break outer;
}
}
- if (match) {
- match.push(face);
- } else {
- groups.push([face]);
- }
}
- if (union) {
- // convert groups of faces to contiguous polygon groups
- groups = groups.map(group => {
- let parr = group.map(arr => {
- return base.newPolygon()
- .add(arr[0].x, arr[0].y, arr[0].z)
- .add(arr[1].x, arr[1].y, arr[1].z)
- .add(arr[2].x, arr[2].y, arr[2].z);
- });
- let union = parr.length === 1 ? parr :
- POLY.union(parr, 0, true, {wasm:false});
- union.merged = parr.length;
- union.face = group[0];
- return union;
- });
+ if (match) {
+ match.push(face);
+ } else {
+ groups.push([face]);
}
- out[norm] = groups;
}
- // console.log(out);
- return out;
+ if (union) {
+ // convert groups of faces to contiguous polygon groups
+ groups = groups.map(group => {
+ let parr = group.map(arr => {
+ return base.newPolygon()
+ .add(arr[0].x, arr[0].y, arr[0].z)
+ .add(arr[1].x, arr[1].y, arr[1].z)
+ .add(arr[2].x, arr[2].y, arr[2].z);
+ });
+ let union = parr.length === 1 ? parr :
+ POLY.union(parr, 0, true, {wasm:false});
+ union.merged = parr.length;
+ union.face = group[0];
+ return union;
+ });
+ }
+ out[norm] = groups;
}
+ // console.log(out);
+ return out;
}
+}
})();
diff --git a/src/kiri/consts.js b/src/kiri/consts.js
index 7a5c2d37..6b36c070 100644
--- a/src/kiri/consts.js
+++ b/src/kiri/consts.js
@@ -131,6 +131,7 @@ kiri.consts = {
MODES,
VIEWS,
SEED,
+ beltfact: Math.cos(Math.PI / 4)
};
})();
diff --git a/src/kiri/main.js b/src/kiri/main.js
index aa1ac760..053412c8 100644
--- a/src/kiri/main.js
+++ b/src/kiri/main.js
@@ -10,7 +10,7 @@
const { feature, platform, selection, settings } = api;
const { COLOR, MODES, PMODES, VIEWS } = consts;
- const LANG = lang.current,
+ const LANG = lang.current,
WIN = self.window,
DOC = self.document,
LOC = self.location,
diff --git a/src/kiri/print.js b/src/kiri/print.js
index 2d1c3700..ce7bb9ef 100644
--- a/src/kiri/print.js
+++ b/src/kiri/print.js
@@ -7,6 +7,8 @@
const { base, kiri } = self;
const { paths, util, newPoint, Polygon } = base;
const { tip2tipEmit } = paths;
+const { numOrDefault } = util;
+const { beltfact } = kiri.consts;
kiri.newPrint = function(settings, widgets, id) {
return new Print(settings, widgets, id);
@@ -70,7 +72,7 @@ class Print {
const { process } = settings;
let shortDist = process.outputShortDistance,
- shellMult = pref(options.extrude, process.outputShellMult),
+ shellMult = numOrDefault(options.extrude, process.outputShellMult),
printSpeed = options.rate || process.outputFeedrate,
moveSpeed = process.outputSeekrate,
minSpeed = process.outputMinSpeed,
@@ -169,638 +171,6 @@ class Print {
}
}
- // fdm only
- slicePrintPath(slice, startPoint, offset, output, opt = {}) {
- const scope = this;
- const { settings } = scope;
- const { device } = settings;
-
- // console.log({slicePrintPath: slice.index, ext:slice.extruder});
- let i,
- preout = [],
- process = opt.params || settings.process,
- extruder = slice.extruder || 0,
- nozzleSize = device.extruders[extruder].extNozzle,
- firstLayer = opt.first || false,
- thinWall = nozzleSize * (opt.thinWall || 1.75),
- retractDist = opt.retractOver || 2,
- solidWidth = process.sliceFillWidth || 1,
- fillMult = opt.mult || process.outputFillMult,
- shellMult = opt.mult || process.outputShellMult || (process.laserSliceHeight >= 0 ? 1 : 0),
- shellOrder = {"out-in":-1,"in-out":1}[process.sliceShellOrder] || -1,
- sparseMult = process.outputSparseMult,
- coastDist = process.outputCoastDist || 0,
- finishSpeed = opt.speed || process.outputFinishrate,
- firstShellSpeed = process.firstLayerRate,
- firstFillSpeed = process.firstLayerFillRate,
- firstPrintMult = process.firstLayerPrintMult,
- printSpeed = opt.speed || (firstLayer ? firstShellSpeed : process.outputFeedrate),
- fillSpeed = opt.speed || opt.fillSpeed || (firstLayer ? firstFillSpeed || firstShellSpeed : process.outputFeedrate),
- infillSpeed = process.sliceFillRate || opt.infillSpeed || fillSpeed || printSpeed,
- moveSpeed = process.outputSeekrate,
- origin = startPoint.add(offset),
- zhop = process.zHopDistance || 0,
- antiBacklash = process.antiBacklash,
- wipeDist = process.outputRetractWipe || 0,
- isBelt = device.bedBelt,
- beltFirst = process.outputBeltFirst || false,
- startClone = startPoint.clone(),
- seedPoint = opt.seedPoint || startPoint,
- z = slice.z,
- lastPoly;
-
- // apply first layer extrusion multipliers
- if (firstLayer) {
- fillMult *= firstPrintMult;
- shellMult *= firstPrintMult;
- sparseMult *= firstPrintMult;
- }
-
- function retract() {
- let array = preout.length ? preout : output;
- if (array.length) {
- let last = array.last();
- last.retract = true;
- if (wipeDist && lastPoly && last.point) {
- let endpoint = last.point.followTo(lastPoly.center(true), wipeDist);
- if (endpoint.inPolygon(lastPoly)) {
- scope.addOutput(array, endpoint);
- }
- }
- } else if (opt.pretract) {
- opt.pretract(wipeDist);
- } else {
- console.log('unable to retract. no preout or output');
- }
- }
-
- function intersectsTop(p1, p2) {
- if (slice.index < 0) {
- return false;
- }
- let int = false;
- slice.topPolysFlat().forEach((poly) => {
- if (!int) poly.forEachSegment((s1, s2) => {
- if (util.intersect(p1,p2,s1,s2,base.key.SEGINT)) {
- return int = true;
- }
- });
- });
- // if intersecting, look for a route around
- if (int && opt.routeAround) {
- return !routeAround(p1, p2);
- }
- return int;
- }
-
- // returns true if routed around or no retract requried
- function routeAround(p1, p2) {
- const dbug = false;
- if (dbug === slice.index) console.log(slice.index, {p1, p2, d: p1.distTo2D(p2)});
-
- let ints = [];
- let tops = slice.topRouteFlat();
- for (let poly of tops) {
- poly.forEachSegment((s1, s2) => {
- let ip = util.intersect(p1,p2,s1,s2,base.key.SEGINT);
- if (ip) {
- ints.push({ip, poly});
- }
- });
- }
-
- // no intersections
- if (ints.length === 0) {
- if (dbug === slice.index) console.log(slice.index, 'no ints');
- return false;
- }
-
- // odd # of intersections ?!? do retraction
- if (ints.length && ints.length % 2 !== 0) {
- if (dbug === slice.index) console.log(slice.index, {odd_intersects: ints});
- return false;
- }
-
- // sort by distance
- ints.sort((a, b) => {
- return a.ip.dist - b.ip.dist;
- });
-
- if (dbug === slice.index) console.log(slice.index, {ints});
-
- // check pairs. eliminate too close points.
- // pairs must intersect same poly or retract.
- for (let i=0; i i);
-
- if (ints.length > 2) {
- if (dbug === slice.index) console.log(slice.index, {complex_route: ints.length});
- return false;
- }
-
- if (ints.length === 2) {
- // can route around intersected top polys
- for (let i=0; ii.ip.dist),
- i1, i2, same: i1.poly === i2.poly,
- route,
- p1, p2, dist: p1.distTo2D(p2),
- r1, r1d, r1s, r1e,
- r2, r2d, r2s, r2e,
- isCW});
-
- for (let p of route) {
- scope.addOutput(preout, p, 0, moveSpeed, extruder);
- }
-
- // output last point
- scope.addOutput(preout, i2.ip, 0, moveSpeed, extruder);
- }
- return true;
- }
-
- return false;
- }
-
- function outputTraces(poly, opt = {}) {
- if (!poly) return;
- if (Array.isArray(poly)) {
- if (opt.sort) {
- let polys = poly.slice().sort((a,b) => {
- return (a.perimeter() - b.perimeter()) * opt.sort;
- });
- let debug = polys.length > 3;
- let last;
- while (polys.length) {
- let next;
- for (let p of polys) {
- if (!last) {
- next = p;
- break;
- }
- if (opt.sort > 0) {
- // in-out
- if (last.isInside(p)) {
- next = p;
- break;
- }
- } else {
- // out-in
- if (p.isInside(last)) {
- next = p;
- break;
- }
- }
- }
- if (next) {
- last = next;
- polys.remove(next);
- outputTraces(next, opt);
- } else {
- last = null;
- }
- }
- } else {
- outputOrderClosest(poly, function(next) {
- outputTraces(next, opt);
- }, null);
- }
- } else {
- let finishShell = poly.depth === 0 && !firstLayer;
- startPoint = scope.polyPrintPath(poly, startPoint, preout, {
- tool: extruder,
- rate: finishShell ? finishSpeed : printSpeed,
- accel: finishShell,
- wipe: process.outputWipeDistance || 0,
- coast: firstLayer ? 0 : coastDist,
- extrude: pref(opt.extrude, shellMult),
- onfirst: function(firstPoint) {
- let from = seedPoint || startPoint;
- if (from.distTo2D(firstPoint) > retractDist) {
- if (intersectsTop(from, firstPoint)) {
- retract();
- }
- }
- seedPoint = null;
- }
- });
- lastPoly = slice.lastPoly = poly;
- }
- }
-
- /**
- * @param {Polygon[]} polys
- */
- function outputSparse(polys, extrude, speed) {
- if (!polys) return;
- let proxy = polys.map((poly) => {
- return {poly: poly, first: poly.first(), last: poly.last()};
- });
- let lp = startPoint;
- startPoint = tip2tipEmit(proxy, startPoint, (el, point, count) => {
- let poly = el.poly;
- if (poly.last() === point) {
- poly.reverse();
- }
- poly.forEachPoint((p, i) => {
- let dist = lp.distTo2D(p);
- let rdst = dist > retractDist;
- let itop = rdst && intersectsTop(lp,p);
- let emit = extrude;
- // retract if dist trigger and crosses a slice top polygon
- if (i === 0) {
- if (itop) {
- retract();
- emit = 0;
- } else if (dist > nozzleSize) {
- emit = 0;
- }
- }
- // let emit = i === 0 ? 0 : extrude;
- // handle shallow cloned infill
- if (poly.z !== undefined) {
- p = p.clone().setZ(poly.z);
- }
- scope.addOutput(preout, p, emit, speed || printSpeed, extruder);
- lp = p;
- }, !poly.open);
- return lp;
- });
- }
-
- function outputThin(lines) {
- if (!lines) {
- return;
- }
- let points = lines.group(2).map(grp => {
- let [ p1, p2 ] = grp;
- return newPoint(
- (p1.x + p2.x) / 2,
- (p1.y + p2.y) / 2,
- (p1.z + p2.z) / 2
- )
- });
- let order = util.orderClosest(points, (p1, p2) => p1.distTo2D(p2));
- if (order.length === 0) {
- return;
- }
- let first = points[0];
- let last = first;
- scope.addOutput(preout, first, 0, moveSpeed, extruder);
- for (let p of order) {
- let dist = last ? last.distTo2D(p) : 0;
- if (dist > thinWall) {
- retract();
- scope.addOutput(preout, p, 0, moveSpeed, extruder);
- }
- scope.addOutput(preout, p, 1, fillSpeed, extruder);
- last = p;
- }
- // close a circle
- if (last && last.distTo2D(first) <= thinWall) {
- scope.addOutput(preout, first, 1, fillSpeed, extruder);
- }
- }
-
- function outputFills(lines, opt = {}) {
- if (!lines || lines.length === 0) {
- return;
- }
- let p, p1, p2, dist, len, found, group, mindist, t1, t2,
- marked = 0,
- start = 0,
- skip = false,
- lastIndex = -1,
- flow = opt.flow || 1,
- near = opt.near || false,
- fast = opt.fast || false,
- fill = (opt.fill >= 0 ? opt.fill : fillMult) * flow,
- thinDist = near ? thinWall : thinWall;
-
- while (lines && marked < lines.length) {
- group = null;
- found = false;
- mindist = Infinity;
-
- // use next nearest line strategy
- if (near)
- for (i=0; i lastIndex) {
- group = p.index;
- }
- if (group !== null) {
- if (p.index !== group) {
- break;
- }
- if (p.index % 2 === 0) {
- t1 = lines[i];
- t2 = lines[i+1];
- } else {
- t2 = lines[i];
- t1 = lines[i+1];
- }
- dist = Math.min(t1.distTo2D(startPoint), t2.distTo2D(startPoint));
- if (dist < mindist) {
- p1 = t1;
- p2 = t2;
- mindist = dist;
- }
- start = i;
- found = true;
- }
- }
-
- // go back to start and try again
- if (!near && !found) {
- if (start === 0 && lastIndex === -1) {
- console.log('infinite loop', lines, {
- marked, i, group, start, lastIndex,
- points: lines.map(p => p.index).join(', ')
- });
- break;
- }
- start = 0;
- lastIndex = -1;
- continue;
- }
-
- dist = startPoint.distToSq2D(p1);
- len = p1.distToSq2D(p2);
-
- // go back to start when dist > retractDist
- if (!near && !fast && !skip && dist > retractDist) {
- skip = true;
- start = 0;
- lastIndex = -1;
- continue;
- }
- skip = false;
-
- // mark as used (temporarily)
- p1.del = true;
- p2.del = true;
- marked += 2;
- lastIndex = p1.index;
-
- // if dist to new segment is less than thinWall
- // and segment length is less than thinWall then
- // just extrude to midpoint of next segment. this is
- // to avoid shaking the printer to death.
- if (dist <= thinDist && len <= thinDist) {
- p2 = p1.midPointTo(p2);
- // this.addOutput(preout, p2, fill * (dist / thinWall), fillSpeed, extruder);
- scope.addOutput(preout, p2, fill, fillSpeed, extruder);
- } else {
- // retract if dist trigger or crosses a slice top polygon
- if (!fast && dist > retractDist && (zhop || intersectsTop(startPoint, p1))) {
- retract();
- }
-
- // anti-backlash on longer move
- if (!fast && antiBacklash && dist > retractDist) {
- scope.addOutput(preout, p1.add({x:antiBacklash,y:-antiBacklash,z:0}), 0, moveSpeed, extruder);
- }
-
- // bridge ends of fill when they're close together
- if (dist < thinDist) {
- scope.addOutput(preout, p1, fill, fillSpeed, extruder);
- } else {
- scope.addOutput(preout, p1, 0, moveSpeed, extruder);
- }
-
- scope.addOutput(preout, p2, fill, fillSpeed, extruder);
- }
-
- startPoint = p2;
- }
-
- // clear delete marks so we can re-print later
- if (lines) lines.forEach(p => { p.del = false });
- }
-
- /**
- * given array of polygons, emit them in next closest order with
- * the special exception that depth is considered into distance
- * so that inner polygons are emitted first.
- *
- * @param {Array} array of Polygons or Polygon wrappers (tops)
- * @param {Function} fn call to emit next candidate
- * @param {Function} fnp convert 'next' object into a Polygon for closeness
- */
- function outputOrderClosest(array, fn, fnp) {
- if (array.length === 1) {
- return fn(array[0]);
- }
- array = array.slice();
- let closest, find, next, order, poly, lastDepth = 0;
- for (;;) {
- order = [];
- closest = null;
- for (i=0; i {
- return a.d - b.d;
- });
- array[order[0].i] = null;
- fn(order[0].n);
- }
- }
-
- let out = [];
- if (slice.tops) {
- out.appendAll(slice.tops);
- };
- if (opt.support && slice.supports) {
- out.appendAll(slice.supports);
- }
-
- let lastTop = null;
- outputOrderClosest(out, function(next) {
- if (next instanceof Polygon) {
- scope.setType('support');
- // support polygon
- next.setZ(z);
- outputTraces([next].appendAll(next.inner || []));
- if (next.fill) {
- next.fill.forEach(p => { p.z = z });
- outputFills(next.fill, {fast: true});
- }
- } else {
- scope.setType('shells');
- if (lastTop && lastTop !== next) {
- retract();
- }
-
- // control of layer start point
- switch (process.sliceLayerStart) {
- case "center":
- startPoint = newPoint(0,0,startPoint.z);
- break;
- case "origin":
- startPoint = origin.clone();
- break;
- }
-
- // optimize start point on belt for tops touching belt
- // and enforce optimal shell order (outer first)
- if (isBelt && opt.onBelt) {
- startPoint = startClone;
- if (beltFirst) {
- shellOrder = -1;
- }
- }
-
- // innermost shells
- let inner = next.innerShells() || [];
-
- // output inner polygons
- if (shellOrder === 1) outputTraces(inner, { sort: shellOrder });
-
- outputTraces(next.shells, { sort: shellOrder });
-
- // output outer polygons
- if (shellOrder === -1) outputTraces(inner, { sort: shellOrder });
-
- // output thin fill
- scope.setType('thin fill');
- outputThin(next.thin_fill);
-
- // then output solid and sparse fill
- scope.setType('solid fill');
- outputFills(next.fill_lines, {flow: solidWidth});
-
- scope.setType('sparse infill');
- outputSparse(next.fill_sparse, sparseMult, infillSpeed);
-
- lastTop = next;
- }
- }, function(obj) {
- // for tops
- return obj instanceof Polygon ? obj : obj.poly;
- });
-
- // produce polishing paths when present
- if (slice.tops.length && slice.tops[0].polish) {
- let {x,y} = slice.tops[0].polish;
- if (x) {
- outputSparse(x, 0, process.polishSpeed);
- }
- if (y) {
- outputSparse(y, 0, process.polishSpeed);
- }
- }
-
- // offset print points
- for (i=0; i Object.clone(o)));
@@ -1181,10 +550,6 @@ class Output {
}
}
-function pref(a,b) {
- return a !== undefined ? a : b;
-}
-
kiri.Print = Print;
})();