grid-apps-cmms/src2/kiri/layers.js
2025-07-04 22:18:48 -04:00

263 lines
8.5 KiB
JavaScript

/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { newPolygon } from '../geo/polygon.js';
import { polygons as POLY } from '../geo/polygons.js';
export class Layers {
constructor() {
this.init();
}
init() {
this.layers = {};
this.profiles = {};
this.stats = {
contour: 0,
flat_line: 0,
flat_poly: 0,
line_poly: 0,
line: 0
};
}
getLayer(layer) {
return this.layers[layer];
}
// in radians
setRotation(x = 0, y = 0, z = 0) {
this.rotation = { x, y, z };
return this;
}
setPosition(x = 0, y = 0, z = 0) {
this.position = { x, y, z };
return this;
}
setLayer(layer, colors, off) {
let layers = this.layers;
if (typeof(colors) === 'number') {
colors = {
line: colors,
face: colors,
opacity: 1
};
}
this.current = layers[layer] = layers[layer] || {
rotation: this.rotation,
position: this.position,
off: off === true,
lines: [], // basic line segments
polys: [], // colors are an attribute on polygons
faces: [], // triangles for areas and flats
norms: undefined, // flats vertex normals
cface: undefined, // flats face color indices
paths: undefined, // 3d extrusion / tube paths (used to be an array, now merged - FIX)
cpath: undefined, // path colors indices
color: colors || {
fat: 0, // render polys & lines with thickness
line: 0,
face: 0,
opacity: 1
},
};
return this;
}
// add a line segment (two points)
addLine(p1, p2) {
this.current.lines.push(p1, p2);
return this;
}
// add an array of line segments
addLines(lines, options) {
if (options) {
// the open option encodes lines as open polygons
options.open = true;
const polys = [];
for (let i=0; i<lines.length-1; i += 2) {
polys.push(new base.Polygon()
.append(lines[i])
.append(lines[i+1])
.setOpen());
}
return this.addPolys(polys, options);
}
for (let i=0; i<lines.length-1; i += 2) {
this.addLine(lines[i], lines[i+1]);
this.stats.line++;
}
return this;
}
// an open or closed polygon
addPoly(poly, options) {
return this.addPolys([poly], options);
}
// a polygon rendered as a webgl line
addPolys(polys, options) {
if (polys.length === 0) {
return this;
}
if (options && options.clean) {
polys = polys.map(p => p.clean(true));
}
if (options && options.flat) {
return this.addFlats(polys, options);
}
if (options && !options.thin) {
return this.addPaths(polys, options);
}
polys = flat(polys);
if (options) {
for (let p of polys) {
if (options.z !== undefined) {
p.setZ(options.z);
}
switch (typeof(options.color)) {
case 'number': p.color = options.color; break;
case 'object': p.color = options.color.line; break;
}
}
}
this.current.polys.appendAll(polys);
this.stats.line_poly += polys.length;
return this;
}
// add an enclosed 3D polygon earcut into faces
// used for FDM solids, bridges, flats debug, CAM hole generation & SLA slice visualization
addAreas(polys, options) {
const faces = this.current.faces;
polys = Array.isArray(polys) ? polys : [ polys ];
for (let poly of polys) {
for (let ep of poly.earcut()) {
for (let p of ep.points) {
faces.push(p.x, p.y, p.z);
}
}
}
if (options && options.outline) {
this.addPolys(polys.clone(true));
}
}
// add a 2D polyline path (usually FDM extrusion paths)
addFlats(polys, options) {
const opts = options || {};
const offset = opts.offset || 1;
polys = flat(polys);
if (!polys.length) {
return;
}
const cur = this.current, faces = cur.faces;
const norms = [];
for (let poly of polys) {
const faceidx = faces.length / 3;
const path = poly.toPath2D(offset * 0.95);
const { left, right, normals } = path;
for (let p of path.faces) {
faces.push(p.x, p.y, p.z);
}
norms.push(normals);
if (opts.outline) {
let p1 = newPolygon().addPoints(left).setOpenValue(poly.open);
let p2 = newPolygon().addPoints(right).setOpenValue(poly.open);
if (poly.z) {
p1.setZ(poly.z);
p2.setZ(poly.z);
}
this.addPolys(p1);
this.addPolys(p2);
}
const color = opts.color ?
(typeof(opts.color) === 'number' ? { line: opts.color, face: opts.color } : opts.color) :
cur.color;
if (!cur.cface) {
cur.cface = [ Object.assign({ start: faceidx, count: Infinity }, color) ];
} else {
// rewrite last color count if color or opacity have changed
const pc = cur.cface[cur.cface.length - 1];
if (pc.face !== color.face || pc.opacity !== color.opacity) {
pc.count = faceidx;
cur.cface.push(Object.assign({ start: faceidx, count: Infinity }, color));
}
}
}
if (cur.norms) {
cur.norms.appendAll(norms.flat());
} else {
cur.norms = norms.flat();
}
return this;
}
// add 3D polyline path (FDM extrusion paths)
addPaths(polys, options) {
const opts = options || {};
const height = opts.height || 1;
const offset = opts.offset || 1;
polys = flat(polys);
if (!polys.length) {
return;
}
for (let poly of polys) {
if (poly.length < (poly.open ? 2 : 3)) {
continue;
}
const z = opts.z || poly.getZ();
const { faces, normals } = poly.toPath3D(offset, height, 0);
const cur = this.current;
const one = cur.paths;
// happens with very short lines being omitted
if (!(faces && normals)) {
continue;
}
if (one) {
// for some reason, incremental merge is faster than all at the end
const add = one.faces.length / 3;
const feces = new Float32Array(one.faces.length + faces.length);
const indln = one.faces.length / 3;
feces.set(one.faces);
feces.set(faces, one.faces.length);
one.faces = feces;
// allow changing colors
if (opts.color) {
if (!cur.cpath) {
cur.cpath = [ Object.assign({ start: 0, count: indln - 1 }, cur.color) ];
}
// rewrite last color count if color or opacity have changed
const pc = cur.cpath[cur.cpath.length - 1];
if (pc.face !== opts.color.face || pc.opacity !== opts.color.opacity) {
pc.count = indln;
cur.cpath.push(Object.assign({ start: indln, count: Infinity }, opts.color));
}
}
if (normals) {
one.norms.appendAll(normals);
}
} else {
cur.paths = { faces, z, norms: normals };
if (opts.color) {
cur.cpath = [ Object.assign({ start: 0, count: Infinity }, opts.color) ];
}
}
this.stats.contour++;
}
return this;
}
}
function flat(polys) {
if (Array.isArray(polys)) {
return POLY.flatten(polys.clone(true), [], true);
} else {
return POLY.flatten([polys.clone(true)], [], true);
}
}