You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
435 lines
10 KiB
435 lines
10 KiB
//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 '../../../build/three.module.js';
|
|
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 <fog_vertex>`,
|
|
`#include <fog_vertex>
|
|
vVisible = visibility;
|
|
`
|
|
);
|
|
shader.fragmentShader = `
|
|
varying float vVisible;
|
|
${shader.fragmentShader}`.replace(
|
|
`#include <clipping_planes_fragment>`,
|
|
`
|
|
if (vVisible > 0.5) discard;
|
|
#include <clipping_planes_fragment>`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export { IFCLoader };
|
|
|