mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 02:07:36 +00:00
Fix crash after undo of assign_class on macOS (#7419)
After assigning an IFC class and undoing, msgbus subscriptions registered with the old Python object wrapper survived (PERSISTENT flag) but could not be cleared because: (1) rollback_link_element looked up objects by their post-link name which no longer exists after undo, and (2) the per-object clear_by_owner calls in rebuild_element_maps used new Python wrappers that didn't match the old subscription owners. Fix by using a dedicated stable object (object_subscription_owner) as the msgbus owner for all per-object subscriptions, allowing rebuild_element_maps to clear all stale subscriptions in one call regardless of Python wrapper identity changes during undo/redo. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,16 +45,19 @@ from bonsai.bim.module.nest.decorator import NestDecorator
|
|||||||
|
|
||||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||||
global_subscription_owner = object()
|
global_subscription_owner = object()
|
||||||
|
# Separate owner for per-object msgbus subscriptions (name, active_material_index).
|
||||||
|
# Using a dedicated owner allows clearing all per-object subscriptions at once
|
||||||
|
# during undo/redo without affecting other global subscriptions.
|
||||||
|
object_subscription_owner = object()
|
||||||
|
|
||||||
|
|
||||||
def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -> None:
|
def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -> None:
|
||||||
try:
|
try:
|
||||||
obj.name
|
obj.name
|
||||||
except:
|
except:
|
||||||
# The object is invalid but somehow still has a callback. Clear all
|
# The object is invalid but somehow still has a callback.
|
||||||
# msgbus subscriptions to prevent useless further triggers.
|
# This can occur during undo/redo when the Python wrapper is stale.
|
||||||
bpy.msgbus.clear_by_owner(obj)
|
return
|
||||||
return # In case the object RNA is gone during an undo / redo operation
|
|
||||||
# Blender names are up to 63 UTF-8 bytes
|
# Blender names are up to 63 UTF-8 bytes
|
||||||
if len(bytes(obj.name, "utf-8")) >= 63:
|
if len(bytes(obj.name, "utf-8")) >= 63:
|
||||||
return
|
return
|
||||||
@@ -189,7 +192,7 @@ def subscribe_to(obj: bpy.types.ID, data_path: str, callback: Callable[[bpy.type
|
|||||||
return
|
return
|
||||||
bpy.msgbus.subscribe_rna(
|
bpy.msgbus.subscribe_rna(
|
||||||
key=subscribe_to,
|
key=subscribe_to,
|
||||||
owner=obj,
|
owner=object_subscription_owner,
|
||||||
args=(
|
args=(
|
||||||
obj,
|
obj,
|
||||||
data_path,
|
data_path,
|
||||||
|
|||||||
@@ -316,11 +316,8 @@ class IfcStore:
|
|||||||
del IfcStore.id_map[data["id"]]
|
del IfcStore.id_map[data["id"]]
|
||||||
if "guid" in data:
|
if "guid" in data:
|
||||||
del IfcStore.guid_map[data["guid"]]
|
del IfcStore.guid_map[data["guid"]]
|
||||||
obj = IfcStore.get_object_by_name(data["obj"])
|
# Note: msgbus subscriptions are cleared globally during
|
||||||
if obj is None:
|
# rebuild_element_maps which runs after every undo/redo.
|
||||||
# obj was just created during this step and didn't existed before.
|
|
||||||
return
|
|
||||||
bpy.msgbus.clear_by_owner(obj)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def commit_link_element(data: OperationData) -> None:
|
def commit_link_element(data: OperationData) -> None:
|
||||||
@@ -367,10 +364,8 @@ class IfcStore:
|
|||||||
del IfcStore.id_map[data["id"]]
|
del IfcStore.id_map[data["id"]]
|
||||||
if "guid" in data:
|
if "guid" in data:
|
||||||
del IfcStore.guid_map[data["guid"]]
|
del IfcStore.guid_map[data["guid"]]
|
||||||
obj = IfcStore.get_object_by_name(data["obj"])
|
# Note: msgbus subscriptions are cleared globally during
|
||||||
# obj might be removed after unlink.
|
# rebuild_element_maps which runs after every undo/redo.
|
||||||
if not obj:
|
|
||||||
bpy.msgbus.clear_by_owner(obj)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unlink_element(
|
def unlink_element(
|
||||||
|
|||||||
@@ -197,12 +197,16 @@ class Ifc(bonsai.core.tool.Ifc):
|
|||||||
if not cls.get():
|
if not cls.get():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Clear all per-object msgbus subscriptions at once using the dedicated
|
||||||
|
# owner. After undo/redo, per-object Python wrappers have new
|
||||||
|
# identities so clearing by individual obj would miss stale
|
||||||
|
# subscriptions registered with the old wrappers.
|
||||||
|
bpy.msgbus.clear_by_owner(bonsai.bim.handler.object_subscription_owner)
|
||||||
|
|
||||||
for obj in bpy.data.objects:
|
for obj in bpy.data.objects:
|
||||||
if obj.library:
|
if obj.library:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
bpy.msgbus.clear_by_owner(obj)
|
|
||||||
|
|
||||||
element = cls.get_entity(obj)
|
element = cls.get_entity(obj)
|
||||||
if not element:
|
if not element:
|
||||||
continue
|
continue
|
||||||
@@ -217,8 +221,6 @@ class Ifc(bonsai.core.tool.Ifc):
|
|||||||
if obj.library:
|
if obj.library:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
bpy.msgbus.clear_by_owner(obj)
|
|
||||||
|
|
||||||
style = cls.get_entity(obj)
|
style = cls.get_entity(obj)
|
||||||
if not style:
|
if not style:
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user