-
Notifications
You must be signed in to change notification settings - Fork 9
4. GUI Application with attributes, properties, quantities and type information
In this example, we will expand on our Basic GUI example, using the same PyQt5 libraries. In our main __init__
method, we want to display everything we can think of for a single object: properties, quantities, the type and also the core attributes. To keep it convenient, we will put that in a second QTreeWidget
.
def __init__(self):
QWidget.__init__(self)
# Prepare Tree Widgets in a stretchable layout
vbox = QVBoxLayout()
self.setLayout(vbox)
# Object Tree
self.object_tree = QTreeWidget()
vbox.addWidget(self.object_tree)
self.object_tree.setColumnCount(3)
self.object_tree.setHeaderLabels(["Name", "Class", "ID"])
This was what we already had before. We now add the possibility to select multiple objects, by setting the SelectionModel
to ExtendedSelection
and by linking any change of our selection to a new .add_data
method.
self.object_tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.object_tree.selectionModel().selectionChanged.connect(self.add_data)
And then we add a second QTreeWidget
and add it inside the same vertical layout, so everything stretches nicely.
# Property Tree
self.property_tree = QTreeWidget()
vbox.addWidget(self.property_tree)
self.property_tree.setColumnCount(3)
self.property_tree.setHeaderLabels(["Name", "Value", "ID/Type"])
The load_file
and add_object_in_tree
and even the main
methods are exactly the same.
For the second tree, we create a new add_data
method, which has no particular arguments, as it will retrieve the selection from the first tree automatically. In this method, we clear the tree, so it is ready to be filled in again, based on the current selection. As we may have multiple items selected, we run through all of them in a for
loop.
def add_data(self):
self.property_tree.clear()
items = self.object_tree.selectedItems()
for item in items:
We then check the string in the third column, that is item.text(2)
since we always count from zero. If it is empty, there is nothing we can do but break
. Otherwise, we assume that it contains a GUID
(normally the .GlobalId
attribute that the object has inherited from IfcRoot
. .by_guid()
can be used to retrieve the actual entity from the IFC file. Again, break when we have no result.
# the GUID is in the third column
buffer = item.text(2)
if not buffer:
break
# find the related object in our IFC file
ifc_object = self.ifc_file.by_guid(buffer)
if ifc_object is None:
break
Beware that this code will only work for so-called rooted
classes. So we may eventually add additional support for other classes.
When we test the application now, we get the second tree, but it is still empty.
Within the add_data
method, we start by adding type, properties and quantities, using almost exactly the same code as in the console application. But instead of dumping it in the console, we place them in a QTreeWidgetItem
. For the column values, we pass the .Name
, class (.is_a()
) and the GUID (.GlobalId
) inside a Tuple. The tree item is placed at the top of the tree (.addTopLevelItem
).
# Properties & Quantities
if hasattr(ifc_object, 'IsDefinedBy'):
for definition in ifc_object.IsDefinedBy:
if definition.is_a('IfcRelDefinesByType'):
type_object = definition.RelatingType
type_item = QTreeWidgetItem([type_object.Name,
type_object.is_a(),
type_object.GlobalId])
self.property_tree.addTopLevelItem(type_item)
For the type that suffices, but the property set (which could also be a quantity set) still needs to add the individual properties, which we'll do in a separate method .add_properties_in_tree
or .add_quantities_in_tree
which receive the set and the tree item containing the set.
if definition.is_a('IfcRelDefinesByProperties'):
property_set = definition.RelatingPropertyDefinition
# the individual properties/quantities
if property_set.is_a('IfcPropertySet'):
properties_item = QTreeWidgetItem([property_set.Name,
property_set.is_a(),
property_set.GlobalId])
self.property_tree.addTopLevelItem(properties_item)
self.add_properties_in_tree(property_set, properties_item)
elif property_set.is_a('IfcElementQuantity'):
quantities_item = QTreeWidgetItem([property_set.Name,
property_set.is_a(),
property_set.GlobalId])
self.property_tree.addTopLevelItem(quantities_item)
self.add_quantities_in_tree(property_set, quantities_item)
The code is again the same as in the console app, with the only difference that we have to fill a tree item and place it underneath its parent.
# PropertySet
def add_properties_in_tree(self, property_set, parent_item):
# the individual properties
for prop in property_set.HasProperties:
unit = str(prop.Unit) if hasattr(prop, 'Unit') else ''
prop_value = '<not handled>'
if prop.is_a('IfcPropertySingleValue'):
prop_value = str(prop.NominalValue.wrappedValue)
parent_item.addChild(QTreeWidgetItem([prop.Name, prop_value, unit]))
Again, very much the same code as before.
# QuantitySet
def add_quantities_in_tree(self, quantity_set, parent_item):
# the individual quantities
for quantity in quantity_set.Quantities:
unit = str(quantity.Unit) if hasattr(quantity, 'Unit') else ''
quantity_value = '<not handled>'
if quantity.is_a('IfcQuantityLength'):
quantity_value = str(quantity.LengthValue)
elif quantity.is_a('IfcQuantityArea'):
quantity_value = str(quantity.AreaValue)
elif quantity.is_a('IfcQuantityVolume'):
quantity_value = str(quantity.VolumeValue)
elif quantity.is_a('IfcQuantityCount'):
quantity_value = str(quantity.CountValue)
parent_item.addChild(QTreeWidgetItem([quantity.Name, quantity_value, unit]))
With the IfcOpenHouse.ifc
example, it works fine, but beware that only the IfcSite
entity has a property set available.
Almost done. We need to add the attributes too.
Start by adding a container tree item in the add_data
method:
def add_data(self):
self.property_tree.clear()
items = self.object_tree.selectedItems()
for item in items:
[...]
# Attributes
attributes_item = QTreeWidgetItem(["Attributes", "", ifc_object.GlobalId])
self.property_tree.addTopLevelItem(attributes_item)
self.add_attributes_in_tree(ifc_object, attributes_item)
And finally we need the method .add_attributes_in_tree
as well. This is done from a for
loop: we go attribute by attribute, using a counter or index att_idx
as we know the total amount of attributes by asking the len
(length) of the object.
Using the index, we receive the name of the attribute (.attribute_name(att_idx)
), its value as a string (str(ifc_object[att_idx])
) and using the name we just received, we can request the type of the attribute (.attribute_type(att_name)
).
Use these three strings to fill a new tree item and put it underneath the parent.
# Attributes
def add_attributes_in_tree(self, ifc_object, parent_item):
# the individual attributes
for att_idx in range(0, len(ifc_object)):
att_name = ifc_object.attribute_name(att_idx)
att_value = str(ifc_object[att_idx])
att_type = ifc_object.attribute_type(att_name)
attribute_item = QTreeWidgetItem([att_name, att_value, att_type])
parent_item.addChild(attribute_item)
Beware that the attributes are in a fixed order and that some of them are values (e.g., STRING
, DOUBLE
), while others may refer to other entities (ENTITY INSTANCE
). In that case, we get a fragment that looks exactly like a line in a STEP file.
You can download the file here.
A big thanks to the IfcOpenShell library and the many people contributing with code, but also examples.