Author Topic: Saving xRecords problem  (Read 4112 times)

0 Members and 1 Guest are viewing this topic.

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16899
  • Superior Stupidity at its best
Saving xRecords problem
« 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
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Find me on https://parler.com @kblackie

kdub_nz

  • Mesozoic keyThumper
  • SuperMod
  • Water Moccasin
  • Posts: 2125
  • class keyThumper<T>:ILazy<T>
Re: Saving xRecords problem
« Reply #1 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



« Last Edit: May 11, 2017, 10:07:16 PM by kdub »
Called Kerry in my other life
Retired; but they dragged me back in !

I live at UTC + 13.00

---
some people complain about loading the dishwasher.
Sometimes the question is more important than the answer.

Atook

  • Swamp Rat
  • Posts: 1027
  • AKA Tim
Re: Saving xRecords problem
« Reply #2 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

« Last Edit: May 11, 2017, 10:30:49 PM by Atook »

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16899
  • Superior Stupidity at its best
Re: Saving xRecords problem
« Reply #3 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!
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Find me on https://parler.com @kblackie

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16899
  • Superior Stupidity at its best
Re: Saving xRecords problem
« Reply #4 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.
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Find me on https://parler.com @kblackie

dgorsman

  • Water Moccasin
  • Posts: 2437
Re: Saving xRecords problem
« Reply #5 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?
If you are going to fly by the seat of your pants, expect friction burns.

try {GreatPower;}
   catch (notResponsible)
      {NextTime(PlanAhead);}
   finally
      {MasterBasics;}

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16899
  • Superior Stupidity at its best
Re: Saving xRecords problem
« Reply #6 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.
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Find me on https://parler.com @kblackie

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Saving xRecords problem
« Reply #7 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.     }
Speaking English as a French Frog

Atook

  • Swamp Rat
  • Posts: 1027
  • AKA Tim
Re: Saving xRecords problem
« Reply #8 on: May 16, 2017, 02:55:20 AM »
Great stuff as usual gile, thank you!

dgorsman

  • Water Moccasin
  • Posts: 2437
Re: Saving xRecords problem
« Reply #9 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.
If you are going to fly by the seat of your pants, expect friction burns.

try {GreatPower;}
   catch (notResponsible)
      {NextTime(PlanAhead);}
   finally
      {MasterBasics;}

Keith™

  • Villiage Idiot
  • Seagull
  • Posts: 16899
  • Superior Stupidity at its best
Re: Saving xRecords problem
« Reply #10 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!
Proud provider of opinion and arrogance since November 22, 2003 at 09:35:31 am
CadJockey Militia Field Marshal

Find me on https://parler.com @kblackie