Skip to content

Commit f01c475

Browse files
committed
feat(core): adds IfcPropertiesManager tutorial
1 parent 52eca72 commit f01c475

File tree

2 files changed

+138
-51
lines changed

2 files changed

+138
-51
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1+
/* MD
2+
### 📄 Managing Your Model Properties
3+
---
4+
When it comes to deal with BIM models there are two important aspects: geometry and information. The latter is so important we have developed a dedicated component to help you in the process of creating, deleting, and editing information in your BIM models. Let's dive in!
5+
6+
### 🏗️ Scaffolding the Project
7+
---
8+
Before we even do something with the properties in your IFC file, let's import the necessary dependencies and programatically load a file in memory using `@thatopen/components` IfcLoader as follows:
9+
*/
10+
11+
// eslint-disable-next-line import/no-extraneous-dependencies
12+
import * as OBC from "@thatopen/components";
113
import * as WEBIFC from "web-ifc";
2-
import * as OBC from "../..";
314

415
const components = new OBC.Components();
516
const ifcLoader = components.get(OBC.IfcLoader);
@@ -8,71 +19,147 @@ const file = await fetch(
819
"https://thatopen.github.io/engine_components/resources/small.ifc",
920
);
1021
const buffer = await file.arrayBuffer();
11-
const model = await ifcLoader.load(new Uint8Array(buffer));
22+
const typedArray = new Uint8Array(buffer);
23+
const model = await ifcLoader.load(typedArray);
1224

13-
const indexer = components.get(OBC.IfcRelationsIndexer);
25+
/* MD
26+
:::tip
1427
15-
// By the time beign, the model relations must be processed to see the new relations when downloading the IFC
16-
await indexer.process(model);
28+
If you're unsure about the loading process of an IFC file in the That Open Engine, take a look at the IfcLoader tutorial.
1729
18-
const propertiesManager = components.get(OBC.IfcPropertiesManager);
30+
:::
1931
20-
// Add a new pset
21-
const { pset } = await propertiesManager.newPset(model, "CalculatedQuantities");
32+
Once the model has been loaded into memory, we can start to do things with the properties 💪
2233
23-
const prop = await propertiesManager.newSingleNumericProperty(
24-
model,
25-
"IfcReal",
26-
"Volume",
27-
12.25,
34+
### ✅ Creating a New IfcPropertySet
35+
---
36+
The first thing we need to do is get an instance of the IfcPropertiesManager component as follows:
37+
*/
38+
39+
const propsManager = components.get(OBC.IfcPropertiesManager);
40+
41+
/* MD
42+
You have two ways to create a new IfcPropertySet: by using the IfcPropertiesManager or using WebIFC. The properties manager component includes some methods that acts as wrappers around instancing IFC entities with WebIFC, which makes easier to create them. Those wrapper method were made for the most common entities you probably want to create, which are IfcPropertySet and IfcPropertySingleValue. However, if you want to have full control over which entities do you create, then is better to stick with WebIFC as a preferred solution. Let's see how to use it in conjunction with the IfcPropertiesManager:
43+
*/
44+
45+
const { handle: ownerHistoryHandle } =
46+
await propsManager.getOwnerHistory(model);
47+
48+
// The loaded model is known to be in version 2x3
49+
const newPset = new WEBIFC.IFC2X3.IfcPropertySet(
50+
new WEBIFC.IFC2X3.IfcGloballyUniqueId(OBC.UUID.create()),
51+
ownerHistoryHandle,
52+
new WEBIFC.IFC2X3.IfcLabel("Custom Property Set"),
53+
null,
54+
[],
2855
);
2956

30-
await propertiesManager.addPropToPset(model, pset.expressID, prop.expressID);
57+
/* MD
58+
The most important thing to know when creating a new entity using WebIFC is that it comes without an expressID. Well, it comes with an expressID but its -1, which is not a valid expressID. So, why it comes with an invalid expressID? Easy, because it doesn't know the IFC where the entity will be added, so its our job to determine the expressID to be applied to it. Typically, the expressID will be the next number of the latest expressID found in the file. Appart of that, the entity must be added to the corresponding model information. To help you with the process so you don't have to do it all by yourself, the IfcPropertiesManager comes with a handy method as follows:
59+
*/
60+
61+
// This not only adds the entity to the model, but it also determines a valid expressID
62+
await propsManager.setData(model, newPset);
63+
64+
/* MD
65+
:::tip
66+
67+
You should always use the method above immediately after a new entity has been created as most information operations are based on expressIDs!
68+
69+
:::
70+
71+
Let's now create a property and add it to the property set:
72+
*/
73+
74+
const newProp = new WEBIFC.IFC2X3.IfcPropertySingleValue(
75+
new WEBIFC.IFC2X3.IfcIdentifier("Custom Property"),
76+
null,
77+
new WEBIFC.IFC2X3.IfcText("Custom Property Text Value"),
78+
null,
79+
);
80+
81+
await propsManager.setData(model, newProp);
82+
newPset.HasProperties.push(new WEBIFC.Handle(newProp.expressID));
83+
84+
/* MD
85+
As the property set has been created with a valid property, the next logical step is to add the set to the elements we want. Surprisingly, this job doesn't belong to the IfcPropertiesManager, but to the IfcRelationsIndexer! The reason is because when it comes to add a property set to another entity, what happens behind the scenes is a new IfcRelation has to be created. Everything that has to be with IfcRelations is managed by the IfcRelationsIndexer, and you can do it as follows:
86+
*/
87+
88+
const indexer = components.get(OBC.IfcRelationsIndexer);
3189
indexer.addEntitiesRelation(
3290
model,
33-
pset.expressID,
91+
newPset.expressID,
3492
{ type: WEBIFC.IFCRELDEFINESBYPROPERTIES, inv: "DefinesOcurrence" },
35-
186,
93+
186, // This is a known expressID for an IfcWall in the loaded file
3694
);
3795

38-
// Modify existing entity attributes
96+
// You can also add the relation like this
97+
// indexer.addEntitiesRelation(
98+
// model,
99+
// 186, // This is a known expressID for an IfcWall in the loaded file
100+
// { type: WEBIFC.IFCRELDEFINESBYPROPERTIES, inv: "IsDefinedBy" },
101+
// newPset.expressID,
102+
// );
103+
104+
/* MD
105+
:::tip
106+
107+
When you relate entities with the IfcRelationsIndexer, the corresponding IfcRelations are not created directly in the IFC file but in its relations maps; in other words, the only thing created is the definition of how both entities relates to each other. That means, you can't expect to have the IfcRelation in the file right away; the IfcRelation is only created when you export the file (see down below the tutorial). If you're new to the IfcRelationsIndexer, check the corresponding tutorial!
108+
109+
:::
110+
111+
### ⚠️ Modifying Existing Entity Attributes
112+
---
113+
Usually we not only have to create new data in the model, but also to modify existing. This process is extremely simple, and can be done as follows:
114+
*/
115+
39116
const entityAttributes = await model.getProperties(186);
40117
if (entityAttributes) {
41-
entityAttributes.Name.value = "New Wall Name";
42-
await propertiesManager.setData(model, entityAttributes);
118+
// Names are optional attributes! So we check if the entity has it.
119+
if (entityAttributes.Name) {
120+
entityAttributes.Name.value = "New Wall Name";
121+
} else {
122+
entityAttributes.Name = new WEBIFC.IFC2X3.IfcIdentifier("New Wall Name");
123+
}
124+
// You not only need to use this method when a new entity has been created, but also when it has been modified!
125+
await propsManager.setData(model, entityAttributes);
43126
}
44127

45-
// Create a new random entity
46-
const ifcTask = new WEBIFC.IFC4X3.IfcTask(
47-
new WEBIFC.IFC4X3.IfcGloballyUniqueId(OBC.UUID.create()),
48-
null,
49-
null,
50-
null,
51-
null,
52-
null,
53-
null,
54-
null,
55-
null,
56-
new WEBIFC.IFC4X3.IfcBoolean(false),
57-
null,
58-
null,
59-
null,
60-
);
128+
/* MD
129+
### ❌ Deleting Entities
130+
---
131+
Just as adding data to the model, sometimes you need to delete information. Just as before, this process is really straightforward and you can go as follows:
132+
*/
133+
134+
await model.setProperties(243, null);
135+
propsManager.registerChange(model, 243);
61136

62-
await propertiesManager.setData(model, ifcTask);
137+
/* MD
138+
:::info
139+
140+
Deleting data is not just removing the entity from the IFC file; for example, if you want to delete a wall, then you should also remove all the entities that defines its shape, its properties, etc, as they are no longer needed. So, many times, deleting one single entity should lead to deleting many others. Right now the engine doesn't have the tools to do it, but we're working on it! Stay tuned 😉
141+
142+
:::
143+
144+
### ⏬ Exporting the Modified Model
145+
---
146+
Lastly, you can use the IfcPropertiesManager to export the modified file! You can proceed as follows:
147+
*/
63148

64-
// Export modified model
65149
const downloadBtn = document.getElementById("download-btn")!;
66150
downloadBtn.addEventListener("click", async () => {
67-
const modifiedBuffer = await propertiesManager.saveToIfc(
68-
model,
69-
new Uint8Array(buffer),
70-
);
71-
72-
const modifiedFile = new File([modifiedBuffer], "small-modified.ifc");
151+
// Here you need to provide the UInt8Array of the original model you want to modify
152+
const modifiedBuffer = await propsManager.saveToIfc(model, typedArray);
153+
const file = new File([modifiedBuffer], "small-modified.ifc");
73154
const a = document.createElement("a");
74-
a.href = URL.createObjectURL(modifiedFile);
75-
a.download = modifiedFile.name;
155+
a.href = URL.createObjectURL(file);
156+
a.download = file.name;
76157
a.click();
77158
URL.revokeObjectURL(a.href);
78159
});
160+
161+
/* MD
162+
### 🎉 Wrap Up!
163+
---
164+
That's it! Now you know how to use the IfcPropertiesManager to add, modify and delete entities in your IFC file. Keep going with more tutorials! 💪
165+
*/

packages/core/src/ifc/IfcPropertiesManager/index.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export class IfcPropertiesManager extends Component implements Disposable {
202202
*/
203203
async newPset(model: FragmentsGroup, name: string, description?: string) {
204204
const schema = IfcPropertiesManager.getIFCSchema(model);
205-
const { ownerHistoryHandle } = await this.getOwnerHistory(model);
205+
const { handle: ownerHistoryHandle } = await this.getOwnerHistory(model);
206206

207207
const psetGlobalId = this.newGUID(model);
208208
const psetName = new WEBIFC[schema].IfcLabel(name);
@@ -610,20 +610,20 @@ export class IfcPropertiesManager extends Component implements Disposable {
610610
return new WEBIFC[schema].IfcGloballyUniqueId(UUID.create());
611611
}
612612

613-
private async getOwnerHistory(model: FragmentsGroup) {
613+
async getOwnerHistory(model: FragmentsGroup) {
614614
const ownerHistories = await model.getAllPropertiesOfType(
615615
WEBIFC.IFCOWNERHISTORY,
616616
);
617617
if (!ownerHistories) {
618618
throw new Error("No OwnerHistory was found.");
619619
}
620620
const keys = Object.keys(ownerHistories).map((key) => parseInt(key, 10));
621-
const ownerHistory = ownerHistories[keys[0]];
622-
const ownerHistoryHandle = new WEBIFC.Handle(ownerHistory.expressID);
623-
return { ownerHistory, ownerHistoryHandle };
621+
const entity = ownerHistories[keys[0]];
622+
const handle = new WEBIFC.Handle(entity.expressID);
623+
return { entity, handle };
624624
}
625625

626-
private registerChange(model: FragmentsGroup, ...expressID: number[]) {
626+
registerChange(model: FragmentsGroup, ...expressID: number[]) {
627627
if (!this.changeMap[model.uuid]) {
628628
this.changeMap[model.uuid] = new Set();
629629
}

0 commit comments

Comments
 (0)