complete refactor of thin wall detection
This commit is contained in:
parent
273eed2bb9
commit
de46926d4c
8 changed files with 197 additions and 165 deletions
|
|
@ -307,6 +307,8 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
BASE.config = {
|
BASE.config = {
|
||||||
|
// size of gcode debug arrow head
|
||||||
|
debug_arrow : 0.25,
|
||||||
// default # of decimal places in generated gcode
|
// default # of decimal places in generated gcode
|
||||||
gcode_decimals : 4,
|
gcode_decimals : 4,
|
||||||
// heal disjoint polygons in slicing (experimental)
|
// heal disjoint polygons in slicing (experimental)
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@
|
||||||
fillArea : fillArea,
|
fillArea : fillArea,
|
||||||
subtract : subtract,
|
subtract : subtract,
|
||||||
flatten : flatten,
|
flatten : flatten,
|
||||||
|
offset : offset,
|
||||||
trimTo : trimTo,
|
trimTo : trimTo,
|
||||||
expand2 : expand2,
|
|
||||||
expand : expand,
|
expand : expand,
|
||||||
expand_lines: expand_lines,
|
expand_lines: expand_lines,
|
||||||
union : union,
|
union : union,
|
||||||
|
|
@ -46,6 +46,10 @@
|
||||||
* Polygon array utility functions
|
* Polygon array utility functions
|
||||||
******************************************************************* */
|
******************************************************************* */
|
||||||
|
|
||||||
|
function numOrDefault(num, def) {
|
||||||
|
return num !== undefined ? num : def;
|
||||||
|
}
|
||||||
|
|
||||||
function toClipper(polys,debug) {
|
function toClipper(polys,debug) {
|
||||||
let out = [];
|
let out = [];
|
||||||
polys.forEach(function(poly) { poly.toClipper(out,debug) });
|
polys.forEach(function(poly) { poly.toClipper(out,debug) });
|
||||||
|
|
@ -62,9 +66,9 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
function fromClipperTree(tnode, z, tops, parent, minarea) {
|
function fromClipperTree(tnode, z, tops, parent, minarea) {
|
||||||
let polys = tops || [],
|
let poly,
|
||||||
poly,
|
polys = tops || [],
|
||||||
min = minarea || 0.1;
|
min = numOrDefault(minarea, 0.1);
|
||||||
|
|
||||||
tnode.m_Childs.forEach(function(child) {
|
tnode.m_Childs.forEach(function(child) {
|
||||||
poly = fromClipperNode(child, z);
|
poly = fromClipperNode(child, z);
|
||||||
|
|
@ -473,75 +477,79 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* by "over" expanding then contracting, this causes shells that are too
|
* offset an array of polygons by distance with options to recurse
|
||||||
* close together to merge and cancel out. it's a more expensive operation
|
* and return resulting gaps from offsets for thin wall detection in
|
||||||
* but prevents shells that are too close together to extrude properly.
|
* in FDM mode and uncleared areas in CAM mode.
|
||||||
*
|
|
||||||
* @param {Polygon[]} polys
|
|
||||||
* @param {number} dist1 first offset distance
|
|
||||||
* @param {number} dist2 2nd thru last offset distance
|
|
||||||
* @param {Polygon[]} out optional collector
|
|
||||||
* @param {number} count offset passes (0 == until no space left)
|
|
||||||
* @param {Function} collector receives output of each pass
|
|
||||||
* @param {Function} thins receives output of each pass
|
|
||||||
* @param {number} [z] defaults to 0
|
|
||||||
* @returns {Polygon[]} last offset
|
|
||||||
*/
|
*/
|
||||||
function expand2(polys, dist1, dist2, out, count, collector, thins, z) {
|
function offset(polys, dist, opt) {
|
||||||
// prepare alignments for clipper lib
|
// cause inner / outer polys to be reversed from each other
|
||||||
alignWindings(polys);
|
alignWindings(polys);
|
||||||
polys.forEach(function(poly) {
|
polys.forEach(function(poly) {
|
||||||
if (poly.inner) setWinding(poly.inner, !poly.isClockwise());
|
if (poly.inner) {
|
||||||
|
setWinding(poly.inner, !poly.isClockwise());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let fact = CONF.clipper,
|
let orig = polys,
|
||||||
clib = self.ClipperLib,
|
opts = opt || {},
|
||||||
clip = clib.Clipper,
|
depth = numOrDefault(opt.depth, 0),
|
||||||
cpft = clib.PolyFillType,
|
clean = opts.clean !== false,
|
||||||
cjnt = clib.JoinType,
|
simple = opts.simple !== false,
|
||||||
cety = clib.EndType,
|
fill = opts.fill || ClipperLib.PolyFillType.pftNonZero,
|
||||||
coff = new clib.ClipperOffset(),
|
join = opts.join || ClipperLib.JoinType.jtMiter,
|
||||||
ctre = new clib.PolyTree(),
|
type = opts.type || ClipperLib.EndType.etClosedPolygon,
|
||||||
orig = polys,
|
coff = new ClipperLib.ClipperOffset(),
|
||||||
over = dist1 * 0.45;
|
ctre = new ClipperLib.PolyTree(),
|
||||||
|
// if dist is array with values, shift out next offset
|
||||||
|
offs = Array.isArray(dist) ? (dist.length > 1 ? dist.shift() : dist[0]) : dist,
|
||||||
|
mina = numOrDefault(opts.minArea, 0.1),
|
||||||
|
zed = opt.z || 0;
|
||||||
|
|
||||||
// inset
|
// setup offset
|
||||||
polys.forEach(function(poly) {
|
polys.forEach(function(poly) {
|
||||||
let clean = clip.CleanPolygons(poly.toClipper(), CONF.clipperClean);
|
// convert to clipper format
|
||||||
let simple = clip.SimplifyPolygons(clean, cpft.pftNonZero);
|
poly = poly.toClipper();
|
||||||
coff.AddPaths(simple, cjnt.jtMiter, cety.etClosedPolygon);
|
if (clean) poly = ClipperLib.Clipper.CleanPolygons(poly, CONF.clipperClean);
|
||||||
|
if (simple) poly = ClipperLib.Clipper.SimplifyPolygons(poly, fill);
|
||||||
|
coff.AddPaths(poly, join, type);
|
||||||
});
|
});
|
||||||
|
// perform offset
|
||||||
|
coff.Execute(ctre, offs * CONF.clipper);
|
||||||
|
// convert back from clipper output format
|
||||||
|
polys = fromClipperTree(ctre, zed, null, null, mina);
|
||||||
|
|
||||||
coff.Execute(ctre, (dist1 + over) * fact);
|
// if specified, perform offset gap analysis
|
||||||
polys = fromClipperTree(ctre, z);
|
if (opt.gaps && polys.length) {
|
||||||
|
let negoff = offset(polys, -offs, {
|
||||||
// outset
|
fill: opt.fill, join: opt.join, type: opt.type, z: opt.z, minArea: mina
|
||||||
coff = new clib.ClipperOffset();
|
|
||||||
ctre = new clib.PolyTree();
|
|
||||||
polys.forEach(function(poly) {
|
|
||||||
let clean = clip.CleanPolygons(poly.toClipper(), CONF.clipperClean);
|
|
||||||
let simple = clip.SimplifyPolygons(clean, cpft.pftNonZero);
|
|
||||||
coff.AddPaths(simple, cjnt.jtMiter, cety.etClosedPolygon);
|
|
||||||
});
|
});
|
||||||
coff.Execute(ctre, -over * fact);
|
let suba = [];
|
||||||
polys = fromClipperTree(ctre, z);
|
let diff = subtract(orig, negoff, suba, null, zed);
|
||||||
|
opt.gaps.push(suba);
|
||||||
|
}
|
||||||
|
|
||||||
// detect possible thin walls
|
if (opt.gaps && !polys.length) {
|
||||||
if (thins) {
|
opt.gaps.push(orig);
|
||||||
let circ1 = sumCirc(orig),
|
}
|
||||||
circ2 = sumCirc(polys),
|
|
||||||
diff = Math.abs(1 - (circ1 / circ2));
|
// if specified, perform up to *count* successive offsets
|
||||||
// if circularity of original and offset differ, possible wall gaps
|
if (polys.length) {
|
||||||
if (diff > 0.2) {
|
// ensure opts has offset accumulator array
|
||||||
thins(orig, out.length ? polys : null, diff, -over);
|
opt.outs = opt.outs || [];
|
||||||
|
// store polys in accumulator
|
||||||
|
opt.outs.push(polys);
|
||||||
|
if (opt.count > 1) {
|
||||||
|
// decrement count, increment depth
|
||||||
|
opt.count--;
|
||||||
|
opt.depth = depth + 1;
|
||||||
|
// call next offset
|
||||||
|
offset(polys, dist, opt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process
|
// if the polys fail to offset at the top level, consider them gaps
|
||||||
if (out) out.appendAll(polys);
|
if (opt.gaps && polys.length === 0 && depth === 0) {
|
||||||
if (collector) collector(polys, count);
|
opt.gaps.push(orig);
|
||||||
if ((count === 0 || count > 1) && polys.length > 0) {
|
|
||||||
expand2(polys, dist2 || dist1, dist2, out, count > 0 ? count-1 : 0, collector, thins, z);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return polys;
|
return polys;
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@
|
||||||
inner: encode(this.inner, state),
|
inner: encode(this.inner, state),
|
||||||
// thinner: encode(this.thinner, state),
|
// thinner: encode(this.thinner, state),
|
||||||
solids: encode(this.solids, state),
|
solids: encode(this.solids, state),
|
||||||
|
thin_fill: encodePointArray(this.thin_fill, state),
|
||||||
fill_lines: encodePointArray(this.fill_lines, state),
|
fill_lines: encodePointArray(this.fill_lines, state),
|
||||||
fill_sparse: encode(this.fill_sparse, state),
|
fill_sparse: encode(this.fill_sparse, state),
|
||||||
polish: encode(this.polish, state)
|
polish: encode(this.polish, state)
|
||||||
|
|
@ -135,6 +136,7 @@
|
||||||
top.inner = decode(v.inner, state);
|
top.inner = decode(v.inner, state);
|
||||||
// top.thinner = decode(v.thinner, state);
|
// top.thinner = decode(v.thinner, state);
|
||||||
top.solids = decode(v.solids, state);
|
top.solids = decode(v.solids, state);
|
||||||
|
top.thin_fill = decodePointArray(v.thin_fill, state);
|
||||||
top.fill_lines = decodePointArray(v.fill_lines, state);
|
top.fill_lines = decodePointArray(v.fill_lines, state);
|
||||||
top.fill_sparse = decode(v.fill_sparse, state);
|
top.fill_sparse = decode(v.fill_sparse, state);
|
||||||
top.polish = decode(v.polish, state);
|
top.polish = decode(v.polish, state);
|
||||||
|
|
|
||||||
|
|
@ -1513,7 +1513,7 @@
|
||||||
antiBacklash: UC.newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, bound:UC.bound(0,3), convert:UC.toInt, modes:FDM, expert:true}),
|
antiBacklash: UC.newInput(LANG.ad_abkl_s, {title:LANG.ad_abkl_l, bound:UC.bound(0,3), convert:UC.toInt, modes:FDM, expert:true}),
|
||||||
sliceRotation: LOCAL ? UC.newInput(LANG.ad_slrt_s, {title:LANG.ad_slrt_l, bound:UC.bound(-45,45), convert:UC.toFloat, modes:FDM, expert:true}) : null,
|
sliceRotation: LOCAL ? UC.newInput(LANG.ad_slrt_s, {title:LANG.ad_slrt_l, bound:UC.bound(-45,45), convert:UC.toFloat, modes:FDM, expert:true}) : null,
|
||||||
gcodePauseLayers: UC.newInput(LANG.ag_paws_s, {title:LANG.ag_paws_l, modes:FDM, expert:true, comma:true}),
|
gcodePauseLayers: UC.newInput(LANG.ag_paws_s, {title:LANG.ag_paws_l, modes:FDM, expert:true, comma:true}),
|
||||||
detectThinWalls: LOCAL ? UC.newBoolean(LANG.ad_thin_s, onBooleanClick, {title: LANG.ad_thin_l, modes:FDM, expert:true}) : null,
|
detectThinWalls: UC.newBoolean(LANG.ad_thin_s, onBooleanClick, {title: LANG.ad_thin_l, modes:FDM, expert:true}),
|
||||||
outputLayerRetract: UC.newBoolean(LANG.ad_lret_s, onBooleanClick, {title:LANG.ad_lret_l, modes:FDM, expert:true}),
|
outputLayerRetract: UC.newBoolean(LANG.ad_lret_s, onBooleanClick, {title:LANG.ad_lret_l, modes:FDM, expert:true}),
|
||||||
|
|
||||||
// SLA
|
// SLA
|
||||||
|
|
|
||||||
|
|
@ -670,29 +670,63 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function outputFills(lines, options) {
|
function outputFills(lines, options) {
|
||||||
|
if (!lines || lines.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let p, p1, p2, dist, len, found, group, mindist, t1, t2,
|
let p, p1, p2, dist, len, found, group, mindist, t1, t2,
|
||||||
marked = 0,
|
marked = 0,
|
||||||
start = 0,
|
start = 0,
|
||||||
skip = false,
|
skip = false,
|
||||||
lastIndex = -1,
|
lastIndex = -1,
|
||||||
opt = options || {},
|
opt = options || {},
|
||||||
|
near = opt.near || false,
|
||||||
fast = opt.fast || false,
|
fast = opt.fast || false,
|
||||||
fill = opt.fill >= 0 ? opt.fill : fillMult;
|
fill = opt.fill >= 0 ? opt.fill : fillMult,
|
||||||
|
thinDist = near ? thinWall * 3 : thinWall;
|
||||||
|
|
||||||
while (lines && marked < lines.length) {
|
while (lines && marked < lines.length) {
|
||||||
found = false;
|
|
||||||
group = null;
|
group = null;
|
||||||
|
found = false;
|
||||||
mindist = Infinity;
|
mindist = Infinity;
|
||||||
|
|
||||||
|
// use next nearest line strategy
|
||||||
|
if (near)
|
||||||
|
for (i=0; i<lines.length; i += 2) {
|
||||||
|
t1 = lines[i];
|
||||||
|
if (t1.del) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
t2 = lines[i+1];
|
||||||
|
let d1 = t1.distToSq2D(startPoint);
|
||||||
|
let d2 = t2.distToSq2D(startPoint);
|
||||||
|
if (d1 < mindist || d2 < mindist) {
|
||||||
|
if (d2 < d1) {
|
||||||
|
p2 = t1;
|
||||||
|
p1 = t2;
|
||||||
|
} else {
|
||||||
|
p1 = t1;
|
||||||
|
p2 = t2;
|
||||||
|
}
|
||||||
|
mindist = Math.min(d1, d2);
|
||||||
|
lastIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// use next index line strategy
|
||||||
// order all points by distance to last point
|
// order all points by distance to last point
|
||||||
|
if (!near)
|
||||||
for (i=start; i<lines.length; i += 2) {
|
for (i=start; i<lines.length; i += 2) {
|
||||||
p = lines[i];
|
p = lines[i];
|
||||||
if (p.del) continue;
|
if (p.del) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (group === null && p.index > lastIndex) {
|
if (group === null && p.index > lastIndex) {
|
||||||
group = p.index;
|
group = p.index;
|
||||||
}
|
}
|
||||||
if (group !== null) {
|
if (group !== null) {
|
||||||
if (p.index !== group) break;
|
if (p.index !== group) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (p.index % 2 === 0) {
|
if (p.index % 2 === 0) {
|
||||||
t1 = lines[i];
|
t1 = lines[i];
|
||||||
t2 = lines[i+1];
|
t2 = lines[i+1];
|
||||||
|
|
@ -712,7 +746,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// go back to start and try again
|
// go back to start and try again
|
||||||
if (!found) {
|
if (!near && !found) {
|
||||||
if (start === 0 && lastIndex === -1) {
|
if (start === 0 && lastIndex === -1) {
|
||||||
console.log('infinite loop', lines, {
|
console.log('infinite loop', lines, {
|
||||||
marked, options, i, group, start, lastIndex,
|
marked, options, i, group, start, lastIndex,
|
||||||
|
|
@ -725,11 +759,11 @@
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
dist = startPoint.distTo2D(p1);
|
dist = startPoint.distToSq2D(p1);
|
||||||
len = p1.distTo2D(p2);
|
len = p1.distToSq2D(p2);
|
||||||
|
|
||||||
// go back to start when dist > retractDist
|
// go back to start when dist > retractDist
|
||||||
if (!fast && !skip && dist > retractDist) {
|
if (!near && !fast && !skip && dist > retractDist) {
|
||||||
skip = true;
|
skip = true;
|
||||||
start = 0;
|
start = 0;
|
||||||
lastIndex = -1;
|
lastIndex = -1;
|
||||||
|
|
@ -747,7 +781,7 @@
|
||||||
// and segment length is less than thinWall then
|
// and segment length is less than thinWall then
|
||||||
// just extrude to midpoint of next segment. this is
|
// just extrude to midpoint of next segment. this is
|
||||||
// to avoid shaking the printer to death.
|
// to avoid shaking the printer to death.
|
||||||
if (dist <= thinWall && len <= thinWall) {
|
if (dist <= thinDist && len <= thinDist) {
|
||||||
p2 = p1.midPointTo(p2);
|
p2 = p1.midPointTo(p2);
|
||||||
addOutput(preout, p2, fill * (dist / thinWall), fillSpeed, extruder);
|
addOutput(preout, p2, fill * (dist / thinWall), fillSpeed, extruder);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -762,7 +796,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// bridge ends of fill when they're close together
|
// bridge ends of fill when they're close together
|
||||||
if (dist < thinWall) {
|
if (dist < thinDist) {
|
||||||
addOutput(preout, p1, fill, fillSpeed, extruder);
|
addOutput(preout, p1, fill, fillSpeed, extruder);
|
||||||
} else {
|
} else {
|
||||||
addOutput(preout, p1, 0, moveSpeed, extruder);
|
addOutput(preout, p1, 0, moveSpeed, extruder);
|
||||||
|
|
@ -854,10 +888,13 @@
|
||||||
outputTraces(poly);
|
outputTraces(poly);
|
||||||
});
|
});
|
||||||
|
|
||||||
// output inner polygons
|
// output outer polygons
|
||||||
if (dir === -1)
|
if (dir === -1)
|
||||||
outputTraces([].appendAll(next.innerTraces() || []));
|
outputTraces([].appendAll(next.innerTraces() || []));
|
||||||
|
|
||||||
|
// output thin fill
|
||||||
|
outputFills(next.thin_fill, {near: true});
|
||||||
|
|
||||||
// then output solid and sparse fill
|
// then output solid and sparse fill
|
||||||
outputFills(next.fill_lines);
|
outputFills(next.fill_lines);
|
||||||
outputSparse(next.fill_sparse, sparseMult);
|
outputSparse(next.fill_sparse, sparseMult);
|
||||||
|
|
|
||||||
|
|
@ -296,28 +296,53 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* given two arrays of points (lines), eliminate intersections of the second
|
* given an array of arrays of points (lines), eliminate intersections
|
||||||
* to the first, then return a unified array.
|
* between groups, then return a unified array of shortest non-intersects.
|
||||||
*
|
*
|
||||||
* @param {Point[]} r1
|
|
||||||
* @param {Point[]} r2
|
|
||||||
* @returns {Point[]}
|
* @returns {Point[]}
|
||||||
*/
|
*/
|
||||||
function cullIntersections(r1, r2) {
|
function cullIntersections() {
|
||||||
if (!(r1 && r2 && r1.length && r2.length)) return;
|
function toLines(pts) {
|
||||||
let valid = r2.slice();
|
let lns = [];
|
||||||
outer: for (let i=0; i<r1.length; i += 2) {
|
for (let i=0, il=pts.length; i<il; i += 2) {
|
||||||
for (let j=0; j<r2.length; j += 2) {
|
lns.push({a: pts[i], b: pts[i+1], l: pts[i].distTo2D(pts[i+1])});
|
||||||
if (UTIL.intersect(r1[i], r1[i+1], r2[j], r2[j+1], BASE.key.SEGINT)) continue outer;
|
|
||||||
}
|
}
|
||||||
valid.push(r1[i]);
|
return lns;
|
||||||
valid.push(r1[i+1]);
|
|
||||||
}
|
}
|
||||||
// add index to point for fill order storeSettingsToServer
|
let aOa = [...arguments].filter(t => t);
|
||||||
for (let k=0; k<valid.length; k++) {
|
if (aOa.length < 1) return;
|
||||||
valid[k].index = Infinity;
|
let aa = toLines(aOa.shift());
|
||||||
|
while (aOa.length) {
|
||||||
|
let bb = toLines(aOa.shift());
|
||||||
|
loop: for (let i=0, il=aa.length; i<il; i++) {
|
||||||
|
let al = aa[i];
|
||||||
|
if (al.del) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
return valid.length > 2 ? valid : [];
|
for (let j=0, jl=bb.length; j<jl; j++) {
|
||||||
|
let bl = bb[j];
|
||||||
|
if (bl.del) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (UTIL.intersect(al.a, al.b, bl.a, bl.b, BASE.key.SEGINT)) {
|
||||||
|
if (al.l < bl.l) {
|
||||||
|
bl.del = true;
|
||||||
|
} else {
|
||||||
|
al.del = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aa = aa.filter(l => !l.del).concat(bb.filter(l => !l.del));
|
||||||
|
}
|
||||||
|
let good = [];
|
||||||
|
for (let i=0, il=aa.length; i<il; i++) {
|
||||||
|
let al = aa[i];
|
||||||
|
good.push(al.a);
|
||||||
|
good.push(al.b);
|
||||||
|
}
|
||||||
|
return good.length > 2 ? good : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -353,10 +378,6 @@
|
||||||
top.inner = [];
|
top.inner = [];
|
||||||
let last = [], z = top.poly.getZ();
|
let last = [], z = top.poly.getZ();
|
||||||
|
|
||||||
if (opt.thin) {
|
|
||||||
top.thin_fill = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count) {
|
if (count) {
|
||||||
// permit offset of 0 for laser and drag knife
|
// permit offset of 0 for laser and drag knife
|
||||||
if (offset1 === 0 && count === 1) {
|
if (offset1 === 0 && count === 1) {
|
||||||
|
|
@ -364,51 +385,29 @@
|
||||||
top.traces = last;
|
top.traces = last;
|
||||||
} else {
|
} else {
|
||||||
if (opt.thin) {
|
if (opt.thin) {
|
||||||
// thin wall offset strategy
|
top.thin_fill = [];
|
||||||
let on1s2 = offset1 * 2,
|
let oso = {z, count, gaps: [], outs: [], minArea: 0.05};
|
||||||
on2s2 = offsetN * 2;
|
POLY.offset(top_poly, [-offset1, -offsetN], oso);
|
||||||
POLY.expand2(
|
|
||||||
top_poly, // reference polygon(s)
|
oso.outs.forEach((polys, i) => {
|
||||||
-offset1, // first inset distance
|
polys.forEach(p => {
|
||||||
-offsetN, // subsequent inset distnace
|
p.depth = i;
|
||||||
top.traces, // accumulator array
|
top.traces.push(p);
|
||||||
count, // numberof insets to perform
|
|
||||||
// on each new offset trace ...
|
|
||||||
function(polys, countNow) {
|
|
||||||
last = polys;
|
|
||||||
// mark each poly with depth (offset #) starting at 0
|
|
||||||
polys.forEach(function(p) {
|
|
||||||
p.depth = count - countNow;
|
|
||||||
if (p.inner) p.inner.forEach(function(pi) {
|
|
||||||
// use negative offset for inners
|
|
||||||
pi.depth = -(count - countNow);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
// thin wall probe
|
// slice.solids.trimmed = slice.solids.trimmed || [];
|
||||||
// p1 = original polys
|
oso.gaps.forEach((polys, i) => {
|
||||||
// p2 = collected polys
|
let off = (i == 0 ? offset1 : offsetN);
|
||||||
// diff = circularity difference
|
polys = POLY.offset(polys, -off * 0.8, {z, minArea: 0});
|
||||||
// dist = over-expansion distance
|
// polys.forEach(p => { slice.solids.trimmed.push(p); });
|
||||||
function(p1, p2, diff, dist) {
|
top.thin_fill.appendAll(cullIntersections(
|
||||||
if (p2) {
|
fillArea(polys, 45, off/2, [], 0.01, off*2),
|
||||||
// nth offset
|
fillArea(polys, 135, off/2, [], 0.01, off*2),
|
||||||
let pall = POLY.nest(POLY.flatten([].appendAll(p1).appendAll(p2)).clone()),
|
// fillArea(polys, 90, off, [], 0.05, off*4),
|
||||||
pnew1 = POLY.expand(pall, -dist, z, null, 1),
|
// fillArea(polys, 180, off, [], 0.05, off*4),
|
||||||
r1 = fillArea(pnew1, 45, offsetN, [], dist / 2, on2s2),
|
));
|
||||||
r2 = fillArea(pnew1, 135, offsetN, [], dist / 2, on2s2),
|
});
|
||||||
rall = top.thin_fill.appendAll(cullIntersections(r1, r2));
|
|
||||||
} else {
|
|
||||||
// first offset
|
|
||||||
let pall = POLY.nest(POLY.flatten([].appendAll(p1).appendAll(p2)).clone()),
|
|
||||||
pnew1 = POLY.expand(pall, -dist, z, null, 1),
|
|
||||||
r1 = fillArea(pnew1, 45, offsetN, [], 0, on1s2),
|
|
||||||
r2 = fillArea(pnew1, 135, offsetN, [], 0, on1s2),
|
|
||||||
rall = top.thin_fill.appendAll(cullIntersections(r1, r2));
|
|
||||||
}
|
|
||||||
// top.thinner.appendAll(pnew1).appendAll(pnew2);
|
|
||||||
},
|
|
||||||
z);
|
|
||||||
} else {
|
} else {
|
||||||
// standard wall offsetting strategy
|
// standard wall offsetting strategy
|
||||||
POLY.expand(
|
POLY.expand(
|
||||||
|
|
@ -796,7 +795,7 @@
|
||||||
|
|
||||||
// create empty filled line array for each top
|
// create empty filled line array for each top
|
||||||
tops.forEach(function(top) {
|
tops.forEach(function(top) {
|
||||||
top.fill_lines = top.thin_fill || [];
|
top.fill_lines = [];
|
||||||
let tofill = [],
|
let tofill = [],
|
||||||
angfill = [];
|
angfill = [];
|
||||||
trims.forEach(function(solid) {
|
trims.forEach(function(solid) {
|
||||||
|
|
@ -825,24 +824,6 @@
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* fill thin areas, if present
|
|
||||||
* @return {boolean} true if filled, false if not
|
|
||||||
*/
|
|
||||||
PRO.doThinFill = function(spacing, angle) {
|
|
||||||
this.tops.forEach(function(top) {
|
|
||||||
if (top.thin_fill && top.thin_fill.length > 0) {
|
|
||||||
if (top.fill_lines) {
|
|
||||||
top.fill_lines.appendAll(top.thin_fill)
|
|
||||||
} else {
|
|
||||||
top.fill_lines = top.thin_fill;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* calculate external overhangs requiring support
|
* calculate external overhangs requiring support
|
||||||
* this is done bottom-up
|
* this is done bottom-up
|
||||||
|
|
|
||||||
|
|
@ -189,14 +189,8 @@
|
||||||
}, "solids");
|
}, "solids");
|
||||||
forSlices(0.35, 0.5, function(slice) {
|
forSlices(0.35, 0.5, function(slice) {
|
||||||
slice.doSolidsFill(fillSpacing, sliceFillAngle, minSolid);
|
slice.doSolidsFill(fillSpacing, sliceFillAngle, minSolid);
|
||||||
slice.doThinFill(fillSpacing, sliceFillAngle);
|
|
||||||
sliceFillAngle += 90.0;
|
sliceFillAngle += 90.0;
|
||||||
}, "solids");
|
}, "solids");
|
||||||
} else if (spro.detectThinWalls) {
|
|
||||||
forSlices(0.35, 0.5, function(slice) {
|
|
||||||
slice.doThinFill(fillSpacing, sliceFillAngle);
|
|
||||||
sliceFillAngle += 90.0;
|
|
||||||
}, "thins");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculations only relevant when supports are enabled
|
// calculations only relevant when supports are enabled
|
||||||
|
|
@ -485,8 +479,10 @@
|
||||||
|
|
||||||
// solid fill
|
// solid fill
|
||||||
if (thin) {
|
if (thin) {
|
||||||
|
solids.lines(top.thin_fill, 0x77bbcc);
|
||||||
solids.lines(top.fill_lines, 0x77bbcc);
|
solids.lines(top.fill_lines, 0x77bbcc);
|
||||||
} else {
|
} else {
|
||||||
|
solids.noodle_lines(top.thin_fill, extoff, 0x88aadd, 0x77bbcc, s.z);
|
||||||
solids.noodle_lines(top.fill_lines, extoff, 0x88aadd, 0x77bbcc, s.z);
|
solids.noodle_lines(top.fill_lines, extoff, 0x88aadd, 0x77bbcc, s.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1347,9 +1343,9 @@
|
||||||
let ao2 = BASE.newSlopeFromAngle(rs.angle - 25);
|
let ao2 = BASE.newSlopeFromAngle(rs.angle - 25);
|
||||||
let sp = BASE.newPoint(point.x, point.y, point.z);
|
let sp = BASE.newPoint(point.x, point.y, point.z);
|
||||||
move.push(sp);
|
move.push(sp);
|
||||||
move.push(sp.projectOnSlope(ao1, 0.5));
|
move.push(sp.projectOnSlope(ao1, BASE.config.debug_arrow));
|
||||||
move.push(sp);
|
move.push(sp);
|
||||||
move.push(sp.projectOnSlope(ao2, 0.5));
|
move.push(sp.projectOnSlope(ao2, BASE.config.debug_arrow));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last = point;
|
last = point;
|
||||||
|
|
|
||||||
|
|
@ -368,6 +368,7 @@ th, tr, td, label {
|
||||||
}
|
}
|
||||||
.pop-lcol > div {
|
.pop-lcol > div {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
@ -425,6 +426,11 @@ th, tr, td, label {
|
||||||
.pop-lcol svg {
|
.pop-lcol svg {
|
||||||
font-size: 20px !important;
|
font-size: 20px !important;
|
||||||
}
|
}
|
||||||
|
#mod-x {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 30px;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ext-buttons button {
|
.ext-buttons button {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue