consolidate stl/obj load/encode into src/load classes
This commit is contained in:
parent
2a45ad87dc
commit
69ef0ff54b
4 changed files with 119 additions and 135 deletions
|
|
@ -4,6 +4,8 @@ import { api } from './api.js';
|
|||
import { space } from '../../moto/space.js';
|
||||
import { THREE } from '../../ext/three.js';
|
||||
import { tool as MeshTool } from '../../mesh/tool.js';
|
||||
import { encode as objEncode } from '../../load/obj.js';
|
||||
import { encode as stlEncode } from '../../load/stl.js';
|
||||
|
||||
const selectedMeshes = [];
|
||||
|
||||
|
|
@ -258,61 +260,33 @@ function exportWidgets(format = "stl") {
|
|||
facets += geo.attributes.position.count;
|
||||
});
|
||||
if (format === "obj") {
|
||||
let obj = [];
|
||||
let vpad = 0;
|
||||
for (let out of outs) {
|
||||
let recs = outs.map(out => {
|
||||
let meta = out.widget.meta;
|
||||
let name = meta.file || 'unnamed';
|
||||
obj.push(`g ${name}`);
|
||||
let { position } = out.geo.attributes;
|
||||
let pvals = position.array;
|
||||
for (let i=0, il=position.count; i<il; i += 3) {
|
||||
let pi = i * position.itemSize;
|
||||
obj.push(`v ${pvals[pi++]} ${pvals[pi++]} ${pvals[pi++]}`);
|
||||
obj.push(`v ${pvals[pi++]} ${pvals[pi++]} ${pvals[pi++]}`);
|
||||
obj.push(`v ${pvals[pi++]} ${pvals[pi++]} ${pvals[pi++]}`);
|
||||
obj.push(`f ${i+1+vpad} ${i+2+vpad} ${i+3+vpad}`);
|
||||
}
|
||||
vpad += position.count;
|
||||
}
|
||||
return obj.join('\n');
|
||||
return { file: name, varr: position.array };
|
||||
});
|
||||
return objEncode(recs);
|
||||
}
|
||||
let stl = new Uint8Array(80 + 4 + facets/3 * 50);
|
||||
let dat = new DataView(stl.buffer);
|
||||
let pos = 84;
|
||||
dat.setInt32(80, facets/3, true);
|
||||
for (let out of outs) {
|
||||
let recs = outs.map(out => {
|
||||
let { position } = out.geo.attributes;
|
||||
let pvals = position.array;
|
||||
for (let i=0, il=position.count; i<il; i += 3) {
|
||||
let pi = i * position.itemSize;
|
||||
let p0 = new THREE.Vector3(pvals[pi++], pvals[pi++], pvals[pi++]);
|
||||
let p1 = new THREE.Vector3(pvals[pi++], pvals[pi++], pvals[pi++]);
|
||||
let p2 = new THREE.Vector3(pvals[pi++], pvals[pi++], pvals[pi++]);
|
||||
let norm = THREE.computeFaceNormal(p0, p1, p2);
|
||||
let xo = 0, yo = 0, zo = 0;
|
||||
if (outs.length > 1) {
|
||||
let {x, y, z} = out.widget.track.pos;
|
||||
xo = x;
|
||||
yo = y;
|
||||
zo = z;
|
||||
let varr = pvals;
|
||||
|
||||
// Apply position offset if exporting multiple widgets
|
||||
if (outs.length > 1) {
|
||||
let {x, y, z} = out.widget.track.pos;
|
||||
varr = new Float32Array(pvals.length);
|
||||
for (let i = 0; i < pvals.length; i += 3) {
|
||||
varr[i] = pvals[i] + x;
|
||||
varr[i+1] = pvals[i+1] + y;
|
||||
varr[i+2] = pvals[i+2] + z;
|
||||
}
|
||||
dat.setFloat32(pos + 0, norm.x, true);
|
||||
dat.setFloat32(pos + 4, norm.y, true);
|
||||
dat.setFloat32(pos + 8, norm.z, true);
|
||||
dat.setFloat32(pos + 12, p0.x + xo, true);
|
||||
dat.setFloat32(pos + 16, p0.y + yo, true);
|
||||
dat.setFloat32(pos + 20, p0.z + zo, true);
|
||||
dat.setFloat32(pos + 24, p1.x + xo, true);
|
||||
dat.setFloat32(pos + 28, p1.y + yo, true);
|
||||
dat.setFloat32(pos + 32, p1.z + zo, true);
|
||||
dat.setFloat32(pos + 36, p2.x + xo, true);
|
||||
dat.setFloat32(pos + 40, p2.y + yo, true);
|
||||
dat.setFloat32(pos + 44, p2.z + zo, true);
|
||||
pos += 50;
|
||||
}
|
||||
}
|
||||
return stl;
|
||||
|
||||
return { file: out.widget.meta.file || 'unnamed', varr };
|
||||
});
|
||||
return stlEncode(recs);
|
||||
}
|
||||
|
||||
function widgets(orall) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,28 @@
|
|||
|
||||
import { util } from '../geo/base.js';
|
||||
|
||||
/**
|
||||
* Export vertex array(s) to OBJ format string
|
||||
* @param {Array} recs - array of {file: string, varr: Float32Array} records
|
||||
* @param {String} header - optional header comment
|
||||
* @returns {String} OBJ format text
|
||||
*/
|
||||
export function encode(recs, header = '# Generated @ https://grid.space') {
|
||||
let p = 1;
|
||||
let obj = [header];
|
||||
for (let rec of recs) {
|
||||
let { file, varr } = rec;
|
||||
obj.push(`g ${file || 'unnamed'}`);
|
||||
for (let i=0; i<varr.length; p += 3) {
|
||||
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
||||
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
||||
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
||||
obj.push(`f ${p} ${p+1} ${p+2}`);
|
||||
}
|
||||
}
|
||||
return obj.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} text
|
||||
* @returns {Array} vertex face array
|
||||
|
|
|
|||
|
|
@ -1,6 +1,76 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
import { THREE } from '../ext/three.js';
|
||||
|
||||
const CDH = 'Content-Disposition';
|
||||
const { Vector3, computeFaceNormal } = THREE;
|
||||
|
||||
/**
|
||||
* Encode vertex array(s) to binary STL format
|
||||
* Automatically computes normals from triangle vertices
|
||||
* @param {Array} recs - array of {file: string, varr: Float32Array} records
|
||||
* @param {String} header - optional header text (max 80 chars)
|
||||
* @returns {Uint8Array} binary STL data
|
||||
*/
|
||||
export function encode(recs, header = '') {
|
||||
// Calculate total vertices
|
||||
let vtot = 0;
|
||||
for (let rec of recs) {
|
||||
vtot += (rec.varr.length / 3);
|
||||
}
|
||||
|
||||
// Create STL buffer: 80 byte header + 4 byte count + (50 bytes per triangle)
|
||||
let stl = new Uint8Array(80 + 4 + vtot/3 * 50);
|
||||
let dat = new DataView(stl.buffer);
|
||||
let pos = 84;
|
||||
|
||||
// Write header text (first 80 bytes)
|
||||
if (header) {
|
||||
header.substring(0, 80).split('').forEach((c, i) => {
|
||||
dat.setUint8(i, c.charCodeAt(0));
|
||||
});
|
||||
}
|
||||
|
||||
// Write triangle count at byte 80
|
||||
dat.setInt32(80, vtot/3, true);
|
||||
|
||||
// Write triangles
|
||||
for (let rec of recs) {
|
||||
let { varr } = rec;
|
||||
for (let i = 0, l = varr.length; i < l;) {
|
||||
// Read three vertices
|
||||
let p0 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
||||
let p1 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
||||
let p2 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
||||
|
||||
// Compute face normal
|
||||
let norm = computeFaceNormal(p0, p1, p2);
|
||||
|
||||
// Write normal (3 floats)
|
||||
dat.setFloat32(pos + 0, norm.x, true);
|
||||
dat.setFloat32(pos + 4, norm.y, true);
|
||||
dat.setFloat32(pos + 8, norm.z, true);
|
||||
|
||||
// Write vertices (9 floats)
|
||||
dat.setFloat32(pos + 12, p0.x, true);
|
||||
dat.setFloat32(pos + 16, p0.y, true);
|
||||
dat.setFloat32(pos + 20, p0.z, true);
|
||||
dat.setFloat32(pos + 24, p1.x, true);
|
||||
dat.setFloat32(pos + 28, p1.y, true);
|
||||
dat.setFloat32(pos + 32, p1.z, true);
|
||||
dat.setFloat32(pos + 36, p2.x, true);
|
||||
dat.setFloat32(pos + 40, p2.y, true);
|
||||
dat.setFloat32(pos + 44, p2.z, true);
|
||||
|
||||
// Attribute byte count (2 bytes) - typically 0
|
||||
dat.setUint16(pos + 48, 0, true);
|
||||
|
||||
pos += 50;
|
||||
}
|
||||
}
|
||||
|
||||
return stl;
|
||||
}
|
||||
|
||||
export class STL {
|
||||
constructor() {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import { CSG } from '../geo/csg.js';
|
|||
import { util } from './util.js';
|
||||
import { tool as meshTool } from './tool.js';
|
||||
import { meshToSTEPWithFaces } from '../load/step.js';
|
||||
import { encode as objEncode } from '../load/obj.js';
|
||||
import { encode as stlEncode } from '../load/stl.js';
|
||||
|
||||
const { Triangle, Vector3, BufferGeometry, BufferAttribute, computeFaceNormal } = THREE;
|
||||
|
||||
|
|
@ -517,51 +519,9 @@ const model = {
|
|||
}
|
||||
switch (format) {
|
||||
case "obj":
|
||||
let p = 1;
|
||||
let obj = [header];
|
||||
for (let rec of recs) {
|
||||
let { file, varr } = rec;
|
||||
obj.push(`g ${file}`);
|
||||
for (let i=0; i<varr.length; p += 3) {
|
||||
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
||||
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
||||
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
||||
obj.push(`f ${p} ${p+1} ${p+2}`);
|
||||
}
|
||||
}
|
||||
return obj.join('\n');
|
||||
return objEncode(recs, header);
|
||||
case "stl":
|
||||
let stl = new Uint8Array(80 + 4 + vtot/3 * 50);
|
||||
let dat = new DataView(stl.buffer);
|
||||
let pos = 84;
|
||||
header.split('').forEach((c,i) => {
|
||||
dat.setUint8(i, c.charCodeAt(0));
|
||||
});
|
||||
// todo put Mesh:Tool info in header
|
||||
dat.setInt32(80, vtot/3, true);
|
||||
for (let rec of recs) {
|
||||
let { id, matrix, file, varr } = rec;
|
||||
for (let i=0, l=varr.length; i<l;) {
|
||||
let p0 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
||||
let p1 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
||||
let p2 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
||||
let norm = computeFaceNormal(p0, p1, p2);
|
||||
dat.setFloat32(pos + 0, norm.x, true);
|
||||
dat.setFloat32(pos + 4, norm.y, true);
|
||||
dat.setFloat32(pos + 8, norm.z, true);
|
||||
dat.setFloat32(pos + 12, p0.x, true);
|
||||
dat.setFloat32(pos + 16, p0.y, true);
|
||||
dat.setFloat32(pos + 20, p0.z, true);
|
||||
dat.setFloat32(pos + 24, p1.x, true);
|
||||
dat.setFloat32(pos + 28, p1.y, true);
|
||||
dat.setFloat32(pos + 32, p1.z, true);
|
||||
dat.setFloat32(pos + 36, p2.x, true);
|
||||
dat.setFloat32(pos + 40, p2.y, true);
|
||||
dat.setFloat32(pos + 44, p2.z, true);
|
||||
pos += 50;
|
||||
}
|
||||
}
|
||||
return stl;
|
||||
return stlEncode(recs, header);
|
||||
default:
|
||||
throw `invalid format "${format}"`;
|
||||
}
|
||||
|
|
@ -617,51 +577,9 @@ const file = {
|
|||
}
|
||||
return meshToSTEPWithFaces(varr);
|
||||
case "obj":
|
||||
let p = 1;
|
||||
let obj = [header];
|
||||
for (let rec of recs) {
|
||||
let { file, varr } = rec;
|
||||
obj.push(`g ${file}`);
|
||||
for (let i=0; i<varr.length; p += 3) {
|
||||
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
||||
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
||||
obj.push(`v ${varr[i++]} ${varr[i++]} ${varr[i++]}`);
|
||||
obj.push(`f ${p} ${p+1} ${p+2}`);
|
||||
}
|
||||
}
|
||||
return obj.join('\n');
|
||||
return objEncode(recs, header);
|
||||
case "stl":
|
||||
let stl = new Uint8Array(80 + 4 + vtot/3 * 50);
|
||||
let dat = new DataView(stl.buffer);
|
||||
let pos = 84;
|
||||
header.split('').forEach((c,i) => {
|
||||
dat.setUint8(i, c.charCodeAt(0));
|
||||
});
|
||||
// todo put Mesh:Tool info in header
|
||||
dat.setInt32(80, vtot/3, true);
|
||||
for (let rec of recs) {
|
||||
let { id, matrix, file, varr } = rec;
|
||||
for (let i=0, l=varr.length; i<l;) {
|
||||
let p0 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
||||
let p1 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
||||
let p2 = new Vector3(varr[i++], varr[i++], varr[i++]);
|
||||
let norm = computeFaceNormal(p0, p1, p2);
|
||||
dat.setFloat32(pos + 0, norm.x, true);
|
||||
dat.setFloat32(pos + 4, norm.y, true);
|
||||
dat.setFloat32(pos + 8, norm.z, true);
|
||||
dat.setFloat32(pos + 12, p0.x, true);
|
||||
dat.setFloat32(pos + 16, p0.y, true);
|
||||
dat.setFloat32(pos + 20, p0.z, true);
|
||||
dat.setFloat32(pos + 24, p1.x, true);
|
||||
dat.setFloat32(pos + 28, p1.y, true);
|
||||
dat.setFloat32(pos + 32, p1.z, true);
|
||||
dat.setFloat32(pos + 36, p2.x, true);
|
||||
dat.setFloat32(pos + 40, p2.y, true);
|
||||
dat.setFloat32(pos + 44, p2.z, true);
|
||||
pos += 50;
|
||||
}
|
||||
}
|
||||
return stl;
|
||||
return stlEncode(recs, header);
|
||||
default:
|
||||
throw `invalid format "${format}"`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue