From 33001e6c6d6d2ddc4b0713b4e10971edc5b42310 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Sat, 7 Sep 2024 11:09:47 +0500 Subject: [PATCH] profile.copy_profile Fixes issue duplicated profile missing it's psets. --- src/bonsai/bonsai/tool/profile.py | 2 +- .../ifcopenshell/api/profile/__init__.py | 2 + .../ifcopenshell/api/profile/copy_profile.py | 47 +++++++++++++++++++ .../ifcopenshell/api/pset/unshare_pset.py | 2 + .../test/api/profile/test_copy_profile.py | 45 ++++++++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/profile/copy_profile.py create mode 100644 src/ifcopenshell-python/test/api/profile/test_copy_profile.py diff --git a/src/bonsai/bonsai/tool/profile.py b/src/bonsai/bonsai/tool/profile.py index 905b7cf753..859f8e4a3b 100644 --- a/src/bonsai/bonsai/tool/profile.py +++ b/src/bonsai/bonsai/tool/profile.py @@ -100,7 +100,7 @@ class Profile(bonsai.core.tool.Profile): @classmethod def duplicate_profile(cls, profile: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: - new_profile = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), profile) + new_profile = ifcopenshell.api.profile.copy_profile(tool.Ifc.get(), profile) # In UI unnamed profiles are not available, so we don't handle them. new_profile.ProfileName = profile.ProfileName + "_copy" return new_profile diff --git a/src/ifcopenshell-python/ifcopenshell/api/profile/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/profile/__init__.py index e26d7725b1..bb882c999a 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/profile/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/profile/__init__.py @@ -26,6 +26,7 @@ from .. import wrap_usecases from .add_arbitrary_profile import add_arbitrary_profile from .add_arbitrary_profile_with_voids import add_arbitrary_profile_with_voids from .add_parameterized_profile import add_parameterized_profile +from .copy_profile import copy_profile from .edit_profile import edit_profile from .remove_profile import remove_profile @@ -35,6 +36,7 @@ __all__ = [ "add_arbitrary_profile", "add_arbitrary_profile_with_voids", "add_parameterized_profile", + "copy_profile", "edit_profile", "remove_profile", ] diff --git a/src/ifcopenshell-python/ifcopenshell/api/profile/copy_profile.py b/src/ifcopenshell-python/ifcopenshell/api/profile/copy_profile.py new file mode 100644 index 0000000000..61d3c7d6cc --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/profile/copy_profile.py @@ -0,0 +1,47 @@ +# 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 ifcopenshell +import ifcopenshell.util.element + + +def copy_profile(file: ifcopenshell.file, profile: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: + """Copies a profile + + All profile's psets are copied. The copied profile is not + associated to any elements. + + :param profile: The IfcProfileDef to copy + :return: The new copy of the profile + + Example: + + .. code:: python + + profile = ifcopenshell.api.profile.add_profile(model, ifc_class="IfcRectangleProfileDef") + + # Let's duplicate the rectangle profile + profile_copy = ifcopenshell.api.profile.copy_profile(model, profile=profile) + """ + new_profile = ifcopenshell.util.element.copy_deep(file, profile) + inverses = file.get_inverse(profile) + psets = [i for i in inverses if i.is_a("IfcProfileProperties")] + for pset in psets: + new_pset = ifcopenshell.util.element.copy_deep(file, pset, exclude=["IfcProfileDef"]) + new_pset.ProfileDefinition = new_profile + return new_profile diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset/unshare_pset.py b/src/ifcopenshell-python/ifcopenshell/api/pset/unshare_pset.py index c88f74f857..acf955879a 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset/unshare_pset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset/unshare_pset.py @@ -85,6 +85,8 @@ def unshare_pset( pset_copies: list[ifcopenshell.entity_instance] = [] for product in products: + # No need to consider about profile/material properties since + # they are assigned to 1 element directly and therefore cannot be shared. pset_copy = ifcopenshell.util.element.copy_deep(file, pset) pset_copies.append(pset_copy) ifcopenshell.api.pset.assign_pset(file, [product], pset_copy) diff --git a/src/ifcopenshell-python/test/api/profile/test_copy_profile.py b/src/ifcopenshell-python/test/api/profile/test_copy_profile.py new file mode 100644 index 0000000000..8cc19b1489 --- /dev/null +++ b/src/ifcopenshell-python/test/api/profile/test_copy_profile.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.profile +import ifcopenshell.api.pset +import ifcopenshell.util.element + + +class TestCopyProfileIFC2X3(test.bootstrap.IFC2X3): + def test_copy_profile(self): + profile = ifcopenshell.api.profile.add_parameterized_profile(self.file, "IfcRectangleProfileDef") + new_profile = ifcopenshell.api.profile.copy_profile(self.file, profile=profile) + assert new_profile.is_a("IfcRectangleProfileDef") + assert profile != new_profile + assert len(self.file.by_type("IfcRectangleProfileDef")) == 2 + + def test_copy_profile_with_pset(self): + profile = ifcopenshell.api.profile.add_parameterized_profile(self.file, "IfcRectangleProfileDef") + pset = ifcopenshell.api.pset.add_pset(self.file, product=profile, name="ProfilePset") + new_profile = ifcopenshell.api.profile.copy_profile(self.file, profile=profile) + + assert len(self.file.by_type("IfcRectangleProfileDef")) == 2 + assert len(psets := self.file.by_type("IfcProfileProperties")) == 2 + assert ifcopenshell.util.element.get_elements_by_pset(pset) == {profile} + assert ifcopenshell.util.element.get_elements_by_pset(psets[1]) == {new_profile} + + +class TestCopyProfileIFC4(test.bootstrap.IFC4, TestCopyProfileIFC2X3): + pass