c28ca65557
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
120 lines
4.0 KiB
TypeScript
120 lines
4.0 KiB
TypeScript
import * as THREE from 'three';
|
|
import * as WebIFC from 'web-ifc';
|
|
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
|
|
|
|
const WASM_PATH = 'https://unpkg.com/web-ifc@0.0.57/';
|
|
|
|
/**
|
|
* Convert an IFC file (ArrayBuffer) into a GLB Blob using web-ifc + GLTFExporter.
|
|
*/
|
|
export async function convertIFCtoGLB(
|
|
buffer: ArrayBuffer,
|
|
fileName: string
|
|
): Promise<{ blob: Blob; fileName: string; fileSize: number }> {
|
|
const ifcApi = new WebIFC.IfcAPI();
|
|
ifcApi.SetWasmPath(WASM_PATH, true);
|
|
await ifcApi.Init();
|
|
|
|
const data = new Uint8Array(buffer);
|
|
const modelID = ifcApi.OpenModel(data);
|
|
|
|
const scene = new THREE.Scene();
|
|
const materials: Map<number, THREE.MeshStandardMaterial> = new Map();
|
|
|
|
ifcApi.StreamAllMeshes(modelID, (mesh: WebIFC.FlatMesh) => {
|
|
const placedGeometries = mesh.geometries;
|
|
const expressID = (mesh as unknown as { expressID?: number }).expressID ?? 0;
|
|
|
|
// One Group per IfcProduct → represents a single "element" (beam, plate…).
|
|
// userData is preserved by GLTFExporter as `extras` and survives reload.
|
|
const elementGroup = new THREE.Group();
|
|
elementGroup.name = `ifc_${expressID}`;
|
|
elementGroup.userData = { ifcElement: true, ifcId: expressID };
|
|
|
|
for (let i = 0; i < placedGeometries.size(); i++) {
|
|
const placedGeometry = placedGeometries.get(i);
|
|
const ifcGeometry = ifcApi.GetGeometry(modelID, placedGeometry.geometryExpressID);
|
|
|
|
const verts = ifcApi.GetVertexArray(
|
|
ifcGeometry.GetVertexData(),
|
|
ifcGeometry.GetVertexDataSize()
|
|
);
|
|
const indices = ifcApi.GetIndexArray(
|
|
ifcGeometry.GetIndexData(),
|
|
ifcGeometry.GetIndexDataSize()
|
|
);
|
|
|
|
const geometry = new THREE.BufferGeometry();
|
|
const positionArray = new Float32Array(verts.length / 2);
|
|
const normalArray = new Float32Array(verts.length / 2);
|
|
|
|
for (let j = 0; j < verts.length; j += 6) {
|
|
const idx = j / 6;
|
|
positionArray[idx * 3] = verts[j];
|
|
positionArray[idx * 3 + 1] = verts[j + 1];
|
|
positionArray[idx * 3 + 2] = verts[j + 2];
|
|
normalArray[idx * 3] = verts[j + 3];
|
|
normalArray[idx * 3 + 1] = verts[j + 4];
|
|
normalArray[idx * 3 + 2] = verts[j + 5];
|
|
}
|
|
|
|
geometry.setAttribute('position', new THREE.BufferAttribute(positionArray, 3));
|
|
geometry.setAttribute('normal', new THREE.BufferAttribute(normalArray, 3));
|
|
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
|
|
const color = placedGeometry.color;
|
|
const colorKey = (color.x * 255) << 16 | (color.y * 255) << 8 | (color.z * 255);
|
|
let material = materials.get(colorKey);
|
|
if (!material) {
|
|
material = new THREE.MeshStandardMaterial({
|
|
color: new THREE.Color(color.x, color.y, color.z),
|
|
metalness: 0.2,
|
|
roughness: 0.8,
|
|
transparent: color.w < 1,
|
|
opacity: color.w,
|
|
side: THREE.DoubleSide,
|
|
});
|
|
materials.set(colorKey, material);
|
|
}
|
|
|
|
const mesh3 = new THREE.Mesh(geometry, material);
|
|
mesh3.userData = { ifcId: expressID };
|
|
|
|
const matrix = new THREE.Matrix4();
|
|
matrix.fromArray(placedGeometry.flatTransformation);
|
|
mesh3.applyMatrix4(matrix);
|
|
|
|
elementGroup.add(mesh3);
|
|
ifcGeometry.delete();
|
|
}
|
|
|
|
if (elementGroup.children.length > 0) scene.add(elementGroup);
|
|
});
|
|
|
|
ifcApi.CloseModel(modelID);
|
|
|
|
// Export to GLB
|
|
const exporter = new GLTFExporter();
|
|
const glb = await new Promise<ArrayBuffer>((resolve, reject) => {
|
|
exporter.parse(
|
|
scene,
|
|
(result) => resolve(result as ArrayBuffer),
|
|
(error) => reject(error),
|
|
{ binary: true }
|
|
);
|
|
});
|
|
|
|
const glbFileName = fileName.replace(/\.ifc$/i, '.glb');
|
|
const blob = new Blob([glb], { type: 'model/gltf-binary' });
|
|
|
|
// Cleanup
|
|
materials.forEach((m) => m.dispose());
|
|
scene.traverse((child) => {
|
|
if (child instanceof THREE.Mesh) {
|
|
child.geometry.dispose();
|
|
}
|
|
});
|
|
|
|
return { blob, fileName: glbFileName, fileSize: blob.size };
|
|
}
|