Install
$ agentstack add skill-impertio-studio-blender-bonsai-ifcopenshell-sverchok-claude-skill-package-ifcos-core-runtime ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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 → 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) #
type(wall.wrapped_data) #
# 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) # — 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](references/methods.md) — Complete signatures for entityinstance, file, bytype, remove, schema
- [Working Code Examples](references/examples.md) — End-to-end runtime patterns
- [Anti-Patterns](references/anti-patterns.md) — Common runtime mistakes and how to avoid them
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Impertio-Studio
- Source: Impertio-Studio/Blender-Bonsai-ifcOpenshell-Sverchok-Claude-Skill-Package
- License: MIT
- Homepage: https://github.com/OpenAEC-Foundation
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.