Libraries used for this code:
PythonOCC
IfcOpenShellPython
PyQt5
The script reads and displays the ifc 3D representation, and assigns to every open cascade geometry the Global ID of its corresponding ifc element.
When the element is clicked on the 3D viewer, in the tables are displayed the props of the ifc element.
import sys
import ifcopenshell
import ifcopenshell.geom
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QTableWidget, QTableWidgetItem, QHeaderView
from PyQt5.QtGui import QFont
from OCC.Display.SimpleGui import init_display
from OCC.Core.Graphic3d import *
from OCC.Core.Quantity import Quantity_Color, Quantity_TOC_RGB
from OCC.Core.AIS import AIS_Shape
from OCC.Core.TopAbs import TopAbs_ShapeEnum
from OCC.Core.TopoDS import TopoDS_Iterator
# Initialize the 3D display
display, start_display, _, _ = init_display()
# Open the IFC file
ifc_file = ifcopenshell.open("test.ifc")
# Configure settings for PythonOCC
settings = ifcopenshell.geom.settings()
settings.set(settings.USE_PYTHON_OPENCASCADE, True)
# Dictionary to map AIS_Shape to GlobalId
shape_to_guid_map = {}
# Function to retrieve all attributes and properties of a product based on GlobalId
def get_attributes_and_properties_by_guid(guid):
product = ifc_file.by_guid(guid)
attributes = {attr: getattr(product, attr, None) for attr in dir(product) if not attr.startswith("_")}
properties = {}
if hasattr(product, "IsDefinedBy"):
for rel in product.IsDefinedBy:
if rel.is_a("IfcRelDefinesByProperties"):
for prop in rel.RelatingPropertyDefinition.PropertySetDefinitions:
if hasattr(prop, "HasProperties"):
for p in prop.HasProperties:
properties[p.Name] = p.NominalValue.wrappedValue if hasattr(p.NominalValue, 'wrappedValue') else p.NominalValue
attributes.update(properties)
return attributes
# Function to retrieve material properties of a product based on GlobalId
def get_material_properties_by_guid(guid):
product = ifc_file.by_guid(guid)
material_properties = {}
if hasattr(product, "HasAssociations"):
for association in product.HasAssociations:
if association.is_a("IfcRelAssociatesMaterial"):
material = association.RelatingMaterial
material_properties["Material"] = material.Name
return material_properties
# Function to add sub-shapes of a compound to the mapping
def add_shape_to_map(shape, guid):
if shape.ShapeType() == TopAbs_ShapeEnum.TopAbs_COMPOUND:
sub_shapes = TopoDS_Iterator(shape)
while sub_shapes.More():
sub_shape = sub_shapes.Value()
shape_to_guid_map[sub_shape] = guid
sub_shapes.Next()
else:
shape_to_guid_map[shape] = guid
# Function to create and display the geometric shapes from IFC
def display_ifc_shapes():
for product in ifc_file.by_type("IfcProduct"):
if product.Representation:
try:
shape = ifcopenshell.geom.create_shape(settings, inst=product)
ais_shape = AIS_Shape(shape.geometry)
# Display shape with color and transparency
r, g, b, a = shape.styles[0] if shape.styles else (0.5, 0.5, 0.5, 0.5) # default color
color = Quantity_Color(abs(r), abs(g), abs(b), Quantity_TOC_RGB)
display.Context.Display(ais_shape, True)
display.Context.SetColor(ais_shape, color, True)
display.Context.SetTransparency(ais_shape, 0.15, True)
# Add GUID to our mapping
guid = product.GlobalId
add_shape_to_map(shape.geometry, guid)
print(f"Added {guid} to shape map")
except RuntimeError:
print(f"Failed to process shape geometry for {product.GlobalId}")
# Function to handle selection events
def on_select(selected_shapes, x, y):
# Reset metadata panel
gui_panel.clear_metadata()
for selected_shape in selected_shapes:
if selected_shape.ShapeType() == TopAbs_ShapeEnum.TopAbs_COMPOUND:
# If shape is a compound, iterate through its sub-shapes
comp = selected_shape
sub_shapes = TopoDS_Iterator(comp)
while sub_shapes.More():
sub_shape = sub_shapes.Value()
guid = shape_to_guid_map.get(sub_shape)
if guid:
metadata = get_attributes_and_properties_by_guid(guid)
material_properties = get_material_properties_by_guid(guid)
metadata.update(material_properties)
gui_panel.update_metadata(metadata)
sub_shapes.Next()
else:
# Otherwise, try to find the GUID for the directly selected shape
guid = shape_to_guid_map.get(selected_shape)
if guid:
metadata = get_attributes_and_properties_by_guid(guid)
material_properties = get_material_properties_by_guid(guid)
metadata.update(material_properties)
gui_panel.update_metadata(metadata)
# Class for the GUI panel
class GUIPanel(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.label_title = QLabel("3D Element Metadata")
self.label_title.setFont(QFont("Arial", 12))
self.layout.addWidget(self.label_title)
# Table for displaying metadata
self.table = QTableWidget()
self.table.setColumnCount(3)
self.table.setHorizontalHeaderLabels(["Index", "Name", "Value"])
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.layout.addWidget(self.table)
def update_metadata(self, metadata):
self.clear_metadata()
self.table.setRowCount(len(metadata))
for row, (key, value) in enumerate(metadata.items()):
self.table.setItem(row, 0, QTableWidgetItem(str(row + 1)))
self.table.setItem(row, 1, QTableWidgetItem(key))
self.table.setItem(row, 2, QTableWidgetItem(str(value)))
def clear_metadata(self):
self.table.setRowCount(0)
# Initialize our GUI
app = QApplication(sys.argv)
gui_panel = GUIPanel()
gui_panel.setGeometry(1000, 100, 400, 600)
gui_panel.show()
# Callback function for selection
display.register_select_callback(on_select)
# Display geometric shapes from the IFC file
display_ifc_shapes()
# Initialize 3D display
display.FitAll()
start_display()
# Properly exit the application when execution ends
sys.exit(app.exec_())