Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Import Excel data into RVT projects. Update element parameters, create schedules, and sync external data sources.
.claude/skills/datadrivenconstruction-excel-to-rvt/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 100% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 89% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 103% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 187% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 154% | 0% |
> Note: RVT is the file format. Examples may reference Autodesk® Revit® APIs. Autodesk and Revit are registered trademarks of Autodesk, Inc.
External data (costs, specifications, classifications) lives in Excel but needs to update Revit:
Automated import of Excel data into Revit using the DDC ImportExcelToRevit tool and Dynamo workflows.
bashImportExcelToRevit.exe <model.rvt> <data.xlsx> [options]
| Option | Description | |--------|-------------| | -sheet | Excel sheet name | | -idcol | Element ID column | | -mapping | Parameter mapping file |
pythonimport 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
python# 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" )
python# 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']}")
python# 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")
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-22 | pass→pass | 10,704 | 7,837 | -27% | 1 | 1 | 0% | 1,691 | 4,299 | +154% | 0 | 0 | — |
case-06 | fail→pass | 11,658 | 4,660 | -60% | 1 | 1 | 0% | 2,027 | 4,059 | +100% | 0 | 0 | — |
case-11 | pass→pass | 4,542 | 4,644 | +2% | 1 | 1 | 0% | 697 | 3,787 | +443% | 0 | 0 | — |
case-01 | fail→fail | 37,235 | 12,017 | -68% | 1 | 1 | 0% | 2,205 | 5,712 | +159% | 0 | 0 | — |
case-02 | fail→fail | 46,897 | 18,395 | -61% | 1 | 1 | 0% | 4,695 | 6,461 | +38% | 0 | 0 | — |
case-03 | fail→fail | 21,850 | 16,926 | -23% | 1 | 1 | 0% | 4,362 | 6,461 | +48% | 0 | 0 | — |
case-04 | fail→pass | 13,500 | 5,844 | -57% | 1 | 1 | 0% | 2,216 | 4,183 | +89% | 0 | 0 | — |
case-05 | fail→pass | 9,980 | 2,545 | -74% | 1 | 1 | 0% | 1,722 | 3,497 | +103% | 0 | 0 | — |
case-07 | pass→pass | 14,356 | 8,469 | -41% | 1 | 1 | 0% | 2,846 | 4,462 | +57% | 0 | 0 | — |
case-08 | pass→pass | 4,011 | 3,465 | -14% | 1 | 1 | 0% | 660 | 3,608 | +447% | 0 | 0 | — |
case-09 | pass→pass | 6,423 | 3,899 | -39% | 1 | 1 | 0% | 1,182 | 3,749 | +217% | 0 | 0 | — |
case-10 | fail→pass | 8,676 | 7,584 | -13% | 1 | 1 | 0% | 1,503 | 4,316 | +187% | 0 | 0 | — |
case-12 | fail→pass | 8,578 | 2,895 | -66% | 1 | 1 | 0% | 1,410 | 3,586 | +154% | 0 | 0 | — |
case-13 | pass→pass | 11,639 | 6,778 | -42% | 1 | 1 | 0% | 1,985 | 4,074 | +105% | 0 | 0 | — |
case-14 | pass→pass | 12,951 | 13,669 | +6% | 1 | 1 | 0% | 2,354 | 5,343 | +127% | 0 | 0 | — |
case-15 | fail→pass | 10,849 | 4,148 | -62% | 1 | 1 | 0% | 1,871 | 3,866 | +107% | 0 | 0 | — |
case-21 | pass→pass | 14,301 | 14,880 | +4% | 1 | 1 | 0% | 2,479 | 5,791 | +134% | 0 | 0 | — |
case-16 | fail→fail | 9,405 | 3,137 | -67% | 1 | 1 | 0% | 1,525 | 3,680 | +141% | 0 | 0 | — |
case-17 | fail→pass | 11,140 | 3,331 | -70% | 1 | 1 | 0% | 1,739 | 3,581 | +106% | 0 | 0 | — |
case-18 | pass→pass | 15,797 | 5,434 | -66% | 1 | 1 | 0% | 2,568 | 4,043 | +57% | 0 | 0 | — |
case-19 | fail→pass | 19,929 | 11,749 | -41% | 1 | 1 | 0% | 3,630 | 5,322 | +47% | 0 | 0 | — |
case-20 | pass→pass | 9,519 | 8,215 | -14% | 1 | 1 | 0% | 1,505 | 4,594 | +205% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +36 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.