mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Alignment API update for station and positioning referents. Fixes bug with fallback position.
This commit is contained in:
@@ -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 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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
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_with_station_equations()
|
||||
|
||||
Reference in New Issue
Block a user