theme-sticky-logo-alt
theme-logo-alt

Purging Material Assets Using the Revit API the Right Way

cleaning
0 Comments

I was asked about a problematic Revit model with 57,000 purgeable material assets. “Just purge them”, you say, but the problem is that even using a shift+click to select the assets locked up the user’s Revit for around 30 minutes while Revit thought about its life choices.

I had experience previously purging elements using the performance advisor, but that doesn’t purge materials. Harry Mattson has posted a great code snippet on boostyourbim that shows how to purge materials using a method that checks for errors on deletion; if the code encounters an error, it rolls back the transaction. Harry points out that it takes a bit of time, as it is deleting and undeleting all the materials within the project. When I tested this code on the Snowden Towers demo project that comes with Revit 2024, it took about 15 minutes from start to finish. It doesn’t purge material assets though, so unfortunately, it doesn’t do what I’m after.

Then I stumbled across a great post on LinkedIn by Emiliano Capasso where he asked the question “How does the eTransmit tool do it?” his solution is to reference eTransmitForRevitDB.dll in his application and call the public method eTransmitUpgradeOMatic which he does like so:

public bool Purge(Application app, Document doc
        {
            eTransmitUpgradeOMatic eTransmitUpgradeOMatic = new eTransmitUpgradeOMatic(app);
            UpgradeFailureType result = eTransmitUpgradeOMatic.purgeUnused(doc);
            if (result == UpgradeFailureType.UpgradeSucceeded) return true;
            else return false;
        })Code language: C# (cs)

The problem with Emiliano’s method is that you don’t have control over what is purged, just like when you tick the purge unused button in eTransmit, it simply purges everything.

It got me thinking: could I implement the same method used in eTransmit but with the ability to filter only the material assets? Turns out you can. A quick peek at the code in the DLL for Revit 2024 reveals that the purgeUnused the GetUnusedElements method, which is a new method introduced in the 2024 API.

This led me to a quick implementation as a C# macro to help the user out in deleting their 57,000 unnamed material assets:

public void ListAndPurgeUnusedMaterialAssets()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;

    // Get all unused elements in the document
    ISet<ElementId> unusedElementIds = doc.GetUnusedElements(new HashSet<ElementId>());

    List<string> unusedAssetNames = new List<string>();
    List<ElementId> unusedAssetIds = new List<ElementId>();

    // Process each unused element and filter for material assets and appearance assets
    foreach (ElementId elementId in unusedElementIds)
    {
        Element elem = doc.GetElement(elementId);
        if (elem != null)
        {
            // Check if the element is an AppearanceAssetElement
            if (elem is AppearanceAssetElement)
            {
                unusedAssetNames.Add(elem.Name + " (Appearance)");
                unusedAssetIds.Add(elementId);
            }
            // Check if the element is in the Material Assets category
            else if (elem.Category != null && elem.Category.Name == "Material Assets")
            {
                unusedAssetNames.Add(elem.Name + " (Material Asset)");
                unusedAssetIds.Add(elementId);
            }
        }
    }

    // Sort the list of names for better readability
    unusedAssetNames.Sort();

    // Display the names of unused assets in a Task Dialog
    string dialogContent = string.Join(Environment.NewLine, unusedAssetNames);
    TaskDialog.Show("Unused Assets", dialogContent);

    // Confirm with the user before deletion
    TaskDialogResult result = TaskDialog.Show("Confirm Deletion", "Are you sure you want to delete the following unused assets?\n\n" + dialogContent, TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No);

    if (result == TaskDialogResult.Yes)
    {
        // Delete the unused assets
        using (Transaction t = new Transaction(doc, "Delete Unused Assets"))
        {
            t.Start();
            try
            {
                doc.Delete(unusedAssetIds);
                t.Commit();
            }
            catch (Exception ex)
            {
                // Handle the exception (could be element can't be deleted, etc.)
                t.RollBack();
                TaskDialog.Show("Error", "An error occurred while deleting assets: " + ex.Message);
            }
        }
    }
}Code language: C# (cs)

This macro picks up all material assets and cross-checks their element IDs with those in the unused element ID list. It’s a tidy solution that gives full control of what you remove from the model.

Before and after running the macro on the Snowden Towers example project

However, this method is new in the 2024 API, which doesn’t help if you work in Revit 2023 and below. Revit 2023 still has an eTransmit tool though, and that eTransmit tool still can purge the models.

If we take a look at the 2023 eTransmit DLL, the code calls the GetUnusedAppearances, GetUnusedMaterials, GetUnusedFamilies, GetUnusedImportCategories, GetUnusedStructures, GetUnusedSymbols, GetUnusedThermals and GetNonDeletableUnusedElements methods.

These methods aren’t documented parts of the API; they are private, so that means they won’t appear in the Visual Studio object browser either. However, using a decompiler like JetBrains dotPeek will reveal their presence.

The problem is, if you attempt to access one of the GetUnused methods directly, you will be hit with the following error when you try to compile your code:

‘Autodesk.Revit.DB.Document’ does not contain a definition for ‘GetUnusedAppearances’ and no extension method ‘GetUnusedAppearances’ accepting a first argument of type ‘Autodesk.Revit.DB.Document’ could be found (are you missing a using directive or an assembly reference?)

So how can you use them? You can invoke the method using System.Reflection like so:

using System;
using System.Reflection;
using Autodesk.Revit.DB;

public void PurgeUnusedMaterialAssets()
{
	UIDocument uidoc = this.ActiveUIDocument;
	Document doc = uidoc.Document;
	
	try
	{
		// Get the MethodInfo for the internal method
		MethodInfo getUnusedAppearancesMethod = typeof(Document).GetMethod("GetUnusedAppearances", BindingFlags.NonPublic | BindingFlags.Instance);
		
		// Invoke the method and get the result
		if (getUnusedAppearancesMethod != null)
		{
			ICollection<ElementId> unusedAppearances = (ICollection<ElementId>)getUnusedAppearancesMethod.Invoke(doc, null);
			
			// Now you have the unused appearances and can proceed to delete them
			// Similar reflection calls would need to be made for the other GetUnused... methods
		}
	}
	catch (Exception ex)
	{
		// Handle exceptions
		TaskDialog.Show("Error", "An error occurred: " + ex.Message);
	}
}
Code language: C# (cs)

This solution for purging model content using the API provides the same level of granularity as the new Revit 2024 API method; there are just a few extra steps to run through each GetUnused method.

Before and after running the macro on the RAC_basic_sample_project.rvt example project in Revit 2023

Here is my streamlined version of the 2023 macro, which specifically looks to purge material assets:

private ICollection<ElementId> GetUnusedAssets(Document doc, string methodName)
{
	MethodInfo method = typeof(Document).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
	if (method != null)
	{
		return (ICollection<ElementId>)method.Invoke(doc, null);
	}
	return new List<ElementId>();
}

private void AddUnusedAssets(Document doc, ICollection<ElementId> elementIds, string assetType, List<string> names, List<ElementId> ids)
{
	foreach (var id in elementIds)
	{
		Element elem = doc.GetElement(id);
		if (elem != null)
		{
			names.Add(elem.Name + " (" + assetType + ")");
			ids.Add(id);
		}
	}
}

public void PurgeUnusedMaterialAssets()
{
	UIDocument uidoc = this.ActiveUIDocument;
	Document doc = uidoc.Document;
	
	List<string> unusedAssetNames = new List<string>();
	List<ElementId> unusedAssetIds = new List<ElementId>();

	try
	{
		// Retrieve unused assets using reflection
		AddUnusedAssets(doc, GetUnusedAssets(doc, "GetUnusedMaterials"), "Material", unusedAssetNames, unusedAssetIds);
		AddUnusedAssets(doc, GetUnusedAssets(doc, "GetUnusedAppearances"), "Appearance", unusedAssetNames, unusedAssetIds);
		AddUnusedAssets(doc, GetUnusedAssets(doc, "GetUnusedStructures"), "Structure", unusedAssetNames, unusedAssetIds);
		AddUnusedAssets(doc, GetUnusedAssets(doc, "GetUnusedThermals"), "Thermal", unusedAssetNames, unusedAssetIds);

		// Sort the list of names for better readability
		unusedAssetNames.Sort();

		// Display the list to the user and ask for confirmation before deletion
		string dialogContent = string.Join(Environment.NewLine, unusedAssetNames);
		TaskDialogResult result = TaskDialog.Show("Confirm Deletion", "Are you sure you want to delete the following unused assets?\n\n" + dialogContent, TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No);

		if (result == TaskDialogResult.Yes)
		{
			// Delete the unused assets within a transaction
			using (Transaction t = new Transaction(doc, "Delete Unused Assets"))
			{
				t.Start();
				doc.Delete(unusedAssetIds);
				t.Commit();
			}
		}
	}
	catch (Exception ex)
	{
		TaskDialog.Show("Error", "An error occurred: " + ex.Message);
	}

}Code language: C# (cs)

Comments

Share on activity feed

Powered by WP LinkPress

Previous
Next

0 Comments

Leave a Reply

15 49.0138 8.38624 1 1 8000 1 https://digitalbbq.au 300 0