From 6603c8459a975a4e80cf2f0bc51c983cdcd0d49a Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Fri, 17 Jul 2026 10:01:23 +0300 Subject: [PATCH] Fix pythonocc-core viewer compatibility in geom.occ_utils and geom.app (#1037, #1098) set_shape_transparency() called AIS_InteractiveContext.SetTransparency(), whose argument count is inconsistent across pythonocc-core versions (reported as a TypeError in #1037). Set transparency directly on the AIS object instead, the same stable pattern already used elsewhere in this file (display_shape() calls ais.SetTransparency() directly, never through the Context), then call Context.UpdateCurrentViewer() to refresh. app.py's viewer used a "SetSelectionPriority(counter)"/"SelectionPriority()" pair as an ad hoc unique key to map a displayed AIS object back to its IFC product. On modern pythonocc-core this crashed with AttributeError because .GetObject() (needed to unwrap the old handle-based API) no longer exists on AIS objects (#1098, PR #1113 partially patched one of the two call sites but left the one in HandleSelection unguarded). Live pythonocc-core 7.9.3 testing showed the GetObject() guard alone is not sufficient: SetSelectionPriority/SelectionPriority themselves have been removed from AIS_InteractiveObject entirely in modern OCCT (only AIS_Trihedron keeps a same-named but unrelated method for datum parts), so gating the .GetObject() call with the existing USE_OCCT_HANDLE flag would still crash the first time a shape is selected. Verified live that AIS objects retain correct __eq__/__hash__ (matching the underlying OCCT instance) across separate SWIG wrapper instances, so ais_to_product is now keyed directly by the AIS object itself, removing the dependency on the removed OCCT API and the GetObject()/handle distinction altogether. Verified live against pythonocc-core 7.9.3 (conda-forge) using real AIS_Shape objects obtained from ifcopenshell.geom.occ_utils.display_shape() and a real IFC file: reproduced both the original TypeError (#1037) and AttributeError (#1098), confirmed both fixes resolve them, and confirmed the ais_to_product dict lookup round trips correctly through a real Context.Select()/SelectedInteractive() call. Could not exercise the full Qt-embedded viewer.finished()/HandleSelection() flow end to end because this pythonocc-core build segfaults natively when creating a second GL context inside a Qt widget on this macOS host, a pre-existing environment issue unrelated to this diff (reproduces identically with unpatched code, before any touched line executes). AI-generated, reviewed and tested by Petru Conduraru. Co-Authored-By: Claude Sonnet 5 --- src/ifcopenshell-python/ifcopenshell/geom/app.py | 14 +++++++------- .../ifcopenshell/geom/occ_utils.py | 6 +++++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/geom/app.py b/src/ifcopenshell-python/ifcopenshell/geom/app.py index d6bab207f1..03d7ac477a 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/app.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/app.py @@ -463,7 +463,6 @@ class application(QtWidgets.QApplication): qtViewer3d.__init__(self, widget) self.ais_to_product = {} self.product_to_ais = {} - self.counter = 0 self.window = widget self.thread = None @@ -488,11 +487,11 @@ class application(QtWidgets.QApplication): ais = display_shape(shape, viewer_handle=v) product = f[shape.data.id] - if USE_OCCT_HANDLE: - ais.GetObject().SetSelectionPriority(self.counter) - self.ais_to_product[self.counter] = product + # Keyed by the AIS object itself (its __eq__/__hash__ track the + # underlying OCCT instance) instead of AIS_InteractiveObject.SetSelectionPriority(), + # which no longer exists on general AIS objects in modern pythonocc-core (#1098). + self.ais_to_product[ais] = product self.product_to_ais[product] = ais - self.counter += 1 QtWidgets.QApplication.processEvents() @@ -573,8 +572,9 @@ class application(QtWidgets.QApplication): v.InitSelected() if v.MoreSelected(): ais = v.SelectedInteractive() - inst = self.ais_to_product[ais.GetObject().SelectionPriority()] - self.instanceSelected.emit(inst) + inst = self.ais_to_product.get(ais) + if inst is not None: + self.instanceSelected.emit(inst) class window(QtWidgets.QMainWindow): diff --git a/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py b/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py index 15a4dfc838..c5ebdf7ad6 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py @@ -225,7 +225,11 @@ def display_shape(shape, clr=None, viewer_handle=None): def set_shape_transparency(ais, t, update_viewer=True): - handle.Context.SetTransparency(ais, t, update_viewer) + # AIS_InteractiveContext.SetTransparency()'s argument count differs across + # pythonocc-core versions (#1037); AIS_InteractiveObject.SetTransparency() is stable. + ais.SetTransparency(t) + if update_viewer: + handle.Context.UpdateCurrentViewer() def get_bounding_box_center(bbox):