Fixed zero length segment for vertical alignment. Refactors curve segment transition code

This commit is contained in:
Richard Brice
2025-08-14 13:56:36 -07:00
parent 6bc318b15c
commit 1fcf2fb53f
4 changed files with 50 additions and 26 deletions
@@ -143,6 +143,9 @@ def _map_circular_arc(file: ifcopenshell.file, design_parameters: entity_instanc
end_angle = math.atan(end_gradient)
dx = math.cos(start_angle)
dy = math.sin(start_angle)
# start and end angles are for the curve tangents
# convert them to be angles of the radii lines
if start_angle < end_angle:
radius = horizontal_length / (math.sin(end_angle) - math.sin(start_angle))
x = -radius * math.sin(start_angle)
@@ -158,14 +161,12 @@ def _map_circular_arc(file: ifcopenshell.file, design_parameters: entity_instanc
parent_curve = file.createIfcCircle(
Position=file.createIfcAxis2Placement2D(
Location=file.createIfcCartesianPoint((0.0, 0.0)),
Location=file.createIfcCartesianPoint((x, y)),
RefDirection=file.createIfcDirection((1.0, 0.0)),
),
Radius=radius,
)
segment_curve_length = radius * math.fabs(end_angle - start_angle)
curve_segment = file.create_entity(
type="IfcCurveSegment",
Transition=transition,
@@ -27,6 +27,7 @@ import math
from ifcopenshell.api.alignment._get_segment_start_point_label import _get_segment_start_point_label
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import _map_alignment_horizontal_segment
from ifcopenshell.api.alignment._map_alignment_vertical_segment import _map_alignment_vertical_segment
from ifcopenshell.api.alignment._update_curve_segment_transition_code import _update_curve_segment_transition_code
@@ -157,20 +158,35 @@ def add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance, in
)
elif layout.is_a("IfcAlignmentVertical"):
last_segment_dist_along = 0.0
last_segment_height = 0.0
last_segment_end_gradient = 0.0
last_segment = None
for rel in layout.IsNestedBy:
if 0 < len(rel.RelatedObjects):
last_segment = rel.RelatedObjects[-1]
last_segment_dist_along = (
last_segment.DesignParameters.StartDistAlong + last_segment.DesignParameters.HorizontalLength
)
last_segment_end_gradient = last_segment.DesignParameters.EndGradient
break
if last_segment:
file.begin_transaction()
last_segment_dist_along = (
last_segment.DesignParameters.StartDistAlong + last_segment.DesignParameters.HorizontalLength
)
last_segment_end_gradient = last_segment.DesignParameters.EndGradient
settings = ifcopenshell.geom.settings()
mapped_segments = _map_alignment_vertical_segment(file, last_segment)
geometry_segment = mapped_segments[0] if mapped_segments[1] == None else mapped_segments[1]
fn = wrapper.map_shape(settings, geometry_segment.wrapped_data)
eval = wrapper.function_item_evaluator(settings, fn)
e = np.array(eval.evaluate(fn.end()))
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
last_segment_height = float(e[1, 3]) / unit_scale
file.discard_transaction()
design_parameters = file.createIfcAlignmentVerticalSegment(
StartDistAlong=last_segment_dist_along,
HorizontalLength=0.0,
StartHeight=0.0,
StartHeight=last_segment_height,
StartGradient=last_segment_end_gradient,
EndGradient=last_segment_end_gradient,
PredefinedType="CONSTANTGRADIENT",
@@ -26,47 +26,52 @@ import math
def get_curve_segment_transition_code(
prev_segment: entity_instance, segment: entity_instance, tolerance: float = 5.0e-4
segment: entity_instance, next_segment: entity_instance, position_tolerance: float = 0.001
) -> str:
"""
Returns the IfcCurveSegment.Transition of prev_segment based on a comparison of
the position, ref. direction, and curvature at the end of the prev_segment and the start of segment.
Returns the IfcCurveSegment.Transition of segment based on a comparison of
the position, ref. direction, and curvature at the end of the segment and the start of next_segment.
:param segment: segment for which the position curve is being being determined
:param next_segment: next segment
:param position_tolerance: tolerance used for evaluation positions. The default is 1mm
:return: the transition code
"""
expected_type = "IfcCurveSegment"
if not prev_segment.is_a(expected_type):
raise TypeError(f"Expected to see '{expected_type}', instead received '{prev_segment.is_a()}'.")
if not segment.is_a(expected_type):
raise TypeError(f"Expected to see '{expected_type}', instead received '{segment.is_a()}'.")
if len(prev_segment.UsingCurves) != 1:
raise TypeError("prev_segment must belong to exactly one curve")
if not next_segment.is_a(expected_type):
raise TypeError(f"Expected to see '{expected_type}', instead received '{next_segment.is_a()}'.")
if len(segment.UsingCurves) != 1:
raise TypeError("segment must belong to exactly one curve")
if prev_segment.UsingCurves[0] != segment.UsingCurves[0]:
if len(next_segment.UsingCurves) != 1:
raise TypeError("next_segment must belong to exactly one curve")
if segment.UsingCurves[0] != next_segment.UsingCurves[0]:
raise TypeError("Both segments must belong to the same curve")
settings = ifcopenshell.geom.settings()
settings.set("COMPUTE_CURVATURE", True)
prev_segment_fn = ifcopenshell_wrapper.map_shape(settings, prev_segment.wrapped_data)
prev_segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, prev_segment_fn)
e = prev_segment_evaluator.evaluate(prev_segment_fn.end())
segment_fn = ifcopenshell_wrapper.map_shape(settings, segment.wrapped_data)
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
e = segment_evaluator.evaluate(segment_fn.end())
end = np.array(e)
# must add the new segment to the container before mapping it, otherwise the segment doesn't
# have enough context to know if it is for horizontal, vertical, cant
segment_fn = ifcopenshell_wrapper.map_shape(settings, segment.wrapped_data)
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
s = segment_evaluator.evaluate(segment_fn.start())
next_segment_fn = ifcopenshell_wrapper.map_shape(settings, next_segment.wrapped_data)
next_segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, next_segment_fn)
s = next_segment_evaluator.evaluate(next_segment_fn.start())
start = np.array(s)
same_position = True if np.allclose(end[:3, 3], start[:3, 3], atol=tolerance) else False
same_gradient = True if np.allclose(end[:3, 0], start[:3, 0], atol=tolerance) else False
same_curvature = True if np.allclose(end[3:, :3], start[3:, :3], atol=tolerance) else False
same_position = True if np.allclose(end[:3, 3], start[:3, 3], atol=position_tolerance) else False
same_gradient = True if np.allclose(end[:3, 0], start[:3, 0]) else False
same_curvature = True if np.allclose(end[3:, :3], start[3:, :3]) else False
transition_code = ""
if same_position: