Load Families Freeze

Hi all,

Issue: I have a pyRevit script that copies and loads families from a source folder into a project. The script works fine for the copying part, but Revit completely freezes after the family loading/upgrading process is complete. The freezing happens after the script has reached the last part which is execution completed. Is it due to the family being upgraded to newer versions which takes up a lot of memory or merely forms.alert causing the freeze.

Any help would be greatly appreciated!

from pyrevit import forms
from pyrevit import revit, DB
from dfl_matrix.folderpathforfamilywise import main as folderpath_main
import clr
import os
import shutil
import re


def is_backup_file(filename):
    return bool(re.search(r'\.\d{4}\.rfa$', filename))


def find_family_files(root_folder):
    family_files = []
    for root, _, files in os.walk(root_folder):
        for file in files:
            if file.endswith(".rfa") and not is_backup_file(file):
                family_files.append(os.path.relpath(os.path.join(root, file), root_folder))
    return family_files


def remove_config():
    CONFIG_FILE_NAME = "config.json"
    CONFIG_DIR = os.path.join(os.environ['APPDATA'], "pyRevit")
    CONFIG_FILE_PATH = os.path.join(CONFIG_DIR, CONFIG_FILE_NAME)
    try:
        if os.path.exists(CONFIG_FILE_PATH):
            os.remove(CONFIG_FILE_PATH)
    except:
        pass


def is_family_loaded(doc, family_name):
    try:
        collector = DB.FilteredElementCollector(doc).OfClass(DB.Family)
        for fam in collector:
            if fam.Name == family_name:
                return True
        return False
    except:
        return False


def is_subdirectory(child, parent):
    try:
        child = os.path.abspath(os.path.normpath(child))
        parent = os.path.abspath(os.path.normpath(parent))
        return parent == child or child.startswith(parent + os.sep)
    except:
        return False


class AutoUpgradeFamilyLoadOptions(DB.IFamilyLoadOptions):
    def OnFamilyFound(self, familyInUse, overwriteParameterValues):
        return True

    def OnSharedFamilyFound(self, sharedFamily, familyInUse, source, overwriteParameterValues):
        return True


def load_family_wise():
    try:
        DIRECTORY, DFLFAMILYLIBRARY_FOLDER, DFL2020_FOLDER = folderpath_main()
        SOURCE_FOLDER = DFL2020_FOLDER
        USER_FOLDER = DIRECTORY

        if not os.path.exists(SOURCE_FOLDER):
            forms.alert("The source folder does not exist: " + SOURCE_FOLDER, exitscript=True)
            return

        project_folder = forms.pick_folder(title="Select Project > Revit Families Folder")

        if not project_folder:
            forms.alert("No project folder selected. Script will exit.", exitscript=True)
            return

        if is_subdirectory(project_folder, USER_FOLDER) or \
                is_subdirectory(project_folder, DIRECTORY) or \
                os.path.abspath(project_folder) == os.path.abspath(DIRECTORY):
            forms.alert("The project folder cannot be Design for Leisure - Documents or any of its subdirectories.",
                        exitscript=True)
            return

        remove_config()

        family_files = find_family_files(SOURCE_FOLDER)
        if not family_files:
            forms.alert("No family files found in source folder.", exitscript=True)
            return

        selected_families = forms.SelectFromList.show(
            family_files,
            multiselect=True,
            title="Select Families to Copy"
        )

        if not selected_families:
            forms.alert("No families selected. Script will exit.", exitscript=True)
            return

        if len(selected_families) > 30:
            response = forms.alert(
                "You've selected {} families, which may cause Revit to freeze. "
                "We recommend processing 30 or fewer at a time. Continue anyway?".format(len(selected_families)),
                yes=True, no=True
            )
            if not response:
                forms.alert("Script cancelled. Please try again with fewer families.", exitscript=True)
                return

        # Copy families
        copied_families = []
        for family in selected_families:
            source_path = os.path.join(SOURCE_FOLDER, family)
            target_path = os.path.join(project_folder, os.path.basename(family))

            try:
                target_dir = os.path.dirname(target_path)
                if not os.path.exists(target_dir):
                    os.makedirs(target_dir)

                shutil.copy2(source_path, target_path)
                copied_families.append(family)
            except:
                continue

        if not copied_families:
            forms.alert("Failed to copy any families. Script will exit.", exitscript=True)
            return

        # Load families in small batches
        BATCH_SIZE = 5
        family_batches = [copied_families[i:i + BATCH_SIZE] for i in range(0, len(copied_families), BATCH_SIZE)]

        doc = revit.doc
        load_options = AutoUpgradeFamilyLoadOptions()

        # Track results
        loaded_families = []
        failed_families = []
        already_loaded_families = []

        # Insert this tracking code inside the family loading loop:
        # Replace the existing family loading section with:
        for family_batch in family_batches:
            with revit.Transaction("Load Family Batch"):
                for family in family_batch:
                    family_path = os.path.join(project_folder, os.path.basename(family))
                    family_name = os.path.splitext(os.path.basename(family))[0]

                    try:
                        if is_family_loaded(doc, family_name):
                            already_loaded_families.append(family_name)
                        else:
                            loaded_family = clr.Reference[DB.Family]()
                            success = doc.LoadFamily(family_path, load_options, loaded_family)
                            if success:
                                loaded_families.append(family_name)
                            else:
                                failed_families.append(family_name)
                    except:
                        failed_families.append(family_name)

        # Replace the final alert with:
        result_message = ""
        if loaded_families:
            result_message += "LOADED ({}):\n".format(len(loaded_families))
            for family in loaded_families[:10]:  # Show max 10 to prevent dialog overflow
                result_message += "- {}\n".format(family)
            if len(loaded_families) > 10:
                result_message += "... and {} more\n\n".format(len(loaded_families) - 10)
            else:
                result_message += "\n"

        if failed_families:
            result_message += "FAILED ({}):\n".format(len(failed_families))
            for family in failed_families[:10]:  # Show max 10 failed
                result_message += "- {}\n".format(family)
            if len(failed_families) > 10:
                result_message += "... and {} more\n\n".format(len(failed_families) - 10)
            else:
                result_message += "\n"

        result_message += "Already loaded: {}".format(len(already_loaded_families))

        forms.alert("Project Folder: {}\n\n{}".format(project_folder, result_message), title="Family Loading Results")

    except:
        pass


if __name__ == "__main__":
    load_family_wise()

I’ve seen issues on my side as well with upgrading families and revit freezing. Revit 2023-2025.
We have separate families built for each version, so we just had to make sure they were all the correct version. But, let me know if you come across a different solution.

Hi Martin, I have finally found a safe way to load families into Revit without freeze, here is the script below! I am using a Custom Failure Preprocessor python class which properly handles failures and warnings during family loading…. and I also dipose the transaction after each family load making the script not consuming too much memory!!! Hope this Helps,

# Safe Loading Classes
class PythonFailurePreprocessor(DB.IFailuresPreprocessor):
    """
    Python implementation of the FailurePreprocessor
    Handles failures during family loading to prevent Revit from freezing
    """

    def __init__(self, log_directory, log_to_file=True):
        self.log_to_file = log_to_file
        self.log_file_path = os.path.join(log_directory, "log_LoadFamilies.txt")
        self.overwrite = True  # Default to overwrite existing families

    def PreprocessFailures(self, failures_accessor):
        """
        Process failures that occur during family loading
        """
        try:
            failure_messages = failures_accessor.GetFailureMessages()

            if failure_messages.Count == 0:
                return DB.FailureProcessingResult.Continue

            if self.log_to_file:
                self._log_failures_to_file(failure_messages, failures_accessor)
            else:
                self._handle_failures_without_logging(failure_messages, failures_accessor)

            # Return based on overwrite setting
            if self.overwrite:
                return DB.FailureProcessingResult.Continue
            else:
                return DB.FailureProcessingResult.ProceedWithRollBack

        except Exception as e:
            add_message("**Error in PreprocessFailures**: {}".format(str(e)))
            return DB.FailureProcessingResult.Continue

    def _log_failures_to_file(self, failure_messages, failures_accessor):
        """Log failures to file and delete warnings"""
        try:
            # Use Python file handling
            append_mode = os.path.exists(self.log_file_path)

            with open(self.log_file_path, 'a' if append_mode else 'w') as writer:
                # Write log header
                now = datetime.now()
                format_string = "%Y%m%d%H%M"
                writer.write("-----------------------------------------------------\n")
                writer.write("log: {0}\n".format(now.strftime(format_string)))

                # Process each failure message
                for failure_message in failure_messages:
                    try:
                        failure_message.GetFailureDefinitionId()
                        description = failure_message.GetDescriptionText()
                        writer.write("{0}\n".format(description))
                        failures_accessor.DeleteWarning(failure_message)
                    except Exception as e:
                        writer.write("Error processing failure message: {0}\n".format(str(e)))

        except Exception as e:
            add_message("**Error logging failures**: {}".format(str(e)))

    def _handle_failures_without_logging(self, failure_messages, failures_accessor):
        """Handle failures without logging"""
        try:
            for failure_message in failure_messages:
                try:
                    failure_message.GetFailureDefinitionId()
                    failures_accessor.DeleteWarning(failure_message)
                except Exception as e:
                    add_message("**Error handling failure message**: {}".format(str(e)))
        except Exception as e:
            add_message("**Error handling failures**: {}".format(str(e)))


class PythonFamilyLoadOptions(DB.IFamilyLoadOptions):
    """
    Python implementation of IFamilyLoadOptions
    Controls how families are loaded when they already exist
    """

    def __init__(self, overwrite_parameters=True):
        self.overwrite_parameters = overwrite_parameters

    def OnFamilyFound(self, family_in_use):
        """Called when family is found in project"""
        # Return tuple: (should_load, overwrite_parameter_values)
        return (True, self.overwrite_parameters)

    def OnSharedFamilyFound(self, shared_family, family_in_use):
        """Called when shared family is found"""
        # Return tuple: (should_load, family_source, overwrite_parameter_values)
        return (True, DB.FamilySource.Project, self.overwrite_parameters)


def safe_load_family(doc, family_path, project_folder):
    """
    Safely load a family with proper error handling and failure preprocessing

    Args:
        doc: Revit document
        family_path: Path to family file
        project_folder: Project folder for logging

    Returns:
        tuple: (success, family_name, error_message)
    """
    family_name = os.path.splitext(os.path.basename(family_path))[0]

    # Check if family is already loaded
    if is_family_loaded(doc, family_name):
        return (True, family_name, "Already loaded")

    # Create transaction with proper error handling
    transaction = DB.Transaction(doc, "Loading Family: {}".format(family_name))

    # Set up failure handling
    failure_options = transaction.GetFailureHandlingOptions()
    failure_preprocessor = PythonFailurePreprocessor(project_folder, log_to_file=True)
    failure_options.SetFailuresPreprocessor(failure_preprocessor)
    transaction.SetFailureHandlingOptions(failure_options)

    try:
        transaction.Start()

        # Create family load options
        family_load_options = PythonFamilyLoadOptions(overwrite_parameters=True)

        # Attempt to load the family
        loaded_family = clr.Reference[DB.Family]()
        success = doc.LoadFamily(family_path, family_load_options, loaded_family)

        if success:
            transaction.Commit()
            return (True, family_name, "Loaded successfully")
        else:
            transaction.RollBack()
            return (False, family_name, "Failed to load (unknown reason)")

    except Exception as e:
        try:
            transaction.RollBack()
        except:
            pass
        return (False, family_name, "Error: {}".format(str(e)))

    finally:
        if transaction:
            transaction.Dispose()


add_message("**Script Initiated**: Load Family Wise | **Revit Version**: {}".format(
    revit.uidoc.Application.Application.VersionNumber))
3 Likes