Author Topic: Handling Symbol Tables and erased objects consistently  (Read 3645 times)

0 Members and 1 Guest are viewing this topic.

Jeff H

  • Needs a day job
  • Posts: 6150
Handling Symbol Tables and erased objects consistently
« on: June 16, 2012, 05:19:47 PM »
I am wondering about ideas on writing some Library code for handling SymbolTables.

The issue explained in quote below from Here
Quote from: Tony T
When a symbol table contains multiple entries with the same name, all except
one of which are erased, Has() returns true regardless of whether the first
entry it finds is erased or not, and the default indexer returns the first
entry it finds, erased or not.

They have changed the indexer behaviour not sure when it changed but think they changed it in 2011 or 2012
 
 
For a little info on the subject
 
I could be wrong be looking at it in reflector the indexer method for 2007 uses AcDbBlockTable::getAt and the argument for getErasedRec is always true.
So would return first record it found erased or not.
 
In 2013 they use a private field for the getErasedRec argument.
 
When a managed wrapper is created for a SymbolTable is assigns a private field that will pass false for getErasedRec argument in the indexer method.
 
The SymbolTable IncludingErased property calls its Copy method which creates another managed wrapper and changing the field to pass true for the getErasedRec argument.
 
The SymbolTable Has method like earlier releases returns true if it contains one unerased record.
 
So halfway there and would be nice if acted like quote below from above link
Quote
The way it should have been done, is to have both Has() and the default
indexer ignore erased entries, unless they are invoked on the copy of the
SymbolTable that's returned by the IncludingErased property.

 
You could write a Has extension method that has a another bool parameter to return true if it contains at least one record erased or not, but pretty much defeats the purpose.

Any ideas on knowing if you working with a default SymbolTable or a copy returned from IncludingErased property?
 
 
 
 
 
 
 

TheMaster

  • Guest
Re: Handling Symbol Tables and erased objects consistently
« Reply #1 on: June 16, 2012, 09:47:11 PM »
I am wondering about ideas on writing some Library code for handling SymbolTables.

The issue explained in quote below from Here
Quote from: Tony T
When a symbol table contains multiple entries with the same name, all except
one of which are erased, Has() returns true regardless of whether the first
entry it finds is erased or not, and the default indexer returns the first
entry it finds, erased or not.

They have changed the indexer behaviour not sure when it changed but think they changed it in 2011 or 2012
 
 
For a little info on the subject
 
I could be wrong be looking at it in reflector the indexer method for 2007 uses AcDbBlockTable::getAt and the argument for getErasedRec is always true.
So would return first record it found erased or not.
 
In 2013 they use a private field for the getErasedRec argument.
 
When a managed wrapper is created for a SymbolTable is assigns a private field that will pass false for getErasedRec argument in the indexer method.
 
The SymbolTable IncludingErased property calls its Copy method which creates another managed wrapper and changing the field to pass true for the getErasedRec argument.
 
The SymbolTable Has method like earlier releases returns true if it contains one unerased record.
 
So halfway there and would be nice if acted like quote below from above link
Quote
The way it should have been done, is to have both Has() and the default
indexer ignore erased entries, unless they are invoked on the copy of the
SymbolTable that's returned by the IncludingErased property.

 
You could write a Has extension method that has a another bool parameter to return true if it contains at least one record erased or not, but pretty much defeats the purpose.

Any ideas on knowing if you working with a default SymbolTable or a copy returned from IncludingErased property?

The only thing that distinguishes them is the values of those fields.

So, you would need to use reflection to access their values, and that will tell you.

TheMaster

  • Guest
Re: Handling Symbol Tables and erased objects consistently
« Reply #2 on: June 16, 2012, 11:26:33 PM »
Any ideas on knowing if you working with a default SymbolTable or a copy returned from IncludingErased property?

This should do it:

Code - C#: [Select]
  1.  
  2. public static class MySymbolTableExtensions
  3. {
  4.   public static bool GetIncludesErased( this SymbolTable table )
  5.   {
  6.     return ! (bool) typeof(SymbolTable).GetField( "m_skipDeleted",
  7.       BindingFlags.Instance | BindingFlags.NonPublic ).GetValue( table );
  8.   }
  9. }
  10.  
« Last Edit: June 17, 2012, 09:56:18 AM by TheMaster »

TheMaster

  • Guest
Re: Handling Symbol Tables and erased objects consistently
« Reply #3 on: June 17, 2012, 01:04:26 PM »

They have changed the indexer behaviour not sure when it changed but think they changed it in 2011 or 2012
 

I just checked and see that (as you noted), the indexer no longer returns erased entries unless it is called on the wrapper returned by IncludingErased. That means half of the problem was fixed, and the other half left broken. They didn't even bother to add an overload for Has() that takes an optional bool to ignore erased records.

The thing that leaves me completely baffled is why the API designers at Autodesk just can't seem to get it right. They can debate how the existing Has() method should work until the cows come home, but failing to provide an overload of it with an optional bool argument is indefensible.

You can exploit the change to the indexer to write a Contains() extension method that ignores erased objects depending on if it's called from the default wrapper or the one returned by IncludingErased:

Code - C#: [Select]
  1. public static class MySymbolTableExtensions
  2. {
  3.   public static bool Contains( this SymbolTable table, string key )
  4.   {
  5.     try
  6.     {
  7.       return ! table[key].IsNull;
  8.     }
  9.     catch( Autodesk.AutoCAD.Runtime.Exception ex )
  10.     {
  11.       if( ex.ErrorStatus == ErrorStatus.KeyNotFound )
  12.         return false;
  13.       throw;
  14.     }
  15.   }
  16. }
  17.  
« Last Edit: June 17, 2012, 01:28:37 PM by TheMaster »

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Handling Symbol Tables and erased objects consistently
« Reply #4 on: June 18, 2012, 10:27:19 AM »
Thanks Tony

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Handling Symbol Tables and erased objects consistently
« Reply #5 on: June 18, 2012, 03:30:23 PM »
Any ideas on knowing if you working with a default SymbolTable or a copy returned from IncludingErased property?

This should do it:

Code - C#: [Select]
  1.  
  2. public static class MySymbolTableExtensions
  3. {
  4.   public static bool GetIncludesErased( this SymbolTable table )
  5.   {
  6.     return ! (bool) typeof(SymbolTable).GetField( "m_skipDeleted",
  7.       BindingFlags.Instance | BindingFlags.NonPublic ).GetValue( table );
  8.   }
  9. }
  10.  

Would it be a good idea to do something like
Code - C#: [Select]
  1.         public static IEnumerable<T> GetTableRecords<T>(this SymbolTable symbolTable, OpenMode mode = OpenMode.ForRead) where T : SymbolTableRecord
  2.         {
  3.             bool includingErased = symbolTable.GetIncludesErased();
  4.  
  5.             foreach (ObjectId id in symbolTable)
  6.             {
  7.                 yield return (T)id.GetObject(mode, includingErased, false);
  8.             }
  9.         }
  10.  

 
Also have one for BlockTableRecords
Code - C#: [Select]
  1.             public static bool GetIncludesErased(this BlockTableRecord btr)
  2.             {
  3.                 return !(bool)typeof(BlockTableRecord ).GetField("m_bSkipDeleted", //// Changed from typeof(SymbolTable)
  4.                                                                                                           /////   to typeof(BlockTableRecord)
  5.                   BindingFlags.Instance | BindingFlags.NonPublic).GetValue(btr);
  6.             }
  7.  
Then for BlockTableRecords code slightly modified from here
Code - C#: [Select]
  1.  
  2.   public static IEnumerable<T> GetObjects<T>(this BlockTableRecord btr, OpenMode mode = OpenMode.ForRead,
  3.                 bool includeObjectsOnLockedLayers = false) where T : Entity
  4.             {
  5.                 bool openErased = btr.GetIncludesErased();
  6.                 bool unfiltered = typeof(T) == typeof(Entity);
  7.                 bool openLocked = mode == OpenMode.ForWrite && includeObjectsOnLockedLayers;
  8.                 RXClass rxclass = RXClass.GetClass(typeof(T));
  9.                 if (unfiltered)
  10.                 {
  11.                     foreach (ObjectId id in btr)
  12.                     {
  13.                         yield return (T)id.GetObject(mode, openErased, openLocked);
  14.                     }
  15.                 }
  16.                 else     // caller wants a subset, so filter by runtime class:
  17.                 {
  18.                     foreach (ObjectId id in btr)
  19.                     {
  20.                         if (id.ObjectClass.IsSubclassOf(rxclass))
  21.                         {
  22.                             yield return (T)id.GetObject(mode, openErased, openLocked);
  23.                         }
  24.                     }
  25.                 }
  26.             }
  27.  

That way if you want to deal with erased objects you just work with a copy returned from IncludingErased.

********Edit**********
Change typeof(SymbolTable) to typeof(BlockTableRecord)
 
« Last Edit: June 19, 2012, 01:05:11 PM by Jeff H »

TheMaster

  • Guest
Re: Handling Symbol Tables and erased objects consistently
« Reply #6 on: June 20, 2012, 01:23:49 AM »
This is how mine works:

Code - C#: [Select]
  1. public static class MyLinqExtensions
  2. {
  3.   public static IEnumerable<T> GetRecords<T>(
  4.        this SymbolTable src,
  5.        OpenMode mode = OpenMode.ForRead,
  6.        bool IncludingErased = false ) where T: SymbolTableRecord
  7.   {
  8.       foreach( ObjectId id in IncludingErased ? src.IncludingErased : src )
  9.       {
  10.          yield return (T) id.GetObject( mode, IncludingErased, false );
  11.       }
  12.    }
  13. }
  14.  
  15.  

TheMaster

  • Guest
Re: Handling Symbol Tables and erased objects consistently
« Reply #7 on: June 20, 2012, 01:27:12 AM »

Also have one for BlockTableRecords
Code - C#: [Select]
  1.             public static bool GetIncludesErased(this BlockTableRecord btr)
  2.             {
  3.                 return !(bool)typeof(BlockTableRecord ).GetField("m_bSkipDeleted", //// Changed from typeof(SymbolTable)
  4.                                                                                                           /////   to typeof(BlockTableRecord)
  5.                   BindingFlags.Instance | BindingFlags.NonPublic).GetValue(btr);
  6.             }
  7.  
Change typeof(SymbolTable) to typeof(BlockTableRecord)

I don't think typeof(BlockTableRecord) will work here, because the m_bSkipDeleted member is a private member of the base type (SymbolTableRecord).


Jeff H

  • Needs a day job
  • Posts: 6150
Re: Handling Symbol Tables and erased objects consistently
« Reply #8 on: June 20, 2012, 11:58:09 AM »
I see SymbolTable has 'm_skipDeleted' field, but not seeing 'm_bSkipDeleted' in SymbolTableRecord.
 
 

TheMaster

  • Guest
Re: Handling Symbol Tables and erased objects consistently
« Reply #9 on: June 25, 2012, 10:13:29 PM »
I see SymbolTable has 'm_skipDeleted' field, but not seeing 'm_bSkipDeleted' in SymbolTableRecord.

Sorry, you're right, I meant SymbolTable rather than SymbolTableRecord, but I may
have misunderstood your intent, because I just noticed that BlockTableRecord has
an m_bSkipDeleted member also, so it depends on whether you were looking to
get erased entities in a block, or erased blocks.

« Last Edit: June 25, 2012, 10:35:34 PM by TheMaster »

Jeff H

  • Needs a day job
  • Posts: 6150
Re: Handling Symbol Tables and erased objects consistently
« Reply #10 on: June 26, 2012, 10:57:14 AM »
Sorry,
 
That was one of those post after reading it a couple a days later and realized part of it went into to the post and part of it was still in my head so made sense to me and not clear to anyone else reading it.
 
Was trying to get it to where I did not have to worry or think about what to pass for openErased argument to GetObject and be decided if I was working with a copy from IncludingErased or not.
 
Something along the lines of
Code - C#: [Select]
  1.  
  2.          [CommandMethod("ErasedEntites")]
  3.         public void ErasedEntites()
  4.         {
  5.             Document doc = Application.DocumentManager.MdiActiveDocument;
  6.             Database db = doc.Database;
  7.             Editor ed = doc.Editor;
  8.  
  9.             using (Transaction trx = db.TransactionManager.StartTransaction())
  10.             {
  11.                 BlockTable bt = db.GetBlockTable();
  12.                 var blockDefinitions = bt.GetTableRecords();
  13.                 var includingErasedBlockDefinitions = bt.IncludingErased.GetTableRecords<BlockTableRecord>();
  14.  
  15.                 foreach (var blk in blockDefinitions)
  16.                 {
  17.                     ed.WriteLine("**********");
  18.                     ed.WriteLine(blk.Name);
  19.                     foreach (var ent in blk.GetObjects())
  20.                     {
  21.                         ed.WriteLine(ent.GetRXClass().Name);
  22.                    
  23.                     }
  24.                 }
  25.  
  26.                 ed.WriteLine("*********Including Erased**********");
  27.                 foreach (var blk in includingErasedBlockDefinitions)
  28.                 {
  29.                     ed.WriteLine("**********");
  30.                     ed.WriteLine(blk.Name);
  31.                     foreach (var ent in blk.IncludingErased.GetObjects())
  32.                     {
  33.                         ed.WriteLine(ent.GetRXClass().Name);
  34.                     }
  35.                 }
  36.                
  37.             }
  38.         }
  39.