move fdm print funcs to fdm mode. code style update

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

2
app.js
View file

@ -410,6 +410,7 @@ const script = {
"geo/slicer",
"geo/mesh",
// "moto/broker",
"kiri/consts",
"kiri/pack",
"kiri/utils",
"kiri/slice",
@ -459,6 +460,7 @@ const script = {
"geo/polygon",
"geo/gyroid",
"geo/slicer",
"kiri/consts",
"kiri-mode/fdm/driver",
"kiri-mode/fdm/slice",
"kiri/utils",

File diff suppressed because it is too large Load diff

View file

@ -4,69 +4,66 @@
(function() {
const KIRI = self.kiri,
BASE = self.base,
UTIL = BASE.util,
POLY = BASE.polygons,
FDM = KIRI.driver.FDM = {
// init, // src/mode/fdm/client.js
// slice, // src/mode/fdm/slice.js
// prepare, // src/mode/fdm/prepare.js
// export, // src/mode/fdm/export.js
getRangeParameters
};
const { kiri } = self;
const FDM = kiri.driver.FDM = { getRangeParameters };
function getRangeParameters(process, index) {
if (index === undefined || index === null || index < 0) {
return process;
}
let ranges = process.ranges;
if (!(ranges && ranges.length)) {
return process;
}
let params = Object.clone(process);
for (let range of ranges) {
if (index >= range.lo && index <= range.hi) {
for (let [key,value] of Object.entries(range.fields)) {
params[key] = value;
params._range = true;
}
// shared by client and worker contexts
function getRangeParameters(process, index) {
if (index === undefined || index === null || index < 0) {
return process;
}
let ranges = process.ranges;
if (!(ranges && ranges.length)) {
return process;
}
let params = Object.clone(process);
for (let range of ranges) {
if (index >= range.lo && index <= range.hi) {
for (let [key, value] of Object.entries(range.fields)) {
params[key] = value;
params._range = true;
}
}
return params;
}
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,
} } )));
};
}
});
});
})();

File diff suppressed because it is too large Load diff

View file

@ -4,317 +4,317 @@
(function() {
if (self.kiri.fill) return;
if (self.kiri.fill) return;
const KIRI = self.kiri,
BASE = self.base,
UTIL = BASE.util,
ROUND = UTIL.round,
DEG2RAD = Math.PI / 180,
FILL = self.kiri.fill = {
hex: fillHexFull,
grid: fillGrid,
gyroid: fillGyroid,
triangle: fillTriangle,
linear: fillLinear,
cubic: fillCubic
},
CACHE = self.kiri.fill_fixed = {
hex: fillHexFull,
grid: fillGrid,
triangle: fillTriangle
};
const KIRI = self.kiri,
BASE = self.base,
UTIL = BASE.util,
ROUND = UTIL.round,
DEG2RAD = Math.PI / 180,
FILL = self.kiri.fill = {
hex: fillHexFull,
grid: fillGrid,
gyroid: fillGyroid,
triangle: fillTriangle,
linear: fillLinear,
cubic: fillCubic
},
CACHE = self.kiri.fill_fixed = {
hex: fillHexFull,
grid: fillGrid,
triangle: fillTriangle
};
function fillHexFull(target) {
fillHex(target, true);
}
function fillHexFull(target) {
fillHex(target, true);
}
/**
* emitter creates a hex infill pattern and sends to target
*
* @param {Object} target
* @param {boolean} full continuous walls
*/
function fillHex(target, full) {
// compute segment lengths (vert/horiz and 45)
let spacing = target.offset();
// let vhlen = (1 / target.density()) * (target.lineWidth() + spacing);
let vhlen = (1 / target.density()) * target.lineWidth() * 0.5;
let anxlen = ROUND(Math.cos(30 * DEG2RAD) * vhlen, 7);
let anylen = ROUND(Math.sin(30 * DEG2RAD) * vhlen, 7);
let bounds = target.bounds();
let even = true;
let evenZ = target.zIndex() % 2 === 0;
let maxy = bounds.max.y + (vhlen + anylen * 2);
let x, y;
/**
* emitter creates a hex infill pattern and sends to target
*
* @param {Object} target
* @param {boolean} full continuous walls
*/
function fillHex(target, full) {
// compute segment lengths (vert/horiz and 45)
let spacing = target.offset();
// let vhlen = (1 / target.density()) * (target.lineWidth() + spacing);
let vhlen = (1 / target.density()) * target.lineWidth() * 0.5;
let anxlen = ROUND(Math.cos(30 * DEG2RAD) * vhlen, 7);
let anylen = ROUND(Math.sin(30 * DEG2RAD) * vhlen, 7);
let bounds = target.bounds();
let even = true;
let evenZ = target.zIndex() % 2 === 0;
let maxy = bounds.max.y + (vhlen + anylen * 2);
let x, y;
if (full || evenZ) {
x = bounds.min.x;
for (;;) {
if (even && x > bounds.max.x) break;
if (!even && x > bounds.max.x + anxlen + spacing) break;
y = bounds.min.y;
target.newline();
while (y <= maxy) {
target.emit(x,y);
y += vhlen;
target.emit(x,y);
if (even) x += anxlen; else x -= anxlen;
y += anylen;
target.emit(x,y);
y += vhlen;
target.emit(x,y);
if (even) x -= anxlen; else x += anxlen;
y += anylen;
}
x += spacing;
if (even) x += (anxlen * 2);
even = !even;
target.newline();
}
} else {
y = bounds.min.y + vhlen;
for (;;) {
if (even && y > bounds.max.y) break;
if (!even && y > bounds.max.y + anylen) break;
x = bounds.min.x;
target.newline();
while (x < bounds.max.x) {
target.emit(x,y);
if (even) y += anylen; else y -= anylen;
x += anxlen;
target.emit(x,y);
x += spacing;
target.emit(x,y);
if (even) y -= anylen; else y += anylen;
x += anxlen;
target.emit(x,y);
x += spacing;
}
y += vhlen;
if (even) y += (anylen * 2);
even = !even;
target.newline();
}
}
}
function fillGyroid(target) {
let bounds = target.bounds();
let height = target.zHeight();
let span_x = bounds.max.x - bounds.min.x;
let span_y = bounds.max.y - bounds.min.y;
let density = target.density();
let tile = 1 + (1 - density) * 15;
let tile_x = span_x / tile;
let tile_y = span_y / tile;
let tile_z = 1 / tile;
let gyroid = BASE.gyroid.slice(target.zValue() * tile_z, (1 - density) * 500);
// gyroid.polys.forEach(poly => {
// for (let tx=0; tx<=tile_x; tx++) {
// for (let ty=0; ty<=tile_y; ty++) {
// target.newline();
// let bx = tx * tile + bounds.min.x;
// let by = ty * tile + bounds.min.y;
// poly.forEach(point => {
// target.emit(bx + point.x * tile, by + point.y * tile);
// });
// }
// }
// });
let polys = [];
for (let tx=0; tx<=tile_x; tx++) {
for (let ty=0; ty<=tile_y; ty++) {
for (let poly of gyroid.polys) {
target.newline();
let points = poly.map(el => {
return {
x: el.x * tile + tx * tile + bounds.min.x,
y: el.y * tile + ty * tile + bounds.min.y,
z: 0
}
});
polys.push(BASE.newPolygon().setOpen(true).addObj(points));
}
}
}
polys = connectOpenPolys(polys);
for (let poly of polys.filter(p => p.perimeter() > 2)) {
if (full || evenZ) {
x = bounds.min.x;
for (;;) {
if (even && x > bounds.max.x) break;
if (!even && x > bounds.max.x + anxlen + spacing) break;
y = bounds.min.y;
target.newline();
for (let point of poly.points) {
target.emit(point.x, point.y);
while (y <= maxy) {
target.emit(x,y);
y += vhlen;
target.emit(x,y);
if (even) x += anxlen; else x -= anxlen;
y += anylen;
target.emit(x,y);
y += vhlen;
target.emit(x,y);
if (even) x -= anxlen; else x += anxlen;
y += anylen;
}
x += spacing;
if (even) x += (anxlen * 2);
even = !even;
target.newline();
}
} else {
y = bounds.min.y + vhlen;
for (;;) {
if (even && y > bounds.max.y) break;
if (!even && y > bounds.max.y + anylen) break;
x = bounds.min.x;
target.newline();
while (x < bounds.max.x) {
target.emit(x,y);
if (even) y += anylen; else y -= anylen;
x += anxlen;
target.emit(x,y);
x += spacing;
target.emit(x,y);
if (even) y -= anylen; else y += anylen;
x += anxlen;
target.emit(x,y);
x += spacing;
}
y += vhlen;
if (even) y += (anylen * 2);
even = !even;
target.newline();
}
}
}
function fillGyroid(target) {
let bounds = target.bounds();
let height = target.zHeight();
let span_x = bounds.max.x - bounds.min.x;
let span_y = bounds.max.y - bounds.min.y;
let density = target.density();
let tile = 1 + (1 - density) * 15;
let tile_x = span_x / tile;
let tile_y = span_y / tile;
let tile_z = 1 / tile;
let gyroid = BASE.gyroid.slice(target.zValue() * tile_z, (1 - density) * 500);
// gyroid.polys.forEach(poly => {
// for (let tx=0; tx<=tile_x; tx++) {
// for (let ty=0; ty<=tile_y; ty++) {
// target.newline();
// let bx = tx * tile + bounds.min.x;
// let by = ty * tile + bounds.min.y;
// poly.forEach(point => {
// target.emit(bx + point.x * tile, by + point.y * tile);
// });
// }
// }
// });
let polys = [];
for (let tx=0; tx<=tile_x; tx++) {
for (let ty=0; ty<=tile_y; ty++) {
for (let poly of gyroid.polys) {
target.newline();
let points = poly.map(el => {
return {
x: el.x * tile + tx * tile + bounds.min.x,
y: el.y * tile + ty * tile + bounds.min.y,
z: 0
}
});
polys.push(BASE.newPolygon().setOpen(true).addObj(points));
}
}
}
polys = connectOpenPolys(polys);
for (let poly of polys.filter(p => p.perimeter() > 2)) {
target.newline();
for (let point of poly.points) {
target.emit(point.x, point.y);
}
}
}
function connectOpenPolys(noff, dist = 0.1) {
if (noff.length <= 1) {
return noff;
}
let heal = 0;
// heal/rejoin open segments that have close endpoints
outer: for(;; heal++) {
let ntmp = noff, tlen = ntmp.length;
for (let i=0; i<tlen; i++) {
let s1 = ntmp[i];
if (!s1 || !s1.open) continue;
for (let j=i+1; j<tlen; j++) {
let s2 = ntmp[j];
if (!s2 || !s2.open) continue;
if (s1.last().distTo2D(s2.first()) <= dist) {
s1.addPoints(s2.points);
ntmp[j] = null;
continue outer;
}
if (s1.first().distTo2D(s2.last()) <= dist) {
s2.addPoints(s1.points);
ntmp[i] = null;
continue outer;
}
if (s1.first().distTo2D(s2.first()) <= dist) {
s1.reverse();
s1.addPoints(s2.points);
ntmp[j] = null;
continue outer;
}
if (s1.last().distTo2D(s2.last()) <= dist) {
s1.addPoints(s2.points.reverse());
ntmp[j] = null;
continue outer;
}
}
}
break;
}
if (heal > 0) {
// cull nulls
noff = noff.filter(o => o);
}
function connectOpenPolys(noff, dist = 0.1) {
if (noff.length <= 1) {
return noff;
}
function fillGrid(target) {
let bounds = target.bounds();
let height = target.zHeight();
let span_x = bounds.max.x - bounds.min.x;
let span_y = bounds.max.y - bounds.min.y;
let offset = target.offset() / 2;
let tile = (1 / target.density()) * target.lineWidth();
let tile_x = tile + offset;
let tile_xc = span_x / tile_x;
let tile_yc = span_y / tile;
for (let tx=0; tx<=tile_xc; tx++) {
target.newline();
for (let ty=0; ty<=tile_yc; ty++) {
let bx = tx * tile_x + bounds.min.x;
let by = ty * tile + bounds.min.y;
if ((tx + ty) % 2) {
target.emit(bx, by);
target.emit(bx + tile_x - offset, by + tile);
} else {
target.emit(bx + tile_x - offset, by);
target.emit(bx, by + tile);
let heal = 0;
// heal/rejoin open segments that have close endpoints
outer: for(;; heal++) {
let ntmp = noff, tlen = ntmp.length;
for (let i=0; i<tlen; i++) {
let s1 = ntmp[i];
if (!s1 || !s1.open) continue;
for (let j=i+1; j<tlen; j++) {
let s2 = ntmp[j];
if (!s2 || !s2.open) continue;
if (s1.last().distTo2D(s2.first()) <= dist) {
s1.addPoints(s2.points);
ntmp[j] = null;
continue outer;
}
if (s1.first().distTo2D(s2.last()) <= dist) {
s2.addPoints(s1.points);
ntmp[i] = null;
continue outer;
}
if (s1.first().distTo2D(s2.first()) <= dist) {
s1.reverse();
s1.addPoints(s2.points);
ntmp[j] = null;
continue outer;
}
if (s1.last().distTo2D(s2.last()) <= dist) {
s1.addPoints(s2.points.reverse());
ntmp[j] = null;
continue outer;
}
}
}
break;
}
function fillCubic(target) {
let bounds = target.bounds();
let span = Math.max(
bounds.max.x - bounds.min.x,
bounds.max.y - bounds.min.y
);
let steps = Math.floor((span / target.lineWidth()) * target.density());
let step = span / steps;
let ztype = Math.floor(target.zIndex() / target.repeat()) % 3;
if (ztype === 1) {
for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
target.newline();
target.emit(tx, bounds.min.y);
target.emit(tx, bounds.max.y);
}
} else if (ztype === 0) {
for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
target.newline();
target.emit(bounds.min.x, ty);
target.emit(bounds.max.x, ty);
}
} else {
step *- Math.sqrt(2);
for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
target.newline();
target.emit(tx, bounds.min.y);
target.emit(tx + 1000, bounds.max.y + 1000);
}
for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
target.newline();
target.emit(bounds.min.x, ty);
target.emit(bounds.min.x + 1000, ty + 1000);
}
}
if (heal > 0) {
// cull nulls
noff = noff.filter(o => o);
}
return noff;
}
function fillLinear(target) {
let bounds = target.bounds();
let span = Math.max(
bounds.max.x - bounds.min.x,
bounds.max.y - bounds.min.y
);
let steps = Math.floor((span / target.lineWidth()) * target.density());
let step = span / steps;
let ztype = Math.floor(target.zIndex() / target.repeat()) % 2;
if (ztype === 1) {
for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
target.newline();
target.emit(tx, bounds.min.y);
target.emit(tx, bounds.max.y);
}
} else if (ztype === 0) {
for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
target.newline();
target.emit(bounds.min.x, ty);
target.emit(bounds.max.x, ty);
}
}
}
function fillGrid(target) {
let bounds = target.bounds();
let height = target.zHeight();
let span_x = bounds.max.x - bounds.min.x;
let span_y = bounds.max.y - bounds.min.y;
let offset = target.offset() / 2;
let tile = (1 / target.density()) * target.lineWidth();
let tile_x = tile + offset;
let tile_xc = span_x / tile_x;
let tile_yc = span_y / tile;
function fillTriangle(target) {
let bounds = target.bounds();
let span_x = bounds.max.x - bounds.min.x;
let span_y = bounds.max.y - bounds.min.y;
let offset = target.offset();
let line_w = target.lineWidth() / 2;
let tile = (1 / target.density()) * (target.lineWidth() * 1.25);
let tile_x = tile + offset*2 + line_w;
let tile_xc = span_x / tile_x;
let tile_yc = span_y / tile;
for (let tx=0; tx<=tile_xc; tx++) {
target.newline();
for (let ty=0; ty<=tile_yc; ty++) {
let bx = tx * tile_x + bounds.min.x;
let by = ty * tile + bounds.min.y;
if ((tx + ty) % 2) {
target.emit(bx, by);
target.emit(bx + tile_x - offset - line_w, by + tile);
} else {
target.emit(bx + tile_x - offset - line_w, by);
target.emit(bx, by + tile);
}
}
}
for (let tx=0; tx<=tile_xc; tx++) {
for (let tx=0; tx<=tile_xc; tx++) {
target.newline();
for (let ty=0; ty<=tile_yc; ty++) {
let bx = tx * tile_x + bounds.min.x;
let xp = bx + tile_x - line_w/2 - offset/2;
target.newline();
target.emit(xp, bounds.min.y);
target.emit(xp, bounds.max.y);
let by = ty * tile + bounds.min.y;
if ((tx + ty) % 2) {
target.emit(bx, by);
target.emit(bx + tile_x - offset, by + tile);
} else {
target.emit(bx + tile_x - offset, by);
target.emit(bx, by + tile);
}
}
}
}
function fillCubic(target) {
let bounds = target.bounds();
let span = Math.max(
bounds.max.x - bounds.min.x,
bounds.max.y - bounds.min.y
);
let steps = Math.floor((span / target.lineWidth()) * target.density());
let step = span / steps;
let ztype = Math.floor(target.zIndex() / target.repeat()) % 3;
if (ztype === 1) {
for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
target.newline();
target.emit(tx, bounds.min.y);
target.emit(tx, bounds.max.y);
}
} else if (ztype === 0) {
for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
target.newline();
target.emit(bounds.min.x, ty);
target.emit(bounds.max.x, ty);
}
} else {
step *- Math.sqrt(2);
for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
target.newline();
target.emit(tx, bounds.min.y);
target.emit(tx + 1000, bounds.max.y + 1000);
}
for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
target.newline();
target.emit(bounds.min.x, ty);
target.emit(bounds.min.x + 1000, ty + 1000);
}
}
}
function fillLinear(target) {
let bounds = target.bounds();
let span = Math.max(
bounds.max.x - bounds.min.x,
bounds.max.y - bounds.min.y
);
let steps = Math.floor((span / target.lineWidth()) * target.density());
let step = span / steps;
let ztype = Math.floor(target.zIndex() / target.repeat()) % 2;
if (ztype === 1) {
for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
target.newline();
target.emit(tx, bounds.min.y);
target.emit(tx, bounds.max.y);
}
} else if (ztype === 0) {
for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
target.newline();
target.emit(bounds.min.x, ty);
target.emit(bounds.max.x, ty);
}
}
}
function fillTriangle(target) {
let bounds = target.bounds();
let span_x = bounds.max.x - bounds.min.x;
let span_y = bounds.max.y - bounds.min.y;
let offset = target.offset();
let line_w = target.lineWidth() / 2;
let tile = (1 / target.density()) * (target.lineWidth() * 1.25);
let tile_x = tile + offset*2 + line_w;
let tile_xc = span_x / tile_x;
let tile_yc = span_y / tile;
for (let tx=0; tx<=tile_xc; tx++) {
target.newline();
for (let ty=0; ty<=tile_yc; ty++) {
let bx = tx * tile_x + bounds.min.x;
let by = ty * tile + bounds.min.y;
if ((tx + ty) % 2) {
target.emit(bx, by);
target.emit(bx + tile_x - offset - line_w, by + tile);
} else {
target.emit(bx + tile_x - offset - line_w, by);
target.emit(bx, by + tile);
}
}
}
for (let tx=0; tx<=tile_xc; tx++) {
let bx = tx * tile_x + bounds.min.x;
let xp = bx + tile_x - line_w/2 - offset/2;
target.newline();
target.emit(xp, bounds.min.y);
target.emit(xp, bounds.max.y);
}
}
})();

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

@ -10,7 +10,7 @@
const { feature, platform, selection, settings } = api;
const { COLOR, MODES, PMODES, VIEWS } = consts;
const LANG = lang.current,
const LANG = lang.current,
WIN = self.window,
DOC = self.document,
LOC = self.location,

View file

@ -7,6 +7,8 @@
const { base, kiri } = self;
const { paths, util, newPoint, Polygon } = base;
const { tip2tipEmit } = paths;
const { numOrDefault } = util;
const { beltfact } = kiri.consts;
kiri.newPrint = function(settings, widgets, id) {
return new Print(settings, widgets, id);
@ -70,7 +72,7 @@ class Print {
const { process } = settings;
let shortDist = process.outputShortDistance,
shellMult = pref(options.extrude, process.outputShellMult),
shellMult = numOrDefault(options.extrude, process.outputShellMult),
printSpeed = options.rate || process.outputFeedrate,
moveSpeed = process.outputSeekrate,
minSpeed = process.outputMinSpeed,
@ -169,638 +171,6 @@ class Print {
}
}
// fdm only
slicePrintPath(slice, startPoint, offset, output, opt = {}) {
const scope = this;
const { settings } = scope;
const { device } = settings;
// console.log({slicePrintPath: slice.index, ext:slice.extruder});
let i,
preout = [],
process = opt.params || settings.process,
extruder = slice.extruder || 0,
nozzleSize = device.extruders[extruder].extNozzle,
firstLayer = opt.first || false,
thinWall = nozzleSize * (opt.thinWall || 1.75),
retractDist = opt.retractOver || 2,
solidWidth = process.sliceFillWidth || 1,
fillMult = opt.mult || process.outputFillMult,
shellMult = opt.mult || process.outputShellMult || (process.laserSliceHeight >= 0 ? 1 : 0),
shellOrder = {"out-in":-1,"in-out":1}[process.sliceShellOrder] || -1,
sparseMult = process.outputSparseMult,
coastDist = process.outputCoastDist || 0,
finishSpeed = opt.speed || process.outputFinishrate,
firstShellSpeed = process.firstLayerRate,
firstFillSpeed = process.firstLayerFillRate,
firstPrintMult = process.firstLayerPrintMult,
printSpeed = opt.speed || (firstLayer ? firstShellSpeed : process.outputFeedrate),
fillSpeed = opt.speed || opt.fillSpeed || (firstLayer ? firstFillSpeed || firstShellSpeed : process.outputFeedrate),
infillSpeed = process.sliceFillRate || opt.infillSpeed || fillSpeed || printSpeed,
moveSpeed = process.outputSeekrate,
origin = startPoint.add(offset),
zhop = process.zHopDistance || 0,
antiBacklash = process.antiBacklash,
wipeDist = process.outputRetractWipe || 0,
isBelt = device.bedBelt,
beltFirst = process.outputBeltFirst || false,
startClone = startPoint.clone(),
seedPoint = opt.seedPoint || startPoint,
z = slice.z,
lastPoly;
// apply first layer extrusion multipliers
if (firstLayer) {
fillMult *= firstPrintMult;
shellMult *= firstPrintMult;
sparseMult *= firstPrintMult;
}
function retract() {
let array = preout.length ? preout : output;
if (array.length) {
let last = array.last();
last.retract = true;
if (wipeDist && lastPoly && last.point) {
let endpoint = last.point.followTo(lastPoly.center(true), wipeDist);
if (endpoint.inPolygon(lastPoly)) {
scope.addOutput(array, endpoint);
}
}
} else if (opt.pretract) {
opt.pretract(wipeDist);
} else {
console.log('unable to retract. no preout or output');
}
}
function intersectsTop(p1, p2) {
if (slice.index < 0) {
return false;
}
let int = false;
slice.topPolysFlat().forEach((poly) => {
if (!int) poly.forEachSegment((s1, s2) => {
if (util.intersect(p1,p2,s1,s2,base.key.SEGINT)) {
return int = true;
}
});
});
// if intersecting, look for a route around
if (int && opt.routeAround) {
return !routeAround(p1, p2);
}
return int;
}
// returns true if routed around or no retract requried
function routeAround(p1, p2) {
const dbug = false;
if (dbug === slice.index) console.log(slice.index, {p1, p2, d: p1.distTo2D(p2)});
let ints = [];
let tops = slice.topRouteFlat();
for (let poly of tops) {
poly.forEachSegment((s1, s2) => {
let ip = util.intersect(p1,p2,s1,s2,base.key.SEGINT);
if (ip) {
ints.push({ip, poly});
}
});
}
// no intersections
if (ints.length === 0) {
if (dbug === slice.index) console.log(slice.index, 'no ints');
return false;
}
// odd # of intersections ?!? do retraction
if (ints.length && ints.length % 2 !== 0) {
if (dbug === slice.index) console.log(slice.index, {odd_intersects: ints});
return false;
}
// sort by distance
ints.sort((a, b) => {
return a.ip.dist - b.ip.dist;
});
if (dbug === slice.index) console.log(slice.index, {ints});
// check pairs. eliminate too close points.
// pairs must intersect same poly or retract.
for (let i=0; i<ints.length; i += 2) {
let i1 = ints[i];
let i2 = ints[i+1];
// different poly. force retract
if (i1.poly !== i2.poly) {
if (dbug === slice.index) console.log(slice.index, {int_diff_poly: ints, i});
return false;
}
// mark invalid intersect pairs (low or zero dist, etc)
// TODO: only if this is the outer pair and there are closer inner pairs
if (i1.ip.distTo2D(i2.ip) < retractDist) {
if (dbug === slice.index) console.log(slice.index, {int_dist_too_small: i1.ip.distTo2D(i2.ip), retractDist});
ints[i] = undefined;
ints[i+1] = undefined;
}
}
// filter out invalid intersection pairs
ints = ints.filter(i => i);
if (ints.length > 2) {
if (dbug === slice.index) console.log(slice.index, {complex_route: ints.length});
return false;
}
if (ints.length === 2) {
// can route around intersected top polys
for (let i=0; i<ints.length; i += 2) {
let i1 = ints[0];
let i2 = ints[1];
// output first point
scope.addOutput(preout, i1.ip, 0, moveSpeed, extruder);
// create two loops around poly
// find shortest of two paths and emit poly points
let poly = i1.poly;
let isCW = poly.isClockwise();
let points = poly.points;
let p1p = isCW ? points : points.slice().reverse(); // CW
let p2p = isCW ? points.slice().reverse() : points; // CCW
let r1s = p1p.indexOf(isCW ? i1.ip.p2 : i1.ip.p1);
let r1e = p1p.indexOf(isCW ? i2.ip.p1 : i2.ip.p2);
let r1 = r1s === r1e ?
[ p1p[r1s] ] : r1s < r1e ?
[ ...p1p.slice(r1s,r1e+1) ] :
[ ...p1p.slice(r1s), ...p1p.slice(0,r1e+1) ];
let r1d = 0;
for (let i=1; i<r1.length; i++) {
r1d += r1[i-1].distTo2D(r1[i]);
}
let r2s = p2p.indexOf(isCW ? i1.ip.p1 : i1.ip.p2);
let r2e = p2p.indexOf(isCW ? i2.ip.p2 : i2.ip.p1);
let r2 = r2s === r2e ?
[ p2p[r2s] ] : r2s < r2e ?
[ ...p2p.slice(r2s,r2e+1) ] :
[ ...p2p.slice(r2s), ...p2p.slice(0,r2e+1) ];
let r2d = 0;
for (let i=1; i<r2.length; i++) {
r2d += r2[i-1].distTo2D(r2[i]);
}
let route = r1d <= r2d ? r1 : r2;
if (dbug === slice.index) console.log(slice.index, {
ints: ints.map(i=>i.ip.dist),
i1, i2, same: i1.poly === i2.poly,
route,
p1, p2, dist: p1.distTo2D(p2),
r1, r1d, r1s, r1e,
r2, r2d, r2s, r2e,
isCW});
for (let p of route) {
scope.addOutput(preout, p, 0, moveSpeed, extruder);
}
// output last point
scope.addOutput(preout, i2.ip, 0, moveSpeed, extruder);
}
return true;
}
return false;
}
function outputTraces(poly, opt = {}) {
if (!poly) return;
if (Array.isArray(poly)) {
if (opt.sort) {
let polys = poly.slice().sort((a,b) => {
return (a.perimeter() - b.perimeter()) * opt.sort;
});
let debug = polys.length > 3;
let last;
while (polys.length) {
let next;
for (let p of polys) {
if (!last) {
next = p;
break;
}
if (opt.sort > 0) {
// in-out
if (last.isInside(p)) {
next = p;
break;
}
} else {
// out-in
if (p.isInside(last)) {
next = p;
break;
}
}
}
if (next) {
last = next;
polys.remove(next);
outputTraces(next, opt);
} else {
last = null;
}
}
} else {
outputOrderClosest(poly, function(next) {
outputTraces(next, opt);
}, null);
}
} else {
let finishShell = poly.depth === 0 && !firstLayer;
startPoint = scope.polyPrintPath(poly, startPoint, preout, {
tool: extruder,
rate: finishShell ? finishSpeed : printSpeed,
accel: finishShell,
wipe: process.outputWipeDistance || 0,
coast: firstLayer ? 0 : coastDist,
extrude: pref(opt.extrude, shellMult),
onfirst: function(firstPoint) {
let from = seedPoint || startPoint;
if (from.distTo2D(firstPoint) > retractDist) {
if (intersectsTop(from, firstPoint)) {
retract();
}
}
seedPoint = null;
}
});
lastPoly = slice.lastPoly = poly;
}
}
/**
* @param {Polygon[]} polys
*/
function outputSparse(polys, extrude, speed) {
if (!polys) return;
let proxy = polys.map((poly) => {
return {poly: poly, first: poly.first(), last: poly.last()};
});
let lp = startPoint;
startPoint = tip2tipEmit(proxy, startPoint, (el, point, count) => {
let poly = el.poly;
if (poly.last() === point) {
poly.reverse();
}
poly.forEachPoint((p, i) => {
let dist = lp.distTo2D(p);
let rdst = dist > retractDist;
let itop = rdst && intersectsTop(lp,p);
let emit = extrude;
// retract if dist trigger and crosses a slice top polygon
if (i === 0) {
if (itop) {
retract();
emit = 0;
} else if (dist > nozzleSize) {
emit = 0;
}
}
// let emit = i === 0 ? 0 : extrude;
// handle shallow cloned infill
if (poly.z !== undefined) {
p = p.clone().setZ(poly.z);
}
scope.addOutput(preout, p, emit, speed || printSpeed, extruder);
lp = p;
}, !poly.open);
return lp;
});
}
function outputThin(lines) {
if (!lines) {
return;
}
let points = lines.group(2).map(grp => {
let [ p1, p2 ] = grp;
return newPoint(
(p1.x + p2.x) / 2,
(p1.y + p2.y) / 2,
(p1.z + p2.z) / 2
)
});
let order = util.orderClosest(points, (p1, p2) => p1.distTo2D(p2));
if (order.length === 0) {
return;
}
let first = points[0];
let last = first;
scope.addOutput(preout, first, 0, moveSpeed, extruder);
for (let p of order) {
let dist = last ? last.distTo2D(p) : 0;
if (dist > thinWall) {
retract();
scope.addOutput(preout, p, 0, moveSpeed, extruder);
}
scope.addOutput(preout, p, 1, fillSpeed, extruder);
last = p;
}
// close a circle
if (last && last.distTo2D(first) <= thinWall) {
scope.addOutput(preout, first, 1, fillSpeed, extruder);
}
}
function outputFills(lines, opt = {}) {
if (!lines || lines.length === 0) {
return;
}
let p, p1, p2, dist, len, found, group, mindist, t1, t2,
marked = 0,
start = 0,
skip = false,
lastIndex = -1,
flow = opt.flow || 1,
near = opt.near || false,
fast = opt.fast || false,
fill = (opt.fill >= 0 ? opt.fill : fillMult) * flow,
thinDist = near ? thinWall : thinWall;
while (lines && marked < lines.length) {
group = null;
found = false;
mindist = Infinity;
// use next nearest line strategy
if (near)
for (i=0; i<lines.length; i += 2) {
t1 = lines[i];
if (t1.del) {
continue;
}
t2 = lines[i+1];
let d1 = t1.distToSq2D(startPoint);
let d2 = t2.distToSq2D(startPoint);
if (d1 < mindist || d2 < mindist) {
if (d2 < d1) {
p2 = t1;
p1 = t2;
} else {
p1 = t1;
p2 = t2;
}
mindist = Math.min(d1, d2);
lastIndex = i;
}
}
// use next index line strategy
// order all points by distance to last point
if (!near)
for (i=start; i<lines.length; i += 2) {
p = lines[i];
if (p.del) {
continue;
}
if (group === null && p.index > lastIndex) {
group = p.index;
}
if (group !== null) {
if (p.index !== group) {
break;
}
if (p.index % 2 === 0) {
t1 = lines[i];
t2 = lines[i+1];
} else {
t2 = lines[i];
t1 = lines[i+1];
}
dist = Math.min(t1.distTo2D(startPoint), t2.distTo2D(startPoint));
if (dist < mindist) {
p1 = t1;
p2 = t2;
mindist = dist;
}
start = i;
found = true;
}
}
// go back to start and try again
if (!near && !found) {
if (start === 0 && lastIndex === -1) {
console.log('infinite loop', lines, {
marked, i, group, start, lastIndex,
points: lines.map(p => p.index).join(', ')
});
break;
}
start = 0;
lastIndex = -1;
continue;
}
dist = startPoint.distToSq2D(p1);
len = p1.distToSq2D(p2);
// go back to start when dist > retractDist
if (!near && !fast && !skip && dist > retractDist) {
skip = true;
start = 0;
lastIndex = -1;
continue;
}
skip = false;
// mark as used (temporarily)
p1.del = true;
p2.del = true;
marked += 2;
lastIndex = p1.index;
// if dist to new segment is less than thinWall
// and segment length is less than thinWall then
// just extrude to midpoint of next segment. this is
// to avoid shaking the printer to death.
if (dist <= thinDist && len <= thinDist) {
p2 = p1.midPointTo(p2);
// this.addOutput(preout, p2, fill * (dist / thinWall), fillSpeed, extruder);
scope.addOutput(preout, p2, fill, fillSpeed, extruder);
} else {
// retract if dist trigger or crosses a slice top polygon
if (!fast && dist > retractDist && (zhop || intersectsTop(startPoint, p1))) {
retract();
}
// anti-backlash on longer move
if (!fast && antiBacklash && dist > retractDist) {
scope.addOutput(preout, p1.add({x:antiBacklash,y:-antiBacklash,z:0}), 0, moveSpeed, extruder);
}
// bridge ends of fill when they're close together
if (dist < thinDist) {
scope.addOutput(preout, p1, fill, fillSpeed, extruder);
} else {
scope.addOutput(preout, p1, 0, moveSpeed, extruder);
}
scope.addOutput(preout, p2, fill, fillSpeed, extruder);
}
startPoint = p2;
}
// clear delete marks so we can re-print later
if (lines) lines.forEach(p => { p.del = false });
}
/**
* given array of polygons, emit them in next closest order with
* the special exception that depth is considered into distance
* so that inner polygons are emitted first.
*
* @param {Array} array of Polygons or Polygon wrappers (tops)
* @param {Function} fn call to emit next candidate
* @param {Function} fnp convert 'next' object into a Polygon for closeness
*/
function outputOrderClosest(array, fn, fnp) {
if (array.length === 1) {
return fn(array[0]);
}
array = array.slice();
let closest, find, next, order, poly, lastDepth = 0;
for (;;) {
order = [];
closest = null;
for (i=0; i<array.length; i++) {
next = array[i];
if (!next) continue;
poly = fnp ? fnp(next) : next;
find = poly.findClosestPointTo(startPoint);
order.push({
i: i,
n: next,
d: find.distance - (poly.depth * thinWall),
});
}
if (order.length === 0) {
return;
}
order.sort((a,b) => {
return a.d - b.d;
});
array[order[0].i] = null;
fn(order[0].n);
}
}
let out = [];
if (slice.tops) {
out.appendAll(slice.tops);
};
if (opt.support && slice.supports) {
out.appendAll(slice.supports);
}
let lastTop = null;
outputOrderClosest(out, function(next) {
if (next instanceof Polygon) {
scope.setType('support');
// support polygon
next.setZ(z);
outputTraces([next].appendAll(next.inner || []));
if (next.fill) {
next.fill.forEach(p => { p.z = z });
outputFills(next.fill, {fast: true});
}
} else {
scope.setType('shells');
if (lastTop && lastTop !== next) {
retract();
}
// control of layer start point
switch (process.sliceLayerStart) {
case "center":
startPoint = newPoint(0,0,startPoint.z);
break;
case "origin":
startPoint = origin.clone();
break;
}
// optimize start point on belt for tops touching belt
// and enforce optimal shell order (outer first)
if (isBelt && opt.onBelt) {
startPoint = startClone;
if (beltFirst) {
shellOrder = -1;
}
}
// innermost shells
let inner = next.innerShells() || [];
// output inner polygons
if (shellOrder === 1) outputTraces(inner, { sort: shellOrder });
outputTraces(next.shells, { sort: shellOrder });
// output outer polygons
if (shellOrder === -1) outputTraces(inner, { sort: shellOrder });
// output thin fill
scope.setType('thin fill');
outputThin(next.thin_fill);
// then output solid and sparse fill
scope.setType('solid fill');
outputFills(next.fill_lines, {flow: solidWidth});
scope.setType('sparse infill');
outputSparse(next.fill_sparse, sparseMult, infillSpeed);
lastTop = next;
}
}, function(obj) {
// for tops
return obj instanceof Polygon ? obj : obj.poly;
});
// produce polishing paths when present
if (slice.tops.length && slice.tops[0].polish) {
let {x,y} = slice.tops[0].polish;
if (x) {
outputSparse(x, 0, process.polishSpeed);
}
if (y) {
outputSparse(y, 0, process.polishSpeed);
}
}
// offset print points
for (i=0; i<preout.length; i++) {
preout[i].point = preout[i].point.add(offset);
}
// add offset points to total print
this.addPrintPoints(preout, output, origin, extruder);
return startPoint.add(offset);
}
parseSVG(code, offset) {
let scope = this,
svg = new DOMParser().parseFromString(code, 'text/xml'),
@ -890,7 +260,6 @@ class Print {
const output = scope.output = [ seq ];
const beltaxis = { X: "X", Y: "Z", Z: "Y", E: "E", F: "F" };
const beltfact = Math.cos(Math.PI / 4);
function LOG() {
console.log(...[...arguments].map(o => Object.clone(o)));
@ -1181,10 +550,6 @@ class Output {
}
}
function pref(a,b) {
return a !== undefined ? a : b;
}
kiri.Print = Print;
})();