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



    « Coming in September: Autodesk DevCamps - Customization and Application Development Conferences | Main | Updating a specific attribute inside a folder of AutoCAD drawings using .NET »

    July 23, 2007

    Updating a specific attribute inside an AutoCAD drawing using .NET

    This suggestion came up in reference to this previous post about using side databases. The request is to be able to open a number of DWG files and modify a particular attribute, saving the files back.

    Rather than jumping in and solving both problems in one post, we'll start today with the problem of updating the attribute and then in the next post we'll look at some code we can use to process a folder of DWGs, opening, updating and saving each one. I'll probably then go one step further and look at the steps needed to extract this code and make it work in RealDWG (for which you will need to have the toolkit installed, of course).

    This is actually the classic way to develop RealDWG applications - work on the code inside AutoCAD, relying only on classes that are available in RealDWG, such as Database - and, once tested, build the code into a RealDWG application. AutoCAD is the perfect UI and test harness for RealDWG code. :-)

    To solve the problem of updating a particular attribute in a drawing, I started by copying a bunch of code from this post, as it handled the recursive iteration of blocks. I created a primary function (UpdateAttributesInDatabase()) that takes a Database parameter - as this will facilitate the future calling of the code on a number of drawings - and added some code to prompt the user for the name of the block and attribute to modify, and of course the new value to which we will set the attribute.

    Here's the C# code:

    using Autodesk.AutoCAD.ApplicationServices;

    using Autodesk.AutoCAD.DatabaseServices;

    using Autodesk.AutoCAD.EditorInput;

    using Autodesk.AutoCAD.Runtime;


    namespace BlockIterator

    {

      public class Commands

      {

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


          UpdateAttributesInDatabase(

            db,

            blockName,

            attbName,

            attbValue

          );

        }


        private void 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

            );

          ed.Regen();


          // Display the results


          ed.WriteMessage(

            "\nProcessing file: " + db.Filename

          );

          ed.WriteMessage(

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

            "attribute {2} in the modelspace.",

            msCount,

            msCount == 1 ? "" : "s",

            attbName

          );

          ed.WriteMessage(

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

            "attribute {2} in the default paperspace.",

            psCount,

            psCount == 1 ? "" : "s",

            attbName

          );

        }


        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;

        }

      }

    }

    I tested this code by adding a block called TEST to the modelspace and to the primary layout, both at the top-level and nested within other blocks. This block contained three attributes, imaginatively named "ONE", "TWO" and "THREE". Here's the command-line output when I ran the code on this drawing, selecting the block TEST and changing attribute ONE to the value of 100:

    Command: UA

    Enter name of block to search for: TEST

    Enter tag of attribute to update: ONE

    Enter new value for attribute: 100

    Regenerating model.

    Processing file: C:\temp\attributes.dwg

    Updated 3 instances of attribute ONE in the modelspace.

    Updated 2 instances of attribute ONE in the default paperspace.

    Next time we'll look at how to run this code on multiple DWG files without loading them into the drawing editor.

    TrackBack

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

    Listed below are links to weblogs that reference Updating a specific attribute inside an AutoCAD drawing using .NET:

    Comments

    Hi Kean,
    Thanks alot for your code. It has been quite helpful for beginners like us. Could you please show us how to precheck whether a block contains attributes inside. I mean something like HasAttributes property in VBA. Also I would like to see how block attributes can be updated from an external database( eg. MS Access). I have already done it using VBA. But haven't seen anything on C#. I hope that's not too much to ask for.

    Thanks alot for your time.

    Hi Mohamed,

    Actually there's really no need for a pre-check: if the block doesn't have attributes then the AttributeCollection property will be empty, so foreach() will do nothing. Getting the AttributeCollection should not be significantly more expensive than checking a boolean flag (if there were one).

    Right now I'm not planning to look at code to access external databases - there should be plenty of available code on the web that can be integrated with the AutoCAD-centric code I provide. It may be something I come back to, but I'd rather focus on the Autodesk side of things, for now.

    Regards,

    Kean

    Hi Kean
    Thank you for your great attribute modification sample, but what made may day was reading ..

    "the next post we'll look at some code we can use to process a folder of DWGs, opening, updating and saving each one"

    I am stuck on just that problem today, I have been using VBA for some time now, so I thought that opening drawings on .NET was just matter of calling DocumenCollection/DocumentManager Open method, but I can't make it work. I have done a lot of searching and have found little help so far, so if you will post a working solution it will be fantastic.

    Hi Kean,

    Your posts are very helpful. Thanks.

    I have a question on the code of this post. Specifically, it is about the method Upate AttributesInBlock(ObjectID, string, string, string).

    In this method, an ObjectID from the side database is passed in, along with block name, attribute name and intended attribute value. Naturally, I'd think we are to search this ObjectId in the side database. However, in your code, you use MdiDocument's TransactionManager to get the BlockReference correspoding to the passed OBjectID.

    Here is what puzzles me: in current Acad application session, there are at least two databases existing: the one with current MDIDocument and the side one, which is the code is targeting. Shouldn't we use the TransactionManager of the side database? Or the Documnt.TransactionManager searches not only the database of MdiDocument, but also all loaded side databases?

    My understanding until now is Document.TransactionManager is the same as Document.Database.TransactionManager. Now I doubt if my understanding is wrong.

    Hi Norman,

    You're absolutely right - it was an unintentional slip from my side. What I should have done right away is remove the variables doc & ed (as they're not relevant in this context).

    Ultimately the code will function correctly, as the transaction manager is really the same between the two objects (hence my laziness). But to make the code easier to reuse in RealDWG, I should have caught that mistake.

    One other effect - this change also allows the use of the Database(false,true) constructor, which does not associate a document with the constructed databases (which is more efficient).

    I'll post the updated code in an update to the next post in the series.

    Regards,

    Kean

    Kean,
    This is probably a newbie question, but is there any other way to open more than one dwg and read the attributes than using RealDWG? I've doen it in VBA using FileDialog, but it's slow and I'm wanting to upgrade it to C#. Is RealDWG necessary to open and read database objects for separate dwg files (other than the one currently open)? I'm not sure my company would pay for another license just for that.
    Thanks,
    John

    John,

    Check out the next post in the series: Updating a specific attribute inside a folder of AutoCAD drawings using .NET.

    This uses the same technique as RealDWG to open drawings in the background, but inside AutoCAD.

    Regards,

    Kean

    Hi,

    How to connect autocad to dot net.
    Could you please ket me know how can I do chage 30 drawings files (Updating a specific attribute inside an AutoCAD drawing using .NET - published topics July 23, 2007) at a time with next advance method.
    thanks
    Regards waki

    waki,

    Sorry - this request isn't clear. If it isn't a problem with the code on this blog, please address it to one of the Autodesk discussion groups.

    Kean

    Hi Kean,

    While inserting a block with attributes using AttributeDefinition i get some error,
    I would appreciate if you could help me with a simple example with my above problem..

    Santosh V

    Hi Santosh,

    This post may be of help. Otherwise please post your code to the AutoCAD .NET Discussion Group (or to ADN, if you're a member).

    Regards,

    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