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 = {
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,30 +505,30 @@
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);
}
@ -554,7 +551,7 @@
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;
@ -565,7 +562,7 @@
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);
});
@ -594,7 +591,7 @@
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,13 +599,13 @@
}
});
sa.splice(ix,1);
API.conf.save();
api.conf.save();
scheduleSupportSave(widget);
fromPillar = undefined;
}
function updateVisiblity() {
API.widgets.all().forEach(w => {
api.widgets.all().forEach(w => {
setSupportVisiblity(w, lastMode === 'FDM' && lastView === VIEWS.ARRANGE);
});
}
@ -621,7 +618,7 @@
function clearAllWidgetSupports() {
let cleared = 0;
API.widgets.all().forEach(widget => {
api.widgets.all().forEach(widget => {
cleared += clearWidgetSupports(widget);
});
return cleared;
@ -635,7 +632,7 @@
cleared++;
});
widget.sups = {};
delete API.widgets.annotate(widget.id).support;
delete api.widgets.annotate(widget.id).support;
scheduleSupportSave(widget);
return cleared;
}
@ -649,9 +646,10 @@
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,19 +661,20 @@
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;
}

View file

@ -4,18 +4,10 @@
(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 };
// shared by client and worker contexts
function getRangeParameters(process, index) {
if (index === undefined || index === null || index < 0) {
return process;
@ -36,36 +28,41 @@
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;
/**
* @returns {Array} gcode lines
*/
FDM.export = function(print, online, ondone, ondebug) {
const { settings, belty, tools } = print;
const { controller, device } = settings;
const { extruders } = device;
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 ---");

View file

@ -4,16 +4,16 @@
(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
@ -25,31 +25,26 @@
// 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);
}, {
@ -767,20 +763,633 @@
return print.render;
};
class Counter {
constructor() {
this.map = {};
this.total = 0;
// 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;
}
put(key) {
const map = this.map;
const kp = key || 'bad';
map[kp] = (map[kp] || 0) + 1;
this.total++;
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);
}
get() {
return { map: this.map, total: this.total };
}
} 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);
}
let lastTop = null;
outputOrderClosest(out, function(next) {
if (next instanceof Polygon) {
print.setType('support');
// support polygon
next.setZ(z);
outputTraces([next].appendAll(next.inner || []));
if (next.fill) {
next.fill.forEach(p => { p.z = z });
outputFills(next.fill, {fast: true});
}
} else {
print.setType('shells');
if (lastTop && lastTop !== next) {
retract();
}
// 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
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() {
@ -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);

View file

@ -5,10 +5,12 @@
(function() {
const { base, kiri, noop } = self;
const { driver, fill, fill_fixed, newSlice, utils } = kiri;
const { consts, driver, fill, fill_fixed, newSlice, utils } = kiri;
const { config, polygons, util, newPoint } = base;
const { fillArea } = polygons;
const { beltfact } = consts;
const { FDM } = driver;
const { getRangeParameters } = FDM;
const POLY = polygons,
tracker = util.pwait,
@ -23,7 +25,6 @@
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,
@ -55,18 +56,17 @@
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,
@ -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";

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;
})();