Author Topic: Purge Tools  (Read 13888 times)

0 Members and 1 Guest are viewing this topic.

Chuck Gabriel

  • Guest
Purge Tools
« on: November 14, 2008, 09:29:14 AM »
I wrote this a while back, and I thought it was generic enough that someone else might find it useful.

Code: [Select]
using System;
using System.Text;
using System.Collections.Generic;

using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.ApplicationServices;

[assembly: ExtensionApplication(typeof(cgabriel.PurgeTools))]
[assembly: CommandClass(typeof(cgabriel.PurgeTools))]

namespace cgabriel
{
    public class PurgeTools : IExtensionApplication
    {

        public void Initialize() { }
        public void Terminate() { }

        public static bool purgeItems(Database db, ObjectIdCollection tableIds, bool silent)
        {
            Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;

            bool itemsPurged = false;

            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                ObjectIdCollection purgeableIds = new ObjectIdCollection();
                foreach (ObjectId tableId in tableIds)
                {
                    SymbolTable table = (SymbolTable)tr.GetObject(tableId, OpenMode.ForRead, false);
                    foreach (ObjectId recordId in table)
                        purgeableIds.Add(recordId);
                }

                db.Purge(purgeableIds);

                if (purgeableIds.Count != 0)
                {
                    itemsPurged = true;
                    foreach (ObjectId id in purgeableIds)
                    {
                        SymbolTableRecord record = (SymbolTableRecord)tr.GetObject(id, OpenMode.ForWrite);
                        string recordName = record.Name;
                        if (!silent)
                        {
                            if (!recordName.Contains("|"))
                            {
                                ed.WriteMessage("\nPurging " +
                                    record.GetType().Name + " " + recordName);
                            }
                        }
                        record.Erase();
                    }
                }
                tr.Commit();
            }

            return itemsPurged;
        }

        public static bool purgeAll(Database db, bool silent)
        {
            ObjectIdCollection tableIds = new ObjectIdCollection();
            tableIds.Add(db.BlockTableId);
            tableIds.Add(db.DimStyleTableId);
            tableIds.Add(db.LayerTableId);
            tableIds.Add(db.LinetypeTableId);
            tableIds.Add(db.RegAppTableId);
            tableIds.Add(db.TextStyleTableId);
            tableIds.Add(db.UcsTableId);
            tableIds.Add(db.ViewportTableId);
            tableIds.Add(db.ViewTableId);
            return purgeItems(db, tableIds, silent);
        }

        [CommandMethod("PurgeTools", "PurgeAll", CommandFlags.Modal | CommandFlags.DocExclusiveLock)]
        public static void purgeAll()
        {
            while (purgeAll(Application.DocumentManager.MdiActiveDocument.Database, false))
                continue;
        }
    }
}

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: Purge Tools
« Reply #1 on: November 15, 2008, 08:49:20 AM »
Nice Work Chuck!

Can you explain what this line does?


Code: [Select]
                 
 if (!recordName.Contains("|"))
    {
        ed.WriteMessage("\nPurging " +
        record.GetType().Name + " " + recordName);
    }
« Last Edit: November 15, 2008, 08:53:41 AM by Daniel »

Chuck Gabriel

  • Guest
Re: Purge Tools
« Reply #2 on: November 15, 2008, 09:04:33 AM »
I found that the purge method was identifying xref-dependent objects as purgeable, and while deleting those objects doesn't seem to cause any corruption, I thought it might panic some users to see messages saying that those items were being purged.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: Purge Tools
« Reply #3 on: November 15, 2008, 10:31:58 AM »
Thank you!  :kewl:

Glenn R

  • Guest
Re: Purge Tools
« Reply #4 on: November 15, 2008, 02:00:23 PM »
Chuckster - nice work as usual. :)

Chuck Gabriel

  • Guest
Re: Purge Tools
« Reply #5 on: November 15, 2008, 03:28:22 PM »
Thanks guys.  Please don't be shy about telling me if I did something bone-headed.

Spike Wilbury

  • Guest
Re: Purge Tools
« Reply #6 on: November 15, 2008, 03:52:52 PM »
Thanks guys.  Please don't be shy about telling me if I did something bone-headed.

OK, you asked for the true - here it goes:

Please do it all again - and don't use this : if (purgeableIds.Count != 0) - anymore, eh!

 :evil:

 :angel:
 :lol:
 :-P
 :ugly:

 :lmao:  :lmao:  :lmao:

Alexander Rivilis

  • Bull Frog
  • Posts: 214
  • Programmer from Kyiv (Ukraine)
Re: Purge Tools
« Reply #7 on: November 15, 2008, 04:04:32 PM »
Hi. Chuck!
It will be more correctly to use this form of purge method:
Code: [Select]
Autodesk.AutoCAD.DatabaseServices.Database.Purge(Autodesk.AutoCAD.DatabaseServices.ObjectIdGraph)From ObjectARX Docs:
Quote
This version of the purge() method works in one pass. The method looks for references between the objects passed in so that it does not need to be called multiple times. In other words, if a Layer and a Linetype are passed in, and the only reference to the Linetype is from the Layer, then the graph returned will indicate that both the Layer and the Linetype can be purged. (The older AcDbObjectIdArray version of purge() would first indicate that only the Layer could be purged. Then a second call, after erasing the Layer, would say that the Linetype could be purged.)

A graph is returned so that you do not need to erase all the objects passed back, just like in the other purge(). However, if you want to selectively erase only part of the objects passed back, you must only erase root-type nodes on the graph. In other words, from the above example, the graph passed back would contain both the Layer and Linetype nodes, but there would be an edge from the Layer to the Linetype. Thus only the Layer would be a root-type node, with no incoming edges. That means that you could erase the Layer by itself, but not the Linetype. If you want to erase the Linetype, then you must also erase the Layer. That's why the return data is in a graph.

Notes

Since the AcDbObjectIdGraph version (this method) does more checking, it is slower than the AcDbObjectIdArray version.
If only one type of symbol is being checked through purge(), and it is not a BTR (which can have references to each other), then the older, faster AcDbObjectIdArray purge() should be used.
Only use this version in cases where there were loops before. In other words, if the types of symbols being passed in can reference each other, then the purge(AcDbObjectIdArray) function would have had to have been called multiple times. Even though this function is slower, since it only has to be called once, it ends up being faster for this inter-reference case.
If you intend to always erase everything returned, then you do not have to examine the graph. You can just iterate on graph nodes and erase the objects, but you must erase all of them. The graph edges only need to be examined if part of the graph is to be erased, to make sure that no edges, or inter-references, are left dangling. In such a case, only nodes with no incoming edges (root-type nodes) can be erased
« Last Edit: November 15, 2008, 04:18:57 PM by Alexander Rivilis »

Chuck Gabriel

  • Guest
Re: Purge Tools
« Reply #8 on: November 15, 2008, 04:54:07 PM »
Alexander,

Thanks for the feedback.

The reason I didn't use that overload is because when I used the native code version in the past, it seemed to be leaving items in the graph that should not have been purged.

See this thread.

Was I doing something wrong or maybe just misunderstanding something?

Luis - there really should be an isEmpty method for all collections, but you were just yankin my chain, weren't you.

Spike Wilbury

  • Guest
Re: Purge Tools
« Reply #9 on: November 15, 2008, 05:15:18 PM »
Luis - there really should be an isEmpty method for all collections, but you were just yankin my chain, weren't you.

Something like that Chuck.... and yes about why not the isEmpty();

But, can you/or did you test it by simple calling:

Code: [Select]
foreach (ObjectId id in purgeableIds)
{
..
}
If there is something on the collection, it will be process no?

:)

Chuck Gabriel

  • Guest
Re: Purge Tools
« Reply #10 on: November 15, 2008, 05:18:34 PM »
I could have done that, but I wanted to return a boolean indicating whether anything was purged, so I put in the check.  I guess I could have just done:

Code: [Select]
return purgeableIds.Count > 0;

at the end of the routine.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Purge Tools
« Reply #11 on: November 15, 2008, 05:21:25 PM »

 ... but purgeableIds would be out of scope , yes ?
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Purge Tools
« Reply #12 on: November 15, 2008, 05:22:51 PM »

Great looking routine Chuck.
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Chuck Gabriel

  • Guest
Re: Purge Tools
« Reply #13 on: November 15, 2008, 05:23:09 PM »

 ... but purgeableIds would be out of scope , yes ?

It is right now, but the declaration could be moved outside of the transaction.  Thanks for the heads up.

Chuck Gabriel

  • Guest
Re: Purge Tools
« Reply #14 on: November 15, 2008, 05:24:45 PM »
Great feedback, by the way, everybody.

jmaeding

  • Bull Frog
  • Posts: 304
  • I'm just here for the Shelties.
Re: Purge Tools
« Reply #15 on: November 19, 2008, 03:11:09 PM »
Note that if you try to clean a drawing that has lots of Application IDs, and xrefs, it will typically run very slow.
Those App IDs are worse than anything else.  I would leave them out, in favor of a tool that cleans them RealDwg style.
That is what Adesk did for me, wrote an exe that cleans regapps and layer filters.  It runs outside of acad, so just does nothing if a corrupt drawing is encountered, and is very fast.  Its 20x faster than any other solution I have seen, so that is a big deal.
Grab the PurgeIDs prog from cadthinking.com to get my prog, the exe's are included.
You run the exe with a filename param, it cleans one file at a time.
James Maeding

Chuck Gabriel

  • Guest
Re: Purge Tools
« Reply #16 on: November 19, 2008, 03:17:02 PM »
Did I read that right?  The folks at Autodesk wrote a custom app for you?  How do I get that kind of clout!?

Bryco

  • Water Moccasin
  • Posts: 1883
Re: Purge Tools
« Reply #17 on: November 19, 2008, 06:42:52 PM »
Has anyone done comparative testing with
1) purgeall then add back template layers and dims
2) Purge all layers except the template layers, purge all dims except the template dims.
 
I use the 2nd version in vba, but I have the feeling using Chuck's code and adding back in what I need will be faster.

TJK44

  • Guest
Re: Purge Tools
« Reply #18 on: May 29, 2013, 04:09:28 PM »
Hi Chuck,

Thanks for your routine, it has helped me out a lot.

I found a need to purge nested items which it did not do. I added a loop to PurgeItems to get this to work.

Code - Visual Basic: [Select]
  1. Public Shared Function purgeItems(ByVal db As Database, ByVal tableIds As ObjectIdCollection, ByVal silent As Boolean) As Boolean
  2.             Dim ed As Editor = Application.DocumentManager.MdiActiveDocument.Editor
  3.             Dim doc As Document = Application.DocumentManager.MdiActiveDocument
  4.             Dim itemsPurged As Boolean = False
  5.  
  6.  
  7.             Using tr As Transaction = db.TransactionManager.StartTransaction()
  8.                 Using docLoc As DocumentLock = doc.LockDocument
  9.                     Dim keepPurging As Boolean = True
  10.                     'loop to purge nested items
  11.                    While keepPurging
  12.                         Dim purgeableIds As New ObjectIdCollection()
  13.                         For Each tableId As ObjectId In tableIds
  14.                             Dim table As SymbolTable = DirectCast(tr.GetObject(tableId, OpenMode.ForRead, False), SymbolTable)
  15.                             For Each recordId As ObjectId In table
  16.                                 purgeableIds.Add(recordId)
  17.                             Next
  18.                         Next
  19.  
  20.                         db.Purge(purgeableIds)
  21.  
  22.                         If purgeableIds.Count <> 0 Then
  23.                             itemsPurged = True
  24.                             For Each id As ObjectId In purgeableIds
  25.                                 Dim record As SymbolTableRecord = DirectCast(tr.GetObject(id, OpenMode.ForWrite), SymbolTableRecord)
  26.                                 Dim recordName As String = record.Name
  27.                                 If Not silent Then
  28.                                     If Not recordName.Contains("|") Then
  29.                                         ed.WriteMessage(vbLf & "Purging " + record.[GetType]().Name & " " & recordName)
  30.                                     End If
  31.                                 End If
  32.                                 record.Erase(True)
  33.                             Next
  34.                         Else
  35.                             keepPurging = False
  36.                         End If
  37.                     End While
  38.                     tr.Commit()
  39.  
  40.                 End Using
  41.             End Using
  42.  
  43.             Return itemsPurged
  44.         End Function
  45.