From 031554223743112d8ef51b0c09c081902f9bfde7 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 2 Apr 2025 18:24:56 +0500 Subject: [PATCH] geometry.remove_representation_item - add element argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use explicit element argument instead of props.representation_obj as representation_obj may not be there (e.g. removing rep items without item mode) or some other object might be in item mode and then booleans won't be unmarked. Found the issue because of the failing test 🥳 --- .../bonsai/bim/module/geometry/operator.py | 5 ++-- src/bonsai/bonsai/tool/geometry.py | 24 ++++++++++++------- src/bonsai/bonsai/tool/model.py | 8 +++++-- src/bonsai/test/tool/test_geometry.py | 5 ++-- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 83e6bff999..3062aa332c 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -2509,11 +2509,12 @@ class RemoveRepresentationItem(bpy.types.Operator, tool.Ifc.Operator): return True def _execute(self, context): - obj = tool.Geometry.get_active_or_representation_obj() + assert (obj := tool.Geometry.get_active_or_representation_obj()) + assert (element := tool.Ifc.get_entity(obj)) ifc_file = tool.Ifc.get() representation_item = ifc_file.by_id(self.representation_item_id) - tool.Geometry.remove_representation_item(representation_item) + tool.Geometry.remove_representation_item(representation_item, element) tool.Geometry.reload_representation(obj) # reload items ui diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index 4fe8116b9c..e97fd5c869 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -202,6 +202,7 @@ class Geometry(bonsai.core.tool.Geometry): @classmethod def delete_ifc_item(cls, obj: bpy.types.Object) -> None: + """Delete IfcRepresentationItem's Object.""" props = tool.Geometry.get_geometry_props() if len(props.item_objs) == 1: return @@ -212,7 +213,9 @@ class Geometry(bonsai.core.tool.Geometry): mesh = obj.data assert isinstance(mesh, bpy.types.Mesh) item = tool.Ifc.get().by_id(tool.Geometry.get_mesh_props(mesh).ifc_definition_id) - cls.remove_representation_item(item) + rep_obj = props.representation_obj + assert (rep_obj := props.representation_obj) and (rep_element := tool.Ifc.get_entity(rep_obj)) + cls.remove_representation_item(item, rep_element) cls.reload_representation(props.representation_obj) bpy.data.objects.remove(obj) @@ -1344,12 +1347,19 @@ class Geometry(bonsai.core.tool.Geometry): ) @classmethod - def remove_representation_item(cls, representation_item: ifcopenshell.entity_instance) -> None: + def remove_representation_item( + cls, representation_item: ifcopenshell.entity_instance, element: ifcopenshell.entity_instance + ) -> None: + """Remove IfcRepresentationItem. + + :param representation_item: item to remove. + :param element: item's element. Is used to unmark manual booleans. + """ # NOTE: we assume it's not the last representation item # otherwise we probably would need to remove representation too # NOTE: a lot of shared code with `geometry.remove_representation` ifc_file = tool.Ifc.get() - shape_aspects = [] + shape_aspects: list[ifcopenshell.entity_instance] = [] consider_inverses = [] styled_item, colour, texture, layer = None, None, None, None @@ -1366,7 +1376,7 @@ class Geometry(bonsai.core.tool.Geometry): [consider_inverses.append(texture := t) for t in getattr(representation_item, "HasTextures", [])] representation = None - boolean_results_to_remove = set() + boolean_results_to_remove: set[ifcopenshell.entity_instance] = set() for inverse in ifc_file.get_inverse(representation_item): if inverse.is_a("IfcShapeRepresentation"): if inverse.OfShapeAspect: @@ -1409,11 +1419,9 @@ class Geometry(bonsai.core.tool.Geometry): also_consider = list(consider_inverses) ifcopenshell.util.element.remove_deep2(ifc_file, representation_item, also_consider=also_consider) - props = tool.Geometry.get_geometry_props() - rep_element = tool.Ifc.get_entity(props.representation_obj) - tool.Model.unmark_manual_booleans(rep_element, [b.id() for b in boolean_results_to_remove]) + tool.Model.unmark_manual_booleans(element, [b.id() for b in boolean_results_to_remove]) for boolean_result in boolean_results_to_remove: - cls.remove_representation_item(boolean_result) + cls.remove_representation_item(boolean_result, element) @classmethod def create_shape_aspect( diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index b31dfacfb9..a3dcc47f02 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -687,8 +687,12 @@ class Model(bonsai.core.tool.Model): @classmethod def unmark_manual_booleans(cls, element: ifcopenshell.entity_instance, boolean_ids: list[int]) -> None: - # NOTE: we use use boolean_ids instead of boolean entities - # so it will be possible to unmark manual booleans after they already was deleted + """Remove boolean ids from ``element``'s 'BBIM_Boolean' pset. + + :param boolean_ids: List of boolean ids to remove. + Ids are used instead of entities to make it possible to unmark already removed booleans. + Provided ids may not be marked as manual booleans previously. + """ pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean") if not pset: return diff --git a/src/bonsai/test/tool/test_geometry.py b/src/bonsai/test/tool/test_geometry.py index 85a0a4c79d..a392cd2ef3 100644 --- a/src/bonsai/test/tool/test_geometry.py +++ b/src/bonsai/test/tool/test_geometry.py @@ -20,6 +20,7 @@ import bpy import math import numpy as np import ifcopenshell +import ifcopenshell.api.geometry import ifcopenshell.api.type import bonsai.core.tool import bonsai.tool as tool @@ -606,13 +607,13 @@ class TestRemoveRepresentationItem(NewFile): items = [ifc.createIfcExtrudedAreaSolid(), ifc.createIfcExtrudedAreaSolid()] representation = ifc.createIfcShapeRepresentation(Items=items, ContextOfItems=context) - tool.Ifc.run("geometry.assign_representation", product=element, representation=representation) + ifcopenshell.api.geometry.assign_representation(ifc, product=element, representation=representation) product_shape = element.Representation shape_aspect = subject.create_shape_aspect(product_shape, representation, items[:1], None) shape_aspect_id = shape_aspect.id() - subject.remove_representation_item(items[0]) + subject.remove_representation_item(items[0], element) assert tool.Ifc.get_entity_by_id(shape_aspect_id) is None assert set(representation.Items) == {items[1]}