From bd68aa64ce62ee270bb8f6acdaa832a2c61428ce Mon Sep 17 00:00:00 2001 From: Juan Hoyos <84806689+HoyosJuan@users.noreply.github.com> Date: Mon, 6 May 2024 14:39:36 -0500 Subject: [PATCH] feat(core): changes and improves IfcPropertiesIndexer to IfcRelationsIndexer (#380) * IfcPropertiesIndexer improved * IfcPropertiesIndexer renamed to IfcRelationsIndexer to better reflect what it does * IfcRelationsIndexer tutorial complete --- .../src/fragment-stream-loader.ts | 28 +- .../src/ifc/IfcPropertiesIndexer/index.ts | 221 ------------ .../src/ifc/IfcRelationsIndexer/example.html | 14 + .../src/ifc/IfcRelationsIndexer/example.ts | 133 ++++++++ .../src/ifc/IfcRelationsIndexer/index.ts | 315 ++++++++++++++++++ packages/components/src/ifc/index.ts | 2 +- resources/small-relations.json | 1 + yarn.lock | 4 +- 8 files changed, 480 insertions(+), 238 deletions(-) delete mode 100644 packages/components/src/ifc/IfcPropertiesIndexer/index.ts create mode 100644 packages/components/src/ifc/IfcRelationsIndexer/example.html create mode 100644 packages/components/src/ifc/IfcRelationsIndexer/example.ts create mode 100644 packages/components/src/ifc/IfcRelationsIndexer/index.ts create mode 100644 resources/small-relations.json diff --git a/packages/components-front/src/fragments/FragmentIfcStreamer/src/fragment-stream-loader.ts b/packages/components-front/src/fragments/FragmentIfcStreamer/src/fragment-stream-loader.ts index 19c51ece6..d7d7a75e7 100644 --- a/packages/components-front/src/fragments/FragmentIfcStreamer/src/fragment-stream-loader.ts +++ b/packages/components-front/src/fragments/FragmentIfcStreamer/src/fragment-stream-loader.ts @@ -238,20 +238,20 @@ export class FragmentStreamLoader types, }; - const { indexesFile } = properties; - const fetched = await fetch(this.url + indexesFile); - const data = await fetched.json(); - - const first = Object.keys(data.entries)[0]; - const indices = await data.entries[first].json(); - - const indexer = this.components.get(OBC.IfcPropertiesIndexer); - const { indexMap } = indexer; - indexMap[group.uuid] = {}; - for (const index of indices) { - const id = index.shift(); - indexMap[group.uuid][id] = new Set(index); - } + // const { indexesFile } = properties; + // const fetched = await fetch(this.url + indexesFile); + // const data = await fetched.json(); + + // const first = Object.keys(data.entries)[0]; + // const indices = await data.entries[first].json(); + + // const indexer = this.components.get(OBC.IfcRelationsIndexer); + // const { indexMap } = indexer; + // indexMap[group.uuid] = {}; + // for (const index of indices) { + // const id = index.shift(); + // indexMap[group.uuid][id] = new Set(index); + // } } this.culler.needsUpdate = true; diff --git a/packages/components/src/ifc/IfcPropertiesIndexer/index.ts b/packages/components/src/ifc/IfcPropertiesIndexer/index.ts deleted file mode 100644 index 282987937..000000000 --- a/packages/components/src/ifc/IfcPropertiesIndexer/index.ts +++ /dev/null @@ -1,221 +0,0 @@ -import * as WEBIFC from "web-ifc"; -import { FragmentsGroup, IfcProperties } from "@thatopen/fragments"; -import { Disposable, Event, Component, Components } from "../../core"; -import { FragmentManager } from "../../fragments/FragmentManager"; -import { IfcPropertiesManager } from "../IfcPropertiesManager"; -import { IfcPropertiesUtils } from "../Utils"; - -interface IndexMap { - [modelID: string]: { [expressID: string]: Set }; -} - -export class IfcPropertiesIndexer extends Component implements Disposable { - static readonly uuid = "23a889ab-83b3-44a4-8bee-ead83438370b" as const; - - /** {@link Disposable.onDisposed} */ - readonly onDisposed = new Event(); - - enabled: boolean = true; - - relationsToProcess = [ - WEBIFC.IFCRELDEFINESBYPROPERTIES, - WEBIFC.IFCRELDEFINESBYTYPE, - WEBIFC.IFCRELASSOCIATESMATERIAL, - WEBIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE, - WEBIFC.IFCRELASSOCIATESCLASSIFICATION, - WEBIFC.IFCRELASSIGNSTOGROUP, - ]; - - indexMap: IndexMap = {}; - - private _propertiesManager: IfcPropertiesManager | null = null; - readonly onPropertiesManagerSet = new Event(); - - // private _entityUIPool: UIPool; - - set propertiesManager(manager: IfcPropertiesManager | null) { - if (this._propertiesManager) return; - this._propertiesManager = manager; - if (manager) { - manager.onElementToPset.add(async ({ model, psetID, elementID }) => { - const modelIndexMap = this.indexMap[model.uuid]; - if (!modelIndexMap) return; - this.setEntityIndex(model, elementID).add(psetID); - }); - - this.onPropertiesManagerSet.trigger(manager); - } - } - - get propertiesManager() { - return this._propertiesManager; - } - - constructor(components: Components) { - super(components); - this.components.add(IfcPropertiesIndexer.uuid, this); - const fragmentManager = components.get(FragmentManager); - fragmentManager.onFragmentsDisposed.add(this.onFragmentsDisposed); - } - - private onFragmentsDisposed = (data: { - groupID: string; - fragmentIDs: string[]; - }) => { - delete this.indexMap[data.groupID]; - }; - - async dispose() { - this.indexMap = {}; - (this.propertiesManager as any) = null; - - this.onPropertiesManagerSet.reset(); - const fragmentManager = this.components.get(FragmentManager); - fragmentManager.onFragmentsDisposed.remove(this.onFragmentsDisposed); - this.onDisposed.trigger(IfcPropertiesIndexer.uuid); - this.onDisposed.reset(); - } - - async getProperties(model: FragmentsGroup, id: string) { - if (!model.hasProperties) return null; - const modelProperties: IfcProperties | undefined = - model.getLocalProperties(); - if (modelProperties === undefined) { - return null; - } - const map = this.indexMap[model.uuid]; - if (!map) return null; - const indices = map[id]; - const idNumber = parseInt(id, 10); - const props = await model.getProperties(idNumber); - if (!props) { - throw new Error("Properties not found!"); - } - const nativeProperties = this.cloneProperty(props); - - if (!nativeProperties) { - throw new Error("Properties not found!"); - } - - const properties = [nativeProperties] as any[]; - - if (indices) { - for (const index of indices) { - const props = await model.getProperties(index); - if (!props) continue; - const pset = this.cloneProperty(props); - if (!pset) continue; - this.getPsetProperties(pset, modelProperties); - this.getNestedPsets(pset, modelProperties); - properties.push(pset); - } - } - - return properties; - } - - private getNestedPsets(pset: { [p: string]: any }, props: any) { - if (pset.HasPropertySets) { - for (const subPSet of pset.HasPropertySets) { - const psetID = subPSet.value; - subPSet.value = this.cloneProperty(props[psetID]); - this.getPsetProperties(subPSet.value, props); - } - } - } - - private getPsetProperties(pset: { [p: string]: any }, props: any) { - if (pset.HasProperties) { - for (const property of pset.HasProperties) { - const psetID = property.value; - const result = this.cloneProperty(props[psetID]); - property.value = { ...result }; - } - } - } - - async process(model: FragmentsGroup) { - if (!model.hasProperties) { - throw new Error("FragmentsGroup properties not found"); - } - this.indexMap[model.uuid] = {}; - // const relations: number[] = []; - // for (const typeID in IfcCategoryMap) { - // const name = IfcCategoryMap[typeID]; - // if (name.startsWith("IFCREL")) relations.push(Number(typeID)); - // } - const setEntities = [WEBIFC.IFCPROPERTYSET, WEBIFC.IFCELEMENTQUANTITY]; - for (const relation of this.relationsToProcess) { - await IfcPropertiesUtils.getRelationMap( - model, - relation, - // eslint-disable-next-line no-loop-func - async (relationID, relatedIDs) => { - const relationEntity = await model.getProperties(relationID); - if (!relationEntity) { - return; - } - if (!setEntities.includes(relationEntity.type)) { - this.setEntityIndex(model, relationID); - } - for (const expressID of relatedIDs) { - this.setEntityIndex(model, expressID).add(relationID); - } - }, - ); - } - } - - private setEntityIndex(model: FragmentsGroup, expressID: number) { - if (!this.indexMap[model.uuid][expressID]) - this.indexMap[model.uuid][expressID] = new Set(); - return this.indexMap[model.uuid][expressID]; - } - - private cloneProperty( - item: { [name: string]: any }, - result: { [name: string]: any } = {}, - ) { - if (!item) { - return result; - } - for (const key in item) { - const value = item[key]; - - const isArray = Array.isArray(value); - const isObject = typeof value === "object" && !isArray && value !== null; - - if (isArray) { - result[key] = []; - const subResult = result[key] as any[]; - this.clonePropertyArray(value, subResult); - } else if (isObject) { - result[key] = {}; - const subResult = result[key]; - this.cloneProperty(value, subResult); - } else { - result[key] = value; - } - } - return result; - } - - private clonePropertyArray(item: any[], result: any[]) { - for (const value of item) { - const isArray = Array.isArray(value); - const isObject = typeof value === "object" && !isArray && value !== null; - - if (isArray) { - const subResult = [] as any[]; - result.push(subResult); - this.clonePropertyArray(value, subResult); - } else if (isObject) { - const subResult = {} as any; - result.push(subResult); - this.cloneProperty(value, subResult); - } else { - result.push(value); - } - } - } -} diff --git a/packages/components/src/ifc/IfcRelationsIndexer/example.html b/packages/components/src/ifc/IfcRelationsIndexer/example.html new file mode 100644 index 000000000..0ffc5a518 --- /dev/null +++ b/packages/components/src/ifc/IfcRelationsIndexer/example.html @@ -0,0 +1,14 @@ + + + + + + + IfcRelationsIndexer + + + + + + + \ No newline at end of file diff --git a/packages/components/src/ifc/IfcRelationsIndexer/example.ts b/packages/components/src/ifc/IfcRelationsIndexer/example.ts new file mode 100644 index 000000000..7a822fee0 --- /dev/null +++ b/packages/components/src/ifc/IfcRelationsIndexer/example.ts @@ -0,0 +1,133 @@ +import * as OBC from "../.."; + +/* MD + ## Getting entity relations the easy way 💪 + --- + If you're aware of the IFC schema, you should know that all the possible information an entity have is not directly inside its attributes. For example, the property sets, classifications, materials, etc, of a wall (or any other element) are not directly in the wall attributes 🤯 but in other entities which are related to the wall using relations.

+ + Now, that is perfect for an schema like the IFC which aims to store all the building data within a single text file in the easiest way possible. However, is not that easy to work just because you need to find the relations you want to get to the element data you're looking for 😪. Luckily for you, the `IfcRelationsIndexer` already gives you an easy way to get the entities which are related with your elements thanks to the inverse attributes! 🔥🔥 + + ### Loading a model 🏦 + First things first, lets load an IFC model to process its relations. + + :::tip + + If you're unsure on the details to load a model, just tool at the [FragmentIfcLoader]() tutorial! + + ::: + */ + +const components = new OBC.Components(); + +const ifcLoader = components.get(OBC.FragmentIfcLoader); +await ifcLoader.setup(); +const file = await fetch("/resources/small.ifc"); +const buffer = await file.arrayBuffer(); +const typedArray = new Uint8Array(buffer); +const model = await ifcLoader.load(typedArray); + +/* MD + Once the model is loaded in memory, you just need to get an instance of the IfcRelationsIndexer and process the model... it's as easy as that! 😎 + */ + +const indexer = components.get(OBC.IfcRelationsIndexer); +await indexer.process(model); + +/* MD + The result of that is basically a map where the keys are the expressIDs and the values are other expressIDs related to the first one and grouped by the type of relation. You don't need to worry too much about the details of that, as the usage is pretty straighforward 🔝. The only thing that matters is you've now an easy way to access the entities related to your element 🙂 + + ### Getting element psets 📄 + One of the most important relations between different entities is the `IfcRelDefinesByProperties`. That relation links together an arbitrary entity with a set of `IfcPropertySet` entities that applies properties. Getting them with the `IfcRelationsIndexer` once the model is indexed is pretty easy: + */ + +const psets = indexer.getEntityRelations(model, 6518, "IsDefinedBy"); +if (psets) { + for (const expressID of psets) { + // You can get the pset attributes like this + const pset = await model.getProperties(expressID); + console.log(pset); + // You can get the pset props like this or iterate over pset.HasProperties yourself + await OBC.IfcPropertiesUtils.getPsetProps( + model, + expressID, + async (propExpressID) => { + const prop = await model.getProperties(propExpressID); + console.log(prop); + }, + ); + } +} + +/* MD + :::tip + + IsDefinedBy is the inverse attribute name in the IFC Schema that holds the relations with property sets 😉 + + ::: + + Awesome! really easy right? + + ### Exporting the indexation + In bigger models, the process to calculate the relations index may take some time. The important thing is that there is no reason to calculate over and over the relations index every time you load a model. If the model hasn't change, their properties shouldn't neither! So, let's download the relations index to load it later. + */ + +const downloadJSON = (json: string, name: string) => { + const file = new File([json], name); + const a = document.createElement("a"); + a.href = URL.createObjectURL(file); + a.download = file.name; + a.click(); + URL.revokeObjectURL(a.href); +}; + +const json = indexer.serializeModelRelations(model); +if (json) downloadJSON(json, "small-relations.json"); + +/* MD + :::tip + + As `@thatopen/components` can be used in either NodeJS and Browser environments, the logic to generate a JSON file may vary! + + ::: + Now, in case you've loaded several models and want to get all the calculated relations, there is also a handy method to do it. + */ + +const allRelationsJSON = indexer.serializeRelations(); +downloadJSON(allRelationsJSON, "relations-index-all.json"); + +/* MD + ### Loading back the relations index + What do we gain with having a pre-processed relations index if we can't use it again, right? Well, let's use the downloaded relations index 😎 + */ + +// Lets first delete the existing model relations +delete indexer.relationMaps[model.uuid]; + +const relationsIndex = await fetch("/resources/small-relations.json"); +indexer.relationMaps[model.uuid] = indexer.getRelationsMapFromJSON( + await relationsIndex.text(), +); + +/* MD + Great! Now try to get again the property sets and you will see everything working nice and neat. In fact, lets try to get the building storey of one element in the IFC 👇 + */ + +const buildingStorey = indexer.getEntityRelations( + model, + 6518, + "ContainedInStructure", +); + +if (buildingStorey && buildingStorey[0]) { + const storey = await model.getProperties(buildingStorey[0]); + console.log(storey); +} + +/* MD + :::tip + + Despite there are some relations that corresponds to only one element (e.g., an element can only have one associated building storey) the `getEntityRelations` will always return an array. That's the reason we take the first buildingStorey relation despite it will always be the only one. + + ::: + Congratulations! Now you know how to get an easy way to get the relations of your model. Keep going with more tutorials! 💪 + */ diff --git a/packages/components/src/ifc/IfcRelationsIndexer/index.ts b/packages/components/src/ifc/IfcRelationsIndexer/index.ts new file mode 100644 index 000000000..eced3ae8b --- /dev/null +++ b/packages/components/src/ifc/IfcRelationsIndexer/index.ts @@ -0,0 +1,315 @@ +import * as WEBIFC from "web-ifc"; +import { FragmentsGroup } from "bim-fragment"; +import { Disposable, Event, Component, Components } from "../../core"; +import { FragmentManager } from "../../fragments/FragmentManager"; +import { IfcPropertiesUtils } from "../Utils"; + +interface RelationMap { + [modelID: string]: Map>; +} + +/** + * Indexer for IFC properties, facilitating the indexing and retrieval of IFC entity relationships. + * It is designed to process models properties by indexing their IFC entities' relations based on predefined inverse attributes, and provides methods to query these relations. + */ +export class IfcRelationsIndexer extends Component implements Disposable { + static readonly uuid = "23a889ab-83b3-44a4-8bee-ead83438370b" as const; + + /** {@link Disposable.onDisposed} */ + readonly onDisposed = new Event(); + + enabled: boolean = true; + + /** + * All the inverse attributes this component can processes. + */ + readonly inverseAttributes = [ + "IsDecomposedBy", + "Decomposes", + "AssociatedTo", + "HasAssociations", + "ClassificationForObjects", + "IsGroupedBy", + "HasAssignments", + "IsDefinedBy", + "DefinesOcurrence", + "IsTypedBy", + "Types", + "Defines", + "ContainedInStructure", + "ContainsElements", + ]; + + inverseAttributesToProcess = [ + "HasAssignments", + "IsDecomposedBy", + "IsDefinedBy", + "IsTypedBy", + "HasAssociations", + "ContainedInStructure", + ]; + + private _relToAttributesMap = new Map< + number, + { forRelating?: string; forRelated?: string } + >(); + + /** + * Holds the relationship mappings for each model processed by the indexer. + * The structure is a map where each key is a model's UUID, and the value is another map. + * This inner map's keys are entity expressIDs, and its values are maps where each key is an index + * representing a specific relation type, and the value is an array of expressIDs of entities + * that are related through that relation type. This structure allows for efficient querying + * of entity relationships within a model. + */ + readonly relationMaps: RelationMap = {}; + + constructor(components: Components) { + super(components); + this.components.add(IfcRelationsIndexer.uuid, this); + const fragmentManager = components.get(FragmentManager); + fragmentManager.onFragmentsDisposed.add(this.onFragmentsDisposed); + this.setRelMap(); + } + + private onFragmentsDisposed = (data: { + groupID: string; + fragmentIDs: string[]; + }) => { + delete this.relationMaps[data.groupID]; + }; + + private getAttributeRels(value: string) { + const keys: number[] = []; + for (const [rel, attribute] of this._relToAttributesMap.entries()) { + const { forRelating, forRelated } = attribute; + if (forRelating === value || forRelated === value) keys.push(rel); + } + return keys; + } + + // TODO: Construct this based on the IFC EXPRESS long form schema? + private setRelMap() { + this._relToAttributesMap.set(WEBIFC.IFCRELAGGREGATES, { + forRelating: "IsDecomposedBy", + forRelated: "Decomposes", + }); + + this._relToAttributesMap.set(WEBIFC.IFCRELASSOCIATESMATERIAL, { + forRelating: "AssociatedTo", + forRelated: "HasAssociations", + }); + + this._relToAttributesMap.set(WEBIFC.IFCRELASSOCIATESCLASSIFICATION, { + forRelating: "ClassificationForObjects", + forRelated: "HasAssociations", + }); + + this._relToAttributesMap.set(WEBIFC.IFCRELASSIGNSTOGROUP, { + forRelating: "IsGroupedBy", + forRelated: "HasAssignments", + }); + + this._relToAttributesMap.set(WEBIFC.IFCRELDEFINESBYPROPERTIES, { + forRelated: "IsDefinedBy", + forRelating: "DefinesOcurrence", + }); + + this._relToAttributesMap.set(WEBIFC.IFCRELDEFINESBYTYPE, { + forRelated: "IsTypedBy", + forRelating: "Types", + }); + + this._relToAttributesMap.set(WEBIFC.IFCRELDEFINESBYTEMPLATE, { + forRelated: "IsDefinedBy", + forRelating: "Defines", + }); + + this._relToAttributesMap.set(WEBIFC.IFCRELCONTAINEDINSPATIALSTRUCTURE, { + forRelated: "ContainedInStructure", + forRelating: "ContainsElements", + }); + } + + /** + * Processes a given model to index its IFC entities relations based on predefined inverse attributes. + * This method iterates through each specified inverse attribute, retrieves the corresponding relations, + * and maps them in a structured way to facilitate quick access to related entities. + * + * The process involves querying the model for each relation type associated with the inverse attributes + * and updating the internal relationMaps with the relationships found. This map is keyed by the model's UUID + * and contains a nested map where each key is an entity's expressID and its value is another map. + * This inner map's keys are the indices of the inverse attributes, and its values are arrays of expressIDs + * of entities that are related through that attribute. + * + * @param model The `FragmentsGroup` model to be processed. It must have properties loaded. + * @returns A promise that resolves to the relations map for the processed model. This map is a detailed + * representation of the relations indexed by entity expressIDs and relation types. + * @throws An error if the model does not have properties loaded. + */ + async process(model: FragmentsGroup) { + if (!model.hasProperties) + throw new Error("FragmentsGroup properties not found"); + + this.relationMaps[model.uuid] = new Map(); + + const relationsMap = this.relationMaps[model.uuid]; + + for (const attribute of this.inverseAttributesToProcess) { + const rels = this.getAttributeRels(attribute); + for (const rel of rels) { + await IfcPropertiesUtils.getRelationMap( + model, + rel, + async (relatingID, relatedID) => { + const inverseAttributes = this._relToAttributesMap.get(rel); + if (!inverseAttributes) return; + const { forRelated: related, forRelating: relating } = + inverseAttributes; + if ( + relating && + this.inverseAttributesToProcess.includes(relating) + ) { + const currentMap = + relationsMap.get(relatingID) ?? new Map(); + const index = this.inverseAttributes.indexOf(relating); + currentMap.set(index, relatedID); + relationsMap.set(relatingID, currentMap); + } + if (related && this.inverseAttributesToProcess.includes(related)) { + for (const id of relatedID) { + const currentMap = + relationsMap.get(id) ?? new Map(); + const index = this.inverseAttributes.indexOf(related); + const relations = currentMap.get(index) ?? []; + relations.push(relatingID); + currentMap.set(index, relations); + relationsMap.set(id, currentMap); + } + } + }, + ); + } + } + + return relationsMap; + } + + /** + * Retrieves the relations of a specific entity within a model based on the given relation name. + * This method searches the indexed relation maps for the specified model and entity, + * returning the IDs of related entities if a match is found. + * + * @param model The `FragmentsGroup` model containing the entity. + * @param expressID The unique identifier of the entity within the model. + * @param relationName The IFC schema inverse attribute of the relation to search for (e.g., "IsDefinedBy", "ContainsElements"). + * @returns An array of express IDs representing the related entities, or `null` if no relations are found + * or the specified relation name is not indexed. + */ + getEntityRelations( + model: FragmentsGroup, + expressID: number, + relationName: string, + ) { + const indexMap = this.relationMaps[model.uuid]; + if (!indexMap) return null; + const entityRelations = indexMap.get(expressID); + const attributeIndex = this.inverseAttributes.indexOf(relationName); + if (!entityRelations || attributeIndex === -1) return null; + const relations = entityRelations.get(attributeIndex); + if (!relations) return null; + return relations; + } + + /** + * Serializes the relations of a specific model into a JSON string. + * This method iterates through the relations indexed for the given model, + * organizing them into a structured object where each key is an expressID of an entity, + * and its value is another object mapping relation indices to arrays of related entity expressIDs. + * The resulting object is then serialized into a JSON string. + * + * @param model The `FragmentsGroup` model whose relations are to be serialized. + * @returns A JSON string representing the serialized relations of the specified model. + * If the model has no indexed relations, `null` is returned. + */ + serializeModelRelations(model: FragmentsGroup) { + const indexMap = this.relationMaps[model.uuid]; + if (!indexMap) return null; + const object: Record> = {}; + for (const [expressID, relations] of indexMap.entries()) { + if (!object[expressID]) object[expressID] = {}; + for (const [relationID, relationEntities] of relations.entries()) { + object[expressID][relationID] = relationEntities; + } + } + return JSON.stringify(object); + } + + /** + * Serializes all relations of every model processed by the indexer into a JSON string. + * This method iterates through each model's relations indexed in `relationMaps`, organizing them + * into a structured JSON object. Each top-level key in this object corresponds to a model's UUID, + * and its value is another object mapping entity expressIDs to their related entities, categorized + * by relation types. The structure facilitates easy access to any entity's relations across all models. + * + * @returns A JSON string representing the serialized relations of all models processed by the indexer. + * If no relations have been indexed, an empty object is returned as a JSON string. + */ + serializeRelations() { + const jsonObject: Record< + string, + Record> + > = {}; + for (const uuid in this.relationMaps) { + const indexMap = this.relationMaps[uuid]; + const object: Record> = {}; + for (const [expressID, relations] of indexMap.entries()) { + if (!object[expressID]) object[expressID] = {}; + for (const [relationID, relationEntities] of relations.entries()) { + object[expressID][relationID] = relationEntities; + } + } + jsonObject[uuid] = object; + } + return JSON.stringify(jsonObject); + } + + /** + * Converts a JSON string representing relations between entities into a structured map. + * This method parses the JSON string to reconstruct the relations map that indexes + * entity relations by their express IDs. The outer map keys are the express IDs of entities, + * and the values are maps where each key is a relation type ID and its value is an array + * of express IDs of entities related through that relation type. + * + * @param json The JSON string to be parsed into the relations map. + * @returns A `Map` where the key is the express ID of an entity as a number, and the value + * is another `Map`. This inner map's key is the relation type ID as a number, and its value + * is an array of express IDs (as numbers) of entities related through that relation type. + */ + getRelationsMapFromJSON(json: string) { + const relations = JSON.parse(json); + const indexMap = new Map>(); + for (const expressID in relations) { + const expressIDRelations = relations[expressID]; + const relationMap = new Map(); + for (const relationID in expressIDRelations) { + relationMap.set(Number(relationID), expressIDRelations[relationID]); + } + indexMap.set(Number(expressID), relationMap); + } + return indexMap; + } + + /** + * Disposes the component, cleaning up resources and detaching event listeners. + * This ensures that the component is properly cleaned up and does not leave behind any + * references that could prevent garbage collection. + */ + dispose() { + (this.relationMaps as any) = {}; + const fragmentManager = this.components.get(FragmentManager); + fragmentManager.onFragmentsDisposed.remove(this.onFragmentsDisposed); + this.onDisposed.trigger(IfcRelationsIndexer.uuid); + this.onDisposed.reset(); + } +} diff --git a/packages/components/src/ifc/index.ts b/packages/components/src/ifc/index.ts index 5556d98c7..0ae71f632 100644 --- a/packages/components/src/ifc/index.ts +++ b/packages/components/src/ifc/index.ts @@ -1,5 +1,5 @@ export * from "./IfcJsonExporter"; export * from "./IfcPropertiesFinder"; -export * from "./IfcPropertiesIndexer"; +export * from "./IfcRelationsIndexer"; export * from "./IfcPropertiesManager"; export * from "./Utils"; diff --git a/resources/small-relations.json b/resources/small-relations.json new file mode 100644 index 000000000..089ab6574 --- /dev/null +++ b/resources/small-relations.json @@ -0,0 +1 @@ +{"119":{"0":[148]},"129":{"0":[138,144],"7":[24544]},"138":{"7":[24460]},"144":{"7":[24519]},"148":{"0":[129]},"186":{"3":[231],"7":[250,253,257],"9":[232],"12":[138]},"232":{"3":[226]},"294":{"3":[297],"7":[298,301,303],"9":[232],"12":[138]},"338":{"3":[341],"7":[342,345,347],"9":[232],"12":[138]},"6471":{"3":[6505]},"6518":{"3":[6521],"7":[6524,6527,6530],"9":[6471],"12":[138]},"6563":{"3":[6566],"7":[6569,6571,6574],"9":[6471],"12":[138]},"6595":{"3":[6598],"7":[6601,6603,6606],"9":[6471],"12":[138]},"6627":{"3":[6630],"7":[6633,6635,6638],"9":[6471],"12":[138]},"6659":{"3":[6662],"7":[6665,6667,6670],"9":[6471],"12":[138]},"6691":{"3":[6694],"7":[6697,6699,6702],"9":[6471],"12":[138]},"6723":{"3":[6726],"7":[6729,6731,6734],"9":[6471],"12":[138]},"6755":{"3":[6758],"7":[6761,6763,6766],"9":[6471],"12":[138]},"6787":{"3":[6790],"7":[6793,6795,6798],"9":[6471],"12":[138]},"7758":{"3":[7780]},"7792":{"3":[7795],"7":[7798],"9":[7758],"12":[138]},"18740":{"3":[18762]},"18774":{"3":[18777],"7":[18780],"9":[18740],"12":[138]},"18799":{"3":[18802],"7":[18804],"9":[18740],"12":[138]},"18819":{"3":[18822],"7":[18824],"9":[18740],"12":[138]},"18839":{"3":[18842],"7":[18844],"9":[18740],"12":[138]},"18859":{"3":[18862],"7":[18864],"9":[7758],"12":[138]},"18879":{"3":[18882],"7":[18884],"9":[18740],"12":[138]},"18899":{"3":[18902],"7":[18904],"9":[18740],"12":[138]},"18919":{"3":[18922],"7":[18924],"9":[18740],"12":[138]},"18939":{"3":[18942],"7":[18944],"9":[18740],"12":[138]},"18959":{"3":[18962],"7":[18964],"9":[7758],"12":[138]},"18979":{"3":[18982],"7":[18984],"9":[18740],"12":[138]},"18999":{"3":[19002],"7":[19004],"9":[18740],"12":[138]},"19019":{"3":[19022],"7":[19024],"9":[18740],"12":[138]},"19039":{"3":[19042],"7":[19044],"9":[18740],"12":[138]},"19059":{"3":[19062],"7":[19064],"9":[7758],"12":[138]},"19079":{"3":[19082],"7":[19084],"9":[18740],"12":[138]},"19099":{"3":[19102],"7":[19104],"9":[18740],"12":[138]},"19119":{"3":[19122],"7":[19124],"9":[18740],"12":[138]},"19139":{"3":[19142],"7":[19144],"9":[18740],"12":[138]},"19159":{"3":[19162],"7":[19164],"9":[7758],"12":[138]},"19179":{"3":[19182],"7":[19184],"9":[18740],"12":[138]},"19199":{"3":[19202],"7":[19204],"9":[18740],"12":[138]},"19219":{"3":[19222],"7":[19224],"9":[18740],"12":[138]},"19239":{"3":[19242],"7":[19244],"9":[18740],"12":[138]},"19259":{"3":[19262],"7":[19264],"9":[7758],"12":[138]},"19279":{"3":[19282],"7":[19284],"9":[18740],"12":[138]},"19299":{"3":[19302],"7":[19304],"9":[18740],"12":[138]},"19319":{"3":[19322],"7":[19324],"9":[18740],"12":[138]},"19339":{"3":[19342],"7":[19344],"9":[18740],"12":[138]},"19359":{"3":[19362],"7":[19364],"9":[7758],"12":[138]},"19379":{"3":[19382],"7":[19384],"9":[18740],"12":[138]},"19399":{"3":[19402],"7":[19404],"9":[18740],"12":[138]},"19419":{"3":[19422],"7":[19424],"9":[18740],"12":[138]},"19439":{"3":[19442],"7":[19444],"9":[18740],"12":[138]},"22474":{"3":[22480]},"22492":{"3":[6475],"7":[22497,22500,22502],"9":[22474],"12":[138]},"22551":{"3":[22569],"7":[22571,22574,22577],"9":[22554],"12":[138]},"22554":{"3":[22566]},"22620":{"3":[22624],"7":[22625,22628,22630],"9":[22623],"12":[144]},"22623":{"3":[22566]},"22655":{"0":[22705,22748,22772,22788,22804,22820,22836,22852,22868,22884,22900,22916,22952,22996,23039,23062,23078,23114,23157,23200,23243,23286,23329,23352,23368,23384,23400,23436,23479,23502,23518,23534,23550,23566,23582,23618,23661,23704,23727,23763,23806,23829,23865],"7":[23924,23926],"9":[23921],"12":[138]},"22678":{"3":[22680]},"22705":{"3":[22680],"7":[23929,23931],"9":[22678]},"22732":{"3":[22680]},"22748":{"3":[22680],"7":[23934,23936],"9":[22732]},"22772":{"3":[22680],"7":[23939,23941],"9":[22732]},"22788":{"3":[22680],"7":[23944,23946],"9":[22678]},"22804":{"3":[22680],"7":[23949,23951],"9":[22678]},"22820":{"3":[22680],"7":[23954,23956],"9":[22732]},"22836":{"3":[22680],"7":[23959,23961],"9":[22732]},"22852":{"3":[22680],"7":[23964,23966],"9":[22678]},"22868":{"3":[22680],"7":[23969,23971],"9":[22678]},"22884":{"3":[22680],"7":[23974,23976],"9":[22732]},"22900":{"3":[22680],"7":[23979,23981],"9":[22732]},"22916":{"3":[22680],"7":[23984,23986],"9":[22678]},"22936":{"3":[22479]},"22952":{"7":[23989,23991],"9":[22936]},"22980":{"3":[22479]},"22996":{"7":[23994,23996],"9":[22980]},"23023":{"3":[22479]},"23039":{"7":[23999,24001],"9":[23023]},"23062":{"7":[24004,24006],"9":[23023]},"23078":{"7":[24009,24011],"9":[23023]},"23098":{"3":[22479]},"23114":{"7":[24014,24016],"9":[23098]},"23141":{"3":[22479]},"23157":{"7":[24019,24021],"9":[23141]},"23184":{"3":[22479]},"23200":{"7":[24024,24026],"9":[23184]},"23227":{"3":[22479]},"23243":{"7":[24029,24031],"9":[23227]},"23270":{"3":[22479]},"23286":{"7":[24034,24036],"9":[23270]},"23313":{"3":[22479]},"23329":{"7":[24039,24041],"9":[23313]},"23352":{"7":[24044,24046],"9":[23270]},"23368":{"7":[24049,24051],"9":[23313]},"23384":{"7":[24054,24056],"9":[23270]},"23400":{"7":[24059,24061],"9":[23313]},"23420":{"3":[22479]},"23436":{"7":[24064,24066],"9":[23420]},"23463":{"3":[22479]},"23479":{"7":[24069,24071],"9":[23463]},"23502":{"7":[24074,24076],"9":[23463]},"23518":{"7":[24079,24081],"9":[23420]},"23534":{"7":[24084,24086],"9":[23420]},"23550":{"7":[24089,24091],"9":[23463]},"23566":{"7":[24094,24096],"9":[23463]},"23582":{"7":[24099,24101],"9":[23420]},"23602":{"3":[22479]},"23618":{"7":[24104,24106],"9":[23602]},"23645":{"3":[22479]},"23661":{"7":[24109,24111],"9":[23645]},"23688":{"3":[22479]},"23704":{"7":[24114,24116],"9":[23688]},"23727":{"7":[24119,24121],"9":[23688]},"23747":{"3":[22479]},"23763":{"7":[24124,24126],"9":[23747]},"23790":{"3":[22479]},"23806":{"7":[24129,24131],"9":[23790]},"23829":{"7":[24134,24136],"9":[23790]},"23849":{"3":[22479]},"23865":{"7":[24139,24141],"9":[23849]}} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 636f254c4..0e2c80cbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1242,7 +1242,7 @@ __metadata: version: 0.0.0-use.local resolution: "@thatopen/components-front@workspace:packages/components-front" dependencies: - "@thatopen/components": 2.0.0-alpha.3 + "@thatopen/components": 2.0.0-alpha.6 "@thatopen/fragments": 2.0.0-alpha.1 "@types/earcut": ^2.1.4 "@types/three": ^0.160.0 @@ -1260,7 +1260,7 @@ __metadata: languageName: unknown linkType: soft -"@thatopen/components@2.0.0-alpha.3, @thatopen/components@workspace:packages/components": +"@thatopen/components@2.0.0-alpha.6, @thatopen/components@workspace:packages/components": version: 0.0.0-use.local resolution: "@thatopen/components@workspace:packages/components" dependencies: