move src2 -> src, web2 -> web, remove old module system, fix deps
This commit is contained in:
parent
1d821d4e24
commit
ce4f5d6e78
488 changed files with 26179 additions and 26609 deletions
681
src.old/kiri-mode/fdm/client.js
Normal file
681
src.old/kiri-mode/fdm/client.js
Normal file
|
|
@ -0,0 +1,681 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: add.three
|
||||
// dep: geo.base
|
||||
// dep: moto.space
|
||||
// dep: kiri.api
|
||||
// dep: kiri.lang
|
||||
// dep: kiri.consts
|
||||
// dep: kiri-mode.fdm.driver
|
||||
gapp.register("kiri-mode.fdm.client", [], (root, exports) => {
|
||||
|
||||
const { Quaternion, Matrix4, Vector3, BufferAttribute, BufferGeometryUtils } = THREE;
|
||||
const { Mesh, BoxGeometry, MeshPhongMaterial, Raycaster, Euler } = THREE;
|
||||
const { base, kiri, moto } = root;
|
||||
const { util } = base;
|
||||
const { api, consts, lang } = kiri;
|
||||
const { FDM } = kiri.driver;
|
||||
const { VIEWS } = consts;
|
||||
const { space } = moto;
|
||||
const LANG = lang.current;
|
||||
|
||||
let lastBox, lastMode, lastView, lastPillar, fromPillar,
|
||||
addingSupports = false,
|
||||
isFdmMode = false,
|
||||
nextID = Date.now(),
|
||||
p1, p2, iw,
|
||||
alert = [],
|
||||
boxes = {},
|
||||
func = {};
|
||||
|
||||
FDM.init = function(kiri, api) {
|
||||
const proc_keys = Object.keys(kiri.conf.defaults.fdm.p);
|
||||
const { ui, uc } = api;
|
||||
const rangeVars = {
|
||||
// slice
|
||||
// "sliceHeight": LANG.sl_lahi_s,
|
||||
"sliceShells": LANG.sl_shel_s,
|
||||
"sliceFillType": LANG.fi_type,
|
||||
"sliceFillWidth": LANG.fi_wdth_s,
|
||||
"sliceFillSparse": LANG.fi_pcnt_s,
|
||||
"sliceFillRate": LANG.ou_feed_s,
|
||||
"sliceFillGrow": LANG.fi_grow_s,
|
||||
"sliceSolidRate": LANG.ou_fini_s,
|
||||
"sliceSolidMinArea": LANG.ad_msol_s,
|
||||
"sliceTopLayers": LANG.sl_ltop_s,
|
||||
"sliceBottomLayers": LANG.sl_lbot_s,
|
||||
"firstLayerRate": LANG.fl_rate_s,
|
||||
"firstLayerPrintMult": LANG.fl_mult_s,
|
||||
"firstLayerFanSpeed": LANG.ou_fans_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,
|
||||
"outputAlternating": LANG.ad_altr_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.ad_rrat_s,
|
||||
"outputRetractDwell": LANG.ad_rdwl_s,
|
||||
"outputLayerRetract": LANG.ad_lret_s
|
||||
};
|
||||
|
||||
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.fdmRanges.innerHTML = '';
|
||||
}
|
||||
|
||||
// re-render ranges menu
|
||||
function updateRanges(ranges = []) {
|
||||
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 => `<div><label class="pad">${rangeVars[a[0]]}</label><span></span><label class="val">${a[1]}</label></div>`).join('');
|
||||
let hover = `<div id="hov_${id}" class="range-detail">${rows}</div>`;
|
||||
let info = `<button id="sel_${id}" class="j-center grow">${range.lo} - ${range.hi}</button><button id="del_${id}"><i class="fa fa-trash"></i></button>`;
|
||||
html.appendAll([
|
||||
`<div id="rng_${id}" class="range-info">${hover}${info}</div>`
|
||||
]);
|
||||
bind.push({id, range});
|
||||
}
|
||||
ui.fdmRanges.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("function.animate", (mode) => {
|
||||
if (!(isFdmMode && lastView === VIEWS.PREVIEW)) {
|
||||
return;
|
||||
}
|
||||
let slider = $('top-slider');
|
||||
slider.style.display = 'flex';
|
||||
slider.oninput = slider.onchange = (ev) => {
|
||||
api.const.STACKS.setFraction(parseInt(ev.target.value)/1000);
|
||||
api.space.update();
|
||||
};
|
||||
$('anim-slider').value = 500;
|
||||
api.const.STACKS.setFraction(0.5);
|
||||
});
|
||||
|
||||
api.event.on("mode.set", mode => {
|
||||
isFdmMode = mode === 'FDM';
|
||||
lastMode = mode;
|
||||
// hide/show fdm mode elements
|
||||
for (let el of [...document.getElementsByClassName('mode-fdm')]) {
|
||||
api.uc.setClass(el, 'hide', !isFdmMode);
|
||||
}
|
||||
updateSupportVisiblity();
|
||||
});
|
||||
|
||||
api.event.on("view.set", view => {
|
||||
$('top-slider').style.display = 'none';
|
||||
lastView = view;
|
||||
updateSupportVisiblity();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
api.event.on("range.updates", updateRanges);
|
||||
|
||||
api.event.on("settings.load", (settings) => {
|
||||
if (settings.mode !== 'FDM') return;
|
||||
restoreSupports(api.widgets.all());
|
||||
updateRanges(settings.process.ranges);
|
||||
});
|
||||
|
||||
api.event.on("settings.saved", (settings) => {
|
||||
updateRanges(settings.process.ranges);
|
||||
});
|
||||
|
||||
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 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();
|
||||
updateSupportVisiblity();
|
||||
// synth support widget for each widget group
|
||||
// iow, create support meshes based on support widget annotation
|
||||
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.mergeGeometries(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;
|
||||
sw.track.support = true;
|
||||
kiri.space.world.add(sw.mesh);
|
||||
}
|
||||
});
|
||||
|
||||
api.event.on("slice.end", () => {
|
||||
if (!isFdmMode) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
api.event.on("key.esc", () => {
|
||||
if (isFdmMode) {
|
||||
func.sdone()
|
||||
}
|
||||
});
|
||||
|
||||
api.event.on("selection.scale", () => {
|
||||
if (isFdmMode) {
|
||||
clearRanges();
|
||||
func.sclear();
|
||||
}
|
||||
});
|
||||
|
||||
api.event.on("widget.delete", widget => {
|
||||
if (isFdmMode) {
|
||||
clearRanges();
|
||||
}
|
||||
});
|
||||
|
||||
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("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];
|
||||
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("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 && lastBox)) 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 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 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 (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 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
|
||||
});
|
||||
} else {
|
||||
lastBox = null;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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 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 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 updateSupportVisiblity() {
|
||||
const visible = (lastMode === 'FDM' && lastView === VIEWS.ARRANGE);
|
||||
for (let w of api.widgets.all()) {
|
||||
setSupportVisiblity(w, visible);
|
||||
}
|
||||
}
|
||||
|
||||
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: opt.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);
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
FDM.delbox = delbox;
|
||||
FDM.addbox = addbox;
|
||||
FDM.restoreSupports = restoreSupports;
|
||||
|
||||
});
|
||||
85
src.old/kiri-mode/fdm/driver.js
Normal file
85
src.old/kiri-mode/fdm/driver.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri-mode.fdm.driver", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { driver } = kiri;
|
||||
const { util } = base;
|
||||
const FDM = driver.FDM = { getRangeParameters, extrudePerMM, extrudeMM };
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// noz = nozzle diameter
|
||||
// fil = filament diameter
|
||||
// slice = slice height
|
||||
function extrudePerMM(noz, fil, slice) {
|
||||
return ((Math.PI * util.sqr(noz / 2)) / (Math.PI * util.sqr(fil / 2))) * (slice / noz);
|
||||
};
|
||||
|
||||
// dist = distance between extrusion points
|
||||
// perMM = amount extruded per MM (from extrudePerMM)
|
||||
// factor = scaling factor (usually 1.0)
|
||||
function extrudeMM(dist, perMM, factor) {
|
||||
return dist * perMM * factor;
|
||||
}
|
||||
|
||||
// defer loading until client and worker exist
|
||||
kiri.load(api => {
|
||||
|
||||
const { client, worker } = kiri;
|
||||
|
||||
if (client) {
|
||||
FDM.support_generate = function(ondone) {
|
||||
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');
|
||||
return ondone([]);
|
||||
}
|
||||
for (let g of gen) {
|
||||
g.widget = widgets[g.id];
|
||||
}
|
||||
ondone(gen);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if (worker) {
|
||||
worker.fdm_support_generate = function(data, send) {
|
||||
const { settings } = data;
|
||||
const widgets = Object.values(worker.cache);
|
||||
const fresh = widgets.filter(widget => FDM.supports(settings, widget));
|
||||
send.done(kiri.codec.encode(fresh.map(widget => { return {
|
||||
id: widget.id,
|
||||
supports: widget.supports,
|
||||
} } )));
|
||||
};
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
999
src.old/kiri-mode/fdm/export.js
Normal file
999
src.old/kiri-mode/fdm/export.js
Normal file
|
|
@ -0,0 +1,999 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: kiri.utils
|
||||
// dep: kiri-mode.fdm.driver
|
||||
gapp.register("kiri-mode.fdm.export", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { util, config } = base;
|
||||
const { FDM } = kiri.driver;
|
||||
const { getRangeParameters } = FDM;
|
||||
const debug = false;
|
||||
|
||||
FDM.export = function(print, online, ondone, ondebug) {
|
||||
const { widgets, settings, belty, tools, firstTool } = print;
|
||||
const { bounds, controller, device, process, filter, mode } = settings;
|
||||
const { extruders, fwRetract } = device;
|
||||
const { bedWidth, bedDepth, bedRound, bedBelt, maxHeight } = device;
|
||||
const { gcodeFan, gcodeLayer, gcodeTrack, gcodePause, gcodeFeature, gcodeChange } = device;
|
||||
|
||||
let model_labels = [];
|
||||
for (let widget of widgets) {
|
||||
model_labels.push(widget.track.grid_id);
|
||||
}
|
||||
|
||||
let layers = print.output,
|
||||
extras = device.extras || {},
|
||||
isBambu = extras.bbl,
|
||||
{ extrudeAbs } = device,
|
||||
{ exportThumb } = controller,
|
||||
extused = Object.keys(print.extruders).map(v => parseInt(v)),
|
||||
timeFactor = (device.gcodeTime || 1) * 1.5,
|
||||
decimals = config.gcode_decimals || 4,
|
||||
zMoveMax = device.deviceZMax || 0,
|
||||
isBelt = bedBelt,
|
||||
bedType = isBelt ? "belt" : "fixed",
|
||||
time = 0,
|
||||
layer = 0,
|
||||
layerno = 0,
|
||||
pause = [],
|
||||
output = [],
|
||||
outputLength = 0, // absolute extruder position
|
||||
lastProgress = 0,
|
||||
progress = 0,
|
||||
distance = 0,
|
||||
emitted = 0,
|
||||
retracted = 0,
|
||||
axis = { X:' X', Y:' Y', Z:' Z', E:' E'},
|
||||
pos = { x:0, y:0, z:0, f:0 },
|
||||
lout = { x:0, y:0, z:0 },
|
||||
last = null,
|
||||
zpos = 0,
|
||||
bmax = 0,
|
||||
blastz = 0,
|
||||
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 ? 0 : (process.arcTolerance || 0),
|
||||
arcMin = 1,
|
||||
arcRes = 20,
|
||||
arcDev = 0.5,
|
||||
arcMax = 40,
|
||||
originCenter = device.originCenter || bedRound,
|
||||
offset = originCenter ? {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
} : {
|
||||
x: bedWidth/2,
|
||||
y: isBelt ? 0 : bedDepth/2,
|
||||
z: 0
|
||||
},
|
||||
tool = firstTool || 0,
|
||||
extruder = extruders[tool],
|
||||
offset_x = extruder.extOffsetX,
|
||||
offset_y = extruder.extOffsetY,
|
||||
extrudeSet = false,
|
||||
nozzleTemp = process.firstLayerNozzleTemp || process.outputTemp,
|
||||
bedTemp = process.firstLayerBedTemp || process.outputBedTemp,
|
||||
fanSpeedSave = undefined,
|
||||
fanSpeedBase = 0,
|
||||
fanSpeed = 0,
|
||||
lastType = undefined,
|
||||
lastNozzleTemp = nozzleTemp,
|
||||
lastBedTemp = bedTemp,
|
||||
lastFanSpeed = fanSpeed,
|
||||
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_base: fanSpeedBase,
|
||||
fan_speed: fanSpeed,
|
||||
speed: fanSpeed, // legacy
|
||||
top: offset.y ? bedDepth : bedDepth/2,
|
||||
left: offset.x ? 0 : -bedWidth/2,
|
||||
right: offset.x ? bedWidth : bedWidth/2,
|
||||
bottom: offset.y ? 0 : -bedDepth/2,
|
||||
z_max: maxHeight,
|
||||
layers: layers.length,
|
||||
progress: 0,
|
||||
nozzle: tool,
|
||||
tool: tool,
|
||||
model_labels: model_labels.sort().join(',')
|
||||
},
|
||||
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 = [],
|
||||
extrudeMM = FDM.extrudeMM,
|
||||
extrudePerMM = FDM.extrudePerMM;
|
||||
|
||||
let tools_used = Object.keys(tools);
|
||||
for (let tool of tools_used) {
|
||||
subst[`tool_used_${tool}`] = true;
|
||||
}
|
||||
subst.tool_count = tools_used.length;
|
||||
|
||||
// encodes an array offset as a single "on" bit in an
|
||||
// 8 byte array, then converts the array to base64 which
|
||||
// is what Bambu's M624 uses to flag an object as currently
|
||||
// being printed. the array is presented at the top of the
|
||||
// gcode as a comment: "; model label id: {model_labels}"
|
||||
function encodeBitOffset(index) {
|
||||
let bytes = new Uint8Array(8);
|
||||
let byteIndex = Math.floor(index / 8);
|
||||
let bitPosition = index % 8;
|
||||
bytes[byteIndex] |= (1 << bitPosition);
|
||||
return btoa(String.fromCharCode(...bytes));
|
||||
}
|
||||
|
||||
function setTempFanSpeed(tempSpeed) {
|
||||
if (tempSpeed > 0) {
|
||||
fanSpeedSave = fanSpeedSave >= 0 ? fanSpeedSave : fanSpeed;
|
||||
fanSpeed = tempSpeed;
|
||||
subst.fan_speed = fanSpeed;
|
||||
subst.fan_speed_base = tempSpeed;
|
||||
} else {
|
||||
fanSpeed = fanSpeedSave >= 0 ? fanSpeedSave : fanSpeed
|
||||
fanSpeedSave = undefined;
|
||||
subst.fan_speed = fanSpeed;
|
||||
subst.fan_speed_base = fanSpeedBase;
|
||||
}
|
||||
}
|
||||
|
||||
// smallish band-aid. refactor above to remove redundancy
|
||||
function updateParams(layer, params) {
|
||||
// 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;
|
||||
fanSpeedBase = params.firstLayerFanSpeed || 0;
|
||||
fanSpeed = layer >= (params.outputFanLayer ?? 1) ?
|
||||
params.outputFanSpeed || 0 : fanSpeedBase;
|
||||
Object.assign(subst, {
|
||||
temp_bed: bedTemp,
|
||||
bed_temp: bedTemp,
|
||||
fan_speed: fanSpeed,
|
||||
fan_speed_base: fanSpeedBase,
|
||||
speed: fanSpeed, // legacy
|
||||
retract_speed: retSpeed,
|
||||
retract_distance: retDist,
|
||||
temp: params.outputTemp, // range
|
||||
temp_bed: params.outputBedTemp, // range
|
||||
bed_temp: params.outputBedTemp, // range
|
||||
});
|
||||
}
|
||||
|
||||
let rloops = [];
|
||||
if (oloops > 0) {
|
||||
// if oloops set, loop entire part
|
||||
rloops.push({
|
||||
start: layers[0].slice.index,
|
||||
end: Infinity,
|
||||
iter: oloops - 1
|
||||
});
|
||||
} else if (oloops < 0) {
|
||||
// if oloops negative, loop entire part to infinity
|
||||
rloops.push({
|
||||
start: layers[0].slice.index,
|
||||
end: Infinity,
|
||||
iter: 0
|
||||
});
|
||||
}
|
||||
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(';; DEFINE ') === 0) {
|
||||
// ignore var declarations
|
||||
} else 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(''));
|
||||
}
|
||||
|
||||
function preamble() {
|
||||
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: ${filter[mode]}`);
|
||||
// inject thumbnail preview
|
||||
if (exportThumb && 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<data.length; i += 78) {
|
||||
append(`; ${data.substring(i, i + 78)}`);
|
||||
}
|
||||
append('; thumbnail end');
|
||||
}
|
||||
append("; --- process ---");
|
||||
for (let pk in process) {
|
||||
appendTok("; ", pk, " = ", process[pk]);
|
||||
}
|
||||
append("; --- startup ---");
|
||||
}
|
||||
|
||||
// looking for ";; PREAMBLE <MODE>" comment
|
||||
let pre = 0;
|
||||
let gcpre = [];
|
||||
for (let line of device.gcodePre) {
|
||||
line = line.trim();
|
||||
if (line.indexOf(";; PREAMBLE") === 0) {
|
||||
if (line === ';; PREAMBLE OFF') pre = 1;
|
||||
else if (line === ';; PREAMBLE END') pre = 2;
|
||||
else gcpre.push(pre = 123);
|
||||
} else if (line.indexOf(";; AXISMAP ") === 0) {
|
||||
let axmap = JSON.parse(line.substring(11).trim());
|
||||
for (let key in axmap) {
|
||||
axis[key] = ` ${axmap[key]}`;
|
||||
}
|
||||
} else {
|
||||
gcpre.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (pre === 0) preamble();
|
||||
|
||||
let t0 = false;
|
||||
let t1 = false;
|
||||
for (let line of gcpre) {
|
||||
if (line === pre) {
|
||||
preamble();
|
||||
continue;
|
||||
}
|
||||
if (line.indexOf('T0') === 0) t0 = true; else
|
||||
if (line.indexOf('T1') === 0) t1 = true; else
|
||||
if (line.indexOf('M82') === 0) {
|
||||
extrudeAbs = true;
|
||||
extrudeSet = true;
|
||||
} else
|
||||
if (line.indexOf('M83') === 0) {
|
||||
extrudeAbs = false;
|
||||
extrudeSet = true;
|
||||
} else
|
||||
if (line.indexOf('G90') === 0 && !extrudeSet) extrudeAbs = true; else
|
||||
if (line.indexOf('G91') === 0 && !extrudeSet) extrudeAbs = false; else
|
||||
if (line.indexOf('G92') === 0) {
|
||||
line.split(";")[0].split(' ').forEach(function (tok) {
|
||||
let val = parseFloat(tok.substring(1) || 0) || 0;
|
||||
switch (tok[0]) {
|
||||
case 'X': pos.x = val; break;
|
||||
case 'Y': pos.y = val; break;
|
||||
case 'Z': pos.z = val; break;
|
||||
case 'E': outputLength = val; break;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (line.indexOf('E') > 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
appendSubPad(line);
|
||||
}
|
||||
|
||||
if (pre === 2) preamble();
|
||||
|
||||
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) {
|
||||
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(axis.X).append(epos.x.toFixed(decimals));
|
||||
if (emit.y) o.append(axis.Y).append(epos.y.toFixed(decimals));
|
||||
if (emit.z) o.append(axis.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(axis.E).append(outputLength.toFixed(decimals));
|
||||
} else {
|
||||
o.append(axis.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;
|
||||
subst.pos_x = epos.x.round(decimals);
|
||||
subst.pos_y = epos.y.round(decimals);
|
||||
subst.pos_z = epos.z.round(decimals);
|
||||
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);
|
||||
|
||||
// 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, path.params);
|
||||
}
|
||||
|
||||
emitPerMM = 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 (gcodePause && pause.indexOf(layer) >= 0) {
|
||||
appendAllSub(gcodePause)
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let cwidget;
|
||||
// iterate through layer outputs
|
||||
for (pidx=0; pidx<path.length; pidx++) {
|
||||
out = path[pidx];
|
||||
speedMMM = (out.speed || process.outputFeedrate) * 60; // range
|
||||
|
||||
// hint to controller that we're working on a specific object
|
||||
// so that gcode between start/stop comments can be cancelled
|
||||
if (out.widget !== cwidget) {
|
||||
if (cwidget) {
|
||||
append(`; end object id: ${cwidget.track.grid_id}`);
|
||||
isBambu && append('M625');
|
||||
}
|
||||
if (out.widget) {
|
||||
let off = model_labels.indexOf(out.widget.track.grid_id);
|
||||
let b64 = encodeBitOffset(off);
|
||||
append(`; start object id: ${out.widget.track.grid_id}`);
|
||||
isBambu && append(`M624 ${b64}`);
|
||||
}
|
||||
cwidget = out.widget;
|
||||
}
|
||||
|
||||
// emit comment on output type chage
|
||||
if (last && out.type !== last.type) {
|
||||
lastType = subst.feature = out.type;
|
||||
if (gcodeFeature && gcodeFeature.length) {
|
||||
appendAllSub(gcodeFeature);
|
||||
} else {
|
||||
append(`; feature ${out.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
// look for extruder change, run scripts, recalc emit factor
|
||||
if (out.tool !== undefined && out.tool != tool) {
|
||||
segments.push({emitted, tool});
|
||||
subst.last_tool = tool;
|
||||
tool = out.tool;
|
||||
subst.nozzle = subst.tool = tool;
|
||||
extruder = extruders[tool];
|
||||
offset_x = extruder.extOffsetX;
|
||||
offset_y = extruder.extOffsetY;
|
||||
emitPerMM = extrudePerMM(
|
||||
lineWidth || extruder.extNozzle,
|
||||
extruder.extFilament,
|
||||
path.layer === 0 ?
|
||||
(process.firstSliceHeight || process.sliceHeight) : path.height);
|
||||
appendAllSub(gcodeChange ?? [ "T{tool}" ]);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// in belt mode, fan can change per segment (for base)
|
||||
if (isBelt) {
|
||||
setTempFanSpeed(out.fan);
|
||||
if (fanSpeed !== lastFanSpeed) {
|
||||
appendAllSub(gcodeFan);
|
||||
lastFanSpeed = fanSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
emitMM = extrudeMM(dist, emitPerMM, out.emit);
|
||||
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)
|
||||
if (dist && speedMMM) {
|
||||
time += (dist / speedMMM) * 60 * timeFactor;
|
||||
}
|
||||
// if (isNaN(time)) {
|
||||
// console.log({ layer, path, dist, speedMMM, timeFactor });
|
||||
// throw "NaN";
|
||||
// }
|
||||
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++;
|
||||
if (cwidget) {
|
||||
append(`; end object id: ${cwidget.track.grid_id}`);
|
||||
isBambu && append('M625');
|
||||
cwidget = undefined;
|
||||
}
|
||||
|
||||
// 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;
|
||||
emitMM = extrudeMM(dist, emitPerMM, e);
|
||||
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.distance = emitted;
|
||||
print.lines = lines;
|
||||
print.bytes = bytes + lines - 1;
|
||||
print.time = time;
|
||||
print.labels = model_labels;
|
||||
|
||||
if (debug) {
|
||||
console.log('segments', segments);
|
||||
console.log('minz', minz);
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
327
src.old/kiri-mode/fdm/fill.js
Normal file
327
src.old/kiri-mode/fdm/fill.js
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.gyroid
|
||||
// dep: kiri-mode.fdm.driver
|
||||
gapp.register("kiri-mode.fdm.fill", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { util } = base;
|
||||
|
||||
const DEG2RAD = Math.PI / 180,
|
||||
FILL = kiri.fill = {
|
||||
hex: fillHexFull,
|
||||
grid: fillGrid,
|
||||
gyroid: fillGyroid,
|
||||
triangle: fillTriangle,
|
||||
linear: fillLinear,
|
||||
cubic: fillCubic
|
||||
},
|
||||
CACHE = kiri.fill_fixed = {
|
||||
hex: fillHexFull,
|
||||
grid: fillGrid,
|
||||
triangle: fillTriangle
|
||||
};
|
||||
|
||||
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 = (Math.cos(30 * DEG2RAD) * vhlen).round(7);
|
||||
let anylen = (Math.sin(30 * DEG2RAD) * vhlen).round(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);
|
||||
|
||||
let polys = [];
|
||||
if (gyroid.dir == 'lr') {
|
||||
for (let ty=0; ty<=tile_y; ty++) {
|
||||
for (let tx=0; tx<=tile_x; tx++) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
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<tlen; i++) {
|
||||
let s1 = ntmp[i];
|
||||
if (!s1 || !s1.open) continue;
|
||||
for (let j=i+1; j<tlen; j++) {
|
||||
let s2 = ntmp[j];
|
||||
if (!s2 || !s2.open) continue;
|
||||
if (s1.last().distTo2D(s2.first()) <= dist) {
|
||||
s1.addPoints(s2.points);
|
||||
ntmp[j] = null;
|
||||
continue outer;
|
||||
}
|
||||
if (s1.first().distTo2D(s2.last()) <= dist) {
|
||||
s2.addPoints(s1.points);
|
||||
ntmp[i] = null;
|
||||
continue outer;
|
||||
}
|
||||
if (s1.first().distTo2D(s2.first()) <= dist) {
|
||||
s1.reverse();
|
||||
s1.addPoints(s2.points);
|
||||
ntmp[j] = null;
|
||||
continue outer;
|
||||
}
|
||||
if (s1.last().distTo2D(s2.last()) <= dist) {
|
||||
s2.reverse();
|
||||
s1.addPoints(s2.points);
|
||||
ntmp[j] = null;
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (heal > 0) {
|
||||
// cull nulls
|
||||
noff = noff.filter(o => o);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
323
src.old/kiri-mode/fdm/post.js
Normal file
323
src.old/kiri-mode/fdm/post.js
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.polygons
|
||||
// dep: kiri-mode.fdm.driver
|
||||
gapp.register("kiri-mode.fdm.post", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { driver } = kiri;
|
||||
const { polygons } = base;
|
||||
const { fillArea } = polygons;
|
||||
const { FDM } = driver;
|
||||
|
||||
const POLY = polygons;
|
||||
|
||||
/**
|
||||
* 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, vaseMode } = post_args;
|
||||
const { shellOffset, fillOffset, clipOffset } = post_args;
|
||||
data.tops = POLY.nest(groups);
|
||||
if (isSynth) {
|
||||
const process = post_args.process;
|
||||
if (process.sliceSupportGrow > 0) {
|
||||
// expand synth supports here so they can be clipped later
|
||||
data.tops = POLY.expand(data.tops, process.sliceSupportGrow, data.z, []);
|
||||
}
|
||||
// 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 = 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;
|
||||
}
|
||||
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 thinType = isSynth ? undefined : process.sliceDetectThin;
|
||||
const nutops = [];
|
||||
// co-locate shell processing with top generation in slicer
|
||||
for (let top of data.tops) {
|
||||
nutops.push(FDM.doTopShells(z, top, count, offset/2, offset, fillOff, {
|
||||
thinType,
|
||||
vaseMode,
|
||||
useAssembly
|
||||
}));
|
||||
}
|
||||
data.clip = clipOffset ? POLY.offset(nutops.map(t => t.simple), clipOffset) : undefined;
|
||||
data.tops = nutops;
|
||||
delete data.groups;
|
||||
};
|
||||
|
||||
function offset_default(params) {
|
||||
let { z, top, count, top_poly, offset1, offsetN, wasm, last, gaps } = params;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
return { last, gaps };
|
||||
}
|
||||
|
||||
function thin_type_1(params) {
|
||||
let { z, top, count, top_poly, offsetN, last, gaps } = params;
|
||||
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);
|
||||
}
|
||||
|
||||
return { last, gaps };
|
||||
}
|
||||
|
||||
function thin_type_2(params) {
|
||||
let { z, top, count, top_poly, offset1, offsetN, last, gaps, wasm } = params;
|
||||
|
||||
top.gaps = gaps;
|
||||
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);
|
||||
});
|
||||
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.push(...polys);
|
||||
});
|
||||
|
||||
return { last, gaps };
|
||||
}
|
||||
|
||||
function thin_type_3(params) {
|
||||
let { z, top, count, offsetN, last, gaps } = params;
|
||||
top.thin_fill = [];
|
||||
top.fill_sparse = [];
|
||||
|
||||
let { noodle, remain } = top.poly.noodle(offsetN);
|
||||
top.shells = noodle;
|
||||
top.gaps = remain;
|
||||
// top.fill_off = noodle;
|
||||
|
||||
// if (remain.length) {
|
||||
// top.gaps = [];
|
||||
// for (let poly of remain) {
|
||||
// let { noodle } = poly.noodle(offsetN);
|
||||
// console.log({ next_noodle: noodle });
|
||||
// top.gaps.push(...noodle);
|
||||
// }
|
||||
// }
|
||||
|
||||
return { last, gaps };
|
||||
}
|
||||
|
||||
/**
|
||||
* may run in minion or worker context. performs shell offsetting
|
||||
* including thin wall detection when enabled
|
||||
*/
|
||||
FDM.doTopShells = function(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.useAssembly;
|
||||
let top_poly = [ top.poly ];
|
||||
|
||||
if (opt.vaseMode) {
|
||||
// remove top poly inners in vase mode
|
||||
top.poly = top.poly.clone(false);
|
||||
}
|
||||
|
||||
top.shells = []; // strategies output shells here
|
||||
top.fill_off = []; // offset from innermost shell for infill
|
||||
top.fill_lines = []; // fill lines inside fill_off
|
||||
|
||||
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;
|
||||
} });
|
||||
let ret = { last, gaps };
|
||||
switch (opt.thinType) {
|
||||
case "type 1":
|
||||
ret = thin_type_1({ z, top, count, top_poly, offsetN, fillOffset });
|
||||
break;
|
||||
case "type 2":
|
||||
ret = thin_type_2({ z, top, count, top_poly, offset1, offsetN, fillOffset, gaps, wasm });
|
||||
break;
|
||||
case "type 3":
|
||||
ret = thin_type_3({ z, top, count, top_poly, offsetN });
|
||||
fillOffset = 0;
|
||||
break;
|
||||
default:
|
||||
ret = offset_default({ z, top, count, top_poly, offset1, offsetN, wasm });
|
||||
break;
|
||||
}
|
||||
last = ret.last || last;
|
||||
gaps = ret.gaps || gaps;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* given an array of arrays of points (lines), eliminate intersections
|
||||
* between groups, then return a unified array of shortest non-intersects.
|
||||
*/
|
||||
function cullIntersections() {
|
||||
function toLines(pts) {
|
||||
let lns = [];
|
||||
for (let i=0, il=pts.length; i<il; i += 2) {
|
||||
lns.push({a: pts[i], b: pts[i+1], l: pts[i].distTo2D(pts[i+1])});
|
||||
}
|
||||
return lns;
|
||||
}
|
||||
let aOa = [...arguments].filter(t => 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<il; i++) {
|
||||
let al = aa[i];
|
||||
if (al.del) {
|
||||
continue;
|
||||
}
|
||||
for (let j=0, jl=bb.length; j<jl; j++) {
|
||||
let bl = bb[j];
|
||||
if (bl.del) {
|
||||
continue;
|
||||
}
|
||||
if (base.util.intersect(al.a, al.b, bl.a, bl.b, base.key.SEGINT)) {
|
||||
if (al.l < bl.l) {
|
||||
bl.del = true;
|
||||
} else {
|
||||
al.del = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
aa = aa.filter(l => !l.del).concat(bb.filter(l => !l.del));
|
||||
}
|
||||
let good = [];
|
||||
for (let i=0, il=aa.length; i<il; i++) {
|
||||
let al = aa[i];
|
||||
good.push(al.a);
|
||||
good.push(al.b);
|
||||
}
|
||||
return good.length > 2 ? good : [];
|
||||
}
|
||||
|
||||
});
|
||||
1516
src.old/kiri-mode/fdm/prepare.js
Normal file
1516
src.old/kiri-mode/fdm/prepare.js
Normal file
File diff suppressed because it is too large
Load diff
1946
src.old/kiri-mode/fdm/slice.js
Normal file
1946
src.old/kiri-mode/fdm/slice.js
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue