mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 09:18:38 +00:00
Reverts from automatically adding stationing to alignments because of missing initial geometry. Adds support for stationing with decreasing values.
This commit is contained in:
@@ -51,7 +51,7 @@ class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
|||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
self.file = tool.Ifc.get()
|
self.file = tool.Ifc.get()
|
||||||
start = time.time()
|
start = time.time()
|
||||||
alignment = ifcopenshell.api.alignment.create_from_csv(self.file, self.filepath)
|
alignment = ifcopenshell.api.alignment.create_from_csv(self.file, self.filepath, start_station=0.0)
|
||||||
|
|
||||||
# IFC 4.1.5.1 alignments cannot be contained in spatial structures, but can be referenced into them
|
# IFC 4.1.5.1 alignments cannot be contained in spatial structures, but can be referenced into them
|
||||||
sites = self.file.by_type("IfcSite")
|
sites = self.file.by_type("IfcSite")
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ This API is defined in terms of the semantic definition of an alignment. The cor
|
|||||||
is created and maintained automatically. The manditory zero length segment for the semantic and geometric definitions
|
is created and maintained automatically. The manditory zero length segment for the semantic and geometric definitions
|
||||||
are automatically created and maintained.
|
are automatically created and maintained.
|
||||||
|
|
||||||
Alignments are created with stationing referents. Each layout segment is assigned a position referent that informs about
|
Stationing is defined explicitly by calling add_stationing_referent() after the layout segments (and therefore the
|
||||||
the start point of the segment. An example is the point of curvature of a horizontal circular curve. The referent is
|
basis curve geometry) exist - create() does not add one. update_key_point_referents() assigns each layout segment a
|
||||||
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.)"
|
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 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.
|
This API does not determine alignment parameters based on rules, such as minimum curve radius as a function of design speed or sight distance.
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ Presently, this API supports:
|
|||||||
1. Creating alignments, both horizontal and vertical, using the PI method. Alignment definition can be read from a CSV file.
|
1. Creating alignments, both horizontal and vertical, using the PI method. Alignment definition can be read from a CSV file.
|
||||||
2. Creating alignments segment by segment.
|
2. Creating alignments segment by segment.
|
||||||
3. Automatic creation of geometric definitions (IfcCompositeCurve, IfcGradientCurve, IfcSegmentedReferenceCurve)
|
3. Automatic creation of geometric definitions (IfcCompositeCurve, IfcGradientCurve, IfcSegmentedReferenceCurve)
|
||||||
4. Automatic definition of stationing
|
4. Explicit definition of stationing, including station equations and reverse (decreasing) stationing
|
||||||
5. Automatic definition of alignment transition point referents
|
5. Automatic definition of alignment transition point referents
|
||||||
6. Utility functions for printing business logical and geometric representations, as well as minimal geometry evaluations
|
6. Utility functions for printing business logical and geometric representations, as well as minimal geometry evaluations
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
from ifcopenshell import entity_instance
|
||||||
|
|
||||||
|
|
||||||
|
def _referent_distance_along(referent: entity_instance) -> float:
|
||||||
|
"""The distance along the basis curve at which a referent is placed.
|
||||||
|
|
||||||
|
Read from IfcLinearPlacement.RelativePlacement.Location.DistanceAlong. An
|
||||||
|
IfcLocalPlacement fallback (semantic-only alignment, or a placement that could not
|
||||||
|
yet be expressed relative to a basis curve) carries no DistanceAlong; it is only ever
|
||||||
|
used for the starting referent, at distance 0.0 (the global origin).
|
||||||
|
|
||||||
|
Referents nested under an alignment are ordered by increasing DistanceAlong (IFC CT
|
||||||
|
4.1.4.4.3), which - for a reverse-stationed alignment - means by decreasing Station.
|
||||||
|
Sort on this, never on Pset_Stationing.Station.
|
||||||
|
"""
|
||||||
|
placement = referent.ObjectPlacement
|
||||||
|
if placement and placement.is_a("IfcLinearPlacement"):
|
||||||
|
return placement.RelativePlacement.Location.DistanceAlong.wrappedValue
|
||||||
|
return 0.0
|
||||||
@@ -22,8 +22,8 @@ import ifcopenshell
|
|||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
import ifcopenshell.api.pset
|
import ifcopenshell.api.pset
|
||||||
import ifcopenshell.guid
|
import ifcopenshell.guid
|
||||||
import ifcopenshell.util.element
|
|
||||||
from ifcopenshell import entity_instance
|
from ifcopenshell import entity_instance
|
||||||
|
from ifcopenshell.api.alignment._referent_distance_along import _referent_distance_along
|
||||||
from ifcopenshell.api.alignment._sort_nest import _sort_nest
|
from ifcopenshell.api.alignment._sort_nest import _sort_nest
|
||||||
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
|
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
|
||||||
|
|
||||||
@@ -35,16 +35,28 @@ def add_stationing_referent(
|
|||||||
distance_along: float,
|
distance_along: float,
|
||||||
station: float,
|
station: float,
|
||||||
incoming_station: Optional[float] = None,
|
incoming_station: Optional[float] = None,
|
||||||
|
has_increasing_station: Optional[bool] = None,
|
||||||
on_basis_curve: Optional[bool] = None,
|
on_basis_curve: Optional[bool] = None,
|
||||||
) -> entity_instance:
|
) -> entity_instance:
|
||||||
"""
|
"""
|
||||||
Adds an IfcReferent to the alignment that defines the stationing system.
|
Adds an IfcReferent to the alignment that defines the stationing system.
|
||||||
|
|
||||||
|
Call this once with ``distance_along=0.0`` to define the starting station, and again
|
||||||
|
for each station equation. If the alignment has no geometry yet, the referent is
|
||||||
|
placed with an IfcLocalPlacement at the global origin; once the basis curve has real
|
||||||
|
segments it is placed with an IfcLinearPlacement at ``distance_along`` on that curve.
|
||||||
|
create_representation() restates an origin-placed starting referent onto the curve
|
||||||
|
when geometry is added later.
|
||||||
|
|
||||||
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
||||||
:param alignment: the alignment to receive the referent
|
:param alignment: the alignment to receive the referent
|
||||||
:param distance_along: distance along the alignment basis curve
|
:param distance_along: distance along the alignment basis curve
|
||||||
:param station: station value
|
:param station: station value
|
||||||
:param incoming_station: station value of the incoming segment, only set to specify a station equation
|
:param incoming_station: station value of the incoming segment, only set to specify a station equation
|
||||||
|
:param has_increasing_station: sets Pset_Stationing.HasIncreasingStation, which records the direction of
|
||||||
|
stationing for the referents nested after this one. Leave None (the default) or pass True for the
|
||||||
|
common case where station values increase with distance along; pass False on the starting referent
|
||||||
|
of a reverse-stationed alignment, where station values decrease as distance along increases.
|
||||||
:param on_basis_curve: whether the referent is positioned on the basis curve or the alignment curve, if None the function will default to the basis curve
|
:param on_basis_curve: whether the referent is positioned on the basis curve or the alignment curve, if None the function will default to the basis curve
|
||||||
:return: referent
|
:return: referent
|
||||||
|
|
||||||
@@ -82,10 +94,13 @@ def add_stationing_referent(
|
|||||||
|
|
||||||
update_fallback_position(file, object_placement)
|
update_fallback_position(file, object_placement)
|
||||||
else:
|
else:
|
||||||
|
# No resolvable basis curve yet: place the referent at the global origin. Once
|
||||||
|
# geometry exists, create_representation() restates the starting referent onto the
|
||||||
|
# curve at DistanceAlong 0.0.
|
||||||
object_placement = file.createIfcLocalPlacement(
|
object_placement = file.createIfcLocalPlacement(
|
||||||
PlacementRelTo=None,
|
PlacementRelTo=None,
|
||||||
RelativePlacement=file.createIfcAxis2Placement2D(
|
RelativePlacement=file.createIfcAxis2Placement2D(
|
||||||
Location=file.createIfcCartesianPoint(alignment.ObjectPlacement.RelativePlacement.Location.Coordinates)
|
Location=file.createIfcCartesianPoint(Coordinates=(0.0, 0.0))
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,6 +126,8 @@ def add_stationing_referent(
|
|||||||
properties = {"Station": station}
|
properties = {"Station": station}
|
||||||
if incoming_station is not None:
|
if incoming_station is not None:
|
||||||
properties["IncomingStation"] = incoming_station
|
properties["IncomingStation"] = incoming_station
|
||||||
|
if has_increasing_station is not None:
|
||||||
|
properties["HasIncreasingStation"] = has_increasing_station
|
||||||
|
|
||||||
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=referent, name="Pset_Stationing")
|
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=referent, name="Pset_Stationing")
|
||||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties=properties)
|
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties=properties)
|
||||||
@@ -123,6 +140,8 @@ def add_stationing_referent(
|
|||||||
else:
|
else:
|
||||||
nest.RelatedObjects += (referent,)
|
nest.RelatedObjects += (referent,)
|
||||||
|
|
||||||
_sort_nest(nest, key=lambda x: ifcopenshell.util.element.get_pset(x, name="Pset_Stationing", prop="Station"))
|
# Referents are ordered by increasing DistanceAlong (IFC CT 4.1.4.4.3), which for a
|
||||||
|
# reverse-stationed alignment is decreasing Station - so sort on DistanceAlong, not Station.
|
||||||
|
_sort_nest(nest, key=_referent_distance_along)
|
||||||
|
|
||||||
return referent
|
return referent
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import ifcopenshell
|
|||||||
import ifcopenshell.api.aggregate
|
import ifcopenshell.api.aggregate
|
||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
import ifcopenshell.api.nest
|
import ifcopenshell.api.nest
|
||||||
import ifcopenshell.util.alignment
|
|
||||||
from ifcopenshell import entity_instance
|
from ifcopenshell import entity_instance
|
||||||
from ifcopenshell.api.alignment._add_zero_length_segment import _add_zero_length_segment
|
from ifcopenshell.api.alignment._add_zero_length_segment import _add_zero_length_segment
|
||||||
from ifcopenshell.api.alignment._create_geometric_representation import (
|
from ifcopenshell.api.alignment._create_geometric_representation import (
|
||||||
@@ -34,7 +33,6 @@ def create(
|
|||||||
include_vertical: bool = False,
|
include_vertical: bool = False,
|
||||||
include_cant: bool = False,
|
include_cant: bool = False,
|
||||||
include_geometry: bool = True,
|
include_geometry: bool = True,
|
||||||
start_station: float = 0.0,
|
|
||||||
) -> entity_instance:
|
) -> entity_instance:
|
||||||
"""
|
"""
|
||||||
Creates a new alignment with a horizontal layout. Optionally, vertical and cant layouts can be created as well.
|
Creates a new alignment with a horizontal layout. Optionally, vertical and cant layouts can be created as well.
|
||||||
@@ -49,19 +47,15 @@ def create(
|
|||||||
The horizontal geometry in the Viennese Bend transition curves depends on the Viennese Bend cant parameters. create_layout_segment() automatically creates
|
The horizontal geometry in the Viennese Bend transition curves depends on the Viennese Bend cant parameters. create_layout_segment() automatically creates
|
||||||
the geometric representation from the semantic definition. The horizontal segment geometric representation will fail if the cant segment is not defined.
|
the geometric representation from the semantic definition. The horizontal segment geometric representation will fail if the cant segment is not defined.
|
||||||
|
|
||||||
If geometric representations are created, the alignment stationing referent is also created using the start_station value. IfcReferent.ObjectPlacement
|
This function does not define the alignment's stationing. Call add_stationing_referent() once the layout
|
||||||
is required for linear positiion elements and IfcLinearPlacement is defined relative to alignment curve geometry.
|
segments (and therefore the basis curve geometry) exist to place the starting-station IfcReferent, and again
|
||||||
This referent's Name follows the same "<alignment name> <station>" convention update_key_point_referents() uses
|
for any station equations. Until stationing is defined, get_alignment_start_station() reports 0.0.
|
||||||
for its own key-point referents (e.g. "MyAlignment 49+00.00"), so that every referent nested under an alignment
|
|
||||||
is identifiable by name alone, without needing to inspect its Pset_Stationing or placement to know which
|
|
||||||
alignment it belongs to.
|
|
||||||
|
|
||||||
:param file:
|
:param file:
|
||||||
:param name: name assigned to IfcAlignment.Name
|
:param name: name assigned to IfcAlignment.Name
|
||||||
:param include_vertical: If True, IfcAlignmentVertical is created. IfcGradientCurve is created if include_geometry is True
|
:param include_vertical: If True, IfcAlignmentVertical is created. IfcGradientCurve is created if include_geometry is True
|
||||||
:param include_cant: If True, IfcAlignmentCant is created. IfcSegmentedReferenceCurve is created if include_geometry is True
|
:param include_cant: If True, IfcAlignmentCant is created. IfcSegmentedReferenceCurve is created if include_geometry is True
|
||||||
:param include_geometry: If True, the geometric representations are added
|
:param include_geometry: If True, the geometric representations are added
|
||||||
:param start_station: station value at the start of the alignment.
|
|
||||||
:return: Returns an IfcAlignment
|
:return: Returns an IfcAlignment
|
||||||
"""
|
"""
|
||||||
alignment = file.createIfcAlignment(
|
alignment = file.createIfcAlignment(
|
||||||
@@ -90,9 +84,6 @@ def create(
|
|||||||
if include_geometry:
|
if include_geometry:
|
||||||
_create_geometric_representation(file, alignment)
|
_create_geometric_representation(file, alignment)
|
||||||
|
|
||||||
referent_name = f"{name} {ifcopenshell.util.alignment.station_as_string(file, start_station)}"
|
|
||||||
referent = ifcopenshell.api.alignment.add_stationing_referent(file, referent_name, alignment, 0.0, start_station)
|
|
||||||
|
|
||||||
for layout in alignment_layouts:
|
for layout in alignment_layouts:
|
||||||
_add_zero_length_segment(file, layout)
|
_add_zero_length_segment(file, layout)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api.aggregate
|
import ifcopenshell.api.aggregate
|
||||||
@@ -123,22 +124,23 @@ def create_as_polyline(
|
|||||||
file: ifcopenshell.file,
|
file: ifcopenshell.file,
|
||||||
name: str,
|
name: str,
|
||||||
points: Sequence[entity_instance],
|
points: Sequence[entity_instance],
|
||||||
start_station: float = 0.0,
|
start_station: Optional[float] = None,
|
||||||
) -> entity_instance:
|
) -> entity_instance:
|
||||||
"""
|
"""
|
||||||
Creates a new IfcAlignment with an IfcPolyline representation.
|
Creates a new IfcAlignment with an IfcPolyline representation.
|
||||||
|
|
||||||
The IfcAlignment is aggreated to IfcProject
|
The IfcAlignment is aggreated to IfcProject
|
||||||
|
|
||||||
The stationing referent created from start_station has Name "<alignment name> <station>"
|
If start_station is given, a STATION IfcReferent named "<alignment name> <station>"
|
||||||
(e.g. "MyAlignment 49+00.00"), the same convention update_key_point_referents() and
|
(e.g. "MyAlignment 49+00.00") is added at distance along 0.0 - the same naming
|
||||||
create() use for their own referents, so every referent nested under an alignment is
|
convention update_key_point_referents() uses for its own referents, so every referent
|
||||||
identifiable by name alone.
|
nested under an alignment is identifiable by name alone. If None (the default), no
|
||||||
|
stationing referent is created.
|
||||||
|
|
||||||
:param file:
|
:param file:
|
||||||
:param name: name assigned to IfcAlignment.Name
|
:param name: name assigned to IfcAlignment.Name
|
||||||
:param points: sequence of points defining the polyline
|
:param points: sequence of points defining the polyline
|
||||||
:param start_station: station value at the start of the alignment
|
:param start_station: station value at the start of the alignment, or None for no stationing referent
|
||||||
:return: Returns an IfcAlignment
|
:return: Returns an IfcAlignment
|
||||||
"""
|
"""
|
||||||
alignment = file.createIfcAlignment(
|
alignment = file.createIfcAlignment(
|
||||||
@@ -149,8 +151,9 @@ def create_as_polyline(
|
|||||||
_create_polyline_representation(file, alignment, points)
|
_create_polyline_representation(file, alignment, points)
|
||||||
|
|
||||||
# define stationing
|
# define stationing
|
||||||
referent_name = f"{alignment.Name} {ifcopenshell.util.alignment.station_as_string(file, start_station)}"
|
if start_station is not None:
|
||||||
referent = ifcopenshell.api.alignment.add_stationing_referent(file, referent_name, alignment, 0.0, start_station)
|
referent_name = f"{alignment.Name} {ifcopenshell.util.alignment.station_as_string(file, start_station)}"
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, referent_name, alignment, 0.0, start_station)
|
||||||
|
|
||||||
# IFC 4.1.4.1.1 Alignment Aggregation To Project
|
# IFC 4.1.4.1.1 Alignment Aggregation To Project
|
||||||
project = file.by_type("IfcProject")[0]
|
project = file.by_type("IfcProject")[0]
|
||||||
|
|||||||
@@ -17,9 +17,11 @@
|
|||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
|
import ifcopenshell.util.alignment
|
||||||
from ifcopenshell import entity_instance
|
from ifcopenshell import entity_instance
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ def create_by_pi_method(
|
|||||||
radii: Sequence[float],
|
radii: Sequence[float],
|
||||||
vpoints: Sequence[Sequence[float]] = None,
|
vpoints: Sequence[Sequence[float]] = None,
|
||||||
lengths: Sequence[float] = None,
|
lengths: Sequence[float] = None,
|
||||||
start_station: float = 0.0,
|
start_station: Optional[float] = None,
|
||||||
) -> entity_instance:
|
) -> entity_instance:
|
||||||
"""
|
"""
|
||||||
Create an alignment using the PI layout method for both horizontal and vertical alignments.
|
Create an alignment using the PI layout method for both horizontal and vertical alignments.
|
||||||
@@ -41,16 +43,21 @@ def create_by_pi_method(
|
|||||||
:param radii: radii values to use for transition
|
:param radii: radii values to use for transition
|
||||||
:param vpoints: (distance_along, Z_height) pairs denoting the location of the vertical PIs, including start and end.
|
:param vpoints: (distance_along, Z_height) pairs denoting the location of the vertical PIs, including start and end.
|
||||||
:param lengths: parabolic vertical curve horizontal length values to use for transition
|
:param lengths: parabolic vertical curve horizontal length values to use for transition
|
||||||
|
:param start_station: if given, the starting station value. A STATION IfcReferent named
|
||||||
|
"<name> <station string>" is added at distance along 0.0 once the geometry exists. If None
|
||||||
|
(the default), no stationing referent is created and get_alignment_start_station() reports 0.0.
|
||||||
:return: Returns an IfcAlignment
|
:return: Returns an IfcAlignment
|
||||||
"""
|
"""
|
||||||
include_vertical = True if vpoints and lengths else False
|
include_vertical = True if vpoints and lengths else False
|
||||||
alignment = ifcopenshell.api.alignment.create(
|
alignment = ifcopenshell.api.alignment.create(file, name, include_vertical=include_vertical)
|
||||||
file, name, include_vertical=include_vertical, start_station=start_station
|
|
||||||
)
|
|
||||||
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||||
ifcopenshell.api.alignment.layout_horizontal_alignment_by_pi_method(file, horizontal_layout, hpoints, radii)
|
ifcopenshell.api.alignment.layout_horizontal_alignment_by_pi_method(file, horizontal_layout, hpoints, radii)
|
||||||
if include_vertical:
|
if include_vertical:
|
||||||
vertical_layout = ifcopenshell.api.alignment.get_vertical_layout(alignment)
|
vertical_layout = ifcopenshell.api.alignment.get_vertical_layout(alignment)
|
||||||
ifcopenshell.api.alignment.layout_vertical_alignment_by_pi_method(file, vertical_layout, vpoints, lengths)
|
ifcopenshell.api.alignment.layout_vertical_alignment_by_pi_method(file, vertical_layout, vpoints, lengths)
|
||||||
|
|
||||||
|
if start_station is not None:
|
||||||
|
referent_name = f"{name} {ifcopenshell.util.alignment.station_as_string(file, start_station)}"
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, referent_name, alignment, 0.0, start_station)
|
||||||
|
|
||||||
return alignment
|
return alignment
|
||||||
|
|||||||
@@ -17,13 +17,17 @@
|
|||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
|
import ifcopenshell.util.alignment
|
||||||
from ifcopenshell import entity_instance
|
from ifcopenshell import entity_instance
|
||||||
|
|
||||||
|
|
||||||
def create_from_csv(file: ifcopenshell.file, filepath: str) -> entity_instance:
|
def create_from_csv(
|
||||||
|
file: ifcopenshell.file, filepath: str, start_station: Optional[float] = None
|
||||||
|
) -> entity_instance:
|
||||||
"""
|
"""
|
||||||
Creates an alignment from PI data stored in a CSV file.
|
Creates an alignment from PI data stored in a CSV file.
|
||||||
|
|
||||||
@@ -55,6 +59,8 @@ def create_from_csv(file: ifcopenshell.file, filepath: str) -> entity_instance:
|
|||||||
The CSV file contains one horizontal alignment, zero, one, or more vertical alignments
|
The CSV file contains one horizontal alignment, zero, one, or more vertical alignments
|
||||||
|
|
||||||
:param filepath: path the to CSV file
|
:param filepath: path the to CSV file
|
||||||
|
:param start_station: if given, the starting station value; a STATION IfcReferent is added at
|
||||||
|
distance along 0.0 once the geometry exists. If None (the default), no stationing referent is created.
|
||||||
:return: IfcAlignment
|
:return: IfcAlignment
|
||||||
"""
|
"""
|
||||||
alignment = None
|
alignment = None
|
||||||
@@ -97,4 +103,9 @@ def create_from_csv(file: ifcopenshell.file, filepath: str) -> entity_instance:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert alignment is not None
|
assert alignment is not None
|
||||||
|
|
||||||
|
if start_station is not None:
|
||||||
|
referent_name = f"{alignment.Name} {ifcopenshell.util.alignment.station_as_string(file, start_station)}"
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, referent_name, alignment, 0.0, start_station)
|
||||||
|
|
||||||
return alignment
|
return alignment
|
||||||
|
|||||||
@@ -22,16 +22,7 @@ import ifcopenshell
|
|||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
from ifcopenshell import entity_instance
|
from ifcopenshell import entity_instance
|
||||||
|
from ifcopenshell.api.alignment._referent_distance_along import _referent_distance_along
|
||||||
|
|
||||||
def _distance_along_of_referent(referent: entity_instance) -> float:
|
|
||||||
placement = referent.ObjectPlacement
|
|
||||||
if placement.is_a("IfcLinearPlacement"):
|
|
||||||
return placement.RelativePlacement.Location.DistanceAlong.wrappedValue
|
|
||||||
# IfcLocalPlacement fallback (e.g. semantic-only alignment, or the placement could not yet
|
|
||||||
# be expressed relative to a basis curve) carries no DistanceAlong; it is only ever used for
|
|
||||||
# the starting referent, at distance 0.0.
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
|
|
||||||
def distance_along_from_station(file: ifcopenshell.file, alignment: entity_instance, station: float) -> Optional[float]:
|
def distance_along_from_station(file: ifcopenshell.file, alignment: entity_instance, station: float) -> Optional[float]:
|
||||||
@@ -41,18 +32,24 @@ def distance_along_from_station(file: ifcopenshell.file, alignment: entity_insta
|
|||||||
If the alignment does not have stationing defined with an IfcReferent, the start of the alignment is assumed
|
If the alignment does not have stationing defined with an IfcReferent, the start of the alignment is assumed
|
||||||
to be at station 0.0. That is, the station is the distance along.
|
to be at station 0.0. That is, the station is the distance along.
|
||||||
|
|
||||||
Station equations (where Pset_Stationing.IncomingStation is set on a referent) are taken into account.
|
Station equations (where Pset_Stationing.IncomingStation is set on a referent) and reverse
|
||||||
|
(decreasing) stationing (where Pset_Stationing.HasIncreasingStation is False) are taken into account.
|
||||||
|
|
||||||
For each STATION referent nested to the alignment, DistanceAlong (D) and the outgoing station (S, i.e.
|
For each STATION referent nested to the alignment, DistanceAlong (D) and the outgoing station (S, i.e.
|
||||||
Pset_Stationing.Station) are read off, sorted by DistanceAlong. The requested station is located within
|
Pset_Stationing.Station) are read off and sorted by DistanceAlong. A direction sign is tracked while
|
||||||
the segment defined by the last referent whose outgoing station is less than or equal to it, and the
|
walking the sorted referents: it starts at +1 and is set to +1 or -1 at any referent that carries an
|
||||||
distance along is computed as D + (station - S) for that referent.
|
explicit Pset_Stationing.HasIncreasingStation, so each referent's region has a sign sigma of +1
|
||||||
|
(increasing) or -1 (decreasing). HasIncreasingStation may flip any number of times along the alignment -
|
||||||
|
unrealistic, but valid IFC, and handled. The governing referent is the last one, by DistanceAlong, for
|
||||||
|
which sigma * (station - S) >= 0, and the distance along is D + sigma * (station - S).
|
||||||
|
|
||||||
If the station falls within a gap introduced by a forward (gap) station equation - that is, it was skipped
|
If the station falls within a gap introduced by a station equation - that is, it was skipped over by the
|
||||||
over by the equation - there is no distance along that corresponds to it, and None is returned.
|
equation - there is no distance along that corresponds to it, and None is returned.
|
||||||
|
|
||||||
Note that an overlap (backward) station equation causes a range of stations to correspond to two distinct
|
Note that an overlap station equation - or a HasIncreasingStation direction reversal, which creates an
|
||||||
distances along the alignment, one on either side of the equation. This implementation returns the distance
|
equivalent overlap zone - causes a range of stations to correspond to two (or more) distinct distances
|
||||||
along in the segment following the equation (i.e. the outgoing side).
|
along the alignment. This implementation returns the most downstream one (largest DistanceAlong), i.e.
|
||||||
|
the match in the region following the last equation / reversal.
|
||||||
|
|
||||||
:param alignment: the alignment
|
:param alignment: the alignment
|
||||||
:param station: station value
|
:param station: station value
|
||||||
@@ -72,31 +69,42 @@ def distance_along_from_station(file: ifcopenshell.file, alignment: entity_insta
|
|||||||
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
||||||
return station - start_station
|
return station - start_station
|
||||||
|
|
||||||
stations = [
|
referents = [
|
||||||
(
|
(
|
||||||
_distance_along_of_referent(referent),
|
_referent_distance_along(referent),
|
||||||
ifcopenshell.util.element.get_pset(referent, name="Pset_Stationing", prop="Station"),
|
ifcopenshell.util.element.get_pset(referent, name="Pset_Stationing", prop="Station"),
|
||||||
|
ifcopenshell.util.element.get_pset(referent, name="Pset_Stationing", prop="HasIncreasingStation"),
|
||||||
)
|
)
|
||||||
for referent in stationing_nest.RelatedObjects
|
for referent in stationing_nest.RelatedObjects
|
||||||
]
|
]
|
||||||
stations.sort(key=lambda entry: entry[0])
|
referents.sort(key=lambda entry: entry[0])
|
||||||
|
|
||||||
|
# Assign each referent's region a direction sign: +1 increasing, -1 decreasing. The sign
|
||||||
|
# starts increasing and flips at any referent carrying an explicit HasIncreasingStation.
|
||||||
|
sigma = 1.0
|
||||||
|
stations = []
|
||||||
|
for distance_along, outgoing_station, has_increasing_station in referents:
|
||||||
|
if has_increasing_station is not None:
|
||||||
|
sigma = 1.0 if has_increasing_station else -1.0
|
||||||
|
stations.append((distance_along, outgoing_station, sigma))
|
||||||
|
|
||||||
index = None
|
index = None
|
||||||
for i, (distance_along, outgoing_station) in enumerate(stations):
|
for i, (distance_along, outgoing_station, region_sign) in enumerate(stations):
|
||||||
if outgoing_station <= station:
|
if region_sign * (station - outgoing_station) >= 0.0:
|
||||||
index = i
|
index = i
|
||||||
|
|
||||||
if index is None:
|
if index is None:
|
||||||
# station precedes the alignment's starting station; extrapolate from the first referent
|
# station precedes the alignment's starting station; extrapolate from the first referent
|
||||||
distance_along, outgoing_station = stations[0]
|
distance_along, outgoing_station, region_sign = stations[0]
|
||||||
return distance_along + (station - outgoing_station)
|
return distance_along + region_sign * (station - outgoing_station)
|
||||||
|
|
||||||
distance_along, outgoing_station = stations[index]
|
distance_along, outgoing_station, region_sign = stations[index]
|
||||||
|
advance = region_sign * (station - outgoing_station)
|
||||||
|
|
||||||
if index + 1 < len(stations):
|
if index + 1 < len(stations):
|
||||||
next_distance_along, _ = stations[index + 1]
|
next_distance_along, _, _ = stations[index + 1]
|
||||||
if station - outgoing_station > next_distance_along - distance_along:
|
if advance > next_distance_along - distance_along:
|
||||||
# the station was skipped over by a forward (gap) station equation
|
# the station was skipped over by a gap station equation
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return distance_along + (station - outgoing_station)
|
return distance_along + advance
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import ifcopenshell.util.element
|
|||||||
|
|
||||||
|
|
||||||
def test_add_positioning_referent():
|
def test_add_positioning_referent():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -37,7 +37,7 @@ def test_add_positioning_referent():
|
|||||||
parent=geometric_representation_context,
|
parent=geometric_representation_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", start_station=2000.0)
|
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment")
|
||||||
|
|
||||||
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||||
segment = ifcopenshell.api.alignment.get_layout_segments(horizontal_layout)[0]
|
segment = ifcopenshell.api.alignment.get_layout_segments(horizontal_layout)[0]
|
||||||
@@ -61,7 +61,7 @@ def test_add_positioning_referent():
|
|||||||
|
|
||||||
|
|
||||||
def test_add_positioning_referent_creates_separate_referent_per_call():
|
def test_add_positioning_referent_creates_separate_referent_per_call():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -74,7 +74,7 @@ def test_add_positioning_referent_creates_separate_referent_per_call():
|
|||||||
parent=geometric_representation_context,
|
parent=geometric_representation_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", start_station=2000.0)
|
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment")
|
||||||
|
|
||||||
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||||
segment = ifcopenshell.api.alignment.get_layout_segments(horizontal_layout)[0]
|
segment = ifcopenshell.api.alignment.get_layout_segments(horizontal_layout)[0]
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import ifcopenshell.api.unit
|
|||||||
from ifcopenshell.api.alignment._add_segment_to_layout import _add_segment_to_layout
|
from ifcopenshell.api.alignment._add_segment_to_layout import _add_segment_to_layout
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ifcopenshell.file(schema="IFC4X3")
|
ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
IFC4X3_AVAILABLE = True
|
IFC4X3_AVAILABLE = True
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
IFC4X3_AVAILABLE = False
|
IFC4X3_AVAILABLE = False
|
||||||
@@ -33,7 +33,7 @@ except RuntimeError:
|
|||||||
|
|
||||||
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
||||||
def test_add_segment_to_layout():
|
def test_add_segment_to_layout():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -47,11 +47,12 @@ def test_add_segment_to_layout():
|
|||||||
)
|
)
|
||||||
|
|
||||||
alignment = ifcopenshell.api.alignment.create(file, "")
|
alignment = ifcopenshell.api.alignment.create(file, "")
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "0+00.00", alignment, distance_along=0.0, station=0.0)
|
||||||
|
|
||||||
stationing_nest = ifcopenshell.api.alignment.get_stationing_nest(file, alignment)
|
stationing_nest = ifcopenshell.api.alignment.get_stationing_nest(file, alignment)
|
||||||
assert (
|
assert (
|
||||||
len(stationing_nest.RelatedObjects) == 1
|
len(stationing_nest.RelatedObjects) == 1
|
||||||
) # the alignment creates the stationing nest and it has one referent to defined the stationing for the alignment
|
) # the stationing nest has one referent that defines the stationing for the alignment
|
||||||
|
|
||||||
horizontal_alignment = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
horizontal_alignment = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import ifcopenshell.util.element
|
|||||||
|
|
||||||
|
|
||||||
def _create_test_file():
|
def _create_test_file():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -43,7 +43,7 @@ def _create_test_alignment_with_vertical(file):
|
|||||||
# include_vertical=True so that get_curve() (IfcGradientCurve, on the "Axis" representation)
|
# include_vertical=True so that get_curve() (IfcGradientCurve, on the "Axis" representation)
|
||||||
# and get_basis_curve() (IfcCompositeCurve, on the "FootPrint" representation) are different
|
# and get_basis_curve() (IfcCompositeCurve, on the "FootPrint" representation) are different
|
||||||
# entities, letting the on_basis_curve option be observed.
|
# entities, letting the on_basis_curve option be observed.
|
||||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", include_vertical=True, start_station=0.0)
|
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", include_vertical=True)
|
||||||
assert ifcopenshell.api.alignment.get_basis_curve(alignment).is_a("IfcCompositeCurve")
|
assert ifcopenshell.api.alignment.get_basis_curve(alignment).is_a("IfcCompositeCurve")
|
||||||
assert ifcopenshell.api.alignment.get_curve(alignment).is_a("IfcGradientCurve")
|
assert ifcopenshell.api.alignment.get_curve(alignment).is_a("IfcGradientCurve")
|
||||||
assert ifcopenshell.api.alignment.get_basis_curve(alignment) != ifcopenshell.api.alignment.get_curve(alignment)
|
assert ifcopenshell.api.alignment.get_basis_curve(alignment) != ifcopenshell.api.alignment.get_curve(alignment)
|
||||||
@@ -110,6 +110,44 @@ def test_add_stationing_referent_on_basis_curve_false():
|
|||||||
assert basis_curve != ifcopenshell.api.alignment.get_basis_curve(alignment)
|
assert basis_curve != ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_stationing_referent_without_geometry_placed_at_global_origin():
|
||||||
|
# An alignment with no resolvable basis curve gets an IfcLocalPlacement at (0, 0),
|
||||||
|
# not an IfcLinearPlacement, and does not require the alignment to have an ObjectPlacement.
|
||||||
|
file = _create_test_file()
|
||||||
|
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", include_geometry=False)
|
||||||
|
|
||||||
|
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "1+00.000", alignment, distance_along=0.0, station=100.0
|
||||||
|
)
|
||||||
|
|
||||||
|
_assert_common_referent_asserts(referent, "1+00.000", 100.0)
|
||||||
|
assert referent.ObjectPlacement.is_a("IfcLocalPlacement")
|
||||||
|
assert referent.ObjectPlacement.RelativePlacement.Location.Coordinates == (0.0, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_stationing_referent_has_increasing_station():
|
||||||
|
file = _create_test_file()
|
||||||
|
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", include_geometry=False)
|
||||||
|
|
||||||
|
# default: HasIncreasingStation is not written
|
||||||
|
default_referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "1+00.000", alignment, distance_along=0.0, station=100.0
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
ifcopenshell.util.element.get_pset(default_referent, name="Pset_Stationing", prop="HasIncreasingStation") is None
|
||||||
|
)
|
||||||
|
|
||||||
|
# explicit False (reverse stationing) is written
|
||||||
|
reverse_referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "R", alignment, distance_along=0.0, station=100.0, has_increasing_station=False
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
ifcopenshell.util.element.get_pset(reverse_referent, name="Pset_Stationing", prop="HasIncreasingStation") is False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
test_add_stationing_referent_on_basis_curve_none_defaults_to_basis_curve()
|
test_add_stationing_referent_on_basis_curve_none_defaults_to_basis_curve()
|
||||||
test_add_stationing_referent_on_basis_curve_true()
|
test_add_stationing_referent_on_basis_curve_true()
|
||||||
test_add_stationing_referent_on_basis_curve_false()
|
test_add_stationing_referent_on_basis_curve_false()
|
||||||
|
test_add_stationing_referent_without_geometry_placed_at_global_origin()
|
||||||
|
test_add_stationing_referent_has_increasing_station()
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import ifcopenshell.api.unit
|
|||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ifcopenshell.file(schema="IFC4X3")
|
ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
IFC4X3_AVAILABLE = True
|
IFC4X3_AVAILABLE = True
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
IFC4X3_AVAILABLE = False
|
IFC4X3_AVAILABLE = False
|
||||||
@@ -33,7 +33,7 @@ except RuntimeError:
|
|||||||
|
|
||||||
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
||||||
def test_add_stationing_to_alignment():
|
def test_add_stationing_to_alignment():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -46,7 +46,10 @@ def test_add_stationing_to_alignment():
|
|||||||
parent=geometric_representation_context,
|
parent=geometric_representation_context,
|
||||||
)
|
)
|
||||||
|
|
||||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", start_station=2000.0)
|
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment")
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "TestAlignment 2+000.000", alignment, distance_along=0.0, station=2000.0
|
||||||
|
)
|
||||||
|
|
||||||
stationing_nest = ifcopenshell.api.alignment.get_stationing_nest(file, alignment)
|
stationing_nest = ifcopenshell.api.alignment.get_stationing_nest(file, alignment)
|
||||||
referent = stationing_nest.RelatedObjects[0]
|
referent = stationing_nest.RelatedObjects[0]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import ifcopenshell.api.context
|
|||||||
import ifcopenshell.api.unit
|
import ifcopenshell.api.unit
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ifcopenshell.file(schema="IFC4X3")
|
ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
IFC4X3_AVAILABLE = True
|
IFC4X3_AVAILABLE = True
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
IFC4X3_AVAILABLE = False
|
IFC4X3_AVAILABLE = False
|
||||||
@@ -32,13 +32,14 @@ except RuntimeError:
|
|||||||
|
|
||||||
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
||||||
def test_add_vertical_alignment():
|
def test_add_vertical_alignment():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||||
|
|
||||||
alignment = ifcopenshell.api.alignment.create(file, "A1", include_vertical=False)
|
alignment = ifcopenshell.api.alignment.create(file, "A1", include_vertical=False)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "0+00.00", alignment, distance_along=0.0, station=0.0)
|
||||||
|
|
||||||
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
|
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
|
||||||
assert len(alignment.IsNestedBy) == 2 # nests for layout and referents
|
assert len(alignment.IsNestedBy) == 2 # nests for layout and referents
|
||||||
|
|||||||
@@ -22,40 +22,6 @@ import pytest
|
|||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
import ifcopenshell.api.context
|
import ifcopenshell.api.context
|
||||||
import ifcopenshell.api.unit
|
import ifcopenshell.api.unit
|
||||||
import ifcopenshell.util.element
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_stationing_referent_name_includes_alignment_name():
|
|
||||||
"""create() creates an initial stationing IfcReferent from start_station
|
|
||||||
(see add_stationing_referent()). Its Name must include the alignment's
|
|
||||||
own name, the same "<alignment name> <station>" convention
|
|
||||||
update_key_point_referents() uses for its own referents -- otherwise
|
|
||||||
this referent is indistinguishable by name alone from the same-named
|
|
||||||
referent of any OTHER alignment in the same file, unlike every other
|
|
||||||
referent in the model."""
|
|
||||||
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
|
||||||
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
|
||||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
|
||||||
ifcopenshell.api.context.add_context(
|
|
||||||
file,
|
|
||||||
context_type="Model",
|
|
||||||
context_identifier="Axis",
|
|
||||||
target_view="MODEL_VIEW",
|
|
||||||
parent=geometric_representation_context,
|
|
||||||
)
|
|
||||||
|
|
||||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", start_station=4900.0)
|
|
||||||
|
|
||||||
referents = [
|
|
||||||
r
|
|
||||||
for r in ifcopenshell.util.element.get_components(alignment)
|
|
||||||
if r.is_a("IfcReferent")
|
|
||||||
and ifcopenshell.util.element.get_pset(r, name="Pset_Stationing", prop="Station") == 4900.0
|
|
||||||
]
|
|
||||||
assert len(referents) == 1
|
|
||||||
assert referents[0].Name == "TestAlignment 49+00.00"
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -67,7 +33,7 @@ except RuntimeError:
|
|||||||
|
|
||||||
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
||||||
def test_create():
|
def test_create():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -88,6 +54,12 @@ def test_create():
|
|||||||
ali = ifcopenshell.api.alignment.create(file, "A1", include_vertical[i], include_cant[i])
|
ali = ifcopenshell.api.alignment.create(file, "A1", include_vertical[i], include_cant[i])
|
||||||
assert ali != None
|
assert ali != None
|
||||||
|
|
||||||
|
# create() does not define stationing - the alignment has no referent nest at all
|
||||||
|
assert ifcopenshell.api.alignment.get_stationing_nest(file, ali) is None
|
||||||
|
assert not any(
|
||||||
|
related.is_a("IfcReferent") for nest in ali.IsNestedBy for related in nest.RelatedObjects
|
||||||
|
)
|
||||||
|
|
||||||
# verify the geometric representation was created
|
# verify the geometric representation was created
|
||||||
curve = ifcopenshell.api.alignment.get_curve(ali)
|
curve = ifcopenshell.api.alignment.get_curve(ali)
|
||||||
assert curve.is_a() == expected_curve_type[i]
|
assert curve.is_a() == expected_curve_type[i]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import ifcopenshell.api.context
|
|||||||
import ifcopenshell.api.unit
|
import ifcopenshell.api.unit
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ifcopenshell.file(schema="IFC4X3")
|
ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
IFC4X3_AVAILABLE = True
|
IFC4X3_AVAILABLE = True
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
IFC4X3_AVAILABLE = False
|
IFC4X3_AVAILABLE = False
|
||||||
@@ -32,7 +32,7 @@ except RuntimeError:
|
|||||||
|
|
||||||
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
||||||
def test_create_by_pi_method():
|
def test_create_by_pi_method():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -51,7 +51,7 @@ def test_create_by_pi_method():
|
|||||||
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
||||||
|
|
||||||
alignment = ifcopenshell.api.alignment.create_by_pi_method(
|
alignment = ifcopenshell.api.alignment.create_by_pi_method(
|
||||||
file, "TestAlignment", coordinates, radii, vpoints, lengths
|
file, "TestAlignment", coordinates, radii, vpoints, lengths, start_station=10000.0
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
|
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ def test_create_representation():
|
|||||||
(12799.99998062693, 89.99999997234107, 0.9999875002340269, -0.004999937569813611),
|
(12799.99998062693, 89.99999997234107, 0.9999875002340269, -0.004999937569813611),
|
||||||
]
|
]
|
||||||
|
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
file.header.file_description.description = ["ViewDefinition [Alignment-basedView]"]
|
file.header.file_description.description = ["ViewDefinition [Alignment-basedView]"]
|
||||||
|
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="FHWA Alignment")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="FHWA Alignment")
|
||||||
@@ -77,9 +77,15 @@ def test_create_representation():
|
|||||||
site = file.createIfcSite(GlobalId=ifcopenshell.guid.new(), Name="Site")
|
site = file.createIfcSite(GlobalId=ifcopenshell.guid.new(), Name="Site")
|
||||||
ifcopenshell.api.aggregate.assign_object(file, relating_object=project, products=[site])
|
ifcopenshell.api.aggregate.assign_object(file, relating_object=project, products=[site])
|
||||||
|
|
||||||
alignment = ifcopenshell.api.alignment.create(
|
alignment = ifcopenshell.api.alignment.create(file, "E-Line", include_vertical=True, include_geometry=False)
|
||||||
file, "E-Line", include_vertical=True, start_station=10000.0, include_geometry=False
|
|
||||||
|
# stationing is defined before the geometry exists, so the referent is placed at the global
|
||||||
|
# origin; create_representation() must restate it onto the basis curve at DistanceAlong 0.0
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "E-Line 100+00.00", alignment, distance_along=0.0, station=10000.0
|
||||||
)
|
)
|
||||||
|
start_referent = ifcopenshell.api.alignment.get_stationing_nest(file, alignment).RelatedObjects[0]
|
||||||
|
assert start_referent.ObjectPlacement.is_a("IfcLocalPlacement")
|
||||||
|
|
||||||
# alignment is referenced into spatial structure of site per CT 4.1.5.1
|
# alignment is referenced into spatial structure of site per CT 4.1.5.1
|
||||||
ifcopenshell.api.spatial.reference_structure(file, products=[alignment], relating_structure=site)
|
ifcopenshell.api.spatial.reference_structure(file, products=[alignment], relating_structure=site)
|
||||||
@@ -420,6 +426,10 @@ def test_create_representation():
|
|||||||
|
|
||||||
ifcopenshell.api.alignment.create_representation(file, alignment)
|
ifcopenshell.api.alignment.create_representation(file, alignment)
|
||||||
|
|
||||||
|
# the starting referent is now placed relative to the basis curve at DistanceAlong 0.0
|
||||||
|
assert start_referent.ObjectPlacement.is_a("IfcLinearPlacement")
|
||||||
|
assert start_referent.ObjectPlacement.RelativePlacement.Location.DistanceAlong.wrappedValue == 0.0
|
||||||
|
|
||||||
curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||||
assert curve.is_a("IfcCompositeCurve")
|
assert curve.is_a("IfcCompositeCurve")
|
||||||
for s in curve.Segments:
|
for s in curve.Segments:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import ifcopenshell.api.context
|
|||||||
import ifcopenshell.api.unit
|
import ifcopenshell.api.unit
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ifcopenshell.file(schema="IFC4X3")
|
ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
IFC4X3_AVAILABLE = True
|
IFC4X3_AVAILABLE = True
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
IFC4X3_AVAILABLE = False
|
IFC4X3_AVAILABLE = False
|
||||||
@@ -31,7 +31,7 @@ except RuntimeError:
|
|||||||
|
|
||||||
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
||||||
def test_distance_along_from_station():
|
def test_distance_along_from_station():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -64,7 +64,7 @@ def test_distance_along_from_station_with_station_equations():
|
|||||||
# Reproduces the worked example from the IFC Alignment Geometry Implementation Guide, chapter 9.2.6:
|
# Reproduces the worked example from the IFC Alignment Geometry Implementation Guide, chapter 9.2.6:
|
||||||
# a gap equation (P3: incoming 14+00.00, outgoing 17+00.00) and an overlap equation
|
# a gap equation (P3: incoming 14+00.00, outgoing 17+00.00) and an overlap equation
|
||||||
# (P4: incoming 19+00.00, outgoing 18+50.00).
|
# (P4: incoming 19+00.00, outgoing 18+50.00).
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -111,5 +111,159 @@ def test_distance_along_from_station_with_station_equations():
|
|||||||
assert distance_along_from_station(file, alignment, 1875.0) == pytest.approx(625.0)
|
assert distance_along_from_station(file, alignment, 1875.0) == pytest.approx(625.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_distance_along_from_station_reverse_stationing_with_gap_equation():
|
||||||
|
# A reverse-stationed alignment: HasIncreasingStation=False on the starting referent, station
|
||||||
|
# labels decreasing as distance along increases, with one gap equation.
|
||||||
|
# R1: D 0.0, Station 20+00.00, HasIncreasingStation=False
|
||||||
|
# R2: D 400.0, Station 15+50.00, IncomingStation 16+00.00 (gap of 50)
|
||||||
|
# R3: D 800.0, Station 11+50.00
|
||||||
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
|
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
||||||
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
|
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||||
|
ifcopenshell.api.context.add_context(
|
||||||
|
file,
|
||||||
|
context_type="Model",
|
||||||
|
context_identifier="Axis",
|
||||||
|
target_view="MODEL_VIEW",
|
||||||
|
parent=geometric_representation_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||||
|
radii = [(1000.0), (1250.0), (950.0)]
|
||||||
|
alignment = ifcopenshell.api.alignment.create_by_pi_method(file, "TestAlignment", coordinates, radii)
|
||||||
|
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "R1", alignment, distance_along=0.0, station=2000.0, has_increasing_station=False
|
||||||
|
)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "R2", alignment, distance_along=400.0, station=1550.0, incoming_station=1600.0
|
||||||
|
)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "R3", alignment, distance_along=800.0, station=1150.0)
|
||||||
|
|
||||||
|
distance_along_from_station = ifcopenshell.api.alignment.distance_along_from_station
|
||||||
|
|
||||||
|
# Sta. 18+00.00 -> governed by R1
|
||||||
|
assert distance_along_from_station(file, alignment, 1800.0) == pytest.approx(200.0)
|
||||||
|
|
||||||
|
# Sta. 13+00.00 -> governed by R2; naive subtraction from the start would overstate by the 50 ft gap
|
||||||
|
assert distance_along_from_station(file, alignment, 1300.0) == pytest.approx(650.0)
|
||||||
|
|
||||||
|
# Sta. 15+75.00 falls inside the range the gap equation at R2 skipped -> no distance along
|
||||||
|
assert distance_along_from_station(file, alignment, 1575.0) is None
|
||||||
|
|
||||||
|
|
||||||
|
def _long_alignment(file):
|
||||||
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
|
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
||||||
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
|
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||||
|
ifcopenshell.api.context.add_context(
|
||||||
|
file,
|
||||||
|
context_type="Model",
|
||||||
|
context_identifier="Axis",
|
||||||
|
target_view="MODEL_VIEW",
|
||||||
|
parent=geometric_representation_context,
|
||||||
|
)
|
||||||
|
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||||
|
radii = [(1000.0), (1250.0), (950.0)]
|
||||||
|
return ifcopenshell.api.alignment.create_by_pi_method(file, "TestAlignment", coordinates, radii)
|
||||||
|
|
||||||
|
|
||||||
|
def test_distance_along_from_station_direction_switch_increasing_then_decreasing():
|
||||||
|
# A peak: station labels rise to R2 then fall. HasIncreasingStation=False on R2 governs the
|
||||||
|
# region after it. Data is self-consistent (no equations): each region is 500 units long with
|
||||||
|
# a 500 station-label swing.
|
||||||
|
# R1 D 0.0 S 1000.0
|
||||||
|
# R2 D 500.0 S 1500.0 HasIncreasingStation=False
|
||||||
|
# R3 D 1000.0 S 1000.0
|
||||||
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
|
alignment = _long_alignment(file)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "R1", alignment, distance_along=0.0, station=1000.0)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "R2", alignment, distance_along=500.0, station=1500.0, has_increasing_station=False
|
||||||
|
)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "R3", alignment, distance_along=1000.0, station=1000.0)
|
||||||
|
|
||||||
|
dafs = ifcopenshell.api.alignment.distance_along_from_station
|
||||||
|
|
||||||
|
# Sta 12+00 appears twice (rising at D 200, falling at D 800); the downstream match is returned
|
||||||
|
assert dafs(file, alignment, 1200.0) == pytest.approx(800.0)
|
||||||
|
# the peak label sits at the single point D 500
|
||||||
|
assert dafs(file, alignment, 1500.0) == pytest.approx(500.0)
|
||||||
|
# the end label
|
||||||
|
assert dafs(file, alignment, 1000.0) == pytest.approx(1000.0)
|
||||||
|
# Sta 16+00 is above the peak - it exists nowhere on the alignment
|
||||||
|
assert dafs(file, alignment, 1600.0) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_distance_along_from_station_direction_switch_decreasing_then_increasing():
|
||||||
|
# A valley: labels fall to R2 then rise. R1 starts the alignment decreasing
|
||||||
|
# (HasIncreasingStation=False); R2 switches it back to increasing.
|
||||||
|
# R1 D 0.0 S 2000.0 HasIncreasingStation=False
|
||||||
|
# R2 D 500.0 S 1500.0 HasIncreasingStation=True
|
||||||
|
# R3 D 1000.0 S 2000.0
|
||||||
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
|
alignment = _long_alignment(file)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "R1", alignment, distance_along=0.0, station=2000.0, has_increasing_station=False
|
||||||
|
)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "R2", alignment, distance_along=500.0, station=1500.0, has_increasing_station=True
|
||||||
|
)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "R3", alignment, distance_along=1000.0, station=2000.0)
|
||||||
|
|
||||||
|
dafs = ifcopenshell.api.alignment.distance_along_from_station
|
||||||
|
|
||||||
|
# Sta 18+00 appears twice (falling at D 200, rising at D 800); the downstream match is returned
|
||||||
|
assert dafs(file, alignment, 1800.0) == pytest.approx(800.0)
|
||||||
|
# the valley label sits at the single point D 500
|
||||||
|
assert dafs(file, alignment, 1500.0) == pytest.approx(500.0)
|
||||||
|
# Sta 14+00 is below the valley - it exists nowhere on the alignment
|
||||||
|
assert dafs(file, alignment, 1400.0) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_distance_along_from_station_multiple_direction_switches():
|
||||||
|
# Not realistic, but valid IFC: stationing direction flips at every referent.
|
||||||
|
# R1 D 0.0 S 1000.0 increasing [0, 300] 1000 -> 1300
|
||||||
|
# R2 D 300.0 S 1300.0 HasIncreasingStation=False decreasing [300, 600] 1300 -> 1000
|
||||||
|
# R3 D 600.0 S 1000.0 HasIncreasingStation=True increasing [600, 900] 1000 -> 1300
|
||||||
|
# R4 D 900.0 S 1300.0 HasIncreasingStation=False decreasing [900, 1200] 1300 -> 1000
|
||||||
|
# R5 D 1200.0 S 1000.0
|
||||||
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
|
alignment = _long_alignment(file)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "R1", alignment, distance_along=0.0, station=1000.0)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "R2", alignment, distance_along=300.0, station=1300.0, has_increasing_station=False
|
||||||
|
)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "R3", alignment, distance_along=600.0, station=1000.0, has_increasing_station=True
|
||||||
|
)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "R4", alignment, distance_along=900.0, station=1300.0, has_increasing_station=False
|
||||||
|
)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "R5", alignment, distance_along=1200.0, station=1000.0)
|
||||||
|
|
||||||
|
dafs = ifcopenshell.api.alignment.distance_along_from_station
|
||||||
|
|
||||||
|
# referents nest in DistanceAlong order regardless of the direction flips
|
||||||
|
nest = ifcopenshell.api.alignment.get_stationing_nest(file, alignment)
|
||||||
|
assert [r.Name for r in nest.RelatedObjects] == ["R1", "R2", "R3", "R4", "R5"]
|
||||||
|
|
||||||
|
# Sta 11+00 appears in every one of the four regions; the last (most downstream) match wins
|
||||||
|
assert dafs(file, alignment, 1100.0) == pytest.approx(1100.0)
|
||||||
|
# the shared min label resolves to the very end of the alignment
|
||||||
|
assert dafs(file, alignment, 1000.0) == pytest.approx(1200.0)
|
||||||
|
# Sta 13+50 is above every peak - nowhere on the alignment
|
||||||
|
assert dafs(file, alignment, 1350.0) is None
|
||||||
|
# a label exactly at a peak
|
||||||
|
assert dafs(file, alignment, 1300.0) == pytest.approx(900.0)
|
||||||
|
|
||||||
|
|
||||||
test_distance_along_from_station()
|
test_distance_along_from_station()
|
||||||
test_distance_along_from_station_with_station_equations()
|
test_distance_along_from_station_with_station_equations()
|
||||||
|
test_distance_along_from_station_reverse_stationing_with_gap_equation()
|
||||||
|
test_distance_along_from_station_direction_switch_increasing_then_decreasing()
|
||||||
|
test_distance_along_from_station_direction_switch_decreasing_then_increasing()
|
||||||
|
test_distance_along_from_station_multiple_direction_switches()
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import ifcopenshell.api.context
|
|||||||
import ifcopenshell.api.unit
|
import ifcopenshell.api.unit
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ifcopenshell.file(schema="IFC4X3")
|
ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
IFC4X3_AVAILABLE = True
|
IFC4X3_AVAILABLE = True
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
IFC4X3_AVAILABLE = False
|
IFC4X3_AVAILABLE = False
|
||||||
@@ -35,7 +35,7 @@ except RuntimeError:
|
|||||||
# compound curve (no tangent between curves)
|
# compound curve (no tangent between curves)
|
||||||
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
||||||
def test_horizontal_layout_by_pi_method():
|
def test_horizontal_layout_by_pi_method():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -51,7 +51,9 @@ def test_horizontal_layout_by_pi_method():
|
|||||||
coordinates = [(838.760, 224.745), (965.926, 258.819), (1226.296, 258.819), (1350.817, 291.415)]
|
coordinates = [(838.760, 224.745), (965.926, 258.819), (1226.296, 258.819), (1350.817, 291.415)]
|
||||||
radii = [(1000.0), (1000.0)]
|
radii = [(1000.0), (1000.0)]
|
||||||
|
|
||||||
alignment = ifcopenshell.api.alignment.create_by_pi_method(file, "TestAlignment", coordinates, radii)
|
alignment = ifcopenshell.api.alignment.create_by_pi_method(
|
||||||
|
file, "TestAlignment", coordinates, radii, start_station=10000.0
|
||||||
|
)
|
||||||
|
|
||||||
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
|
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
|
||||||
assert len(alignment.IsNestedBy) == 2
|
assert len(alignment.IsNestedBy) == 2
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ LENGTHS = [1600.0, 1200.0, 2000.0, 800.0]
|
|||||||
|
|
||||||
|
|
||||||
def _new_file():
|
def _new_file():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -49,7 +49,7 @@ def _new_file():
|
|||||||
|
|
||||||
|
|
||||||
def _new_file_no_context():
|
def _new_file_no_context():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -341,7 +341,8 @@ def test_single_real_segment_produces_only_boundary_labels():
|
|||||||
|
|
||||||
def test_start_station_composes_for_child_alignment():
|
def test_start_station_composes_for_child_alignment():
|
||||||
file = _new_file()
|
file = _new_file()
|
||||||
alignment = ifcopenshell.api.alignment.create(file, "A1", include_vertical=False, start_station=100.0)
|
alignment = ifcopenshell.api.alignment.create(file, "A1", include_vertical=False)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "A1 1+00.00", alignment, distance_along=0.0, station=100.0)
|
||||||
ifcopenshell.api.alignment.add_vertical_layout(file, alignment)
|
ifcopenshell.api.alignment.add_vertical_layout(file, alignment)
|
||||||
ifcopenshell.api.alignment.add_vertical_layout(file, alignment) # forces the child-alignment split
|
ifcopenshell.api.alignment.add_vertical_layout(file, alignment) # forces the child-alignment split
|
||||||
|
|
||||||
@@ -379,7 +380,8 @@ def test_rel_nests_from_ancestor_used_for_naming_and_nesting():
|
|||||||
to an ancestor alignment's own rel_nests -- e.g. the same one already holding that
|
to an ancestor alignment's own rel_nests -- e.g. the same one already holding that
|
||||||
ancestor's horizontal key points -- rather than the child's generic "Child of X" name."""
|
ancestor's horizontal key points -- rather than the child's generic "Child of X" name."""
|
||||||
file = _new_file()
|
file = _new_file()
|
||||||
alignment = ifcopenshell.api.alignment.create(file, "A1", include_vertical=False, start_station=100.0)
|
alignment = ifcopenshell.api.alignment.create(file, "A1", include_vertical=False)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "A1 1+00.00", alignment, distance_along=0.0, station=100.0)
|
||||||
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||||
horizontal_nest = ifcopenshell.api.alignment.update_key_point_referents(file, horizontal)
|
horizontal_nest = ifcopenshell.api.alignment.update_key_point_referents(file, horizontal)
|
||||||
horizontal_count = len(horizontal_nest.RelatedObjects)
|
horizontal_count = len(horizontal_nest.RelatedObjects)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import ifcopenshell.api.context
|
|||||||
import ifcopenshell.api.unit
|
import ifcopenshell.api.unit
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ifcopenshell.file(schema="IFC4X3")
|
ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
IFC4X3_AVAILABLE = True
|
IFC4X3_AVAILABLE = True
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
IFC4X3_AVAILABLE = False
|
IFC4X3_AVAILABLE = False
|
||||||
@@ -35,7 +35,7 @@ except RuntimeError:
|
|||||||
# compound vertical curve (no gradient between curves)
|
# compound vertical curve (no gradient between curves)
|
||||||
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
@pytest.mark.skipif(not IFC4X3_AVAILABLE, reason="IFC4X3 not available")
|
||||||
def test_vertical_layout_by_pi_method():
|
def test_vertical_layout_by_pi_method():
|
||||||
file = ifcopenshell.file(schema="IFC4X3")
|
file = ifcopenshell.file(schema="IFC4X3_ADD2")
|
||||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
||||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
@@ -61,6 +61,8 @@ def test_vertical_layout_by_pi_method():
|
|||||||
|
|
||||||
ifcopenshell.api.alignment.create_layout_segment(file, hlayout, segment1)
|
ifcopenshell.api.alignment.create_layout_segment(file, hlayout, segment1)
|
||||||
|
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(file, "0+00.00", alignment, distance_along=0.0, station=0.0)
|
||||||
|
|
||||||
vpoints = [(0.0, 110.0), (400.0, 100.0), (800.0, 115.0), (1300.0, 125.0), (1800.0, 105.0)]
|
vpoints = [(0.0, 110.0), (400.0, 100.0), (800.0, 115.0), (1300.0, 125.0), (1800.0, 105.0)]
|
||||||
lengths = [(800.0), (0.0), (1000.0)]
|
lengths = [(800.0), (0.0), (1000.0)]
|
||||||
vlayout = ifcopenshell.api.alignment.get_vertical_layout(alignment)
|
vlayout = ifcopenshell.api.alignment.get_vertical_layout(alignment)
|
||||||
|
|||||||
Reference in New Issue
Block a user