IfcOpenShell – Read geom as mesh

IfcOpenShell is a library used to work with IFC schema. This library is licensed under LGPL 3.0 (libre and open source).

Some features included :

  • Parse ifc
  • Create geom from ifc representation
  • Display geom using pythonOCC
  • Ifc importer for Blender
  • Ifc importer for 3ds Max
  • Convert geometry to many formats

Some projects using IfcOpenShell :

  • FreeCAD : parametric CAD modeler including a BIM workbench
  • BIMserver : Multi-user self-hostable BIM platform with a plugin ecosystem to view, analyse, merge etc…

In this article we will use a function which generate a mesh from an ifc entity in our case an IfcWall and see what we get.
A simple wall created with FreeCAD and exported to .ifc :

Base wall created in FreeCAD

Prerequisite : IfcOpenShell installed. Version used here : 0.6.0a1

First we need to import ifcopenshell and open the ifc file :

import ifcopenshell
from ifcopenshell import geom


def read_geom(ifc_path):

    ifc_file = ifcopenshell.open(ifc_path)

    settings = geom.settings()

geom.settings is used to set conversion options. By default, ifcopenshell generate a mesh with vertices, edges and faces. ifc_file.by_type("IfcClass") is a very handy way to get all element of a chosen class (including subclass). So if for example you IfcBuildingElement it will also include IfcWall, IfcWindow, IfcSlab, IfcBeam etc…
geom.create_shape(settings, ifc_entity) is the function which convert the ifc entity into a mesh. We can observe that vertices are stored in a single tuple not by xyz triplet. Same for edges and faces.

    for ifc_entity in ifc_file.by_type("IfcWall"):
        shape = geom.create_shape(settings, ifc_entity)
        # ios stands for IfcOpenShell
        ios_vertices = shape.geometry.verts
        ios_edges = shape.geometry.edges
        ios_faces = shape.geometry.faces

        # IfcOpenShell store vertices in a single tuple, same for edges and faces
        print(ios_vertices)
        print(ios_edges)
        print(ios_faces)
        """ Above will result in :
(0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 10.0, 0.0, 0.0, 10.0, 0.0, 3.0, 10.0, 0.2, 0.0, 10.0, 0.2, 3.0, 0.0, 0.2, 0.0, 0.0, 0.2, 3.0)
(0, 1, 1, 3, 0, 2, 2, 3, 2, 3, 2, 4, 3, 5, 4, 5, 4, 5, 5, 7, 4, 6, 6, 7, 6, 7, 0, 6, 1, 7, 0, 1, 0, 6, 0, 2, 4, 6, 2, 4, 1, 7, 1, 3, 5, 7, 3, 5)
(1, 0, 3, 3, 0, 2, 3, 2, 4, 5, 3, 4, 5, 4, 7, 7, 4, 6, 7, 6, 0, 1, 7, 0, 0, 6, 2, 2, 6, 4, 3, 7, 1, 5, 7, 3)
"""

It is obvious that vertices are x,y,z triplet one by one. But how are defined edges and faces ? An edge is a line bounded by 2 vertices but values we see are not vertices. A face in a mesh is a triangle surface bounded by 3 vertices and 3 edges. If we make a set of edges and faces values we get a set of length 8.

        print(set(ios_edges))
        print(set(ios_faces))
        """ Above will result in :
{0, 1, 2, 3, 4, 5, 6, 7}
{0, 1, 2, 3, 4, 5, 6, 7}
"""

If we group vertices by by 3 values (x,y,z), edges by 2 (vertex1, vertex2), and faces by 3 (3 vertices or 3 edges) we see that our wall geometry is defined by 8 vertices, 24 edges and 12 faces. Edges and faces values are both referring vertices indexes.

        # Let's parse it and prepare it for FreeCAD import
        vertices = [
            FreeCAD.Vector(ios_vertices[i : i + 3])
            for i in range(0, len(ios_vertices), 3)
        ]
        edges = [ios_edges[i : i + 2] for i in range(0, len(ios_edges), 2)]
        faces = [tuple(ios_faces[i : i + 3]) for i in range(0, len(ios_faces), 3)]

        print(
            f"This {ifc_entity.is_a()} has been defined by {len(vertices)} vertices, {len(edges)} edges and {len(faces)} faces"
        )
        print(vertices)
        print(edges)
        print(faces)
        """ Above will result in :
This IfcWall has been defined by 8 vertices, 24 edges and 12 faces
[(0.0, 0.0, 0.0), (0.0, 0.0, 3.0), (10.0, 0.0, 0.0), (10.0, 0.0, 3.0), (10.0, 0.2, 0.0), (10.0, 0.2, 3.0), (0.0, 0.2, 0.0), (0.0, 0.2, 3.0)]
[(0, 1), (1, 3), (0, 2), (2, 3), (2, 3), (2, 4), (3, 5), (4, 5), (4, 5), (5, 7), (4, 6), (6, 7), (6, 7), (0, 6), (1, 7), (0, 1), (0, 6), (0, 2), (4, 6), (2, 4), (1, 7), (1, 3), (5, 7), (3, 5)]
[(1, 0, 3), (3, 0, 2), (3, 2, 4), (5, 3, 4), (5, 4, 7), (7, 4, 6), (7, 6, 0), (1, 7, 0), (0, 6, 2), (2, 6, 4), (3, 7, 1), (5, 7, 3)]
"""
        return {"vertices": vertices, "edges": edges, "faces": faces}

Of course FreeCAD already has a better way to import an IfcWall but let’s use our mesh to generate a geometry :

import FreeCAD
import FreeCADGui
import Mesh

if __name__ == "__main__":
    mesh_values = read_geom(
        "/home/cyril/git/pythoncvc.net/IfcOpenShellSamples/Wall.ifc"
    )

    # Create a FreeCAD geometry. A FreeCAD can take vertices and faces as input
    mesh = Mesh.Mesh((mesh_values["vertices"], mesh_values["faces"]))
    # Ifc lenght internal unit : meter. FreeCAD internal unit : mm.
    scale_factor = 1000
    matrix = FreeCAD.Matrix()
    matrix.scale(scale_factor, scale_factor, scale_factor)
    mesh.transform(matrix)

    # Allow you to embed FreeCAD in python https://www.freecadweb.org/wiki/Embedding_FreeCAD
    FreeCADGui.showMainWindow()
    doc = FreeCAD.newDocument()

    # Add geometry to FreeCAD scenegraph (Coin)
    fc_mesh = doc.addObject("Mesh::Feature", "IfcMesh")
    fc_mesh.Mesh = mesh

    # Set Draw Style to display mesh edges. Orient view and fit to wall
    FreeCADGui.runCommand("Std_DrawStyle",1) 
    FreeCADGui.Selection.addSelection(fc_mesh)
    FreeCADGui.activeView().viewIsometric()
    FreeCADGui.SendMsgToActiveView("ViewSelection")
    
    FreeCADGui.exec_loop()
Resulting geometry in FreeCAD

Mesh is fast to display but it is usually not what you want to use in a BIM authoring software. So next time we will see how to generate a boundary representation.

Full source code is available here.
But it will be moved soon out of github due to recent discrimination toward some country : https://github.com/1995parham/github-do-not-ban-us
Star the repo and share !

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.