add minions and 1st implementation with poly array union

This commit is contained in:
Stewart Allen 2021-06-19 22:44:08 -04:00
commit 1884cd10d1
5 changed files with 245 additions and 123 deletions

25
app.js
View file

@ -325,6 +325,29 @@ const script = {
"kiri/codec",
"kiri/worker"
].map(p => `src/${p}.js`),
minion : [
"kiri",
"license",
"ext/clip2",
"ext/earcut",
"add/array",
"add/class",
"geo/base",
"geo/debug",
"geo/point",
"geo/points",
"geo/slope",
"geo/line",
"geo/bounds",
"geo/polygon",
"geo/polygons",
"geo/gyroid",
"kiri/slice",
"kiri/layers",
"kiri/widget",
"kiri/codec",
"kiri/minion"
].map(p => `src/${p}.js`),
engine : [
"kiri",
"license",
@ -342,7 +365,6 @@ const script = {
"geo/polygon",
"geo/polygons",
"moto/kv",
// "moto/ajax",
"moto/load-stl",
"kiri/conf",
"kiri/client",
@ -550,6 +572,7 @@ function prepareScripts() {
code.meta = concatCode(script.meta);
code.kiri = concatCode(script.kiri);
code.worker = concatCode(script.worker);
code.minion = concatCode(script.minion);
code.engine = concatCode(script.engine);
code.frame = concatCode(script.frame);
}

34
src/kiri/minion.js Normal file
View file

@ -0,0 +1,34 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
"use strict";
let BASE = self.base,
KIRI = self.kiri,
UTIL = BASE.util,
POLY = BASE.polygons,
CODEC = KIRI.codec;
// catch clipper alerts and convert to console messages
self.alert = function(o) {
console.log(o);
};
self.onmessage = function(msg) {
let data = msg.data;
switch (data.cmd) {
case "union":
if (!(data.polys && data.polys.length)) {
self.postMessage({union: CODEC.encode([])});
return;
}
let polys = CODEC.decode(data.polys);
let union = POLY.union(polys, data.minarea || 0, true);
self.postMessage({union: CODEC.encode(union)});
break;
default:
self.postMessage({error: "invalid command"});
break;
}
};
// console.log(`kiri | init mini | ${KIRI.version || "rogue"}`);

View file

@ -5,20 +5,81 @@
let BASE = self.base,
KIRI = self.kiri,
UTIL = BASE.util,
POLY = BASE.polygons,
time = UTIL.time,
qtpi = Math.cos(Math.PI/4),
concurrent = self.Worker ? navigator.hardwareConcurrency || 0 : 0,
current = self.worker = {
print: null,
snap: null
},
wgroup = {},
wcache = {};
wcache = {},
minions = [],
minionq = [];
// catch clipper alerts and convert to console messages
self.alert = function(o) {
console.log(o);
};
// start concurrent workers (minions)
if (concurrent) {
for (let i=0; i < concurrent; i++) {
minions.push(new Worker(`/code/minion.js?${self.kiri.version}`));
}
console.log(`kiri | init mini | ${KIRI.version || "rogue"} | ${concurrent}`);
}
// for concurrent operations
KIRI.minions = {
union: function(polys, minarea) {
return new Promise((resolve, reject) => {
if (concurrent === 0 || polys.length * 2 < concurrent) {
resolve(POLY.union(polys, 0, true));
}
let polyper = Math.ceil(polys.length / concurrent);
let running = 0;
let union = [];
let receiver = function(msg) {
let polys = KIRI.codec.decode(msg.data.union);
union.appendAll(polys);
if (--running === 0) {
resolve(POLY.union(union, 0, true));
}
};
for (let i=0; i<polys.length; i += polyper) {
minions[running].onmessage = receiver;
minions[running].postMessage({
cmd: "union",
minarea,
polys: KIRI.codec.encode(polys.slice(i, i + polyper))
});
running++;
}
});
},
queue: function(work) {
return new Promise((resolve, reject) => {
minionq.push({work, resolve, reject});
kick();
});
},
kick: function() {
if (minions.length && minionq.length) {
let qrec = minionq.shift();
let minion = minions.shift();
minion.onmessage = (msg) => {
qrec.resolve(msg.data);
minions.push(minion);
};
minion.postMessage(work);
}
}
};
console.log(`kiri | init work | ${KIRI.version || "rogue"}`);
BASE.debug.disable();

View file

@ -567,7 +567,7 @@
let dy = cc.y - arcQ.ySum / arcQ.center.length;
dc = Math.sqrt(dx * dx + dy * dy);
}
// if new point is off the arc
// if (deem || depm || desp || dc > arcDist || cc.r < arcMin || cc.r > arcMax || dist > cc.r) {
if (deem || depm || desp || dc * arcQ.center.length / arcQ.rSum > arcDist || dist > cc.r || cc.r > arcMax || radFault || !arcValid()) {

View file

@ -162,6 +162,10 @@
}
function onSliceDone(slices) {
onSliceDoneAsync(slices).then(ondone);
}
async function onSliceDoneAsync(slices) {
// remove all empty slices above part but leave below
// for multi-part (multi-extruder) setups where the void is ok
// also reverse because slicing occurs bottom-up
@ -294,6 +298,7 @@
}
sliceFillAngle += 90.0;
}, "offsets");
// add lead in when specified in belt mode
if (!isSynth && isBelt) {
// find adjusted zero point from slices
@ -461,16 +466,18 @@
// auto support generation
if (!isBelt && !isSynth && supportDensity && spro.sliceSupportEnable) {
// create shadow for clipping supports
let mark = Date.now();
let shadow = null;
let alltops = slices.map(slice => slice.topPolys()).flat();
let alllen = alltops.map(p => p.deepLength).reduce((a,v)=>a+v);
// console.log({alllen});
if (alllen > 100000) {
alltops = alltops.map(p => p.clean(true, undefined, 10000));
// console.log({alllen});
alltops = alltops.map(p => p.clean(true, undefined, 5000));
// console.log({reduced: alltops.map(p => p.deepLength).reduce((a,v)=>a+v)});
}
shadow = POLY.union(alltops,0.1,true);
// let mark = Date.now();
// shadow = POLY.union(alltops, 0.1, true);
shadow = await KIRI.minions.union(alltops, 0.1);
// console.log({unioned_in: Date.now() - mark});
if (spro.sliceSupportExtra) {
shadow = POLY.offset(shadow, spro.sliceSupportExtra);
}
@ -502,9 +509,6 @@
widget.belt.miny = -bounds.miny;
widget.belt.midy = (bounds.miny + bounds.maxy) / 2;
}
// report slicing complete
ondone();
}
}
@ -602,138 +606,138 @@
*/
function doShells(slice, count, offsetN, fillOffset, opt = {}) {
let offset1 = offsetN / 2;
let shellout = 0;
slice.tops.forEach(function(top) {
let top_poly = [ top.poly ];
if (slice.index === 0) {
// console.log({slice_top_0: top_poly, count});
// segment polygon
}
if (slice.index === 0) {
// console.log({slice_top_0: top_poly, count});
// segment polygon
}
for (let top of slice.tops) {
doTopShells(slice.z, top, count, offset1, offsetN, fillOffset, opt);
}
}
if (opt.vase) {
// remove top poly inners in vase mode
top.poly = top.poly.clone(false);
}
function doTopShells(z, top, count, offset1, offsetN, fillOffset, opt) {
let top_poly = [ top.poly ];
top.shells = [];
top.fill_off = [];
top.fill_lines = [];
if (opt.vase) {
// remove top poly inners in vase mode
top.poly = top.poly.clone(false);
}
let last = [],
gaps = [],
z = top.poly.getZ();
top.shells = [];
top.fill_off = [];
top.fill_lines = [];
if (count) {
// permit offset of 0 for laser and drag knife
if (offset1 === 0 && count === 1) {
last = top_poly.clone(true);
top.shells = last;
} else {
// heal top open polygons if the ends are close (benchy tilt test)
top_poly.forEach(p => { if (p.open) {
let dist = p.first().distTo2D(p.last());
if (dist < 1) p.open = false;
} });
if (opt.danger && opt.thin) {
top.thin_fill = [];
top.fill_sparse = [];
let layers = POLY.inset(top_poly, offsetN, count, z);
last = layers.last().mid;
top.shells = layers.map(r => r.mid).flat();
top.gaps = layers.map(r => r.gap).flat();
let off = offsetN;
let min = off * 0.75;
let max = off * 4;
for (let poly of layers.map(r => r.gap).flat()) {
let centers = poly.centers(off/2, z, min, max, {lines:false});
top.fill_sparse.appendAll(centers);
// top.fill_lines.appendAll(centers);
}
} else if (opt.thin) {
top.thin_fill = [];
let oso = {z, count, gaps: [], outs: [], minArea: 0.05};
POLY.offset(top_poly, [-offset1, -offsetN], oso);
let last = [],
gaps = [];
oso.outs.forEach((polys, i) => {
polys.forEach(p => {
p.depth = i;
if (p.fill_off) {
p.fill_off.forEach(pi => pi.depth = i);
if (count) {
// permit offset of 0 for laser and drag knife
if (offset1 === 0 && count === 1) {
last = top_poly.clone(true);
top.shells = last;
} else {
// heal top open polygons if the ends are close (benchy tilt test)
top_poly.forEach(p => { if (p.open) {
let dist = p.first().distTo2D(p.last());
if (dist < 1) p.open = false;
} });
if (opt.danger && opt.thin) {
top.thin_fill = [];
top.fill_sparse = [];
let layers = POLY.inset(top_poly, offsetN, count, z);
last = layers.last().mid;
top.shells = layers.map(r => r.mid).flat();
top.gaps = layers.map(r => r.gap).flat();
let off = offsetN;
let min = off * 0.75;
let max = off * 4;
for (let poly of layers.map(r => r.gap).flat()) {
let centers = poly.centers(off/2, z, min, max, {lines:false});
top.fill_sparse.appendAll(centers);
// top.fill_lines.appendAll(centers);
}
} else if (opt.thin) {
top.thin_fill = [];
let oso = {z, count, gaps: [], outs: [], minArea: 0.05};
POLY.offset(top_poly, [-offset1, -offsetN], oso);
oso.outs.forEach((polys, i) => {
polys.forEach(p => {
p.depth = i;
if (p.fill_off) {
p.fill_off.forEach(pi => pi.depth = i);
}
if (p.inner) {
for (let pi of p.inner) {
pi.depth = p.depth;
}
}
top.shells.push(p);
});
last = polys;
});
// slice.solids.trimmed = slice.solids.trimmed || [];
oso.gaps.forEach((polys, i) => {
let off = (i == 0 ? offset1 : offsetN);
polys = POLY.offset(polys, -off * 0.8, {z, minArea: 0});
top.thin_fill.appendAll(cullIntersections(
fillArea(polys, 45, off/2, [], 0.01, off*2),
fillArea(polys, 135, off/2, [], 0.01, off*2),
));
gaps = polys;
});
} else {
// standard wall offsetting strategy
POLY.expand(
top_poly, // reference polygon(s)
-offset1, // first inset distance
z, // set new polys to this z
top.shells, // accumulator array
count, // number of insets to perform
-offsetN, // subsequent inset distance
// 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.fill_off) p.fill_off.forEach(function(pi) {
// use negative offset for inners
pi.depth = -(count - countNow);
});
if (p.inner) {
for (let pi of p.inner) {
pi.depth = p.depth;
}
}
top.shells.push(p);
});
last = polys;
});
// slice.solids.trimmed = slice.solids.trimmed || [];
oso.gaps.forEach((polys, i) => {
let off = (i == 0 ? offset1 : offsetN);
polys = POLY.offset(polys, -off * 0.8, {z, minArea: 0});
top.thin_fill.appendAll(cullIntersections(
fillArea(polys, 45, off/2, [], 0.01, off*2),
fillArea(polys, 135, off/2, [], 0.01, off*2),
));
gaps = polys;
});
} else {
// standard wall offsetting strategy
POLY.expand(
top_poly, // reference polygon(s)
-offset1, // first inset distance
z, // set new polys to this z
top.shells, // accumulator array
count, // number of insets to perform
-offsetN, // subsequent inset distance
// 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.fill_off) p.fill_off.forEach(function(pi) {
// use negative offset for inners
pi.depth = -(count - countNow);
});
if (p.inner) {
for (let pi of p.inner) {
pi.depth = p.depth;
}
}
});
});
}
}
} else {
// no shells, just infill, is permitted
last = [top.poly];
}
} else {
// no shells, just infill, is permitted
last = [top.poly];
}
// generate fill offset poly set from last offset to top.fill_off
if (fillOffset && last.length > 0) {
// if gaps present, remove that area from fill inset
if (gaps.length) {
let nulast = [];
POLY.subtract(last, gaps, nulast, null, slice.z);
last = nulast;
}
last.forEach(function(inner) {
POLY.offset([inner], -fillOffset, {outs: top.fill_off, flat: true, z: slice.z});
});
// generate fill offset poly set from last offset to top.fill_off
if (fillOffset && last.length > 0) {
// if gaps present, remove that area from fill inset
if (gaps.length) {
let nulast = [];
POLY.subtract(last, gaps, nulast, null, z);
last = nulast;
}
last.forEach(function(inner) {
POLY.offset([inner], -fillOffset, {outs: top.fill_off, flat: true, z});
});
}
// for diffing
top.last = last;
shellout += top.shells.length;
});
};
// for diffing
top.last = last;
}
/**
* Create an entirely solid layer by filling all top polygons