Files
speckle-server/packages/ifc-parser/parser.js
T
2021-06-24 10:26:29 +02:00

180 lines
5.9 KiB
JavaScript

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'
}
}