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 :
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()
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