Skip to content

Commit

Permalink
feat(core): IfcRelationsIndexer is between 2 and 5 times faster when …
Browse files Browse the repository at this point in the history
…processing a model
  • Loading branch information
HoyosJuan committed Nov 2, 2024
1 parent 7924186 commit 3894a5c
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 115 deletions.
188 changes: 73 additions & 115 deletions packages/core/src/ifc/IfcRelationsIndexer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
InverseAttribute,
IfcRelations,
IfcRelation,
RelationsProcessingConfig,
EntitiesRelatedEvent,
} from "./src";
import { relToAttributesMap } from "./src/relToAttributesMap";
import { IfcPropertiesManager } from "../IfcPropertiesManager";
Expand Down Expand Up @@ -127,7 +129,7 @@ export class IfcRelationsIndexer extends Component implements Disposable {

private indexRelations(
relationsMap: RelationsMap,
relAttrs: any,
relAttrs: Record<string, any>,
related: InverseAttribute,
relating: InverseAttribute,
) {
Expand All @@ -142,44 +144,29 @@ export class IfcRelationsIndexer extends Component implements Disposable {
const relatedIDs = relAttrs[relatedKey].map((el: any) => el.value);

// forRelating
const index = this.getAttributeIndex(relating);
if (index !== null) {
let currentMap = relationsMap.get(relatingID);
if (!currentMap) {
currentMap = new Map();
relationsMap.set(relatingID, currentMap);
}
let indexMap = currentMap.get(index);
if (!indexMap) {
indexMap = [];
currentMap.set(index, indexMap);
}
for (const id of relatedIDs) {
indexMap.push(id);
}
const indexMap = this.getEntityRelations(
relationsMap,
relatingID,
relating,
);
for (const id of relatedIDs) {
indexMap.push(id);
}

// forRelated
for (const id of relatedIDs) {
const index = this.getAttributeIndex(related);
if (index === null) continue;
let currentMap = relationsMap.get(id);
if (!currentMap) {
currentMap = new Map();
relationsMap.set(id, currentMap);
}
let relations = currentMap.get(index);
if (!relations) {
relations = [];
currentMap.set(index, relations);
}
const relations = this.getEntityRelations(relationsMap, id, related);
relations.push(relatingID);
}
}

getAttributeIndex(inverseAttribute: InverseAttribute) {
const index = this._inverseAttributes.indexOf(inverseAttribute);
if (index === -1) return null;
if (index === -1) {
throw new Error(
`IfcRelationsIndexer: ${inverseAttribute} is not a valid IFC Inverse Attribute name or its not supported yet by this component.`,
);
}
return index;
}

Expand Down Expand Up @@ -215,35 +202,35 @@ export class IfcRelationsIndexer extends Component implements Disposable {
* 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) {
async process(
model: FragmentsGroup,
config?: Partial<RelationsProcessingConfig>,
) {
if (!model.hasProperties)
throw new Error("FragmentsGroup properties not found");

let relationsMap = this.relationMaps[model.uuid];
if (relationsMap) {
return relationsMap;
if (!relationsMap) {
relationsMap = new Map();
this.relationMaps[model.uuid] = relationsMap;
}

relationsMap = new Map();
const entities = model.getLocalProperties();
if (!entities) return relationsMap;

for (const relType of this._ifcRels) {
const relsAttrs = await model.getAllPropertiesOfType(relType);
if (!relsAttrs) {
continue;
}
const relationsToProcess = config?.relationsToProcess ?? this._ifcRels;

const relInverseAttributes = this._relToAttributesMap.get(relType);
for (const [_, entity] of Object.entries(entities)) {
if (!relationsToProcess.includes(entity.type)) continue;
const relInverseAttributes = this._relToAttributesMap.get(entity.type);
if (!relInverseAttributes) {
continue;
}

const { forRelated: related, forRelating: relating } =
relInverseAttributes;

for (const expressID in relsAttrs) {
const relAttrs = relsAttrs[expressID];
this.indexRelations(relationsMap, relAttrs, related, relating);
}
this.indexRelations(relationsMap, entity, related, relating);
}

this.setRelationMap(model, relationsMap);
Expand Down Expand Up @@ -291,31 +278,46 @@ export class IfcRelationsIndexer extends Component implements Disposable {
*
* @param model The `FragmentsGroup` model containing the entity, or its UUID.
* @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.
* @param attribute 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. If the array is empty, no relations were found.
*/
getEntityRelations(
model: FragmentsGroup | string,
model: FragmentsGroup | string | RelationsMap,
expressID: number,
relationName: InverseAttribute,
attribute: InverseAttribute,
) {
const id = model instanceof FragmentsGroup ? model.uuid : model;
const indexMap = this.relationMaps[id];
if (!indexMap) {
throw new Error(
`IfcRelationsIndexer: model ${id} has no relations indexed.`,
);
const index = this.getAttributeIndex(attribute);
let relationsMap: RelationsMap;

if (model instanceof FragmentsGroup) {
relationsMap = this.relationMaps[model.uuid];
} else if (typeof model === "string") {
relationsMap = this.relationMaps[model];
} else {
relationsMap = model;
}

if (
!relationsMap &&
(model instanceof FragmentsGroup || typeof model === "string")
) {
relationsMap = new Map();
const id = model instanceof FragmentsGroup ? model.uuid : model;
this.relationMaps[id] = relationsMap;
}
const entityRelations = indexMap.get(expressID);
const attributeIndex = this.getAttributeIndex(relationName);
if (entityRelations === undefined || attributeIndex === null) {
return null;

let entityRelations = relationsMap.get(expressID);
if (!entityRelations) {
entityRelations = new Map();
relationsMap.set(expressID, entityRelations);
}
const relations = entityRelations.get(attributeIndex);

let relations = entityRelations.get(index);
if (!relations) {
return null;
relations = [];
entityRelations.set(index, relations);
}

return relations;
}

Expand Down Expand Up @@ -445,8 +447,6 @@ export class IfcRelationsIndexer extends Component implements Disposable {
const set: Set<number> = new Set();
for (const [id, map] of relations) {
const index = this.getAttributeIndex(inv);
if (index === null)
throw new Error("IfcRelationsIndexer: invalid inverse attribute name");
const rels = map.get(index);
if (rels && rels.includes(expressID)) set.add(id);
}
Expand Down Expand Up @@ -477,30 +477,18 @@ export class IfcRelationsIndexer extends Component implements Disposable {
);
if (!existingRelations) {
const attributeIndex = this.getAttributeIndex(relationName);
if (!attributeIndex) {
throw new Error(
`IfcRelationsIndexer: ${relationName} is not a valid relation name.`,
);
}
const entityRelations = this.relationMaps[model.uuid].get(expressID);
entityRelations?.set(attributeIndex, relIDs);
} else {
existingRelations.push(...relIDs);
}
}

// removeEntitiesRelation(
// model: FragmentsGroup,
// relatingID: number,
// relationName: InverseAttribute,
// ...relatedIDs: number[]
// ) {}

/**
* Converts the relations made into actual IFC data.
*
* @remarks This function iterates through the changes made to the relations and applies them to the corresponding BIM model.
* It only make sense to use if the relations need to be write in the IFC file.
* It only make sense to use it if the relations need to be write in the IFC file.
*
* @returns A promise that resolves when all the relation changes have been applied.
*/
Expand Down Expand Up @@ -539,7 +527,7 @@ export class IfcRelationsIndexer extends Component implements Disposable {
}
}

// Use to create the corresponding IfcRelationship
// Used to create the corresponding IfcRelationship with the IfcPropertiesManager
private readonly _changeMap: {
[modelID: string]: DataMap<
IfcRelation,
Expand All @@ -552,16 +540,7 @@ export class IfcRelationsIndexer extends Component implements Disposable {
* The event provides information about the type of relation, the inverse attribute,
* the IDs of the entities related, and the IDs of the entities that are being related.
*/
readonly onEntitiesRelated = new Event<{
/** The type of the IFC relation. */
relType: IfcRelation;
/** The inverse attribute of the relation. */
invAttribute: InverseAttribute;
/** The IDs of the entities that are relating. */
relatingIDs: number[];
/** The IDs of the entities that are being related. */
relatedIDs: number[];
}>();
readonly onEntitiesRelated = new Event<EntitiesRelatedEvent>();

addEntitiesRelation(
model: FragmentsGroup,
Expand All @@ -571,9 +550,11 @@ export class IfcRelationsIndexer extends Component implements Disposable {
) {
const { type, inv } = rel;

// TODO: Allow to create the relation even if the model wasn't previously processed
const relationsMap = this.relationMaps[model.uuid];
if (!relationsMap) return;
let relationsMap = this.relationMaps[model.uuid];
if (!relationsMap) {
relationsMap = new Map() as RelationsMap;
this.relationMaps[model.uuid] = relationsMap;
}

if (!this._ifcRels.includes(type)) return;

Expand Down Expand Up @@ -625,36 +606,13 @@ export class IfcRelationsIndexer extends Component implements Disposable {

// forRelating
for (const id of relatingExpressID) {
let currentMap = relationsMap.get(id);
if (!currentMap) {
currentMap = new Map();
relationsMap.set(id, currentMap);
}
const index = this.getAttributeIndex(relating);
if (index !== null) {
let indexMap = currentMap.get(index);
if (!indexMap) {
indexMap = [];
currentMap.set(index, indexMap);
}
indexMap.push(...relatedExpressID);
}
const indexMap = this.getEntityRelations(model, id, relating);
indexMap.push(...relatedExpressID);
}

// forRelated
for (const id of relatedExpressID) {
let currentMap = relationsMap.get(id);
if (!currentMap) {
currentMap = new Map();
relationsMap.set(id, currentMap);
}
const index = this.getAttributeIndex(related);
if (index === null) continue;
let relations = currentMap.get(index);
if (!relations) {
relations = [];
currentMap.set(index, relations);
}
const relations = this.getEntityRelations(model, id, related);
relations.push(...relatingExpressID);
}
}
Expand Down
40 changes: 40 additions & 0 deletions packages/core/src/ifc/IfcRelationsIndexer/src/newVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const actual = {
186: {
7: [250, 253, 257],
},
250: {
8: [186],
},
253: {
8: [186],
},
257: {
8: [186],
},
};

// This new version of the relations map easily allows to delete relations
const newVersion = {
186: {
// 7 is IsDefinedBy
7: [{ 259: [250] }, { 263: [253] }, { 266: [257] }], // 259, 263 and 266 are IfcRelDefinesByProperties | 250, 253 and 257 are IfcPropertySet
},
190: {
// 7 is IsDefinedBy
7: [{ 259: [250] }], // 259 is IfcRelDefinesByProperties | 250 is IfcPropertySet
},
250: {
// 8 is DefinesOcurrence
8: [{ 259: [186, 190] }], // 259 is IsRelDefinesByProperties | 186 and 190 are IfcProduct
},
};

interface V2Schema {
[expressID: number]: {
[invAttrIndex: number]: {
[IfcRelationship: number]: number[];
};
};
}

type RelationsMap = Map<number, Map<number, { [ifcRel: number]: number[] }>>;
19 changes: 19 additions & 0 deletions packages/core/src/ifc/IfcRelationsIndexer/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,22 @@ export type IfcRelations = [
];

export type IfcRelation = IfcRelations[number];

export interface RelationsProcessingConfig {
relationsToProcess: IfcRelation[];
}

/**
* Interface definition of an Entities Related Event from the IfcRelationsIndexer.
* This event gets triggered when two or more entities has been related with each other.
*/
export interface EntitiesRelatedEvent {
/** The type of the IFC relation. */
relType: IfcRelation;
/** The inverse attribute of the relation. */
invAttribute: InverseAttribute;
/** The IDs of the entities that are relating. */
relatingIDs: number[];
/** The IDs of the entities that are being related. */
relatedIDs: number[];
}

0 comments on commit 3894a5c

Please sign in to comment.