Alignments can now be created with or without geometric representations

This commit is contained in:
Richard Brice
2025-08-25 16:05:47 -07:00
parent 8903d6a85c
commit 9331e89d15
6 changed files with 248 additions and 169 deletions
@@ -60,6 +60,7 @@ def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, seg
# swap the last two segments # swap the last two segments
ifcopenshell.api.nest.reorder_nesting(file, segment, -1, -1) ifcopenshell.api.nest.reorder_nesting(file, segment, -1, -1)
if curve:
# add the new segment to the geometric representation curve # add the new segment to the geometric representation curve
_add_segment_to_curve(file, segment, curve) _add_segment_to_curve(file, segment, curve)
@@ -33,27 +33,31 @@ def create(
name: str, name: str,
include_vertical: bool = False, include_vertical: bool = False,
include_cant: bool = False, include_cant: bool = False,
include_geometry: bool = True,
start_station: float = 0.0, start_station: float = 0.0,
) -> entity_instance: ) -> entity_instance:
""" """
Creates a new IfcAlignment with an IfcRelNests nesting an IfcReferent (for stationing) and IfcAlignmentHorizontal. The nest relationship can optionally Creates a new alignment with a horizontal layout. Optionally, vertical and cant layouts can be created as well.
include IfcAlignmentVertical and IfcAlignmentCant. Geometric representations for the alignment layouts (IfcCompositeCurve, The geometric representations are created as well, unless they are explicitly excluded.
IfcGradientCurve, IfcSegmentedReferenceCurve) are created as well. Zero length segments are added at the end of the layouts and geometric representations.
The alignment is automatically aggreated to the project if it exists.
Zero length segments are added at the end. Use get_horizontal_layout(alignment), get_vertical_layout(alignment) and get_cant_layout(alignment) to get the
corresponding IfcAlignmentHorizontal, IfcAlignmentVertical, and IfcAlignmentCant layout entities.
The IfcAlignment is aggreated to IfcProject If the alignment has Viennese Bend transition curves, create the segments in the cant layout before the horizontal layout using create_layout_segment().
The horizontal geometry in the Viennese Bend transition curves depends on the Viennese Bend cant parameters. create_layout_segment() automatically creates
the geometric representation from the semantic definition. The horizontal segment geometric representation will fail if the cant segment is not defined.
Use get_horizontal_layout(alignment) to get the IfcAlignmentHorizontal layout. If geometric representations are created, the alignment stationing referent is also created using the start_station value. IfcReferent.ObjectPlacement
is required for linear positiion elements and IfcLinearPlacement is defined relative to alignment curve geometry.
If the alignment has Viennese Bend transition curves, create the cant layout before the horizontal layout. This is because the horizontal layout
in the Viennese Bend transition curves depends on the Viennese Bend cant parameters.
:param file: :param file:
:param name: name assigned to IfcAlignment.Name :param name: name assigned to IfcAlignment.Name
:param include_vertical: If True, IfcAlignmentVertical and IfcGradientCurve are created :param include_vertical: If True, IfcAlignmentVertical is created. IfcGradientCurve is created if include_geometry is True
:param include_cant: If True, IfcAlignmentCant and IfcSegmentedReferenceCurve are created :param include_cant: If True, IfcAlignmentCant is created. IfcSegmentedReferenceCurve is created if include_geometry is True
:param start_station: station value at the start of the alignment :param include_geometry: If True, the geometric representations are added
:param start_station: station value at the start of the alignment.
:return: Returns an IfcAlignment :return: Returns an IfcAlignment
""" """
alignment = file.createIfcAlignment( alignment = file.createIfcAlignment(
@@ -73,16 +77,19 @@ def create(
ifcopenshell.api.nest.assign_object(file, related_objects=alignment_layouts, relating_object=alignment) ifcopenshell.api.nest.assign_object(file, related_objects=alignment_layouts, relating_object=alignment)
if include_geometry:
_create_geometric_representation(file, alignment) _create_geometric_representation(file, alignment)
for layout in alignment_layouts: for layout in alignment_layouts:
_add_zero_length_segment(file, layout) _add_zero_length_segment(file, layout)
if include_geometry:
# define stationing # define stationing
name = ifcopenshell.util.alignment.station_as_string(file, start_station) name = ifcopenshell.util.alignment.station_as_string(file, start_station)
referent = ifcopenshell.api.alignment.add_stationing_referent(file, alignment, 0.0, start_station, name) referent = ifcopenshell.api.alignment.add_stationing_referent(file, alignment, 0.0, start_station, name)
ifcopenshell.api.nest.reorder_nesting(file, referent, -1, 0) ifcopenshell.api.nest.reorder_nesting(file, referent, -1, 0)
# IFC 4.1.4.1.1 Alignment Aggregation To Project # IFC 4.1.4.1.1 Alignment Aggregation To Project
project = file.by_type("IfcProject")[0] project = file.by_type("IfcProject")[0]
if project: if project:
@@ -23,20 +23,21 @@ from ifcopenshell import entity_instance
import math import math
from ifcopenshell import ifcopenshell_wrapper from ifcopenshell import ifcopenshell_wrapper
import numpy as np import numpy as np
from typing import Union
from ifcopenshell.api.alignment._add_segment_to_layout import _add_segment_to_layout from ifcopenshell.api.alignment._add_segment_to_layout import _add_segment_to_layout
def create_layout_segment( def create_layout_segment(
file: ifcopenshell.file, layout: entity_instance, design_parameters: entity_instance file: ifcopenshell.file, layout: entity_instance, design_parameters: entity_instance
) -> np.array: ) -> Union[np.array, None]:
""" """
Creates a new IfcAlignmentSegment using the IfcAlignmentParameterSegment design parameters. 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 The new segment is appended to the layout alignment and the corresponding IfcCurveSegment is created in the geometric representation if it exists.
:param layout: The layout to receive the new layout segment. This parameter is expected to be IfcAlignmentHorizontal, IfcAlignmentVertical or IfcAlignmentCant :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 :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. :return: 4x4 matrix at end of segment as np.array intended to be used as the start point geometry for the next segment or None if there is the geometric representation is not defined.
""" """
expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"] expected_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"]
if not layout.is_a() in expected_types: if not layout.is_a() in expected_types:
@@ -60,6 +61,7 @@ def create_layout_segment(
alignment = ifcopenshell.api.alignment.get_alignment(layout) alignment = ifcopenshell.api.alignment.get_alignment(layout)
curve = ifcopenshell.api.alignment.get_curve(alignment) curve = ifcopenshell.api.alignment.get_curve(alignment)
if curve:
if layout.is_a("IfcAlignmentHorizontal"): if layout.is_a("IfcAlignmentHorizontal"):
if curve.is_a("IfcGradientCurve"): if curve.is_a("IfcGradientCurve"):
curve = curve.BaseCurve curve = curve.BaseCurve
@@ -82,3 +84,5 @@ def create_layout_segment(
end = np.array(e) end = np.array(e)
return end return end
else:
return None
@@ -21,12 +21,11 @@ import ifcopenshell.api.alignment
import ifcopenshell.util.element import ifcopenshell.util.element
from ifcopenshell import entity_instance from ifcopenshell import entity_instance
def get_alignment_station(file: ifcopenshell.file, alignment: entity_instance) -> float: 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 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, the referent is checked for PredefinedType of STATION and an occurance of Pset_Stationing.Station,
otherwise start station is taken to be 0.0. otherwise returns 0.0.
""" """
if not alignment.is_a("IfcAlignment"): if not alignment.is_a("IfcAlignment"):
@@ -0,0 +1,68 @@
# 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.unit
def test_create_no_geometry():
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])
# creates an IfcAlignment with an IfcAlignmentHorizontal layout containing only the zero length segment
ali = ifcopenshell.api.alignment.create(file, "A1", include_vertical=True,include_geometry=False)
# 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(ali)
assert curve == None
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 end == None
design_parameters = file.createIfcAlignmentVerticalSegment(
StartDistAlong=0.0,
HorizontalLength=50.0,
StartHeight=20.0,
StartGradient=1.0 / 100.0,
EndGradient=1.0 / 100.0,
PredefinedType="CONSTANTGRADIENT",
)
end = ifcopenshell.api.alignment.create_layout_segment(file, vertical_alignment, design_parameters)
assert end == None
test_create_no_geometry()