diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/__init__.py index 9154159bb5..fb51da7cde 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/__init__.py @@ -60,6 +60,8 @@ from .create_segment_representations import create_segment_representations from .create_representation import create_representation from .distance_along_from_station import distance_along_from_station from .get_alignment import get_alignment +from .get_alignment_layout_nest import get_alignment_layout_nest +from .get_alignment_segment_nest import get_alignment_segment_nest from .get_alignment_station import get_alignment_station from .get_curve_segment_transition_code import get_curve_segment_transition_code from .get_layout_segments import get_layout_segments @@ -74,6 +76,7 @@ from .get_curve import get_curve from .get_layout_curve import get_layout_curve from .get_mapped_segments import get_mapped_segments from .get_parent_alignment import get_parent_alignment +from .get_referent_nest import get_referent_nest from .has_zero_length_segment import has_zero_length_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 @@ -97,7 +100,9 @@ __all__ = [ "create_segment_representations", "distance_along_from_station", "get_alignment", + "get_alignment_layout_nest", "get_alignment_layouts", + "get_alignment_segment_nest", "get_alignment_station", "get_axis_subcontext", "get_basis_curve", @@ -109,6 +114,7 @@ __all__ = [ "get_layout_curve", "get_layout_segments", "get_parent_alignment", + "get_referent_nest", "get_vertical_layout", "has_zero_length_segment", "layout_horizontal_alignment_by_pi_method", 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 cd0d89e234..b02f033fab 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 @@ -173,7 +173,8 @@ 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 - prev_segment = layout.IsNestedBy[0].RelatedObjects[-3] if 2 < len(layout.IsNestedBy[0].RelatedObjects) else None + 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 @@ -183,7 +184,8 @@ def _add_segment_to_layout(file: ifcopenshell.file, layout: entity_instance, seg # 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] + ref_nest = ifcopenshell.api.alignment.get_referent_nest(file,alignment) + stationing_referent = ref_nest.RelatedObjects[0] p = curve_evaluator.evaluate( stationing_referent.ObjectPlacement.RelativePlacement.Location.DistanceAlong.wrappedValue ) 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 eece9ce581..286f660319 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/add_stationing_referent.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/add_stationing_referent.py @@ -133,7 +133,9 @@ def add_stationing_referent( ) 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) + + nest = ifcopenshell.api.alignment.get_referent_nest(file,element) + nest.RelatedObjects += (referent,) if len(alignment.Positions) == 0: rel_positions = file.createIfcRelPositions( diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py index 670ce47e77..8fb591a256 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/create.py @@ -87,7 +87,6 @@ def create( # 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 diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment.py index 21dc9f9d2b..3086bac669 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment.py +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment.py @@ -16,16 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . -import ifcopenshell -import ifcopenshell.util from ifcopenshell import entity_instance -from collections.abc import Sequence - -import ifcopenshell.util.representation - def get_alignment(layout: entity_instance) -> entity_instance: """ Returns the alignment that nests this layout """ - return layout.Nests[0].RelatingObject if 0 < len(layout.Nests) else None + alignment = None + for nest in layout.Nests: + if nest.RelatingObject.is_a("IfcAlignment"): + alignment = nest.RelatingObject + break + + return alignment diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment_layout_nest.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment_layout_nest.py new file mode 100644 index 0000000000..495157ce14 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment_layout_nest.py @@ -0,0 +1,36 @@ +# 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 ifcopenshell +from ifcopenshell import entity_instance + +def get_alignment_layout_nest(alignment: entity_instance) -> entity_instance: + """ + Searches for the IfcRelNest that contains IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant + + :param alignment: the alignment + :return: Returns the IfcRelNests containing the alignment layout + """ + layout_types = ["IfcAlignmentHorizontal", "IfcAlignmentVertical", "IfcAlignmentCant"] + + for nest in alignment.IsNestedBy: + for related_object in nest.RelatedObjects: + if related_object.is_a() in layout_types: + return nest + + return None diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment_segment_nest.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment_segment_nest.py new file mode 100644 index 0000000000..49cb7628e5 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/get_alignment_segment_nest.py @@ -0,0 +1,33 @@ +# 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 ifcopenshell +from ifcopenshell import entity_instance + +def get_alignment_segment_nest(layout: entity_instance) -> entity_instance: + """ + Searches for the IfcRelNest that contains IfcAlignmentSegment + + :param layout: an alignment layout, expected to be one of IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant + :return: Returns the IfcRelNests + """ + for nest in layout.IsNestedBy: + for related_object in nest.RelatedObjects: + if related_object.is_a("IfcAlignmentSegment"): + return nest + return None diff --git a/src/ifcopenshell-python/ifcopenshell/api/alignment/get_referent_nest.py b/src/ifcopenshell-python/ifcopenshell/api/alignment/get_referent_nest.py new file mode 100644 index 0000000000..74eab17056 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/alignment/get_referent_nest.py @@ -0,0 +1,36 @@ +# 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 ifcopenshell +from ifcopenshell import entity_instance + +def get_referent_nest(file: ifcopenshell.file,entity: entity_instance) -> entity_instance: + """ + Searches for the IfcRelNest that contains IfcReferent. If one is not found, a empty IfcRelNests is created. + + :param file: + :param entity: any entity that is the parent in a nesting relationship, but intended to be IfcAlignment, IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant + :return: Returns the IfcRelNests. + """ + for nest in entity.IsNestedBy: + for related_object in nest.RelatedObjects: + if related_object.is_a("IfcReferent"): + return nest + + nest = file.createIfcRelNests(GlobalId=ifcopenshell.guid.new(),RelatingObject=entity,RelatedObjects=[]) + return nest diff --git a/src/ifcopenshell-python/test/api/alignment/test_add_vertical_alignment.py b/src/ifcopenshell-python/test/api/alignment/test_add_vertical_alignment.py index 256c9eab37..6287d28738 100644 --- a/src/ifcopenshell-python/test/api/alignment/test_add_vertical_alignment.py +++ b/src/ifcopenshell-python/test/api/alignment/test_add_vertical_alignment.py @@ -32,23 +32,20 @@ def test_add_vertical_alignment(): alignment = ifcopenshell.api.alignment.create(file, "A1", include_vertical=False) 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) == 2 # nests for layout and referents + assert len(alignment.IsNestedBy[0].RelatedObjects) == 1 + assert ifcopenshell.api.alignment.get_referent_nest(file,alignment).RelatedObjects[0].is_a("IfcReferent") + assert alignment.IsNestedBy[0].RelatedObjects[0].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) 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) == 2 + assert (len(alignment.IsNestedBy[0].RelatedObjects) == 2) + assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcAlignmentHorizontal") + assert alignment.IsNestedBy[0].RelatedObjects[1].is_a("IfcAlignmentVertical") curve = ifcopenshell.api.alignment.get_curve(alignment) assert curve.is_a("IfcGradientCurve") @@ -56,9 +53,7 @@ def test_add_vertical_alignment(): vertical_layout = ifcopenshell.api.alignment.add_vertical_layout(file, alignment) 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 + assert (len(alignment.IsDecomposedBy[0].RelatedObjects) == 2) for child_alignment in alignment.IsDecomposedBy[0].RelatedObjects: assert child_alignment.is_a("IfcAlignment") @@ -66,10 +61,9 @@ def test_add_vertical_alignment(): 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) == 2 # nesting IfcReferent and IfcAlignmentHorizontal - assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcReferent") - assert alignment.IsNestedBy[0].RelatedObjects[1].is_a("IfcAlignmentHorizontal") + assert len(alignment.IsNestedBy) == 2 + assert len(alignment.IsNestedBy[0].RelatedObjects) == 1 + assert alignment.IsNestedBy[0].RelatedObjects[0].is_a("IfcAlignmentHorizontal") test_add_vertical_alignment() 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 0f1bb5fd3a..95868f80a6 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,22 +46,15 @@ def test_create_by_pi_method(): ) 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 + 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") + 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 test_create_by_pi_method() diff --git a/src/ifcopenshell-python/test/api/alignment/test_horizontal_layout_by_pi_method.py b/src/ifcopenshell-python/test/api/alignment/test_horizontal_layout_by_pi_method.py index fd3321b425..c500d3d66e 100644 --- a/src/ifcopenshell-python/test/api/alignment/test_horizontal_layout_by_pi_method.py +++ b/src/ifcopenshell-python/test/api/alignment/test_horizontal_layout_by_pi_method.py @@ -45,16 +45,13 @@ def test_horizontal_layout_by_pi_method(): 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 + assert len(alignment.IsNestedBy) == 2 + referent_nest = ifcopenshell.api.alignment.get_referent_nest(file,alignment) + layout_nest = ifcopenshell.api.alignment.get_alignment_layout_nest(alignment) + assert referent_nest.RelatedObjects[0].is_a("IfcReferent") + assert layout_nest.RelatedObjects[0].is_a("IfcAlignmentHorizontal") + segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(layout_nest.RelatedObjects[0]) + assert (len(segment_nest.RelatedObjects) == 3) # segments in horizontal layout test_horizontal_layout_by_pi_method() diff --git a/src/ifcopenshell-python/test/api/alignment/test_vertical_layout_by_pi_method.py b/src/ifcopenshell-python/test/api/alignment/test_vertical_layout_by_pi_method.py index be9f9b55a3..9d39cf4b36 100644 --- a/src/ifcopenshell-python/test/api/alignment/test_vertical_layout_by_pi_method.py +++ b/src/ifcopenshell-python/test/api/alignment/test_vertical_layout_by_pi_method.py @@ -58,22 +58,13 @@ def test_vertical_layout_by_pi_method(): 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 + assert len(alignment.IsNestedBy) == 2 + referent_nest = ifcopenshell.api.alignment.get_referent_nest(file,alignment) + layout_nest = ifcopenshell.api.alignment.get_alignment_layout_nest(alignment) + assert(len(referent_nest.RelatedObjects) == 1) + assert(len(layout_nest.RelatedObjects) == 2) + segment_nest = ifcopenshell.api.alignment.get_alignment_segment_nest(vlayout) + assert(len(segment_nest.RelatedObjects) == 3) test_vertical_layout_by_pi_method()