Purging registered application names from a folder of AutoCAD drawings using .NET

In the last post we looked at some code to programmatically purge Registered Application names from the drawing currently active in AutoCAD. In this post we take the "batching" code first used in this previous post and apply it to this problem.

What we end up with is an additional command called PF which asks the user to specify a folder and then purges the RegApps from the DWGs in that folder, saving those files that end up being modified with the "_purged" suffix.

One point to note is the use of the Database.RetainOriginalThumbnailBitmap property: as we're not making any graphical changes it's fairly safe to set this to true, which retains the pervious thumbnail bitmap, rather than it being blank in the new drawing. If you were to set it to true after graphical changes nothing especially serious would happen, but it could be confusing for users if the preview differed substantially from the DWG contents.

Here's the C# code with the additional lines in red:

    1 using Autodesk.AutoCAD.ApplicationServices;

    2 using Autodesk.AutoCAD.DatabaseServices;

    3 using Autodesk.AutoCAD.EditorInput;

    4 using Autodesk.AutoCAD.Runtime;

    5 using System.IO;

    6 using System;

    7

    8 namespace Purger

    9 {

   10   public class Commands

   11   {

   12     [CommandMethod("PF")]

   13     public void PurgeFiles()

   14     {

   15       Document doc =

   16         Application.DocumentManager.MdiActiveDocument;

   17       Editor ed = doc.Editor;

   18

   19       PromptResult pr =

   20         ed.GetString(

   21           "\nEnter folder containing DWGs to process: "

   22         );

   23       if (pr.Status != PromptStatus.OK)

   24         return;

   25       string pathName = pr.StringResult;

   26

   27       string[] fileNames =

   28         Directory.GetFiles(pathName, "*.dwg");

   29

   30       // We'll use some counters to keep track

   31       // of how the processing is going

   32

   33       int processed = 0, saved = 0, problem = 0;

   34

   35       foreach (string fileName in fileNames)

   36       {

   37         if (fileName.EndsWith(

   38               ".dwg",

   39               StringComparison.CurrentCultureIgnoreCase

   40             )

   41         )

   42         {

   43           string outputName =

   44             fileName.Substring(

   45               0,

   46               fileName.Length - 4) +

   47             "_purged.dwg";

   48           Database db = new Database(false, true);

   49           using (db)

   50           {

   51             try

   52             {

   53               ed.WriteMessage(

   54                 "\n\nProcessing file: " + fileName

   55               );

   56

   57               db.ReadDwgFile(

   58                 fileName,

   59                 FileShare.ReadWrite,

   60                 false,

   61                 ""

   62               );

   63

   64               db.RetainOriginalThumbnailBitmap = true;

   65

   66               int objectsPurged =

   67                 PurgeDatabase(db);

   68

   69               // Display the results

   70

   71               ed.WriteMessage(

   72                 "\nPurged {0} object{1}",

   73                 objectsPurged,

   74                 objectsPurged == 1 ? "" : "s"

   75               );

   76

   77               // Only save if we changed something

   78

   79               if (objectsPurged > 0)

   80               {

   81                 ed.WriteMessage(

   82                   "\nSaving to file: {0}", outputName

   83                 );

   84

   85                 db.SaveAs(

   86                   outputName,

   87                   DwgVersion.Current

   88                 );

   89                 saved++;

   90               }

   91               processed++;

   92             }

   93             catch (System.Exception ex)

   94             {

   95               ed.WriteMessage(

   96                 "\nProblem processing file: {0} - \"{1}\"",

   97                 fileName,

   98                 ex.Message

   99               );

  100               problem++;

  101             }

  102           }

  103         }

  104       }

  105       ed.WriteMessage(

  106         "\n\nSuccessfully processed {0} files," +

  107         " of which {1} had objects to purge" +

  108         " and an additional {2} had errors " +

  109         "during reading/processing.",

  110         processed,

  111         saved,

  112         problem

  113       );

  114     }

  115

  116     [CommandMethod("PC")]

  117     public void PurgeCurrentDocument()

  118     {

  119       Document doc =

  120         Application.DocumentManager.MdiActiveDocument;

  121       Database db = doc.Database;

  122       Editor ed = doc.Editor;

  123

  124       int count =

  125         PurgeDatabase(db);

  126

  127       ed.WriteMessage(

  128         "\nPurged {0} object{1} from " +

  129         "the current database.",

  130         count,

  131         count == 1 ? "" : "s"

  132       );

  133     }

  134

  135     private static int PurgeDatabase(Database db)

  136     {

  137       int idCount = 0;

  138

  139       Transaction tr =

  140         db.TransactionManager.StartTransaction();

  141       using (tr)

  142       {

  143         // Create the list of objects to "purge"

  144

  145         ObjectIdCollection idsToPurge =

  146           new ObjectIdCollection();

  147

  148         // Add all the Registered Application names

  149

  150         RegAppTable rat =

  151           (RegAppTable)tr.GetObject(

  152             db.RegAppTableId,

  153             OpenMode.ForRead

  154         );

  155

  156         foreach (ObjectId raId in rat)

  157         {

  158           if (raId.IsValid)

  159           {

  160             idsToPurge.Add(raId);

  161           }

  162         }

  163

  164         // Call the Purge function to filter the list

  165

  166         db.Purge(idsToPurge);

  167

  168         Document doc =

  169           Application.DocumentManager.MdiActiveDocument;

  170         Editor ed = doc.Editor;

  171

  172         ed.WriteMessage(

  173           "\nRegistered applications being purged: "

  174         );

  175

  176         // Erase each of the objects we've been

  177         // allowed to

  178

  179         foreach (ObjectId id in idsToPurge)

  180         {

  181           DBObject obj =

  182             tr.GetObject(id, OpenMode.ForWrite);

  183

  184           // Let's just add to me "debug" code

  185           // to list the registered applications

  186           // we're erasing

  187

  188           RegAppTableRecord ratr =

  189             obj as RegAppTableRecord;

  190           if (ratr != null)

  191           {

  192             ed.WriteMessage(

  193               "\"{0}\" ",

  194               ratr.Name

  195             );

  196           }

  197

  198           obj.Erase();

  199         }

  200

  201         // Return the number of objects erased

  202         // (i.e. purged)

  203

  204         idCount = idsToPurge.Count;

  205         tr.Commit();

  206       }

  207       return idCount;

  208     }

  209   }

  210 }

You can download the source file from here.

August 15, 2007 in AutoCAD, AutoCAD .NET, Batch processing, Drawing structure, Purge | Permalink | Comments (10) | TrackBack

Purging registered application names in the current AutoCAD drawing using .NET

Purging can seriously reduce the size of AutoCAD drawings by removing unnecessary symbol table and dictionary entries. The PURGE command in AutoCAD allows you to safely purge these non-graphical objects (layers, linetypes, block definitions, etc.).

Since AutoCAD 2005 (if I recall correctly), PURGE also supports the removal of Registered Application names. Applications that make use of Extended Entity Data (XData) must register unique application names in drawings. A few applications have, in the past, created many more names than they required, and as these RegApp names get copied from drawing to drawing (when they get XRefed, for instance), they ended spreading from DWG file to DWG file (one comparison I remember hearing was to a virus, although that was perhaps a little extreme). To the best of my knowledge the applications that mistakenly caused this problem have now been fixed (and shall remain nameless), but there are still drawings out there with a lot of these records, which is ultimately why we extended PURGE to address the problem from within AutoCAD.

Anyway, I've chosen to implement a command to purge these RegApp names - even though it's now there in the product - really just an example of how to code this type of functionality. It could very easily be extended to handle the specific data you feel needs purging in your company's (or customers') DWG files.

The Database.Purge() function available in .NET wraps AcDbDatabase::purge(). These functions are both misleadingly named, as neither of them actually performs a purge on the database. What they do - and this is actually more useful, as it gives you more control - is filter a list of object IDs you pass in, removing any that cannot safely be purged by your application. So they would more accurately be named TellMeWhatCanSafelyBePurged(), or something like that. Typically objects get removed from the list because a reference exists somewhere in the drawing to that object - such as from an entity to a layer (making the layer dangerous to purge). The code calling the Purge function will usually then erase the objects that have been returned. And that's how the PURGE command is implemented.

Here's some C# code that purges the Registered Application names in the current document:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using System.IO;

using System;


namespace Purger

{

  public class Commands

  {

    [CommandMethod("PC")]

    public void PurgeCurrentDocument()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;


      int count =

        PurgeDatabase(db);


      ed.WriteMessage(

        "\nPurged {0} object{1} from " +

        "the current database.",

        count,

        count == 1 ? "" : "s"

      );

    }


    private static int PurgeDatabase(Database db)

    {

      int idCount = 0;


      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        // Create the list of objects to "purge"


        ObjectIdCollection idsToPurge =

          new ObjectIdCollection();


        // Add all the Registered Application names


        RegAppTable rat =

          (RegAppTable)tr.GetObject(

            db.RegAppTableId,

            OpenMode.ForRead

        );


        foreach (ObjectId raId in rat)

        {

          if (raId.IsValid)

          {

            idsToPurge.Add(raId);

          }

        }


        // Call the Purge function to filter the list


        db.Purge(idsToPurge);


        Document doc =

          Application.DocumentManager.MdiActiveDocument;

        Editor ed = doc.Editor;


        ed.WriteMessage(

          "\nRegistered applications being purged: "

        );


        // Erase each of the objects we've been

        // allowed to


        foreach (ObjectId id in idsToPurge)

        {

          DBObject obj =

            tr.GetObject(id, OpenMode.ForWrite);


          // Let's just add to me "debug" code

          // to list the registered applications

          // we're erasing


          RegAppTableRecord ratr =

            obj as RegAppTableRecord;

          if (ratr != null)

          {

            ed.WriteMessage(

              "\"{0}\" ",

              ratr.Name

            );

          }


          obj.Erase();

        }


        // Return the number of objects erased

        // (i.e. purged)


        idCount = idsToPurge.Count;

        tr.Commit();

      }

      return idCount;

    }

  }

}

Here's what happens when you run the PC command:

Command: PC

Registered applications being purged: "AcadAnnoPO" "PE_URL" "AcadAnnoAV"

"ACAD_EXEMPT_FROM_CAD_STANDARDS"

Purged 4 objects from the current database.

In the next post we'll look at extending this to - once again - work on a folder of drawings.

August 13, 2007 in AutoCAD, AutoCAD .NET, Drawing structure, Purge | Permalink | Comments (4) | TrackBack