move fdm print funcs to fdm mode. code style update
This commit is contained in:
parent
8eed635c3f
commit
0dd617feeb
10 changed files with 5055 additions and 5088 deletions
2
app.js
2
app.js
|
|
@ -410,6 +410,7 @@ const script = {
|
|||
"geo/slicer",
|
||||
"geo/mesh",
|
||||
// "moto/broker",
|
||||
"kiri/consts",
|
||||
"kiri/pack",
|
||||
"kiri/utils",
|
||||
"kiri/slice",
|
||||
|
|
@ -459,6 +460,7 @@ const script = {
|
|||
"geo/polygon",
|
||||
"geo/gyroid",
|
||||
"geo/slicer",
|
||||
"kiri/consts",
|
||||
"kiri-mode/fdm/driver",
|
||||
"kiri-mode/fdm/slice",
|
||||
"kiri/utils",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
} } )));
|
||||
};
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ---");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ kiri.consts = {
|
|||
MODES,
|
||||
VIEWS,
|
||||
SEED,
|
||||
beltfact: Math.cos(Math.PI / 4)
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue