@ecogis/gis-web-ifc-three
Version:
This library is the implementation of [web-ifc](https://github.com/tomvandig/web-ifc) for [THREE.js](https://github.com/mrdoob/three.js/). This is the official IFCLoader of Three.js.
1,564 lines (1,349 loc) • 83 kB
JavaScript
import * as WebIFC from 'web-ifc';
import { IFCSPACE, IFCOPENINGELEMENT, IFCPRODUCTDEFINITIONSHAPE, IFCRELAGGREGATES, IFCRELCONTAINEDINSPATIALSTRUCTURE, IFCRELDEFINESBYPROPERTIES, IFCRELASSOCIATESMATERIAL, IFCRELDEFINESBYTYPE, IFCPROJECT, IFCBUILDING } from 'web-ifc';
import { Mesh, Color, MeshLambertMaterial, DoubleSide, Matrix4, BufferGeometry, BufferAttribute, Loader, FileLoader } from 'three';
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils';
const nullIfcManagerErrorMessage = 'IfcManager is null!';
class IFCModel extends Mesh {
constructor() {
super(...arguments);
this.modelID = IFCModel.modelIdCounter++;
this.ifcManager = null;
this.mesh = this;
}
static dispose() {
IFCModel.modelIdCounter = 0;
}
setIFCManager(manager) {
this.ifcManager = manager;
}
setWasmPath(path) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
this.ifcManager.setWasmPath(path);
}
close(scene) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
this.ifcManager.close(this.modelID, scene);
}
getExpressId(geometry, faceIndex) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
return this.ifcManager.getExpressId(geometry, faceIndex);
}
getAllItemsOfType(type, verbose) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
return this.ifcManager.getAllItemsOfType(this.modelID, type, verbose);
}
getItemProperties(id, recursive = false) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
return this.ifcManager.getItemProperties(this.modelID, id, recursive);
}
getPropertySets(id, recursive = false) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
return this.ifcManager.getPropertySets(this.modelID, id, recursive);
}
getTypeProperties(id, recursive = false) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
return this.ifcManager.getTypeProperties(this.modelID, id, recursive);
}
getIfcType(id) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
return this.ifcManager.getIfcType(this.modelID, id);
}
getSpatialStructure() {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
return this.ifcManager.getSpatialStructure(this.modelID);
}
getSubset(material) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
return this.ifcManager.getSubset(this.modelID, material);
}
removeSubset(material, customID) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
this.ifcManager.removeSubset(this.modelID, material, customID);
}
createSubset(config) {
if (this.ifcManager === null)
throw new Error(nullIfcManagerErrorMessage);
const modelConfig = {
...config,
modelID: this.modelID
};
return this.ifcManager.createSubset(modelConfig);
}
}
IFCModel.modelIdCounter = 0;
class IFCParser {
constructor(state, BVH) {
this.state = state;
this.BVH = BVH;
this.loadedModels = 0;
this.optionalCategories = {
[IFCSPACE]: true,
[IFCOPENINGELEMENT]: false
};
this.geometriesByMaterials = {};
this.loadingState = {
total: 0,
current: 0,
step: 0.1
};
this.currentWebIfcID = -1;
this.currentModelID = -1;
}
async setupOptionalCategories(config) {
this.optionalCategories = config;
}
async parse(buffer, coordinationMatrix) {
if (this.state.api.wasmModule === undefined)
await this.state.api.Init();
await this.newIfcModel(buffer);
this.loadedModels++;
if (coordinationMatrix) {
await this.state.api.SetGeometryTransformation(this.currentWebIfcID, coordinationMatrix);
}
return this.loadAllGeometry(this.currentWebIfcID);
}
getAndClearErrors(_modelId) {}
notifyProgress(loaded, total) {
if (this.state.onProgress)
this.state.onProgress({
loaded,
total
});
}
async newIfcModel(buffer) {
const data = new Uint8Array(buffer);
this.currentWebIfcID = await this.state.api.OpenModel(data, this.state.webIfcSettings);
this.currentModelID = this.state.useJSON ? this.loadedModels : this.currentWebIfcID;
this.state.models[this.currentModelID] = {
modelID: this.currentModelID,
mesh: {},
types: {},
jsonData: {}
};
}
async loadAllGeometry(modelID) {
this.addOptionalCategories(modelID);
await this.initializeLoadingState(modelID);
this.state.api.StreamAllMeshes(modelID, (mesh) => {
this.updateLoadingState();
this.streamMesh(modelID, mesh);
});
this.notifyLoadingEnded();
const geometries = [];
const materials = [];
Object.keys(this.geometriesByMaterials).forEach((key) => {
const geometriesByMaterial = this.geometriesByMaterials[key].geometries;
const merged = mergeGeometries(geometriesByMaterial);
materials.push(this.geometriesByMaterials[key].material);
geometries.push(merged);
});
const combinedGeometry = mergeGeometries(geometries, true);
this.cleanUpGeometryMemory(geometries);
if (this.BVH)
this.BVH.applyThreeMeshBVH(combinedGeometry);
const model = new IFCModel(combinedGeometry, materials);
this.state.models[this.currentModelID].mesh = model;
return model;
}
async initializeLoadingState(modelID) {
const shapes = await this.state.api.GetLineIDsWithType(modelID, IFCPRODUCTDEFINITIONSHAPE);
this.loadingState.total = shapes.size();
this.loadingState.current = 0;
this.loadingState.step = 0.1;
}
notifyLoadingEnded() {
this.notifyProgress(this.loadingState.total, this.loadingState.total);
}
updateLoadingState() {
const realCurrentItem = Math.min(this.loadingState.current++, this.loadingState.total);
if (realCurrentItem / this.loadingState.total >= this.loadingState.step) {
const currentProgress = Math.ceil(this.loadingState.total * this.loadingState.step);
this.notifyProgress(currentProgress, this.loadingState.total);
this.loadingState.step += 0.1;
}
}
addOptionalCategories(modelID) {
const optionalTypes = [];
for (let key in this.optionalCategories) {
if (this.optionalCategories.hasOwnProperty(key)) {
const category = parseInt(key);
if (this.optionalCategories[category])
optionalTypes.push(category);
}
}
this.state.api.StreamAllMeshesWithTypes(this.currentWebIfcID, optionalTypes, (mesh) => {
this.streamMesh(modelID, mesh);
});
}
streamMesh(modelID, mesh) {
const placedGeometries = mesh.geometries;
const size = placedGeometries.size();
for (let i = 0; i < size; i++) {
const placedGeometry = placedGeometries.get(i);
let itemMesh = this.getPlacedGeometry(modelID, mesh.expressID, placedGeometry);
let geom = itemMesh.geometry.applyMatrix4(itemMesh.matrix);
this.storeGeometryByMaterial(placedGeometry.color, geom);
}
}
getPlacedGeometry(modelID, expressID, placedGeometry) {
const geometry = this.getBufferGeometry(modelID, expressID, placedGeometry);
const mesh = new Mesh(geometry);
mesh.matrix = this.getMeshMatrix(placedGeometry.flatTransformation);
mesh.matrixAutoUpdate = false;
return mesh;
}
getBufferGeometry(modelID, expressID, placedGeometry) {
const geometry = this.state.api.GetGeometry(modelID, placedGeometry.geometryExpressID);
const verts = this.state.api.GetVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize());
const indices = this.state.api.GetIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize());
const buffer = this.ifcGeometryToBuffer(expressID, verts, indices);
geometry.delete();
return buffer;
}
storeGeometryByMaterial(color, geometry) {
let colID = `${color.x}${color.y}${color.z}${color.w}`;
if (this.geometriesByMaterials[colID]) {
this.geometriesByMaterials[colID].geometries.push(geometry);
return;
}
const col = new Color().setRGB(color.x, color.y, color.z, 'srgb');
const material = new MeshLambertMaterial({
color: col,
side: DoubleSide
});
material.transparent = color.w !== 1;
if (material.transparent)
material.opacity = color.w;
this.geometriesByMaterials[colID] = {
material,
geometries: [geometry]
};
}
getMeshMatrix(matrix) {
const mat = new Matrix4();
mat.fromArray(matrix);
return mat;
}
ifcGeometryToBuffer(expressID, vertexData, indexData) {
const geometry = new BufferGeometry();
const posFloats = new Float32Array(vertexData.length / 2);
const normFloats = new Float32Array(vertexData.length / 2);
const idAttribute = new Uint32Array(vertexData.length / 6);
for (let i = 0; i < vertexData.length; i += 6) {
posFloats[i / 2] = vertexData[i];
posFloats[i / 2 + 1] = vertexData[i + 1];
posFloats[i / 2 + 2] = vertexData[i + 2];
normFloats[i / 2] = vertexData[i + 3];
normFloats[i / 2 + 1] = vertexData[i + 4];
normFloats[i / 2 + 2] = vertexData[i + 5];
idAttribute[i / 6] = expressID;
}
geometry.setAttribute('position', new BufferAttribute(posFloats, 3));
geometry.setAttribute('normal', new BufferAttribute(normFloats, 3));
geometry.setAttribute('expressID', new BufferAttribute(idAttribute, 1));
geometry.setIndex(new BufferAttribute(indexData, 1));
return geometry;
}
cleanUpGeometryMemory(geometries) {
geometries.forEach(geometry => geometry.dispose());
Object.keys(this.geometriesByMaterials).forEach((materialID) => {
const geometriesByMaterial = this.geometriesByMaterials[materialID];
geometriesByMaterial.geometries.forEach(geometry => geometry.dispose());
geometriesByMaterial.geometries = [];
geometriesByMaterial.material = null;
});
this.geometriesByMaterials = {};
}
}
class ItemsMap {
constructor(state) {
this.state = state;
this.map = {};
}
generateGeometryIndexMap(modelID) {
if (this.map[modelID])
return;
const geometry = this.getGeometry(modelID);
const items = this.newItemsMap(modelID, geometry);
for (const group of geometry.groups) {
this.fillItemsWithGroupInfo(group, geometry, items);
}
}
getSubsetID(modelID, material, customID = 'DEFAULT') {
const baseID = modelID;
const materialID = material ? material.uuid : 'DEFAULT';
return `${baseID} - ${materialID} - ${customID}`;
}
dispose() {
Object.values(this.map).forEach(model => {
model.indexCache = null;
model.map = null;
});
this.map = null;
}
getGeometry(modelID) {
const geometry = this.state.models[modelID].mesh.geometry;
if (!geometry)
throw new Error('Model without geometry.');
if (!geometry.index)
throw new Error('Geometry must be indexed');
return geometry;
}
newItemsMap(modelID, geometry) {
const startIndices = geometry.index.array;
this.map[modelID] = {
indexCache: startIndices.slice(0, geometry.index.array.length),
map: new Map()
};
return this.map[modelID];
}
fillItemsWithGroupInfo(group, geometry, items) {
let prevExpressID = -1;
const materialIndex = group.materialIndex;
const materialStart = group.start;
const materialEnd = materialStart + group.count - 1;
let objectStart = -1;
let objectEnd = -1;
for (let i = materialStart; i <= materialEnd; i++) {
const index = geometry.index.array[i];
const bufferAttr = geometry.attributes.expressID;
const expressID = bufferAttr.array[index];
if (prevExpressID === -1) {
prevExpressID = expressID;
objectStart = i;
}
const isEndOfMaterial = i === materialEnd;
if (isEndOfMaterial) {
const store = this.getMaterialStore(items.map, expressID, materialIndex);
store.push(objectStart, materialEnd);
break;
}
if (prevExpressID === expressID)
continue;
const store = this.getMaterialStore(items.map, prevExpressID, materialIndex);
objectEnd = i - 1;
store.push(objectStart, objectEnd);
prevExpressID = expressID;
objectStart = i;
}
}
getMaterialStore(map, id, matIndex) {
if (map.get(id) === undefined) {
map.set(id, {});
}
const storedIfcItem = map.get(id);
if (storedIfcItem === undefined)
throw new Error('Geometry map generation error');
if (storedIfcItem[matIndex] === undefined) {
storedIfcItem[matIndex] = [];
}
return storedIfcItem[matIndex];
}
}
class SubsetUtils {
static getAllIndicesOfGroup(modelID, ids, materialIndex, items, flatten = true) {
const indicesByGroup = [];
for (const expressID of ids) {
const entry = items.map.get(expressID);
if (!entry)
continue;
const value = entry[materialIndex];
if (!value)
continue;
SubsetUtils.getIndexChunk(value, indicesByGroup, materialIndex, items, flatten);
}
return indicesByGroup;
}
static getIndexChunk(value, indicesByGroup, materialIndex, items, flatten) {
const pairs = value.length / 2;
for (let pair = 0; pair < pairs; pair++) {
const pairIndex = pair * 2;
const start = value[pairIndex];
const end = value[pairIndex + 1];
for (let j = start; j <= end; j++) {
if (flatten)
indicesByGroup.push(items.indexCache[j]);
else {
if (!indicesByGroup[materialIndex])
indicesByGroup[materialIndex] = [];
indicesByGroup[materialIndex].push(items.indexCache[j]);
}
}
}
}
}
class SubsetCreator {
constructor(state, items, subsets, BVH) {
this.state = state;
this.items = items;
this.subsets = subsets;
this.BVH = BVH;
this.tempIndex = [];
}
createSubset(config, subsetID) {
if (!this.items.map[config.modelID])
this.items.generateGeometryIndexMap(config.modelID);
if (!this.subsets[subsetID])
this.initializeSubset(config, subsetID);
this.filterIndices(config, subsetID);
this.constructSubsetByMaterial(config, subsetID);
config.ids.forEach(id => this.subsets[subsetID].ids.add(id));
this.subsets[subsetID].mesh.geometry.setIndex(this.tempIndex);
this.tempIndex.length = 0;
const subset = this.subsets[subsetID].mesh;
if (config.applyBVH)
this.BVH.applyThreeMeshBVH(subset.geometry);
if (config.scene)
config.scene.add(subset);
return this.subsets[subsetID].mesh;
}
dispose() {
this.tempIndex = [];
}
initializeSubset(config, subsetID) {
const model = this.state.models[config.modelID].mesh;
const subsetGeom = new BufferGeometry();
this.initializeSubsetAttributes(subsetGeom, model);
if (!config.material)
this.initializeSubsetGroups(subsetGeom, model);
const mesh = new Mesh(subsetGeom, config.material || model.material);
mesh.modelID = config.modelID;
const bvh = Boolean(config.applyBVH);
this.subsets[subsetID] = {
ids: new Set(),
mesh,
bvh
};
model.add(mesh);
}
initializeSubsetAttributes(subsetGeom, model) {
subsetGeom.setAttribute('position', model.geometry.attributes.position);
subsetGeom.setAttribute('normal', model.geometry.attributes.normal);
subsetGeom.setAttribute('expressID', model.geometry.attributes.expressID);
subsetGeom.setIndex([]);
}
initializeSubsetGroups(subsetGeom, model) {
subsetGeom.groups = JSON.parse(JSON.stringify(model.geometry.groups));
this.resetGroups(subsetGeom);
}
filterIndices(config, subsetID) {
const geometry = this.subsets[subsetID].mesh.geometry;
if (config.removePrevious) {
geometry.setIndex([]);
this.resetGroups(geometry);
return;
}
const previousIndices = geometry.index.array;
const previousIDs = this.subsets[subsetID].ids;
config.ids = config.ids.filter(id => !previousIDs.has(id));
this.tempIndex = Array.from(previousIndices);
}
constructSubsetByMaterial(config, subsetID) {
const model = this.state.models[config.modelID].mesh;
const newIndices = {
count: 0
};
for (let i = 0; i < model.geometry.groups.length; i++) {
this.insertNewIndices(config, subsetID, i, newIndices);
}
}
insertNewIndices(config, subsetID, materialIndex, newIndices) {
const items = this.items.map[config.modelID];
const indicesOfOneMaterial = SubsetUtils.getAllIndicesOfGroup(config.modelID, config.ids, materialIndex, items);
if (!config.material) {
this.insertIndicesAtGroup(subsetID, indicesOfOneMaterial, materialIndex, newIndices);
} else {
indicesOfOneMaterial.forEach(index => this.tempIndex.push(index));
}
}
insertIndicesAtGroup(subsetID, indicesByGroup, index, newIndices) {
const currentGroup = this.getCurrentGroup(subsetID, index);
currentGroup.start += newIndices.count;
let newIndicesPosition = currentGroup.start + currentGroup.count;
newIndices.count += indicesByGroup.length;
if (indicesByGroup.length > 0) {
let position = newIndicesPosition;
const start = this.tempIndex.slice(0, position);
const end = this.tempIndex.slice(position);
this.tempIndex = Array.prototype.concat.apply([], [start, indicesByGroup, end]);
currentGroup.count += indicesByGroup.length;
}
}
getCurrentGroup(subsetID, groupIndex) {
const geometry = this.subsets[subsetID].mesh.geometry;
return geometry.groups[groupIndex];
}
resetGroups(geometry) {
geometry.groups.forEach((group) => {
group.start = 0;
group.count = 0;
});
}
}
class SubsetManager {
constructor(state, BVH) {
this.subsets = {};
this.state = state;
this.items = new ItemsMap(state);
this.BVH = BVH;
this.subsetCreator = new SubsetCreator(state, this.items, this.subsets, this.BVH);
}
getAllSubsets() {
return this.subsets;
}
getSubset(modelID, material, customId) {
const subsetID = this.getSubsetID(modelID, material, customId);
return this.subsets[subsetID].mesh;
}
removeSubset(modelID, material, customID) {
const subsetID = this.getSubsetID(modelID, material, customID);
const subset = this.subsets[subsetID];
if (!subset)
return;
if (subset.mesh.parent)
subset.mesh.removeFromParent();
subset.mesh.geometry.attributes = {};
subset.mesh.geometry.index = null;
subset.mesh.geometry.dispose();
subset.mesh.geometry = null;
delete this.subsets[subsetID];
}
createSubset(config) {
const subsetID = this.getSubsetID(config.modelID, config.material, config.customID);
return this.subsetCreator.createSubset(config, subsetID);
}
removeFromSubset(modelID, ids, customID, material) {
const subsetID = this.getSubsetID(modelID, material, customID);
if (!this.subsets[subsetID])
return;
const previousIDs = this.subsets[subsetID].ids;
ids.forEach((id) => {
if (previousIDs.has(id))
previousIDs.delete(id);
});
return this.createSubset({
modelID,
removePrevious: true,
material,
customID,
applyBVH: this.subsets[subsetID].bvh,
ids: Array.from(previousIDs),
scene: this.subsets[subsetID].mesh.parent
});
}
clearSubset(modelID, customID, material) {
const subsetID = this.getSubsetID(modelID, material, customID);
if (!this.subsets[subsetID])
return;
this.subsets[subsetID].ids.clear();
const subset = this.getSubset(modelID, material, customID);
subset.geometry.setIndex([]);
}
dispose() {
this.items.dispose();
this.subsetCreator.dispose();
Object.values(this.subsets).forEach(subset => {
subset.ids = null;
subset.mesh.removeFromParent();
const mats = subset.mesh.material;
if (Array.isArray(mats))
mats.forEach(mat => mat.dispose());
else
mats.dispose();
subset.mesh.geometry.index = null;
subset.mesh.geometry.dispose();
const geom = subset.mesh.geometry;
if (geom.disposeBoundsTree)
geom.disposeBoundsTree();
subset.mesh = null;
});
this.subsets = null;
}
getSubsetID(modelID, material, customID = 'DEFAULT') {
const baseID = modelID;
const materialID = material ? material.uuid : 'DEFAULT';
return `${baseID} - ${materialID} - ${customID}`;
}
}
const IdAttrName = 'expressID';
const PropsNames = {
aggregates: {
name: IFCRELAGGREGATES,
relating: 'RelatingObject',
related: 'RelatedObjects',
key: 'children'
},
spatial: {
name: IFCRELCONTAINEDINSPATIALSTRUCTURE,
relating: 'RelatingStructure',
related: 'RelatedElements',
key: 'children'
},
psets: {
name: IFCRELDEFINESBYPROPERTIES,
relating: 'RelatingPropertyDefinition',
related: 'RelatedObjects',
key: 'hasPsets'
},
materials: {
name: IFCRELASSOCIATESMATERIAL,
relating: 'RelatingMaterial',
related: 'RelatedObjects',
key: 'hasMaterial'
},
type: {
name: IFCRELDEFINESBYTYPE,
relating: 'RelatingType',
related: 'RelatedObjects',
key: 'hasType'
}
};
class BasePropertyManager {
constructor(state) {
this.state = state;
}
async getPropertySets(modelID, elementID, recursive = false) {
return await this.getProperty(modelID, elementID, recursive, PropsNames.psets);
}
async getTypeProperties(modelID, elementID, recursive = false) {
return await this.getProperty(modelID, elementID, recursive, PropsNames.type);
}
async getMaterialsProperties(modelID, elementID, recursive = false) {
return await this.getProperty(modelID, elementID, recursive, PropsNames.materials);
}
async getSpatialNode(modelID, node, treeChunks, includeProperties) {
await this.getChildren(modelID, node, treeChunks, PropsNames.aggregates, includeProperties);
await this.getChildren(modelID, node, treeChunks, PropsNames.spatial, includeProperties);
}
async getChildren(modelID, node, treeChunks, propNames, includeProperties) {
const children = treeChunks[node.expressID];
if (children == undefined)
return;
const prop = propNames.key;
const nodes = [];
for (let i = 0; i < children.length; i++) {
const child = children[i];
let node = this.newNode(modelID, child);
if (includeProperties) {
const properties = await this.getItemProperties(modelID, node.expressID);
node = {
...properties, ...node
};
}
await this.getSpatialNode(modelID, node, treeChunks, includeProperties);
nodes.push(node);
}
node[prop] = nodes;
}
newNode(modelID, id) {
const typeName = this.getNodeType(modelID, id);
return {
expressID: id,
type: typeName,
children: []
};
}
async getSpatialTreeChunks(modelID) {
const treeChunks = {};
await this.getChunks(modelID, treeChunks, PropsNames.aggregates);
await this.getChunks(modelID, treeChunks, PropsNames.spatial);
return treeChunks;
}
saveChunk(chunks, propNames, rel) {
const relating = rel[propNames.relating].value;
const related = rel[propNames.related].map((r) => r.value);
if (chunks[relating] == undefined) {
chunks[relating] = related;
} else {
chunks[relating] = chunks[relating].concat(related);
}
}
getRelated(rel, propNames, IDs) {
const element = rel[propNames.relating];
if (!element) {
return console.warn(`The object with ID ${rel.expressID} has a broken reference.`);
}
if (!Array.isArray(element))
IDs.push(element.value);
else
element.forEach((ele) => IDs.push(ele.value));
}
static isRelated(id, rel, propNames) {
const relatedItems = rel[propNames.related];
if (Array.isArray(relatedItems)) {
const values = relatedItems.map((item) => item.value);
return values.includes(id);
}
return relatedItems.value === id;
}
static newIfcProject(id) {
return {
expressID: id,
type: 'IFCPROJECT',
children: []
};
}
async getProperty(modelID, elementID, recursive = false, propName) {}
async getChunks(modelID, chunks, propNames) {}
async getItemProperties(modelID, expressID, recursive = false) {}
getNodeType(modelID, id) {}
}
class WebIfcPropertyManager extends BasePropertyManager {
async getItemProperties(modelID, id, recursive = false) {
return this.state.api.GetLine(modelID, id, recursive);
}
async getHeaderLine(modelID, headerType) {
return this.state.api.GetHeaderLine(modelID, headerType);
}
async getSpatialStructure(modelID, includeProperties) {
const chunks = await this.getSpatialTreeChunks(modelID);
const allLines = await this.state.api.GetLineIDsWithType(modelID, IFCPROJECT);
const projectID = allLines.get(0);
const project = WebIfcPropertyManager.newIfcProject(projectID);
await this.getSpatialNode(modelID, project, chunks, includeProperties);
return project;
}
async getAllItemsOfType(modelID, type, verbose) {
let items = [];
const lines = await this.state.api.GetLineIDsWithType(modelID, type);
for (let i = 0; i < lines.size(); i++)
items.push(lines.get(i));
if (!verbose)
return items;
const result = [];
for (let i = 0; i < items.length; i++) {
result.push(await this.state.api.GetLine(modelID, items[i]));
}
return result;
}
async getProperty(modelID, elementID, recursive = false, propName) {
const propSetIds = await this.getAllRelatedItemsOfType(modelID, elementID, propName);
const result = [];
for (let i = 0; i < propSetIds.length; i++) {
result.push(await this.state.api.GetLine(modelID, propSetIds[i], recursive));
}
return result;
}
getNodeType(modelID, id) {
const typeID = this.state.models[modelID].types[id];
return this.state.api.GetNameFromTypeCode(typeID);
}
async getChunks(modelID, chunks, propNames) {
const relation = await this.state.api.GetLineIDsWithType(modelID, propNames.name);
for (let i = 0; i < relation.size(); i++) {
const rel = await this.state.api.GetLine(modelID, relation.get(i), false);
this.saveChunk(chunks, propNames, rel);
}
}
async getAllRelatedItemsOfType(modelID, id, propNames) {
const lines = await this.state.api.GetLineIDsWithType(modelID, propNames.name);
const IDs = [];
for (let i = 0; i < lines.size(); i++) {
const rel = await this.state.api.GetLine(modelID, lines.get(i));
const isRelated = BasePropertyManager.isRelated(id, rel, propNames);
if (isRelated)
this.getRelated(rel, propNames, IDs);
}
return IDs;
}
}
class JSONPropertyManager extends BasePropertyManager {
async getItemProperties(modelID, id, recursive = false) {
return {
...this.state.models[modelID].jsonData[id]
};
}
async getHeaderLine(modelID) {
return {};
}
async getSpatialStructure(modelID, includeProperties) {
const chunks = await this.getSpatialTreeChunks(modelID);
const projectsIDs = await this.getAllItemsOfType(modelID, IFCPROJECT, false);
const projectID = projectsIDs[0];
const project = JSONPropertyManager.newIfcProject(projectID);
await this.getSpatialNode(modelID, project, chunks, includeProperties);
return {
...project
};
}
async getAllItemsOfType(modelID, type, verbose) {
const data = this.state.models[modelID].jsonData;
const typeName = await this.state.api.GetNameFromTypeCode(type);
if (!typeName) {
throw new Error(`Type not found: ${type}`);
}
return this.filterItemsByType(data, typeName, verbose);
}
async getProperty(modelID, elementID, recursive = false, propName) {
const resultIDs = await this.getAllRelatedItemsOfType(modelID, elementID, propName);
const result = this.getItemsByID(modelID, resultIDs);
if (recursive) {
result.forEach(result => this.getReferencesRecursively(modelID, result));
}
return result;
}
getNodeType(modelID, id) {
return this.state.models[modelID].jsonData[id].type;
}
async getChunks(modelID, chunks, propNames) {
const relation = await this.getAllItemsOfType(modelID, propNames.name, true);
relation.forEach(rel => {
this.saveChunk(chunks, propNames, rel);
});
}
filterItemsByType(data, typeName, verbose) {
const result = [];
Object.keys(data).forEach(key => {
const numKey = parseInt(key);
if (data[numKey].type.toUpperCase() === typeName) {
result.push(verbose ? {
...data[numKey]
} : numKey);
}
});
return result;
}
async getAllRelatedItemsOfType(modelID, id, propNames) {
const lines = await this.getAllItemsOfType(modelID, propNames.name, true);
const IDs = [];
lines.forEach(line => {
const isRelated = JSONPropertyManager.isRelated(id, line, propNames);
if (isRelated)
this.getRelated(line, propNames, IDs);
});
return IDs;
}
getItemsByID(modelID, ids) {
const data = this.state.models[modelID].jsonData;
const result = [];
ids.forEach(id => result.push({
...data[id]
}));
return result;
}
getReferencesRecursively(modelID, jsonObject) {
if (jsonObject == undefined)
return;
const keys = Object.keys(jsonObject);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
this.getJSONItem(modelID, jsonObject, key);
}
}
getJSONItem(modelID, jsonObject, key) {
if (Array.isArray(jsonObject[key])) {
return this.getMultipleJSONItems(modelID, jsonObject, key);
}
if (jsonObject[key] && jsonObject[key].type === 5) {
jsonObject[key] = this.getItemsByID(modelID, [jsonObject[key].value])[0];
this.getReferencesRecursively(modelID, jsonObject[key]);
}
}
getMultipleJSONItems(modelID, jsonObject, key) {
jsonObject[key] = jsonObject[key].map((item) => {
if (item.type === 5) {
item = this.getItemsByID(modelID, [item.value])[0];
this.getReferencesRecursively(modelID, item);
}
return item;
});
}
}
const geometryTypes = new Set([
1123145078, 574549367, 1675464909, 2059837836, 3798115385, 32440307, 3125803723, 3207858831,
2740243338, 2624227202, 4240577450, 3615266464, 3724593414, 220341763, 477187591, 1878645084,
1300840506, 3303107099, 1607154358, 1878645084, 846575682, 1351298697, 2417041796, 3049322572,
3331915920, 1416205885, 776857604, 3285139300, 3958052878, 2827736869, 2732653382, 673634403,
3448662350, 4142052618, 2924175390, 803316827, 2556980723, 1809719519, 2205249479, 807026263,
3737207727, 1660063152, 2347385850, 3940055652, 2705031697, 3732776249, 2485617015, 2611217952,
1704287377, 2937912522, 2770003689, 1281925730, 1484403080, 3448662350, 4142052618, 3800577675,
4006246654, 3590301190, 1383045692, 2775532180, 2047409740, 370225590, 3593883385, 2665983363,
4124623270, 812098782, 3649129432, 987898635, 1105321065, 3510044353, 1635779807, 2603310189,
3406155212, 1310608509, 4261334040, 2736907675, 3649129432, 1136057603, 1260505505, 4182860854,
2713105998, 2898889636, 59481748, 3749851601, 3486308946, 3150382593, 1062206242, 3264961684,
15328376, 1485152156, 370225590, 1981873012, 2859738748, 45288368, 2614616156, 2732653382,
775493141, 2147822146, 2601014836, 2629017746, 1186437898, 2367409068, 1213902940, 3632507154,
3900360178, 476780140, 1472233963, 2804161546, 3008276851, 738692330, 374418227, 315944413,
3905492369, 3570813810, 2571569899, 178912537, 2294589976, 1437953363, 2133299955, 572779678,
3092502836, 388784114, 2624227202, 1425443689, 3057273783, 2347385850, 1682466193, 2519244187,
2839578677, 3958567839, 2513912981, 2830218821, 427810014
]);
class PropertySerializer {
constructor(webIfc) {
this.webIfc = webIfc;
}
dispose() {
this.webIfc = null;
}
async serializeAllProperties(modelID, maxSize, event) {
const blobs = [];
await this.getPropertiesAsBlobs(modelID, blobs, maxSize, event);
return blobs;
}
async getPropertiesAsBlobs(modelID, blobs, maxSize, event) {
const geometriesIDs = await this.getAllGeometriesIDs(modelID);
let properties = await this.initializePropertiesObject(modelID);
const allLinesIDs = await this.webIfc.GetAllLines(modelID);
const linesCount = allLinesIDs.size();
let lastEvent = 0.1;
let counter = 0;
for (let i = 0; i < linesCount; i++) {
const id = allLinesIDs.get(i);
if (!geometriesIDs.has(id)) {
await this.getItemProperty(modelID, id, properties);
counter++;
}
if (maxSize && counter > maxSize) {
blobs.push(new Blob([JSON.stringify(properties)], {
type: 'application/json'
}));
properties = {};
counter = 0;
}
if (event && i / linesCount > lastEvent) {
event(i, linesCount);
lastEvent += 0.1;
}
}
blobs.push(new Blob([JSON.stringify(properties)], {
type: 'application/json'
}));
}
async getItemProperty(modelID, id, properties) {
try {
const props = await this.webIfc.GetLine(modelID, id);
if (props.type) {
props.type = this.webIfc.GetNameFromTypeCode(props.type);
}
this.formatItemProperties(props);
properties[id] = props;
} catch (e) {
console.log(`There was a problem getting the properties of the item with ID ${id}`);
}
}
formatItemProperties(props) {
Object.keys(props).forEach((key) => {
const value = props[key];
if (value && value.value !== undefined)
props[key] = value.value;
else if (Array.isArray(value))
props[key] = value.map((item) => {
if (item && item.value)
return item.value;
return item;
});
});
}
async initializePropertiesObject(modelID) {
return {
coordinationMatrix: await this.webIfc.GetCoordinationMatrix(modelID),
globalHeight: await this.getBuildingHeight(modelID)
};
}
async getBuildingHeight(modelID) {
const building = await this.getBuilding(modelID);
let placement;
const siteReference = building.ObjectPlacement.PlacementRelTo;
if (siteReference)
placement = siteReference.RelativePlacement.Location;
else
placement = building.ObjectPlacement.RelativePlacement.Location;
const transform = placement.Coordinates.map((coord) => coord.value);
return transform[2];
}
async getBuilding(modelID) {
const allBuildingsIDs = await this.webIfc.GetLineIDsWithType(modelID, IFCBUILDING);
const buildingID = allBuildingsIDs.get(0);
return this.webIfc.GetLine(modelID, buildingID, true);
}
async getAllGeometriesIDs(modelID) {
const geometriesIDs = new Set();
const geomTypesArray = Array.from(geometryTypes);
for (let i = 0; i < geomTypesArray.length; i++) {
const category = geomTypesArray[i];
const ids = await this.webIfc.GetLineIDsWithType(modelID, category);
const idsSize = ids.size();
for (let j = 0; j < idsSize; j++) {
geometriesIDs.add(ids.get(j));
}
}
return geometriesIDs;
}
}
class PropertyManager {
constructor(state) {
this.state = state;
this.webIfcProps = new WebIfcPropertyManager(state);
this.jsonProps = new JSONPropertyManager(state);
this.currentProps = this.webIfcProps;
this.serializer = new PropertySerializer(this.state.api);
}
getExpressId(geometry, faceIndex) {
if (!geometry.index)
throw new Error('Geometry does not have index information.');
const geoIndex = geometry.index.array;
const bufferAttr = geometry.attributes[IdAttrName];
return bufferAttr.getX(geoIndex[3 * faceIndex]);
}
async getHeaderLine(modelID, headerType) {
this.updateCurrentProps();
return this.currentProps.getHeaderLine(modelID, headerType);
}
async getItemProperties(modelID, elementID, recursive = false) {
this.updateCurrentProps();
return this.currentProps.getItemProperties(modelID, elementID, recursive);
}
async getAllItemsOfType(modelID, type, verbose) {
this.updateCurrentProps();
return this.currentProps.getAllItemsOfType(modelID, type, verbose);
}
async getPropertySets(modelID, elementID, recursive = false) {
this.updateCurrentProps();
return this.currentProps.getPropertySets(modelID, elementID, recursive);
}
async getTypeProperties(modelID, elementID, recursive = false) {
this.updateCurrentProps();
return this.currentProps.getTypeProperties(modelID, elementID, recursive);
}
async getMaterialsProperties(modelID, elementID, recursive = false) {
this.updateCurrentProps();
return this.currentProps.getMaterialsProperties(modelID, elementID, recursive);
}
async getSpatialStructure(modelID, includeProperties) {
this.updateCurrentProps();
if (!this.state.useJSON && includeProperties) {
console.warn('Including properties in getSpatialStructure with the JSON workflow disabled can lead to poor performance.');
}
return await this.currentProps.getSpatialStructure(modelID, includeProperties);
}
updateCurrentProps() {
this.currentProps = this.state.useJSON ? this.jsonProps : this.webIfcProps;
}
}
class TypeManager {
constructor(state) {
this.state = state;
this.state = state;
}
async getAllTypes(worker) {
for (let modelID in this.state.models) {
if (this.state.models.hasOwnProperty(modelID)) {
const types = this.state.models[modelID].types;
if (Object.keys(types).length == 0) {
await this.getAllTypesOfModel(parseInt(modelID), worker);
}
}
}
}
async getAllTypesOfModel(modelID, worker) {
const result = {};
const elements = await this.state.api.GetIfcEntityList(modelID);
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const lines = await this.state.api.GetLineIDsWithType(modelID, element);
const size = lines.size();
for (let i = 0; i < size; i++)
result[lines.get(i)] = element;
}
if (this.state.worker.active && worker) {
await worker.workerState.updateModelStateTypes(modelID, result);
}
this.state.models[modelID].types = result;
}
}
class BvhManager {
initializeMeshBVH(computeBoundsTree, disposeBoundsTree, acceleratedRaycast) {
this.computeBoundsTree = computeBoundsTree;
this.disposeBoundsTree = disposeBoundsTree;
this.acceleratedRaycast = acceleratedRaycast;
this.setupThreeMeshBVH();
}
applyThreeMeshBVH(geometry) {
if (this.computeBoundsTree)
geometry.computeBoundsTree();
}
setupThreeMeshBVH() {
if (!this.computeBoundsTree || !this.disposeBoundsTree || !this.acceleratedRaycast)
return;
BufferGeometry.prototype.computeBoundsTree = this.computeBoundsTree;
BufferGeometry.prototype.disposeBoundsTree = this.disposeBoundsTree;
Mesh.prototype.raycast = this.acceleratedRaycast;
}
}
var WorkerActions;
(function(WorkerActions) {
WorkerActions["updateStateUseJson"] = "updateStateUseJson";
WorkerActions["updateStateWebIfcSettings"] = "updateStateWebIfcSettings";
WorkerActions["updateModelStateTypes"] = "updateModelStateTypes";
WorkerActions["updateModelStateJsonData"] = "updateModelStateJsonData";
WorkerActions["loadJsonDataFromWorker"] = "loadJsonDataFromWorker";
WorkerActions["dispose"] = "dispose";
WorkerActions["Close"] = "Close";
WorkerActions["DisposeWebIfc"] = "DisposeWebIfc";
WorkerActions["Init"] = "Init";
WorkerActions["OpenModel"] = "OpenModel";
WorkerActions["CreateModel"] = "CreateModel";
WorkerActions["ExportFileAsIFC"] = "ExportFileAsIFC";
WorkerActions["GetGeometry"] = "GetGeometry";
WorkerActions["GetLine"] = "GetLine";
WorkerActions["GetAndClearErrors"] = "GetAndClearErrors";
WorkerActions["WriteLine"] = "WriteLine";
WorkerActions["FlattenLine"] = "FlattenLine";
WorkerActions["GetRawLineData"] = "GetRawLineData";
WorkerActions["WriteRawLineData"] = "WriteRawLineData";
WorkerActions["GetLineIDsWithType"] = "GetLineIDsWithType";
WorkerActions["GetAllLines"] = "GetAllLines";
WorkerActions["SetGeometryTransformation"] = "SetGeometryTransformation";
WorkerActions["GetCoordinationMatrix"] = "GetCoordinationMatrix";
WorkerActions["GetVertexArray"] = "GetVertexArray";
WorkerActions["GetIndexArray"] = "GetIndexArray";
WorkerActions["getSubArray"] = "getSubArray";
WorkerActions["CloseModel"] = "CloseModel";
WorkerActions["StreamAllMeshes"] = "StreamAllMeshes";
WorkerActions["StreamAllMeshesWithTypes"] = "StreamAllMeshesWithTypes";
WorkerActions["IsModelOpen"] = "IsModelOpen";
WorkerActions["LoadAllGeometry"] = "LoadAllGeometry";
WorkerActions["GetFlatMesh"] = "GetFlatMesh";
WorkerActions["SetWasmPath"] = "SetWasmPath";
WorkerActions["GetNameFromTypeCode"] = "GetNameFromTypeCode";
WorkerActions["GetIfcEntityList"] = "GetIfcEntityList";
WorkerActions["GetTypeCodeFromName"] = "GetTypeCodeFromName";
WorkerActions["parse"] = "parse";
WorkerActions["setupOptionalCategories"] = "setupOptionalCategories";
WorkerActions["getExpressId"] = "getExpressId";
WorkerActions["initializeProperties"] = "initializeProperties";
WorkerActions["getAllItemsOfType"] = "getAllItemsOfType";
WorkerActions["getItemProperties"] = "getItemProperties";
WorkerActions["getMaterialsProperties"] = "getMaterialsProperties";
WorkerActions["getPropertySets"] = "getPropertySets";
WorkerActions["getSpatialStructure"] = "getSpatialStructure";
WorkerActions["getTypeProperties"] = "getTypeProperties";
WorkerActions["getHeaderLine"] = "getHeaderLine";
})(WorkerActions || (WorkerActions = {}));
var WorkerAPIs;
(function(WorkerAPIs) {
WorkerAPIs["workerState"] = "workerState";
WorkerAPIs["webIfc"] = "webIfc";
WorkerAPIs["properties"] = "properties";
WorkerAPIs["parser"] = "parser";
})(WorkerAPIs || (WorkerAPIs = {}));
class Vector {
constructor(vector) {
this._data = {};
this._size = vector.size;
const keys = Object.keys(vector).filter((key) => key.indexOf('size') === -1).map(key => parseInt(key));
keys.forEach((key) => this._data[key] = vector[key]);
}
size() {
return this._size;
}
get(index) {
return this._data[index];
}
}
class IfcGeometry {
constructor(vector) {
this._GetVertexData = vector.GetVertexData;
this._GetVertexDataSize = vector.GetVertexDataSize;
this._GetIndexData = vector.GetIndexData;
this._GetIndexDataSize = vector.GetIndexDataSize;
}
GetVertexData() {
return this._GetVertexData;
}
GetVertexDataSize() {
return this._GetVertexDataSize;
}
GetIndexData() {
return this._GetIndexData;
}
GetIndexDataSize() {
return this._GetIndexDataSize;
}
}
class FlatMesh {
constructor(serializer, flatMesh) {
this.expressID = flatMesh.expressID;
this.geometries = serializer.reconstructVector(flatMesh.geometries);
}
}
class FlatMeshVector {
constructor(serializer, vector) {
this._data = {};
this._size = vector.size;
const keys = Object.keys(vector).filter((key) => key.indexOf('size') === -1).map(key => parseInt(key));
keys.forEach(key => this._data[key] = serializer.reconstructFlatMesh(vector[key]));
}
size() {
return this._size;
}
get(index) {
return this._data[index];
}
}
class SerializedMaterial {
constructor(material) {
this.color = [material.color.r, material.color.g, material.color.b];
this.opacity = material.opacity;
this.transparent = material.transparent;
}
}
class MaterialReconstructor {
static new(material) {
return new MeshLambertMaterial({
color: new Color(material.color[0], material.color[1], material.color[2]),
opacity: material.opacity,
transparent: material.transparent,
side: DoubleSide
});
}
}
class SerializedGeometry {
constructor(geometry) {
var _a,
_b,
_c,
_d;
this.position = ((_a = geometry.attributes.position) === null || _a === void 0 ? void 0 : _a.array) || [];
this.normal = ((_b = geometry.attributes.normal) === null || _b === void 0 ? void 0 : _b.array) || [];
this.expressID = ((_c = geometry.attributes.expressID) === null || _c === void 0 ? void 0 : _c.array) || [];
this.index = ((_d = geometry.index) === null || _d === void 0 ? void 0 : _d.array) || [];
this.groups = geometry.groups;
}
}
class GeometryReconstructor {
static new(serialized) {
const geom = new BufferGeometry();
GeometryReconstructor.set(geom, 'expressID', new Uint32Array(serialized.expressID), 1);
GeometryReconstructor.set(geom, 'position', new Float32Array(serialized.position), 3);
GeometryReconstructor.set(geom, 'normal', new Float32Array(serialized.normal), 3);
geom.setIndex(Array. from (serialized.index));
geom.groups = serialized.groups;
return geom;
}
static set(geom, name, data, size) {
if (data.length > 0) {
geom.setAttribute(name, new BufferAttribute(data, size));
}
}
}
class SerializedMesh {
constructor(model) {
this.materials = [];
this.modelID = model.modelID;
this.geometry = new SerializedGeometry(model.geometry);
if (Array.isArray(model.material)) {
model.material.forEach(mat => {
this.materials.push(new SerializedMaterial(mat));
});
} else {
this.materials.push(new SerializedMaterial(model.material));
}
}
}
class MeshReconstructor {
static new(serialized) {
const model = new IFCModel();
model.modelID = serialized.modelID;
model.geometry = GeometryReconstructor.new(serialized.geometry);
MeshReconstructor.getMaterials(serialized, model);
return model;
}
static getMaterials(serialized, model) {
model.material = [];
const mats = model.material;
serialized.materials.forEach(mat => {
mats.push(MaterialReconstructor.new(mat));
});
}
}
class Serializer {
serializeVector(vector) {
const size = vector.size();
const serialized = {
size
};
for (let i = 0; i < size; i++) {
serialized[i] = vector.get(i);
}
return serialized;
}
reconstructVector(vector) {
return new Vector(vector);
}
serializeIfcGeometry(geometry) {
const GetVertexData = geometry.GetVertexData();
const GetVertexDataSize = geometry.GetVertexDataSize();
const GetIndexData = geometry.GetIndexData();
const GetIndexDataSize = geometry.GetIndexDataSize();
return {
GetVertexData,
GetVertexDataSize,
GetIndexData,
GetIndexDataSize
};
}
reconstructIfcGeometry(geometry) {
return new IfcGeometry(geometry);
}
serializeFlatMesh(flatMesh) {
return {
expressID: flatMesh.expressID,
geometries: this.serializeVector(flatMesh.geometries)
};
}
reconstructFlatMesh(flatMesh) {
return new FlatMesh(this, flatMesh);
}
serializeFlatMeshVector(vector) {
const size = vector.size();
const serialized = {
size
};
for (let i = 0; i < size; i++) {
const flatMesh = vector.get(i);
serialized[i] = this.serializeFlatMesh(flatMesh);
}
return serialized;
}
reconstructFlatMeshVector(vector) {
return new FlatMeshVector(this, vector);
}
serializeIfcModel(model) {
return new SerializedMesh(model);
}
reconstructIfcModel(model) {
return MeshReconstructor.new(model);
}
}
class PropertyHandler {
constructor(handler) {
this.handler = handler;
this.API = WorkerAPIs.properties;
}
getExpressId(geometry, faceIndex) {
if (!geometry.index)
throw new Error('Geometry does not have index information.');
const geoIndex = geometry.index.array;
const bufferAttr = geometry.attributes[IdAttrName];
return bufferAttr.getX(geoIndex[3 * faceIndex]);
}
getHeaderLine(modelID, headerType) {
return this.handler.request(this.API, WorkerActions.getHeaderLine, {
modelID,
headerType
});
}
getAllItemsOfType(modelID, type, verbose) {
return this.handler.request(this.API, WorkerActions.getAllI