Kean Walmsley

July 2009

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Twitter Updates

    follow me on Twitter



    « Purging registered application names in the current AutoCAD drawing using .NET | Main | CommandComplete bonus tool »

    August 15, 2007

    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.

    TrackBack

    TrackBack URL for this entry:
    http://www.typepad.com/services/trackback/6a00d83452464869e200e54edf01e88834

    Listed below are links to weblogs that reference Purging registered application names from a folder of AutoCAD drawings using .NET:

    Comments

    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.

    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.

    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.

    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

    The change seems to be simpler than I expected...

    db.SaveAs(
    outputName,
    db.LastSavedAsVersion // instead of
    // DwgVersion.Current
    );

    Kean

    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

    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
    );

    Thank you, Kean.
    I hope this will be fixed in the next version.

    Regards,
    Roland

    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.

    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

    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

    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

    Verify your Comment

    Previewing your Comment

    This is only a preview. Your comment has not yet been posted.

    Working...
    Your comment could not be posted. Error type:
    Your comment has been posted. Post another comment

    The letters and numbers you entered did not match the image. Please try again.

    As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

    Having trouble reading this image? View an alternate.

    Working...

    Post a comment

    Feed & Share

    Search