diff --git a/packages/components/src/ifc/IfcRelationsIndexer/example.ts b/packages/components/src/ifc/IfcRelationsIndexer/example.ts index 7a822fee0..a46c4157f 100644 --- a/packages/components/src/ifc/IfcRelationsIndexer/example.ts +++ b/packages/components/src/ifc/IfcRelationsIndexer/example.ts @@ -92,7 +92,7 @@ if (json) downloadJSON(json, "small-relations.json"); 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(); +const allRelationsJSON = indexer.serializeAllRelations(); downloadJSON(allRelationsJSON, "relations-index-all.json"); /* MD diff --git a/packages/components/src/ifc/IfcRelationsIndexer/index.ts b/packages/components/src/ifc/IfcRelationsIndexer/index.ts index b0c21cdfd..3a0035c43 100644 --- a/packages/components/src/ifc/IfcRelationsIndexer/index.ts +++ b/packages/components/src/ifc/IfcRelationsIndexer/index.ts @@ -3,6 +3,9 @@ import { FragmentsGroup } from "@thatopen/fragments"; import { Disposable, Event, Component, Components } from "../../core"; import { FragmentManager } from "../../fragments/FragmentManager"; import { IfcPropertiesUtils } from "../Utils"; +import { getRelationMap } from "./src/get-relation-map"; + +// TODO: Refactor to combine logic from process and processFromWebIfc interface RelationMap { [modelID: string]: Map>; @@ -195,6 +198,59 @@ export class IfcRelationsIndexer extends Component implements Disposable { return relationsMap; } + /** + * Processes a given model from a WebIfc API to index its IFC entities relations based on predefined inverse attributes. + * + * @param ifcApi - The WebIfc API instance from which to retrieve the model's properties. + * @param modelID - The unique identifier of the model within the WebIfc API. + * @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. + */ + async processFromWebIfc(ifcApi: WEBIFC.IfcAPI, modelID: number) { + const relationsMap = new Map>(); + + const properties: Record> = {}; + const lines = ifcApi.GetAllLines(modelID); + + for (let i = 0; i < lines.size(); i++) { + const line = lines.get(i); + const attrs = await ifcApi.properties.getItemProperties(modelID, line); + properties[line] = attrs; + } + + for (const attribute of this.inverseAttributesToProcess) { + const rels = this.getAttributeRels(attribute); + for (const rel of rels) { + getRelationMap(properties, rel, (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, @@ -222,21 +278,17 @@ export class IfcRelationsIndexer extends Component implements Disposable { } /** - * 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, + * Serializes the relations of a given relation map into a JSON string. + * This method iterates through the relations in the given map, 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. + * @param relationMap - The map of relations to be serialized. The map keys are expressIDs of entities, and the values are maps where each key is a relation type ID and its value is an array of expressIDs of entities related through that relation type. + * @returns A JSON string representing the serialized relations of the given relation map. */ - serializeModelRelations(model: FragmentsGroup) { - const indexMap = this.relationMaps[model.uuid]; - if (!indexMap) return null; + serializeRelations(relationMap: Map>) { const object: Record> = {}; - for (const [expressID, relations] of indexMap.entries()) { + for (const [expressID, relations] of relationMap.entries()) { if (!object[expressID]) object[expressID] = {}; for (const [relationID, relationEntities] of relations.entries()) { object[expressID][relationID] = relationEntities; @@ -245,6 +297,24 @@ export class IfcRelationsIndexer extends Component implements Disposable { return JSON.stringify(object); } + /** + * 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 relationsMap = this.relationMaps[model.uuid]; + if (!relationsMap) return null; + const json = this.serializeRelations(relationsMap); + return json; + } + /** * 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 @@ -255,7 +325,7 @@ export class IfcRelationsIndexer extends Component implements Disposable { * @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() { + serializeAllRelations() { const jsonObject: Record< string, Record> diff --git a/packages/components/src/ifc/IfcRelationsIndexer/src/get-relation-map.ts b/packages/components/src/ifc/IfcRelationsIndexer/src/get-relation-map.ts new file mode 100644 index 000000000..1d581a34c --- /dev/null +++ b/packages/components/src/ifc/IfcRelationsIndexer/src/get-relation-map.ts @@ -0,0 +1,37 @@ +export const getRelationMap = ( + properties: Record>, + relationType: number, + onElementsFound?: (relatingID: number, relatedIDs: number[]) => void, +) => { + const defaultCallback = () => {}; + const _onElementsFound = onElementsFound ?? defaultCallback; + const result: { [relatingID: number]: number[] } = {}; + const ids = Object.keys(properties); + for (const expressID of ids) { + const prop = properties[expressID]; + + if (!prop) continue; + + const isRelation = prop.type === relationType; + const relatingKey = Object.keys(prop).find((key) => + key.startsWith("Relating"), + ); + + const relatedKey = Object.keys(prop).find((key) => + key.startsWith("Related"), + ); + + if (!(isRelation && relatingKey && relatedKey)) continue; + const relating = properties[prop[relatingKey]?.value]; + const related = prop[relatedKey]; + + if (!relating || !related) continue; + + if (!(related && Array.isArray(related))) continue; + const elements = related.map((el: any) => el.value); + + _onElementsFound(relating.expressID, elements); + result[relating.expressID] = elements; + } + return result; +};