mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 06:39:13 +00:00
MEP bends - fixed bunch of bugs related to adjusting already existing bends types
This commit is contained in:
@@ -367,6 +367,7 @@ class MEPGenerator:
|
|||||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||||
precision = VTX_PRECISION / si_conversion
|
precision = VTX_PRECISION / si_conversion
|
||||||
angle_precision = degrees(precision)
|
angle_precision = degrees(precision)
|
||||||
|
start_port_match = True
|
||||||
|
|
||||||
segments_data = []
|
segments_data = []
|
||||||
for segment, port in zip(segments, ports, strict=True):
|
for segment, port in zip(segments, ports, strict=True):
|
||||||
@@ -376,8 +377,9 @@ class MEPGenerator:
|
|||||||
return
|
return
|
||||||
segments_data.append((segment_type, port.PredefinedType, port.SystemType))
|
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):
|
def compatible_with_bbim_data(fitting_type):
|
||||||
|
nonlocal start_port_match
|
||||||
|
start_port_match = True
|
||||||
if not bbim_data:
|
if not bbim_data:
|
||||||
return True
|
return True
|
||||||
fitting_type_obj = tool.Ifc.get_object(fitting_type)
|
fitting_type_obj = tool.Ifc.get_object(fitting_type)
|
||||||
@@ -386,16 +388,39 @@ class MEPGenerator:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
fitting_bbim_data = fitting_bbim_data["data_dict"]
|
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]
|
requested_value = bbim_data[key]
|
||||||
fitting_value = fitting_bbim_data[key]
|
fitting_value = fitting_bbim_data[second_key]
|
||||||
|
|
||||||
if isinstance(requested_value, float):
|
if isinstance(requested_value, float):
|
||||||
compare_precision = angle_precision if key == "angle" else precision
|
compare_precision = angle_precision if key == "angle" else precision
|
||||||
compare = tool.Cad.is_x(requested_value, fitting_value, compare_precision)
|
compare = tool.Cad.is_x(requested_value, fitting_value, compare_precision)
|
||||||
elif isinstance(fitting_value, list):
|
elif isinstance(fitting_value, list):
|
||||||
compare = tool.Cad.are_vectors_equal(requested_value, Vector(fitting_value), precision)
|
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 False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -450,7 +475,7 @@ class MEPGenerator:
|
|||||||
connected_port = tool.System.get_connected_port(start_port)
|
connected_port = tool.System.get_connected_port(start_port)
|
||||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||||
element_type = ifcopenshell.util.element.get_type(connected_element)
|
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
|
return packed_data
|
||||||
|
|
||||||
@@ -1031,11 +1056,17 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if error_msg:
|
if error_msg:
|
||||||
self.report({"ERROR"}, error_msg)
|
self.report({"ERROR"}, error_msg)
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
non_lateral_axis = 0 if lateral_axis == 1 else 1
|
||||||
|
|
||||||
O = V(0, 0, 0)
|
def get_bend_rotation():
|
||||||
angle = pi - tool.Cad.angle_edges(
|
O = V(0, 0, 0)
|
||||||
(get_z_basis(start_object) * start_segment_sign, O), (get_z_basis(end_object) * end_segment_sign, O)
|
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])
|
lateral_sign = tool.Cad.sign(profile_offset[lateral_axis])
|
||||||
radial_offset = V(0, 0, 0)
|
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
|
bend_type = fitting_data["fitting_type"] if fitting_data else None
|
||||||
start_port_match = fitting_data["start_port_match"] if fitting_data else True
|
start_port_match = fitting_data["start_port_match"] if fitting_data else True
|
||||||
|
|
||||||
rotate_lateral_axis = None
|
# use current segments axes if no fitting type found
|
||||||
flip_z_axis_type = None
|
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:
|
if bend_type:
|
||||||
bend_obj = tool.Ifc.get_object(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"]
|
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"]
|
lateral_axis_type, lateral_sign_type = bbim_data["lateral_axis"], bbim_data["lateral_sign"]
|
||||||
flip_z_axis_type = bbim_data["flip_z_axis"]
|
non_lateral_axis_type = 0 if lateral_axis_type == 1 else 1
|
||||||
|
z_sign_type = bbim_data.get("z_axis_sign", None)
|
||||||
# if the lateral axis of the compatible fitting type doesn't match
|
# TODO: drop flip_z_axis a bit later
|
||||||
# with the current lateral bend lateral axis, we'll adjust the rotation
|
if z_sign_type is None:
|
||||||
if (lateral_axis_type != lateral_axis) or (lateral_sign_type != lateral_sign):
|
z_sign_type = -1 if bbim_data["flip_z_axis"] else 1
|
||||||
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)
|
|
||||||
|
|
||||||
# TODO: handle the case without creating a representation in the first place?
|
# TODO: handle the case without creating a representation in the first place?
|
||||||
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep)
|
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
|
fitting_obj.matrix_world = start_object.matrix_world
|
||||||
context.view_layer.update()
|
context.view_layer.update()
|
||||||
|
|
||||||
# depending on fitting direction we may need to flip it or attach it's origin to end segment
|
# depending on bend direction we may need to rotate it to match
|
||||||
# direction can be different depending on:
|
# we just calculate the matrix basises - it's simpler than describing all possible conditions
|
||||||
# - order of the current segments
|
def get_fitting_matrix():
|
||||||
# - order of the segments that were used with the same fitting type before
|
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))
|
def axis_direction(current_axis_sign, type_axis_sign):
|
||||||
# if there are no mismatches or everything matches up we don't need to flip the transition
|
return -1 if current_axis_sign != type_axis_sign else 1
|
||||||
if start_port_match != direction_match:
|
|
||||||
fitting_obj.matrix_world = start_object.matrix_world @ Matrix.Rotation(radians(180), 4, "XY"[lateral_axis])
|
matrix.col[2] = start_object_z_basis * axis_direction(start_segment_sign, z_sign_type)
|
||||||
if rotate_lateral_axis:
|
matrix.col[lateral_axis_type] = start_object_lateral_basis * axis_direction(lateral_sign, lateral_sign_type)
|
||||||
fitting_obj.matrix_world = fitting_obj.matrix_world @ rotate_lateral_axis
|
if not start_port_match:
|
||||||
fitting_obj.location = start_segment_extend_point if start_port_match else end_segment_extend_point
|
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
|
# add ports and connect them
|
||||||
ports = tool.System.add_ports(fitting_obj, offset_end_port=start_object_rotation @ (radial_offset * V(1, 1, 0)))
|
ports = tool.System.add_ports(fitting_obj, offset_end_port=start_object_rotation @ (radial_offset * V(1, 1, 0)))
|
||||||
|
|||||||
@@ -1456,7 +1456,7 @@ class ShapeBuilder:
|
|||||||
"angle": degrees(theta),
|
"angle": degrees(theta),
|
||||||
"lateral_axis": lateral_axis,
|
"lateral_axis": lateral_axis,
|
||||||
"lateral_sign": lateral_sign,
|
"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],
|
"main_profile_dimension": profile_dim[lateral_axis],
|
||||||
}
|
}
|
||||||
return rep, bend_data
|
return rep, bend_data
|
||||||
|
|||||||
Reference in New Issue
Block a user