Help with Purging and Reloading Families using Python Revit API

Hello everyone,

I’m attempting to create a script to automate the process of opening Revit family files, purging unused elements, and reloading the families back into the current project. I’m using Python for this, and I’m working within the Dynamo/Revit API environment. Here are the details of what I want to achieve, what I’ve set up, and where I’m running into issues.

Objective:

  • I need to automate the purging of Revit family files.
  • The goal is to:
    1. Open each .rfa family file located in a specific folder.
    2. Purge unused elements (e.g., views, groups, materials, annotation symbols, etc.).
    3. Save and close the family.
    4. Load the family back into the current Revit project.

How I Set Up the Script:

  • The script is written in Python and uses the Revit API via Dynamo.
  • Here’s the high-level flow of the script:
    • The script accesses a folder containing .rfa family files (C:\Revit_Families).
    • For each family, it opens the file, brings the document to the UI to ensure it’s not running in the background, and attempts to purge unused elements.
    • The purging is done by collecting all possible types that could be purged (views, groups, materials, line patterns, etc.) and deleting them manually.
    • I run the purge operation three times in a loop to ensure all dependencies are handled and everything possible is purged.
    • After purging, the family is saved and reloaded into the main Revit project.

The Code: Here is the code I’m currently working with:

import clr
clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.Elements)

import os
from System.Collections.Generic import List
import System

# Set parameters
run_it = True  # Run script flag
deeply = True  # Deep purge flag
folder_path = r'C:\Revit_Families'  # Hardcoded path to the folder containing family files

# Determine document to use
current_doc = DocumentManager.Instance.CurrentDBDocument

# Check if folder path exists
if not os.path.exists(folder_path):
    OUT = "Folder path does not exist. Please check the folder path."
    raise Exception("Folder path does not exist.")

# Get all .rfa files in the folder
family_files = [f for f in os.listdir(folder_path) if f.endswith('.rfa')]
if not family_files:
    OUT = "No Revit family files found in the specified folder."
    raise Exception("No Revit family files found.")

# Open Family Documents and Purge without running in the background
app = current_doc.Application
for family_file in family_files:
    family_path = os.path.join(folder_path, family_file)
    
    # Open Family Document
    try:
        opened_family_doc = app.OpenDocumentFile(family_path)
    except Exception as e:
        print(f"Error opening family document {family_file}: {e}")
        continue

    # Show family document in UI to prevent background processing
    app.ActiveUIDocument = opened_family_doc

    # Purge unused elements manually by targeting specific types
    def purge_unused_elements(doc):
        try:
            t = Transaction(doc, "Purge Unused Elements")
            t.Start()
            
            # Collect and delete unused families
            families = FilteredElementCollector(doc).OfClass(Family).WhereElementIsNotElementType().ToElementIds()
            if families.Count > 0:
                doc.Delete(families)

            # Collect and delete unused views
            views = FilteredElementCollector(doc).OfClass(View).WhereElementIsNotElementType().ToElementIds()
            unused_views = [v for v in views if not doc.GetElement(v).IsTemplate]
            if len(unused_views) > 0:
                doc.Delete(List[ElementId](unused_views))

            # Collect and delete unused filters
            filters = FilteredElementCollector(doc).OfClass(ParameterFilterElement).ToElementIds()
            if filters.Count > 0:
                doc.Delete(filters)

            # Collect and delete unused materials
            materials = FilteredElementCollector(doc).OfClass(Material).ToElementIds()
            if materials.Count > 0:
                doc.Delete(materials)

            # Collect and delete unused groups
            groups = FilteredElementCollector(doc).OfClass(Group).ToElementIds()
            if groups.Count > 0:
                doc.Delete(groups)

            # Collect and delete unused line patterns
            line_patterns = FilteredElementCollector(doc).OfClass(LinePatternElement).ToElementIds()
            if line_patterns.Count > 0:
                doc.Delete(line_patterns)

            # Collect and delete unused annotation symbols
            annotation_symbols = FilteredElementCollector(doc).OfClass(FamilySymbol).OfCategory(BuiltInCategory.OST_GenericAnnotation).ToElementIds()
            if annotation_symbols.Count > 0:
                doc.Delete(annotation_symbols)

            # Collect and delete unused text elements
            text_elements = FilteredElementCollector(doc).OfClass(TextElement).ToElementIds()
            if text_elements.Count > 0:
                doc.Delete(text_elements)

            t.Commit()
        except Exception as e:
            if t.HasStarted() and not t.HasEnded():
                t.RollBack()
            print(f"Error during purge: {e}")

    # Purge 3 times to ensure all elements are removed
    for _ in range(3):
        purge_unused_elements(opened_family_doc)

    # Save Family Document
    try:
        t = Transaction(opened_family_doc, "Save Family Document")
        t.Start()
        opened_family_doc.Save()
        t.Commit()
    except Exception as e:
        if t.HasStarted() and not t.HasEnded():
            t.RollBack()
        print(f"Error during save: {e}")

    # Close Family Document
    opened_family_doc.Close(False)

    # Load Family back into Current Project
    try:
        t = Transaction(current_doc, "Load Family into Project")
        t.Start()
        current_doc.LoadFamily(family_path)
        t.Commit()
    except Exception as e:
        if t.HasStarted() and not t.HasEnded():
            t.RollBack()
        print(f"Error during load: {e}")

# Output
OUT = "All families purged and reloaded successfully"

Hi @BIMTeam and welcome :wink: another way could probably be birdtool multiplayer

2 Likes

Hi,

It seems this python script (ChatGPT ?) doesn’t purge unused elements in any way, and could even corrupt a project (since it deletes almost all elements in families)

Given the speed with which the post was created, I wonder if it was even written by an AI + POST web request. :face_with_monocle:

Admin super powers telling me the AI post generation is unlikely. Couple that with the lack of ‘purge unused’ capability and I can see how you could work your way into this code… does seem to need a good overhaul though.

I recommend dropping the Python and using the OOTB purge node, and then processing all of the rfa files by way of bird tools’ Dynamo multi player.

1 Like