diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index da86b69a91..40edf5199b 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -540,8 +540,15 @@ def _get_element_value(element: ifcopenshell.entity_instance, keys: list[str]) - value = results or None if value and len(value) == 1: value = value[0] + elif key in value: + value = value[key] else: - value = value.get(key, None) + # A nested complex quantity/property (IfcPhysicalComplexQuantity / + # IfcComplexProperty) is represented as a dict whose nested members + # live under a "properties" sub-dict. Descend into it so that nested + # values are reachable with the natural "Qto.Complex.Nested" path. + subprops = value.get("properties") + value = subprops.get(key, None) if isinstance(subprops, dict) else None elif isinstance(value, (list, tuple, set)): # If we use regex if isinstance(key, str) and key.isnumeric(): try: diff --git a/src/ifcopenshell-python/test/util/test_selector.py b/src/ifcopenshell-python/test/util/test_selector.py index af80141e81..c4c7a6ce17 100644 --- a/src/ifcopenshell-python/test/util/test_selector.py +++ b/src/ifcopenshell-python/test/util/test_selector.py @@ -195,6 +195,37 @@ class TestGetElementValue(test.bootstrap.IFC4): assert subject.get_element_value(element, "/Pset_.*Common/.Status") == ["New"] assert subject.get_element_value(element, "/Pset_.*Common/.Status.0") == "New" + def test_selecting_a_nested_complex_quantity(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + complex_quantity = self.file.create_entity( + "IfcPhysicalComplexQuantity", + Name="Layer1", + Discrimination="layer", + HasQuantities=[ + self.file.create_entity("IfcQuantityLength", Name="Width", LengthValue=0.1), + self.file.create_entity("IfcQuantityLength", Name="Height", LengthValue=2.5), + ], + ) + quantity = self.file.create_entity( + "IfcElementQuantity", + GlobalId=ifcopenshell.guid.new(), + Name="Qto_Custom", + Quantities=[complex_quantity, self.file.create_entity("IfcQuantityArea", Name="NetArea", AreaValue=5.0)], + ) + self.file.create_entity( + "IfcRelDefinesByProperties", + GlobalId=ifcopenshell.guid.new(), + RelatedObjects=[element], + RelatingPropertyDefinition=quantity, + ) + # A simple quantity in the same set still resolves normally. + assert subject.get_element_value(element, "Qto_Custom.NetArea") == 5.0 + # Nested quantities of a complex quantity are reachable with the natural path. + assert subject.get_element_value(element, "Qto_Custom.Layer1.Width") == 0.1 + assert subject.get_element_value(element, "Qto_Custom.Layer1.Height") == 2.5 + # The explicit "properties" path is preserved for backwards compatibility. + assert subject.get_element_value(element, "Qto_Custom.Layer1.properties.Width") == 0.1 + class TestFilterElements(test.bootstrap.IFC4): def test_selecting_by_globalid(self):