TheSwamp

Code Red => .NET => Topic started by: Keith™ on May 11, 2017, 06:38:44 PM

Title: Saving xRecords problem
Post by: Keith™ on May 11, 2017, 06:38:44 PM
I need to save data in drawings. This data is non-graphical and is saved as an xRecord.
The first time the data is written, everything is ok. I can read the data to verify it is there and save the drawing. However, when I re-open the drawing and attempt to add more data, it seems to work, but AutoCAD crashes whenever I save the drawing. On the occasion when it does save the file, when I try to re-open the file, I get the following error INTERNAL ERROR: !dbAnnoScaleCompositionReactori.cpp@1415: eInvalidIndex

This is the relevant code

Code - Visual Basic: [Select]
  1.     Public Function GetNotes(ByVal layout As String) As String
  2.         Dim acDoc As Document = Autodesk.AutoCAD.ApplicationServices.Core.Application.DocumentManager.MdiActiveDocument
  3.         Dim acDB As Database = acDoc.Database
  4.         Dim rValue As String = ""
  5.         Using acLckDoc = acDoc.LockDocument()
  6.             Using acTrans As Transaction = acDB.TransactionManager.StartTransaction
  7.                 Dim acDict As DBDictionary = DirectCast(acTrans.GetObject(acDB.LayoutDictionaryId, OpenMode.ForRead), DBDictionary)
  8.                 Dim lData As Xrecord
  9.                 Try
  10.                     Dim dataID As ObjectId = acDict.GetAt(layout)
  11.                     lData = DirectCast(acTrans.GetObject(dataID, OpenMode.ForRead), Xrecord)
  12.                     For Each tv As TypedValue In lData.Data
  13.                         If tv.TypeCode = DxfCode.Text Then
  14.                             rValue &= tv.Value.ToString() & vbCrLf
  15.                         End If
  16.                     Next
  17.                 Catch
  18.                     rValue = "No Notes for layout: " & layout
  19.                 End Try
  20.             End Using
  21.         End Using
  22.         Return rValue
  23.     End Function
  24.  
  25.     Public Sub SetNotes(ByVal notes As String, ByVal layout As String)
  26.         Dim acDoc As Document = Autodesk.AutoCAD.ApplicationServices.Core.Application.DocumentManager.MdiActiveDocument
  27.         Dim acDB As Database = acDoc.Database
  28.         Using acLckDoc = acDoc.LockDocument()
  29.             Using acTrans As Transaction = acDB.TransactionManager.StartTransaction
  30.                 Dim acDict As DBDictionary = DirectCast(acTrans.GetObject(acDB.LayoutDictionaryId, OpenMode.ForWrite), DBDictionary)
  31.                 Dim lData As Xrecord
  32.                 Try
  33.                     'Does the xRecord already exist?
  34.                    Dim dataID As ObjectId = acDict.GetAt(layout)
  35.                     lData = DirectCast(acTrans.GetObject(dataID, OpenMode.ForWrite), Xrecord)
  36.                     lData.Data = New ResultBuffer(New TypedValue(DxfCode.Text, notes))
  37.                 Catch
  38.                     'Apparently not, lets create a new one.
  39.                    lData = New Xrecord()
  40.                     lData.Data = New ResultBuffer(New TypedValue(DxfCode.Text, notes))
  41.                     acDict.SetAt(layout, lData)
  42.                     acTrans.AddNewlyCreatedDBObject(lData, True)
  43.                 End Try
  44.                 acTrans.Commit()
  45.             End Using
  46.         End Using
  47.     End Sub
Title: Re: Saving xRecords problem
Post by: kdub_nz on May 11, 2017, 09:56:40 PM
Keith,
Looks to me that you are trying to cast a Layout to an Xrecord.

Code - vb.net: [Select]
  1. lData = DirectCast(acTrans.GetObject(dataID, OpenMode.ForWrite), Xrecord)

I'm pretty sure this can't be done.


added:
personally I wouldn't use try/catch as you have for checking data ... it's designed for catching exceptions



Title: Re: Saving xRecords problem
Post by: Atook on May 11, 2017, 10:23:40 PM
Keith, I can't see the problem directly, but maybe the try/catch is hiding an error. I use something like this to edit/add xrecords. key is a string, rBuff is a ResultBuffer.
Code - C#: [Select]
  1. if(myDbDictionary.Contains(key))
  2. {
  3.     //  edit the existing xrecord
  4.     var myXRec = (Xrecord) tr.GetObject(myDbDictionary.GetAt(key), OpenMode.ForWrite);
  5.     myXRec.Data = rBuff;
  6. }
  7. else
  8. {
  9.     // add the xrecord
  10.     Xrecord newXRec = new Xrecord() {Data= rBuff};
  11.     myDbDictionary.SetAt(key, newXRec);
  12.     tr.AddNewlyCreatedDBObject(newXRec, true);                                 
  13. }
  14. tr.Commit();

I think I put this code together after reading this: http://aucache.autodesk.com/au2012/sessionsFiles/2146/2646/handout_2146_CP2146.pdf

Title: Re: Saving xRecords problem
Post by: Keith™ on May 12, 2017, 05:24:02 PM
I think I figured out the issue ... It seems to be working now..

I changed this line
Code - Visual Basic: [Select]
  1.         Dim acDict As DBDictionary = DirectCast(acTrans.GetObject(acDB.LayoutDictionaryId, OpenMode.ForRead), DBDictionary)

to this
Code - Visual Basic: [Select]
  1.         Dim acDict As DBDictionary = DirectCast(acTrans.GetObject(acDB.NamedObjectsDictionaryId, OpenMode.ForRead), DBDictionary)

and this line

Code - Visual Basic: [Select]
  1.         Dim acDict As DBDictionary = DirectCast(acTrans.GetObject(acDB.LayoutDictionaryId, OpenMode.ForWrite), DBDictionary)

to this
Code - Visual Basic: [Select]
  1.         Dim acDict As DBDictionary = DirectCast(acTrans.GetObject(acDB.NamedObjectsDictionaryId, OpenMode.ForWrite), DBDictionary)

LayoutDictionary is a vastly different thing than NamedObjectsDictionary.  :idiot2: :idiot2:


Keith,
Looks to me that you are trying to cast a Layout to an Xrecord.

Code - vb.net: [Select]
  1. lData = DirectCast(acTrans.GetObject(dataID, OpenMode.ForWrite), Xrecord)

I'm pretty sure this can't be done.


added:
personally I wouldn't use try/catch as you have for checking data ... it's designed for catching exceptions

It's not casting a layout ... if you look at the code a little closer, you will see that dataID is supposed to be the ObjectId of an xRecord ... Here is what was happening and how that came to be as it is with the Try/Catch wrapper.

acDict.GetAt(layout) should return the ObjectID of what is supposed to be an xRecord with a name that is passed by the calling function .. the name of a layout in the drawing ... if there is no xRecord existing by that name, acDict.GetAt throws an exception so I put the wrapper in there to create an xRecord by the name that doesn't exist (see rValue  = "No Notes for layout: " & layout).

Anyway, it was a bandaid at the time until I read the post by Atook ...


Keith, I can't see the problem directly, but maybe the try/catch is hiding an error. I use something like this to edit/add xrecords. key is a string, rBuff is a ResultBuffer.
Code - C#: [Select]
  1. if(myDbDictionary.Contains(key))
  2. {
  3.     //  edit the existing xrecord
  4.     var myXRec = (Xrecord) tr.GetObject(myDbDictionary.GetAt(key), OpenMode.ForWrite);
  5.     myXRec.Data = rBuff;
  6. }
  7. else
  8. {
  9.     // add the xrecord
  10.     Xrecord newXRec = new Xrecord() {Data= rBuff};
  11.     myDbDictionary.SetAt(key, newXRec);
  12.     tr.AddNewlyCreatedDBObject(newXRec, true);                                 
  13. }
  14. tr.Commit();

I think I put this code together after reading this: http://aucache.autodesk.com/au2012/sessionsFiles/2146/2646/handout_2146_CP2146.pdf

Contains resolves my Try/Catch issue ... much cleaner. I've implemented that now that I have figured out the underlying problem!


Its working like a champ .. except I do have one thing that I am attempting to resolve ...
User creates a drawing with a few layouts ... they add an xRecord to the layout (stored as the layout name) .. then later renames the layout. I can no longer filter for the layout name because it will not find the xRecord even though it exists ...

I moved to xRecords because they are more flexible than xData and you can store larger amounts of information .. but the downside is that I can't attach it to a specific layout tab because xRecords live at the document level. I have currently resorted to saving the xRecords with a name that is the handle of a layout. That should prevent any issues with not being able to find xRecords BUT ... if a layout is deleted I need the xRecord to go away too .. don't want to be polluting drawings with useless crap. I'll leave that up to users AND I want to be sure that if a user renames a layout I'll still be able to pair it up with the xRecord.

Thanks for the help!
Title: Re: Saving xRecords problem
Post by: Keith™ on May 12, 2017, 05:45:56 PM
I think I resolved the issue ... search the xRecords for an entry named after the handle, without regard to the layout name ... pass the layout name and handle to the function.

Now to get the xRecords to disappear if a tab is deleted.
Title: Re: Saving xRecords problem
Post by: dgorsman on May 15, 2017, 10:32:23 AM
"... XRecords live at the document level...".   :?  You can add extension dictionaries (which host XRecrods and other Extension Dictionaries) to pretty much anything.  Or are you referring to something else?
Title: Re: Saving xRecords problem
Post by: Keith™ on May 15, 2017, 07:57:44 PM
Ok, so you are saying that I can add an extension dictionary to the current layout, then add an xrecord to that particular layout ...
What is this sorcery you speak of ...

The goal is to have the ability to add embedded documentation and data that applies to a paperspace layout or model space. Typically xrecords are document level, not being associated with any particular object ... at least that was my understanding.

I'd love to hear more about how this works. It would eliminate so much overhead if I can implement that.
Title: Re: Saving xRecords problem
Post by: gile on May 16, 2017, 02:28:23 AM
Hi,

Here're some extension methods to get or set any DBObject extension dictionary xrecord data.

Code - C#: [Select]
  1.     public static class DBOjectExtension
  2.     {
  3.         /// <summary>
  4.         /// Gets the data from an Xrecord of the DBObject extension dictionary.
  5.         /// </summary>
  6.         /// <param name="source">Instance of DBObject.</param>
  7.         /// <param name="key">Xrecord key.</param>
  8.         /// <returns>the Xrecord data if found, else null.</returns>
  9.         public static ResultBuffer GetXrecData(this DBObject source, string key)
  10.         {
  11.             if (source == null)
  12.                 throw new ArgumentNullException("source");
  13.  
  14.             var tr = source.Database.TransactionManager.TopTransaction;
  15.             if (tr == null)
  16.                 throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);
  17.  
  18.             var xdictId = source.ExtensionDictionary;
  19.             if (xdictId.IsNull)
  20.                 return null;
  21.  
  22.             var xdict = (DBDictionary)tr.GetObject(xdictId, OpenMode.ForRead);
  23.             if (!xdict.Contains(key))
  24.                 return null;
  25.  
  26.             var xrec = tr.GetObject(xdict.GetAt(key), OpenMode.ForRead) as Xrecord;
  27.             if (xrec == null)
  28.                 return null;
  29.  
  30.             return xrec.Data;
  31.         }
  32.  
  33.         /// <summary>
  34.         /// Sets the data of an Xrecord of the DBObject extension dictionary.
  35.         /// The extension dictionary and xrecord are created if not already exist.
  36.         /// </summary>
  37.         /// <param name="source">Instance of DBObject.</param>
  38.         /// <param name="key">Xrecord key.</param>
  39.         /// <param name="data">Data to fill the xrecord.</param>
  40.         public static void SetXrecData(this DBObject source, string key, ResultBuffer data)
  41.         {
  42.             if (source == null)
  43.                 throw new ArgumentNullException("source");
  44.  
  45.             var tr = source.Database.TransactionManager.TopTransaction;
  46.             if (tr == null)
  47.                 throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);
  48.  
  49.             if (source.ExtensionDictionary.IsNull)
  50.             {
  51.                 source.UpgradeOpen();
  52.                 source.CreateExtensionDictionary();
  53.             }
  54.             var xdict = (DBDictionary)tr.GetObject(source.ExtensionDictionary, OpenMode.ForRead);
  55.  
  56.             Xrecord xrec;
  57.             if (xdict.Contains(key))
  58.             {
  59.                 xrec = (Xrecord)tr.GetObject(xdict.GetAt(key), OpenMode.ForWrite);
  60.             }
  61.             else
  62.             {
  63.                 xdict.UpgradeOpen();
  64.                 xrec = new Xrecord();
  65.                 xdict.SetAt(key, xrec);
  66.                 tr.AddNewlyCreatedDBObject(xrec, true);
  67.             }
  68.             xrec.Data = data;
  69.         }
  70.     }
Title: Re: Saving xRecords problem
Post by: Atook on May 16, 2017, 02:55:20 AM
Great stuff as usual gile, thank you!
Title: Re: Saving xRecords problem
Post by: dgorsman on May 16, 2017, 10:16:38 AM
Ok, so you are saying that I can add an extension dictionary to the current layout, then add an xrecord to that particular layout ...
What is this sorcery you speak of ...

The goal is to have the ability to add embedded documentation and data that applies to a paperspace layout or model space. Typically xrecords are document level, not being associated with any particular object ... at least that was my understanding.

I'd love to hear more about how this works. It would eliminate so much overhead if I can implement that.

I sometimes find it helpful to think of Extension Dictionaries and XRecords as analogs of XML elements and properties, respectively.  You can add Dictionaries as children of other dictionaries ad infinitum (within reason).  Each Dictionary can hold XRecords, either exclusively or alongside other Dictionaries.  And all of it is name-indexed (no more "Was that index 31 or 32...") so you can build pseudo-XPath notation to look up nested data.

Almost everything in AutoCAD can take an Extension Dictionary.  As a property of the DBObject base object it should inherit to almost every other object and entity.
Title: Re: Saving xRecords problem
Post by: Keith™ on May 17, 2017, 12:10:52 AM
Cool stuff ... I do believe I'll be changing the code to this method. It will make managing the data much easier!