diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py index fefe450bff..6d07550d7e 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py @@ -283,7 +283,7 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator): if r.is_a("IfcRelAssignsToGroup") if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name ] - tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], product=aggregate) + tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], products=[aggregate]) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/group/operator.py b/src/blenderbim/blenderbim/bim/module/group/operator.py index c7dbdd6c8b..ddb5bb8845 100644 --- a/src/blenderbim/blenderbim/bim/module/group/operator.py +++ b/src/blenderbim/blenderbim/bim/module/group/operator.py @@ -212,7 +212,7 @@ class UnassignGroup(bpy.types.Operator, tool.Ifc.Operator): "group.unassign_group", self.file, **{ - "product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id), + "products": [self.file.by_id(product.BIMObjectProperties.ifc_definition_id)], "group": self.file.by_id(self.group), } ) diff --git a/src/blenderbim/blenderbim/core/drawing.py b/src/blenderbim/blenderbim/core/drawing.py index aeddf1d512..e74865e484 100644 --- a/src/blenderbim/blenderbim/core/drawing.py +++ b/src/blenderbim/blenderbim/core/drawing.py @@ -251,7 +251,7 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati drawing_tool.copy_representation(drawing, new_drawing) drawing_tool.set_name(new_drawing, drawing_name) group = drawing_tool.get_drawing_group(new_drawing) - ifc.run("group.unassign_group", group=group, product=new_drawing) + ifc.run("group.unassign_group", group=group, products=[new_drawing]) new_group = ifc.run("group.add_group") ifc.run("group.edit_group", group=new_group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"}) ifc.run("group.assign_group", group=new_group, products=[new_drawing]) @@ -261,7 +261,7 @@ def duplicate_drawing(ifc, drawing_tool, drawing=None, should_duplicate_annotati continue new_annotation = ifc.run("root.copy_class", product=annotation) drawing_tool.copy_representation(annotation, new_annotation) - ifc.run("group.unassign_group", group=group, product=new_annotation) + ifc.run("group.unassign_group", group=group, products=[new_annotation]) ifc.run("group.assign_group", group=new_group, products=[new_annotation]) old_reference = drawing_tool.get_drawing_document(new_drawing) diff --git a/src/blenderbim/test/core/test_drawing.py b/src/blenderbim/test/core/test_drawing.py index 55c6dfc3f8..b07d0f8368 100644 --- a/src/blenderbim/test/core/test_drawing.py +++ b/src/blenderbim/test/core/test_drawing.py @@ -378,7 +378,7 @@ class TestDuplicateDrawing: drawing.copy_representation("drawing", "new_drawing").should_be_called() drawing.set_name("new_drawing", "unique_name").should_be_called() drawing.get_drawing_group("new_drawing").should_be_called().will_return("group") - ifc.run("group.unassign_group", group="group", product="new_drawing").should_be_called() + ifc.run("group.unassign_group", group="group", products=["new_drawing"]).should_be_called() ifc.run("group.add_group").should_be_called().will_return("new_group") ifc.run( "group.edit_group", group="new_group", attributes={"Name": "unique_name", "ObjectType": "DRAWING"} @@ -387,7 +387,7 @@ class TestDuplicateDrawing: drawing.get_group_elements("group").should_be_called().will_return(["drawing", "annotation"]) ifc.run("root.copy_class", product="annotation").should_be_called().will_return("new_annotation") drawing.copy_representation("annotation", "new_annotation").should_be_called() - ifc.run("group.unassign_group", group="group", product="new_annotation").should_be_called() + ifc.run("group.unassign_group", group="group", products=["new_annotation"]).should_be_called() ifc.run("group.assign_group", group="new_group", products=["new_annotation"]).should_be_called() drawing.get_drawing_document("new_drawing").should_be_called().will_return("old_reference") diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py b/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py index 2c4684b59c..3a212c33c7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py +++ b/src/ifcopenshell-python/ifcopenshell/api/group/assign_group.py @@ -63,8 +63,9 @@ class Usecase: ) rel = self.settings["group"].IsGroupedBy[0] related_objects = set(rel.RelatedObjects) or set() - for obj in self.settings["products"]: - related_objects.add(obj) - rel.RelatedObjects = list(related_objects) + products = set(self.settings["products"]) + if products.issubset(related_objects): + return rel + rel.RelatedObjects = list(related_objects | products) ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) return rel diff --git a/src/ifcopenshell-python/ifcopenshell/api/group/unassign_group.py b/src/ifcopenshell-python/ifcopenshell/api/group/unassign_group.py index 161b4b5fb5..57d8d88556 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/group/unassign_group.py +++ b/src/ifcopenshell-python/ifcopenshell/api/group/unassign_group.py @@ -22,13 +22,13 @@ import ifcopenshell.util.element class Usecase: - def __init__(self, file, product=None, group=None): - """Unassigns a product from a group + def __init__(self, file: ifcopenshell.file, products: list[ifcopenshell.entity_instance], group: ifcopenshell.entity_instance): + """Unassigns products from a group If the product isn't assigned to the group, nothing will happen. - :param product: A IfcProduct element to unassign from the group - :type product: ifcopenshell.entity_instance.entity_instance + :param products: A list of IfcProduct elements to unassign from the group + :type products: list[ifcopenshell.entity_instance.entity_instance] :param group: The IfcGroup to unassign from :type group: ifcopenshell.entity_instance.entity_instance :return: None @@ -43,21 +43,22 @@ class Usecase: ifcopenshell.api.run("group.assign_group", model, products=furniture, group=group) bad_furniture = furniture[0] - ifcopenshell.api.run("group.unassign_group", model, product=bad_furniture, group=group) + ifcopenshell.api.run("group.unassign_group", model, products=[bad_furniture], group=group) """ self.file = file self.settings = { - "product": product, + "products": products, "group": group, } - def execute(self): + def execute(self) -> None: if not self.settings["group"].IsGroupedBy: return rel = self.settings["group"].IsGroupedBy[0] related_objects = set(rel.RelatedObjects) or set() - related_objects.remove(self.settings["product"]) - if len(related_objects): + products = set(self.settings["products"]) + related_objects -= products + if related_objects: rel.RelatedObjects = list(related_objects) ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) else: diff --git a/src/ifcopenshell-python/test/api/group/test_assign_group.py b/src/ifcopenshell-python/test/api/group/test_assign_group.py new file mode 100644 index 0000000000..88870b4c15 --- /dev/null +++ b/src/ifcopenshell-python/test/api/group/test_assign_group.py @@ -0,0 +1,45 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2022 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import test.bootstrap +import ifcopenshell.api + + +class TestAssignGroup(test.bootstrap.IFC4): + def test_assign_group_for_multiple_elements(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcPump") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcPump") + group = ifcopenshell.api.run("group.add_group", self.file) + + # assign group for multiple elements + ifcopenshell.api.run("group.assign_group", self.file, products=[element, element2], group=group) + assert len(rels := self.file.by_type("IfcRelAssignsToGroup")) == 1 + rel = rels[0] + assert rel.RelatingGroup == group + assert rel.RelatedObjects == (element, element2) + + def test_reuse_existing_relationship(self): + self.test_assign_group_for_multiple_elements() + rel = self.file.by_type("IfcRelAssignsToGroup")[0] + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcPump") + group = self.file.by_type("IfcGroup")[0] + ifcopenshell.api.run("group.assign_group", self.file, products=[element], group=group) + + assert len(self.file.by_type("IfcRelAssignsToGroup")) == 1 + assert rel.RelatingGroup == group + assert set(rel.RelatedObjects) == set(self.file.by_type("IfcPump")) diff --git a/src/ifcopenshell-python/test/api/group/test_unassign_group.py b/src/ifcopenshell-python/test/api/group/test_unassign_group.py new file mode 100644 index 0000000000..edc2ecbca0 --- /dev/null +++ b/src/ifcopenshell-python/test/api/group/test_unassign_group.py @@ -0,0 +1,43 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2022 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import test.bootstrap +import ifcopenshell.api + + +class TestAssignGroup(test.bootstrap.IFC4): + def test_unassignment(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcPump") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcPump") + element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcPump") + group = ifcopenshell.api.run("group.add_group", self.file) + ifcopenshell.api.run("group.assign_group", self.file, products=[element, element2, element3], group=group) + ifcopenshell.api.run("group.unassign_group", self.file, products=[element2, element3], group=group) + + assert len(rels := self.file.by_type("IfcRelAssignsToGroup")) == 1 + rel = rels[0] + assert rel.RelatingGroup == group + assert rel.RelatedObjects == (element,) + + def test_remove_relationship_unassigning_last_element(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcPump") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcPump") + group = ifcopenshell.api.run("group.add_group", self.file) + ifcopenshell.api.run("group.assign_group", self.file, products=[element, element2], group=group) + ifcopenshell.api.run("group.unassign_group", self.file, products=[element, element2], group=group) + assert len(self.file.by_type("IfcRelAssignsToGroup")) == 0