-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
ArchIFC.py
466 lines (373 loc) · 17.5 KB
/
ArchIFC.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
"""This modules sets up and manages the IFC-related properties, types
and attributes of Arch/BIM objects.
"""
import FreeCAD, os, json
if FreeCAD.GuiUp:
from PySide.QtCore import QT_TRANSLATE_NOOP
else:
def QT_TRANSLATE_NOOP(ctx,txt):
return txt
import ArchIFCSchema
def uncamel(t):
return ''.join(map(lambda x: x if x.islower() else " "+x, t[3:]))[1:]
IfcTypes = [uncamel(t) for t in ArchIFCSchema.IfcProducts.keys()]
class IfcRoot:
"""This class defines the common methods and properties for managing IFC data.
IFC, or Industry Foundation Classes are a standardised way to digitally
describe the built environment. The ultimate goal of IFC is to provide
better interoperability between software that deals with the built
environment. You can learn more here:
https://technical.buildingsmart.org/standards/ifc/
You can learn more about the technical details of the IFC schema here:
https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/
This class is further segmented down into IfcProduct and IfcContext.
"""
def setProperties(self, obj):
"""Give the object properties for storing IFC data.
Also migrate old versions of IFC properties to the new property names
using the .migrateDeprecatedAttributes() method.
"""
if not "IfcData" in obj.PropertiesList:
obj.addProperty("App::PropertyMap","IfcData","IFC",QT_TRANSLATE_NOOP("App::Property","IFC data"))
if not "IfcType" in obj.PropertiesList:
obj.addProperty("App::PropertyEnumeration","IfcType","IFC",QT_TRANSLATE_NOOP("App::Property","The type of this object"))
obj.IfcType = self.getCanonicalisedIfcTypes()
if not "IfcProperties" in obj.PropertiesList:
obj.addProperty("App::PropertyMap","IfcProperties","IFC",QT_TRANSLATE_NOOP("App::Property","IFC properties of this object"))
self.migrateDeprecatedAttributes(obj)
def onChanged(self, obj, prop):
"""Method called when the object has a property changed.
If the object's IfcType has changed, change the object's properties
that relate to IFC attributes in order to match the IFC schema
definition of the new IFC type.
If a property changes that is in the "IFC Attributes" group, also
change the value stored in the IfcData property's JSON.
Parameters
----------
prop: string
The name of the property that has changed.
"""
if prop == "IfcType":
self.setupIfcAttributes(obj)
self.setupIfcComplexAttributes(obj)
if prop in obj.PropertiesList:
if obj.getGroupOfProperty(prop) == "IFC Attributes":
self.setObjIfcAttributeValue(obj, prop, obj.getPropertyByName(prop))
def setupIfcAttributes(self, obj):
"""Set up the IFC attributes in the object's properties.
Add the attributes specified in the object's IFC type schema, to the
object's properties. Do not re-add them if they're already present.
Also remove old IFC attribute properties that no longer appear in the
schema for backwards compatibility.
Do so using the .addIfcAttributes() and
.purgeUnusedIfcAttributesFromPropertiesList() methods.
Learn more about IFC attributes here:
https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/schema/chapter-3.htm#attribute
"""
ifcTypeSchema = self.getIfcTypeSchema(obj.IfcType)
if ifcTypeSchema is None:
return
self.purgeUnusedIfcAttributesFromPropertiesList(ifcTypeSchema, obj)
self.addIfcAttributes(ifcTypeSchema, obj)
def setupIfcComplexAttributes(self, obj):
"""Add the IFC type's complex attributes to the object.
Get the object's IFC type schema, and add the schema for the type's
complex attributes within the IfcData property.
"""
ifcTypeSchema = self.getIfcTypeSchema(obj.IfcType)
if ifcTypeSchema is None:
return
IfcData = obj.IfcData
if "complex_attributes" not in IfcData:
IfcData["complex_attributes"] = "{}"
ifcComplexAttributes = json.loads(IfcData["complex_attributes"])
for attribute in ifcTypeSchema["complex_attributes"]:
if attribute["name"] not in ifcComplexAttributes.keys():
ifcComplexAttributes[attribute["name"]] = {}
IfcData["complex_attributes"] = json.dumps(ifcComplexAttributes)
obj.IfcData = IfcData
def getIfcTypeSchema(self, IfcType):
"""Get the schema of the IFC type provided.
If the IFC type is undefined, return the schema of the
IfcBuildingElementProxy.
Parameter
---------
IfcType: str
The IFC type whose schema you want.
Returns
-------
dict
Returns the schema of the type as a dict.
None
Returns None if the IFC type does not exist.
"""
name = "Ifc" + IfcType.replace(" ", "")
if IfcType == "Undefined":
name = "IfcBuildingElementProxy"
if name in self.getIfcSchema():
return self.getIfcSchema()[name]
return None
def getIfcSchema(self):
"""Get the IFC schema of all types relevant to this class.
Intended to be overwritten by the classes that inherit this class.
Returns
-------
dict
The schema of all the types relevant to this class.
"""
return {}
def getCanonicalisedIfcTypes(self):
"""Get the names of IFC types, converted to the form used in Arch.
Change the names of all IFC types to a more human readable form which
is used instead throughout Arch instead of the raw type names. The
names have the "Ifc" stripped from the start of their name, and spaces
inserted between the words.
Returns
-------
list of str
The list of every IFC type name in their form used in Arch. List
will have names in the same order as they appear in the schema's
JSON, as per the .keys() method of dicts.
"""
schema = self.getIfcSchema()
return [''.join(map(lambda x: x if x.islower() else " "+x, t[3:]))[1:] for t in schema.keys()]
def getIfcAttributeSchema(self, ifcTypeSchema, name):
"""Get the schema of an IFC attribute with the given name.
Convert the IFC attribute's name from the human readable version Arch
uses, and convert it to the less readable name it has in the IFC
schema.
Parameters
----------
ifcTypeSchema: dict
The schema of the IFC type to access the attribute of.
name: str
The name the attribute has in Arch.
Returns
-------
dict
Returns the schema of the attribute.
None
Returns None if the IFC type does not have the attribute requested.
"""
for attribute in ifcTypeSchema["attributes"]:
if attribute["name"].replace(' ', '') == name:
return attribute
return None
def addIfcAttributes(self, ifcTypeSchema, obj):
"""Add the attributes of the IFC type's schema to the object's properties.
Add the attributes as properties of the object. Also add the
attribute's schema within the object's IfcData property. Do so using
the .addIfcAttribute() method.
Also add expressions to copy data from the object's editable
properties. This means the IFC properties will remain accurate with
the actual values of the object. Do not do so for all IFC properties.
Do so using the .addIfcAttributeValueExpressions() method.
Learn more about expressions here:
https://wiki.freecadweb.org/Expressions
Do not add the attribute if the object has a property with the
attribute's name. Also do not add the attribute if its name is
RefLatitude, RefLongitude, or Name.
Parameters
----------
ifcTypeSchema: dict
The schema of the IFC type.
"""
for attribute in ifcTypeSchema["attributes"]:
if attribute["name"] in obj.PropertiesList \
or attribute["name"] == "RefLatitude" \
or attribute["name"] == "RefLongitude" \
or attribute["name"] == "Name":
continue
self.addIfcAttribute(obj, attribute)
self.addIfcAttributeValueExpressions(obj, attribute)
def addIfcAttribute(self, obj, attribute):
"""Add an IFC type's attribute to the object, within its properties.
Add the attribute's schema to the object's IfcData property, as an
item under its "attributes" array.
Also add the attribute as a property of the object.
Parameters
----------
attribute: dict
The attribute to add. Should have the structure of an attribute
found within the IFC schemas.
"""
if not hasattr(obj, "IfcData"):
return
IfcData = obj.IfcData
if "attributes" not in IfcData:
IfcData["attributes"] = "{}"
IfcAttributes = json.loads(IfcData["attributes"])
IfcAttributes[attribute["name"]] = attribute
IfcData["attributes"] = json.dumps(IfcAttributes)
obj.IfcData = IfcData
if attribute["is_enum"]:
obj.addProperty("App::PropertyEnumeration",
attribute["name"],
"IFC Attributes",
QT_TRANSLATE_NOOP("App::Property", "Description of IFC attributes are not yet implemented"))
setattr(obj, attribute["name"], attribute["enum_values"])
else:
import ArchIFCSchema
propertyType = "App::" + ArchIFCSchema.IfcTypes[attribute["type"]]["property"]
obj.addProperty(propertyType,
attribute["name"],
"IFC Attributes",
QT_TRANSLATE_NOOP("App::Property", "Description of IFC attributes are not yet implemented"))
def addIfcAttributeValueExpressions(self, obj, attribute):
"""Add expressions for IFC attributes, so they stay accurate with the object.
Add expressions to the object that copy data from the editable
properties of the object. This ensures that the IFC attributes will
remain accurate with the actual values of the object.
Currently, add expressions for the following IFC attributes:
- OverallWidth
- OverallHeight
- ElevationWithFlooring
- Elevation
- NominalDiameter
- BarLength
- RefElevation
- LongName
Learn more about expressions here:
https://wiki.freecadweb.org/Expressions
Parameters
----------
attribute: dict
The schema of the attribute to add the expression for.
"""
if obj.getGroupOfProperty(attribute["name"]) != "IFC Attributes" \
or attribute["name"] not in obj.PropertiesList:
return
if attribute["name"] == "OverallWidth":
if "Length" in obj.PropertiesList:
obj.setExpression("OverallWidth", "Length.Value")
elif "Width" in obj.PropertiesList:
obj.setExpression("OverallWidth", "Width.Value")
elif obj.Shape and (obj.Shape.BoundBox.XLength > obj.Shape.BoundBox.YLength):
obj.setExpression("OverallWidth", "Shape.BoundBox.XLength")
elif obj.Shape:
obj.setExpression("OverallWidth", "Shape.BoundBox.YLength")
elif attribute["name"] == "OverallHeight":
if "Height" in obj.PropertiesList:
obj.setExpression("OverallHeight", "Height.Value")
else:
obj.setExpression("OverallHeight", "Shape.BoundBox.ZLength")
elif attribute["name"] == "ElevationWithFlooring" and "Shape" in obj.PropertiesList:
obj.setExpression("ElevationWithFlooring", "Shape.BoundBox.ZMin")
elif attribute["name"] == "Elevation" and "Placement" in obj.PropertiesList:
obj.setExpression("Elevation", "Placement.Base.z")
elif attribute["name"] == "NominalDiameter" and "Diameter" in obj.PropertiesList:
obj.setExpression("NominalDiameter", "Diameter.Value")
elif attribute["name"] == "BarLength" and "Length" in obj.PropertiesList:
obj.setExpression("BarLength", "Length.Value")
elif attribute["name"] == "RefElevation" and "Elevation" in obj.PropertiesList:
obj.setExpression("RefElevation", "Elevation.Value")
elif attribute["name"] == "LongName":
obj.LongName = obj.Label
def setObjIfcAttributeValue(self, obj, attributeName, value):
"""Change the value of an IFC attribute within the IfcData property's json.
Parameters
----------
attributeName: str
The name of the attribute to change.
value:
The new value to set.
"""
IfcData = obj.IfcData
if "attributes" not in IfcData:
IfcData["attributes"] = "{}"
IfcAttributes = json.loads(IfcData["attributes"])
if isinstance(value, FreeCAD.Units.Quantity):
value = float(value)
if not attributeName in IfcAttributes:
IfcAttributes[attributeName] = {}
IfcAttributes[attributeName]["value"] = value
IfcData["attributes"] = json.dumps(IfcAttributes)
obj.IfcData = IfcData
def setObjIfcComplexAttributeValue(self, obj, attributeName, value):
"""Changes the value of the complex attribute in the IfcData property JSON.
Parameters
----------
attributeName: str
The name of the attribute to change.
value:
The new value to set.
"""
IfcData = obj.IfcData
IfcAttributes = json.loads(IfcData["complex_attributes"])
IfcAttributes[attributeName] = value
IfcData["complex_attributes"] = json.dumps(IfcAttributes)
obj.IfcData = IfcData
def getObjIfcComplexAttribute(self, obj, attributeName):
"""Get the value of the complex attribute, as stored in the IfcData JSON.
Parameters
----------
attributeName: str
The name of the complex attribute to access.
Returns
-------
The value of the complex attribute.
"""
return json.loads(obj.IfcData["complex_attributes"])[attributeName]
def purgeUnusedIfcAttributesFromPropertiesList(self, ifcTypeSchema, obj):
"""Remove properties representing IFC attributes if they no longer appear.
Remove the property representing an IFC attribute, if it does not
appear in the schema of the IFC type provided. Also, remove the
property if its attribute is an enum type, presumably for backwards
compatibility.
Learn more about IFC enums here:
https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/schema/chapter-3.htm#enumeration
"""
for property in obj.PropertiesList:
if obj.getGroupOfProperty(property) != "IFC Attributes":
continue
ifcAttribute = self.getIfcAttributeSchema(ifcTypeSchema, property)
if ifcAttribute is None or ifcAttribute["is_enum"] is True:
obj.removeProperty(property)
def migrateDeprecatedAttributes(self, obj):
"""Update the object to use the newer property names for IFC related properties.
"""
if "Role" in obj.PropertiesList:
r = obj.Role
obj.removeProperty("Role")
if r in IfcTypes:
obj.IfcType = r
FreeCAD.Console.PrintMessage("Upgrading "+obj.Label+" Role property to IfcType\n")
if "IfcRole" in obj.PropertiesList:
r = obj.IfcRole
obj.removeProperty("IfcRole")
if r in IfcTypes:
obj.IfcType = r
FreeCAD.Console.PrintMessage("Upgrading "+obj.Label+" IfcRole property to IfcType\n")
if "IfcAttributes"in obj.PropertiesList:
obj.IfcData = obj.IfcAttributes
obj.removeProperty("IfcAttributes")
class IfcProduct(IfcRoot):
"""This class is subclassed by classes that have a specific location in space.
The obvious example are actual structures, such as the _Wall class, but it
also includes the _Floor class, which is just a grouping of all the
structures that make up one floor of a building.
You can learn more about how products fit into the IFC schema here:
https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/schema/ifckernel/lexical/ifcproduct.htm
"""
def getIfcSchema(self):
"""Get the IFC schema of all IFC types that inherit from IfcProducts.
Returns
-------
dict
The schema of all the types relevant to this class.
"""
return ArchIFCSchema.IfcProducts
class IfcContext(IfcRoot):
"""This class is subclassed by classes that define a particular context.
Currently, only the _Project inherits this class.
You can learn more about how contexts fit into the IFC schema here:
https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/schema/ifckernel/lexical/ifccontext.htm
"""
def getIfcSchema(self):
"""Get the IFC schema of all IFC types that inherit from IfcContexts.
Returns
-------
dict
The schema of all the types relevant to this class.
"""
return ArchIFCSchema.IfcContexts