Files
speckle-server/packages/fileimport-service/ifc/parser.js
T
2022-03-30 12:54:24 +03:00

373 lines
12 KiB
JavaScript

const WebIFC = require('web-ifc/web-ifc-api-node')
const ServerAPI = require('./api.js')
module.exports = class IFCParser {
constructor({ serverApi }) {
this.api = new WebIFC.IfcAPI()
this.serverApi = serverApi || new ServerAPI()
}
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.project.__closure = {}
this.cache = {}
this.closureCache = {}
// Steps: create and store in speckle all the geometries (meshes) from this project and store them
// as reference objects in this.productGeo
this.productGeo = {}
await this.createGeometries()
console.log(`Geometries created: ${Object.keys(this.productGeo).length} meshes.`)
// Lastly, traverse the ifc project object and parse it into something friendly; as well as
// replace all its geometries with actual references to speckle meshes from the productGeo map
await this.traverse(this.project, true, 0)
let id = await this.serverApi.saveObject(this.project)
return { id, tCount: Object.keys(this.project.__closure).length }
}
async createGeometries() {
this.rawGeo = this.api.LoadAllGeometry(this.modelId)
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 placedGeom = mesh.geometries.get(j)
let geom = this.api.GetGeometry(this.modelId, placedGeom.geometryExpressID)
let matrix = placedGeom.flatTransformation
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())
}
const { vertices } = this.extractVertexData(raw.vertices)
for (let k = 0; k < vertices.length; k += 3) {
let x = vertices[k],
y = vertices[k + 1],
z = vertices[k + 2]
vertices[k] = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12]
vertices[k + 1] =
(matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14]) * -1
vertices[k + 2] = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13]
}
// Since all faces are triangles, we must add a `0` before each group of 3.
let spcklFaces = []
for (let i = 0; i < raw.indices.length; i++) {
if (i % 3 === 0) spcklFaces.push(0)
spcklFaces.push(raw.indices[i])
}
// Create a propper Speckle Mesh
let spcklMesh = {
speckle_type: 'Objects.Geometry.Mesh',
units: 'm',
volume: 0,
area: 0,
faces: spcklFaces,
vertices: Array.from(vertices),
renderMaterial: placedGeom.color
? this.colorToMaterial(placedGeom.color)
: null
}
let id = await this.serverApi.saveObject(spcklMesh)
let ref = { speckle_type: 'reference', referencedId: id }
this.productGeo[prodId].push(ref)
}
}
}
async traverse(
element,
recursive = true,
depth = 0,
specialTypes = [
{ type: 'IfcProject', key: 'Name' },
{ type: 'IfcBuilding', key: 'Name' },
{ type: 'IfcSite', key: 'Name' }
]
) {
// Fast exit if null/undefined
if (!element) return
// If array, traverse all items in it.
if (Array.isArray(element)) {
return await Promise.all(
element.map(
async (el) => await this.traverse(el, recursive, depth + 1, specialTypes)
)
)
}
// If it has no expressID, its either a simple type or a { type, value } object.
if (!element.expressID) {
return await Promise.resolve(
element.value !== null && element.value !== undefined ? element.value : element
)
}
if (this.cache[element.expressID.toString()])
return this.cache[element.expressID.toString()]
// If you got here -> It's an IFC Element: create base object, upload and return ref.
// console.log( `Traversing element ${element.expressID}; Recurse: ${recursive}; Stack ${depth}` )
// Traverse all key/value pairs first.
for (let key of Object.keys(element)) {
element[key] = await this.traverse(
element[key],
recursive,
depth + 1,
specialTypes
)
}
// Assign speckle_type and empty closure table.
element.speckle_type = element.constructor.name
element.__closure = {}
// Find spatial children and assign to element
const spatialChildrenIds = this.getAllRelatedItemsOfType(
element.expressID,
WebIFC.IFCRELAGGREGATES,
'RelatingObject',
'RelatedObjects'
)
if (spatialChildrenIds.length > 0)
element.rawSpatialChildren = spatialChildrenIds.map((childId) =>
this.api.GetLine(this.modelId, childId, true)
)
// Find children and populate element
const childrenIds = this.getAllRelatedItemsOfType(
element.expressID,
WebIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE,
'RelatingStructure',
'RelatedElements'
)
if (childrenIds.length > 0)
element.rawChildren = childrenIds.map((childId) =>
this.api.GetLine(this.modelId, childId, true)
)
// Find related property sets
const psetsIds = this.getAllRelatedItemsOfType(
element.expressID,
WebIFC.IFCRELDEFINESBYPROPERTIES,
'RelatingPropertyDefinition',
'RelatedObjects'
)
if (psetsIds.length > 0)
element.rawPsets = psetsIds.map((childId) =>
this.api.GetLine(this.modelId, childId, true)
)
// Find related type properties
const typePropsId = this.getAllRelatedItemsOfType(
element.expressID,
WebIFC.IFCRELDEFINESBYTYPE,
'RelatingType',
'RelatedObjects'
)
if (typePropsId.length > 0)
element.rawTypeProps = typePropsId.map((childId) =>
this.api.GetLine(this.modelId, childId, true)
)
// Lookup geometry in generated geometries object
if (this.productGeo[element.expressID]) {
element['@displayValue'] = this.productGeo[element.expressID]
this.productGeo[element.expressID].forEach((ref) => {
this.project.__closure[ref.referencedId.toString()] = depth
element.__closure[ref.referencedId.toString()] = 1
})
}
const isSpecial = specialTypes.find((t) => t.type === element.speckle_type)
// Recurse all children
if (recursive) {
await this.processSubElements(
element,
'rawSpatialChildren',
'spatialChildren',
isSpecial,
recursive,
depth,
specialTypes
)
await this.processSubElements(
element,
'rawChildren',
'children',
isSpecial,
recursive,
depth,
specialTypes
)
await this.processSubElements(
element,
'rawPsets',
'propertySets',
false,
recursive,
depth,
specialTypes
)
await this.processSubElements(
element,
'rawTypeProps',
'typeProps',
false,
recursive,
depth,
specialTypes
)
if (
element.children ||
element.spatialChildren ||
element.propertySets ||
element.typeProps
) {
console.log(
`${element.constructor.name} ${element.GlobalId}:\n\tchildren count: ${
element.children ? element.children.length : '0'
};\n\tspatial children count: ${
element.spatialChildren ? element.spatialChildren.length : '0'
};\n\tproperty sets count: ${
element.propertySets ? element.propertySets.length : 0
};\n\ttype properties: ${element.typeProps ? element.typeProps.length : 0}`
)
}
}
if (
this.productGeo[element.expressID] ||
element.spatialChildren ||
element.children
) {
let id = await this.serverApi.saveObject(element)
let ref = { speckle_type: 'reference', referencedId: id }
this.cache[element.expressID.toString()] = ref
this.closureCache[element.expressID.toString()] = element.__closure
return ref
} else {
this.cache[element.expressID.toString()] = element
this.closureCache[element.expressID.toString()] = element.__closure
return element
}
}
async processSubElements(
element,
key,
newKey,
isSpecial,
recursive,
depth,
specialTypes
) {
if (element[key]) {
if (!isSpecial) element[newKey] = []
let childCount = {}
for (let child of element[key]) {
let res = await this.traverse(child, recursive, depth + 1, specialTypes)
if (res.referencedId) {
if (isSpecial) {
let name = child[isSpecial.key]
if (!name || name.length === 0) name = 'Undefined'
if (!childCount[name]) childCount[name] = 0
if (childCount[name] > 0) name += '-' + childCount[name]++
element[name] = res
} else element[newKey].push(res)
this.project.__closure[res.referencedId.toString()] = depth
element.__closure[res.referencedId.toString()] = 1
// adds to parent (this element) the child's closure tree.
if (this.closureCache[child.expressID.toString()]) {
for (let key of Object.keys(
this.closureCache[child.expressID.toString()]
)) {
element.__closure[key] =
this.closureCache[child.expressID.toString()][key] + 1
}
}
}
}
delete element[key]
}
}
// (c) https://github.com/agviegas/web-ifc-three
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 }
}
// (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
}
colorToMaterial(color) {
let intColor =
(color.w << 24) + ((color.x * 255) << 16) + ((color.y * 255) << 8) + color.z * 255
return {
diffuse: intColor,
opacity: color.w,
metalness: 0,
roughness: 1,
speckle_type: 'Objects.Other.RenderMaterial'
}
}
}