add concurrent slicing, new threaded preference checkbox

This commit is contained in:
Stewart Allen 2021-06-20 22:15:46 -04:00
commit 8e6a1e5747
9 changed files with 283 additions and 179 deletions

1
app.js
View file

@ -345,6 +345,7 @@ const script = {
"mode/fdm/driver", "mode/fdm/driver",
"mode/fdm/slice", "mode/fdm/slice",
"kiri/slice", "kiri/slice",
"kiri/slicer",
"kiri/layers", "kiri/layers",
"kiri/widget", "kiri/widget",
"kiri/codec", "kiri/codec",

View file

@ -676,9 +676,10 @@
exportLocal: false, exportLocal: false,
exportPreview: false, exportPreview: false,
decimate: true, decimate: true,
detail: "good", detail: "100",
animesh: "200", animesh: "200",
healMesh: false, healMesh: false,
threaded: false,
ortho: false, ortho: false,
devel: false devel: false
}, },

View file

@ -115,10 +115,10 @@
level = level.value; level = level.value;
let rez = BASE.config.clipperClean; let rez = BASE.config.clipperClean;
switch (level) { switch (level) {
case 'best': rez = 50; break; case '100': rez = 50; break;
case 'good': rez = BASE.config.clipperClean; break; case '75': rez = BASE.config.clipperClean; break;
case 'fair': rez = 500; break; case '50': rez = 500; break;
case 'poor': rez = 1000; break; case '20': rez = 1000; break;
} }
KIRI.client.config({ KIRI.client.config({
base: { clipperClean: rez } base: { clipperClean: rez }
@ -157,6 +157,7 @@
control.exportPreview = UI.exportPreview.checked; control.exportPreview = UI.exportPreview.checked;
control.decimate = UI.decimate.checked; control.decimate = UI.decimate.checked;
control.healMesh = UI.healMesh.checked; control.healMesh = UI.healMesh.checked;
control.threaded = UI.threaded.checked;
control.ortho = UI.ortho.checked; control.ortho = UI.ortho.checked;
control.devel = UI.devel.checked; control.devel = UI.devel.checked;
SPACE.view.setZoom(control.reverseZoom, control.zoomSpeed); SPACE.view.setZoom(control.reverseZoom, control.zoomSpeed);
@ -1642,6 +1643,7 @@
detail: UC.newSelect(LANG.pt_qual_s, {title: LANG.pt_qual_l, action: detailSave}, "detail"), detail: UC.newSelect(LANG.pt_qual_s, {title: LANG.pt_qual_l, action: detailSave}, "detail"),
decimate: UC.newBoolean(LANG.pt_deci_s, booleanSave, {title: LANG.pt_deci_l}), decimate: UC.newBoolean(LANG.pt_deci_s, booleanSave, {title: LANG.pt_deci_l}),
healMesh: UC.newBoolean(LANG.pt_heal_s, booleanSave, {title: LANG.pt_heal_l}), healMesh: UC.newBoolean(LANG.pt_heal_s, booleanSave, {title: LANG.pt_heal_l}),
threaded: UC.newBoolean(LANG.pt_thrd_s, booleanSave, {title: LANG.pt_thrd_l}),
prefadd: UC.checkpoint($('prefs-add')), prefadd: UC.checkpoint($('prefs-add')),
@ -2432,6 +2434,7 @@
UI.autoSave.checked = control.autoSave; UI.autoSave.checked = control.autoSave;
UI.decimate.checked = control.decimate; UI.decimate.checked = control.decimate;
UI.healMesh.checked = control.healMesh; UI.healMesh.checked = control.healMesh;
UI.threaded.checked = control.threaded;
UI.ortho.checked = control.ortho; UI.ortho.checked = control.ortho;
UI.devel.checked = control.devel; UI.devel.checked = control.devel;
lineTypeSave(); lineTypeSave();

View file

@ -165,10 +165,10 @@
{ name: "8", id: 8 } { name: "8", id: 8 }
], ],
detail: [ detail: [
{ name: "best" }, { name: "100" },
{ name: "good" }, { name: "75" },
{ name: "fair" }, { name: "50" },
{ name: "poor" }, { name: "25" },
], ],
linetype: [ linetype: [
{ name: "path" }, { name: "path" },

View file

@ -77,6 +77,22 @@ const funcs = {
reply({ clips }); reply({ clips });
}, },
sliceBucket: data => {
let { points, slices, options } = data;
let i = 0, p = 0, realp = new Array(points.length / 3);
while (i < points.length) {
realp[p++] = BASE.newPoint(points[i++], points[i++], points[i++]);
}
let output = [];
for (let slice of slices) {
output.push({
slice,
tops: KIRI.slicer.sliceZ(slice.z, realp, options).tops
});
}
reply({ output: CODEC.encode(output) });
},
bad: data => { bad: data => {
reply({error: "invalid command"}); reply({error: "invalid command"});
} }

View file

@ -7,9 +7,10 @@
if (self.kiri.slicer) return; if (self.kiri.slicer) return;
self.kiri.slicer = { self.kiri.slicer = {
slice: slice, slice,
sliceWidget: sliceWidget, sliceZ,
connectLines: connectLines sliceWidget,
connectLines
}; };
let KIRI = self.kiri, let KIRI = self.kiri,
@ -67,11 +68,11 @@
zLines = {}, // map count of z index lines zLines = {}, // map count of z index lines
zScale, // bucket span in z units zScale, // bucket span in z units
timeStart = time(), timeStart = time(),
slices = [],
zSum = 0.0, zSum = 0.0,
buckets = [], buckets = [],
i, j = 0, k, p1, p2, p3, px, i, j = 0, k, p1, p2, p3, px,
CPRO = KIRI.driver.CAM.process; CPRO = KIRI.driver.CAM.process,
concurrent = options.concurrent ? KIRI.minions.concurrent : 0;
if (options.add) { if (options.add) {
zMax += zInc; zMax += zInc;
@ -133,12 +134,20 @@
*/ */
let bucketCount = Math.max(1, Math.ceil(zMax / (zSum / points.length)) - 1); let bucketCount = Math.max(1, Math.ceil(zMax / (zSum / points.length)) - 1);
if (concurrent > 1) {
if (bucketCount < concurrent) {
bucketCount = concurrent;
} else if (bucketCount > 128) {
bucketCount = 128;
}
}
zScale = 1 / (zMax / bucketCount); zScale = 1 / (zMax / bucketCount);
if (bucketCount > 1) { if (bucketCount > 1) {
// create empty buckets // create empty buckets
for (i = 0; i < bucketCount + 1; i++) { for (i = 0; i < bucketCount + 1; i++) {
buckets.push({ points: []}); buckets.push({ points: [], slices: [] });
} }
// copy triples into all matching z-buckets // copy triples into all matching z-buckets
@ -158,7 +167,7 @@
} }
} }
} else { } else {
buckets.push({ points }); buckets.push({ points, slices: [] });
} }
// we need Z ordered list for laser auto or adaptive fdm slicing // we need Z ordered list for laser auto or adaptive fdm slicing
@ -259,7 +268,7 @@
} }
} }
// create a Slice for each z offset in the zIndexes array // create buckets data structure
for (let i = 0; i < zIndexes.length; i++) { for (let i = 0; i < zIndexes.length; i++) {
let ik = zIndexes[i].toFixed(5), let ik = zIndexes[i].toFixed(5),
onFlat = false, onFlat = false,
@ -270,11 +279,14 @@
if (!useFlats && (onFlat || onLine)) { if (!useFlats && (onFlat || onLine)) {
zIndexes[i] -= -0.001; zIndexes[i] -= -0.001;
} }
// slice next layer and add to slices[] array bucketZ(i, zIndexes[i], zHeights[i], onFlat, onLine, zThick[i]);
sliceZ(zIndexes[i], zHeights[i], onFlat, onLine, zThick[i]);
onupdate(i / zIndexes.length); onupdate(i / zIndexes.length);
} }
// create slices from each bucketed region
sliceBuckets().then(slices => {
slices = slices.sort((a,b) => a.index - b.index);
// connect slices into linked list for island/bridge projections // connect slices into linked list for island/bridge projections
for (i=1; i<slices.length; i++) { for (i=1; i<slices.length; i++) {
slices[i-1].up = slices[i]; slices[i-1].up = slices[i];
@ -285,6 +297,44 @@
// pass Slices array back to ondone function // pass Slices array back to ondone function
ondone(slices); ondone(slices);
});
function bucketZ(index, z, height, thick) {
let bucket = buckets[Math.floor(z * zScale)];
bucket.slices.push({
index, z, height, thick
});
}
async function sliceBuckets() {
let output = [];
// console.log({buckets});
if (concurrent < 2) {
sliceBucketsLocal(buckets, output);
} else {
let promises = [];
for (let bucket of buckets) {
promises.push(KIRI.minions.sliceBucket(bucket, options, output));
}
await Promise.all(promises);
}
return output;
}
function sliceBucketsLocal(buckets, output) {
for (let bucket of buckets) {
for (let slice of bucket.slices) {
let { index, z, height, thick } = slice;
let { lines, groups, tops } = sliceZ(z, bucket.points, options);
slice = newSlice(z).addTops(tops);
slice.height = height;
slice.index = index;
slice.thick = thick;
output.push(slice);
}
}
}
}
/** ***** SLICING FUNCTIONS ***** */ /** ***** SLICING FUNCTIONS ***** */
@ -337,6 +387,7 @@
} }
return cached; return cached;
} }
/** /**
* Given two points and hints about their edges, * Given two points and hints about their edges,
* return a new Line object with points sorted * return a new Line object with points sorted
@ -366,11 +417,10 @@
* @param {number} z * @param {number} z
* @param {number} [height] optional real height (fdm) * @param {number} [height] optional real height (fdm)
*/ */
function sliceZ(z, height, onflat, online, thick) { function sliceZ(z, points, options = {}) {
let phash = {}, let phash = {},
lines = [], lines = [],
slice = newSlice(z), p1, p2, p3;
points = buckets[Math.floor(z * zScale)].points;
// iterate over matching buckets for this z offset // iterate over matching buckets for this z offset
for (let i = 0; i < points.length; ) { for (let i = 0; i < points.length; ) {
@ -410,44 +460,41 @@
} }
} }
// allow empty slices in CAM swap mode (for topos w/ gaps) if (lines.length == 0 && options.noEmpty) {
// if (lines.length == 0 && !(options.swapX || options.swapY)) return; return;
if (lines.length == 0 && options.noEmpty) return; }
slice.height = height;
slice.index = slices.length;
slice.thick = thick;
// de-dup and group lines // de-dup and group lines
lines = removeDuplicateLines(lines); lines = removeDuplicateLines(lines);
let groups = connectLines(lines, slice.z); let groups = connectLines(lines, z);
// simplistic healing of bad meshes // simplistic healing of bad meshes
if (options.union) { if (options.union) {
groups = POLY.flatten(POLY.union(POLY.nest(groups), null, true), null, true); groups = POLY.flatten(POLY.union(POLY.nest(groups), null, true), null, true);
} }
if ((debug && lines.excessive) || xray) { // if ((debug && lines.excessive) || xray) {
const dash = xray || 3; // const dash = xray || 3;
lines.forEach((line, i) => { // lines.forEach((line, i) => {
const group = i % dash; // const group = i % dash;
const color = [ 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff ][group]; // const color = [ 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff ][group];
slice.output().setLayer(`xl-${group}`, color).addLine(line.p1, line.p2); // slice.output().setLayer(`xl-${group}`, color).addLine(line.p1, line.p2);
}); // });
} // }
let tops = POLY.nest(groups);
// identify and add tops to slice // identify and add tops to slice
POLY.nest(groups).forEach((poly, i) => { // POLY.nest(groups).forEach((poly, i) => {
slice.addTop(poly); // slice.addTop(poly);
if (xray) { // if (xray) {
const group = i % (xray || 3); // const group = i % (xray || 3);
const color = [ 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff ][group]; // const color = [ 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff ][group];
slice.output().setLayer(`xg-${group}`, color).addPoly(poly); // slice.output().setLayer(`xg-${group}`, color).addPoly(poly);
} // }
}); // });
slices.push(slice); return { lines, groups, tops };
}
} }
/** /**

View file

@ -8,7 +8,8 @@ let BASE = self.base,
POLY = BASE.polygons, POLY = BASE.polygons,
time = UTIL.time, time = UTIL.time,
qtpi = Math.cos(Math.PI/4), qtpi = Math.cos(Math.PI/4),
concurrent = self.Worker ? navigator.hardwareConcurrency || 0 : 0, ccvalue = navigator ? navigator.hardwareConcurrency || 0 : 0,
concurrent = self.Worker && ccvalue >= 2 ? ccvalue : 0,
current = self.worker = { current = self.worker = {
print: null, print: null,
snap: null snap: null
@ -38,7 +39,7 @@ KIRI.minions = {
union: function(polys, minarea) { union: function(polys, minarea) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (concurrent === 0 || polys.length * 2 < concurrent) { if (concurrent < 2 || polys.length * 2 < concurrent) {
resolve(POLY.union(polys, 0, true)); resolve(POLY.union(polys, 0, true));
return; return;
} }
@ -65,7 +66,7 @@ KIRI.minions = {
fill: function(polys, angle, spacing, output, minLen, maxLen) { fill: function(polys, angle, spacing, output, minLen, maxLen) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (concurrent === 0) { if (concurrent < 2) {
resolve(POLY.fillArea(polys, angle, spacing, [], minLen, maxLen)); resolve(POLY.fillArea(polys, angle, spacing, [], minLen, maxLen));
return; return;
} }
@ -89,7 +90,7 @@ KIRI.minions = {
clip: function(slice, polys, lines) { clip: function(slice, polys, lines) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (concurrent === 0) { if (concurrent < 2) {
reject("concurrent clip unavaiable"); reject("concurrent clip unavaiable");
} }
minwork.queue({ minwork.queue({
@ -111,8 +112,40 @@ KIRI.minions = {
}); });
}, },
queue: function(work, ondone) { sliceBucket: function(bucket, options, output) {
minionq.push({work, ondone}); return new Promise((resolve, reject) => {
if (concurrent < 2) {
reject("concurrent slice unavaiable");
}
let { points, slices } = bucket;
let i = 0, floatP = new Float32Array(points.length * 3);
for (let p of points) {
floatP[i++] = p.x;
floatP[i++] = p.y;
floatP[i++] = p.z;
}
minwork.queue({
cmd: "sliceBucket",
points: floatP,
slices,
options
}, data => {
for (let rec of KIRI.codec.decode(data.output)) {
let { slice, tops } = rec;
let newSlice = KIRI.newSlice(slice.z);
newSlice.index = slice.index;
newSlice.thick = slice.thick;
newSlice.height = slice.height;
newSlice.addTops(tops);
output.push(newSlice);
}
resolve();
}, [ floatP.buffer ]);
});
},
queue: function(work, ondone, direct) {
minionq.push({work, ondone, direct});
minwork.kick(); minwork.kick();
}, },
@ -125,7 +158,7 @@ KIRI.minions = {
qrec.ondone(msg.data); qrec.ondone(msg.data);
minwork.kick(); minwork.kick();
}; };
minion.postMessage(qrec.work); minion.postMessage(qrec.work, qrec.direct);
} }
} }
}; };

View file

@ -83,7 +83,7 @@
sliceFillAngle = spro.sliceFillAngle, sliceFillAngle = spro.sliceFillAngle,
supportDensity = spro.sliceSupportDensity, supportDensity = spro.sliceSupportDensity,
beltfact = Math.cos(Math.PI/4), beltfact = Math.cos(Math.PI/4),
doConcurrent = ctrl.danger && KIRI.minions.concurrent; doConcurrent = ctrl.threaded && KIRI.minions.concurrent;
isFlat = ctrl.lineType === "flat"; isFlat = ctrl.lineType === "flat";
isThin = !isFlat && ctrl.lineType === "line"; isThin = !isFlat && ctrl.lineType === "line";
@ -155,7 +155,8 @@
// debug: true, // debug: true,
// xray: 3, // xray: 3,
// view: view, // view: view,
indices: spro.indices indices: spro.indices,
concurrent: doConcurrent
}, onSliceDone, onSliceUpdate); }, onSliceDone, onSliceUpdate);
function onSliceUpdate(update) { function onSliceUpdate(update) {

View file

@ -235,13 +235,15 @@ kiri.lang['en-us'] = {
lo_menu: "layout", lo_menu: "layout",
pt_menu: "parts", pt_menu: "slicer",
pt_deci_s: "decimate", pt_deci_s: "decimate",
pt_deci_l: ["enable or disable point decimation","during port import. for faster slicing","and lower memory usage"], pt_deci_l: ["enable or disable point decimation","during port import. for faster slicing","and lower memory usage"],
pt_qual_s: "quality", pt_qual_s: "detail",
pt_qual_l: ["level of detail to retain","during slicing operations","lower is faster"], pt_qual_l: ["level of detail to retain","during slicing operations","lower is faster"],
pt_heal_s: "heal mesh", pt_heal_s: "heal mesh",
pt_heal_l: ["attempt to heal","non-manifold meshes","adds to slicing time"], pt_heal_l: ["attempt to heal","non-manifold meshes","adds to slicing time"],
pt_thrd_s: "threaded",
pt_thrd_l: ["use parallel processin","when browser permits"],
xp_menu: "exports", xp_menu: "exports",