| name | ifcos-core-runtime |
| description | Use when debugging IfcOpenShell crashes, entity reference errors, or performance issues. Prevents the common pitfall of comparing entities with == instead of checking .id() or identity, or holding references to entities after removal. Covers C++ binding behavior, entity invalidation, by_type() return semantics, thread safety, memory management, PascalCase attributes, and installation patterns. Keywords: IfcOpenShell runtime, C++ binding, entity invalidation, by_type, thread safety, memory management, PascalCase, installation, entity identity, install IfcOpenShell, pip install ifcopenshell.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires IfcOpenShell Python library. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
IfcOpenShell Python Runtime
Quick Reference
Critical Warnings
- ALWAYS use
== for entity comparison, NEVER is. Each query returns a new Python wrapper object.
- ALWAYS set entity references to
None after calling model.remove(). The C++ object is deallocated regardless of Python reference count.
- ALWAYS keep a reference to the
ifcopenshell.file object 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.file from 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.schema before 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.
import ifcopenshell
model = ifcopenshell.open("model.ifc")
wall = model.by_type("IfcWall")[0]
type(wall)
type(wall.wrapped_data)
wall.file
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
wall = model.by_type("IfcWall")[0]
wall_a = model.by_id(wall.id())
wall_b = model.by_id(wall.id())
wall_a == wall_b
wall_a is wall_b
wall_a.id() == wall_b.id()
Pattern 3: Entity Invalidation After Removal
wall = model.by_type("IfcWall")[0]
wall_id = wall.id()
model.remove(wall)
wall = None
try:
still_exists = model.by_id(wall_id)
except RuntimeError:
still_exists = None
other_wall = model.by_type("IfcWall")[0]
ifcopenshell.api.run("root.remove_product", model, product=other_wall)
other_wall = None
Pattern 4: by_type() Return Semantics
walls = model.by_type("IfcWall")
type(walls)
len(walls)
wall_list = list(walls)
wall_list.append(some_other_entity)
all_walls = model.by_type("IfcWall")
only_walls = model.by_type("IfcWall", include_subtypes=False)
Pattern 5: PascalCase Attribute Access
wall = model.by_type("IfcWall")[0]
name = wall.Name
global_id = wall.GlobalId
description = wall.Description
object_type = wall.ObjectType
owner_history = wall.OwnerHistory
name = wall[2]
if wall.Description is not None:
print(wall.Description)
wall.is_a("IfcWall")
wall.is_a("IfcBuildingElement")
wall.is_a("IfcProduct")
wall.is_a("IfcSlab")
info = wall.get_info()
Pattern 6: Thread Safety
import concurrent.futures
def count_type(filepath, ifc_class):
model = ifcopenshell.open(filepath)
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"]
}
def process_copy(filepath, output_path):
local_model = ifcopenshell.open(filepath)
local_model.write(output_path)
Pattern 7: Memory Management for Large Models
model = ifcopenshell.open("large_building.ifc")
name = wall.Name
material = ifcopenshell.util.element.get_material(wall)
del model
import gc
gc.collect()
Pattern 8: File Lifecycle: Keep File Reference Alive
def get_walls():
model = ifcopenshell.open("model.ifc")
return model.by_type("IfcWall")
walls = get_walls()
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
Schema-Specific Attribute Differences
schema = model.schema
try:
pt = task.PredefinedType
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
/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
ifcopenshell.version
model = ifcopenshell.open("model.ifc")
model.schema
model.schema_identifier
model.schema_version
Reference Links