diff --git a/src/blenderbim/blenderbim/bim/module/model/mep.py b/src/blenderbim/blenderbim/bim/module/model/mep.py index df53529232..4a8669d186 100644 --- a/src/blenderbim/blenderbim/bim/module/model/mep.py +++ b/src/blenderbim/blenderbim/bim/module/model/mep.py @@ -367,6 +367,7 @@ class MEPGenerator: si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) precision = VTX_PRECISION / si_conversion angle_precision = degrees(precision) + start_port_match = True segments_data = [] for segment, port in zip(segments, ports, strict=True): @@ -376,8 +377,9 @@ class MEPGenerator: return segments_data.append((segment_type, port.PredefinedType, port.SystemType)) - # TODO: test it with flipped transition where start length != end length def compatible_with_bbim_data(fitting_type): + nonlocal start_port_match + start_port_match = True if not bbim_data: return True fitting_type_obj = tool.Ifc.get_object(fitting_type) @@ -386,16 +388,39 @@ class MEPGenerator: return False fitting_bbim_data = fitting_bbim_data["data_dict"] - for key in bbim_data: + + def compare_value(key, second_key=None): + second_key = second_key or key requested_value = bbim_data[key] - fitting_value = fitting_bbim_data[key] + fitting_value = fitting_bbim_data[second_key] if isinstance(requested_value, float): compare_precision = angle_precision if key == "angle" else precision compare = tool.Cad.is_x(requested_value, fitting_value, compare_precision) elif isinstance(fitting_value, list): compare = tool.Cad.are_vectors_equal(requested_value, Vector(fitting_value), precision) - if not compare: + return compare + + ignore_keys = [] + if predefined_type == "BEND": + ignore_keys.extend(("start_length", "end_length")) + # for bends there is a special case when lengths might not match + # but fitting is still compatible if we flip it + # since bend connects segments of the same type + default_lengths_match = compare_value("start_length") and compare_value("end_length") + if not default_lengths_match: + switched_lengths_match = compare_value("start_length", "end_length") and compare_value( + "end_length", "start_length" + ) + if switched_lengths_match: + start_port_match = False + else: + return False + + for key in bbim_data: + if key in ignore_keys: + continue + if not compare_value(key): return False return True @@ -450,7 +475,7 @@ class MEPGenerator: connected_port = tool.System.get_connected_port(start_port) connected_element = tool.System.get_port_relating_element(connected_port) element_type = ifcopenshell.util.element.get_type(connected_element) - packed_data["start_port_match"] = element_type == segments_data[0][0] + packed_data["start_port_match"] = element_type == segments_data[0][0] and start_port_match return packed_data @@ -1031,11 +1056,17 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): if error_msg: self.report({"ERROR"}, error_msg) return {"CANCELLED"} + non_lateral_axis = 0 if lateral_axis == 1 else 1 - O = V(0, 0, 0) - angle = pi - tool.Cad.angle_edges( - (get_z_basis(start_object) * start_segment_sign, O), (get_z_basis(end_object) * end_segment_sign, O) - ) + def get_bend_rotation(): + O = V(0, 0, 0) + edge1 = (get_z_basis(start_object) * start_segment_sign, O) + edge2 = (get_z_basis(end_object) * end_segment_sign, O) + angle = pi - tool.Cad.angle_edges(edge1, edge2) + axis = (edge2[1] - edge2[0]).cross(edge1[1] - edge1[0]) + return angle, axis + + angle, rotation_axis = get_bend_rotation() lateral_sign = tool.Cad.sign(profile_offset[lateral_axis]) radial_offset = V(0, 0, 0) @@ -1126,27 +1157,20 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): bend_type = fitting_data["fitting_type"] if fitting_data else None start_port_match = fitting_data["start_port_match"] if fitting_data else True - rotate_lateral_axis = None - flip_z_axis_type = None + # use current segments axes if no fitting type found + lateral_axis_type = lateral_axis + lateral_sign_type = lateral_sign + z_sign_type = start_segment_sign + non_lateral_axis_type = non_lateral_axis if bend_type: bend_obj = tool.Ifc.get_object(bend_type) bbim_data = tool.Model.get_modeling_bbim_pset_data(bend_obj, "BBIM_Fitting")["data_dict"] lateral_axis_type, lateral_sign_type = bbim_data["lateral_axis"], bbim_data["lateral_sign"] - flip_z_axis_type = bbim_data["flip_z_axis"] - - # if the lateral axis of the compatible fitting type doesn't match - # with the current lateral bend lateral axis, we'll adjust the rotation - if (lateral_axis_type != lateral_axis) or (lateral_sign_type != lateral_sign): - m = Matrix.Identity(3) - current_lateral_axis = m[lateral_axis] * lateral_sign - type_lateral_axis = m[lateral_axis_type] * lateral_sign_type - if lateral_axis_type == lateral_axis: - rotation_axis = m[2] # just V(0,0,1) - else: - rotation_axis = type_lateral_axis.cross(current_lateral_axis) - rotation_angle = current_lateral_axis.angle(type_lateral_axis) - # rotation axis is always Z but need to consider the rotation direction - rotate_lateral_axis = Matrix.Rotation(rotation_angle, 4, rotation_axis) + non_lateral_axis_type = 0 if lateral_axis_type == 1 else 1 + z_sign_type = bbim_data.get("z_axis_sign", None) + # TODO: drop flip_z_axis a bit later + if z_sign_type is None: + z_sign_type = -1 if bbim_data["flip_z_axis"] else 1 # TODO: handle the case without creating a representation in the first place? ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep) @@ -1182,18 +1206,36 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): fitting_obj.matrix_world = start_object.matrix_world context.view_layer.update() - # depending on fitting 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 fitting type before + # depending on bend direction we may need to rotate it to match + # we just calculate the matrix basises - it's simpler than describing all possible conditions + def get_fitting_matrix(): + matrix = Matrix.Identity(3) + start_object_z_basis = tool.Cad.get_basis_vector(start_object, 2) + start_object_lateral_basis = tool.Cad.get_basis_vector(start_object, lateral_axis) - direction_match = True if flip_z_axis_type is None else (flip_z_axis_type == (start_segment_sign == -1)) - # if there are no mismatches or everything matches up we don't need to flip the transition - if start_port_match != direction_match: - fitting_obj.matrix_world = start_object.matrix_world @ Matrix.Rotation(radians(180), 4, "XY"[lateral_axis]) - if rotate_lateral_axis: - fitting_obj.matrix_world = fitting_obj.matrix_world @ rotate_lateral_axis - fitting_obj.location = start_segment_extend_point if start_port_match else end_segment_extend_point + def axis_direction(current_axis_sign, type_axis_sign): + return -1 if current_axis_sign != type_axis_sign else 1 + + matrix.col[2] = start_object_z_basis * axis_direction(start_segment_sign, z_sign_type) + matrix.col[lateral_axis_type] = start_object_lateral_basis * axis_direction(lateral_sign, lateral_sign_type) + if not start_port_match: + matrix.col[2] *= -1 + + if non_lateral_axis_type == 0: + non_lateral_axis = matrix.col[lateral_axis_type].cross(matrix.col[2]) + else: + non_lateral_axis = matrix.col[2].cross(matrix.col[lateral_axis_type]) + matrix.col[non_lateral_axis_type] = non_lateral_axis + + if not start_port_match: + angle_sign = np.sign(rotation_axis.dot(non_lateral_axis)) + matrix = matrix @ Matrix.Rotation(angle * angle_sign, 3, "XY"[non_lateral_axis_type]) + + matrix = matrix.to_4x4() + matrix.translation = start_segment_extend_point if start_port_match else end_segment_extend_point + return matrix + + fitting_obj.matrix_world = get_fitting_matrix() # add ports and connect them ports = tool.System.add_ports(fitting_obj, offset_end_port=start_object_rotation @ (radial_offset * V(1, 1, 0))) diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py index b85ab7b411..43822dfc6c 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py @@ -1456,7 +1456,7 @@ class ShapeBuilder: "angle": degrees(theta), "lateral_axis": lateral_axis, "lateral_sign": lateral_sign, - "flip_z_axis": flip_z_axis, + "z_axis_sign": -1 if flip_z_axis else 1, "main_profile_dimension": profile_dim[lateral_axis], } return rep, bend_data