Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Automate BIM workflows using visual programming and Python. Create parametric schedules, export data, batch modify elements, and integrate with external data sources.
.claude/skills/datadrivenconstruction-bim-visual-programming-automation/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 123% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 103% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 89% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 88% | 0% |
This skill provides visual programming scripts and Python nodes for automating BIM workflows. Extract data, modify elements in batch, generate schedules, and integrate with external systems.
> Note: Examples use Autodesk® Revit® and Dynamo™ APIs. Autodesk, Revit, and Dynamo are registered trademarks of Autodesk, Inc.
Key Capabilities:
python# Dynamo Python Script - Export all walls to Excel import clr clr.AddReference('RevitAPI') clr.AddReference('RevitServices') from RevitServices.Persistence import DocumentManager from Autodesk.Revit.DB import FilteredElementCollector, BuiltInCategory doc = DocumentManager.Instance.CurrentDBDocument # Get all walls collector = FilteredElementCollector(doc) walls = collector.OfCategory(BuiltInCategory.OST_Walls).WhereElementIsNotElementType().ToElements() # Extract data wall_data = [] for wall in walls: wall_data.append({ 'id': wall.Id.IntegerValue, 'name': wall.Name, 'length': wall.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH).AsDouble() * 0.3048, 'area': wall.get_Parameter(BuiltInParameter.HOST_AREA_COMPUTED).AsDouble() * 0.0929 }) OUT = wall_data
python# Dynamo Python Node - Extract all element data import clr clr.AddReference('RevitAPI') clr.AddReference('RevitServices') clr.AddReference('RevitNodes') from RevitServices.Persistence import DocumentManager from Autodesk.Revit.DB import * import Revit clr.ImportExtensions(Revit.Elements) doc = DocumentManager.Instance.CurrentDBDocument def get_element_data(element): """Extract data from Revit element""" data = { 'id': element.Id.IntegerValue, 'category': element.Category.Name if element.Category else None, 'name': element.Name, 'level': None, 'parameters': {} } # Get level level_param = element.get_Parameter(BuiltInParameter.SCHEDULE_LEVEL_PARAM) if level_param: level_id = level_param.AsElementId() if level_id.IntegerValue > 0: level = doc.GetElement(level_id) data['level'] = level.Name if level else None # Get all parameters for param in element.Parameters: try: if param.HasValue: if param.StorageType == StorageType.Double: data['parameters'][param.Definition.Name] = param.AsDouble() elif param.StorageType == StorageType.Integer: data['parameters'][param.Definition.Name] = param.AsInteger() elif param.StorageType == StorageType.String: data['parameters'][param.Definition.Name] = param.AsString() except: pass return data def extract_category(category_enum): """Extract all elements of a category""" collector = FilteredElementCollector(doc) elements = collector.OfCategory(category_enum).WhereElementIsNotElementType().ToElements() return [get_element_data(e) for e in elements] # Extract structural elements categories = [ BuiltInCategory.OST_Walls, BuiltInCategory.OST_Floors, BuiltInCategory.OST_StructuralColumns, BuiltInCategory.OST_StructuralFraming, BuiltInCategory.OST_Doors, BuiltInCategory.OST_Windows ] all_data = {} for cat in categories: cat_name = cat.ToString().replace('OST_', '') all_data[cat_name] = extract_category(cat) OUT = all_data
python# Dynamo Python - QTO Export import clr clr.AddReference('RevitAPI') clr.AddReference('RevitServices') from RevitServices.Persistence import DocumentManager from Autodesk.Revit.DB import * doc = DocumentManager.Instance.CurrentDBDocument def get_qto_data(): """Generate QTO data from model""" qto = {} # Walls walls = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Walls)\ .WhereElementIsNotElementType().ToElements() wall_qto = {} for wall in walls: wall_type = doc.GetElement(wall.GetTypeId()) type_name = wall_type.get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_NAME).AsString() if type_name not in wall_qto: wall_qto[type_name] = {'count': 0, 'area': 0, 'length': 0} wall_qto[type_name]['count'] += 1 area_param = wall.get_Parameter(BuiltInParameter.HOST_AREA_COMPUTED) if area_param: wall_qto[type_name]['area'] += area_param.AsDouble() * 0.0929 # sqft to m2 length_param = wall.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH) if length_param: wall_qto[type_name]['length'] += length_param.AsDouble() * 0.3048 # ft to m qto['Walls'] = wall_qto # Floors floors = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Floors)\ .WhereElementIsNotElementType().ToElements() floor_qto = {} for floor in floors: floor_type = doc.GetElement(floor.GetTypeId()) type_name = floor_type.get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_NAME).AsString() if type_name not in floor_qto: floor_qto[type_name] = {'count': 0, 'area': 0, 'volume': 0} floor_qto[type_name]['count'] += 1 area_param = floor.get_Parameter(BuiltInParameter.HOST_AREA_COMPUTED) if area_param: floor_qto[type_name]['area'] += area_param.AsDouble() * 0.0929 vol_param = floor.get_Parameter(BuiltInParameter.HOST_VOLUME_COMPUTED) if vol_param: floor_qto[type_name]['volume'] += vol_param.AsDouble() * 0.0283 # cuft to m3 qto['Floors'] = floor_qto return qto OUT = get_qto_data()
python# Dynamo Python - Batch update parameters 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 def batch_update_parameter(elements, param_name, values): """Update parameter for multiple elements""" TransactionManager.Instance.EnsureInTransaction(doc) results = [] for elem, value in zip(elements, values): try: param = elem.LookupParameter(param_name) if param and not param.IsReadOnly: if param.StorageType == StorageType.String: param.Set(str(value)) elif param.StorageType == StorageType.Double: param.Set(float(value)) elif param.StorageType == StorageType.Integer: param.Set(int(value)) results.append(True) else: results.append(False) except Exception as e: results.append(str(e)) TransactionManager.Instance.TransactionTaskDone() return results # Input from Dynamo nodes elements = IN[0] # List of elements param_name = IN[1] # Parameter name (string) values = IN[2] # List of values OUT = batch_update_parameter(elements, param_name, values)
python# Dynamo Python - Copy elements to levels import clr clr.AddReference('RevitAPI') clr.AddReference('RevitServices') from RevitServices.Persistence import DocumentManager from RevitServices.Transactions import TransactionManager from Autodesk.Revit.DB import * from System.Collections.Generic import List doc = DocumentManager.Instance.CurrentDBDocument def copy_to_levels(elements, target_levels): """Copy elements to multiple levels""" TransactionManager.Instance.EnsureInTransaction(doc) copied = [] element_ids = List[ElementId]([e.Id for e in elements]) for level in target_levels: # Calculate offset source_level = doc.GetElement(elements[0].LevelId) offset = XYZ(0, 0, level.Elevation - source_level.Elevation) # Copy new_ids = ElementTransformUtils.CopyElements( doc, element_ids, offset ) copied.extend([doc.GetElement(id) for id in new_ids]) TransactionManager.Instance.TransactionTaskDone() return copied elements = IN[0] target_levels = IN[1] OUT = copy_to_levels(elements, target_levels)
python# Dynamo Python - Create view schedule 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 def create_wall_schedule(schedule_name): """Create a wall schedule with QTO fields""" TransactionManager.Instance.EnsureInTransaction(doc) # Create schedule schedule = ViewSchedule.CreateSchedule( doc, ElementId(BuiltInCategory.OST_Walls) ) schedule.Name = schedule_name # Add fields definition = schedule.Definition schedulable = definition.GetSchedulableFields() # Find and add specific fields field_names = ['Type', 'Level', 'Length', 'Area', 'Volume'] for sf in schedulable: if sf.GetName(doc) in field_names: definition.AddField(sf) # Add sorting/grouping type_field = None for field in definition.GetFieldOrder(): if definition.GetField(field).GetName() == 'Type': type_field = field break if type_field: sorting = ScheduleSortGroupField(type_field, ScheduleSortOrder.Ascending) sorting.ShowHeader = True sorting.ShowFooter = True definition.AddSortGroupField(sorting) TransactionManager.Instance.TransactionTaskDone() return schedule schedule_name = IN[0] OUT = create_wall_schedule(schedule_name)
python# Dynamo Python - Import Excel and update Revit import clr clr.AddReference('RevitAPI') clr.AddReference('RevitServices') from RevitServices.Persistence import DocumentManager from RevitServices.Transactions import TransactionManager from Autodesk.Revit.DB import * # Requires Excel data as input from Dynamo Excel nodes doc = DocumentManager.Instance.CurrentDBDocument def update_from_excel(excel_data, id_column, param_columns): """Update Revit elements from Excel data""" TransactionManager.Instance.EnsureInTransaction(doc) results = [] for row in excel_data: try: # Get element by ID elem_id = ElementId(int(row[id_column])) element = doc.GetElement(elem_id) if element: row_result = {'id': elem_id.IntegerValue, 'updates': {}} for col_name, col_index in param_columns.items(): param = element.LookupParameter(col_name) if param and not param.IsReadOnly: value = row[col_index] if param.StorageType == StorageType.String: param.Set(str(value)) elif param.StorageType == StorageType.Double: param.Set(float(value)) row_result['updates'][col_name] = value results.append(row_result) except Exception as e: results.append({'error': str(e)}) TransactionManager.Instance.TransactionTaskDone() return results excel_data = IN[0] # 2D list from Excel id_column = IN[1] # Column index for element ID param_columns = IN[2] # Dict: param_name -> column_index OUT = update_from_excel(excel_data, id_column, param_columns)
1. Categories (Input)
|
2. All Elements of Category (Revit)
|
3. Element.GetParameterValueByName (Multiple parameters)
|
4. Python Script (Process and calculate)
|
5. List.Transpose
|
6. Data.ExportExcel
|
7. File Path (Output)| Task | Method | Performance | |------|--------|-------------| | Get Elements | FilteredElementCollector | Fast | | Get Parameter | element.LookupParameter() | Fast | | Set Parameter | TransactionManager required | Moderate | | Copy Elements | ElementTransformUtils | Moderate | | Create Views | ViewSchedule.CreateSchedule | Slow | | Delete Elements | Document.Delete | Fast |
python# Built-in parameters for quantities QUANTITY_PARAMS = { 'Length': BuiltInParameter.CURVE_ELEM_LENGTH, 'Area': BuiltInParameter.HOST_AREA_COMPUTED, 'Volume': BuiltInParameter.HOST_VOLUME_COMPUTED, 'Height': BuiltInParameter.WALL_USER_HEIGHT_PARAM, 'Width': BuiltInParameter.DOOR_WIDTH, 'Level': BuiltInParameter.SCHEDULE_LEVEL_PARAM } # Unit conversion (Imperial to Metric) CONVERSIONS = { 'feet_to_meters': 0.3048, 'sqft_to_sqm': 0.0929, 'cuft_to_cum': 0.0283 }
ifc-data-extraction for IFC exportqto-report for advanced quantity reportsn8n-workflow-automation for external integration| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→pass | 12,601 | 7,848 | -38% | 1 | 1 | 0% | 2,369 | 5,281 | +123% | 0 | 0 | — |
case-01 | pass→pass | 16,577 | 14,119 | -15% | 1 | 1 | 0% | 3,461 | 6,517 | +88% | 0 | 0 | — |
case-03 | pass→pass | 15,320 | 14,085 | -8% | 1 | 1 | 0% | 2,931 | 6,399 | +118% | 0 | 0 | — |
case-04 | pass→pass | 12,339 | 7,713 | -37% | 1 | 1 | 0% | 2,466 | 5,152 | +109% | 0 | 0 | — |
case-05 | fail→pass | 16,172 | 11,487 | -29% | 1 | 1 | 0% | 2,944 | 5,977 | +103% | 0 | 0 | — |
case-06 | fail→fail | 14,968 | 11,215 | -25% | 1 | 1 | 0% | 2,808 | 5,695 | +103% | 0 | 0 | — |
case-07 | pass→pass | 12,792 | 10,139 | -21% | 1 | 1 | 0% | 2,331 | 5,452 | +134% | 0 | 0 | — |
case-08 | pass→pass | 17,289 | 15,710 | -9% | 1 | 1 | 0% | 3,250 | 6,516 | +100% | 0 | 0 | — |
case-09 | fail→pass | 13,518 | 4,172 | -69% | 1 | 1 | 0% | 2,343 | 4,337 | +85% | 0 | 0 | — |
case-10 | pass→pass | 8,269 | 7,429 | -10% | 1 | 1 | 0% | 1,663 | 5,072 | +205% | 0 | 0 | — |
case-11 | fail→fail | 11,720 | 8,167 | -30% | 1 | 1 | 0% | 2,053 | 5,030 | +145% | 0 | 0 | — |
case-12 | fail→pass | 17,710 | 11,675 | -34% | 1 | 1 | 0% | 3,007 | 5,681 | +89% | 0 | 0 | — |
case-13 | pass→pass | 9,170 | 4,701 | -49% | 1 | 1 | 0% | 1,629 | 4,396 | +170% | 0 | 0 | — |
case-14 | pass→pass | 9,300 | 3,933 | -58% | 1 | 1 | 0% | 1,653 | 4,282 | +159% | 0 | 0 | — |
case-15 | pass→pass | 7,970 | 5,138 | -36% | 1 | 1 | 0% | 1,423 | 4,509 | +217% | 0 | 0 | — |
case-16 | pass→pass | 13,595 | 12,686 | -7% | 1 | 1 | 0% | 2,361 | 5,936 | +151% | 0 | 0 | — |
case-17 | pass→pass | 8,684 | 5,275 | -39% | 1 | 1 | 0% | 1,417 | 4,470 | +215% | 0 | 0 | — |
case-18 | pass→pass | 11,672 | 5,804 | -50% | 1 | 1 | 0% | 1,933 | 4,615 | +139% | 0 | 0 | — |
case-19 | pass→pass | 7,230 | 4,192 | -42% | 1 | 1 | 0% | 1,232 | 4,284 | +248% | 0 | 0 | — |
case-20 | fail→fail | 17,674 | 16,290 | -8% | 1 | 1 | 0% | 3,283 | 7,002 | +113% | 0 | 0 | — |
case-21 | fail→fail | 23,814 | 29,056 | +22% | 1 | 1 | 0% | 4,764 | 9,786 | +105% | 0 | 0 | — |
case-22 | fail→fail | 16,919 | 15,787 | -7% | 1 | 1 | 0% | 3,428 | 6,914 | +102% | 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. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 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.