From 94ff8349d34d364d3309d4327a2bc3d277b168c4 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 3 Feb 2022 12:04:20 +1100 Subject: [PATCH] You can now connect, disconnect, and set flow directions of distribution ports in Blender. --- .../blenderbim/bim/module/system/__init__.py | 4 ++ .../blenderbim/bim/module/system/operator.py | 36 ++++++++++++++++++ .../blenderbim/bim/module/system/ui.py | 29 ++++++++++++++ src/blenderbim/blenderbim/core/system.py | 15 ++++++++ src/blenderbim/blenderbim/core/tool.py | 1 + src/blenderbim/blenderbim/tool/system.py | 4 ++ src/blenderbim/test/core/test_system.py | 11 ++++++ src/blenderbim/test/files/image.jpg | Bin 0 -> 19526 bytes src/blenderbim/test/tool/test_system.py | 13 ++++++- .../ifcopenshell/util/system.py | 25 ++++++++++++ .../test/util/test_system.py | 9 +++++ 11 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/blenderbim/test/files/image.jpg diff --git a/src/blenderbim/blenderbim/bim/module/system/__init__.py b/src/blenderbim/blenderbim/bim/module/system/__init__.py index 5a0a122d90..f87f83740d 100644 --- a/src/blenderbim/blenderbim/bim/module/system/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/system/__init__.py @@ -23,14 +23,17 @@ classes = ( operator.AddPort, operator.AddSystem, operator.AssignSystem, + operator.ConnectPort, operator.DisableEditingSystem, operator.DisableSystemEditingUI, + operator.DisconnectPort, operator.EditSystem, operator.EnableEditingSystem, operator.HidePorts, operator.LoadSystems, operator.RemoveSystem, operator.SelectSystemProducts, + operator.SetFlowDirection, operator.ShowPorts, operator.UnassignSystem, prop.System, @@ -38,6 +41,7 @@ classes = ( ui.BIM_PT_systems, ui.BIM_PT_object_systems, ui.BIM_PT_ports, + ui.BIM_PT_port, ui.BIM_UL_systems, ui.BIM_UL_object_systems, ) diff --git a/src/blenderbim/blenderbim/bim/module/system/operator.py b/src/blenderbim/blenderbim/bim/module/system/operator.py index c7337de87d..290654884e 100644 --- a/src/blenderbim/blenderbim/bim/module/system/operator.py +++ b/src/blenderbim/blenderbim/bim/module/system/operator.py @@ -159,3 +159,39 @@ class AddPort(bpy.types.Operator, Operator): def _execute(self, context): core.add_port(tool.Ifc, tool.System, element=tool.Ifc.get_entity(context.active_object)) + + +class ConnectPort(bpy.types.Operator, Operator): + bl_idname = "bim.connect_port" + bl_label = "Connect Ports" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return len(context.selected_objects) == 2 + + def _execute(self, context): + obj1 = context.active_object + obj2 = context.selected_objects[0] if context.selected_objects[1] == obj1 else context.selected_objects[1] + core.connect_port(tool.Ifc, port1=tool.Ifc.get_entity(obj1), port2=tool.Ifc.get_entity(obj2)) + + +class DisconnectPort(bpy.types.Operator, Operator): + bl_idname = "bim.disconnect_port" + bl_label = "Disconnect Ports" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + core.disconnect_port(tool.Ifc, port=tool.Ifc.get_entity(context.active_object)) + + +class SetFlowDirection(bpy.types.Operator, Operator): + bl_idname = "bim.set_flow_direction" + bl_label = "Set Flow Direction" + bl_options = {"REGISTER", "UNDO"} + direction: bpy.props.StringProperty() + + def _execute(self, context): + core.set_flow_direction( + tool.Ifc, tool.System, port=tool.Ifc.get_entity(context.active_object), direction=self.direction + ) diff --git a/src/blenderbim/blenderbim/bim/module/system/ui.py b/src/blenderbim/blenderbim/bim/module/system/ui.py index ca3b2d383f..33a0e975f6 100644 --- a/src/blenderbim/blenderbim/bim/module/system/ui.py +++ b/src/blenderbim/blenderbim/bim/module/system/ui.py @@ -156,6 +156,35 @@ class BIM_PT_ports(Panel): row.operator("bim.add_port", icon="ADD", text="") +class BIM_PT_port(Panel): + bl_label = "IFC Port" + bl_idname = "BIM_PT_port" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + bl_parent_id = "BIM_PT_services_object" + + @classmethod + def poll(cls, context): + if not context.active_object: + return False + element = tool.Ifc.get_entity(context.active_object) + if not element or not element.is_a("IfcPort"): + return False + return True + + def draw(self, context): + self.props = context.scene.BIMSystemProperties + row = self.layout.row(align=True) + row.operator("bim.connect_port", icon="PLUGIN", text="") + row.operator("bim.disconnect_port", icon="UNLINKED", text="") + row.operator("bim.set_flow_direction", icon="FORWARD", text="").direction = "SOURCE" + row.operator("bim.set_flow_direction", icon="BACK", text="").direction = "SINK" + row.operator("bim.set_flow_direction", icon="ARROW_LEFTRIGHT", text="").direction = "SOURCEANDSINK" + row.operator("bim.set_flow_direction", icon="RESTRICT_INSTANCED_ON", text="").direction = "NOTDEFINED" + + class BIM_UL_systems(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): system_icons = { diff --git a/src/blenderbim/blenderbim/core/system.py b/src/blenderbim/blenderbim/core/system.py index 244e212bd3..cde210638f 100644 --- a/src/blenderbim/blenderbim/core/system.py +++ b/src/blenderbim/blenderbim/core/system.py @@ -81,3 +81,18 @@ def add_port(ifc, system, element=None): obj = system.create_empty_at_cursor_with_element_orientation(element) port = system.run_root_assign_class(obj=obj, ifc_class="IfcDistributionPort") ifc.run("system.assign_port", element=element, port=port) + + +def connect_port(ifc, port1=None, port2=None): + ifc.run("system.connect_port", port1=port1, port2=port2) + + +def disconnect_port(ifc, port=None): + ifc.run("system.disconnect_port", port=port) + + +def set_flow_direction(ifc, system, port=None, direction=None): + port2 = system.get_connected_port(port) + if not port2: + return + ifc.run("system.connect_port", port1=port, port2=port2, direction=direction) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 964c0d8a65..d42ecf32c3 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -390,6 +390,7 @@ class System: def disable_system_editing_ui(cls): pass def enable_system_editing_ui(cls): pass def export_system_attributes(cls): pass + def get_connected_port(cls, port): pass def get_ports(cls, element): pass def import_system_attributes(cls, system): pass def import_systems(cls): pass diff --git a/src/blenderbim/blenderbim/tool/system.py b/src/blenderbim/blenderbim/tool/system.py index e7d5051f81..b2109330a1 100644 --- a/src/blenderbim/blenderbim/tool/system.py +++ b/src/blenderbim/blenderbim/tool/system.py @@ -56,6 +56,10 @@ class System(blenderbim.core.tool.System): def export_system_attributes(cls): return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMSystemProperties.system_attributes) + @classmethod + def get_connected_port(cls, port): + return ifcopenshell.util.system.get_connected_port(port) + @classmethod def get_ports(cls, element): return ifcopenshell.util.system.get_ports(element) diff --git a/src/blenderbim/test/core/test_system.py b/src/blenderbim/test/core/test_system.py index dfed8ac817..2cf955bf4b 100644 --- a/src/blenderbim/test/core/test_system.py +++ b/src/blenderbim/test/core/test_system.py @@ -113,3 +113,14 @@ class TestAddPort: system.run_root_assign_class(obj="obj", ifc_class="IfcDistributionPort").should_be_called().will_return("port") ifc.run("system.assign_port", element="element", port="port").should_be_called() subject.add_port(ifc, system, element="element") + + +class TestSetFlowDirection: + def test_run(self, ifc, system): + system.get_connected_port("port").should_be_called().will_return("port2") + ifc.run("system.connect_port", port1="port", port2="port2", direction="direction").should_be_called() + subject.set_flow_direction(ifc, system, port="port", direction="direction") + + def test_do_not_set_a_direction_if_port_is_not_connected(self, ifc, system): + system.get_connected_port("port").should_be_called().will_return(None) + subject.set_flow_direction(ifc, system, port="port", direction="direction") diff --git a/src/blenderbim/test/files/image.jpg b/src/blenderbim/test/files/image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d8edccee7493e038789dd9246661f1a4efd5b202 GIT binary patch literal 19526 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 literal 0 HcmV?d00001 diff --git a/src/blenderbim/test/tool/test_system.py b/src/blenderbim/test/tool/test_system.py index a3eec4d20c..0e8fc3f021 100644 --- a/src/blenderbim/test/tool/test_system.py +++ b/src/blenderbim/test/tool/test_system.py @@ -36,9 +36,8 @@ class TestCreateEmptyAtCursorWithElementOrientation(NewFile): obj = bpy.data.objects.new("Object", None) element = ifc.createIfcWall() tool.Ifc.link(element, obj) - bpy.context.scene.cursor.matrix.translation[0] = 5 obj = subject.create_empty_at_cursor_with_element_orientation(element) - assert obj.matrix_world.translation[0] == 5 + assert obj.matrix_world == bpy.context.scene.cursor.matrix class TestDeleteElementObjects(NewFile): @@ -83,6 +82,16 @@ class TestExportSystemAttributes(NewFile): } +class TestGetConnectedPort(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + port1 = ifcopenshell.api.run("system.add_port", ifc) + port2 = ifcopenshell.api.run("system.add_port", ifc) + ifcopenshell.api.run("system.connect_port", ifc, port1=port1, port2=port2) + assert subject.get_connected_port(port1) == port2 + assert subject.get_connected_port(port2) == port1 + + class TestGetPorts(NewFile): def test_run(self): ifc = ifcopenshell.file() diff --git a/src/ifcopenshell-python/ifcopenshell/util/system.py b/src/ifcopenshell-python/ifcopenshell/util/system.py index c703c1fe08..acbedc8305 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/system.py +++ b/src/ifcopenshell-python/ifcopenshell/util/system.py @@ -43,3 +43,28 @@ def get_ports(element): for rel in element.HasPorts or []: results.append(rel.RelatingPort) return results + + +def get_connected_port(port): + for rel in port.ConnectedTo: + return rel.RelatedPort + for rel in port.ConnectedFrom: + return rel.RelatingPort + + +def get_connected_to(element): + # Note: this code is for IFC2X3. IFC4 has a different approach. + results = [] + for rel in element.HasPorts: + for rel2 in rel.RelatingPort.ConnectedTo: + results.extend([r.RelatedElement for r in rel2.RelatedPort.ContainedIn if r.RelatedElement != element]) + return results + + +def get_connected_from(element): + # Note: this code is for IFC2X3. IFC4 has a different approach. + results = [] + for rel in element.HasPorts: + for rel2 in rel.RelatingPort.ConnectedFrom: + results.extend([r.RelatedElement for r in rel2.RelatingPort.ContainedIn if r.RelatedElement != element]) + return results diff --git a/src/ifcopenshell-python/test/util/test_system.py b/src/ifcopenshell-python/test/util/test_system.py index 3ec569e970..8c603b216f 100644 --- a/src/ifcopenshell-python/test/util/test_system.py +++ b/src/ifcopenshell-python/test/util/test_system.py @@ -61,3 +61,12 @@ class TestGetPortsIFC2X3(test.bootstrap.IFC2X3): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcFlowSegment") ifcopenshell.api.run("system.assign_port", self.file, element=element, port=port) assert subject.get_ports(element) == [port] + + +class TestGetConnectedPort(test.bootstrap.IFC4): + def test_run(self): + port1 = ifcopenshell.api.run("system.add_port", self.file) + port2 = ifcopenshell.api.run("system.add_port", self.file) + ifcopenshell.api.run("system.connect_port", self.file, port1=port1, port2=port2) + assert subject.get_connected_port(port1) == port2 + assert subject.get_connected_port(port2) == port1