move fdm print funcs to fdm mode. code style update

This commit is contained in:
Stewart Allen 2022-02-19 19:17:10 -05:00
commit 0dd617feeb
10 changed files with 5055 additions and 5088 deletions

2
app.js
View file

@ -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",

View file

@ -4,32 +4,29 @@
(function() {
let KIRI = self.kiri,
BASE = self.base,
UTIL = BASE.util,
FDM = KIRI.driver.FDM,
SPACE, API, VIEWS, LANG, PROC, UI, UC,
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;
let lastBox, lastMode, lastView, lastPillar, fromPillar,
addingSupports = false,
isFdmMode = false,
nextID = Date.now(),
p1, p2, iw,
lastBox,
lastMode, lastView,
lastPillar, fromPillar,
isFdmMode = false,
addingSupports = false,
alert = [],
boxes = {},
func = {};
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 rangeVars = {
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,
@ -71,8 +68,8 @@
};
for (let key of Object.keys(rangeVars)) {
if (UI[key]) {
UI[key].range = true;
if (ui[key]) {
ui[key].range = true;
}
}
@ -87,12 +84,12 @@
}
function clearRanges() {
API.conf.get().process.ranges = [];
UI.rangeGroup.firstElementChild.innerHTML = '';
api.conf.get().process.ranges = [];
ui.rangeGroup.firstElementChild.innerHTML = '';
}
function updateRanges(ranges = []) {
UI.rangeGroup.style.display = isFdmMode && ranges && ranges.length ? 'flex' : 'none';
ui.rangeGroup.style.display = isFdmMode && ranges && ranges.length ? 'flex' : 'none';
let html = [];
let bind = [];
let now = Date.now();
@ -107,7 +104,7 @@
]);
bind.push({id, range});
}
UI.rangeGroup.firstElementChild.innerHTML = html.join('');
ui.rangeGroup.firstElementChild.innerHTML = html.join('');
for (let rec of bind) {
$(`sel_${rec.id}`).onclick = () => {
api.show.layer(rec.range.hi, rec.range.lo);
@ -125,14 +122,14 @@
return;
}
for (let btn of [
UI.ssaGen,
UI.ssmAdd,
UI.ssmDun,
UI.ssmClr
ui.ssaGen,
ui.ssmAdd,
ui.ssmDun,
ui.ssmClr
]) {
btn.disabled = UI.sliceSupportEnable.checked;
btn.disabled = ui.sliceSupportEnable.checked;
}
if (UI.sliceSupportEnable.checked) {
if (ui.sliceSupportEnable.checked) {
func.sclear();
}
});
@ -141,11 +138,11 @@
if (!isFdmMode) {
return;
}
if (!API.conf.get().controller.danger) {
if (!api.conf.get().controller.danger) {
return;
}
let pct = 0;
let int = setInterval(function() {
let int = setInterval(() => {
if (pct <= 1) {
api.const.STACKS.setFraction(pct);
pct += 0.05;
@ -170,21 +167,21 @@
updateVisiblity();
filterSynth();
func.sdone();
// let ranges = API.conf.get().process.ranges;
// 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;
for (let key of proc_keys) {
if (ui[key] && !ui[key].range) {
ui[key].disabled = true;
}
}
} else {
for (let key of PROC) {
if (UI[key]) UI[key].disabled = false;
for (let key of proc_keys) {
if (ui[key]) ui[key].disabled = false;
}
}
}
// UI.rangeGroup.style.display = ranges && ranges.length ? 'flex' : 'none';
// ui.rangeGroup.style.display = ranges && ranges.length ? 'flex' : 'none';
});
api.event.on("range.updates", updateRanges);
api.event.on("settings.load", (settings) => {
@ -197,7 +194,7 @@
updateRanges(settings.process.ranges);
func.updateSupportButtons();
// let ranges = settings.process.ranges;
// UI.rangeGroup.style.display = isFdmMode && ranges && ranges.length ? 'flex' : 'none';
// ui.rangeGroup.style.display = isFdmMode && ranges && ranges.length ? 'flex' : 'none';
});
api.event.on("button.click", target => {
switch (target) {
@ -225,7 +222,7 @@
api.hide.alert(undefined,alerts);
for (let rec of array) {
let { widget, supports } = rec;
let wa = API.widgets.annotate(widget.id);
let wa = api.widgets.annotate(widget.id);
let ws = wa.support || [];
for (let support of supports) {
let { from, to, mid } = support;
@ -262,7 +259,7 @@
api.event.on("fdm.supports.clear", func.sclear = () => {
func.sdone();
if (clearAllWidgetSupports()) {
API.conf.save();
api.conf.save();
}
});
api.event.on("slice.begin", () => {
@ -286,16 +283,16 @@
});
for (let box of boxen) {
// eliminate / normalize uv to allow other widget merge
box.setAttribute('uv',new THREE.BufferAttribute(new Float32Array(0), 3));
box.setAttribute('uv',new BufferAttribute(new Float32Array(0), 3));
}
let bbg = THREE.BufferGeometryUtils.mergeBufferGeometries(boxen);
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);
kiri.space.world.add(sw.mesh);
}
});
api.event.on("slice.end", () => {
@ -325,7 +322,7 @@
if (!isFdmMode) {
return;
}
let ann = API.widgets.annotate(widget.id);
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);
@ -337,7 +334,7 @@
if (!isFdmMode) {
return;
}
let ann = API.widgets.annotate(widget.id);
let ann = api.widgets.annotate(widget.id);
let sups = ann.support || [];
sups.forEach(sup => {
let wsup = widget.sups[sup.id];
@ -352,13 +349,13 @@
if (x || y) {
clearWidgetSupports(widget);
} else {
let ann = API.widgets.annotate(widget.id);
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));
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;
@ -392,7 +389,7 @@
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 targets = api.widgets.meshes().append(space.internals().platform);
let from = fromPillar,
d = {
x: rec.x - from.x,
@ -400,7 +397,7 @@
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 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) {
@ -412,23 +409,23 @@
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);
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();
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)
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 THREE.Raycaster(rp, dn)
let idn = new Raycaster(rp, dn)
.intersectObjects(targets, false)
.filter(t => !t.object.pillar);
if (idn.length && idn.length % 2 === 0) {
@ -475,14 +472,14 @@
iw = null;
}
p1 = point;
let dir = new THREE.Vector3(0,1,0)
let ray = new THREE.Raycaster(point, dir);
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 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
@ -508,41 +505,41 @@
xpdebug.destroy();
xpdebug = undefined;
}
let poly = BASE.newPolygon().setOpen();
let poly = base.newPolygon().setOpen();
for (let rec of msg.arcQ) {
rec.z += 1;
poly.addObj(rec);
}
let layers = new KIRI.Layers();
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(
layer.addPoly(base.newPolygon().centerCircle(
center, center.r, 20
));
}
xpdebug = new KIRI.Stack(SPACE.world, false);
xpdebug = new kiri.Stack(space.world, false);
xpdebug.addLayers(layers);
xpdebug.setVisible(0,Infinity);
xpdebug.show();
SPACE.refresh();
space.refresh();
console.log(msg, poly);
}
});
}
}
let xpdebug;
let supsave = {};
let suptimer;
let xpdebug;
let supsave = {};
let suptimer;
function scheduleSupportSave(widget) {
function scheduleSupportSave(widget) {
clearTimeout(suptimer);
supsave[widget.id] = widget;
suptimer = setTimeout(() => {
@ -550,29 +547,29 @@
w.saveState();
}
}, 50);
}
}
function activeSupports() {
function activeSupports() {
const active = [];
API.widgets.all().forEach(widget => {
api.widgets.all().forEach(widget => {
Object.values(widget.sups || {}).forEach(support => {
active.push(support.box);
support.box.support = true;
});
});
return active;
}
}
function restoreSupports(widgets) {
function restoreSupports(widgets) {
widgets.forEach(widget => {
const supports = API.widgets.annotate(widget.id).support || [];
const supports = api.widgets.annotate(widget.id).support || [];
supports.forEach(pos => {
addWidgetSupport(widget, pos);
});
});
}
}
function 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
@ -587,14 +584,14 @@
scheduleSupportSave(widget);
}
return sups[id];
}
}
function removeWidgetSupport(widget, rec) {
const {box, id} = rec;
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 sa = api.widgets.annotate(widget.id).support;
let ix = 0;
sa.forEach((rec,i) => {
if (rec.id === id) {
@ -602,32 +599,32 @@
}
});
sa.splice(ix,1);
API.conf.save();
api.conf.save();
scheduleSupportSave(widget);
fromPillar = undefined;
}
}
function updateVisiblity() {
API.widgets.all().forEach(w => {
function updateVisiblity() {
api.widgets.all().forEach(w => {
setSupportVisiblity(w, lastMode === 'FDM' && lastView === VIEWS.ARRANGE);
});
}
}
function setSupportVisiblity(widget, bool) {
function setSupportVisiblity(widget, bool) {
Object.values(widget.sups || {}).forEach(support => {
support.box.visible = bool;
});
}
}
function clearAllWidgetSupports() {
function clearAllWidgetSupports() {
let cleared = 0;
API.widgets.all().forEach(widget => {
api.widgets.all().forEach(widget => {
cleared += clearWidgetSupports(widget);
});
return cleared;
}
}
function clearWidgetSupports(widget) {
function clearWidgetSupports(widget) {
let cleared = 0;
Object.values(widget.sups || {}).forEach(support => {
widget.adds.remove(support.box);
@ -635,23 +632,24 @@
cleared++;
});
widget.sups = {};
delete API.widgets.annotate(widget.id).support;
delete api.widgets.annotate(widget.id).support;
scheduleSupportSave(widget);
return cleared;
}
}
function delbox(name) {
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 = {}) {
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({
const box = boxes[name] = new Mesh(
new BoxGeometry(dim.x, dim.y, dim.z),
new MeshPhongMaterial({
transparent: true,
opacity: 0.5,
color
@ -663,24 +661,25 @@
lastBox = {point, dim};
const group = opt.group || SPACE.scene
const group = opt.group || space.scene
group.add(box);
box.groupTo = group;
if (dim.rz) {
opt.rotate = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), dim.rz);
opt.rotate = new Quaternion().setFromAxisAngle(new Vector3(0,0,1), dim.rz);
}
if (opt.rotate) {
opt.matrix = new THREE.Matrix4().makeRotationFromQuaternion(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;
return box;
}
FDM.delbox = delbox;
FDM.addbox = addbox;
FDM.restoreSupports = restoreSupports;
})();

View file

@ -4,19 +4,11 @@
(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) {
// shared by client and worker contexts
function getRangeParameters(process, index) {
if (index === undefined || index === null || index < 0) {
return process;
}
@ -27,46 +19,51 @@
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)) {
for (let [key, value] of Object.entries(range.fields)) {
params[key] = value;
params._range = true;
}
}
}
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,
} } )));
};
}
});
});
})();

View file

@ -4,24 +4,19 @@
(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;
FDM.export = function(print, online, ondone, ondebug) {
const { settings, belty, tools } = print;
const { controller, device } = settings;
const { extruders } = device;
/**
* @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,
@ -45,7 +40,7 @@
output = [],
outputLength = 0, // absolute extruder position
lastProgress = 0,
decimals = BASE.config.gcode_decimals || 4,
decimals = base.config.gcode_decimals || 4,
progress = 0,
distance = 0,
emitted = 0,
@ -57,7 +52,7 @@
bmax = 0,
blastz = 0,
process = settings.process,
belt_add_y = (process.firstLayerYOffset || 0) - (print.belty || 0),
belt_add_y = (process.firstLayerYOffset || 0) - (belty || 0),
oloops = process.outputLoops || 0,
zhop = process.zHopDistance || 0, // range
lineWidth = process.sliceLineWidth || 0,
@ -132,7 +127,7 @@
purgeOn = 0,
purgeOff = 0;
let tools_used = Object.keys(print.tools);
let tools_used = Object.keys(tools);
for (let tool of tools_used) {
subst[`tool_used_${tool}`] = true;
}
@ -256,7 +251,7 @@
append(arg.join(''));
}
append(`; Generated by Kiri:Moto ${KIRI.version}`);
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}`);
@ -674,8 +669,8 @@
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 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;
@ -717,7 +712,7 @@
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);
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());
@ -812,14 +807,14 @@
let cl = arcQ.center.length;
let cc;
let angle = BASE.util.thetaDiff(
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 = BASE.util.center2pr(from, to, arcQ.rSum / cl, gc === "G3");
cc = util.center2pr(from, to, arcQ.rSum / cl, gc === "G3");
}
if (!cc) {
@ -866,7 +861,7 @@
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);
let cc = util.center2d(arcQ[0], arcQ[i+1], arcQ[arcQ.length - 1], 1);
if (!cc) {
return false;
}
@ -906,7 +901,7 @@
// 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);
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;
}
@ -921,8 +916,8 @@
}
segments.push({emitted, tool});
subst.material = UTIL.round(emitted,2);
subst.time = UTIL.round(time,2);
subst.material = util.round(emitted,2);
subst.time = util.round(time,2);
subst['print_time'] = subst.time;
append("; --- shutdown ---");
@ -943,6 +938,6 @@
console.log('segments', segments);
console.log('minz', minz);
}
};
};
})();

View file

@ -4,9 +4,9 @@
(function() {
if (self.kiri.fill) return;
if (self.kiri.fill) return;
const KIRI = self.kiri,
const KIRI = self.kiri,
BASE = self.base,
UTIL = BASE.util,
ROUND = UTIL.round,
@ -25,17 +25,17 @@
triangle: fillTriangle
};
function fillHexFull(target) {
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) {
function fillHex(target, full) {
// compute segment lengths (vert/horiz and 45)
let spacing = target.offset();
// let vhlen = (1 / target.density()) * (target.lineWidth() + spacing);
@ -97,9 +97,9 @@
target.newline();
}
}
}
}
function fillGyroid(target) {
function fillGyroid(target) {
let bounds = target.bounds();
let height = target.zHeight();
let span_x = bounds.max.x - bounds.min.x;
@ -147,9 +147,9 @@
target.emit(point.x, point.y);
}
}
}
}
function connectOpenPolys(noff, dist = 0.1) {
function connectOpenPolys(noff, dist = 0.1) {
if (noff.length <= 1) {
return noff;
}
@ -193,9 +193,9 @@
noff = noff.filter(o => o);
}
return noff;
}
}
function fillGrid(target) {
function fillGrid(target) {
let bounds = target.bounds();
let height = target.zHeight();
let span_x = bounds.max.x - bounds.min.x;
@ -220,9 +220,9 @@
}
}
}
}
}
function fillCubic(target) {
function fillCubic(target) {
let bounds = target.bounds();
let span = Math.max(
bounds.max.x - bounds.min.x,
@ -256,9 +256,9 @@
target.emit(bounds.min.x + 1000, ty + 1000);
}
}
}
}
function fillLinear(target) {
function fillLinear(target) {
let bounds = target.bounds();
let span = Math.max(
bounds.max.x - bounds.min.x,
@ -280,9 +280,9 @@
target.emit(bounds.max.x, ty);
}
}
}
}
function fillTriangle(target) {
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;
@ -315,6 +315,6 @@
target.emit(xp, bounds.min.y);
target.emit(xp, bounds.max.y);
}
}
}
})();

View file

@ -4,52 +4,47 @@
(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) {
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 = [],
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 = 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,
isThin = lineType === "line",
isFlat = lineType === "flat",
isDanger = danger || false,
isPalette = filamentSource === 'palette3',
firstLayerHeight = isBelt ? sliceHeight : firstSliceHeight || sliceHeight,
layerno = 0,
zoff = 0,
zmin = 0,
shield,
layerout = [],
print = self.worker.print = KIRI.newPrint(settings, widgets),
beltfact = Math.cos(Math.PI/4);
output = [],
layerout = [];
// compute bounds if missing
if (!bounds) {
@ -76,7 +71,7 @@
widgets.filter(w => w.slices.length).forEach(function(widget) {
let tops = [];
let slices = [];
if (draftShield) {
if (outputDraftShield) {
slices.appendAll(widget.slices);
} else {
slices.push(widget.slices[0]);
@ -93,7 +88,7 @@
tops.push(support.clone());
});
}
if (draftShield) {
if (outputDraftShield) {
tops = POLY.union(tops,1,true);
}
// nest and offset tops
@ -129,7 +124,7 @@
printPoint = null;
let raft = function(height, angle, spacing, speed, extrude) {
let slice = KIRI.newSlice(zoff + height / 2);
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();
@ -142,8 +137,8 @@
t.fill_lines = POLY.fillArea(t.inner, angle, spacing, []);
})
offset.z = slice.z;
printPoint = print.slicePrintPath(slice, printPoint, offset, layerout, {
speed: speed,
printPoint = slicePrintPath(print, slice, printPoint, offset, layerout, {
speed,
mult: extrude,
});
layerout.z = zoff + height;
@ -206,7 +201,7 @@
}
}
// recompute bounds for purge block offsets
let bbounds = BASE.newBounds();
let bbounds = base.newBounds();
brims.forEach(brim => {
bbounds.merge(brim.bounds);
});
@ -230,14 +225,14 @@
if (!slice.supports) {
continue;
}
let sslice = KIRI.newSlice(slice.z);
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);
let swidget = kiri.newWidget(null,widget.group);
swidget.slices = sslices;
swidget.support = true;
swidget.rotinfo = widget.rotinfo;
@ -266,7 +261,7 @@
}
// determine size/location of purge blocks
let blokw = Math.sqrt(purgeTower);
let blokw = Math.sqrt(outputPurgeTower || 0);
let blokh = blokw;
let blokpos, walkpos, blok;
@ -354,7 +349,7 @@
// generate purge block for given nozzle
function purge(nozzle, track, layer, start, z, using, offset) {
if (!purgeTower || extcount < 2) {
if (!outputPurgeTower || extcount < 2) {
return start;
}
let rec = track.shift();
@ -543,7 +538,8 @@
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(
printPoint = slicePrintPath(
print,
slice,
beltStart ? newPoint(-5000, 5000, 0) : printPoint.sub(offset),
offset,
@ -576,7 +572,7 @@
lastPoly = slice.lastPoly;
lastLayer = layerout;
if (layerRetract && layerout.length) {
if (outputLayerRetract && layerout.length) {
layerout.last().retract = true;
}
}
@ -593,7 +589,7 @@
}
// draft shield
if (layerno > 0 && shield && draftShield) {
if (layerno > 0 && shield && outputDraftShield) {
print.setType('shield');
shield = POLY.setZ(shield.clone(), printPoint.z);
let preout = [];
@ -668,7 +664,7 @@
for (let rec of layer) {
overate = rec.overate >= 0 ? rec.overate : overate;
let brate = params.firstLayerRate || firstLayerRate;
let bmult = params.firstLayerPrintMult || firstLayerMult;
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;
@ -713,7 +709,7 @@
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 + b, y, z), 0, outputSeekrate, tool);
print.addOutput(tmpout, newPoint(maxx + g, y, z), emit, firstLayerRate, tool).retract = true;
}
// inside brim
@ -726,25 +722,25 @@
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 - 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, firstLayerSeek, tool);
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, firstLayerSeek, tool);
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, firstLayerSeek, tool);
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, firstLayerSeek, first.tool);
// print.addOutput(tmpout, newPoint(minx, maxy, minz), 0, outputSeekrate, first.tool);
// print.lastPoint = newPoint(minx, maxy, minz);
}
layer.splice(0,0,...tmpout);
@ -752,7 +748,7 @@
}
// render if not explicitly disabled
if (render) {
if (settings.render !== false) {
print.render = FDM.prepareRender(output, (progress, layer) => {
update(0.5 + progress * 0.5, "render", layer);
}, {
@ -765,35 +761,648 @@
}
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<ints.length; i += 2) {
let i1 = ints[i];
let i2 = ints[i+1];
// different poly. force retract
if (i1.poly !== i2.poly) {
if (dbug === slice.index) console.log(slice.index, {int_diff_poly: ints, i});
return false;
}
// mark invalid intersect pairs (low or zero dist, etc)
// TODO: only if this is the outer pair and there are closer inner pairs
if (i1.ip.distTo2D(i2.ip) < retractDist) {
if (dbug === slice.index) console.log(slice.index, {int_dist_too_small: i1.ip.distTo2D(i2.ip), retractDist});
ints[i] = undefined;
ints[i+1] = undefined;
}
}
// filter out invalid intersection pairs
ints = ints.filter(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; i<ints.length; i += 2) {
let i1 = ints[0];
let i2 = ints[1];
// output first point
print.addOutput(preout, i1.ip, 0, moveSpeed, extruder);
// create two loops around poly
// find shortest of two paths and emit poly points
let poly = i1.poly;
let isCW = poly.isClockwise();
let points = poly.points;
let p1p = isCW ? points : points.slice().reverse(); // CW
let p2p = isCW ? points.slice().reverse() : points; // CCW
let r1s = p1p.indexOf(isCW ? i1.ip.p2 : i1.ip.p1);
let r1e = p1p.indexOf(isCW ? i2.ip.p1 : i2.ip.p2);
let r1 = r1s === r1e ?
[ p1p[r1s] ] : r1s < r1e ?
[ ...p1p.slice(r1s,r1e+1) ] :
[ ...p1p.slice(r1s), ...p1p.slice(0,r1e+1) ];
let r1d = 0;
for (let i=1; i<r1.length; i++) {
r1d += r1[i-1].distTo2D(r1[i]);
}
let r2s = p2p.indexOf(isCW ? i1.ip.p1 : i1.ip.p2);
let r2e = p2p.indexOf(isCW ? i2.ip.p2 : i2.ip.p1);
let r2 = r2s === r2e ?
[ p2p[r2s] ] : r2s < r2e ?
[ ...p2p.slice(r2s,r2e+1) ] :
[ ...p2p.slice(r2s), ...p2p.slice(0,r2e+1) ];
let r2d = 0;
for (let i=1; i<r2.length; i++) {
r2d += r2[i-1].distTo2D(r2[i]);
}
let route = r1d <= r2d ? r1 : r2;
if (dbug === slice.index) console.log(slice.index, {
ints: ints.map(i=>i.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<lines.length; i += 2) {
t1 = lines[i];
if (t1.del) {
continue;
}
t2 = lines[i+1];
let d1 = t1.distToSq2D(startPoint);
let d2 = t2.distToSq2D(startPoint);
if (d1 < mindist || d2 < mindist) {
if (d2 < d1) {
p2 = t1;
p1 = t2;
} else {
p1 = t1;
p2 = t2;
}
mindist = Math.min(d1, d2);
lastIndex = i;
}
}
// use next index line strategy
// order all points by distance to last point
if (!near)
for (let i=start; i<lines.length; i += 2) {
p = lines[i];
if (p.del) {
continue;
}
if (group === null && p.index > 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);
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();
}
// anti-backlash on longer move
if (!fast && antiBacklash && dist > retractDist) {
print.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) {
print.addOutput(preout, p1, fill, fillSpeed, extruder);
} else {
print.addOutput(preout, p1, 0, moveSpeed, extruder);
}
print.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 (let i=0; i<array.length; i++) {
next = array[i];
if (!next) continue;
poly = fnp ? fnp(next) : next;
find = poly.findClosestPointTo(startPoint);
order.push({
i: i,
n: next,
d: find.distance - (poly.depth * thinWall),
});
}
if (order.length === 0) {
return;
}
order.sort((a,b) => {
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);
}
class Counter {
constructor() {
this.map = {};
this.total = 0;
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});
}
put(key) {
const map = this.map;
const kp = key || 'bad';
map[kp] = (map[kp] || 0) + 1;
this.total++;
} else {
print.setType('shells');
if (lastTop && lastTop !== next) {
retract();
}
get() {
return { map: this.map, total: this.total };
// 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;
}
}
FDM.isDark = function() {
// 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<preout.length; i++) {
preout[i].point = preout[i].point.add(offset);
}
// add offset points to total print
print.addPrintPoints(preout, output, origin, extruder);
return startPoint.add(offset);
}
FDM.isDark = function() {
return current.print.settings.controller.dark ? true : false;
};
};
FDM.rateToColor = function(rate, max) {
FDM.rateToColor = function(rate, max) {
return FDM.isDark() ?
darkColorFunction(rate/max, 1, 0.85) :
currentColorFunction(rate/max, 1, 0.85);
};
};
FDM.prepareRender = function(levels, update, opts = {}) {
FDM.prepareRender = function(levels, update, opts = {}) {
levels = levels.filter(level => level.length);
if (levels.length === 0) {
self.worker.print.maxSpeed = 0;
@ -874,7 +1483,7 @@
const changes = [];
const retracts = [];
const engages = [];
const output = new KIRI.Layers();
const output = new kiri.Layers();
layers.push(output);
const pushPrint = (toolid, poly) => {
@ -981,8 +1590,8 @@
.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 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);
@ -1017,19 +1626,19 @@
});
// console.log({retractz});
return layers;
}
}
const colorFunctions = FDM.colorFunctions = {
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;
let currentColorFunction = colorFunctions.default;
let darkColorFunction = colorFunctions.dark;
// hsv values all = 0 to 1
function hsv2rgb(h, s, v) {
// 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);
@ -1042,9 +1651,9 @@
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) {
function color5(rgb, inc, seg) {
const dec = 1 - inc;
switch (seg) {
case 0:
@ -1073,9 +1682,9 @@
rgb.b = dec;
break;
}
}
}
function color4d(rgb, inc, seg) {
function color4d(rgb, inc, seg) {
const dec = 1 - inc;
switch (seg) {
case 0:
@ -1104,10 +1713,10 @@
rgb.b = 1;
break;
}
}
}
function color4(rgb, inc, seg) {
function color4(rgb, inc, seg) {
const dec = 1 - inc;
switch (seg) {
case 0:
@ -1131,6 +1740,6 @@
rgb.b = 0;
break;
}
}
}
})();

View file

@ -4,13 +4,15 @@
(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,
const POLY = polygons,
tracker = util.pwait,
lopacity = 0.6,
opacity = 1,
@ -23,17 +25,16 @@
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;
let isThin = false, // force line rendering
let isThin = false, // force line rendering
isFlat = false, // force flat rendering
offset = 0; // poly line generation offsets
function vopt(opt) {
function vopt(opt) {
if (opt) {
if (isFlat) {
opt.flat = true;
@ -43,30 +44,29 @@
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) {
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);
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
data.tops = tops;
delete data.groups;
return;
}
const index = zIndexes.indexOf(z);
const range = FDM.getRangeParameters(process, index);
const range = getRangeParameters(process, index);
// calculate fractional shells
let shellFrac = (range.sliceShells - (range.sliceShells | 0));
let sliceShells = range.sliceShells | 0;
@ -86,7 +86,7 @@
const fillOff = fillOffset * spaceMult;
const nutops = [];
// co-locate shell processing with top generation in slicer
for (let top of tops) {
for (let top of data.tops) {
nutops.push(FDM.share.doTopShells(z, top, count, offset/2, offset, fillOff, {
vase: vaseMode,
thin: process.detectThinWalls && !isSynth,
@ -97,9 +97,9 @@
data.clip = clipOffset ? POLY.offset(nutops.map(t => t.simple), clipOffset) : undefined;
data.tops = nutops;
delete data.groups;
};
};
FDM.sliceAll = function(settings, onupdate) {
FDM.sliceAll = function(settings, onupdate) {
// future home of brim and anchor generation
let widgets = Object.values(kiri.worker.cache)
.filter(w => !w.meta.disabled)
@ -123,9 +123,9 @@
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
@ -138,7 +138,7 @@
* @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) {
FDM.slice = function(settings, widget, onupdate, ondone) {
let render = settings.render !== false,
{ process, device, controller } = settings,
isBelt = device.bedBelt,
@ -161,8 +161,7 @@
fillOffset = lineWidth * fillOffsetMult,
clipOffset = process.sliceSupportOffset,
sliceFillAngle = process.sliceFillAngle,
supportDensity = process.sliceSupportDensity,
beltfact = Math.cos(Math.PI/4);
supportDensity = process.sliceSupportDensity;
// override globals used by vopt()
isFlat = controller.lineType === "flat";
@ -782,13 +781,13 @@
}
}
}
}
function bound(v,min,max) {
function bound(v,min,max) {
return Math.max(min,Math.min(max,v));
}
}
function doRender(slice, isSynth, params, devel) {
function doRender(slice, isSynth, params, devel) {
const output = slice.output();
const height = slice.height / 2;
const solidWidth = params.sliceFillWidth || 1;
@ -873,18 +872,18 @@
}
// console.log(slice.index, slice.render.stats);
}
}
// shared with SLA driver and minions
FDM.share = {
// 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.
@ -898,13 +897,13 @@
* @param {number} fillOffset
* @param {Obejct} options
*/
function doShells(slice, count, offset1, offsetN, fillOffset, opt = {}) {
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 = {}) {
function doTopShells(z, top, count, offset1, offsetN, fillOffset, opt = {}) {
// pretend we're a top object in minions
if (!top.poly) {
top = { poly: top };
@ -1040,9 +1039,9 @@
// 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.
*
@ -1062,13 +1061,13 @@
});
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 = {}) {
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
@ -1232,14 +1231,14 @@
}
}
}
};
};
/**
/**
* 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 = {}) {
function doDiff(slice, minArea, options = {}) {
const { sla, fakedown } = options;
if (slice.index === 0 && !fakedown) {
return;
@ -1261,42 +1260,42 @@
POLY.subtract(topInner, downInner, bridges, flats, slice.z, minArea, {
wasm: true
});
};
};
/**
/**
*
*
* @param {Polygon[]} polys
*/
function addSolidFills(slice, 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) {
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) {
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) {
function doSolidsFill(slice, spacing, angle, minArea, fillQ) {
let minarea = minArea || 1,
tops = slice.tops,
solids = slice.solids;
@ -1384,20 +1383,20 @@
}
}
}
}
}
function doFillArea(fillQ, polys, angle, spacing, output, minLen, maxLen) {
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 = {}) {
async function doSupport(slice, proc, shadow, opt = {}) {
let maxBridge = proc.sliceSupportSpan || 5,
minArea = proc.supportMinArea || 0.1,
pillarSize = proc.sliceSupportSize,
@ -1554,9 +1553,9 @@
depth++;
}
}
}
function doSupportFill(promises, slice, linewidth, density, minArea, isBelt) {
function doSupportFill(promises, slice, linewidth, density, minArea, isBelt) {
let supports = slice.supports,
nsB = [],
nsC = [],
@ -1583,9 +1582,9 @@
// re-assign new supports back to slice
slice.supports = supports;
};
};
function fillSupportPolys(promises, polys, linewidth, density, z, isBelt) {
function fillSupportPolys(promises, polys, linewidth, density, z, isBelt) {
// calculate fill density
let spacing = linewidth * (1 / density);
polys.forEach(function (poly) {
@ -1599,9 +1598,9 @@
}
return true;
});
}
}
/**
/**
*
* @param {Slice} slice
* @param {Polygon[]} polys
@ -1610,7 +1609,7 @@
* @param {boolean} first
* @returns {*}
*/
function projectSolid(slice, polys, count, up, first) {
function projectSolid(slice, polys, count, up, first) {
if (!slice || slice.isSolidLayer || count <= 0) {
return;
}
@ -1625,15 +1624,15 @@
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 cullIntersections() {
function toLines(pts) {
let lns = [];
for (let i=0, il=pts.length; i<il; i += 2) {
@ -1675,9 +1674,9 @@
good.push(al.b);
}
return good.length > 2 ? good : [];
}
}
FDM.supports = function(settings, widget) {
FDM.supports = function(settings, widget) {
let isBelt = settings.device.bedBelt;
let process = settings.process;
let size = process.sliceSupportSize;
@ -1815,9 +1814,9 @@
widget.supports = add;
return add.length > 0;
};
};
class Vector3Cache {
class Vector3Cache {
constructor() {
this.cache = {};
}
@ -1831,9 +1830,9 @@
}
return val;
}
}
}
class Coplanars {
class Coplanars {
constructor() {
this.cache = {};
}
@ -1895,6 +1894,6 @@
// console.log(out);
return out;
}
}
}
})();

View file

@ -131,6 +131,7 @@ kiri.consts = {
MODES,
VIEWS,
SEED,
beltfact: Math.cos(Math.PI / 4)
};
})();

View file

@ -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<ints.length; i += 2) {
let i1 = ints[i];
let i2 = ints[i+1];
// different poly. force retract
if (i1.poly !== i2.poly) {
if (dbug === slice.index) console.log(slice.index, {int_diff_poly: ints, i});
return false;
}
// mark invalid intersect pairs (low or zero dist, etc)
// TODO: only if this is the outer pair and there are closer inner pairs
if (i1.ip.distTo2D(i2.ip) < retractDist) {
if (dbug === slice.index) console.log(slice.index, {int_dist_too_small: i1.ip.distTo2D(i2.ip), retractDist});
ints[i] = undefined;
ints[i+1] = undefined;
}
}
// filter out invalid intersection pairs
ints = ints.filter(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; i<ints.length; i += 2) {
let i1 = ints[0];
let i2 = ints[1];
// output first point
scope.addOutput(preout, i1.ip, 0, moveSpeed, extruder);
// create two loops around poly
// find shortest of two paths and emit poly points
let poly = i1.poly;
let isCW = poly.isClockwise();
let points = poly.points;
let p1p = isCW ? points : points.slice().reverse(); // CW
let p2p = isCW ? points.slice().reverse() : points; // CCW
let r1s = p1p.indexOf(isCW ? i1.ip.p2 : i1.ip.p1);
let r1e = p1p.indexOf(isCW ? i2.ip.p1 : i2.ip.p2);
let r1 = r1s === r1e ?
[ p1p[r1s] ] : r1s < r1e ?
[ ...p1p.slice(r1s,r1e+1) ] :
[ ...p1p.slice(r1s), ...p1p.slice(0,r1e+1) ];
let r1d = 0;
for (let i=1; i<r1.length; i++) {
r1d += r1[i-1].distTo2D(r1[i]);
}
let r2s = p2p.indexOf(isCW ? i1.ip.p1 : i1.ip.p2);
let r2e = p2p.indexOf(isCW ? i2.ip.p2 : i2.ip.p1);
let r2 = r2s === r2e ?
[ p2p[r2s] ] : r2s < r2e ?
[ ...p2p.slice(r2s,r2e+1) ] :
[ ...p2p.slice(r2s), ...p2p.slice(0,r2e+1) ];
let r2d = 0;
for (let i=1; i<r2.length; i++) {
r2d += r2[i-1].distTo2D(r2[i]);
}
let route = r1d <= r2d ? r1 : r2;
if (dbug === slice.index) console.log(slice.index, {
ints: ints.map(i=>i.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<lines.length; i += 2) {
t1 = lines[i];
if (t1.del) {
continue;
}
t2 = lines[i+1];
let d1 = t1.distToSq2D(startPoint);
let d2 = t2.distToSq2D(startPoint);
if (d1 < mindist || d2 < mindist) {
if (d2 < d1) {
p2 = t1;
p1 = t2;
} else {
p1 = t1;
p2 = t2;
}
mindist = Math.min(d1, d2);
lastIndex = i;
}
}
// use next index line strategy
// order all points by distance to last point
if (!near)
for (i=start; i<lines.length; i += 2) {
p = lines[i];
if (p.del) {
continue;
}
if (group === null && p.index > 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<array.length; i++) {
next = array[i];
if (!next) continue;
poly = fnp ? fnp(next) : next;
find = poly.findClosestPointTo(startPoint);
order.push({
i: i,
n: next,
d: find.distance - (poly.depth * thinWall),
});
}
if (order.length === 0) {
return;
}
order.sort((a,b) => {
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<preout.length; i++) {
preout[i].point = preout[i].point.add(offset);
}
// add offset points to total print
this.addPrintPoints(preout, output, origin, extruder);
return startPoint.add(offset);
}
parseSVG(code, offset) {
let scope = this,
svg = new DOMParser().parseFromString(code, 'text/xml'),
@ -890,7 +260,6 @@ class Print {
const output = scope.output = [ seq ];
const beltaxis = { X: "X", Y: "Z", Z: "Y", E: "E", F: "F" };
const beltfact = Math.cos(Math.PI / 4);
function LOG() {
console.log(...[...arguments].map(o => Object.clone(o)));
@ -1181,10 +550,6 @@ class Output {
}
}
function pref(a,b) {
return a !== undefined ? a : b;
}
kiri.Print = Print;
})();