| name | ifc-qto-extraction |
| description | Extract quantities from IFC/Revit models for quantity takeoff. Uses DDC converters to get element counts, areas, volumes, lengths with grouping and reporting. |
IFC Quantity Takeoff Extraction
Extract structured quantity data from BIM models (IFC, Revit) for cost estimation, material ordering, and progress tracking.
Business Case
Problem: Manual quantity takeoff is:
- Time-consuming (40-80 hours for medium project)
- Error-prone (human counting mistakes)
- Not repeatable (changes require full rework)
- Disconnected from design (no live updates)
Solution: Automated QTO from BIM that:
- Extracts all quantities in minutes
- Groups by type, level, zone
- Updates instantly with model changes
- Exports to Excel for pricing
ROI: 90% reduction in QTO time, near-zero counting errors
DDC Tools Used
┌──────────────────────────────────────────────────────────────────────┐
│ QTO EXTRACTION PIPELINE │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ INPUT CONVERT ANALYZE │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ .rvt │ │ DDC │ │ Python │ │
│ │ .ifc │─────────►│Converter│───────────►│ pandas │ │
│ │ .dwg │ │ │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ │
│ │ .xlsx │ │ Grouped │ │
│ │ raw data│ │ QTO │ │
│ └─────────┘ └─────────┘ │
│ │ │
│ OUTPUT ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ QTO Report │ │
│ │ • Element counts by type │ │
│ │ • Areas (m², ft²) │ │
│ │ • Volumes (m³, ft³) │ │
│ │ • Lengths (m, ft) │ │
│ │ • Weights (kg, tons) │ │
│ │ • Grouped by level/zone/system │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
CLI Commands
Revit to Excel (with BBox for volumes)
RvtExporter.exe "C:\Models\Building.rvt"
RvtExporter.exe "C:\Models\Building.rvt" complete bbox
RvtExporter.exe "C:\Models\Building.rvt" complete bbox schedule
IFC to Excel
IfcExporter.exe "C:\Models\Building.ifc"
DWG to Excel (2D areas)
DwgExporter.exe "C:\Drawings\FloorPlan.dwg"
Python Implementation
import pandas as pd
import numpy as np
from pathlib import Path
import subprocess
from typing import List, Dict, Optional
from dataclasses import dataclass
@dataclass
class QuantityItem:
"""Single quantity line item"""
category: str
type_name: str
count: int
area: float = 0.0
volume: float = 0.0
length: float = 0.0
weight: float = 0.0
unit_area: str = "m²"
unit_volume: str = "m³"
unit_length: str = "m"
level: str = ""
zone: str = ""
class BIMQuantityExtractor:
"""Extract quantities from BIM models using DDC converters"""
def __init__(self, converter_path: str):
self.converter_path = Path(converter_path)
def convert_model(self, model_path: , options: [] = ) -> Path:
model = Path(model_path)
options = options [, ]
ext = model.suffix.lower()
converters = {
: ,
: ,
: ,
: ,
:
}
converter = .converter_path / converters.get(ext, )
cmd = [(converter), (model)] + options
result = subprocess.run(cmd, capture_output=, text=)
result.returncode != :
RuntimeError()
xlsx_path = model.with_suffix()
xlsx_path
() -> pd.DataFrame:
xlsx = Path(xlsx_path)
xlsx.exists():
FileNotFoundError()
df = pd.read_excel(xlsx, sheet_name=)
df.columns = df.columns..strip()
df
() -> [QuantityItem]:
include_categories df.columns:
df = df[df[].isin(include_categories)]
quantities = []
(category, type_name), group df.groupby([, group_by]):
item = QuantityItem(
category=(category),
type_name=(type_name),
count=(group)
)
area_cols = [, , , ]
col area_cols:
col group.columns:
item.area = group[col].()
vol_cols = [, , ]
col vol_cols:
col group.columns:
item.volume = group[col].()
len_cols = [, , ]
col len_cols:
col group.columns:
item.length = group[col].()
group.columns:
levels = group[].dropna().unique()
item.level = .join((l) l levels)
quantities.append(item)
quantities
() -> [, [QuantityItem]]:
result = {}
df.columns:
result[] = .extract_quantities(df, group_by)
result
level, level_df df.groupby():
level_name = (level) pd.notna(level)
result[level_name] = .extract_quantities(level_df, group_by)
result
() -> :
concrete_categories = [
, ,
, ,
, ,
, ,
,
]
concrete_df = df[df[].isin(concrete_categories)]
{
: concrete_df[].() concrete_df.columns ,
: concrete_df.groupby()[].().to_dict() concrete_df.columns {},
: (concrete_df)
}
() -> :
wall_categories = [, , ]
walls = df[df[].isin(wall_categories)]
result = {
: ,
: ,
: {}
}
walls.columns:
result[] = walls[].()
walls.columns:
result[] = walls[].()
walls.columns:
type_name, group walls.groupby():
result[][type_name] = {
: (group),
: group[].() group.columns ,
: group[].() group.columns
}
result
() -> :
records = []
q quantities:
records.append({
: q.category,
: q.type_name,
: q.count,
: (q.area, ),
: (q.volume, ),
: (q.length, ),
: q.level
})
df = pd.DataFrame(records)
df = df.sort_values([, ])
pd.ExcelWriter(output_path, engine=) writer:
summary = df.groupby().agg({
: ,
: ,
: ,
:
}).()
summary.to_excel(writer, sheet_name=)
df.to_excel(writer, sheet_name=, index=)
df.columns df[].notna().():
level_summary = df.groupby([, ]).agg({
: ,
: ,
:
}).()
level_summary.to_excel(writer, sheet_name=)
output_path
() -> :
by_category = {}
q quantities:
q.category by_category:
by_category[q.category] = []
by_category[q.category].append(q)
total_count = (q.count q quantities)
total_area = (q.area q quantities)
total_volume = (q.volume q quantities)
html =
category, items (by_category.items()):
cat_count = (i.count i items)
cat_area = (i.area i items)
cat_volume = (i.volume i items)
html +=
item (items, key= x: x.type_name):
html +=
html +=
(output_path, , encoding=) f:
f.write(html)
output_path
() -> :
datetime datetime
extractor = BIMQuantityExtractor(converter_path)
()
xlsx_path = extractor.convert_model(model_path, [, ])
()
df = extractor.load_bim_data(xlsx_path)
quantities = extractor.extract_quantities(df)
output_dir = output_dir Path(model_path).parent
timestamp = datetime.now().strftime()
excel_path = Path(output_dir) /
html_path = Path(output_dir) /
extractor.generate_qto_report(quantities, (excel_path))
extractor.generate_html_report(quantities, (html_path))
concrete = extractor.calculate_concrete_quantities(df)
walls = extractor.calculate_wall_quantities(df)
{
: (excel_path),
: (html_path),
: {
: (df),
: df[].nunique() df.columns ,
: df[].nunique() df.columns
},
: concrete,
: walls
}
__name__ == :
result = extract_qto_from_model(
model_path=,
converter_path=,
output_dir=
)
()
()
()
n8n Workflow Integration
name: BIM QTO Extraction
trigger:
type: webhook
path: /qto-extract
steps:
- convert_model:
node: Execute Command
command: |
"C:\DDC\RvtExporter.exe" "{{$json.model_path}}" complete bbox schedule
- load_excel:
node: Spreadsheet File
operation: read
file: "={{$json.model_path.replace('.rvt', '.xlsx')}}"
- group_quantities:
node: Code
code: |
const grouped = {};
items.forEach(item => {
const type = item.json['Type Name'];
if (!grouped[type]) {
[] {
,
,
}
}
[]
[] []
[] []
}
Best Practices
- Model Quality: Ensure BIM model has proper levels and types assigned
- Units: Verify model units match expected output units
- Categories: Use consistent category naming for grouping
- Updates: Re-run QTO after design changes
- Validation: Cross-check totals against manual spot checks
Common Quantity Formulas
formwork_area = concrete_volume * 6
rebar_weight = concrete_volume * 100
paint_area = wall_area * 2
ceiling_area = floor_area * 0.95
"Measure twice, cut once. Or better yet, measure automatically from the model."