Counting the RegAppIds for the active AutoCAD drawing and its external references using .NET

Many of you will be aware of what has been referred to as the "RegAppId virus": drawings that have been unfortunately polluted with excessive Registered Application IDs – which are used by applications to attach XData to entities – have these RegAppIds brought into a main drawing when Xrefed, duplicating and duplicating IDs that were often redundant in the first place. In fact – back in the day – I remember a very popular 3rd party application (which has long since been fixed and shall remain nameless) that erroneously used to create hundreds (if not thousands) of these IDs in each drawing it touched.

Back in AutoCAD 2005 (if I recall correctly) we extended the PURGE command to deal with RegAppIds, which was a partial solution to the problem. We also released a standalone RegAppId cleanup tool, which can help, too. Some developers have more specific requirements regarding RegAppId cleanup, especially when it comes to identifying the source of RegAppIds in a "polluted" drawing set.

This week we're going to look at one potential way to help identify these problematic files. Part of the issue is that when working with many such drawings, some may be corrupted, etc. We also want to avoid having each drawing to be loaded in the AutoCAD editor, as that will cause the identification process to work more slowly.

We do, however, still want to use AutoCAD to execute this code, even on drawings that are not editor-resident: we don't want to have to use RealDWG to build a tool.

I see the solution developing in steps that we'll see in a number of (hopefully consecutive) blog posts:

  1. Implement a command to collect RegAppId information for the active document
  2. Extend this command to work on a drawing not loaded in the editor
  3. Save our RegAppId information to some persistent location (XML?)
  4. Create a modified version of ScriptPro 2.0 (one of our Plugins of the Month) to call our command without opening the drawing

Step 1 is addressed in today's post – we'll hopefully manage to complete the other steps in the following ones. I haven't yet decided whether we should tackle the actual purge in this series of posts, or whether to keep it to identification, only. We'll see how things go.

Here's our initial command implementation in C#:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using System.Collections.Generic;

using System.IO;

using System;

 

namespace XrefRegApps

{

  public class Commands

  {

    [CommandMethod("XRA")]

    public void ExternalReferenceRegisteredApps()

    {

      Document doc =

          Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Get the XrefGraph for the active document's database

 

      XrefGraph xg = db.GetHostDwgXrefGraph(true);

 

      // Loop through the nodes in the graph

 

      for (int i = 0; i < xg.NumNodes; i++)

      {

        // Get each node and check its status

 

        XrefGraphNode xgn = xg.GetXrefNode(i);

 

        switch (xgn.XrefStatus)

    
    {

          // If Un*, print a message

 

          case XrefStatus.Unresolved:

            ed.WriteMessage(

              "\nUnresolved xref \"{0}\"", xgn.Name

            );

            break;

          case XrefStatus.Unloaded:

            ed.WriteMessage(

              "\nUnloaded xref \"{0}\"", xgn.Name

            );

            break;

          case XrefStatus.Unreferenced:

            ed.WriteMessage(

              "\nUnreferenced xref \"{0}\"", xgn.Name

            );

            break;

          case XrefStatus.Resolved:

          {

            // If Resolved, get the RegAppTable

 

            Database xdb = xgn.Database;

            if (xdb != null)

            {

              Transaction tr =

                xdb.TransactionManager.StartTransaction();

              using (tr)

              {

                RegAppTable rat =

                  (RegAppTable)tr.GetObject(

                    xdb.RegAppTableId,

                    OpenMode.ForRead

                  );

 

                // Collect the contained RegAppTableRecord names

                // (i.e. the RegAppIds) in a list

 

                List<String> ratIds = new List<string>();

                foreach (ObjectId id in rat)

                {

                  RegAppTableRecord ratr =

                    (RegAppTableRecord)tr.GetObject(

                      id,

                      OpenMode.ForRead

                    );

                  ratIds.Add(ratr.Name);

                }

 

                // Print the drawing information

                // and the RegAppId count

 

                ed.WriteMessage(

                  "\nDrawing \"{0}\" (\"{1}\") with {2} RegAppIds",

                  xgn.Name,

                  xdb.Filename,

                  ratIds.Count

                );

 

                // Even if only reading, commit the transaction

                // (it's cheaper than aborting)

 

                tr.Commit();

              }

            }

            break;

          }

        }

      }

    }

  }

}

If we load the "Sample/Sheet Sets/Civil/Master Site Plan.dwg" file that ships with AutoCAD into the editor and run our XRA command, we should see the following output:

Command: XRA

Drawing "Master Site Plan" ("C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Civil\Master Site Plan.dwg") with 18 RegAppIds

Drawing "PB-BASE" ("C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Civil\PB-BASE.dwg") with 7 RegAppIds

Drawing "PB-EX41" ("C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Civil\PB-EX41.dwg") with 6 RegAppIds

Drawing "PB-EX61" ("C:\Program Files\Autodesk\AutoCAD 2011\Sample\Sheet Sets\Civil\PB-EX61.dwg") with 5 RegAppIds

If we want to find out the details of the specific RegAppIds – which we will eventually be writing to some kind of XML document, I expect – we can add a few lines of code after our existing WriteMessage() call:

ratIds.ForEach(

  delegate(string name) { ed.WriteMessage(" " + name); }

);

This will use WriteMessage() to print each RegAppId string – separated by spaces – to the command-line.

This is likely to be common issue that many of you will already have addressed in some way. Feel free to post a comment should you have any input. I have no doubt that other (and quite possibly better) solutions exist, but it seemed to me that this approach would be of more general interest to people wanting to perform some kind of batch processing operation without having to have drawings loaded in the AutoCAD editor.

5 responses to “Counting the RegAppIds for the active AutoCAD drawing and its external references using .NET”

  1. Another item that will be needed, and seems to fit nicely within the topic of gathering info on unopene drawings, is that of gathering xref paths.
    When we modify scriptpro, we will have to add a column to list the app ids count.
    We need that, because the next step will be selecting those "dirty" files, and picking a button that says "add xrefs for selected files to the list".
    So we need the ability to gather the xref paths, verify the files exist, and add them to the grid.
    Then we run that again on the dirty files that iteration added, until no dirty files get added.
    You also would want to have the cleaning process run via backgroundworker, so we could reliably show and cancel its progress.
    Some other features that have proven very useful are:
    1) allow backup of files to a folder before purge
    2) add files via a text file list of paths
    3) select items as "to do" based on some app id count
    4) button to copy selected items path to the clipboard
    5) button to copy selected files to a folder
    6) option to preserve file date modified and attributes, so purging does not cause backup systems to back up tons of files if not desired.
    thanks

  2. The code I've prototyped for the XML portion does gather the path (via the Filename property of the xdb variable).

    So you'll be able to implement your own logic for purging individual files (which may or may not be addressed by this series of posts).

    How you launch the purge process is up to you, but as it'll be run by a seperate process (acad.exe) there's not really any benefit from using a background worker, as far as I can tell.

    As mentioned, the main focus of this series is on the reporting side of things: all the items you've mentioned on the purge side of things are altogether possible (as I'm sure you know) and will likely be left as an exercise for the reader.

    Kean

  3. oh, you did gather xref paths, I need to read the code 🙂
    I forgot the scriptpro code was available too, so now i see why you picked it as a starting point. the only issue with it is I am totally green on wpf, and am just getting down the patterns needed to do databinding.
    thx

  4. Hi Kean,

    can you please tell me, How do i embed the raster image inside the drawing?

    Thanks,
    Muthu
    Incontrol Tech, Malaysia.

  5. Hi Muthu,

    This isn't a forum for support.

    Your comment doesn't appear to relate to this post, so please submit your question to the ADN team, if you're a member, or otherwise the AutoCAD .NET Discussion Group.

    Regards,

    Kean

Leave a Reply

Your email address will not be published. Required fields are marked *