Purge in Revit 23

GeniusLoci node Purge Unused works beautifully in Revit 23…

However I cannot get it to work in Revit 25.

I’m trying to make the code work in Py3 but so far hitting a wall.

I have tried all 3 Del options and none work.

This is the code taken from the node.

#Part of script by Matthew Taylor translated in Python by Oliver E Green

#Alban de Chasteigner 2020
#twitter : @geniusloci_bim
#geniusloci.bim@gmail.com

import clr
import System
from System.Collections.Generic import List

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

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

bool=IN[0]
deeply=IN[1]
inputdoc = UnwrapElement(IN[2]) if isinstance(IN[2],list) else [UnwrapElement(IN[2])]
inputdoc = inputdoc[0]

if inputdoc == None:
    doc = DocumentManager.Instance.CurrentDBDocument
elif isinstance (inputdoc, Document) :
    doc = inputdoc
elif isinstance (inputdoc, RevitLinkInstance) :
    doc = inputdoc.GetLinkDocument()
else: doc = DocumentManager.Instance.CurrentDBDocument

#GetPurgeableElements by Oliver E Green 
def GetPurgeableElements(doc, rule_id_list):
    failure_messages = PerformanceAdviser.GetPerformanceAdviser().ExecuteRules(doc, rule_id_list)
    if len(failure_messages) > 0:
        purgeable_element_ids = failure_messages[0].GetFailingElements()
        return purgeable_element_ids

#A constant 
PURGE_GUID = "e8c63650-70b7-435a-9010-ec97660c1bda"

#A generic list of PerformanceAdviserRuleIds as required by the ExecuteRules method
rule_id_list = List[PerformanceAdviserRuleId]()

#Iterating through all PerformanceAdviser rules looking to find that which matches PURGE_GUID
for rule_id in PerformanceAdviser.GetPerformanceAdviser().GetAllRuleIds():
    if str(rule_id.Guid) == PURGE_GUID:
        rule_id_list.Add(rule_id)
        break

#Attempting to retrieve the elements which can be purged
purgeable_element_ids = GetPurgeableElements(doc, rule_id_list)

TransactionManager.Instance.ForceCloseTransaction()

trans = Transaction(doc,'Purge')
trans.Start()

if bool and purgeable_element_ids != None:
    doc.Delete(purgeable_element_ids)

#Materials to delete
toKeep=[]
elements = FilteredElementCollector(doc).WhereElementIsElementType().ToElements()
for elem in elements:
    matlist=[]
    for matid in elem.GetMaterialIds(False):
        matlist.append(matid)
    toKeep.extend(matlist)

compound=FilteredElementCollector(doc).OfClass(HostObjAttributes).WhereElementIsElementType().ToElements()
compoundStructure = [comp.GetCompoundStructure() for comp in compound]
for compoundStr in compoundStructure:
    if compoundStr != None:
        layerCount=compoundStr.LayerCount
        for j in range (0, layerCount):
            if compoundStr.GetMaterialId(j) != ElementId(-1):
                toKeep.append(compoundStr.GetMaterialId(j))

materials = FilteredElementCollector(doc).OfClass(Material).ToElementIds()

#Assets to delete
appearanceAssetIds = FilteredElementCollector(doc).OfClass(AppearanceAssetElement).ToElementIds()
#The PropertySetElement contains either the Thermal or Structural Asset
propertySet = FilteredElementCollector(doc).OfClass(PropertySetElement).ToElementIds()

thermal=[doc.GetElement(id).ThermalAssetId for id in set(toKeep)]
structural=[doc.GetElement(id).StructuralAssetId for id in set(toKeep)]
appearanceAssets=[doc.GetElement(id).AppearanceAssetId for id in set(toKeep)]

propertySet2 = [e for e in propertySet if e not in thermal and e not in structural]
appearanceAssetIds2 = [e for e in appearanceAssetIds if e not in appearanceAssets]
matToDelete=[m for m in materials if not m in toKeep]

views = FilteredElementCollector(doc).OfClass(View).WhereElementIsNotElementType().ToElements()

#Unused filters to delete
filterIds = FilteredElementCollector(doc).OfClass(ParameterFilterElement).ToElementIds()

usedFilterIds = []
if not doc.IsFamilyDocument:
    for view in views:
        viewFilterIds = []
        if view.AreGraphicsOverridesAllowed():
            viewFilterIds = view.GetFilters()
        usedFilterIds.extend(viewFilterIds)
unusedFilterIds = [u for u in filterIds if u not in usedFilterIds]
    
#Unused view Templates to delete
appliedtemplates = [v.ViewTemplateId for v in views]
templates = [v.Id for v in views if v.IsTemplate == True]
templatesToDelete = [t for t in templates if t not in appliedtemplates]

#LinePatterns to delete
linePatterns = FilteredElementCollector(doc).OfClass(LinePatternElement).ToElements()
linesToDelete = [l.Id for l in linePatterns if l.Name.startswith("IMPORT")]

#"Imports in Families" Object Styles to delete
ImportCat = doc.Settings.Categories.get_Item(BuiltInCategory.OST_ImportObjectStyles)
importsInFamily=[c.Id for c in ImportCat.SubCategories]

if bool and matToDelete != None:
    [doc.Delete(m) for m in matToDelete]
if bool and appearanceAssetIds2 != None:
    for a in appearanceAssetIds2:
        try:doc.Delete(a)
        except:pass
if bool and propertySet2 != None:
    [doc.Delete(p) for p in propertySet2]
if deeply and unusedFilterIds != None:
    [doc.Delete(u) for u in unusedFilterIds]
if deeply and templatesToDelete != None:
    [doc.Delete(t) for t in templatesToDelete]
if deeply and linesToDelete != None:
    [doc.Delete(l) for l in linesToDelete]
if deeply and importsInFamily != None:
    [doc.Delete(i) for i in importsInFamily]

trans.Commit()

if purgeable_element_ids != None:
    OUT= '%d elements deleted' %(len([purg for purg in purgeable_element_ids]+matToDelete+appearanceAssetIds2+propertySet2+[f for f in unusedFilterIds]+templatesToDelete+linesToDelete+importsInFamily)),[purg for purg in purgeable_element_ids]+matToDelete+appearanceAssetIds2+propertySet2+[f for f in unusedFilterIds]+templatesToDelete+linesToDelete+importsInFamily
else:
    OUT= '%d elements deleted' %(len(matToDelete+appearanceAssetIds2+propertySet2+[f for f in unusedFilterIds]+templatesToDelete+linesToDelete+importsInFamily)),matToDelete+appearanceAssetIds2+propertySet2+[f for f in unusedFilterIds]+templatesToDelete+linesToDelete+importsInFamily

Why not use the out of the box node instead?

I want to know how to make the code work.

Seems a fools errand, but if you must…

The error is indicating a data type issue. The delete method needs an particular data type - either an ICollection of ElementIds or an ElementId. Best guess is that by moving that content into a definition or otherwise modifying the collection to another type of object. Try [doc.Delete(i) for i in purgeable_element_ids] and see if that works.

learning is never a fools errand.

I would say to investigate the source code here though:

4 Likes

Was more directed at making it work “in Python just because”, as the issues there are significant and the C# code is sufficient (that link was shared in another channel about 24 hours to this topic starting).

I do appreciate a good Python hunt. The architecture is about as close to 1:1 steps as you see (get the purgeable elements via the performance advisor rule and then delete them) which is why I think it is a conversion issue…

@Alien which Revit version are you in?

Actually… my C# isn’t working for materials… :frowning:
Which is why I thought I’d have some ‘fun’ in Python first. :upside_down_face: :crazy_face:

This is in Revit 25 with Py3
The R23 with Iron Python works fine.

1 Like

Which build of 2025? I believe that there are some nice CPython updates for 2025.4…

1 Like

Revit 25.2.0.38

The C# is for an add-in not a Dynamo thing. Dunno if that makes a difference?

Hey,

Just to expand a little, ‘Purge Unused’ only got added to the Revit API in 2024… So all that stuff with ‘PerformanceAdviser.GetPerformanceAdviser().GetAllRuleIds()’ isn’t needed now (I don’t believe!)

Alban is doing extra stuff like removing Line patterns with IMPORT in them, I don’t think those patterns get removed by purging, so it is still interesting to read the code and see what is going on.

Hope that’s useful,

Mark

2 Likes

Not sure Purge Unused exists - do you mean Get Unused Elements?

Personally I haven’t tried it as everything I’ve done has had to stretch to 2023 or prior, but @Alien if you’re only in 2024 and 2025 that might be a bit easier. The Python may still fail at the ‘delete’ section though.

The top one is PostableCommand Enumeration which brings up the user dialogue. I wanted it to be automatic and run in the background.

The second one… Not entirely sure… I’m struggling.

Revit 25 only. I decided on a nice, fresh start seeing as we’re in .NET8 now.

In C# I’ve got it to purge everything except materials and material assets. Back looking at the C# this morning, I have one more idea… but very, very close to saying, “moose it” and slapping in the PostableCommand :person_facepalming:

I’ll look at the Dynamo Python again later too once I get too fed up of the C# :crazy_face:

Brain… hurts :rofl:

1 Like

@Alien I would suggest you have a look at the following code as this may help you get very close to what you want within python without the headache your currently experiencing.

I created this code and it switches the methods used depending on the revit version, the code should support any version of revit after revit 2020. This code should remove everything except for Extensible storage elements.

dynamoPython/revitAPI/PurgeUnused.py at master · Amoursol/dynamoPython · GitHub

2 Likes

Hey,

Yeah you don’t want to use a postable, it’s very limited, basically it is a one time call to the API, you can’t then use the result to do anything more in the code.

To get the functionality of ‘Purge Unused’ without a postable, you can use ‘Get Unused Elements’ as of 2024.

Hope that helps,

Mark

2 Likes

Yeah Revit 2024, added the “Unused Elements” API methods for purging.

Oh wow!!! You sir are an absolute genius!!

Translated to C# and it works!!

I am so happy right now!

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System.Collections.Generic;

public class Purge
{
    public static void PurgeAll(Document doc)
    {
        int purgeCount = 0;
        int delTotal = 0;

        HashSet<ElementId> catIdList = new HashSet<ElementId>();

        using (Transaction transaction = new Transaction(doc, "Purge Unused stuff"))
        {
            transaction.Start();

            while (purgeCount < 10)
            {
                // Get all unused elements
                ICollection<ElementId> purgeElements = doc.GetUnusedElements(catIdList);

                if (purgeElements.Count > 0)
                {
                    doc.Delete(purgeElements);
                    delTotal += purgeElements.Count;
                }
                else
                {
                    break;
                }

                purgeCount++;
            }

            transaction.Commit();
        }

        // Display results
        string message = (delTotal == 0)
            ? "Nothing purgeable found in the project."
            : $"Purge ran {purgeCount} times and deleted {delTotal} unused elements.";

        TaskDialog.Show("Purge Complete", message);
    }
}

2 Likes

The tips with the eTransmitForRevitDB library work with all versions.

purge_unsed

import clr
import sys
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

# Import RevitAPI
clr.AddReference("RevitAPI")
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

# Import DocumentManager and TransactionManager
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application
uidoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument
sdkNumber = int(app.VersionNumber)

pf_path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86)
prf_path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFiles)

eTransmit = None
try:
    clr.AddReference('eTransmitForRevitDB')
    import eTransmitForRevitDB as eTransmit
except:
    path_etransmit = prf_path + '\\Autodesk\\eTransmit for Revit {}'.format(sdkNumber)
    if System.IO.Directory.Exists(path_etransmit):
        sys.path.append(path_etransmit)
        clr.AddReference('eTransmitForRevitDB')
        import eTransmitForRevitDB as eTransmit
        

def purgeUnsed(currentDoc):
    if eTransmit is not None:
        eTransmitUpgradeOMatic = eTransmit.eTransmitUpgradeOMatic(app)
        resultPurg = eTransmitUpgradeOMatic.purgeUnused(currentDoc)
        return resultPurg
    return "Failed"

OUT = purgeUnsed(doc)
3 Likes

Nice!

It left in one purgeable item strangely

If you are going to remove all links from the project file you could just go over each of these categories and get all elements of that type, then just delete them.