If you're reading this, we've just migrated servers! If anything looks broken please email dion@thinkmoult.com :)

Relationships in IFC

edited September 2021 in General

Hi Everyone,

I am one of the developers of Ladybug Tools Legacy plugins and currently, I am working on creating a translator from IFC to HBJSON. I have been using ifcOpenShell in Python 3.7.0 and I have been successful in extracting triangulated mesh geometry from IFC elements so far.

Before writing this post, I have also read all discussion here and looked at code examples here.

I am iterating over elements in an IFC file using ifcOpenShell and I am struggling to find how I can know the parent IfcSpace and/or IfcWall for an IfcWindow element. Any direction on this will be very helpful.

Thanks!

Tagged:
bitacovir

Comments

  • @devngc use ifcopenshell.util.element.get_container(wall_or_window) :)

  • edited September 2021

    Thanks @Moult! That is giving me IfcBuildingStorey element. I am trying to get IfcSpace that an IfcWindow is attached to. Please see the screen shot from Solibri below. Solibri is able to tell that the selected window (highlighted in green) belongs to an IfcSpace Office[109]. How can we find this relationship using ifcOpenShell?

  • The reverse could also work for me. If there's a way to know the IfcOpeningElement, IfcWindow, and IfcDoor elements related to an IfcSpace. That would also work for me.

  • edited September 2021

    @devngc if it gives you the IfcBuildingStorey element, that means that the window is contained in the storey, not the space. This is fairly typical of BIM applications - because in theory the window is not "inside" the space. From an FM perspective and architectural room data sheet perspective, this is wrong, a window should belong to the primary space through which you should access it for maintenance purposes. However, the sad reality is that many objects are not properly contained in our BIM data.

    The "Nearest Spaces" shown in Solibri is not an official IFC relationship. Using Solibri as a viewer may mislead people into thinking that certain relationships and data exist in IFC where it actually does not, and is simply something that Solibri derives.

    Can you share your file? I might be able to suggest a way to derive it inexpensively.

  • Also, ping @aothms as he has quite a few clever tricks and has a lot of experience in analysing these types of data.

  • Thank you! Here's the file that you see loaded in Solibri.

  • Cheers, there are no explicit relationships in the IFC that I can see that link the window to the space.

    One possibility of how Solibri is deriving this, is looking at the ObjectPlacement of the window (i.e. its origin point) and the ObjectPlacement of each space, and measuring the closest space. This obviously has room for error, but indeed I get the same result as Solibri if I derive it this way. If you want to, you can find the matrix of the ObjectPlacement of any element (wall, window, etc) using ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement). The advantage of this method is that it is the cheapest.

    Another potentially more accurate way is to use the actual geometry of the window. Given that windows do not intersect with the space, thinking out loud perhaps you could find the normal and centroid of every face in an IfcSpace, and then a normal and centroid of the largest face for each window. Then, by comparing normals and centroids, you can map windows to spaces. Hopefully, this is not too expensive, as windows are typically mapped so you calculate once, then many windows should share the same mapped instance of the geometry. Happy to demonstrate how to do this if you want to go down this route.

    I should note that the file does have space boundaries, which is what you really want - proper explicit stuff for energy analysis, not guesswork and deriving. However, it only has space boundaries linking the space to the slab, not the space to the window.

  • edited September 2021

    Hopefully, this is not too expensive,

    Thanks, @Moult. I believe it will be too expensive and that's the reason I am seeking help here to not go that route. For Pollination, we have to think about the scale of models people will bring. I have seen neighborhood-scale energy models and doing this exercise of finding the nearest window will be too expensive at a large scale with 100s of zones.

    The object placement route seems interesting to me. I will try that. In case that seems too expensive, then we address this problem using a user manual for the best practices for the designers. i will endorse that the windows shall be placed at the space level and not at the storey level.

    I am not sure what you mean by a space boundary. If this what you're referring to? Also, what is the best resource to read more on the spae boundaries?

  • @devngc you'd might be pleasantly surprised, I suspect a neighbourhood scale energy model limited to processing spaces and windows could be relatively fast even for huge models (under 5 minutes). It processes geometry using all cores available, and instancing likely means that a full building may only have a handful of window types.

    IfcOpenShell can generate a BVH tree as well.

    Yes, space boundaries are how IFC describes analytical models for energy modeling: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/link/ifcrelspaceboundary2ndlevel.htm

  • Thinking aloud, I can see that a window can belong to a space/room rather than a storey, same for external doors. From a FM perspective, an internal door belongs to the room that is less of a circulation space of the pair, but this distinction isn't always obvious. I assume that a door can't be in two spaces as this risks it being counted twice (and may be invalid IFC). I guess in this situation the door gets assigned to a random space.

  • By the way, elements can be assigned to two spaces, but only one space as its "primary assignment". All other assignments become a secondary assignment. For example, multistorey walls or windows may be assigned to the lowest storey as their primary assignment using a containment relationship, and to other storeys using a references relationship.

  • edited September 2021

    IfcOpenShell can generate a BVH tree as well.

    That would be my recommendation. I tend to rely on geometry as much as possible for reliability. Initialize ifcopenshell.geom.tree() add an ifcopenshell.geom.iterator(..., include='IfcSpace') you can use ifcopenshell.geom.settings(DISABLE_OPENING_SUBTRACTION=True) as it won't impact the results (spaces don't typically have openings, but perhaps you want to rely on this for other geometric queries). That way you generate only geometry for the spaces which is generally pretty cheap computationally. Then you use the IfcWindow placements (so not geometry) to query the BVH tree as Moult showed. With tree.select((x,y,z), extend=0.2) you get a configurable search radius around that point. You can also offset the (x y z) with the IfcWindow.OverallWidth / 2. and OverallHeight so that you start you search closer to the center of the window without the need to actually evaluate it's geometry.

    Edit: quick note for tree.select((x,y,z), extend=0.2) get the latest available build as there have been issues with this previously.

    Arv
  • Wouldn't it be nice to use the IfcRelSpaceBoundary relationship?

  • Looks like sadly it does not apply to holes, as they still can't cut two walls.

  • @stephen_l said:
    Looks like sadly it does not apply to holes, as they still can't cut two walls.

    If you are talking about IfcRelSpaceBoundary yes it applies to openings. If opening is filled with a door or a window it is related to this element. If it is not filled then it is related to the IfcOpeningElement.

  • Thank you all for your responses.
    Hi @Moult
    I have been looking into the BVH route. I have shared a small gist that I am using to generate window placement. Can you please explain what this output is? The window I am using is shown in the image below.

  • edited September 2021

    It's a 4x4 numpy matrix that describes the position and orientation of the window globally (so not relatively to the spatial container as it's defined in IFC). To get to the position coordinates you can index it as location[0:3,3]

  • edited September 2021

    Hey @Moult,
    Can you share a snippet of code as how you found the nearest IfcSpace using ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)?

    I can get the placement matrix as shown below but I don't understand how to use this information to search the nearest IfcSpace

    import pathlib
    import ifcopenshell
    from ifcopenshell import geom
    from ifcopenshell.util.placement import get_local_placement
    
    
    file_path = pathlib.Path("D:\simulation\ifc\SmallOffice_d_IFC2x3.ifc")
    ifc = ifcopenshell.open(file_path)
    
    window = ifc.by_guid('3C7TIwP8T3FOauijHfp$AR')
    location = get_local_placement(window.ObjectPlacement)
    print(location)
    
    # Output is
    [[1.000e+00 0.000e+00 0.000e+00 1.515e+03]
     [0.000e+00 1.000e+00 0.000e+00 0.000e+00]
     [0.000e+00 0.000e+00 1.000e+00 8.000e+02]
     [0.000e+00 0.000e+00 0.000e+00 1.000e+00]]
    
  • Hey @aothms,
    Running the following code gives me an empty list.

    import pathlib
    import ifcopenshell
    from ifcopenshell import geom
    from ifcopenshell.util.placement import get_local_placement
    
    # Get IFC file
    file_path = pathlib.Path("D:\simulation\ifc\SmallOffice_d_IFC2x3.ifc")
    ifc = ifcopenshell.open(file_path)
    
    # Get window location
    window = ifc.by_guid('3C7TIwP8T3FOauijHfp$AR')
    location = window.ObjectPlacement.PlacementRelTo.RelativePlacement.Location[0]
    
    # setup BVH tree
    tree_settings = ifcopenshell.geom.settings()
    tree_settings.set(tree_settings.DISABLE_OPENING_SUBTRACTIONS, True)
    t = ifcopenshell.geom.tree(ifc, tree_settings)
    
    # search tree
    a = t.select(location, extend=0.3)
    print(a)
    
    # This outputs []
    
  • edited September 2021

    Oh and replace

    location = window.ObjectPlacement.PlacementRelTo.RelativePlacement.Location[0]
    

    with

    m4 = ifcopenshell.util.placement.get_local_placement(window.ObjectPlacement)
    location = tuple(map(float, m4[0:3,3]))
    

    That's the difference between local and global coordinates. A window is typically defined relative to it's opening so the local window placement by itself isn't very meaningful.

    tuple(map(float, )) is because ifcopenshell doesn't understand numpy arrays so it needs to be converted to a native python tuple

  • edited September 2021

    Thanks for your support @aothms, but the output is still an empty list. I used the lastest build from the link you shared and also used the location as you suggested.

  • Just confirming that select using a tuple also returned empty results for me.

    However this approach using opening elements (assuming they exist) seems to work quite nicely:

    import time
    import pathlib
    import ifcopenshell
    import multiprocessing
    from ifcopenshell import geom
    from ifcopenshell.util.placement import get_local_placement
    
    start = time.time()
    
    # Get IFC file
    file_path = pathlib.Path("/home/dion/Downloads/SmallOffice_d_IFC2x3.ifc")
    ifc = ifcopenshell.open(file_path)
    
    # Get window location
    window = ifc.by_guid('3C7TIwP8T3FOauijHfp$AR')
    window = ifc.by_id(385026)
    opening = window.FillsVoids[0].RelatingOpeningElement
    
    include_elements = ifc.by_type("IfcOpeningElement") + ifc.by_type("IfcSpace")
    
    # setup BVH tree
    tree_settings = ifcopenshell.geom.settings()
    iterator = ifcopenshell.geom.iterator(
        tree_settings, ifc, multiprocessing.cpu_count(), include=include_elements
    )
    assert iterator.initialize()
    t = ifcopenshell.geom.tree()
    while True:
        t.add_element(iterator.get_native())
        shape = iterator.get()
        if not iterator.next():
            break
    
    # search tree
    a = t.select(opening, extend=0.01)
    print(a)
    
    devngcCyril
  • Sorry, one other consideration. Units. The tree is built up in meters, but the data we're getting directly from the file is in the original file units, which is mm in this case. This script below gave good results for me. I also adapted it to only build the tree and create geom for the spaces.

    import pathlib
    import ifcopenshell
    import ifcopenshell.geom
    
    from ifcopenshell.util.placement import get_local_placement
    from ifcopenshell.util.unit import calculate_unit_scale
    
    # Get IFC file
    ifc = ifcopenshell.open("SmallOffice_d_IFC2x3.ifc")
    length_unit_factor = calculate_unit_scale(ifc)
    
    # Get window location
    window = ifc.by_guid('3C7TIwP8T3FOauijHfp$AR')
    m4 = ifcopenshell.util.placement.get_local_placement(window.ObjectPlacement)
    location = tuple(map(float, m4[0:3,3] * length_unit_factor))
    
    # setup BVH tree
    tree_settings = ifcopenshell.geom.settings()
    tree_settings.set(tree_settings.DISABLE_TRIANGULATION, True)
    tree_settings.set(tree_settings.DISABLE_OPENING_SUBTRACTIONS, True)
    it = ifcopenshell.geom.iterator(tree_settings, ifc, include=("IfcSpace",))
    t = ifcopenshell.geom.tree()
    t.add_iterator(it)
    
    # search tree
    a = t.select(location, extend=0.4)
    print(a)
    
    devngcCyril
  • D'oh! Missed it! :D

  • I am a beginner Python/IFC user. All of this is pretty overwhelming, however I am enjoying at the same time. I have been able to extract some data, but the geometry stuff is still pretty new to me.

    I also am trying to find out how to find all windows belonging to a certain space. As I understand, this not easy. I was wondering, is it possible to search for the closest space in direction (and opposite direction) of the face of a window?

    I also tried @aothms last reply. I am getting errors at the "setup BVH tree":
    'tree' object has no attribute 'add_iterator'

    However I found https://wiki.osarch.org/index.php?title=IfcOpenShell_code_examples
    I think I should put some time in understanding geom I guess :)

  • 'tree' object has no attribute 'add_iterator'

    You're likely running an older version. Try https://github.com/IfcOpenBot/IfcOpenShell/commit/883b8a523c63027f2f6c91650385d47edba5521b#comments

    search for the closest space in direction (and opposite direction) of the face of a window?

    There's quite a few adhoc things in the latest version of the tree. You can try:

    tree.ray_intersect() https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/src/ifcgeom/IfcGeomTree.h#L435

    example usage in python

    https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/src/ifcopenshell-python/ifcopenshell/draw.py#L262

    FerdiVbrunopostle
Sign In or Register to comment.