OSArch Community

Relationships in IFC

  1. D

    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!

  2. M

    @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.

  3. M

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

  4. D

    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?

  5. D

    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.

  6. M

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

  7. D

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

  8. M

    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.

  9. D

    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?

  10. M

    @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

  11. B

    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.

  12. M

    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.

  13. A

    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.

  14. C

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

  15. S

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

  16. C

    @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.

  17. D

    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.

  18. A

    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]

  19. D

    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]]
    
  20. D

    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 []
    
  21. A
  22. A

    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

  23. D

    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.

  24. M

    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)
    
  25. A

    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)
    
  1. Page 1
  2. 2

Login or Register to reply.