Adding XData to AutoCAD entities using .NET

A follow-up question came in related to the last post, around how to add XData to entities using .NET.

Extended Entity Data (XData) is a legacy mechanism to attach additional information to AutoCAD entities. I say "legacy" as there are limits inherent to using XData, which make other mechanisms more appropriate when storing significant amounts of data. The global XData limit is 16 KBytes per object, and this potentially needs to be shared between multiple applications.

With AutoCAD 2007 came the complete switch to Unicode for string representation, which increases the storage requirements for strings in XData. Internally, the limit has increased to cope with this, but because of round-tripping requirements with older versions no more XData should be stored than the previously recommended limit.

XData is still popular for a number of reasons - it's simple to code against and is quite efficient for small amounts of data - but for any significant usage the preferred mechanism is to store XRecords in each object's Extension Dictionary, as demonstrated in this previous post. It's also possible to expose your own custom data objects from ObjectARX (C++) to .NET with a Managed Wrapper, but that's a much more advanced topic.

I've written some C# code that implements two commands - one to attach some XData to an entity chosen by the user (SXD), and the other to access and dump the XData attached to a selected entity to the command-line (GXD).

Here it is:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

namespace ExtendedEntityData

{

  public class Commands

  {

    [CommandMethod("GXD")]

    static public void GetXData()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      // Ask the user to select an entity

      // for which to retrieve XData

      PromptEntityOptions opt =

        new PromptEntityOptions(

          "\nSelect entity: "

        );

      PromptEntityResult res =

        ed.GetEntity(opt);

      if (res.Status == PromptStatus.OK)

      {

        Transaction tr =

          doc.TransactionManager.StartTransaction();

        using (tr)

        {

          DBObject obj =

            tr.GetObject(

              res.ObjectId,

              OpenMode.ForRead

            );

          ResultBuffer rb = obj.XData;

          if (rb == null)

          {

            ed.WriteMessage(

              "\nEntity does not have XData attached."

            );

          }

          else

          {

            int n = 0;

            foreach (TypedValue tv in rb)

            {

              ed.WriteMessage(

                "\nTypedValue {0} - type: {1}, value: {2}",

                n++,

                tv.TypeCode,

                tv.Value

              );

            }

            rb.Dispose();

          }

        }

      }

    }

    [CommandMethod("SXD")]

    static public void SetXData()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      // Ask the user to select an entity

      // for which to set XData

      PromptEntityOptions opt =

        new PromptEntityOptions(

          "\nSelect entity: "

        );

      PromptEntityResult res =

        ed.GetEntity(opt);

      if (res.Status == PromptStatus.OK)

      {

        Transaction tr =

          doc.TransactionManager.StartTransaction();

        using (tr)

        {

          DBObject obj =

            tr.GetObject(

              res.ObjectId,

              OpenMode.ForWrite

            );

          AddRegAppTableRecord("KEAN");

          ResultBuffer rb =

            new ResultBuffer(

              new TypedValue(1001, "KEAN"),

              new TypedValue(1000, "This is a test string")

            );

          obj.XData = rb;

          rb.Dispose();

          tr.Commit();

        }

      }

    }

    static void AddRegAppTableRecord(string regAppName)

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      Database db = doc.Database;

      Transaction tr =

        doc.TransactionManager.StartTransaction();

      using (tr)

      {

        RegAppTable rat =

          (RegAppTable)tr.GetObject(

            db.RegAppTableId,

            OpenMode.ForRead,

            false

          );

        if (!rat.Has(regAppName))

        {

          rat.UpgradeOpen();

          RegAppTableRecord ratr =

            new RegAppTableRecord();

          ratr.Name = regAppName;

          rat.Add(ratr);

          tr.AddNewlyCreatedDBObject(ratr, true);

        }

        tr.Commit();

      }

    }

  }

}

One point to note is the need to add a Registered Application to the appropriate table inside the drawing. The name you use for this should be prefixed with your Registered Developer Symbol (RDS), to prevent conflicts with other applications.

Out first weekend in Beijing was fine. We started gently, by sleeping late and making a trip to Wal-Mart, of all places! The Wal-Mart near to where we live in Beijing is - like all other Wal-Marts I've visited in the US - enormous. One very helpful thing for us was the labelling of all sections in both English and Mandarin. It was really a fun experience checking out the different produce and trying to work out what exactly we were buying!

A few key differences to Wal-Marts in the US:

  • You can select your fish (and other fresh-/sea-water produce) from tanks - much as you can in restaurants in San Francisco's Chinatown (and presumably many restaurants in China, too :-).
  • You can't take the trolley out of the store, whether getting into your own car or into a taxi. This posed quite a logistical challenge for us, as we had two kids (each with strollers) and had bought some bulk items to last us well into our 7-week stay. Ultimately I settled on leaving my wife and kids outside, while I made 3 trips up and down the escalators, ferrying shopping bags.

Oh, and I was also a little disappointed the store wasn't called "The Great Wal-Mart" (groan 😉

57 responses to “Adding XData to AutoCAD entities using .NET”

  1. Nikolay Poleshchuk Avatar
    Nikolay Poleshchuk

    Why does Autodesk use legacy xdata for annotation scaling?

  2. Kean Walmsley Avatar

    Because it's efficient for small amounts of data (and annotation scaling doesn't need much additional data - the same as for the Hyperlink feature in AutoCAD). XData is fine if you're storing up to around 1KB - anything more than that and I'd recommend ExtDicts + XRecords.

    With hindsight, the word "legacy" has some negative connotations... the point I'm trying to make is that it is very risky to rely on it for substantial amounts of additional data (>1 KB per app).

  3. Nikolay Poleshchuk Avatar
    Nikolay Poleshchuk

    Kean, thank you. It would more usual for me to get annotative data from ordinary DXF codes. Xdata may accumulate from various applications.
    Can you show an example (in C# or C++) of adding a new annotation scale to an entity?

  4. Oh, I see what your question is... we couldn't go back and put the additional annotative data as "standard" group codes, as that would involve a change in the DWG & DXF format - something that only happens every 3 releases (and isn't due until the release after next).

    I will add an annotative scaling sample to my to-do list for the blog... (you should check the one on the ObjectARX SDK in the meantime.)

  5. You forgot to dispose of your ResultBuffers in both your above code samples. It has been my experience that failing to dispose of a ResultBuffer will cause AutoCAD to randomly go BOOM at some latter point when accessing XData.

  6. The finalizer should handle the underlying resources' disposal - but you never know exactly when that might happen, and I can see that with tight loops processing lots of entities you might run into trouble.

    It is, indeed, better practice to explicitly dispose of the ResultBuffers, so I've modified my code to do so.

    Thanks for the comment!

  7. Thank you for your great examples, Kean.
    It would be really great if you could show us an example in C# of adding a new annotation scale to an entity like Nikolay said.

  8. Hi Kean Walmsley,

    What are all the References we need to add for the coding

    "Adding XData to AutoCAD entities using .NET",

    Please send me ASAP.

    Regards,
    Senthil Prabu,
    Banglore,
    INDIA.

  9. Hi Senthil,

    You need references to acmgd.dll and acdbmdg.dll. See this previous post - it should help you get started:

    keanw.com/...

    Regards,

    Kean

  10. Senthil Prabu B R Avatar
    Senthil Prabu B R

    i am working as a CAD programmer, usally, i am creating the programs using LISP & VBA, Now i am trying to create the Program through VB.Net

    Here i tried a sample code, While compiling the following errors is showing.

    "Namespace or type 'EditorInput' for the Imports 'Autodesk.AutoCAD.EditorInput' cannot be found."

    Please give me an idea, What to do?

    Thanks & Regards,
    Senthil Prabu BR
    Banglore,
    INDIA.

  11. acmgd.dll should include what you need... what version of AutoCAD are you using?

  12. Senthil Prabu B R Avatar
    Senthil Prabu B R

    Hi Kean,

    I am using
    AutoCAD - ADT 2005
    Microsoft Visual Studio 2003

    for this sample programs which version we need to use?

    Regards,
    Senthil Prabu B R

  13. Hi Senthil,

    You need to download the ObjectARX SDK for AutoCAD 2005 from autodesk.com/obj.... In the samples/dotNet folder you will find some samples.

    The .NET API was really a "preview" in AutoCAD 2005. AutoCAD 2006 had the first "complete" .NET API available. So you may well have difficulty running some of the samples posted to this blog.

    Regards,

    Kean

  14. Roland,

    OK, I hear you... I'm working on it. 🙂

    Kean

  15. Hi,

    How do i move the link and label template from one object to another programmatically? [Using C# or VB.NET]

    My scenario is, I just want to relocate the employee into one room to another.

    [I'm showing the information about the employee in the Room. I want to move those detail into another Room ]

    Can anyone help me on this?

    Prabhu P
    Chennai.

  16. Hi Prabhu,

    This post should be of some use to you.

    Regards,

    Kean

  17. Hi Kean.
    Your blog - grat job.

    So... this is my problem.
    How can I change one value in xdata?
    For example, this is my procedure. What I write wrong?
    public static void PutAtrExdata(ObjectId ObjectId, string DataAtr, string DataValue)
    {
    Database db = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Database;
    using (Transaction tm = db.TransactionManager.StartTransaction())
    {
    DBObject obj = tm.GetObject(ObjectId, OpenMode.ForWrite);
    TypedValue[] exd;
    exd = obj.XData.AsArray();
    for (int i = 0; i < exd.Length; i++)
    {
    if (exd[i].Value.ToString() == DataAtr)
    {
    exd.SetValue("rower", 5);
    }
    }
    ResultBuffer rb = new ResultBuffer(exd);

    obj.XData = rb;
    rb.Dispose();
    tm.Commit();
    }
    }

    Regards,
    Marek
    Jaworzno
    POLAND

  18. Kean Walmsley Avatar

    You need to change your SetValue call:

    exd.SetValue(new TypedValue(exd[i].TypeCode, DataValue), i);

    Regards,

    Kean

  19. Gagan Gajabaharia Avatar
    Gagan Gajabaharia

    Hi Kean,

    Can you show how to clear/delete or just simply do away with the XData that is associated with a DBObject? I tried to do this by simply setting it to nullptr as in dbObj->XData = nullptr; - but it just doesn't work - because next time when I check for the XData on the same object, it is still there, and valid. Any thoughts much appreciated. I have also posted this q in the a.a.c.dotnet forum, I hope you don't mind multi-post. Thanks!

  20. Gagan Gajabaharia Avatar
    Gagan Gajabaharia

    Never mind, Kean, I got it. I need to set a new resbuf to DBObject - setting to nullptr won't (and shouldn't) work, obviously.

  21. Hi,
    I am working on a windows application by using c#.net. I have a drawing on which there are two rectangles. One is a rack object and another is a window object. I want to add these two objects to AutoCAD as a window object and a rack object. How can we accomplish this as rectangle is same object for AutoCAD. Can we define our own objects in AutoCAD by using the existing ones.

  22. Kean Walmsley Avatar

    You can create Dynamic Blocks (recommended) or use C++ to define custom objects (lots of work).

    Kean

  23. Hi Kean,

    Thanks for your reply. I want to create a drawing in AutoCAD by using my own custom objects and then import it to my windows application.(I have to use API such as ObjectARX or a tool such as DOTNETARX)Can i use Dynamic blocks in this case? Please suggest.

    Regards
    Amy

  24. Kean Walmsley Avatar

    "Import" could mean a lot of things. If the external application need to read DWG data, then you would use RealDWG and get access to your custom objects (or to the Dynamic Block data).

    I'd recommend investing a few days in trying out a simple case with Dynamic Blocks before starting down the long road to custom objects.

    Kean

  25. Thanks Kean
    This was really very helpful.

    Amy

  26. Just a warning about XData - ATTSYNC erases it.

    From AutoCAD Help ...
    Warning - ATTSYNC removes any format or property changes made with the ATTEDIT or EATTEDIT commands. It also deletes any extended data associated with the block, and might affect dynamic blocks and blocks created by third-party applications

    Thanks for the blog.
    cheers
    Ewen

  27. I Have the same Gagan Gajabaharia problem..
    Cannot clear my xdata if i set to Null my Xdata field. any idea ? Thanks

  28. Gagan solved the problem himself, if you look at his next comment... hopefully that also works for you, Giuliano.

    Kean

  29. Hi Kean,

    is it possible to read object data from a dwg file?

    regards,

  30. Kean Walmsley Avatar

    I assume you mean Map 3D's object data and unfortunately I don't know the answer: please ask your question on the AutoCAD Map 3D Developer Discussion Group.

    Kean

  31. I set the result buffer to null
    but it didn't work

  32. Also Setting Dbobject to a new empty result buffer doesn't work

  33. Try creating an empty ResultBuffer and assigning that to the XData property.

    Kean

  34. This example shows add xdata to AutoCAD.

    How to edit XData and set it back to object?

    my code is like this

    objFixture = trans.GetObject(objID, OpenMode.ForWrite)
    xdataFixture = objFixture.GetXDataForApplication(STR_XDATA_FIXTURE)
    Dim varFixXData() As TypedValue = xdataFixture.AsArray
    If UBound(varFixXData) <> MAX_INDEX_XDATA_FIXTURE Then
    ReDim Preserve varFixXData(MAX_INDEX_XDATA_FIXTURE)
    End If
    varFixXData(INDEX_XDATA_FIXTURE_ZONE_HANDLE) = New TypedValue(1000, strZoneHandle)
    AddRegAppTableRecord(STR_XDATA_FIXTURE)
    Dim rbNewFixXData As ResultBuffer = New ResultBuffer(varFixXData)
    objFixture.XData = rbNewFixXData
    rbNewFixXData.Dispose()
    xdataFixture.Dispose()

    trans.Commit()

    but it doesn't work, any help

    Thanks

  35. Kean Walmsley Avatar

    I don't have time to troubleshoot your code: the SXD command should be clear enough and functional.

    If you need more help I suggest contacting the ADN team, if you're a member, or otherwise posting to the AutoCAD .NET Discussion Group.

    Kean

  36. I had the same problem as izz, but managed to solve it after some investigation.
    The ResultBufer actually can't be empty, it has to contain one TypedValue with the DxfCode.ExtendedDataRegAppName for the application, that it belonged to.
    It is also safe for other applications, that might have some XData stored in the entity, because it only removes the XData related to the specified application.

    I hope this helped someone.

  37. Hello. I am write on VB.net, and I am using Xdata
    Please give me this code on VB.net

  38. Kean Walmsley Avatar

    I don't provide this service, but this post should help.

    Kean

  39. Thank you very mutch Kean. You help me to translate C# code into VB.net code.

  40. how to change customDP properties in AutoCAD?

  41. Kean Walmsley Avatar

    Sorry - I don't understand the question. And as it doesn't seem related to this post, please post it to the ADN team, if you're a member, or otherwise the AutoCAD .NET Discussion Group.

    Kean

  42. Dear Mr. Walmsley,
    is there a way to hook up btween a line and data from table in AutoCAD? when the data in table changed, the line also changed which just works like in EXCEL.

  43. Certainly, but it will take some work.

    And as this is off-topic, I'm going to suggest you post your question on one of our discussion forums (discussion.autodesk.com).

    Regards,

    Kean

  44. Hi,Kean.
    I want to draw a polyline, add an XData to it,then return the ObjectId. Then I add the ObjectId to a objectIdCollection. Sometime later, I will iterate the ObjectIdCollection, I want get the XData back. Can this be done?

  45. Kean Walmsley Avatar

    Hi Qiao,

    Certainly - XData persists along with the entity it's attached to.

    Regards,

    Kean

  46. Kean,

    Thanks for the post, very helpful. One question I have though is with ACAD Dimensions. I have written code that programmatically creates a dimension style and sets the dimension properties. When creating dimensions this way, they result in have style overrides, which I know is stored in XData. Is there a way to remove the XData so the style overrides won't show? I've tried to set the DimStyleTableRecord.XData to nothing as well as the DimStyleTableRecord.HasSaveVersionOverride to false.

    Sorry if this is on the wrong post, but I thought the XData would fix it and it's not working.

    Thanks!!

  47. Steve,

    Try setting the XData to nothing by passing through a ResultBuffer containing just the Registered Application Name. Here's an analogous command to the one in this post, although I haven't seen what the effect is on dimension overrides, specifically:

    [CommandMethod("KXD")]
    static public void KillXData()
    {
    Document doc =
    Application.DocumentManager.MdiActiveDocument;
    Editor ed = doc.Editor;

    // Ask the user to select an entity
    // for which to set XData
    PromptEntityOptions opt =
    new PromptEntityOptions(
    "\nSelect entity: "
    );
    PromptEntityResult res =
    ed.GetEntity(opt);

    if (res.Status == PromptStatus.OK)
    {
    Transaction tr =
    doc.TransactionManager.StartTransaction();
    using (tr)
    {
    DBObject obj =
    tr.GetObject(
    res.ObjectId,
    OpenMode.ForWrite
    );
    AddRegAppTableRecord("KEAN");
    ResultBuffer rb =
    new ResultBuffer(
    new TypedValue(1001, "KEAN")
    );
    obj.XData = rb;
    rb.Dispose();
    tr.Commit();
    }
    }
    }

    Cheers,

    Kean

  48. Kean,

    How would I modify this to attach xdata to layer 0. Instead of having the user select an object I just passed the object id of layer 0 and everything seemed to work perfectly per adding a watch in visual studio. However, when I try to recall the xdata it seems to have vanished!

    Thanks,
    Mark

  49. Mark,

    Interesting. I'd personally avoid placing XData on core, non-graphical objects such as layers, but I suppose it's theoretically possible.

    I don't know why it's not working for you, though: please contact ADN or post your question to the AutoCAD .NET Discussion Group.

    Regards,

    Kean

  50. Christopher Clayton Bertsch Avatar
    Christopher Clayton Bertsch

    I'm trying to make a small plumbing app and I'm wondering how one goes about looping through xdata on an entity, or entities?

    For example, lets say I'm trying to attach xdata to a line and store things PIPE(string) and then other info such as Distrobution(string), GPM(decimal), velocity(decimal), etc. Then on a circle I want to attach FIXTURE(string) and other data that could be different from the "pipe" xdata.

    Now I want to loop through a single entity, or group of entities...How do I know which xdata is what? How do I know when I get to the GPM value on an entity that is a line and contains the xdata PIPE? My only thought was to use strings for all the xdata so I could prefix everything with what the the 'tag' or 'property' is(PIPE, GPM: 10, Distribution: HOT, Velocity: 8, etc)....I know that would be a terrible way to do it. Do I just look for PIPE or FIXTURE and then keep track of the xdata by index, what is recommended?

    1. You might try using PIPE and FIXTURE (with the appropriate prefix to make them unique, of course) as the Registered Application Ids. The XData should be grouped by these when you loop through them.

      Kean

      1. Christopher Clayton Bertsch Avatar
        Christopher Clayton Bertsch

        So instead of using my company name as the regapp name, use something like PIPEgpm, PIPEfuxtureUnits, PIPEvelocity? Never thought of that...That would work, thanks!

  51. Hello Kean

    according to the link you posted re: registered symbols - autodesk says that it is no longer being supported.........what is going on here? does that mean i can name things in my plugin whatever i want?

    rgds
    Ben

    1. Hello Ben,

      Yes, I noticed that some time ago.

      This does increase the risk of name clashes, yes. Your best bet is to choose something suitably obscure (hopefully unique) as a prefix.

      Regards.

      Kean

  52. Hi all

    In the code above we are looping through the result buffer:

    foreach (TypedValue tv in rb)
    {
    ed.WriteMessage("\nTypedValue {0} - type: {1}, value: {2}", n++, tv.TypeCode, tv.Value);
    }

    What if I know exactly what I want - can one go straight to the particular type of data that they are after. e.g. I want to go after only strings? Can one get there without looping through everything?

    rgds

    Ben

    1. Hi Ben,

      One way or another you're going to have to loop through to find all the strings: you can't rely on things being in a particular order. You could use Linq or some other higher level approach to hide the looping (at least syntactically), but that's what it's going to have to do, under the hood.

      Regards,

      Kean

  53. Hi Kean - this blog post has proven immensely useful. But suppose that there are many registered apps attached to the same entity.....as i understand it all the typed values will be mashed up into the one result buffer.......how will one separate one xdata application from another while looping - because in my particular case I would like them to be separate.

    Ideally something like

    Resultbuffer[] rbs = obj.XData

    and then:

    foreach( ResultBuffer rb in rbs)

    {

    // do stuff for each xData application

    }

    Is something like that at all possible? Because right now:

    foreach (TypedValue tv in rb)
    {
    // do stuff
    // all the registered app values are being looped here
    }

    all the apps are being looped through. But I want to go through an app one at a time. Does my question make any sense?

Leave a Reply to ttnna Cancel reply

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