Add feature to get parent of a particular IFC class

This commit is contained in:
Dion Moult
2026-03-20 23:10:00 +11:00
parent 7b6e82a9cc
commit d0f20371bd
3 changed files with 50 additions and 6 deletions
@@ -1234,7 +1234,9 @@ def get_controls(element: ifcopenshell.entity_instance) -> Generator[ifcopenshel
yield rel.RelatingControl yield rel.RelatingControl
def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: def get_parent(
element: ifcopenshell.entity_instance, ifc_class: Optional[str] = None
) -> Union[ifcopenshell.entity_instance, None]:
"""Get the parent in the spatial heirarchy """Get the parent in the spatial heirarchy
IFC features a spatial hierarchy tree of all objects. Each spatial element IFC features a spatial hierarchy tree of all objects. Each spatial element
@@ -1251,6 +1253,8 @@ def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.enti
- Voiding: the opening voids another physical element, such as a hole in a wall - Voiding: the opening voids another physical element, such as a hole in a wall
:param element: Any physical or spatial element in the tree :param element: Any physical or spatial element in the tree
:param ifc_class: Optionally filter the type of parent you're after. For
example, you may be after the storey, not a space.
:return: Its parent. This must exist for any valid file, or None if we've reached the IfcProject. :return: Its parent. This must exist for any valid file, or None if we've reached the IfcProject.
Example: Example:
@@ -1260,7 +1264,7 @@ def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.enti
element = file.by_type("IfcWall")[0] element = file.by_type("IfcWall")[0]
parent = ifcopenshell.util.element.get_parent(element) parent = ifcopenshell.util.element.get_parent(element)
""" """
return ( parent = (
get_container(element, should_get_direct=True) get_container(element, should_get_direct=True)
or get_aggregate(element) or get_aggregate(element)
or get_nest(element) or get_nest(element)
@@ -1268,6 +1272,16 @@ def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.enti
or get_voided_element(element) or get_voided_element(element)
) )
if not ifc_class:
return parent
while parent:
if parent.is_a(ifc_class):
return parent
parent = get_parent(parent)
return None
def get_filled_void(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: def get_filled_void(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
"""If the element is filling a void, get the void """If the element is filling a void, get the void
@@ -440,13 +440,13 @@ def _get_element_value(element: ifcopenshell.entity_instance, keys: list[str]) -
elif key == "container": elif key == "container":
value = ifcopenshell.util.element.get_container(value) value = ifcopenshell.util.element.get_container(value)
elif key == "space": elif key == "space":
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSpace") value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcSpace")
elif key == "storey": elif key == "storey":
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuildingStorey") value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcBuildingStorey")
elif key == "building": elif key == "building":
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuilding") value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcBuilding")
elif key == "site": elif key == "site":
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSite") value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcSite")
elif key == "parent": elif key == "parent":
value = ifcopenshell.util.element.get_parent(value) value = ifcopenshell.util.element.get_parent(value)
elif key in ("types", "occurrences"): elif key in ("types", "occurrences"):
@@ -38,6 +38,7 @@ import ifcopenshell.api.sequence
import ifcopenshell.api.spatial import ifcopenshell.api.spatial
import ifcopenshell.api.style import ifcopenshell.api.style
import ifcopenshell.api.type import ifcopenshell.api.type
import ifcopenshell.api.feature
import ifcopenshell.guid import ifcopenshell.guid
import ifcopenshell.util.element as subject import ifcopenshell.util.element as subject
import test.bootstrap import test.bootstrap
@@ -891,6 +892,35 @@ class TestGetlayers(test.bootstrap.IFC4, TestGetlayersIFC2X3):
assert subject.get_layers(self.file, element) == [layer] assert subject.get_layers(self.file, element) == [layer]
class TestGetParentIFC4(test.bootstrap.IFC4):
def test_getting_the_parent_of_an_element(self):
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
building = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding")
ifcopenshell.api.spatial.assign_container(self.file, products=[element], relating_structure=building)
assert subject.get_parent(element) == building
def test_getting_the_specific_parent_of_an_element(self):
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
building = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding")
storey = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuildingStorey")
ifcopenshell.api.aggregate.assign_object(self.file, products=[storey], relating_object=building)
ifcopenshell.api.spatial.assign_container(self.file, products=[element], relating_structure=storey)
assert subject.get_parent(element, ifc_class="IfcBuilding") == building
assert subject.get_parent(element, ifc_class="IfcSite") == None
def test_getting_the_specific_parent_of_an_element_via_voiding(self):
wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
building = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding")
ifcopenshell.api.spatial.assign_container(self.file, products=[wall], relating_structure=building)
opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement")
ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall)
window = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWindow")
ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=window)
assert subject.get_parent(window, ifc_class="IfcWall") == wall
assert subject.get_parent(window, ifc_class="IfcBuilding") == building
assert subject.get_parent(window, ifc_class="IfcSite") == None
class TestGetContainerIFC4(test.bootstrap.IFC4): class TestGetContainerIFC4(test.bootstrap.IFC4):
def test_getting_the_spatial_container_of_an_element(self): def test_getting_the_spatial_container_of_an_element(self):
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")