new model viewer
Build and Push Docker Image / build (push) Successful in 20s

This commit is contained in:
jason
2026-04-23 09:06:40 -05:00
parent 0a47b90e21
commit 18b3463487
3 changed files with 86 additions and 25 deletions
+63 -9
View File
@@ -179,6 +179,24 @@ function makeMaterial(color: [number, number, number] | null): THREE.MeshStandar
})
}
// ---- Binary decode helpers (geometry v2) ---------------------------------
function b64ToFloat32(b64: string): Float32Array {
const bin = atob(b64)
const buf = new ArrayBuffer(bin.length)
const u8 = new Uint8Array(buf)
for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i)
return new Float32Array(buf)
}
function b64ToUint32(b64: string): Uint32Array {
const bin = atob(b64)
const buf = new ArrayBuffer(bin.length)
const u8 = new Uint8Array(buf)
for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i)
return new Uint32Array(buf)
}
// ---- STEP/STP loader (geometry JSON, pre-processed server-side) ----------
async function loadStepGeometry(): Promise<void> {
@@ -193,28 +211,64 @@ async function loadStepGeometry(): Promise<void> {
throw new Error(`Could not load geometry (HTTP ${res.status})`)
}
setLoading('Building 3D scene…')
const data = await res.json() as GeometryFile
// Stream the response body so there is visible activity during large downloads
let text: string
if (res.body) {
const reader = res.body.getReader()
const chunks: Uint8Array[] = []
let loaded = 0
while (true) {
const { done, value } = await reader.read()
if (done) break
chunks.push(value)
loaded += value.byteLength
setLoading(`Downloading geometry… ${Math.round(loaded / 1024)} KB`)
}
setLoading('Parsing geometry…')
text = new TextDecoder().decode(
chunks.reduce((acc, c) => { const m = new Uint8Array(acc.length + c.length); m.set(acc); m.set(c, acc.length); return m }, new Uint8Array(0))
)
} else {
text = await res.text()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data = JSON.parse(text) as GeometryFile & { version: number; meshes: any[] }
if (!data.meshes || data.meshes.length === 0) {
throw new Error('Geometry file contains no meshes.')
}
setLoading('Building 3D scene…')
const group = new THREE.Group()
const isV2 = data.version >= 2
for (const mesh of data.meshes) {
const geo = new THREE.BufferGeometry()
geo.setAttribute('position', new THREE.Float32BufferAttribute(mesh.positions, 3))
if (mesh.normals && mesh.normals.length > 0) {
geo.setAttribute('normal', new THREE.Float32BufferAttribute(mesh.normals, 3))
let positions: Float32Array
let normals: Float32Array | null
let indices: Uint32Array | null
if (isV2) {
positions = b64ToFloat32(mesh.positions)
normals = mesh.normals ? b64ToFloat32(mesh.normals) : null
indices = mesh.indices ? b64ToUint32(mesh.indices) : null
} else {
// Legacy v1: plain number arrays
positions = new Float32Array(mesh.positions as number[])
normals = mesh.normals ? new Float32Array(mesh.normals as number[]) : null
indices = mesh.indices ? new Uint32Array(mesh.indices as number[]) : null
}
if (mesh.indices && mesh.indices.length > 0) {
geo.setIndex(new THREE.Uint32BufferAttribute(mesh.indices, 1))
geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))
if (normals && normals.length > 0) {
geo.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3))
}
if (!mesh.normals || mesh.normals.length === 0) {
if (indices && indices.length > 0) {
geo.setIndex(new THREE.Uint32BufferAttribute(indices, 1))
}
if (!normals || normals.length === 0) {
geo.computeVertexNormals()
}