Updates update_key_point_referents to confirm to CT 4.1.4.4.3

This commit is contained in:
Richard Brice
2026-08-05 07:26:27 -07:00
parent 6f3acc84ee
commit 048242783e
2 changed files with 50 additions and 30 deletions
@@ -32,21 +32,6 @@ from ifcopenshell.api.alignment._sort_nest import _sort_nest
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
def _get_key_point_referent_nest(layout: entity_instance) -> Optional[entity_instance]:
"""
Searches layout.IsNestedBy for the IfcRelNests whose RelatedObjects are IfcReferent.
This is distinct from both get_stationing_nest (scoped to the parent IfcAlignment, and
specifically the STATION/station-equation nest) and get_alignment_segment_nest (the *segment*
nest that also lives on layout.IsNestedBy, holding IfcAlignmentSegment, never IfcReferent).
"""
for nest in layout.IsNestedBy:
for related_object in nest.RelatedObjects:
if related_object.is_a("IfcReferent"):
return nest
return None
def _remove_referent(file: ifcopenshell.file, referent: entity_instance) -> None:
"""Cleanly deletes a key-point IfcReferent: its Pset_Stationing, its ObjectPlacement (if
exclusively owned by it), and finally the referent itself."""
@@ -129,9 +114,11 @@ def update_key_point_referents(
get_stationing_nest) -- key-point referents never belong in either of those.
:param layout: IfcAlignmentHorizontal, IfcAlignmentVertical, or IfcAlignmentCant
:param rel_nests: an existing IfcRelNests to (re)populate. May live anywhere (e.g. the parent
IfcAlignment, the layout, or elsewhere) -- the caller decides. If omitted, an existing
referent-nest already on `layout` is reused, or a new one is created and related to `layout`.
:param rel_nests: an existing IfcRelNests to (re)populate; its RelatingObject must be the
IfcAlignment that nests `layout` (TypeError is raised otherwise). If omitted, a new
IfcRelNests is always created and related to that IfcAlignment -- there is no implicit
search for or reuse of a previously created nest. Callers who want to regenerate into an
existing nest must pass it back in explicitly via `rel_nests`.
:param clear: if True, deletes all IfcReferent currently in rel_nests.RelatedObjects (and their
Pset_Stationing) before regenerating. If False (default), new referents are appended to
whatever already exists -- no deduplication.
@@ -167,12 +154,20 @@ def update_key_point_referents(
f"Expected entity type to be one of {[_ for _ in expected_types]}, instead received {layout.is_a()}"
)
if rel_nests is None:
rel_nests = _get_key_point_referent_nest(layout)
if rel_nests is None:
rel_nests = file.createIfcRelNests(
GlobalId=ifcopenshell.guid.new(), RelatingObject=layout, RelatedObjects=()
alignment = ifcopenshell.api.alignment.get_alignment(layout)
if alignment is None:
raise ValueError(f"{layout.is_a()} #{layout.id()} is not nested under an IfcAlignment.")
if rel_nests is not None:
if not rel_nests.RelatingObject.is_a("IfcAlignment"):
raise TypeError(
f"Expected rel_nests.RelatingObject to be IfcAlignment, instead received "
f"{rel_nests.RelatingObject.is_a()}"
)
else:
rel_nests = file.createIfcRelNests(
GlobalId=ifcopenshell.guid.new(), RelatingObject=alignment, RelatedObjects=()
)
if clear:
for referent in list(rel_nests.RelatedObjects):
@@ -189,7 +184,6 @@ def update_key_point_referents(
)
return rel_nests
alignment = ifcopenshell.api.alignment.get_alignment(layout)
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
is_horizontal = layout.is_a("IfcAlignmentHorizontal")
@@ -82,13 +82,13 @@ def test_default_rel_nests_created_when_none_provided():
nest = ifcopenshell.api.alignment.update_key_point_referents(file, horizontal)
assert nest.is_a("IfcRelNests")
assert nest.RelatingObject == horizontal
assert nest.RelatingObject == alignment
assert nest.id() != segment_nest.id()
assert len(nest.RelatedObjects) == 8
assert all(r.is_a("IfcReferent") for r in nest.RelatedObjects)
def test_second_call_without_rel_nests_reuses_existing_nest():
def test_second_call_without_rel_nests_creates_separate_nest():
file = _new_file()
alignment = _build_alignment(file)
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
@@ -97,18 +97,31 @@ def test_second_call_without_rel_nests_reuses_existing_nest():
nest1 = ifcopenshell.api.alignment.update_key_point_referents(file, horizontal)
nest2 = ifcopenshell.api.alignment.update_key_point_referents(file, horizontal)
assert nest1.id() == nest2.id()
assert len(nest2.RelatedObjects) == 16
assert nest1.id() != nest2.id()
assert len(nest1.RelatedObjects) == 8
assert len(nest2.RelatedObjects) == 8
segment_count_after = len(ifcopenshell.api.alignment.get_alignment_segment_nest(horizontal).RelatedObjects)
assert segment_count_after == segment_count_before
def test_passing_previous_nest_back_in_accumulates():
file = _new_file()
alignment = _build_alignment(file)
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
nest1 = ifcopenshell.api.alignment.update_key_point_referents(file, horizontal)
nest2 = ifcopenshell.api.alignment.update_key_point_referents(file, horizontal, rel_nests=nest1)
assert nest1.id() == nest2.id()
assert len(nest2.RelatedObjects) == 16
def test_provided_rel_nests_is_used_as_is():
file = _new_file()
alignment = _build_alignment(file)
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
# the nest may live anywhere the caller chooses, e.g. hung off the parent IfcAlignment
# rel_nests.RelatingObject must be the IfcAlignment that nests `layout`
rel_nests = file.createIfcRelNests(GlobalId=ifcopenshell.guid.new(), RelatingObject=alignment, RelatedObjects=())
result = ifcopenshell.api.alignment.update_key_point_referents(file, horizontal, rel_nests=rel_nests)
@@ -118,6 +131,17 @@ def test_provided_rel_nests_is_used_as_is():
assert len(result.RelatedObjects) == 8
def test_provided_rel_nests_with_wrong_relating_object_raises_type_error():
file = _new_file()
alignment = _build_alignment(file)
horizontal = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
rel_nests = file.createIfcRelNests(GlobalId=ifcopenshell.guid.new(), RelatingObject=horizontal, RelatedObjects=())
with pytest.raises(TypeError):
ifcopenshell.api.alignment.update_key_point_referents(file, horizontal, rel_nests=rel_nests)
def test_clear_true_removes_old_referents_and_psets():
file = _new_file()
alignment = _build_alignment(file)
@@ -356,8 +380,10 @@ def test_returns_ifc_rel_nests():
test_wrong_layout_type_raises_type_error()
test_default_rel_nests_created_when_none_provided()
test_second_call_without_rel_nests_reuses_existing_nest()
test_second_call_without_rel_nests_creates_separate_nest()
test_passing_previous_nest_back_in_accumulates()
test_provided_rel_nests_is_used_as_is()
test_provided_rel_nests_with_wrong_relating_object_raises_type_error()
test_clear_true_removes_old_referents_and_psets()
test_clear_false_appends_without_dedup()
test_default_horizontal_labels_and_order()