Added docstrings for Arch IFC files

This commit is contained in:
David Daish
2020-04-06 16:57:17 +12:00
committed by Yorik van Havre
parent 3a5555f67a
commit 15abfc23f3
3 changed files with 378 additions and 6 deletions

View File

@@ -1,6 +1,8 @@
import FreeCAD, os, json
"This modules sets up and manages the IFC-related properties, types and attributes of Arch/BIM objects"
__doc__="""This modules sets up and manages the IFC-related properties, types
and attributes of Arch/BIM objects.
"""
if FreeCAD.GuiUp:
from PySide.QtCore import QT_TRANSLATE_NOOP
@@ -13,7 +15,27 @@ import ArchIFCSchema
IfcTypes = [''.join(map(lambda x: x if x.islower() else " "+x, t[3:]))[1:] 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 enviroment. The ultimate goal of IFC is to provide
better interoperability between software that deals with the built
enviroment. 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):
"""Gives the object properties for storing IFC data.
Also migrates 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"))
@@ -27,6 +49,22 @@ class IfcRoot:
self.migrateDeprecatedAttributes(obj)
def onChanged(self, obj, prop):
"""Method called when the object has a property changed.
If the object's IfcType has changed, this method changes 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, this
method will 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)
@@ -35,6 +73,20 @@ class IfcRoot:
self.setObjIfcAttributeValue(obj, prop, obj.getPropertyByName(prop))
def setupIfcAttributes(self, obj):
"""Sets up the IFC attributes in the object's properties.
Adds the attributes specified in the object's IFC type schema, to the
object's properties. Does not re-add them if they're already present.
Also removes old IFC attribute properties that no longer appear in the
schema for backwards compatability.
Does 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
@@ -42,6 +94,12 @@ class IfcRoot:
self.addIfcAttributes(ifcTypeSchema, obj)
def setupIfcComplexAttributes(self, obj):
"""Adds the IFC type's complex attributes to the object.
Gets the object's IFC type schema, and adds the schema for the type's
complex attributes within the IfcData property.
"""
ifcTypeSchema = self.getIfcTypeSchema(obj.IfcType)
if ifcTypeSchema is None:
return
@@ -56,6 +114,23 @@ class IfcRoot:
obj.IfcData = IfcData
def getIfcTypeSchema(self, IfcType):
"""Gets the schema of the IFC type provided.
If the IFC type is undefined, returns 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"
@@ -64,19 +139,91 @@ class IfcRoot:
return None
def getIfcSchema(self):
"""Gets 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):
"""This method gets the names of IFC types, converted to the form used in Arch.
This method changes 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):
"""Gets the schema of an IFC attribute with the given name.
This method does so by converting the IFC attribute's name from the
human readable version Arch uses, and converting 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):
"""Adds the attributes of the IFC type's schema to the object's properties.
Adds the attributes as properties of the object. Also adds the
attribute's schema within the object's IfcData property. It does so
using the .addIfcAttribute() method.
Also adds 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. This is not done for all IFC
properties. It does so using the .addIfcAttributeValueExpressions()
method.
Learn more about expressions here:
https://wiki.freecadweb.org/Expressions
Does not add the attribute if the object has a property with the
attribute's name. Also does not add the attribute if it's 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" \
@@ -87,24 +234,71 @@ class IfcRoot:
self.addIfcAttributeValueExpressions(obj, attribute)
def addIfcAttribute(self, obj, attribute):
"""Adds an IFC type's attribute to the object, within it's properties.
Adds the attribute's schema to the object's IfcData property, as an
item under it's "attributes" array.
Also adds 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"))
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"))
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):
"""Adds expressions for IFC attributes, so they stay accurate with the object.
Adds 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 adds 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
@@ -136,6 +330,15 @@ class IfcRoot:
obj.LongName = obj.Label
def setObjIfcAttributeValue(self, obj, attributeName, value):
"""Changes 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"] = "{}"
@@ -149,6 +352,16 @@ class IfcRoot:
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
@@ -156,9 +369,32 @@ class IfcRoot:
obj.IfcData = IfcData
def getObjIfcComplexAttribute(self, obj, attributeName):
"""Gets 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):
"""Removes properties representing IFC attributes if they no longer appear.
Removes the property representing an IFC attribute, if it does not
appear in the schema of the IFC type provided. Also, removes the
property if it's attribute is an enum type, presumably for backwards
compatability.
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
@@ -167,6 +403,9 @@ class IfcRoot:
obj.removeProperty(property)
def migrateDeprecatedAttributes(self, obj):
"""Updates the object to use the newer property names for IFC related properties.
"""
if "Role" in obj.PropertiesList:
r = obj.Role
obj.removeProperty("Role")
@@ -186,9 +425,41 @@ class IfcRoot:
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):
"""Gets 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):
"""Gets 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

View File

@@ -1,10 +1,15 @@
import FreeCAD, os, json
ifcVersions = ["IFC4", "IFC2X3"]
__doc__ = """Provides the IFC schema data as dicts, by loading the JSON schema files.
Provides the data as IfcContexts, IfcProducts and IfcTypes.
"""
ifcVersions = ["IFC4", "IFC2X3"]
IfcVersion = ifcVersions[FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("IfcVersion",0)]
with open(os.path.join(FreeCAD.getResourceDir(), "Mod", "Arch", "Presets",
"ifc_contexts_" + IfcVersion + ".json")) as f:
"ifc_contexts_" + IfcVersion + ".json")) as f:
IfcContexts = json.load(f)
with open(os.path.join(FreeCAD.getResourceDir(), "Mod", "Arch", "Presets",

View File

@@ -4,14 +4,45 @@ if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtGui
__doc__ = """View providers and UI elements for the Ifc classes."""
class IfcContextView:
"""A default view provider for IfcContext objects."""
def setEdit(self, viewObject, mode):
"""Method called when the document requests the object to enter edit mode.
Opens the IfcContextUi as the task panel.
Edit mode is entered when a user double clicks on an object in the tree
view, or when they use the menu option [Edit -> Toggle Edit Mode].
Parameters
----------
mode: int or str
The edit mode the document has requested. Set to 0 when requested via
a double click or [Edit -> Toggle Edit Mode].
Returns
-------
bool
If edit mode was entered.
"""
# What does mode do?
FreeCADGui.Control.showDialog(IfcContextUI(viewObject.Object))
return True
class IfcContextUI:
"""A default task panel for editing context objects."""
def __init__(self, object):
"""Initialises the task panel.
Defines the layout, and prefills the form with the
data already written to the object.
"""
self.object = object
self.lineEditObjects = []
self.createBaseLayout()
@@ -20,6 +51,11 @@ class IfcContextUI:
self.form = self.baseWidget
def accept(self):
"""This method runs as a callback when the user selects the ok button.
It writes the data entered into the forms to the object's IfcData
property.
"""
data = {}
for lineEdit in self.lineEditObjects:
data[lineEdit.objectName()] = lineEdit.text()
@@ -27,10 +63,18 @@ class IfcContextUI:
return True
def createBaseLayout(self):
"""Defines the basic layout of the task panel."""
self.baseWidget = QtGui.QWidget()
self.baseLayout = QtGui.QVBoxLayout(self.baseWidget)
def createMapConversionFormLayout(self):
"""Creates form entries for the data being edited.
Creates form entries for each of the data points being edited within
the IFC complex attribute, RepresentationContexts.
"""
self.baseLayout.addWidget(self.createLabel("Target Coordinate Reference System"))
self.baseLayout.addLayout(self.createFormEntry("name", "Name"))
self.baseLayout.addLayout(self.createFormEntry("description", "Description"))
@@ -46,25 +90,77 @@ class IfcContextUI:
self.baseLayout.addLayout(self.createFormEntry("orthogonal_height", "Orthogonal height"))
self.baseLayout.addLayout(self.createFormEntry("true_north", "True north (anti-clockwise from +Y)"))
self.baseLayout.addLayout(self.createFormEntry("scale", "Scale"))
def prefillMapConversionForm(self):
"""Prefills each of the form entries with the exising value.
Gets the existing value from the object's IfcData, specifically the complex
attribute, RepresentationContexts.
"""
data = ArchIFC.IfcRoot.getObjIfcComplexAttribute(self, self.object, "RepresentationContexts")
for lineEdit in self.lineEditObjects:
if lineEdit.objectName() in data.keys():
lineEdit.setText(data[lineEdit.objectName()])
def createFormEntry(self, name, label):
"""Creates a form entry.
The name corresponds to the data point being edited in the
RepresentationContexts complex attribute. The label is a human readable
version of the name.
Parameters
----------
name: str
The name of the datapoint within the RepresentationContexts
attribute being editied.
label: str
A human readable version of the name.
Returns
-------
<PySide2.QtWidgets.QWidget>
Widget containing the label and form.
"""
layout = QtGui.QHBoxLayout(self.baseWidget)
layout.addWidget(self.createLabel(label))
layout.addWidget(self.createLineEdit(name))
return layout
def createLabel(self, value):
"""Creates a translated label.
Parameters
----------
value: str
The human readable label text.
Returns
-------
<PySide2.QtWidgets.QWidget>
The label Qt widget.
"""
label = QtGui.QLabel(self.baseWidget)
label.setText(QtGui.QApplication.translate("Arch", value, None))
return label
def createLineEdit(self, name):
"""Creates a form with the name specified.
Parameters
----------
name: str
The name of the datapoint within the RepresentationContexts
attribute being editied.
Returns
-------
<PySide2.QtWidgets.QWidget>
The form Qt widget.
"""
lineEdit = QtGui.QLineEdit(self.baseWidget)
lineEdit.setObjectName(name)
self.lineEditObjects.append(lineEdit)