@@ -3463,6 +3463,27 @@ def _execute(self, context):
34633463 props .is_editing = False
34643464
34653465
3466+ def resolve_representation_item (
3467+ operator : bpy .types .Operator , item_id : int
3468+ ) -> Union [ifcopenshell .entity_instance , None ]:
3469+ """Resolve an item id from the representation items UI list.
3470+
3471+ ``BIMObjectGeometryProperties.items`` is stored in the .blend, so its ids can
3472+ outlive the instances they point to - reopening a .blend next to its .ifc
3473+ replays a list that was built against a different state of the file. Refresh
3474+ the list and report instead of letting ``by_id`` raise
3475+ ``RuntimeError: Instance #N not found``.
3476+ """
3477+ if item := tool .Ifc .get_entity_by_id (item_id ):
3478+ return item
3479+ operator .report (
3480+ {"ERROR" }, f"Representation item #{ item_id } no longer exists, items list has been refreshed - try again."
3481+ )
3482+ bpy .ops .bim .disable_editing_representation_items ()
3483+ bpy .ops .bim .enable_editing_representation_items ()
3484+ return None
3485+
3486+
34663487class RemoveRepresentationItem (bpy .types .Operator , tool .Ifc .Operator ):
34673488 bl_idname = "bim.remove_representation_item"
34683489 bl_label = "Remove Representation Item"
@@ -3486,9 +3507,9 @@ def poll(cls, context):
34863507 def _execute (self , context ):
34873508 assert (obj := tool .Geometry .get_active_or_representation_obj ())
34883509 assert (element := tool .Ifc .get_entity (obj ))
3489- ifc_file = tool .Ifc .get ()
34903510
3491- representation_item = ifc_file .by_id (self .representation_item_id )
3511+ if not (representation_item := resolve_representation_item (self , self .representation_item_id )):
3512+ return {"CANCELLED" }
34923513 tool .Geometry .remove_representation_item (representation_item , element )
34933514 tool .Geometry .reload_representation (obj )
34943515
@@ -3514,7 +3535,8 @@ def execute(self, context):
35143535 obj = tool .Geometry .get_active_or_representation_obj ()
35153536 obj_props = tool .Geometry .get_object_geometry_props (obj )
35163537 assert obj_props .active_item
3517- item = tool .Ifc .get ().by_id (obj_props .active_item .ifc_definition_id )
3538+ if not (item := resolve_representation_item (self , obj_props .active_item .ifc_definition_id )):
3539+ return {"CANCELLED" }
35183540 item_ids = self .get_nested_item_ids (item )
35193541
35203542 props = tool .Geometry .get_geometry_props ()
@@ -3577,13 +3599,13 @@ def _execute(self, context):
35773599 obj = tool .Geometry .get_active_or_representation_obj ()
35783600 assert obj
35793601 props = tool .Geometry .get_object_geometry_props (obj )
3580- props .is_editing_item_style = True
3581- ifc_file = tool .Ifc .get ()
35823602
35833603 # set dropdown to currently active style
35843604 assert (active_item := props .active_item )
35853605 representation_item_id = active_item .ifc_definition_id
3586- representation_item = ifc_file .by_id (representation_item_id )
3606+ if not (representation_item := resolve_representation_item (self , representation_item_id )):
3607+ return {"CANCELLED" }
3608+ props .is_editing_item_style = True
35873609 style = tool .Style .get_representation_item_style (representation_item )
35883610 if style :
35893611 props .representation_item_style = str (style .id ())
@@ -3609,7 +3631,8 @@ def _execute(self, context):
36093631
36103632 assert (active_item := props .active_item )
36113633 representation_item_id = active_item .ifc_definition_id
3612- representation_item = ifc_file .by_id (representation_item_id )
3634+ if not (representation_item := resolve_representation_item (self , representation_item_id )):
3635+ return {"CANCELLED" }
36133636
36143637 tool .Style .assign_style_to_representation_item (representation_item , surface_style )
36153638 tool .Geometry .reload_representation (obj )
@@ -3647,9 +3670,7 @@ def _execute(self, context):
36473670
36483671 # Get active representation item
36493672 active_representation_item_id = active_props .active_item .ifc_definition_id
3650- active_representation_item = tool .Ifc .get_entity_by_id (active_representation_item_id )
3651- if not active_representation_item :
3652- self .report ({"ERROR" }, f"Couldn't find representation item by id { active_representation_item_id } ." )
3673+ if not (active_representation_item := resolve_representation_item (self , active_representation_item_id )):
36533674 return {"CANCELLED" }
36543675
36553676 # Retrieve styles applied to the active representation item
@@ -3732,7 +3753,8 @@ def _execute(self, context):
37323753
37333754 assert props .active_item
37343755 representation_item_id = props .active_item .ifc_definition_id
3735- representation_item = ifc_file .by_id (representation_item_id )
3756+ if not (representation_item := resolve_representation_item (self , representation_item_id )):
3757+ return {"CANCELLED" }
37363758
37373759 if props .representation_item_shape_aspect == "NEW" :
37383760 active_representation = tool .Geometry .get_active_representation (obj )
@@ -3803,7 +3825,8 @@ def _execute(self, context):
38033825
38043826 assert props .active_item
38053827 representation_item_id = props .active_item .ifc_definition_id
3806- representation_item = ifc_file .by_id (representation_item_id )
3828+ if not (representation_item := resolve_representation_item (self , representation_item_id )):
3829+ return {"CANCELLED" }
38073830 shape_aspect = ifc_file .by_id (props .active_item .shape_aspect_id )
38083831
38093832 # unassign items before removing items as removing items
@@ -4406,7 +4429,8 @@ def _execute(self, context):
44064429 new_layer = ifc_file .by_id (int (props .representation_item_layer ))
44074430
44084431 assert props .active_item
4409- item = ifc_file .by_id (int (props .active_item .ifc_definition_id ))
4432+ if not (item := resolve_representation_item (self , int (props .active_item .ifc_definition_id ))):
4433+ return {"CANCELLED" }
44104434 item_layer = next (iter (item .LayerAssignment ), None )
44114435
44124436 # We assume just 1 layer can be assigned to representation item.
@@ -4434,7 +4458,8 @@ def _execute(self, context):
44344458 obj = context .active_object
44354459 assert obj
44364460 props = tool .Geometry .get_object_geometry_props (obj )
4437- item = ifc_file .by_id (props .active_item .ifc_definition_id )
4461+ if not (item := resolve_representation_item (self , props .active_item .ifc_definition_id )):
4462+ return {"CANCELLED" }
44384463 layer = item .LayerAssignment [0 ] # If there is no layer, then button is not visible in UI.
44394464 ifcopenshell .api .layer .unassign_layer (ifc_file , [item ], layer )
44404465 bpy .ops .bim .enable_editing_representation_items ()
0 commit comments