mirror of
https://github.com/CNCKitchen/stlTexturizer.git
synced 2026-04-07 22:11:32 +00:00
perf: replace Three.js STLExporter with direct binary writer
Write binary STL directly from BufferGeometry typed arrays using Uint8Array.set() bulk copies. Eliminates the Three.js STLExporter overhead: no Mesh/Material creation, no identity matrix multiplication, no redundant normal recomputation, no per-float DataView calls.
This commit is contained in:
+55
-16
@@ -1,24 +1,65 @@
|
|||||||
import * as THREE from 'three';
|
|
||||||
import { STLExporter } from 'three/addons/exporters/STLExporter.js';
|
|
||||||
|
|
||||||
const exporter = new STLExporter();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export a BufferGeometry as a binary STL file download.
|
* Fast binary STL exporter — writes directly from BufferGeometry arrays.
|
||||||
*
|
*
|
||||||
* @param {THREE.BufferGeometry} geometry
|
* Eliminates Three.js STLExporter overhead:
|
||||||
|
* - No Mesh/Material creation
|
||||||
|
* - No identity matrix multiplication per vertex
|
||||||
|
* - No redundant normal recomputation
|
||||||
|
* - Bulk Uint8Array.set() instead of per-float DataView calls
|
||||||
|
*
|
||||||
|
* @param {THREE.BufferGeometry} geometry – non-indexed with position + normal
|
||||||
* @param {string} [filename]
|
* @param {string} [filename]
|
||||||
*/
|
*/
|
||||||
export function exportSTL(geometry, filename = 'textured.stl') {
|
export function exportSTL(geometry, filename = 'textured.stl') {
|
||||||
// Geometry is already in the original Z-up orientation (the loader never rotates it;
|
const posArr = geometry.attributes.position.array;
|
||||||
// the viewer uses a Z-up camera instead). Export as-is so slicers receive the correct pose.
|
const norArr = geometry.attributes.normal
|
||||||
const mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial());
|
? geometry.attributes.normal.array
|
||||||
const result = exporter.parse(mesh, { binary: true });
|
: null;
|
||||||
|
const triCount = (posArr.length / 9) | 0;
|
||||||
|
|
||||||
// result is an ArrayBuffer in binary mode
|
// Binary STL: 80-byte header + 4-byte tri count + 50 bytes per triangle
|
||||||
const blob = new Blob([result], { type: 'application/octet-stream' });
|
const bufLen = 84 + 50 * triCount;
|
||||||
|
const buffer = new ArrayBuffer(bufLen);
|
||||||
|
const bytes = new Uint8Array(buffer);
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
|
||||||
|
// Header: 80 bytes (already zero-filled)
|
||||||
|
view.setUint32(80, triCount, true);
|
||||||
|
|
||||||
|
// Reinterpret source arrays as raw bytes for bulk copy
|
||||||
|
const posSrc = new Uint8Array(posArr.buffer, posArr.byteOffset, posArr.byteLength);
|
||||||
|
const norSrc = norArr
|
||||||
|
? new Uint8Array(norArr.buffer, norArr.byteOffset, norArr.byteLength)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
for (let i = 0; i < triCount; i++) {
|
||||||
|
const dst = 84 + i * 50;
|
||||||
|
const srcOff = i * 36; // 9 floats * 4 bytes
|
||||||
|
|
||||||
|
if (norSrc) {
|
||||||
|
// Normal: copy first vertex normal (12 bytes) — flat shading, all 3 identical
|
||||||
|
bytes.set(norSrc.subarray(srcOff, srcOff + 12), dst);
|
||||||
|
} else {
|
||||||
|
// Compute face normal from cross product
|
||||||
|
const b = i * 9;
|
||||||
|
const ux = posArr[b+3]-posArr[b], uy = posArr[b+4]-posArr[b+1], uz = posArr[b+5]-posArr[b+2];
|
||||||
|
const vx = posArr[b+6]-posArr[b], vy = posArr[b+7]-posArr[b+1], vz = posArr[b+8]-posArr[b+2];
|
||||||
|
const nx = uy*vz-uz*vy, ny = uz*vx-ux*vz, nz = ux*vy-uy*vx;
|
||||||
|
const len = Math.sqrt(nx*nx + ny*ny + nz*nz) || 1;
|
||||||
|
view.setFloat32(dst, nx/len, true);
|
||||||
|
view.setFloat32(dst + 4, ny/len, true);
|
||||||
|
view.setFloat32(dst + 8, nz/len, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertices: 36 bytes (3 vertices * 3 floats * 4 bytes)
|
||||||
|
bytes.set(posSrc.subarray(srcOff, srcOff + 36), dst + 12);
|
||||||
|
|
||||||
|
// Attribute byte count: 0 (already zero-filled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download
|
||||||
|
const blob = new Blob([buffer], { type: 'application/octet-stream' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = filename;
|
a.download = filename;
|
||||||
@@ -26,7 +67,5 @@ export function exportSTL(geometry, filename = 'textured.stl') {
|
|||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
|
|
||||||
// Revoke after a short delay so the download has time to start
|
|
||||||
setTimeout(() => URL.revokeObjectURL(url), 10000);
|
setTimeout(() => URL.revokeObjectURL(url), 10000);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user