Selector syntax

A common task in querying IFC models is to filter or search for elements which match particular criteria. For example, you might want to find all plasterboard walls with a 2 hour fire rating on level 3.

Alternatively, you might want to fetch some data about a single element. For example, you might want to fetch the fire rating property of an element, or the type description of an element, or the net volume of a list of elements.

Once you’ve retreived your data, you might want to format it in some way. You might want to ensure that all names are always uppercase. Or you might want to take length values defined in feet, and apply imperial formatting such that it shows both feet and inches including fractions.

These three usecases of filtering, getting a value, and formatting that value are common and used in many utilities, such as in Bonsai, IfcCSV, IfcDiff, IfcClash, IfcPatch, and IfcFM.

IfcOpenShell provides a custom syntax to consistently and concisely describe filters, value queries, and formatting rules.

Filtering elements

Filtering is typically used to select any IFC element or type.

import ifcopenshell
import ifcopenshell.util.selector

model = ifcopenshell.open("model.ifc")
# Get all concrete walls and slabs.
ifcopenshell.util.selector.filter_elements(model, "IfcWall, IfcSlab, material=concrete")

Example Query

Description

IfcElement

All physical IfcElements including subclasses like walls, doors, windows, etc. Yep, that’s it! Nothing else. Literally just IfcElement.

IfcWall, IfcSlab

All walls and slabs. Technically, this is either a wall or a slab, but it’s easier to describe it as all walls and slabs

IfcWall, IfcSlab, material=concrete

All walls made out of concrete and slabs made out of concrete. The material checks any assigned IfcMaterial with a matching name or category attribute.

325Q7Fhnf67OZC$$r43uzK

A single element. Yep, just the GlobalId, nothing else! Easy.

325Q7Fhnf67OZC$$r43uzK, 2VlJ7nbF5AFfQQuRvSWexT

A bunch of arbitrary elements.

IfcWall, ! 325Q7Fhnf67OZC$$r43uzK

All walls except that one element.

IfcElement, ! IfcWall

All elements except for walls.

IfcDoor, Name=D01

Any doors named D01, notice how attributes match the IFC Attribute naming exactly

IfcDoor, Name=/D[0-9]{2}$/

Any doors with the naming scheme of D followed by two numbers. The trailing $ matters: without it D123 would match too, because a regex is only anchored at the start. See Regex values are anchored at the start.

IfcWall, Pset_WallCommon.FireRating=2HR

Any 2 hour fire rated wall

IfcWall, Pset_WallCommon.ThermalTransmittance=1.5

Any wall with a U-value of 1.5. A decimal number is the one kind of value that may contain a . unquoted. See Quoting values in filters.

IfcWall, IfcColumn, IfcBeam, IfcFooting, /Pset_.*Common/.LoadBearing=TRUE

Any load bearing structure

IfcElement, /Pset_.*Common/.FireRating != NULL

Any element with a fire rating property

IfcWall, type=WT01, location="Level 3"

Any walls of wall type WT01 on level 3. We quote Level 3 because it contains a space, but a space is only one of several characters that force quoting - see Quoting values in filters.

IfcElement, classification=/Pr_.*/

Any maintainable product according to Uniclass tables

IfcWall, IfcSlab, ! 325Q7Fhnf67OZC$$r43uzK, material=concrete, /Pset_.*Common/.FireRating=2HR

Notice how there are intuitive rules that class and instance filters are OR whereas other filters are AND So here is any wall or slab except that one element that has a material of concrete and has a 2 hour fire rating

IfcSlab, material=concrete + IfcDoor

Finally, you can union facet lists together. So here is all concrete slabs, as well as all doors (regardless of concrete)

IfcDoor, IfcWindow + IfcWall, IfcSlab, material=concrete + 325Q7Fhnf67OZC$$r43uzK

Here’s another example of unioning facet groups. All doors and window, and all concrete walls and slabs, plus that one random element

IfcPump, location="Level 3"

Locations bubble up the hierarchy. So if a pump is in a space and that space is on Level 3, then you can say “all pumps on level 3” which will include that pump in the space.

IfcElement, query:"parent.Name"="My Site"

Only elements immediately under “My Site” in the spatial hierarchy. Unlike the location and parent filters, which both match at any depth, the parent query key resolves the direct parent only, so nested storeys (and their contents) are excluded.

The filter elements syntax works by specifying one or more groups of filters separated by a + character. Each filter group will return a set of filtered elements, and these are unioned together.

filter_group[ + filter_group]*

A filter group consists of one or more filters separated by a , character. The filters are chained and apply from left to right.

filter[, filter]*

Each group is evaluated independently, so a filter narrows only the group it is written in. Criteria that should apply to the whole result must be repeated in every group:

IfcWall, location="Level 3" + IfcSlab, location="Level 3"

Written as IfcWall, location="Level 3" + IfcSlab, the second group would contribute slabs from every level. There is no parenthesis syntax to factor a shared filter out of several groups.

Any part of a query may be commented out using a /* ... */ block comment. This lets you temporarily disable part of a query without deleting the text, for example IfcWall + /* IfcSlab, material=concrete */ selects only walls while keeping the slab criteria on hand. Block comments may span multiple lines.

Below is the table of filters to choose from. Most of these filters will filter previously added elements in your filter group based on their criteria.

There are two exceptions - if elements are not provided to filter_elements Class and GlobalId filters (without [!]) will add new elements to the filter group , otherwise they’ll also filter elements based on criteria.

If neither Class and GlobalId and elements are not provided then filter will search through all IfcTypeProducts and IfcProducts in the IFC project.

Filter

Type

Usage

Example

Class

Add

[!] {{ifc_class_name}}

IfcWall adds all IfcWall elements and their subclasses. ! IfcWall subtracts all non-IfcWall elements from the filter group.

GlobalId

Add

[!] {{global_id}}

325Q7Fhnf67OZC$$r43uzK adds the single element with that GlobalId attribute. ! 325Q7Fhnf67OZC$$r43uzK subtracts that single element.

Attribute

Filter

{{name}}{{=}}{{value}}

Name=Foo specifies the criteria that elements must have a Name attribute with a value of Foo. Attribute names must be spelled exactly the same as in IFC, which means that they must start with an uppercase character. For convenience, PredefinedType will be get using ifcopenshell.util.element.get_predefined_type() instead of getting the attribute directly.

Property

Filter

{{pset}}.{{prop}}{{=}}{{value}}

Pset_WallCommon.FireRating=2HR specifies the criteria that elements must have a Pset_WallCommon property set, with a FireRating property within it with a value of 2HR. The property set name and the property name are separated by a .. A property may hold more than one value, in which case the check applies to the list as a whole - see Properties with multiple values.

Type

Filter

type{{=}}{{value}}

type=Foo specifies the criteria that elements must have a type which has a Name attribute with a value of Foo.

Material

Filter

material{{=}}{{value}}

material=Foo specifies the criteria that elements must have a IfcMaterial assigned directly or indirectly (such as within a layer set). That IfcMaterial must have either a Name or Category attribute with a value of Foo.

Classification

Filter

classification{{=}}{{value}}

classification=Foo specifies the criteria that elements must have an IfcClassificationReference with an Identification attribute with a value of Foo.

Location

Filter

location{{=}}{{value}}

location=Foo specifies the criteria that elements must be contained directly or indirectly in a spatial element with a Name attribute with a value of Foo.

Parent

Filter

parent{{=}}{{value}}

parent=Foo specifies the criteria that elements must be a direct or indirect child in the spatial hierarchy to an element with a Name attribute with a value of Foo.

Query

Filter

query:{{keys}}{{=}}{{value}}

query:"types.count"=0 specifies the criteria that elements must have zero type occurrences. The query keys corresponds to the syntax used in the Getting element values section. Note that the keys are quoted: they usually contain a ., which an unquoted string may not. See Quoting values in filters.

Note

The location and parent filters both match at any depth in the spatial hierarchy. To match only elements immediately contained in (or aggregated under) a spatial element, use the parent query key, which resolves the direct parent only. For example, query:"parent.Name"="My Site" selects elements directly under My Site but excludes anything nested inside its sub-storeys or spaces.

When you specify a filter with a {{=}} check, you can choose from one of the following comparison checks:

Comparison

Description

=

Must equal the value. The data type of the value is automatically converted to match.

!=

Must not equal the value.

>

Must be greater than the value.

>=

Must be greater than or equal to the value.

<

Must be less than the value.

<=

Must be less than or equal to the value.

*=

Must contain the value.

!*=

Must not contain the value.

When you specify a {{pset}}, {{prop}}, or {{value}}, there are three ways you can do so:

Value Type

Example

Description

Quoted string

"foo \"bar\" baz"

The value must be in double quotes. The value may contain spaces, symbols, and other characters. If you need to use a double quote, you can escape it with a backslash. This is the safest, most general way to specify a value.

Unquoted string

foobarbaz

For convenience, if your value contains none of the characters listed under Quoting values in filters below, you are free to specify it as an unquoted string.

Regex string

/foo.*baz/

You may specify a Python-compatible regex pattern delimited by forward slashes. The pattern is anchored at the start but not at the end - see Regex values are anchored at the start. You can learn more about regular expressions from Beginners Regex tutorial and Online Regex testing website.

Regex values are anchored at the start

A regex value is matched with re.match(), which anchors the pattern at the start of the value but not at its end. A pattern therefore matches any value that begins with it, which is a prefix match rather than a full one:

Name=/D[0-9]{2}/     # matches D01, but also D123 and D01A
Name=/D[0-9]{2}$/    # matches D01 only

Add a trailing $ whenever you mean an exact match. This is easy to miss with values drawn from an enumeration, where /DEMOLISH/ also picks up DEMOLISHED.

Quoting values in filters

An unquoted {{pset}}, {{prop}}, {{keys}}, or {{value}} may not contain any of the following characters:

,  .  =  >  <  *  !  and whitespace

There is one exception, and it is only for a {{value}}: a value that is a plain decimal number may contain the . unquoted, so ThermalTransmittance=1.5 is fine. A {{pset}}, {{prop}}, or {{keys}} containing a . always needs quoting.

Otherwise, if yours contains one of these characters, quote it. Every one of them except , is a syntax error when left unquoted. The , is the more dangerous case, because it does not error: it is read as the separator between two filters. So Name=Foo,IfcWall does not look for the literal name Foo,IfcWall, it quietly means “named Foo and an IfcWall”. Write Name="Foo,IfcWall" to match the literal value.

The . still separates a property set from a property, so outside of a number it cannot appear in an unquoted value:

Pset_WallCommon.ThermalTransmittance=1.5    # a number, no quotes needed
Pset_WallCommon.ThermalTransmittance>-.5    # signed and leading dot too
Name=v1.2                                   # syntax error, not a number
Name="v1.2"                                 # correct

Quoting a number is still allowed and means exactly the same thing. Either way the check is not a text comparison - >, >=, <, and <= compare numerically, so ThermalTransmittance>0.9 does match a value of 1.5.

Note

Query keys obey this same rule, and they nearly always contain a ., so in practice they always need quoting. Write query:"types.count"=0. Written as query:types.count=0 it does not error, it is silently read as a property filter looking for a count property inside a property set named query:types, which is not what you asked for.

Properties with multiple values

Not every property holds a single value. An IfcPropertyEnumeratedValue or IfcPropertyListValue holds a list of them, and this is common in practice: the Status property in the standard common property sets is an enumerated value, so an element that is existing and scheduled for demolition carries both EXISTING and DEMOLISH at once.

When a property holds a list, the comparison is applied to the list as a whole rather than to a single value:

Comparison

Matches when

=

Any item in the list equals the value.

!=

No item in the list equals the value.

The same applies to the other comparisons: *= matches if any item contains the value, !*= if none do. Negation always applies to the list as a whole, so != stays the exact complement of =.

This means you match on the presence of one value and ignore whatever else sits alongside it. Given a Status of EXISTING, DEMOLISH:

IfcDoor, /Pset_.*Common/.Status=DEMOLISH     # matches, EXISTING is ignored
IfcDoor, /Pset_.*Common/.Status!=DEMOLISH    # does not match

Note that a regex value is not a way to test several values at once, because it is matched against each item separately. Status=/(EXISTING|DEMOLISH)/ matches an element whose status is only EXISTING, which is rarely what is intended.

Requiring a combination of values

Because , chains filters, repeating the same property gives you a combination. This selects only elements carrying both values, and so excludes one that is merely EXISTING:

IfcDoor, /Pset_.*Common/.Status=EXISTING, /Pset_.*Common/.Status=DEMOLISH

Excluding a combination is the inverse, and needs a union. There is no parenthesis syntax, so apply De Morgan’s law by hand - not (A and B) is (not A) or (not B):

IfcDoor, /Pset_.*Common/.Status!=EXISTING + IfcDoor, /Pset_.*Common/.Status!=DEMOLISH

That returns every door except those that are both existing and demolished. A door that is TEMPORARY, DEMOLISH is kept, because it satisfies the first group.

Warning

A + unions whole filter groups, so a filter written in one group does not constrain any other. Any criteria that should apply to the whole result has to be repeated in every group:

IfcDoor, /Pset_.*Common/.Status!=EXISTING, location!="Level 3"
 + IfcDoor, /Pset_.*Common/.Status!=DEMOLISH, location!="Level 3"

Leaving location off the second group would let doors on Level 3 back in through that group.

Getting element values

Given a single element, this syntax provides a simple way to extract a value without needing to write complex code for it.

import ifcopenshell
import ifcopenshell.util.selector

# Get the Name attribute of the wall's type.
ifcopenshell.util.selector.get_element_value(wall, "type.Name")

Example Query

Description

class

Get the IFC class of the element.

Name

Get the Name attribute.

Pset_WallCommon.Status

Get the value of the Status property in the Pset_WallCommon property set.

/Pset_.*Common/.Status

Get the value of the Status property in the any common property set.

type.Name

Get the Name attribute of the element’s relating type.

types.count

Count the number of occurrences of a type.

storey.Name

Get the Name attribute of the storey that the element is contained in.

materials.count

Count the number of materials assigned to an element.

material.Name

IfcMaterial: The name of the assigned material. IfcMaterialLayerSet: name of the LayerSetName. IfcMaterialProfileSet: The name of the overall material profile set. IfcMaterialConstituent: The name of the overall material constituent set.

material.item.0.Name

IfcMaterial: N/A. IfcMaterialLayerSet: The name of the 1st material layer. IfcMaterialProfileSet: The name of the 1st material profile. IfcMaterialConstituent: The name of the 1st material constituent.

material.item.Material.Name.0

IfcMaterial: The assigned material name. IfcMaterialLayerSet: The material name of the 1st material layer. IfcMaterialProfileSet: The material name of the 1st material profile. IfcMaterialConstituent: The material name of the 1st material constituent.

The element value syntax works by specifying one or more query keys separated by a . character. Each query key returns data based of the results of the previous key.

key[.key]*

Valid keys are:

Key

Description

id

Gets the IFC ID (equivalent to .id())

class

Gets the IFC class (equivalent to .is_a())

predefined_type

Gets the predefined type of the element, taking into account inheritance.

{{attribute}}

Gets the value of the attribute you specify. Attributes always start with an uppercase letter.

{{pset}}

This gets the property set with the same name specified in {{pset}}. Note that this can be ambiguous with {{attribute}}. If there is an ambiguity, {{attribute}} takes priority.

{{prop}}

If the previous key returns a property set, {{prop}} gets the value of a property with the same name specified in {{prop}}. For this reason, often you specify both keys together, like this: {{pset}}.{{prop}}.

type

Gets the relating type of an element occurrence.

types or occurrences

Gets the related objects of an element type.

container

Gets the immediate spatial element that an element is contained in.

space

Gets the first IfcSpace spatial element that an element is contained in.

storey

Gets the first IfcBuildingStorey spatial element that an element is contained in.

building

Gets the first IfcBuilding spatial element that an element is contained in.

site

Gets the first IfcSite spatial element that an element is contained in.

parent

Gets the immediate parent element in the spatial hierarchy (the direct spatial container, or the direct aggregate/nest/fill/void parent). Combine with .Name in a query filter to match only immediate children, e.g. query:"parent.Name"="My Site".

classification

Gets the element’s classification reference(s)

group

Gets the element’s group(s)

system

Gets the element’s system(s). This is a subset of group(s).

zone

Gets the element’s zone(s). This is a subset of group(s).

material or mat

Gets the assigned material, which may be a material set.

item or i

If the previous key returns a material set, gets the relevant material set items

materials or mats

Gets a list of IfcMaterials assigned directly or indirectly (such as via a material set) to the element

profiles

Gets a list of IfcProfileDefs assigned (such as via a material profile) or used (such as in an extrusion) in the element

x

Gets the X coordinate of the element’s placement

y

Gets the Y coordinate of the element’s placement

z

Gets the Z coordinate of the element’s placement

easting

Gets the map easting of the element’s placement

northing

Gets the map northing of the element’s placement

elevation

Gets the map elevation of the element’s placement

rotation_x

Gets the X Euler rotation of the element’s placement in degrees

rotation_y

Gets the Y Euler rotation of the element’s placement in degrees

rotation_z

Gets the Z Euler rotation of the element’s placement in degrees (e.g. plan rotation of a symbol)

count

If the previous key returns multiple things, count that list. Otherwise, return 1.

{{number}}

If the previous key returns multiple things, fetch the {{number}} index (e.g. 0, 1, 2, 3, etc) item in that list.

When you specify a {{pset}} or {{prop}}, there are three ways you can do so:

Value Type

Example

Description

Quoted string

"foo \"bar\" baz"

The value must be in double quotes. The value may contain spaces, symbols, and other characters. If you need to use a double quote, you can escape it with a backslash. This is the safest, most general way to specify a value.

Unquoted string

foobarbaz

For convenience, if your key contains none of the characters listed under Quoting keys in value queries below, you are free to specify it as an unquoted string.

Regex string

/foo.*baz/

You may specify a Python-compatible regex pattern delimited by forward slashes. You can learn more about regular expressions from Beginners Regex tutorial and Online Regex testing website.

Quoting keys in value queries

The characters that need quoting here are not the same set as in Quoting values in filters. An unquoted key may not contain any of:

.  =  /  and whitespace

All four are a syntax error when left unquoted, so there is no silent misreading to worry about in this position. The . is the key separator and the / delimits a regex, so a property set or property whose own name contains either - or a space - has to be quoted:

"Fire Rating Data".FireRating

The remaining characters that force quoting in a filter - ,, >, <, *, and ! - are accepted unquoted here.

Formatting

Given a value, this syntax allows a simple way to specify a set of formatting rules. This is useful for configuring outputs of how data should be presented.

import ifcopenshell
import ifcopenshell.util.selector

# Get the Name attribute of the wall's type.
value = ifcopenshell.util.selector.get_element_value(wall, "type.Name")
# Always display names in uppercase.
ifcopenshell.util.selector.format(f'upper("{value}")')

Formatting queries are written similar to how you’d write functions or formulas in spreadsheets. For example upper("foo") will produce FOO. You may nest formulas, for example concat(title("foo"), lower("Bar")) will produce Foobar. Strings must be double quoted.

Function

Example

Result

Description

upper({{value}})

upper("Foo")

FOO

Uppercases a string.

lower({{value}})

lower("Foo")

foo

Lowercases a string.

title({{value}})

title("foo")

Foo

Titlecases a string.

concat({{value}}[, {{value2}}]*)

concat("foo", "bar")

foobar

Concatenates two or more strings.

round({{value}}, {{precision}})

round(3.123, 0.1)

3.1

Rounds {{value}} to the nearest {{precision}}.

int({{value}})

int(3.123)

3

Truncates the decimal part of the {{value}}.

number({{value}}[, {{decimal_separator}}[, {{thousands_separator}}]])

number(1234.56, ",", ".")

1.234,56

Formats {{value}} with an optional custom {{decimal_separator}} and {{thousands_separator}}. The default separators are . and ,.

metric_length({{value}}, {{precision}}, {{decimals}})

metric_length(3.123, 0.1, 2)

3.10

Rounds {{value}} to the nearest {{precision}} then displays using a certain amount of decimal places.

imperial_length({{value}}, {{precision}}, {{input_unit}}, {{output_unit}}, {{suppress_zero_inches}})

imperial_length(3.0, 4, "foot", "foot", true) OR imperial_length(3.0, 4, "foot", "foot", false)

3' OR 3' - 0"

The {{value}} may be specified either as foot or inch depending on {{input_unit}}. The {{value}} is then rounded to the nearest 1/{{precision}} inch, then formatted using fractional feet and inches if {{output_unit}} is set to foot, or just inches if {{output_unit}} is set to inch. When {{suppress_zero_inches}} is true (default), measurements with zero inches will omit the inch portion (e.g., 3' instead of 3' - 0").

sort({{values}})

sort({{mats.Name}})

Name1, Name2

Sorts a list of items.

reverse({{values}})

reverse({{mats.Name}})

Name2, Name1

Reverses a list of items.

join({{separator}}, {{values}})

join("-", {{mats.Name}})

Name1-Name2

Joins a list of items with a custom separator. By default, all lists a rendered as comma separated.

{{value1}}[+-*/]{{value2}}

{{z}}+3

5

Does arithmetic. Typical operators such as +, -, *, and / are allowed and can be mixed with other variables and formatting functions.

When using queries in an IfcAnnotation tag surround with backticks. Examples:

  • ``number({{Qto_WallBaseQuantities.Width}}, ",",".")``

  • ``round({{Qto_BuildingElementProxyQuantities.NetVolume}},.1)``

  • ``join(", OVER ", reverse({{material.item.Material.Name}}))``