This is an introductory blog to python and IfcOpenshell for IFC file processing. We will be going step by step from setting up the required dev tools, to writing and running custom processing scripts. The aim is to explain few python basics, easing the way to IFC data extraction and manipulation, all beginner-friendly.
Requirements
To get things started, install the follwing tools for your operating system.
Python
The program that interprets and executes python code, can be downloaded through the official python.org.
For compatibility reasons, at the time of writing this blog, the version of python should be <3.15, >=3.10.
Visual Studio Code
VS Code is a code editor, that provides a convenient user experience, for both writing and editing code files of virtually any coding language. Code files are basically text files that have a special extension (.py : Python, .cs : C# …).
Can be downloaded via code.visualstudio.com
Now that we have the necessary tools, the following is a quick introduction to some of the basic fundamentals of the python programming language.
Python basics
ℹ️ If you have prior experience with python scripting, you can skip this section.
This is in no way a thorough look into python, but I hope it’ll ignite a spark of curiosity for you to explore, trust me it’s worth it.
When we run python code, interpreter reads the text from top to bottom, handling each line as an instruction and evaluating its result.
One simple rule, anything following # is considered as a comment, be it at the beginning or in the middle of a line.
Variables and data types
A variable is a unit of data that can be assigned a unique name. A variable can be assigned any unique name, except a few ones reserved by language (for, if, int, dict, list, print, len...)
x = 2 # the variable x is assigned the value of 2 of type int (integer)y = 2.45 # floatt = 'hello world' # str (string), assigned using single quotes 'text' or double quotes "text"_list= [1, 2.3, 'test', t] # listb = True # bool, a binary value that's either True or False_dict = {"a":5, "b":x} # dict, a collection of keys and values_set = {"wall01", "wall02", 21} # set, a list of unique items
Loops
A loop, as its name implies, loops repeatedly over the same instructions. Loops are used to iterate over a sequence (list, dict, str, …), processing each item sequentially at a time (for loop). They can also be implemented to retry the same instructions over and over again until an escape condition is met (while loop).
Using for as example:
# Iterating over the previously definde list of itemsfor item in items:print(item) # prints the str representation of the item to the screen#...#other instructions
Note that indention is used to identify blocks of code, thus is not optional.
Conditionals
Often times we want code to execute under a certain condition
x = 3 # assign x the value of 3if x%2 == 0 : # checks if the remainder of the division is 0print('even') # ❌ this won't executeelse:print('odd') # ✅ this will execute
Functions
A function is a way to group a set of instructions, making them easily reusable. In visual programming tools like dynamo, a function is comparable to a node. It can take some input values (arguments), and return some other values as output.
# Defining the functiondef isEven (value):if value%2 == 0 : # checks if the reminder of the division is 0return 'even'else:return 'odd'# Calling the functionr = isEven(4) # the variable r is then assigned the return value of the function => 'even' in this case
In object-oriented programming (OOP), an object can have multiple properties (values) and methods (sets of instructions).
When talking about objects, we should mention classes. A class is a blueprint from witch an object can be instantiated. You can think of a class as an object type, an object is then a type instance.
Given a class Person with attributes (Name, BirthDate) and a method getAge(). Say the variable person1 is already defined with the information of an individual, we can interact with it as follows:
# Having this classperson1.Name # Paulperson1.BirthDate # 04/05/1990person1.getAge() # will return the calculated age at this day => '36'
If this lightning, minimalist python introduction was understandable enough, then great 🎉 !! You can seek official documentation for more 🚀.
If not, you’re highly advised to check official documentation or any other online resources or even your favourite AI, you will be getting thorough explanations.
IFCOpenshell
IfcOpenshell, is an open-source project that provides various tools and libraries.
In this blog we will be working with the ifcopenshell python library, used to work with IFC files.
There are other libraries related to the IfcOpenshell project (full list) designed for other openBIM formats like BCF, IDS, and for interoperability with other tools.
For another Dynamo analogy, think of a library as an external package, containing various nodes.
Where the analogy stops is that both this library and the IFC schema are open source, so one can use them and build solutions around them with zero vendor dependency and complete freedom.
For our hands on IFC processing demo, we will build the following pipeline
flowchart LR
A[📂 Open files] --> B[📄 Extract CSV<br/>with attributes]
B --> C[✏️ Edit attributes]
C --> D[🔄 Update IFC]
D --> E[💾 Save IFC]To get going, first let's create a working directory, python-ifc-automation for example.
Launch VS Code and open the created folder.
Next, we will be fetching the library using pip, the official python package manager that comes along with python. A library is a collection of files containing code that serves a specific purpose.
Installing IfcOpenShell
Using VS Code, you can follow these steps:
- Open the terminal (you can also use the shortcut
Ctrl+ù).
A terminal or a command line is a way to interact with an operating system through typing commands and instructions, instead of clicking on buttons in user interface. - Type in this command to install
ifcopenshellpip install ifcopenshell
You can also open the integrated terminal from the ui Terminal > New Terminal
First, create an IFC folder, this is where the .ifc files will be located.
Extracting attributes
Create a file at the root of the working folder extract_attributes.py and paste the following code.
import ifcopenshell # importing the library we've installedimport csv # importing the built in csv library# Creating a variable with the path to the IFC file, it can be relative or absoluteifc_file_path = "IFC/AC20-FZK-Haus.ifc" # Change this to target another file# Using the open() function from ifcopenshell to parse the ifc modelifc_file = ifcopenshell.open(ifc_file_path)# select all IfcElements (IfcElement is the parent category containing all AEC products) from the modelifcElements = ifc_file.by_type('IfcElement') # Can be replaced with any specific class (IfcWall, IfcWindow ....)# Initializing an empty list where we will store data from the elementsifcElementsData = []for ifcElement in ifcElements:# for each element get_info() return a dictionnary with the attribute# for example {'Name' : 'Exterior_wall_01' , 'Description':'Description_01'.....}ifcElementData = ifcElement.get_info(scalar_only=True)# Adding the individual element data to the listifcElementsData.append(ifcElementData)# crating a set of attribute namesattributes = set()for ifcElementData in ifcElementsData:# ifcElementData.keys() return the keys from dictionnary :# {'Name' : 'Exterior_wall_01' , 'Description': 'Description_01' .....} => ('Name', 'Description',...)attributes.update(ifcElementData.keys())# Saving the datawith open("IFC/AC20-FZK-Haus.csv", "w", newline="", encoding="utf-8") as f:writer = csv.DictWriter(f, fieldnames=attributes)writer.writeheader()writer.writerows(ifcElementsData)
Make sure to save the file, you can use the shortcut Ctrl + s
In the terminal run this command python .\extract_attributes.py, you can use the tab key ↹ for autocomplete.
You should see the CSV file AC20-FZK-Haus.csv generated in the IFC folder.
You can install the CSV extension that helps to interact with CSV data.
Updating attributes
The next step of this pipeline, is to update the attributes through the CSV. Some attributes are read only and should not be edited.
Create a new python file update_attributes.py and paste the following code
# Update IFC element attributes from a CSV file (one row per element,# matched to the model by GlobalId).import ifcopenshellimport csvifc_file_path = "IFC/AC20-FZK-Haus.ifc"# Load the whole model into memory. Nothing is saved until write() below.ifc_file = ifcopenshell.open(ifc_file_path)# newline="" lets the csv module handle line endings itself.with open('IFC/AC20-FZK-Haus.csv', mode='r', newline="") as f:# DictReader turns each row into a dict keyed by the header row.reader = csv.DictReader(f)# Convert to a list now; the reader only works while the file is open.attributesCSV = list(reader)def infer_type(value):"""CSV values are always text. Turn "42" into 42, "4.2" into 4.2,and leave anything else as a string."""try:return int(value)except ValueError:try:return float(value)except ValueError:return valuefor newElementAttribute in attributesCSV:# .get() returns None instead of crashing if the column is missing.elementGlobalId = newElementAttribute.get('GlobalId')# No ID means we don't know which element to update, so skip the row.if not elementGlobalId:continueelement = ifc_file.by_guid(elementGlobalId)# Names of the attributes this element actually has.elementAttributeNames = element.get_info().keys()for attribute, value in newElementAttribute.items():# Skip fields ifcopenshell manages, attributes this element doesn't# have (writing those raises an error), and empty cells.if (attribute not in ('id', 'GlobalId', 'type')) and (attribute in elementAttributeNames) and (value != ""):element.__setattr__(attribute, infer_type(value)) # The attribute value is set here# Save to a new file so the original stays untouched.ifc_file.write('IFC/AC20-FZK-Haus_updated.ifc')
Edit the previously generated CSV to update some attributes. In the example below, the description attribute is filled for all elements.
Use this command to run the script python .\update_attributes.py
Batch attributes extraction
The previous extract_attributes.py runs only for one file at a time, and its path is manually written each time. For a better user experience, we will update it to scan for all .ifc files within the IFC folder. The script then runs the same extraction logic per file.
Create a new file batch_extract_attributes.py and paste the following code:
# Export the attributes of every element in each IFC file to a CSVimport ifcopenshellimport csvimport glob # finds files matching a wildcard pattern# Putting the extraction logic in a functiondef extract_attributes_2_csv(ifc_file_path):ifc_file = ifcopenshell.open(ifc_file_path)# Every physical element (walls, doors, slabs...). This skips things# like spaces, storeys and relationships.ifcElements = ifc_file.by_type('IfcElement')ifcElementsData = []for ifcElement in ifcElements:# get_info() returns a dict of the element's attributes.# scalar_only=True drops references to other IFC objects, keeping# only plain values that fit in a CSV cell.ifcElementsData.append(ifcElement.get_info(scalar_only=True))# Different element types have different attributes, so collect the# union of all keys to use as the CSV header. A set discards duplicates.attributes = set()for ifcElementData in ifcElementsData:attributes.update(ifcElementData.keys())# Build the output path by swapping the extension: house.ifc -> house.csvifc_extension = ifc_file_path.split(".")[-1]csv_file_path = ifc_file_path.replace(ifc_extension, 'csv')with open(csv_file_path, "w", newline="", encoding="utf-8") as f:# DictWriter maps each dict's keys to columns. Any attribute an# element lacks is simply left blank.writer = csv.DictWriter(f, fieldnames=attributes)writer.writeheader()writer.writerows(ifcElementsData)# Scans and lists all .ifc files in the IFC folder; * matches any filename.ifc_file_paths = glob.glob('IFC/*.ifc')for ifc_file_path in ifc_file_paths:print('Processing : ', ifc_file_path)extract_attributes_2_csv(ifc_file_path)print('Done : ', ifc_file_path)
You can add additional IFC files in the IFC folder
To run the batch processing, use this following command
python .\batch_extract_attributes.py
For ease of use, we can set up an executable file (.bat on windows), so that we don’t have to go through the VS Code every time.
Create a file run_batch_extraction.bat and paste these commands
python .\batch_extract_attributes.pypause
Works on Windows. For linux and macOS use a .sh file, with minor adjustments Now we can run the extraction script by running the .bat file
It’s also possible to run script using the ui
Extract attributes:
make sure to save files upon edit (ctrl+s)
- Instead of processing one file, try to batch process a list of files inside the IFC (by using a loop for example).
Going further
Taking this, try to implement some of the following features
- batch update attributes
- Include properties and quantities
- Use Excel for the extraction results instead of CSV
💡 You may need to install additional libraries, ChatGPT your way out.
Why learn to code when AI does it so well? My view is that using LLMs to get a result is great, but actually being able to understand, verify, and improve that result is even better. Furthermore, having the fundamentals is essential for properly framing a problem and being able to grasp the underlying algorithmic concepts. In other words, in a world with calculators, knowing how to count is anything but futile.