Purge Unused using Performance Adviser

We repeatedly looked at ways to detect and purge unused elements. A list of some previous discussions of the topic was given last time we looked at purge and detecting an empty view.

Matt Taylor, associate and CAD developer at WSP, was the first to congratulate on The Building Coder's ten-year anniversary.

He now adds something really special to celebrate this:

Broomstick

Purge Unused using the Performance Adviser

I’m sharing with you a new discovery of mine.

Apparently, nobody has previously publicly discovered a simple and effective way of purging all unused elements.

I now found one:

I have successfully used the Performance Adviser to do a similar job to the native Purge Unused command.

Please refer to my RevitPurgeUnused GitLab repository.

While the code will compile back to Revit 2012, it actually throws an InternalException for versions 2012-2016 (in my experience).

It doesn’t do a perfect job (e.g., it doesn’t purge materials and material assets), but it is very, very good, and quite fast.

I also added a note of my solution to some of the existing threads on this topic in the Revit API discussion forum:

Very many thanks to Matt for sharing this solution to one of the top developer wish list items!

PurgeTool.vb implements GetPurgeableElements

Here is Matt's VB.NET code in PurgeTool.vb, defining the long-sought-after GetPurgeableElements method:

#Region "Imported Namespaces"
Imports System
Imports System.Collections.Generic
Imports Autodesk.Revit.DB
#End Region

Public Class PurgeTool
  ''' <summary>
  ''' The guid of the 'Project contains unused families and types' PerformanceAdviserRuleId.
  ''' </summary>
  Const PurgeGuid As String = "e8c63650-70b7-435a-9010-ec97660c1bda"

  ''' <summary>
  ''' Get all purgeable elements.
  ''' Intended for Revit 2017+ as versions up to and including Revit 2016 throw an InternalException.
  ''' </summary>
  ''' <param name="doc"></param>
  ''' <param name="purgeableElementIds"></param>
  ''' <returns>True if successful.</returns>
  Shared Function GetPurgeableElements(doc As Document, ByRef purgeableElementIds As ICollection(Of ElementId)) As Boolean
    purgeableElementIds = New List(Of ElementId)()

    Try
      'create a new list of rules.
      Dim ruleIds As IList(Of PerformanceAdviserRuleId) = New List(Of PerformanceAdviserRuleId)
      Dim ruleId As PerformanceAdviserRuleId = Nothing
      'find the intended rule.
      If GetPerformanceAdvisorRuleId(PurgeGuid, ruleId) Then
        'add the rule to the new list.
        ruleIds.Add(ruleId)
      Else
        'cannot find rule.
        Return False
      End If
      'execute our chosen rule only.
      Dim failureMessages As IList(Of FailureMessage) = PerformanceAdviser.GetPerformanceAdviser().ExecuteRules(doc, ruleIds)
      If failureMessages.Count > 0 Then
        'If there are any purgeable elements, we should have a failure message.
        'the failure message should have a collection of failing elements - set to our byref collection
        purgeableElementIds = failureMessages.Item(0).GetFailingElements
      End If
      'no errors - return true.
      Return True
    Catch ex As Autodesk.Revit.Exceptions.InternalException
      'this exception gets thrown a lot in earlier versions of Revit - up to and including Revit 2016.

    End Try
    'likely thrown an internal exception
    Return False
  End Function

  ''' <summary>
  ''' Find a PerformanceAdviserRuleId with a guid that matches a supplied guid.
  ''' </summary>
  ''' <param name="guidStr"></param>
  ''' <param name="ruleId"></param>
  ''' <returns>true if successful, along with the rule as a byref.</returns>
  Private Shared Function GetPerformanceAdvisorRuleId(ByVal guidStr As String, ByRef ruleId As PerformanceAdviserRuleId) As Boolean
    ruleId = Nothing
    Dim guid As Guid = New Guid(guidStr)
    For Each rule As PerformanceAdviserRuleId In PerformanceAdviser.GetPerformanceAdviser().GetAllRuleIds
      'check if the rule Id matches our rule guid
      If rule.Guid.Equals(guid) Then
        'it does - set rule to our byref object
        ruleId = rule
        Return True
      End If
    Next
    'failed to find the rule matching our guid.
    Return Nothing
  End Function
End Class

PurgeUnused.vb External Command

The result is used like this in the external command defined by PurgeUnused.vb:

  Dim purgeableElements As ICollection(Of ElementId) = Nothing
  If PurgeTool.GetPurgeableElements(doc, purgeableElements) AndAlso purgeableElements.Count > 0 Then
    Using transaction As New Transaction(doc, "Purge Unused")
      transaction.Start()
      doc.Delete(purgeableElements)
      transaction.Commit()
      Return Result.Succeeded
    End Using
  Else
    Return Result.Failed
  End If