UNPKG

geom-parse-stl

Version:

Parse a STL (StereoLithography) ASCII string, ArrayBuffer or ArrayBuffer with ASCII data, and return a simplicial complex.

141 lines (118 loc) 4.27 kB
/** @module geom-parse-stl */ import typedArrayConstructor from "typed-array-constructor"; /** * Parse a STL ASCII string and return a simplicial complex. * * @param {string} asciiString * @returns {import("./types.js").SimplicialComplex} */ function parseStlAscii(asciiString) { const facetCount = asciiString.split("endfacet").length - 1; const size = facetCount * 3; const positions = new Float32Array(size * 3); const faceNormals = new Float32Array(size); const cells = new (typedArrayConstructor(size))(size); let name; const lines = asciiString.split("\n"); let facetIndex = 0; let facetOffset = 0; let j = 0; for (let i = 0; i < lines.length; i++) { const [keyword, ...tokens] = lines[i] .replaceAll(/[\s]+/g, " ") .trim() .split(" ") .filter((part) => part !== ""); switch (keyword) { case "solid": name = tokens.join(" "); break; case "facet": facetOffset = facetIndex * 3; if (tokens[0] === "normal") { faceNormals[facetOffset] = parseFloat(tokens[1]); faceNormals[facetOffset + 1] = parseFloat(tokens[2]); faceNormals[facetOffset + 2] = parseFloat(tokens[3]); } cells[facetOffset] = facetOffset; cells[facetOffset + 1] = facetOffset + 1; cells[facetOffset + 2] = facetOffset + 2; break; case "outer": j = 0; break; case "vertex": { const vertexOffset = facetOffset * 3 + j * 3; positions[vertexOffset] = parseFloat(tokens[0]); positions[vertexOffset + 1] = parseFloat(tokens[1]); positions[vertexOffset + 2] = parseFloat(tokens[2]); j++; break; } case "endfacet": facetIndex++; break; case "endloop": case "endsolid": break; default: console.warn(`Unrecognized keyword "${keyword}"`); } } return { positions, faceNormals, cells, name }; } const HEADER_SIZE = 80; const FACET_COUNT_SIZE = 4; const FACET_SIZE = 50; const readUInt32LE = (dataView, offset = 0) => dataView.getUint32(offset, true); const readFloat32LE = (dataView, offset = 0) => dataView.getFloat32(offset, true); /** * Parse a STL ArrayBuffer or ArrayBuffer with ASCII data and return a simplicial complex. * * @param {ArrayBuffer} arrayBuffer * @returns {import("./types.js").SimplicialComplex} */ function parseStlBinary(arrayBuffer) { const dataView = new DataView(arrayBuffer); const facetCount = readUInt32LE(dataView, HEADER_SIZE); let byteOffset = HEADER_SIZE + FACET_COUNT_SIZE; if (byteOffset + facetCount * FACET_SIZE !== arrayBuffer.byteLength) { return parseStlAscii(new TextDecoder("utf-8").decode(arrayBuffer)); } const size = facetCount * 3; const positions = new Float32Array(size * 3); const faceNormals = new Float32Array(size); const cells = new (typedArrayConstructor(size))(size); let facetIndex = 0; for (let i = 0; i < facetCount; i++) { const facetOffset = facetIndex * 3; faceNormals[facetOffset] = readFloat32LE(dataView, byteOffset); faceNormals[facetOffset + 1] = readFloat32LE(dataView, byteOffset + 4); faceNormals[facetOffset + 2] = readFloat32LE(dataView, byteOffset + 8); byteOffset += 12; for (let j = 0; j < 3; j++) { const vertexOffset = facetOffset * 3 + j * 3; positions[vertexOffset] = readFloat32LE(dataView, byteOffset); positions[vertexOffset + 1] = readFloat32LE(dataView, byteOffset + 4); positions[vertexOffset + 2] = readFloat32LE(dataView, byteOffset + 8); byteOffset += 12; } cells[facetOffset] = facetOffset; cells[facetOffset + 1] = facetOffset + 1; cells[facetOffset + 2] = facetOffset + 2; byteOffset += 2; facetIndex++; } return { positions, faceNormals, cells }; } /** * Parse a STL (StereoLithography) ASCII string, ArrayBuffer or ArrayBuffer with ASCII data, and return a simplicial complex. * * @see https://paulbourke.net/dataformats/stl/ * @param {string|ArrayBuffer} stl * @returns {import("./types.js").SimplicialComplex} */ const parseStl = (stl) => typeof stl === "string" ? parseStlAscii(stl) : parseStlBinary(stl); export { parseStl, parseStlAscii, parseStlBinary };