← Back to Knowledge Hub Module 9 → 9.3

9.3 Extracting Properties and Quantities with Code


Introduction

The last lesson stopped deliberately at direct attributes, values like Name and GlobalId sitting right on an entity, reachable with plain dot notation. Most of what makes an IFC element actually useful, a wall's fire rating, a column's concrete grade, a slab's net volume, doesn't live that way. It lives in separate entities connected to the element through a relationship, and reaching it takes real, specific code. This lesson covers exactly that: how properties and quantities actually attach to an element in the schema, and the correct way to pull them out.

Where Properties and Quantities Actually Live

An element doesn't carry its properties directly. It's linked to them through a relationship entity called IfcRelDefinesByProperties, which connects the element to a property set definition. That definition can be either of two entity types: an IfcPropertySet, holding ordinary named properties, or an IfcElementQuantity, holding quantities specifically, net volumes, areas, lengths, and the like. Both are valid property-definition targets of IfcRelDefinesByProperties, different entity types with different internal structures, but reachable through the exact same relationship, which is why properties and quantities can be pulled out through one mechanism rather than two unrelated ones.

Walking this manually looks like this:

for relationship in wall.IsDefinedBy:
    if relationship.is_a("IfcRelDefinesByProperties"):
        definition = relationship.RelatingPropertyDefinition
        print(definition.Name)

That loop is real and it works, and seeing it once is worth doing, because it shows exactly what's happening underneath: IsDefinedBy exposes the inverse relationships through which the object is defined, filtering it for IfcRelDefinesByProperties isolates the property-definition relationships specifically, and RelatingPropertyDefinition is the actual property set or quantity set on the other end. But writing this loop by hand every time is not the practical way to work, and IfcOpenShell provides something better.

The Practical Way: ifcopenshell.util.element.get_psets()

import ifcopenshell.util.element

psets = ifcopenshell.util.element.get_psets(wall)

This one call does the relationship-walking above internally and returns a dictionary: property set names as keys, and for each one, a dictionary of property or quantity names and their values. By default it returns everything attached to the element, both properties and quantities together, and it also includes property sets inherited from the element's type where applicable, not only ones assigned directly to the specific occurrence.

Two optional flags narrow the result to one kind or the other:

properties_only = ifcopenshell.util.element.get_psets(wall, psets_only=True)
quantities_only = ifcopenshell.util.element.get_psets(wall, qtos_only=True)

psets_only=True restricts the result to sets originating from IfcPropertySet. qtos_only=True restricts it to sets originating from a quantity set like IfcElementQuantity. Same function, same underlying relationship, two different filters over what comes back.

Worth knowing about too: when the exact property set and property name are already known in advance, ifcopenshell.util.element.get_pset(element, name, prop=None) gives a more targeted, more efficient lookup than pulling every set with get_psets() and then picking one out. This lesson sticks with get_psets() since it's the right tool for exploring an unfamiliar file, but get_pset() is worth reaching for once a script knows exactly what it's after.

Properties and Quantities, Seen in Returned Data

The dictionary structure looks the same regardless of which kind of set it came from, which is worth seeing directly rather than just described. For example, a returned property set could look like this:

{
    "Pset_WallCommon": {
        "id": 173875,
        "IsExternal": True,
        "FireRating": "2HR"
    }
}

And a quantity set, retrieved the same way with qtos_only=True, could look like this:

{
    "Qto_WallBaseQuantities": {
        "id": 173877,
        "NetSideArea": 4.2,
        "GrossVolume": 0.84
    }
}

These IDs and values are illustrative, not universal, and a real file will return whatever its own sets actually contain. Nothing about the dictionary's structure tells you which kind of set you're looking at beyond the names themselves. Named sets starting with Pset_ and Qto_ follow IFC's own naming convention for standardized property and quantity sets, but the prefix itself isn't what makes a set standardized, and a name alone shouldn't be treated as proof of it. A project can define its own custom sets with entirely different naming, which is part of why filtering by kind through psets_only or qtos_only is the more reliable approach rather than relying on naming patterns.

Every returned set also carries an id key, an IFC entity ID associated with the returned definition. This can be useful when tracing a value back into the IFC graph, though inherited type properties need a bit more care when interpreting that ID, since an inherited set's id reflects the occurrence rather than necessarily the type's own original property set. Worth knowing this exists rather than assuming every id traces to exactly one unambiguous place.

A Complete Script

Putting this together with what 9.2 already built:

import ifcopenshell
import ifcopenshell.util.element

model = ifcopenshell.open("/path/to/model.ifc")

walls = model.by_type("IfcWall")

for wall in walls:
    quantities = ifcopenshell.util.element.get_psets(wall, qtos_only=True)
    qto = quantities.get("Qto_WallBaseQuantities")
    if qto:
        print(wall.Name, "-", qto.get("NetVolume"))
    else:
        print(wall.Name, "- no base quantities found")

This opens a file, goes through every wall, and prints its net volume where a base quantity set exists for it. Run against a real, well-exported file, most walls will print a number. Some might not, and that's not a bug in this script. Retrieving NetVolume here confirms the value is present and reachable, nothing more. Whether that value is itself trustworthy is a separate question this lesson deliberately doesn't answer yet.

What's Next

That else branch above isn't decoration. Not every element in a real file has the quantity set a script expects, values can come back as None even when a set exists, and property and quantity values need to be interpreted against their own IFC unit definitions, which can vary by model and can be set explicitly at the property level rather than assumed from the project default. The next lesson goes into exactly these failure modes, the ones a clean example like this one is quietly avoiding.

Key Takeaways

  • Properties and quantities aren't direct attributes. They're reached through IfcRelDefinesByProperties, a relationship whose RelatingPropertyDefinition can be either an IfcPropertySet (properties) or an IfcElementQuantity (quantities), two different entity types both reachable through the same relationship.
  • ifcopenshell.util.element.get_psets(element) walks that relationship correctly and returns a dictionary of set names to their contents, including type-inherited sets by default. get_pset() offers a more targeted lookup when the exact set and property are already known.
  • psets_only=True and qtos_only=True filter the result to properties or quantities specifically. Without either, both come back together in the same dictionary.
  • Returned sets each include an id key tied to the underlying IFC entity, though inherited sets need extra care when interpreting that ID, since it reflects the occurrence rather than always the type's original set.
  • Pset_ and Qto_ prefixes follow IFC's naming convention for standardized sets, but the name alone doesn't guarantee standardization, and projects can define entirely custom sets, which is why filtering by kind is more reliable than filtering by name.
  • Retrieving a quantity confirms it's present and reachable. It says nothing about whether the value itself is trustworthy, and values also need interpreting against their own IFC unit definitions, both of which the next lesson addresses directly.

Discuss this lesson

Comments use a free GitHub account — takes under a minute to create, and keeps discussions spam-free and permanently archived.