Kean Walmsley


  • About the Author
    Kean on Google+

April 2014

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      







« Updating a specific attribute inside an AutoCAD drawing using .NET | Main | YASA (Yet Another Simpsons Avatar) »

July 25, 2007

Updating a specific attribute inside a folder of AutoCAD drawings using .NET

In the last post we looked at some code to search the current drawing for a particular attribute and update its value. In this post - as promised - we're going to look at how to extend this application to work on a folder of drawings, updating those that contain the attribute and saving them to a new filename.

Rather than implement a fancy, graphical user interface, I've stuck with my approach of using the command-line for input and output. If you wish to implement your own UI, please do - it's really easy using .NET. I get the occasional request to do this myself, but I prefer to keep my posts focused - there are other posts on this blog that focus specifically on UI-related issues.

I ended up changing the code somewhat - I had been printing the results of the batching process from within the UpdateAttributesInDatabase() function. As I get closer to extracting the functionality to a RealDWG application, I've started reducing the dependencies on the Editor (as this will not be available later on). The WriteMessage() calls are now in the commands themselves, instead of in the processing functions.

In addition to the previous UA (UpdateAttribute) command, I've added a new one called UAIF (UpdateAttributeInFiles). This one queries for some additional data (such as the path to the folder to process), and then uses some handy .NET Framework functionality to iterate through the drawings stored in a particular location. Rather than overwrite the originals, the updated files get saved to a new name (with "-updated" added to the filename). It is left as an exercise to put these in a separate folder or to save back to the original location (I'd rather not get blamed for overwriting valuable data :-).

Here's the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using System.IO;

using System;


namespace AttributeUpdater

{

  public class Commands

  {

    [CommandMethod("UAIF")]

    public void UpdateAttributeInFiles()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;


      // Have the user choose the block and attribute

      // names, and the new attribute value


      PromptResult pr =

        ed.GetString(

          "\nEnter folder containing DWGs to process: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string pathName = pr.StringResult;


      pr =

        ed.GetString(

          "\nEnter name of block to search for: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string blockName = pr.StringResult.ToUpper();


      pr =

        ed.GetString(

          "\nEnter tag of attribute to update: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string attbName = pr.StringResult.ToUpper();


      pr =

        ed.GetString(

          "\nEnter new value for attribute: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string attbValue = pr.StringResult;


      string[] fileNames =

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


      // We'll use some counters to keep track

      // of how the processing is going


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


      foreach (string fileName in fileNames)

      {

        if (fileName.EndsWith(

              ".dwg",

              StringComparison.CurrentCultureIgnoreCase

            )

        )

        {

          string outputName =

            fileName.Substring(

              0,

              fileName.Length - 4) +

            "_updated.dwg";

          Database db = new Database(false, false);

          using (db)

          {

            try

            {

              ed.WriteMessage(

                "\n\nProcessing file: " + fileName

              );


              db.ReadDwgFile(

                fileName,

                FileShare.ReadWrite,

                false,

                ""

              );

              int attributesChanged =

                UpdateAttributesInDatabase(

                  db,

                  blockName,

                  attbName,

                  attbValue

                );


              // Display the results


              ed.WriteMessage(

                "\nUpdated {0} instance{1} of " +

                "attribute {2}.",

                attributesChanged,

                attributesChanged == 1 ? "" : "s",

                attbName

              );


              // Only save if we changed something


              if (attributesChanged > 0)

              {

                ed.WriteMessage(

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

                );


                db.SaveAs(

                  outputName,

                  DwgVersion.Current

                );

                saved++;

              }

              processed++;

            }

            catch (System.Exception ex)

            {

              ed.WriteMessage(

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

                fileName,

                ex.Message

              );

              problem++;

            }

          }

        }

      }

      ed.WriteMessage(

        "\n\nSuccessfully processed {0} files, of which {1} had " +

        "attributes to update and an additional {2} had errors " +

        "during reading/processing.",

        processed,

        saved,

        problem

      );

    }


    [CommandMethod("UA")]

    public void UpdateAttribute()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;


      // Have the user choose the block and attribute

      // names, and the new attribute value


      PromptResult pr =

        ed.GetString(

          "\nEnter name of block to search for: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string blockName = pr.StringResult.ToUpper();


      pr =

        ed.GetString(

          "\nEnter tag of attribute to update: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string attbName = pr.StringResult.ToUpper();


      pr =

        ed.GetString(

          "\nEnter new value for attribute: "

        );

      if (pr.Status != PromptStatus.OK)

        return;

      string attbValue = pr.StringResult;


      ed.WriteMessage(

        "\nProcessing file: " + db.Filename

      );


      int count =

        UpdateAttributesInDatabase(

          db,

          blockName,

          attbName,

          attbValue

        );


      ed.Regen();


      // Display the results


      ed.WriteMessage(

        "\nUpdated {0} instance{1} of " +

        "attribute {2}.",

        count,

        count == 1 ? "" : "s",

        attbName

      );

    }


    private int UpdateAttributesInDatabase(

      Database db,

      string blockName,

      string attbName,

      string attbValue

    )

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;


      // Get the IDs of the spaces we want to process

      // and simply call a function to process each


      ObjectId msId, psId;

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        msId =

          bt[BlockTableRecord.ModelSpace];

        psId =

          bt[BlockTableRecord.PaperSpace];


        // Not needed, but quicker than aborting

        tr.Commit();

      }

      int msCount =

        UpdateAttributesInBlock(

          msId,

          blockName,

          attbName,

          attbValue

        );

      int psCount =

        UpdateAttributesInBlock(

          psId,

          blockName,

          attbName,

          attbValue

        );

      return msCount + psCount;

    }


    private int UpdateAttributesInBlock(

      ObjectId btrId,

      string blockName,

      string attbName,

      string attbValue

    )

    {

      // Will return the number of attributes modified


      int changedCount = 0;

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;


      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            btrId,

            OpenMode.ForRead

          );


        // Test each entity in the container...


        foreach (ObjectId entId in btr)

        {

          Entity ent =

            tr.GetObject(entId, OpenMode.ForRead)

            as Entity;


          if (ent != null)

          {

            BlockReference br = ent as BlockReference;

            if (br != null)

            {

              BlockTableRecord bd =

                (BlockTableRecord)tr.GetObject(

                  br.BlockTableRecord,

                  OpenMode.ForRead

              );


              // ... to see whether it's a block with

              // the name we're after


              if (bd.Name.ToUpper() == blockName)

              {


                // Check each of the attributes...


                foreach (

                  ObjectId arId in br.AttributeCollection

                )

                {

                  DBObject obj =

                    tr.GetObject(

                      arId,

                      OpenMode.ForRead

                    );

                  AttributeReference ar =

                    obj as AttributeReference;

                  if (ar != null)

                  {

                    // ... to see whether it has

                    // the tag we're after


                    if (ar.Tag.ToUpper() == attbName)

                    {

                      // If so, update the value

                      // and increment the counter


                      ar.UpgradeOpen();

                      ar.TextString = attbValue;

                      ar.DowngradeOpen();

                      changedCount++;

                    }

                  }

                }

              }


              // Recurse for nested blocks

              changedCount +=

                UpdateAttributesInBlock(

                  br.BlockTableRecord,

                  blockName,

                  attbName,

                  attbValue

                );

            }

          }

        }

        tr.Commit();

      }

      return changedCount;

    }

  }

}

Here's what happens when I run it on my temp folder (which is full of crud, and that's putting it politely):

Command: UAIF

Enter folder containing DWGs to process: c:\temp

Enter name of block to search for: TEST

Enter tag of attribute to update: ONE

Enter new value for attribute: 1234



Processing file: c:\temp\-old_recover.dwg

Problem processing file: c:\temp\-old_recover.dwg - "eBadDwgHeader"



Processing file: c:\temp\4076612-2.DWG

Updated 0 instances of attribute ONE.



Processing file: c:\temp\attributes.dwg

Updated 5 instances of attribute ONE.

Saving to file: c:\temp\attributes_updated.dwg



[Deleted a lot of uninteresting reports]



Successfully processed 42 files, of which 1 had attributes to update and an

additional 3 had errors during reading/processing.

You'll see that some DWGs have failed to load: hopefully because they're in need of recovery rather than the workings of this code. The above technique should handle this gracefully, allowing you to go back and fix the problematic ones.

Update 1:

Norman Yuan pointed out a mistake in this code - I've been using the TransactionManager from the current document, rather than from the db. This isn't dramatically bad, but would have become more of a problem as we move to RealDWG. What I should really have done sooner is remove the doc and ed variables from the UpdateAttributesIn...() functions, so that I would not have been in a position to use them by mistake. I now pass in the Database as a parameter, which allows us to use it to get the appropriate TransactionManager. One other benefit this code enables is the choice of constructing the database without an associated document:

Database db = new Database(false, true);

This makes the code work more efficiently (as well as being cleaner).

Here are the updated functions - not the entire code:

    private int UpdateAttributesInDatabase(

      Database db,

      string blockName,

      string attbName,

      string attbValue

    )

    {

      // Get the IDs of the spaces we want to process

      // and simply call a function to process each


      ObjectId msId, psId;

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTable bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId,

            OpenMode.ForRead

          );

        msId =

          bt[BlockTableRecord.ModelSpace];

        psId =

          bt[BlockTableRecord.PaperSpace];


        // Not needed, but quicker than aborting

        tr.Commit();

      }

      int msCount =

        UpdateAttributesInBlock(

          db,

          msId,

          blockName,

          attbName,

          attbValue

        );

      int psCount =

        UpdateAttributesInBlock(

          db,

          psId,

          blockName,

          attbName,

          attbValue

        );

      return msCount + psCount;

    }


    private int UpdateAttributesInBlock(

      Database db,

      ObjectId btrId,

      string blockName,

      string attbName,

      string attbValue

    )

    {

      // Will return the number of attributes modified


      int changedCount = 0;


      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            btrId,

            OpenMode.ForRead

          );


        // Test each entity in the container...


        foreach (ObjectId entId in btr)

        {

          Entity ent =

            tr.GetObject(entId, OpenMode.ForRead)

            as Entity;


          if (ent != null)

          {

            BlockReference br = ent as BlockReference;

            if (br != null)

            {

              BlockTableRecord bd =

                (BlockTableRecord)tr.GetObject(

                  br.BlockTableRecord,

                  OpenMode.ForRead

              );


              // ... to see whether it's a block with

              // the name we're after


              if (bd.Name.ToUpper() == blockName)

              {


                // Check each of the attributes...


                foreach (

                  ObjectId arId in br.AttributeCollection

                )

                {

                  DBObject obj =

                    tr.GetObject(

                      arId,

                      OpenMode.ForRead

                    );

                  AttributeReference ar =

                    obj as AttributeReference;

                  if (ar != null)

                  {

                    // ... to see whether it has

                    // the tag we're after


                    if (ar.Tag.ToUpper() == attbName)

                    {

                      // If so, update the value

                      // and increment the counter


                      ar.UpgradeOpen();

                      ar.TextString = attbValue;

                      ar.DowngradeOpen();

                      changedCount++;

                    }

                  }

                }

              }


              // Recurse for nested blocks

              changedCount +=

                UpdateAttributesInBlock(

                  db,

                  br.BlockTableRecord,

                  blockName,

                  attbName,

                  attbValue

                );

            }

          }

        }

        tr.Commit();

      }

      return changedCount;

    }

  }

Update 2:

The above code does not realign attributes after editing their values: if your attributes are anything other than left-justified, you will need to make a call to AdjustAlignment on the attribute reference after editing it.

There's a trick to this: you need to make sure the working database is set to the drawing you're working on, as well as passing it as an argument to the function.

You could set the working database early in the code, or insert this code to do it locally (the choice is yours):

  ar.TextString = attbValue;

  // Begin alignment code

  Database wdb = HostApplicationServices.WorkingDatabase;

  HostApplicationServices.WorkingDatabase = db;

  ar.AdjustAlignment(db);

  HostApplicationServices.WorkingDatabase = wdb;

  // End alignment code

  ar.DowngradeOpen();

I've left the line before and the line after in the above snippet, so it should be clear where the code needs inserting.

TrackBack

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

Listed below are links to weblogs that reference Updating a specific attribute inside a folder of AutoCAD drawings using .NET:

blog comments powered by Disqus

10 Random Posts