← Back to Knowledge Hub Module 9 → 9.2

9.2 Reading an IFC File Programmatically: First Script


Introduction

The last lesson confirmed IfcOpenShell installs and loads correctly, using nothing but an empty, in-memory model to prove it. This lesson opens an actual file for the first time and pulls something real out of it. Everything here is deliberately small: one file, one type of element, a handful of attributes. The point isn't to do anything sophisticated yet. It's to build the small number of operations that every later script in this module reuses, and to see them actually work against a real model.

Opening a File

IfcOpenShell's own documentation gives the pattern directly:

import ifcopenshell

model = ifcopenshell.open("/path/to/model.ifc")

ifcopenshell.open() takes a file path and returns a model object, everything in that IFC file loaded into memory and ready to query. This is the same kind of object 9.1's sanity check created with ifcopenshell.file(), except now it's populated with real data rather than empty.

Two things commonly go wrong here, worth knowing about before they happen rather than after. If the path is wrong, misspelled, pointing at a folder that's been moved, or just doesn't exist, the call fails with an error reporting the file can't be found. IfcOpenShell can also fail while opening a file if the file is inaccessible, malformed, or declares a schema version it doesn't support, though the exact failure and message depend on what's actually wrong with the file rather than following one single standardized pattern. Either way, the failure happens immediately, at the open() call itself, not silently later on. That's a deliberate design choice worth appreciating: a script that gets past this line has a genuinely loaded model to work with, not something that will fail unpredictably three lines further down.

Querying by Type

The single most common first operation on a loaded model is asking for every entity of one kind:

walls = model.by_type("IfcWall")

by_type() returns every entity in the file matching that IFC class, wrapped as a list. It normally includes entities whose IFC schema type is derived from the requested class, following IFC's own entity inheritance hierarchy, so model.by_type("IfcElement") returns instances of the many specific entity types derived from IfcElement, not only entities literally typed IfcElement itself. The string passed in has to match a real IFC entity name exactly; a typo or a name the schema doesn't recognize produces an error at that call, rather than silently returning nothing.

An empty list back from by_type() isn't necessarily an error though. It's a real, meaningful result: the file genuinely contains zero entities of that type. This distinction matters enough to be explicit about:

walls = model.by_type("IfcWall")

if not walls:
    print("No walls found.")

An exception means the operation itself failed, a bad type name, a schema-level problem. An empty list means the operation succeeded and simply found nothing. Once real automation starts making decisions based on what a query returns, this difference, between a failed check and a check that correctly found zero matches, is exactly the kind of thing worth never confusing.

Reading Attributes Off an Entity

Once a list of entities exists, each one carries attributes accessible with plain dot notation, matching the IFC schema's own naming exactly, including its capitalization:

wall = walls[0]
print(wall.GlobalId)
print(wall.Name)

GlobalId is the IFC object's globally unique identifier, intended to identify that object consistently within and across IFC-based workflows. It's designed to remain stable when an object's other attributes change, but it isn't an unconditional guarantee: an export, a re-import, or certain software operations can produce a different GlobalId for what a person would still consider the same real-world element. Name is the human-readable label an author gave the element, which may or may not be meaningful depending on how carefully the originating software or user named things.

These are direct attributes, values sitting on the entity itself, no lookup required beyond the dot notation. This matters because it's the first half of a distinction the next lesson goes much deeper on. Not everything worth knowing about an element lives this way. Quantities, most custom properties, and material information all live one or more steps away from the entity itself, reached through relationships rather than a direct attribute. This lesson deliberately stops at direct attributes. Reaching into properties and quantities is 9.3's job.

A Complete First Script

Putting the pieces together, this is a small, complete script that opens a file, counts every wall in it, and prints each one's name:

import ifcopenshell

model = ifcopenshell.open("/path/to/model.ifc")

walls = model.by_type("IfcWall")
print(f"Found {len(walls)} walls")

for wall in walls:
    print(wall.GlobalId, "-", wall.Name)

Run against any real IFC file with walls in it, this prints a count followed by one line per wall, its identifier and its name. Nothing here depends on any particular authoring tool or a specific project; the same three operations, open, query by type, read an attribute, work identically against a file exported from any IFC-producing software, since they're operating on the schema itself rather than on anything tool-specific.

What's Next

Direct attributes like Name and GlobalId are only part of what an IFC entity carries. The next lesson goes into the part that matters most for anything resembling real extraction work: properties and quantities, where they actually live in the file's structure, and how to read them correctly.

Key Takeaways

  • ifcopenshell.open(path) loads a real IFC file into memory and returns a model object. A wrong path, an inaccessible file, or an unsupported schema fails at this call, rather than failing unpredictably later.
  • model.by_type("IfcClassName") returns every entity of that class, following IFC's entity inheritance hierarchy, so subclasses are included automatically. The class name must exactly match a real IFC entity name.
  • An empty list from by_type() is a valid, meaningful result, not necessarily a sign of failure. An exception means the operation itself failed; an empty list means it succeeded and found nothing, a distinction worth keeping precise.
  • Direct attributes like GlobalId and Name are read with plain dot notation matching the schema's own naming and capitalization exactly.
  • GlobalId is designed to identify an object consistently, but export, re-import, or certain operations can still produce a different one for what is conceptually the same element. Name is a human-readable label whose usefulness depends entirely on how carefully the authoring software or user populated it.
  • Direct attributes are only one layer of what an entity carries. Properties, quantities, and material data live one or more steps further away, reached through relationships rather than direct dot access, which is where the next lesson picks up.

Discuss this lesson

Comments use a free GitHub account — takes under a minute to create, and keeps discussions spam-free and permanently archived.