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.

41 responses to “Updating a specific attribute inside an AutoCAD drawing using .NET”

  1. 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.

  2. Kean Walmsley Avatar

    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

  3. 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.

  4. 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.

  5. 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

  6. 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

  7. 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

  8. 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

  9. 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

  10. 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

  11. Kean Walmsley Avatar

    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

  12. Hı Kean this is not about this topic but how con we add menu bar and toolbar to run our application in C#

  13. Hi alyoşa,

    I unfortunately don't have the time to answer questions that are not specifically related to my posts. Please post such questions to the ADN team, if you're a member, or via the AutoCAD .NET Discussion Group.

    Regards,

    Kean

  14. Kean, this post has been very helpful. I am struggling with one part, though. I have a form that calls your UpdateAttributesInDatabase() and UpdateAttributesInBlock() exactly as you have written them.

    When calling UpdateAttributesInDatabase(), the line "ar.UpgradeOpen();" caused AutoCAD to give this error:

    AutoCAD Error Aborting
    FATAL ERROR: Unhandled e0434f4dh Exception at 7c812a6bh

    This only seems to occur when calling UpdateAttributesInDatabase() from a button click. If I call the method in the form constructor or the form Load() event, everything works fine. Can you help? I've tried everything I could think of. Thanks, you do great work.

  15. I suspect you've implemented a modeless dialog and have forgotten to lock the document...

    If you're modeless, the best thing to do is to launch a command via Document.SendStringToExecute(). That will resolve all sorts of subtle locking & communication issues.

    Kean

  16. I I try to open a file with Proxy Graphics in it while 3rd party prgram is loaded I get a eWasErased Error. But if I run thru normal Vanilla AutoCAD everything work fine. Why. Error occurs when reading Database from File? Anyone got any ideas as to why? Any help greatly appreciated,

  17. I suggest reporting this issue to the provider of the 3rd party application.

    Kean

  18. How do you deal with the multiple sheets

  19. If you mean multiple layouts, just iterate through them (the above just looks at modelspace and the initial paperspace layout).

    Kean

  20. Sumedh Karandikar Avatar
    Sumedh Karandikar

    <commandmethod("runtest")> _
    Public Sub Test()

    Dim dm As DocumentCollection = Application.DocumentManager

    Dim ed As Editor = dm.MdiActiveDocument.Editor

    Dim destDb As Database = dm.MdiActiveDocument.Database

    Dim sourceDb As New Database(False, True)

    Dim sourceFileName As PromptResult

    Try
    ' Get name of DWG from which to copy blocks
    sourceFileName = ed.GetString(vbLf & "Enter the name of the source drawing: ")

    ' Read the DWG into a side database
    sourceDb.ReadDwgFile(sourceFileName.StringResult, System.IO.FileShare.Read, True, "")

    ' Create a variable to store the list of block identifiers
    Dim blockIds As New ObjectIdCollection()
    Dim tm As Autodesk.AutoCAD.DatabaseServices.TransactionManager = sourceDb.TransactionManager
    Using myT As Transaction = tm.StartTransaction()

    ' Open the block table
    Dim bt As BlockTable = DirectCast(tm.GetObject(sourceDb.BlockTableId, OpenMode.ForRead, False), BlockTable)
    ' Check each block in the block table
    For Each btrId As ObjectId In bt
    ''''''''''''''
    ' Dim br As New BlockReference(New Point3d(0, 0, 0), id)
    'Dim br As New BlockReference(New Point3d(0, 0, 0), btrId)

    '''''''''''''''''
    Dim btr As BlockTableRecord = DirectCast(tm.GetObject(btrId, OpenMode.ForWrite, False), BlockTableRecord)
    If btr.HasAttributeDefinitions Then
    '''''''''''''
    Dim br As New BlockReference(New Point3d(), btr.ObjectId)
    '''''''''''''''
    For Each chkObjID As ObjectId In btr

    Dim chkObj As Entity = tm.GetObject(chkObjID, OpenMode.ForWrite)
    If TypeOf chkObj Is AttributeDefinition Then
    Dim anAttrib As AttributeDefinition = chkObj
    If chkObj.BlockName = "REFERENCE DRAWINGS" Then
    Dim str As String = (" \n Attribute Tag: " + anAttrib.Tag)
    ed.WriteMessage(str)

    End If

    If anAttrib.Tag = "REFDWG1" Then
    anAttrib.UpgradeOpen()
    anAttrib.TextString = "changged-101"
    'btr.AppendEntity(ar)

    End If
    'tm.Editor.WriteMessage(vbLf & anAttrib.Tag)
    End If

    Next

    End If

    If Not btr.IsAnonymous AndAlso Not btr.IsLayout Then
    'btr.Name to get block name
    blockIds.Add(btrId)
    End If
    btr.Dispose()

    Next
    End Using

    ' Copy blocks from source to destination database
    Dim mapping As New IdMapping()
    sourceDb.WblockCloneObjects(blockIds, destDb.BlockTableId, mapping, DuplicateRecordCloning.Replace, False)
    ed.WriteMessage((vbLf & "Copied " & blockIds.Count.ToString() & " block definitions from ") + sourceFileName.StringResult & " to the current drawing.")

    Catch ex As Autodesk.AutoCAD.Runtime.Exception
    ed.WriteMessage(vbLf & "Error during copy: " + ex.Message)
    End Try
    sourceDb.Dispose()

    End Sub
    We are using this code to update value of attribute in an autocad drawing. The drawing is created using Autocad 13.
    When we execute the code we neither get any error nor the value is updated. Let us know if you have experienced a similar issue.

  21. Hello

    First Timer.
    I understand that these posts are old.

    I am using VB.net
    I can't seem to use ctype to convert the object ID from the table record into an entity. not using ctype does not work either. it seems that my acEnt object is from the Interop library, and the obejct id is from the database services. Also the id from the block table record is of rotated dimension type.

    Can someone assist?

  22. Hi Bauron,

    You can use a transaction to open an ObjectID and get an entity. Search the blog for "GetObject" - you'll find plenty of examples.

    I suggest working through the intro material on the AutoCAD developer center (autodesk.com/developautocad).

    Regards,

    Kean

  23. Just found this in my spam folder. Please redirect your question to the AutoCAD .NET Discussion Group - this isn't the right place to request support.

    Thanks,

    Kean

  24. Hi Kean.

    Could You show me some examples with UI?
    I can't find any.

    Thanks a lot.

  25. Hi Makaveli,

    There's a user interface category of posts, which contains a number of posts that have command-line and graphical UIs.

    Regards,

    Kean

  26. Hi Kean.

    Based on this post I'm writing a function in order to change the TextString property of an attribute in my drawing.
    (Using VB.NET express 2008 on Autocad 2010 x86 system)
    Here's a code excerption:

    In a modeless Form
    [...]
    Dim attRef As AttributeReference

    attRef = acTrans.GetObject(myAttributeID, OpenMode.ForWrite)
    attRef.UpgradeOpen()
    attRef.TextString = myTextBox.Text
    attRef.DowngradeOpen()
    acTrans.Commit()
    [...]

    At this stage, to see the change, I am obliged to pick with the mouse pointer the title bar of my modeless form and move it slightly. The change is not visible until I move my form.

    If I add Application.UpdateScreen() after the acTrans.Commit() the attribute is displayed according to the change, without the need to move the form.

    How do we can explain this behavior?

    Tanks & regards

    Luigi

  27. Hi Luigi,

    The calls to UpgradeOpen() and DowngradeOpen() are redundant - the object is already being opened ForWrite.

    Have you tried using TransactionManager.QueueForGraphicsFlush() followed by TransactionManager.FlushGraphics()? You might also want to keep the Application.UpdateScreen() call in there afterwards.

    I hope this helps,

    Kean

  28. Hi Kean.,

    I have an autocad file(dwg),also there is a db for all the elemts in that file.Is there any way to communicate this dwg file with db,Is there any hidden ids for all the elements?

  29. Hi Sreekanth,

    Please post support questions to the discussion groups or ADN, in future.

    There's a handle for each DWG-resident object that you can use to link to records in an external DB.

    Regards,

    Kean

  30. Wilbert Holkema Avatar
    Wilbert Holkema

    Hello Kean,

    I am trying to edit attribute references and used this example as a start. After the code was ready and I was testing it, I did for some reason the Undo command. Then AutoCAD crashes.
    Then back to the base: I used this example as it is and after the test I did also an Undo. AutoCAD crashes again.
    What is going wrong?

    1. Kean Walmsley Avatar

      Hello Wilbert,

      The sample as posted seems to work fine for me with undo. Can you provide exact steps for me to reproduce the crash?

      Thanks,

      Kean

      P.S. It might help to know what version of AutoCAD you're using, for starters. It works fine for me with 2015 & 2016.

      1. Wilbert Holkema Avatar
        Wilbert Holkema

        Oops, sorry I bothered you with this: It is something in my code.
        On the event Document.Database.ObjectModified I am spotting changes on attributes. In your code I added a bypass to prevent this.
        I thought the Undo action would undoing the actions in my code, but obviously not, it's just undoing the changes it made. Of course 🙂

  31. Hi Kean,

    Is there a way to set Preset Attribute definition to false from code? I want to set it to Yes because I don't want the edit attribute pop up to open on creating block definition from the block.

    Thanks for any help..

    1. Hi Rohit, I don't clearly understand what you mean, could u pls clarify? rgds Ben

    2. Hi Rohit,

      Yes - please provide more information on what you're looking for. This is probably best posted on the AutoCAD .NET Discussion Group, in fact.

      Regards,

      Kean

  32. Hello Kean and readers

    will the code in this blog be able to update invisible attributes? br.AttributeCollection does not seem to be capturing invisible attributes in the collection.

    regards

    Ben

    1. Hello Ben,

      This post should help:

      adndevblog.typepad.com/autocad/

      Regards,

      Kean

  33. Jürgen A. Becker Avatar

    Hi Kean,

    I found your Posting but it does'nt answer a question I have.
    How can I update an attribute in a nested block?

    Many thanks for your answer.

    Cheers Jürgen

    1. Hi Jürgen,

      This code does recurse to work with nested blocks, as far as I can tell. You might need a little work to adapt this for your needs, but the concept should work.

      I don't work with AutoCAD at all, these days, but someone on the discussion groups should be able to help.

      Best,

      Kean

  34. Hi,
    i wants to update only one block attribute ,when i use this code this is updating all attributes in the block,please let me know which parameter i can add to update the one unique block

    1. Please submit support requests to the AutoCAD .NET forum.

      Kean

Leave a Reply to KAFIL Cancel reply

Your email address will not be published. Required fields are marked *