//Docs: https://agviegas.github.io/ifcjs-docs/#/ import * as WebIFC from './ifc/web-ifc-api.js'; import { FileLoader, Loader, Mesh, Color, MeshBasicMaterial, MeshLambertMaterial, DoubleSide, Matrix4, BufferGeometry, BufferAttribute, } from 'three'; import {BufferGeometryUtils} from "../utils/BufferGeometryUtils.js" const ifcAPI = new WebIFC.IfcAPI(); class IFCLoader extends Loader { constructor( manager ) { super( manager ); this.modelID = 0; this.mapFaceindexID = {}; this.mapIDGeometry = {}; this.selectedObjects = []; this.highlightMaterial = new MeshBasicMaterial({ color: 0xff0000, depthTest: false, side: DoubleSide }); } load( url, onLoad, onProgress, onError ) { const scope = this; const loader = new FileLoader( scope.manager ); loader.setPath( scope.path ); loader.setResponseType( 'arraybuffer' ); loader.setRequestHeader( scope.requestHeader ); loader.setWithCredentials( scope.withCredentials ); loader.load( url, async function ( buffer ) { try { onLoad( await scope.parse( buffer ) ); } catch ( e ) { if ( onError ) { onError( e ); } else { console.error( e ); } scope.manager.itemError( url ); } }, onProgress, onError ); } setWasmPath( path ) { ifcAPI.SetWasmPath( path ); } getExpressId( faceIndex ) { for (let index in this.mapFaceindexID) { if (parseInt(index) >= faceIndex) return this.mapFaceindexID[index]; } return -1; } highlightItems( expressIds, scene, material = this.highlightMaterial ) { this.removePreviousSelection(scene); expressIds.forEach((id) => { if (!this.mapIDGeometry[id]) return; var mesh = new Mesh(this.mapIDGeometry[id], material); mesh.renderOrder = 1; scene.add(mesh); this.selectedObjects.push(mesh); return; }); } removePreviousSelection( scene ) { if (this.selectedObjects.length > 0){ this.selectedObjects.forEach((object) => scene.remove(object)); } } setItemsVisibility( expressIds, geometry, visible = false ) { this.setupVisibility(geometry); var previous = 0; for (var current in this.mapFaceindexID) { if (expressIds.includes(this.mapFaceindexID[current])) { for (var i = previous; i <= current; i++) this.setVertexVisibility(geometry, i, visible); } previous = current; } geometry.attributes.visibility.needsUpdate = true; } setVertexVisibility( geometry, index, visible ) { var isVisible = visible ? 0 : 1; var geoIndex = geometry.index.array; geometry.attributes.visibility.setX(geoIndex[3 * index], isVisible); geometry.attributes.visibility.setX(geoIndex[3 * index + 1], isVisible); geometry.attributes.visibility.setX(geoIndex[3 * index + 2], isVisible); } setupVisibility( geometry ) { if (!geometry.attributes.visibility) { var visible = new Float32Array(geometry.getAttribute('position').count); geometry.setAttribute('visibility', new BufferAttribute(visible, 1)); } } getItemProperties( elementID, all = false ) { const properties = ifcAPI.GetLine(this.modelID, elementID); if (all) { const propSetIds = this.getAllRelatedItemsOfType(elementID, WebIFC.IFCRELDEFINESBYPROPERTIES, "RelatedObjects", "RelatingPropertyDefinition"); properties.hasPropertySets = propSetIds.map((id) => ifcAPI.GetLine(this.modelID, id, true)); const typeId = this.getAllRelatedItemsOfType(elementID, WebIFC.IFCRELDEFINESBYTYPE, "RelatedObjects", "RelatingType"); properties.hasType = typeId.map((id) => ifcAPI.GetLine(this.modelID, id, true)); } // properties.type = properties.constructor.name; return properties; } getSpatialStructure() { let lines = ifcAPI.GetLineIDsWithType(this.modelID, WebIFC.IFCPROJECT); let ifcProjectId = lines.get(0); let ifcProject = ifcAPI.GetLine(this.modelID, ifcProjectId); this.getAllSpatialChildren(ifcProject); return ifcProject; } getAllSpatialChildren( spatialElement ) { const id = spatialElement.expressID; const spatialChildrenID = this.getAllRelatedItemsOfType(id, WebIFC.IFCRELAGGREGATES, "RelatingObject", "RelatedObjects"); spatialElement.hasSpatialChildren = spatialChildrenID.map((id) => ifcAPI.GetLine(this.modelID, id, false)); spatialElement.hasChildren = this.getAllRelatedItemsOfType(id, WebIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE, "RelatingStructure", "RelatedElements"); spatialElement.hasSpatialChildren.forEach(child => this.getAllSpatialChildren(child)); } getAllRelatedItemsOfType ( elementID, type, relation, relatedProperty ) { const lines = ifcAPI.GetLineIDsWithType(this.modelID, type); const IDs = []; for (let i = 0; i < lines.size(); i++) { const relID = lines.get(i); const rel = ifcAPI.GetLine(this.modelID, relID); const relatedItems = rel[relation]; let foundElement = false; if (Array.isArray(relatedItems)){ relatedItems.forEach((relID) => { if (relID.value === elementID) foundElement = true; }); } else foundElement = (relatedItems.value === elementID); if (foundElement) { var element = rel[relatedProperty]; if (!Array.isArray(element)) IDs.push(element.value); else element.forEach(ele => IDs.push(ele.value)) } } return IDs; } async parse( buffer ) { const geometryByMaterials = {}; const mapIDGeometry = this.mapIDGeometry; const mapFaceindexID = this.mapFaceindexID; if ( ifcAPI.wasmModule === undefined ) { await ifcAPI.Init(); } const data = new Uint8Array( buffer ); this.modelID = ifcAPI.OpenModel( 'example.ifc', data ); return loadAllGeometry( this.modelID ); function loadAllGeometry(modelID) { saveAllPlacedGeometriesByMaterial(modelID); return generateAllGeometriesByMaterial(); } function generateAllGeometriesByMaterial() { const { materials, geometries } = getMaterialsAndGeometries(); const allGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries, true); return new Mesh(allGeometry, materials); } function getMaterialsAndGeometries() { const materials = []; const geometries = []; let totalFaceCount = 0; for (let i in geometryByMaterials) { materials.push(geometryByMaterials[i].material); const currentGeometries = geometryByMaterials[i].geometry; geometries.push(BufferGeometryUtils.mergeBufferGeometries(currentGeometries)); for (let j in geometryByMaterials[i].indices) { const globalIndex = parseInt(j, 10) + parseInt(totalFaceCount, 10); mapFaceindexID[globalIndex] = geometryByMaterials[i].indices[j]; } totalFaceCount += geometryByMaterials[i].lastIndex; } return { materials, geometries }; } function saveAllPlacedGeometriesByMaterial(modelID) { const flatMeshes = ifcAPI.LoadAllGeometry(modelID); for (let i = 0; i < flatMeshes.size(); i++) { const flatMesh = flatMeshes.get(i); const productId = flatMesh.expressID; const placedGeometries = flatMesh.geometries; for (let j = 0; j < placedGeometries.size(); j++) { savePlacedGeometryByMaterial(modelID, placedGeometries.get(j), productId); } } } function savePlacedGeometryByMaterial(modelID, placedGeometry, productId) { const geometry = getBufferGeometry(modelID, placedGeometry); geometry.computeVertexNormals(); const matrix = getMeshMatrix(placedGeometry.flatTransformation); geometry.applyMatrix4(matrix); storeGeometryForHighlight(productId, geometry); saveGeometryByMaterial(geometry, placedGeometry, productId); } function getBufferGeometry(modelID, placedGeometry) { const geometry = ifcAPI.GetGeometry(modelID, placedGeometry.geometryExpressID); const verts = ifcAPI.GetVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize()); const indices = ifcAPI.GetIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize()); return ifcGeometryToBuffer(verts, indices); } function getMeshMatrix(matrix) { const mat = new Matrix4(); mat.fromArray(matrix); return mat; } function storeGeometryForHighlight(productId, geometry) { if (!mapIDGeometry[productId]) { mapIDGeometry[productId] = geometry; return; } const geometries = [mapIDGeometry[productId], geometry]; mapIDGeometry[productId] = BufferGeometryUtils.mergeBufferGeometries(geometries, true); } function ifcGeometryToBuffer(vertexData, indexData) { const geometry = new BufferGeometry(); const { vertices, normals } = extractVertexData(vertexData); geometry.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)); geometry.setAttribute('normal', new BufferAttribute(new Float32Array(normals), 3)); geometry.setIndex(new BufferAttribute(indexData, 1)); return geometry; } function extractVertexData(vertexData) { const vertices = []; const normals = []; let isNormalData = false; for (let i = 0; i < vertexData.length; i++) { isNormalData ? normals.push(vertexData[i]) : vertices.push(vertexData[i]); if ((i + 1) % 3 == 0) isNormalData = !isNormalData; } return { vertices, normals }; } function saveGeometryByMaterial(geometry, placedGeometry, productId) { const color = placedGeometry.color; const id = `${color.x}${color.y}${color.z}${color.w}`; createMaterial(id, color); const currentGeometry = geometryByMaterials[id]; currentGeometry.geometry.push(geometry); currentGeometry.lastIndex += geometry.index.count / 3; currentGeometry.indices[currentGeometry.lastIndex] = productId; } function createMaterial(id, color) { if (!geometryByMaterials[id]){ const col = new Color(color.x, color.y, color.z); const newMaterial = new MeshLambertMaterial({ color: col, side: DoubleSide }); newMaterial.onBeforeCompile = materialHider; newMaterial.transparent = color.w !== 1; if (newMaterial.transparent) newMaterial.opacity = color.w; geometryByMaterials[id] = initializeGeometryByMaterial(newMaterial); } } function initializeGeometryByMaterial(newMaterial) { return { material: newMaterial, geometry: [], indices: {}, lastIndex: 0 }; } function materialHider(shader) { shader.vertexShader = ` attribute float sizes; attribute float visibility; varying float vVisible; ${shader.vertexShader}`.replace( `#include `, `#include vVisible = visibility; ` ); shader.fragmentShader = ` varying float vVisible; ${shader.fragmentShader}`.replace( `#include `, ` if (vVisible > 0.5) discard; #include ` ); } } } export { IFCLoader };