Added docstrings for Arch IFC files
This commit is contained in:
committed by
Yorik van Havre
parent
3a5555f67a
commit
15abfc23f3
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user