mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Alignment API update for station and positioning referents. Fixes bug with fallback position.
This commit is contained in:
@@ -49,6 +49,7 @@ Future versions of this API may support:
|
|||||||
|
|
||||||
from ._get_segment_start_point_label import register_referent_name_callback
|
from ._get_segment_start_point_label import register_referent_name_callback
|
||||||
from .add_stationing_referent import add_stationing_referent
|
from .add_stationing_referent import add_stationing_referent
|
||||||
|
from .add_positioning_referent import add_positioning_referent
|
||||||
from .add_vertical_layout import add_vertical_layout
|
from .add_vertical_layout import add_vertical_layout
|
||||||
from .add_zero_length_segment import add_zero_length_segment
|
from .add_zero_length_segment import add_zero_length_segment
|
||||||
from .create import create
|
from .create import create
|
||||||
@@ -94,6 +95,7 @@ from .util import *
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"add_stationing_referent",
|
"add_stationing_referent",
|
||||||
|
"add_positioning_referent",
|
||||||
"add_vertical_layout",
|
"add_vertical_layout",
|
||||||
"add_zero_length_segment",
|
"add_zero_length_segment",
|
||||||
"create",
|
"create",
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# 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.api.alignment.update_fallback_position import update_fallback_position
|
||||||
|
import ifcopenshell.api.pset
|
||||||
|
import ifcopenshell.guid
|
||||||
|
from ifcopenshell import entity_instance
|
||||||
|
|
||||||
|
|
||||||
|
def add_positioning_referent(
|
||||||
|
file: ifcopenshell.file,
|
||||||
|
name: str,
|
||||||
|
alignment: entity_instance,
|
||||||
|
distance_along: float,
|
||||||
|
station: float,
|
||||||
|
positioned_product: entity_instance,
|
||||||
|
) -> entity_instance:
|
||||||
|
"""
|
||||||
|
Semantically defines the position of a product along an alignment by adding an IfcReferent to the alignment that defines the stationing system.
|
||||||
|
|
||||||
|
:param alignment: the alignment to receive the referent
|
||||||
|
:param distance_along: distance along the alignment basis curve
|
||||||
|
:param station: station value
|
||||||
|
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
||||||
|
:param positioned_product: the product whose position is informed by the referent
|
||||||
|
:return: referent
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
alignment = model.by_type("IfcAlignment")[0]
|
||||||
|
pier = model.by_type("IfcBridgePart")[0]
|
||||||
|
ifcopenshell.api.alignment.add_positioning_referent(model,name="Pier 1 Sta 1+00",alignment=alignment,distance_along=0.0,station=100.0,positioned_product=pier)
|
||||||
|
"""
|
||||||
|
|
||||||
|
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||||
|
|
||||||
|
object_placement = None
|
||||||
|
representation = None
|
||||||
|
if basis_curve and basis_curve.is_a("IfcCompositeCurve") and 0 < len(basis_curve.Segments):
|
||||||
|
object_placement = file.createIfcLinearPlacement(
|
||||||
|
RelativePlacement=file.createIfcAxis2PlacementLinear(
|
||||||
|
Location=file.createIfcPointByDistanceExpression(
|
||||||
|
DistanceAlong=file.createIfcLengthMeasure(distance_along),
|
||||||
|
OffsetLateral=None,
|
||||||
|
OffsetVertical=None,
|
||||||
|
OffsetLongitudinal=None,
|
||||||
|
BasisCurve=basis_curve,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
update_fallback_position(file, object_placement)
|
||||||
|
else:
|
||||||
|
object_placement = file.createIfcLocalPlacement(
|
||||||
|
PlacementRelTo=None,
|
||||||
|
RelativePlacement=file.createIfcAxis2Placement2D(
|
||||||
|
Location=file.createIfcCartesianPoint(alignment.ObjectPlacement.RelativePlacement.Location.Coordinates)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# this commented out code is what you would do to add a geometric representation of the referent
|
||||||
|
# the example is a circle. a better way would be to pass a representation into the function
|
||||||
|
# representation = file.create_entity(
|
||||||
|
# name="IfcCircle",
|
||||||
|
# position=file.createIfcAxis2Placement2D(Location=file.createIfcCartesianPoint(Coordinates=(0.0, 0.0)),
|
||||||
|
# radius=1.0)
|
||||||
|
# )
|
||||||
|
|
||||||
|
# create referent for the station
|
||||||
|
referent = file.createIfcReferent(
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
OwnerHistory=None,
|
||||||
|
Name=name,
|
||||||
|
Description=None,
|
||||||
|
ObjectType=None,
|
||||||
|
ObjectPlacement=object_placement,
|
||||||
|
Representation=representation,
|
||||||
|
PredefinedType="POSITION",
|
||||||
|
)
|
||||||
|
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=referent, name="Pset_Stationing")
|
||||||
|
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": station})
|
||||||
|
|
||||||
|
if len(referent.Positions) == 0:
|
||||||
|
rel_positions = file.createIfcRelPositions(
|
||||||
|
GlobalId=ifcopenshell.guid.new(),
|
||||||
|
RelatingPositioningElement=referent,
|
||||||
|
RelatedProducts=[
|
||||||
|
positioned_product,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
referent.Positions[0].RelatedProducts += (positioned_product,)
|
||||||
|
|
||||||
|
return referent
|
||||||
@@ -16,35 +16,33 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import numpy as np
|
from typing import Optional
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
|
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
|
||||||
import ifcopenshell.api.pset
|
import ifcopenshell.api.pset
|
||||||
import ifcopenshell.geom
|
|
||||||
import ifcopenshell.guid
|
import ifcopenshell.guid
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import ifcopenshell.util.unit
|
from ifcopenshell import entity_instance
|
||||||
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def add_stationing_referent(
|
def add_stationing_referent(
|
||||||
file: ifcopenshell.file,
|
file: ifcopenshell.file,
|
||||||
|
name: str,
|
||||||
alignment: entity_instance,
|
alignment: entity_instance,
|
||||||
distance_along: float,
|
distance_along: float,
|
||||||
station: float,
|
station: float,
|
||||||
name: str,
|
incoming_station: Optional[float] = None,
|
||||||
positioned_product: entity_instance,
|
|
||||||
) -> entity_instance:
|
) -> entity_instance:
|
||||||
"""
|
"""
|
||||||
Adds an IfcReferent to the alignment with the Pset_Stationing property set.
|
Adds an IfcReferent to the alignment that defines the stationing system.
|
||||||
|
|
||||||
|
: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 name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
:param incoming_station: station value of the incoming segment, only set to specify a station equation
|
||||||
:param positioned_product: the product whose position is informed by the referent
|
|
||||||
:return: referent
|
:return: referent
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@@ -52,7 +50,7 @@ def add_stationing_referent(
|
|||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
alignment = model.by_type("IfcAlignment")[0]
|
alignment = model.by_type("IfcAlignment")[0]
|
||||||
ifcopenshell.api.alignment.add_stationing_referent(model,alignment=alignment,distance_along=0.0,station=100.0)
|
ifcopenshell.api.alignment.add_stationing_referent(model,name="1+00.0",alignment=alignment,distance_along=0.0,station=100.0)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||||
@@ -100,8 +98,12 @@ def add_stationing_referent(
|
|||||||
Representation=representation,
|
Representation=representation,
|
||||||
PredefinedType="STATION",
|
PredefinedType="STATION",
|
||||||
)
|
)
|
||||||
|
properties = {"Station": station}
|
||||||
|
if incoming_station is not None:
|
||||||
|
properties["IncomingStation"] = incoming_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={"Station": station})
|
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties=properties)
|
||||||
|
|
||||||
nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||||
if nest is None:
|
if nest is None:
|
||||||
@@ -115,15 +117,4 @@ def add_stationing_referent(
|
|||||||
nest.RelatedObjects, key=lambda x: ifcopenshell.util.element.get_pset(x, name="Pset_Stationing", prop="Station")
|
nest.RelatedObjects, key=lambda x: ifcopenshell.util.element.get_pset(x, name="Pset_Stationing", prop="Station")
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(referent.Positions) == 0:
|
|
||||||
rel_positions = file.createIfcRelPositions(
|
|
||||||
GlobalId=ifcopenshell.guid.new(),
|
|
||||||
RelatingPositioningElement=referent,
|
|
||||||
RelatedProducts=[
|
|
||||||
positioned_product,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
referent.Positions[0].RelatedProducts += (positioned_product,)
|
|
||||||
|
|
||||||
return referent
|
return referent
|
||||||
|
|||||||
@@ -51,18 +51,6 @@ def _move_vertical_layout_to_child_alignment(
|
|||||||
# aggregate the child alignment to the parent alignment
|
# aggregate the child alignment to the parent alignment
|
||||||
ifcopenshell.api.aggregate.assign_object(file, products=[child_alignment], relating_object=parent_alignment)
|
ifcopenshell.api.aggregate.assign_object(file, products=[child_alignment], relating_object=parent_alignment)
|
||||||
|
|
||||||
# move all referents positioning segments of the vertical layout to the referent nest of the child alignment
|
|
||||||
child_referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, child_alignment)
|
|
||||||
parent_referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, parent_alignment)
|
|
||||||
for referent in parent_referent_nest.RelatedObjects:
|
|
||||||
for product in referent.Positions[0].RelatedProducts:
|
|
||||||
if product.is_a("IfcAlignmentSegment") and product.Nests[0].RelatingObject == vertical_layout:
|
|
||||||
# ifcopenshell.api.nest.change_nest(file,referent,child_alignment) - this doesn't work because referent is assigned to child_alignment.IsNestedBy[0].RelatedObjects
|
|
||||||
# and it needs to be assigned to child_alignment.IsNestedBy[1].RelatedObjects
|
|
||||||
# move the referent manually - unassign it and add it to the child alignment's referent nest
|
|
||||||
ifcopenshell.api.nest.unassign_object(file, [referent])
|
|
||||||
child_referent_nest.RelatedObjects += (referent,)
|
|
||||||
|
|
||||||
# if the parent alignment has a representation, move the Axis/Curve3D represention to the child alignment
|
# if the parent alignment has a representation, move the Axis/Curve3D represention to the child alignment
|
||||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||||
if base_curve:
|
if base_curve:
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ def create(
|
|||||||
|
|
||||||
referent_name = ifcopenshell.util.alignment.station_as_string(file, start_station)
|
referent_name = ifcopenshell.util.alignment.station_as_string(file, start_station)
|
||||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
file, alignment, 0.0, start_station, referent_name, alignment
|
file, referent_name, alignment, 0.0, start_station
|
||||||
)
|
)
|
||||||
|
|
||||||
for layout in alignment_layouts:
|
for layout in alignment_layouts:
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ def create_as_polyline(
|
|||||||
|
|
||||||
# define stationing
|
# define stationing
|
||||||
name = ifcopenshell.util.alignment.station_as_string(file, start_station)
|
name = ifcopenshell.util.alignment.station_as_string(file, start_station)
|
||||||
referent = ifcopenshell.api.alignment.add_stationing_referent(file, alignment, 0.0, start_station, name, alignment)
|
referent = ifcopenshell.api.alignment.add_stationing_referent(file, 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]
|
||||||
|
|||||||
@@ -16,23 +16,47 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api.alignment
|
import ifcopenshell.api.alignment
|
||||||
|
import ifcopenshell.util.element
|
||||||
from ifcopenshell import entity_instance
|
from ifcopenshell import entity_instance
|
||||||
|
|
||||||
|
|
||||||
def distance_along_from_station(file: ifcopenshell.file, alignment: entity_instance, station: float) -> float:
|
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]:
|
||||||
"""
|
"""
|
||||||
Given a station, returns the distance along the horizontal alignment.
|
Given a station, returns the distance along the horizontal alignment.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
.. note:: The current implementation does not account for station equations and assumes stationing is increasing along the alignment.
|
Station equations (where Pset_Stationing.IncomingStation is set on a referent) are taken into account.
|
||||||
|
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
|
||||||
|
the segment defined by the last referent whose outgoing station is less than or equal to it, and the
|
||||||
|
distance along is computed as D + (station - S) for that referent.
|
||||||
|
|
||||||
|
If the station falls within a gap introduced by a forward (gap) station equation - that is, it was skipped
|
||||||
|
over by the 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
|
||||||
|
distances along the alignment, one on either side of the equation. This implementation returns the distance
|
||||||
|
along in the segment following the equation (i.e. the outgoing side).
|
||||||
|
|
||||||
:param alignment: the alignment
|
:param alignment: the alignment
|
||||||
:param station: station value
|
:param station: station value
|
||||||
:return: distance along the horizontal alignment
|
:return: distance along the horizontal alignment, or None if the station falls inside a station equation gap
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@@ -43,6 +67,33 @@ def distance_along_from_station(file: ifcopenshell.file, alignment: entity_insta
|
|||||||
print(dist_along) # 100.00
|
print(dist_along) # 100.00
|
||||||
"""
|
"""
|
||||||
|
|
||||||
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||||
dist_along = station - start_station
|
if referent_nest is None:
|
||||||
return dist_along
|
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
||||||
|
return station - start_station
|
||||||
|
|
||||||
|
stations = [
|
||||||
|
(_distance_along_of_referent(referent), ifcopenshell.util.element.get_pset(referent, name="Pset_Stationing", prop="Station"))
|
||||||
|
for referent in referent_nest.RelatedObjects
|
||||||
|
]
|
||||||
|
stations.sort(key=lambda entry: entry[0])
|
||||||
|
|
||||||
|
index = None
|
||||||
|
for i, (distance_along, outgoing_station) in enumerate(stations):
|
||||||
|
if outgoing_station <= station:
|
||||||
|
index = i
|
||||||
|
|
||||||
|
if index is None:
|
||||||
|
# station precedes the alignment's starting station; extrapolate from the first referent
|
||||||
|
distance_along, outgoing_station = stations[0]
|
||||||
|
return distance_along + (station - outgoing_station)
|
||||||
|
|
||||||
|
distance_along, outgoing_station = stations[index]
|
||||||
|
|
||||||
|
if index + 1 < len(stations):
|
||||||
|
next_distance_along, _ = stations[index + 1]
|
||||||
|
if station - outgoing_station > next_distance_along - distance_along:
|
||||||
|
# the station was skipped over by a forward (gap) station equation
|
||||||
|
return None
|
||||||
|
|
||||||
|
return distance_along + (station - outgoing_station)
|
||||||
|
|||||||
@@ -36,9 +36,12 @@ def update_fallback_position(file: ifcopenshell.file, lp: entity_instance):
|
|||||||
|
|
||||||
p = ifcopenshell.util.placement.get_local_placement(lp)
|
p = ifcopenshell.util.placement.get_local_placement(lp)
|
||||||
|
|
||||||
x = float(p[0, 3])
|
|
||||||
y = float(p[1, 3])
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
||||||
z = float(p[2, 3])
|
|
||||||
|
x = float(p[0, 3])*unit_scale
|
||||||
|
y = float(p[1, 3])*unit_scale
|
||||||
|
z = float(p[2, 3])*unit_scale
|
||||||
|
|
||||||
rx = float(p[0, 0])
|
rx = float(p[0, 0])
|
||||||
ry = float(p[1, 0])
|
ry = float(p[1, 0])
|
||||||
|
|||||||
@@ -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.api.alignment
|
||||||
|
import ifcopenshell.api.context
|
||||||
|
import ifcopenshell.api.unit
|
||||||
|
import ifcopenshell.util.element
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_positioning_referent():
|
||||||
|
file = ifcopenshell.file(schema="IFC4X3")
|
||||||
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
|
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||||
|
axis_model_representation_subcontext = 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=2000.0)
|
||||||
|
|
||||||
|
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||||
|
segment = ifcopenshell.api.alignment.get_layout_segments(horizontal_layout)[0]
|
||||||
|
|
||||||
|
referent = ifcopenshell.api.alignment.add_positioning_referent(
|
||||||
|
file, "P.C.", alignment, distance_along=0.0, station=2000.0, positioned_product=segment
|
||||||
|
)
|
||||||
|
|
||||||
|
assert referent.is_a("IfcReferent")
|
||||||
|
assert referent.PredefinedType == "POSITION"
|
||||||
|
assert referent.Name == "P.C."
|
||||||
|
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing")
|
||||||
|
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing", prop="Station") == 2000.0
|
||||||
|
assert referent.ObjectPlacement != None
|
||||||
|
|
||||||
|
assert len(referent.Positions) == 1
|
||||||
|
rel_positions = referent.Positions[0]
|
||||||
|
assert rel_positions.is_a("IfcRelPositions")
|
||||||
|
assert rel_positions.RelatingPositioningElement == referent
|
||||||
|
assert rel_positions.RelatedProducts == (segment,)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_positioning_referent_creates_separate_referent_per_call():
|
||||||
|
file = ifcopenshell.file(schema="IFC4X3")
|
||||||
|
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||||
|
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||||
|
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||||
|
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||||
|
axis_model_representation_subcontext = 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=2000.0)
|
||||||
|
|
||||||
|
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||||
|
segment = ifcopenshell.api.alignment.get_layout_segments(horizontal_layout)[0]
|
||||||
|
|
||||||
|
first_referent = ifcopenshell.api.alignment.add_positioning_referent(
|
||||||
|
file, "P.C.", alignment, distance_along=0.0, station=2000.0, positioned_product=segment
|
||||||
|
)
|
||||||
|
|
||||||
|
other_product = file.createIfcBuildingElementProxy(GlobalId=ifcopenshell.guid.new(), Name="Sign")
|
||||||
|
second_referent = ifcopenshell.api.alignment.add_positioning_referent(
|
||||||
|
file, "P.C.", alignment, distance_along=0.0, station=2000.0, positioned_product=other_product
|
||||||
|
)
|
||||||
|
|
||||||
|
# each call creates its own IfcReferent, each with its own IfcRelPositions to the product passed in
|
||||||
|
assert first_referent != second_referent
|
||||||
|
assert len(first_referent.Positions) == 1
|
||||||
|
assert first_referent.Positions[0].RelatedProducts == (segment,)
|
||||||
|
assert len(second_referent.Positions) == 1
|
||||||
|
assert second_referent.Positions[0].RelatedProducts == (other_product,)
|
||||||
|
|
||||||
|
|
||||||
|
test_add_positioning_referent()
|
||||||
|
test_add_positioning_referent_creates_separate_referent_per_call()
|
||||||
@@ -48,5 +48,26 @@ def test_add_stationing_to_alignment():
|
|||||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing", prop="Station") == 2000.0
|
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing", prop="Station") == 2000.0
|
||||||
assert referent.ObjectPlacement != None
|
assert referent.ObjectPlacement != None
|
||||||
|
|
||||||
|
# add a station equation at 1000 distance along. this is station 3+000 in coming and 4+000 outgoing.
|
||||||
|
# this is a gap equation.
|
||||||
|
second_referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "4+000.000", alignment, distance_along=1000.0, station=4000.0, incoming_station=3000.0
|
||||||
|
)
|
||||||
|
|
||||||
|
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||||
|
assert len(referent_nest.RelatedObjects) == 2
|
||||||
|
|
||||||
|
assert second_referent == referent_nest.RelatedObjects[1]
|
||||||
|
|
||||||
|
assert second_referent.PredefinedType == "STATION"
|
||||||
|
assert second_referent.Name == "4+000.000"
|
||||||
|
assert ifcopenshell.util.element.get_pset(element=second_referent, name="Pset_Stationing")
|
||||||
|
assert ifcopenshell.util.element.get_pset(element=second_referent, name="Pset_Stationing", prop="Station") == 4000.0
|
||||||
|
assert (
|
||||||
|
ifcopenshell.util.element.get_pset(element=second_referent, name="Pset_Stationing", prop="IncomingStation")
|
||||||
|
== 3000.0
|
||||||
|
)
|
||||||
|
assert second_referent.ObjectPlacement != None
|
||||||
|
|
||||||
|
|
||||||
test_add_stationing_to_alignment()
|
test_add_stationing_to_alignment()
|
||||||
|
|||||||
@@ -53,4 +53,56 @@ def test_distance_along_from_station():
|
|||||||
assert ifcopenshell.api.alignment.distance_along_from_station(file, alignment, 17525.36) == pytest.approx(7525.36)
|
assert ifcopenshell.api.alignment.distance_along_from_station(file, alignment, 17525.36) == pytest.approx(7525.36)
|
||||||
|
|
||||||
|
|
||||||
|
def test_distance_along_from_station_with_station_equations():
|
||||||
|
# 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
|
||||||
|
# (P4: incoming 19+00.00, outgoing 18+50.00).
|
||||||
|
file = ifcopenshell.file(schema="IFC4X3")
|
||||||
|
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")
|
||||||
|
axis_model_representation_subcontext = 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)]
|
||||||
|
vpoints = [(0.0, 100.0), (2000.0, 135.0), (5000.0, 105.0), (7400.0, 153.0), (9800.0, 105.0), (12800.0, 90.0)]
|
||||||
|
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
||||||
|
|
||||||
|
alignment = ifcopenshell.api.alignment.create_by_pi_method(
|
||||||
|
file, "TestAlignment", coordinates, radii, vpoints, lengths, start_station=1000.0
|
||||||
|
)
|
||||||
|
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "P3", alignment, distance_along=400.0, station=1700.0, incoming_station=1400.0
|
||||||
|
)
|
||||||
|
ifcopenshell.api.alignment.add_stationing_referent(
|
||||||
|
file, "P4", alignment, distance_along=600.0, station=1850.0, incoming_station=1900.0
|
||||||
|
)
|
||||||
|
|
||||||
|
distance_along_from_station = ifcopenshell.api.alignment.distance_along_from_station
|
||||||
|
|
||||||
|
# between P2 and P3: Sta. 13+00.00
|
||||||
|
assert distance_along_from_station(file, alignment, 1300.0) == pytest.approx(300.0)
|
||||||
|
|
||||||
|
# between P3 and P4: Sta. 18+00.00
|
||||||
|
assert distance_along_from_station(file, alignment, 1800.0) == pytest.approx(500.0)
|
||||||
|
|
||||||
|
# between P4 and P5: Sta. 19+25.00
|
||||||
|
assert distance_along_from_station(file, alignment, 1925.0) == pytest.approx(675.0)
|
||||||
|
|
||||||
|
# Sta. 15+00.00 falls inside the gap opened by the equation at P3 and has no corresponding distance along
|
||||||
|
assert distance_along_from_station(file, alignment, 1500.0) is None
|
||||||
|
|
||||||
|
# Sta. 18+75.00 falls inside the overlap zone at P4; the post-equation (outgoing) match is returned
|
||||||
|
assert distance_along_from_station(file, alignment, 1875.0) == pytest.approx(625.0)
|
||||||
|
|
||||||
|
|
||||||
test_distance_along_from_station()
|
test_distance_along_from_station()
|
||||||
|
test_distance_along_from_station_with_station_equations()
|
||||||
|
|||||||
Reference in New Issue
Block a user