Adds update_alignment_parameter_segment_tags function

This commit is contained in:
Richard Brice
2026-08-07 14:33:04 -07:00
parent 048242783e
commit c5ba22451f
7 changed files with 434 additions and 14 deletions
@@ -25,7 +25,7 @@ are automatically created and maintained.
Alignments are created with stationing referents. Each layout segment is assigned a position referent that informs about
the start point of the segment. An example is the point of curvature of a horizontal circular curve. The referent is
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)"
nested to the segment representing the circular arc and is named with the alignment name and an indicator of the position and the station, e.g. "MyAlignment 145+98.32 (P.C.)"
This API does not determine alignment parameters based on rules, such as minimum curve radius as a function of design speed or sight distance.
@@ -89,6 +89,7 @@ from .layout_vertical_alignment_by_pi_method import (
layout_vertical_alignment_by_pi_method,
)
from .name_segments import name_segments
from .update_alignment_parameter_segment_tags import update_alignment_parameter_segment_tags
from .update_end_point import update_end_point
from .update_fallback_position import update_fallback_position
from .update_key_point_referents import update_key_point_referents
@@ -132,6 +133,7 @@ __all__ = [
"layout_vertical_alignment_by_pi_method",
"name_segments",
"register_referent_name_callback",
"update_alignment_parameter_segment_tags",
"update_end_point",
"update_fallback_position",
"update_key_point_referents",
@@ -0,0 +1,30 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.alignment
def _get_key_point_tag(file: ifcopenshell.file, label: str, station: float) -> str:
"""
Builds the station-and-label text shared by update_alignment_parameter_segment_tags (used
directly as IfcAlignmentParameterSegment.StartTag/EndTag) and update_key_point_referents (used,
prefixed with the alignment name, as IfcReferent.Name): "<station> (<label>)", e.g.
"145+98.32 (P.O.B.)".
"""
return f"{ifcopenshell.util.alignment.station_as_string(file, station)} ({label})"
@@ -0,0 +1,100 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.alignment
from ifcopenshell import entity_instance
from ifcopenshell.api.alignment._get_key_point_tag import _get_key_point_tag
from ifcopenshell.api.alignment._get_segment_start_point_label import (
_get_segment_start_point_label,
)
def update_alignment_parameter_segment_tags(file: ifcopenshell.file, layout: entity_instance) -> None:
"""
Sets IfcAlignmentParameterSegment.StartTag/EndTag for every segment transition in an alignment
layout. Unlike update_key_point_referents, this does not create any IfcReferent or IfcRelNests --
it only mutates the StartTag/EndTag string attributes already present on each segment's
DesignParameters.
For each transition between two consecutive segments, the outgoing segment's EndTag and the
incoming segment's StartTag are both set to the same computed tag (they describe the same
physical point), using the same label-and-station format as update_key_point_referents' Name
minus the alignment name (via _get_key_point_tag), e.g. "145+98.32 (P.C.)". Every real segment
ends up with both StartTag and EndTag populated: the first segment's StartTag and the last
segment's EndTag come from the "Beginning of Alignment"/"End of Alignment" boundary labels.
Labels come from _get_segment_start_point_label -- if a callback has been registered via
register_referent_name_callback(), its output is used instead of the built-in labels, exactly as
in update_key_point_referents.
:param layout: IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant
:return: None -- this function mutates segment.DesignParameters.StartTag/EndTag in place
Example:
.. code:: python
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
ifcopenshell.api.alignment.update_alignment_parameter_segment_tags(model, horizontal)
"""
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
if not layout.is_a() in expected_types:
raise TypeError(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
)
alignment = ifcopenshell.api.alignment.get_alignment(layout)
if alignment is None:
raise ValueError(f"{layout.is_a()} #{layout.id()} is not nested under an IfcAlignment.")
segments = list(ifcopenshell.api.alignment.get_layout_segments(layout))
if segments and ifcopenshell.api.alignment.has_zero_length_segment(layout):
segments = segments[:-1]
if not segments:
return
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
is_horizontal = layout.is_a("IfcAlignmentHorizontal")
distance_along = 0.0
prev_segment = None
for segment in segments:
dp = segment.DesignParameters
seg_distance_along = distance_along if is_horizontal else dp.StartDistAlong
label = _get_segment_start_point_label(prev_segment, segment)
station = start_station + seg_distance_along
tag = _get_key_point_tag(file, label, station)
dp.StartTag = tag
if prev_segment is not None:
prev_segment.DesignParameters.EndTag = tag
if is_horizontal:
distance_along += dp.SegmentLength
else:
distance_along = dp.StartDistAlong + dp.HorizontalLength
prev_segment = segment
label = _get_segment_start_point_label(prev_segment, None)
station = start_station + distance_along
prev_segment.DesignParameters.EndTag = _get_key_point_tag(file, label, station)
@@ -22,9 +22,9 @@ import ifcopenshell
import ifcopenshell.api.alignment
import ifcopenshell.api.pset
import ifcopenshell.guid
import ifcopenshell.util.alignment
import ifcopenshell.util.element
from ifcopenshell import entity_instance
from ifcopenshell.api.alignment._get_key_point_tag import _get_key_point_tag
from ifcopenshell.api.alignment._get_segment_start_point_label import (
_get_segment_start_point_label,
)
@@ -76,7 +76,7 @@ def _create_key_point_referent(
),
)
name = f"{label} ({ifcopenshell.util.alignment.station_as_string(file, station)})"
name = f"{alignment.Name} {_get_key_point_tag(file, label, station)}"
referent = file.createIfcReferent(
GlobalId=ifcopenshell.guid.new(),
@@ -105,7 +105,8 @@ def update_key_point_referents(
Creates IfcReferent key-point markers for every segment transition in an alignment layout.
Labels are derived from _get_segment_start_point_label (e.g. "P.C.", "P.T.", "P.O.B.",
"P.V.C.", ...), with the station appended, e.g. "P.C. (145+98.32)". Different jurisdictions use
"P.V.C.", ...), and combined with the alignment name and station to build the Name, e.g.
"MyAlignment 145+98.32 (P.C.)". Different jurisdictions use
different naming systems for these key points -- register_referent_name_callback() lets a
caller override the default horizontal/vertical/cant labeling before calling this function; if
a callback is registered, its output is used here instead of the built-in labels. Referents are
@@ -145,7 +146,7 @@ def update_key_point_referents(
ifcopenshell.api.alignment.register_referent_name_callback(horizontal=my_horizontal_labels)
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
nest = ifcopenshell.api.alignment.update_key_point_referents(model, horizontal)
# nest.RelatedObjects[0].Name starts with "Start (" instead of the default "P.O.B. ("
# nest.RelatedObjects[0].Name ends with "(Start)" instead of the default "(P.O.B.)"
"""
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]