grid-apps-cmms/src/kiri-mode/cam/topo4.js

423 lines
14 KiB
JavaScript
Raw Normal View History

2023-01-16 13:56:08 -05:00
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
"use strict";
// dep: geo.base
// dep: geo.line
// dep: geo.point
// dep: geo.polygon
// dep: geo.polygons
// dep: kiri.slice
// dep: kiri-mode.cam.driver
// dep: kiri-mode.cam.slicer2
// dep: moto.broker
gapp.register("kiri-mode.cam.topo", [], (root, exports) => {
const { base, kiri } = root;
const { driver, newSlice } = kiri;
const { CAM } = driver;
2023-01-17 00:57:37 -05:00
const { polygons, newPoint, newPolygon, sliceConnect } = base;
2023-01-16 13:56:08 -05:00
const PRO = CAM.process;
const POLY = polygons;
const RAD2DEG = 180 / Math.PI;
2023-01-17 00:57:37 -05:00
const DEG2RAD = Math.PI / 180;
2023-01-16 13:56:08 -05:00
class Topo4 {
constructor() { }
async generate(opt = {}) {
let { state, op, onupdate, ondone } = opt;
let { widget, settings, tabs } = opt.state;
let { controller, process } = settings;
let axis = op.axis.toLowerCase(),
tool = new CAM.Tool(settings, op.tool),
bounds = widget.getBoundingBox().clone(),
density = parseInt(controller.animesh || 100) * 2500,
{ min, max } = bounds,
span = {
x: max.x - min.x,
y: max.y - min.y
},
contour = {
x: axis === "x",
y: axis === "y"
},
zMin = min.z + 0.0001,
2023-01-16 23:33:24 -05:00
tolerance = op.tolerance,
2023-01-16 13:56:08 -05:00
resolution = tolerance ? tolerance : 1 / Math.sqrt(density / (span.x * span.y)),
step = this.step = (tool.traceOffset() * 2) * op.step,
angle = this.angle = op.angle || 1;
2023-01-16 13:56:08 -05:00
if (tolerance === 0) {
console.log(widget.id, 'topo4 auto tolerance', resolution.round(4));
}
2023-02-11 15:13:06 -05:00
this.zBottom = state.zBottom;
2023-01-16 15:14:58 -05:00
this.resolution = resolution;
2023-01-16 23:33:24 -05:00
this.vertices = widget.getGeoVertices().toShared();
this.tool = tool.generateProfile(resolution).profile;
2023-02-13 23:10:42 -05:00
this.maxo = tool.profileDim.maxo * resolution;
this.diam = tool.fluteDiameter();
2023-01-16 13:56:08 -05:00
onupdate(0, "lathe");
2023-01-16 18:27:38 -05:00
2023-01-16 23:33:24 -05:00
const range = this.range = { min: Infinity, max: -Infinity };
const slices = this.sliced = await this.slice(onupdate);
2023-01-16 15:14:58 -05:00
for (let slice of slices) {
2023-01-16 23:33:24 -05:00
range.min = Math.min(range.min, slice.z);
range.max = Math.max(range.max, slice.z);
2023-01-16 15:14:58 -05:00
slice.output()
.setLayer("lathe", { line: 0x888888 })
.addPolys(slice.topPolys());
}
2023-01-16 13:56:08 -05:00
2023-01-16 18:27:38 -05:00
const lathe = await this.lathe(onupdate);
2023-02-13 13:35:43 -05:00
// ondone([...slices, ...lathe]);
2023-01-16 18:27:38 -05:00
onupdate(1, "lathe");
2023-02-13 13:35:43 -05:00
ondone(lathe);
2023-01-16 13:56:08 -05:00
}
2023-01-16 15:14:58 -05:00
async slice(onupdate) {
2023-01-16 23:33:24 -05:00
const { vertices, resolution } = this;
2023-01-16 15:14:58 -05:00
const range = this.range = { min: Infinity, max: -Infinity };
const box = this.box = new THREE.Box2();
// swap XZ in shared array
for (let i=0, l=vertices.length; i<l; i += 3) {
const x = vertices[i];
const z = vertices[i + 2];
vertices[i] = z;
vertices[i + 2] = x;
range.min = Math.min(range.min, x);
range.max = Math.max(range.max, x);
}
// add tool radius to slice min/max range ot fully carve part
range.min -= this.diam / 2;
range.max += this.diam / 2;
2023-01-16 15:14:58 -05:00
const shards = Math.ceil(Math.min(25, vertices.length / 27000));
const step = (range.max - range.min) / shards;
let index = 0;
let slices = this.slices = [];
let slice = { min: range.min, max: range.min + step, index };
2023-01-16 15:14:58 -05:00
for (let z = range.min; z < range.max; z += resolution) {
if (z > slice.max) {
slices.push(slice);
slice = { min: z, max: z + step, index };
}
index++;
}
slices.push(slice);
// console.log({ shards, range, step, slices });
2023-01-16 15:14:58 -05:00
if (kiri.minions.running > 1) {
return await this.sliceMinions(onupdate);
} else {
return await this.sliceWorker(onupdate);
}
}
async sliceWorker(onupdate) {
const { vertices, slices, resolution } = this;
2023-01-16 18:27:38 -05:00
const { codec } = kiri;
2023-01-16 15:14:58 -05:00
let output = [];
let complete = 0;
for (let slice of slices) {
const recs = new kiri.topo_slicer(slice.index)
.setFromArray(vertices, slice)
.slice(resolution)
.map(rec => {
const slice = kiri.newSlice(rec.z);
for (let line of rec.lines) {
const { p1, p2 } = line;
if (!p1.swapped) { p1.swapXZ(); p1.swapped = true }
if (!p2.swapped) { p2.swapXZ(); p2.swapped = true }
}
slice.index = rec.index;
slice.addTops(sliceConnect(rec.lines));
2023-01-16 18:27:38 -05:00
const points = codec.encodePointArray(rec.lines.map(l => [ l.p1, l.p2 ]).flat());
const shared = new Float32Array(new SharedArrayBuffer(points.length * 4));
shared.set(points);
slice.shared = shared;
2023-01-16 15:14:58 -05:00
return slice;
});
output.appendAll(recs);
onupdate(++complete /slices.length);
}
return output;
}
async sliceMinions(onupdate) {
2023-01-16 23:33:24 -05:00
const { codec } = kiri;
const { queue, putCache, clearCache } = this;
const { vertices, slices, resolution } = this;
putCache("vertices", vertices);
2023-01-16 15:14:58 -05:00
let complete = 0;
let promises = slices.map(slice => {
2023-01-16 23:33:24 -05:00
return queue("topo4_slice", {
resolution,
slice
}).then(data => {
onupdate(++complete / slices.length);
return data;
2023-01-16 15:14:58 -05:00
});
});
// merge boxes for all rasters for contouring clipping
const output = codec.decode(await Promise.all(promises))
.map(rec => rec.recs)
.flat()
2023-01-16 18:27:38 -05:00
.map(rec => newSlice(rec.z)
.addTops(rec.polys)
.setFields({ shared: rec.shared }))
2023-01-16 15:14:58 -05:00
.sort((a,b) => a.z - b.z);
2023-01-16 23:33:24 -05:00
clearCache();
2023-01-16 15:14:58 -05:00
return output;
}
2023-01-16 18:27:38 -05:00
async lathe(onupdate) {
2023-01-16 23:33:24 -05:00
if (false && kiri.minions.running > 1) {
2023-01-16 18:27:38 -05:00
return await this.latheMinions(onupdate);
} else {
return await this.latheWorker(onupdate);
}
}
2023-01-17 00:57:37 -05:00
lathePath(slices, tool) {
2023-02-13 23:10:42 -05:00
const { resolution, step, zBottom, maxo } = this;
2023-01-16 23:33:24 -05:00
const tlen = tool.length;
const slen = slices.length;
const sinc = Math.max(1, Math.ceil(step / resolution));
2023-01-17 00:57:37 -05:00
const heights = [];
// cull slice lines to only the ones in range (5x faster)
const oslices = [];
for (let slice of slices) {
const lines = slice.lines;
const plen = lines.length;
const rec = { z: slice.z, lines: [] };
for (let i = 0; i < plen; ) {
++i; // skip x which should match slice.z
let py0 = lines[i++];
const pz0 = lines[i++];
++i; // skip x which should match slice.z
let py1 = lines[i++];
const pz1 = lines[i++];
if ((py0 < -maxo && py1 < -maxo) || (py0 > maxo && py1 > maxo)) {
continue;
}
rec.lines.push(0, py0, pz0, 0, py1, pz1);
}
oslices.push(rec);
}
2023-01-16 23:33:24 -05:00
// iterate over all slices (real x = z)
// find max real z using z ray intersect from tool point to slice lines + offset
for (let si = 0; si < slen; si += sinc) {
const rx = oslices[si].z;
2023-01-16 23:33:24 -05:00
let mz = 0;
// iterate over tool offsets
for (let ti = 0; ti < tlen; ) {
// tool offset in grid units from present x (si)
const xo = tool[ti++]; // x grid offset (slice)
2023-01-17 00:57:37 -05:00
const yo = tool[ti++] * resolution; // y grid offset (mult rez to get real y)
2023-01-16 23:33:24 -05:00
const zo = tool[ti++]; // real z delta offset
// get slice index corresponding with offset
const ts = si + xo;
// outside of slice array, skip
if (ts < 0 || ts >= slen) continue;
const slice = oslices[ts];
2023-01-16 23:33:24 -05:00
const lines = slice.lines;
const plen = lines.length;
for (let i = 0; i < plen; ) {
++i; // skip x which should match slice.z
2023-01-17 06:41:37 -05:00
let py0 = lines[i++];
2023-01-16 23:33:24 -05:00
const pz0 = lines[i++];
++i; // skip x which should match slice.z
2023-01-17 06:41:37 -05:00
let py1 = lines[i++];
2023-01-16 23:33:24 -05:00
const pz1 = lines[i++];
2023-01-17 00:57:37 -05:00
if ((py0 <= yo && py1 >= yo) || (py1 <= yo && py0 >= yo)) {
2023-01-17 06:41:37 -05:00
const dz = pz1 - pz0;
2023-01-17 00:57:37 -05:00
const dy = Math.abs(py1 - py0);
2023-01-17 06:41:37 -05:00
const fr = Math.abs(yo - py0) / dy;
2023-01-17 00:57:37 -05:00
const lz = pz0 + dz * fr + zo;
2023-01-16 23:33:24 -05:00
// check z height
2023-01-17 00:57:37 -05:00
mz = Math.max(mz, lz);
2023-01-16 23:33:24 -05:00
}
}
}
2023-02-11 15:13:06 -05:00
heights.push(rx, 0, Math.max(zBottom, mz));
2023-01-16 23:33:24 -05:00
}
2023-01-17 00:57:37 -05:00
return heights;
}
async latheWorker(onupdate) {
const { sliced, tool } = this;
2023-01-16 23:33:24 -05:00
const rota = this.angle * DEG2RAD;
const steps = (Math.PI * 2) / rota;
2023-01-17 00:57:37 -05:00
const axis = new THREE.Vector3(1, 0, 0);
const mrot = new THREE.Matrix4().makeRotationAxis(axis, rota);
2023-01-17 00:57:37 -05:00
const slices = sliced.map(s => { return { z: s.z, lines: s.shared } });
const paths = [];
const recs = [];
// this.lathePath(slices, tool, paths);
let angle = 0;
let count = 0;
// for each step angle, find Z spine heights, produce record
2023-01-17 00:57:37 -05:00
while (count++ < steps) {
const heights = this.lathePath(slices, tool, paths);
2023-02-13 13:35:43 -05:00
recs.push({ angle, heights, degrees: angle * RAD2DEG });
2023-01-17 00:57:37 -05:00
angle -= rota;
for (let lines of slices.map(s => s.lines)) {
rotatePoints(lines, mrot);
2023-01-17 00:57:37 -05:00
}
}
2023-02-13 15:38:39 -05:00
count = recs[0].heights.length / 3;
while (count-- > 0) {
let slice = newSlice(count);
2023-02-13 13:35:43 -05:00
slice.camLines = [ newPolygon().setOpen() ];
paths.push(slice);
}
2023-01-17 00:57:37 -05:00
for (let rec of recs) {
const { degrees, heights } = rec;
[...heights].group(3).forEach((a,i) => {
paths[i].camLines[0].push( newPoint(a[0], a[1], a[2]).setA(degrees) );
});
2023-02-13 13:35:43 -05:00
}
for (let slice of paths) {
const poly = slice.camLines[0];
if (!poly.length) {
2023-02-13 15:38:39 -05:00
console.log('empty', slice);
continue;
}
slice.camLines[0].push(poly.points[0].clone());
2023-01-17 00:57:37 -05:00
slice.output()
.setLayer("lathe", { line: 0xffff00 })
.addPoly(poly.clone().applyRotations());
2023-01-17 00:57:37 -05:00
}
2023-02-13 13:35:43 -05:00
// console.log({ tool, slices, paths });
2023-01-17 00:57:37 -05:00
return paths;
2023-01-16 18:27:38 -05:00
}
async latheMinions(onupdate) {
2023-01-16 23:33:24 -05:00
const { codec } = kiri;
const { sliced, resolution, tool, step } = this;
2023-01-16 23:33:24 -05:00
const { putCache, clearCache, queue } = this;
console.log({ sliced });
putCache("lathe", {
tool,
step,
2023-01-16 23:33:24 -05:00
range,
resolution,
slices: sliced.map(s => { return { z: s.z, lines: s.shared } })
});
2023-01-16 18:27:38 -05:00
let complete = 0;
2023-01-16 23:33:24 -05:00
let promises = sliced.map(slice => {
return queue("topo4_lathe", {
// resolution
}).then(data => {
onupdate(++complete / sliced.length);
return data;
2023-01-16 18:27:38 -05:00
});
});
// merge boxes for all rasters for contouring clipping
const output = await Promise.all(promises);
2023-01-16 23:33:24 -05:00
console.log({ output });
2023-01-16 18:27:38 -05:00
2023-01-16 23:33:24 -05:00
clearCache();
2023-01-16 18:27:38 -05:00
return [];
}
2023-01-16 23:33:24 -05:00
putCache(key, data) {
kiri.worker.putCache({ key, data }, { done: data => { } });
}
clearCache() {
kiri.worker.clearCache({}, { done: data => { } });
}
queue(cmd, params) {
return new Promise(resolve => {
kiri.minions.queue({ cmd, ...params }, resolve);
});
}
2023-01-16 13:56:08 -05:00
}
2023-01-17 00:57:37 -05:00
function rotatePoints(lines, rot) {
new THREE.BufferAttribute(lines, 3).applyMatrix4(rot);
}
2023-01-16 13:56:08 -05:00
CAM.Topo4 = async function(opt) {
return new Topo4().generate(opt);
};
moto.broker.subscribe("minion.started", msg => {
const { funcs, cache, reply, log } = msg;
2023-01-16 15:14:58 -05:00
const { codec } = kiri;
2023-01-16 13:56:08 -05:00
2023-01-16 18:27:38 -05:00
funcs.topo4_slice = (data, seq) => {
2023-01-16 23:33:24 -05:00
const { slice, resolution } = data;
const vertices = cache.vertices;
2023-01-16 15:14:58 -05:00
const recs = new kiri.topo_slicer(slice.index)
.setFromArray(vertices, slice)
.slice(resolution)
.map(rec => {
const { z, index, lines } = rec;
for (let line of lines) {
const { p1, p2 } = line;
if (!p1.swapped) { p1.swapXZ(); p1.swapped = true }
if (!p2.swapped) { p2.swapXZ(); p2.swapped = true }
}
2023-01-16 18:27:38 -05:00
const points = codec.encodePointArray(lines.map(l => [ l.p1, l.p2 ]).flat());
const shared = new Float32Array(new SharedArrayBuffer(points.length * 4));
shared.set(points);
return {
z, index, shared,
polys: codec.encode(sliceConnect(lines)),
};
2023-01-16 15:14:58 -05:00
});
// only pass back bounds of rasters to be merged
reply({ seq, recs });
2023-01-16 13:56:08 -05:00
};
2023-01-16 18:27:38 -05:00
funcs.topo4_lathe = (data, seq) => {
2023-01-16 23:33:24 -05:00
// console.log({ topo4_lathe: data });
2023-01-16 13:56:08 -05:00
2023-01-16 23:33:24 -05:00
// const { resolution } = data;
const { resolution, sliced, tool, step } = cache.lathe;
2023-01-16 18:27:38 -05:00
reply({ seq, abc: 123 });
}
2023-01-16 13:56:08 -05:00
});
});