Purge unused materials for another #RTCEUR API wish

Yesterday I looked at how to purge unused families and family types. Purging materials needs a different approach because they are not used directly in the project but are referenced by other objects.

Here is an approach that uses the DocumentChanged event, rolling back Transaction Groups, and other good things do find materials that can be deleted without elements being modified. It takes a bit of time to run because it is deleting all materials and undoing the deletions that modify other elements.

material-purge

public static int modifiedByDeleteMaterial = 0;
public static bool checkForPurgeMaterials = false;
public static Document _doc = null;
public static string materialName = "";
public void purgeMaterials()
{
    Document doc = this.ActiveUIDocument.Document;
    _doc = doc;
    Application app = doc.Application;
    app.DocumentChanged += documentChanged_PurgeMaterials;
    List<Element> materials = new FilteredElementCollector(doc).OfClass(typeof(Material)).ToList();
    string deletedMaterials = "";
    int unusedMaterialCount = 0;
    foreach (Element material in materials)
    {
        modifiedByDeleteMaterial = 0;
        materialName = material.Name + " (id " + material.Id + ")";
        using (TransactionGroup tg = new TransactionGroup(doc, "Delete Material: " + materialName))
        {
            tg.Start();
            using (Transaction t = new Transaction(doc, "delete material"))
            {
                t.Start();
                checkForPurgeMaterials = true;
                doc.Delete(material.Id);
                
                // commit the transaction to trigger the DocumentChanged event
                t.Commit();
            }
            checkForPurgeMaterials = false;
            
            if (modifiedByDeleteMaterial == 1)
            {
                unusedMaterialCount++;
                deletedMaterials += materialName + Environment.NewLine;
                tg.Assimilate();
            }
            else // rollback the transaction group to undo the deletion
                tg.RollBack();
        }
    }
    
    TaskDialog td = new TaskDialog("Info");
    td.MainInstruction = "Deleted " + unusedMaterialCount + " materials";
    td.MainContent = deletedMaterials;
    td.Show();

    app.DocumentChanged -= documentChanged_PurgeMaterials;
}

private static void documentChanged_PurgeMaterials(object sender, Autodesk.Revit.DB.Events.DocumentChangedEventArgs e)
{
    // do not check when rolling back the transaction group
    if (!checkForPurgeMaterials)
    {
        return;
    }
    
    List<ElementId> deleted = e.GetDeletedElementIds().ToList();
    List<ElementId> modified = e.GetModifiedElementIds().ToList();
    
    // for debugging
    string s = "";
    foreach (ElementId id in modified)
    {
        Element modifiedElement = _doc.GetElement(id);
        s += modifiedElement.Category.Name + " " + modifiedElement.Name + " (" +  id.IntegerValue + ")" + Environment.NewLine;
    }
    //TaskDialog.Show("d", materialName + Environment.NewLine + "Deleted = " + deleted.Count + ", Modified = " + modified.Count + Environment.NewLine + s);
    
    // how many elements were modified and deleted when this material was deleted?
    // if 1, then the material is unused and should be deleted
    modifiedByDeleteMaterial = deleted.Count + modified.Count;
}
Advertisements

2 thoughts on “Purge unused materials for another #RTCEUR API wish

  1. Hi,
    I slightly modified code for purge family document materials. I´m not sure is it right solution but better than nothing. Thank you for the original solution!
    Cheers
    Veli

    Public Shared Sub documentChanged_PurgeMaterials(ByVal sender As Object, ByVal args As Autodesk.Revit.DB.Events.DocumentChangedEventArgs)
    If Not checkForPurgeMaterials Then Return
    Dim countFamilyIntance As Integer = 0
    Dim countFamilySymbol As Integer = 0
    Dim countFamily As Integer = 0
    Dim app As Autodesk.Revit.ApplicationServices.Application = CType(sender, Autodesk.Revit.ApplicationServices.Application)
    Dim doc As Document = args.GetDocument
    Dim deleted As IList(Of ElementId) = args.GetDeletedElementIds.ToList
    Dim modified As IList(Of ElementId) = args.GetModifiedElementIds.ToList
    Debug.WriteLine(“ElemModified: ” + modified.Count.ToString + ” ElemDeleted: ” + deleted.Count.ToString)
    app.WriteJournalComment(“ElemModified: ” + modified.Count.ToString + ” ElemDeleted: ” + deleted.Count.ToString, True)
    Dim elem As Element = Nothing
    For Each elemId As ElementId In modified
    elem = doc.GetElement(elemId)
    If Not elem Is Nothing Then
    Debug.WriteLine(“ElemType: ” + elem.GetType().Name + ” ElemName: ” + elem.Name + ” Id: ” + elem.Id.ToString)
    app.WriteJournalComment(“ElemType: ” + elem.GetType().Name + ” ElemName: ” + elem.Name + ” Id: ” + elem.Id.ToString, True)
    If elem.GetType Is GetType(Family) And elem.Name = “” Then
    countFamily += 1
    ElseIf elem.GetType Is GetType(FamilyInstance) Then
    countFamilyIntance += 1
    ElseIf elem.GetType Is GetType(FamilySymbol) Then
    countFamilySymbol += 1
    End If
    End If
    Next

    ‘how many elements were modified And deleted when this material was deleted? if 1, then the material Is unused And should be deleted
    Debug.WriteLine(“countFamilyIntance: ” + countFamilyIntance.ToString + ” countFamilySymbol: ” + countFamilySymbol.ToString + ” countFamily: ” + countFamily.ToString)
    app.WriteJournalComment(“countFamilyIntance: ” + countFamilyIntance.ToString + ” countFamilySymbol: ” + countFamilySymbol.ToString + ” countFamily: ” + countFamily.ToString, True)
    If countFamilyIntance = 0 Then
    modifiedByDeleteMaterial = 1
    Else
    modifiedByDeleteMaterial = deleted.Count + modified.Count
    End If
    End Sub

  2. Hi again,
    there is little problem with materials that have appearance assets involved. Materials are removed from family document properly but appearance assets are not removed with materials. So next I try to remove AppearanceAssetElement elements from family document through .NET API and Revit throws an exception. (Exception thrown: ‘Autodesk.Revit.Exceptions.ArgumentException’ in RevitAPI.dll
    Exception: ElementId cannot be deleted.)
    I know that appearance assets can be removed by UI with Purge functionality but not through .NET API. Have you tried to do this programmatically?

    Cheers
    Veli V.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s