excel-to-rvt
Scannednpx machina-cli add skill datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction/excel-to-rvt --openclawExcel to RVT Import
Note: RVT is the file format. Examples may reference Autodesk® Revit® APIs. Autodesk and Revit are registered trademarks of Autodesk, Inc.
Business Case
Problem Statement
External data (costs, specifications, classifications) lives in Excel but needs to update Revit:
- Cost estimates need to link to model elements
- Classification codes need assignment
- Custom parameters need population
- Manual entry is slow and error-prone
Solution
Automated import of Excel data into Revit using the DDC ImportExcelToRevit tool and Dynamo workflows.
Business Value
- Automation - Batch update thousands of parameters
- Accuracy - Eliminate manual data entry errors
- Sync - Keep external data in sync with model
- Flexibility - Update any writable parameter
Technical Implementation
Methods
- ImportExcelToRevit CLI - Direct command-line update
- Dynamo Script - Visual programming approach
- Revit API - Full programmatic control
ImportExcelToRevit CLI
ImportExcelToRevit.exe <model.rvt> <data.xlsx> [options]
| Option | Description |
|---|---|
-sheet | Excel sheet name |
-idcol | Element ID column |
-mapping | Parameter mapping file |
Python Implementation
import subprocess
import pandas as pd
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple
from dataclasses import dataclass
import json
@dataclass
class ImportResult:
"""Result of Excel import to Revit."""
elements_processed: int
elements_updated: int
elements_failed: int
parameters_updated: int
errors: List[str]
class ExcelToRevitImporter:
"""Import Excel data into Revit models."""
def __init__(self, tool_path: str = "ImportExcelToRevit.exe"):
self.tool_path = Path(tool_path)
def import_data(self, revit_file: str,
excel_file: str,
sheet_name: str = "Elements",
id_column: str = "ElementId",
parameter_mapping: Dict[str, str] = None) -> ImportResult:
"""Import Excel data into Revit."""
# Build command
cmd = [
str(self.tool_path),
revit_file,
excel_file,
"-sheet", sheet_name,
"-idcol", id_column
]
# Add mapping file if provided
if parameter_mapping:
mapping_file = self._create_mapping_file(parameter_mapping)
cmd.extend(["-mapping", mapping_file])
# Execute
result = subprocess.run(cmd, capture_output=True, text=True)
# Parse result (format depends on tool)
return self._parse_result(result)
def _create_mapping_file(self, mapping: Dict[str, str]) -> str:
"""Create temporary mapping file."""
mapping_path = Path("temp_mapping.json")
with open(mapping_path, 'w') as f:
json.dump(mapping, f)
return str(mapping_path)
def _parse_result(self, result: subprocess.CompletedProcess) -> ImportResult:
"""Parse CLI result."""
# This is placeholder - actual parsing depends on tool output
if result.returncode == 0:
return ImportResult(
elements_processed=0,
elements_updated=0,
elements_failed=0,
parameters_updated=0,
errors=[]
)
else:
return ImportResult(
elements_processed=0,
elements_updated=0,
elements_failed=0,
parameters_updated=0,
errors=[result.stderr]
)
class DynamoScriptGenerator:
"""Generate Dynamo scripts for Revit data import."""
def generate_parameter_update_script(self,
mappings: Dict[str, str],
excel_path: str,
output_path: str) -> str:
"""Generate Dynamo Python script for parameter updates."""
mappings_json = json.dumps(mappings)
script = f'''
# Dynamo Python Script - Excel to Revit Parameter Update
# Generated by DDC
import clr
import sys
sys.path.append(r'C:\\Program Files (x86)\\IronPython 2.7\\Lib')
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
clr.AddReference('Microsoft.Office.Interop.Excel')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
from Autodesk.Revit.DB import *
import Microsoft.Office.Interop.Excel as Excel
# Configuration
excel_path = r'{excel_path}'
mappings = {mappings_json}
# Open Excel
excel_app = Excel.ApplicationClass()
excel_app.Visible = False
workbook = excel_app.Workbooks.Open(excel_path)
worksheet = workbook.Worksheets[1]
# Get Revit document
doc = DocumentManager.Instance.CurrentDBDocument
# Read Excel data
used_range = worksheet.UsedRange
rows = used_range.Rows.Count
cols = used_range.Columns.Count
# Find column indices
headers = {{}}
for col in range(1, cols + 1):
header = str(worksheet.Cells[1, col].Value2 or '')
headers[header] = col
# Process rows
TransactionManager.Instance.EnsureInTransaction(doc)
updated_count = 0
error_count = 0
for row in range(2, rows + 1):
try:
# Get element ID
element_id_col = headers.get('ElementId', 1)
element_id = int(worksheet.Cells[row, element_id_col].Value2 or 0)
element = doc.GetElement(ElementId(element_id))
if not element:
continue
# Update mapped parameters
for excel_col, revit_param in mappings.items():
if excel_col in headers:
col_idx = headers[excel_col]
value = worksheet.Cells[row, col_idx].Value2
if value is not None:
param = element.LookupParameter(revit_param)
if param and not param.IsReadOnly:
if param.StorageType == StorageType.Double:
param.Set(float(value))
elif param.StorageType == StorageType.Integer:
param.Set(int(value))
elif param.StorageType == StorageType.String:
param.Set(str(value))
updated_count += 1
except Exception as e:
error_count += 1
TransactionManager.Instance.TransactionTaskDone()
# Cleanup
workbook.Close(False)
excel_app.Quit()
OUT = f"Updated: {{updated_count}}, Errors: {{error_count}}"
'''
with open(output_path, 'w') as f:
f.write(script)
return output_path
def generate_schedule_creator(self,
schedule_name: str,
category: str,
fields: List[str],
output_path: str) -> str:
"""Generate script to create Revit schedule from Excel structure."""
fields_json = json.dumps(fields)
script = f'''
# Dynamo Python Script - Create Schedule
# Generated by DDC
import clr
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
from Autodesk.Revit.DB import *
doc = DocumentManager.Instance.CurrentDBDocument
fields = {fields_json}
# Get category
category = Category.GetCategory(doc, BuiltInCategory.OST_{category})
TransactionManager.Instance.EnsureInTransaction(doc)
# Create schedule
schedule = ViewSchedule.CreateSchedule(doc, category.Id)
schedule.Name = "{schedule_name}"
# Add fields
definition = schedule.Definition
for field_name in fields:
# Find schedulable field
for sf in definition.GetSchedulableFields():
if sf.GetName(doc) == field_name:
definition.AddField(sf)
break
TransactionManager.Instance.TransactionTaskDone()
OUT = schedule
'''
with open(output_path, 'w') as f:
f.write(script)
return output_path
class ExcelDataValidator:
"""Validate Excel data before Revit import."""
def __init__(self, revit_elements: pd.DataFrame):
"""Initialize with exported Revit elements."""
self.revit_data = revit_elements
self.valid_ids = set(revit_elements['ElementId'].astype(str).tolist())
def validate_import_data(self, import_df: pd.DataFrame,
id_column: str = 'ElementId') -> Dict[str, Any]:
"""Validate import data against Revit export."""
results = {
'valid': True,
'total_rows': len(import_df),
'matching_ids': 0,
'missing_ids': [],
'invalid_ids': [],
'warnings': []
}
import_ids = import_df[id_column].astype(str).tolist()
for import_id in import_ids:
if import_id in self.valid_ids:
results['matching_ids'] += 1
else:
results['invalid_ids'].append(import_id)
if results['invalid_ids']:
results['valid'] = False
results['warnings'].append(
f"{len(results['invalid_ids'])} element IDs not found in Revit model"
)
results['match_rate'] = round(
results['matching_ids'] / results['total_rows'] * 100, 1
) if results['total_rows'] > 0 else 0
return results
def check_parameter_types(self, import_df: pd.DataFrame,
type_definitions: Dict[str, str]) -> List[str]:
"""Check if values match expected parameter types."""
errors = []
for column, expected_type in type_definitions.items():
if column not in import_df.columns:
continue
for idx, value in import_df[column].items():
if pd.isna(value):
continue
if expected_type == 'number':
try:
float(value)
except ValueError:
errors.append(f"Row {idx}: '{column}' should be number, got '{value}'")
elif expected_type == 'integer':
try:
int(value)
except ValueError:
errors.append(f"Row {idx}: '{column}' should be integer, got '{value}'")
return errors
Quick Start
# Generate Dynamo script
generator = DynamoScriptGenerator()
mappings = {
'OmniClass_Code': 'OmniClass Number',
'Unit_Cost': 'Cost',
'Material_Type': 'Material'
}
generator.generate_parameter_update_script(
mappings=mappings,
excel_path="enriched_data.xlsx",
output_path="update_revit.py"
)
Validation
# Validate before import
validator = ExcelDataValidator(revit_export_df)
validation = validator.validate_import_data(import_df)
if validation['valid']:
print(f"Ready to import. Match rate: {validation['match_rate']}%")
else:
print(f"Issues found: {validation['warnings']}")
Complete Workflow
# 1. Export from Revit
# RvtExporter.exe model.rvt complete
# 2. Load and validate
revit_df = pd.read_excel("model.xlsx")
validator = ExcelDataValidator(revit_df)
# 3. Prepare import data
import_df = pd.read_excel("enriched_data.xlsx")
validation = validator.validate_import_data(import_df)
# 4. Generate update script
if validation['valid']:
generator = DynamoScriptGenerator()
generator.generate_parameter_update_script(
mappings={'Classification': 'OmniClass Number'},
excel_path="enriched_data.xlsx",
output_path="apply_updates.py"
)
print("Run apply_updates.py in Dynamo to update Revit")
Resources
- GitHub: DDC Update Revit from Excel
- Dynamo: https://dynamobim.org/
Source
git clone https://github.com/datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction/blob/main/1_DDC_Toolkit/CAD-Converters/excel-to-rvt/SKILL.mdView on GitHub Overview
Automates importing Excel data into Revit projects to update element parameters, generate schedules, and keep external data sources in sync. It leverages the DDC ImportExcelToRevit tool and Dynamo workflows for flexible automation across models.
How This Skill Works
Use the ImportExcelToRevit CLI to map an Excel sheet to Revit elements by a defined ID column, with an optional parameter mapping file. Dynamo scripts or the Revit API can drive the same workflow for more complex scenarios. The provided Python example demonstrates building the CLI command, supplying sheet, id column, and mapping, then parsing results to summarize updates.
When to Use It
- Batch update thousands of element parameters from Excel during model updates
- Link costs, specifications, and classifications from Excel to corresponding Revit parameters
- Create or refresh schedules using external data without manual entry
- Keep external Excel data sources in sync with the Revit model across revisions
- Set up repeatable imports for ongoing projects using a reusable mapping file
Quick Start
- Step 1: Prepare your Excel file with an ElementId column and the target data
- Step 2: Run the CLI import, e.g. ImportExcelToRevit.exe model.rvt data.xlsx -sheet Elements -idcol ElementId [-mapping mapping.json]
- Step 3: Open Revit to verify results and adjust the mapping as needed
Best Practices
- Clean and normalize Excel data before import (consistent column names and data types)
- Define a clear parameter mapping file that maps Excel columns to Revit parameters
- Test the import on a copy of the model and validate results in Revit
- Version control both the Excel source and the mapping file for traceability
- Document any exceptions or special cases so future imports are deterministic
Example Use Cases
- Import cost estimates from Excel and populate cost-related parameters on elements
- Apply classification codes from Excel to element parameters and schedules
- Populate material quantities and properties into Revit schedules from a data sheet
- Synchronize supplier data or external datasets with model properties through regular imports
- Re-run imports after design changes to keep model data aligned with external data