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.

Subscribe via RSS
Hi Kean,
Is there any chance that you could show how to open drawings from a form to execute custom routines on them using .NET. I have been trying to find a solution for some time without any success. Could you create a simple example that would at least show how to switch the contexts, application & document? There have been a few others posts on the user group, but not a good solution. May be something that for you could be so simple, will help a lot of people.
Posted by: HJohn | August 16, 2007 at 07:13 PM
Hi,
The best approach is to separate the drawing walkthrough routine from the database operation itself.
You may structure some kind of custom action that will be performed at each drawing. Then from a simple interface you can enable those custom actions and then perform those ones inside each drawing inside the loop.
Regards.
Posted by: Fernando Malard | August 16, 2007 at 08:45 PM
Hi Kean.
It might be helpful to inform your readers that batch operations on DWG files also has the possibly-unintended side-effect of 'upgrading' those files from whatever release version they were saved in, to the current release version.
Some may not realize that doing this with drawings produced saved by older versions of vertical products like ADT or Civil3D, can lead to utter catastrophy if they haven't backed up everything first.
Posted by: Tony Tanzillo | August 19, 2007 at 03:17 AM
Hi Tony,
In the examples I've shown I've saved to a different filename, but yes - if you need to maintain the original file version then a little more code is required (I'll look at posting something about this).
Thanks,
Kean
Posted by: Kean | August 20, 2007 at 11:44 AM
The change seems to be simpler than I expected...
db.SaveAs(
outputName,
db.LastSavedAsVersion // instead of
// DwgVersion.Current
);
Kean
Posted by: Kean | August 21, 2007 at 03:59 PM
Dear Kean,
thank you for this sample code.
Do you know if it is possible to get the variable "ANNOTATIVEDWG" for each of the db with your code? I just want to make drawings in a directory annotative but could not find anything in Database which can set it annotative.
Regards,
Roland
Posted by: Roland Feletic | August 22, 2007 at 11:04 AM
Dear Roland,
It looks as Database doesn't have this property exposed in AutoCAD 2008, so until it's fixed (hopefully in the next release) you'll need to set it using another approach.
If you're in AutoCAD (i.e. not RealDWG) then you should be able to just call Application.SetSystemVariable() to set it.
You'll need to make sure you set HostApplicationServices.WorkingDatabase = db;
and then set it back to the previous value before the SaveAs (at least that's what I noticed when I just tried this out).
Regards,
Kean
Oh, and I also noticed that I did need a little more code for the version maintenance, after all:
DwgVersion ver =
(db.LastSavedAsVersion == DwgVersion.MC0To0 ?
DwgVersion.Current :
db.LastSavedAsVersion
);
db.SaveAs(
outputName,
ver
);
Posted by: Kean | August 22, 2007 at 12:02 PM
Thank you, Kean.
I hope this will be fixed in the next version.
Regards,
Roland
Posted by: Roland Feletic | August 22, 2007 at 02:33 PM
Hi Kean.
Thanks for giving that some attention.
I'm not sure about this because I haven't tested it, but I'm pretty sure that AutoCAD verticals upgrade custom objects to the current release when the drawing saved in the earlier release is opened.
I'm not sure that saving down to the previous AutoCAD DWG version will address that particular problem. I suppose that's because custom objects implemented by the various verticals do not seem to support saving themsleves to older versions, in the same way that AutoCAD does for native database objects.
Posted by: Tony Tanzillo | August 27, 2007 at 11:47 PM
Hi Tony,
It's quite possible that one or more of our AutoCAD-based verticals do this (I haven't looked into it, myself).
Ultimately it comes down to a number of implementation decisions... (which I’m going to talk through more for the benefit of people implementing their own custom objects, not to make excuses for what we do in our products.)
Providers of custom objects could choose to link the versions of their own objects to a specific DWG version (by querying AcDbDwgFiler::dwgVersion() in their dwgOutFields() implementation), but that can be very limiting as one may want to up the version in between format changes, which do not happen every release. It also raises problems of how best to dumb down objects to previous versions. With recently-upgraded (and unchanged) objects it's OK, but you'd still have to find a way to tag the data that originally defined the object on drawing load (and make sure any changes made to the object were handled gracefully when stored back to the old format).
Then there's round-tripping: how to store the additional data with the old versions of the objects, allowing them to resurrect their behaviour when they come back into the newer release of the product. These problems are also there when custom object implementers support saveAs(), of course.
An alternative would be to provide object enablers for previous versions that support newer versions of the objects, but that might also be a substantial effort and blur the line of what functionality is delivered in which release (creating strange behaviour as old products get partially upgraded feature sets).
Anyway – all this to say that yes, I can understand how this happens from an implementation perspective. I don’t have any magic solutions, however – it’s an inherently difficult problem.
Cheers,
Kean
Posted by: Kean | August 28, 2007 at 10:29 AM
Hi Kean,
I was wondering if there's an easy way to modify the objects to purge. For example, if a particular text style was included in the drawing that I did not want to be purged. Can this easily be done?
Thanks Mike
Posted by: Mike | July 11, 2009 at 04:17 AM
Hi Mike,
There are a couple of ways:
You can maintain your own list of objects "to keep" and remove any items that are on this list from idsToPurge before you erase them.
Or you can create an object that's owned at some level by the Database (an Xrecord placed inside the Named Objects Dictionary should do it) that contains "hard" references to the objects you wish to keep (which means using DxfCode.HardPointerId - or one of the indeces just following it - when creating your TypedValues). This has the advantage of also stopping the standard PURGE command from removing those objects.
Cheers,
Kean
Posted by: Kean Walmsley | July 11, 2009 at 07:38 AM