Alignment api update (#6817)

* Some basic COGO survey points functions

* Update alignment api

Includes defining alignment segment by segment, automatic geometry definitions, and automatric stationing and referents

* Fixes bonsai import alignment from csv

* Adds DMS angle conversion functions to COGO api

* Updated per @civilx64 review comments

* Fixes problem with segment representations

* Fixes problem with segment transition codes

* Allows for compound vertical and horizontal curves

* Implements callbacks for referent naming

* Renames angle_from_bearing to bearing2dd for consistency with ifcopenshell.util.geolocation.dms2dd. Removes angle_from_dms because it duplicates dms2dd

* Documents register_referent_name_callback

* Fixes all sorts of problems with Cant/SegRefCurve implementation

* refactor referent unit tests to use a fixture for test setup

* lint with black

---------

Co-authored-by: Scott Lecher <civilx64@gmail.com>
This commit is contained in:
Richard Brice
2025-07-08 10:09:14 -07:00
committed by GitHub
parent ad8d02bfed
commit 40f92d15c3
76 changed files with 3645 additions and 1839 deletions
@@ -21,7 +21,6 @@
import os
import ifcopenshell.api.alignment
import ifcopenshell.api.alignment.add_stationing_to_alignment
import bpy
import json
@@ -62,9 +61,7 @@ class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
def _execute(self, context):
self.file = tool.Ifc.get()
start = time.time()
alignment = ifcopenshell.api.alignment.create_alignment_from_csv(self.file, self.filepath)
ifcopenshell.api.alignment.create_geometric_representation(self.file, alignment)
ifcopenshell.api.alignment.add_stationing_to_alignment(self.file, alignment=alignment, start_station=0.0)
alignment = ifcopenshell.api.alignment.create_from_csv(self.file, self.filepath)
# IFC 4.1.5.1 alignments cannot be contained in spatial structures, but can be referenced into them
sites = self.file.by_type("IfcSite")
@@ -17,7 +17,15 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
"""
Manages alignment layout (business logical) and alignment geometry (geometric representations).
Manages alignment layout (semantic definition) and alignment geometry (geometric definition).
This API is defined in terms of the semantic definition of an alignment. The corresponding geometric definition
is created and maintained automatically. The manditory zero length segment for the semantic and geometric definitions
are automatically created and maintained.
Alignments are created with stationing referents. Each layout segment is assigned a position referent that informs about
the start point of the segment. An example is the point of curvature of a horizontal circular curve. The referent is
nested to the segment representing the circular arc and is named with a indicator of the position and the station, e.g. "P.C. (145+98.32)"
This API does not determine alignment parameters based on rules, such as minimum curve radius as a function of design speed or sight distance.
@@ -25,14 +33,13 @@ This API is under development and subject to code breaking changes in the future
Presently, this API supports:
1. Creating alignments, both horizontal and vertical, using the PI method. Alignment definition can be read from a CSV file.
2. Adding business logic and geometric segments to the end of an alignment
3. Adding and removing the zero length segment at the end of alignments
4. Creating geometric representations from a business logical definition
5. Mapping individual business logical segments to geometric segments (complete for horizontal, missing clothoid for vertical, not implemented for cant)
6. Using curve geometry to determine IfcCurveSegment.Transition transition code.
7. Utility functions for printing business logical and geometric representations, as well as minimal geometry evaluations
2. Creating alignments segment by segment.
3. Automatic creation of geometric definitions (IfcCompositeCurve, IfcGradientCurve, IfcSegmentedReferenceCurve)
4. Automatic definition of stationing
5. Automatic definition of alignment transition point referents
6. Utility functions for printing business logical and geometric representations, as well as minimal geometry evaluations
Future versions of this API will support:
Future versions of this API may support:
1. Defining alignments using the PI method, including transition spirals
2. Updating horizontal curve definitions by revising transition spiral parameters and circular curve radii
3. Updating vertical curve definitions by revising horizontal length of curves
@@ -40,61 +47,59 @@ Future versions of this API will support:
5. Adding a segment at any location along a curve
"""
from .add_segment_to_curve import add_segment_to_curve
from .add_segment_to_layout import add_segment_to_layout
from .add_stationing_to_alignment import add_stationing_to_alignment
from .add_vertical_alignment_by_pi_method import add_vertical_alignment_by_pi_method
from .add_vertical_alignment import add_vertical_alignment
from .add_zero_length_segment import add_zero_length_segment
from .create_alignment_by_pi_method import create_alignment_by_pi_method
from .create_alignment_from_csv import create_alignment_from_csv
from .create_horizontal_alignment_by_pi_method import create_horizontal_alignment_by_pi_method
from .create_geometric_representation import create_geometric_representation
from .create_vertical_alignment_by_pi_method import create_vertical_alignment_by_pi_method
from .add_stationing_referent import add_stationing_referent
from .add_vertical_layout import add_vertical_layout
from .create_layout_segment import create_layout_segment
from .create_alignment import create
from .create_by_pi_method import create_by_pi_method
from .create_from_csv import create_from_csv
from .create_segment_representations import create_segment_representations
from .distance_along_from_station import distance_along_from_station
from .get_alignment import get_alignment
from .get_alignment_station import get_alignment_station
from .get_layout_segments import get_layout_segments
from .get_horizontal_layout import get_horizontal_layout
from .get_vertical_layout import get_vertical_layout
from .get_cant_layout import get_cant_layout
from .get_alignment_layouts import get_alignment_layouts
from .get_axis_subcontext import get_axis_subcontext
from .get_basis_curve import get_basis_curve
from .get_child_alignments import get_child_alignments
from .get_curve import get_curve
from .get_layout_curve import get_layout_curve
from .get_parent_alignment import get_parent_alignment
from .has_zero_length_segment import has_zero_length_segment
from .map_alignment_segments import map_alignment_segments
from .map_alignment_horizontal_segment import map_alignment_horizontal_segment
from .map_alignment_vertical_segment import map_alignment_vertical_segment
from .map_alignment_cant_segment import map_alignment_cant_segment
from .layout_horizontal_alignment_by_pi_method import layout_horizontal_alignment_by_pi_method
from .layout_vertical_alignment_by_pi_method import layout_vertical_alignment_by_pi_method
from .name_segments import name_segments
from .remove_last_segment import remove_last_segment
from .remove_zero_length_segment import remove_zero_length_segment
from .update_curve_segment_transition_code import update_curve_segment_transition_code
from .util import *
from ._get_segment_start_point_label import register_referent_name_callback
__all__ = [
"add_segment_to_curve",
"add_segment_to_layout",
"add_stationing_to_alignment",
"add_vertical_alignment_by_pi_method",
"add_vertical_alignment",
"add_zero_length_segment",
"create_alignment_by_pi_method",
"create_alignment_from_csv",
"create_horizontal_alignment_by_pi_method",
"create_geometric_representation",
"create_vertical_alignment_by_pi_method",
"add_stationing_referent",
"add_vertical_layout",
"create_layout_segment",
"create",
"create_by_pi_method",
"create_from_csv",
"distance_along_from_station",
"get_alignment",
"get_alignment_station",
"get_layout_segments",
"get_horizontal_layout",
"get_vertical_layout",
"get_cant_layout",
"get_alignment_layouts",
"get_axis_subcontext",
"get_basis_curve",
"get_child_alignments",
"get_curve",
"get_layout_curve",
"get_parent_alignment",
"has_zero_length_segment",
"map_alignment_segments",
"map_alignment_horizontal_segment",
"map_alignment_vertical_segment",
"map_alignment_cant_segment",
"layout_horizontal_alignment_by_pi_method",
"layout_vertical_alignment_by_pi_method",
"name_segments",
"remove_last_segment",
"remove_zero_length_segment",
"update_curve_segment_transition_code",
"register_referent_name_callback",
]
@@ -0,0 +1,132 @@
# 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.geom
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
import numpy as np
from ifcopenshell import entity_instance
from ifcopenshell.api.alignment._update_curve_segment_transition_code import _update_curve_segment_transition_code
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import __map_alignment_horizontal_segment
from ifcopenshell.api.alignment._map_alignment_vertical_segment import _map_alignment_vertical_segment
from ifcopenshell.api.alignment._map_alignment_cant_segment import _map_alignment_cant_segment
def _add_curve_segment_to_composite_curve(
file: ifcopenshell.file, curve_segment: entity_instance, composite_curve: entity_instance
):
if 0 < len(curve_segment.UsingCurves):
raise TypeError("IfcCurveSegment cannot belong to other curves")
settings = ifcopenshell.geom.settings()
if composite_curve.Segments == None or 0 == len(composite_curve.Segments):
# this is the first segment so just add it
if composite_curve.Segments == None:
composite_curve.Segments = []
# the last segment is always discontinuous
curve_segment.Transition = "DISCONTINUOUS"
composite_curve.Segments += (curve_segment,)
assert len(curve_segment.UsingCurves) == 1
else:
# get the segment before the zero length segment
prev_segment = (
composite_curve.Segments[-2]
if composite_curve.Segments != None and 1 < len(composite_curve.Segments)
else None
)
curve_segment.Transition = "CONTINUOUS"
# must add the new segment to the curve before updating the transition code
# add the new segment before the zero length segment
zero_length_segment = composite_curve.Segments[-1]
if prev_segment:
segments = composite_curve.Segments[0:-1]
segments += (
curve_segment,
zero_length_segment,
)
composite_curve.Segments = []
composite_curve.Segments += segments
_update_curve_segment_transition_code(prev_segment, curve_segment)
else:
composite_curve.Segments = (curve_segment, zero_length_segment)
settings = ifcopenshell.geom.settings()
segment_fn = ifcopenshell_wrapper.map_shape(settings, curve_segment.wrapped_data)
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
e = segment_evaluator.evaluate(segment_fn.end())
end = np.array(e)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
x = float(end[0, 3]) / unit_scale
y = float(end[1, 3]) / unit_scale
dx = float(end[0, 0])
dy = float(end[1, 0])
# assume IfcAxis2Placement2D
zero_length_segment.Placement.Location.Coordinates = (x, y)
zero_length_segment.Placement.RefDirection.DirectionRatios = (dx, dy)
_update_curve_segment_transition_code(curve_segment, zero_length_segment)
def _add_segment_to_curve(file: ifcopenshell.file, segment: entity_instance, curve: entity_instance) -> None:
"""
Creates an IfcCurveSegment from the IfcAlignmentSegment and adds it to the representation curve. The IfcCurveSegment is added
at the end of the curve, but before the manditory zero length segment. The IfcCurveSegment.Transition for the segment
that preceeds the new segment is updated.
:param segment: The segment to be added to the curve
:param curve: The representation curve receiving the segment
:return: None
"""
expected_types = ["IfcAlignmentSegment"]
if not segment.is_a() in expected_types:
raise TypeError(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{segment.is_a()}"
)
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment") and not curve.is_a("IfcCompositeCurve"):
raise TypeError(f"Expected to see IfcCompositeCurve, instead received '{curve.is_a()}'.")
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment") and not curve.is_a("IfcGradientCurve"):
raise TypeError(f"Expected to see IfcGradientCurve, instead received '{curve.is_a()}'.")
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment") and not curve.is_a("IfcSegmentedReferenceCurve"):
raise TypeError(f"Expected to see IfcSegmentedReferenceCurve, instead received '{curve.is_a()}'.")
expected_type = "IfcCompositeCurve"
if not curve.is_a(expected_type):
raise TypeError(f"Expected to see {expected_type}, instead received {curve.is_a()}.")
# map the IfcAlignmentSegment to an IfcCurveSegment (or two in the case of helmert curves)
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
mapped_segments = __map_alignment_horizontal_segment(file, segment)
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
mapped_segments = _map_alignment_vertical_segment(file, segment)
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
cant_layout = segment.Nests[0].RelatingObject
mapped_segments = _map_alignment_cant_segment(file, segment, cant_layout.RailHeadDistance)
else:
assert False
for mapped_segment in mapped_segments:
if mapped_segment:
_add_curve_segment_to_composite_curve(file, mapped_segment, curve)
@@ -0,0 +1,198 @@
# 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
import ifcopenshell.api.nest
from ifcopenshell import ifcopenshell_wrapper
import numpy as np
import math
from ifcopenshell import entity_instance
from typing import Sequence
from ifcopenshell.api.alignment._add_segment_to_curve import _add_segment_to_curve
from ifcopenshell.api.alignment._get_mapped_segments import _get_mapped_segments
from ifcopenshell.api.alignment._get_segment_start_point_label import _get_segment_start_point_label
def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, segment: entity_instance) -> None:
"""
Adds an IfcAlignmentSegment to a layout alignment (IfcAlignmentHorizontal/Vertical/Cant). This segment is added at the end
of the layout, before the manditory zero length segment. An IfcCurveSegment is created for the corresponding geometric representation.
:param layout: The layout alignment
:param segment: The segment to be appended
:return: None
"""
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
if not layout.is_a() in expected_types:
raise TypeError(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
)
if not (segment.is_a("IfcAlignmentSegment")):
raise TypeError(f"Expected to see IfcAlignmentSegment, instead received {segment.is_a()}.")
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
# add the new segment to the layout
ifcopenshell.api.nest.assign_object(file, related_objects=[segment], relating_object=layout)
# segment is attached at the end, but this is after the zero length segment
# swap the last two segments
ifcopenshell.api.nest.reorder_nesting(file, segment, -1, -1)
# add the new segment to the geometric representation curve
_add_segment_to_curve(file, segment, curve)
# gather information to:
# (1) add a referent at the start of this segment
# (2) update the name of the zero length segment's referent
# get the distance along the alignment to the start of the new segment
dist_along = 0.0
if layout.is_a("IfcAlignmentHorizontal"):
for nest in layout.IsNestedBy:
for seg in nest.RelatedObjects:
if seg.is_a("IfcAlignmentSegment"):
dist_along += seg.DesignParameters.SegmentLength
# the length of the current segment is in dist_along, so subtract it out
dist_along -= segment.DesignParameters.SegmentLength
else:
dist_along = segment.DesignParameters.StartDistAlong
# get the station of the start of the segment
alignment = ifcopenshell.api.alignment.get_alignment(layout)
start_station = ifcopenshell.api.alignment.get_alignment_station(file, alignment)
station = start_station + dist_along
# update the zero length layout segment
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
zero_length_segment = layout.IsNestedBy[0].RelatedObjects[-1]
# DesignParameters.StartPoint for IfcAlignmentHorizontalSegment is automatically updated when the
# geometric representation is updated because the semantic and geometric data use the same IfcPoint.
# This is not the case of IfcAlignmentVerticalSegment and IfcAlignmentCantSegment. For these
# segment types, the design parameters of the zero length segment must be updated explicitly.
if zero_length_segment.DesignParameters.is_a(
"IfcAlignmentVerticalSegment"
) or zero_length_segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
# get the geometric representation for the new segment
mapped_segments = _get_mapped_segments(file, segment)
mapped_segment = mapped_segments[0] if mapped_segments[1] == None else mapped_segments[1]
# compute the end point matrix
settings = ifcopenshell.geom.settings()
segment_fn = ifcopenshell_wrapper.map_shape(settings, mapped_segment.wrapped_data)
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
e = segment_evaluator.evaluate(segment_fn.end())
end = np.array(e)
# update the zero length segment semantic representation parameters
if zero_length_segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
y = float(end[1, 3]) / unit_scale
zero_length_segment.DesignParameters.StartHeight = y
dx = float(end[0, 0])
dy = float(end[1, 0])
zero_length_segment.DesignParameters.StartGradient = dy / dx
zero_length_segment.DesignParameters.EndGradient = zero_length_segment.DesignParameters.StartGradient
else:
z = float(end[2, 3]) / unit_scale
dx = float(end[0, 1])
dy = float(end[1, 1])
dz = float(end[2, 1])
ds = math.sqrt(dx * dx + dy * dy)
slope = dz / ds
railhead = layout.RailHeadDistance
zero_length_segment.DesignParameters.StartCantLeft = z + slope * railhead / 2.0
zero_length_segment.DesignParameters.StartCantRight = z - slope * railhead / 2.0
# updated the referent's name because the referent is now at a new station
start_dist_along = 0.0
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
start_dist_along = dist_along + segment.DesignParameters.SegmentLength
else:
start_dist_along = segment.DesignParameters.StartDistAlong + segment.DesignParameters.HorizontalLength
zero_length_segment.DesignParameters.StartDistAlong = start_dist_along
end_referent = zero_length_segment.IsNestedBy[0].RelatedObjects[0]
end_referent.Name = f"{_get_segment_start_point_label(zero_length_segment,None)} ({ifcopenshell.util.stationing.station_as_string(file,start_station+start_dist_along)})"
# update the referent's geometric representation's location
end_referent.ObjectPlacement.RelativePlacement.Location.DistanceAlong.wrappedValue = start_dist_along
settings = ifcopenshell.geom.settings()
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
curve_fn = ifcopenshell_wrapper.map_shape(settings, basis_curve.wrapped_data)
curve_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, curve_fn)
p = curve_evaluator.evaluate(start_dist_along * unit_scale)
p = np.array(p)
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])
ry = float(p[1, 0])
rz = float(p[2, 0])
ax = float(p[0, 2])
ay = float(p[1, 2])
az = float(p[2, 2])
end_referent.ObjectPlacement.CartesianPosition.Location.Coordinates = (x, y, z)
end_referent.ObjectPlacement.CartesianPosition.Axis.DirectionRatios = (ax, ay, az)
end_referent.ObjectPlacement.CartesianPosition.RefDirection.DirectionRatios = (rx, ry, rz)
# create the start of segment referent
# get the previous segment. Working from the end of the basis curve, -1 is zero length segment
# -2 is the newly added segment, so -3 is the segment occuring just before the newly added segment
prev_segment = layout.IsNestedBy[0].RelatedObjects[-3] if 2 < len(layout.IsNestedBy[0].RelatedObjects) else None
name = f"{_get_segment_start_point_label(prev_segment,segment)} ({ifcopenshell.util.stationing.station_as_string(file,station)})"
referent = ifcopenshell.api.alignment.add_stationing_referent(
file, segment, basis_curve=basis_curve, distance_along=dist_along, station=station, name=name
)
if len(curve.Segments) == 2 and layout.is_a("IfcAlignmentHorizontal"):
# this is the first real segment in the horizontal alignment
# update the location of the alignment's stationing referent
alignment = ifcopenshell.api.alignment.get_alignment(layout)
stationing_referent = alignment.IsNestedBy[0].RelatedObjects[0]
p = curve_evaluator.evaluate(
stationing_referent.ObjectPlacement.RelativePlacement.Location.DistanceAlong.wrappedValue
)
p = np.array(p)
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])
ry = float(p[1, 0])
rz = float(p[2, 0])
ax = float(p[0, 2])
ay = float(p[1, 2])
az = float(p[2, 2])
stationing_referent.ObjectPlacement.CartesianPosition.Location.Coordinates = (x, y, z)
stationing_referent.ObjectPlacement.CartesianPosition.Axis.DirectionRatios = (ax, ay, az)
stationing_referent.ObjectPlacement.CartesianPosition.RefDirection.DirectionRatios = (rx, ry, rz)
@@ -0,0 +1,144 @@
# 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
import ifcopenshell.api.nest
from ifcopenshell import entity_instance
from ifcopenshell.api.alignment._get_segment_start_point_label import _get_segment_start_point_label
def _add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance) -> None:
"""
Adds a zero length segment to the end of a layout. Also adds a zero length segment to the end of the corresponding geometric curve.
If the layout already has a zero length segment, nothing is changed
:param layout: An IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant
:return: None
"""
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
if not layout.is_a() in expected_types:
raise TypeError(
f"Expected layout type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
)
if ifcopenshell.api.alignment.has_zero_length_segment(layout):
return
segment = None
if layout.is_a("IfcAlignmentHorizontal"):
design_parameters = file.createIfcAlignmentHorizontalSegment(
StartPoint=file.createIfcCartesianPoint(
(0.0, 0.0)
), # this is a little problematic. need to know the end point and tangent
StartDirection=0.0, # of the previous segment, which requires geometry mapping
StartRadiusOfCurvature=0.0,
EndRadiusOfCurvature=0.0,
SegmentLength=0.0,
PredefinedType="LINE",
)
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
elif layout.is_a("IfcAlignmentVertical"):
last_segment_dist_along = 0.0
last_segment_end_gradient = 0.0
for rel in layout.IsNestedBy:
if 0 < len(rel.RelatedObjects):
last_segment = rel.RelatedObjects[1]
last_segment_dist_along = (
last_segment.DesignParameters.StartDistAlong + last_segment.DesignParameters.HorizontalLength
)
last_segment_end_gradient = last_segment.DesignParameters.EndGradient
design_parameters = file.createIfcAlignmentVerticalSegment(
StartDistAlong=last_segment_dist_along,
HorizontalLength=0.0,
StartHeight=0.0,
StartGradient=last_segment_end_gradient,
EndGradient=last_segment_end_gradient,
PredefinedType="CONSTANTGRADIENT",
)
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
elif layout.is_a("IfcAlignmentCant"):
last_segment_dist_along = 0.0
last_segment_cant_left = 0.0
last_segment_cant_right = 0.0
for rel in layout.IsNestedBy:
if 0 < len(rel.RelatedObjects):
last_segment = rel.RelatedObjects[1]
last_segment_dist_along = (
last_segment.DesignParameters.StartDistAlong + last_segment.DesignParameters.HorizontalLength
)
last_segment_cant_left = (
last_segment.DesignParameters.EndCantLeft
if last_segment.DesignParameters.EndCantLeft != None
else last_segment.DesignParameters.StartCantLeft
)
last_segment_cant_right = (
last_segment.DesignParameters.EndCantRight
if last_segment.DesignParameters.EndCantRight != None
else last_segment.DesignParameters.StartCantRight
)
design_parameters = file.createIfcAlignmentCantSegment(
StartDistAlong=last_segment_dist_along,
HorizontalLength=0.0,
StartCantLeft=last_segment_cant_left,
StartCantRight=last_segment_cant_right,
PredefinedType="CONSTANTCANT",
)
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
ifcopenshell.api.nest.assign_object(file, related_objects=[segment], relating_object=layout)
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
has_zero_length_segment = False
if curve.Segments != None and 0 < len(curve.Segments):
last_segment = curve.Segments[-1]
has_zero_length_segment = (
True
if last_segment.Transition == "DISCONTINUOUS" and last_segment.SegmentLength.wrappedValue == 0.0
else False
)
if not has_zero_length_segment:
parent_curve = file.createIfcLine(
Pnt=file.createIfcCartesianPoint(Coordinates=((0.0, 0.0))),
Dir=file.createIfcVector(
Orientation=file.createIfcDirection(DirectionRatios=((1.0, 0.0))),
Magnitude=1.0,
),
)
curve_segment = file.createIfcCurveSegment(
Transition="DISCONTINUOUS",
Placement=file.createIfcAxis2Placement2D(
Location=file.createIfcCartesianPoint((0.0, 0.0)),
RefDirection=file.createIfcDirection((1.0, 0.0)),
),
SegmentStart=file.createIfcLengthMeasure(0.0),
SegmentLength=file.createIfcLengthMeasure(0.0),
ParentCurve=parent_curve,
)
curve.Segments = [
curve_segment,
]
name = f"{_get_segment_start_point_label(segment,None)} {ifcopenshell.util.stationing.station_as_string(file,0.0)}"
ifcopenshell.api.alignment.add_stationing_referent(file, segment, curve, 0.0, 0.0, name=name)
@@ -25,17 +25,17 @@ import math
from collections.abc import Sequence
def create_geometric_representation(file: ifcopenshell.file, alignment: entity_instance) -> None:
def _create_geometric_representation(file: ifcopenshell.file, alignment: entity_instance) -> None:
"""
Create geometric representation for the alignment.
Create geometric representation for the alignment and its nested layouts.
There are 5 different cases:
There are 5 different cases (the IfcCurve created is indicated):
1) Horizontal only
2) Horizontal + Vertical
3) Horizontal + Vertical + Cant
4) Vertical only (this occurs when horizontal is reused from a parent alignment)
5) Vertical + Cant (this occurs when horizontal is reused from a parent alignment)
1) Horizontal only -> IfcCompositeCurve
2) Horizontal + Vertical -> IfcCompositeCurve and IfcGradientCurve
3) Horizontal + Vertical + Cant -> IfcCompositeCurve and IfcSegmentedReferentCurve
4) Vertical only (this occurs when horizontal is reused from a parent alignment) -> IfcGradientCurve
5) Vertical + Cant (this occurs when horizontal is reused from a parent alignment) -> IfcSegmentedReferenceCurve
:param alignment: The alignment for which the representation is being created
:return: None
@@ -43,7 +43,7 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
expected_type = "IfcAlignment"
if not alignment.is_a(expected_type):
raise TypeError("Expected '{expected_type}' but got '{alignment.is_a()}'")
raise TypeError(f"Expected {expected_type} but got {alignment.is_a()}")
placement = file.createIfcLocalPlacement(
PlacementRelTo=None,
@@ -60,11 +60,8 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
if len(layouts) == 1 and len(children) == 0:
assert layouts[0].is_a("IfcAlignmentHorizontal")
# Horizontal only - IFC CT 4.1.7.1.1.1
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[0])
composite_curve = file.createIfcCompositeCurve()
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
representation = file.create_entity(
type="IfcShapeRepresentation",
composite_curve = file.createIfcCompositeCurve(Segments=[], SelfIntersect=False)
representation = file.createIfcShapeRepresentation(
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="Axis",
RepresentationType="Curve2D",
@@ -75,12 +72,8 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
# Horizontal and Vertical - IFC CT 4.1.7.1.1.1
assert layouts[0].is_a("IfcAlignmentHorizontal")
assert layouts[1].is_a("IfcAlignmentVertical")
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[0])
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[1])
composite_curve = file.createIfcCompositeCurve()
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
representation = file.create_entity(
type="IfcShapeRepresentation",
composite_curve = file.createIfcCompositeCurve(Segments=[], SelfIntersect=False)
representation = file.createIfcShapeRepresentation(
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="FootPrint",
RepresentationType="Curve2D",
@@ -88,10 +81,8 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
)
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
gradient_curve = file.createIfcGradientCurve(BaseCurve=composite_curve)
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[1], gradient_curve)
representation = file.create_entity(
type="IfcShapeRepresentation",
gradient_curve = file.createIfcGradientCurve(Segments=[], BaseCurve=composite_curve, SelfIntersect=False)
representation = file.createIfcShapeRepresentation(
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="Axis",
RepresentationType="Curve3D",
@@ -103,13 +94,8 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
assert layouts[0].is_a("IfcAlignmentHorizontal")
assert layouts[1].is_a("IfcAlignmentVertical")
assert layouts[2].is_a("IfcAlignmentCant")
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[0])
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[1])
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[2])
composite_curve = file.createIfcCompositeCurve()
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
representation = file.create_entity(
type="IfcShapeRepresentation",
composite_curve = file.createIfcCompositeCurve(Segments=[], SelfIntersect=False)
representation = file.createIfcShapeRepresentation(
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="FootPrint",
RepresentationType="Curve2D",
@@ -117,10 +103,10 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
)
ifcopenshell.api.geometry.assign_representation(file, alignment, representation)
gradient_curve = file.createIfcGradientCurve(BaseCurve=composite_curve)
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[1], gradient_curve)
segmented_reference_curve = file.createIfcSegmentedReferenceCurve(BaseCurve=gradient_curve)
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[2], segmented_reference_curve)
gradient_curve = file.createIfcGradientCurve(Segments=[], BaseCurve=composite_curve, SelfIntersect=False)
segmented_reference_curve = file.createIfcSegmentedReferenceCurve(
Segments=[], BaseCurve=gradient_curve, SelfIntersect=False
)
representation = file.create_entity(
type="IfcShapeRepresentation",
ContextOfItems=axis_geom_subcontext,
@@ -132,11 +118,8 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
else:
# Reusing Horizontal - CT 4.1.4.4.1.2
# Create a representation on the parent alignment
ifcopenshell.api.alignment.add_zero_length_segment(file, layouts[0])
composite_curve = file.createIfcCompositeCurve()
ifcopenshell.api.alignment.map_alignment_segments(file, layouts[0], composite_curve)
representation = file.create_entity(
type="IfcShapeRepresentation",
composite_curve = file.createIfcCompositeCurve(Segments=[], SelfIntersect=False)
representation = file.createIfcShapeRepresentation(
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="FootPrint",
RepresentationType="Curve2D",
@@ -149,12 +132,9 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
child_layouts = ifcopenshell.api.alignment.get_alignment_layouts(child_alignment)
if len(child_layouts) == 1:
assert child_layouts[0].is_a("IfcAlignmentVertical")
ifcopenshell.api.alignment.add_zero_length_segment(file, child_layouts[0])
base_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
gradient_curve = file.createIfcGradientCurve(BaseCurve=base_curve)
ifcopenshell.api.alignment.map_alignment_segments(file, child_layouts[0], gradient_curve)
representation = file.create_entity(
type="IfcShapeRepresentation",
gradient_curve = file.createIfcGradientCurve(Segments=[], BaseCurve=base_curve, SelfIntersect=False)
representation = file.createIfcShapeRepresentation(
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="Axis",
RepresentationType="Curve3D",
@@ -164,15 +144,12 @@ def create_geometric_representation(file: ifcopenshell.file, alignment: entity_i
elif len(child_layouts) == 2:
assert child_layouts[0].is_a("IfcAlignmentVertical")
assert child_layouts[1].is_a("IfcAlignmentCant")
ifcopenshell.api.alignment.add_zero_length_segment(file, child_layouts[0])
ifcopenshell.api.alignment.add_zero_length_segment(file, child_layouts[1])
base_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
gradient_curve = file.createIfcGradientCurve(BaseCurve=base_curve)
ifcopenshell.api.alignment.map_alignment_segments(file, child_layouts[0], gradient_curve)
segmented_reference_curve = file.createIfcSegmentedReferenceCurve(BaseCurve=gradient_curve)
ifcopenshell.api.alignment.map_alignment_segments(file, child_layouts[1], segmented_reference_curve)
representation = file.create_entity(
type="IfcShapeRepresentation",
gradient_curve = file.createIfcGradientCurve(Segments=[], BaseCurve=base_curve, SelfIntersect=False)
segmented_reference_curve = file.createIfcSegmentedReferenceCurve(
Segments=[], BaseCurve=gradient_curve, SelfIntersect=False
)
representation = file.creatIfcShapeRepresentation(
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="Axis",
RepresentationType="Curve3D",
@@ -0,0 +1,60 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.alignment
from ifcopenshell import entity_instance
from typing import Sequence
def __get_curve_segment_count(segment: entity_instance) -> int:
"""
returns the number of IfcCurveSegment that an IfcAlignmentSegment maps to.
generally this is a 1 to 1 mapping, with helmert curve being the exception
"""
if segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
return 2 if segment.DesignParameters.PredefinedType == "HELMERTCURVE" else 1
elif segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
return 1
elif segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
return 2 if segment.DesignParameters.PredefinedType == "HELMERTCURVE" else 1
def _get_mapped_segments(file: ifcopenshell.file, layout_segment: entity_instance) -> Sequence[entity_instance]:
"""
Finds the IfcCurveSegment related to segment
"""
expected_type = "IfcAlignmentSegment"
if not layout_segment.is_a(expected_type):
raise TypeError(f"Expected to see type '{expected_type}', instead received '{layout_segment.is_a()}'.")
layout = layout_segment.Nests[0].RelatingObject
alignment = ifcopenshell.api.alignment.get_alignment(layout)
curve = ifcopenshell.api.alignment.get_curve(alignment)
index = 0
for seg in layout.IsNestedBy[0].RelatedObjects:
index += __get_curve_segment_count(seg)
if seg == layout_segment:
break
segment_count = __get_curve_segment_count(layout_segment)
if segment_count == 1:
return (curve.Segments[index - segment_count], None)
else:
return (curve.Segments[index - segment_count], curve.Segment[index])
@@ -0,0 +1,314 @@
# 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
from ifcopenshell import entity_instance
from typing import Sequence
_horizontal_callback = None
_vertical_callback = None
_cant_callback = None
def register_referent_name_callback(horizontal=None, vertical=None, cant=None):
"""
Referents are automatically created at the start of each horizontal, vertical, and cant segment.
The referents represent key points in the alignment layout such as Point of Curvature, Point of Tangent, and others.
Different juristicions use different naming systems for these key points.
The referent name callback functions provide a customizable method for naming these referents. If a callback is registered,
it is called when creating the referent name, otherwise the default naming is used.
The callback function signature is
def mycallback(prev_segment : entity_instance, segment : entity_instance) -> str:
The callback function returns a string that is used in the referent name for the referent at the start of `segment`.
The callback must accomodate the following cases:
* prev_segment = None and segment != None - this indicates the last segment so the "End of Alignment" name is returned
* prev_segment != None and segment == None - this indicates the first segment so the "Beginning of Alignment" name is returned
* prev_segment != None and segment != None - this indicates an intermediate segment so a name representitive of the transition is returned
Setting any or all of the callbacks to None causes the default naming to be used.
"""
global _horizontal_callback
_horizontal_callback = horizontal
global _vertical_callback
_vertical_callback = vertical
global _cant_callback
_cant_callback = cant
def _horizontal_label(prev_segment: entity_instance, segment: entity_instance) -> str:
if prev_segment == None and segment != None:
label = "P.O.B."
elif prev_segment != None and segment == None:
label = "P.O.E."
else:
lookup_table = {
"BLOSSCURVE": {
"BLOSSCURVE": "xx",
"CIRCULARARC": "S.C.",
"CLOTHOID": "xx",
"COSINECURVE": "xx",
"CUBIC": "xx",
"HELMERTCURVE": "xx",
"LINE": "S.T.",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"CIRCULARARC": {
"BLOSSCURVE": "C.S.",
"CIRCULARARC": "P.C.C.",
"CLOTHOID": "C.S.",
"COSINECURVE": "C.S.",
"CUBIC": "C.S.",
"HELMERTCURVE": "C.S.",
"LINE": "P.T.",
"SINECURVE": "C.S.",
"VIENNESEBEND": "C.S.",
},
"CLOTHOID": {
"BLOSSCURVE": "xx",
"CIRCULARARC": "S.C.",
"CLOTHOID": "xx",
"COSINECURVE": "xx",
"CUBIC": "xx",
"HELMERTCURVE": "xx",
"LINE": "S.T.",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"COSINECURVE": {
"BLOSSCURVE": "xx",
"CIRCULARARC": "S.C.",
"CLOTHOID": "xx",
"COSINECURVE": "xx",
"CUBIC": "xx",
"HELMERTCURVE": "xx",
"LINE": "S.T.",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"CUBIC": {
"BLOSSCURVE": "xx",
"CIRCULARARC": "S.C.",
"CLOTHOID": "xx",
"COSINECURVE": "xx",
"CUBIC": "xx",
"HELMERTCURVE": "xx",
"LINE": "S.T.",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"HELMERTCURVE": {
"BLOSSCURVE": "xx",
"CIRCULARARC": "S.C.",
"CLOTHOID": "xx",
"COSINECURVE": "xx",
"CUBIC": "xx",
"HELMERTCURVE": "xx",
"LINE": "S.T.",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"LINE": {
"BLOSSCURVE": "T.S.",
"CIRCULARARC": "P.C.",
"CLOTHOID": "T.S.",
"COSINECURVE": "T.S.",
"CUBIC": "T.S.",
"HELMERTCURVE": "T.S.",
"LINE": "P.I.",
"SINECURVE": "T.S.",
"VIENNESEBEND": "T.S.",
},
"SINECURVE": {
"BLOSSCURVE": "xx",
"CIRCULARARC": "S.C.",
"CLOTHOID": "xx",
"COSINECURVE": "xx",
"CUBIC": "xx",
"HELMERTCURVE": "xx",
"LINE": "S.T.",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"VIENNESEBEND": {
"BLOSSCURVE": "xx",
"CIRCULARARC": "S.C.",
"CLOTHOID": "xx",
"COSINECURVE": "xx",
"CUBIC": "xx",
"HELMERTCURVE": "xx",
"LINE": "S.T.",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
}
label = lookup_table[prev_segment.DesignParameters.PredefinedType][segment.DesignParameters.PredefinedType]
return label
def _vertical_label(prev_segment: entity_instance, segment: entity_instance) -> str:
if prev_segment == None and segment != None:
label = "V.P.O.B."
elif prev_segment != None and segment == None:
label = "V.P.O.E."
else:
lookup_table = {
"CIRCULARARC": {"CIRCULARARC": "xx", "CLOTHOID": "xx", "CONSTANTGRADIENT": "xx", "PARABOLICARC": "xx"},
"CLOTHOID": {"CIRCULARARC": "xx", "CLOTHOID": "xx", "CONSTANTGRADIENT": "xx", "PARABOLICARC": "xx"},
"CONSTANTGRADIENT": {
"CIRCULARARC": "xx",
"CLOTHOID": "xx",
"CONSTANTGRADIENT": "P.V.I",
"PARABOLICARC": "P.V.C.",
},
"PARABOLICARC": {
"CIRCULARARC": "xx",
"CLOTHOID": "xx",
"CONSTANTGRADIENT": "P.V.T.",
"PARABOLICARC": "V.C.C.",
},
}
label = lookup_table[prev_segment.DesignParameters.PredefinedType][segment.DesignParameters.PredefinedType]
return label
def _cant_label(prev_segment: entity_instance, segment: entity_instance) -> str:
if prev_segment == None and segment != None:
label = "C.P.O.B."
elif prev_segment != None and segment == None:
label = "C.P.O.E."
else:
lookup_table = {
"BLOSSCURVE": {
"BLOSSCURVE": "xx",
"CONSTANTCANT": "xx",
"COSINECURVE": "xx",
"HELMERTCURVE": "xx",
"LINEARTRANSITION": "xx",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"CONSTANTCANT": {
"BLOSSCURVE": "xx",
"CONSTANTCANT": "xx",
"COSINECURVE": "xx",
"HELMERTCURVE": "xx",
"LINEARTRANSITION": "xx",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"COSINECURVE": {
"BLOSSCURVE": "xx",
"CONSTANTCANT": "xx",
"COSINECURVE": "xx",
"HELMERTCURVE": "xx",
"LINEARTRANSITION": "xx",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"HELMERTCURVE": {
"BLOSSCURVE": "xx",
"CONSTANTCANT": "xx",
"COSINECURVE": "xx",
"HELMERTCURVE": "xx",
"LINEARTRANSITION": "xx",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"LINEARTRANSITION": {
"BLOSSCURVE": "xx",
"CONSTANTCANT": "xx",
"COSINECURVE": "xx",
"HELMERTCURVE": "xx",
"LINEARTRANSITION": "xx",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"SINECURVE": {
"BLOSSCURVE": "xx",
"CONSTANTCANT": "xx",
"COSINECURVE": "xx",
"HELMERTCURVE": "xx",
"LINEARTRANSITION": "xx",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
"VIENNESEBEND": {
"BLOSSCURVE": "xx",
"CONSTANTCANT": "xx",
"COSINECURVE": "xx",
"HELMERTCURVE": "xx",
"LINEARTRANSITION": "xx",
"SINECURVE": "xx",
"VIENNESEBEND": "xx",
},
}
label = lookup_table[prev_segment.DesignParameters.PredefinedType][segment.DesignParameters.PredefinedType]
return label
def _get_segment_start_point_label(prev_segment: entity_instance, segment: entity_instance) -> str:
"""
Returns the label for the start point of a segment. Typically used in the name of an IfcReferent
"""
if prev_segment != None and segment != None and prev_segment.is_a() != segment.is_a():
raise TypeError(
f"Expected entity type to be the same type, instead received {prev_segment.is_a()} and {segment.is_a()}"
)
expected_types = ["IfcAlignmentHorizontalSegment", "IfcAlignmentVerticalSegment", "IfcAlignmentCantSegment"]
if prev_segment != None and not prev_segment.DesignParameters.is_a() in expected_types:
raise TypeError(
f"Expected prev_segment.DesignParameters type to be one of {[_ for _ in expected_types]}, instead received {prev_segment.DesignParameters.is_a()}"
)
if segment != None and not segment.DesignParameters.is_a() in expected_types:
raise TypeError(
f"Expected segment.DesignParameters type to be one of {[_ for _ in expected_types]}, instead received {segment.DesignParameters.is_a()}"
)
s = segment if segment != None else prev_segment
if s.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
global _horizontal_callback
if _horizontal_callback:
label = _horizontal_callback(prev_segment, segment)
else:
label = _horizontal_label(prev_segment, segment)
elif s.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
global _vertical_callback
if _vertical_callback:
label = _vertical_callback(prev_segment, segment)
else:
label = _vertical_label(prev_segment, segment)
elif s.DesignParameters.is_a("IfcAlignmentCantSegment"):
global _cant_callback
if _cant_callback:
label = _cant_callback(prev_segment, segment)
else:
label = _cant_label(prev_segment, segment)
return label
@@ -380,7 +380,7 @@ def _map_viennese_bend(
raise NotImplementedError("VIENNESEBEND not implemented")
def map_alignment_cant_segment(
def _map_alignment_cant_segment(
file: ifcopenshell.file, segment: entity_instance, rail_head_distance: float
) -> Sequence[entity_instance]:
"""
@@ -401,7 +401,7 @@ def _map_viennese_bend(file: ifcopenshell.file, design_parameters: entity_instan
raise NotImplementedError("VIENNESEBEND not implemented")
def map_alignment_horizontal_segment(file: ifcopenshell.file, segment: entity_instance) -> Sequence[entity_instance]:
def __map_alignment_horizontal_segment(file: ifcopenshell.file, segment: entity_instance) -> Sequence[entity_instance]:
"""
Creates IfcCurveSegment entities for the represention of the supplied IfcAlignmentHorizontalSegment business logic entity instance.
A pair of entities is returned because a single business logic segment of type HELMERTCURVE maps to two representaiton entities.
@@ -189,7 +189,7 @@ def _map_clothoid(file: ifcopenshell.file, design_parameters: entity_instance) -
raise NotImplementedError("mapping for IfcVerticalSegment.CLOTHOID not implemented")
def map_alignment_vertical_segment(file: ifcopenshell.file, segment: entity_instance) -> Sequence[entity_instance]:
def _map_alignment_vertical_segment(file: ifcopenshell.file, segment: entity_instance) -> Sequence[entity_instance]:
"""
Creates IfcCurveSegment entities for the represention of the supplied IfcAlignmentVerticalSegment business logic entity instance.
A pair of entities is returned for consistency with map_alignment_horizontal_segment and map_alignment_cant_segment.
@@ -25,7 +25,7 @@ import numpy as np
import math
def update_curve_segment_transition_code(prev_segment: entity_instance, segment: entity_instance) -> None:
def _update_curve_segment_transition_code(prev_segment: entity_instance, segment: entity_instance) -> None:
"""
Updates IfcCurveSegment.Transition of prev_segment based on a comparison of
the position, ref. direction, and curvature at the end of the prev_segment and the start of segment.
@@ -62,9 +62,9 @@ def update_curve_segment_transition_code(prev_segment: entity_instance, segment:
s = segment_evaluator.evaluate(segment_fn.start())
start = np.array(s)
same_position = True if np.allclose(end[:3], start[:3]) else False
same_gradient = True if np.allclose(end[:0], start[:0]) else False
same_curvature = True if np.allclose(end[3:], start[3:]) else False
same_position = True if np.allclose(end[:3, 3], start[:3, 3]) else False
same_gradient = True if np.allclose(end[:3, 0], start[:3, 0]) else False
same_curvature = True if np.allclose(end[3:, :3], start[3:, :3]) else False
if same_position:
prev_segment.Transition = "CONTINUOUS"
@@ -1,76 +0,0 @@
# 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
import ifcopenshell.geom
from ifcopenshell import entity_instance
def add_segment_to_curve(file: ifcopenshell.file, segment: entity_instance, composite_curve: entity_instance) -> None:
"""
Adds a segment to a composite curve. The segment must not belong to another composite curve (len(segment.UsingCurves) == 0).
If the composite curve does not have any segments, the segment is simply appended to the curve.
If the composite curve has segments, the position, ref. direction, and curvature at the end of the last segment is
compared to the position, ref. direction and curvature at the start of the new segment. The IfcCurveSegment.Transition of the last curve segment is updated.
:param segment: The segment to be added to the curve
:param composite_curve: The curve receiving the segment
:return: None
"""
expected_type = "IfcCurveSegment"
if not segment.is_a(expected_type):
raise TypeError(f"Expected to see '{expected_type}', instead received '{segment.is_a()}'.")
if 0 < len(segment.UsingCurves):
raise TypeError("IfcCurveSegment cannot belong to other curves")
expected_type = "IfcCompositeCurve"
if not composite_curve.is_a(expected_type):
raise TypeError(f"Expected to see '{expected_type}', instead received '{composite_curve.is_a()}'.")
settings = ifcopenshell.geom.settings()
if composite_curve.Segments == None or 0 == len(composite_curve.Segments):
# this is the first segment so just add it
if composite_curve.Segments == None:
composite_curve.Segments = []
# the last segment is always discontinuous
segment.Transition = "DISCONTINUOUS"
composite_curve.Segments += (segment,)
assert len(segment.UsingCurves) == 1
else:
zero_length_segment = (
ifcopenshell.api.alignment.remove_zero_length_segment(file, composite_curve)
if ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
else None
)
prev_segment = composite_curve.Segments[-1]
# the last segment is always discontinuous
segment.Transition = "DISCONTINUOUS"
# must add the new segment to the curve before updating the transition code
composite_curve.Segments += (segment,)
ifcopenshell.api.alignment.update_curve_segment_transition_code(prev_segment, segment)
if zero_length_segment:
ifcopenshell.api.alignment.add_segment_to_curve(zero_length_segment, composite_curve)
@@ -1,51 +0,0 @@
# 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
import ifcopenshell.api.nest
from ifcopenshell import entity_instance
def add_segment_to_layout(file: ifcopenshell.file, alignment: entity_instance, segment: entity_instance) -> None:
"""
Adds a segment to a layout alignment (horizontal, vertical, or cant)
:param alignment: The alignment
:param segment: The segment to be appended
:return: None
"""
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
if not alignment.is_a() in expected_types:
raise TypeError(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{alignment.is_a()}"
)
if not (segment.is_a("IfcAlignmentSegment")):
raise TypeError(f"Expected to see IfcAlignmentSegment, instead received '{segment.is_a()}.")
zero_length_segment = (
ifcopenshell.api.alignment.remove_zero_length_segment(file, alignment)
if ifcopenshell.api.alignment.has_zero_length_segment(alignment)
else None
)
ifcopenshell.api.nest.assign_object(file, related_objects=[segment], relating_object=alignment)
if zero_length_segment:
ifcopenshell.api.nest.assign_object(file, related_objects=[zero_length_segment], relating_object=alignment)
@@ -0,0 +1,137 @@
# 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
import ifcopenshell.api.nest
import ifcopenshell.api.pset
import ifcopenshell.util.stationing
import ifcopenshell.guid
from ifcopenshell import entity_instance
from ifcopenshell import ifcopenshell_wrapper
import numpy as np
def add_stationing_referent(
file: ifcopenshell.file,
element: entity_instance,
basis_curve: entity_instance,
distance_along: float,
station: float,
name: str,
) -> entity_instance:
"""
Adds an IfcReferent to the element with the Pset_Stationing property set.
If element is an IfcAlignment, IfcReferent.PredefinedType is set to "STATION", otherwise "POSITION"
:param element: the element to receive the referent, expected to be an IfcAlignment or IfcAlignmentSegment
:param basis_curve: the basis curve for positining
:param distance_along: distance along the basis curve
:param station: station value
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
:return: referent
Example:
.. code:: python
alignment = model.by_type("IfcAlignment")[0]
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
ifcopenshell.api.alignment.add_stationing_referent(model,entity=alignment,basis_curve=basis_curve,distance_along=0.0,station=100.0)
"""
object_placement = None
representation = None
if basis_curve:
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
settings = ifcopenshell.geom.settings()
fn = ifcopenshell_wrapper.map_shape(settings, basis_curve.wrapped_data)
evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, fn)
p = evaluator.evaluate(distance_along * unit_scale)
p = np.array(p)
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])
ry = float(p[1, 0])
rz = float(p[2, 0])
ax = float(p[0, 2])
ay = float(p[1, 2])
az = float(p[2, 2])
object_placement = file.createIfcLinearPlacement(
RelativePlacement=file.createIfcAxis2PlacementLinear(
Location=file.createIfcPointByDistanceExpression(
DistanceAlong=file.createIfcLengthMeasure(distance_along),
OffsetLateral=None,
OffsetVertical=None,
OffsetLongitudinal=None,
BasisCurve=basis_curve,
)
),
CartesianPosition=file.createIfcAxis2Placement3D(
Location=file.createIfcCartesianPoint(((x, y, z))),
Axis=file.createIfcDirection((ax, ay, az)),
RefDirection=file.createIfcDirection((rx, ry, rz)),
),
)
# 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
predefined_type = "STATION" if element.is_a("IfcAlignment") else "POSITION"
referent = file.createIfcReferent(
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=name,
Description=None,
ObjectType=None,
ObjectPlacement=object_placement,
Representation=representation,
PredefinedType=predefined_type,
)
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.nest.assign_object(file, related_objects=[referent], relating_object=element)
alignment = element
if element.is_a("IfcAlignmentSegment"):
layout = element.Nests[0].RelatingObject
alignment = ifcopenshell.api.alignment.get_alignment(layout)
if len(alignment.Positions) == 0:
rel_positions = file.createIfcRelPositions(
GlobalId=ifcopenshell.guid.new(),
RelatingPositioningElement=alignment,
RelatedProducts=[
referent,
],
)
else:
alignment.Positions[0].RelatedProducts += (referent,)
return referent
@@ -1,83 +0,0 @@
# 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
import ifcopenshell.api.nest
import ifcopenshell.api.pset
import ifcopenshell.util.stationing
import ifcopenshell.guid
from ifcopenshell import entity_instance
def add_stationing_to_alignment(
file: ifcopenshell.file, alignment: entity_instance, start_station: float, plus_seperator=3, accuracy=3
) -> None:
"""
Adds stationing to an alignment by creating an IfcReferent with the Pset_Stationing property set to establish the stationing at the start of the alignment.
Note - this function assumes the stationing has not been previously defined
:param alignment: the alignment to be stationed
:param start_station: station value at the start of the alignment
:return: None
Example:
.. code:: python
alignment = model.by_type("IfcAlignment")[0]
ifcopenshell.api.alignment.add_stationing_to_alignment(model,alignment=alignment,start_station=100.0)
"""
# 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
object_placement = None
representation = None
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
if basis_curve:
object_placement = file.createIfcLinearPlacement(
RelativePlacement=file.createIfcAxis2PlacementLinear(
Location=file.createIfcPointByDistanceExpression(
DistanceAlong=file.createIfcLengthMeasure(0.0),
OffsetLateral=None,
OffsetVertical=None,
OffsetLongitudinal=None,
BasisCurve=basis_curve,
)
),
CartesianPosition=None,
)
# representation = file.create_entity(
# name="IfcCircle",
# position=file.createIfcAxis2Placement2D(Location=file.createIfcCartesianPoint(Coordinates=(0.0, 0.0)),
# radius=1.0)
# )
# create referent for start station
start_referent = file.createIfcReferent(
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=ifcopenshell.util.stationing.station_as_string(start_station, plus_seperator, accuracy),
Description=None,
ObjectType=None,
ObjectPlacement=object_placement,
Representation=representation,
PredefinedType="STATION",
)
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=start_referent, name="Pset_Stationing")
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": start_station})
ifcopenshell.api.nest.assign_object(file, related_objects=[start_referent], relating_object=alignment)
@@ -1,199 +0,0 @@
# 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.aggregate
import ifcopenshell.api.alignment
import ifcopenshell.api.geometry
import ifcopenshell.api.nest
import ifcopenshell.api.root
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.stationing
import ifcopenshell.api
from ifcopenshell import entity_instance
def _move_vertical_to_child_alignment(
file: ifcopenshell.file, parent_alignment: entity_instance, vertical_alignment: entity_instance
):
"""
Creates a new child alignment and aggregates it to the parent alignment. Moves the vertical alignment from the parent
alignment to the child alignment. Also moves the "Axis/Curve3D" representation to the child alignment, if present.
This function supports the transition of vertical alignment between CT 4.1.4.4.1.1 and 4.1.4.4.1.2 because a subsequent
vertical alignment is being added and the Alignment Layout - Reusing Horizontal Layout concept applies.
"""
# unhook the vertical alignment from the parent alignment
ifcopenshell.api.nest.unassign_object(file, related_objects=[vertical_alignment])
# create the child alignment
child_alignment = ifcopenshell.api.root.create_entity(
file, ifc_class="IfcAlignment", name=f"Child of {parent_alignment.Name}"
)
# nest the vertical alignment onto the child alignment
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_alignment], relating_object=child_alignment)
# aggreage the child alignment to the parent alignment
ifcopenshell.api.aggregate.assign_object(file, products=[child_alignment], relating_object=parent_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)
if base_curve:
representations = ifcopenshell.util.representation.get_representations_iter(parent_alignment)
for representation in representations:
if representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve3D":
ifcopenshell.api.geometry.unassign_representation(file, parent_alignment, representation)
ifcopenshell.api.geometry.assign_representation(file, child_alignment, representation)
break
def add_vertical_alignment(
file: ifcopenshell.file, parent_alignment: entity_instance, vertical_alignment: entity_instance
) -> None:
"""
Adds a vertical alignment to a previously created alignment.
If this is the first vertical alignment assigned to the parent_alignment the IFC CT 4.1.4.4.1.1 Alignment Layout - Horizontal, Vertical and Cant
is followed. If this is the second or subsequent vertical alignment assigned to the parent_alignment the
IFC CT 4.1.4.4.1.2 Alignment Layout - Reusing Horizontal Layout is followed.
When the second vertical alignment is added, the structure of the IFC model must transition from one concept template to the other.
Specifically, the following occurs:
1) The first child IfcAlignment is created and is IfcRelAggregates with the parent alignment.
2) The first vertical alignment is unassigned from the IfcRelNests of the parent alignment and assigned to the new child alignment IfcRelNests
3) A second child IfcAlignment is created ant is is IfcRelAggregates with the parent alignment.
4) The vertical_alignment is assigned to the second child alignment
For the third and subsequent vertical alignments, a new child alignment is created and aggregated to the parent alignment and an IfcAlignmentVertical is created
from vpoints and lengths and assigned to the new child alignment.
If the parent_alignment has a geometric representation, a geometric representation will be created for the vertical alignment.
:param parent_alignment: The parent alignment
:param vertical_alignment: The vertical alignment to be added
:return: None
"""
# get all the child alignments under alignment
child_alignments = [
c for c in ifcopenshell.util.element.get_decomposition(parent_alignment) if c.is_a("IfcAlignment")
]
# Get all the IfcAlignmentVertical that are nesting alignment (there should be 0 or 1)
# if 0, alignment is just horizontal and we are adding the first vertical so it will nest to the alignment,
# or there are multiple vertical and they nest to the aggregated child alignments
# if 1, there is one vertical alignments. Move it to a child alignment
vertical_alignments_nesting_alignment = [
c for c in ifcopenshell.util.element.get_components(parent_alignment) if c.is_a("IfcAlignmentVertical")
]
# move the vertical alignment to a child alignment because there is going to be more than one vertical
assert len(vertical_alignments_nesting_alignment) == 0 or len(vertical_alignments_nesting_alignment) == 1
for vertical_alignment_nesting_alignment in vertical_alignments_nesting_alignment:
_move_vertical_to_child_alignment(file, parent_alignment, vertical_alignment_nesting_alignment)
if len(child_alignments) == 0 and len(vertical_alignments_nesting_alignment) == 0:
# this is the first vertical alignment so nest it into the parent alignment (IFC CT 4.1.4.4.1.1)
ifcopenshell.api.nest.assign_object(
file, related_objects=[vertical_alignment], relating_object=parent_alignment
)
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
if base_curve:
# the parent alignment has a Representation so create a representation for the vertical
gradient_curve = file.create_entity(
type="IfcGradientCurve", Segments=[], SelfIntersect=False, BaseCurve=base_curve, EndPoint=None
)
# using the business logic definition of vertical_alignment, create the curve segments and assign to gradient_curve
ifcopenshell.api.alignment.map_alignment_segments(file, vertical_alignment, gradient_curve)
# Per IFC CT 4.1.7.1.1.1, the shape representation for Horizontal geometry only is
# RepresentationIdentifier="Axis" and RepresentationType="Curve2D".
# However, per IFC CT 4.1.7.1.1.2 and 3 the shape represenation with Horizontal, Vertical and Cant
# is RepresentationIdentifier="FootPrint" and RepresentationType="Curve2D" for the horizontal and
# RepresentationIdentifier="Axis" and RepresentationType="Curve3D" for the 2.5D curve.
# Since the alignment is transitioning from horizontal only to horizontal+vertical, the
# RepresentationIdentifier must change from "Axis" to "FootPrint"
representations = ifcopenshell.util.representation.get_representations_iter(parent_alignment)
for representation in representations:
if representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve2D":
representation.RepresentationIdentifier = "FootPrint"
break
# create the Axis,Curve3D representation
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
axis3d_shape_representation = file.create_entity(
type="IfcShapeRepresentation",
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="Axis",
RepresentationType="Curve3D",
Items=(gradient_curve,),
)
ifcopenshell.api.geometry.assign_representation(file, parent_alignment, axis3d_shape_representation)
else:
# there are multiple vertical reusing the horizontal (IFC CT 4.1.4.4.1.2)
# this is the second or subsequent vertical reusing the horizontal
# create a new child alignment for the new vertical
child_alignment = file.create_entity(
type="IfcAlignment",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=f"Child of {parent_alignment.Name}",
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
PredefinedType=None,
)
# Aggregate the child alignment to the parent alignment
ifcopenshell.api.aggregate.assign_object(file, (child_alignment,), parent_alignment)
# nest the vertical under the child alignment
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_alignment], relating_object=child_alignment)
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
if base_curve:
child_alignment.ObjectPlacement = parent_alignment.ObjectPlacement
# the parent alignment has a Representation so create a representation for the vertical
gradient_curve = file.create_entity(
type="IfcGradientCurve", Segments=[], SelfIntersect=False, BaseCurve=base_curve, EndPoint=None
)
ifcopenshell.api.alignment.map_alignment_segments(file, vertical_alignment, gradient_curve)
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
# create the Curve3D representation
axis3d_shape_representation = file.create_entity(
type="IfcShapeRepresentation",
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="Axis",
RepresentationType="Curve3D",
Items=(gradient_curve,),
)
# add the representation to the child alignment
ifcopenshell.api.geometry.assign_representation(file, child_alignment, axis3d_shape_representation)
@@ -1,59 +0,0 @@
# 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.add_vertical_alignment
from ifcopenshell import entity_instance
from collections.abc import Sequence
def add_vertical_alignment_by_pi_method(
file: ifcopenshell.file,
parent_alignment: entity_instance,
vpoints: Sequence[Sequence[float]],
lengths: Sequence[float],
) -> None:
"""
Adds a vertical alignment to a previously created alignment using the PI method.
If this is the first vertical alignment assigned to the parent_alignment the IFC CT 4.1.4.4.1.1 Alignment Layout - Horizontal, Vertical and Cant
is followed. If this is the second or subsequent vertical alignment assigned to the parent_alignment the
IFC CT 4.1.4.4.1.2 Alignment Layout - Reusing Horizontal Layout is followed.
When the second vertical alignment is added, the structure of the IFC model must transition from one concept template to the other.
Specifically, the following occurs:
1) The first child IfcAlignment is created and is IfcRelAggregates with the parent alignment.
2) The first vertical alignment is unassigned from the IfcRelNests of the parent alignment and assigned to the new child alignment IfcRelNests
3) A second child IfcAlignment is created and it is IfcRelAggregates with the parent alignment.
4) An IfcAlignmentVertical is created from vpoints and lengths and it is assigned to the second child alignment
For the third and subsequent vertical alignments, a new child alignment is created and aggregated to the parent alignment and an IfcAlignmentVertical is created
from vpoints and lengths and assigned to the new child alignment.
If the parent_alignment has a geometric representation, a geometric representation will be created for the vertical alignment.
:param parent_alignment: The parent alignment
:param vpoints: A sequence of (D,Z) points where D is distance along horizontal and Z is elevation
:param: lengths: Lengths of parabolic vertical curves occuring at each VPI
:return: None
"""
vertical_alignment = ifcopenshell.api.alignment.create_vertical_alignment_by_pi_method(
file, parent_alignment.Name, vpoints, lengths
)
ifcopenshell.api.alignment.add_vertical_alignment(file, parent_alignment, vertical_alignment)
@@ -0,0 +1,192 @@
# 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.aggregate
import ifcopenshell.api.alignment
import ifcopenshell.api.geometry
import ifcopenshell.api.nest
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.api
from ifcopenshell import entity_instance
from ifcopenshell.api.alignment._add_zero_length_segment import _add_zero_length_segment
def _move_vertical_layout_to_child_alignment(
file: ifcopenshell.file, parent_alignment: entity_instance, vertical_layout: entity_instance
):
"""
Creates a new child alignment and aggregates it to the parent alignment. Moves the vertical alignment from the parent
alignment to the child alignment. Also moves the "Axis/Curve3D" representation to the child alignment, if present.
This function supports the transition of vertical alignment between CT 4.1.4.4.1.1 and 4.1.4.4.1.2 because a subsequent
vertical alignment is being added and the Alignment Layout - Reusing Horizontal Layout concept applies.
"""
# unhook the vertical layout from the parent alignment
ifcopenshell.api.nest.unassign_object(file, related_objects=[vertical_layout])
# create the child alignment
child_alignment = file.createIfcAlignment(
GlobalId=ifcopenshell.guid.new(), Name=f"Child of {parent_alignment.Name}"
)
# nest the vertical layout onto the child alignment
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_layout], relating_object=child_alignment)
# aggreage the child alignment to the parent alignment
ifcopenshell.api.aggregate.assign_object(file, products=[child_alignment], relating_object=parent_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)
if base_curve:
representations = ifcopenshell.util.representation.get_representations_iter(parent_alignment)
for representation in representations:
if representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve3D":
ifcopenshell.api.geometry.unassign_representation(file, parent_alignment, representation)
ifcopenshell.api.geometry.assign_representation(file, child_alignment, representation)
break
def add_vertical_layout(file: ifcopenshell.file, parent_alignment: entity_instance) -> entity_instance:
"""
Adds a vertical layout to a previously created alignment.
If this is the first vertical layout assigned to the parent_alignment the IFC CT 4.1.4.4.1.1 Alignment Layout - Horizontal, Vertical and Cant
is followed. If this is the second or subsequent vertical layout assigned to the parent_alignment the
IFC CT 4.1.4.4.1.2 Alignment Layout - Reusing Horizontal Layout is followed.
When the second vertical layout is added, the structure of the IFC model must transition from one concept template to the other.
Specifically, the following occurs:
1) The first child IfcAlignment is created and is IfcRelAggregates with the parent alignment.
2) The first vertical layout is unassigned from the IfcRelNests of the parent alignment and is IfcRelNests to the new child alignment.
3) A second child IfcAlignment is created and it is IfcRelAggregates with the parent alignment.
4) The vertical layout is IfcRelNests to the second child alignment
For the third and subsequent vertical layouts, a new child alignment is created and aggregated to the parent alignment.
A zero segment length terminated IfcGradientCurve is created for the new vertical layout
:param parent_alignment: The parent alignment
:return: The new vertical layout, including the manditory zero length segment
"""
vertical_layout = file.createIfcAlignmentVertical(GlobalId=ifcopenshell.guid.new())
# get all the child alignments under alignment
child_alignments = [
c for c in ifcopenshell.util.element.get_decomposition(parent_alignment) if c.is_a("IfcAlignment")
]
# Get all the IfcAlignmentVertical that are nesting alignment (there should be 0 or 1)
# if 0, alignment is just horizontal and we are adding the first vertical so it will nest to the alignment,
# or there are multiple vertical and they nest to the aggregated child alignments
# if 1, there is one vertical alignments. Move it to a child alignment
vertical_layouts_nesting_alignment = [
c for c in ifcopenshell.util.element.get_components(parent_alignment) if c.is_a("IfcAlignmentVertical")
]
# move the vertical layout to a child alignment because there is going to be more than one vertical
assert len(vertical_layouts_nesting_alignment) == 0 or len(vertical_layouts_nesting_alignment) == 1
for vertical_layout_nesting_alignment in vertical_layouts_nesting_alignment:
_move_vertical_layout_to_child_alignment(file, parent_alignment, vertical_layout_nesting_alignment)
if len(child_alignments) == 0 and len(vertical_layouts_nesting_alignment) == 0:
# this is the first vertical layout so nest it into the parent alignment (IFC CT 4.1.4.4.1.1)
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_layout], relating_object=parent_alignment)
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
# the parent alignment has a Representation so create a representation for the vertical
gradient_curve = file.createIfcGradientCurve(
Segments=[], SelfIntersect=False, BaseCurve=base_curve, EndPoint=None
)
# Per IFC CT 4.1.7.1.1.1, the shape representation for Horizontal geometry only is
# RepresentationIdentifier="Axis" and RepresentationType="Curve2D".
# However, per IFC CT 4.1.7.1.1.2 and 3 the shape represenation with Horizontal, Vertical and Cant
# is RepresentationIdentifier="FootPrint" and RepresentationType="Curve2D" for the horizontal and
# RepresentationIdentifier="Axis" and RepresentationType="Curve3D" for the 2.5D curve.
# Since the alignment is transitioning from horizontal only to horizontal+vertical, the
# RepresentationIdentifier must change from "Axis" to "FootPrint"
representations = ifcopenshell.util.representation.get_representations_iter(parent_alignment)
for representation in representations:
if representation.RepresentationIdentifier == "Axis" and representation.RepresentationType == "Curve2D":
representation.RepresentationIdentifier = "FootPrint"
break
# create the Axis,Curve3D representation
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
axis3d_shape_representation = file.createIfcShapeRepresentation(
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="Axis",
RepresentationType="Curve3D",
Items=(gradient_curve,),
)
ifcopenshell.api.geometry.assign_representation(file, parent_alignment, axis3d_shape_representation)
else:
# there are multiple vertical reusing the horizontal (IFC CT 4.1.4.4.1.2)
# this is the second or subsequent vertical reusing the horizontal
# create a new child alignment for the new vertical
child_alignment = file.createIfcAlignment(
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=f"Child of {parent_alignment.Name}",
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
PredefinedType=None,
)
# Aggregate the child alignment to the parent alignment
ifcopenshell.api.aggregate.assign_object(file, (child_alignment,), parent_alignment)
# nest the vertical under the child alignment
ifcopenshell.api.nest.assign_object(file, related_objects=[vertical_layout], relating_object=child_alignment)
child_alignment.ObjectPlacement = parent_alignment.ObjectPlacement
# the parent alignment has a Representation so create a representation for the vertical
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
gradient_curve = file.createIfcGradientCurve(
Segments=[], SelfIntersect=False, BaseCurve=base_curve, EndPoint=None
)
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
# create the Curve3D representation
axis3d_shape_representation = file.createIfcShapeRepresentation(
ContextOfItems=axis_geom_subcontext,
RepresentationIdentifier="Axis",
RepresentationType="Curve3D",
Items=(gradient_curve,),
)
# add the representation to the child alignment
ifcopenshell.api.geometry.assign_representation(file, child_alignment, axis3d_shape_representation)
# All alignment layouts must end with a zero length segment. Their geometric representations must also end with a zero length segment.
# Now that all the geometry is setup, add the zero length segment to the layout, which also adds a zero length segment to the representation
_add_zero_length_segment(file, vertical_layout)
return vertical_layout
@@ -1,154 +0,0 @@
# 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
import ifcopenshell.api.nest
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
import numpy as np
from ifcopenshell import entity_instance
def add_zero_length_segment(file: ifcopenshell.file, entity: entity_instance) -> None:
"""
Adds a zero length segment to the end of entity.
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve (or subtype)
:return: None
"""
expected_types = [
"IfcAlignmentHorizontal",
"IfcAlignmentVertical",
"IfcAlignmentCant",
"IfcCompositeCurve",
"IfcGradientCurve",
"IfcSegmentedReferenceCurve",
]
if not entity.is_a() in expected_types:
raise TypeError(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{entity.is_a()}"
)
if ifcopenshell.api.alignment.has_zero_length_segment(entity):
return # do nothing if the entity already has a zero length segment
if entity.is_a("IfcCompositeCurve"):
last_segment = entity.Segments[-1]
settings = ifcopenshell.geom.settings()
segment_fn = ifcopenshell_wrapper.map_shape(settings, last_segment.wrapped_data)
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
e = segment_evaluator.evaluate(segment_fn.end())
end = np.array(e)
x = float(end[0, 3])
y = float(end[1, 3])
dx = float(end[0, 0])
dy = float(end[1, 0])
parent_curve = file.createIfcLine(
Pnt=file.createIfcCartesianPoint(Coordinates=((0.0, 0.0))),
Dir=file.createIfcVector(
Orientation=file.createIfcDirection(DirectionRatios=((1.0, 0.0))),
Magnitude=1.0,
),
)
curve_segment = file.createIfcCurveSegment(
Transition="DISCONTINUOUS",
Placement=file.createIfcAxis2Placement2D(
Location=file.createIfcCartesianPoint((x, y)),
RefDirection=file.createIfcDirection((dx, dy)),
),
SegmentStart=file.createIfcLengthMeasure(0.0),
SegmentLength=file.createIfcLengthMeasure(0.0),
ParentCurve=parent_curve,
)
ifcopenshell.api.alignment.add_segment_to_curve(file, curve_segment, entity)
else:
for rel in entity.IsNestedBy:
if 0 < len(rel.RelatedObjects):
last_segment = rel.RelatedObjects[-1]
if last_segment.is_a("IfcAlignmentSegment"):
if entity.is_a("IfcAlignmentHorizontal"):
design_parameters = file.createIfcAlignmentHorizontalSegment(
StartPoint=file.createIfcCartesianPoint(
(0.0, 0.0)
), # this is a little problematic. need to know the end point and tangent
StartDirection=0.0, # of the previous segment, which requires geometry mapping
SegmentLength=0.0,
PredefinedType="LINE",
)
segment = file.createIfcAlignmentSegment(
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
ifcopenshell.api.nest.assign_object(
file,
related_objects=[
segment,
],
relating_object=entity,
)
break
elif entity.is_a("IfcAlignmentVertical"):
design_parameters = file.createIfcAlignmentVerticalSegment(
StartDistAlong=last_segment.DesignParameters.StartDistAlong
+ last_segment.DesignParameters.HorizontalLength,
HorizontalLength=0.0,
StartHeight=0.0,
StartGradient=last_segment.DesignParameters.EndGradient,
EndGradient=last_segment.DesignParameters.EndGradient,
PredefinedType="CONSTANTGRADIENT",
)
segment = file.createIfcAlignmentSegment(
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
ifcopenshell.api.nest.assign_object(
file,
related_objects=[
segment,
],
relating_object=entity,
)
break
elif entity.is_a("IfcAlignmentCant"):
design_parameters = file.createIfcAlignmentCantSegment(
StartDistAlong=last_segment.DesignParameters.StartDistAlong
+ last_segment.DesignParameters.HorizontalLength,
HorizontalLength=0.0,
StartCantLeft=(
last_segment.DesignParameters.EndCantLeft
if last_segment.DesignParameters.EndCantLeft != None
else last_segment.DesignParameters.StartCantLeft
),
StartCantRight=(
last_segment.DesignParameters.EndCantRight
if last_segment.DesignParameters.EndCantRight != None
else last_segment.DesignParameters.StartCantRight
),
PredefinedType="CONSTANTCANT",
)
segment = file.createIfcAlignmentSegment(
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
ifcopenshell.api.nest.assign_object(
file,
related_objects=[
segment,
],
relating_object=entity,
)
break
@@ -0,0 +1,87 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.alignment
from ifcopenshell import entity_instance
from ifcopenshell.api.alignment._create_geometric_representation import _create_geometric_representation
from ifcopenshell.api.alignment._add_zero_length_segment import _add_zero_length_segment
def create(
file: ifcopenshell.file,
name: str,
include_vertical: bool = False,
include_cant: bool = False,
start_station: float = 0.0,
) -> entity_instance:
"""
Creates a new IfcAlignment with an IfcRelNests nesting an IfcReferent (for stationing) and IfcAlignmentHorizontal. The nest relationship can optionally
include IfcAlignmentVertical and IfcAlignmentCant. Geometric representations for the alignment layouts (IfcCompositeCurve,
IfcGradientCurve, IfcSegmentedReferenceCurve) are created as well.
Zero length segments are added at the end.
The IfcAlignment is aggreated to IfcProject
Use get_horizontal_layout(alignment) to get the IfcAlignmentHorizontal layout.
:param file:
:param name: name assigned to IfcAlignment.Name
:param include_vertical: If True, IfcAlignmentVertical and IfcGradientCurve are created
:param include_cant: If True, IfcAlignmentCant and IfcSegmentedReferenceCurve are created
:param start_station: station value at the start of the alignment
:return: Returns an IfcAlignment
"""
alignment = file.createIfcAlignment(
GlobalId=ifcopenshell.guid.new(),
Name=name,
)
alignment_layouts = []
alignment_layouts.append(file.createIfcAlignmentHorizontal(GlobalId=ifcopenshell.guid.new()))
if include_vertical:
alignment_layouts.append(file.createIfcAlignmentVertical(GlobalId=ifcopenshell.guid.new()))
if include_cant:
alignment_layouts.append(file.createIfcAlignmentCant(GlobalId=ifcopenshell.guid.new(), RailHeadDistance=1.0))
ifcopenshell.api.nest.assign_object(file, related_objects=alignment_layouts, relating_object=alignment)
_create_geometric_representation(file, alignment)
for layout in alignment_layouts:
_add_zero_length_segment(file, layout)
# define stationing
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
name = ifcopenshell.util.stationing.station_as_string(file, start_station)
referent = ifcopenshell.api.alignment.add_stationing_referent(
file, alignment, basis_curve, 0.0, start_station, name
)
ifcopenshell.api.nest.reorder_nesting(file, referent, -1, 0)
# IFC 4.1.4.1.1 Alignment Aggregation To Project
project = file.by_type("IfcProject")[0]
if project:
ifcopenshell.api.aggregate.assign_object(file, products=[alignment], relating_object=project)
return alignment
@@ -24,59 +24,34 @@ from ifcopenshell import entity_instance
from collections.abc import Sequence
def create_alignment_by_pi_method(
def create_by_pi_method(
file: ifcopenshell.file,
alignment_name: str,
name: str,
hpoints: Sequence[Sequence[float]],
radii: Sequence[float],
vpoints: Sequence[Sequence[float]] = None,
lengths: Sequence[float] = None,
alignment_description: str = None,
start_station: float = 0.0,
) -> entity_instance:
"""
Create an alignment using the PI layout method for both horizontal and vertical alignments.
If vpoints and lengths are omitted, only a horizontal alignment is created. Only the business logic
entities are creaed. Use create_geometric_representation() to create the geometric entities.
If vpoints and lengths are omitted, only a horizontal alignment is created.
:param alignment_name: value for Name attribute
:param name: value for Name attribute
:param points: (X,Y) pairs denoting the location of the horizontal PIs, including start and end
: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 lengths: parabolic vertical curve horizontal length values to use for transition
:param alignment_description: value for Description attribute
:return: Returns an IfcAlignment
"""
alignments = []
horizontal_alignment = ifcopenshell.api.alignment.create_horizontal_alignment_by_pi_method(
file, alignment_name, hpoints, radii
include_vertical = True if vpoints and lengths else False
alignment = ifcopenshell.api.alignment.create(
file, name, include_vertical=include_vertical, start_station=start_station
)
alignments.append(horizontal_alignment)
if vpoints and lengths:
vertical_alignment = ifcopenshell.api.alignment.create_vertical_alignment_by_pi_method(
file, alignment_name, vpoints, lengths
)
alignments.append(vertical_alignment)
# create the alignment
alignment = file.create_entity(
type="IfcAlignment",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=alignment_name,
Description=alignment_description,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
PredefinedType=None,
)
# nest the horizontal and vertical under the alignment
ifcopenshell.api.nest.assign_object(file, related_objects=alignments, relating_object=alignment)
# IFC 4.1.4.1.1 Alignment Aggregation To Project
project = file.by_type("IfcProject")[0]
ifcopenshell.api.aggregate.assign_object(file, products=[alignment], relating_object=project)
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
ifcopenshell.api.alignment.layout_horizontal_alignment_by_pi_method(file, horizontal_layout, hpoints, radii)
if include_vertical:
vertical_layout = ifcopenshell.api.alignment.get_vertical_layout(alignment)
ifcopenshell.api.alignment.layout_vertical_alignment_by_pi_method(file, vertical_layout, vpoints, lengths)
return alignment
@@ -17,27 +17,19 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.aggregate
import ifcopenshell.api.alignment
import ifcopenshell.api.geometry
import ifcopenshell.api.nest
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.stationing
import ifcopenshell.api
from ifcopenshell import entity_instance
from ifcopenshell.api.alignment import get_axis_subcontext
import math
from typing import Sequence
import csv
def create_alignment_from_csv(file: ifcopenshell.file, filepath: str) -> entity_instance:
def create_from_csv(file: ifcopenshell.file, filepath: str) -> entity_instance:
"""
Creates an alignment from PI data stored in a CSV file. Only the business logic
entities are creaed. Use create_geometric_representation() to create the geometric entities.
Creates an alignment from PI data stored in a CSV file.
The format of the file is:
@@ -94,22 +86,16 @@ def create_alignment_from_csv(file: ifcopenshell.file, filepath: str) -> entity_
radii = radii[1:-1] # The first radius value is a placeholder, remove it
if row_count == 1:
# create the alignment
alignment = file.createIfcAlignment(GlobalId=ifcopenshell.guid.new())
# create the horizontal alignment
horizontal_alignment = ifcopenshell.api.alignment.create_horizontal_alignment_by_pi_method(
file, "Alignment_from_CSV", coordinates, radii
)
# nest them together
ifcopenshell.api.nest.assign_object(
file, related_objects=(horizontal_alignment,), relating_object=alignment
alignment = ifcopenshell.api.alignment.create(file, "Alignment_from_CSV")
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
ifcopenshell.api.alignment.layout_horizontal_alignment_by_pi_method(
file, horizontal_layout, coordinates, radii
)
else:
# add all subsequent vertical alignments
ifcopenshell.api.alignment.add_vertical_alignment_by_pi_method(file, alignment, coordinates, radii)
# IFC 4.1.4.1.1 Alignment Aggregation To Project
project = file.by_type("IfcProject")[0]
ifcopenshell.api.aggregate.assign_object(file, products=[alignment], relating_object=project)
vertical_layout = ifcopenshell.api.alignment.add_vertical_layout(file, alignment)
ifcopenshell.api.alignment.layout_vertical_alignment_by_pi_method(
file, vertical_layout, coordinates, radii
)
return alignment
@@ -1,216 +0,0 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.alignment
from ifcopenshell import entity_instance
import math
from collections.abc import Sequence
def create_horizontal_alignment_by_pi_method(
file: ifcopenshell.file, name: str, hpoints: Sequence[Sequence[float]], radii: Sequence[float]
) -> entity_instance:
"""
Create a horizontal alignment using the PI layout method.
:param name: value for Name attribute
:param hpoints: (X, Y) pairs denoting the location of the horizontal PIs, including start (POB) and end (POE).
:param radii: radius values to use for transition
:return: Returns a IfcAlignmentHorizontal
"""
if not (len(hpoints) - 2 == len(radii)):
raise ValueError("radii should have two fewer elements that hpoints")
# Create the horizontal alignment (IfcAlignmentHorizontal) and nest alignment segments
horizontal_alignment = file.create_entity(
type="IfcAlignmentHorizontal",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=f"{name} - Horizontal",
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
)
xBT, yBT = hpoints[0]
xPI, yPI = hpoints[1]
i = 1
for radius in radii:
# back tangent
dxBT = xPI - xBT
dyBT = yPI - yBT
angleBT = math.atan2(dyBT, dxBT)
lengthBT = math.sqrt(dxBT * dxBT + dyBT * dyBT)
# forward tangent
i += 1
xFT, yFT = hpoints[i]
dxFT = xFT - xPI
dyFT = yFT - yPI
angleFT = math.atan2(dyFT, dxFT)
delta = angleFT - angleBT
tangent = abs(radius * math.tan(delta / 2))
lc = abs(radius * delta)
radius *= delta / abs(delta)
xPC = xPI - tangent * math.cos(angleBT)
yPC = yPI - tangent * math.sin(angleBT)
xPT = xPI + tangent * math.cos(angleFT)
yPT = yPI + tangent * math.sin(angleFT)
tangent_run = lengthBT - tangent
# create back tangent run
pt = file.create_entity(
type="IfcCartesianPoint",
Coordinates=(xBT, yBT),
)
design_parameters = file.create_entity(
type="IfcAlignmentHorizontalSegment",
StartTag=None,
EndTag=None,
StartPoint=pt,
StartDirection=angleBT,
StartRadiusOfCurvature=0.0,
EndRadiusOfCurvature=0.0,
SegmentLength=tangent_run,
GravityCenterLineHeight=None,
PredefinedType="LINE",
)
alignment_segment = file.create_entity(
type="IfcAlignmentSegment",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=None,
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
DesignParameters=design_parameters,
)
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
# create circular curve
if radius != 0.0:
pc = file.create_entity(
type="IfcCartesianPoint",
Coordinates=(xPC, yPC),
)
design_parameters = file.create_entity(
type="IfcAlignmentHorizontalSegment",
StartTag=None,
EndTag=None,
StartPoint=pc,
StartDirection=angleBT,
StartRadiusOfCurvature=float(radius),
EndRadiusOfCurvature=float(radius),
SegmentLength=lc,
GravityCenterLineHeight=None,
PredefinedType="CIRCULARARC",
)
alignment_segment = file.create_entity(
type="IfcAlignmentSegment",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=None,
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
DesignParameters=design_parameters,
)
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
xBT = xPT
yBT = yPT
xPI = xFT
yPI = yFT
# done processing radii
# create last tangent run
dx = xPI - xBT
dy = yPI - yBT
angleBT = math.atan2(dy, dx)
tangent_run = math.sqrt(dx * dx + dy * dy)
pt = file.create_entity(type="IfcCartesianPoint", Coordinates=(xBT, yBT))
design_parameters = file.create_entity(
type="IfcAlignmentHorizontalSegment",
StartTag=None,
EndTag=None,
StartPoint=pt,
StartDirection=angleBT,
StartRadiusOfCurvature=0.0,
EndRadiusOfCurvature=0.0,
SegmentLength=tangent_run,
GravityCenterLineHeight=None,
PredefinedType="LINE",
)
alignment_segment = file.create_entity(
type="IfcAlignmentSegment",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=None,
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
DesignParameters=design_parameters,
)
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
# create zero length terminator segment
poe = file.create_entity(type="IfcCartesianPoint", Coordinates=(xPI, yPI))
design_parameters = file.create_entity(
type="IfcAlignmentHorizontalSegment",
StartTag="POE",
EndTag="POE",
StartPoint=poe,
StartDirection=angleBT,
StartRadiusOfCurvature=0.0,
EndRadiusOfCurvature=0.0,
SegmentLength=0.0,
GravityCenterLineHeight=None,
PredefinedType="LINE",
)
alignment_segment = file.create_entity(
type="IfcAlignmentSegment",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=None,
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
DesignParameters=design_parameters,
)
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
return horizontal_alignment
@@ -0,0 +1,85 @@
# 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
import ifcopenshell.api.alignment.get_alignment
import ifcopenshell.geom
from ifcopenshell import entity_instance
import math
from ifcopenshell import ifcopenshell_wrapper
import numpy as np
from ifcopenshell.api.alignment._add_segment_to_layout import _add_segment_to_layout
def create_layout_segment(
file: ifcopenshell.file, layout: entity_instance, design_parameters: entity_instance
) -> np.array:
"""
Creates a new IfcAlignmentSegment using the IfcAlignmentParameterSegment design parameters.
The new segment is appended to the layout alignment and the corresponding IfcCurveSegment is created in the geometric representation
:param layout: The layout to receive the new layout segment. This parameter is expected to be IfcAlignmentHorizontal, IfcAlignmentVertical or IfcAlignmentCant
:param design_parameters: The parameters defining the segment. Expected to be the appropreate subclass of IfcAlignmentParameterSegment
:return: 4x4 matrix at end of segment as np.array intended to be used as the start point geometry for the next segment.
"""
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
if not layout.is_a() in expected_types:
raise TypeError(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
)
if layout.is_a("IfcAlignmentHorizontal") and not design_parameters.is_a("IfcAlignmentHorizontalSegment"):
raise TypeError("Expected design_parameters to be IfcAlignmentHorizontalSegment")
elif layout.is_a("IfcAlignmentVertical") and not design_parameters.is_a("IfcAlignmentVerticalSegment"):
raise TypeError("Expected design_parameters to be IfcAlignmentVerticalSegment")
elif layout.is_a("IfcAlignmentCant") and not design_parameters.is_a("IfcAlignmentCantSegment"):
raise TypeError("Expected design_parameters to be IfcAlignmentCantSegment")
# create the segment and add it to the layout.
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
_add_segment_to_layout(file, layout, segment) # adds to layout and geometric representation
# compute the 4x4 matrix at the end of the segment so this information can be
# returned and used when defining the next segment
alignment = ifcopenshell.api.alignment.get_alignment(layout)
curve = ifcopenshell.api.alignment.get_curve(alignment)
if layout.is_a("IfcAlignmentHorizontal"):
if curve.is_a("IfcGradientCurve"):
curve = curve.BaseCurve
elif curve.is_a("IfcSegmentedReferenceCurve"):
curve = (
curve.BaseCurve.BaseCurve
) # layout is horizontal and curve is segmented ref ... we want the curve's base curve
elif layout.is_a("IfcAlignmentVertical"):
if curve.is_a("IfcSegmentedReferenceCurve"):
curve = curve.BaseCurve
# the new segment is two from the end... the end segment is zero length
curve_segment = curve.Segments[-2]
settings = ifcopenshell.geom.settings()
segment_fn = ifcopenshell_wrapper.map_shape(settings, curve_segment.wrapped_data)
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
e = segment_evaluator.evaluate(segment_fn.end())
end = np.array(e)
return end
@@ -58,7 +58,7 @@ def create_segment_representations(
)
curve_segments = curve.Segments
segments = nested_alignment.IsNestedBy[0].RelatingObjects
segments = nested_alignment.IsNestedBy[0].RelatedObjects
for curve_segment, alignment_segment in zip(curve_segments, segments):
axis_representation = file.create_entity(
@@ -1,194 +0,0 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.alignment
from ifcopenshell import entity_instance
import math
from collections.abc import Sequence
def create_vertical_alignment_by_pi_method(
file: ifcopenshell.file, name: str, vpoints: Sequence[Sequence[float]], lengths: Sequence[float]
) -> entity_instance:
"""
Create a vertical alignment using the PI layout method.
:param name: value for Name attribute
:param base_curve: base curve representing the 2D projection of the gradient curve
:param vpoints: (distance_along, Z_height) pairs denoting the location of the vertical PIs, including start and end.
:param lengths: horizontal length of parabolic vertical curves
:return: IfcAlignmentHorizontal
"""
if not (len(vpoints) - 2 == len(lengths)):
raise ValueError("lengths should have two fewer elements that vpoints")
# Create the vertical alignment (IfcAlignmentVertical) and nest alignment segments
vertical_alignment = file.create_entity(
type="IfcAlignmentVertical",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=f"{name} - Vertical",
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
)
xPBG, yPBG = vpoints[0]
xPVI, yPVI = vpoints[1]
i = 1
for length in lengths:
# back gradient
dxBG = xPVI - xPBG
dyBG = yPVI - yPBG
start_slope = math.tan(math.atan2(dyBG, dxBG))
# forward gradient
i += 1
xPFG, yPFG = vpoints[i]
dxFG = xPFG - xPVI
dyFG = yPFG - yPVI
end_slope = math.tan(math.atan2(dyFG, dxFG))
xEVC = xPVI + length / 2.0
yEVC = yPVI + end_slope * length / 2.0
# create gradient
gradient_length = dxBG - length / 2.0
design_parameters = file.create_entity(
type="IfcAlignmentVerticalSegment",
StartTag=None,
EndTag=None,
StartDistAlong=xPBG,
HorizontalLength=gradient_length,
StartHeight=yPBG,
StartGradient=start_slope,
EndGradient=start_slope,
RadiusOfCurvature=None,
PredefinedType="CONSTANTGRADIENT",
)
alignment_segment = file.create_entity(
type="IfcAlignmentSegment",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=None,
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
DesignParameters=design_parameters,
)
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
# create vertical curve
if 0.0 < length:
k = (end_slope - start_slope) / length
xBVC = xPVI - length / 2.0
yBVC = yPVI - start_slope * length / 2.0
design_parameters = file.create_entity(
type="IfcAlignmentVerticalSegment",
StartTag=None,
EndTag=None,
StartDistAlong=xBVC,
HorizontalLength=length,
StartHeight=yBVC,
StartGradient=start_slope,
EndGradient=end_slope,
RadiusOfCurvature=1 / k,
PredefinedType="PARABOLICARC",
)
alignment_segment = file.create_entity(
type="IfcAlignmentSegment",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=None,
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
DesignParameters=design_parameters,
)
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
# start of next curve is end of this curve
xPBG = xEVC
yPBG = yEVC
xPVI = xPFG
yPVI = yPFG
# create last gradient run
dx = xPVI - xPBG
dy = yPVI - yPBG
slope = math.tan(math.atan2(dy, dx))
gradient_length = dx
design_parameters = file.create_entity(
type="IfcAlignmentVerticalSegment",
StartTag=None,
EndTag=None,
StartDistAlong=xPBG,
HorizontalLength=gradient_length,
StartHeight=yPBG,
StartGradient=slope,
EndGradient=slope,
RadiusOfCurvature=None,
PredefinedType="CONSTANTGRADIENT",
)
alignment_segment = file.create_entity(
type="IfcAlignmentSegment",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=None,
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
DesignParameters=design_parameters,
)
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
# create zero length terminator segment
design_parameters = file.create_entity(
type="IfcAlignmentVerticalSegment",
StartTag="VPOE",
EndTag="VPOE",
StartDistAlong=xPVI,
HorizontalLength=0.0,
StartHeight=yPVI,
StartGradient=slope,
EndGradient=slope,
RadiusOfCurvature=None,
PredefinedType="CONSTANTGRADIENT",
)
alignment_segment = file.create_entity(
type="IfcAlignmentSegment",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=None,
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
DesignParameters=design_parameters,
)
ifcopenshell.api.alignment.add_segment_to_layout(file, vertical_alignment, alignment_segment)
return vertical_alignment
@@ -43,12 +43,6 @@ def distance_along_from_station(file: ifcopenshell.file, alignment: entity_insta
print(dist_along) # 100.00
"""
start_station = 0.0
components = ifcopenshell.util.element.get_components(alignment)
for c in components:
if c.is_a("IfcReferent") and ifcopenshell.util.element.get_predefined_type(c) == "STATION":
start_station = ifcopenshell.util.element.get_pset(c, name="Pset_Stationing", prop="Station")
break
start_station = ifcopenshell.api.alignment.get_alignment_station(file, alignment)
dist_along = station - start_station
return dist_along
@@ -17,19 +17,15 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.alignment
import ifcopenshell.util
from ifcopenshell import entity_instance
import ifcopenshell.api.alignment.remove_last_segment
from typing import Sequence
import ifcopenshell.util.representation
def remove_zero_length_segment(file: ifcopenshell.file, entity: entity_instance) -> entity_instance:
def get_alignment(layout: entity_instance) -> entity_instance:
"""
Removes the zero length segment from the end of entity.
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve
:return: The zero length segment
Returns the alignment that nests this layout
"""
if not ifcopenshell.api.alignment.has_zero_length_segment(entity):
return None
return ifcopenshell.api.alignment.remove_last_segment(file, entity)
return layout.Nests[0].RelatingObject if 0 < len(layout.Nests) else None
@@ -0,0 +1,40 @@
# 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
from ifcopenshell import entity_instance
def get_alignment_station(file: ifcopenshell.file, alignment: entity_instance) -> float:
"""
Returns the start station of the alignment. If the alignment is nested by an IfcReferent
the referent is checked for PredefinedType of STATION and an occurance of Pset_Stationing.Station,
otherwise start station is taken to be 0.0.
"""
if not alignment.is_a("IfcAlignment"):
raise TypeError(f"Expected entity type to be IfcAlignment, instead received {alignment.is_a()}")
start_station = 0.0
components = ifcopenshell.util.element.get_components(alignment)
for c in components:
if c.is_a("IfcReferent") and ifcopenshell.util.element.get_predefined_type(c) == "STATION":
start_station = ifcopenshell.util.element.get_pset(c, name="Pset_Stationing", prop="Station")
break
return start_station
@@ -45,6 +45,10 @@ def get_basis_curve(alignment: entity_instance) -> entity_instance:
representation.RepresentationIdentifier == "FootPrint" and representation.RepresentationType == "Curve2D"
):
axis = representation
break
return None if axis.Items == None or len(axis.Items) == 0 else axis.Items[0]
return None if axis == None or axis.Items == None or len(axis.Items) == 0 else axis.Items[0]
if axis == None and 0 < len(alignment.Decomposes):
parent_alignment = alignment.Decomposes[0].RelatingObject
return get_basis_curve(parent_alignment)
return None
@@ -0,0 +1,31 @@
# 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 get_cant_layout(alignment: entity_instance) -> entity_instance:
"""
Returns the IfcAlignmentCant assocated with this alignment
"""
for rel in alignment.IsNestedBy:
for layout in rel.RelatedObjects:
if layout.is_a("IfcAlignmentCant"):
return layout
return None
@@ -26,7 +26,7 @@ import ifcopenshell.util.element
def get_child_alignments(alignment: entity_instance) -> Sequence[entity_instance]:
"""
Returns the aggregated child alignments to this alignment
Returns the aggregated child alignments to this alignment per CT 4.1.4.4.1.2 Alignment Layout - Reusing Horizontal Layout
Example:
@@ -0,0 +1,31 @@
# 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 get_horizontal_layout(alignment: entity_instance) -> entity_instance:
"""
Returns the IfcAlignmentHorizontal assocated with this alignment
"""
for rel in alignment.IsNestedBy:
for layout in rel.RelatedObjects:
if layout.is_a("IfcAlignmentHorizontal"):
return layout
return None
@@ -0,0 +1,55 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util
from ifcopenshell import entity_instance
import ifcopenshell.util.representation
def get_layout_curve(layout: entity_instance) -> entity_instance:
"""
Returns the representation curve for the layout. This will be an IfcCompositeCurve, IfcGradientCurve, or IfcSegmentReferenceCurve
for IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant, respectively.
:param layout: An alignment layout
:return: The geometric representation curve
Example:
.. code:: python
alignment = model.by_type("IfcAlignment")[0]
layout = ifcopenshell.api.get_horizontal_layout(alignment)
composite_curve = ifcopenshell.api.alignment.get_layout_curve(layout)
"""
alignment = ifcopenshell.api.alignment.get_alignment(layout)
curve = ifcopenshell.api.alignment.get_curve(alignment)
if layout.is_a("IfcAlignmentHorizontal"):
# Layout is horizontal so get the IfcCompositeCurve
if curve.is_a("IfcGradientCurve"):
curve = curve.BaseCurve
elif curve.is_a("IfcSegmentedReferenceCurve"):
curve = curve.BaseCurve.BaseCurve
elif layout.is_a("IfcAlignmentVertical"):
# Layout is vertical so get the IfcGradientCurve
if curve.is_a("IfcSegmentedReferenceCurve"):
curve = curve.BaseCurve
return curve
@@ -0,0 +1,44 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util
from ifcopenshell import entity_instance
from typing import Sequence
import ifcopenshell.util.element
def get_layout_segments(layout: entity_instance) -> Sequence[entity_instance]:
"""
Returns the IfcAlignmentSegment nested to this alignment layout
Example:
.. code:: python
horizontal = model.by_type("IfcAlignmentHorizontal")[0]
segments = ifcopenshell.api.alignment.get_layout_segments(horizontal)
"""
segments = []
for rel in layout.IsNestedBy:
for segment in rel.RelatedObjects:
if segment.is_a("IfcAlignmentSegment"):
segments.append(segment)
return segments
@@ -0,0 +1,31 @@
# 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 get_vertical_layout(alignment: entity_instance) -> entity_instance:
"""
Returns the IfcAlignmentVertical assocated with this alignment
"""
for rel in alignment.IsNestedBy:
for layout in rel.RelatedObjects:
if layout.is_a("IfcAlignmentVertical"):
return layout
return None
@@ -22,40 +22,29 @@ import ifcopenshell.util.element
from ifcopenshell import entity_instance
def has_zero_length_segment(entity: entity_instance) -> bool:
def has_zero_length_segment(layout: entity_instance) -> bool:
"""
Returns true if the entity ends with a zero length segment. If the entity is an IfcCompositeCurve the IfcCurveSegment.Transition must be DISCONTINUOUS
Returns true if the layout ends with a zero length segment.
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve
:param layout: An IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant
:return: True if the zero length segment is present
"""
expected_types = [
"IfcAlignmentHorizontal",
"IfcAlignmentVertical",
"IfcAlignmentCant",
"IfcCompositeCurve",
"IfcGradientCurve",
"IfcSegmentedReferenceCurve",
]
if not entity.is_a() in expected_types:
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
if not layout.is_a() in expected_types:
raise TypeError(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{entity.is_a()}"
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{layout.is_a()}"
)
if entity.is_a("IfcCompositeCurve"):
last_segment = entity.Segments[-1]
return last_segment.Transition == "DISCONTINUOUS" and last_segment.SegmentLength.wrappedValue == 0.0
else:
segments = ifcopenshell.util.element.get_components(entity)
for rel in entity.IsNestedBy:
if 0 < len(rel.RelatedObjects):
last_segment = rel.RelatedObjects[-1]
if last_segment.is_a("IfcAlignmentSegment"):
if last_segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
return last_segment.DesignParameters.SegmentLength == 0.0
elif last_segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
return last_segment.DesignParameters.HorizontalLength == 0.0
elif last_segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
return last_segment.DesignParameters.HorizontalLength == 0.0
result = False
for rel in layout.IsNestedBy:
if 0 < len(rel.RelatedObjects):
last_segment = rel.RelatedObjects[-1]
if last_segment.is_a("IfcAlignmentSegment"):
if last_segment.DesignParameters.is_a("IfcAlignmentHorizontalSegment"):
result = last_segment.DesignParameters.SegmentLength == 0.0
elif last_segment.DesignParameters.is_a("IfcAlignmentVerticalSegment"):
result = last_segment.DesignParameters.HorizontalLength == 0.0
elif last_segment.DesignParameters.is_a("IfcAlignmentCantSegment"):
result = last_segment.DesignParameters.HorizontalLength == 0.0
return False
return result
@@ -0,0 +1,141 @@
# 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
from ifcopenshell import entity_instance
import math
from collections.abc import Sequence
import ifcopenshell.api.alignment.create_layout_segment
def layout_horizontal_alignment_by_pi_method(
file: ifcopenshell.file, layout: entity_instance, hpoints: Sequence[Sequence[float]], radii: Sequence[float]
) -> None:
"""
Appends IfcAlignmentHorizontalSegment to a previously defined IfcAlignmentHorizontal using the PI layout method.
The zero length segment is updated.
:param file: file
:param layout: An IfcAlignmentHorizontal layout
:param hpoints: (X, Y) pairs denoting the location of the horizontal PIs, including start (POB) and end (POE).
:param radii: radius values to use for transition
:return: None
"""
if not (len(hpoints) - 2 == len(radii)):
raise ValueError("radii should have two fewer elements that hpoints")
xBT, yBT = hpoints[0]
xPI, yPI = hpoints[1]
i = 1
for radius in radii:
# back tangent
dxBT = xPI - xBT
dyBT = yPI - yBT
angleBT = math.atan2(dyBT, dxBT)
lengthBT = math.sqrt(dxBT * dxBT + dyBT * dyBT)
# forward tangent
i += 1
xFT, yFT = hpoints[i]
dxFT = xFT - xPI
dyFT = yFT - yPI
angleFT = math.atan2(dyFT, dxFT)
delta = angleFT - angleBT
tangent = abs(radius * math.tan(delta / 2))
lc = abs(radius * delta)
radius *= delta / abs(delta)
xPC = xPI - tangent * math.cos(angleBT)
yPC = yPI - tangent * math.sin(angleBT)
xPT = xPI + tangent * math.cos(angleFT)
yPT = yPI + tangent * math.sin(angleFT)
tangent_run = lengthBT - tangent
# create back tangent run
if 1.0e-03 < tangent_run:
pt = file.createIfcCartesianPoint(
Coordinates=(xBT, yBT),
)
design_parameters = file.createIfcAlignmentHorizontalSegment(
StartTag=None,
EndTag=None,
StartPoint=pt,
StartDirection=angleBT,
StartRadiusOfCurvature=0.0,
EndRadiusOfCurvature=0.0,
SegmentLength=tangent_run,
GravityCenterLineHeight=None,
PredefinedType="LINE",
)
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
# create circular curve
if radius != 0.0:
pc = file.createIfcCartesianPoint(
Coordinates=(xPC, yPC),
)
design_parameters = file.createIfcAlignmentHorizontalSegment(
StartTag=None,
EndTag=None,
StartPoint=pc,
StartDirection=angleBT,
StartRadiusOfCurvature=float(radius),
EndRadiusOfCurvature=float(radius),
SegmentLength=lc,
GravityCenterLineHeight=None,
PredefinedType="CIRCULARARC",
)
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
xBT = xPT
yBT = yPT
xPI = xFT
yPI = yFT
# done processing radii
# create last tangent run
dx = xPI - xBT
dy = yPI - yBT
angleBT = math.atan2(dy, dx)
tangent_run = math.sqrt(dx * dx + dy * dy)
if 1.0e-03 < tangent_run:
pt = file.createIfcCartesianPoint(Coordinates=(xBT, yBT))
design_parameters = file.createIfcAlignmentHorizontalSegment(
StartTag=None,
EndTag=None,
StartPoint=pt,
StartDirection=angleBT,
StartRadiusOfCurvature=0.0,
EndRadiusOfCurvature=0.0,
SegmentLength=tangent_run,
GravityCenterLineHeight=None,
PredefinedType="LINE",
)
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
@@ -0,0 +1,123 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.alignment
from ifcopenshell import entity_instance
import math
from collections.abc import Sequence
import ifcopenshell.api.alignment.create_layout_segment
def layout_vertical_alignment_by_pi_method(
file: ifcopenshell.file, layout: entity_instance, vpoints: Sequence[Sequence[float]], lengths: Sequence[float]
) -> None:
"""
Appends IfcAlignmentVerticalSegment to a previously defined IfcAlignmentVertical using the PI layout method.
The zero length segment is updated.
:param file: file
:param layout: An IfcAlignmentVertical layout
:param vpoints: (distance_along, Z_height) pairs denoting the location of the vertical PIs, including start and end.
:param lengths: horizontal length of parabolic vertical curves
:return: None
"""
if not (len(vpoints) - 2 == len(lengths)):
raise ValueError("lengths should have two fewer elements that vpoints")
xPBG, yPBG = vpoints[0]
xPVI, yPVI = vpoints[1]
i = 1
for length in lengths:
# back gradient
dxBG = xPVI - xPBG
dyBG = yPVI - yPBG
start_slope = math.tan(math.atan2(dyBG, dxBG))
# forward gradient
i += 1
xPFG, yPFG = vpoints[i]
dxFG = xPFG - xPVI
dyFG = yPFG - yPVI
end_slope = math.tan(math.atan2(dyFG, dxFG))
xEVC = xPVI + length / 2.0
yEVC = yPVI + end_slope * length / 2.0
# create gradient
gradient_length = dxBG - length / 2.0
if 1.0e-03 < gradient_length:
design_parameters = file.createIfcAlignmentVerticalSegment(
StartTag=None,
EndTag=None,
StartDistAlong=xPBG,
HorizontalLength=gradient_length,
StartHeight=yPBG,
StartGradient=start_slope,
EndGradient=start_slope,
RadiusOfCurvature=None,
PredefinedType="CONSTANTGRADIENT",
)
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
# create vertical curve
if 0.0 < length:
k = (end_slope - start_slope) / length
xBVC = xPVI - length / 2.0
yBVC = yPVI - start_slope * length / 2.0
design_parameters = file.createIfcAlignmentVerticalSegment(
StartTag=None,
EndTag=None,
StartDistAlong=xBVC,
HorizontalLength=length,
StartHeight=yBVC,
StartGradient=start_slope,
EndGradient=end_slope,
RadiusOfCurvature=1 / k,
PredefinedType="PARABOLICARC",
)
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
# start of next curve is end of this curve
xPBG = xEVC
yPBG = yEVC
xPVI = xPFG
yPVI = yPFG
# create last gradient run
dx = xPVI - xPBG
dy = yPVI - yPBG
slope = math.tan(math.atan2(dy, dx))
gradient_length = dx
if 1.0e-03 < gradient_length:
design_parameters = file.createIfcAlignmentVerticalSegment(
StartTag=None,
EndTag=None,
StartDistAlong=xPBG,
HorizontalLength=gradient_length,
StartHeight=yPBG,
StartGradient=slope,
EndGradient=slope,
RadiusOfCurvature=None,
PredefinedType="CONSTANTGRADIENT",
)
ifcopenshell.api.alignment.create_layout_segment(file, layout, design_parameters)
@@ -1,68 +0,0 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.alignment
from ifcopenshell import entity_instance
def map_alignment_segments(
file: ifcopenshell.file, alignment: entity_instance, composite_curve: entity_instance
) -> None:
"""
Creates IfcCurveSegment entities for the supplied alignment business logic entity instance and assigns them to the composite curve.
End-Start points of adjacent segments are evaluated and the IfcCurveSegment.Transition is set.
This function does not create an IfcShapeRepresentation. Use create_geometric_representation to create all the representations
for an alignment. This function only populates the composite curve with IfcCurveSegment entities.
:param alignment: The business logic alignment, expected to be IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant
:param composite_curve: The IfcCompositeCurve (or subclass) which will receive the IfcCurveSegment
:return: None
"""
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
if not alignment.is_a() in expected_types:
raise TypeError(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{alignment.is_a()}"
)
if alignment.is_a("IfcAlignmentHorizontal") and not composite_curve.is_a("IfcCompositeCurve"):
raise TypeError(f"Expected to see IfcCompositeCurve, instead received '{composite_curve.is_a()}'.")
elif alignment.is_a("IfcAlignmentVertical") and not composite_curve.is_a("IfcGradientCurve"):
raise TypeError(f"Expected to see IfcGradientCurve, instead received '{composite_curve.is_a()}'.")
elif alignment.is_a("IfcAlignmentCant") and not composite_curve.is_a("IfcSegmentedReferenceCurve"):
raise TypeError(f"Expected to see IfcSegmentedReferenceCurve, instead received '{composite_curve.is_a()}'.")
composite_curve.SelfIntersect = False
for rel_nests in alignment.IsNestedBy:
for layout in rel_nests.RelatedObjects:
if layout.is_a("IfcLinearElement"):
if alignment.is_a("IfcAlignmentHorizontal"):
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, layout)
elif alignment.is_a("IfcAlignmentVertical"):
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, layout)
elif alignment.is_a("IfcAlignmentCant"):
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(
file, layout, alignment.RailHeadDistance
)
else:
assert False
for mapped_segment in mapped_segments:
if mapped_segment:
ifcopenshell.api.alignment.add_segment_to_curve(file, mapped_segment, composite_curve)
@@ -20,21 +20,21 @@ import ifcopenshell
from ifcopenshell import entity_instance
def name_segments(prefix: str, alignment: entity_instance) -> None:
def name_segments(prefix: str, layout: entity_instance) -> None:
"""
Sets the segment name like ("H1" for horizontal, "V1" for vertical, "C1" for cant)
Sets the IfcAlignmentSegment.Name attribute using a prefix and sequence number (e.g. "H1" for horizontal, "V1" for vertical, "C1" for cant)
:param prefix: The naming prefix
:param alignment: The alignment whose segments are to be named. This should be a IfcAlignmentHorizontal, IfcAlignmentVertical or IfcAlignmentCant
:param layout: The layout alignment whose segments are to be named. This should be a IfcAlignmentHorizontal, IfcAlignmentVertical or IfcAlignmentCant
"""
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
if not alignment.is_a() in expected_types:
if not layout.is_a() in expected_types:
raise TypeError(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{v.is_a()}"
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{layout.is_a()}"
)
i = 1
for rel in alignment.IsNestedBy:
for rel in layout.IsNestedBy:
for segment in rel.RelatedObjects:
if segment.is_a("IfcAlignmentSegment"):
segment.Name = f"{prefix}{i}"
@@ -1,59 +0,0 @@
# 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
import ifcopenshell.api.nest
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
import numpy as np
from ifcopenshell import entity_instance
import ifcopenshell.util
import ifcopenshell.util.element
def remove_last_segment(file: ifcopenshell.file, entity: entity_instance) -> entity_instance:
"""
Removes the last segment from the end of entity.
:param entity: An IfcAlignmentHorizontal, IfcAlignmentVertical, IfcAlignmentCant or IfcCompositeCurve
:return: The segment
"""
expected_types = [
"IfcAlignmentHorizontal",
"IfcAlignmentVertical",
"IfcAlignmentCant",
"IfcCompositeCurve",
"IfcGradientCurve",
"IfcSegmentedReferenceCurve",
]
if not entity.is_a() in expected_types:
raise TypeError(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received '{entity.is_a()}"
)
if entity.is_a("IfcCompositeCurve"):
last_segment = entity.Segments[-1]
entity.Segments = tuple(set(entity.Segments) - {last_segment})
entity.Segments[-1].Transition = "DISCONTINUOUS"
return last_segment
else:
components = ifcopenshell.util.element.get_components(entity)
last_segment = components[-1]
ifcopenshell.api.nest.unassign_object(file, (last_segment,))
return last_segment
@@ -123,6 +123,23 @@ def print_alignment(alignment, indent=0):
print_alignment(child, indent + 2)
def print_alignment_deep(alignment, indent=0):
"""
Debugging function to print alignment decomposition, including layout segments
"""
print(" " * indent, alignment)
for rel in alignment.IsNestedBy:
for child in rel.RelatedObjects:
print_alignment_deep(child, indent + 2)
if child.is_a("IfcAlignmentSegment"):
print(" " * (indent + 4), child.DesignParameters)
for agg in alignment.IsDecomposedBy:
for child in agg.RelatedObjects:
print_alignmen_deep(child, indent + 2)
def print_composite_curve(curve):
"""
Debugging function to print composite curve segments
@@ -131,3 +148,17 @@ def print_composite_curve(curve):
for segment in curve.Segments:
print(" " * 2, segment)
def print_composite_curve_deep(curve):
"""
Debugging function to print composite curve segments, including curve segment details
"""
print(str(curve)[0:100])
for segment in curve.Segments:
print(" " * 2, segment)
print(" " * 4, segment.ParentCurve)
print(" " * 4, segment.Placement)
print(" " * 4, segment.Placement.Location)
print(" " * 4, segment.Placement.RefDirection)
@@ -0,0 +1,33 @@
# 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/>.
"""
Coordinate Geometry (cogo) functions primarily for survey points and control monument for layout, parcels, etc.
"""
from .add_survey_point import add_survey_point
from .assign_survey_point import assign_survey_point
from .edit_survey_point import edit_survey_point
from .bearing2dd import bearing2dd
__all__ = [
"add_survey_point",
"assign_survey_point",
"edit_survey_point",
"bearing2dd",
]
@@ -0,0 +1,47 @@
# 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
from ifcopenshell import entity_instance
import typing
def add_survey_point(file: ifcopenshell.file, survey_point: entity_instance, site:entity_instance = None) -> entity_instance:
"""
Adds a single survey point to the model based on IFC Concept Template 4.1.7.1.2.5.
Survey points are located relative to IfcRepresentationContext.WorldCoordinateSystem
:param survey_point: The survey point
:return: an IfcAnnotation entity
Example:
.. code:: python
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint(4000.0,3500.0)))
"""
context = ifcopenshell.util.representation.get_context(file,"Model","Annotation","MODEL_VIEW")
shape_representation = file.createIfcShapeRepresentation(ContextOfItems=context,RepresentationIdentifier='Annotation',RepresentationType='Point',Items=[survey_point])
representation = file.createIfcProductDefinitionShape(Representations=[shape_representation])
annotation = file.createIfcAnnotation(ifcopenshell.guid.new(),ObjectPlacement=context.WorldCoordinateSystem,Representation=representation,PredefinedType="SURVEY")
if (site == None):
site = file.by_type("IfcSite")[0]
ifcopenshell.api.spatial.assign_container(file,relating_structure=site,products=[annotation])
return annotation
@@ -0,0 +1,38 @@
# 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
from ifcopenshell import entity_instance
import typing
def assign_survey_point(annotation: entity_instance, survey_point: entity_instance):
"""
Assigns a coordinate point to a survey point annotation
:param annotaton: The survey point annotation
:param survey_point: The survey point
:return: None
Example:
.. code:: python
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint(4000.0,3500.0)))
ifcopenshell.api.cogo.assign_surve_point(annotation,file.createIfcCartesianPoint(4000.0,3500.0,100.0))
"""
annotation.Representation.Representations[0].Items = [survey_point]
@@ -0,0 +1,106 @@
# 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.util.geolocation
def bearing2dd(bearing: str)->float:
"""
Converts a quadrant bearing string to decimal degrees
The format of the string is "N|S dd (mm (ss.s)) E|W"
where:
N|S is N or S for North or South
dd is degree (required)
mm is minute (optional, but required if second is provided)
ss.s is second (required)
E|W is E or W for East or West
:param str: the bearing string
:return: Angle in radian
"""
error_msg = "Invalid bearing string"
bearing = bearing.strip() # trim external white space
bearing = ' '.join(bearing.split()) # make sure all parts separated by a single space
parts = bearing.split()
nParts = len(parts)
if nParts < 3 or 5 < nParts:
raise ValueError(error_msg)
cY = parts[0]
cY = cY.upper()
if cY != 'N' and cY != 'S':
raise ValueError(error_msg)
cX = parts[-1]
cX = cX.upper()
if cX != 'E' and cX != 'W':
raise ValueError(error_msg)
d = 0
m = 0
s = 0.
ms = 0
if nParts == 3:
d = int(parts[1])
elif nParts == 4:
d = int(parts[1])
m = int(parts[2])
elif nParts == 5:
d = int(parts[1])
m = int(parts[2])
s = float(parts[3])
# s in a decimal number
# need to break it into whole seconds and milliseconds
ms = 100.*(s - int(s))
s = int(s)
if d < 0 or (m < 0 or 60 <= m) or (s < 0 or 60 <= s) or ms < 0:
raise ValueError(error_msg)
if cY == 'N' and cX == 'E':
angle = 90.
sign = -1.
elif cY == 'N' and cX == 'W':
angle = 90.
sign = 1.
elif cY == 'S' and cX == 'E':
angle = 270.
sign = 1.
elif cY == 'S' and cX == 'W':
angle = 270.
sign = -1.
try:
dms = ifcopenshell.util.geolocation.dms2dd(d,m,s,ms)
except ValueError:
raise ValueError(error_msg)
if dms < 0. or 90. < dms:
raise ValueError(error_msg)
angle += sign*dms
# S 90 E will evaluate to 360
if angle == 360.:
angle = 0.
return angle
@@ -0,0 +1,40 @@
# 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
from ifcopenshell import entity_instance
import typing
def edit_survey_point(annotation: entity_instance, x:float,y:float,z:float=0.0):
"""
Edits the location of a previously defined survey point
:param survey_point: The survey point
:return: None
Example:
.. code:: python
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint(4000.0,3500.0)))
ifcopenshell.api.cogo.edit_surve_point(annotation,3500.0,2000.0)
"""
if annotation.Representation.Representations[0].Items[0].Dim == 2:
annotation.Representation.Representations[0].Items[0].Coordinates = ((x,y))
else:
annotation.Representation.Representations[0].Items[0].Coordinates = ((x,y,z))
@@ -17,15 +17,29 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import math
import ifcopenshell
import ifcopenshell.util.unit
def station_as_string(file: ifcopenshell.file, sta: float):
"""
Returns a stringized version of a station. Example 100.0 is 1+00.00 as a stationing string.
If the project units are SI-based, the string is in the format xxx+yyy.zzz
If the project units are Emperial-based, the string is in the format xx+yy.zz
:param station: the station to be stringized
:return: stringized station
"""
unit_type = ifcopenshell.util.unit.get_project_unit(file,"LENGTHUNIT")
if unit_type.is_a("IfcConversionBasedUnit"):
station = ifcopenshell.util.unit.convert(sta,from_unit=unit_type.Name,from_prefix=None,to_unit="foot",to_prefix=None)
plus_seperator = 2
precision = 2
else:
station = ifcopenshell.util.unit.convert(sta,from_unit=unit_type.Name,from_prefix=unit_type.Prefix,to_unit="meter",to_prefix=None)
plus_seperator = 3
precision = 3
def station_as_string(station: float, plus_seperator=3, accuracy=3):
"""
Returns a stringized version of a station. Example 100.0 is 1+00.00 as a stationing string
@param station: the station to be stringized
@param plus_seperator: location of the '+' symbol relative to the decimal place (typically 2 for US units and 3 for SI units)
@param accuracy: number of digits following the decimal place
"""
value = math.fabs(station)
shifter = math.pow(10.0, plus_seperator)
@@ -34,13 +48,13 @@ def station_as_string(station: float, plus_seperator=3, accuracy=3):
# Check to make sure that v2 is not basically the same as shifter
# If station = 69500.00000, we sometimes get 694+100.00 instead of 695+00.00
if math.isclose(v2 - shifter, 5.0 * math.pow(10.0, -(accuracy + 1))):
if math.isclose(v2-shifter, 0., abs_tol=5.0 * math.pow(10.0, -(precision + 1))):
v2 = 0.0
v1 += 1
v1 = -1 * v1 if station < 0 else v1
station_string = "{:d}+{:0{}.{}f}".format(v1, v2, plus_seperator + accuracy + 1, accuracy)
station_string = "{:d}+{:0{}.{}f}".format(v1, v2, plus_seperator + precision + 1, precision)
# special case when v1 is 0 and station is negative, the string above doesn't get the leading
# negative sign. this snippet fixes that
@@ -1,72 +0,0 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
def test_add_segment_to_curve():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
)
circular_arc = file.createIfcCurveSegment(
Placement=file.createIfcAxis2Placement2d(
file.createIfcCartesianPoint((4084.115884, 3889.462938)),
file.createIfcDirection((0.224530986099614, 0.974466949814685)),
),
SegmentStart=file.createIfcLengthMeasure(0.0),
SegmentLength=file.createIfcLengthMeasure(-1848.115835),
ParentCurve=file.createIfcCircle(
Position=file.createIfcAxis2Placement2d(
file.createIfcCartesianPoint((0.0, 0.0)), file.createIfcDirection((1.0, 0.0))
),
Radius=1250.0,
),
)
line = file.createIfcCurveSegment(
Placement=file.createIfcAxis2Placement2d(
file.createIfcCartesianPoint((5469.395067, 4847.56631)),
file.createIfcDirection((0.991014275066766, -0.133756146078947)),
),
SegmentStart=file.createIfcLengthMeasure(0.0),
SegmentLength=file.createIfcLengthMeasure(1564.635765),
ParentCurve=file.createIfcLine(
Pnt=file.createIfcCartesianPoint((0.0, 0.0)),
Dir=file.createIfcVector(Orientation=file.createIfcDirection((1.0, 0.0)), Magnitude=1.0),
),
)
composite_curve = file.createIfcCompositeCurve(SelfIntersect=False)
ifcopenshell.api.alignment.add_segment_to_curve(file, circular_arc, composite_curve)
assert circular_arc.UsingCurves[0] == composite_curve
assert composite_curve.Segments[-1] == circular_arc
ifcopenshell.api.alignment.add_segment_to_curve(file, line, composite_curve)
assert line.UsingCurves[0] == composite_curve
assert composite_curve.Segments[-1] == line
@@ -20,10 +20,13 @@ import pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
from ifcopenshell.api.alignment._add_segment_to_layout import _add_segment_to_layout
def test_add_segment_to_layout():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -33,16 +36,8 @@ def test_add_segment_to_layout():
parent=geometric_representation_context,
)
horizontal_alignment = file.create_entity(
type="IfcAlignmentHorizontal",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=None,
Name=None,
Description=None,
ObjectType=None,
ObjectPlacement=None,
Representation=None,
)
alignment = ifcopenshell.api.alignment.create(file,"")
horizontal_alignment = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
design_parameters = file.create_entity(
type="IfcAlignmentHorizontalSegment",
@@ -68,8 +63,9 @@ def test_add_segment_to_layout():
DesignParameters=design_parameters,
)
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal_alignment, alignment_segment)
_add_segment_to_layout(file, horizontal_alignment, alignment_segment)
assert len(horizontal_alignment.IsNestedBy) == 1
assert len(horizontal_alignment.IsNestedBy[0].RelatedObjects) == 1
assert len(horizontal_alignment.IsNestedBy[0].RelatedObjects) == 2 # The the segment we added and the automatically created zero length segment
assert horizontal_alignment.IsNestedBy[0].RelatedObjects[0] == alignment_segment
assert alignment_segment.IsNestedBy[0].RelatedObjects[0].is_a("IfcReferent") # a referent is automatically added at the start of the segment
@@ -21,10 +21,11 @@ import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.util.element
def test_add_stationing_to_alignment():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -34,17 +35,9 @@ def test_add_stationing_to_alignment():
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_alignment_by_pi_method(
file, "TestAlignment", coordinates, radii, vpoints, lengths
alignment = ifcopenshell.api.alignment.create(
file, "TestAlignment", start_station=2000.
)
ifcopenshell.api.alignment.create_geometric_representation(file, alignment)
ifcopenshell.api.alignment.add_stationing_to_alignment(file, alignment, 2000.0)
for rel in alignment.IsNestedBy:
for referent in rel.RelatedObjects:
@@ -21,9 +21,11 @@ import ifcopenshell.api.alignment
import ifcopenshell.api.context
def test_add_vertical_by_pi_method():
def test_add_vertical_alignment():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -33,42 +35,44 @@ def test_add_vertical_by_pi_method():
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)]
# single horizontal alignment
alignment = ifcopenshell.api.alignment.create_alignment_by_pi_method(file, "TestAlignment", coordinates, radii)
alignment = ifcopenshell.api.alignment.create(file,"A1",include_vertical=False)
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
assert len(alignment.IsNestedBy) == 1 # nesting IfcAlignemtHorizontal
assert len(alignment.IsNestedBy[0].RelatedObjects) == 1 # nesting one IfcAlignmentHorizontal
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcAlignmentHorizontal")
assert (
len(alignment.IsNestedBy[0].RelatedObjects[0].IsNestedBy) == 1
) # nesting of segments beneath IfcAlignmentHorizontal
assert len(alignment.IsNestedBy[0].RelatedObjects[0].IsNestedBy[0].RelatedObjects) == 8 # segments
assert len(alignment.IsNestedBy) == 1 # one nest
assert len(alignment.IsNestedBy[0].RelatedObjects) == 2 # nesting IfcReferent, IfcAlignmentHorizontal
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcReferent")
assert alignment.IsNestedBy[0].RelatedObjects[1].is_a("IfcAlignmentHorizontal")
curve = ifcopenshell.api.alignment.get_curve(alignment)
assert curve.is_a("IfcCompositeCurve")
vertical_layout = ifcopenshell.api.alignment.add_vertical_layout(file,alignment)
# add first vertical
ifcopenshell.api.alignment.add_vertical_alignment_by_pi_method(file, alignment, vpoints, lengths)
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
assert len(alignment.IsNestedBy) == 1 # 1 nesting relationsip for the alignments
assert len(alignment.IsNestedBy[0].RelatedObjects) == 2 # nesting IfcAlignmentHorizontal and IfcAlignmentVertical
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcAlignmentHorizontal")
assert alignment.IsNestedBy[0].RelatedObjects[1].is_a("IfcAlignmentVertical")
assert len(alignment.IsNestedBy) == 1 # one nest
assert len(alignment.IsNestedBy[0].RelatedObjects) == 3 # nesting IfcReferent, IfcAlignmentHorizontal, IfcAlignmentVertical
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcReferent")
assert alignment.IsNestedBy[0].RelatedObjects[1].is_a("IfcAlignmentHorizontal")
assert alignment.IsNestedBy[0].RelatedObjects[2].is_a("IfcAlignmentVertical")
curve = ifcopenshell.api.alignment.get_curve(alignment)
assert curve.is_a("IfcGradientCurve")
# add a second vertical alignment
vertical_layout = ifcopenshell.api.alignment.add_vertical_layout(file,alignment)
# add second vertical
ifcopenshell.api.alignment.add_vertical_alignment_by_pi_method(file, alignment, vpoints, lengths)
assert len(alignment.IsDecomposedBy) == 1 # 1 IfcRelAggreates relationship for the child algiments
assert (
len(alignment.IsDecomposedBy[0].RelatedObjects) == 2
) # two child alignments, one for the first vertical and one for the vertical just added
for child_alignment in alignment.IsDecomposedBy[0].RelatedObjects:
assert child_alignment.is_a("IfcAlignment")
assert len(child_alignment.IsNestedBy) == 1 # one nesting relationship for the IfcAlignmentVertical
assert len(child_alignment.IsNestedBy[0].RelatedObjects) == 1 # The IfcAlignmentVertical
assert child_alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcAlignmentVertical")
assert len(alignment.IsNestedBy) == 1 # 1 nesting relationsip for the alignments
assert len(alignment.IsNestedBy[0].RelatedObjects) == 1 # nesting one IfcAlignmentHorizontal
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcAlignmentHorizontal")
assert len(alignment.IsNestedBy[0].RelatedObjects) == 2 # nesting IfcReferent and IfcAlignmentHorizontal
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcReferent")
assert alignment.IsNestedBy[0].RelatedObjects[1].is_a("IfcAlignmentHorizontal")
test_add_vertical_alignment()
@@ -0,0 +1,69 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.alignment.get_layout_segments
import ifcopenshell.api.context
def test_create_alignment():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
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,
)
include_vertical = [False,True,True]
include_cant = [False, False, True]
expected_curve_type = ["IfcCompositeCurve","IfcGradientCurve","IfcSegmentedReferenceCurve"]
for i in range(0,3) :
ali = ifcopenshell.api.alignment.create(file,"A1",include_vertical[i],include_cant[i])
assert ali != None
# verify the geometric representation was created
curve = ifcopenshell.api.alignment.get_curve(ali)
assert(curve.is_a() == expected_curve_type[i])
assert len(curve.Segments) == 1
horiz = ifcopenshell.api.alignment.get_horizontal_layout(ali)
vert = ifcopenshell.api.alignment.get_vertical_layout(ali)
cant = ifcopenshell.api.alignment.get_cant_layout(ali)
assert horiz != None
if include_vertical[i]:
assert vert != None
if include_cant[i]:
assert cant != None
# verify each layout has a zero length segment (which also tests if the geometry curve has a zero length segment)
alignments = [horiz,vert,cant]
for a in alignments:
if a != None:
segments = ifcopenshell.api.alignment.get_layout_segments(a)
assert len(segments) == 1
assert ifcopenshell.api.alignment.has_zero_length_segment(a) # there is a check in this function for the geometry curve
@@ -0,0 +1,57 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
def test_create_alignment_pi_method():
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)
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
assert len(alignment.IsNestedBy) == 1 # one nest
assert len(alignment.IsNestedBy[0].RelatedObjects) == 3 # nesting IfcReferent, IfcAlignmentHorizontal, IfcAlignmentVertical
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcReferent")
assert alignment.IsNestedBy[0].RelatedObjects[1].is_a("IfcAlignmentHorizontal")
assert alignment.IsNestedBy[0].RelatedObjects[2].is_a("IfcAlignmentVertical")
assert (
len(alignment.IsNestedBy[0].RelatedObjects[1].IsNestedBy) == 1
) # nesting of segments beneath IfcAlignmentHorizontal
assert len(alignment.IsNestedBy[0].RelatedObjects[1].IsNestedBy[0].RelatedObjects) == 8 # segments in horizontal layout
assert len(alignment.IsNestedBy[0].RelatedObjects[2].IsNestedBy[0].RelatedObjects) == 10 # segments in vertical layout
test_create_alignment_pi_method()
@@ -0,0 +1,275 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
from ifcopenshell import entity_instance
import math
def _test_horizontal() -> ifcopenshell.file:
file = ifcopenshell.file(schema="IFC4X3_ADD2")
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,
)
# creates an IfcAlignment with an IfcAlignmentHorizontal layout containing only the zero length segment
ali = ifcopenshell.api.alignment.create(file,"A1")
# append a segment to the horizontal layout
horizontal_alignment = ifcopenshell.api.alignment.get_horizontal_layout(ali)
curve = ifcopenshell.api.alignment.get_curve(horizontal_alignment)
assert curve == None # for single horizontal, geometric representation is on IfcAlignment
curve = ifcopenshell.api.alignment.get_curve(ali)
assert curve.is_a("IfcCompositeCurve")
assert len(curve.Segments) == 1
design_parameters = file.create_entity(
type="IfcAlignmentHorizontalSegment",
StartTag=None,
EndTag=None,
StartPoint=file.createIfcCartesianPoint(Coordinates=((0.0, 0.0))),
StartDirection=0.0,
StartRadiusOfCurvature=0.0,
EndRadiusOfCurvature=0.0,
SegmentLength=100.0,
GravityCenterLineHeight=None,
PredefinedType="LINE",
)
end = ifcopenshell.api.alignment.create_layout_segment(file,horizontal_alignment,design_parameters)
assert len(horizontal_alignment.IsNestedBy[0].RelatedObjects) == 2
x = end[0,3]
y = end[1,3]
z = end[2,3]
assert x == 100.0
assert y == 0.0
assert z == 0.0
curve = ifcopenshell.api.alignment.get_curve(ali)
assert curve.is_a("IfcCompositeCurve")
assert len(curve.Segments) == 2
design_parameters = file.create_entity(
type="IfcAlignmentHorizontalSegment",
StartTag=None,
EndTag=None,
StartPoint=file.createIfcCartesianPoint(Coordinates=((x.item(), y.item()))),
StartDirection=math.pi/6,
StartRadiusOfCurvature=0.0,
EndRadiusOfCurvature=0.0,
SegmentLength=50.0,
GravityCenterLineHeight=None,
PredefinedType="LINE",
)
end = ifcopenshell.api.alignment.create_layout_segment(file,horizontal_alignment,design_parameters)
assert len(horizontal_alignment.IsNestedBy[0].RelatedObjects) == 3
x = end[0,3]
y = end[1,3]
z = end[2,3]
assert x == 100.0 + 50.0*math.cos(math.pi/6)
assert y == 50.0*math.sin(math.pi/6)
assert z == 0.0
curve = ifcopenshell.api.alignment.get_curve(ali)
assert curve.is_a("IfcCompositeCurve")
assert len(curve.Segments) == 3
return file
def _test_horizontal_vertical():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
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,
)
# creates an IfcAlignment with an IfcAlignmentHorizontal layout containing only the zero length segment
ali = ifcopenshell.api.alignment.create(file,"A1",True)
# append a segment to the horizontal layout
horizontal_alignment = ifcopenshell.api.alignment.get_horizontal_layout(ali)
vertical_alignment = ifcopenshell.api.alignment.get_vertical_layout(ali)
curve = ifcopenshell.api.alignment.get_curve(horizontal_alignment)
assert curve == None
curve = ifcopenshell.api.alignment.get_curve(vertical_alignment)
assert curve == None
basis_curve = ifcopenshell.api.alignment.get_basis_curve(ali)
assert basis_curve.is_a("IfcCompositeCurve")
curve = ifcopenshell.api.alignment.get_curve(ali)
assert curve.is_a("IfcGradientCurve")
assert len(basis_curve.Segments) == 1
assert len(curve.Segments) == 1
design_parameters = file.create_entity(
type="IfcAlignmentHorizontalSegment",
StartTag=None,
EndTag=None,
StartPoint=file.createIfcCartesianPoint(Coordinates=((0.0, 0.0))),
StartDirection=0.0,
StartRadiusOfCurvature=0.0,
EndRadiusOfCurvature=0.0,
SegmentLength=100.0,
GravityCenterLineHeight=None,
PredefinedType="LINE",
)
end = ifcopenshell.api.alignment.create_layout_segment(file,horizontal_alignment,design_parameters)
basis_curve = ifcopenshell.api.alignment.get_basis_curve(ali)
assert basis_curve.is_a("IfcCompositeCurve")
assert len(basis_curve.Segments) == 2
design_parameters = file.createIfcAlignmentVerticalSegment(
StartDistAlong=0.0,
HorizontalLength=50.0,
StartHeight=20.0,
StartGradient = 1./100.,
EndGradient = 1./100.,
PredefinedType="CONSTANTGRADIENT"
)
end = ifcopenshell.api.alignment.create_layout_segment(file,vertical_alignment,design_parameters)
assert len(vertical_alignment.IsNestedBy[0].RelatedObjects) == 2
x = end[0,3]
y = end[1,3]
z = end[2,3]
assert x == 50.
assert y == 20.5
assert z == 0.0
dx = end[0,0]
dy = end[1,0]
gradient = dy/dx
design_parameters = file.createIfcAlignmentVerticalSegment(
StartDistAlong=50.0,
HorizontalLength=50.0,
StartHeight=y.item(),
StartGradient = -gradient,
EndGradient = -gradient,
PredefinedType="CONSTANTGRADIENT"
)
end = ifcopenshell.api.alignment.create_layout_segment(file,vertical_alignment,design_parameters)
assert len(vertical_alignment.IsNestedBy[0].RelatedObjects) == 3
x = end[0,3]
y = end[1,3]
z = end[2,3]
assert x == 100.
assert y == 20.
assert z == 0.0
basis_curve = ifcopenshell.api.alignment.get_basis_curve(ali)
assert basis_curve.is_a("IfcCompositeCurve")
curve = ifcopenshell.api.alignment.get_curve(ali)
assert curve.is_a("IfcGradientCurve")
assert len(basis_curve.Segments) == 2
assert len(curve.Segments) == 3
def _test_horizontal_vertical2(file: ifcopenshell.file):
ali = file.by_type("IfcAlignment")[0]
vertical_alignment = ifcopenshell.api.alignment.get_vertical_layout(ali)
assert vertical_alignment == None
vertical_alignment = ifcopenshell.api.alignment.add_vertical_layout(file,ali)
design_parameters = file.createIfcAlignmentVerticalSegment(
StartDistAlong=0.0,
HorizontalLength=50.0,
StartHeight=20.0,
StartGradient = 1./100.,
EndGradient = 1./100.,
PredefinedType="CONSTANTGRADIENT"
)
end = ifcopenshell.api.alignment.create_layout_segment(file,vertical_alignment,design_parameters)
assert len(vertical_alignment.IsNestedBy[0].RelatedObjects) == 2
x = end[0,3]
y = end[1,3]
z = end[2,3]
assert x == 50.
assert y == 20.5
assert z == 0.0
dx = end[0,0]
dy = end[1,0]
gradient = dy/dx
design_parameters = file.createIfcAlignmentVerticalSegment(
StartDistAlong=50.0,
HorizontalLength=50.0,
StartHeight=y.item(),
StartGradient = -gradient,
EndGradient = -gradient,
PredefinedType="CONSTANTGRADIENT"
)
end = ifcopenshell.api.alignment.create_layout_segment(file,vertical_alignment,design_parameters)
assert len(vertical_alignment.IsNestedBy[0].RelatedObjects) == 3
x = end[0,3]
y = end[1,3]
z = end[2,3]
assert x == 100.
assert y == 20.
assert z == 0.0
curve = ifcopenshell.api.alignment.get_curve(ali)
assert curve.is_a("IfcGradientCurve")
assert len(curve.Segments) == 3
def test_append_segment():
file = _test_horizontal()
_test_horizontal_vertical()
_test_horizontal_vertical2(file)
test_append_segment()
@@ -20,10 +20,11 @@ import pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
def test_add_stationing_to_alignment():
def test_distance_along_from_station():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -38,18 +39,15 @@ def test_add_stationing_to_alignment():
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_alignment_by_pi_method(
file, "TestAlignment", coordinates, radii, vpoints, lengths
alignment = ifcopenshell.api.alignment.create_by_pi_method(
file, "TestAlignment", coordinates, radii, vpoints, lengths,start_station=10000.0
)
# test alignment without stationing referent
assert ifcopenshell.api.alignment.distance_along_from_station(file, alignment, 500.0) == pytest.approx(500.0)
# add stationing referent
ifcopenshell.api.alignment.add_stationing_to_alignment(file, alignment, 10000.0)
# Station 138+83.96
assert ifcopenshell.api.alignment.distance_along_from_station(file, alignment, 13883.96) == pytest.approx(3883.96)
# Station 175+25.36
assert ifcopenshell.api.alignment.distance_along_from_station(file, alignment, 17525.36) == pytest.approx(7525.36)
test_distance_along_from_station()
@@ -0,0 +1,54 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
def test_get_alignment():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
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,
)
include_vertical = [False,True,True]
include_cant = [False, False, True]
for i in range(0,3) :
ali = ifcopenshell.api.alignment.create(file,"A1",include_vertical[i],include_cant[i])
assert ali != None
horiz = ifcopenshell.api.alignment.get_horizontal_layout(ali)
vert = ifcopenshell.api.alignment.get_vertical_layout(ali)
cant = ifcopenshell.api.alignment.get_cant_layout(ali)
assert ali == ifcopenshell.api.alignment.get_alignment(horiz)
if include_vertical[i]:
assert ali == ifcopenshell.api.alignment.get_alignment(vert)
if include_cant[i]:
assert ali == ifcopenshell.api.alignment.get_alignment(cant)
@@ -18,15 +18,15 @@
import pytest
# import test.bootstrap
import ifcopenshell.api.alignment
import ifcopenshell.api.context
# class TestGetBasisCurve(test.bootstrap.IFC4X3):
def test_horizontal():
def _test_horizontal():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -36,18 +36,16 @@ def test_horizontal():
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_alignment_by_pi_method(file, "TestAlignment", coordinates, radii)
ifcopenshell.api.alignment.create_geometric_representation(file, alignment)
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment")
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
assert basis_curve.is_a("IfcCompositeCurve")
def test_horizontal_and_vertical():
def _test_horizontal_and_vertical():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -57,14 +55,33 @@ def test_horizontal_and_vertical():
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_alignment_by_pi_method(
file, "TestAlignment", coordinates, radii, vpoints, lengths
alignment = ifcopenshell.api.alignment.create(
file, "TestAlignment", include_vertical=True
)
ifcopenshell.api.alignment.create_geometric_representation(file, alignment)
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
assert basis_curve.is_a("IfcCompositeCurve")
def _test_horizontal_and_vertical_and_cant():
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", include_vertical=True,include_cant=True
)
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
assert basis_curve.is_a("IfcCompositeCurve")
def test_get_basis_curve():
_test_horizontal()
_test_horizontal_and_vertical()
_test_horizontal_and_vertical_and_cant()
@@ -16,17 +16,13 @@
# 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 pytest
# import test.bootstrap
import ifcopenshell.api.alignment
import ifcopenshell.api.context
# class TestGetCurve(test.bootstrap.IFC4X3):
def test_horizontal():
def test_get_curve():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -36,35 +32,14 @@ def test_horizontal():
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_alignment_by_pi_method(file, "TestAlignment", coordinates, radii)
ifcopenshell.api.alignment.create_geometric_representation(file, alignment)
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment",include_vertical=False,include_cant=False)
curve = ifcopenshell.api.alignment.get_curve(alignment)
assert curve.is_a("IfcCompositeCurve")
def test_horizontal_and_vertical():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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_alignment_by_pi_method(
file, "TestAlignment", coordinates, radii, vpoints, lengths
)
ifcopenshell.api.alignment.create_geometric_representation(file, alignment)
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment",include_vertical=True,include_cant=False)
curve = ifcopenshell.api.alignment.get_curve(alignment)
assert curve.is_a("IfcGradientCurve")
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment",include_vertical=True,include_cant=True)
curve = ifcopenshell.api.alignment.get_curve(alignment)
assert curve.is_a("IfcSegmentedReferenceCurve")
@@ -0,0 +1,57 @@
# 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
def test_get_layout_curve():
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",include_vertical=False,include_cant=False)
layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
assert curve.is_a("IfcCompositeCurve")
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment",include_vertical=True,include_cant=False)
layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
assert curve.is_a("IfcCompositeCurve")
layout = ifcopenshell.api.alignment.get_vertical_layout(alignment)
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
assert curve.is_a("IfcGradientCurve")
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment",include_vertical=True,include_cant=True)
layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
assert curve.is_a("IfcCompositeCurve")
layout = ifcopenshell.api.alignment.get_vertical_layout(alignment)
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
assert curve.is_a("IfcGradientCurve")
layout = ifcopenshell.api.alignment.get_cant_layout(alignment)
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
assert curve.is_a("IfcSegmentedReferenceCurve")
@@ -16,18 +16,15 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.alignment.has_zero_length_segment
import ifcopenshell.api.alignment.remove_zero_length_segment
import ifcopenshell.api.context
import ifcopenshell.guid
import ifcopenshell.api.nest
def _test_business_definition():
def _test_horizontal():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -37,41 +34,15 @@ def _test_business_definition():
parent=geometric_representation_context,
)
horizontal = file.createIfcAlignmentHorizontal("Horizontal Alignment")
design_parameters = file.createIfcAlignmentHorizontalSegment(
StartPoint=file.createIfcCartesianPoint((0.0, 0.0)),
StartDirection=0.0,
SegmentLength=100.0,
PredefinedType="LINE",
)
segment = file.createIfcAlignmentSegment(GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters)
ifcopenshell.api.nest.assign_object(
file,
related_objects=[
segment,
],
relating_object=horizontal,
)
assert False == ifcopenshell.api.alignment.has_zero_length_segment(horizontal)
ifcopenshell.api.alignment.add_zero_length_segment(file, horizontal)
assert len(horizontal.IsNestedBy[0].RelatedObjects) == 2
alignment = ifcopenshell.api.alignment.create(file,"TestAlignment")
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
assert True == ifcopenshell.api.alignment.has_zero_length_segment(horizontal)
zero_length_segment = ifcopenshell.api.alignment.remove_zero_length_segment(file, horizontal)
assert len(horizontal.IsNestedBy[0].RelatedObjects) == 1
assert False == ifcopenshell.api.alignment.has_zero_length_segment(horizontal)
ifcopenshell.api.alignment.add_segment_to_layout(file, horizontal, zero_length_segment)
assert len(horizontal.IsNestedBy[0].RelatedObjects) == 2
assert True == ifcopenshell.api.alignment.has_zero_length_segment(horizontal)
def _test_geometric_definition():
def _test_horizontal_vertical():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -80,44 +51,39 @@ def _test_geometric_definition():
target_view="MODEL_VIEW",
parent=geometric_representation_context,
)
circular_arc = file.createIfcCurveSegment(
Placement=file.createIfcAxis2Placement2d(
file.createIfcCartesianPoint((4084.115884, 3889.462938)),
file.createIfcDirection((0.224530986099614, 0.974466949814685)),
),
SegmentStart=file.createIfcLengthMeasure(0.0),
SegmentLength=file.createIfcLengthMeasure(-1848.115835),
ParentCurve=file.createIfcCircle(
Position=file.createIfcAxis2Placement2d(
file.createIfcCartesianPoint((0.0, 0.0)), file.createIfcDirection((1.0, 0.0))
),
Radius=1250.0,
),
alignment = ifcopenshell.api.alignment.create(file,"TestAlignment",include_vertical=True)
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
assert True == ifcopenshell.api.alignment.has_zero_length_segment(horizontal)
vertical = ifcopenshell.api.alignment.get_vertical_layout(alignment)
assert True == ifcopenshell.api.alignment.has_zero_length_segment(vertical)
def _test_horizontal_vertical_cant():
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,
)
composite_curve = file.createIfcCompositeCurve(Segments=(circular_arc,), SelfIntersect=False)
assert False == ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
ifcopenshell.api.alignment.add_zero_length_segment(file, composite_curve)
assert True == ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
assert len(composite_curve.Segments) == 2
zero_length_segment = ifcopenshell.api.alignment.remove_zero_length_segment(file, composite_curve)
assert len(composite_curve.Segments) == 1
assert False == ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
ifcopenshell.api.alignment.add_segment_to_curve(file, zero_length_segment, composite_curve)
assert len(composite_curve.Segments) == 2
assert True == ifcopenshell.api.alignment.has_zero_length_segment(composite_curve)
segment = composite_curve.Segments[-1]
assert segment.Placement.Location.Coordinates == (5469.394535876198, 4847.567078630914)
assert segment.Placement.RefDirection.DirectionRatios == (0.9910142986043448, -0.13375597168627318)
alignment = ifcopenshell.api.alignment.create(file,"TestAlignment",include_vertical=True,include_cant=True)
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
assert True == ifcopenshell.api.alignment.has_zero_length_segment(horizontal)
vertical = ifcopenshell.api.alignment.get_vertical_layout(alignment)
assert True == ifcopenshell.api.alignment.has_zero_length_segment(vertical)
cant = ifcopenshell.api.alignment.get_cant_layout(alignment)
assert True == ifcopenshell.api.alignment.has_zero_length_segment(cant)
def test_has_zero_length_segment():
_test_business_definition()
_test_geometric_definition()
_test_horizontal()
_test_horizontal_vertical()
_test_horizontal_vertical_cant()
test_has_zero_length_segment()
@@ -0,0 +1,55 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
# other test cases cover the typical vertical by PI method (test_create_alignment_by_pi_method)
# this test will focus on the edge cases of no initial tangent run, no final tangent run, and
# compound curve (no tangent between curves)
def test_horizontal_layout_by_pi_method():
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 = [(838.760, 224.745), (965.926, 258.819), (1226.296, 258.819), (1350.817, 291.415)]
radii = [(1000.0), (1000.0)]
alignment = ifcopenshell.api.alignment.create_by_pi_method(file, "TestAlignment", coordinates, radii)
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
assert len(alignment.IsNestedBy) == 1 # one nest
assert len(alignment.IsNestedBy[0].RelatedObjects) == 2 # nesting IfcReferent, IfcAlignmentHorizontal
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcReferent")
assert alignment.IsNestedBy[0].RelatedObjects[1].is_a("IfcAlignmentHorizontal")
assert (
len(alignment.IsNestedBy[0].RelatedObjects[1].IsNestedBy) == 1
) # nesting of segments beneath IfcAlignmentHorizontal
assert len(alignment.IsNestedBy[0].RelatedObjects[1].IsNestedBy[0].RelatedObjects) == 3 # segments in horizontal layout
test_horizontal_layout_by_pi_method()
@@ -19,7 +19,7 @@
import pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
from ifcopenshell.api.alignment._map_alignment_cant_segment import _map_alignment_cant_segment
def _BlossCurve_100_0_300_1000_1_Meter(file):
design_parameters = file.createIfcAlignmentCantSegment(
@@ -36,7 +36,7 @@ def _BlossCurve_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -68,7 +68,7 @@ def _BlossCurve_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -100,7 +100,7 @@ def _BlossCurve_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -132,7 +132,7 @@ def _BlossCurve_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -164,7 +164,7 @@ def _BlossCurve_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -196,7 +196,7 @@ def _BlossCurve_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -228,7 +228,7 @@ def _BlossCurve_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -260,7 +260,7 @@ def _BlossCurve_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -292,7 +292,7 @@ def _ConstantCant_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -321,7 +321,7 @@ def _ConstantCant_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -350,7 +350,7 @@ def _ConstantCant_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -379,7 +379,7 @@ def _ConstantCant_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -408,7 +408,7 @@ def _ConstantCant_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -437,7 +437,7 @@ def _ConstantCant_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -466,7 +466,7 @@ def _ConstantCant_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -495,7 +495,7 @@ def _ConstantCant_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -524,7 +524,7 @@ def _CosineCurve_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -554,7 +554,7 @@ def _CosineCurve_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -584,7 +584,7 @@ def _CosineCurve_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -614,7 +614,7 @@ def _CosineCurve_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -644,7 +644,7 @@ def _CosineCurve_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -674,7 +674,7 @@ def _CosineCurve_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -704,7 +704,7 @@ def _CosineCurve_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -734,7 +734,7 @@ def _CosineCurve_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -764,7 +764,7 @@ def _HelmertCurve_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -809,7 +809,7 @@ def _HelmertCurve_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -854,7 +854,7 @@ def _HelmertCurve_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -899,7 +899,7 @@ def _HelmertCurve_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -944,7 +944,7 @@ def _HelmertCurve_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -989,7 +989,7 @@ def _HelmertCurve_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1034,7 +1034,7 @@ def _HelmertCurve_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1079,7 +1079,7 @@ def _HelmertCurve_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1124,7 +1124,7 @@ def _LinearTransition_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1155,7 +1155,7 @@ def _LinearTransition_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1186,7 +1186,7 @@ def _LinearTransition_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1217,7 +1217,7 @@ def _LinearTransition_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1248,7 +1248,7 @@ def _LinearTransition_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1279,7 +1279,7 @@ def _LinearTransition_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1310,7 +1310,7 @@ def _LinearTransition_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1341,7 +1341,7 @@ def _LinearTransition_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1372,7 +1372,7 @@ def _SineCurve_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1403,7 +1403,7 @@ def _SineCurve_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1434,7 +1434,7 @@ def _SineCurve_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1465,7 +1465,7 @@ def _SineCurve_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1496,7 +1496,7 @@ def _SineCurve_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1527,7 +1527,7 @@ def _SineCurve_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1558,7 +1558,7 @@ def _SineCurve_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1589,7 +1589,7 @@ def _SineCurve_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segments = _map_alignment_cant_segment(file, alignment_segment, 1.5)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -21,7 +21,7 @@
import pytest
import ifcopenshell.api.alignment
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import __map_alignment_horizontal_segment
def _BlossCurve_100_0_300_1000_1_Meter(file):
design_parameters = file.createIfcAlignmentHorizontalSegment(
@@ -37,7 +37,7 @@ def _BlossCurve_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -68,7 +68,7 @@ def _BlossCurve_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -99,7 +99,7 @@ def _BlossCurve_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -130,7 +130,7 @@ def _BlossCurve_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -161,7 +161,7 @@ def _BlossCurve_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -192,7 +192,7 @@ def _BlossCurve_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -223,7 +223,7 @@ def _BlossCurve_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -254,7 +254,7 @@ def _BlossCurve_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -285,7 +285,7 @@ def _CircularArc_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -313,7 +313,7 @@ def _CircularArc_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -341,7 +341,7 @@ def _CircularArc_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -369,7 +369,7 @@ def _CircularArc_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -397,7 +397,7 @@ def _CircularArc_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -425,7 +425,7 @@ def _CircularArc_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -453,7 +453,7 @@ def _CircularArc_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -481,7 +481,7 @@ def _CircularArc_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -509,7 +509,7 @@ def _Clothoid_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -537,7 +537,7 @@ def _Clothoid_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -565,7 +565,7 @@ def _Clothoid_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -593,7 +593,7 @@ def _Clothoid_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -621,7 +621,7 @@ def _Clothoid_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -649,7 +649,7 @@ def _Clothoid_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -677,7 +677,7 @@ def _Clothoid_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -705,7 +705,7 @@ def _Clothoid_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -733,7 +733,7 @@ def _CosineCurve_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -762,7 +762,7 @@ def _CosineCurve_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -791,7 +791,7 @@ def _CosineCurve_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -820,7 +820,7 @@ def _CosineCurve_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -849,7 +849,7 @@ def _CosineCurve_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -878,7 +878,7 @@ def _CosineCurve_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -907,7 +907,7 @@ def _CosineCurve_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -936,7 +936,7 @@ def _CosineCurve_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -965,7 +965,7 @@ def _Cubic_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -994,7 +994,7 @@ def _Cubic_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1023,7 +1023,7 @@ def _Cubic_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1052,7 +1052,7 @@ def _Cubic_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1081,7 +1081,7 @@ def _Cubic_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1110,7 +1110,7 @@ def _Cubic_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1139,7 +1139,7 @@ def _Cubic_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1168,7 +1168,7 @@ def _Cubic_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1197,7 +1197,7 @@ def _HelmertCurve_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1245,7 +1245,7 @@ def _HelmertCurve_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1293,7 +1293,7 @@ def _HelmertCurve_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1341,7 +1341,7 @@ def _HelmertCurve_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1389,7 +1389,7 @@ def _HelmertCurve_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1437,7 +1437,7 @@ def _HelmertCurve_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1485,7 +1485,7 @@ def _HelmertCurve_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1533,7 +1533,7 @@ def _HelmertCurve_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1581,7 +1581,7 @@ def _Line_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1609,7 +1609,7 @@ def _Line_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1637,7 +1637,7 @@ def _Line_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1665,7 +1665,7 @@ def _Line_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1693,7 +1693,7 @@ def _Line_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1721,7 +1721,7 @@ def _Line_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1749,7 +1749,7 @@ def _Line_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1777,7 +1777,7 @@ def _Line_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1805,7 +1805,7 @@ def _SineCurve_100_0_300_1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1835,7 +1835,7 @@ def _SineCurve_100_0__300__1000_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1865,7 +1865,7 @@ def _SineCurve_100_0_300_inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1895,7 +1895,7 @@ def _SineCurve_100_0__300__inf_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1925,7 +1925,7 @@ def _SineCurve_100_0_1000_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1955,7 +1955,7 @@ def _SineCurve_100_0__1000__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -1985,7 +1985,7 @@ def _SineCurve_100_0_inf_300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -2015,7 +2015,7 @@ def _SineCurve_100_0__inf__300_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_horizontal_segment(file, alignment_segment)
mapped_segments = __map_alignment_horizontal_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert "DISCONTINUOUS" == mapped_segment.Transition
@@ -21,7 +21,7 @@
import pytest
import ifcopenshell.api.alignment
from ifcopenshell.api.alignment._map_alignment_vertical_segment import _map_alignment_vertical_segment
def _CircularArc_100_0_10_0_0_0_0_5_1_Meter(file):
design_parameters = file.createIfcAlignmentVerticalSegment(
@@ -37,7 +37,7 @@ def _CircularArc_100_0_10_0_0_0_0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -66,7 +66,7 @@ def _CircularArc_100_0_10_0_0_0__0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -97,7 +97,7 @@ def _CircularArc_100_0_10_0_0_5_0_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -128,7 +128,7 @@ def _CircularArc_100_0_10_0__0_5_0_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -159,7 +159,7 @@ def _CircularArc_100_0_10_0_0_5_1_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -192,7 +192,7 @@ def _CircularArc_100_0_10_0__0_5__1_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -225,7 +225,7 @@ def _CircularArc_100_0_10_0_1_0_0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -258,7 +258,7 @@ def _CircularArc_100_0_10_0__1_0__0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -291,7 +291,7 @@ def _ConstantGradient_100_0_10_0_0_0_0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -320,7 +320,7 @@ def _ConstantGradient_100_0_10_0_0_0__0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -349,7 +349,7 @@ def _ConstantGradient_100_0_10_0_0_5_0_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -380,7 +380,7 @@ def _ConstantGradient_100_0_10_0__0_5_0_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -411,7 +411,7 @@ def _ConstantGradient_100_0_10_0_0_5_1_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -442,7 +442,7 @@ def _ConstantGradient_100_0_10_0__0_5__1_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -473,7 +473,7 @@ def _ConstantGradient_100_0_10_0_1_0_0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -504,7 +504,7 @@ def _ConstantGradient_100_0_10_0__1_0__0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -535,7 +535,7 @@ def _ParabolicArc_100_0_10_0_0_0_0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -564,7 +564,7 @@ def _ParabolicArc_100_0_10_0_0_0__0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -593,7 +593,7 @@ def _ParabolicArc_100_0_10_0_0_5_0_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -624,7 +624,7 @@ def _ParabolicArc_100_0_10_0__0_5_0_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -655,7 +655,7 @@ def _ParabolicArc_100_0_10_0_0_5_1_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -686,7 +686,7 @@ def _ParabolicArc_100_0_10_0__0_5__1_0_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -717,7 +717,7 @@ def _ParabolicArc_100_0_10_0_1_0_0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -748,7 +748,7 @@ def _ParabolicArc_100_0_10_0__1_0__0_5_1_Meter(file):
GlobalId=ifcopenshell.guid.new(), DesignParameters=design_parameters
)
mapped_segments = ifcopenshell.api.alignment.map_alignment_vertical_segment(file, alignment_segment)
mapped_segments = _map_alignment_vertical_segment(file, alignment_segment)
mapped_segment = mapped_segments[0]
assert len(mapped_segments) == 2
assert mapped_segments[1] == None
@@ -23,7 +23,9 @@ import ifcopenshell.api.context
def test_name_segments():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -38,7 +40,7 @@ def test_name_segments():
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_alignment_by_pi_method(
alignment = ifcopenshell.api.alignment.create_by_pi_method(
file, "TestAlignment", coordinates, radii, vpoints, lengths
)
@@ -0,0 +1,124 @@
# 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 pytest
from pytest import fixture
import ifcopenshell.api.alignment
import ifcopenshell.api.context
@pytest.fixture(scope="module")
def default_names_alignment():
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)
yield alignment
def _hcallback(prev_segment, segment):
if (prev_segment is None) and (segment is not None):
label = "A"
elif (prev_segment is not None) and (segment is None):
label = "Z"
else:
label = "Q"
return label
def _vcallback(prev_segment, segment):
if (prev_segment is None) and (segment is not None):
label = "a"
elif (prev_segment is not None) and (segment is None):
label = "z"
else:
label = "q"
return label
@pytest.fixture(scope="module")
def callback_alignment():
ifcopenshell.api.alignment.register_referent_name_callback(_hcallback, _vcallback, None)
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)
yield alignment
def test_with_default_names(default_names_alignment):
hlayout = ifcopenshell.api.alignment.get_horizontal_layout(default_names_alignment)
assert "P.O.B." in hlayout.IsNestedBy[0].RelatedObjects[0].IsNestedBy[0].RelatedObjects[0].Name
assert "P.C." in hlayout.IsNestedBy[0].RelatedObjects[1].IsNestedBy[0].RelatedObjects[0].Name
assert "P.T." in hlayout.IsNestedBy[0].RelatedObjects[2].IsNestedBy[0].RelatedObjects[0].Name
assert "P.O.E." in hlayout.IsNestedBy[0].RelatedObjects[-1].IsNestedBy[0].RelatedObjects[0].Name
vlayout = ifcopenshell.api.alignment.get_vertical_layout(default_names_alignment)
assert "V.P.O.B." in vlayout.IsNestedBy[0].RelatedObjects[0].IsNestedBy[0].RelatedObjects[0].Name
assert "P.V.C." in vlayout.IsNestedBy[0].RelatedObjects[1].IsNestedBy[0].RelatedObjects[0].Name
assert "P.V.T." in vlayout.IsNestedBy[0].RelatedObjects[2].IsNestedBy[0].RelatedObjects[0].Name
assert "V.P.O.E." in vlayout.IsNestedBy[0].RelatedObjects[-1].IsNestedBy[0].RelatedObjects[0].Name
def test_with_callbacks(callback_alignment):
hlayout = ifcopenshell.api.alignment.get_horizontal_layout(callback_alignment)
assert "A" in hlayout.IsNestedBy[0].RelatedObjects[0].IsNestedBy[0].RelatedObjects[0].Name
assert "Q" in hlayout.IsNestedBy[0].RelatedObjects[1].IsNestedBy[0].RelatedObjects[0].Name
assert "Q" in hlayout.IsNestedBy[0].RelatedObjects[2].IsNestedBy[0].RelatedObjects[0].Name
assert "Z" in hlayout.IsNestedBy[0].RelatedObjects[-1].IsNestedBy[0].RelatedObjects[0].Name
vlayout = ifcopenshell.api.alignment.get_vertical_layout(callback_alignment)
assert "a" in vlayout.IsNestedBy[0].RelatedObjects[0].IsNestedBy[0].RelatedObjects[0].Name
assert "q" in vlayout.IsNestedBy[0].RelatedObjects[1].IsNestedBy[0].RelatedObjects[0].Name
assert "q" in vlayout.IsNestedBy[0].RelatedObjects[2].IsNestedBy[0].RelatedObjects[0].Name
assert "z" in vlayout.IsNestedBy[0].RelatedObjects[-1].IsNestedBy[0].RelatedObjects[0].Name
@@ -17,13 +17,15 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import ifcopenshell.api.alignment
from ifcopenshell.api.alignment._update_curve_segment_transition_code import _update_curve_segment_transition_code
import ifcopenshell.api.context
def _test1():
file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(Name="Test")
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,
@@ -79,7 +81,7 @@ def _test1():
composite_curve = file.createIfcCompositeCurve(Segments=(circular_arc, line), SelfIntersect=False)
ifcopenshell.api.alignment.update_curve_segment_transition_code(circular_arc, line)
_update_curve_segment_transition_code(circular_arc, line)
assert circular_arc.Transition == "CONTSAMEGRADIENT"
@@ -94,6 +96,7 @@ def _test2():
target_view="MODEL_VIEW",
parent=geometric_representation_context,
)
# 30=IFCCARTESIANPOINT((0.,0.));
# 31=IFCALIGNMENTHORIZONTALSEGMENT($,$,#30,0.523598775598299,0.,0.,27.8843513637174,$,.LINE.);
# 32=IFCALIGNMENTSEGMENT('3$jiMaOgfAoujgvRyMLw0X',$,'H1',$,$,#111,#113,#31);
@@ -220,29 +223,23 @@ def _test2():
),
)
composite_curve = file.createIfcCompositeCurve(Segments=[], SelfIntersect=False)
composite_curve = file.createIfcCompositeCurve(Segments=[line1,clothoid1,circular_arc,clothoid2,line2], SelfIntersect=False)
# add_segment_to_curve calls update_curve_segment_transition_code
ifcopenshell.api.alignment.add_segment_to_curve(file, line1, composite_curve)
assert line1.Transition == "DISCONTINUOUS"
ifcopenshell.api.alignment.add_segment_to_curve(file, clothoid1, composite_curve)
_update_curve_segment_transition_code(line1,clothoid1)
assert line1.Transition == "CONTSAMEGRADIENTSAMECURVATURE"
assert clothoid1.Transition == "DISCONTINUOUS"
ifcopenshell.api.alignment.add_segment_to_curve(file, circular_arc, composite_curve)
_update_curve_segment_transition_code(clothoid1,circular_arc)
assert clothoid1.Transition == "CONTSAMEGRADIENTSAMECURVATURE"
assert circular_arc.Transition == "DISCONTINUOUS"
ifcopenshell.api.alignment.add_segment_to_curve(file, clothoid2, composite_curve)
_update_curve_segment_transition_code(circular_arc,clothoid2)
assert circular_arc.Transition == "CONTSAMEGRADIENTSAMECURVATURE"
assert clothoid2.Transition == "DISCONTINUOUS"
ifcopenshell.api.alignment.add_segment_to_curve(file, line2, composite_curve)
_update_curve_segment_transition_code(clothoid2,line2)
assert clothoid2.Transition == "CONTSAMEGRADIENTSAMECURVATURE"
assert line2.Transition == "DISCONTINUOUS"
def test_update_curve_segment_transition_code():
_test1()
_test2()
test_update_curve_segment_transition_code()
@@ -0,0 +1,71 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
# other test cases cover the typical vertical by PI method (test_create_alignment_by_pi_method)
# this test will focus on the edge cases of no initial gradient, no final gradient, and
# compound vertical curve (no gradient between curves)
def test_vertical_layout_by_pi_method():
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,
)
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", include_vertical=True)
hlayout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
segment1 = file.createIfcAlignmentHorizontalSegment(
StartPoint=file.createIfcCartesianPoint(Coordinates=((0.,0.))), # Actual coordinate unknown
StartDirection=0.0,
StartRadiusOfCurvature=0.0,
EndRadiusOfCurvature=0.0,
SegmentLength=10000.0,
PredefinedType = "LINE"
)
ifcopenshell.api.alignment.create_layout_segment(file,hlayout,segment1)
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)]
vlayout = ifcopenshell.api.alignment.get_vertical_layout(alignment)
ifcopenshell.api.alignment.layout_vertical_alignment_by_pi_method(file,vlayout,vpoints,lengths)
assert len(alignment.IsDecomposedBy) == 0 # no child alignments
assert len(alignment.IsNestedBy) == 1 # one nest
assert len(alignment.IsNestedBy[0].RelatedObjects) == 3 # nesting IfcReferent, IfcAlignmentHorizontal, IfcAlignmentVertical
assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcReferent")
assert alignment.IsNestedBy[0].RelatedObjects[1].is_a("IfcAlignmentHorizontal")
assert alignment.IsNestedBy[0].RelatedObjects[2].is_a("IfcAlignmentVertical")
assert (
len(alignment.IsNestedBy[0].RelatedObjects[1].IsNestedBy) == 1
) # nesting of segments beneath IfcAlignmentHorizontal
assert len(alignment.IsNestedBy[0].RelatedObjects[1].IsNestedBy[0].RelatedObjects) == 2 # segments in horizontal layout
assert len(alignment.IsNestedBy[0].RelatedObjects[2].IsNestedBy[0].RelatedObjects) == 3 # segments in vertical layout
test_vertical_layout_by_pi_method()
@@ -0,0 +1,45 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.cogo
def test_add_survey_point():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
project = file.createIfcProject(Name="Test")
site = file.createIfcSite(GlobalId=ifcopenshell.guid.new(),Name="MySite")
ifcopenshell.api.aggregate.assign_object(file,relating_object=project,products=[site])
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="Annotation",
target_view="MODEL_VIEW",
parent=geometric_representation_context,
)
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint((50.0,10.0)))
assert annotation
assert annotation.PredefinedType == "SURVEY"
assert annotation.Representation.Representations[0].RepresentationIdentifier == "Annotation"
assert annotation.Representation.Representations[0].RepresentationType == "Point"
assert annotation.Representation.Representations[0].Items[0].Coordinates == pytest.approx((50.0,10.0))
@@ -0,0 +1,48 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.cogo
def test_assign_survey_point():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
project = file.createIfcProject(Name="Test")
site = file.createIfcSite(GlobalId=ifcopenshell.guid.new(),Name="MySite")
ifcopenshell.api.aggregate.assign_object(file,relating_object=project,products=[site])
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="Annotation",
target_view="MODEL_VIEW",
parent=geometric_representation_context,
)
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint((50.0,10.0)))
assert annotation
assert annotation.PredefinedType == "SURVEY"
assert annotation.Representation.Representations[0].RepresentationIdentifier == "Annotation"
assert annotation.Representation.Representations[0].RepresentationType == "Point"
assert annotation.Representation.Representations[0].Items[0].Coordinates == pytest.approx((50.0,10.0))
ifcopenshell.api.cogo.assign_survey_point(annotation,file.createIfcCartesianPoint((20.0,30.0,40.0)))
assert annotation.Representation.Representations[0].Items[0].Coordinates == pytest.approx((20.0,30.0,40.0))
@@ -0,0 +1,73 @@
# 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 pytest
import ifcopenshell.api.cogo
import math
def test_bearing2dd():
assert 44.743888875 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("N 45 15 22.5 E"))
assert 135.256111125 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("N 45 15 22.5 W"))
assert 224.743888875 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("S 45 15 22.5 W"))
assert 315.256111125 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("S 45 15 22.5 E"))
assert 44.743888875 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("n 45 15 22.5 e"))
assert 135.256111125 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("n 45 15 22.5 w"))
assert 224.743888875 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("s 45 15 22.5 w"))
assert 315.256111125 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("s 45 15 22.5 e"))
assert 0.0 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("N 90 E"))
assert 0.0 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("S 90 E"))
assert 180. == pytest.approx(ifcopenshell.api.cogo.bearing2dd("N 90 W"))
assert 180. == pytest.approx(ifcopenshell.api.cogo.bearing2dd("S 90 W"))
assert 120. == pytest.approx(ifcopenshell.api.cogo.bearing2dd("N 30 W"))
assert 120.16666666666667 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("N 30 10 W"))
assert 89.999722222222228 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("N 00 00 1 E"))
assert 89.99972222222222 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("N 0 0 1 E"))
assert 89.99972222222222 == pytest.approx(ifcopenshell.api.cogo.bearing2dd("N 00 00 1.0 E"))
with pytest.raises(ValueError,match="Invalid bearing string"):
ifcopenshell.api.cogo.bearing2dd("Bad String")
with pytest.raises(ValueError,match="Invalid bearing string"):
ifcopenshell.api.cogo.bearing2dd("Very Bad String")
with pytest.raises(ValueError,match="Invalid bearing string"):
ifcopenshell.api.cogo.bearing2dd("N 100 15 22.5 E")
with pytest.raises(ValueError,match="Invalid bearing string"):
ifcopenshell.api.cogo.bearing2dd("N -45 15 22.5 E")
with pytest.raises(ValueError,match="Invalid bearing string"):
ifcopenshell.api.cogo.bearing2dd("N 45 -15 22.5 E")
with pytest.raises(ValueError,match="Invalid bearing string"):
ifcopenshell.api.cogo.bearing2dd("N 45 88 22.5 E")
with pytest.raises(ValueError,match="Invalid bearing string"):
ifcopenshell.api.cogo.bearing2dd("N 45 15 -22.5 E")
with pytest.raises(ValueError,match="Invalid bearing string"):
ifcopenshell.api.cogo.bearing2dd("N 45 15 99.5 E")
test_bearing2dd()
@@ -0,0 +1,48 @@
# 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 pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.cogo
def test_edit_survey_point():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
project = file.createIfcProject(Name="Test")
site = file.createIfcSite(GlobalId=ifcopenshell.guid.new(),Name="MySite")
ifcopenshell.api.aggregate.assign_object(file,relating_object=project,products=[site])
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="Annotation",
target_view="MODEL_VIEW",
parent=geometric_representation_context,
)
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint((50.0,10.0)))
assert annotation
assert annotation.PredefinedType == "SURVEY"
assert annotation.Representation.Representations[0].RepresentationIdentifier == "Annotation"
assert annotation.Representation.Representations[0].RepresentationType == "Point"
assert annotation.Representation.Representations[0].Items[0].Coordinates == pytest.approx((50.0,10.0))
ifcopenshell.api.cogo.edit_survey_point(annotation,20.0,30.0)
assert annotation.Representation.Representations[0].Items[0].Coordinates == pytest.approx((20.0,30.0))
@@ -16,31 +16,67 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.stationing as sta
def _test_si_stations():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(),Name="Test")
length = ifcopenshell.api.unit.add_si_unit(file,unit_type="LENGTHUNIT") # meter
ifcopenshell.api.unit.assign_unit(file,units=[length])
s = sta.station_as_string(file,0.0)
assert s == "0+000.000"
s = sta.station_as_string(file,100.00)
assert s == "0+100.000"
s = sta.station_as_string(file,-100.00)
assert s == "-0+100.000"
s = sta.station_as_string(file,123456.789)
assert s == "123+456.789"
s = sta.station_as_string(file,-123456.789)
assert s == "-123+456.789"
def _test_si_stations_millimeter():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(),Name="Test")
ifcopenshell.api.unit.assign_unit(file)
s = sta.station_as_string(file,100.00)
assert s == "0+000.100"
s = sta.station_as_string(file,1000.00)
assert s == "0+001.000"
def _test_us_stations():
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])
s = sta.station_as_string(file,0.0)
assert s == "0+00.00"
s = sta.station_as_string(file,100.00)
assert s == "1+00.00"
s = sta.station_as_string(file,-100.00)
assert s == "-1+00.00"
s = sta.station_as_string(file,123456.789)
assert s == "1234+56.79"
s = sta.station_as_string(file,-123456.789)
assert s == "-1234+56.79"
def test_station_as_string():
# test with a bunch of "random" station values
s = sta.station_as_string(0.0)
assert s == "0+000.000"
_test_si_stations()
_test_si_stations_millimeter()
_test_us_stations()
s = sta.station_as_string(0.0, 2, 2)
assert s == "0+00.00"
s = sta.station_as_string(0.0, 2)
assert s == "0+00.000"
s = sta.station_as_string(100.00)
assert s == "0+100.000"
s = sta.station_as_string(-100.00)
assert s == "-0+100.000"
s = sta.station_as_string(123456.789, 2, 2)
assert s == "1234+56.79"
s = sta.station_as_string(-123456.789, 2, 2)
assert s == "-1234+56.79"
s = sta.station_as_string(123456.789, 3, 4)
assert s == "123+456.7890"
test_station_as_string()