From 39100b7c97767fc0452dec356d8216fa43997cc4 Mon Sep 17 00:00:00 2001 From: Richard Brice <37087370+RickBrice@users.noreply.github.com> Date: Wed, 27 Aug 2025 11:32:25 -0700 Subject: [PATCH] Fixes problems with referents --- .../api/alignment/_add_segment_to_layout.py | 10 +++-- .../api/alignment/_add_zero_length_segment.py | 5 ++- .../api/alignment/add_stationing_referent.py | 18 +++++++-- .../ifcopenshell/api/alignment/create.py | 2 +- .../api/alignment/create_as_polyline.py | 4 +- .../ifcopenshell/api/alignment/util.py | 8 ++++ .../alignment/test_add_segment_to_layout.py | 13 +++---- .../api/alignment/test_create_by_pi_method.py | 23 ++++++++---- .../test/api/alignment/test_name_segments.py | 20 +++++----- .../test/api/alignment/test_referent_names.py | 37 +++++++++++-------- 10 files changed, 85 insertions(+), 55 deletions(-) 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 b02f033fab..4baf0a4e8f 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 @@ -89,7 +89,8 @@ def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, seg # update the zero length layout segment unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file) - zero_length_segment = layout.IsNestedBy[0].RelatedObjects[-1] + segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout) + zero_length_segment = segment_nest.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 @@ -136,7 +137,8 @@ def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, seg 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] + referent_nest = ifcopenshell.api.alignment.get_referent_nest(file,layout) + end_referent = referent_nest.RelatedObjects[-1] 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 @@ -173,12 +175,12 @@ def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, seg # 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 - segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout) prev_segment = segment_nest.RelatedObjects[-3] if 2 < len(segment_nest.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 + file, layout, distance_along=dist_along, station=station, name=name, positioned_product=segment ) + ifcopenshell.api.nest.reorder_nesting(file,referent,-1,-1) if len(curve.Segments) == 2 and layout.is_a("IfcAlignmentHorizontal"): # this is the first real segment in the horizontal alignment 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 db74fc472f..dacae0be54 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,9 @@ 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] + segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout) + segment = segment_nest.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) + ifcopenshell.api.alignment.add_stationing_referent(file, layout, 0.0, station, name, segment) diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/add_stationing_referent.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/add_stationing_referent.py index 286f660319..2c68fbb2a4 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/add_stationing_referent.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/add_stationing_referent.py @@ -34,6 +34,7 @@ def add_stationing_referent( distance_along: float, station: float, name: str, + positioned_product: entity_instance ) -> entity_instance: """ Adds an IfcReferent to the element with the Pset_Stationing property set. @@ -43,6 +44,7 @@ def add_stationing_referent( :param distance_along: distance along the alignment curve :param station: station value :param name: name to assign to IfcReferent.Name, typically a stringized version of the station value + :param positioned_product: the product whose position is informed by the referent :return: referent Example: @@ -54,9 +56,17 @@ def add_stationing_referent( """ alignment = element + layout_types = [ + "IfcAlignmentHorizontal", + "IfcAlignmentVertical", + "IfcAlignmentCant", + ] + if element.is_a("IfcAlignmentSegment"): layout = element.Nests[0].RelatingObject alignment = ifcopenshell.api.alignment.get_alignment(layout) + elif element.is_a() in layout_types: + alignment = ifcopenshell.api.alignment.get_alignment(element) basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment) @@ -137,15 +147,15 @@ def add_stationing_referent( nest = ifcopenshell.api.alignment.get_referent_nest(file,element) nest.RelatedObjects += (referent,) - if len(alignment.Positions) == 0: + if len(referent.Positions) == 0: rel_positions = file.createIfcRelPositions( GlobalId=ifcopenshell.guid.new(), - RelatingPositioningElement=alignment, + RelatingPositioningElement=referent, RelatedProducts=[ - referent, + positioned_product, ], ) else: - alignment.Positions[0].RelatedProducts += (referent,) + referent.Positions[0].RelatedProducts += (positioned_product,) return referent diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py index 8fb591a256..67c3757bcb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py @@ -86,7 +86,7 @@ def create( 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) + referent = ifcopenshell.api.alignment.add_stationing_referent(file, alignment, 0.0, start_station, name, alignment) # IFC 4.1.4.1.1 Alignment Aggregation To Project diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/create_as_polyline.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/create_as_polyline.py index cd7ebeb896..d8c48d1b3c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/create_as_polyline.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/create_as_polyline.py @@ -137,13 +137,11 @@ def create_as_polyline( Name=name, ) - # _create_layout(file,alignment,points) - _create_polyline_representation(file, alignment, points) # 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) + referent = ifcopenshell.api.alignment.add_stationing_referent(file, alignment, 0.0, start_station, name, alignment) ifcopenshell.api.nest.reorder_nesting(file, referent, -1, 0) # IFC 4.1.4.1.1 Alignment Aggregation To Project diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/util.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/util.py index 7f24b4eddf..f0295b331b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/util.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/util.py @@ -162,3 +162,11 @@ def print_composite_curve_deep(curve): print(" " * 4, segment.Placement) print(" " * 4, segment.Placement.Location) print(" " * 4, segment.Placement.RefDirection) + +def print_positioned_products(file: ifcopenshell.file): + referents = file.by_type("IfcReferent") + for referent in referents: + print(referent) + for rel in referent.Positions: + for product in rel.RelatedProducts: + print(" " * 2,product) \ No newline at end of file diff --git a/src/ifcopenshell-python/test/api/alignment/test_add_segment_to_layout.py b/src/ifcopenshell-python/test/api/alignment/test_add_segment_to_layout.py index 5c796554ce..1b0e72c51c 100644 --- a/src/ifcopenshell-python/test/api/alignment/test_add_segment_to_layout.py +++ b/src/ifcopenshell-python/test/api/alignment/test_add_segment_to_layout.py @@ -67,14 +67,11 @@ def test_add_segment_to_layout(): _add_segment_to_layout(file, horizontal_alignment, alignment_segment) - assert len(horizontal_alignment.IsNestedBy) == 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 + assert len(horizontal_alignment.IsNestedBy) == 2 + referent_nest = ifcopenshell.api.alignment.get_referent_nest(file,horizontal_alignment) + segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(horizontal_alignment) + assert (len(segment_nest.RelatedObjects) == 2) + assert (len(referent_nest.RelatedObjects) == 2) test_add_segment_to_layout() diff --git a/src/ifcopenshell-python/test/api/alignment/test_create_by_pi_method.py b/src/ifcopenshell-python/test/api/alignment/test_create_by_pi_method.py index 95868f80a6..4486db600a 100644 --- a/src/ifcopenshell-python/test/api/alignment/test_create_by_pi_method.py +++ b/src/ifcopenshell-python/test/api/alignment/test_create_by_pi_method.py @@ -46,15 +46,22 @@ def test_create_by_pi_method(): ) assert len(alignment.IsDecomposedBy) == 0 # no child alignments - assert len(alignment.IsNestedBy) == 2 # one nest - assert (len(alignment.IsNestedBy[0].RelatedObjects) == 2) - assert ifcopenshell.api.alignment.get_referent_nest(file,alignment).RelatedObjects[0].is_a("IfcReferent") + assert len(alignment.IsNestedBy) == 2 + layout_nest = ifcopenshell.api.alignment.get_alignment_layout_nest(alignment) - assert layout_nest.RelatedObjects[0].is_a("IfcAlignmentHorizontal") - assert layout_nest.RelatedObjects[1].is_a("IfcAlignmentVertical") - assert (len(layout_nest.RelatedObjects[0].IsNestedBy) == 1) # nesting of segments beneath IfcAlignmentHorizontal - assert (len(layout_nest.RelatedObjects[0].IsNestedBy[0].RelatedObjects) == 8) # segments in horizontal layout - assert (len(layout_nest.RelatedObjects[1].IsNestedBy[0].RelatedObjects) == 10) # segments in vertical layout + assert (len(layout_nest.RelatedObjects) == 2) + + horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment) + horizontal_segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(horizontal_layout) + assert (len(horizontal_segment_nest.RelatedObjects) == 8) + horizontal_referent_nest = ifcopenshell.api.alignment.get_referent_nest(file,horizontal_layout) + assert (len(horizontal_referent_nest.RelatedObjects) == 8) + + vertical_layout = ifcopenshell.api.alignment.get_vertical_layout(alignment) + vertical_segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(vertical_layout) + assert (len(vertical_segment_nest.RelatedObjects) == 10) + vertical_referent_nest = ifcopenshell.api.alignment.get_referent_nest(file,vertical_layout) + assert (len(vertical_referent_nest.RelatedObjects) == 10) test_create_by_pi_method() diff --git a/src/ifcopenshell-python/test/api/alignment/test_name_segments.py b/src/ifcopenshell-python/test/api/alignment/test_name_segments.py index 89629b78c9..ee84bcde85 100644 --- a/src/ifcopenshell-python/test/api/alignment/test_name_segments.py +++ b/src/ifcopenshell-python/test/api/alignment/test_name_segments.py @@ -45,12 +45,14 @@ def test_name_segments(): file, "TestAlignment", coordinates, radii, vpoints, lengths ) - for rel in alignment.IsNestedBy: - for a in rel.RelatedObjects: - if a.is_a("IfcLinearElement"): - ifcopenshell.api.alignment.name_segments("Q", a) - i = 1 - for sr in a.IsNestedBy: - for s in sr.RelatedObjects: - assert f"Q{i}" == s.Name - i += 1 + layout_nest = ifcopenshell.api.alignment.get_alignment_layout_nest(alignment) + for layout in layout_nest.RelatedObjects: + assert layout.is_a("IfcLinearElement") + ifcopenshell.api.alignment.name_segments("Q", layout) + segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout) + i = 1 + for segment in segment_nest.RelatedObjects: + assert f"Q{i}" == segment.Name + i += 1 + +test_name_segments() \ No newline at end of file diff --git a/src/ifcopenshell-python/test/api/alignment/test_referent_names.py b/src/ifcopenshell-python/test/api/alignment/test_referent_names.py index 835233f227..06c0bad61b 100644 --- a/src/ifcopenshell-python/test/api/alignment/test_referent_names.py +++ b/src/ifcopenshell-python/test/api/alignment/test_referent_names.py @@ -98,31 +98,36 @@ def callback_alignment(): def test_with_default_names(default_names_alignment): hlayout = ifcopenshell.api.alignment.get_horizontal_layout(default_names_alignment) + referent_nest = ifcopenshell.api.alignment.get_referent_nest(None,hlayout) - 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 + assert "P.O.B." in referent_nest.RelatedObjects[0].Name + assert "P.C." in referent_nest.RelatedObjects[1].Name + assert "P.T." in referent_nest.RelatedObjects[2].Name + assert "P.O.E." in referent_nest.RelatedObjects[-1].Name vlayout = ifcopenshell.api.alignment.get_vertical_layout(default_names_alignment) + referent_nest = ifcopenshell.api.alignment.get_referent_nest(None,vlayout) - 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 + assert "V.P.O.B." in referent_nest.RelatedObjects[0].Name + assert "P.V.C." in referent_nest.RelatedObjects[1].Name + assert "P.V.T." in referent_nest.RelatedObjects[2].Name + assert "V.P.O.E." in referent_nest.RelatedObjects[-1].Name def test_with_callbacks(callback_alignment): hlayout = ifcopenshell.api.alignment.get_horizontal_layout(callback_alignment) + referent_nest = ifcopenshell.api.alignment.get_referent_nest(None,hlayout) - 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 + assert "A" in referent_nest.RelatedObjects[0].Name + assert "Q" in referent_nest.RelatedObjects[1].Name + assert "Q" in referent_nest.RelatedObjects[2].Name + assert "Z" in referent_nest.RelatedObjects[-1].Name vlayout = ifcopenshell.api.alignment.get_vertical_layout(callback_alignment) + referent_nest = ifcopenshell.api.alignment.get_referent_nest(None,vlayout) + + assert "a" in referent_nest.RelatedObjects[0].Name + assert "q" in referent_nest.RelatedObjects[1].Name + assert "q" in referent_nest.RelatedObjects[2].Name + assert "z" in referent_nest.RelatedObjects[-1].Name - 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