From e93ff7a6b12e9d931feff6e92802faae453990f5 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 11 Sep 2023 10:29:25 +0500 Subject: [PATCH] MEP bends - more robust start/end points detection also support for bends with angles > 90 degrees and some other bugs fixed --- .../blenderbim/bim/module/model/mep.py | 39 ++++++++++++------- .../blenderbim/bim/module/model/wall.py | 4 +- src/blenderbim/blenderbim/tool/cad.py | 12 ++++++ .../ifcopenshell/util/shape_builder.py | 17 ++++++-- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/mep.py b/src/blenderbim/blenderbim/bim/module/model/mep.py index 934902cf91..88d3e7a708 100644 --- a/src/blenderbim/blenderbim/bim/module/model/mep.py +++ b/src/blenderbim/blenderbim/bim/module/model/mep.py @@ -35,7 +35,7 @@ import blenderbim.core.type import blenderbim.core.root import blenderbim.core.geometry import blenderbim.tool as tool -from math import pi, degrees, radians, sin, cos, asin +from math import pi, degrees, radians, sin, cos, asin, tan from copy import copy from mathutils import Vector, Matrix from ifcopenshell.util.shape_builder import ShapeBuilder @@ -973,10 +973,20 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): end_segment_data["start_point"]: end_segment_data["start_port"], end_segment_data["end_point"]: end_segment_data["end_port"], } - (start_point, end_point), (first_segment_start, second_segment_end) = tool.Cad.closest_points( - (start_segment_data["start_point"], start_segment_data["end_point"]), - (end_segment_data["start_point"], end_segment_data["end_point"]), + + get_z_basis = lambda o: o.matrix_world.col[2].normalized().to_3d() + segments_intersection_ws = tool.Cad.intersect_edges( + (start_object.location, start_object.location + get_z_basis(start_object)), + (end_object.location, end_object.location + get_z_basis(end_object)), + )[0] + + start_point, first_segment_start = tool.Cad.closest_and_furthest_vectors( + segments_intersection_ws, (start_segment_data["start_point"], start_segment_data["end_point"]) ) + end_point, second_segment_end = tool.Cad.closest_and_furthest_vectors( + segments_intersection_ws, (end_segment_data["start_point"], end_segment_data["end_point"]) + ) + start_port = points_ports_map[start_point] end_port = points_ports_map[end_point] start_point_on_origin = start_point == start_segment_data["start_point"] @@ -1032,7 +1042,6 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): return {"CANCELLED"} O = V(0, 0, 0) - get_z_basis = lambda o: o.matrix_world.col[2].normalized().to_3d() angle = tool.Cad.angle_edges((get_z_basis(start_object), O), (get_z_basis(end_object), O)) lateral_sign = tool.Cad.sign(profile_offset[lateral_axis]) @@ -1042,18 +1051,18 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): radial_offset.z = ref_point_radius * sin(angle) def get_segments_extend(): - end_segment_z_local = to_start_object_space @ get_z_basis(end_object) - segments_intersection = tool.Cad.intersect_edges( - (V(0, 0, 1), V(0, 0, 0)), (profile_offset + end_segment_z_local, profile_offset) - )[0] + segments_intersection = segments_intersection_ws - start_point + segments_intersection = to_start_object_space @ segments_intersection - curent_start_offset = segments_intersection.length - required_start_offset = abs(radial_offset.z) + # since tangent segments are equal + # if drawn for the circle from the same point + required_offset = ref_point_radius * tan(angle / 2) + + current_start_offset = segments_intersection.length current_end_offset = (segments_intersection - profile_offset).length - required_end_offset = abs(radial_offset[lateral_axis]) - start_extend = curent_start_offset - required_start_offset - end_extend = current_end_offset - required_end_offset + start_extend = current_start_offset - required_offset + end_extend = current_end_offset - required_offset return start_extend, end_extend @@ -1076,7 +1085,6 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): # adjust segments to fit the radius and angle start_segment_extend, end_segment_extend = get_segments_extend() - start_segment_extend_point = start_point + start_segment_sign * start_segment_extend * get_z_basis(start_object) projection = check_new_segment_length(first_segment_start, start_point, start_segment_extend_point) if projection is not None: @@ -1108,6 +1116,7 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): angle, self.radius / si_conversion, profile_offset / si_conversion, + flip_z_axis=start_segment_sign == -1, ) bpy.ops.bim.create_shape_from_step_id(step_id=rep.id(), should_include_curves=True) diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index 2544234f64..e2aa7e6a56 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -1367,9 +1367,7 @@ class DumbWallJoiner: clamp_axis = clamp_axis[::-1] vectors = tuple([clamp_point_by_direction(v, clamp_axis) for v in vectors]) - closest = tool.Cad.closest_vector(ref_point_2d.to_3d(), vectors) - farthest = vectors[1] if closest == vectors[0] else vectors[0] - return closest, farthest + return tool.Cad.closest_and_furthest_vectors(ref_point_2d.to_3d(), vectors) bbn, bbf = get_closest_and_furthest_vectors(axis1["base"][i], (bb1, bb2), bba1) bsn, bsf = get_closest_and_furthest_vectors(axis1["side"][i], (bs1, bs2)) diff --git a/src/blenderbim/blenderbim/tool/cad.py b/src/blenderbim/blenderbim/tool/cad.py index d45921ecab..aca5ee6e06 100644 --- a/src/blenderbim/blenderbim/tool/cad.py +++ b/src/blenderbim/blenderbim/tool/cad.py @@ -192,6 +192,18 @@ class Cad: distance_test = (v1 - pt).length >= (v2 - pt).length return v1 if distance_test else v2 + @classmethod + def closest_and_furthest_vectors(cls, pt, e): + """ + > pt: vector + > e: 2 vector tuple + < returns the two vectors closest to and furthest from pt. + """ + if isinstance(e, tuple) and all([isinstance(co, Vector) for co in e]): + closest = cls.closest_vector(pt, e) + furthest = e[1] if closest == e[0] else e[0] + return closest, furthest + @classmethod def coords_tuple_from_edge_idx(cls, bm, idx): """bm is a bmesh representation""" diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py index 1a6c5b9c12..7b8586a19b 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py @@ -1278,7 +1278,14 @@ class ShapeBuilder: return angle def mep_bend_shape( - self, segment, start_length: float, end_length: float, angle: float, radius: float, profile_offset: Vector + self, + segment, + start_length: float, + end_length: float, + angle: float, + radius: float, + profile_offset: Vector, + flip_z_axis: bool, ): """ @@ -1290,9 +1297,11 @@ class ShapeBuilder: :param radius: bend radius :param type: float :param profile_offset: offset between start and end segments in local space of start segment - used mainly to determine the bend axes and their direction. - Values themselves are replaced by the radius. + used mainly to determine the seconn bend axis and it's direction. :param type: Vector + :param flip_z_axis: since we cannot determine z axis direction from the profile offset, + there is an option to flip it if bend is going by start segment Z- axis. + :param type: bool :return: tuple of Model/Body/MODEL_VIEW IfcRepresentation and transition shape data """ @@ -1318,7 +1327,7 @@ class ShapeBuilder: lateral_axis = next(i for i in range(2) if not is_x(rounded_offset[i], 0)) non_lateral_axis = 1 if lateral_axis == 0 else 0 lateral_sign = sign(profile_offset[lateral_axis]) - z_sign = sign(profile_offset.z) + z_sign = -1 if flip_z_axis else 1 rep_items = []