From fd69d917765644563302eda34d837301690535bb Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 9 Nov 2023 17:16:32 +0500 Subject: [PATCH] UI for assigning controls to flow elements #3981 Example - https://imgur.com/a/CZQIoHp --- .../blenderbim/bim/module/system/__init__.py | 2 + .../blenderbim/bim/module/system/data.py | 21 ++++++ .../blenderbim/bim/module/system/operator.py | 64 +++++++++++++++++++ .../blenderbim/bim/module/system/ui.py | 63 ++++++++++++++++++ src/blenderbim/blenderbim/tool/system.py | 12 ++++ .../test/bim/feature/system.feature | 25 ++++++++ src/blenderbim/test/tool/test_system.py | 27 ++++++++ 7 files changed, 214 insertions(+) diff --git a/src/blenderbim/blenderbim/bim/module/system/__init__.py b/src/blenderbim/blenderbim/bim/module/system/__init__.py index cb1c8d24dc..0c500595da 100644 --- a/src/blenderbim/blenderbim/bim/module/system/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/system/__init__.py @@ -24,6 +24,7 @@ classes = ( operator.AddSystem, operator.AddZone, operator.AssignSystem, + operator.AssignUnassignFlowControl, operator.ConnectPort, operator.DisableEditingSystem, operator.DisableEditingZone, @@ -54,6 +55,7 @@ classes = ( ui.BIM_PT_active_object_zones, ui.BIM_PT_ports, ui.BIM_PT_port, + ui.BIM_PT_flow_controls, ui.BIM_UL_systems, ui.BIM_UL_zones, ) diff --git a/src/blenderbim/blenderbim/bim/module/system/data.py b/src/blenderbim/blenderbim/bim/module/system/data.py index 645c770440..a1c8665e7e 100644 --- a/src/blenderbim/blenderbim/bim/module/system/data.py +++ b/src/blenderbim/blenderbim/bim/module/system/data.py @@ -72,6 +72,7 @@ class ObjectSystemData: "systems": cls.systems(), # AFTER SYSTEMS "connected_elements": cls.connected_elements(), + "flow_controls_data": cls.flow_controls_data(), } cls.is_loaded = True @@ -91,6 +92,26 @@ class ObjectSystemData: return set() return tool.System.get_connected_elements(cls.element) + @classmethod + def flow_controls_data(cls): + flow_controls_data = {} + if not cls.element or not ( + cls.element.is_a("IfcDistributionControlElement") or cls.element.is_a("IfcDistributionFlowElement") + ): + return flow_controls_data + + if cls.element.is_a("IfcDistributionControlElement"): + flow_controls_data["type"] = "IfcDistributionControlElement" + flow_element = tool.System.get_flow_control_flow_element(cls.element) + flow_element_obj = tool.Ifc.get_object(flow_element).name if flow_element else None + flow_controls_data["flow_element"] = flow_element, flow_element_obj + else: + flow_controls_data["type"] = "IfcDistributionFlowElement" + controls = [(c, tool.Ifc.get_object(c).name) for c in tool.System.get_flow_element_controls(cls.element)] + flow_controls_data["controls"] = controls + + return flow_controls_data + class PortData: data = {} diff --git a/src/blenderbim/blenderbim/bim/module/system/operator.py b/src/blenderbim/blenderbim/bim/module/system/operator.py index de617ae7b3..cef76d5979 100644 --- a/src/blenderbim/blenderbim/bim/module/system/operator.py +++ b/src/blenderbim/blenderbim/bim/module/system/operator.py @@ -421,3 +421,67 @@ class RemoveZone(bpy.types.Operator, Operator): def _execute(self, context): ifcopenshell.api.run("system.remove_system", tool.Ifc.get(), system=tool.Ifc.get().by_id(self.zone)) bpy.ops.bim.load_zones() + + +class AssignUnassignFlowControl(bpy.types.Operator, Operator): + bl_idname = "bim.assign_unassign_flow_control" + bl_label = "Assign/Unassign Flow Control" + bl_options = {"REGISTER", "UNDO"} + flow_element: bpy.props.IntProperty(options={"SKIP_SAVE"}) + flow_control: bpy.props.IntProperty(options={"SKIP_SAVE"}) + assign: bpy.props.BoolProperty(name="Assign/Unassign", default=True, options={"SKIP_SAVE"}) + + def _execute(self, context): + ifc_file = tool.Ifc.get() + flow_element = None + flow_controls = [] + from_selected_objects = False + + if self.flow_element != 0: + flow_element = ifc_file.by_id(self.flow_element) + if self.flow_control != 0: + flow_controls = [ifc_file.by_id(self.flow_control)] + + # if not provided as arguments tried to get them from + # the selected objects + if not flow_element or flow_controls: + from_selected_objects = True + for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element: + continue + + if element.is_a("IfcDistributionControlElement") and self.flow_control == 0: + flow_controls.append(element) + elif element.is_a("IfcDistributionFlowElement") and self.flow_element == 0: + if flow_element: + self.report( + {"ERROR"}, + "More than one flow element selected. Control can be assigned to only 1 flow element.", + ) + return {"CANCELLED"} + flow_element = element + + if not flow_element: + self.report({"ERROR"}, "No flow element selected.") + return {"CANCELLED"} + + if not flow_controls: + self.report({"ERROR"}, "No flow controls selected.") + return {"CANCELLED"} + + for control in flow_controls: + if self.assign: + tool.Ifc.run( + "system.assign_flow_control", relating_flow_element=flow_element, related_flow_control=control + ) + else: + tool.Ifc.run( + "system.unassign_flow_control", relating_flow_element=flow_element, related_flow_control=control + ) + + if from_selected_objects: + self.report( + {"INFO"}, f"{len(flow_controls)} flow controls were {'assigned' if self.assign else 'unassigned'}." + ) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/system/ui.py b/src/blenderbim/blenderbim/bim/module/system/ui.py index f986ca0883..063a28c4ee 100644 --- a/src/blenderbim/blenderbim/bim/module/system/ui.py +++ b/src/blenderbim/blenderbim/bim/module/system/ui.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +import bpy import blenderbim.bim.helper import blenderbim.tool as tool from blenderbim.bim.helper import prop_with_search @@ -272,6 +273,68 @@ class BIM_PT_port(Panel): ).direction = flow_direction +class BIM_PT_flow_controls(Panel): + bl_label = "Flow Controls" + bl_idname = "BIM_PT_flow_controls" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + bl_order = 1 + bl_parent_id = "BIM_PT_tab_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("IfcDistributionControlElement") or element.is_a("IfcDistributionFlowElement") + ): + return False + return True + + def draw(self, context): + if not ObjectSystemData.is_loaded: + ObjectSystemData.load() + + def display_element(control_id, flow_element_id, displayed_object_name): + displayed_object = bpy.data.objects[displayed_object_name] + row = self.layout.row(align=True) + op = row.operator("bim.assign_unassign_flow_control", text="", icon="X") + op.flow_control = control_id + op.flow_element = flow_element_id + op.assign = False + row.operator( + "bim.select_entity", text="", icon="RESTRICT_SELECT_OFF" + ).ifc_id = displayed_object.BIMObjectProperties.ifc_definition_id + row.label(text=f"{displayed_object_name}") + + element = tool.Ifc.get_entity(context.active_object) + flow_controls_data = ObjectSystemData.data["flow_controls_data"] + if flow_controls_data["type"] == "IfcDistributionFlowElement": + controls = flow_controls_data["controls"] + row = self.layout.row(align=True) + if controls: + row.label(text="Controls assigned to the element:") + else: + row.label(text="No controls assigned to the flow element") + row.operator("bim.assign_unassign_flow_control", text="", icon="ADD").assign = True + if controls: + for control_data in controls: + control, control_obj_name = control_data + display_element(control.id(), element.id(), control_obj_name) + else: + flow_element, flow_element_obj_name = flow_controls_data["flow_element"] + row = self.layout.row(align=True) + if flow_element: + row.label(text="Flow element controlled by the flow control:") + display_element(element.id(), flow_element.id(), flow_element_obj_name) + else: + row.label(text="No flow element controlled by the flow control") + row.operator("bim.assign_unassign_flow_control", text="", icon="ADD").assign = True + + class BIM_PT_zones(Panel): bl_label = "Zones" bl_idname = "BIM_PT_zones" diff --git a/src/blenderbim/blenderbim/tool/system.py b/src/blenderbim/blenderbim/tool/system.py index 0ce26d14f2..4a2c148c24 100644 --- a/src/blenderbim/blenderbim/tool/system.py +++ b/src/blenderbim/blenderbim/tool/system.py @@ -368,3 +368,15 @@ class System(blenderbim.core.tool.System): @classmethod def is_mep_element(cls, element): return element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting") + + @classmethod + def get_flow_element_controls(cls, element): + if not element.HasControlElements: + return [] + return [control for control in element.HasControlElements[0].RelatedControlElements] + + @classmethod + def get_flow_control_flow_element(cls, element): + if not element.AssignedToFlowElement: + return + return element.AssignedToFlowElement[0].RelatingFlowElement diff --git a/src/blenderbim/test/bim/feature/system.feature b/src/blenderbim/test/bim/feature/system.feature index 486f01fbaf..addd965b95 100644 --- a/src/blenderbim/test/bim/feature/system.feature +++ b/src/blenderbim/test/bim/feature/system.feature @@ -101,3 +101,28 @@ Scenario: Select system products And I press "bim.assign_system(system={system})" When I press "bim.select_system_products(system={system})" Then nothing happens + +Scenario: Assign flow controls to flow element_type + Given an empty IFC project + And I add an empty + And the object "Empty" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElement" + And I set "scene.BIMRootProperties.ifc_class" to "IfcActuator" + And I press "bim.assign_class" + And I add an empty + And the object "Empty" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElement" + And I set "scene.BIMRootProperties.ifc_class" to "IfcActuator" + And I press "bim.assign_class" + And I add an empty + And the object "Empty" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElement" + And I set "scene.BIMRootProperties.ifc_class" to "IfcFlowSegment" + And I press "bim.assign_class" + And the object "IfcActuator/Empty" is selected + And additionally the object "IfcActuator/Empty.001" is selected + And additionally the object "IfcFlowSegment/Empty" is selected + When I press "bim.assign_unassign_flow_control(assign=True)" + And the variable "assigned_controls" is "set(tool.System.get_flow_element_controls({ifc}.by_type('IfcFlowSegment')[0]))" + Then the variable "assigned_controls" is "set[{ifc}.by_type('IfcActuator')]" + diff --git a/src/blenderbim/test/tool/test_system.py b/src/blenderbim/test/tool/test_system.py index 605e1204c6..419c46cc7f 100644 --- a/src/blenderbim/test/tool/test_system.py +++ b/src/blenderbim/test/tool/test_system.py @@ -277,3 +277,30 @@ class TestSetActiveSystem(NewFile): system = ifcopenshell.api.run("system.add_system", ifc, ifc_class="IfcSystem") subject.set_active_edited_system(system) assert bpy.context.scene.BIMSystemProperties.edited_system_id == system.id() + + +class TestFlowElementAndControls(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + flow_element = ifc.createIfcFlowSegment() + flow_control = ifc.createIfcController() + flow_control1 = ifc.createIfcController() + + assert len(subject.get_flow_element_controls(flow_element)) == 0 + assert subject.get_flow_control_flow_element(flow_control) == None + + ifcopenshell.api.run( + "system.assign_flow_control", + ifc, + related_flow_control=flow_control, + relating_flow_element=flow_element, + ) + ifcopenshell.api.run( + "system.assign_flow_control", + ifc, + related_flow_control=flow_control1, + relating_flow_element=flow_element, + ) + controls = subject.get_flow_element_controls(flow_element) + assert set(controls) == set((flow_control, flow_control1)) + assert subject.get_flow_control_flow_element(flow_control) == flow_element