From 7cf172b96b0bec2b2b0a980f57bdd0e5b46c36d8 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sun, 19 Jul 2026 16:18:31 +0300 Subject: [PATCH] docs: cover reading properties and quantities from an element and its type in C++ getting started Fixes issue #3910's documentation gap. IsDefinedBy() returns IfcRelDefinesByProperties relationship objects, not the property set itself, and RelatingPropertyDefinition() must be used to reach the IfcPropertySet or IfcElementQuantity. Properties can also come from an element's type via IsTypedBy() -> RelatingType() -> HasPropertySets(), a path that is easy to miss because it works differently. Adds a worked, beginner-commented, compilable example covering both paths. Generated with the assistance of an AI coding tool. (cherry picked from commit 8fb896609498676c2ab2c21a6cd8fbd38ff35606) --- .../docs/ifcopenshell/getting_started.rst | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/src/ifcopenshell-python/docs/ifcopenshell/getting_started.rst b/src/ifcopenshell-python/docs/ifcopenshell/getting_started.rst index 92f89def7f..fd5166c9e4 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell/getting_started.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell/getting_started.rst @@ -12,6 +12,8 @@ The basis of all parsing and getting information from the IFC starts with obtain return 1; } +.. _schema-agnostic-parsing-of-ifcs: + Schema-agnostic parsing of IFCs ------------------------------- @@ -107,6 +109,143 @@ The following function shows how property sets can be extracted from a given ``I }); } +Reading properties and quantities from an element +--------------------------------------------------- + +A frequent point of confusion is that ``IsDefinedBy()`` does *not* return the property set +or quantity set itself. It is an inverse attribute that lists every +``IfcRelDefinesByProperties`` relationship pointing at the element, and the relationship +still needs to be unwrapped via ``RelatingPropertyDefinition()`` to reach the actual +``IfcPropertySet`` (regular properties) or ``IfcElementQuantity`` (physical quantities such +as length, area, or volume). Both classes derive from ``IfcPropertySetDefinition``, so a +single cast check tells you which one you got: + +.. code-block:: c++ + + for (auto& rel : *element.IsDefinedBy()) { + // IsDefinedBy() can also contain IfcRelDefinesByType, IfcRelDefinesByObject, or + // IfcRelDefinesByTemplate relationships, so filter for the one we want first. + auto* defines_by_props = rel->as(); + if (!defines_by_props) + continue; + + // This is the step that's easy to miss: the relationship is not the pset. + auto* pset_def = defines_by_props->RelatingPropertyDefinition(); + + if (auto* pset = pset_def->as()) { + for (auto* prop : *pset->HasProperties()) { + if (auto* single = prop->as()) { + std::cout << single->Name() << " = " + << single->NominalValue()->data().toString() << std::endl; + } + } + } else if (auto* qto = pset_def->as()) { + for (auto* quantity : *qto->Quantities()) { + if (auto* length = quantity->as()) { + std::cout << length->Name() << " = " << length->LengthValue() << std::endl; + } + // IfcQuantityArea, IfcQuantityVolume, IfcQuantityWeight, IfcQuantityCount, + // and IfcQuantityTime all follow the same pattern. + } + } + } + +There is a second, easily-missed source of properties: the element's **type**. Properties +assigned to a type (e.g. a shared "IfcDuctSegmentType") apply to every element of that type, +and are reached completely differently, through ``IsTypedBy()`` and then +``RelatingType()->HasPropertySets()`` directly, with no relationship to unwrap: + +.. code-block:: c++ + + auto* typed_by = element.IsTypedBy(); + if (typed_by && typed_by->size()) { + // Unlike IsDefinedBy(), a type only ever has one IfcRelDefinesByType relationship. + auto* type = (*typed_by->begin())->RelatingType(); + if (auto* psets = type->HasPropertySets()) { + for (auto* pset_def : *psets) { + // pset_def can again be either an IfcPropertySet or an IfcElementQuantity, + // and is handled exactly as above. + std::cout << pset_def->data().toString() << std::endl; + } + } + } + +A complete, compilable example that ties both paths together for a chosen element (here +``IfcDuctSegment``, but any IFC class works the same way) looks like this: + +.. code-block:: c++ + + #include + #include + #include + + void print_pset_or_qto(Ifc4::IfcPropertySetDefinition* pset_def) { + if (auto* pset = pset_def->as()) { + for (auto* prop : *pset->HasProperties()) { + if (auto* single = prop->as()) { + std::cout << " " << single->Name() << " = " + << single->NominalValue()->data().toString() << std::endl; + } + } + } else if (auto* qto = pset_def->as()) { + for (auto* quantity : *qto->Quantities()) { + if (auto* length = quantity->as()) { + std::cout << " " << length->Name() << " = " << length->LengthValue() << std::endl; + } + } + } + } + + int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + IfcParse::IfcFile file(argv[1]); + if (!file.good()) { + std::cerr << "Unable to parse .ifc file" << std::endl; + return 1; + } + + auto duct_segments = file.instances_by_type(); + if (duct_segments->size() == 0) { + std::cerr << "No IfcDuctSegment instances found" << std::endl; + return 1; + } + + Ifc4::IfcDuctSegment* element = *duct_segments->begin(); + std::cout << "Inspecting " << element->Name().get_value_or("") << std::endl; + + // 1. Properties and quantities attached directly to the element instance. + std::cout << "Element-level property sets:" << std::endl; + for (auto& rel : *element->IsDefinedBy()) { + if (auto* defines_by_props = rel->as()) { + print_pset_or_qto(defines_by_props->RelatingPropertyDefinition()); + } + } + + // 2. Properties and quantities inherited from the element's type, if any. + std::cout << "Type-level property sets:" << std::endl; + auto* typed_by = element->IsTypedBy(); + if (typed_by && typed_by->size()) { + auto* type = (*typed_by->begin())->RelatingType(); + if (auto* psets = type->HasPropertySets()) { + for (auto* pset_def : *psets) { + print_pset_or_qto(pset_def); + } + } + } + + return 0; + } + +.. note:: + + The example above hardcodes the ``Ifc4`` namespace for readability. See + :ref:`Schema-agnostic parsing of IFCs` above for how to + write the same logic so it works against any schema version at once. + Defensive programming with IfcOpenshell ---------------------------------------