Bonsai QTO (Quantity Takeoff): Implementation Skill
Bonsai v0.8.x | Module: bonsai.bim.module.qto | NEVER use blenderbim.bim.module.qto
1. Quick Reference
Critical Warnings
- ALWAYS verify objects have mesh geometry before running QTO calculators. Empties, curves, and lights cause failures.
- ALWAYS ensure objects have an IFC class assigned via Bonsai before running QTO. Calculator functions query IFC properties.
- ALWAYS assign
Pset_MaterialCommon.MassDensity BEFORE running weight calculations (get_net_weight, get_gross_weight).
- ALWAYS re-run QTO calculations after modifying geometry. Quantities are NOT live-linked to geometry.
- NEVER assume
get_gross_volume() accounts for openings. Gross = original geometry WITHOUT openings. Net = WITH openings subtracted.
- NEVER use the legacy
blenderbim module path. ALWAYS use bonsai.bim.module.qto.
Decision Tree: Which QTO Approach?
Need quantities for IFC elements?
├── Single element, single quantity?
│ └── Use bim.calculate_single_quantity operator
│ Parameters: calculator, qto_name, prop_name, calculator_function
│
├── Single element, all base quantities?
│ └── Use bim.perform_quantity_take_off with element selected
│ Select object → Set qto_rule → Execute operator
│
├── Multiple elements, standard base quantities?
│ └── Use bim.perform_quantity_take_off in batch mode
│ Select all target objects → Set qto_rule → Execute operator
│
├── All elements of a class?
│ └── Use bim.perform_quantity_take_off with no selection
│ Operator processes all IfcElement instances matching the rule
│
├── Custom quantity (edge lengths, face areas)?
│ └── Use mesh-level operators in Edit Mode:
│ - bim.calculate_edge_lengths (selected edges)
│ - bim.calculate_face_areas (selected faces)
│ - bim.calculate_circle_radius (selected vertices)
│
└── Need gross vs net distinction?
├── With openings (doors/windows in walls) → Use net variants
│ get_net_volume(), get_net_surface_area(), get_net_footprint_area()
└── Without openings (original geometry) → Use gross variants
get_gross_volume(), get_gross_surface_area(), get_gross_footprint_area()
2. Module Architecture
File Structure
| File |
Purpose |
__init__.py |
Module registration |
operator.py |
8 QTO operators |
prop.py |
BIMQtoProperties with calculator/rule settings |
ui.py |
Panel UI for quantity takeoff |
data.py |
Data loading and caching |
calculator.py |
Geometry calculation engine (30+ functions) |
helper.py |
Mesh quantity calculation helpers |
Two-Layer System
- Calculator functions (
calculator.py) — Extract numeric values from Blender geometry (bpy.types.Object)
- QTO rules (JSON configuration) — Map IFC classes to standard quantity sets and calculator functions
Core/Tool/Operator Pattern
bonsai.core.qto → Pure logic functions (accept tool interfaces)
bonsai.tool.Qto → Blender-specific implementations
bonsai.bim.module.qto → Operators that wire core to tool
3. Calculator Functions by Category
Linear Quantities
| Function |
Returns |
Notes |
get_linear_length() |
Longest bounding box edge |
Simple bbox calculation |
get_length() |
Parametric axis length |
AXIS2/AXIS3 aware |
get_width() |
Width dimension |
From bbox or parametric data |
get_height() |
Height (Z dimension) |
From bounding box |
get_depth() |
Depth dimension |
For slabs, footings |
get_perimeter() |
Net footprint perimeter |
Perimeter of lowest polygon |
get_gross_perimeter() |
Gross footprint perimeter |
Without opening subtractions |
Area Quantities
| Function |
Returns |
Notes |
get_net_footprint_area() |
Lowest polygon face area |
Net of openings |
get_gross_footprint_area() |
Footprint without openings |
Gross geometry |
get_roofprint_area() |
Highest polygon face area |
For roofs |
get_side_area() |
Lateral face areas |
Directional filter support |
get_lateral_area() |
Side surfaces |
Lateral faces only |
get_top_area() |
+Z normal faces |
Configurable angle threshold (default 45 deg) |
get_net_surface_area() |
Total surface (net) |
With opening subtractions |
get_gross_surface_area() |
Total surface (gross) |
Without opening subtractions |
get_cross_section_area() |
Area at cut plane |
For columns, beams |
get_projected_area() |
Projection onto x/y/z |
Axis-specific projection |
get_opening_area() |
Opening areas |
Normal vector angle filtering |
get_formwork_area() |
Surface minus top faces |
For concrete formwork |
get_side_formwork_area() |
Lateral surfaces only |
Excludes Z-normal faces |
Volume Quantities
| Function |
Returns |
Notes |
get_net_volume() |
Volume with openings |
BMesh-calculated |
get_gross_volume() |
Volume without openings |
Uses disable-opening-subtractions flag |
get_space_net_volume() |
Space volume minus walls/columns |
Decomposition-aware |
Weight Quantities
| Function |
Returns |
Notes |
get_net_weight() |
net_volume x mass_density |
Requires Pset_MaterialCommon.MassDensity |
get_gross_weight() |
gross_volume x mass_density |
Requires Pset_MaterialCommon.MassDensity |
| Profile-based weight |
Pset_ProfileMechanical.MassPerLength x length |
For steel profiles |
Specialized Quantities
| Function |
Returns |
Notes |
get_stair_length() |
Hypotenuse of (length^2 + height^2) |
For stairs |
get_finish_floor_height() |
Floor finish height |
Decomposition-aware |
get_finish_ceiling_height() |
Ceiling height |
Decomposition-aware |
get_contact_area() |
Polygon intersection between objects |
Requires Shapely |
4. QTO Rules (JSON Configuration)
Rules map IFC classes to standard quantity sets and calculator functions. Stored as JSON in ifc5d.qto.rules.
Rule Structure
{
"Name": "Qto_WallBaseQuantities",
"Description": "Standard wall base quantities per IFC4",
"Calculator": "Blender",
"Mappings": {
"IfcWall": {
"Qto_WallBaseQuantities": {
"Length": "get_length",
"Width": "get_width",
"Height": "get_height",
"GrossFootprintArea": "get_gross_footprint_area",
"NetFootprintArea": "get_net_footprint_area",
"GrossSideArea": "get_gross_surface_area",
"NetSideArea": "get_net_surface_area",
"GrossVolume": "get_gross_volume",
"NetVolume": "get_net_volume"
}
}
}
}
Rule Selection
BIMQtoProperties.qto_rule — Enum populated from tool.Qto.get_qto_rules()
- Rules are filtered by IFC schema version (IFC4 vs IFC4X3)
BIMQtoProperties.fallback=True — Tries alternative calculators if primary lacks support
5. Standard Quantity Sets
| IFC Class |
Quantity Set |
Key Quantities |
IfcWall |
Qto_WallBaseQuantities |
Length, Width, Height, GrossVolume, NetVolume, GrossSideArea, NetSideArea |
IfcSlab |
Qto_SlabBaseQuantities |
Width, Length, Depth, GrossArea, NetArea, GrossVolume, NetVolume, Perimeter |
IfcColumn |
Qto_ColumnBaseQuantities |
Length, CrossSectionArea, OuterSurfaceArea, GrossVolume, NetVolume, GrossWeight |
IfcBeam |
Qto_BeamBaseQuantities |
Length, CrossSectionArea, OuterSurfaceArea, GrossVolume, NetVolume, GrossWeight |
IfcDoor |
Qto_DoorBaseQuantities |
Height, Width, Area |
IfcWindow |
Qto_WindowBaseQuantities |
Height, Width, Area |
IfcSpace |
Qto_SpaceBaseQuantities |
Height, FinishCeilingHeight, FinishFloorHeight, GrossFloorArea, NetFloorArea |
IfcRoof |
Qto_RoofBaseQuantities |
GrossArea, NetArea, ProjectedArea |
IfcStair |
Qto_StairBaseQuantities |
Length, GrossVolume, NetVolume |
6. Operators Reference
| Operator |
bl_idname |
Use Case |
CalculateCircleRadius |
bim.calculate_circle_radius |
Selected vertices in Edit Mode |
CalculateEdgeLengths |
bim.calculate_edge_lengths |
Selected edges in Edit Mode |
CalculateFaceAreas |
bim.calculate_face_areas |
Selected faces in Edit Mode |
CalculateObjectVolumes |
bim.calculate_object_volumes |
Mesh volumes in Object Mode |
CalculateFormworkArea |
bim.calculate_formwork_area |
Surface minus top faces |
CalculateSideFormworkArea |
bim.calculate_side_formwork_area |
Lateral surfaces only |
CalculateSingleQuantity |
bim.calculate_single_quantity |
One quantity via specific calculator function |
PerformQuantityTakeOff |
bim.perform_quantity_take_off |
Batch QTO using rules |
PerformQuantityTakeOff: Primary Batch Operator
# Bonsai v0.8.x: Batch quantity takeoff
import bpy
bpy.ops.bim.perform_quantity_take_off(
qto_rule="Qto_WallBaseQuantities" # Rule name from JSON config
)
Behavior: Processes selected objects. If no objects selected, processes ALL IfcElement instances matching the rule's IFC class filter.
CalculateSingleQuantity: Targeted Calculation
# Bonsai v0.8.x: Single quantity calculation
bpy.ops.bim.calculate_single_quantity(
calculator="Blender",
qto_name="Qto_WallBaseQuantities",
prop_name="GrossVolume",
calculator_function="get_gross_volume"
)
7. Cost Integration
QTO integrates with Bonsai cost management:
# Bonsai v0.8.x: QTO-Cost linkage
# tool.Qto.get_related_cost_item_quantities(product) returns:
# [{"cost_item": <IfcCostItem>, "quantities": [<matched quantity properties>]}]
Unit conversion: tool.Qto.convert_to_project_units() handles measurement system conversion using IFC schema unit definitions.
8. External Dependencies
| Dependency |
Used By |
Purpose |
BMesh |
Volume calculations |
Mesh volume computation |
mathutils.BVHTree |
Proximity detection |
Spatial queries |
Shapely |
get_contact_area() |
2D polygon intersection |
ifcopenshell.api.geometry |
Gross calculations |
Shape generation with opening control |
9. Gross vs. Net: Critical Distinction
Gross quantities:
- Original element geometry WITHOUT openings
- Wall with door opening → gross volume = solid wall volume
- Use when: material ordering, structural analysis
Net quantities:
- Element geometry WITH openings subtracted
- Wall with door opening → net volume = wall volume minus door void
- Use when: actual material in place, cost calculation
ALWAYS choose the correct variant. Using gross when net is required (or vice versa) produces incorrect quantity takeoffs.
10. Reference Files
- references/methods.md — Complete API signatures for all calculator functions and operators
- references/examples.md — Working QTO workflow examples
- references/anti-patterns.md — Common QTO mistakes and correct approaches
1---2name: bonsai-impl-qto3description: Use when calculating quantities from IFC elements in Bonsai -- area, length, volume, weight, or custom quantity sets. Prevents the common mistake of manually computing quantities instead of using QtoCalculator for automated base quantity extraction from geometry. Covers IfcElementQuantity sets, bulk quantity operations, and the complete QTO pipeline from geometry analysis to export. Keywords: quantity takeoff, QTO, IfcElementQuantity, area, volume, length, weight, QtoCalculator, base quantities, cost estimation, calculate area, how much material, measure building.4license: MIT5---67# Bonsai QTO (Quantity Takeoff): Implementation Skill89> **Bonsai v0.8.x** | Module: `bonsai.bim.module.qto` | NEVER use `blenderbim.bim.module.qto`1011## 1. Quick Reference1213### Critical Warnings1415- **ALWAYS** verify objects have mesh geometry before running QTO calculators. Empties, curves, and lights cause failures.16- **ALWAYS** ensure objects have an IFC class assigned via Bonsai before running QTO. Calculator functions query IFC properties.17- **ALWAYS** assign `Pset_MaterialCommon.MassDensity` BEFORE running weight calculations (`get_net_weight`, `get_gross_weight`).18- **ALWAYS** re-run QTO calculations after modifying geometry. Quantities are NOT live-linked to geometry.19- **NEVER** assume `get_gross_volume()` accounts for openings. Gross = original geometry WITHOUT openings. Net = WITH openings subtracted.20- **NEVER** use the legacy `blenderbim` module path. ALWAYS use `bonsai.bim.module.qto`.2122### Decision Tree: Which QTO Approach?2324```25Need quantities for IFC elements?26├── Single element, single quantity?27│ └── Use bim.calculate_single_quantity operator28│ Parameters: calculator, qto_name, prop_name, calculator_function29│30├── Single element, all base quantities?31│ └── Use bim.perform_quantity_take_off with element selected32│ Select object → Set qto_rule → Execute operator33│34├── Multiple elements, standard base quantities?35│ └── Use bim.perform_quantity_take_off in batch mode36│ Select all target objects → Set qto_rule → Execute operator37│38├── All elements of a class?39│ └── Use bim.perform_quantity_take_off with no selection40│ Operator processes all IfcElement instances matching the rule41│42├── Custom quantity (edge lengths, face areas)?43│ └── Use mesh-level operators in Edit Mode:44│ - bim.calculate_edge_lengths (selected edges)45│ - bim.calculate_face_areas (selected faces)46│ - bim.calculate_circle_radius (selected vertices)47│48└── Need gross vs net distinction?49 ├── With openings (doors/windows in walls) → Use net variants50 │ get_net_volume(), get_net_surface_area(), get_net_footprint_area()51 └── Without openings (original geometry) → Use gross variants52 get_gross_volume(), get_gross_surface_area(), get_gross_footprint_area()53```5455## 2. Module Architecture5657### File Structure5859| File | Purpose |60|------|---------|61| `__init__.py` | Module registration |62| `operator.py` | 8 QTO operators |63| `prop.py` | `BIMQtoProperties` with calculator/rule settings |64| `ui.py` | Panel UI for quantity takeoff |65| `data.py` | Data loading and caching |66| `calculator.py` | Geometry calculation engine (30+ functions) |67| `helper.py` | Mesh quantity calculation helpers |6869### Two-Layer System70711. **Calculator functions** (`calculator.py`) — Extract numeric values from Blender geometry (`bpy.types.Object`)722. **QTO rules** (JSON configuration) — Map IFC classes to standard quantity sets and calculator functions7374### Core/Tool/Operator Pattern7576```77bonsai.core.qto → Pure logic functions (accept tool interfaces)78bonsai.tool.Qto → Blender-specific implementations79bonsai.bim.module.qto → Operators that wire core to tool80```8182## 3. Calculator Functions by Category8384### Linear Quantities8586| Function | Returns | Notes |87|----------|---------|-------|88| `get_linear_length()` | Longest bounding box edge | Simple bbox calculation |89| `get_length()` | Parametric axis length | AXIS2/AXIS3 aware |90| `get_width()` | Width dimension | From bbox or parametric data |91| `get_height()` | Height (Z dimension) | From bounding box |92| `get_depth()` | Depth dimension | For slabs, footings |93| `get_perimeter()` | Net footprint perimeter | Perimeter of lowest polygon |94| `get_gross_perimeter()` | Gross footprint perimeter | Without opening subtractions |9596### Area Quantities9798| Function | Returns | Notes |99|----------|---------|-------|100| `get_net_footprint_area()` | Lowest polygon face area | Net of openings |101| `get_gross_footprint_area()` | Footprint without openings | Gross geometry |102| `get_roofprint_area()` | Highest polygon face area | For roofs |103| `get_side_area()` | Lateral face areas | Directional filter support |104| `get_lateral_area()` | Side surfaces | Lateral faces only |105| `get_top_area()` | +Z normal faces | Configurable angle threshold (default 45 deg) |106| `get_net_surface_area()` | Total surface (net) | With opening subtractions |107| `get_gross_surface_area()` | Total surface (gross) | Without opening subtractions |108| `get_cross_section_area()` | Area at cut plane | For columns, beams |109| `get_projected_area()` | Projection onto x/y/z | Axis-specific projection |110| `get_opening_area()` | Opening areas | Normal vector angle filtering |111| `get_formwork_area()` | Surface minus top faces | For concrete formwork |112| `get_side_formwork_area()` | Lateral surfaces only | Excludes Z-normal faces |113114### Volume Quantities115116| Function | Returns | Notes |117|----------|---------|-------|118| `get_net_volume()` | Volume with openings | BMesh-calculated |119| `get_gross_volume()` | Volume without openings | Uses `disable-opening-subtractions` flag |120| `get_space_net_volume()` | Space volume minus walls/columns | Decomposition-aware |121122### Weight Quantities123124| Function | Returns | Notes |125|----------|---------|-------|126| `get_net_weight()` | net_volume x mass_density | Requires `Pset_MaterialCommon.MassDensity` |127| `get_gross_weight()` | gross_volume x mass_density | Requires `Pset_MaterialCommon.MassDensity` |128| Profile-based weight | `Pset_ProfileMechanical.MassPerLength` x length | For steel profiles |129130### Specialized Quantities131132| Function | Returns | Notes |133|----------|---------|-------|134| `get_stair_length()` | Hypotenuse of (length^2 + height^2) | For stairs |135| `get_finish_floor_height()` | Floor finish height | Decomposition-aware |136| `get_finish_ceiling_height()` | Ceiling height | Decomposition-aware |137| `get_contact_area()` | Polygon intersection between objects | Requires Shapely |138139## 4. QTO Rules (JSON Configuration)140141Rules map IFC classes to standard quantity sets and calculator functions. Stored as JSON in `ifc5d.qto.rules`.142143### Rule Structure144145```json146{147 "Name": "Qto_WallBaseQuantities",148 "Description": "Standard wall base quantities per IFC4",149 "Calculator": "Blender",150 "Mappings": {151 "IfcWall": {152 "Qto_WallBaseQuantities": {153 "Length": "get_length",154 "Width": "get_width",155 "Height": "get_height",156 "GrossFootprintArea": "get_gross_footprint_area",157 "NetFootprintArea": "get_net_footprint_area",158 "GrossSideArea": "get_gross_surface_area",159 "NetSideArea": "get_net_surface_area",160 "GrossVolume": "get_gross_volume",161 "NetVolume": "get_net_volume"162 }163 }164 }165}166```167168### Rule Selection169170- `BIMQtoProperties.qto_rule` — Enum populated from `tool.Qto.get_qto_rules()`171- Rules are filtered by IFC schema version (IFC4 vs IFC4X3)172- `BIMQtoProperties.fallback=True` — Tries alternative calculators if primary lacks support173174## 5. Standard Quantity Sets175176| IFC Class | Quantity Set | Key Quantities |177|-----------|-------------|----------------|178| `IfcWall` | `Qto_WallBaseQuantities` | Length, Width, Height, GrossVolume, NetVolume, GrossSideArea, NetSideArea |179| `IfcSlab` | `Qto_SlabBaseQuantities` | Width, Length, Depth, GrossArea, NetArea, GrossVolume, NetVolume, Perimeter |180| `IfcColumn` | `Qto_ColumnBaseQuantities` | Length, CrossSectionArea, OuterSurfaceArea, GrossVolume, NetVolume, GrossWeight |181| `IfcBeam` | `Qto_BeamBaseQuantities` | Length, CrossSectionArea, OuterSurfaceArea, GrossVolume, NetVolume, GrossWeight |182| `IfcDoor` | `Qto_DoorBaseQuantities` | Height, Width, Area |183| `IfcWindow` | `Qto_WindowBaseQuantities` | Height, Width, Area |184| `IfcSpace` | `Qto_SpaceBaseQuantities` | Height, FinishCeilingHeight, FinishFloorHeight, GrossFloorArea, NetFloorArea |185| `IfcRoof` | `Qto_RoofBaseQuantities` | GrossArea, NetArea, ProjectedArea |186| `IfcStair` | `Qto_StairBaseQuantities` | Length, GrossVolume, NetVolume |187188## 6. Operators Reference189190| Operator | bl_idname | Use Case |191|----------|-----------|----------|192| `CalculateCircleRadius` | `bim.calculate_circle_radius` | Selected vertices in Edit Mode |193| `CalculateEdgeLengths` | `bim.calculate_edge_lengths` | Selected edges in Edit Mode |194| `CalculateFaceAreas` | `bim.calculate_face_areas` | Selected faces in Edit Mode |195| `CalculateObjectVolumes` | `bim.calculate_object_volumes` | Mesh volumes in Object Mode |196| `CalculateFormworkArea` | `bim.calculate_formwork_area` | Surface minus top faces |197| `CalculateSideFormworkArea` | `bim.calculate_side_formwork_area` | Lateral surfaces only |198| `CalculateSingleQuantity` | `bim.calculate_single_quantity` | One quantity via specific calculator function |199| `PerformQuantityTakeOff` | `bim.perform_quantity_take_off` | Batch QTO using rules |200201### PerformQuantityTakeOff: Primary Batch Operator202203```python204# Bonsai v0.8.x: Batch quantity takeoff205import bpy206207bpy.ops.bim.perform_quantity_take_off(208 qto_rule="Qto_WallBaseQuantities" # Rule name from JSON config209)210```211212**Behavior**: Processes selected objects. If no objects selected, processes ALL `IfcElement` instances matching the rule's IFC class filter.213214### CalculateSingleQuantity: Targeted Calculation215216```python217# Bonsai v0.8.x: Single quantity calculation218bpy.ops.bim.calculate_single_quantity(219 calculator="Blender",220 qto_name="Qto_WallBaseQuantities",221 prop_name="GrossVolume",222 calculator_function="get_gross_volume"223)224```225226## 7. Cost Integration227228QTO integrates with Bonsai cost management:229230```python231# Bonsai v0.8.x: QTO-Cost linkage232# tool.Qto.get_related_cost_item_quantities(product) returns:233# [{"cost_item": <IfcCostItem>, "quantities": [<matched quantity properties>]}]234```235236Unit conversion: `tool.Qto.convert_to_project_units()` handles measurement system conversion using IFC schema unit definitions.237238## 8. External Dependencies239240| Dependency | Used By | Purpose |241|------------|---------|---------|242| `BMesh` | Volume calculations | Mesh volume computation |243| `mathutils.BVHTree` | Proximity detection | Spatial queries |244| `Shapely` | `get_contact_area()` | 2D polygon intersection |245| `ifcopenshell.api.geometry` | Gross calculations | Shape generation with opening control |246247## 9. Gross vs. Net: Critical Distinction248249```250Gross quantities:251 - Original element geometry WITHOUT openings252 - Wall with door opening → gross volume = solid wall volume253 - Use when: material ordering, structural analysis254255Net quantities:256 - Element geometry WITH openings subtracted257 - Wall with door opening → net volume = wall volume minus door void258 - Use when: actual material in place, cost calculation259```260261**ALWAYS** choose the correct variant. Using gross when net is required (or vice versa) produces incorrect quantity takeoffs.262263## 10. Reference Files264265- [references/methods.md](references/methods.md) — Complete API signatures for all calculator functions and operators266- [references/examples.md](references/examples.md) — Working QTO workflow examples267- [references/anti-patterns.md](references/anti-patterns.md) — Common QTO mistakes and correct approaches