diff --git a/features/steps/thens/alignment.py b/features/steps/thens/alignment.py index 26173570..aaa16e6e 100644 --- a/features/steps/thens/alignment.py +++ b/features/steps/thens/alignment.py @@ -7,7 +7,7 @@ import ifcopenshell.util.unit from utils import ifc43x_alignment_validation as ifc43 -from utils import geometry +from utils.geometry import AlignmentSegmentContinuityCalculation from utils import ifc from validation_handling import gherkin_ifc from . import ValidationOutcome, OutcomeSeverity @@ -377,6 +377,12 @@ def step_impl(context, inst, continuity_type): for previous, current in inst: entity_contexts = ifc.recurrently_get_entity_attr(context, current, 'IfcRepresentation', 'ContextOfItems') precision = ifc.get_precision_from_contexts(entity_contexts) + continuity_calc = AlignmentSegmentContinuityCalculation( + previous_segment=previous, + segment_to_analyze=current, + length_unit_scale_factor=length_unit_scale_factor, + ) + continuity_calc.run() # calculate number of significant figures to display # use the precision of the geometric context plus one additional digit to accommodate rounding @@ -387,19 +393,11 @@ def step_impl(context, inst, continuity_type): if (continuity_type == "position") and (current.Transition in position_transition_codes): expected = precision - observed = geometry.alignment_segment_positional_difference( - length_unit_scale_factor, - previous, - current - ) + observed = continuity_calc.positional_difference elif (continuity_type == "tangency") and (current.Transition in tangency_transition_codes): expected = math.atan2(precision, current.SegmentLength.wrappedValue) - observed = geometry.alignment_segment_angular_difference( - length_unit_scale_factor, - previous, - current - ) + observed = continuity_calc.directional_difference else: return @@ -415,5 +413,6 @@ def step_impl(context, inst, continuity_type): "observed": observed, "num_digits": display_sig_figs, "context": f"calculated deviation in {continuity_type.lower()}", + "continuity_details": continuity_calc.to_dict(), }, severity=OutcomeSeverity.WARNING) diff --git a/features/steps/utils/geometry.py b/features/steps/utils/geometry.py index d03572a9..07d0d79b 100644 --- a/features/steps/utils/geometry.py +++ b/features/steps/utils/geometry.py @@ -1,6 +1,7 @@ from dataclasses import dataclass import operator import math +from typing import Dict import numpy as np @@ -14,6 +15,7 @@ GEOM_TOLERANCE = 1E-12 + def get_edges(file, inst, sequence_type=frozenset, oriented=False): edge_type = tuple if oriented else frozenset @@ -162,12 +164,11 @@ def evaluate_segment(segment: ifcopenshell.entity_instance, dist_along: float) - return np.array(prev_trans_matrix, dtype=np.float64).T - -def alignment_segment_positional_difference( - length_unit_scale_factor: float, previous_segment: ifcopenshell.entity_instance, - segment_to_analyze: ifcopenshell.entity_instance): +@dataclass +class AlignmentSegmentContinuityCalculation: """ - Use ifcopenshell to determine the difference in cartesian position between segments of an IfcAlignment. + Use ifcopenshell to determine the difference in cartesian position and tangent direction + between segments of an IfcAlignment. The expected entity type is either `IfcCurveSegment` or `IfcCompositeCurveSegment`. :param length_unit_scale_factor: Scale factor between the project units and metric units used internally by @@ -177,48 +178,71 @@ def alignment_segment_positional_difference( :param segment_to_analyze: The segment under analysis. The calculated end point of the previous segment will be compared to the calculated start point of this segment. """ - - u = abs(previous_segment.SegmentLength.wrappedValue) * length_unit_scale_factor - prev_end_transform = evaluate_segment(segment=previous_segment, dist_along=u) - current_start_transform = evaluate_segment(segment=segment_to_analyze, dist_along=0.0) - - e0 = prev_end_transform[3][0] / length_unit_scale_factor - e1 = prev_end_transform[3][1] / length_unit_scale_factor - preceding_end = (e0, e1) - - s0 = current_start_transform[3][0] / length_unit_scale_factor - s1 = current_start_transform[3][1] / length_unit_scale_factor - current_start = (s0, s1) - - return math.dist(preceding_end, current_start) - - -def alignment_segment_angular_difference( - length_unit_scale_factor: float, previous_segment: ifcopenshell.entity_instance, - segment_to_analyze: ifcopenshell.entity_instance): - """ - Use ifcopenshell to determine the difference in tangent direction angle between segments of an IfcAlignment. - The expected entity type is either `IfcCurveSegment` or `IfcCompositeCurveSegment`. - - :param length_unit_scale_factor: Scale factor between the project units and metric units used internally by - ifcopenshell - :param previous_segment: The segment that precede the segment being analyzed. The ending direction of this segment - will be determined via ifcopenshell geometry calculations. - :param segment_to_analyze: The segment under analysis. The calculated ending direction of the previous segment - will be compared to the calculated starting direction of this segment. - """ - u = abs(float(previous_segment.SegmentLength.wrappedValue)) * length_unit_scale_factor - prev_end_transform = evaluate_segment(segment=previous_segment, dist_along=u) - current_start_transform = evaluate_segment(segment=segment_to_analyze, dist_along=0.0) - - prev_i = prev_end_transform[0][0] - prev_j = prev_end_transform[0][1] - preceding_end_direction = math.atan2(prev_j, prev_i) - - curr_i = current_start_transform[0][0] - curr_j = current_start_transform[0][1] - current_start_direction = math.atan2(curr_j, curr_i) - - delta = abs(current_start_direction - preceding_end_direction) - - return delta + previous_segment: ifcopenshell.entity_instance + segment_to_analyze: ifcopenshell.entity_instance + length_unit_scale_factor: float + preceding_end_point: tuple = None + preceding_end_direction: float = None + current_start_point: tuple = None + current_start_direction: float = None + positional_difference: float = None + directional_difference: float = None + + def _calculate_positional_difference(self) -> None: + + u = abs(self.previous_segment.SegmentLength.wrappedValue) * self.length_unit_scale_factor + prev_end_transform = evaluate_segment(segment=self.previous_segment, dist_along=u) + current_start_transform = evaluate_segment(segment=self.segment_to_analyze, dist_along=0.0) + + e0 = prev_end_transform[3][0] / self.length_unit_scale_factor + e1 = prev_end_transform[3][1] / self.length_unit_scale_factor + self.preceding_end_point = (e0, e1) + + s0 = current_start_transform[3][0] / self.length_unit_scale_factor + s1 = current_start_transform[3][1] / self.length_unit_scale_factor + self.current_start_point = (s0, s1) + + self.positional_difference = math.dist( + self.preceding_end_point, self.current_start_point) + + + def _calculate_directional_difference(self) -> None: + u = abs(float(self.previous_segment.SegmentLength.wrappedValue)) * self.length_unit_scale_factor + prev_end_transform = evaluate_segment(segment=self.previous_segment, dist_along=u) + current_start_transform = evaluate_segment(segment=self.segment_to_analyze, dist_along=0.0) + + prev_i = prev_end_transform[0][0] + prev_j = prev_end_transform[0][1] + self.preceding_end_direction = math.atan2(prev_j, prev_i) + + curr_i = current_start_transform[0][0] + curr_j = current_start_transform[0][1] + self.current_start_direction = math.atan2(curr_j, curr_i) + + self.directional_difference = abs(self.current_start_direction - self.preceding_end_direction) + + def run(self) -> None: + """ + Run the calculation + """ + self._calculate_positional_difference() + self._calculate_directional_difference() + + def to_dict(self) -> Dict: + """ + Serialize dataclass to a dictionary + + This method is required because dataclasses.asdict() will fail + because ifcopenshell.entity_instances are of type SwigPyObject which cannot be pickled + """ + return { + "previous_segment": str(self.previous_segment), + "segment_to_analyze": str(self.segment_to_analyze), + "length_unit_scale_factor": self.length_unit_scale_factor, + "preceding_end_point": tuple(self.preceding_end_point), + "preceding_end_direction": self.preceding_end_direction, + "current_start_point": tuple(self.current_start_point), + "current_start_direction": self.current_start_direction, + "positional_difference": self.positional_difference, + "directional_difference": self.directional_difference, + } \ No newline at end of file