Merge branch 'rel-4.3' into drill-optimization
This commit is contained in:
commit
394f4a2c8b
30 changed files with 1083 additions and 650 deletions
|
|
@ -91,11 +91,11 @@ function area2(p1, p2, p3) {
|
|||
}
|
||||
|
||||
function isCloseTo(v1, v2, dist) {
|
||||
return Math.abs(v1 - v2) <= (dist || base.config.precision_merge);
|
||||
return Math.abs(v1 - v2) <= (dist ?? base.config.precision_merge);
|
||||
}
|
||||
|
||||
function inCloseRange(val, min, max) {
|
||||
return (isCloseTo(val, min) || val >= min) && (isCloseTo(val, max) || val <= max);
|
||||
function inCloseRange(val, min, max, precisiom) {
|
||||
return (isCloseTo(val, min, precisiom) || val >= min) && (isCloseTo(val, max, precisiom) || val <= max);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -232,7 +232,7 @@ function intersect(p1, p2, p3, p4, test, parallelok) {
|
|||
/**
|
||||
* used by {@link rayIntersect} and {@link Polygon.trace}
|
||||
*/
|
||||
function intersectRayLine(ro, s1, p1, p2, infinite) {
|
||||
function intersectRayLine(ro, s1, p1, p2) {
|
||||
let keys = base.key,
|
||||
p1x = ro.x,
|
||||
p1y = ro.y,
|
||||
|
|
@ -259,11 +259,11 @@ function intersectRayLine(ro, s1, p1, p2, infinite) {
|
|||
a = n1 / d;
|
||||
b = n2 / d;
|
||||
|
||||
if (infinite || (inCloseRange(b, 0, 1) && a >= 0)) {
|
||||
if (inCloseRange(b, 0, 1, 0.00) && a >= 0) {
|
||||
let ip = newPoint(
|
||||
p1x + (a * s1x),
|
||||
p1y + (a * s1y),
|
||||
p2.z || ro.z,
|
||||
p2.z ?? ro.z,
|
||||
keys.NONE
|
||||
);
|
||||
ip.dist = a;
|
||||
|
|
|
|||
|
|
@ -114,11 +114,16 @@ class Point {
|
|||
return this.key;
|
||||
}
|
||||
|
||||
clone() {
|
||||
clone(keys) {
|
||||
let p = newPoint(this.x, this.y, this.z, this._key);
|
||||
if (this.a !== undefined) {
|
||||
p.a = this.a;
|
||||
}
|
||||
if (keys) {
|
||||
for (let key of keys) {
|
||||
p[key] = this[key];
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1104,6 +1104,18 @@ export class Polygon {
|
|||
return this.first().isEqual(this.last());
|
||||
}
|
||||
|
||||
closeIf(dist = 1) {
|
||||
let closeDist = this.first().distTo2D(this.last());
|
||||
if (closeDist < 0.001) {
|
||||
this.points.pop();
|
||||
return this.setClosed();
|
||||
} else if (closeDist <= dist) {
|
||||
return this.setClosed();
|
||||
} else {
|
||||
return this.setOpen();
|
||||
}
|
||||
}
|
||||
|
||||
fixClosed() {
|
||||
if (this.appearsClosed()) {
|
||||
this.points.pop();
|
||||
|
|
@ -1311,15 +1323,15 @@ export class Polygon {
|
|||
pp2.pos = ip2;
|
||||
}
|
||||
});
|
||||
list.sort(function(p1, p2) {
|
||||
return util.distSq(lp1, p1) - util.distSq(lp1, p2);
|
||||
});
|
||||
if (deep && this.inner) {
|
||||
this.inner.forEach(p => {
|
||||
let ints = p.intersections(lp1, lp2);
|
||||
if (ints) list.appendAll(ints);
|
||||
});
|
||||
}
|
||||
list.sort(function(p1, p2) {
|
||||
return util.distSq(lp1, p1) - util.distSq(lp1, p2);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
@ -1380,6 +1392,23 @@ export class Polygon {
|
|||
return poly;
|
||||
}
|
||||
|
||||
/**
|
||||
* emit the shortest poly that connects two points on a closed CW poly
|
||||
* because traveling around the 'end point' may yield a shorter route.
|
||||
* if the poly is open, use emitSegment()
|
||||
*/
|
||||
emitShortestSegment(i1, i2) {
|
||||
if (this.open) {
|
||||
return this.emitSegment(i1, i2);
|
||||
}
|
||||
let start = i1.p2.pos,
|
||||
end = i2.p1.pos,
|
||||
points = this.points,
|
||||
seg0 = new Polygon([ i1, ...points.slice(start, end + 1), i2 ]).setOpen(),
|
||||
seg1 = new Polygon([ i2, ...points.slice(end +1 ), ...points.slice(0, start), i1 ]).setOpen();
|
||||
return seg0.perimeter() < seg1.perimeter() ? seg0 : seg1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Polygon} poly
|
||||
* @param {number} [tolerance]
|
||||
|
|
@ -2042,13 +2071,9 @@ export class Polygon {
|
|||
|
||||
// use expand / deflate technique instead
|
||||
if (opt.pump) {
|
||||
let p2 = POLY.offset([this], opt.pump, {
|
||||
z
|
||||
});
|
||||
let p2 = POLY.offset([this], opt.pump, { z });
|
||||
if (p2) {
|
||||
p2 = POLY.offset(p2, -opt.pump, {
|
||||
z
|
||||
});
|
||||
p2 = POLY.offset(p2, -opt.pump, { z });
|
||||
return p2;
|
||||
}
|
||||
return null;
|
||||
|
|
@ -2316,9 +2341,12 @@ export class Polygon {
|
|||
return obj;
|
||||
}
|
||||
|
||||
// split long straight lines into segments no longer than max
|
||||
// and return a new polygon
|
||||
segment(max = 1) {
|
||||
// split long straight lines into segments no longer than `max`
|
||||
// and return a new polygon. optionally `mark` points with their
|
||||
// derived segment start point (hinting for thin walls), `wrap`
|
||||
// the poly first point by appending to the end (adaptive walls),
|
||||
// or stopping segmentation at `maxoff`
|
||||
segment(max = 1, mark = false, wrap = false, maxoff = Infinity) {
|
||||
const newp = [];
|
||||
const points = this.points;
|
||||
const length = points.length;
|
||||
|
|
@ -2330,9 +2358,14 @@ export class Polygon {
|
|||
const dy = p2.y - p1.y;
|
||||
const dl = Math.sqrt(dx * dx + dy * dy);
|
||||
newp.push(p1);
|
||||
if (dl < max) {
|
||||
if (mark) p1.segment = p1;
|
||||
// if segment shorter than max (delta) or
|
||||
// maxoff is exhausted (only segmenting up to some length)
|
||||
// then skip sub-segmenting the current segment
|
||||
if (dl < max || maxoff < 0) {
|
||||
continue;
|
||||
}
|
||||
maxoff -= dl;
|
||||
const div = dl / max;
|
||||
const fit = div | 0;
|
||||
const add = fit - 1;
|
||||
|
|
@ -2344,10 +2377,15 @@ export class Polygon {
|
|||
newp.push(newPoint(ox, oy, (p1.z + p2.z) / 2));
|
||||
ox += ix;
|
||||
oy += iy;
|
||||
// mark new point with first point of originating segment
|
||||
if (mark) newp.peek().segment = p1;
|
||||
}
|
||||
}
|
||||
if (newp.length > length) {
|
||||
return newPolygon().addPoints(newp.map(p => p.clone())).setOpenValue(this.open);
|
||||
if (wrap) {
|
||||
newp.push(newp[0]);
|
||||
}
|
||||
return newPolygon().addPoints(newp.map(p => p.clone(['segment']))).setOpenValue(this.open);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -645,7 +645,7 @@ export function offset(polys, dist, opts = {}) {
|
|||
for (let poly of polys) {
|
||||
// convert to clipper format
|
||||
poly = poly.toClipper();
|
||||
if (clean) poly = CleanPolygons(poly, config.clipperClean);
|
||||
if (clean) poly = CleanPolygons(poly, opts.cleanDist ?? config.clipperClean);
|
||||
if (simple) poly = SimplifyPolygons(poly, fill);
|
||||
coff.AddPaths(poly, join, type);
|
||||
}
|
||||
|
|
@ -871,6 +871,7 @@ export function fillArea(polys, angle, spacing, output, minLen, maxLen) {
|
|||
* @returns {Point[]}
|
||||
*/
|
||||
export function rayIntersect(start, slope, polygons, for_fill) {
|
||||
throw "deprecated";
|
||||
let i = 0,
|
||||
flat = [],
|
||||
points = [],
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ Polygon.prototype.encode = function(state) {
|
|||
|
||||
state.poly[this.id] = this;
|
||||
|
||||
return {
|
||||
let enc = {
|
||||
type: TYPE.POLY,
|
||||
id: this.id,
|
||||
array: encodePointArray(this.points, state, this.z),
|
||||
|
|
@ -322,6 +322,20 @@ Polygon.prototype.encode = function(state) {
|
|||
color: this.color,
|
||||
open: this.open
|
||||
};
|
||||
|
||||
// encode extended point info for tagged polys
|
||||
if (this._epk) {
|
||||
let epk = enc.epk = {};
|
||||
for (let key of this._epk) {
|
||||
let arr = epk[key] = [];
|
||||
let pts = this.points;
|
||||
for (let i=0, l=pts.length; i<l; i++) {
|
||||
if (pts[i][key]) arr.push(i, pts[i][key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return enc;
|
||||
};
|
||||
|
||||
registerDecoder(TYPE.POLY, function(v, state) {
|
||||
|
|
@ -340,6 +354,18 @@ registerDecoder(TYPE.POLY, function(v, state) {
|
|||
poly.push(newPoint(array[vid++], array[vid++], array[vid++]));
|
||||
}
|
||||
|
||||
// decode extended point when present
|
||||
if (v.epk) {
|
||||
let pts = poly.points;
|
||||
for (let [key, arr] of Object.entries(v.epk)) {
|
||||
for (let i=0; i<arr.length; ) {
|
||||
let pos = arr[i++];
|
||||
let val = arr[i++];
|
||||
pts[pos][key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.poly[v.id] = poly;
|
||||
|
||||
poly.id = v.id;
|
||||
|
|
|
|||
|
|
@ -327,8 +327,10 @@ export const conf = {
|
|||
outputRetractDwell: 20,
|
||||
outputBrimCount: 2,
|
||||
outputBrimOffset: 2,
|
||||
outputScarfLength: 0,
|
||||
outputShortPoly: 100.0,
|
||||
outputMinSpeed: 10.0,
|
||||
outputMinLayerTime: 0,
|
||||
outputCoastDist: 0,
|
||||
outputPurgeTower: 0,
|
||||
outputBeltFirst: false,
|
||||
|
|
@ -949,7 +951,9 @@ export const conf = {
|
|||
hidden: {
|
||||
"fdm-base": true,
|
||||
"fdm-cool": true,
|
||||
"fdm-heat": true,
|
||||
"fdm-fill": true,
|
||||
"fdm-solid": true,
|
||||
"fdm-supp": true,
|
||||
"fdm-xprt": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const LISTS = {
|
|||
{ name: "center" },
|
||||
{ name: "origin" },
|
||||
{ name: "random" },
|
||||
{ name: "spiral" },
|
||||
],
|
||||
infill: [
|
||||
{ name: "none" },
|
||||
|
|
@ -115,9 +116,10 @@ const LISTS = {
|
|||
],
|
||||
thin: [
|
||||
{ name: "off" },
|
||||
{ name: "type 1" },
|
||||
{ name: "type 2" },
|
||||
{ name: "type 3" }
|
||||
{ name: "basic" },
|
||||
{ name: "adaptive" },
|
||||
{ name: "legacy 1" },
|
||||
{ name: "legacy 2" }
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -80,11 +80,9 @@ class Print {
|
|||
return lastOut;
|
||||
}
|
||||
|
||||
addPrintPoints(input, output, startPoint, tool) {
|
||||
addPrintPoints(input, output) {
|
||||
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);
|
||||
}
|
||||
|
|
@ -98,6 +96,7 @@ class Print {
|
|||
* @param {Array} output - the array to print to
|
||||
* @param {Object} [options] - optional parameters
|
||||
* @param {boolean} [options.ccw] - set the polygon to be counter-clockwise
|
||||
* @param {boolean} [options.scarf] - scarf seam permitted
|
||||
* @param {number} [options.extrude] - extrude factor for the polygon
|
||||
* @param {number} [options.rate] - print speed in mm/s
|
||||
* @param {number} [options.coast] - distance to coast at the end of the polygon
|
||||
|
|
@ -119,15 +118,16 @@ class Print {
|
|||
const { settings } = scope;
|
||||
const { process } = settings;
|
||||
|
||||
let shortDist = process.outputShortDistance,
|
||||
shellMult = numOrDefault(options.extrude, process.outputShellMult),
|
||||
let shellMult = numOrDefault(options.extrude, process.outputShellMult),
|
||||
printSpeed = options.rate || process.outputFeedrate,
|
||||
moveSpeed = process.outputSeekrate,
|
||||
minSpeed = process.outputMinSpeed,
|
||||
nozzleSize = options.nozzleSize,
|
||||
coastDist = options.coast || 0,
|
||||
closest = options.simple ? poly.first() : poly.findClosestPointTo(startPoint),
|
||||
perimeter = poly.perimeter(),
|
||||
close = !options.open,
|
||||
scarf = !poly.open ? (options.scarf ?? 0) : false,
|
||||
tool = options.tool,
|
||||
last = startPoint,
|
||||
first = true;
|
||||
|
|
@ -137,10 +137,74 @@ class Print {
|
|||
printSpeed = minSpeed + (printSpeed - minSpeed) * (perimeter / process.outputShortPoly);
|
||||
}
|
||||
|
||||
poly.forEachPoint((point, pos, points, count) => {
|
||||
if (first) {
|
||||
// if not starting at first point in poly, rotate to move start to index = 0
|
||||
let pp = poly.points;
|
||||
if (closest.index > 0) {
|
||||
let cio = pp.indexOf(closest.point);
|
||||
pp = poly.points = [ ...pp.slice(cio), ...pp.slice(0, cio) ];
|
||||
}
|
||||
|
||||
// scarf sanity checks
|
||||
if (scarf) {
|
||||
// cancel scarf for thin wall polys
|
||||
if (pp.filter(p => p.skip || p.moved).length) {
|
||||
scarf = 0;
|
||||
} else {
|
||||
// cancel scarf if any point.z differs
|
||||
let z0 = pp[0].z;
|
||||
let zd = 0;
|
||||
for (let p of pp) zd += Math.abs(p.z - z0);
|
||||
if (zd) scarf = 0;
|
||||
}
|
||||
// console.log({ scarf });
|
||||
}
|
||||
|
||||
// when creating scarf seams, segment poly up to seam length
|
||||
// create array of step up points at start of poly with increasing z
|
||||
// and increasing shellMult and then append the same points on the back
|
||||
// end of the poly with fixed z and decreasing shellMult
|
||||
if (scarf) {
|
||||
let epz = Math.max(...poly.points.map(p => p.z));
|
||||
let spz = startPoint.z;
|
||||
poly = poly.segment(options.nozzleSize ?? 0.4, false, false, scarf * 2);
|
||||
pp = poly.points;
|
||||
let lp, sp = [];
|
||||
for (let p of pp) {
|
||||
let d = lp?.distTo2D(p) ?? 0;
|
||||
sp.push(lp = p);
|
||||
scarf -= d;
|
||||
if (scarf <= 0) break;
|
||||
}
|
||||
let fcs = 1.0; // flow compensation seam
|
||||
let fco = (1 / sp.length) * 0.0; // flow compensation offset (- half step)
|
||||
let zd = (epz - spz) / sp.length;
|
||||
let zi = 1;
|
||||
for (let p of sp) {
|
||||
p.z -= zd * (sp.length - zi);
|
||||
p.moved = (((zi++) / sp.length) * fcs) - 1 - fco;
|
||||
}
|
||||
let esp = sp.map(p => p.clone()); // ending scarf points
|
||||
for (let p of esp) {
|
||||
p.z = epz;
|
||||
p.moved = (((--zi) / esp.length) * fcs) - 1 - fco;
|
||||
}
|
||||
pp.push(...esp);
|
||||
scarf = true;
|
||||
}
|
||||
|
||||
// scarf manages its own close point
|
||||
if (close && !scarf) {
|
||||
pp.push(pp[0]);
|
||||
}
|
||||
|
||||
let lpo;
|
||||
for (let point of pp) {
|
||||
if (point.skip && lpo?.skip) {
|
||||
scope.addOutput(output, point, 0, moveSpeed, tool);
|
||||
} else if (first) {
|
||||
// if (point.skip) console.log({ skip: point });
|
||||
if (options.onfirst) {
|
||||
options.onfirst(point);
|
||||
options.onfirst(point, output);
|
||||
}
|
||||
// move to first output point on poly
|
||||
let out = scope.addOutput(output, point, 0, moveSpeed, tool);
|
||||
|
|
@ -150,7 +214,8 @@ class Print {
|
|||
first = false;
|
||||
} else {
|
||||
let seglen = last.distTo2D(point);
|
||||
if (coastDist && shellMult && perimeter - seglen <= coastDist) {
|
||||
// cancel coast when using scarf seam
|
||||
if (!scarf && coastDist && shellMult && perimeter - seglen <= coastDist) {
|
||||
let delta = perimeter - coastDist;
|
||||
let offset = seglen - delta;
|
||||
let offPoint = last.offsetPointFrom(point, offset)
|
||||
|
|
@ -158,10 +223,13 @@ class Print {
|
|||
shellMult = 0;
|
||||
}
|
||||
perimeter -= seglen;
|
||||
scope.addOutput(output, point, shellMult, printSpeed, tool);
|
||||
// increase mult by % of point moved relative to nozzle radius
|
||||
let multOut = shellMult + (point.moved ?? 0);
|
||||
// to increase shellMult when point.inc set for collapsed points
|
||||
scope.addOutput(output, point, multOut, printSpeed, tool);
|
||||
}
|
||||
last = point;
|
||||
}, close, closest.index);
|
||||
last = lpo = point;
|
||||
}
|
||||
|
||||
this.lastPoly = poly;
|
||||
|
||||
|
|
@ -169,6 +237,14 @@ class Print {
|
|||
}
|
||||
|
||||
constReplace(str, consts, start, pad, short) {
|
||||
function tryeval(str) {
|
||||
try {
|
||||
return eval(`{ ${str} }`)
|
||||
} catch (e) {
|
||||
console.log({ eval_error: e, str });
|
||||
return str;
|
||||
}
|
||||
}
|
||||
let cs = str.indexOf("{", start || 0),
|
||||
ce = str.indexOf("}", cs),
|
||||
tok, nutok, nustr;
|
||||
|
|
@ -192,8 +268,7 @@ class Print {
|
|||
}
|
||||
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} }`);
|
||||
let evl = tryeval(eva.join(''));
|
||||
nutok = evl;
|
||||
if (pad === 666) {
|
||||
return evl;
|
||||
|
|
|
|||
|
|
@ -181,8 +181,8 @@ async function path(levels, update, opts = {}) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (lastOut) { // if last outpoint is defined
|
||||
|
||||
// if last outpoint is defined
|
||||
if (lastOut) {
|
||||
if (arrowAll || lastOut.emit !== out.emit) {
|
||||
heads.push({p1: lastOutPoint, p2: outPoint});
|
||||
}
|
||||
|
|
@ -192,7 +192,7 @@ async function path(levels, update, opts = {}) {
|
|||
// Math.abs(op.y - lp.y),
|
||||
// Math.abs(op.z - lp.z));
|
||||
// if (moved < 0.0001) return;
|
||||
if( is_cam() && (out.emit == 2 || out.emit == 3 )) { // cam arc emit
|
||||
if (is_cam() && (out.emit == 2 || out.emit == 3 )) { // cam arc emit
|
||||
// checks if a new poly should be started
|
||||
if (!lastOut.emit || (ckspeed && out.speed !== lastOut.speed) || lastEnd) {
|
||||
current = newPolygon().setOpen();
|
||||
|
|
@ -200,17 +200,13 @@ async function path(levels, update, opts = {}) {
|
|||
current.color = color(out);
|
||||
pushPrint(out.tool, current);
|
||||
}
|
||||
|
||||
out.arcPoints.forEach(p => {
|
||||
current.push(p);
|
||||
})
|
||||
|
||||
current.push(outPoint);
|
||||
|
||||
} else if (out.emit) {
|
||||
// explicity G1 in CAM mode
|
||||
// just a non-move in other modes
|
||||
|
||||
// checks if a new poly should be started
|
||||
if (!lastOut.emit || (ckspeed && out.speed !== lastOut.speed) || lastEnd) {
|
||||
current = newPolygon().setOpen();
|
||||
|
|
|
|||
|
|
@ -286,7 +286,7 @@ function updateFieldsFromSettings(setrec, uirec = api.ui, opt = {}) {
|
|||
|
||||
function updateExtruderFields(device) {
|
||||
const { ui } = api;
|
||||
const { LANG } = api.const;
|
||||
const { current: LANG } = api.const.LANG;
|
||||
if (device.extruders && device.extruders[device.internal]) {
|
||||
updateFieldsFromSettings(device.extruders[device.internal]);
|
||||
ui.extruder.innerHTML =
|
||||
|
|
|
|||
|
|
@ -144,7 +144,9 @@ class Slice {
|
|||
} else {
|
||||
// create top object from object bundle passed back by slicePost()
|
||||
let top = new Top(data.poly);
|
||||
top.thin_fill = data.thin_fill ? data.thin_fill.map(p => newPoint(p.x,p.y,p.z)) : undefined;
|
||||
top.thin_fill = data.thin_fill?.map(p => newPoint(p.x,p.y,p.z));
|
||||
top.thin_wall = data.thin_wall;
|
||||
top.thin_sort = data.thin_sort;
|
||||
top.fill_lines = data.fill_lines;
|
||||
top.fill_sparse = data.fill_sparse;
|
||||
top.fill_off = data.fill_off;
|
||||
|
|
|
|||
|
|
@ -360,6 +360,10 @@ class Widget {
|
|||
this.setOpacity(opacity ?? 1);
|
||||
}
|
||||
|
||||
refreshVisualState() {
|
||||
this.setVisualState(this.getVisualState());
|
||||
}
|
||||
|
||||
restoreVisualState() {
|
||||
if (this.cache.vizstate) {
|
||||
this.setVisualState(this.cache.vizstate);
|
||||
|
|
@ -613,6 +617,7 @@ class Widget {
|
|||
this.roto = [];
|
||||
this.center();
|
||||
this.setModified();
|
||||
this.refreshVisualState();
|
||||
}
|
||||
|
||||
mirror() {
|
||||
|
|
|
|||
|
|
@ -5,59 +5,59 @@ import { clearPops, env } from "./client.js";
|
|||
let alert, lastWidget;
|
||||
export let helicalOn = false;
|
||||
export function selectHelical() {
|
||||
if (helicalOn) {
|
||||
return helicalDone();
|
||||
}
|
||||
clearPops();
|
||||
alert = api.show.alert("analyzing surfaces...", 1000);
|
||||
let cylinders = env.poppedRec.cylinders;
|
||||
if (helicalOn) {
|
||||
return helicalDone();
|
||||
}
|
||||
clearPops();
|
||||
alert = api.show.alert("analyzing surfaces...", 1000);
|
||||
let cylinders = env.poppedRec.cylinders;
|
||||
|
||||
CAM.surface_prep(false, () => {
|
||||
api.hide.alert(alert);
|
||||
alert = api.show.alert("[esc] cancels surface selection");
|
||||
for (let [wid, arr] of Object.entries(cylinders)) {
|
||||
let widget = api.widgets.forid(wid);
|
||||
if ( widget && arr?.length )
|
||||
for (let faceid of arr) {
|
||||
CAM.cylinderToggle(widget, faceid, (faceids) => {
|
||||
// cylinders[widget.id] = faceids;
|
||||
});
|
||||
CAM.surface_prep(false, () => {
|
||||
api.hide.alert(alert);
|
||||
alert = api.show.alert("[esc] cancels surface selection");
|
||||
for (let [wid, arr] of Object.entries(cylinders)) {
|
||||
let widget = api.widgets.forid(wid);
|
||||
if (widget && arr?.length)
|
||||
for (let faceid of arr) {
|
||||
CAM.cylinderToggle(widget, faceid, (faceids) => {
|
||||
// cylinders[widget.id] = faceids;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
helicalOn = env.hoveredOp;
|
||||
helicalOn.classList.add("editing");
|
||||
api.feature.on_mouse_up = (obj, ev) => {
|
||||
let { face } = obj;
|
||||
let min = Math.min(face.a, face.b, face.c);
|
||||
let faceid = min / 3;
|
||||
let widget = (lastWidget = obj.object.widget);
|
||||
CAM.cylinderToggle(widget, faceid, ({ faces, error }) => {
|
||||
if (error) {
|
||||
api.show.alert(error, 3000);
|
||||
console.log(error);
|
||||
return;
|
||||
}
|
||||
cylinders[widget.id] = faces;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function helicalDone() {
|
||||
if (!(helicalOn && env.poppedRec && env.poppedRec.cylinders)) {
|
||||
return;
|
||||
}
|
||||
let { cylinders } = env.poppedRec;
|
||||
for (let wid of Object.keys(cylinders)) {
|
||||
let widget = api.widgets.forid(wid);
|
||||
if (widget) {
|
||||
CAM.cylinderClear(widget);
|
||||
} else {
|
||||
delete cylinders[wid];
|
||||
}
|
||||
}
|
||||
});
|
||||
helicalOn = env.hoveredOp;
|
||||
helicalOn.classList.add("editing");
|
||||
api.feature.on_mouse_up = (obj, ev) => {
|
||||
let { face } = obj;
|
||||
let min = Math.min(face.a, face.b, face.c);
|
||||
let faceid = min / 3;
|
||||
let widget = (lastWidget = obj.object.widget);
|
||||
CAM.cylinderToggle(widget, faceid, ({ faces, error }) => {
|
||||
if ( error ) {
|
||||
api.show.alert(error, 3000);
|
||||
console.log( error );
|
||||
return;
|
||||
}
|
||||
cylinders[widget.id] = faces;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function helicalDone(){
|
||||
if (!( helicalOn && env.poppedRec && env.poppedRec.cylinders )) {
|
||||
return;
|
||||
}
|
||||
let { cylinders } = env.poppedRec;
|
||||
for (let wid of Object.keys(cylinders)) {
|
||||
let widget = api.widgets.forid(wid);
|
||||
if (widget) {
|
||||
CAM.cylinderClear(widget);
|
||||
} else {
|
||||
delete cylinders[wid];
|
||||
}
|
||||
}
|
||||
api.hide.alert(alert);
|
||||
api.feature.on_mouse_up = undefined;
|
||||
helicalOn.classList.remove("editing");
|
||||
helicalOn = false;
|
||||
api.hide.alert(alert);
|
||||
api.feature.on_mouse_up = undefined;
|
||||
helicalOn.classList.remove("editing");
|
||||
helicalOn = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,6 +25,16 @@ export function originSelect() {
|
|||
env.hover = pointHover;
|
||||
env.hoverUp = pointHoverUp;
|
||||
|
||||
// less precise point key for hi-res meshes that leads to dups
|
||||
function updateKey(pt) {
|
||||
pt._key = [
|
||||
((pt.x * 1000) | 0),
|
||||
((pt.y * 1000) | 0),
|
||||
((pt.z * 1000) | 0)
|
||||
].join(' ');
|
||||
return pt;
|
||||
}
|
||||
|
||||
CAM.traces((ids) => {
|
||||
api.hide.alert(alert);
|
||||
alert = api.show.alert("select origin<br>[esc] to cancel", 1000);
|
||||
|
|
@ -53,14 +63,14 @@ export function originSelect() {
|
|||
});
|
||||
for (let poly of polys) {
|
||||
if (poly.circularity() >= 0.99) {
|
||||
let center = poly.calcCircleCenter();
|
||||
let center = updateKey(poly.calcCircleCenter());
|
||||
if (points[center.key]) {
|
||||
continue;
|
||||
}
|
||||
addpoint(widget, center);
|
||||
} else if (poly.isOpen()) {
|
||||
incpoint(widget, poly.first());
|
||||
incpoint(widget, poly.last());
|
||||
incpoint(widget, updateKey(poly.first()));
|
||||
incpoint(widget, updateKey(poly.last()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export class OpHelical extends CamOp {
|
|||
}
|
||||
let { zmin, zmax, diam, center, interior, faces } = res;
|
||||
// console.log({ zmin, zmax, diam, center, interior });
|
||||
let radius = diam >> 1;
|
||||
let radius = diam /2;
|
||||
zmax = fromTop ? stock.z: zmax;
|
||||
|
||||
let radAdd,
|
||||
|
|
@ -138,14 +138,17 @@ export class OpHelical extends CamOp {
|
|||
);
|
||||
// console.log({down,toBottom,ofFull,ofCircle,startAngle,finalAngle,finalEnd});
|
||||
|
||||
//circle down to final point
|
||||
poly.addPoints(
|
||||
arcToPath(startPoint.setZ(currentZ), finalEnd, numSegs , {
|
||||
clockwise,
|
||||
radius,
|
||||
center,
|
||||
})
|
||||
);
|
||||
//if not already very close to final point
|
||||
if( ofCircle >= 0.001 ){
|
||||
//circle down to final point
|
||||
poly.addPoints(
|
||||
arcToPath(startPoint.setZ(currentZ), finalEnd, numSegs , {
|
||||
clockwise,
|
||||
radius,
|
||||
center,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
//if starting at the bottom, reverse poly
|
||||
if ( reverse ){
|
||||
|
|
@ -169,9 +172,9 @@ export class OpHelical extends CamOp {
|
|||
let lineOut = radius - entryOffset;
|
||||
let centerOffset = (entryOffset**2 + 2*entryOffset*lineOut) / (2*(entryOffset + lineOut));
|
||||
//add entry points
|
||||
entry.push(...
|
||||
entry.push(
|
||||
newPoint(0,-lineOut,0),
|
||||
arcToPath(
|
||||
...arcToPath(
|
||||
newPoint(0,-lineOut,0),
|
||||
newPoint(radius,0,0),
|
||||
128,
|
||||
|
|
@ -186,12 +189,15 @@ export class OpHelical extends CamOp {
|
|||
}
|
||||
}
|
||||
//for each point
|
||||
//rotate by start angle
|
||||
// add to hole center
|
||||
let invert = ((clockwise && !reverse ) || (!clockwise && reverse) ) ? -1 : 1
|
||||
let entryAngle = reverse ? finalAngle : startAngle
|
||||
entry = entry.map(p => {
|
||||
//rotate by start angle
|
||||
// add to hole center
|
||||
// TODO: there's totally a way to do this with a matrix
|
||||
p.y *= clockwise ? 1 : -1
|
||||
p.rotate(startAngle)
|
||||
// logical XOR
|
||||
p.y *= invert
|
||||
p.rotate(entryAngle)
|
||||
return p.add(entryCenter)
|
||||
})
|
||||
//append to start of poly
|
||||
|
|
|
|||
|
|
@ -132,15 +132,17 @@ export async function cam_slice(settings, widget, onupdate, ondone) {
|
|||
}
|
||||
|
||||
if (stock.x && stock.y && stock.z && !isIndexed) {
|
||||
if (stock.x + 0.00001 < bounds.max.x - bounds.min.x) {
|
||||
let maxDelta = 1e-3;
|
||||
|
||||
if (stock.x + maxDelta < (bounds.max.x - bounds.min.x)) {
|
||||
return error('stock X too small for part. resize stock or use offset stock');
|
||||
}
|
||||
|
||||
if (stock.y + 0.00001 < bounds.max.y - bounds.min.y) {
|
||||
if (stock.y + maxDelta < (bounds.max.y - bounds.min.y)) {
|
||||
return error('stock Y too small for part. resize stock or use offset stock');
|
||||
}
|
||||
|
||||
if (stock.z + 0.00001 < bounds.max.z - bounds.min.z) {
|
||||
if (stock.z + maxDelta < (bounds.max.z - bounds.min.z)) {
|
||||
return error('stock Z too small for part. resize stock or use offset stock');
|
||||
}
|
||||
}
|
||||
|
|
@ -510,8 +512,7 @@ export async function traces(settings, widget) {
|
|||
widget.traces = polylines;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of arrays of perpindicular triangles in the mesh.
|
||||
|
|
@ -523,110 +524,109 @@ export async function traces(settings, widget) {
|
|||
* @param {object} opts - options object
|
||||
* @return {array} - array of arrays of triangle data
|
||||
*/
|
||||
export async function cylinders(settings, widget, opts){
|
||||
|
||||
let {} = opts ?? {}
|
||||
|
||||
let {array:verts} = widget.mesh.geometry.attributes.position
|
||||
let perpTriangles = []
|
||||
export async function cylinders(settings, widget, opts) {
|
||||
let { } = opts ?? {};
|
||||
let { array: verts } = widget.mesh.geometry.attributes.position;
|
||||
let perpTriangles = [];
|
||||
|
||||
//iterate over all triangles
|
||||
for(let i = 0; i < verts.length; i+=9){
|
||||
let a = [verts[i], verts[i+1], verts[i+2]],
|
||||
b = [verts[i+3], verts[i+4], verts[i+5]],
|
||||
c = [verts[i+6], verts[i+7], verts[i+8]];
|
||||
for (let i = 0; i < verts.length; i += 9) {
|
||||
let a = [verts[i], verts[i + 1], verts[i + 2]],
|
||||
b = [verts[i + 3], verts[i + 4], verts[i + 5]],
|
||||
c = [verts[i + 6], verts[i + 7], verts[i + 8]];
|
||||
|
||||
//calculate normal
|
||||
let normal = new THREE.Vector3(a[0]-b[0], a[1]-b[1], a[2]-b[2])
|
||||
.cross(new THREE.Vector3(b[0]-c[0], b[1]-c[1], b[2]-c[2]))
|
||||
.normalize()
|
||||
let normal = new THREE.Vector3(a[0] - b[0], a[1] - b[1], a[2] - b[2])
|
||||
.cross(new THREE.Vector3(b[0] - c[0], b[1] - c[1], b[2] - c[2]))
|
||||
.normalize();
|
||||
|
||||
// if perpindicular normal, and at least 2 Zs are the same
|
||||
if(normal.z.round(5) == 0 && ! (a[2] != b[2] && b[2] != c[2])){
|
||||
if (normal.z.round(5) == 0 && !(a[2] != b[2] && b[2] != c[2])) {
|
||||
let minZ = Math.min(a[2], b[2], c[2]),
|
||||
maxZ = Math.max(a[2], b[2], c[2]);
|
||||
if(minZ == maxZ){ // all Zs are the same indicated malformed geometry
|
||||
if (minZ == maxZ) { // all Zs are the same indicated malformed geometry
|
||||
continue;
|
||||
}
|
||||
perpTriangles.push({...[a,b,c], normal,minZ,maxZ,i:index})
|
||||
perpTriangles.push({ ...[a, b, c], normal, minZ, maxZ, i: index });
|
||||
}
|
||||
|
||||
}
|
||||
//map where zmax, zmin -> triangleData
|
||||
let cylinderTriangles = new Map()
|
||||
for(let t of perpTriangles){
|
||||
let hash = `${t.minZ.round(5)},${t.maxZ.round(5)}`
|
||||
if(!cylinderTriangles.has(hash)){
|
||||
cylinderTriangles.set(hash,[])
|
||||
let cylinderTriangles = new Map();
|
||||
for (let t of perpTriangles) {
|
||||
let hash = `${t.minZ.round(5)},${t.maxZ.round(5)}`;
|
||||
if (!cylinderTriangles.has(hash)) {
|
||||
cylinderTriangles.set(hash, []);
|
||||
}
|
||||
cylinderTriangles.get(hash).push(t)
|
||||
cylinderTriangles.get(hash).push(t);
|
||||
}
|
||||
|
||||
return Array.from(cylinderTriangles.values())
|
||||
|
||||
return Array.from(cylinderTriangles.values());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find all triangles that are part of the same cylinder as the given triangle
|
||||
* @param {Widget} widget - widget object
|
||||
* @param {number} face - index of face in triangle vertex data
|
||||
* @return {array} - array of all triangle data that belong to the same cylinder
|
||||
* @requires surface_prep must be called first
|
||||
*/
|
||||
/**
|
||||
* Find all triangles that are part of the same cylinder as the given triangle
|
||||
* @param {Widget} widget - widget object
|
||||
* @param {number} face - index of face in triangle vertex data
|
||||
* @return {array} - array of all triangle data that belong to the same cylinder
|
||||
* @requires surface_prep must be called first
|
||||
*/
|
||||
export function cylinder_find (widget, face){
|
||||
CAM.surface_prep(widget,false);
|
||||
return widget.tool.findCylinderSurface(face);
|
||||
}
|
||||
|
||||
export function cylinder_poly_find(widget, face){
|
||||
export function cylinder_poly_find(widget, face) {
|
||||
let faces = cylinder_find(widget, face);
|
||||
let vert = widget.getGeoVertices({ unroll: true, translate: true }).map(v => v.round(4));
|
||||
let slicer = new cam_slicer(widget,{})
|
||||
let opts = {
|
||||
dedup:false,
|
||||
edges: false,
|
||||
over:true
|
||||
}
|
||||
let firstOffset = faces[0]*9
|
||||
let [x1,y1,z1,x2,y2,z2,x3,y3,z3]= Array.from(vert.subarray(firstOffset,firstOffset+9))
|
||||
let zs = [z1,z2,z3].map(z => z.round(5))
|
||||
let slicer = new cam_slicer(widget, {});
|
||||
let firstOffset = faces[0] * 9;
|
||||
let [x1, y1, z1, x2, y2, z2, x3, y3, z3] = Array.from(vert.subarray(firstOffset, firstOffset + 9));
|
||||
let zs = [z1, z2, z3].map(z => z.round(5));
|
||||
let zmin = Math.min(...zs);
|
||||
let zmax = Math.max(...zs);
|
||||
let zmid = (zmin+zmax)/2
|
||||
let cylVerts = new Float32Array(faces.length*9)
|
||||
|
||||
let zmid = (zmin + zmax) / 2;
|
||||
let cylVerts = new Float32Array(faces.length * 9);
|
||||
let opts = {
|
||||
dedup: false,
|
||||
edges: false,
|
||||
over: true
|
||||
};
|
||||
|
||||
let i = 0
|
||||
//for each cylinder face index
|
||||
for(let index of faces){
|
||||
let off = index*9
|
||||
for (let index of faces) {
|
||||
let off = index * 9;
|
||||
//copy the 9 triangle vertices floats in
|
||||
for(let j = 0;j < 9;j++){
|
||||
cylVerts[i] = vert[off+j]
|
||||
i++
|
||||
for (let j = 0; j < 9; j++) {
|
||||
cylVerts[i] = vert[off + j];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
//slice the poly at the midpoint
|
||||
let poly = slicer.sliceZ(zmid, cylVerts,opts)?.polys?.at(0)
|
||||
if(!poly) throw "slicing returned no poly"
|
||||
let poly = slicer.sliceZ(zmid, cylVerts, opts)?.polys?.at(0);
|
||||
if (!poly) throw "slicing returned no poly";
|
||||
|
||||
let circular = poly.circularity() > 0.98;
|
||||
|
||||
//throw error if not circular
|
||||
if(!circular) throw "faces must be circular"
|
||||
|
||||
if (!circular) throw "faces must be circular";
|
||||
|
||||
//calculate circle properties
|
||||
let area = poly.area(),
|
||||
diam = Math.sqrt(area / Math.PI)* 2,
|
||||
diam = Math.sqrt(area / Math.PI) * 2,
|
||||
center = poly.calcCircleCenter();
|
||||
|
||||
//find direction using normal to center point
|
||||
|
||||
//find direction using normal to center point
|
||||
let point = newPoint(x1, y1, z1),
|
||||
delta = point.setZ(center.z).sub(center),
|
||||
normal = new THREE.Vector3(x1-x2, y1-y2, z1-z2)
|
||||
.cross(new THREE.Vector3(x2-x3, y2-y3, z2-z3)),
|
||||
normal = new THREE.Vector3(x1 - x2, y1 - y2, z1 - z2)
|
||||
.cross(new THREE.Vector3(x2 - x3, y2 - y3, z2 - z3)),
|
||||
dotProd = normal.dot(delta),
|
||||
interior = dotProd < 0
|
||||
interior = dotProd < 0
|
||||
|
||||
return {faces, zmin, zmax, poly, circular, area, diam, center,interior}
|
||||
return { faces, zmin, zmax, poly, circular, area, diam, center, interior }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ export function init() {
|
|||
let idn = new Raycaster(rp, dn)
|
||||
.intersectObjects(targets, false)
|
||||
.filter(t => !t.object.pillar);
|
||||
if (idn.length && idn.length % 2 === 0) {
|
||||
if (idn.length) {
|
||||
iw = iup[0].object.widget || iw;
|
||||
let wa = api.widgets.annotate(iw.id);
|
||||
let ws = (wa.support = wa.support || []);
|
||||
|
|
@ -471,10 +471,11 @@ export function init() {
|
|||
iw = null;
|
||||
}
|
||||
p1 = point;
|
||||
// looking up by default (assumes platform intersect)
|
||||
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
|
||||
// when intersecting underside of object, project down
|
||||
if (int && int.face && int.face.normal.z < -0.1) {
|
||||
dir.y = -1;
|
||||
}
|
||||
|
|
@ -482,7 +483,7 @@ export function init() {
|
|||
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
|
||||
if (i2.length && i2.length % 2 === 0) {
|
||||
if (i2.length) {
|
||||
p2 = i2[0].point;
|
||||
iw = i2[0].object.widget || iw;
|
||||
let p1y = Math.max(0, p1.y);
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
retSpeed = process.outputRetractSpeed * 60 || 1, // range
|
||||
retDwell = process.outputRetractDwell || 0, // range
|
||||
timeDwell = retDwell / 1000,
|
||||
arcDist = isBelt ? 0 : (process.arcTolerance || 0),
|
||||
scarfZ = process.outputScarfLength ? true : false,
|
||||
arcDist = isBelt || scarfZ ? 0 : (process.arcTolerance || 0),
|
||||
arcMin = 1,
|
||||
arcRes = 20,
|
||||
arcDev = 0.5,
|
||||
|
|
@ -111,7 +112,11 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
progress: 0,
|
||||
nozzle: tool,
|
||||
tool: tool,
|
||||
model_labels: model_labels.sort().join(',')
|
||||
oid: '',
|
||||
oname: '',
|
||||
model_labels: model_labels.sort().join(','),
|
||||
total_time: Math.ceil(print.total_time / 60),
|
||||
remain_time: Math.ceil(print.total_time / 60),
|
||||
},
|
||||
pidx, path, out, speedMMM, emitMM, emitPerMM, lastp, laste, dist,
|
||||
lines = 0,
|
||||
|
|
@ -525,6 +530,7 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
// retract before first move
|
||||
retract();
|
||||
|
||||
subst.remain_time = Math.ceil(print.total_time / 60);
|
||||
while (layer < layers.length) {
|
||||
path = layers[layer];
|
||||
layerno = path.slice.index;
|
||||
|
|
@ -598,7 +604,7 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
}
|
||||
|
||||
// move Z to layer height
|
||||
if (layer > 0 || !isBelt) {
|
||||
if (layer === 0 || ((layer > 0 || !isBelt) && !scarfZ)) {
|
||||
moveTo({z:zpos}, seekMMM);
|
||||
}
|
||||
|
||||
|
|
@ -620,6 +626,8 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
let b64 = encodeBitOffset(off);
|
||||
append(`; start object id: ${out.widget.track.grid_id}`);
|
||||
isBambu && append(`M624 ${b64}`);
|
||||
subst.oid = out.widget.track.grid_id;
|
||||
subst.oname = out.widget.meta.file;
|
||||
}
|
||||
cwidget = out.widget;
|
||||
}
|
||||
|
|
@ -779,12 +787,20 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
} else {
|
||||
// emitMM = emitPerMM * out.emit * dist;
|
||||
emitMM = extrudeMM(dist, emitPerMM, out.emit);
|
||||
moveTo({x:x, y:y, e:emitMM}, speedMMM);
|
||||
if (scarfZ) {
|
||||
moveTo({x, y, z:z + path.height/2, e:emitMM}, speedMMM);
|
||||
} else {
|
||||
moveTo({x, y, e:emitMM}, speedMMM);
|
||||
}
|
||||
emitted += emitMM;
|
||||
}
|
||||
} else {
|
||||
drainQ();
|
||||
moveTo({x:x, y:y}, seekMMM);
|
||||
if (scarfZ) {
|
||||
moveTo({x, y, z:z + path.height/2}, speedMMM);
|
||||
} else {
|
||||
moveTo({x, y}, seekMMM);
|
||||
}
|
||||
// TODO disabling out of plane z moves until a better mechanism
|
||||
// can be built that doesn't rely on computed zpos from layer heights...
|
||||
// when making z moves (like polishing) allow slowdown vs fast seek
|
||||
|
|
@ -830,11 +846,13 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
append(`M808`);
|
||||
}
|
||||
drainQ();
|
||||
|
||||
print.total_time -= path.print_time;
|
||||
subst.remain_time = Math.ceil(print.total_time / 60);
|
||||
}
|
||||
|
||||
function emitQrec(rec) {
|
||||
let {e, x, y, dist, emitPerMM, speedMMM} = rec;
|
||||
// emitMM = emitPerMM * e * dist;
|
||||
emitMM = extrudeMM(dist, emitPerMM, e);
|
||||
moveTo({x:x, y:y, e:emitMM}, speedMMM);
|
||||
emitted += emitMM;
|
||||
|
|
@ -958,7 +976,6 @@ export function fdm_export(print, online, ondone, ondebug) {
|
|||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
if (inloops) {
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ export function menu() {
|
|||
sliceBottomLayers: newInput(LANG.sl_lbot_s, { title:LANG.sl_lbot_l, convert:toInt }),
|
||||
separator: newBlank({ class:"set-sep", driven }),
|
||||
sliceAdaptive: newBoolean(LANG.ad_adap_s, onBooleanClick, { title: LANG.ad_adap_l }),
|
||||
outputLayerRetract: newBoolean(LANG.ad_lret_s, onBooleanClick, {title:LANG.ad_lret_l}),
|
||||
sliceLayerStart: newSelect(LANG.sl_strt_s, {title:LANG.sl_strt_l}, "start"),
|
||||
_____: newGroup(LANG.sw_menu, $('fdm-walls'), { modes:FDM, driven, hideable, separator, group:"fdm-walls" }),
|
||||
sliceShells: newInput(LANG.sl_shel_s, { title:LANG.sl_shel_l, convert:toFloat }),
|
||||
sliceLineWidth: newInput(LANG.sl_line_s, { title:LANG.sl_line_l, convert:toFloat, bound:bound(0,5) }),
|
||||
|
|
@ -82,23 +84,25 @@ export function menu() {
|
|||
sliceDetectThin: newSelect(LANG.ad_thin_s, { title: LANG.ad_thin_l, action: thinWallSave }, "thin"),
|
||||
outputAlternating: newBoolean(LANG.ad_altr_s, onBooleanClick, {title:LANG.ad_altr_l}),
|
||||
sliceZInterleave: newBoolean(LANG.ad_zint_s, onBooleanClick, {title:LANG.ad_zint_l, show:zIntShow}),
|
||||
_____: newGroup(LANG.fs_menu, $('fdm-solid'), { modes:FDM, driven, hideable, separator, group:"fdm-solid" }),
|
||||
sliceFillAngle: newInput(LANG.fi_angl_s, {title:LANG.fi_angl_l, convert:toFloat}),
|
||||
sliceFillOverlap: newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:toFloat, bound:bound(0.0,2.0)}),
|
||||
sliceFillGrow: newInput(LANG.fi_grow_s, {title:LANG.fi_grow_l, convert:toFloat}),
|
||||
sliceSolidMinArea: newInput(LANG.ad_msol_s, {title:LANG.ad_msol_l, convert:toFloat}),
|
||||
_____: newGroup(LANG.fi_menu, $('fdm-fill'), { modes:FDM, driven, hideable, separator, group:"fdm-fill" }),
|
||||
sliceFillType: newSelect(LANG.fi_type, {trigger}, "infill"),
|
||||
sliceFillSparse: newInput(LANG.fi_pcnt_s, {title:LANG.fi_pcnt_l, convert:toFloat, bound:bound(0.0,1.0), show:hasInfill}),
|
||||
sliceFillRepeat: newInput(LANG.fi_rept_s, {title:LANG.fi_rept_l, convert:toInt, bound:bound(1,10), show:fillIsLinear}),
|
||||
sliceFillOverlap: newInput(LANG.fi_over_s, {title:LANG.fi_over_l, convert:toFloat, bound:bound(0.0,2.0), show:hasInfill}),
|
||||
separator: newBlank({ class:"set-sep", driven }),
|
||||
sliceFillRate: newInput(LANG.ou_feed_s, {title:LANG.ou_feed_l, convert:toInt, bound:bound(0,500)}),
|
||||
sliceSolidRate: newInput(LANG.ou_fini_s, {title:LANG.ou_fini_l, convert:toInt, bound:bound(0,500)}),
|
||||
separator: newBlank({ class:"set-sep", driven }),
|
||||
sliceFillGrow: newInput(LANG.fi_grow_s, {title:LANG.fi_grow_l, convert:toFloat}),
|
||||
sliceFillAngle: newInput(LANG.fi_angl_s, {title:LANG.fi_angl_l, convert:toFloat}),
|
||||
_____: newGroup(LANG.fh_menu, $('fdm-heat'), { modes:FDM, driven, hideable, separator, group:"fdm-heat" }),
|
||||
outputTemp: newInput(LANG.ou_nozl_s, {title:LANG.ou_nozl_l, convert:toInt}),
|
||||
outputBedTemp: newInput(LANG.ou_bedd_s, {title:LANG.ou_bedd_l, convert:toInt}),
|
||||
_____: newGroup(LANG.fc_menu, $('fdm-cool'), { modes:FDM, driven, hideable, separator, group:"fdm-cool" }),
|
||||
outputFanLayer: newInput(LANG.ou_fanl_s, { title:LANG.ou_fanl_l, convert:toInt, bound:bound(0,255) }),
|
||||
outputFanSpeed: newInput(LANG.ou_fans_s, {title:LANG.ou_fans_l, convert:toInt, bound:bound(0,255)}),
|
||||
outputMinLayerTime: newInput(LANG.ou_layt_s, { title:LANG.ou_layt_l, convert:toInt, bound:bound(0,200) }),
|
||||
_____: newGroup(LANG.sp_menu, $('fdm-support'), { modes:FDM, driven, hideable, separator, group:"fdm-supp" }),
|
||||
sliceSupportNozzle: newSelect(LANG.sp_nozl_s, {title:LANG.sp_nozl_l, show:isMultiHead}, "extruders"),
|
||||
sliceSupportDensity: newInput(LANG.sp_dens_s, {title:LANG.sp_dens_l, convert:toFloat, bound:bound(0.0,1.0)}),
|
||||
|
|
@ -164,23 +168,21 @@ export function menu() {
|
|||
outputRetractSpeed: newInput(LANG.ad_rrat_s, {title:LANG.ad_rrat_l, convert:toInt}),
|
||||
outputRetractWipe: newInput(LANG.ad_wpln_s, {title:LANG.ad_wpln_l, convert:toFloat, bound:bound(0.0,10)}),
|
||||
separator: newBlank({ class:"set-sep", driven }),
|
||||
sliceLayerStart: newSelect(LANG.sl_strt_s, {title:LANG.sl_strt_l}, "start"),
|
||||
outputLayerRetract: newBoolean(LANG.ad_lret_s, onBooleanClick, {title:LANG.ad_lret_l}),
|
||||
outputAvoidGaps: newBoolean(LANG.ad_agap_s, onBooleanClick, {title:LANG.ad_agap_l}),
|
||||
separator: newBlank({ class:"set-sep", driven, show:isBelt }),
|
||||
outputBeltFirst: newBoolean(LANG.ad_lbir_s, onBooleanClick, {title:LANG.ad_lbir_l, show:isBelt}),
|
||||
_____: newGroup(LANG.ad_menu, $('fdm-expert'), { modes:FDM, driven, hideable, separator, group:"fdm-xprt" }),
|
||||
sliceAngle: newInput(LANG.sl_angl_s, {title:LANG.sl_angl_l, convert:toFloat, show:isBelt}),
|
||||
antiBacklash: newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, convert:toFloat, bound:bound(0,3)}),
|
||||
arcTolerance: newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, convert:toFloat, bound:bound(0,1.0), show:() => { return isNotBelt() }}),
|
||||
outputRetractDwell: newInput(LANG.ad_rdwl_s, {title:LANG.ad_rdwl_l, convert:toInt}),
|
||||
sliceSolidMinArea: newInput(LANG.ad_msol_s, {title:LANG.ad_msol_l, convert:toFloat}),
|
||||
outputMinSpeed: newInput(LANG.ad_mins_s, {title:LANG.ad_mins_l, convert:toFloat, bound:bound(1,200)}),
|
||||
outputPurgeTower: newInput(LANG.ad_purg_s, {title:LANG.ad_purg_l, convert:toInt, bound:bound(0,1000)}),
|
||||
outputScarfLength: newInput(LANG.ad_scar_s, {title:LANG.ad_scar_l, convert:toFloat, bound:bound(0,1000)}),
|
||||
outputShortPoly: newInput(LANG.ad_spol_s, {title:LANG.ad_spol_l, convert:toFloat, bound:bound(0,10000)}),
|
||||
outputCoastDist: newInput(LANG.ad_scst_s, {title:LANG.ad_scst_l, convert:toFloat, bound:bound(0.0,10)}),
|
||||
zHopDistance: newInput(LANG.ad_zhop_s, {title:LANG.ad_zhop_l, convert:toFloat, bound:bound(0,3.0)}),
|
||||
arcTolerance: newInput(LANG.ad_arct_s, {title:LANG.ad_arct_l, convert:toFloat, bound:bound(0,1.0), show:() => { return isNotBelt() }}),
|
||||
antiBacklash: newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, convert:toInt, bound:bound(0,3)}),
|
||||
outputLoops: newInput(LANG.ag_loop_s, {title:LANG.ag_loop_l, convert:toInt, bound:bound(-1,1000), show:isBelt}),
|
||||
outputPurgeTower: newInput(LANG.ad_purg_s, {title:LANG.ad_purg_l, convert:toInt, bound:bound(0,1000)}),
|
||||
|
||||
fdmRanges: $('fdm-ranges'),
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { polygons as POLY, fillArea } from '../../../geo/polygons.js';
|
|||
import { getRangeParameters } from './driver.js';
|
||||
import { slicer } from '../../../geo/slicer.js';
|
||||
import { Point } from '../../../geo/point.js';
|
||||
import { Slope } from '../../../geo/slope.js';
|
||||
|
||||
slicer.slicePost.FDM = slicePost;
|
||||
|
||||
|
|
@ -20,7 +21,26 @@ export function slicePost(data, options) {
|
|||
const { useAssembly, post_args, zIndexes } = options;
|
||||
const { process, isSynth, vaseMode } = post_args;
|
||||
const { shellOffset, fillOffset, clipOffset } = post_args;
|
||||
data.tops = POLY.nest(groups);
|
||||
|
||||
if (options.pump) {
|
||||
let nugroups = [];
|
||||
let pump = shellOffset / 10;
|
||||
for (let p of groups) {
|
||||
let retop = POLY.offset([ p ], -pump, {
|
||||
join: ClipperLib.JoinType.jtRound,
|
||||
arc: (1/pump) * 4
|
||||
});
|
||||
retop = POLY.offset(retop, pump, {
|
||||
join: ClipperLib.JoinType.jtRound,
|
||||
arc: (1/pump) * 4
|
||||
});
|
||||
nugroups.push(...retop);
|
||||
}
|
||||
data.tops = POLY.nest(nugroups);
|
||||
} else {
|
||||
data.tops = POLY.nest(groups);
|
||||
}
|
||||
|
||||
if (isSynth) {
|
||||
const process = post_args.process;
|
||||
if (process.sliceSupportGrow > 0) {
|
||||
|
|
@ -69,7 +89,10 @@ export function slicePost(data, options) {
|
|||
};
|
||||
|
||||
function offset_default(params) {
|
||||
let { z, top, count, top_poly, offset1, offsetN, wasm, last, gaps } = params;
|
||||
let { z, top, count, top_poly, offset1, offsetN, wasm, last, gaps, thin } = params;
|
||||
|
||||
let first;
|
||||
|
||||
// standard wall offsetting strategy
|
||||
POLY.offset(
|
||||
top_poly,
|
||||
|
|
@ -81,6 +104,7 @@ function offset_default(params) {
|
|||
outs: top.shells,
|
||||
flat: true,
|
||||
call: (polys, onCount) => {
|
||||
first = first || polys;
|
||||
last = polys;
|
||||
// mark each poly with depth (offset #) starting at 0
|
||||
for (let p of polys) {
|
||||
|
|
@ -99,6 +123,79 @@ function offset_default(params) {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!thin) {
|
||||
return { last, gaps };
|
||||
}
|
||||
|
||||
const deltaBig = 160;
|
||||
const deltSmall = 20;
|
||||
|
||||
// look for close points on adjacent segmented polys and merge
|
||||
let test_polys = first !== last ? [ ...first, ...last ] : last;
|
||||
let test_flats = POLY.flatten(test_polys ?? []);
|
||||
|
||||
// pre-compute segment angles
|
||||
for (let poly of test_flats)
|
||||
poly.forEachSegment((p0, p1) => {
|
||||
p0.angle = new Slope(p0, p1);
|
||||
});
|
||||
|
||||
// segment each poly to be tested
|
||||
let thin_test = test_flats.map(poly => {
|
||||
return {
|
||||
poly,
|
||||
points: poly.segment(offset1, true).points
|
||||
}
|
||||
});
|
||||
|
||||
for (let pi=0, pil=thin_test.length; pi<pil; pi++) {
|
||||
for (let pj=pi; pj<pil; pj++) {
|
||||
let prec0 = thin_test[pi];
|
||||
let prec1 = thin_test[pj];
|
||||
let pp0 = prec0.points;
|
||||
let pp1 = prec1.points;
|
||||
let moved = false;
|
||||
for (let i=0; i<pp0.length; i++) {
|
||||
let p0 = pp0[i];
|
||||
if (p0.moved !== undefined) continue;
|
||||
for (let j=0; j<pp1.length; j++) {
|
||||
let p1 = pp1[j];
|
||||
if (p1.moved !== undefined) continue;
|
||||
if (p0 === p1 || p0.segment === p1.segment) continue;
|
||||
let dist = p0.distTo2D(p1);
|
||||
if (dist < offset1) {
|
||||
let diff = p0.segment.angle.angleDiff(p1.segment.angle,true);
|
||||
if (diff < deltaBig) continue;
|
||||
// merge points marking point offset and skip
|
||||
let mid = p0.midPointTo(p1);
|
||||
let inc = p0.distTo2D(mid) / offset1;
|
||||
moved = p0.moved = p1.moved = inc.round(4);
|
||||
p1.skip = 1;
|
||||
p0.x = p1.x = mid.x;
|
||||
p0.y = p1.y = mid.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (moved) {
|
||||
prec0.moved = prec1.moved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let rec of thin_test) {
|
||||
if (rec.moved) {
|
||||
let last_seg;
|
||||
// tag for codec extended point encoding
|
||||
rec.poly._epk = ["moved", "skip"];
|
||||
rec.poly.points = rec.points.filter((p,i) => {
|
||||
let keep = (last_seg !== p.segment || p.moved || p.skip);
|
||||
last_seg = p.segment;
|
||||
return keep;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { last, gaps };
|
||||
}
|
||||
|
||||
|
|
@ -164,31 +261,189 @@ function thin_type_2(params) {
|
|||
|
||||
function thin_type_3(params) {
|
||||
// offsetN usually = extrusion width (nozzle diameter)
|
||||
let { z, top, count, offsetN, last, gaps } = params;
|
||||
let { z, top, count, offsetN } = params;
|
||||
|
||||
// produce trace from outside of poly inward no more than max inset
|
||||
let { noodle, remain } = top.poly.noodle(offsetN * count);
|
||||
let noodleWidth = offsetN * count;
|
||||
let { noodle, remain } = top.poly.noodle(noodleWidth);
|
||||
for (let n of POLY.flatten(noodle).sort((a,b) => a.depth - b.depth)) {
|
||||
n.shell = [1,-1][Math.floor(n.depth / 2)];
|
||||
// console.log('map', n.depth, n.shell, n.length, n.area().round(3));
|
||||
}
|
||||
|
||||
// top.shells = noodle;
|
||||
top.gaps = last = remain;
|
||||
// re-expand inner offset so fill offsets align properly with expectation
|
||||
remain = POLY.offset(remain, offsetN / 2);
|
||||
|
||||
let thin = top.thin_fill = [];
|
||||
let sparse = top.fill_sparse = [];
|
||||
let debugNoodle = false;
|
||||
let debugRemain = false;
|
||||
let debugExtrusion = false;
|
||||
|
||||
let shells = top.shells; // only used for debug
|
||||
let traces = top.thin_wall = [];
|
||||
|
||||
let midR = offsetN * 0.5; // nominal extrusion width
|
||||
let minR = midR * 0.75; // smallest interior fill or single wall width
|
||||
let maxR = midR * 1.5; // max double wall extrusion width
|
||||
let maxF = midR * 2; // max interior extrusion width
|
||||
|
||||
let lines = [];
|
||||
let polys = [];
|
||||
|
||||
// show inset "noodle"
|
||||
if (debugNoodle) polys.push(...noodle);
|
||||
|
||||
outer: for (let i=0; i<count; i++) {
|
||||
let next = [];
|
||||
|
||||
// process each top level noodle separately
|
||||
for (let n of noodle.sort((a,b) => b.area() - a.area()))
|
||||
trace_noodle(
|
||||
[ n ],
|
||||
noodleWidth,
|
||||
minR,
|
||||
i === 0 ? midR : midR * 1.25,
|
||||
i === 0 ? maxR : maxF,
|
||||
{
|
||||
shell: n.shell,
|
||||
lines,
|
||||
polys,
|
||||
remain: next,
|
||||
traces,
|
||||
brute: false,
|
||||
showChainIntersect: false
|
||||
});
|
||||
|
||||
// show remaining noodle after single trace
|
||||
if (debugRemain) polys.push(...POLY.setZ(POLY.flatten(next), z));
|
||||
|
||||
noodle = next;
|
||||
}
|
||||
|
||||
// console.log({ lines, polys, trace });
|
||||
|
||||
// show extrusion
|
||||
let zo = z + 0.1;
|
||||
if (debugExtrusion)
|
||||
for (let trace of traces)
|
||||
for (let point of trace) {
|
||||
polys.push(newPolygon().centerCircle(point, point.r, 12).setZ(zo = zo + 0.005));
|
||||
}
|
||||
|
||||
if (!debugExtrusion)
|
||||
POLY.setZ([...lines, ...polys], z);
|
||||
|
||||
if (lines.length) top.thin_fill = lines;
|
||||
if (polys.length) shells.push(...polys);
|
||||
|
||||
// create shell depth annotation that will survive serialization
|
||||
top.thin_sort = traces.map(t => t.shell);
|
||||
|
||||
// remove point.segment markers for codec encoding between minion and worker
|
||||
for (let trace of traces) {
|
||||
for (let pt of trace) {
|
||||
delete pt.s;
|
||||
}
|
||||
}
|
||||
|
||||
// console.log({ traces: traces.map(t => [t.length, t.shell]) });
|
||||
// console.log({ this: (self.kiri_minion?.name ?? self.kiri_worker), traces, polys });
|
||||
return { traces, last: remain };
|
||||
}
|
||||
|
||||
// trace a single extrusion line around the inside of the noodle poly
|
||||
function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) {
|
||||
let {
|
||||
brute,
|
||||
showInset,
|
||||
showChainIntersect,
|
||||
minArea = midR * midR,
|
||||
shellStep = 1 / 100, // diameter step as % of nozzle
|
||||
lines = [],
|
||||
polys = [],
|
||||
remain = [],
|
||||
traces = [],
|
||||
shell = 0
|
||||
} = opt;
|
||||
|
||||
// increment shell with direction (1=outside, -1=inside)
|
||||
let shellAdd = shell > 0 ? 1 : -1;
|
||||
let sstep = 1 / shellStep;
|
||||
let insets = [];
|
||||
let scale = 1000;
|
||||
let minR = offsetN / 2;
|
||||
let maxR = minR * 1.5;
|
||||
let minSpur = offsetN * 2;
|
||||
let dstep = minR / 4;
|
||||
let dtotl = 0;
|
||||
|
||||
let showNoodle = false;
|
||||
let mergeChains = true;
|
||||
let interpolateShortSpur = true;
|
||||
let showChainRawPoints = false;
|
||||
let showChainInterpPoints = true;
|
||||
let showMidline = true;
|
||||
let showMidPoints = false;
|
||||
let showMidNormals = false;
|
||||
let showMidRadii = true;
|
||||
let showNexuses = false;
|
||||
let inset = POLY.offset(noodle, -dstep, {
|
||||
join: ClipperLib.JoinType.jtRound,
|
||||
arc: (1/dstep) * 4
|
||||
});
|
||||
|
||||
while (inset && inset.length) {
|
||||
dtotl += dstep;
|
||||
for (let p of POLY.flatten(inset, [])) {
|
||||
p.dtotl = dtotl;
|
||||
}
|
||||
insets.push(inset);
|
||||
if (showInset) {
|
||||
polys.appendAll(inset);
|
||||
}
|
||||
if (!brute) {
|
||||
break;
|
||||
}
|
||||
inset = POLY.offset(inset, -dstep, {
|
||||
minArea: 0
|
||||
});
|
||||
}
|
||||
|
||||
// project from first inset and find greated offset intersection.
|
||||
// this examples show how to brute force create a medial axis from
|
||||
// micro stepping insets. the same intersect technique used with
|
||||
// medial axis can be applied to this structure to produce an
|
||||
// equivalent output. not as computationally efficient, but much simpler.
|
||||
// if (brute) {
|
||||
// let { intersectRayLine } = base.util;
|
||||
// let tests = insets.slice(1);
|
||||
// let source = POLY.flatten(insets[0], []);
|
||||
// for (let poly of source) {
|
||||
// // segment the smallest inset and test against all other insets
|
||||
// poly.segment(minR).forEachSegment((p1, p2) => {
|
||||
// let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 };
|
||||
// let len = pointDist(p1, p2);
|
||||
// let dx = (p1.x - p2.x) / len;
|
||||
// let dy = (p1.y - p2.y) / len;
|
||||
// let np2 = { x: np1.x + dy * 10, y: np1.y - dx * 10 };
|
||||
// let max = { dtotl: poly.dtotl, dist: Infinity, maxd: Infinity };
|
||||
// let term = false;
|
||||
// outer: for (let test of tests) {
|
||||
// for (let tpoly of POLY.flatten(test, [])) {
|
||||
// tpoly.forEachSegment((tp1, tp2) => {
|
||||
// let int = intersectRayLine(np1, { dx: dy, dy: -dx }, tp1, tp2);
|
||||
// if (int && int.dist > int.p1.poly.dtotl) {
|
||||
// return;
|
||||
// }
|
||||
// if (int && int.dist <= max.maxd && int.p1.poly.dtotl > max.dtotl) {
|
||||
// max.dtotl = int.p1.poly.dtotl;
|
||||
// max.dist = int.dist;
|
||||
// max.int = int;
|
||||
// } else if (int && int.dist <= max.maxd && int.p1.poly.dtotl === max.dtotl && int.dist < max.dist) {
|
||||
// max.dtotl = int.p1.poly.dtotl;
|
||||
// max.dist = int.dist;
|
||||
// max.int = int;
|
||||
// }
|
||||
// });
|
||||
// if (term) {
|
||||
// break outer;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (max.int) {
|
||||
// lines.push(new Point(np1.x, np1.y));
|
||||
// lines.push(new Point(max.int.x, max.int.y));
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// return { lines, polys };
|
||||
// }
|
||||
|
||||
let pointMap = new Map();
|
||||
let lineMap = new Map();
|
||||
|
|
@ -240,11 +495,8 @@ function thin_type_3(params) {
|
|||
return rec;
|
||||
}
|
||||
|
||||
let chains = [];
|
||||
|
||||
// for each standalone noodle, construct medial axis
|
||||
// map medial axis points into de-duplicated segments
|
||||
// which will be re-connected into chains
|
||||
for (let poly of noodle) {
|
||||
// scale the polygon (including inners) so the medial axis code works properly
|
||||
poly = poly.clone(true).scale({ x:scale, y:scale, z:scale });
|
||||
|
|
@ -253,349 +505,174 @@ function thin_type_3(params) {
|
|||
// construct medial axis lines. this appears to produce duplicate
|
||||
// reverse segments in a subset of cases
|
||||
let ma = JSPoly.construct_medial_axis(out, inr);
|
||||
// dedup the points so we can track them uniquely (for chain intersections)
|
||||
if (ma.length === 0) throw('cannot construct medial axis');
|
||||
// dedup the points so we can track them uniquely
|
||||
for (let { point0, point1 } of ma) {
|
||||
pointsToLine(point0, point1);
|
||||
}
|
||||
}
|
||||
|
||||
// connect segments into chains
|
||||
for (let seg of lineMap.values()) {
|
||||
let { p0, p1 } = seg;
|
||||
let len = seg.len = pointDist(p0, p1);
|
||||
// filter out lines where both points' radii are under the
|
||||
// minR threshold. shorten and fixup lines where minR falls
|
||||
// somewhere along the gradient between ends.
|
||||
if (interpolateShortSpur)
|
||||
if (p0.r < minR || p1.r < minR) {
|
||||
if (p0.r > minR && len > minSpur) {
|
||||
// move p1 toward p0 until minR met
|
||||
let pct = (minR - p1.r) / (p0.r - p1.r);
|
||||
let dx = p1.x - p0.x;
|
||||
let dy = p1.y - p0.y;
|
||||
p1.x -= dx * pct;
|
||||
p1.y -= dy * pct;
|
||||
p1.r = minR;
|
||||
pointMap.delete(p1.key);
|
||||
} else if (p1.r > minR && len > minSpur) {
|
||||
// move p0 toward p1 until minR met
|
||||
let pct = (minR - p0.r) / (p1.r - p0.r);
|
||||
let dx = p0.x - p1.x;
|
||||
let dy = p0.y - p1.y;
|
||||
p0.x -= dx * pct;
|
||||
p0.y -= dy * pct;
|
||||
p0.r = minR;
|
||||
pointMap.delete(p0.key);
|
||||
} else {
|
||||
// both points eliminated
|
||||
pointMap.delete(p0.key);
|
||||
pointMap.delete(p1.key);
|
||||
lineMap.delete(seg.key);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// for each line segment either add it to a chain or start a new chain
|
||||
let add;
|
||||
for (let chain of chains) {
|
||||
if (p0.count === 2 && p0 === chain[0]) {
|
||||
// prefix chain with p1
|
||||
chain.splice(0,0,p1);
|
||||
add = true;
|
||||
} else if (p0.count === 2 && p0 === chain.peek()) {
|
||||
// append chain with p1
|
||||
chain.push(p1);
|
||||
add = true;
|
||||
} else if (p1.count === 2 && p1 === chain[0]) {
|
||||
// prefix chain with p0
|
||||
chain.splice(0,0,p0);
|
||||
add = true;
|
||||
} else if (p1.count === 2 && p1 === chain.peek()) {
|
||||
// append chain with p0
|
||||
chain.push(p0);
|
||||
add = true;
|
||||
}
|
||||
if (add) {
|
||||
chain.total += len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!add) {
|
||||
chains.push([ p0, p1 ]);
|
||||
chains.peek().total = len;
|
||||
}
|
||||
}
|
||||
|
||||
// todo: merge chains at next of 3 or more preferencing
|
||||
// merging longer chains. then shorten remaining nexus chain
|
||||
// intersections by minR
|
||||
|
||||
// render inset "noodle"
|
||||
if (showNoodle) {
|
||||
top.shells.appendAll(noodle);
|
||||
}
|
||||
|
||||
// divide a segment/redius into 1 or more equal subsegments
|
||||
function div(cr) {
|
||||
if (cr <= maxR) {
|
||||
return [ cr ];
|
||||
} else if (cr <= maxR * 2) {
|
||||
return [ cr / 2, cr / 2 ];
|
||||
let rem = (cr - midR * 2);
|
||||
if (rem < minR) {
|
||||
return [ cr / 2, cr / 2 ];
|
||||
}
|
||||
return [ midR, rem, midR ];
|
||||
} else if (cr <= maxR * 3) {
|
||||
let rem = cr - minR * 2;
|
||||
if (rem < maxR) {
|
||||
return [ minR, rem, minR ];
|
||||
} else {
|
||||
return [ minR, rem / 2, rem / 2, minR ];
|
||||
let rem = (cr - midR * 2);
|
||||
if (rem < minR) {
|
||||
return [ cr / 3, cr / 3, cr / 3 ];
|
||||
}
|
||||
return [ midR, rem, midR ];
|
||||
} else {
|
||||
return [ minR, ...div(cr - minR * 2), minR ];
|
||||
return [ midR, cr - midR * 2, midR ];
|
||||
}
|
||||
}
|
||||
|
||||
// filter chains that are too short from consideration
|
||||
chains = chains.filter(c => c.total >= minR);
|
||||
chains.sort((c1,c2) => c2.total - c1.total);
|
||||
// medial axis segments for comparison
|
||||
let segs = [...lineMap.values()];
|
||||
|
||||
function merge1chain() {
|
||||
let clen = chains.length;
|
||||
for (let i=0; i<clen; i++) {
|
||||
let ci = chains[i];
|
||||
if (!ci) continue;
|
||||
for (let j=i+1; j<clen; j++) {
|
||||
let cj = chains[j];
|
||||
if (!cj) continue;
|
||||
if (ci[0] === cj[0]) {
|
||||
ci.reverse().appendAll(cj)
|
||||
ci.total += cj.total;
|
||||
cj.merged = true;
|
||||
chains[j] = undefined;
|
||||
return true;
|
||||
} else if (ci.peek() === cj.peek()) {
|
||||
ci.appendAll(cj.reverse());
|
||||
ci.total += cj.total;
|
||||
cj.merged = true;
|
||||
chains[j] = undefined;
|
||||
return true;
|
||||
} else if (ci.peek() === cj[0]) {
|
||||
ci.appendAll(cj);
|
||||
ci.total += cj.total;
|
||||
cj.merged = true;
|
||||
chains[j] = undefined;
|
||||
return true;
|
||||
} else if (ci[0] === cj.peek()) {
|
||||
cj.appendAll(ci);
|
||||
cj.total += ci.total;
|
||||
ci.merged = true;
|
||||
chains[i] = undefined;
|
||||
return true;
|
||||
}
|
||||
// project inset segment normals onto closest medial segment
|
||||
// use this to find a radius which we divide into extrusion lanes
|
||||
if (!brute)
|
||||
for (let insetp of inset) {
|
||||
let { intersectRayLine } = base.util;
|
||||
let parent = new Polygon();
|
||||
let inner = [];
|
||||
for (let poly of insetp.flattenTo([])) {
|
||||
let nupoly = poly.parent ? new Polygon() : parent;
|
||||
if (poly.parent) {
|
||||
inner.push(nupoly);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// map chains to nexus via endpoints
|
||||
let nexus = {};
|
||||
function addEp(point, chain) {
|
||||
let key = point.key;
|
||||
let rec = nexus[key];
|
||||
if (!rec) rec = nexus[key] = { point, chains: [] };
|
||||
if (rec.chains.indexOf(chain) < 0) rec.chains.push(chain);
|
||||
}
|
||||
// since chains are sorted longest to shortest,
|
||||
// they will appear in the nexus record longest to shortest
|
||||
for (let chain of chains) {
|
||||
addEp(chain[0], chain);
|
||||
addEp(chain.peek(), chain);
|
||||
}
|
||||
|
||||
// connect chains end to end following nexus branches that result
|
||||
// in the longest final merged chain (open or closed)
|
||||
// map chain endpoints to nexus points
|
||||
while (mergeChains && merge1chain()) ;
|
||||
|
||||
// filter out null chains that were merged into other chains
|
||||
chains = chains.filter(chain => chain);
|
||||
|
||||
// filter dup points from connecting ends
|
||||
chains = chains.map(chain => chain.filter((v, i) => i === 0 || v !== chain[i - 1]));
|
||||
|
||||
// mark closed chains
|
||||
// todo use exact point match by eliminating slice above
|
||||
// then later removing repeated points in the resulting chain from merges
|
||||
for (let chain of chains) {
|
||||
chain.closed = (chain[0] === chain.peek());
|
||||
console.log(chain.closed ? 'closed' : 'open');
|
||||
if (chain.closed) chain.pop();
|
||||
}
|
||||
|
||||
// shorten chain length by minR distance by popping or moving points
|
||||
function shorten(chain) {
|
||||
for (let rem=0, i=chain.length-1; i>=1 && rem < minR; i--) {
|
||||
let c1 = chain[i];
|
||||
let c2 = chain[i-1];
|
||||
let d = pointDist(c1,c2);
|
||||
if (rem + d <= minR) {
|
||||
chain.pop();
|
||||
rem += d;
|
||||
} else {
|
||||
let diff = minR - rem;
|
||||
// move c1 toward c2
|
||||
let pct = diff / d;
|
||||
let dx = c1.x - c2.x;
|
||||
let dy = c1.y - c2.y;
|
||||
chain[i] = {
|
||||
x: c1.x - dx * pct,
|
||||
y: c1.y - dy * pct,
|
||||
r: c1.r
|
||||
};
|
||||
rem = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shorten chains terminating at nexus by midR
|
||||
for (let rec of Object.values(nexus)) {
|
||||
let { point } = rec;
|
||||
rec.shorts = 0;
|
||||
if (rec.chains.length < 3) continue;
|
||||
for (let chain of chains.filter(c => !c.closed)) {
|
||||
if (pointDist(chain[0], point) < minR) {
|
||||
chain.reverse();
|
||||
shorten(chain);
|
||||
chain.reverse();
|
||||
rec.shorts++;
|
||||
} else if (chain.peek() === point) {
|
||||
shorten(chain);
|
||||
rec.shorts++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showNexuses)
|
||||
for (let rec of Object.values(nexus)) {
|
||||
let { point } = rec;
|
||||
top.shells.push(newPolygon().centerCircle(point, 0.2, 6 - rec.shorts));
|
||||
}
|
||||
|
||||
function renderPointNormal(pt, ndx, ndy) {
|
||||
if (showMidNormals) {
|
||||
let xo = ndx * pt.r;
|
||||
let yo = ndy * pt.r;
|
||||
thin.push(new Point(pt.x + yo, pt.y - xo));
|
||||
thin.push(new Point(pt.x - yo, pt.y + xo));
|
||||
}
|
||||
if (showMidRadii) {
|
||||
const Sx = pt.x + ndy * pt.r;
|
||||
const Sy = pt.y - ndx * pt.r;
|
||||
const circ = top.shells;
|
||||
const pop = div(pt.r);
|
||||
let acc = 0;
|
||||
for (let r of pop) {
|
||||
const t = acc + r;
|
||||
const px = Sx + -ndy * t;
|
||||
const py = Sy + ndx * t;
|
||||
circ.push(newPolygon().centerCircle({x:px, y:py}, r, 10));
|
||||
acc += r * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gather point offsets into shells
|
||||
let zi = z;
|
||||
for (let chain of chains) {
|
||||
// zi += 0.1;
|
||||
if (showChainRawPoints)
|
||||
for (let pt of chain) {
|
||||
top.shells.push(newPolygon().centerCircle(pt, pt.r ?? 0.1, 10));
|
||||
}
|
||||
// draw medial axis chain
|
||||
let { closed, length } = chain;
|
||||
let term = closed ? length : length - 1;
|
||||
let segs = [];
|
||||
for (let i=0; i<term; i++) {
|
||||
let p0 = chain[i];
|
||||
let p1 = chain[(i+1) % length];
|
||||
let len = pointDist(p0, p1);
|
||||
// medial axis segment
|
||||
if (showMidline) {
|
||||
thin.push(new Point(p0.x, p0.y, zi));
|
||||
thin.push(new Point(p1.x, p1.y, zi));
|
||||
}
|
||||
// compute medial axis segment normal
|
||||
let offs = len <= offsetN ? [ 0 ] : base.util.lerp(0, len, offsetN, true);
|
||||
let step = 1 / offs.length;
|
||||
let dr = (p1.r - p0.r);
|
||||
let dx = (p1.x - p0.x);
|
||||
let dy = (p1.y - p0.y);
|
||||
let ndx = dx / len;
|
||||
let ndy = dy / len;
|
||||
segs.push({ p0, p1, offs, step, dr, dx, dy, ndx, ndy });
|
||||
}
|
||||
console.log({ segs });
|
||||
// compute chain subdivisions
|
||||
let slen = segs.length;
|
||||
for (let si=0; si<slen; si++) {
|
||||
let seg = segs[si];
|
||||
let segp = segs[(si - 1 + slen) % slen];
|
||||
let { p0, p1, offs, step, dr, dx, dy, ndx, ndy } = seg;
|
||||
let first = si === 0;
|
||||
let last = si === slen - 1;
|
||||
let mid = !(first || last);
|
||||
// for length 2, first and last segment are the same
|
||||
if (showMidPoints) {
|
||||
if (first) {
|
||||
// todo: handle closed
|
||||
top.shells.push(newPolygon().centerCircle(p0, 0.08, 10));
|
||||
}
|
||||
if (mid) {
|
||||
// mid segments
|
||||
top.shells.push(newPolygon().centerCircle(p0, 0.08, 10));
|
||||
}
|
||||
if (last) {
|
||||
// todo: handle closed
|
||||
top.shells.push(newPolygon().centerCircle(p0, 0.08, 10));
|
||||
if (closed) {
|
||||
console.log('closed');
|
||||
} else {
|
||||
top.shells.push(newPolygon().centerCircle(p1, 0.08, 10));
|
||||
let sadd = shellAdd > 0 ? (poly.parent ? shellAdd : 0) : (poly.parent ? 0 : shellAdd);
|
||||
let ltpo;
|
||||
let trace = [];
|
||||
trace.shell = shell + sadd;
|
||||
poly.segment(minR, true).forEachSegment((p1, p2) => {
|
||||
let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 };
|
||||
let len = pointDist(p1, p2);
|
||||
let dx = (p1.x - p2.x) / len;
|
||||
let dy = (p1.y - p2.y) / len;
|
||||
let min = { dist: Infinity };
|
||||
for (let seg of segs) {
|
||||
let { p0, p1 } = seg;
|
||||
let int = intersectRayLine(np1, { dx: dy, dy: -dx }, p0, p1);
|
||||
if (int?.dist < min.dist && int?.dist < noodleWidth) {
|
||||
min = int;
|
||||
min.seg = seg;
|
||||
}
|
||||
}
|
||||
if (min.p1) {
|
||||
let mp1 = min.seg;
|
||||
if (showChainIntersect) {
|
||||
lines.push(new Point(np1.x, np1.y));
|
||||
lines.push(new Point(min.x, min.y));
|
||||
}
|
||||
let mr = min.dist + dstep;
|
||||
if (mr < minR) {
|
||||
nupoly.push(new Point(min.x, min.y));
|
||||
return;
|
||||
}
|
||||
// use intersection off and divide into extrusion lanes
|
||||
let pop = div(mr);
|
||||
let odd = (pop.length % 2 === 1);
|
||||
let len = Math.ceil(pop.length / 2);
|
||||
// check if segment was claimed by a different originating segment
|
||||
// for odd wall counts so that they're not emitted twice
|
||||
// todo: failing for noodle width single wall on circular objects
|
||||
// update claim system to eliminate overlapping close singles
|
||||
// and/or extend segment definition to curve within angle deviation
|
||||
// instead of strict point/point segments
|
||||
if (odd && mp1.claimed && mp1.claimed !== p1.segment) len--;
|
||||
if (len === 0) {
|
||||
nupoly.push(new Point(min.x, min.y));
|
||||
return;
|
||||
}
|
||||
let npo;
|
||||
if (pop.length === 1) {
|
||||
// let rad_step = ((pop[0] * sstep) | 0) / sstep;
|
||||
// for single wall place it exactly on medial axis
|
||||
nupoly.push(new Point(min.x, min.y));
|
||||
npo = { x: min.x, y: min.y, r: mr, s: p1.segment };
|
||||
} else {
|
||||
// radius rounded to nearest step
|
||||
let rad_step = ((pop[0] * sstep) | 0) / sstep;
|
||||
// otherwise use first division
|
||||
// compensate for minimal inset
|
||||
let off = -dstep + rad_step;
|
||||
npo = {
|
||||
x: np1.x + dy * off,
|
||||
y: np1.y - dx * off,
|
||||
r: rad_step,
|
||||
s: p1.segment
|
||||
};
|
||||
// trace inset by full diameter
|
||||
nupoly.push(new Point(np1.x + dy * off * 2, np1.y - dx * off * 2));
|
||||
}
|
||||
let lpo = trace.peek();
|
||||
// when outputting the same segment at the same radius
|
||||
// it's safe to drop the last point since path output will connect them
|
||||
if (trace.length > 1 && lpo.s === npo.s && lpo.r === npo.r) {
|
||||
trace.pop();
|
||||
}
|
||||
trace.push(npo);
|
||||
if (odd && !mp1.claimed) {
|
||||
mp1.claimed = p1.segment;
|
||||
}
|
||||
// detect trace jump and start new trace
|
||||
if (ltpo && pointDist(ltpo, trace.peek()) > maxR) {
|
||||
// start new trace removing jump point
|
||||
// and adding that to the new trace
|
||||
traces.push(trace);
|
||||
let nutrace = [ trace.pop() ];
|
||||
nutrace.shell = trace.shell;
|
||||
// close last trace if endpoints near enough
|
||||
if (pointDist(ltpo, trace[0] <= maxR)) {
|
||||
trace.push(trace[0]);
|
||||
}
|
||||
trace = nutrace;
|
||||
}
|
||||
// update last trace point out
|
||||
ltpo = trace.peek();
|
||||
}
|
||||
});
|
||||
// repeat first point of trace (if close enough to be closed)
|
||||
if (trace[0] && pointDist(trace[0], trace.peek()) <= maxR) {
|
||||
trace.push(trace[0]);
|
||||
}
|
||||
{
|
||||
// todo: handle closed
|
||||
if (first) {
|
||||
renderPointNormal(p0, ndx, ndy);
|
||||
}
|
||||
if (mid) {
|
||||
renderPointNormal(p0, ((ndx + segp.ndx)/2), ((ndy + segp.ndy)/2));
|
||||
}
|
||||
if (last) {
|
||||
renderPointNormal(p1, ndx, ndy);
|
||||
}
|
||||
}
|
||||
// interpolate across the length of the chain segment
|
||||
if (showChainInterpPoints)
|
||||
for (let indx = 1; indx < offs.length; indx++) {
|
||||
const inc = indx * step;
|
||||
const cr = p0.r + dr * inc;
|
||||
const cx = p0.x + dx * inc;
|
||||
const cy = p0.y + dy * inc;
|
||||
const xo = ndx * cr;
|
||||
const yo = ndy * cr;
|
||||
if (showMidPoints) {
|
||||
top.shells.push(newPolygon().centerCircle({ x:cx, y:cy }, 0.05, 10));
|
||||
}
|
||||
renderPointNormal({ x:cx, y:cy, r:cr }, ndx, ndy);
|
||||
if (trace.length) {
|
||||
traces.push(trace);
|
||||
}
|
||||
}
|
||||
|
||||
// subtract inner extrusion offsets from parent extrusion offset
|
||||
// this can result in more top level polys and must be processed
|
||||
// iteratively.
|
||||
let setA = [ parent ];
|
||||
for (let setB of inner) {
|
||||
setA = POLY.subtract(setA, [ setB ], []);
|
||||
}
|
||||
|
||||
// clean and re-nest the resulting polygon soup
|
||||
// subtraction cleans pre-subtract but not post and these
|
||||
// outlines can have sharp unprintable interiors
|
||||
let ret = POLY.nest(
|
||||
POLY.flatten(setA, [], true).map(p => {
|
||||
return p.clean().simplify();
|
||||
}).flat().map(p => {
|
||||
p.shell = shell + shellAdd;
|
||||
return p;
|
||||
})
|
||||
).filter(p => p.area() >= minArea);
|
||||
|
||||
remain.push(...ret);
|
||||
}
|
||||
|
||||
POLY.setZ([...thin, ...top.shells], z);
|
||||
// POLY.setZ([...top.shells], z);
|
||||
|
||||
return { last, gaps };
|
||||
return { lines, polys, remain, traces };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -638,22 +715,32 @@ export function doTopShells(z, top, count, offset1, offsetN, fillOffset, opt = {
|
|||
if (dist < 1) p.open = false;
|
||||
} });
|
||||
let ret = { last, gaps };
|
||||
switch (opt.thinType) {
|
||||
case "type 1":
|
||||
let ttype = opt.thinType;
|
||||
switch (ttype) {
|
||||
case "legacy 1":
|
||||
ret = thin_type_1({ z, top, count, top_poly, offsetN, fillOffset });
|
||||
break;
|
||||
case "type 2":
|
||||
case "legacy 2":
|
||||
ret = thin_type_2({ z, top, count, top_poly, offset1, offsetN, fillOffset, gaps, wasm });
|
||||
break;
|
||||
case "type 3":
|
||||
ret = thin_type_3({ z, top, count, top_poly, offsetN });
|
||||
break;
|
||||
case "adaptive":
|
||||
try {
|
||||
ret = thin_type_3({ z, top, count, offsetN });
|
||||
break;
|
||||
} catch (e) {
|
||||
console.log('adaptive layer failed. falling back to basic.', e);
|
||||
ttype = "basic";
|
||||
}
|
||||
case "basic":
|
||||
default:
|
||||
ret = offset_default({ z, top, count, top_poly, offset1, offsetN, wasm });
|
||||
ret = offset_default({
|
||||
z, top, count, top_poly, offset1, offsetN, wasm,
|
||||
thin: ttype === 'basic'
|
||||
});
|
||||
break;
|
||||
}
|
||||
last = ret.last || last;
|
||||
gaps = ret.gaps || gaps;
|
||||
last = ret.last ?? last;
|
||||
gaps = ret.gaps ?? gaps;
|
||||
}
|
||||
} else {
|
||||
// no shells, just infill, is permitted
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { base, util } from '../../../geo/base.js';
|
||||
import { poly2polyEmit, tip2tipEmit } from '../../../geo/paths.js';
|
||||
import { newBounds } from '../../../geo/bounds.js';
|
||||
import { newPoint } from '../../../geo/point.js';
|
||||
import { newPoint, Point } from '../../../geo/point.js';
|
||||
import { newPolygon, Polygon } from '../../../geo/polygon.js';
|
||||
import { polygons as POLY, fillArea } from '../../../geo/polygons.js';
|
||||
import { newPrint } from '../../core/print.js';
|
||||
|
|
@ -41,6 +41,7 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
zneg = 0,
|
||||
zoff = 0,
|
||||
zmin = 0,
|
||||
zmax = 0,
|
||||
shield,
|
||||
output = [],
|
||||
layerout = [];
|
||||
|
|
@ -141,6 +142,7 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
});
|
||||
layerout.z = zoff + height;
|
||||
layerout.height = height;
|
||||
layerout.print_time = printPoint.layer_time;
|
||||
output.append(layerout);
|
||||
|
||||
layerout = [];
|
||||
|
|
@ -195,7 +197,7 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
});
|
||||
});
|
||||
|
||||
print.addPrintPoints(preout, layerout, null);
|
||||
print.addPrintPoints(preout, layerout);
|
||||
|
||||
if (preout.length) {
|
||||
// retract between brims and print
|
||||
|
|
@ -224,6 +226,7 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
}
|
||||
for (let slice of widget.slices) {
|
||||
zmin = Math.min(zmin, slice.z);
|
||||
zmax = Math.max(zmax, slice.z);
|
||||
if (!slice.supports) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -469,6 +472,9 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
let lastExt;
|
||||
let lastOut;
|
||||
|
||||
print.zmax = zmax;
|
||||
print.total_time = 0;
|
||||
|
||||
// walk cake layers bottom up
|
||||
for (let layer of cake) {
|
||||
// track purge blocks generated for each layer
|
||||
|
|
@ -570,6 +576,8 @@ export async function fdm_prepare(widgets, settings, update) {
|
|||
}
|
||||
}
|
||||
);
|
||||
layerout.print_time = printPoint.layer_time;
|
||||
print.total_time += printPoint.layer_time;
|
||||
print.setWidget(null);
|
||||
|
||||
lastOut = slice;
|
||||
|
|
@ -796,6 +804,8 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
process = opt.params || settings.process,
|
||||
originCenter = device.originCenter || bedRound,
|
||||
extruder = parseInt(slice.extruder || 0),
|
||||
scarfLength = process.outputScarfLength || 0,
|
||||
minLayerTime = process.outputMinLayerTime || 0,
|
||||
nozzleSize = process.sliceLineWidth || device.extruders[extruder].extNozzle,
|
||||
firstLayer = (opt.first || false) && !opt.support,
|
||||
thinWall = nozzleSize * (opt.thinWall || 1.75),
|
||||
|
|
@ -805,10 +815,10 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
shellOrder = {"out-in":-1,"in-out":1,"alternate":-2}[process.sliceShellOrder] || -1,
|
||||
sparseMult = process.outputSparseMult,
|
||||
coastDist = process.outputCoastDist || 0,
|
||||
finishSpeed = opt.speed || process.outputFinishrate,
|
||||
firstShellSpeed = process.firstLayerRate,
|
||||
firstFillSpeed = process.firstLayerFillRate,
|
||||
firstPrintMult = process.firstLayerPrintMult,
|
||||
finishSpeed = firstLayer ? firstShellSpeed : (opt.speed || process.outputFinishrate),
|
||||
printSpeed = opt.speed || (firstLayer ? firstShellSpeed : process.outputFeedrate),
|
||||
fillSpeed = opt.speed || opt.fillSpeed || (firstLayer ? firstFillSpeed || firstShellSpeed : process.outputFeedrate),
|
||||
infillSpeed = process.sliceFillRate || opt.infillSpeed || fillSpeed || printSpeed,
|
||||
|
|
@ -839,10 +849,21 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
shellOrder = -shellOrder;
|
||||
}
|
||||
|
||||
// update fill speed when solids detected
|
||||
if (slice.finishSolids) {
|
||||
fillSpeed = process.sliceSolidRate || finishSpeed;
|
||||
}
|
||||
|
||||
// on flats layers also slow down shell print speed
|
||||
if (slice.isFlatsLayer) {
|
||||
printSpeed = finishSpeed || printSpeed;
|
||||
}
|
||||
|
||||
// override: adapt bridge layer speed
|
||||
if (slice.isBridgeLayer) {
|
||||
fillSpeed /= 2;
|
||||
}
|
||||
|
||||
// apply first layer extrusion multipliers
|
||||
if (firstLayer) {
|
||||
fillMult *= firstPrintMult;
|
||||
|
|
@ -873,7 +894,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
|
||||
// return true if move path from p1 to p2 intersects a
|
||||
// top and a path around the top (inside print) was not found
|
||||
function intersectsTop(p1, p2) {
|
||||
function requiresRetract(p1, p2) {
|
||||
if (slice.index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -898,15 +919,20 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
if (dbug === slice.index) console.log(slice.index, {p1, p2, d: p1.distTo2D(p2)});
|
||||
|
||||
let ints = [];
|
||||
let intk = new Set();
|
||||
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) {
|
||||
if (ip) ip._key = undefined;
|
||||
if (ip && !intk.has(ip.key)) {
|
||||
ints.push({ip, poly});
|
||||
intk.add(ip.key);
|
||||
ip.p1.poly.hits = (ip.p1.poly.hits ?? 0) + 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
ints = ints.filter(i => i.poly.hits === 2);
|
||||
|
||||
// no intersections
|
||||
if (ints.length === 0) {
|
||||
|
|
@ -1021,8 +1047,8 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// solid infill
|
||||
function outputTraces(poly, opt = {}) {
|
||||
// output shell wall
|
||||
function outputTraces(poly, opt = {}, traceNo = 0) {
|
||||
if (!poly) return;
|
||||
if (Array.isArray(poly)) {
|
||||
if (opt.sort) {
|
||||
|
|
@ -1055,30 +1081,32 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
if (next) {
|
||||
last = next;
|
||||
polys.remove(next);
|
||||
outputTraces(next, opt);
|
||||
outputTraces(next, opt, traceNo++);
|
||||
} else {
|
||||
last = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
outputOrderClosest(poly, function(next) {
|
||||
outputTraces(next, opt);
|
||||
outputTraces(next, opt, traceNo++);
|
||||
}, null);
|
||||
}
|
||||
} else {
|
||||
let finishShell = poly.depth === 0 && !firstLayer;
|
||||
let scarf = (slice.index > 0 && traceNo === 0 && z - startPoint?.z > 1e-3) ? scarfLength : 0;
|
||||
startPoint = print.polyPrintPath(poly, startPoint, preout, {
|
||||
ccw: opt.shell && process.outputAlternating && slice.index % 2,
|
||||
tool: extruder,
|
||||
rate: finishShell ? finishSpeed : printSpeed,
|
||||
accel: finishShell,
|
||||
wipe: process.outputWipeDistance || 0,
|
||||
ccw: opt.shell && process.outputAlternating && slice.index % 2,
|
||||
coast: firstLayer ? 0 : coastDist,
|
||||
extrude: numOrDefault(opt.extrude, shellMult),
|
||||
onfirst: function(firstPoint) {
|
||||
nozzleSize,
|
||||
rate: finishShell ? finishSpeed : printSpeed,
|
||||
scarf,
|
||||
tool: extruder,
|
||||
onfirst: (firstPoint, preout) => {
|
||||
let from = seedPoint || startPoint;
|
||||
if (from.distTo2D(firstPoint) > retractDist) {
|
||||
if (intersectsTop(from, firstPoint)) {
|
||||
if (requiresRetract(from, firstPoint)) {
|
||||
retract();
|
||||
}
|
||||
}
|
||||
|
|
@ -1089,6 +1117,59 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
function minThinDist(np, trace) {
|
||||
return Math.min(
|
||||
Math.hypot(np.x - trace[0].x, np.y - trace[0].y),
|
||||
Math.hypot(np.x - trace.peek().x, np.y - trace.peek().y),
|
||||
);
|
||||
}
|
||||
|
||||
function outputAdaptiveWalls(thin, sort) {
|
||||
// restore thin shell order annotation and sort on it
|
||||
thin.forEach((a,i) => a.shell = sort[i] * shellOrder);
|
||||
thin.forEach(t => t.shell = t.shell < 0 ? Math.abs(t.shell) + 0.5 : t.shell);
|
||||
thin.sort((a,b) => a.shell - b.shell);
|
||||
let np = print.lastPoint;
|
||||
// perform nearest poly to poly algorithm on shells
|
||||
for (;;) {
|
||||
let min = { dist: Infinity };
|
||||
for (let trace of thin) {
|
||||
if (!min.trace) {
|
||||
min.trace = trace;
|
||||
} else if (np && min.trace.shell === trace.shell) {
|
||||
let dist = minThinDist(np, trace);
|
||||
if (dist < min.dist) min = { dist, trace };
|
||||
}
|
||||
}
|
||||
if (!min.trace) {
|
||||
startPoint = np;
|
||||
return;
|
||||
}
|
||||
let { trace } = min;
|
||||
let fp = trace[0];
|
||||
let lp = trace.peek();
|
||||
// reverse trace if far end closer
|
||||
if (np && Math.hypot(np.x - lp.x, np.y - lp.y) < Math.hypot(np.x - fp.x, np.y - fp.y)) {
|
||||
trace.reverse();
|
||||
}
|
||||
// remove emitted trace
|
||||
thin = thin.filter(t => t !== trace);
|
||||
// retract if moving > retractDist
|
||||
if (np && np.distTo2D(new Point(trace[0].x, trace[0].y)) > retractDist) {
|
||||
retract();
|
||||
}
|
||||
let outputSpeed = trace.shell === 1 || trace.shell === 1.5 ? finishSpeed : printSpeed;
|
||||
for (let pt of trace) {
|
||||
np = new Point(pt.x, pt.y, z);
|
||||
if (pt === trace[0]) {
|
||||
print.addOutput(preout, np, 0, moveSpeed);
|
||||
} else {
|
||||
print.addOutput(preout, np, ((pt.r * 2) / nozzleSize) * shellMult, outputSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* output sparse infill
|
||||
* @param {Polygon[]} polys
|
||||
|
|
@ -1107,7 +1188,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
poly.forEachPoint((p, i) => {
|
||||
let dist = lp.distTo2D(p);
|
||||
let rdst = dist > retractDist;
|
||||
let itop = rdst && intersectsTop(lp,p);
|
||||
let itop = rdst && requiresRetract(lp,p);
|
||||
let emit = extrude;
|
||||
// retract if dist trigger and crosses a slice top polygon
|
||||
if (i === 0) {
|
||||
|
|
@ -1151,7 +1232,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
}
|
||||
let first = points[0];
|
||||
let last = first;
|
||||
if (startPoint && startPoint.distTo2D(first) > thinWall && intersectsTop(startPoint, first)) {
|
||||
if (startPoint && startPoint.distTo2D(first) > thinWall && requiresRetract(startPoint, first)) {
|
||||
retract();
|
||||
}
|
||||
print.addOutput(preout, first, 0, moveSpeed, extruder);
|
||||
|
|
@ -1185,10 +1266,10 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
start = 0,
|
||||
skip = false,
|
||||
lastIndex = -1,
|
||||
raft = opt.raft || false,
|
||||
raft = opt.raft ?? false,
|
||||
flow = opt.flow || 1,
|
||||
near = opt.near || (antiBacklash ? false : true),
|
||||
fast = opt.fast || false, // support infill only!
|
||||
near = opt.near ?? (antiBacklash ? false : true),
|
||||
fast = opt.fast ?? false, // support infill only!
|
||||
fill = (opt.fill >= 0 ? opt.fill : fillMult) * flow,
|
||||
thinDist = near ? thinWall : thinWall;
|
||||
|
||||
|
|
@ -1300,7 +1381,7 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
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))) {
|
||||
if (!fast && dist > retractDist && (zhop || requiresRetract(startPoint, p1))) {
|
||||
retract();
|
||||
}
|
||||
|
||||
|
|
@ -1409,7 +1490,15 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
}
|
||||
|
||||
// control of layer start point
|
||||
if (!lastTop)
|
||||
switch (process.sliceLayerStart) {
|
||||
case "spiral": {
|
||||
let center = top.poly.bounds.center();
|
||||
let angle = (Math.PI * 2) * (slice.z / print.zmax);
|
||||
let x = center.x + Math.cos(angle)*10000;
|
||||
let y = center.y + Math.sin(angle)*10000;
|
||||
startPoint = newPoint(x,y,startPoint.z);
|
||||
} break;
|
||||
case "last":
|
||||
break;
|
||||
case "random":
|
||||
|
|
@ -1444,6 +1533,9 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
// raft
|
||||
if (top.traces) outputTraces(top.traces);
|
||||
|
||||
// thin wall v3
|
||||
if (top.thin_wall) outputAdaptiveWalls(top.thin_wall, top.thin_sort);
|
||||
|
||||
// innermost shells
|
||||
let inner = next.innerShells() || [];
|
||||
let shells = next.shells || [];
|
||||
|
|
@ -1492,13 +1584,38 @@ function slicePrintPath(print, slice, startPoint, offset, output, opt = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
// adjust layer output speeds (by slowing them down)
|
||||
// if layer print time is less than minimum (required for cooling)
|
||||
let lp, td = 0, tt = 0;
|
||||
for (let p of preout) {
|
||||
if (!p.emit) continue;
|
||||
let mms = p.speed ?? 1;
|
||||
let dst = lp?.distTo2D(p.point) ?? 0;
|
||||
if (dst) {
|
||||
tt += (dst/mms);
|
||||
td += dst;
|
||||
}
|
||||
lp = p.point;
|
||||
}
|
||||
if (minLayerTime && tt < minLayerTime) {
|
||||
let fact = tt / minLayerTime;
|
||||
for (let p of preout) {
|
||||
if (p.emit) p.speed *= fact;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
print.addPrintPoints(preout, output);
|
||||
|
||||
return startPoint.add(offset);
|
||||
// create next layer start point and annotate
|
||||
// with print time for previous layer (hack to
|
||||
// avoid changing return signature rn)
|
||||
let newStart = startPoint.add(offset);
|
||||
newStart.layer_time = Math.max(tt, minLayerTime);
|
||||
return newStart;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) {
|
|||
}
|
||||
}
|
||||
if (process.xray) {
|
||||
slice.index = process.xrayi.shift();
|
||||
slice.index = process.xray.shift();
|
||||
slice.lines = lines;
|
||||
slice.groups = groups;
|
||||
slice.xray = process.xray;
|
||||
|
|
@ -498,6 +498,7 @@ export function fdm_slice(settings, widget, onupdate, ondone) {
|
|||
let spaceMult = slice.index === 0 ? process.firstLayerLineMult || 1 : 1;
|
||||
let isBottom = slice.index < bottomLayers;
|
||||
let isTop = topLayers && slice.index > slices.length - topLayers - 1;
|
||||
let isTopBase = isTop && slice.index === slices.length - topLayers;
|
||||
let isDense = range.sliceFillSparse > 0.995;
|
||||
let isSolid = (isBottom || ((isTop || isDense) && !vaseMode)) && !isSynth;
|
||||
let solidWidth = isSolid ? range.sliceFillWidth || 1 : 0;
|
||||
|
|
@ -505,6 +506,13 @@ export function fdm_slice(settings, widget, onupdate, ondone) {
|
|||
let fillSpace = fillSpacing * spaceMult * solidWidth;
|
||||
doSolidLayerFill(slice, fillSpace, sliceFillAngle);
|
||||
}
|
||||
if (slice.index === slices.length - 1) {
|
||||
slice.isFlatsLayer = true;
|
||||
}
|
||||
if (isTopBase) {
|
||||
// mark the first top solid supporting layer as a bridge
|
||||
slice.isBridgeLayer = true;
|
||||
}
|
||||
sliceFillAngle = (sliceFillAngle + 90.0) % 360;
|
||||
}, "solid layers");
|
||||
|
||||
|
|
@ -997,31 +1005,45 @@ function doRender(slice, isSynth, params, opt = {}) {
|
|||
.setLayer('part', Color.part)
|
||||
.addPolys([top.poly]);
|
||||
|
||||
output
|
||||
if (top.shells?.length) output
|
||||
.setLayer(isSynth ? "support" : "shells", isSynth ? Color.support : Color.shell)
|
||||
.addPolys(top.shells || [], vopt({ offset, height, clean: true }));
|
||||
|
||||
output
|
||||
if (isThin && top.thin_wall?.length) output
|
||||
.setLayer("walls", Color.thin)
|
||||
.addPolys(top.thin_wall.map(a => a.map(p => newPolygon().centerCircle(p, p.r, 12).setZ(slice.z))).flat(), vopt({ offset, height }));
|
||||
|
||||
if (!isThin && top.thin_wall?.length) output
|
||||
.setLayer("walls", Color.thin)
|
||||
.addPolys(top.thin_wall.map(a =>
|
||||
a.length === 1 ?
|
||||
newPolygon().centerCircle(a[0], a[0].r/2, 12) :
|
||||
newPolygon(a.map(p => newPoint(p.x, p.y)))
|
||||
.setZ(slice.z)
|
||||
.closeIf(height)
|
||||
), vopt({ offset, height }));
|
||||
|
||||
if (top.fill_lines?.length) output
|
||||
.setLayer("solid fill", isSynth ? Color.support : Color.fill)
|
||||
.addLines(top.fill_lines || [], vopt({ offset: offset * solidWidth, height, z:slice.z }));
|
||||
|
||||
if (!(slice.belt && slice.belt.anchor)) output
|
||||
if (!(slice.belt?.anchor)) output
|
||||
.setLayer("sparse fill", Color.infill)
|
||||
.addPolys(top.fill_sparse || [], vopt({ offset, height, outline: true, trace:true }))
|
||||
|
||||
if (slice.belt && slice.belt.anchor) output
|
||||
if (slice.belt?.anchor) output
|
||||
.setLayer("anchor", Color.anchor)
|
||||
.addPolys(top.fill_sparse || [], vopt({ offset, height, outline: true, trace:true }))
|
||||
|
||||
if (top.thin_fill) output
|
||||
if (top.thin_fill?.length) output
|
||||
.setLayer("thin fill", Color.thin)
|
||||
.addLines(top.thin_fill, vopt({ offset, height }));
|
||||
|
||||
if (top.gaps && devel) output
|
||||
if (top.gaps?.length && devel) output
|
||||
.setLayer("gaps", Color.gaps)
|
||||
.addPolys(top.gaps, vopt({ offset, height, thin: true }));
|
||||
|
||||
if (isThin && devel && top.fill_off && top.fill_off.length) {
|
||||
if (isThin && devel && top.fill_off?.length) {
|
||||
slice.output()
|
||||
.setLayer('fill inset', Color.inset)
|
||||
.addPolys(top.fill_off);
|
||||
|
|
@ -1359,8 +1381,9 @@ function addSolidFills(slice, polys) {
|
|||
export function projectFlats(slice, count, expand) {
|
||||
if (!slice.down || !slice.flats) return;
|
||||
// these flats are marked for finishing print speed
|
||||
if (slice.flats.length) slice.finishSolids = true;
|
||||
if (slice && slice.flats && slice.flats.length) {
|
||||
if (slice.flats?.length) {
|
||||
slice.finishSolids = true;
|
||||
slice.isFlatsLayer = true;
|
||||
const flats = expand ? POLY.expand(slice.flats, expand) : slice.flats;
|
||||
projectSolid(slice, flats, count, false, true);
|
||||
}
|
||||
|
|
@ -1372,7 +1395,10 @@ export function projectFlats(slice, count, expand) {
|
|||
export function projectBridges(slice, count) {
|
||||
if (!slice.up || !slice.bridges) return;
|
||||
// these flats are marked for finishing print speed
|
||||
if (slice.bridges.length) slice.finishSolids = true;
|
||||
if (slice.bridges?.length) {
|
||||
slice.finishSolids = true;
|
||||
slice.isBridgeLayer = true;
|
||||
}
|
||||
projectSolid(slice, slice.bridges, count, true, true);
|
||||
};
|
||||
|
||||
|
|
@ -1732,6 +1758,9 @@ function fillSupportPolys(args) {
|
|||
* @returns {*}
|
||||
*/
|
||||
function projectSolid(slice, polys, count, up, first) {
|
||||
if (slice && !up && count === 1) {
|
||||
slice.isBridgeLayer = true;
|
||||
}
|
||||
if (!slice || count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -241,13 +241,12 @@ class MeshTool {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the adjacent faces for a given face index.
|
||||
*
|
||||
* @param {number} face - The index of the face for which adjacent faces are to be found.
|
||||
* @returns {number[]} An array of face indices that are adjacent to the given face.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieves the adjacent faces for a given face index.
|
||||
*
|
||||
* @param {number} face - The index of the face for which adjacent faces are to be found.
|
||||
* @returns {number[]} An array of face indices that are adjacent to the given face.
|
||||
*/
|
||||
getAdjacentFaces(face) {
|
||||
const { faces, sides } = this.getIndex();
|
||||
const foff = face * 6;
|
||||
|
|
@ -319,31 +318,31 @@ class MeshTool {
|
|||
* 3. Filtering faces with normals perpendicular to Z axis
|
||||
* 4. Recursively adding adjacent faces with the same Z values and normal perpendicular to Z axis
|
||||
*/
|
||||
findCylinderSurface(face){
|
||||
face = Number(face)
|
||||
|
||||
const radianTolerance = 8 * (Math.PI/180);
|
||||
findCylinderSurface(face) {
|
||||
face = Number(face);
|
||||
const radianTolerance = 8 * (Math.PI / 180);
|
||||
const vertexTolerance = 1e-4;
|
||||
//helper function
|
||||
const dissimilar = (a,b,c) => Math.abs(a-b) > vertexTolerance &&
|
||||
Math.abs(b-c) > vertexTolerance &&
|
||||
Math.abs(a-c) > vertexTolerance;
|
||||
|
||||
// helper function
|
||||
const dissimilar = (a, b, c) => Math.abs(a - b) > vertexTolerance &&
|
||||
Math.abs(b - c) > vertexTolerance &&
|
||||
Math.abs(a - c) > vertexTolerance;
|
||||
|
||||
let found = {},
|
||||
out = [face],
|
||||
{vertices} = this,
|
||||
{faces} = this.getIndex(),
|
||||
faceOffset = face*9,
|
||||
zs = [2,5,8].map(i => vertices[faceOffset+i].round(5)), //get zs from face vertices
|
||||
{ vertices } = this,
|
||||
{ faces } = this.getIndex(),
|
||||
faceOffset = face * 9,
|
||||
zs = [2, 5, 8].map(i => vertices[faceOffset + i].round(5)), //get zs from face vertices
|
||||
lowZ = Math.min(zs),
|
||||
highZ = Math.max(zs),
|
||||
checked = {},
|
||||
check = [face];
|
||||
|
||||
//check for errors, and throw if found
|
||||
if(dissimilar(...zs)){
|
||||
|
||||
// check for errors, and throw if found
|
||||
if (dissimilar(...zs)) {
|
||||
throw `face must have only 2 Z values. found: ${zs.join(", ")}`
|
||||
}else if(Math.abs( faces[face * 6 + 2] > 1e-6 )){
|
||||
} else if (Math.abs(faces[face * 6 + 2] > 1e-6)) {
|
||||
throw "face's normal must be perpendicular to Z axis"
|
||||
}
|
||||
|
||||
|
|
@ -352,11 +351,12 @@ class MeshTool {
|
|||
const face = check.shift();
|
||||
const froot = face * 6;
|
||||
const vroot = face * 9
|
||||
|
||||
|
||||
// filter faces to those perpendicular to +z axis
|
||||
if ( Math.abs( faces[froot + 2] > 1e-6 )) continue;
|
||||
//filter faces with verts don't share the z values of the first face
|
||||
if(
|
||||
if (Math.abs(faces[froot + 2] > 1e-6)) continue;
|
||||
|
||||
// filter faces with verts don't share the z values of the first face
|
||||
if (
|
||||
!(vertices[vroot + 2] !== lowZ || vertices[vroot + 2] !== highZ) &&
|
||||
!(vertices[vroot + 5] !== lowZ || vertices[vroot + 5] !== highZ) &&
|
||||
!(vertices[vroot + 8] !== lowZ || vertices[vroot + 8] !== highZ)
|
||||
|
|
@ -369,7 +369,7 @@ class MeshTool {
|
|||
}
|
||||
const aroot = f * 6;
|
||||
let sum = 0;
|
||||
for (let i=0; i<3; i++) {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
sum += Math.pow(faces[froot + i] - faces[aroot + i], 2);
|
||||
}
|
||||
const fn = Math.sqrt(sum);
|
||||
|
|
@ -381,6 +381,7 @@ class MeshTool {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
const terms = {
|
||||
COPYRIGHT: "Copyright (C) Stewart Allen <sa@grid.space> - All Rights Reserved",
|
||||
LICENSE: "See the license.md file included with the source distribution",
|
||||
VERSION: "4.3.0"
|
||||
VERSION: "4.3.1"
|
||||
};
|
||||
|
||||
export const beta = 1;
|
||||
export const beta = 0;
|
||||
export const license = terms;
|
||||
export const version = terms.VERSION;
|
||||
|
|
|
|||
Loading…
Reference in a new issue