diff --git a/src/bonsai/bonsai/core/drawing.py b/src/bonsai/bonsai/core/drawing.py index 88c12295fa..f30c49974d 100644 --- a/src/bonsai/bonsai/core/drawing.py +++ b/src/bonsai/bonsai/core/drawing.py @@ -61,20 +61,17 @@ def edit_assigned_product( element = ifc.get_entity(obj) assert element - # Get ALL existing products, not just one - existing_products = [] - if hasattr(element, "HasAssignments"): - for rel in element.HasAssignments: - if rel.is_a("IfcRelAssignsToProduct"): - existing_products.append(rel.RelatingProduct) + # Temporarily accomodate existing bug and check for multiple products. + existing_products = drawing.get_assigned_product_workaround(element) + if existing_products != [product]: + + if product in existing_products: + assert product is not None + existing_products.remove(product) - # Only proceed if the assignment is different - if not (len(existing_products) == 1 and existing_products[0] == product): - # Unassign from ALL existing products for existing_product in existing_products: ifc.run("drawing.unassign_product", relating_product=existing_product, related_object=element) - # Assign to the new product if product: ifc.run("drawing.assign_product", relating_product=product, related_object=element) diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index acfce461df..830062d40a 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -343,6 +343,7 @@ class Drawing: def get_annotation_context(cls, target_view, object_type=None): pass def get_annotation_representation(cls, element_type): pass def get_assigned_product(cls, element): pass + def get_assigned_product_workaround(cls, element): pass def get_body_context(cls): pass def get_default_drawing_path(cls, name): pass def get_default_drawing_resource_path(cls, resource): pass diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index 1de010694a..3a19be6ae5 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -893,6 +893,33 @@ class Drawing(bonsai.core.tool.Drawing): return axis return product + @classmethod + def get_assigned_product_workaround( + cls, element: ifcopenshell.entity_instance + ) -> list[ifcopenshell.entity_instance]: + """Get all products assigned to the element. + + A workaround allowing to unassign accumulated products until we properly resolve #4014. + In theory annotations should have more than one product assigned, + but there's still undefined bug causing that in some cases. + """ + + assigned_products: list[ifcopenshell.entity_instance] = [] + for rel in element.HasAssignments: + if not rel.is_a("IfcRelAssignsToProduct"): + continue + assigned_products.append(rel.RelatingProduct) + + if len(assigned_products) > 1: + print( + f"WARNING. Detected multiple assigned products ({len(assigned_products)}) for annotation '{element}'." + "\nIf you can reproduce this, please report it to Bonsai developers " + "at https://github.com/IfcOpenShell/IfcOpenShell/issues/4014." + "\nAssigned products:\n" + "\n".join([str(p) for p in assigned_products]) + ) + + return assigned_products + @classmethod def import_annotations_in_group(cls, group: ifcopenshell.entity_instance) -> None: elements = set( diff --git a/src/bonsai/test/core/test_drawing.py b/src/bonsai/test/core/test_drawing.py index 782d4750fe..957bd79f79 100644 --- a/src/bonsai/test/core/test_drawing.py +++ b/src/bonsai/test/core/test_drawing.py @@ -58,7 +58,7 @@ class TestDisableEditingAssignedProduct: class TestEditAssignedProduct: def test_text_annotation(self, ifc, drawing): ifc.get_entity("obj").should_be_called().will_return("element") - drawing.get_assigned_product("element").should_be_called().will_return("existing_product") + drawing.get_assigned_product_workaround("element").should_be_called().will_return(["existing_product"]) ifc.run( "drawing.unassign_product", relating_product="existing_product", related_object="element" ).should_be_called() @@ -68,7 +68,7 @@ class TestEditAssignedProduct: def test_non_text_annotation(self, ifc, drawing): ifc.get_entity("obj").should_be_called().will_return("element") - drawing.get_assigned_product("element").should_be_called().will_return("existing_product") + drawing.get_assigned_product_workaround("element").should_be_called().will_return(["existing_product"]) ifc.run( "drawing.unassign_product", relating_product="existing_product", related_object="element" ).should_be_called()