diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_element.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_element.py new file mode 100644 index 0000000000..80760f6b15 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_element.py @@ -0,0 +1,60 @@ +# 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, + "description": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + incompatible_connections = [] + + for rel in self.settings["relating_element"].ConnectedFrom: + if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == self.settings["related_element"]: + incompatible_connections.append(rel) + + for rel in self.settings["related_element"].ConnectedTo: + if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == self.settings["relating_element"]: + incompatible_connections.append(rel) + + if incompatible_connections: + for connection in set(incompatible_connections): + self.file.remove(connection) + + for rel in self.settings["relating_element"].ConnectedTo: + if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == self.settings["related_element"]: + rel.Description = self.settings["description"] + return rel + + return self.file.createIfcRelConnectsElements( + ifcopenshell.guid.new(), + OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file), + Description=self.settings["description"], + RelatingElement=self.settings["relating_element"], + RelatedElement=self.settings["related_element"], + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py index b7acdef393..efec41fee2 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py @@ -36,6 +36,8 @@ class Usecase: def execute(self): incompatible_connections = [] for rel in self.settings["relating_element"].ConnectedTo: + if not rel.is_a("IfcRelConnectsPathElements"): + continue if rel.RelatedElement == self.settings["related_element"]: incompatible_connections.append(rel) elif ( @@ -45,6 +47,8 @@ class Usecase: incompatible_connections.append(rel) for rel in self.settings["relating_element"].ConnectedFrom: + if not rel.is_a("IfcRelConnectsPathElements"): + continue if ( rel.RelatedConnectionType in ["ATSTART", "ATEND"] and rel.RelatedConnectionType == self.settings["relating_connection"] @@ -52,6 +56,8 @@ class Usecase: incompatible_connections.append(rel) for rel in self.settings["related_element"].ConnectedFrom: + if not rel.is_a("IfcRelConnectsPathElements"): + continue if ( rel.RelatedConnectionType in ["ATSTART", "ATEND"] and rel.RelatedConnectionType == self.settings["related_connection"] @@ -59,6 +65,8 @@ class Usecase: incompatible_connections.append(rel) for rel in self.settings["related_element"].ConnectedTo: + if not rel.is_a("IfcRelConnectsPathElements"): + continue if rel.RelatedElement == self.settings["relating_element"]: incompatible_connections.append(rel) elif ( diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_element.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_element.py new file mode 100644 index 0000000000..793c96a71c --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/disconnect_element.py @@ -0,0 +1,51 @@ +# 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 . + + +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): + incompatible_connections = [] + + for rel in self.settings["relating_element"].ConnectedTo: + if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == self.settings["related_element"]: + incompatible_connections.append(rel) + + for rel in self.settings["relating_element"].ConnectedFrom: + if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == self.settings["related_element"]: + incompatible_connections.append(rel) + + for rel in self.settings["related_element"].ConnectedTo: + if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == self.settings["relating_element"]: + incompatible_connections.append(rel) + + for rel in self.settings["related_element"].ConnectedFrom: + if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == self.settings["relating_element"]: + incompatible_connections.append(rel) + + if incompatible_connections: + for connection in set(incompatible_connections): + self.file.remove(connection) diff --git a/src/ifcopenshell-python/test/api/geometry/test_connect_element.py b/src/ifcopenshell-python/test/api/geometry/test_connect_element.py new file mode 100644 index 0000000000..1b4123b731 --- /dev/null +++ b/src/ifcopenshell-python/test/api/geometry/test_connect_element.py @@ -0,0 +1,87 @@ +# 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_an_element(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_element", + self.file, + relating_element=wall1, + related_element=wall2, + description="FOOBAR", + ) + assert rel.is_a() == "IfcRelConnectsElements" + assert rel.RelatingElement == wall1 + assert rel.RelatedElement == wall2 + assert rel.Description == "FOOBAR" + + def test_reconnecting_an_element_changes_the_description(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_element", + self.file, + relating_element=wall1, + related_element=wall2, + description="FOOBAR", + ) + assert rel.is_a() == "IfcRelConnectsElements" + assert rel.RelatingElement == wall1 + assert rel.RelatedElement == wall2 + assert rel.Description == "FOOBAR" + + total_elements = len([e for e in self.file]) + rel = ifcopenshell.api.run( + "geometry.connect_element", + self.file, + relating_element=wall1, + related_element=wall2, + description="FOOBAZ", + ) + assert len([e for e in self.file]) == total_elements + assert rel.Description == "FOOBAZ" + + def test_reconnecting_and_changing_the_order(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_element", + self.file, + relating_element=wall1, + related_element=wall2, + description="FOOBAR", + ) + assert rel.RelatingElement == wall1 + assert rel.RelatedElement == wall2 + + total_elements = len([e for e in self.file]) + rel = ifcopenshell.api.run( + "geometry.connect_element", + self.file, + relating_element=wall2, + related_element=wall1, + description="FOOBAZ", + ) + assert rel.RelatingElement == wall2 + assert rel.RelatedElement == wall1 diff --git a/src/ifcopenshell-python/test/api/geometry/test_disconnect_element.py b/src/ifcopenshell-python/test/api/geometry/test_disconnect_element.py new file mode 100644 index 0000000000..ebb3b8c7a8 --- /dev/null +++ b/src/ifcopenshell-python/test/api/geometry/test_disconnect_element.py @@ -0,0 +1,36 @@ +# 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_an_element(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_element", self.file, relating_element=wall1, related_element=wall2) + ifcopenshell.api.run("geometry.disconnect_element", self.file, relating_element=wall1, related_element=wall2) + assert not self.file.by_type("IfcRelConnectsElements") + + def test_order_does_not_matter(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_element", self.file, relating_element=wall1, related_element=wall2) + ifcopenshell.api.run("geometry.disconnect_element", self.file, relating_element=wall2, related_element=wall1) + assert not self.file.by_type("IfcRelConnectsElements")