2020-06-03 22:39:30 -04:00
|
|
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
2017-04-18 17:09:33 -04:00
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
const { base, kiri } = self;
|
|
|
|
|
const { paths, util, newPoint, Polygon } = base;
|
|
|
|
|
const { tip2tipEmit } = paths;
|
|
|
|
|
|
|
|
|
|
kiri.newPrint = function(settings, widgets, id) {
|
|
|
|
|
return new Print(settings, widgets, id);
|
|
|
|
|
};
|
2017-04-18 17:09:33 -04:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
class Print {
|
|
|
|
|
constructor(settings, widgets, id) {
|
2017-04-18 17:09:33 -04:00
|
|
|
this.id = id || new Date().getTime().toString(36);
|
|
|
|
|
this.settings = settings;
|
|
|
|
|
this.widgets = widgets;
|
2022-02-19 16:16:49 -05:00
|
|
|
this.lastPoint = null;
|
|
|
|
|
this.lastEmit = null;
|
|
|
|
|
this.lastOut = null;
|
|
|
|
|
this.lastPos = null;
|
|
|
|
|
this.tools = {};
|
|
|
|
|
// set to 1 to enable flow rate analysis (console)
|
|
|
|
|
this.debugE = settings ? (settings.controller.devel ? 1 : 0) : 0;
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
setType(type) {
|
|
|
|
|
this.nextType = type;
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
addOutput(array, point, emit, speed, tool) {
|
|
|
|
|
let { lastPoint, lastEmit, lastOut } = this;
|
2017-12-13 08:15:37 -05:00
|
|
|
// drop duplicates (usually intruced by FDM bisections)
|
2018-11-20 10:02:31 -05:00
|
|
|
if (lastPoint && point) {
|
|
|
|
|
// nested due to uglify confusing browser
|
2022-02-19 16:16:49 -05:00
|
|
|
const { x, y, z } = lastPoint;
|
|
|
|
|
if (point.x == x && point.y == y && point.z == z && lastEmit == emit) {
|
2020-11-16 11:07:21 -05:00
|
|
|
return lastOut;
|
2018-11-20 10:02:31 -05:00
|
|
|
}
|
2017-11-11 15:37:59 -05:00
|
|
|
}
|
2020-11-18 13:17:05 -05:00
|
|
|
// if (emit && emit < 1) console.log(emit);
|
2022-02-19 16:16:49 -05:00
|
|
|
this.lastPoint = point;
|
|
|
|
|
this.lastEmit = emit;
|
|
|
|
|
this.lastOut = lastOut = new Output(point, emit, speed, tool, this.nextType);
|
|
|
|
|
if (tool !== undefined) {
|
|
|
|
|
this.tools[tool] = true;
|
|
|
|
|
}
|
2020-11-16 11:07:21 -05:00
|
|
|
array.push(lastOut);
|
2022-02-19 16:16:49 -05:00
|
|
|
this.nextType = undefined;
|
2020-11-16 11:07:21 -05:00
|
|
|
return lastOut;
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
addPrintPoints(input, output, startPoint, tool) {
|
|
|
|
|
if (this.startPoint && input.length > 0) {
|
|
|
|
|
this.lastPoint = this.startPoint;
|
|
|
|
|
// TODO: revisit seek to origin as the first move
|
|
|
|
|
// addOutput(output, startPoint, 0, undefined, tool);
|
|
|
|
|
}
|
|
|
|
|
output.appendAll(input);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fdm & laser
|
|
|
|
|
polyPrintPath(poly, startPoint, output, options = {}) {
|
2017-04-18 17:09:33 -04:00
|
|
|
poly.setClockwise();
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
const scope = this;
|
|
|
|
|
const { settings } = scope;
|
|
|
|
|
const { process } = settings;
|
|
|
|
|
|
|
|
|
|
let shortDist = process.outputShortDistance,
|
2020-03-04 21:47:05 -05:00
|
|
|
shellMult = pref(options.extrude, process.outputShellMult),
|
2017-12-13 16:04:56 -05:00
|
|
|
printSpeed = options.rate || process.outputFeedrate,
|
|
|
|
|
moveSpeed = process.outputSeekrate,
|
2019-05-04 09:56:21 -04:00
|
|
|
minSpeed = process.outputMinSpeed,
|
2022-02-19 16:16:49 -05:00
|
|
|
coastDist = options.coast || 0,
|
2020-04-10 21:26:07 -04:00
|
|
|
closest = options.simple ? poly.first() : poly.findClosestPointTo(startPoint),
|
2018-01-11 08:57:46 -05:00
|
|
|
perimeter = poly.perimeter(),
|
2020-04-10 21:26:07 -04:00
|
|
|
close = !options.open,
|
2022-02-19 16:16:49 -05:00
|
|
|
tool = options.tool,
|
2019-04-02 19:08:19 -07:00
|
|
|
last = startPoint,
|
2022-02-19 16:16:49 -05:00
|
|
|
first = true;
|
2017-04-18 17:09:33 -04:00
|
|
|
|
2019-04-02 19:08:19 -07:00
|
|
|
// if short, use calculated print speed based on sliding scale
|
|
|
|
|
if (perimeter < process.outputShortPoly) {
|
2019-05-04 09:56:21 -04:00
|
|
|
printSpeed = minSpeed + (printSpeed - minSpeed) * (perimeter / process.outputShortPoly);
|
2018-01-11 08:57:46 -05:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
poly.forEachPoint((point, pos, points, count) => {
|
2017-04-18 17:09:33 -04:00
|
|
|
if (first) {
|
2019-04-02 19:08:19 -07:00
|
|
|
if (options.onfirst) {
|
|
|
|
|
options.onfirst(point);
|
|
|
|
|
}
|
2021-03-12 12:39:36 -05:00
|
|
|
// move to first output point on poly
|
2022-02-19 16:16:49 -05:00
|
|
|
let out = scope.addOutput(output, point, 0, moveSpeed, tool);
|
2021-11-21 21:19:24 -05:00
|
|
|
if (options.onfirstout) {
|
|
|
|
|
options.onfirstout(out);
|
|
|
|
|
}
|
2022-02-19 16:16:49 -05:00
|
|
|
first = false;
|
|
|
|
|
} else {
|
|
|
|
|
let seglen = last.distTo2D(point);
|
|
|
|
|
if (coastDist && shellMult && perimeter - seglen <= coastDist) {
|
|
|
|
|
let delta = perimeter - coastDist;
|
|
|
|
|
let offset = seglen - delta;
|
|
|
|
|
let offPoint = last.offsetPointFrom(point, offset)
|
|
|
|
|
scope.addOutput(output, offPoint, shellMult, printSpeed, tool);
|
|
|
|
|
shellMult = 0;
|
|
|
|
|
}
|
|
|
|
|
perimeter -= seglen;
|
|
|
|
|
scope.addOutput(output, point, shellMult, printSpeed, tool);
|
|
|
|
|
}
|
|
|
|
|
last = point;
|
|
|
|
|
}, close, closest.index);
|
|
|
|
|
|
|
|
|
|
return output.last().point;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extrudePerMM(noz, fil, slice) {
|
|
|
|
|
return ((Math.PI * util.sqr(noz / 2)) / (Math.PI * util.sqr(fil / 2))) * (slice / noz);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constReplace(str, consts, start, pad, short) {
|
|
|
|
|
let cs = str.indexOf("{", start || 0),
|
|
|
|
|
ce = str.indexOf("}", cs),
|
|
|
|
|
tok, nutok, nustr;
|
|
|
|
|
if (cs >=0 && ce > cs) {
|
|
|
|
|
tok = str.substring(cs+1,ce);
|
|
|
|
|
let eva = [];
|
|
|
|
|
for (let [k,v] of Object.entries(consts)) {
|
|
|
|
|
switch (typeof v) {
|
|
|
|
|
case 'number':
|
|
|
|
|
case 'boolean':
|
|
|
|
|
eva.push(`let ${k} = ${v};`);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (v === undefined) v = '';
|
|
|
|
|
eva.push(`let ${k} = "${v.replace(/\"/g,"\\\"")}";`);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
eva.push(`function range(a,b) { return (a + (layer / layers) * (b-a)).round(4) }`)
|
|
|
|
|
eva.push(`try {( ${tok} )} catch (e) {console.log(e);0}`);
|
|
|
|
|
let scr = eva.join('');
|
|
|
|
|
let evl = eval(`{ ${scr} }`);
|
|
|
|
|
nutok = evl;
|
|
|
|
|
if (pad === 666) {
|
|
|
|
|
return evl;
|
|
|
|
|
}
|
|
|
|
|
if (pad) {
|
|
|
|
|
nutok = nutok.toString();
|
|
|
|
|
let oldln = ce-cs+1;
|
|
|
|
|
let tokln = nutok.length;
|
|
|
|
|
if (tokln < oldln) {
|
|
|
|
|
short = (short || 1) + (oldln - tokln);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
nustr = str.replace("{"+tok+"}",nutok);
|
|
|
|
|
return this.constReplace(nustr, consts, ce+1+(nustr.length-str.length), pad, short);
|
|
|
|
|
} else {
|
|
|
|
|
// insert compensating spaces for accumulated replace string shortages
|
|
|
|
|
if (short) {
|
|
|
|
|
let si = str.indexOf(';');
|
|
|
|
|
if (si > 0) {
|
|
|
|
|
str = str.replace(';', ';'.padStart(short,' '));
|
2017-12-13 16:04:56 -05:00
|
|
|
}
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
2022-02-19 16:16:49 -05:00
|
|
|
return str;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-18 17:09:33 -04:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
// fdm only
|
|
|
|
|
slicePrintPath(slice, startPoint, offset, output, opt = {}) {
|
|
|
|
|
const scope = this;
|
|
|
|
|
const { settings } = scope;
|
|
|
|
|
const { device } = settings;
|
2017-04-18 17:09:33 -04:00
|
|
|
|
2020-04-09 22:42:40 -04:00
|
|
|
// console.log({slicePrintPath: slice.index, ext:slice.extruder});
|
2020-03-14 20:26:00 -04:00
|
|
|
let i,
|
2017-04-18 17:09:33 -04:00
|
|
|
preout = [],
|
2021-02-11 14:33:00 -05:00
|
|
|
process = opt.params || settings.process,
|
2020-04-09 22:42:40 -04:00
|
|
|
extruder = slice.extruder || 0,
|
2021-01-11 19:14:07 -05:00
|
|
|
nozzleSize = device.extruders[extruder].extNozzle,
|
2018-01-14 21:49:45 -05:00
|
|
|
firstLayer = opt.first || false,
|
2020-04-09 22:42:40 -04:00
|
|
|
thinWall = nozzleSize * (opt.thinWall || 1.75),
|
2018-01-15 22:17:56 -05:00
|
|
|
retractDist = opt.retractOver || 2,
|
2021-03-15 11:00:05 -04:00
|
|
|
solidWidth = process.sliceFillWidth || 1,
|
2018-01-14 18:43:43 -05:00
|
|
|
fillMult = opt.mult || process.outputFillMult,
|
|
|
|
|
shellMult = opt.mult || process.outputShellMult || (process.laserSliceHeight >= 0 ? 1 : 0),
|
2020-11-21 12:32:55 -05:00
|
|
|
shellOrder = {"out-in":-1,"in-out":1}[process.sliceShellOrder] || -1,
|
2017-11-10 15:02:14 -05:00
|
|
|
sparseMult = process.outputSparseMult,
|
2019-04-02 19:08:19 -07:00
|
|
|
coastDist = process.outputCoastDist || 0,
|
2018-01-14 18:43:43 -05:00
|
|
|
finishSpeed = opt.speed || process.outputFinishrate,
|
2018-01-18 21:53:46 -05:00
|
|
|
firstShellSpeed = process.firstLayerRate,
|
|
|
|
|
firstFillSpeed = process.firstLayerFillRate,
|
2018-01-19 19:23:02 -05:00
|
|
|
firstPrintMult = process.firstLayerPrintMult,
|
2018-01-18 21:53:46 -05:00
|
|
|
printSpeed = opt.speed || (firstLayer ? firstShellSpeed : process.outputFeedrate),
|
|
|
|
|
fillSpeed = opt.speed || opt.fillSpeed || (firstLayer ? firstFillSpeed || firstShellSpeed : process.outputFeedrate),
|
2021-01-25 08:49:42 -05:00
|
|
|
infillSpeed = process.sliceFillRate || opt.infillSpeed || fillSpeed || printSpeed,
|
2017-12-13 16:04:56 -05:00
|
|
|
moveSpeed = process.outputSeekrate,
|
2017-04-18 17:09:33 -04:00
|
|
|
origin = startPoint.add(offset),
|
2018-02-28 10:10:57 -05:00
|
|
|
zhop = process.zHopDistance || 0,
|
2018-02-07 23:29:52 -05:00
|
|
|
antiBacklash = process.antiBacklash,
|
2021-02-09 15:38:05 -05:00
|
|
|
wipeDist = process.outputRetractWipe || 0,
|
2021-01-11 19:14:07 -05:00
|
|
|
isBelt = device.bedBelt,
|
2021-05-02 10:52:48 -04:00
|
|
|
beltFirst = process.outputBeltFirst || false,
|
2021-01-11 19:14:07 -05:00
|
|
|
startClone = startPoint.clone(),
|
2021-03-11 17:24:44 -05:00
|
|
|
seedPoint = opt.seedPoint || startPoint,
|
2021-02-09 15:38:05 -05:00
|
|
|
z = slice.z,
|
|
|
|
|
lastPoly;
|
2017-04-18 17:09:33 -04:00
|
|
|
|
2018-01-19 19:23:02 -05:00
|
|
|
// apply first layer extrusion multipliers
|
|
|
|
|
if (firstLayer) {
|
|
|
|
|
fillMult *= firstPrintMult;
|
|
|
|
|
shellMult *= firstPrintMult;
|
|
|
|
|
sparseMult *= firstPrintMult;
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-09 21:55:25 -05:00
|
|
|
function retract() {
|
2021-02-09 15:38:05 -05:00
|
|
|
let array = preout.length ? preout : output;
|
|
|
|
|
if (array.length) {
|
|
|
|
|
let last = array.last();
|
|
|
|
|
last.retract = true;
|
|
|
|
|
if (wipeDist && lastPoly && last.point) {
|
2021-02-09 19:08:28 -05:00
|
|
|
let endpoint = last.point.followTo(lastPoly.center(true), wipeDist);
|
|
|
|
|
if (endpoint.inPolygon(lastPoly)) {
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(array, endpoint);
|
2021-02-09 19:08:28 -05:00
|
|
|
}
|
2021-02-09 15:38:05 -05:00
|
|
|
}
|
2021-02-09 13:09:56 -05:00
|
|
|
} else if (opt.pretract) {
|
2021-02-09 15:38:05 -05:00
|
|
|
opt.pretract(wipeDist);
|
2021-02-09 13:09:56 -05:00
|
|
|
} else {
|
|
|
|
|
console.log('unable to retract. no preout or output');
|
|
|
|
|
}
|
2017-12-09 21:55:25 -05:00
|
|
|
}
|
|
|
|
|
|
2018-01-16 16:01:53 -05:00
|
|
|
function intersectsTop(p1, p2) {
|
2021-04-28 15:05:35 -04:00
|
|
|
if (slice.index < 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-03-14 20:26:00 -04:00
|
|
|
let int = false;
|
2022-02-19 16:16:49 -05:00
|
|
|
slice.topPolysFlat().forEach((poly) => {
|
|
|
|
|
if (!int) poly.forEachSegment((s1, s2) => {
|
|
|
|
|
if (util.intersect(p1,p2,s1,s2,base.key.SEGINT)) {
|
2021-03-12 12:39:36 -05:00
|
|
|
return int = true;
|
2018-01-16 16:01:53 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2021-04-30 09:49:13 -04:00
|
|
|
// if intersecting, look for a route around
|
2021-11-03 10:07:19 -04:00
|
|
|
if (int && opt.routeAround) {
|
2021-04-30 09:49:13 -04:00
|
|
|
return !routeAround(p1, p2);
|
|
|
|
|
}
|
2018-01-16 16:01:53 -05:00
|
|
|
return int;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-17 20:51:51 -04:00
|
|
|
// returns true if routed around or no retract requried
|
2021-04-30 09:49:13 -04:00
|
|
|
function routeAround(p1, p2) {
|
2021-03-12 12:39:36 -05:00
|
|
|
const dbug = false;
|
2021-04-29 12:50:30 -04:00
|
|
|
if (dbug === slice.index) console.log(slice.index, {p1, p2, d: p1.distTo2D(p2)});
|
|
|
|
|
|
2021-03-12 12:39:36 -05:00
|
|
|
let ints = [];
|
2021-04-29 12:50:30 -04:00
|
|
|
let tops = slice.topRouteFlat();
|
2021-03-12 12:39:36 -05:00
|
|
|
for (let poly of tops) {
|
2022-02-19 16:16:49 -05:00
|
|
|
poly.forEachSegment((s1, s2) => {
|
|
|
|
|
let ip = util.intersect(p1,p2,s1,s2,base.key.SEGINT);
|
2021-03-12 12:39:36 -05:00
|
|
|
if (ip) {
|
|
|
|
|
ints.push({ip, poly});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 14:44:30 -05:00
|
|
|
// no intersections
|
|
|
|
|
if (ints.length === 0) {
|
2021-04-29 12:50:30 -04:00
|
|
|
if (dbug === slice.index) console.log(slice.index, 'no ints');
|
2021-03-12 14:44:30 -05:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 12:39:36 -05:00
|
|
|
// odd # of intersections ?!? do retraction
|
|
|
|
|
if (ints.length && ints.length % 2 !== 0) {
|
|
|
|
|
if (dbug === slice.index) console.log(slice.index, {odd_intersects: ints});
|
2021-05-17 20:51:51 -04:00
|
|
|
return false;
|
2021-03-12 12:39:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sort by distance
|
|
|
|
|
ints.sort((a, b) => {
|
|
|
|
|
return a.ip.dist - b.ip.dist;
|
|
|
|
|
});
|
|
|
|
|
|
2021-04-29 12:50:30 -04:00
|
|
|
if (dbug === slice.index) console.log(slice.index, {ints});
|
|
|
|
|
|
2021-03-12 12:39:36 -05:00
|
|
|
// check pairs. eliminate too close points.
|
|
|
|
|
// pairs must intersect same poly or retract.
|
|
|
|
|
for (let i=0; i<ints.length; i += 2) {
|
2021-04-29 12:50:30 -04:00
|
|
|
let i1 = ints[i];
|
|
|
|
|
let i2 = ints[i+1];
|
2021-03-12 12:39:36 -05:00
|
|
|
// different poly. force retract
|
|
|
|
|
if (i1.poly !== i2.poly) {
|
|
|
|
|
if (dbug === slice.index) console.log(slice.index, {int_diff_poly: ints, i});
|
2021-05-17 20:51:51 -04:00
|
|
|
return false;
|
2021-03-12 12:39:36 -05:00
|
|
|
}
|
2021-04-29 12:50:30 -04:00
|
|
|
// mark invalid intersect pairs (low or zero dist, etc)
|
2021-04-30 09:49:13 -04:00
|
|
|
// TODO: only if this is the outer pair and there are closer inner pairs
|
2021-03-12 12:39:36 -05:00
|
|
|
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});
|
2021-04-29 12:50:30 -04:00
|
|
|
ints[i] = undefined;
|
|
|
|
|
ints[i+1] = undefined;
|
2021-03-12 12:39:36 -05:00
|
|
|
}
|
|
|
|
|
}
|
2021-04-29 12:50:30 -04:00
|
|
|
// filter out invalid intersection pairs
|
|
|
|
|
ints = ints.filter(i => i);
|
2021-03-12 12:39:36 -05:00
|
|
|
|
2021-05-17 20:51:51 -04:00
|
|
|
if (ints.length > 2) {
|
|
|
|
|
if (dbug === slice.index) console.log(slice.index, {complex_route: ints.length});
|
|
|
|
|
return false;
|
2021-03-12 12:39:36 -05:00
|
|
|
}
|
|
|
|
|
|
2021-05-17 20:51:51 -04:00
|
|
|
if (ints.length === 2) {
|
2021-03-12 12:39:36 -05:00
|
|
|
// 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
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, i1.ip, 0, moveSpeed, extruder);
|
2021-03-12 12:39:36 -05:00
|
|
|
|
|
|
|
|
// 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,
|
2021-04-29 12:50:30 -04:00
|
|
|
p1, p2, dist: p1.distTo2D(p2),
|
2021-03-12 12:39:36 -05:00
|
|
|
r1, r1d, r1s, r1e,
|
|
|
|
|
r2, r2d, r2s, r2e,
|
|
|
|
|
isCW});
|
|
|
|
|
|
|
|
|
|
for (let p of route) {
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, p, 0, moveSpeed, extruder);
|
2021-03-12 12:39:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// output last point
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, i2.ip, 0, moveSpeed, extruder);
|
2021-03-12 12:39:36 -05:00
|
|
|
}
|
2021-05-17 20:51:51 -04:00
|
|
|
return true;
|
2021-03-12 12:39:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-18 22:47:52 -05:00
|
|
|
function outputTraces(poly, opt = {}) {
|
2017-04-18 17:09:33 -04:00
|
|
|
if (!poly) return;
|
|
|
|
|
if (Array.isArray(poly)) {
|
2021-02-18 22:47:52 -05:00
|
|
|
if (opt.sort) {
|
2022-02-19 16:16:49 -05:00
|
|
|
let polys = poly.slice().sort((a,b) => {
|
2021-02-18 22:47:52 -05:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-20 22:50:03 -05:00
|
|
|
} else {
|
|
|
|
|
outputOrderClosest(poly, function(next) {
|
|
|
|
|
outputTraces(next, opt);
|
|
|
|
|
}, null);
|
2021-02-18 22:47:52 -05:00
|
|
|
}
|
2017-04-18 17:09:33 -04:00
|
|
|
} else {
|
2020-03-14 20:26:00 -04:00
|
|
|
let finishShell = poly.depth === 0 && !firstLayer;
|
2017-12-13 16:04:56 -05:00
|
|
|
startPoint = scope.polyPrintPath(poly, startPoint, preout, {
|
2020-04-09 22:42:40 -04:00
|
|
|
tool: extruder,
|
2018-01-14 18:43:43 -05:00
|
|
|
rate: finishShell ? finishSpeed : printSpeed,
|
2017-12-15 12:16:41 -05:00
|
|
|
accel: finishShell,
|
2019-04-02 19:08:19 -07:00
|
|
|
wipe: process.outputWipeDistance || 0,
|
|
|
|
|
coast: firstLayer ? 0 : coastDist,
|
2021-02-18 22:47:52 -05:00
|
|
|
extrude: pref(opt.extrude, shellMult),
|
2017-12-13 16:04:56 -05:00
|
|
|
onfirst: function(firstPoint) {
|
2021-03-11 17:24:44 -05:00
|
|
|
let from = seedPoint || startPoint;
|
2021-03-11 16:52:58 -05:00
|
|
|
if (from.distTo2D(firstPoint) > retractDist) {
|
2021-03-12 12:39:36 -05:00
|
|
|
if (intersectsTop(from, firstPoint)) {
|
|
|
|
|
retract();
|
|
|
|
|
}
|
2019-03-04 18:12:00 -05:00
|
|
|
}
|
2021-03-11 17:24:44 -05:00
|
|
|
seedPoint = null;
|
2017-12-09 21:55:25 -05:00
|
|
|
}
|
2017-10-29 22:35:28 -04:00
|
|
|
});
|
2021-02-09 18:29:43 -05:00
|
|
|
lastPoly = slice.lastPoly = poly;
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Polygon[]} polys
|
|
|
|
|
*/
|
2020-03-05 22:26:33 -05:00
|
|
|
function outputSparse(polys, extrude, speed) {
|
2017-04-18 17:09:33 -04:00
|
|
|
if (!polys) return;
|
2022-02-19 16:16:49 -05:00
|
|
|
let proxy = polys.map((poly) => {
|
2017-10-29 19:08:10 -04:00
|
|
|
return {poly: poly, first: poly.first(), last: poly.last()};
|
|
|
|
|
});
|
2020-03-14 20:26:00 -04:00
|
|
|
let lp = startPoint;
|
2022-02-19 16:16:49 -05:00
|
|
|
startPoint = tip2tipEmit(proxy, startPoint, (el, point, count) => {
|
2020-03-14 20:26:00 -04:00
|
|
|
let poly = el.poly;
|
2020-03-11 08:37:49 -04:00
|
|
|
if (poly.last() === point) {
|
|
|
|
|
poly.reverse();
|
|
|
|
|
}
|
2022-02-19 16:16:49 -05:00
|
|
|
poly.forEachPoint((p, i) => {
|
2020-11-19 21:33:47 -05:00
|
|
|
let dist = lp.distTo2D(p);
|
|
|
|
|
let rdst = dist > retractDist;
|
|
|
|
|
let itop = rdst && intersectsTop(lp,p);
|
|
|
|
|
let emit = extrude;
|
2018-01-16 16:01:53 -05:00
|
|
|
// retract if dist trigger and crosses a slice top polygon
|
2020-11-19 21:33:47 -05:00
|
|
|
if (i === 0) {
|
|
|
|
|
if (itop) {
|
|
|
|
|
retract();
|
|
|
|
|
emit = 0;
|
2020-11-19 21:42:27 -05:00
|
|
|
} else if (dist > nozzleSize) {
|
2020-11-19 21:33:47 -05:00
|
|
|
emit = 0;
|
|
|
|
|
}
|
2018-01-16 16:01:53 -05:00
|
|
|
}
|
2020-11-19 21:33:47 -05:00
|
|
|
// let emit = i === 0 ? 0 : extrude;
|
2021-10-25 22:09:22 -04:00
|
|
|
// handle shallow cloned infill
|
|
|
|
|
if (poly.z !== undefined) {
|
|
|
|
|
p = p.clone().setZ(poly.z);
|
|
|
|
|
}
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, p, emit, speed || printSpeed, extruder);
|
2017-10-29 19:46:34 -04:00
|
|
|
lp = p;
|
2021-02-22 22:37:38 -05:00
|
|
|
}, !poly.open);
|
2020-03-11 08:37:49 -04:00
|
|
|
return lp;
|
2017-04-18 17:09:33 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-30 17:35:10 -05:00
|
|
|
function outputThin(lines) {
|
2021-12-30 17:39:25 -05:00
|
|
|
if (!lines) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-12-30 17:35:10 -05:00
|
|
|
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
|
|
|
|
|
)
|
|
|
|
|
});
|
2022-02-19 16:16:49 -05:00
|
|
|
let order = util.orderClosest(points, (p1, p2) => p1.distTo2D(p2));
|
2021-12-30 17:35:10 -05:00
|
|
|
if (order.length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let first = points[0];
|
|
|
|
|
let last = first;
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, first, 0, moveSpeed, extruder);
|
2021-12-30 17:35:10 -05:00
|
|
|
for (let p of order) {
|
|
|
|
|
let dist = last ? last.distTo2D(p) : 0;
|
|
|
|
|
if (dist > thinWall) {
|
|
|
|
|
retract();
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, p, 0, moveSpeed, extruder);
|
2021-12-30 17:35:10 -05:00
|
|
|
}
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, p, 1, fillSpeed, extruder);
|
2021-12-30 17:35:10 -05:00
|
|
|
last = p;
|
|
|
|
|
}
|
|
|
|
|
// close a circle
|
|
|
|
|
if (last && last.distTo2D(first) <= thinWall) {
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, first, 1, fillSpeed, extruder);
|
2021-12-30 17:35:10 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-15 11:00:05 -04:00
|
|
|
function outputFills(lines, opt = {}) {
|
2020-11-01 21:11:26 -05:00
|
|
|
if (!lines || lines.length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-14 20:26:00 -04:00
|
|
|
let p, p1, p2, dist, len, found, group, mindist, t1, t2,
|
2018-02-11 16:18:46 -05:00
|
|
|
marked = 0,
|
2018-02-11 22:59:15 -05:00
|
|
|
start = 0,
|
2018-02-12 08:42:33 -05:00
|
|
|
skip = false,
|
2020-03-04 17:40:11 -05:00
|
|
|
lastIndex = -1,
|
2021-03-15 11:00:05 -04:00
|
|
|
flow = opt.flow || 1,
|
2020-11-01 21:11:26 -05:00
|
|
|
near = opt.near || false,
|
2020-03-04 17:40:11 -05:00
|
|
|
fast = opt.fast || false,
|
2021-03-15 11:00:05 -04:00
|
|
|
fill = (opt.fill >= 0 ? opt.fill : fillMult) * flow,
|
2020-11-01 23:23:58 -05:00
|
|
|
thinDist = near ? thinWall : thinWall;
|
2018-02-11 22:59:15 -05:00
|
|
|
|
|
|
|
|
while (lines && marked < lines.length) {
|
|
|
|
|
group = null;
|
2020-11-01 21:11:26 -05:00
|
|
|
found = false;
|
2017-04-18 17:09:33 -04:00
|
|
|
mindist = Infinity;
|
2018-02-11 22:59:15 -05:00
|
|
|
|
2020-11-01 21:11:26 -05:00
|
|
|
// 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
|
2017-12-09 21:55:25 -05:00
|
|
|
// order all points by distance to last point
|
2020-11-01 21:11:26 -05:00
|
|
|
if (!near)
|
2018-02-11 22:59:15 -05:00
|
|
|
for (i=start; i<lines.length; i += 2) {
|
|
|
|
|
p = lines[i];
|
2020-11-01 21:11:26 -05:00
|
|
|
if (p.del) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2018-02-11 22:59:15 -05:00
|
|
|
if (group === null && p.index > lastIndex) {
|
|
|
|
|
group = p.index;
|
|
|
|
|
}
|
|
|
|
|
if (group !== null) {
|
2020-11-01 21:11:26 -05:00
|
|
|
if (p.index !== group) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-02-11 22:59:15 -05:00
|
|
|
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;
|
|
|
|
|
}
|
2018-02-11 16:18:46 -05:00
|
|
|
}
|
2018-02-11 22:59:15 -05:00
|
|
|
|
|
|
|
|
// go back to start and try again
|
2020-11-01 21:11:26 -05:00
|
|
|
if (!near && !found) {
|
2020-05-06 14:54:03 -04:00
|
|
|
if (start === 0 && lastIndex === -1) {
|
|
|
|
|
console.log('infinite loop', lines, {
|
2021-08-30 15:05:29 -04:00
|
|
|
marked, i, group, start, lastIndex,
|
2020-05-06 14:54:03 -04:00
|
|
|
points: lines.map(p => p.index).join(', ')
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-02-11 22:59:15 -05:00
|
|
|
start = 0;
|
|
|
|
|
lastIndex = -1;
|
2018-02-11 16:18:46 -05:00
|
|
|
continue;
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
2017-12-08 13:27:15 -05:00
|
|
|
|
2020-11-01 21:11:26 -05:00
|
|
|
dist = startPoint.distToSq2D(p1);
|
|
|
|
|
len = p1.distToSq2D(p2);
|
2017-12-13 16:04:56 -05:00
|
|
|
|
2018-02-12 08:42:33 -05:00
|
|
|
// go back to start when dist > retractDist
|
2020-11-01 21:11:26 -05:00
|
|
|
if (!near && !fast && !skip && dist > retractDist) {
|
2018-02-12 08:42:33 -05:00
|
|
|
skip = true;
|
|
|
|
|
start = 0;
|
|
|
|
|
lastIndex = -1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
skip = false;
|
|
|
|
|
|
2018-02-13 14:46:37 -05:00
|
|
|
// mark as used (temporarily)
|
|
|
|
|
p1.del = true;
|
|
|
|
|
p2.del = true;
|
|
|
|
|
marked += 2;
|
|
|
|
|
lastIndex = p1.index;
|
|
|
|
|
|
2018-02-11 22:59:15 -05:00
|
|
|
// 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.
|
2020-11-01 21:11:26 -05:00
|
|
|
if (dist <= thinDist && len <= thinDist) {
|
2018-02-11 22:59:15 -05:00
|
|
|
p2 = p1.midPointTo(p2);
|
2022-02-19 16:16:49 -05:00
|
|
|
// this.addOutput(preout, p2, fill * (dist / thinWall), fillSpeed, extruder);
|
|
|
|
|
scope.addOutput(preout, p2, fill, fillSpeed, extruder);
|
2018-02-11 22:59:15 -05:00
|
|
|
} else {
|
|
|
|
|
// retract if dist trigger or crosses a slice top polygon
|
2020-04-11 21:26:00 -04:00
|
|
|
if (!fast && dist > retractDist && (zhop || intersectsTop(startPoint, p1))) {
|
2018-02-11 22:59:15 -05:00
|
|
|
retract();
|
2017-10-29 22:35:28 -04:00
|
|
|
}
|
2017-10-29 16:51:32 -04:00
|
|
|
|
2018-02-11 22:59:15 -05:00
|
|
|
// anti-backlash on longer move
|
2020-04-11 21:26:00 -04:00
|
|
|
if (!fast && antiBacklash && dist > retractDist) {
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, p1.add({x:antiBacklash,y:-antiBacklash,z:0}), 0, moveSpeed, extruder);
|
2018-02-11 22:59:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// bridge ends of fill when they're close together
|
2020-11-01 21:11:26 -05:00
|
|
|
if (dist < thinDist) {
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, p1, fill, fillSpeed, extruder);
|
2018-02-11 22:59:15 -05:00
|
|
|
} else {
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, p1, 0, moveSpeed, extruder);
|
2018-02-11 22:59:15 -05:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.addOutput(preout, p2, fill, fillSpeed, extruder);
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
2018-02-11 22:59:15 -05:00
|
|
|
|
|
|
|
|
startPoint = p2;
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
2018-02-12 08:42:33 -05:00
|
|
|
|
2017-04-18 17:09:33 -04:00
|
|
|
// clear delete marks so we can re-print later
|
2022-02-19 16:16:49 -05:00
|
|
|
if (lines) lines.forEach(p => { p.del = false });
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2017-12-15 08:54:39 -05:00
|
|
|
* 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.
|
|
|
|
|
*
|
2021-02-18 22:47:52 -05:00
|
|
|
* @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
|
2017-04-18 17:09:33 -04:00
|
|
|
*/
|
2021-02-18 22:47:52 -05:00
|
|
|
function outputOrderClosest(array, fn, fnp) {
|
2017-12-14 20:47:50 -05:00
|
|
|
if (array.length === 1) {
|
2017-12-14 23:05:27 -05:00
|
|
|
return fn(array[0]);
|
2017-12-14 20:47:50 -05:00
|
|
|
}
|
2017-04-18 17:09:33 -04:00
|
|
|
array = array.slice();
|
2020-03-14 20:26:00 -04:00
|
|
|
let closest, find, next, order, poly, lastDepth = 0;
|
2017-04-18 17:09:33 -04:00
|
|
|
for (;;) {
|
2017-12-14 23:05:27 -05:00
|
|
|
order = [];
|
2017-04-18 17:09:33 -04:00
|
|
|
closest = null;
|
|
|
|
|
for (i=0; i<array.length; i++) {
|
|
|
|
|
next = array[i];
|
|
|
|
|
if (!next) continue;
|
|
|
|
|
poly = fnp ? fnp(next) : next;
|
|
|
|
|
find = poly.findClosestPointTo(startPoint);
|
2017-12-14 23:05:27 -05:00
|
|
|
order.push({
|
|
|
|
|
i: i,
|
|
|
|
|
n: next,
|
2018-01-19 19:23:02 -05:00
|
|
|
d: find.distance - (poly.depth * thinWall),
|
2017-12-14 23:05:27 -05:00
|
|
|
});
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
2017-12-14 23:05:27 -05:00
|
|
|
if (order.length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-02-19 16:16:49 -05:00
|
|
|
order.sort((a,b) => {
|
2017-12-14 23:05:27 -05:00
|
|
|
return a.d - b.d;
|
|
|
|
|
});
|
|
|
|
|
array[order[0].i] = null;
|
|
|
|
|
fn(order[0].n);
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-23 16:19:28 -04:00
|
|
|
let out = [];
|
|
|
|
|
if (slice.tops) {
|
|
|
|
|
out.appendAll(slice.tops);
|
|
|
|
|
};
|
|
|
|
|
if (opt.support && slice.supports) {
|
|
|
|
|
out.appendAll(slice.supports);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-14 20:26:00 -04:00
|
|
|
let lastTop = null;
|
2020-04-23 16:19:28 -04:00
|
|
|
outputOrderClosest(out, function(next) {
|
2017-04-18 17:09:33 -04:00
|
|
|
if (next instanceof Polygon) {
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.setType('support');
|
2017-04-18 17:09:33 -04:00
|
|
|
// support polygon
|
|
|
|
|
next.setZ(z);
|
2017-11-01 16:33:06 -04:00
|
|
|
outputTraces([next].appendAll(next.inner || []));
|
2020-11-22 20:21:41 -05:00
|
|
|
if (next.fill) {
|
2022-02-19 16:16:49 -05:00
|
|
|
next.fill.forEach(p => { p.z = z });
|
2020-11-22 20:21:41 -05:00
|
|
|
outputFills(next.fill, {fast: true});
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
} else {
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.setType('shells');
|
2021-05-01 01:01:15 -04:00
|
|
|
if (lastTop && lastTop !== next) {
|
|
|
|
|
retract();
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
// 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'),
|
|
|
|
|
lines = [...svg.getElementsByTagName('polyline')],
|
|
|
|
|
output = scope.output = [],
|
|
|
|
|
bounds = scope.bounds = {
|
|
|
|
|
max: { x:-Infinity, y:-Infinity, z:-Infinity},
|
|
|
|
|
min: { x:Infinity, y:Infinity, z:Infinity}
|
|
|
|
|
};
|
|
|
|
|
lines.forEach(line => {
|
|
|
|
|
let seq = [];
|
|
|
|
|
let points = [...line.points];
|
|
|
|
|
points.forEach(point => {
|
|
|
|
|
if (offset) {
|
|
|
|
|
point.x += offset.x;
|
|
|
|
|
point.y += offset.y;
|
|
|
|
|
}
|
|
|
|
|
if (point.x) bounds.min.x = Math.min(bounds.min.x, point.x);
|
|
|
|
|
if (point.x) bounds.max.x = Math.max(bounds.max.x, point.x);
|
|
|
|
|
if (point.y) bounds.min.y = Math.min(bounds.min.y, point.y);
|
|
|
|
|
if (point.y) bounds.max.y = Math.max(bounds.max.y, point.y);
|
|
|
|
|
if (point.z) bounds.min.z = Math.min(bounds.min.z, point.z);
|
|
|
|
|
if (point.z) bounds.max.z = Math.max(bounds.max.z, point.z);
|
|
|
|
|
const { x, y, z } = point; // SVGPoint is not serializable
|
|
|
|
|
scope.addOutput(seq, { x, y, z }, seq.length > 0);
|
|
|
|
|
});
|
|
|
|
|
output.push(seq);
|
|
|
|
|
});
|
|
|
|
|
scope.imported = code;
|
|
|
|
|
scope.lines = lines.length;
|
|
|
|
|
scope.bytes = code.length;
|
|
|
|
|
return scope.output;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
parseGCode(gcode, offset, progress, done, opts = {}) {
|
|
|
|
|
const fdm = opts.fdm;
|
|
|
|
|
const belt = opts.belt;
|
|
|
|
|
const lines = gcode
|
|
|
|
|
.toUpperCase()
|
|
|
|
|
.replace("X", " X")
|
|
|
|
|
.replace("Y", " Y")
|
|
|
|
|
.replace("Z", " Z")
|
|
|
|
|
.replace("E", " E")
|
|
|
|
|
.replace("F", " F")
|
|
|
|
|
.replace(" ", " ")
|
|
|
|
|
.split("\n");
|
|
|
|
|
|
|
|
|
|
const scope = this,
|
|
|
|
|
// morph = false,
|
|
|
|
|
morph = true,
|
|
|
|
|
bounds = scope.bounds = {
|
|
|
|
|
max: { x:-Infinity, y:-Infinity, z:-Infinity},
|
|
|
|
|
min: { x:Infinity, y:Infinity, z:Infinity}
|
|
|
|
|
},
|
|
|
|
|
pos = {
|
|
|
|
|
X: 0.0,
|
|
|
|
|
Y: 0.0,
|
|
|
|
|
Z: 0.0,
|
|
|
|
|
F: 0.0,
|
|
|
|
|
E: 0.0
|
|
|
|
|
},
|
|
|
|
|
off = {
|
|
|
|
|
X: offset ? offset.x || 0 : 0,
|
|
|
|
|
Y: offset ? offset.y || 0 : 0,
|
|
|
|
|
Z: offset ? offset.z || 0 : 0
|
|
|
|
|
},
|
|
|
|
|
xoff = {
|
|
|
|
|
X: 0,
|
|
|
|
|
Y: 0,
|
|
|
|
|
Z: 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let dz = 0,
|
|
|
|
|
abs = true,
|
|
|
|
|
absE = true,
|
|
|
|
|
defh = 0,
|
|
|
|
|
height = 0,
|
|
|
|
|
factor = 1,
|
|
|
|
|
tool = 0,
|
|
|
|
|
minf = Infinity,
|
|
|
|
|
maxf = 0,
|
|
|
|
|
seq = [],
|
|
|
|
|
autolayer = true,
|
|
|
|
|
newlayer = false,
|
|
|
|
|
arcdivs = Math.PI / 24,
|
|
|
|
|
hasmoved = false;
|
|
|
|
|
|
|
|
|
|
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)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function G2G3(g2, line) {
|
|
|
|
|
const rec = {};
|
|
|
|
|
|
|
|
|
|
line.forEach(tok => {
|
|
|
|
|
rec[tok.charAt(0)] = parseFloat(tok.substring(1));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let center = { x:0, y:0, r:0 };
|
|
|
|
|
|
|
|
|
|
if (rec.I !== undefined && rec.J !== undefined) {
|
|
|
|
|
center.x = pos.X + rec.I;
|
|
|
|
|
center.y = pos.Y + rec.J;
|
|
|
|
|
center.r = Math.sqrt(rec.I*rec.I + rec.J*rec.J);
|
|
|
|
|
} else if (rec.R !== undefined) {
|
|
|
|
|
let pd = { x: rec.X - pos.X, y: rec.Y - pos.Y };
|
|
|
|
|
let dst = Math.sqrt(pd.x * pd.x + pd.y * pd.y) / 2;
|
|
|
|
|
let pr2;
|
|
|
|
|
if (Math.abs(dst - rec.R) < 0.001) {
|
|
|
|
|
// center point radius
|
|
|
|
|
pr2 = { x: (rec.X + pos.X) / 2, y: (rec.Y + pos.Y) / 2};
|
|
|
|
|
} else {
|
|
|
|
|
// triangulate
|
|
|
|
|
pr2 = base.util.center2pr({
|
|
|
|
|
x: pos.X,
|
|
|
|
|
y: pos.Y
|
|
|
|
|
}, {
|
|
|
|
|
x: rec.X,
|
|
|
|
|
y: rec.Y
|
|
|
|
|
}, rec.R, g2);
|
|
|
|
|
}
|
|
|
|
|
center.x = pr2.x;
|
|
|
|
|
center.y = pr2.y;
|
|
|
|
|
center.r = rec.R;
|
|
|
|
|
} else {
|
|
|
|
|
console.log({malfomed_arc: line});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// line angles
|
|
|
|
|
let a1 = Math.atan2(center.y - pos.Y, center.x - pos.X) + Math.PI;
|
|
|
|
|
let a2 = Math.atan2(center.y - rec.Y, center.x - rec.X) + Math.PI;
|
|
|
|
|
let ad = base.util.thetaDiff(a1, a2, g2);
|
|
|
|
|
let steps = Math.max(Math.floor(Math.abs(ad) / arcdivs), 3);
|
|
|
|
|
let step = (Math.abs(ad) > 0.001 ? ad : Math.PI * 2) / steps;
|
|
|
|
|
let rot = a1 + step;
|
|
|
|
|
|
|
|
|
|
// LOG({first: pos, last: rec, center, a1, a2, ad, step, rot, line});
|
|
|
|
|
// G0G1(false, [`X${center.x}`, `Y${center.y}`, `E1`]);
|
|
|
|
|
|
|
|
|
|
let pc = { X: pos.X, Y: pos.Y };
|
|
|
|
|
for (let i=0; i<=steps-2; i++) {
|
|
|
|
|
let np = {
|
|
|
|
|
X: center.x + Math.cos(rot) * center.r,
|
|
|
|
|
Y: center.y + Math.sin(rot) * center.r
|
|
|
|
|
};
|
|
|
|
|
rot += step;
|
|
|
|
|
G0G1(false, [`X${np.X}`, `Y${np.Y}`, `E1`]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
G0G1(false, [`X${rec.X}`, `Y${rec.Y}`, `E1`]);
|
|
|
|
|
|
|
|
|
|
pos.X = rec.X;
|
|
|
|
|
pos.Y = rec.Y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function G0G1(g0, line) {
|
|
|
|
|
const mov = {};
|
|
|
|
|
const axes = {};
|
2020-12-27 19:55:30 -05:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
line.forEach(tok => {
|
|
|
|
|
let axis = tok.charAt(0);
|
|
|
|
|
if (morph && belt) {
|
|
|
|
|
axis = beltaxis[axis];
|
|
|
|
|
}
|
|
|
|
|
let val = parseFloat(tok.substring(1));
|
|
|
|
|
axes[axis] = val;
|
|
|
|
|
if (abs) {
|
|
|
|
|
pos[axis] = val;
|
|
|
|
|
} else {
|
|
|
|
|
mov[axis] = val;
|
|
|
|
|
pos[axis] += val;
|
2021-01-11 19:14:07 -05:00
|
|
|
}
|
2022-02-19 16:16:49 -05:00
|
|
|
});
|
2021-01-11 19:14:07 -05:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
const point = newPoint(
|
|
|
|
|
factor * pos.X + off.X + xoff.X,
|
|
|
|
|
factor * pos.Y + off.Y + xoff.Y,
|
|
|
|
|
factor * pos.Z + off.Z + xoff.Z + dz
|
|
|
|
|
);
|
2019-03-04 18:12:00 -05:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
if (morph && belt) {
|
|
|
|
|
point.y -= point.z * beltfact;
|
|
|
|
|
point.z *= beltfact;
|
|
|
|
|
}
|
2021-02-18 22:47:52 -05:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
const retract = (fdm && pos.E < 0) || undefined;
|
|
|
|
|
const moving = g0 || (fdm && (pos.E <= 0 || !(axes.X || axes.Y || axes.Z)));
|
2020-01-28 18:16:28 -05:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
if (!moving && point.x) bounds.min.x = Math.min(bounds.min.x, point.x);
|
|
|
|
|
if (!moving && point.x) bounds.max.x = Math.max(bounds.max.x, point.x);
|
|
|
|
|
if (!moving && point.y) bounds.min.y = Math.min(bounds.min.y, point.y);
|
|
|
|
|
if (!moving && point.y) bounds.max.y = Math.max(bounds.max.y, point.y);
|
|
|
|
|
if (!moving && point.z) bounds.min.z = Math.min(bounds.min.z, point.z);
|
|
|
|
|
if (!moving && point.z) bounds.max.z = Math.max(bounds.max.z, point.z);
|
2019-03-04 18:12:00 -05:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
// update max speed
|
|
|
|
|
if (pos.F) minf = Math.min(minf, pos.F);
|
|
|
|
|
maxf = Math.max(maxf, pos.F);
|
2020-11-01 21:11:26 -05:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
// always add moves to the current sequence
|
|
|
|
|
if (moving) {
|
|
|
|
|
scope.addOutput(seq, point, false, pos.F, tool).retract = retract;
|
|
|
|
|
scope.lastPos = Object.assign({}, pos);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-01-31 09:33:10 -05:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
if (seq.Z === undefined) {
|
|
|
|
|
seq.Z = pos.Z;
|
|
|
|
|
}
|
2020-01-28 18:16:28 -05:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
if (fdm && height === 0) {
|
|
|
|
|
seq.height = defh = height = pos.Z;
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
// non-move in a new plane means burp out
|
|
|
|
|
// the old sequence and start a new one
|
|
|
|
|
if (newlayer || (autolayer && seq.Z != pos.Z)) {
|
|
|
|
|
newlayer = false;
|
|
|
|
|
let nh = (defh || pos.Z - seq.Z);
|
|
|
|
|
seq = [];
|
|
|
|
|
seq.height = height = nh;
|
|
|
|
|
if (fdm) dz = -height / 2;
|
|
|
|
|
output.push(seq);
|
2020-03-05 18:36:12 -05:00
|
|
|
}
|
2022-02-19 16:16:49 -05:00
|
|
|
|
|
|
|
|
if (!hasmoved && !moving) {
|
|
|
|
|
seq.height = seq.Z = pos.Z;
|
|
|
|
|
hasmoved = true;
|
2020-03-04 21:47:05 -05:00
|
|
|
}
|
2020-03-04 17:40:11 -05:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
// debug extrusion rate
|
|
|
|
|
const lastPos = scope.lastPos;
|
|
|
|
|
if (scope.debugE && fdm && lastPos && pos.E) {
|
|
|
|
|
// extruder move
|
|
|
|
|
let dE = (absE ? pos.E - scope.lastPosE : pos.E);
|
|
|
|
|
// distance moved in XY
|
|
|
|
|
let dV = Math.sqrt(
|
|
|
|
|
(Math.pow(pos.X - lastPos.X, 2)) +
|
|
|
|
|
(Math.pow(pos.Y - lastPos.Y, 2))
|
|
|
|
|
);
|
|
|
|
|
// filament per mm
|
|
|
|
|
let dR = (dE / dV);
|
|
|
|
|
if (dV > 2 && dE > 0.001) {
|
|
|
|
|
let lab = (absE ? 'aA' : 'rR')[scope.debugE++ % 2];
|
|
|
|
|
console.log(lab, height.toFixed(2), dV.toFixed(2), dE.toFixed(3), dR.toFixed(4), pos.F.toFixed(0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// add point to current sequence
|
|
|
|
|
scope.addOutput(seq, point, true, pos.F, tool).retract = retract;
|
|
|
|
|
scope.lastPos = Object.assign({}, pos);
|
|
|
|
|
scope.lastPosE = pos.E;
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
lines.forEach((line, idx) => {
|
|
|
|
|
if (line.indexOf(';LAYER:') === 0) {
|
|
|
|
|
newlayer = true;
|
|
|
|
|
autolayer = false;
|
|
|
|
|
}
|
|
|
|
|
if (line.indexOf('- LAYER ') > 0) {
|
|
|
|
|
seq.height = defh;
|
|
|
|
|
const hd = line.replace('(','').replace(')','').split(' ');
|
|
|
|
|
defh = parseFloat(hd[4]);
|
|
|
|
|
if (fdm) dz = -defh / 2;
|
|
|
|
|
newlayer = true;
|
|
|
|
|
autolayer = false;
|
|
|
|
|
}
|
|
|
|
|
line = line.split(";")[0].split(" ").filter(v => v);
|
|
|
|
|
let cmd = line.shift();
|
|
|
|
|
if (!cmd) return;
|
|
|
|
|
if (cmd.charAt(0) === 'T') {
|
|
|
|
|
let ext = scope.settings.device.extruders;
|
|
|
|
|
let pos = parseInt(cmd.charAt(1));
|
|
|
|
|
if (ext && ext[pos]) {
|
|
|
|
|
xoff.X = -ext[pos].extOffsetX;
|
|
|
|
|
xoff.Y = -ext[pos].extOffsetY;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pos.E = 0.0;
|
|
|
|
|
switch (cmd) {
|
|
|
|
|
case 'M82':
|
|
|
|
|
absE = true;
|
|
|
|
|
break;
|
|
|
|
|
case 'M83':
|
|
|
|
|
absE = false;
|
|
|
|
|
break;
|
|
|
|
|
case 'G20':
|
|
|
|
|
factor = 25.4;
|
|
|
|
|
break;
|
|
|
|
|
case 'G21':
|
|
|
|
|
factor = 1;
|
|
|
|
|
break;
|
|
|
|
|
case 'G90':
|
|
|
|
|
// absolute positioning
|
|
|
|
|
abs = true;
|
|
|
|
|
break;
|
|
|
|
|
case 'G91':
|
|
|
|
|
// relative positioning
|
|
|
|
|
abs = false;
|
|
|
|
|
break;
|
|
|
|
|
case 'G92':
|
|
|
|
|
line.forEach(tok => {
|
|
|
|
|
pos[tok.charAt(0)] = parseFloat(tok.substring(1));
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case 'G10':
|
|
|
|
|
seq.last().retract = true;
|
|
|
|
|
break;
|
|
|
|
|
case 'G11':
|
|
|
|
|
break;
|
|
|
|
|
case 'G0':
|
|
|
|
|
G0G1(true, line);
|
|
|
|
|
break;
|
|
|
|
|
case 'G1':
|
|
|
|
|
G0G1(false, line);
|
|
|
|
|
break;
|
|
|
|
|
case 'G2':
|
|
|
|
|
// turn arc into a series of points
|
|
|
|
|
G2G3(true, line)
|
|
|
|
|
break;
|
|
|
|
|
case 'G3':
|
|
|
|
|
// turn arc into a series of points
|
|
|
|
|
G2G3(false, line);
|
|
|
|
|
break;
|
|
|
|
|
case 'M6':
|
|
|
|
|
tool = parseInt(line[0].substring(1));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
});
|
2017-04-18 17:09:33 -04:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
// recenter for visualization
|
|
|
|
|
// if (false && morph && belt) {
|
|
|
|
|
// for (let layer of output) {
|
|
|
|
|
// for (let rec of layer) {
|
|
|
|
|
// let point = rec.point;
|
|
|
|
|
// point.y -= bounds.min.z;
|
|
|
|
|
// point.z += bounds.min.y;
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
2017-04-18 17:09:33 -04:00
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
scope.imported = gcode;
|
|
|
|
|
scope.lines = lines.length;
|
|
|
|
|
scope.bytes = gcode.length;
|
|
|
|
|
scope.minSpeed = Math.floor(minf / 60);
|
|
|
|
|
scope.maxSpeed = Math.floor(maxf / 60);
|
|
|
|
|
scope.belt = belt;
|
|
|
|
|
|
|
|
|
|
done({ output: scope.output });
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Output {
|
|
|
|
|
constructor(point, emit, speed, tool, type) {
|
|
|
|
|
this.point = point; // point to emit
|
|
|
|
|
this.emit = emit; // emit (feed for printers, power for lasers, cut for cam)
|
|
|
|
|
this.speed = speed;
|
|
|
|
|
this.tool = tool;
|
|
|
|
|
this.type = type;
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-19 16:16:49 -05:00
|
|
|
clone(z) {
|
|
|
|
|
let o = new Output(
|
|
|
|
|
this.point.clone(),
|
|
|
|
|
this.emit,
|
|
|
|
|
this.speed,
|
|
|
|
|
this.tool,
|
|
|
|
|
this.type
|
|
|
|
|
);
|
|
|
|
|
if (z !== undefined) {
|
|
|
|
|
o.point.setZ(z);
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
2022-02-19 16:16:49 -05:00
|
|
|
return o;
|
2017-04-18 17:09:33 -04:00
|
|
|
}
|
2022-02-19 16:16:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pref(a,b) {
|
|
|
|
|
return a !== undefined ? a : b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kiri.Print = Print;
|
2017-04-18 17:09:33 -04:00
|
|
|
|
|
|
|
|
})();
|