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:
- Implement a command to collect RegAppId information for the active document
- Extend this command to work on a drawing not loaded in the editor
- Save our RegAppId information to some persistent location (XML?)
- 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.