diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py new file mode 100644 index 0000000000..05028ee4cd --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py @@ -0,0 +1,65 @@ +# 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 ifcopenshell +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "relating_element": None, + "related_element": None, + "relating_connection": "NOTDEFINED", + "related_connection": "NOTDEFINED", + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + existing_connection = [ + r + for r in self.settings["relating_element"].ConnectedTo + if r.RelatedElement == self.settings["related_element"] + ] + + if existing_connection: + existing_connection = existing_connection[0] + if existing_connection.RelatingConnectionType != self.settings["relating_connection"]: + existing_connection.RelatingConnectionType = self.settings["relating_connection"] + if existing_connection.RelatedConnectionType != self.settings["related_connection"]: + existing_connection.RelatedConnectionType = self.settings["related_connection"] + return existing_connection[0] + + if [ + r + for r in self.settings["related_element"].ConnectedTo + if r.RelatedElement == self.settings["relating_element"] + ]: + # No cyclical connections allowed, I assume + return + + return self.file.createIfcRelConnectsPathElements( + ifcopenshell.guid.new(), + OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file), + RelatingElement=self.settings["relating_element"], + RelatedElement=self.settings["related_element"], + RelatingConnectionType=self.settings["relating_connection"], + RelatedConnectionType=self.settings["related_connection"], + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_path.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_path.py new file mode 100644 index 0000000000..d6b05f6132 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_path.py @@ -0,0 +1,41 @@ +# 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 ifcopenshell +import ifcopenshell.api + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = { + "relating_element": None, + "related_element": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + existing_connection = [ + r + for r in self.settings["relating_element"].ConnectedTo + if r.RelatedElement == self.settings["related_element"] + ] + + if existing_connection: + self.file.remove(existing_connection[0]) diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 1047c3602d..269b425312 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -398,8 +398,7 @@ def get_parts(element): def replace_attribute(element, old, new): for i, attribute in enumerate(element): if has_element_reference(attribute, old): - new_attribute = element.walk(lambda v: v == old, lambda v: new, attribute) - element[i] = new_attribute + element[i] = element.walk(lambda v: v == old, lambda v: new, attribute) def has_element_reference(value, element): diff --git a/src/ifcopenshell-python/test/api/geometry/test_connect_path.py b/src/ifcopenshell-python/test/api/geometry/test_connect_path.py new file mode 100644 index 0000000000..e47121969d --- /dev/null +++ b/src/ifcopenshell-python/test/api/geometry/test_connect_path.py @@ -0,0 +1,55 @@ +# 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 TestConnectPath(test.bootstrap.IFC4): + def test_connecting_a_path(self): + wall1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + wall2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + rel = ifcopenshell.api.run( + "geometry.connect_path", + self.file, + relating_element=wall1, + related_element=wall2, + relating_connection="ATSTART", + related_connection="ATEND", + ) + assert rel.is_a("IfcRelConnectsPathElements") + assert rel.RelatingElement == wall1 + assert rel.RelatedElement == wall2 + assert rel.RelatingConnectionType == "ATSTART" + assert rel.RelatedConnectionType == "ATEND" + + def test_doing_nothing_if_the_element_is_already_connected(self): + wall1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + wall2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("geometry.connect_path", self.file, relating_element=wall1, related_element=wall2) + total_elements = len([e for e in self.file]) + ifcopenshell.api.run("geometry.connect_path", self.file, relating_element=wall1, related_element=wall2) + assert len([e for e in self.file]) == total_elements + + def test_preventing_cyclical_connections(self): + wall1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + wall2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("geometry.connect_path", self.file, relating_element=wall1, related_element=wall2) + total_elements = len([e for e in self.file]) + ifcopenshell.api.run("geometry.connect_path", self.file, relating_element=wall2, related_element=wall1) + assert len([e for e in self.file]) == total_elements diff --git a/src/ifcopenshell-python/test/api/geometry/test_disconnect_path.py b/src/ifcopenshell-python/test/api/geometry/test_disconnect_path.py new file mode 100644 index 0000000000..e6927f9cc4 --- /dev/null +++ b/src/ifcopenshell-python/test/api/geometry/test_disconnect_path.py @@ -0,0 +1,44 @@ +# 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 TestDisconnectPath(test.bootstrap.IFC4): + def test_disconnecting_a_path(self): + wall1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + wall2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("geometry.connect_path", self.file, relating_element=wall1, related_element=wall2) + ifcopenshell.api.run("geometry.disconnect_path", self.file, relating_element=wall1, related_element=wall2) + assert not self.file.by_type("IfcRelConnectsPathElements") + + def test_doing_nothing_if_there_is_no_connection(self): + wall1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + wall2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + total_elements = len([e for e in self.file]) + ifcopenshell.api.run("geometry.disconnect_path", self.file, relating_element=wall1, related_element=wall2) + assert len([e for e in self.file]) == total_elements + + def test_that_order_matters(self): + wall1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + wall2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run("geometry.connect_path", self.file, relating_element=wall1, related_element=wall2) + total_elements = len([e for e in self.file]) + ifcopenshell.api.run("geometry.disconnect_path", self.file, relating_element=wall2, related_element=wall1) + assert len([e for e in self.file]) == total_elements