From 45f81b47800acd30b5881e61e9badbe3c0556826 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 2 Aug 2023 16:28:45 +0500 Subject: [PATCH] adjust obstruction position on changing segment length Now when you change the segment's length connected obstructions will adjust. Same for changing obstruction's length. You can also move obstructions/segment around and then use "Regenerate" to make them fit. Small demo - https://imgur.com/a/atm8Mdx --- .../blenderbim/bim/module/model/mep.py | 67 ++++++++++++++++--- .../blenderbim/bim/module/model/profile.py | 15 ++++- .../blenderbim/bim/module/system/operator.py | 1 + src/blenderbim/blenderbim/tool/system.py | 5 +- 4 files changed, 74 insertions(+), 14 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/mep.py b/src/blenderbim/blenderbim/bim/module/model/mep.py index 1f3872506e..4f62b6b2e0 100644 --- a/src/blenderbim/blenderbim/bim/module/model/mep.py +++ b/src/blenderbim/blenderbim/bim/module/model/mep.py @@ -41,6 +41,7 @@ float_is_zero = lambda f: 0.0001 >= f >= -0.0001 class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.fit_flow_segments" + bl_description = "Add a fitting based on currently selected elements and cursor" bl_label = "Fit Flow Segments" bl_options = {"REGISTER", "UNDO"} @@ -135,24 +136,69 @@ class MEPGenerator: self.file = tool.Ifc.get() self.collection = bpy.context.view_layer.active_layer_collection.collection - element = tool.Ifc.get_entity(obj) - representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") + segment = tool.Ifc.get_entity(obj) + representation = ifcopenshell.util.representation.get_representation(segment, "Model", "Body", "MODEL_VIEW") extrusion = tool.Model.get_extrusion(representation) si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) length = extrusion.Depth * si_conversion - end_port_matrix = Matrix.Translation((0, 0, length)) + start_port_matrix = obj.matrix_world @ Matrix() + end_port_matrix = obj.matrix_world @ Matrix.Translation((0, 0, length)) - ports = tool.System.get_ports(element) - if not ports: - start_port_matrix = Matrix() + ports = tool.System.get_ports(segment) + if segment.is_a("IfcFlowSegment") and not ports: for mat in [start_port_matrix, end_port_matrix]: - port = tool.Ifc.run("system.add_port", element=element) + # TODO: specify PredefinedType based on the segment type + port = tool.Ifc.run("system.add_port", element=segment) port.FlowDirection = "NOTDEFINED" - tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=obj.matrix_world @ mat, is_si=True) + tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=mat, is_si=True) return - end_port = self.get_segment_data(element)["end_port"] - tool.Model.edit_element_placement(end_port, obj.matrix_world @ end_port_matrix) + # adjust current segment ports and related flow segments + segment_data = self.get_segment_data(segment) + + for port_position in ("start_port", "end_port"): + port = segment_data.get(port_position, None) + if not port: + continue + + # no need to correct start port position - it's corrected automatically + # as DumbProfileJoiner already moved the general segment position in that case + if port_position == "end_port": + tool.Model.edit_element_placement(port, end_port_matrix) + + connected_port = tool.System.get_connected_port(port) + if not connected_port: + continue + + # handle only obstructions for now + connected_element = tool.System.get_port_relating_element(connected_port) + + def get_predefined_type(element): + element_type = ifcopenshell.util.element.get_type(element) + if element_type: + return element_type.PredefinedType + return element.PredefinedType + + connected_obj = tool.Ifc.get_object(connected_element) + connected_element_length = connected_obj.dimensions.z + if (segment.is_a("IfcFlowSegment") and get_predefined_type(connected_element) == "OBSTRUCTION") or ( + segment.is_a("IfcFlowFitting") and connected_element.is_a("IfcFlowSegment") + ): + if port_position == "start_port": + if segment.is_a("IfcFlowFitting"): + profile_joiner = DumbProfileJoiner() + connected_element_length = ( + tool.Model.get_flow_segment_axis(connected_obj)[0] + - tool.Model.get_flow_segment_axis(obj)[0] + ).length + + connected_port_matrix = start_port_matrix @ Matrix.Translation((0, 0, -connected_element_length)) + else: + connected_port_matrix = end_port_matrix + connected_obj.matrix_world = connected_port_matrix + if port_position == "start_port" and segment.is_a("IfcFlowFitting"): + profile_joiner = DumbProfileJoiner() + profile_joiner.set_depth(connected_obj, connected_element_length) def get_segment_data(self, segment): ports = tool.System.get_ports(segment) @@ -275,6 +321,7 @@ class MEPGenerator: profile_joiner.set_depth(obstruction_obj, length) obstruction = tool.Ifc.get_entity(obstruction_obj) + # TODO: specify PredefinedType based on the segment type obstruction_port = tool.Ifc.run("system.add_port", element=obstruction) port_local_position = Matrix.Translation((0, 0, length)) if at_segment_start else Matrix() tool.Ifc.run( diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py index ed7a601b4a..10aa597da4 100644 --- a/src/blenderbim/blenderbim/bim/module/model/profile.py +++ b/src/blenderbim/blenderbim/bim/module/model/profile.py @@ -491,7 +491,7 @@ class DumbProfileJoiner: should_sync_changes_first=False, ) tool.Geometry.record_object_materials(obj) - if element.is_a("IfcFlowSegment"): + if element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting"): # lazy import to avoid circular import errors from blenderbim.bim.module.model.mep import MEPGenerator @@ -803,14 +803,23 @@ class RecalculateProfile(bpy.types.Operator, tool.Ifc.Operator): class DumbProfileRecalculator: def recalculate(self, profiles): + "`profiles` is a list of blender profile objects" queue = set() + + # also recalculate all connected elements for profile in profiles: element = tool.Ifc.get_entity(profile) queue.add((element, profile)) + connected_elements = [] + for rel in getattr(element, "ConnectedTo", []): - queue.add((rel.RelatedElement, tool.Ifc.get_object(rel.RelatedElement))) + connected_elements.append(rel.RelatedElement) for rel in getattr(element, "ConnectedFrom", []): - queue.add((rel.RelatingElement, tool.Ifc.get_object(rel.RelatingElement))) + connected_elements.append(rel.RelatingElement) + + for element in connected_elements: + queue.add((element, tool.Ifc.get_object(element))) + joiner = DumbProfileJoiner() for element, profile in queue: if profile: diff --git a/src/blenderbim/blenderbim/bim/module/system/operator.py b/src/blenderbim/blenderbim/bim/module/system/operator.py index f8d78c99d9..828977060e 100644 --- a/src/blenderbim/blenderbim/bim/module/system/operator.py +++ b/src/blenderbim/blenderbim/bim/module/system/operator.py @@ -168,6 +168,7 @@ class HidePorts(bpy.types.Operator, Operator): class AddPort(bpy.types.Operator, Operator): bl_idname = "bim.add_port" + bl_description = "Add port at current cursor position" bl_label = "Add Port" bl_options = {"REGISTER", "UNDO"} diff --git a/src/blenderbim/blenderbim/tool/system.py b/src/blenderbim/blenderbim/tool/system.py index cc6975df37..e6aa4ecabc 100644 --- a/src/blenderbim/blenderbim/tool/system.py +++ b/src/blenderbim/blenderbim/tool/system.py @@ -64,6 +64,10 @@ class System(blenderbim.core.tool.System): def get_ports(cls, element): return ifcopenshell.util.system.get_ports(element) + @classmethod + def get_port_relating_element(cls, port): + return port.Nests[0].RelatingObject + @classmethod def import_system_attributes(cls, system): props = bpy.context.scene.BIMSystemProperties @@ -129,7 +133,6 @@ class System(blenderbim.core.tool.System): ifc_representation_class=ifc_representation_class, ) - @classmethod def select_system_products(cls, system): tool.Spatial.select_products(ifcopenshell.util.system.get_system_elements(system))