bonsai-syntax-spatial
Bonsai Spatial Structure Syntax
Version: Bonsai v0.8.x | Blender 4.2.0+ | Python 3.11 Module path:
bonsai.*— NEVERblenderbim.*Source files:core/spatial.py,tool/spatial.py,bim/module/spatial/
Critical Warnings
- ALWAYS use
bonsai.*imports. NEVER useblenderbim.*— renamed in 2024 (v0.8.0+). - ALWAYS build spatial hierarchy via
IfcRelAggregatesusingifcopenshell.api.run("aggregate.assign_object", ...). NEVER create spatial elements without connecting them to the hierarchy. - ALWAYS use
ifcopenshell.api.run("spatial.assign_container", ...)to place physical elements in spatial containers. NEVER set containment via direct attribute manipulation. - ALWAYS pass
productsas a list (v0.8+ breaking change):products=[wall], NOTproduct=wall. - NEVER delete
IfcProject— it is the root of every IFC model. Bonsai operators enforce this constraint. - ALWAYS set a default container (typically
IfcBuildingStorey) before creating elements. Usebpy.ops.bim.set_default_container(container=storey_id). - NEVER assign an element to multiple containers via
IfcRelContainedInSpatialStructure— an element can only be contained in ONE spatial structure. UseIfcRelReferencedInSpatialStructurefor multi-storey references. - ALWAYS use
ifcopenshell.api.run("root.create_entity", ...)to create spatial elements. NEVER usemodel.create_entity()— it skips GlobalId generation and ownership assignment.
IFC Spatial Hierarchy
IfcProject (exactly 1 per file, root node)
└── IfcSite (via IfcRelAggregates)
└── IfcBuilding (via IfcRelAggregates)
└── IfcBuildingStorey (via IfcRelAggregates)
├── IfcSpace (via IfcRelAggregates)
├── IfcWall (via IfcRelContainedInSpatialStructure)
├── IfcDoor (via IfcRelContainedInSpatialStructure)
└── IfcColumn (via IfcRelContainedInSpatialStructure)
Spatial Elements
| IFC Class | Purpose | Required? | Typical Count |
|---|---|---|---|
IfcProject |
Root of model | YES (exactly 1) | 1 |
IfcSite |
Geographic land parcel | Recommended | 1+ |
IfcBuilding |
Physical building structure | Recommended | 1+ |
IfcBuildingStorey |
Floor level | Recommended | 1+ per building |
IfcSpace |
Room, corridor, void | Optional | 0+ per storey |
Three Key Relationships
| Relationship | IFC Class | Purpose | Cardinality |
|---|---|---|---|
| Spatial decomposition | IfcRelAggregates |
Parent-child between spatial elements | 1:N |
| Spatial containment | IfcRelContainedInSpatialStructure |
Places physical elements in a spatial zone | 1:N (exclusive) |
| Spatial reference | IfcRelReferencedInSpatialStructure |
Non-exclusive reference (multi-storey elements) | M:N |
Decision Tree: Spatial Operations
What spatial operation do you need?
│
├── Create spatial hierarchy (Project/Site/Building/Storey)?
│ ├── Create elements: root.create_entity with ifc_class="IfcSite" etc.
│ └── Connect hierarchy: aggregate.assign_object
│
├── Place physical element (wall/door/column) in a storey or space?
│ └── spatial.assign_container (products=[element], relating_structure=storey)
│
├── Remove element from container?
│ └── spatial.unassign_container (products=[element])
│
├── Reference element in additional storey (multi-storey column)?
│ └── spatial.reference_structure (products=[element], relating_structure=storey2)
│
├── Remove spatial reference?
│ └── spatial.dereference_structure (products=[element], relating_structure=storey2)
│
├── Query spatial containment?
│ └── ifcopenshell.util.element.get_container(element)
│
├── Query spatial decomposition?
│ └── ifcopenshell.util.element.get_decomposition(container)
│
├── Delete spatial container?
│ └── bpy.ops.bim.delete_container(container=container_id)
│ NOTE: Cannot delete IfcProject
│
└── Generate IfcSpace from walls?
├── From cursor/object position: bpy.ops.bim.generate_space()
└── From selected walls: bpy.ops.bim.generate_spaces_from_walls()
Essential Patterns
Pattern 1: Build Complete Spatial Hierarchy
# Bonsai v0.8.x — Build spatial hierarchy from scratch
# Requires: active Bonsai project in Blender
import bpy
import ifcopenshell
import ifcopenshell.api
model = ifcopenshell.file(schema="IFC4")
# Step 1: Create project (root)
project = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcProject", name="My Project")
# Step 2: Assign units (required before geometry)
ifcopenshell.api.run("unit.assign_unit", model)
# Step 3: Create spatial elements
site = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcSite", name="Default Site")
building = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcBuilding", name="Building A")
ground_floor = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcBuildingStorey", name="Ground Floor")
first_floor = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcBuildingStorey", name="First Floor")
# Step 4: Connect hierarchy via IfcRelAggregates
ifcopenshell.api.run("aggregate.assign_object", model,
products=[site], relating_object=project)
ifcopenshell.api.run("aggregate.assign_object", model,
products=[building], relating_object=site)
ifcopenshell.api.run("aggregate.assign_object", model,
products=[ground_floor, first_floor], relating_object=building)
Pattern 2: Assign Element to Container
# Bonsai v0.8.x — Place wall in a storey
wall = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcWall", name="Wall 001")
# Assign to ground floor (IfcRelContainedInSpatialStructure)
ifcopenshell.api.run("spatial.assign_container", model,
products=[wall], relating_structure=ground_floor)
Pattern 3: Multi-Storey Element Reference
# Bonsai v0.8.x — Column spanning two storeys
column = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcColumn", name="Column C1")
# Primary containment in ground floor
ifcopenshell.api.run("spatial.assign_container", model,
products=[column], relating_structure=ground_floor)
# Additional reference in first floor (IfcRelReferencedInSpatialStructure)
ifcopenshell.api.run("spatial.reference_structure", model,
products=[column], relating_structure=first_floor)
Pattern 4: Query Spatial Information
# Bonsai v0.8.x — Query spatial relationships
import ifcopenshell.util.element
# Get container of an element
container = ifcopenshell.util.element.get_container(wall)
# Returns: IfcBuildingStorey or None
# Get all elements in a container
elements = ifcopenshell.util.element.get_decomposition(building)
# Returns: list of child spatial elements
# Get all parts (direct children via IfcRelAggregates)
parts = ifcopenshell.util.element.get_parts(building)
# Returns: list of IfcBuildingStorey instances
Pattern 5: Bonsai Operators for Spatial Operations
# Bonsai v0.8.x — Using Bonsai operators (inside Blender)
import bpy
# Set default container for new elements
bpy.ops.bim.set_default_container(container=storey.id())
# Assign selected objects to a container
bpy.ops.bim.assign_container(container=storey.id())
# Remove container assignment from selected objects
bpy.ops.bim.remove_container()
# Reference selected objects from a structure
bpy.ops.bim.reference_structure()
# Delete a spatial container (NOT IfcProject)
bpy.ops.bim.delete_container(container=storey.id())
# Copy selected objects to another container
bpy.ops.bim.copy_to_container(container=target_storey.id())
# Generate IfcSpace from surrounding walls
bpy.ops.bim.generate_space()
# Generate spaces from selected walls
bpy.ops.bim.generate_spaces_from_walls()
# Load spatial decomposition tree in UI
bpy.ops.bim.import_spatial_decomposition()
# Select all objects in a container
bpy.ops.bim.select_similar_container(is_recursive=True)
# Toggle IfcSpace visibility
bpy.ops.bim.toggle_spatial_elements(is_visible=True)
Bonsai Three-Layer Architecture (Spatial Module)
DELIVERY LAYER — bim/module/spatial/operator.py
BIM_OT_assign_container, BIM_OT_delete_container, etc.
Calls: core.spatial.assign_container(tool.Ifc, tool.Spatial, ...)
─────────────────────────────────────────────────────────
DOMAIN LAYER — core/spatial.py
assign_container(), reference_structure(), generate_space(), etc.
Validates via: spatial.can_contain(), spatial.can_reference()
Calls: ifc.run("spatial.assign_container", ...)
─────────────────────────────────────────────────────────
TOOL LAYER — tool/spatial.py
Spatial.get_container(), Spatial.can_contain(), etc.
Concrete implementation using bpy + ifcopenshell
Operator Execution Pattern
# bim/module/spatial/operator.py (Delivery Layer)
class BIM_OT_assign_container(bpy.types.Operator):
bl_idname = "bim.assign_container"
bl_label = "Assign Container"
bl_options = {"REGISTER", "UNDO"}
container: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
# Dependency injection: pass tool implementations to core
core.spatial.assign_container(
tool.Ifc, tool.Collector, tool.Spatial,
container=tool.Ifc.get().by_id(self.container),
objs=context.selected_objects,
)
return {"FINISHED"}
Key Rules
- Operators call
IfcStore.execute_ifc_operator(self, context)— wraps IFC ops with undo. - Actual logic goes in
_execute(), NOTexecute(). - Tool classes (
tool.Ifc,tool.Spatial) are passed as dependency injection. bl_optionsALWAYS includes"UNDO"for IFC-modifying operators.
Spatial Validation Rules
Containment Validation (can_contain)
An element can be contained in a spatial structure element ONLY if:
- The container is an
IfcSpatialStructureElement(IFC2X3) or alsoIfcExternalSpatialStructureElement(IFC4+) - The element has a
ContainedInStructureinverse attribute
Reference Validation (can_reference)
An element can reference a spatial structure ONLY if:
- The structure is an
IfcSpatialStructureElement(IFC2X3) orIfcSpatialElement(IFC4+) - The element has a
ReferencedInStructuresinverse attribute
Default Project Structure
When creating a new IFC project via Bonsai UI (File > New IFC Project), Bonsai generates:
IfcProject "My Project"
└── IfcSite "My Site"
└── IfcBuilding "My Building"
└── IfcBuildingStorey "My Storey" (Elevation: 0.0)
The default container is set to IfcBuildingStorey "My Storey".
Space Generation
Bonsai provides two automated space generation methods:
From Context (Surrounding Walls)
- Set default container (an
IfcBuildingStorey) - Position cursor or select an object at the space location
- Call
bpy.ops.bim.generate_space() - Bonsai bisects visible walls/columns at storey elevation, polygonizes boundaries, finds the enclosing polygon, and extrudes it to create an
IfcSpace
From Selected Walls
- Select wall objects that enclose spaces
- Call
bpy.ops.bim.generate_spaces_from_walls() - Bonsai computes the union of wall footprints and creates
IfcSpaceelements from interior holes
IFC Schema Version Differences
| Entity / Feature | IFC2X3 | IFC4 | IFC4X3 |
|---|---|---|---|
IfcSite |
Yes | Yes | Yes |
IfcBuilding |
Yes | Yes | Yes |
IfcBuildingStorey |
Yes | Yes | Yes |
IfcSpace |
Yes | Yes | Yes |
IfcFacility |
No | No | Yes |
IfcFacilityPart |
No | No | Yes |
IfcSpatialElement (base class) |
No | Yes | Yes |
IfcExternalSpatialStructureElement |
No | Yes | Yes |
| Containment validation class | IfcSpatialStructureElement |
+ IfcExternalSpatialStructureElement |
+ IfcExternalSpatialStructureElement |
| Reference validation class | IfcSpatialStructureElement |
IfcSpatialElement |
IfcSpatialElement |
Source Code Paths
| Component | Path in IfcOpenShell Monorepo |
|---|---|
| Core logic | src/bonsai/bonsai/core/spatial.py |
| Tool implementation | src/bonsai/bonsai/tool/spatial.py |
| Operators | src/bonsai/bonsai/bim/module/spatial/operator.py |
| UI panels | src/bonsai/bonsai/bim/module/spatial/ui.py |
| Properties | src/bonsai/bonsai/bim/module/spatial/prop.py |
| Interface contract | src/bonsai/bonsai/core/tool.py (class Spatial) |
Dependencies
- ifcos-syntax-api — For
ifcopenshell.api.run()invocation patterns and module reference - bonsai-core-architecture — For three-layer architecture, IfcStore, tool.Ifc patterns
Reference Links
- Spatial API Method Signatures — Complete signatures for core, tool, and operator methods
- Working Code Examples — End-to-end spatial structure examples
- Anti-Patterns — Common spatial structure 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