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 0000000000..d8edccee74 Binary files /dev/null and b/src/blenderbim/test/files/image.jpg differ 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