# Using IfcOpenShell and pythonOCC to construct new geometry 9

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.

The IfcOpenHouse model as imported into a regular CAD application

## Getting started

# Specify to return pythonOCC shapes from ifcopenshell.geom.create_shape()
settings = ifcopenshell.geom.settings()

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

The IfcOpenHouse model as imported in the pythonOCC environment

## 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))

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)

The walls of the IfcOpenHouse model and it’s bounding box center point

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

The inner faces of the bounding walls

## 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()
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.

A halfspace solid is an infinite solid bounded by some surface, in this case an infinite plane

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

The resulting space volume as generated from the bounding walls and roof

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.

Thomas Krijnen is the main author and contributor to IfcOpenShell, a software library for working with IFC files. Thomas works hard on obtaining his PhD degree at the University of Technology in Eindhoven, the Netherlands.

## 9 thoughts on “Using IfcOpenShell and pythonOCC to construct new geometry”

• Johannes Römpp

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

• thomas Post author

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

• Franke

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.

• Franke

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:
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!

• thomas Post author

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.

• Thi Nguyen

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?

• thomas Post author

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.

• Omar Zerhouni

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