ifcos-core-runtime
IfcOpenShell Python Runtime
Quick Reference
Critical Warnings
- ALWAYS use
==for entity comparison, NEVERis. Each query returns a new Python wrapper object. - ALWAYS set entity references to
Noneafter callingmodel.remove(). The C++ object is deallocated regardless of Python reference count. - ALWAYS keep a reference to the
ifcopenshell.fileobject alive while any entities from it are in use. If the file is garbage-collected, all entity wrappers become dangling pointers (segfault). - NEVER write to an
ifcopenshell.filefrom multiple threads. The C++ backend has no locking. Concurrent writes corrupt the model or crash. - NEVER iterate all entities and filter manually when
by_type()exists.by_type()uses an internal index and is 10-100x faster. - NEVER use
get_info(recursive=True)on large models. It materializes the entire entity graph into memory. - ALWAYS use named attribute access (
wall.Name) instead of positional index access (wall[2]). Positional indices vary by entity type and schema version. - ALWAYS check
model.schemabefore accessing schema-specific attributes.
Decision Tree: Entity Reference Safety
Working with entity references?
├── Reading attributes?
│ ├── One attribute → entity.AttributeName (PascalCase)
│ ├── All attributes → entity.get_info() (returns dict)
│ └── Bulk read → entity.get_info(scalar_only=True) (faster)
│
├── Comparing entities?
│ ├── Same entity? → entity_a == entity_b (value equality)
│ ├── Same STEP ID? → entity_a.id() == entity_b.id()
│ └── NEVER use → entity_a is entity_b (always False)
│
├── Removing entities?
│ ├── With relationship cleanup → ifcopenshell.api.run("root.remove_product", model, product=entity)
│ ├── Low-level removal → model.remove(entity) (does NOT clean relationships)
│ └── After removal → entity_ref = None (ALWAYS nullify)
│
└── Storing references across operations?
├── Store entity.id() instead of the entity object
├── Re-fetch with model.by_id(stored_id) when needed
└── NEVER cache entity objects across remove/undo operations
Decision Tree: Performance
Performance-critical operation?
├── Querying entities by type?
│ ├── Use model.by_type("IfcWall") → indexed, returns tuple
│ ├── First call builds index (slow), subsequent calls are instant
│ └── NEVER iterate all entities with manual is_a() filtering
│
├── Reading many attributes?
│ ├── Single entity → entity.get_info() (one C++ round-trip)
│ ├── Scalar values only → entity.get_info(scalar_only=True)
│ └── AVOID get_info(recursive=True) (massive memory allocation)
│
├── Bulk entity creation?
│ ├── < 100 entities → ifcopenshell.api.run() (safe, handles metadata)
│ ├── > 100 entities → model.create_entity() (5-10x faster, no undo tracking)
│ └── Bulk mode → handle GlobalId, OwnerHistory (IFC2X3) manually
│
└── Large model (500MB+ / 100k+ entities)?
├── Memory: ifcopenshell.open() loads ENTIRE file into RAM
├── Streaming: ifcopenshell.open(path, should_stream=True) (sequential only)
├── Parallel: open SEPARATE file instances per thread
└── Cleanup: del model + gc.collect() to release C++ memory
Essential Patterns
Pattern 1: C++ Binding Architecture
IfcOpenShell Python objects are thin wrappers around C++ objects managed by the ifcopenshell_wrapper module.
# Schema-agnostic
import ifcopenshell
model = ifcopenshell.open("model.ifc")
wall = model.by_type("IfcWall")[0]
# Python wrapper delegates to C++ core
type(wall) # <class 'ifcopenshell.entity_instance'>
type(wall.wrapped_data) # <class 'ifcopenshell_wrapper.entity_instance'>
# The wrapper holds a reference to its parent file
wall.file # Returns the ifcopenshell.file object
Key implication: Python garbage collection does NOT control C++ memory. The C++ backend allocates and deallocates independently. Calling model.remove(entity) frees the C++ object immediately, even if Python references still exist.
Pattern 2: Entity Identity
# Schema-agnostic
wall = model.by_type("IfcWall")[0]
# Two queries for the same entity return DIFFERENT Python wrapper objects
wall_a = model.by_id(wall.id())
wall_b = model.by_id(wall.id())
wall_a == wall_b # True — value equality via wrapped_data comparison
wall_a is wall_b # False — different Python wrapper objects
# ALWAYS compare with == or by STEP ID
wall_a.id() == wall_b.id() # True — same STEP ID (#42)
Pattern 3: Entity Invalidation After Removal
# Schema-agnostic
wall = model.by_type("IfcWall")[0]
wall_id = wall.id()
# Low-level removal deallocates the C++ object
model.remove(wall)
# DANGER: accessing wall after removal causes undefined behavior
# wall.Name → segfault or garbage data
# ALWAYS nullify references after removal
wall = None
# To re-check existence, query by ID
try:
still_exists = model.by_id(wall_id)
except RuntimeError:
still_exists = None # Entity was removed
# PREFER API removal for products (cleans up relationships)
other_wall = model.by_type("IfcWall")[0]
ifcopenshell.api.run("root.remove_product", model, product=other_wall)
other_wall = None # Still must nullify
Pattern 4: by_type() Return Semantics
# Schema-agnostic
walls = model.by_type("IfcWall")
type(walls) # <class 'tuple'> — NOT a list
len(walls) # Number of IfcWall instances
# First call for a type builds an internal C++ index (slower)
# Subsequent calls for the same type use the cached index (instant)
# To mutate the result, convert to list
wall_list = list(walls)
wall_list.append(some_other_entity)
# Include subtypes (default behavior)
all_walls = model.by_type("IfcWall") # Includes IfcWallStandardCase in IFC2X3/IFC4
# Exclude subtypes
only_walls = model.by_type("IfcWall", include_subtypes=False)
Pattern 5: PascalCase Attribute Access
# Schema-agnostic
wall = model.by_type("IfcWall")[0]
# Named access — PascalCase matching the IFC schema
name = wall.Name # str or None
global_id = wall.GlobalId # str (22-char encoded GUID)
description = wall.Description # str or None
object_type = wall.ObjectType # str or None
owner_history = wall.OwnerHistory # entity_instance or None
# Positional access — fragile, varies by type and schema
name = wall[2] # AVOID: index 2 = Name for IfcWall, but varies for other types
# Check if attribute has a value (IFC $null → Python None)
if wall.Description is not None:
print(wall.Description)
# is_a() checks the full class hierarchy
wall.is_a("IfcWall") # True
wall.is_a("IfcBuildingElement") # True (parent class)
wall.is_a("IfcProduct") # True (grandparent)
wall.is_a("IfcSlab") # False
# get_info() returns all attributes as a dict
info = wall.get_info()
# {"id": 42, "type": "IfcWall", "GlobalId": "...", "Name": "...", ...}
Pattern 6: Thread Safety
# Schema-agnostic
# SAFE: Read-only operations from multiple threads (no concurrent writes)
import concurrent.futures
def count_type(filepath, ifc_class):
model = ifcopenshell.open(filepath) # Separate instance per thread
return len(model.by_type(ifc_class))
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = {
executor.submit(count_type, "model.ifc", cls): cls
for cls in ["IfcWall", "IfcSlab", "IfcDoor"]
}
# SAFE: Separate file instances per thread for writes
def process_copy(filepath, output_path):
local_model = ifcopenshell.open(filepath)
# Modify local_model freely — independent C++ instance
local_model.write(output_path)
# UNSAFE: Multiple threads writing to the SAME model object
# This WILL corrupt data or crash — no C++ locks exist
Pattern 7: Memory Management for Large Models
# Schema-agnostic
# Standard open loads entire file into memory
model = ifcopenshell.open("large_building.ifc") # May use 4-8GB RAM
# Access specific attributes instead of materializing everything
name = wall.Name # 1 attribute
material = ifcopenshell.util.element.get_material(wall) # Targeted query
# AVOID: get_info(recursive=True) on large models
# deep_info = wall.get_info(recursive=True) # Massive memory allocation
# Release a large model from memory
del model
import gc
gc.collect() # Ensures Python wrappers are cleaned up and C++ memory freed
Pattern 8: File Lifecycle — Keep File Reference Alive
# Schema-agnostic
# DANGEROUS: File reference lost while entities are in use
def get_walls():
model = ifcopenshell.open("model.ifc")
return model.by_type("IfcWall") # model goes out of scope after return
walls = get_walls()
# model is garbage-collected → all entity wrappers are dangling pointers
# walls[0].Name → SEGFAULT
# SAFE: Return both the model and the entities
def get_walls_safe():
model = ifcopenshell.open("model.ifc")
walls = model.by_type("IfcWall")
return model, walls
model, walls = get_walls_safe()
walls[0].Name # Safe — model reference kept alive
Schema-Specific Attribute Differences
# ALWAYS check schema before accessing schema-specific attributes
schema = model.schema # "IFC2X3", "IFC4", "IFC4X3"
# Entities that differ by schema:
# IFC2X3/IFC4: IfcWallStandardCase exists
# IFC4X3: IfcWallStandardCase does NOT exist — use IfcWall
# IFC2X3: IfcDoorStyle, IfcWindowStyle
# IFC4+: IfcDoorType, IfcWindowType
# IFC2X3: OwnerHistory is REQUIRED on rooted entities
# IFC4+: OwnerHistory is OPTIONAL
# IFC4X3: IfcBuiltElement replaces IfcBuildingElement
# IFC4X3: New entities: IfcAlignment, IfcRoad, IfcBridge, IfcFacility
# Accessing a non-existent attribute raises AttributeError
try:
pt = task.PredefinedType # Exists in IFC4, not in IFC2X3
except AttributeError:
pt = None
Installation
pip (Recommended for Most Users)
pip install ifcopenshell
python -c "import ifcopenshell; print(ifcopenshell.version)"
- Pre-built wheels for Python 3.8-3.12 on Linux, macOS, Windows
- Package name is
ifcopenshell(all lowercase) - Bundles C++ core and OpenCASCADE dependencies
conda (Recommended for Complex Environments)
conda install -c conda-forge ifcopenshell
- Better dependency resolution for OpenCASCADE
- More up-to-date than pip releases
Blender Integration
# Bonsai addon bundles ifcopenshell — no separate install needed
# For standalone install into Blender's Python:
/path/to/blender/python/bin/python -m pip install ifcopenshell
Platform Notes
| Platform | Note |
|---|---|
| Windows | pip works out-of-the-box. For Blender: install into Blender's Python. Watch for PATH conflicts. |
| macOS | Works on Intel and Apple Silicon. Use native arm64 Python, not Rosetta. |
| Linux | Works on most distributions. Headless/Docker: no GPU needed. |
Version Detection
import ifcopenshell
# Library version
ifcopenshell.version # e.g., "0.8.1"
# Schema of an open file
model = ifcopenshell.open("model.ifc")
model.schema # "IFC2X3", "IFC4", or "IFC4X3"
model.schema_identifier # e.g., "IFC4_ADD2"
model.schema_version # e.g., (4, 0, 2, 1)
Reference Links
- API Method Signatures — Complete signatures for entity_instance, file, by_type, remove, schema
- Working Code Examples — End-to-end runtime patterns
- Anti-Patterns — Common runtime mistakes and how to avoid them
More from openaec-foundation/computational-design-day-delft-march-2026
blender-core-api
Guides Blender Python API usage including bpy module structure, RNA data access, context system, dependency graph, and operator invocation. Activates when writing bpy scripts, creating Blender addons, or accessing Blender data blocks programmatically.
1blender-syntax-panels
Defines Blender UI panel creation including bpy.types.Panel, draw() method, UILayout API (row/column/box/split), bl_space_type, bl_region_type, bl_category, sub-panels, draw_header, menus, and UIList. Activates when creating custom Blender panels, building addon interfaces, or working with UILayout elements.
1ifcos-syntax-api
Documents the ifcopenshell.api module system with all 30+ API modules, invocation patterns via api.run() and direct module calls, parameter conventions, and module categorization. Activates when creating IFC entities, modifying properties, managing spatial structure, or using any ifcopenshell.api function.
1bonsai-impl-bcf
Guides implementation of BIM Collaboration Format (BCF) workflows in Bonsai including creating BCF topics, adding viewpoints with camera snapshots, managing comments, importing/exporting BCF files (v2.1 and v3.0), and integrating BCF issue tracking with IFC element references. Activates when working with BCF files, BIM issue tracking, clash report management, or collaboration workflows in Bonsai.
1ifcos-impl-mep
Guides MEP (Mechanical, Electrical, Plumbing) modeling in IFC using ifcopenshell.api.system including IfcSystem, IfcDistributionElement, ports, connections, flow segments, fittings, and MEP-specific property sets. Activates when creating HVAC systems, piping networks, electrical circuits, or MEP elements in IFC models.
1ifcos-impl-cost
Guides IFC cost management using ifcopenshell.api.cost including cost schedules, cost items, cost values, cost quantities, and 5D BIM workflows. Activates when implementing cost estimation in IFC models, creating cost schedules, or linking quantities to cost items.
1