New IFC facet-based selector syntax

edited July 2023 in General

Some of you may be familiar with the selector syntax. It looks like this: .IfcWall means you want to select all IfcWalls (and subtypes). .IfcWall[Name=Foo] means you want to select all walls named Foo. It is used in a number of places: drawing object filters, model loading filters, clash set filters, IfcCSV export filters, Blender saved search groups, IfcPatch recipes, etc.

The selector syntax was created at a time when nothing existed and I tried to do my best to come up with a way to select objects that could handle attributes and properties, and slowly things like materials, guids, and so on were bolted on. Since then, I have learned a lot about how people have used the syntax and I feel as though it has a lot of flaws that make it inconvenient. In addition, a lot of people have been asking for an improved user interface for these selectors and also the ability to have saved searches. There is a really complex UI already built with a lot of effort but I feel it could be heavily simplified:

I've been working on redesign a new selector syntax intended to supersede the old one for most common users. The goals is to be simple to read, simple to write, and much more useful with the tradeoff that it is less explicit and doesn't accommodate complex AND/OR groupings. It is inspired by the "facets" in IDS, which have been designed over many months by buildingSMART working groups who have collectively worked out usecases on how to do "applicable entity filters". Although it is very similar to IDS (which is another benefit, since IDS specifies contractual requirements you want to be able to recreate those applicability filters) it also contains a few upgrades of my own (my personal complaints about the shortcomings in IDS).

It works like this, you can specify a list of "filters" separated by commas. A filter can filter an individual instance, a class, attribute, type, material, property, classification, or location. Filters are chained one after another. Let's see some examples:

# All IfcElements. Yep, that's it! Nothing else. Literally just "IfcElement".
IfcElement

# All walls and slabs.
IfcWall, IfcSlab

# All walls and slabs made out of concrete (checks any IfcMaterial with matching name or category)
IfcWall, IfcSlab, material=concrete

# A single element. Yep, just the GlobalId, nothing else! Easy.
325Q7Fhnf67OZC$$r43uzK

# A bunch of arbitrary elements.
325Q7Fhnf67OZC$$r43uzK, 2VlJ7nbF5AFfQQuRvSWexT

# All walls except that one element.
IfcWall, ! 325Q7Fhnf67OZC$$r43uzK

# All elements except for walls.
IfcElement, ! IfcWall

# Any doors named D01, notice how attributes match the IFC Attribute naming exactly
IfcDoor, Name=D01

# Any doors with the naming scheme of D followed by two numbers:
IfcDoor, Name=/D[0-9]{2}/

# Any 2 hour fire rated wall
IfcWall, Pset_WallCommon.FireRating=2HR

# Any load bearing structure
IfcWall, IfcColumn, IfcBeam, IfcFooting, /Pset_.*Common/.LoadBearing=TRUE

# Any element with a fire rating property
IfcElement, /Pset_.*Common/.FireRating != NULL

# Any walls of wall type WT01 on level 3 (we quote Level 3 since it has a space)
IfcWall, type=WT01, location="Level 3"

# Any maintainable product according to Uniclass tables
IfcElement, classification=/Pr_.*/

# 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
IfcWall, IfcSlab, ! 325Q7Fhnf67OZC$$r43uzK, material=concrete, /Pset_.*Common/.FireRating=2HR

# Finally, you can union facet lists together. So here is all concrete slabs, as well as all doors (regardless of concrete)
IfcSlab, material=concrete + IfcDoor

# Here's another example of unioning facet groups.
# All doors and window, and all concrete walls and slabs, plus that one random element
IfcDoor, IfcWindow + IfcWall, IfcSlab, material=concrete + 325Q7Fhnf67OZC$$r43uzK

# 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.
IfcPump, location="Level 3"

I hope you can see from the sample that it is a lot more intuitive and uses names rather than GlobalIds were possible (in the old selector for example selecting things like "stuff on level 1" is very difficult). It's also much easier to read (no nested brackets () and | and & and little dots . that people keep on missing) and easier to copy paste (no # prefix on GlobalIds). Despite the lack of explicit AND/OR I believe the chaining and unions should satisfy most usecases. It's also "shorter" for many usecases compared to the old selector. Also you can either leave values unquoted e.g. material=concrete or quoted material="Isn't this great" or regex material=/CON[0-9]*/.

This also means that it will now be easier to make a gentler UI for it :) Saved searches here we come!

NigelCoentheoryshawCyrilthunderbolt_132atomkarincaDarth_BlenderArvAceCGRand 6 others.
«13

Comments

  • Looks good, though I think OR/AND/NOT/() stuff is familiar to people who use Excel etc.. so this would be justifiable.

    I'm happy to see regular expressions, but since they are demarcated with // you could also seamlessly support globbing with bare * and ? characters

    theoryshaw
  • I also like that the selector is heavily simplified. But I find it hard to distinguish between OR and AND. Classes are always "OR" (as they cannot be AND). But what if we need all elements that are material = concrete OR name=/concrete/? Would that be IfcElement, material=concrete + IfcElement, name=/concrete/?
    (maybe answering my own question here...).
    What are the differences to IDS (and the mentioned shortcomings)?

  • Cheers, there is actually still OR and AND, just that it isn't explicit and you can't put brackets (especially because I've found many AEC folks actually get confused between OR and AND - like they might say I want walls and doors, this is actually "or"). For example, within a single group of filters, chained instances or entity facets are "OR" whereas other chained facets (property, etc) are "AND". Multiple filter groups are always joined with "OR".

    Indeed globbing could be supported. I think this is a first pass and it would naturally evolve over time.

    @tobenz you're right that classes are always OR, everything else is AND. In IDS everything is AND, so your answer is absolutely correct. material=concrete + IfcElement, name=/concrete/

    IDS differences:

    1. Classes are OR, instead of AND. In IDS classes are AND which are pretty useless.
    2. You can select instances by GlobalID. IDS doesn't have this capability. Naturally instances are also OR.
    3. You can select types by name. IDS doesn't have this capability.
    4. Instead of a "partof" facet we have a "location" facet which is specific to containment location, not aggregation / nesting / etc.
    5. You can explicitly filter NULL instead of empty string.
    6. IDS cannot union facet groups. This is a huge limitation.

    For anybody who wants to check out what this means from a UI perspective it's now been implemented for searches. You can load and save these search queries.

    On a related note you can also store (generated / custom) colour legends based on properties / attributes / etc.

    tobenzbrunopostleNigelAceBimlooseratomkarincatheoryshawCGRSigmaDimensionsMassimo
  • Thanks for explaining this in detail! Can the saved searches be reused / referenced in other searches? Same for filtering: chained filters would be quite useful for visualizing classification hierarchies (e.g. by first, then second, third... character of the classification code). Something similar to the outliner but for e.g. omniclass code?

  • Wow this is awesome. I've been wanting something like that for a long time !!
    Just a heads-up for everyone, don't forget that every UI list in Blender has a handy filtering system if you click on the arrow in the lower left ;)


    theoryshawtobenzNigelCoenAceatomkarincaMassimobdamay
  • Amazing. Some questions:
    1. Is it possible to ask for instance to select all IfcSlabs with .NetArea more than a specific amount of square meters? How to deal with quantities requests?
    2. In my opinion the old selector was useful to copy/paste query syntax to use in drawing tags too: e.g. {{Qto_SpaceBaseQuantities.NetFloorArea}} in order to get Spaces's NetArea. Afaik now using traditional selector doesn't display the query syntax. Is there a possibility to find it somewhere?
    Thanks

    atomkarincaAceCGR
  • Quantity filtering is not yet possible, but we should definitely implement it.

    Yes, we definitely need an advanced mode to write custom queries. You can however find the query in the IfcGroup description but it's a bit hard to copy paste from there.

  • Yes, we definitely need an advanced mode to write custom queries. You can however find the query in the IfcGroup description but it's a bit hard to copy paste from there.

    Got it, i find it. Thanks

  • Is there a way to just 'count' the instances that have Pset_DoorCommon.Status = NEW?

    The following counts all the instances in the project, including Pset_DoorCommon.Status = EXISTING

    MassimoAcetim
  • Wrote a little LibreCalc spreasheet (attached) that converts the old selector syntax to the new facet syntax.
    It's probably not foolproof, so please improve if you come upon any unique problems.

    Acebruno_perdigaoatomkarincaGorgious
  • @theoryshaw said:
    Wrote a little LibreCalc spreasheet (attached) that converts the old selector syntax to the new facet syntax.
    It's probably not foolproof, so please improve if you come upon any unique problems.

    Thankyou !

  • edited August 2023

    Using this the drawing's Exclude property...
    IfcGeographicElement+IfcFurniture,PredefinedType=CHAIR+IfcFurniture,PredefinedType=TABLE
    I get the following error.
    Am i missing something?

    Python: Traceback (most recent call last):
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\bim\module\drawing\operator.py", line 1461, in invoke
        return self.execute(context)
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\bim\module\drawing\operator.py", line 1470, in execute
        core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing)
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\core\drawing.py", line 429, in activate_drawing_view
        drawing_tool.activate_drawing(camera)
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\tool\drawing.py", line 1620, in activate_drawing
        filtered_elements = cls.get_drawing_elements(drawing) | cls.get_drawing_spaces(drawing)
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\tool\drawing.py", line 1519, in get_drawing_elements
        elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\libs\site\packages\ifcopenshell\util\selector.py", line 126, in filter_elements
        transformer.transform(filter_elements_grammar.parse(query))
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\libs\site\packages\lark\lark.py", line 333, in parse
        return self.parser.parse(text, start=start)
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\libs\site\packages\lark\parser_frontends.py", line 194, in parse
        return self._parse(text, start)
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\libs\site\packages\lark\parser_frontends.py", line 54, in _parse
        return self.parser.parse(input, start, *args)
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\libs\site\packages\lark\parsers\earley.py", line 292, in parse
        to_scan = self._parse(stream, columns, to_scan, start_symbol)
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\libs\site\packages\lark\parsers\xearley.py", line 138, in _parse
        to_scan = scan(i, to_scan)
      File "C:\Users\Owner\AppData\Roaming\Blender Foundation\Blender\3.6\scripts\addons\blenderbim\libs\site\packages\lark\parsers\xearley.py", line 115, in scan
        raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect.name for item in to_scan}, set(to_scan))
    lark.exceptions.UnexpectedCharacters: No terminal defined for '=' at line 1 col 83
    
    edType=CHAIR+IfcFurniture,PredefinedType=TABLE
                                            ^
    
    Expecting: {'COMMA', 'PLUS'}
    
    
  • Upon further troubleshooting found out spaces are important around +'s and I think ,'s.
    this doesn't work.
    IfcGeographicElement+IfcFurniture,PredefinedType=CHAIR+IfcFurniture,PredefinedType=TABLE
    this works
    IfcGeographicElement + IfcFurniture,PredefinedType=CHAIR + IfcFurniture,PredefinedType=TABLE

  • Fixed spreadsheet too.

  • Might this...

    be better placed in the BlenderBIM Add-on Sphinx documentation and expanded with additional examples covering enhancements made since the initial posting?

  • edited August 2023

    I see a commit on this: src/ifcopenshell-python/docs/ifcopenshell-python.rst (thanks, @Moult) but I don't see it listed under the BlenderBIM Add-on Sphinx user documentation. I am confused.

  • Looking through the documentation in the commit, it would appear that it is not possible to have a query such as "Doors to be installed over the next six months". Am I correct?

  • @Lurker it's in the IfcOpenShell docs: https://blenderbim.org/docs-python/ifcopenshell-python/selector_syntax.html because it is used in multiple tools and available in IfcOpenShell-Python.

    Correct, it's not (yet) possible. It's pretty easy to add though. Would you like to try?

  • edited August 2023

    @Moult said:
    @Lurker it's in the IfcOpenShell docs: https://blenderbim.org/docs-python/ifcopenshell-python/selector_syntax.html because it is used in multiple tools and available in IfcOpenShell-Python.

    So user documentation is split between IfcOpenShell docs (have to know the URL?), the Sphinx documentation (accessible from the BlenderBIM Add-on website), and some residual stuff on the OSArch wiki. I understand the rationale, but it would be more convenient if the Sphinx documentation at least had a link to the IfcOpenShell docs (or multiple links, one link whenever the selection system is used within a BlenderBIM Add-on feature). Similarly, if there is a good reason to have something on the OSArch wiki, leave it there and insert a link into the Sphinx documentation. In this way, the Sphinx documentation becomes the first place to find information.

    Correct, it's not (yet) possible. It's pretty easy to add though. Would you like to try?

    >
    Beyond my pay grade (I would have to learn Python first) :(. My question was more of a clarification rather than a request for an enhancement.

    I see the selector tool as eventually being the core of a powerful custom report generator.

  • edited August 2023

    Hi, i'm trying to use the new facet-based selector in the UI but i have a question: if i want to select all the walls that are typed by "IfcWallType/WAL300", what have i to write?
    I tried selecting Type from the menu and writing "type=WAL300" but it seems that it doesn't work because it returns 0 elements instead of 6 elements...also, if i write only "WAL300" i get an error...am i missing something?
    UPDATE: Nevermind, i was using it in a wrong way...i didn't change the facet so i was searching for a type in a "class" facet i had created before...

  • @Massimo did you have a class selector first? E.g. IfcWall, type=WAL300

    @Lurker the IfcOpenShell docs is accessible from https://ifcopenshell.org in the same way that the BlenderBIM Add-on docs are accessible from https://blenderbim.org/ - the official docs should always be the first point of call, with external resources like the OSArch Wiki being a backup.

    Massimo
  • @Moult thanks for the answer, but i was using it in a wrong way..i was writing every time with always the "class" facet selected...

  • @Moult said:
    @Lurker it's in the IfcOpenShell docs: https://blenderbim.org/docs-python/ifcopenshell-python/selector_syntax.html because it is used in multiple tools and available in IfcOpenShell-Python.

    nice!.. i'll take down the wiki page then.

  • Is there an equivalent for...

    ``int({{Qto_SpaceBaseQuantities.NetFloorArea}})`` SF
    

    in the new facet system?
    I tried...

    ``round({{Qto_SpaceBaseQuantities.NetFloorArea}})`` SF
    

    but it doesn't work.
    The following works though, but I'd like to remove the decimal place.

    ``round({{Qto_SpaceBaseQuantities.NetFloorArea}},.1)`` SF
    
  • The round function means "round to the nearest X". This means that rounding to 1 is probably what you want :)

    ``round({{Qto_SpaceBaseQuantities.NetFloorArea}},1)``
    
    tim
  • edited September 2023

    I had tried that, but it still has the decimal in it.

  • Now fixed I believe :)

    theoryshawAce

  • Regarding query-syntax, the '>=' sign doesn't seem to work

Sign In or Register to comment.