mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-17 14:02:27 +00:00
fixed issue adding bend between segments located at the same point
also added some tests
This commit is contained in:
@@ -311,6 +311,7 @@ class MEPGenerator:
|
|||||||
profile_joiner.set_depth(connected_obj, connected_element_length)
|
profile_joiner.set_depth(connected_obj, connected_element_length)
|
||||||
|
|
||||||
def get_segment_data(self, segment):
|
def get_segment_data(self, segment):
|
||||||
|
"""returns points data is in world space"""
|
||||||
ports = tool.System.get_ports(segment)
|
ports = tool.System.get_ports(segment)
|
||||||
segment_object = tool.Ifc.get_object(segment)
|
segment_object = tool.Ifc.get_object(segment)
|
||||||
start_point = segment_object.location
|
start_point = segment_object.location
|
||||||
@@ -1025,7 +1026,14 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
# TODO: profile offset may need to be flipped (check transition code)
|
# TODO: profile offset may need to be flipped (check transition code)
|
||||||
to_start_object_space = start_object_rotation.inverted()
|
to_start_object_space = start_object_rotation.inverted()
|
||||||
profile_offset = (to_start_object_space @ end_point) - (to_start_object_space @ start_point)
|
ref_point = end_point.copy()
|
||||||
|
end_segment_dir = (second_segment_end - end_point).normalized()
|
||||||
|
# we prioritize direction between end_point and start_point for bend_vector
|
||||||
|
# if those point match we use general end segment direction
|
||||||
|
if tool.Cad.is_x((end_point - start_point).length, 0):
|
||||||
|
ref_point = end_point + end_segment_dir
|
||||||
|
bend_vector = (to_start_object_space @ ref_point) - (to_start_object_space @ start_point)
|
||||||
|
|
||||||
z_axis_end_object_local = to_start_object_space @ tool.Cad.get_basis_vector(end_object, 2)
|
z_axis_end_object_local = to_start_object_space @ tool.Cad.get_basis_vector(end_object, 2)
|
||||||
|
|
||||||
def check_for_double_bends():
|
def check_for_double_bends():
|
||||||
@@ -1049,7 +1057,7 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
non_lateral_axis = 0 if lateral_axes[0] == 1 else 1
|
non_lateral_axis = 0 if lateral_axes[0] == 1 else 1
|
||||||
non_lateral_axis_offset = profile_offset[non_lateral_axis]
|
non_lateral_axis_offset = bend_vector[non_lateral_axis]
|
||||||
if not tool.Cad.is_x(non_lateral_axis_offset, 0):
|
if not tool.Cad.is_x(non_lateral_axis_offset, 0):
|
||||||
return (
|
return (
|
||||||
None,
|
None,
|
||||||
@@ -1075,7 +1083,7 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
angle, rotation_axis = get_bend_rotation()
|
angle, rotation_axis = get_bend_rotation()
|
||||||
|
|
||||||
lateral_sign = tool.Cad.sign(profile_offset[lateral_axis])
|
lateral_sign = tool.Cad.sign(bend_vector[lateral_axis])
|
||||||
radial_offset = V(0, 0, 0)
|
radial_offset = V(0, 0, 0)
|
||||||
ref_point_radius = self.radius + profile_dim[lateral_axis]
|
ref_point_radius = self.radius + profile_dim[lateral_axis]
|
||||||
radial_offset[lateral_axis] = ref_point_radius * (1 - cos(angle)) * lateral_sign
|
radial_offset[lateral_axis] = ref_point_radius * (1 - cos(angle)) * lateral_sign
|
||||||
@@ -1083,21 +1091,19 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
end_port_offset = radial_offset + V(0, 0, self.start_length * start_segment_sign)
|
end_port_offset = radial_offset + V(0, 0, self.start_length * start_segment_sign)
|
||||||
end_port_offset += z_axis_end_object_local * (self.end_length * -end_segment_sign)
|
end_port_offset += z_axis_end_object_local * (self.end_length * -end_segment_sign)
|
||||||
|
|
||||||
def get_segments_extend():
|
def get_segments_extend_points():
|
||||||
segments_intersection = segments_intersection_ws - start_point
|
|
||||||
segments_intersection = to_start_object_space @ segments_intersection
|
|
||||||
|
|
||||||
# since tangent segments are equal
|
# since tangent segments are equal
|
||||||
# if drawn for the circle from the same point
|
# if drawn for the circle from the same point
|
||||||
required_offset = ref_point_radius * tan(angle / 2)
|
required_offset = ref_point_radius * tan(angle / 2)
|
||||||
|
|
||||||
current_start_offset = segments_intersection.length
|
start_segment_extend_point = segments_intersection_ws - start_segment_sign * (
|
||||||
current_end_offset = (segments_intersection - profile_offset).length
|
self.start_length + required_offset
|
||||||
|
) * get_z_basis(start_object)
|
||||||
|
end_segment_extend_point = segments_intersection_ws - end_segment_sign * (
|
||||||
|
self.end_length + required_offset
|
||||||
|
) * get_z_basis(end_object)
|
||||||
|
|
||||||
start_extend = current_start_offset - (required_offset + self.start_length)
|
return start_segment_extend_point, end_segment_extend_point
|
||||||
end_extend = current_end_offset - (required_offset + self.end_length)
|
|
||||||
|
|
||||||
return start_extend, end_extend
|
|
||||||
|
|
||||||
def check_new_segment_length(start_point, end_point, extend_point):
|
def check_new_segment_length(start_point, end_point, extend_point):
|
||||||
"""Check if segment is placed too near to the bend point.
|
"""Check if segment is placed too near to the bend point.
|
||||||
@@ -1117,8 +1123,7 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# adjust segments to fit the radius and angle
|
# adjust segments to fit the radius and angle
|
||||||
start_segment_extend, end_segment_extend = get_segments_extend()
|
start_segment_extend_point, end_segment_extend_point = get_segments_extend_points()
|
||||||
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)
|
projection = check_new_segment_length(first_segment_start, start_point, start_segment_extend_point)
|
||||||
if projection is not None:
|
if projection is not None:
|
||||||
self.report(
|
self.report(
|
||||||
@@ -1127,7 +1132,6 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
)
|
)
|
||||||
return {"ERROR"}
|
return {"ERROR"}
|
||||||
|
|
||||||
end_segment_extend_point = end_point + end_segment_sign * end_segment_extend * get_z_basis(end_object)
|
|
||||||
projection = check_new_segment_length(second_segment_end, end_point, end_segment_extend_point)
|
projection = check_new_segment_length(second_segment_end, end_point, end_segment_extend_point)
|
||||||
if projection is not None:
|
if projection is not None:
|
||||||
self.report(
|
self.report(
|
||||||
@@ -1148,7 +1152,7 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
self.end_length / si_conversion,
|
self.end_length / si_conversion,
|
||||||
angle,
|
angle,
|
||||||
self.radius / si_conversion,
|
self.radius / si_conversion,
|
||||||
profile_offset / si_conversion,
|
bend_vector / si_conversion,
|
||||||
flip_z_axis=start_segment_sign == -1,
|
flip_z_axis=start_segment_sign == -1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -530,6 +530,7 @@ Scenario: Create a MEP transition
|
|||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
And I create default MEP types
|
And I create default MEP types
|
||||||
And the variable "element_types" is "[str(e.id()) for e in {ifc}.by_type('IfcDuctSegmentType')]"
|
And the variable "element_types" is "[str(e.id()) for e in {ifc}.by_type('IfcDuctSegmentType')]"
|
||||||
|
|
||||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_types}[0]"
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_types}[0]"
|
||||||
And I press "bim.add_constr_type_instance"
|
And I press "bim.add_constr_type_instance"
|
||||||
And I rename the object "IfcDuctSegment/DuctSegment" to "IfcDuctSegment/RectSegment"
|
And I rename the object "IfcDuctSegment/DuctSegment" to "IfcDuctSegment/RectSegment"
|
||||||
@@ -542,11 +543,70 @@ Scenario: Create a MEP transition
|
|||||||
And the object "IfcDuctSegment/CircleSegment" is moved to "2.5,0,0"
|
And the object "IfcDuctSegment/CircleSegment" is moved to "2.5,0,0"
|
||||||
And the object "IfcDuctSegment/CircleSegment" is selected
|
And the object "IfcDuctSegment/CircleSegment" is selected
|
||||||
And additionally the object "IfcDuctSegment/RectSegment" is selected
|
And additionally the object "IfcDuctSegment/RectSegment" is selected
|
||||||
And I press "bim.mep_add_transition()"
|
And I press "bim.mep_add_transition"
|
||||||
|
|
||||||
Then the object "IfcDuctSegment/RectSegment" is at "0,0,0"
|
Then the object "IfcDuctFitting/DuctFitting" exists
|
||||||
|
And the object "IfcDuctFittingType/Transition" exists
|
||||||
|
And the object "IfcDuctSegment/RectSegment" is at "0,0,0"
|
||||||
And the object "IfcDuctSegment/RectSegment" dimensions are "0.4,0.2,2.370096"
|
And the object "IfcDuctSegment/RectSegment" dimensions are "0.4,0.2,2.370096"
|
||||||
And the object "IfcDuctSegment/CircleSegment" is at "3.1299,0,0"
|
And the object "IfcDuctSegment/CircleSegment" is at "3.1299,0,0"
|
||||||
And the object "IfcDuctSegment/CircleSegment" dimensions are "0.1000, 0.09927, 2.370096"
|
And the object "IfcDuctSegment/CircleSegment" dimensions are "0.1000, 0.09927, 2.370096"
|
||||||
And the object "IfcDuctFitting/DuctFitting" is at "2.370096, 0.0000, 0.0000"
|
And the object "IfcDuctFitting/DuctFitting" is at "2.370096, 0.0000, 0.0000"
|
||||||
And the object "IfcDuctFitting/DuctFitting" dimensions are "0.4000, 0.2000, 0.759807"
|
And the object "IfcDuctFitting/DuctFitting" dimensions are "0.4000, 0.2000, 0.759807"
|
||||||
|
|
||||||
|
Scenario: Create a MEP bend between intersecting with different locations
|
||||||
|
Given an empty IFC project
|
||||||
|
And I create default MEP types
|
||||||
|
And the variable "element_types" is "[str(e.id()) for e in {ifc}.by_type('IfcDuctSegmentType')]"
|
||||||
|
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_types}[0]"
|
||||||
|
And I set "scene.BIMModelProperties.extrusion_depth" to "5.0"
|
||||||
|
And I press "bim.add_constr_type_instance"
|
||||||
|
And I rename the object "IfcDuctSegment/DuctSegment" to "IfcDuctSegment/Seg1"
|
||||||
|
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_types}[0]"
|
||||||
|
And I press "bim.add_constr_type_instance"
|
||||||
|
And I rename the object "IfcDuctSegment/DuctSegment" to "IfcDuctSegment/Seg2"
|
||||||
|
And the object "IfcDuctSegment/Seg2" is rotated by "0,0,90" deg
|
||||||
|
|
||||||
|
And the object "IfcDuctSegment/Seg2" is moved to "6,1,1"
|
||||||
|
And the object "IfcDuctSegment/Seg1" is selected
|
||||||
|
And additionally the object "IfcDuctSegment/Seg2" is selected
|
||||||
|
And I press "bim.mep_add_bend"
|
||||||
|
|
||||||
|
Then the object "IfcDuctFitting/DuctFitting" exists
|
||||||
|
And the object "IfcDuctFittingType/Bend" exists
|
||||||
|
And the object "IfcDuctSegment/Seg1" is at "0,0,1.0"
|
||||||
|
And the object "IfcDuctSegment/Seg1" dimensions are "0.4,0.2,5.5"
|
||||||
|
And the object "IfcDuctSegment/Seg2" is at "6.0,0.5,1.0"
|
||||||
|
And the object "IfcDuctSegment/Seg2" dimensions are "0.4,0.2,5.5"
|
||||||
|
And the object "IfcDuctFitting/DuctFitting" is at "6.0, 0.5, 1.0"
|
||||||
|
And the object "IfcDuctFitting/DuctFitting" dimensions are "0.7, 0.2, 0.7"
|
||||||
|
|
||||||
|
Scenario: Create a MEP bend between intersecting segments at the same location
|
||||||
|
Given an empty IFC project
|
||||||
|
And I create default MEP types
|
||||||
|
And the variable "element_types" is "[str(e.id()) for e in {ifc}.by_type('IfcDuctSegmentType')]"
|
||||||
|
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_types}[0]"
|
||||||
|
And I set "scene.BIMModelProperties.extrusion_depth" to "5.0"
|
||||||
|
And I press "bim.add_constr_type_instance"
|
||||||
|
And I rename the object "IfcDuctSegment/DuctSegment" to "IfcDuctSegment/Seg1"
|
||||||
|
|
||||||
|
And I set "scene.BIMModelProperties.relating_type_id" to "{element_types}[0]"
|
||||||
|
And I press "bim.add_constr_type_instance"
|
||||||
|
And I rename the object "IfcDuctSegment/DuctSegment" to "IfcDuctSegment/Seg2"
|
||||||
|
And the object "IfcDuctSegment/Seg2" is rotated by "0,0,90" deg
|
||||||
|
|
||||||
|
And the object "IfcDuctSegment/Seg1" is selected
|
||||||
|
And additionally the object "IfcDuctSegment/Seg2" is selected
|
||||||
|
And I press "bim.mep_add_bend"
|
||||||
|
|
||||||
|
Then the object "IfcDuctFitting/DuctFitting" exists
|
||||||
|
And the object "IfcDuctFittingType/Bend" exists
|
||||||
|
And the object "IfcDuctSegment/Seg1" is at "0.5,0,1.0"
|
||||||
|
And the object "IfcDuctSegment/Seg1" dimensions are "0.4,0.2,4.5"
|
||||||
|
And the object "IfcDuctSegment/Seg2" is at "0.0,0.5,1.0"
|
||||||
|
And the object "IfcDuctSegment/Seg2" dimensions are "0.4,0.2,4.5"
|
||||||
|
And the object "IfcDuctFitting/DuctFitting" is at "0.0, 0.5, 1.0"
|
||||||
|
And the object "IfcDuctFitting/DuctFitting" dimensions are "0.7, 0.2, 0.7"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from blenderbim.tool.brick import BrickStore
|
|||||||
from blenderbim.bim.module.model.data import AuthoringData
|
from blenderbim.bim.module.model.data import AuthoringData
|
||||||
from pytest_bdd import scenarios, given, when, then, parsers
|
from pytest_bdd import scenarios, given, when, then, parsers
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
|
from math import radians
|
||||||
|
|
||||||
scenarios("feature")
|
scenarios("feature")
|
||||||
|
|
||||||
@@ -241,13 +242,23 @@ def then_the_object_name_is_selected(name):
|
|||||||
assert obj in bpy.context.selected_objects
|
assert obj in bpy.context.selected_objects
|
||||||
|
|
||||||
|
|
||||||
|
@given(parsers.parse('the object "{name}" is rotated by "{rotation_deg}" deg'))
|
||||||
|
@when(parsers.parse('the object "{name}" is rotated by "{rotation_deg}" deg'))
|
||||||
|
def the_object_name_is_rotated_by(name, rotation_deg):
|
||||||
|
rotation_deg = [radians(float(rot)) for rot in rotation_deg.split(",")]
|
||||||
|
obj = the_object_name_exists(name)
|
||||||
|
obj.rotation_euler[0] += rotation_deg[0]
|
||||||
|
obj.rotation_euler[1] += rotation_deg[1]
|
||||||
|
obj.rotation_euler[2] += rotation_deg[2]
|
||||||
|
bpy.context.view_layer.update() # make sure matrix is updated
|
||||||
|
|
||||||
|
|
||||||
@given(parsers.parse('the object "{name}" is moved to "{location}"'))
|
@given(parsers.parse('the object "{name}" is moved to "{location}"'))
|
||||||
@when(parsers.parse('the object "{name}" is moved to "{location}"'))
|
@when(parsers.parse('the object "{name}" is moved to "{location}"'))
|
||||||
def the_object_name_is_moved_to_location(name, location):
|
def the_object_name_is_moved_to_location(name, location):
|
||||||
location = [float(co) for co in location.split(",")]
|
location = [float(co) for co in location.split(",")]
|
||||||
the_object_name_exists(name).matrix_world[0][3] = location[0]
|
obj = the_object_name_exists(name)
|
||||||
the_object_name_exists(name).matrix_world[1][3] = location[1]
|
obj.matrix_world.translation = location
|
||||||
the_object_name_exists(name).matrix_world[2][3] = location[2]
|
|
||||||
|
|
||||||
|
|
||||||
@given(parsers.parse('the object "{name}" is scaled to "{scale}"'))
|
@given(parsers.parse('the object "{name}" is scaled to "{scale}"'))
|
||||||
|
|||||||
@@ -1172,10 +1172,9 @@ class ShapeBuilder:
|
|||||||
"""
|
"""
|
||||||
print = lambda *args, **kwargs: __builtins__["print"](*args, **kwargs) if verbose else None
|
print = lambda *args, **kwargs: __builtins__["print"](*args, **kwargs) if verbose else None
|
||||||
|
|
||||||
# offsets tend to have bunch of float point garbage
|
# vectors tend to have bunch of float point garbage
|
||||||
# that can result in errors when we're calculating value for square root below
|
# that can result in errors when we're calculating value for square root below
|
||||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
offset = round_vector_to_precision(profile_offset, 1)
|
||||||
offset = round_vector_to_precision(profile_offset, si_conversion)
|
|
||||||
diff = start_half_dim.xy - end_half_dim.xy
|
diff = start_half_dim.xy - end_half_dim.xy
|
||||||
diff = Vector([abs(i) for i in diff])
|
diff = Vector([abs(i) for i in diff])
|
||||||
|
|
||||||
@@ -1320,7 +1319,7 @@ class ShapeBuilder:
|
|||||||
end_length: float,
|
end_length: float,
|
||||||
angle: float,
|
angle: float,
|
||||||
radius: float,
|
radius: float,
|
||||||
profile_offset: Vector,
|
bend_vector: Vector,
|
||||||
flip_z_axis: bool,
|
flip_z_axis: bool,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -1332,8 +1331,9 @@ class ShapeBuilder:
|
|||||||
:param type: float
|
:param type: float
|
||||||
:param radius: bend radius
|
:param radius: bend radius
|
||||||
:param type: float
|
:param type: float
|
||||||
:param profile_offset: offset between start and end segments in local space of start segment
|
:param bend_vector: offset between start and end segments in local space of start segment
|
||||||
used mainly to determine the seconn bend axis and it's direction.
|
used mainly to determine the second bend axis and it's direction (positive or negative),
|
||||||
|
the actual magnitude of the vector is not important (though near zero values will be ignored).
|
||||||
:param type: Vector
|
:param type: Vector
|
||||||
:param flip_z_axis: since we cannot determine z axis direction from the profile offset,
|
: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.
|
there is an option to flip it if bend is going by start segment Z- axis.
|
||||||
@@ -1359,10 +1359,10 @@ class ShapeBuilder:
|
|||||||
is_circular_profile = profile.is_a("IfcCircleProfileDef")
|
is_circular_profile = profile.is_a("IfcCircleProfileDef")
|
||||||
profile_dim = get_dim(profile, start_length)
|
profile_dim = get_dim(profile, start_length)
|
||||||
|
|
||||||
rounded_offset = round_vector_to_precision(profile_offset, si_conversion)
|
rounded_bend_vector = round_vector_to_precision(bend_vector, si_conversion)
|
||||||
lateral_axis = next(i for i in range(2) if not is_x(rounded_offset[i], 0))
|
lateral_axis = next(i for i in range(2) if not is_x(rounded_bend_vector[i], 0))
|
||||||
non_lateral_axis = 1 if lateral_axis == 0 else 0
|
non_lateral_axis = 1 if lateral_axis == 0 else 0
|
||||||
lateral_sign = sign(profile_offset[lateral_axis])
|
lateral_sign = sign(bend_vector[lateral_axis])
|
||||||
z_sign = -1 if flip_z_axis else 1
|
z_sign = -1 if flip_z_axis else 1
|
||||||
|
|
||||||
rep_items = []
|
rep_items = []
|
||||||
|
|||||||
Reference in New Issue
Block a user