From 2b1e17681faa12faec4d366acdd8d466d3872a9e Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 24 Aug 2023 18:14:29 +0500 Subject: [PATCH] Fixed bug with segments incorrectly adjusted after adding transition If transition was really long comparing to segments length then DumbProfileJoiner.join_E would start change their their length in unexpected way - ATSTART when you'd still expect ATEND and vice versa. Also fixed the bug when segments length was incorrectly calculated - it wasn't calculating it world space when it makes more sense here to calculate it by local segments Z axis. --- .../blenderbim/bim/module/model/mep.py | 37 +++++++++++++++---- .../blenderbim/bim/module/model/profile.py | 14 +++++-- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/mep.py b/src/blenderbim/blenderbim/bim/module/model/mep.py index a0282a9a24..5d203ad817 100644 --- a/src/blenderbim/blenderbim/bim/module/model/mep.py +++ b/src/blenderbim/blenderbim/bim/module/model/mep.py @@ -688,7 +688,13 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator): ) if p not in (start_point, end_point) ] - entire_length = (first_segment_start - second_segment_end).length + + def get_segments_length(): + start_dir = (start_point - first_segment_start).normalized() + segments_vector = second_segment_end - first_segment_start + return segments_vector.dot(start_dir) + + entire_length = get_segments_length() # can't rely on (end_point-start_point) here because # transition might change the segments length and therefore direction will be changed @@ -717,28 +723,40 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator): f"Failed to add transition - transition length is larger the segments and the distance between them.\n" + f"Transition length: {full_transition_length:.2f}m, segments length: {entire_length:.2f}m", ) - # TODO: handle the case without creating a representation in the first place? ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep) return {"CANCELLED"} + # calculate bunch of points to for adjustments middle_point = keep_only_z_axis((start_point + end_point) / 2 - start_point) + start_point start_segment_extend_point = middle_point - segments_dir * full_transition_length / 2 end_segment_extend_point = middle_point + segments_dir * full_transition_length / 2 + profile_offset_ws transition_dir = keep_only_z_axis(end_segment_extend_point - start_segment_extend_point).normalized() - DumbProfileJoiner().join_E(start_object, start_segment_extend_point) - DumbProfileJoiner().join_E(end_object, end_segment_extend_point) + # adjust the segments + end_object_rotation = end_object.matrix_world.to_quaternion() + end_object_z_basis = end_object_rotation.to_matrix().col[2] # z basis vector + if tool.Cad.is_x(start_object_z_basis.dot(transition_dir), 1): + start_connection = "ATEND" + else: + start_connection = "ATSTART" + if tool.Cad.is_x(end_object_z_basis.dot(transition_dir), 1): + end_connection = "ATSTART" + else: + end_connection = "ATEND" + DumbProfileJoiner().join_E(start_object, start_segment_extend_point, start_connection) + DumbProfileJoiner().join_E(end_object, end_segment_extend_point, end_connection) + + # find the compatible fitting type fitting_data = MEPGenerator().get_compatible_fitting_type( [start_element, end_element], [start_port, end_port], "TRANSITION" ) - transition_type = fitting_data["fitting_type"] if fitting_data else None if transition_type: # TODO: handle the case without creating a representation in the first place? ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep) - start_port_match = fitting_data["start_port_match"] if fitting_data else True + # create new fitting type if nothing is compatible if not transition_type: mesh = bpy.data.meshes.new("Transition") obj = bpy.data.objects.new("Transition", mesh) @@ -762,16 +780,21 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator): ) # NOTE: at this point we loose current blender objects selection + # create transition element bpy.ops.bim.add_constr_type_instance(relating_type_id=transition_type.id()) transition_obj = bpy.context.active_object # adjust transition segment rotation and location + # required since we'll base our `transition_obj_dir` on this transition_obj.matrix_world = start_object.matrix_world context.view_layer.update() + # depending on transition direction we may need to flip it or attach it's origin to end segment + # direction can be different depending on: + # - order of the current segments + # - order of the segments that were used with the same transition type before transition_obj_dir = tool.Cad.get_edge_direction(tool.Model.get_flow_segment_axis(transition_obj)) direction_match = tool.Cad.are_vectors_equal(transition_dir, transition_obj_dir) - # if there are no mismatches or everything matches up we don't need to flip the transition if start_port_match != direction_match: transition_obj.matrix_world = start_object.matrix_world @ Matrix.Rotation(radians(180), 4, "X") diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py index 10aa597da4..8bac8d9199 100644 --- a/src/blenderbim/blenderbim/bim/module/model/profile.py +++ b/src/blenderbim/blenderbim/bim/module/model/profile.py @@ -272,13 +272,21 @@ class DumbProfileJoiner: body = copy.deepcopy(axis1) self.recreate_profile(element1, profile1, axis, body) - def join_E(self, profile1, target): + def join_E(self, profile1, target, connection=None): + """`connection` = `ATEND` / `ATSTART` to explicitly define the reference point for the join. + + For example if profile 1m long and `target` is at (0, 0, 0.1) and `connection` = `None` + it will implicitly use `connection` = `ATSTART` resulting in profile object 0.9m long and moved to (0, 0, 0.1). + + But with `connection` = `ATEND` it will result in the profile object 0.1m long, locaiton unchanged. + """ element1 = tool.Ifc.get_entity(profile1) if not element1: return axis1 = self.get_profile_axis(profile1) - intersect, connection = mathutils.geometry.intersect_point_line(target, *axis1) - connection = "ATEND" if connection > 0.5 else "ATSTART" + intersect, connection_value = mathutils.geometry.intersect_point_line(target, *axis1) + if connection is None: + connection = "ATEND" if connection_value > 0.5 else "ATSTART" ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element1, connection_type=connection)