I made some further improvements.
- This version works from project files and the family editor.
- You can now choose which parameters to purge from a list. The list also describes if the parameters are shared/non-shared, so you can filter and select one type or the other or all of them.
- More parameters are excluded. Anything used in table schedules, panel schedules, sheet properties, and view filters gets filtered out. I wanted to extend it to exclude any parameters that are used by tag families in the project, but the parameters in a tag’s label are currently inaccessible in the API.
Future work could be done to give the user a list of all the parameters and explain where they are used, but that would mean setting up data objects to track that info, and building a UI to display it, which are both fairly time consuming.
"""Purge Unused Parameters
Select parameters for removal from a list of all the unused parameters in the active project or family.
Author: c.poupin, adapted by Perry Lackowski
Tested: 2023.1
"""
import sys
import clr
import System
from System.Collections.Generic import List
# Add references to Revit API and other necessary libraries
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Transactions import TransactionManager
from pyrevit import revit, script, forms
doc = revit.doc
output = script.get_output()
###
### FUNCTION DEFINITIONS
###
# Generate a set of all parameter ids that are used in any of the table Schedules
def get_schedule_parameter_ids(doc):
schedule_parameter_ids = set()
schedules = DB.FilteredElementCollector(doc).OfClass(DB.ViewSchedule).ToElements()
for schedule in schedules:
schedule_definitions = schedule.Definition.GetFieldOrder()
for field_id in schedule_definitions:
field = schedule.Definition.GetField(field_id)
schedule_parameter_ids.add(field.ParameterId)
return schedule_parameter_ids
# Generate a set of all parameter ids that are used in any of the Sheet properties
def get_sheet_parameter_ids(doc):
sheet_parameter_ids = set()
sheets = DB.FilteredElementCollector(doc).OfClass(DB.ViewSheet).ToElements()
for sheet in sheets:
parameters = sheet.Parameters
for param in parameters:
sheet_parameter_ids.add(param.Id)
return sheet_parameter_ids
# Generate a set of all parameter ids that are used in any of the Panel Schedules
def get_panel_schedule_parameter_ids(doc):
panel_schedule_parameter_ids = set()
# Get all panel schedule templates
panel_schedules = DB.FilteredElementCollector(doc).OfClass(DB.Electrical.PanelScheduleTemplate).WhereElementIsNotElementType().ToElements()
# Define the section types to iterate through
section_types = [DB.SectionType.Header, DB.SectionType.Body, DB.SectionType.Footer, DB.SectionType.Summary]
for panel_schedule in panel_schedules:
# print("Panel Schedule: {}".format(panel_schedule.Name))
for section_type in section_types:
# print("Section Type: {}".format(section_type))
# Get the section data
section_data = panel_schedule.GetSectionData(section_type)
# Iterate through rows and columns to get cell data
for row in range(section_data.NumberOfRows):
for col in range(section_data.NumberOfColumns):
cell_data = section_data.GetCellText(row, col)
param_id = section_data.GetCellParamId(row, col)
if cell_data:
# print("Row {}, Column {}: {}".format(row, col, cell_data))
pass
if param_id.IntegerValue != -1:
param = doc.GetElement(param_id)
if param:
param_name = param.Name
# print("Row {}, Column {}: Parameter Name: {}".format(row, col, param_name))
panel_schedule_parameter_ids.add(param_id)
return panel_schedule_parameter_ids
# Generate a set of all parameter ids that are used in any of the View Filters
def get_view_filter_parameter_ids(doc):
# ParameterFilterElement or (View Filters)
parameter_filter_elements = DB.FilteredElementCollector(doc).OfClass(DB.ParameterFilterElement).ToElements()
parameter_ids_used_in_project_view_filters = set()
for parameter_filter_element in parameter_filter_elements:
# print("{} {}".format(output.linkify(parameter_filter_element.Id), parameter_filter_element.Name))
# View Filters have two ways to access the parameters they use.
# the first lists the parameters that apply to all categories...
parameter_ids_used_in_all_categories = parameter_filter_element.GetElementFilterParameters()
for parameter_id_used_in_all_categories in parameter_ids_used_in_all_categories:
parameter_ids_used_in_project_view_filters.add(parameter_id_used_in_all_categories)
# ...and the second lists only parameters that belong to specific
# categories. In the case of our project template, these have all
# been empty, but it doesn't hurt to add them anyway.
category_ids = parameter_filter_element.GetCategories()
for category_id in category_ids:
parameter_ids_used_in_category = parameter_filter_element.GetElementFilterParametersForCategory(category_id)
# print(parameter_ids_used_in_category)
# print(parameter_ids_used_in_category.Count)
# parameter_ids_used_in_category = [x.Id for x in parameter_ids_used_in_category]
for parameter_id_used_in_category in parameter_ids_used_in_category:
parameter_ids_used_in_project_view_filters.add(parameter_id_used_in_category)
# Note: some of these will likely be built in parameters, with negative ids.
# I could probably filter them out but I don't think they are harming anything.
return parameter_ids_used_in_project_view_filters
# Function to generate the list of unused parameters in a project.
# This actually returns parameter InternalDefinitions, to be specific.
def get_unused_project_parameters(doc):
assert not doc.IsFamilyDocument
# Collect all parameters that are in use on a schedule or schedule template or sheet.
used_parameter_ids = set()
used_parameter_ids.update(get_schedule_parameter_ids(doc))
used_parameter_ids.update(get_panel_schedule_parameter_ids(doc))
used_parameter_ids.update(get_sheet_parameter_ids(doc))
used_parameter_ids.update(get_view_filter_parameter_ids(doc))
def_toRemove = []
parameter_iterator = doc.ParameterBindings.ForwardIterator()
while parameter_iterator.MoveNext():
parameter_internal_def = parameter_iterator.Key
binding = parameter_iterator.Current
if parameter_internal_def.IsValidObject and isinstance(binding, InstanceBinding):
if parameter_internal_def.Id in used_parameter_ids: #skip these items
continue
categories = binding.Categories # type categories that contain the parameter
filterCats = DB.ElementMulticategoryFilter(List[DB.ElementId]([cat.Id for cat in categories]))
#check if any elements in the bound categories have a value
elements_using_param = [
elem for elem in DB.FilteredElementCollector(doc).WherePasses(filterCats).WhereElementIsNotElementType().ToElements()
if elem.get_Parameter(parameter_internal_def) is not None and elem.get_Parameter(parameter_internal_def).HasValue
]
if not elements_using_param:
def_toRemove.append(doc.GetElement(parameter_internal_def.Id))
return def_toRemove
# Function to generate the list of unused parameters in a single family.
# This actually returns a list of FamilyParameters, to be specific.
def get_unused_family_parameters(doc):
# Get all family parameters
family_manager = doc.FamilyManager
# Dictionary to store parameters that are blank for all types
unused_parameters = {}
all_parameters = []
#Code is a bit complex here because if you want to see the values for the parameters in a family editor, you can only see one set of values at a time.
# if you want to see the values for a different Type, you need to switch the CurrentType, then access the parameters again. Unlike in a project setting,
# since we're approaching from the CurrentType in the family manager instead of from the parameters themselves (which have no accessible values in the family setting),
# you can't see what storage type the parameters are. This is why you have to check all the types AsString, AsDouble, AsInteger, etc.
# Iterate over all family types
for family_type in family_manager.Types:
family_manager.CurrentType = family_type
# print(" ------- {} ------ ".format(family_type.Name))
for param in family_manager.Parameters:
if param.Definition.BuiltInParameter != DB.BuiltInParameter.INVALID:
continue
param_name = param.Definition.Name
# Initialize the dictionary entry if it doesn't exist
if param_name not in unused_parameters:
unused_parameters[param_name] = 0
all_parameters.append(param)
if family_manager.CurrentType.AsValueString(param) == None and \
(family_manager.CurrentType.AsString(param) == None or family_manager.CurrentType.AsString(param) == "") and \
family_manager.CurrentType.AsDouble(param) == None and \
family_manager.CurrentType.AsElementId(param) == DB.ElementId.InvalidElementId and \
family_manager.CurrentType.AsInteger(param) == None:
pass
else:
unused_parameters[param_name] += 1
# print("{}: {}, {}, {}, {}, {}, {} {}".format(param_name,
# param.StorageType,
# param_value_string,
# family_manager.CurrentType.AsString(param),
# family_manager.CurrentType.AsDouble(param),
# family_manager.CurrentType.AsInteger(param),
# family_manager.CurrentType.AsElementId(param),
# result
# ))
# Collect the parameters that are blank for all family types
blank_parameters = [param for param in all_parameters \
if unused_parameters[param.Definition.Name] == 0]
return blank_parameters
# Function to remove project parameters
def remove_unused_parameters_from_project(doc):
# Generate the list of parameters to remove
unused_parameters = get_unused_project_parameters(doc)
if not unused_parameters: return None
parameter_tuples = []
for param in unused_parameters:
if param.GetType() == DB.ParameterElement:
param_type = "Project Parameter"
elif param.GetType() == DB.SharedParameterElement: # otherwise it would be a DB.SharedParemeterElement
param_type = "Shared Project Parameter"
else:
param_type = "param_type_not_set"
parameter_tuples.append((param, param.Name, param_type))
parameter_tuples_to_delete = display_and_select_parameters(parameter_tuples)
result = delete_parameters(parameter_tuples_to_delete)
return result
# Function to remove family parameters
def remove_unused_parameters_from_family(doc):
unused_parameters = get_unused_family_parameters(doc)
if not unused_parameters: return None
parameter_tuples = []
for param in unused_parameters:
if param.IsShared:
param_type = "Shared Family Parameter"
else:
param_type = "Family Parameter"
parameter_tuples.append((param, param.Definition.Name, param_type))
parameter_tuples_to_delete = display_and_select_parameters(parameter_tuples)
result = delete_parameters(parameter_tuples_to_delete)
return result
def delete_parameters(param_tuple_list):
result = []
# Force close any open transactions to ensure a clean slate
TransactionManager.Instance.ForceCloseTransaction()
for param, param_name, param_type in param_tuple_list:
result.append([param_name, param_type, output.linkify(param.Id)])
doc.Delete(param.Id)
return result
# Used with forms.SelectFromList to convert the parameter tuples into a
# usable string for the dialog box.
class MyOptionParamTuplesToListItems(forms.TemplateListItem):
@property
def name(self):
param, param_name, param_type = self.item
return '{} ({})'.format(param_name, param_type)
def display_and_select_parameters(param_tuples):
list_items = [MyOptionParamTuplesToListItems(x) for x in param_tuples]
# print(list_items)
list_items = sorted(list_items, key=lambda x: x.name)
# Use pyRevit forms to display a checklist dialog
selected_param_tuples = forms.SelectFromList.show(
list_items,
title="Select Unused Parameters to Purge",
multiselect=True
)
# If nothing was selected, exit early
if not selected_param_tuples:
t.Dispose()
sys.exit()
return selected_param_tuples
###
### MAIN CODE
###
# Start a transaction to remove the parameters
t = DB.Transaction(doc, "Remove Parameters")
t.Start()
if doc.IsFamilyDocument:
results = remove_unused_parameters_from_family(doc)
else:
results = remove_unused_parameters_from_project(doc)
t.Commit()
if results:
output.print_md("**{} parameter(s) removed.**".format(len(results)))
#Sort by type then name
results = sorted(results, key=lambda x: (x[1], x[0]))
#print results
for name, type, link in results:
print("{}: {} - {}".format(type, link, name))
output.print_md("**{} parameter(s) removed. Script complete.**".format(len(results)))
else:
forms.alert('No parameters found for removal.')