[This article contains several snippets of source code for illustration purposes, the full source code can be downloaded at the bottom]
In this short exercise we are going to build on top the IfcOpenHouse model. It is one of the first IFC models entirely built using program code. It is a nice model, but it has a few shortcomings: for example, it does not define an IfcSpace instance that describes the interior space bounded by the walls and roof. In this exercise we are going to model the geometry of this space automatically from the geometry of the bounding elements and calculate the interior volume of the model.
Getting started
# Specify to return pythonOCC shapes from ifcopenshell.geom.create_shape() settings = ifcopenshell.geom.settings() settings.set(settings.USE_PYTHON_OPENCASCADE, True) # Initialize a graphical display window occ_display = ifcopenshell.geom.utils.initialize_display() # Open the IFC file using IfcOpenShell ifc_file = ifcopenshell.open("IfcOpenHouse.ifc") # Display the geometrical contents of the file using Python OpenCascade products = ifc_file.by_type("IfcProduct") for product in products: if product.is_a("IfcOpeningElement"): continue if product.Representation: shape = ifcopenshell.geom.create_shape(settings, product).geometry display_shape = ifcopenshell.geom.utils.display_shape(shape) if product.is_a("IfcPlate"): # Plates are the transparent parts of the window assembly # in the IfcOpenHouse model ifcopenshell.geom.utils.set_shape_transparency(display_shape, 0.8)
Importing an IFC model is pretty straightforward. References are obtained to instances of IfcProduct subtypes. These classes include all building elements that have a 3d representation. This also includes IfcOpeningElements, the mechanism used to extract openings for windows and doors, typically from walls. The geometry of these elements is not imported (note that continue means continue to next element in the iteration). IfcOpenShell can be used to create a 3d shape for the representation of the building element. Note that in IFC geometry is decomposed of IfcRepresentationItems of varying complexity. IfcOpenShell hides this complexity and provides a uniform Boundary Representation (BRep) for every product.
Filtering elements
However, for this exercise we are not interested in all types of products. Initially we are only interested in the four walls of the building.
# Get a list of all walls in the file walls = ifc_file.by_type("IfcWall") # Create a list of wall representation shapes # and compute the bounding box of these shapes wall_shapes = [] bbox = OCC.Bnd.Bnd_Box() for wall in walls: shape = ifcopenshell.geom.create_shape(settings, wall).geometry wall_shapes.append((wall, shape)) OCC.BRepBndLib.BRepBndLib_Add(shape, bbox) ifcopenshell.geom.utils.display_shape(shape) # Calculate the center/average of the bounding box bounding_box_center = ifcopenshell.geom.utils.get_bounding_box_center(bbox) print "Bounding box center: %.2f %.2f %.2f" % ( bounding_box_center.X(), bounding_box_center.Y(), bounding_box_center.Z()) occ_display.DisplayMessage(bounding_box_center, "Center", update=True)
In addition to simply obtaining 3d shapes for the walls, the middle point of the interior space is determined by calculating the average of the geometrical bounding box. This middle point is necessary later on to determine which sides of the walls to base the internal space volume on.

The distinction between topology and geometry. In which a Face is a composed of a Wire on a base surface. The Wire consists of Edges and Vertices. The Edges have an underlying curve
In open cascade and most other geometry kernels there is a distinction between geometry and topology, for more information about these concepts read the following blog post.
Extracting faces
# Now create halfspace solids from the inner faces of the wall halfspaces = [] for wall, shape in wall_shapes: topo = OCC.Utils.Topo(shape) for face in topo.faces(): surf = OCC.BRep.BRep_Tool.Surface(face) obj = surf.GetObject() assert obj.DynamicType().GetObject().Name() == "Geom_Plane" plane = OCC.Geom.Handle_Geom_Plane.DownCast(surf).GetObject() if plane.Axis().Direction().Z() == 0: face_bbox = OCC.Bnd.Bnd_Box() OCC.BRepBndLib.BRepBndLib_Add(face, face_bbox) face_center = ifcopenshell.geom.utils.get_bounding_box_center(face_bbox).XYZ() face_normal = plane.Axis().Direction().XYZ() face_towards_center = bounding_box_center.XYZ() - face_center face_towards_center.Normalize() dot = face_towards_center.Dot(face_normal) if dot < -0.8: ifcopenshell.geom.utils.display_shape(face) face_plane = plane.Pln() new_face = OCC.BRepBuilderAPI.BRepBuilderAPI_MakeFace(face_plane).Face() halfspace = OCC.BRepPrimAPI.BRepPrimAPI_MakeHalfSpace( new_face, bounding_box_center).Solid() halfspaces.append(halfspace)
We need to determine which of the faces of the wall volumes points towards the middle point so that these can become part of the bounding volume of the interior space. OpenCascade has several types of shapes: Solids, Shells, Faces, Wires, Edges and Vertices and compounds of these. The shape returned by IfcOpenShell for the walls is probably a Solid, but we do not really need to now that. We can simply iterate over the faces that bound the solid. Then we can obtain a reference to the underlying surface of that face. Since the IfcOpenHouse model only consists of planar geometry we know that the surface of the face conforms to a plane. If the direction of the normal vector perpendicular to the plane points towards the middle point we know that it is one of the faces we are interested in. This is done by calculating the dot product of the two vectors, read more on wikipedia. Note that the 0.8 constant is rather arbitrary. Technically also some of the faces of the window opening subtractions point towards the middle point of the space.
Note that faces do not necessarily need to be bounded. From the plane equations that are obtained by iterating over the faces of the bounding walls we can obtain halfspace solids. You could imagine that the intersection of all these halfspace solids forms the volume of our interior space. But first a similar trick is done to obtain the bottom faces of the roof elements.
# Create halfspace solids from the bottom faces of the roofs roofs = ifc_file.by_type("IfcRoof") for roof in roofs: shape = ifcopenshell.geom.create_shape(settings, roof).geometry topo = OCC.Utils.Topo(shape) for face in topo.faces(): surf = OCC.BRep.BRep_Tool.Surface(face) plane = OCC.Geom.Handle_Geom_Plane.DownCast(surf).GetObject() assert obj.DynamicType().GetObject().Name() == "Geom_Plane" if plane.Axis().Direction().Z() > 0.7: face_plane = plane.Pln() new_face = OCC.BRepBuilderAPI.BRepBuilderAPI_MakeFace(face_plane).Face() halfspace = OCC.BRepPrimAPI.BRepPrimAPI_MakeHalfSpace( new_face, bounding_box_center).Solid() halfspaces.append(halfspace)
Creating the space volume
Unfortunately creating a bounded solid from only infinite solids does not work very well in OpenCascade. Hence we start off with a bounded solid that we know will surely fit the entire volume of our space. The halfspaces created from the products in the IFC files are subtracted from this box.
# Create an initial box from which to cut the halfspaces common_shape = OCC.BRepPrimAPI.BRepPrimAPI_MakeBox( OCC.gp.gp_Pnt(-10, -10, 0), OCC.gp.gp_Pnt(10, 10, 10)).Solid() for halfspace in halfspaces: common_shape = OCC.BRepAlgoAPI.BRepAlgoAPI_Common( common_shape, halfspace).Shape() ifcopenshell.geom.utils.display_shape(common_shape) # Calculate the volume properties of the resulting space shape props = OCC.GProp.GProp_GProps() OCC.BRepGProp.BRepGProp_VolumeProperties(shape, props) print "Space volume: %.3f cubic meter" % props.Mass()
This example only scratches the surface of the possibilities of Python, OpenCascade and IfcOpenShell. OpenCascade provides a wide variety of shape analysis and fixing tools and descriptive measures can be extracted, such as the space volume in this example.
![]() pythonOCC <= 0.6 Download the full source code of this recipe | ![]() pythonOCC 0.16.0 Download the full source code of this recipe |
Hi Thomas
I was wondering, if there is already a way to bring this newly constructed geometry (space) in to an ifc-file, as ifcspace (ifcclosedshell) in this case.
I tried the helloWall example and it works fine. But is there a way, to translate the box or any TopoDSShape to an ifc-entity directly?
Regards
Johannes
Hi Johannes,
Great question, an academy article on this is long overdue (feel free to step in case you feel like it). In recent IfcOpenShell versions there is ifcopenshell.geom.tesselate() and ifcopenshell.geom.serialize(). tesselate() will always work irrespective of shape and IFC schema, it just meshes the geometry, serialize() will fail for curved surfaces in 2×3 since they are not supported (and even in 4 a lot of viewers will not support them). Both return an IfcProductDefinitionShape that you can assign to a IfcProduct.Representation directly.
Kind regards,
Thomas
Hello Thomas,
I have tried to run the code of part “Geting started” after completing all installation steps hopefully, the display window just crashed.
I have also tried to add “raw_input()”, and then I could see the display window but it was always running. On chance to manipulate the ifc model there.
I have installed Qt4 and pyqt5. Would that be the prolem? I appreciate it, if you could give me some suggestions.
It is a little wird to anser own question, but I hope my experience could help anyone who wanna try something on this.
———————————-
the setup steps could be simpler:
1. download Anaconda
2. build an environment in Anaconda, the python version is not a problem, but you must make sure other packages matching the version of python.
3. just run the code from http://www.pythonocc.org/download/ in the terminal of the environment.
4. download the IfcOpenShell package, extract it into the Lib\site-packages\ directory of the Python installation folder. (just following the original instruction)
5. If you want to try the example of the IfcOpenHouse, remember to add raw_input() , which exists in the source code file, beneath the code in part “getting started”. Otherwise, the viewer window will crash!
Thanks for those suggestions. If you’re using Anaconda, you can also install ifcopenshell using conda
conda install -c conda-forge -c oce -c dlr-sc -c ifcopenshell ifcopenshell
Indeed, a python script is executed from top to bottom and ends at the last line. You can add a raw_input() [input() in python3] or time.sleep() or ifcopenshell.geom.utils.main_loop() should also work.
Hello Frank and Thomas.
Thank you for your instructions. I tried to follow the instructions of Frank to install pythonocc-core==0.18.1 for python 2.7. At the third step, when i ran the command below in anaconda prompt:
conda install -c conda-forge -c dlr-sc -c pythonocc -c oce pythonocc-core==0.18.1
Anaconda prompt ran during 8 hours without results (I can not import OCC in my python script).
Could you please tell me how did you build the environment in Anaconda at your second step?
Thank you in advance for your help!
Perhaps try without the `-c conda-forge` for pythonocc version 0.18 all the modules should be available on the other channels. You can also try to install just pythonocc-core without a version number (you’ll get a newer version) from the conda-forge or pythonocc channel.
It seems there is an incompatibility with the new version of pythonOCC-core.
Running this code, I get this message as an error :
Traceback (most recent call last):
File “C:/Users/TOSHIBA/AppData/Roaming/JetBrains/PyCharmCE2020.1/scratches/scratch_22.py”, line 27, in
occ_display = ifcopenshell.geom.utils.initialize_display()
File “C:\ProgramData\Anaconda3\envs\EnvPy36\lib\site-packages\ifcopenshell\geom\occ_utils.py”, line 78, in initialize_display
setup()
File “C:\ProgramData\Anaconda3\envs\EnvPy36\lib\site-packages\ifcopenshell\geom\occ_utils.py”, line 57, in setup
viewer = viewer_handle.GetObject()
AttributeError: ‘V3d_Viewer’ object has no attribute ‘GetObject’
TKOpenGl.WinSystem | Type: Error | ID: 6 | Severity: High | Message:
wglMakeCurrent() has failed. Descripteur non valide
There have been various fixes for compatibility with OCC versions. Can you try a more recent file from the github repo? https://github.com/IfcOpenShell/IfcOpenShell/commit/e2784fb69b8e657eba3fe0625a4ce460cd0a60f6 Just substitute the occ_utils.py file you have locally.