Jelajahi Sumber

Update IFCLoader (#22113)

Antonio González Viegas 4 tahun lalu
induk
melakukan
4ebe96fc75

+ 764 - 315
examples/jsm/loaders/IFCLoader.js

@@ -1,435 +1,884 @@
-//Docs: https://agviegas.github.io/ifcjs-docs/#/
-
-import * as WebIFC from './ifc/web-ifc-api.js';
 import {
-	FileLoader,
-	Loader,
+	IFCRELAGGREGATES,
+	IFCRELCONTAINEDINSPATIALSTRUCTURE,
+	IFCRELDEFINESBYPROPERTIES,
+	IFCRELDEFINESBYTYPE,
+	IFCPROJECT,
+	IfcAPI,
+} from "./ifc/web-ifc-api.js";
+import {
+	BufferAttribute,
+	BufferGeometry,
 	Mesh,
+	Matrix4,
 	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 });
-
+	Group,
+	Loader,
+	FileLoader,
+} from "../../../build/three.module.js";
+import { BufferGeometryUtils } from "../utils/BufferGeometryUtils.js";
+
+const IdAttrName = "expressID";
+const merge = (geoms, createGroups = false) => {
+	return BufferGeometryUtils.mergeBufferGeometries(geoms, createGroups);
+};
+const newFloatAttr = (data, size) => {
+	return new BufferAttribute(new Float32Array(data), size);
+};
+const newIntAttr = (data, size) => {
+	return new BufferAttribute(new Uint32Array(data), size);
+};
+const DEFAULT = "default";
+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",
+	},
+	type: {
+		name: IFCRELDEFINESBYTYPE,
+		relating: "RelatingType",
+		related: "RelatedObjects",
+		key: "hasType",
+	},
+};
+
+class IFCParser {
+	constructor(state) {
+		this.currentID = -1;
+		this.state = state;
 	}
 
-	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 {
+	async parse(buffer) {
+		if (this.state.api.wasmModule === undefined) await this.state.api.Init();
+		this.currentID = this.newIfcModel(buffer);
+		return this.loadAllGeometry();
+	}
 
-					onLoad( await scope.parse( buffer ) );
+	initializeMeshBVH(computeBoundsTree, disposeBoundsTree, acceleratedRaycast) {
+		this.computeBoundsTree = computeBoundsTree;
+		this.disposeBoundsTree = disposeBoundsTree;
+		this.acceleratedRaycast = acceleratedRaycast;
+		this.setupThreeMeshBVH();
+	}
 
-				} catch ( e ) {
+	setupThreeMeshBVH() {
+		if (
+			!this.computeBoundsTree ||
+			!this.disposeBoundsTree ||
+			!this.acceleratedRaycast
+		)
+			return;
+		BufferGeometry.prototype.computeBoundsTree = this.computeBoundsTree;
+		BufferGeometry.prototype.disposeBoundsTree = this.disposeBoundsTree;
+		Mesh.prototype.raycast = this.acceleratedRaycast;
+	}
 
-					if ( onError ) {
+	applyThreeMeshBVH(geometry) {
+		if (this.computeBoundsTree) geometry.computeBoundsTree();
+	}
 
-						onError( e );
+	newIfcModel(buffer) {
+		const data = new Uint8Array(buffer);
+		const modelID = this.state.api.OpenModel(data);
+		this.state.models[modelID] = {
+			modelID,
+			mesh: {},
+			items: {},
+			types: {},
+		};
+		return modelID;
+	}
 
-					} else {
+	loadAllGeometry() {
+		this.saveAllPlacedGeometriesByMaterial();
+		return this.generateAllGeometriesByMaterial();
+	}
 
-						console.error( e );
+	generateAllGeometriesByMaterial() {
+		const { geometry, materials } = this.getGeometryAndMaterials();
+		this.applyThreeMeshBVH(geometry);
+		const mesh = new Mesh(geometry, materials);
+		mesh.modelID = this.currentID;
+		this.state.models[this.currentID].mesh = mesh;
+		return mesh;
+	}
 
-					}
+	getGeometryAndMaterials() {
+		const items = this.state.models[this.currentID].items;
+		const mergedByMaterial = [];
+		const materials = [];
+		for (let materialID in items) {
+			materials.push(items[materialID].material);
+			const geometries = Object.values(items[materialID].geometries);
+			mergedByMaterial.push(merge(geometries));
+		}
+		const geometry = merge(mergedByMaterial, true);
+		return {
+			geometry,
+			materials,
+		};
+	}
 
-					scope.manager.itemError( url );
+	saveAllPlacedGeometriesByMaterial() {
+		const flatMeshes = this.state.api.LoadAllGeometry(this.currentID);
+		for (let i = 0; i < flatMeshes.size(); i++) {
+			const flatMesh = flatMeshes.get(i);
+			const placedGeom = flatMesh.geometries;
+			for (let j = 0; j < placedGeom.size(); j++) {
+				this.savePlacedGeometry(placedGeom.get(j), flatMesh.expressID);
+			}
+		}
+	}
 
-				}
+	savePlacedGeometry(placedGeometry, id) {
+		const geometry = this.getBufferGeometry(placedGeometry);
+		geometry.computeVertexNormals();
+		const matrix = this.getMeshMatrix(placedGeometry.flatTransformation);
+		geometry.applyMatrix4(matrix);
+		this.saveGeometryByMaterial(geometry, placedGeometry, id);
+	}
 
-			},
-			onProgress,
-			onError
+	getBufferGeometry(placed) {
+		const geometry = this.state.api.GetGeometry(
+			this.currentID,
+			placed.geometryExpressID
 		);
-
+		const vertexData = this.getVertices(geometry);
+		const indices = this.getIndices(geometry);
+		const { vertices, normals } = this.extractVertexData(vertexData);
+		return this.ifcGeomToBufferGeom(vertices, normals, indices);
 	}
 
-	setWasmPath( path ) {
-
-		ifcAPI.SetWasmPath( path );
-
+	getVertices(geometry) {
+		const vData = geometry.GetVertexData();
+		const vDataSize = geometry.GetVertexDataSize();
+		return this.state.api.GetVertexArray(vData, vDataSize);
 	}
 
-	getExpressId( faceIndex ) {
+	getIndices(geometry) {
+		const iData = geometry.GetIndexData();
+		const iDataSize = geometry.GetIndexDataSize();
+		return this.state.api.GetIndexArray(iData, iDataSize);
+	}
 
-		for (let index in this.mapFaceindexID) {
+	getMeshMatrix(matrix) {
+		const mat = new Matrix4();
+		mat.fromArray(matrix);
+		return mat;
+	}
 
-		  if (parseInt(index) >= faceIndex) return this.mapFaceindexID[index];
+	ifcGeomToBufferGeom(vertices, normals, indexData) {
+		const geometry = new BufferGeometry();
+		geometry.setAttribute("position", newFloatAttr(vertices, 3));
+		geometry.setAttribute("normal", newFloatAttr(normals, 3));
+		geometry.setIndex(new BufferAttribute(indexData, 1));
+		return geometry;
+	}
 
+	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 -1;
+		return {
+			vertices,
+			normals,
+		};
 	}
 
-	highlightItems( expressIds, scene, material = this.highlightMaterial ) {
-
-		this.removePreviousSelection(scene);
-
-		expressIds.forEach((id) => {
+	saveGeometryByMaterial(geom, placedGeom, id) {
+		const color = placedGeom.color;
+		const colorID = `${color.x}${color.y}${color.z}${color.w}`;
+		this.storeGeometryAttribute(id, geom);
+		this.createMaterial(colorID, color);
+		const item = this.state.models[this.currentID].items[colorID];
+		const currentGeom = item.geometries[id];
+		if (!currentGeom) return (item.geometries[id] = geom);
+		const merged = merge([currentGeom, geom]);
+		item.geometries[id] = merged;
+	}
 
-			if (!this.mapIDGeometry[id]) return;
-			var mesh = new Mesh(this.mapIDGeometry[id], material);
-			mesh.renderOrder = 1;
-			scene.add(mesh);
-			this.selectedObjects.push(mesh);
-			return;
+	storeGeometryAttribute(id, geometry) {
+		const size = geometry.attributes.position.count;
+		const idAttribute = new Array(size).fill(id);
+		geometry.setAttribute(IdAttrName, newIntAttr(idAttribute, 1));
+	}
 
+	createMaterial(colorID, color) {
+		const items = this.state.models[this.currentID].items;
+		if (items[colorID]) return;
+		const col = new Color(color.x, color.y, color.z);
+		const newMaterial = new MeshLambertMaterial({
+			color: col,
+			side: DoubleSide,
 		});
+		newMaterial.transparent = color.w !== 1;
+		if (newMaterial.transparent) newMaterial.opacity = color.w;
+		items[colorID] = {
+			material: newMaterial,
+			geometries: {},
+		};
 	}
+}
 
-	removePreviousSelection( scene ) {
-
-		if (this.selectedObjects.length > 0){
-
-			this.selectedObjects.forEach((object) => scene.remove(object));
-
-		} 
+class SubsetManager {
+	constructor(state) {
+		this.state = state;
+		this.selected = {};
 	}
 
-	setItemsVisibility( expressIds, geometry, visible = false ) {
-
-		this.setupVisibility(geometry);
-		var previous = 0;
+	getSubset(modelID, material) {
+		const currentMat = this.matIDNoConfig(modelID, material);
+		if (!this.selected[currentMat]) return null;
+		return this.selected[currentMat].mesh;
+	}
 
-		for (var current in this.mapFaceindexID) {
+	removeSubset(modelID, scene, material) {
+		const currentMat = this.matIDNoConfig(modelID, material);
+		if (!this.selected[currentMat]) return;
+		if (scene) scene.remove(this.selected[currentMat].mesh);
+		delete this.selected[currentMat];
+	}
 
-			if (expressIds.includes(this.mapFaceindexID[current])) {
+	createSubset(config) {
+		if (!this.isConfigValid(config)) return;
+		if (this.isPreviousSelection(config)) return;
+		if (this.isEasySelection(config))
+			return this.addToPreviousSelection(config);
+		this.updatePreviousSelection(config.scene, config);
+		return this.createSelectionInScene(config);
+	}
 
-				for (var i = previous; i <= current; i++) this.setVertexVisibility(geometry, i, visible);
+	createSelectionInScene(config) {
+		const filtered = this.filter(config);
+		const { geomsByMaterial, materials } = this.getGeomAndMat(filtered);
+		const hasDefaultMaterial = this.matID(config) == DEFAULT;
+		const geometry = merge(geomsByMaterial, hasDefaultMaterial);
+		const mats = hasDefaultMaterial ? materials : config.material;
+		const mesh = new Mesh(geometry, mats);
+		this.selected[this.matID(config)].mesh = mesh;
+		mesh.modelID = config.modelID;
+		config.scene.add(mesh);
+		return mesh;
+	}
 
-			}
+	isConfigValid(config) {
+		return (
+			this.isValid(config.scene) &&
+			this.isValid(config.modelID) &&
+			this.isValid(config.ids) &&
+			this.isValid(config.removePrevious)
+		);
+	}
 
-			previous = current;
+	isValid(item) {
+		return item != undefined && item != null;
+	}
 
+	getGeomAndMat(filtered) {
+		const geomsByMaterial = [];
+		const materials = [];
+		for (let matID in filtered) {
+			const geoms = Object.values(filtered[matID].geometries);
+			if (!geoms.length) continue;
+			materials.push(filtered[matID].material);
+			if (geoms.length > 1) geomsByMaterial.push(merge(geoms));
+			else geomsByMaterial.push(...geoms);
 		}
-
-		geometry.attributes.visibility.needsUpdate = true;
-
+		return {
+			geomsByMaterial,
+			materials,
+		};
 	}
 
-	setVertexVisibility( geometry, index, visible ) {
+	updatePreviousSelection(scene, config) {
+		const previous = this.selected[this.matID(config)];
+		if (!previous) return this.newSelectionGroup(config);
+		scene.remove(previous.mesh);
+		config.removePrevious
+			? (previous.ids = new Set(config.ids))
+			: config.ids.forEach((id) => previous.ids.add(id));
+	}
 
-		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);
-		
+	newSelectionGroup(config) {
+		this.selected[this.matID(config)] = {
+			ids: new Set(config.ids),
+			mesh: {},
+		};
 	}
 
-	setupVisibility( geometry ) {
+	isPreviousSelection(config) {
+		if (!this.selected[this.matID(config)]) return false;
+		if (this.containsIds(config)) return true;
+		const previousIds = this.selected[this.matID(config)].ids;
+		return JSON.stringify(config.ids) === JSON.stringify(previousIds);
+	}
 
-		if (!geometry.attributes.visibility) {
+	containsIds(config) {
+		const newIds = config.ids;
+		const previous = Array.from(this.selected[this.matID(config)].ids);
+		return newIds.every(
+			(
+				(i) => (v) =>
+					(i = previous.indexOf(v, i) + 1)
+			)(0)
+		);
+	}
 
-		  var visible = new Float32Array(geometry.getAttribute('position').count);
-		  geometry.setAttribute('visibility', new BufferAttribute(visible, 1));
+	addToPreviousSelection(config) {
+		const previous = this.selected[this.matID(config)];
+		const filtered = this.filter(config);
+		const geometries = Object.values(filtered)
+			.map((i) => Object.values(i.geometries))
+			.flat();
+		const previousGeom = previous.mesh.geometry;
+		previous.mesh.geometry = merge([previousGeom, ...geometries]);
+		config.ids.forEach((id) => previous.ids.add(id));
+	}
 
+	filter(config) {
+		const items = this.state.models[config.modelID].items;
+		const filtered = {};
+		for (let matID in items) {
+			filtered[matID] = {
+				material: items[matID].material,
+				geometries: this.filterGeometries(
+					new Set(config.ids),
+					items[matID].geometries
+				),
+			};
 		}
-
+		return filtered;
 	}
 
-	getItemProperties( elementID, all = false ) {
+	filterGeometries(selectedIDs, geometries) {
+		const ids = Array.from(selectedIDs);
+		return Object.keys(geometries)
+			.filter((key) => ids.includes(parseInt(key, 10)))
+			.reduce((obj, key) => {
+				return {
+					...obj,
+					[key]: geometries[key],
+				};
+			}, {});
+	}
 
-		const properties = ifcAPI.GetLine(this.modelID, elementID);
-	
-		if (all) {
+	isEasySelection(config) {
+		const matID = this.matID(config);
+		const def = this.matIDNoConfig(config.modelID);
+		if (!config.removePrevious && matID != def && this.selected[matID])
+			return true;
+	}
 
-		  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;
+	matID(config) {
+		if (!config.material) return DEFAULT;
+		const name = config.material.uuid || DEFAULT;
+		return name.concat(" - ").concat(config.modelID.toString());
+	}
 
+	matIDNoConfig(modelID, material) {
+		let name = DEFAULT;
+		if (material) name = material.uuid;
+		return name.concat(" - ").concat(modelID.toString());
 	}
+}
 
-	getSpatialStructure() {
+const IfcElements = {
+	103090709: "IFCPROJECT",
+	4097777520: "IFCSITE",
+	4031249490: "IFCBUILDING",
+	3124254112: "IFCBUILDINGSTOREY",
+	3856911033: "IFCSPACE",
+	25142252: "IFCCONTROLLER",
+	32344328: "IFCBOILER",
+	76236018: "IFCLAMP",
+	90941305: "IFCPUMP",
+	177149247: "IFCAIRTERMINALBOX",
+	182646315: "IFCFLOWINSTRUMENT",
+	263784265: "IFCFURNISHINGELEMENT",
+	264262732: "IFCELECTRICGENERATOR",
+	277319702: "IFCAUDIOVISUALAPPLIANCE",
+	310824031: "IFCPIPEFITTING",
+	331165859: "IFCSTAIR",
+	342316401: "IFCDUCTFITTING",
+	377706215: "IFCMECHANICALFASTENER",
+	395920057: "IFCDOOR",
+	402227799: "IFCELECTRICMOTOR",
+	413509423: "IFCSYSTEMFURNITUREELEMENT",
+	484807127: "IFCEVAPORATOR",
+	486154966: "IFCWINDOWSTANDARDCASE",
+	629592764: "IFCLIGHTFIXTURE",
+	630975310: "IFCUNITARYCONTROLELEMENT",
+	635142910: "IFCCABLECARRIERFITTING",
+	639361253: "IFCCOIL",
+	647756555: "IFCFASTENER",
+	707683696: "IFCFLOWSTORAGEDEVICE",
+	738039164: "IFCPROTECTIVEDEVICE",
+	753842376: "IFCBEAM",
+	812556717: "IFCTANK",
+	819412036: "IFCFILTER",
+	843113511: "IFCCOLUMN",
+	862014818: "IFCELECTRICDISTRIBUTIONBOARD",
+	900683007: "IFCFOOTING",
+	905975707: "IFCCOLUMNSTANDARDCASE",
+	926996030: "IFCVOIDINGFEATURE",
+	979691226: "IFCREINFORCINGBAR",
+	987401354: "IFCFLOWSEGMENT",
+	1003880860: "IFCELECTRICTIMECONTROL",
+	1051757585: "IFCCABLEFITTING",
+	1052013943: "IFCDISTRIBUTIONCHAMBERELEMENT",
+	1062813311: "IFCDISTRIBUTIONCONTROLELEMENT",
+	1073191201: "IFCMEMBER",
+	1095909175: "IFCBUILDINGELEMENTPROXY",
+	1156407060: "IFCPLATESTANDARDCASE",
+	1162798199: "IFCSWITCHINGDEVICE",
+	1329646415: "IFCSHADINGDEVICE",
+	1335981549: "IFCDISCRETEACCESSORY",
+	1360408905: "IFCDUCTSILENCER",
+	1404847402: "IFCSTACKTERMINAL",
+	1426591983: "IFCFIRESUPPRESSIONTERMINAL",
+	1437502449: "IFCMEDICALDEVICE",
+	1509553395: "IFCFURNITURE",
+	1529196076: "IFCSLAB",
+	1620046519: "IFCTRANSPORTELEMENT",
+	1634111441: "IFCAIRTERMINAL",
+	1658829314: "IFCENERGYCONVERSIONDEVICE",
+	1677625105: "IFCCIVILELEMENT",
+	1687234759: "IFCPILE",
+	1904799276: "IFCELECTRICAPPLIANCE",
+	1911478936: "IFCMEMBERSTANDARDCASE",
+	1945004755: "IFCDISTRIBUTIONELEMENT",
+	1973544240: "IFCCOVERING",
+	1999602285: "IFCSPACEHEATER",
+	2016517767: "IFCROOF",
+	2056796094: "IFCAIRTOAIRHEATRECOVERY",
+	2058353004: "IFCFLOWCONTROLLER",
+	2068733104: "IFCHUMIDIFIER",
+	2176052936: "IFCJUNCTIONBOX",
+	2188021234: "IFCFLOWMETER",
+	2223149337: "IFCFLOWTERMINAL",
+	2262370178: "IFCRAILING",
+	2272882330: "IFCCONDENSER",
+	2295281155: "IFCPROTECTIVEDEVICETRIPPINGUNIT",
+	2320036040: "IFCREINFORCINGMESH",
+	2347447852: "IFCTENDONANCHOR",
+	2391383451: "IFCVIBRATIONISOLATOR",
+	2391406946: "IFCWALL",
+	2474470126: "IFCMOTORCONNECTION",
+	2769231204: "IFCVIRTUALELEMENT",
+	2814081492: "IFCENGINE",
+	2906023776: "IFCBEAMSTANDARDCASE",
+	2938176219: "IFCBURNER",
+	2979338954: "IFCBUILDINGELEMENTPART",
+	3024970846: "IFCRAMP",
+	3026737570: "IFCTUBEBUNDLE",
+	3027962421: "IFCSLABSTANDARDCASE",
+	3040386961: "IFCDISTRIBUTIONFLOWELEMENT",
+	3053780830: "IFCSANITARYTERMINAL",
+	3079942009: "IFCOPENINGSTANDARDCASE",
+	3087945054: "IFCALARM",
+	3101698114: "IFCSURFACEFEATURE",
+	3127900445: "IFCSLABELEMENTEDCASE",
+	3132237377: "IFCFLOWMOVINGDEVICE",
+	3171933400: "IFCPLATE",
+	3221913625: "IFCCOMMUNICATIONSAPPLIANCE",
+	3242481149: "IFCDOORSTANDARDCASE",
+	3283111854: "IFCRAMPFLIGHT",
+	3296154744: "IFCCHIMNEY",
+	3304561284: "IFCWINDOW",
+	3310460725: "IFCELECTRICFLOWSTORAGEDEVICE",
+	3319311131: "IFCHEATEXCHANGER",
+	3415622556: "IFCFAN",
+	3420628829: "IFCSOLARDEVICE",
+	3493046030: "IFCGEOGRAPHICELEMENT",
+	3495092785: "IFCCURTAINWALL",
+	3508470533: "IFCFLOWTREATMENTDEVICE",
+	3512223829: "IFCWALLSTANDARDCASE",
+	3518393246: "IFCDUCTSEGMENT",
+	3571504051: "IFCCOMPRESSOR",
+	3588315303: "IFCOPENINGELEMENT",
+	3612865200: "IFCPIPESEGMENT",
+	3640358203: "IFCCOOLINGTOWER",
+	3651124850: "IFCPROJECTIONELEMENT",
+	3694346114: "IFCOUTLET",
+	3747195512: "IFCEVAPORATIVECOOLER",
+	3758799889: "IFCCABLECARRIERSEGMENT",
+	3824725483: "IFCTENDON",
+	3825984169: "IFCTRANSFORMER",
+	3902619387: "IFCCHILLER",
+	4074379575: "IFCDAMPER",
+	4086658281: "IFCSENSOR",
+	4123344466: "IFCELEMENTASSEMBLY",
+	4136498852: "IFCCOOLEDBEAM",
+	4156078855: "IFCWALLELEMENTEDCASE",
+	4175244083: "IFCINTERCEPTOR",
+	4207607924: "IFCVALVE",
+	4217484030: "IFCCABLESEGMENT",
+	4237592921: "IFCWASTETERMINAL",
+	4252922144: "IFCSTAIRFLIGHT",
+	4278956645: "IFCFLOWFITTING",
+	4288193352: "IFCACTUATOR",
+	4292641817: "IFCUNITARYEQUIPMENT",
+};
+
+class PropertyManager {
+	constructor(state) {
+		this.state = state;
+	}
 
-		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;
+	getExpressId(geometry, faceIndex) {
+		if (!geometry.index) return;
+		const geoIndex = geometry.index.array;
+		return geometry.attributes[IdAttrName].getX(geoIndex[3 * faceIndex]);
+	}
 
+	getItemProperties(modelID, id, recursive = false) {
+		return this.state.api.GetLine(modelID, id, recursive);
 	}
-	
-	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));
-		
+	getAllItemsOfType(modelID, type, verbose) {
+		const items = [];
+		const lines = this.state.api.GetLineIDsWithType(modelID, type);
+		for (let i = 0; i < lines.size(); i++) items.push(lines.get(i));
+		if (verbose) return items.map((id) => this.state.api.GetLine(modelID, id));
+		return items;
 	}
-	
-	getAllRelatedItemsOfType ( elementID, type, relation, relatedProperty ) {
 
-		const lines = ifcAPI.GetLineIDsWithType(this.modelID, type);
-		const IDs = [];
+	getPropertySets(modelID, elementID, recursive = false) {
+		const propSetIds = this.getAllRelatedItemsOfType(
+			modelID,
+			elementID,
+			PropsNames.psets
+		);
+		return propSetIds.map((id) =>
+			this.state.api.GetLine(modelID, id, recursive)
+		);
+	}
 
-		for (let i = 0; i < lines.size(); i++) {
+	getTypeProperties(modelID, elementID, recursive = false) {
+		const typeId = this.getAllRelatedItemsOfType(
+			modelID,
+			elementID,
+			PropsNames.type
+		);
+		return typeId.map((id) => this.state.api.GetLine(modelID, id, recursive));
+	}
 
-		  	const relID = lines.get(i);
-		  	const rel = ifcAPI.GetLine(this.modelID, relID);
-		  	const relatedItems = rel[relation];
-		  	let foundElement = false;
-	
-		  	if (Array.isArray(relatedItems)){
+	getSpatialStructure(modelID) {
+		const chunks = this.getSpatialTreeChunks(modelID);
+		const projectID = this.state.api
+			.GetLineIDsWithType(modelID, IFCPROJECT)
+			.get(0);
+		const project = this.newIfcProject(projectID);
+		this.getSpatialNode(modelID, project, chunks);
+		return project;
+	}
 
-			  relatedItems.forEach((relID) => {
+	newIfcProject(id) {
+		return {
+			expressID: id,
+			type: "IFCPROJECT",
+			children: [],
+		};
+	}
 
-				  if (relID.value === elementID) foundElement = true;
+	getSpatialTreeChunks(modelID) {
+		const treeChunks = {};
+		this.getChunks(modelID, treeChunks, PropsNames.aggregates);
+		this.getChunks(modelID, treeChunks, PropsNames.spatial);
+		return treeChunks;
+	}
 
-				});
+	getChunks(modelID, chunks, propNames) {
+		const relation = this.state.api.GetLineIDsWithType(modelID, propNames.name);
+		for (let i = 0; i < relation.size(); i++) {
+			const rel = this.state.api.GetLine(modelID, relation.get(i), false);
+			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);
 			}
-			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 );
+	getSpatialNode(modelID, node, treeChunks) {
+		this.getChildren(modelID, node, treeChunks, PropsNames.aggregates);
+		this.getChildren(modelID, node, treeChunks, PropsNames.spatial);
+	}
 
-		function loadAllGeometry(modelID) {
+	getChildren(modelID, node, treeChunks, propNames) {
+		const children = treeChunks[node.expressID];
+		if (children == undefined || children == null) return;
+		const prop = propNames.key;
+		node[prop] = children.map((child) => {
+			const node = this.newNode(modelID, child);
+			this.getSpatialNode(modelID, node, treeChunks);
+			return node;
+		});
+	}
 
-			saveAllPlacedGeometriesByMaterial(modelID);
-			return generateAllGeometriesByMaterial();
+	newNode(modelID, id) {
+		const typeID = this.state.models[modelID].types[id].toString();
+		const typeName = IfcElements[typeID];
+		return {
+			expressID: id,
+			type: typeName,
+			children: [],
+		};
+	}
 
+	getAllRelatedItemsOfType(modelID, id, propNames) {
+		const lines = this.state.api.GetLineIDsWithType(modelID, propNames.name);
+		const IDs = [];
+		for (let i = 0; i < lines.size(); i++) {
+			const rel = this.state.api.GetLine(modelID, lines.get(i));
+			const isRelated = this.isRelated(id, rel, propNames);
+			if (isRelated) this.getRelated(rel, propNames, IDs);
 		}
-	
-		function generateAllGeometriesByMaterial() {
+		return IDs;
+	}
 
-			const { materials, geometries } = getMaterialsAndGeometries();
-			const allGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries, true);
-			return new Mesh(allGeometry, materials);
+	getRelated(rel, propNames, IDs) {
+		const element = rel[propNames.relating];
+		if (!Array.isArray(element)) IDs.push(element.value);
+		else element.forEach((ele) => IDs.push(ele.value));
+	}
 
+	isRelated(id, rel, propNames) {
+		const relatedItems = rel[propNames.related];
+		if (Array.isArray(relatedItems)) {
+			const values = relatedItems.map((item) => item.value);
+			return values.includes(id);
 		}
-	
-		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 relatedItems.value === id;
+	}
+}
 
-			return { materials, geometries };
+class TypeManager {
+	constructor(state) {
+		this.state = state;
+	}
 
+	getAllTypes() {
+		for (let modelID in this.state.models) {
+			const types = this.state.models[modelID].types;
+			if (Object.keys(types).length == 0)
+				this.getAllTypesOfModel(parseInt(modelID));
 		}
-	
-		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;
+	getAllTypesOfModel(modelID) {
+		this.state.models[modelID].types;
+		const elements = Object.keys(IfcElements).map((e) => parseInt(e));
+		const types = this.state.models[modelID].types;
+		elements.forEach((type) => {
+			const lines = this.state.api.GetLineIDsWithType(modelID, type);
+			for (let i = 0; i < lines.size(); i++) types[lines.get(i)] = type;
+		});
+	}
+}
 
-				for (let j = 0; j < placedGeometries.size(); j++) {
+let modelIdCounter = 0;
 
-					savePlacedGeometryByMaterial(modelID, placedGeometries.get(j), productId);
+class IFCModel extends Group {
+	constructor(mesh, ifc) {
+		super();
+		this.mesh = mesh;
+		this.ifc = ifc;
+		this.modelID = modelIdCounter++;
+	}
 
-				}
-			}
-		}
+	setWasmPath(path) {
+		this.ifc.setWasmPath(path);
+	}
 
-		function savePlacedGeometryByMaterial(modelID, placedGeometry, productId) {
+	close(scene) {
+		this.ifc.close(this.modelID, scene);
+	}
 
-			const geometry = getBufferGeometry(modelID, placedGeometry);
-			geometry.computeVertexNormals();
-			const matrix = getMeshMatrix(placedGeometry.flatTransformation);
-			geometry.applyMatrix4(matrix);
-			storeGeometryForHighlight(productId, geometry);
-			saveGeometryByMaterial(geometry, placedGeometry, productId);
+	getExpressId(geometry, faceIndex) {
+		return this.ifc.getExpressId(geometry, faceIndex);
+	}
 
-		}
+	getAllItemsOfType(type, verbose) {
+		return this.ifc.getAllItemsOfType(this.modelID, type, verbose);
+	}
 
-		function getBufferGeometry(modelID, placedGeometry) {
+	getItemProperties(id, recursive = false) {
+		return this.ifc.getItemProperties(this.modelID, id, recursive);
+	}
 
-			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);
+	getPropertySets(id, recursive = false) {
+		return this.ifc.getPropertySets(this.modelID, id, recursive);
+	}
 
-		}
+	getTypeProperties(id, recursive = false) {
+		return this.ifc.getTypeProperties(this.modelID, id, recursive);
+	}
 
-		function getMeshMatrix(matrix) {
+	getIfcType(id) {
+		return this.ifc.getIfcType(this.modelID, id);
+	}
 
-			const mat = new Matrix4();
-			mat.fromArray(matrix);
-			return mat;
+	getSpatialStructure() {
+		return this.ifc.getSpatialStructure(this.modelID);
+	}
 
-		}
-	
-		function storeGeometryForHighlight(productId, geometry) {
+	getSubset(material) {
+		return this.ifc.getSubset(this.modelID, material);
+	}
 
-			if (!mapIDGeometry[productId]) {
+	removeSubset(scene, material) {
+		this.ifc.removeSubset(this.modelID, scene, material);
+	}
 
-				mapIDGeometry[productId] = geometry;
-				return;
+	createSubset(config) {
+		const modelConfig = {
+			...config,
+			modelID: this.modelID,
+		};
+		return this.ifc.createSubset(modelConfig);
+	}
+}
 
-			}
+class IFCManager {
+	constructor() {
+		this.state = {
+			models: [],
+			api: new IfcAPI(),
+		};
+		this.parser = new IFCParser(this.state);
+		this.subsets = new SubsetManager(this.state);
+		this.properties = new PropertyManager(this.state);
+		this.types = new TypeManager(this.state);
+	}
 
-			const geometries = [mapIDGeometry[productId], geometry];
-			mapIDGeometry[productId] = BufferGeometryUtils.mergeBufferGeometries(geometries, true);
+	async parse(buffer) {
+		const mesh = await this.parser.parse(buffer);
+		this.types.getAllTypes();
+		return new IFCModel(mesh, this);
+	}
 
-		}
-	
-		function ifcGeometryToBuffer(vertexData, indexData) {
+	setWasmPath(path) {
+		this.state.api.SetWasmPath(path);
+	}
 
-			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;
+	setupThreeMeshBVH(computeBoundsTree, disposeBoundsTree, acceleratedRaycast) {
+		this.parser.initializeMeshBVH(
+			computeBoundsTree,
+			disposeBoundsTree,
+			acceleratedRaycast
+		);
+	}
 
-		}
-	
-		function extractVertexData(vertexData) {
+	close(modelID, scene) {
+		this.state.api.CloseModel(modelID);
+		if (scene) scene.remove(this.state.models[modelID].mesh);
+		delete this.state.models[modelID];
+	}
 
-			const vertices = [];
-			const normals = [];
-			let isNormalData = false;
+	getExpressId(geometry, faceIndex) {
+		return this.properties.getExpressId(geometry, faceIndex);
+	}
 
-			for (let i = 0; i < vertexData.length; i++) {
+	getAllItemsOfType(modelID, type, verbose) {
+		return this.properties.getAllItemsOfType(modelID, type, verbose);
+	}
 
-				isNormalData ? normals.push(vertexData[i]) : vertices.push(vertexData[i]);
-				if ((i + 1) % 3 == 0) isNormalData = !isNormalData;
+	getItemProperties(modelID, id, recursive = false) {
+		return this.properties.getItemProperties(modelID, id, recursive);
+	}
 
-			}
+	getPropertySets(modelID, id, recursive = false) {
+		return this.properties.getPropertySets(modelID, id, recursive);
+	}
 
-			return { vertices, normals };
+	getTypeProperties(modelID, id, recursive = false) {
+		return this.properties.getTypeProperties(modelID, id, recursive);
+	}
 
-		}
-	
-		function saveGeometryByMaterial(geometry, placedGeometry, productId) {
+	getIfcType(modelID, id) {
+		const typeID = this.state.models[modelID].types[id];
+		return IfcElements[typeID.toString()];
+	}
 
-			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;
+	getSpatialStructure(modelID) {
+		return this.properties.getSpatialStructure(modelID);
+	}
 
-		}
-	
-		function createMaterial(id, color) {
+	getSubset(modelID, material) {
+		return this.subsets.getSubset(modelID, material);
+	}
 
-			if (!geometryByMaterials[id]){
+	removeSubset(modelID, scene, material) {
+		this.subsets.removeSubset(modelID, scene, material);
+	}
 
-				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);
+	createSubset(config) {
+		return this.subsets.createSubset(config);
+	}
+}
 
-			}
-		}
-	
-		function initializeGeometryByMaterial(newMaterial) {
+class IFCLoader extends Loader {
+	constructor(manager) {
+		super(manager);
+		this.ifcManager = new IFCManager();
+	}
 
-			return {
-				material: newMaterial,
-				geometry: [],
-				indices: {},
-				lastIndex: 0
+	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 {
+					if (typeof buffer == "string") {
+						throw new Error("IFC files must be given as a buffer!");
+					}
+					onLoad(await scope.parse(buffer));
+				} catch (e) {
+					if (onError) {
+						onError(e);
+					} else {
+						console.error(e);
+					}
+					scope.manager.itemError(url);
+				}
+			},
+			onProgress,
+			onError
+		);
+	}
 
-			};
-	  	}
-
-		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>`
-			);
-		}
+	parse(buffer) {
+		return this.ifcManager.parse(buffer);
 	}
 }
 
 export { IFCLoader };
+//# sourceMappingURL=IFCLoader.js.map

+ 11 - 3
examples/jsm/loaders/ifc/web-ifc-api.js

@@ -4696,7 +4696,7 @@ var require_web_ifc = __commonJS((exports, module) => {
       __ATINIT__.push({func: function() {
         ___wasm_call_ctors();
       }});
-      var asmLibraryArg = {x: ___assert_fail, A: ___sys_fcntl64, O: ___sys_ioctl, P: ___sys_open, U: __embind_finalize_value_array, v: __embind_finalize_value_object, S: __embind_register_bool, t: __embind_register_class, s: __embind_register_class_constructor, d: __embind_register_class_function, R: __embind_register_emval, C: __embind_register_float, j: __embind_register_function, l: __embind_register_integer, i: __embind_register_memory_view, D: __embind_register_std_string, u: __embind_register_std_wstring, V: __embind_register_value_array, g: __embind_register_value_array_element, w: __embind_register_value_object, m: __embind_register_value_object_field, T: __embind_register_void, q: __emval_as, b: __emval_decref, L: __emval_get_global, n: __emval_get_property, k: __emval_incref, Q: __emval_instanceof, E: __emval_is_number, y: __emval_new_array, f: __emval_new_cstring, r: __emval_new_object, p: __emval_run_destructors, h: __emval_set_property, e: __emval_take_value, c: _abort, M: _clock_gettime, H: _emscripten_memcpy_big, o: _emscripten_resize_heap, J: _environ_get, K: _environ_sizes_get, B: _fd_close, N: _fd_read, F: _fd_seek, z: _fd_write, a: wasmMemory, G: _setTempRet0, I: _strftime_l};
+      var asmLibraryArg = {x: ___assert_fail, A: ___sys_fcntl64, O: ___sys_ioctl, P: ___sys_open, U: __embind_finalize_value_array, s: __embind_finalize_value_object, S: __embind_register_bool, v: __embind_register_class, u: __embind_register_class_constructor, d: __embind_register_class_function, R: __embind_register_emval, C: __embind_register_float, i: __embind_register_function, m: __embind_register_integer, j: __embind_register_memory_view, D: __embind_register_std_string, w: __embind_register_std_wstring, V: __embind_register_value_array, g: __embind_register_value_array_element, t: __embind_register_value_object, k: __embind_register_value_object_field, T: __embind_register_void, q: __emval_as, b: __emval_decref, L: __emval_get_global, n: __emval_get_property, l: __emval_incref, Q: __emval_instanceof, E: __emval_is_number, y: __emval_new_array, f: __emval_new_cstring, r: __emval_new_object, p: __emval_run_destructors, h: __emval_set_property, e: __emval_take_value, c: _abort, M: _clock_gettime, H: _emscripten_memcpy_big, o: _emscripten_resize_heap, J: _environ_get, K: _environ_sizes_get, B: _fd_close, N: _fd_read, F: _fd_seek, z: _fd_write, a: wasmMemory, G: _setTempRet0, I: _strftime_l};
       var asm = createWasm();
       var ___wasm_call_ctors = Module["___wasm_call_ctors"] = function() {
         return (___wasm_call_ctors = Module["___wasm_call_ctors"] = Module["asm"]["X"]).apply(null, arguments);
@@ -45299,9 +45299,14 @@ var IfcAPI = class {
       console.error(`Could not find wasm module at './web-ifc' from web-ifc-api.ts`);
     }
   }
-  OpenModel(filename, data) {
+  OpenModel(data, settings) {
     this.wasmModule["FS_createDataFile"]("/", "filename", data, true, true, true);
-    let result = this.wasmModule.OpenModel(filename);
+    let s = {
+      COORDINATE_TO_ORIGIN: false,
+      USE_FAST_BOOLS: false,
+      ...settings
+    };
+    let result = this.wasmModule.OpenModel(s);
     this.wasmModule["FS_unlink"]("/filename");
     return result;
   }
@@ -45399,6 +45404,9 @@ var IfcAPI = class {
   LoadAllGeometry(modelID) {
     return this.wasmModule.LoadAllGeometry(modelID);
   }
+  GetFlatMesh(modelID, expressID) {
+    return this.wasmModule.GetFlatMesh(modelID, expressID);
+  }
   SetWasmPath(path) {
     WasmPath = path;
   }

TEMPAT SAMPAH
examples/jsm/loaders/ifc/web-ifc.wasm


+ 2 - 2
examples/webgl_loader_ifc.html

@@ -61,10 +61,10 @@
 
 				//Setup IFC Loader
 				const ifcLoader = new IFCLoader();
-				ifcLoader.setWasmPath( 'jsm/loaders/ifc/' );
+				ifcLoader.ifcManager.setWasmPath( 'jsm/loaders/ifc/' );
 				ifcLoader.load( 'models/ifc/rac_advanced_sample_project.ifc', function ( model ) {
 
-					scene.add( model );
+					scene.add( model.mesh );
 					render();
 
 				} );