From 9331e89d153b04ff2d9517ee9beea197375c6797 Mon Sep 17 00:00:00 2001 From: Richard Brice <37087370+RickBrice@users.noreply.github.com> Date: Mon, 25 Aug 2025 16:05:47 -0700 Subject: [PATCH] Alignments can now be created with or without geometric representations --- .../api/alignment/_add_segment_to_layout.py | 247 +++++++++--------- .../api/alignment/_add_zero_length_segment.py | 10 +- .../ifcopenshell/api/alignment/create.py | 41 +-- .../api/alignment/create_layout_segment.py | 48 ++-- .../api/alignment/get_alignment_station.py | 3 +- .../api/alignment/test_create_no_geometry.py | 68 +++++ 6 files changed, 248 insertions(+), 169 deletions(-) create mode 100644 src/ifcopenshell-python/test/api/alignment/test_create_no_geometry.py diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/_add_segment_to_layout.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/_add_segment_to_layout.py index 1d798ec89e..cd0d89e234 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/_add_segment_to_layout.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/_add_segment_to_layout.py @@ -60,132 +60,92 @@ def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, seg # 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) + if curve: + # 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 + # 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 + # 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 = ifcopenshell.api.alignment.get_mapped_segments(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 + # the length of the current segment is in dist_along, so subtract it out + dist_along -= segment.DesignParameters.SegmentLength 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 + dist_along = segment.DesignParameters.StartDistAlong - 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.alignment.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) - - start_station = ifcopenshell.api.alignment.get_alignment_station(file, alignment) - end_referent_station = start_station + start_dist_along - pset_stationing = ifcopenshell.api.pset.add_pset(file, product=end_referent, name="Pset_Stationing") - ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": end_referent_station}) - - # 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.alignment.station_as_string(file,station)})" - referent = ifcopenshell.api.alignment.add_stationing_referent( - file, segment, 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 + # get the station of the start of the segment 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 - ) + 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 = ifcopenshell.api.alignment.get_mapped_segments(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.alignment.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 @@ -200,6 +160,47 @@ def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, seg 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) + 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) + + start_station = ifcopenshell.api.alignment.get_alignment_station(file, alignment) + end_referent_station = start_station + start_dist_along + pset_stationing = ifcopenshell.api.pset.add_pset(file, product=end_referent, name="Pset_Stationing") + ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": end_referent_station}) + + # 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.alignment.station_as_string(file,station)})" + referent = ifcopenshell.api.alignment.add_stationing_referent( + file, segment, 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) diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/_add_zero_length_segment.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/_add_zero_length_segment.py index 1f53e33ba9..db74fc472f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/_add_zero_length_segment.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/_add_zero_length_segment.py @@ -48,8 +48,8 @@ def _add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance) - if curve: ifcopenshell.api.alignment.add_zero_length_segment(file, curve) - segment = layout.IsNestedBy[0].RelatedObjects[-1] - alignment = ifcopenshell.api.alignment.get_alignment(layout) - station = ifcopenshell.api.alignment.get_alignment_station(file, alignment) - name = f"{_get_segment_start_point_label(segment,None)} ({ifcopenshell.util.alignment.station_as_string(file,station)})" - ifcopenshell.api.alignment.add_stationing_referent(file, segment, 0.0, station, name=name) + segment = layout.IsNestedBy[0].RelatedObjects[-1] + alignment = ifcopenshell.api.alignment.get_alignment(layout) + station = ifcopenshell.api.alignment.get_alignment_station(file, alignment) + name = f"{_get_segment_start_point_label(segment,None)} ({ifcopenshell.util.alignment.station_as_string(file,station)})" + ifcopenshell.api.alignment.add_stationing_referent(file, segment, 0.0, station, name=name) diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py index 7abb768174..670ce47e77 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py @@ -33,27 +33,31 @@ def create( name: str, include_vertical: bool = False, include_cant: bool = False, + include_geometry: bool = True, 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. + Creates a new alignment with a horizontal layout. Optionally, vertical and cant layouts can be created as well. + The geometric representations are created as well, unless they are explicitly excluded. + 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 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. + 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. :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 + :param include_vertical: If True, IfcAlignmentVertical is created. IfcGradientCurve is created if include_geometry is True + :param include_cant: If True, IfcAlignmentCant is created. IfcSegmentedReferenceCurve is created if include_geometry is True + :param include_geometry: If True, the geometric representations are added + :param start_station: station value at the start of the alignment. :return: Returns an IfcAlignment """ alignment = file.createIfcAlignment( @@ -73,15 +77,18 @@ def create( ifcopenshell.api.nest.assign_object(file, related_objects=alignment_layouts, relating_object=alignment) - _create_geometric_representation(file, alignment) + if include_geometry: + _create_geometric_representation(file, alignment) for layout in alignment_layouts: _add_zero_length_segment(file, layout) - # define stationing - name = ifcopenshell.util.alignment.station_as_string(file, start_station) - referent = ifcopenshell.api.alignment.add_stationing_referent(file, alignment, 0.0, start_station, name) - ifcopenshell.api.nest.reorder_nesting(file, referent, -1, 0) + if include_geometry: + # define stationing + name = ifcopenshell.util.alignment.station_as_string(file, start_station) + referent = ifcopenshell.api.alignment.add_stationing_referent(file, alignment, 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] diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/create_layout_segment.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/create_layout_segment.py index e002b8b223..1cf7ac9386 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/create_layout_segment.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/create_layout_segment.py @@ -23,20 +23,21 @@ from ifcopenshell import entity_instance import math from ifcopenshell import ifcopenshell_wrapper import numpy as np +from typing import Union 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: +) -> Union[np.array, None]: """ 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 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"] if not layout.is_a() in expected_types: @@ -59,26 +60,29 @@ def create_layout_segment( # returned and used when defining the next segment alignment = ifcopenshell.api.alignment.get_alignment(layout) curve = ifcopenshell.api.alignment.get_curve(alignment) + + if curve: + 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 - 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] - # the new segment is two from the end... the end segment is zero length - curve_segment = curve.Segments[-2] + settings = ifcopenshell.geom.settings() - 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) - 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 + return end + else: + return None diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment_station.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment_station.py index 2f5feabd7d..9ca8120bf3 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment_station.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment_station.py @@ -21,12 +21,11 @@ import ifcopenshell.api.alignment import ifcopenshell.util.element 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. + otherwise returns 0.0. """ if not alignment.is_a("IfcAlignment"): diff --git a/src/ifcopenshell-python/test/api/alignment/test_create_no_geometry.py b/src/ifcopenshell-python/test/api/alignment/test_create_no_geometry.py new file mode 100644 index 0000000000..308525fbe0 --- /dev/null +++ b/src/ifcopenshell-python/test/api/alignment/test_create_no_geometry.py @@ -0,0 +1,68 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2025 Thomas Krijnen +# +# 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 . + +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()