Bonsai: inline get_root_context, trim docstrings, confirm get_parent_library unchanged

Per Moult's round 3 review. get_root_context added nothing over
ifc_file.by_type("IfcProject")[0], which is guaranteed by the IFC Project
Context concept template; remove it and inline the call at its three sites
(operator.py's RefreshLibrary and AddProjectLibrary, data.py's
parent_libraries_enum). Trim the get_parent_library docstring to one line;
its logic is untouched by this PR, byte for byte identical to origin/v0.8.0,
and still returns None only when project_library has neither Nests nor
HasContext, never for a library declared directly to IfcProject.

Rework test_project_library_data.py to match: replace the two
get_root_context-specific tests with one that exercises the real call site
(ProjectLibraryData.parent_libraries_enum raising IndexError for a file
without IfcProject), and add an explicit test that get_parent_library
returns None for a genuinely orphaned library. Also drop a long inline
comment that restated what the test body already shows.

Verified live in headless Blender (isolated profile, source-loaded, never
the real profile): all 17 test/bim/module/project tests pass, including the
new get_parent_library None-for-orphan case. Ran the full test/bim suite
before and after on the identical harness: 82 failed/1335 passed both times,
same failing tests (all pre-existing, unrelated to this module).

This change was made with the assistance of an AI tool.
This commit is contained in:
Petru Conduraru
2026-07-24 09:47:15 +03:00
parent 56d0e0d189
commit 3458d55321
4 changed files with 19 additions and 40 deletions
+1 -1
View File
@@ -162,7 +162,7 @@ class ProjectLibraryData:
library_file = IfcStore.library_file
if library_file is None or library_file.schema == "IFC2X3":
return results
root = tool.Project.get_root_context(library_file)
root = library_file.by_type("IfcProject")[0]
results.append((str(root.id()), f"{root.is_a()} {root.Name or 'Unnamed'}", root.Description or ""))
for library_id, data in cls.data["project_libraries"].items():
results.append((str(library_id), data["Name"] or "Unnamed", data["Description"] or ""))
@@ -281,7 +281,7 @@ class RefreshLibrary(bpy.types.Operator):
elements = {e for e in elements if not tool.Project.is_element_assigned_to_project_library(e, rels)}
self.props.add_library_project_library("Unassigned", len(elements), 0, False)
root_context = tool.Project.get_root_context(library_file)
root_context = library_file.by_type("IfcProject")[0]
hierarchy = tool.Project.get_project_hierarchy(library_file)
tool.Project.load_project_libraries_to_ui(root_context, hierarchy)
return {"FINISHED"}
@@ -807,7 +807,7 @@ class AddProjectLibrary(bpy.types.Operator):
props = tool.Project.get_project_props()
library_file = IfcStore.library_file
assert library_file
root_context = tool.Project.get_root_context(library_file)
root_context = library_file.by_type("IfcProject")[0]
project_library = ifcopenshell.api.root.create_entity(library_file, "IfcProjectLibrary")
ifcopenshell.api.project.assign_declaration(library_file, [project_library], root_context)
ProjectLibraryData.load() # Update enum.
+2 -18
View File
@@ -388,30 +388,14 @@ class Project(bonsai.core.tool.Project):
def get_parent_library(
cls, project_library: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
"""Return the IfcContext that declares or nests ``project_library``.
Every IfcProjectLibrary in a supported (spec-valid) file is either nested
under another library or declared to the file's IfcProject. Returns ``None``
only as a defensive fallback for malformed data with neither relationship.
"""
"""Return the IfcContext that declares or nests ``project_library``, or ``None``
if neither relationship is present."""
if nests := project_library.Nests:
return nests[0].RelatingObject
if has_context := project_library.HasContext:
return has_context[0].RelatingContext
return None
@classmethod
def get_root_context(cls, ifc_file: ifcopenshell.file) -> ifcopenshell.entity_instance:
"""Return the file's IfcProject.
Per the IFC Project Context concept template, every project data set (this
includes library files) shall contain exactly one IfcProject; there is no
such thing as a spec-valid file rooted on IfcProjectLibrary alone. A file
without an IfcProject is not supported and this raises IndexError. Caller is
responsible for the IFC2X3 guard; IfcContext does not exist in that schema.
"""
return ifc_file.by_type("IfcProject")[0]
@classmethod
def get_project_hierarchy(cls, ifc_file: ifcopenshell.file) -> HiearchyDict:
"""Get project hierarchy in the following form:
@@ -55,23 +55,18 @@ class TestLibraryFile(NewIfc):
"""Project-library UI code operating on a spec-valid model (IfcProject root).
A file containing only IfcProjectLibrary and no IfcProject is not valid IFC and
is not supported; see test_get_root_context_raises_for_a_file_without_a_project.
is not supported; see test_parent_libraries_enum_raises_for_a_file_without_a_project.
"""
def test_get_root_context_returns_the_project(self):
library_file = _make_library_file()
project = library_file.by_type("IfcProject")[0]
root = tool.Project.get_root_context(library_file)
assert root == project
def test_get_root_context_raises_for_a_file_without_a_project(self):
def test_parent_libraries_enum_raises_for_a_file_without_a_project(self):
library_file = ifcopenshell.api.project.create_file(version="IFC4")
ifcopenshell.api.root.create_entity(library_file, ifc_class="IfcProjectLibrary", name="RootLib")
with pytest.raises(IndexError):
tool.Project.get_root_context(library_file)
IfcStore.library_file = library_file
try:
with pytest.raises(IndexError):
ProjectLibraryData.parent_libraries_enum()
finally:
IfcStore.library_file = None
def test_get_parent_library_returns_project_for_declared_root_library(self):
library_file = _make_library_file()
@@ -80,6 +75,12 @@ class TestLibraryFile(NewIfc):
assert tool.Project.get_parent_library(root) == project
def test_get_parent_library_returns_none_for_an_orphaned_library(self):
library_file = ifcopenshell.api.project.create_file(version="IFC4")
orphan = ifcopenshell.api.root.create_entity(library_file, ifc_class="IfcProjectLibrary", name="Orphan")
assert tool.Project.get_parent_library(orphan) is None
def test_get_project_hierarchy_roots_libraries_under_the_project(self):
library_file = _make_library_file(with_child=True)
project = library_file.by_type("IfcProject")[0]
@@ -92,12 +93,6 @@ class TestLibraryFile(NewIfc):
assert child in hierarchy[root]
def test_project_library_data_loads_with_unique_enum_keys(self):
# Regression test for the ci-bonsai-daily failure: parent_libraries_enum()
# must never emit two entries with the same STEP id (Blender's EnumProperty
# requires unique keys). The original failure only occurred because the root
# context could incorrectly resolve to an IfcProjectLibrary that was also
# collected by project_libraries(); on a spec-valid model root is always the
# IfcProject, whose id never collides with a library id.
IfcStore.library_file = _make_library_file(with_child=True)
try:
ProjectLibraryData.is_loaded = False