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:

1 Like

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

Hi Jacob and Sovitek,

Yes, the code was generated using ChatGPT. Previously, I had created a similar script using Python for purging a project over a decade ago. When approached by the team to tackle this task, it seemed fairly straightforward:

  1. Open the families exported from the project.
  2. Purge three times.
  3. Reload.

In the past, I primarily wrote Dynamo scripts using either out-of-the-box nodes or Python. I found that distributing scripts involving external packages to global teams often led to complications. They would struggle to load the necessary packages and, in many cases, abandon the script entirely. Python has traditionally been a more reliable solution for sharing scripts across teams.

When a script proves popular and effective, I typically port it into our firm-wide company Revit add-in and translate it to C#. Over the years, my role has shifted from computational designer to BIM Management, I find myself creating more quick, task-specific scripts for large teams. Tools like ChatGPT have significantly reduced the time it takes to create complex scripts—from weeks to just days—by generating 80% of the work, leaving me to fine-tune the remaining 20% with a strong background in programming in C#, Python, and Java.

I published this code quickly to validate its feasibility and to check if similar solutions already exist. Many years ago I’d previously invested significant time in attempts to adjust view titles on sheets programmatically and other tasks that weren’t possible through the API. I wanted to avoid wasting effort this time around.

Thank you again for your help. I’ll explore Bird Tools as suggested.

Best regards,
Danny Bentley