diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 68c562c1c0..11b817c77f 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -338,7 +338,7 @@ class UnassignLibraryDeclaration(bpy.types.Operator): ifcopenshell.api.run( "project.unassign_declaration", self.file, - definition=self.file.by_id(self.definition), + definitions=[self.file.by_id(self.definition)], relating_context=self.file.by_type("IfcProjectLibrary")[0], ) element_name = self.props.active_library_element diff --git a/src/ifcopenshell-python/ifcopenshell/api/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/__init__.py index 1d21eaf40f..4249ed96aa 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/__init__.py @@ -120,6 +120,9 @@ ARGUMENTS_DEPRECATION = { "project.assign_declaration": partial( batching_argument_deprecation, prev_argument="definition", new_argument="definitions" ), + "project.unassign_declaration": partial( + batching_argument_deprecation, prev_argument="definition", new_argument="definitions" + ), } diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/unassign_declaration.py b/src/ifcopenshell-python/ifcopenshell/api/project/unassign_declaration.py index 7e93f558fc..47c5194bdd 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/unassign_declaration.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/unassign_declaration.py @@ -22,13 +22,19 @@ import ifcopenshell.util.element class Usecase: - def __init__(self, file, definition=None, relating_context=None): - """Unassigns an object to a project or project library + def __init__( + self, + file: ifcopenshell.file, + definitions: list[ifcopenshell.entity_instance], + relating_context: ifcopenshell.entity_instance, + ): + """Unassigns a list of objects from a project or project library Typically used to remove an asset from a project library. - :param definition: The object you want to undeclare. Typically an asset. - :type definition: ifcopenshell.entity_instance.entity_instance + :param definitions: The list of objects you want to undeclare. + Typically a list of assets. + :type definitions: list[ifcopenshell.entity_instance.entity_instance] :param relating_context: The IfcProject, or more commonly the IfcProjectLibrary that you want the object to no longer be part of. :type relating_context: ifcopenshell.entity_instance.entity_instance @@ -49,25 +55,25 @@ class Usecase: ifcopenshell.api.run("project.assign_declaration", library, definitions=[context], relating_context=root) # Remove the library from our project - ifcopenshell.api.run("project.unassign_declaration", library, definition=context, relating_context=root) + ifcopenshell.api.run("project.unassign_declaration", library, definitions=[context], relating_context=root) """ self.file = file self.settings = { - "definition": definition, + "definitions": definitions, "relating_context": relating_context, } def execute(self): - if not self.settings["definition"].HasContext: - return - rel = self.settings["definition"].HasContext[0] - related_definitions = set(rel.RelatedDefinitions) or set() - related_definitions.remove(self.settings["definition"]) - if len(related_definitions): - rel.RelatedDefinitions = list(related_definitions) - ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) - else: - history = rel.OwnerHistory - self.file.remove(rel) - if history: - ifcopenshell.util.element.remove_deep2(self.file, history) + definitions = set(self.settings["definitions"]) + rels = {rel for obj in definitions if (rel := next(iter(obj.HasContext), None))} + + for rel in rels: + related_definitions = set(rel.RelatedDefinitions) - definitions + if related_definitions: + rel.RelatedDefinitions = list(related_definitions) + ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) + else: + history = rel.OwnerHistory + self.file.remove(rel) + if history: + ifcopenshell.util.element.remove_deep2(self.file, history) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py index a9e6f000eb..b3573db5d5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py @@ -57,7 +57,7 @@ class Usecase: ifcopenshell.api.run( "project.unassign_declaration", self.file, - definition=self.settings["work_schedule"], + definitions=[self.settings["work_schedule"]], relating_context=self.file.by_type("IfcContext")[0], ) rel_aggregates = ifcopenshell.api.run( diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py index 4c0ee504f4..870efa1876 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py @@ -62,7 +62,7 @@ class Usecase: ifcopenshell.api.run( "project.unassign_declaration", self.file, - definition=self.settings["task"], + definitions=[self.settings["task"]], relating_context=self.file.by_type("IfcContext")[0], ) if self.settings["task"].TaskTime: diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py index a630c3f42e..242165c201 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py @@ -50,7 +50,7 @@ class Usecase: ifcopenshell.api.run( "project.unassign_declaration", self.file, - definition=self.settings["work_calendar"], + definitions=[self.settings["work_calendar"]], relating_context=self.file.by_type("IfcContext")[0], ) if self.settings["work_calendar"].Controls: diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py index cb8c638799..905a7b9b8a 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py @@ -50,7 +50,7 @@ class Usecase: ifcopenshell.api.run( "project.unassign_declaration", self.file, - definition=self.settings["work_plan"], + definitions=[self.settings["work_plan"]], relating_context=self.file.by_type("IfcContext")[0], ) history = self.settings["work_plan"].OwnerHistory diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py index 935c9f1832..b8bbcd4616 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py @@ -54,7 +54,7 @@ class Usecase: ifcopenshell.api.run( "project.unassign_declaration", self.file, - definition=self.settings["work_schedule"], + definitions=[self.settings["work_schedule"]], relating_context=self.file.by_type("IfcContext")[0], ) if self.settings["work_schedule"].Declares: diff --git a/src/ifcopenshell-python/test/api/project/test_unassign_declaration.py b/src/ifcopenshell-python/test/api/project/test_unassign_declaration.py new file mode 100644 index 0000000000..60dd46f7ad --- /dev/null +++ b/src/ifcopenshell-python/test/api/project/test_unassign_declaration.py @@ -0,0 +1,82 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 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 +from typing import Union + + +# NOTE: supported only in IFC4+ +class TestUnassignDeclaration(test.bootstrap.IFC4): + def get_context(self, definition: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: + rel = next(iter(definition.HasContext), None) + if rel is not None: + return rel.RelatingContext + + def test_unassigning_a_definition(self): + library = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProjectLibrary") + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element_type2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run( + "project.assign_declaration", self.file, definitions=[element_type, element_type2], relating_context=library + ) + ifcopenshell.api.run( + "project.unassign_declaration", + self.file, + definitions=[element_type, element_type2], + relating_context=library, + ) + assert self.get_context(element_type) == None + assert len(self.file.by_type("IfcRelDeclares")) == 0 + + def test_doing_nothing_if_there_was_no_declaration(self): + library = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProjectLibrary") + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element_type2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run( + "project.unassign_declaration", + self.file, + definitions=[element_type, element_type2], + relating_context=library, + ) + assert self.get_context(element_type) == None + assert self.get_context(element_type2) == None + + def test_updating_the_rel_when_a_reference_is_removed_with_multipled_elements(self): + library = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProjectLibrary") + element_type1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element_type2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element_type3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run( + "project.assign_declaration", self.file, definitions=[element_type1], relating_context=library + ) + rel = self.file.by_type("IfcRelDeclares")[0] + + ifcopenshell.api.run( + "project.assign_declaration", + self.file, + definitions=[element_type2, element_type3], + relating_context=library, + ) + ifcopenshell.api.run( + "project.unassign_declaration", + self.file, + definitions=[element_type1, element_type2], + relating_context=library, + ) + assert rel.RelatedDefinitions == (element_type3,) diff --git a/src/ifcopenshell-python/test/api/test_api.py b/src/ifcopenshell-python/test/api/test_api.py index 2f4db8786f..00d33b8b8a 100644 --- a/src/ifcopenshell-python/test/api/test_api.py +++ b/src/ifcopenshell-python/test/api/test_api.py @@ -23,6 +23,7 @@ import ifcopenshell.util.constraint import ifcopenshell.util.element import ifcopenshell.util.system from datetime import datetime +from typing import Union def deprecation_check(test): @@ -326,3 +327,24 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4): ) assert get_declared_definitions(library) == {element_type} assert len(self.file.by_type("IfcRelDeclares")) == 1 + + @deprecation_check + def test_unassigning_a_definition(self): + def get_context(definition: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: + rel = next(iter(definition.HasContext), None) + if rel is not None: + return rel.RelatingContext + + library = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProjectLibrary") + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run( + "project.assign_declaration", self.file, definitions=[element_type], relating_context=library + ) + ifcopenshell.api.run( + "project.unassign_declaration", + self.file, + definition=element_type, + relating_context=library, + ) + assert get_context(element_type) == None + assert len(self.file.by_type("IfcRelDeclares")) == 0