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 |
|---|---|
|
All physical IfcElements including subclasses like walls, doors, windows, etc. Yep, that’s it! Nothing else. Literally just |
|
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 |
|
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. |
|
A single element. Yep, just the GlobalId, nothing else! Easy. |
|
A bunch of arbitrary elements. |
|
All walls except that one element. |
|
All elements except for walls. |
|
Any doors named D01, notice how attributes match the IFC Attribute naming exactly |
|
Any doors with the naming scheme of D followed by two numbers. The trailing |
|
Any 2 hour fire rated wall |
|
Any wall with a U-value of 1.5. A decimal number is the one kind of value that may contain a |
|
Any load bearing structure |
|
Any element with a fire rating property |
|
Any walls of wall type WT01 on level 3. We quote |
|
Any maintainable product according to Uniclass tables |
|
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 |
|
Finally, you can union facet lists together. So here is all concrete slabs, as well as all doors (regardless of concrete) |
|
Here’s another example of unioning facet groups. All doors and window, and all concrete walls and slabs, plus that one random element |
|
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. |
|
Only elements immediately under “My Site” in the spatial hierarchy. Unlike the |
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 |
|
|
GlobalId |
Add |
|
|
Attribute |
Filter |
|
|
Property |
Filter |
|
|
Type |
Filter |
|
|
Material |
Filter |
|
|
Classification |
Filter |
|
|
Location |
Filter |
|
|
Parent |
Filter |
|
|
Query |
Filter |
|
|
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 |
|
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 |
|
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 |
|
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 |
|---|---|
|
Get the IFC class of the element. |
|
Get the |
|
Get the value of the |
|
Get the value of the |
|
Get the |
|
Count the number of occurrences of a type. |
|
Get the |
|
Count the number of materials assigned to an element. |
|
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. |
|
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. |
|
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 |
|---|---|
|
Gets the IFC ID (equivalent to |
|
Gets the IFC class (equivalent to |
|
Gets the predefined type of the element, taking into account inheritance. |
|
Gets the value of the attribute you specify. Attributes always start with an uppercase letter. |
|
This gets the property set with the same name specified in |
|
If the previous key returns a property set, |
|
Gets the relating type of an element occurrence. |
|
Gets the related objects of an element type. |
|
Gets the immediate spatial element that an element is contained in. |
|
Gets the first IfcSpace spatial element that an element is contained in. |
|
Gets the first IfcBuildingStorey spatial element that an element is contained in. |
|
Gets the first IfcBuilding spatial element that an element is contained in. |
|
Gets the first IfcSite spatial element that an element is contained in. |
|
Gets the immediate parent element in the spatial hierarchy (the direct spatial container, or the direct aggregate/nest/fill/void parent). Combine with |
|
Gets the element’s classification reference(s) |
|
Gets the element’s group(s) |
|
Gets the element’s system(s). This is a subset of group(s). |
|
Gets the element’s zone(s). This is a subset of group(s). |
|
Gets the assigned material, which may be a material set. |
|
If the previous key returns a material set, gets the relevant material set items |
|
Gets a list of IfcMaterials assigned directly or indirectly (such as via a material set) to the element |
|
Gets a list of IfcProfileDefs assigned (such as via a material profile) or used (such as in an extrusion) in the element |
|
Gets the X coordinate of the element’s placement |
|
Gets the Y coordinate of the element’s placement |
|
Gets the Z coordinate of the element’s placement |
|
Gets the map easting of the element’s placement |
|
Gets the map northing of the element’s placement |
|
Gets the map elevation of the element’s placement |
|
Gets the X Euler rotation of the element’s placement in degrees |
|
Gets the Y Euler rotation of the element’s placement in degrees |
|
Gets the Z Euler rotation of the element’s placement in degrees (e.g. plan rotation of a symbol) |
|
If the previous key returns multiple things, count that list. Otherwise, return 1. |
|
If the previous key returns multiple things, fetch the |
When you specify a {{pset}} or {{prop}}, there are three ways you can
do so:
Value Type |
Example |
Description |
|---|---|---|
Quoted string |
|
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 |
|
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 |
|
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 |
|---|---|---|---|
|
|
|
Uppercases a string. |
|
|
|
Lowercases a string. |
|
|
|
Titlecases a string. |
|
|
|
Concatenates two or more strings. |
|
|
|
Rounds |
|
|
|
Truncates the decimal part of the |
|
|
|
Formats {{value}} with an optional custom {{decimal_separator}} and {{thousands_separator}}. The default separators are |
|
|
|
Rounds |
|
|
|
The |
|
|
|
Sorts a list of items. |
|
|
|
Reverses a list of items. |
|
|
|
Joins a list of items with a custom separator. By default, all lists a rendered as comma separated. |
|
|
|
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}}))``