const WebIFC = require( 'web-ifc/web-ifc-api-node' ) module.exports = class IFCParser { constructor() { this.api = new WebIFC.IfcAPI() } async parse( data ) { if ( this.api.wasmModule === undefined ) await this.api.Init() this.modelId = this.api.OpenModel( data, { COORDINATE_TO_ORIGIN: true, USE_FAST_BOOLS: true } ) this.projectId = this.api.GetLineIDsWithType( this.modelId, WebIFC.IFCPROJECT ).get( 0 ) this.project = this.api.GetLine( this.modelId, this.projectId, true ) this.createGeometries() this.traverse( this.project, true ) return this.project } createGeometries() { // TODO: this is where we can alreadt create speckle meshes and plop them in the db. this.rawGeo = this.api.LoadAllGeometry( this.modelId ) this.productGeo = {} for( let i = 0; i < this.rawGeo.size(); i++ ) { const mesh = this.rawGeo.get( i ) const prodId = mesh.expressID this.productGeo[prodId ] = [] for( let j = 0; j < mesh.geometries.size(); j++ ) { let geom = this.api.GetGeometry( this.modelId, mesh.geometries.get( j ).geometryExpressID ) // TODO: actually create Speckle Mesh let raw = { color: geom.color, // NOTE: material: x, y, z = rgb, w = opacity vertices: this.api.GetVertexArray( geom.GetVertexData(), geom.GetVertexDataSize() ), indices: this.api.GetIndexArray( geom.GetIndexData(), geom.GetIndexDataSize() ) } let spcklFaces = [ ] for ( let i = 0; i < raw.indices.length; i++ ) { if( i % 3 === 0 ) spcklFaces.push( 0 ) spcklFaces.push( raw.indices[i] ) } let spcklMesh = { speckle_type: 'Objects.Geometry.Mesh', units: 'm', volume: 0, area: 0, faces: spcklFaces, vertices: raw.vertices, renderMaterial: geom.color ? colorToSpeckleMaterial( geom.color.r, geom.colr.g, geom.color.b ) : undefined } // Send the mesh and swap for speckle ref this.productGeo[prodId].push( spcklMesh ) } } } traverse( element, recursive = true, depth = 0, map = {} ) { // NOTE: creates the base object. // TODO: combine with alan's value unwrapping console.log( `Traversing element ${element.expressID}; Recurse: ${recursive}; Stack ${depth}` ) depth++ if ( !element ) return element.speckle_type = element.constructor.name const spatialChildrenIds = this.getAllRelatedItemsOfType( element.expressID, WebIFC.IFCRELAGGREGATES, 'RelatingObject', 'RelatedObjects' ) element.spatialChildren = spatialChildrenIds.map( ( childId ) => this.api.GetLine( this.modelId, childId, false ) ) const childrenIds = this.getAllRelatedItemsOfType( element.expressID, WebIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE, 'RelatingStructure', 'RelatedElements' ) element.children = childrenIds.map( ( childId ) => this.api.GetLine( this.modelId, childId, false ) ) // Lookup geometry in generated geometries object console.log( `${element.constructor.name} (${element.expressID}) mesh:`, this.productGeo[element.expressID]?.length ) if ( recursive ) { element.spatialChildren.forEach( ( child ) => this.traverse( child, recursive, depth ) ) // NOTE: unsure if this is needed. element.children.forEach( ( child ) => this.traverse( child, recursive, depth ) ) } } // (c) https://github.com/agviegas/web-ifc-three/blob/907e08b5673d5e1c18261a4fceade7189d6b2db7/src/IFC/PropertyManager.ts#L110 getAllRelatedItemsOfType( elementID, type, relation, relatedProperty ) { const lines = this.api.GetLineIDsWithType( this.modelId, type ) const IDs = [] for ( let i = 0; i < lines.size(); i++ ) { const relID = lines.get( i ) const rel = this.api.GetLine( this.modelId, relID ) const relatedItems = rel[relation] let foundElement = false if ( Array.isArray( relatedItems ) ) { const values = relatedItems.map( ( item ) => item.value ) foundElement = values.includes( elementID ) } else foundElement = ( relatedItems.value === elementID ) if ( foundElement ) { const element = rel[relatedProperty] if ( !Array.isArray( element ) ) IDs.push( element.value ) else element.forEach( ( ele ) => IDs.push( ele.value ) ) } } return IDs } static unwrapValues( entity ) { const unwraped = {} Object.keys( entity ).forEach( ( key ) => { unwraped[key] = IFCParser.parseValue( entity[key] ) } ) return unwraped } static parseValue( entity ) { if ( !entity ) return // NOTE: this stands in for an express reference. if ( entity.value && entity.type !== 5 ) { return entity.value } if ( Array.isArray( entity ) ) { const returnValue = entity.map( ( ent ) => IFCParser.unwrapValues( ent ) ) return returnValue } if ( typeof entity === 'object' ) { const returnValue = {} Object.assign( returnValue, entity ) returnValue.speckle_type = 'Base' return returnValue } return entity } } function colorToSpeckleMaterial( r,g,b ) { function rgba2int( r, g, b, a ) { if ( typeof r === 'string' && arguments.length === 1 ) { const [ r1, g1, b1, a1 ] = r .match( /^rgba?\((\d+\.?\d*)[,\s]*(\d+\.?\d*)[,\s]*(\d+\.?\d*)[,\s\/]*(.+)?\)$/ ) .slice( 1 ); [ r, g, b ] = [ r1, g1, b1 ].map( ( v ) => parseFloat( v ) ) a = a1 ? a1.endsWith( '%' ) ? parseInt( a1.substring( 0, a1.length - 1 ), 10 ) / 100 : parseFloat( a1 ) : null } return a ? ( ( r & 0xff ) << 24 ) + ( ( g & 0xff ) << 16 ) + ( ( b & 0xff ) << 8 ) + ( Math.floor( a * 0xff ) & 0xff ) : ( ( r & 0xff ) << 16 ) + ( ( g & 0xff ) << 8 ) + ( b & 0xff ) } return { diffuse: rgba2int( r, g, b ), opacity: 1, emissive: rgba2int( 0, 0, 0 ), metalness: 0, roughness: 1, speckle_type: 'Objects.Other.RenderMaterial' } }