From 69ef0ff54b4ebc342adcb620abaca20f3214b767 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Tue, 23 Dec 2025 21:20:11 -0500 Subject: [PATCH] consolidate stl/obj load/encode into src/load classes --- src/kiri/core/selection.js | 68 +++++++++------------------ src/load/obj.js | 22 +++++++++ src/load/stl.js | 70 ++++++++++++++++++++++++++++ src/mesh/work.js | 94 +++----------------------------------- 4 files changed, 119 insertions(+), 135 deletions(-) diff --git a/src/kiri/core/selection.js b/src/kiri/core/selection.js index 6ef28db1..2cc97abf 100644 --- a/src/kiri/core/selection.js +++ b/src/kiri/core/selection.js @@ -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 { let { position } = out.geo.attributes; let pvals = position.array; - for (let i=0, il=position.count; i 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) { diff --git a/src/load/obj.js b/src/load/obj.js index 4a8b1f01..ec767b0e 100644 --- a/src/load/obj.js +++ b/src/load/obj.js @@ -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 -- 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() { diff --git a/src/mesh/work.js b/src/mesh/work.js index 214854d4..091a0faa 100644 --- a/src/mesh/work.js +++ b/src/mesh/work.js @@ -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 { - 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 { - 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