Skip to content

Commit 40f92d1

Browse files
RickBricecivilx64
andauthored
Alignment api update (#6817)
* Some basic COGO survey points functions * Update alignment api Includes defining alignment segment by segment, automatic geometry definitions, and automatric stationing and referents * Fixes bonsai import alignment from csv * Adds DMS angle conversion functions to COGO api * Updated per @civilx64 review comments * Fixes problem with segment representations * Fixes problem with segment transition codes * Allows for compound vertical and horizontal curves * Implements callbacks for referent naming * Renames angle_from_bearing to bearing2dd for consistency with ifcopenshell.util.geolocation.dms2dd. Removes angle_from_dms because it duplicates dms2dd * Documents register_referent_name_callback * Fixes all sorts of problems with Cant/SegRefCurve implementation * refactor referent unit tests to use a fixture for test setup * lint with black --------- Co-authored-by: Scott Lecher <civilx64@gmail.com>
1 parent ad8d02b commit 40f92d1

76 files changed

Lines changed: 3634 additions & 1828 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/bonsai/bonsai/bim/module/alignment/operator.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import os
2222

2323
import ifcopenshell.api.alignment
24-
import ifcopenshell.api.alignment.add_stationing_to_alignment
2524

2625
import bpy
2726
import json
@@ -62,9 +61,7 @@ def poll(cls, context):
6261
def _execute(self, context):
6362
self.file = tool.Ifc.get()
6463
start = time.time()
65-
alignment = ifcopenshell.api.alignment.create_alignment_from_csv(self.file, self.filepath)
66-
ifcopenshell.api.alignment.create_geometric_representation(self.file, alignment)
67-
ifcopenshell.api.alignment.add_stationing_to_alignment(self.file, alignment=alignment, start_station=0.0)
64+
alignment = ifcopenshell.api.alignment.create_from_csv(self.file, self.filepath)
6865

6966
# IFC 4.1.5.1 alignments cannot be contained in spatial structures, but can be referenced into them
7067
sites = self.file.by_type("IfcSite")

src/ifcopenshell-python/ifcopenshell/api/alignment/__init__.py

Lines changed: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,84 +17,89 @@
1717
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
1818

1919
"""
20-
Manages alignment layout (business logical) and alignment geometry (geometric representations).
20+
Manages alignment layout (semantic definition) and alignment geometry (geometric definition).
21+
22+
This API is defined in terms of the semantic definition of an alignment. The corresponding geometric definition
23+
is created and maintained automatically. The manditory zero length segment for the semantic and geometric definitions
24+
are automatically created and maintained.
25+
26+
Alignments are created with stationing referents. Each layout segment is assigned a position referent that informs about
27+
the start point of the segment. An example is the point of curvature of a horizontal circular curve. The referent is
28+
nested to the segment representing the circular arc and is named with a indicator of the position and the station, e.g. "P.C. (145+98.32)"
2129
2230
This API does not determine alignment parameters based on rules, such as minimum curve radius as a function of design speed or sight distance.
2331
2432
This API is under development and subject to code breaking changes in the future.
2533
2634
Presently, this API supports:
2735
1. Creating alignments, both horizontal and vertical, using the PI method. Alignment definition can be read from a CSV file.
28-
2. Adding business logic and geometric segments to the end of an alignment
29-
3. Adding and removing the zero length segment at the end of alignments
30-
4. Creating geometric representations from a business logical definition
31-
5. Mapping individual business logical segments to geometric segments (complete for horizontal, missing clothoid for vertical, not implemented for cant)
32-
6. Using curve geometry to determine IfcCurveSegment.Transition transition code.
33-
7. Utility functions for printing business logical and geometric representations, as well as minimal geometry evaluations
36+
2. Creating alignments segment by segment.
37+
3. Automatic creation of geometric definitions (IfcCompositeCurve, IfcGradientCurve, IfcSegmentedReferenceCurve)
38+
4. Automatic definition of stationing
39+
5. Automatic definition of alignment transition point referents
40+
6. Utility functions for printing business logical and geometric representations, as well as minimal geometry evaluations
3441
35-
Future versions of this API will support:
42+
Future versions of this API may support:
3643
1. Defining alignments using the PI method, including transition spirals
3744
2. Updating horizontal curve definitions by revising transition spiral parameters and circular curve radii
3845
3. Updating vertical curve definitions by revising horizontal length of curves
3946
4. Removing a segment at any location along a curve
4047
5. Adding a segment at any location along a curve
4148
"""
4249

43-
from .add_segment_to_curve import add_segment_to_curve
44-
from .add_segment_to_layout import add_segment_to_layout
45-
from .add_stationing_to_alignment import add_stationing_to_alignment
46-
from .add_vertical_alignment_by_pi_method import add_vertical_alignment_by_pi_method
47-
from .add_vertical_alignment import add_vertical_alignment
48-
from .add_zero_length_segment import add_zero_length_segment
49-
from .create_alignment_by_pi_method import create_alignment_by_pi_method
50-
from .create_alignment_from_csv import create_alignment_from_csv
51-
from .create_horizontal_alignment_by_pi_method import create_horizontal_alignment_by_pi_method
52-
from .create_geometric_representation import create_geometric_representation
53-
from .create_vertical_alignment_by_pi_method import create_vertical_alignment_by_pi_method
50+
from .add_stationing_referent import add_stationing_referent
51+
from .add_vertical_layout import add_vertical_layout
52+
from .create_layout_segment import create_layout_segment
53+
from .create_alignment import create
54+
from .create_by_pi_method import create_by_pi_method
55+
from .create_from_csv import create_from_csv
56+
from .create_segment_representations import create_segment_representations
5457
from .distance_along_from_station import distance_along_from_station
58+
from .get_alignment import get_alignment
59+
from .get_alignment_station import get_alignment_station
60+
from .get_layout_segments import get_layout_segments
61+
from .get_horizontal_layout import get_horizontal_layout
62+
from .get_vertical_layout import get_vertical_layout
63+
from .get_cant_layout import get_cant_layout
5564
from .get_alignment_layouts import get_alignment_layouts
5665
from .get_axis_subcontext import get_axis_subcontext
5766
from .get_basis_curve import get_basis_curve
5867
from .get_child_alignments import get_child_alignments
5968
from .get_curve import get_curve
69+
from .get_layout_curve import get_layout_curve
6070
from .get_parent_alignment import get_parent_alignment
6171
from .has_zero_length_segment import has_zero_length_segment
62-
from .map_alignment_segments import map_alignment_segments
63-
from .map_alignment_horizontal_segment import map_alignment_horizontal_segment
64-
from .map_alignment_vertical_segment import map_alignment_vertical_segment
65-
from .map_alignment_cant_segment import map_alignment_cant_segment
72+
from .layout_horizontal_alignment_by_pi_method import layout_horizontal_alignment_by_pi_method
73+
from .layout_vertical_alignment_by_pi_method import layout_vertical_alignment_by_pi_method
6674
from .name_segments import name_segments
67-
from .remove_last_segment import remove_last_segment
68-
from .remove_zero_length_segment import remove_zero_length_segment
69-
from .update_curve_segment_transition_code import update_curve_segment_transition_code
7075
from .util import *
7176

77+
from ._get_segment_start_point_label import register_referent_name_callback
78+
7279
__all__ = [
73-
"add_segment_to_curve",
74-
"add_segment_to_layout",
75-
"add_stationing_to_alignment",
76-
"add_vertical_alignment_by_pi_method",
77-
"add_vertical_alignment",
78-
"add_zero_length_segment",
79-
"create_alignment_by_pi_method",
80-
"create_alignment_from_csv",
81-
"create_horizontal_alignment_by_pi_method",
82-
"create_geometric_representation",
83-
"create_vertical_alignment_by_pi_method",
80+
"add_stationing_referent",
81+
"add_vertical_layout",
82+
"create_layout_segment",
83+
"create",
84+
"create_by_pi_method",
85+
"create_from_csv",
8486
"distance_along_from_station",
87+
"get_alignment",
88+
"get_alignment_station",
89+
"get_layout_segments",
90+
"get_horizontal_layout",
91+
"get_vertical_layout",
92+
"get_cant_layout",
8593
"get_alignment_layouts",
8694
"get_axis_subcontext",
8795
"get_basis_curve",
8896
"get_child_alignments",
8997
"get_curve",
98+
"get_layout_curve",
9099
"get_parent_alignment",
91100
"has_zero_length_segment",
92-
"map_alignment_segments",
93-
"map_alignment_horizontal_segment",
94-
"map_alignment_vertical_segment",
95-
"map_alignment_cant_segment",
101+
"layout_horizontal_alignment_by_pi_method",
102+
"layout_vertical_alignment_by_pi_method",
96103
"name_segments",
97-
"remove_last_segment",
98-
"remove_zero_length_segment",
99-
"update_curve_segment_transition_code",
104+
"register_referent_name_callback",
100105
]
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# IfcOpenShell - IFC toolkit and geometry engine
2+
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
3+
#
4+
# This file is part of IfcOpenShell.
5+
#
6+
# IfcOpenShell is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Lesser General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# IfcOpenShell is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
18+
19+
import ifcopenshell
20+
import ifcopenshell.geom
21+
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
22+
import numpy as np
23+
from ifcopenshell import entity_instance
24+
25+
from ifcopenshell.api.alignment._update_curve_segment_transition_code import _update_curve_segment_transition_code
26+
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import __map_alignment_horizontal_segment
27+
from ifcopenshell.api.alignment._map_alignment_vertical_segment import _map_alignment_vertical_segment
28+
from ifcopenshell.api.alignment._map_alignment_cant_segment import _map_alignment_cant_segment
29+
30+
31+
def _add_curve_segment_to_composite_curve(
32+
file: ifcopenshell.file, curve_segment: entity_instance, composite_curve: entity_instance
33+
):
34+
if 0 < len(curve_segment.UsingCurves):
35+
raise TypeError("IfcCurveSegment cannot belong to other curves")
36+
37+
settings = ifcopenshell.geom.settings()
38+
if composite_curve.Segments == None or 0 == len(composite_curve.Segments):
39+
# this is the first segment so just add it
40+
if composite_curve.Segments == None:
41+
composite_curve.Segments = []
42+
43+
# the last segment is always discontinuous
44+
curve_segment.Transition = "DISCONTINUOUS"
45+
46+
composite_curve.Segments += (curve_segment,)
47+
assert len(curve_segment.UsingCurves) == 1
48+
else:
49+
# get the segment before the zero length segment
50+
prev_segment = (
51+
composite_curve.Segments[-2]
52+
if composite_curve.Segments != None and 1 < len(composite_curve.Segments)
53+
else None
54+
)
55+
56+
curve_segment.Transition = "CONTINUOUS"
57+
58+
# must add the new segment to the curve before updating the transition code
59+
# add the new segment before the zero length segment
60+
zero_length_segment = composite_curve.Segments[-1]
61+
62+
if prev_segment:
63+
segments = composite_curve.Segments[0:-1]
64+
segments += (
65+
curve_segment,
66+
zero_length_segment,
67+
)
68+
composite_curve.Segments = []
69+
composite_curve.Segments += segments
70+
_update_curve_segment_transition_code(prev_segment, curve_segment)
71+
else:
72+
composite_curve.Segments = (curve_segment, zero_length_segment)
73+
74+
settings = ifcopenshell.geom.settings()
75+
segment_fn = ifcopenshell_wrapper.map_shape(settings, curve_segment.wrapped_data)
76+
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
77+
e = segment_evaluator.evaluate(segment_fn.end())
78+
end = np.array(e)
79+
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
80+
x = float(end[0, 3]) / unit_scale
81+
y = float(end[1, 3]) / unit_scale
82+
dx = float(end[0, 0])
83+
dy = float(end[1, 0])
84+
85+
# assume IfcAxis2Placement2D
86+
zero_length_segment.Placement.Location.Coordinates = (x, y)
87+
zero_length_segment.Placement.RefDirection.DirectionRatios = (dx, dy)
88+
89+
_update_curve_segment_transition_code(curve_segment, zero_length_segment)
90+
91+
92+
def _add_segment_to_curve(file: ifcopenshell.file, segment: entity_instance, curve: entity_instance) -> None:
93+
"""
94+
Creates an IfcCurveSegment from the IfcAlignmentSegment and adds it to the representation curve. The IfcCurveSegment is added
95+
at the end of the curve, but before the manditory zero length segment. The IfcCurveSegment.Transition for the segment
96+
that preceeds the new segment is updated.
97+
98+
:param segment: The segment to be added to the curve
99+
:param curve: The representation curve receiving the segment
100+
:return: None
101+
"""
102+
expected_types = ["IfcAlignmentSegment"]
103+
if not segment.is_a() in expected_types:
104+
raise TypeError(
105+
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{segment.is_a()}"
106+
)
107+
108+
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment") and not curve.is_a("IfcCompositeCurve"):
109+
raise TypeError(f"Expected to see IfcCompositeCurve, instead received '{curve.is_a()}'.")
110+
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment") and not curve.is_a("IfcGradientCurve"):
111+
raise TypeError(f"Expected to see IfcGradientCurve, instead received '{curve.is_a()}'.")
112+
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment") and not curve.is_a("IfcSegmentedReferenceCurve"):
113+
raise TypeError(f"Expected to see IfcSegmentedReferenceCurve, instead received '{curve.is_a()}'.")
114+
115+
expected_type = "IfcCompositeCurve"
116+
if not curve.is_a(expected_type):
117+
raise TypeError(f"Expected to see {expected_type}, instead received {curve.is_a()}.")
118+
119+
# map the IfcAlignmentSegment to an IfcCurveSegment (or two in the case of helmert curves)
120+
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
121+
mapped_segments = __map_alignment_horizontal_segment(file, segment)
122+
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
123+
mapped_segments = _map_alignment_vertical_segment(file, segment)
124+
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
125+
cant_layout = segment.Nests[0].RelatingObject
126+
mapped_segments = _map_alignment_cant_segment(file, segment, cant_layout.RailHeadDistance)
127+
else:
128+
assert False
129+
130+
for mapped_segment in mapped_segments:
131+
if mapped_segment:
132+
_add_curve_segment_to_composite_curve(file, mapped_segment, curve)

0 commit comments

Comments
 (0)