From 49e7abb4dca5d5493e1dfe0b90628c5e95aa6d12 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 11 Apr 2024 15:52:33 +0500 Subject: [PATCH] type.unassign_type - support batching #4474 --- .../blenderbim/bim/module/type/operator.py | 2 +- .../ifcopenshell/api/__init__.py | 3 + .../ifcopenshell/api/root/remove_product.py | 5 +- .../ifcopenshell/api/type/unassign_type.py | 47 ++++++++------- src/ifcopenshell-python/test/api/test_api.py | 8 +++ .../test/api/type/test_unassign_type.py | 60 +++++++++++++++++++ 6 files changed, 99 insertions(+), 26 deletions(-) create mode 100644 src/ifcopenshell-python/test/api/type/test_unassign_type.py diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py index 254e27ada9..76f58d42db 100644 --- a/src/blenderbim/blenderbim/bim/module/type/operator.py +++ b/src/blenderbim/blenderbim/bim/module/type/operator.py @@ -78,7 +78,7 @@ class UnassignType(bpy.types.Operator): element = tool.Ifc.get_entity(obj) if not element or element.is_a("IfcElementType"): continue - ifcopenshell.api.run("type.unassign_type", self.file, related_object=element) + ifcopenshell.api.run("type.unassign_type", self.file, related_objects=[element]) active_representation = tool.Geometry.get_active_representation(obj) active_context = active_representation.ContextOfItems diff --git a/src/ifcopenshell-python/ifcopenshell/api/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/__init__.py index ccea070c98..04fdfde724 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/__init__.py @@ -76,6 +76,9 @@ ARGUMENTS_DEPRECATION = { "type.assign_type": partial( batching_argument_deprecation, prev_argument="related_object", new_argument="related_objects" ), + "type.unassign_type": partial( + batching_argument_deprecation, prev_argument="related_object", new_argument="related_objects" + ), } diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py index a7ceb64f4e..43faafcf23 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py @@ -129,10 +129,9 @@ class Usecase: ifcopenshell.api.run("material.unassign_material", self.file, product=self.settings["product"]) elif inverse.is_a("IfcRelDefinesByType"): if inverse.RelatingType == self.settings["product"]: - for related_object in inverse.RelatedObjects: - ifcopenshell.api.run("type.unassign_type", self.file, related_object=related_object) + ifcopenshell.api.run("type.unassign_type", self.file, related_objects=inverse.RelatedObjects) else: - ifcopenshell.api.run("type.unassign_type", self.file, related_object=self.settings["product"]) + ifcopenshell.api.run("type.unassign_type", self.file, related_objects=[self.settings["product"]]) elif inverse.is_a("IfcRelSpaceBoundary"): ifcopenshell.api.run("boundary.remove_boundary", self.file, boundary=inverse) elif inverse.is_a("IfcRelFillsElement"): diff --git a/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py b/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py index dbafade861..c376a58971 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py +++ b/src/ifcopenshell-python/ifcopenshell/api/type/unassign_type.py @@ -18,14 +18,18 @@ import ifcopenshell import ifcopenshell.api +import ifcopenshell.util.element class Usecase: - def __init__(self, file, related_object=None): - """Unassigns a type of an occurrence + def __init__(self, file: ifcopenshell.file, related_objects: list[ifcopenshell.entity_instance]): + """Unassigns a type from occurrences - :param related_object: The IfcElement occurrence. - :type related_object: ifcopenshell.entity_instance.entity_instance + Note that unassigning a type doesn't automatically remove mapped representations + and material usages associated with the previously assigned type. + + :param related_objects: List of IfcElement occurrences. + :type related_objects: list[ifcopenshell.entity_instance.entity_instance] :return: None :rtype: None @@ -45,31 +49,30 @@ class Usecase: ifcopenshell.api.run("type.assign_type", model, related_objects=[furniture], relating_type=furniture_type) # Change our mind. Maybe it's a different type? - ifcopenshell.api.run("type.unassign_type", model, related_object=furniture) + ifcopenshell.api.run("type.unassign_type", model, related_objects=[furniture]) """ self.file = file - self.settings = {"related_object": related_object} + self.settings = {"related_objects": related_objects} + + def execute(self) -> None: + related_objects = set(self.settings["related_objects"]) - def execute(self): if self.file.schema == "IFC2X3": - is_typed_by = None - is_defined_by = self.settings["related_object"].IsDefinedBy - for rel in is_defined_by: - if rel.is_a("IfcRelDefinesByType"): - is_typed_by = rel + rels = set( + rel + for object in related_objects + if (rel := next((rel for rel in object.IsDefinedBy if rel.is_a("IfcRelDefinesByType")), None)) + ) else: - is_typed_by = self.settings["related_object"].IsTypedBy - if is_typed_by: - is_typed_by = is_typed_by[0] + rels = set(rel for object in related_objects if (rel := next((rel for rel in object.IsTypedBy), None))) - if is_typed_by: - related_objects = list(is_typed_by.RelatedObjects) - related_objects.remove(self.settings["related_object"]) + for rel in rels: + related_objects = set(rel.RelatedObjects) - related_objects if related_objects: - is_typed_by.RelatedObjects = related_objects - ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": is_typed_by}) + rel.RelatedObjects = list(related_objects) + ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) else: - history = is_typed_by.OwnerHistory - self.file.remove(is_typed_by) + history = rel.OwnerHistory + self.file.remove(rel) if history: ifcopenshell.util.element.remove_deep2(self.file, history) diff --git a/src/ifcopenshell-python/test/api/test_api.py b/src/ifcopenshell-python/test/api/test_api.py index c19c378427..8a33238806 100644 --- a/src/ifcopenshell-python/test/api/test_api.py +++ b/src/ifcopenshell-python/test/api/test_api.py @@ -131,3 +131,11 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4): rel = ifcopenshell.api.run("type.assign_type", self.file, related_object=element1, relating_type=element_type) assert ifcopenshell.util.element.get_type(element1) == element_type assert rel.is_a("IfcRelDefinesByType") + + @deprecation_check + def test_unassigning_a_type(self): + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=element_type) + ifcopenshell.api.run("type.unassign_type", self.file, related_object=element) + assert ifcopenshell.util.element.get_type(element) is None diff --git a/src/ifcopenshell-python/test/api/type/test_unassign_type.py b/src/ifcopenshell-python/test/api/type/test_unassign_type.py new file mode 100644 index 0000000000..adab2387f9 --- /dev/null +++ b/src/ifcopenshell-python/test/api/type/test_unassign_type.py @@ -0,0 +1,60 @@ +# 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 pytest +import test.bootstrap +import ifcopenshell.api +import ifcopenshell.util.element + + +class TestUnassignType(test.bootstrap.IFC4): + def test_unassigning_a_type(self): + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run( + "type.assign_type", self.file, related_objects=[element1, element2], relating_type=element_type + ) + ifcopenshell.api.run("type.unassign_type", self.file, related_objects=[element1, element2]) + assert ifcopenshell.util.element.get_type(element1) is None + assert ifcopenshell.util.element.get_type(element2) is None + + def test_the_rel_is_kept_if_there_are_more_typed_elements(self): + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element1], relating_type=element_type) + ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element2], relating_type=element_type) + ifcopenshell.api.run("type.unassign_type", self.file, related_objects=[element1]) + assert len(self.file.by_type("IfcRelDefinesByType")) == 1 + + def test_the_rel_is_purged_if_there_are_no_more_typed_elements(self): + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + context = self.file.createIfcGeometricRepresentationContext() + rep = self.file.createIfcShapeRepresentation(ContextOfItems=context) + ifcopenshell.api.run("geometry.assign_representation", self.file, product=element_type, representation=rep) + ifcopenshell.api.run("material.assign_material", self.file, product=element_type, type="IfcMaterialLayerSet") + + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=element_type) + ifcopenshell.api.run("type.unassign_type", self.file, related_objects=[element]) + assert len(self.file.by_type("IfcRelDefinesByType")) == 0 + + +class TestUnassignTypeIFC2X3(test.bootstrap.IFC2X3, TestUnassignType): + pass