Author Topic: ObjectIdFilter  (Read 9379 times)

0 Members and 1 Guest are viewing this topic.

TheMaster

  • Guest
ObjectIdFilter
« on: April 17, 2013, 02:25:11 AM »
In another thread, I showed one of the experiments I did relating to discovering types of managed wrappers.  As it turns out, that code performed well enough to earn a place in my LINQ library. What I also have done, is merged it with another class that I routinely use to do complex filtering of ObjectIds (e.g, matching one of several types), to come up with a new implementation that is as fast as hand-written code, and has the added benefit of dynamic composition.

And so, attached are ObjectIdFilter.cs and a test class.

Code - Text: [Select]
  1. /// ObjectIdFilter.cs   Copyright (c)  2012  Tony Tanzillo
  2. /// <summary>
  3. ///
  4. /// ObjectIdFilter class
  5. ///
  6. /// Used to efficiently do simple and complex filtering
  7. /// of ObjectIds based on associated managed wrapper types.
  8. ///
  9. /// This class helps to simplify Linq code that performs
  10. /// complex filtering of ObjectIds based on the associated
  11. /// managed wrapper type of each ObjectId.
  12. ///
  13. /// An example:
  14. ///
  15. ///    BlockTableRecord btr = //.. (open some BlockTableRecord)
  16. ///    
  17. ///    // Get a filter that includes only entities
  18. ///    // whose managed wrappers are derived from
  19. ///    // AutoDesk.AutoCAD.DatabaseServices.Curve:
  20. ///    
  21. ///    ObjectIdFilter curveFilter = new ObjectIdFilter<Curve>();
  22. ///    
  23. ///    // Get an object that enumerates the ObjectIds
  24. ///    // of all Curve objects in the BlockTableRecord:
  25. ///    
  26. ///    var curveIds = btr.Cast<ObjectId>().Where( curveFilter.Predicate );
  27. ///    
  28. ///    // In AutoCAD 2013 or later, the above line can
  29. ///    // be reduced to:
  30. ///    
  31. ///    var curveIds = btr.Where( curveFilter.Predicate );
  32. ///    
  33. ///    // Additionally, in all AutoCAD releases, the
  34. ///    // GetObjectIds() method of the ObjectIdFilter
  35. ///    // class can be used to do the equivalent:
  36. ///    
  37. ///    var curveIds = curveFilter.GetObjectIds( btr );
  38. ///
  39. /// Complex Filtering Criteria:
  40. ///
  41. /// The ObjectIdFilter class as used above offers little
  42. /// advantages over explicit testing of each ObjectId's
  43. /// ObjectClass property with standard Linq methods, but
  44. /// the ObjectIdFilter was actually created to simplify
  45. /// a more-complicated problem, which involves filtering
  46. /// against multiple types, both related and unrelated;
  47. /// filtering against types derived from multiple types;
  48. /// and being more-selective, by explicitly including or
  49. /// excluding certain specified types individually.
  50. ///
  51. /// For example, the following filter will include only
  52. /// the ObjectIds of Lines, Arcs, and Polylines:
  53. ///
  54. ///    var curveSubSetFilter = new ObjectIdFilter(
  55. ///        typeof( Line ),
  56. ///        typeof( Arc ),
  57. ///        typeof( Polyline )
  58. ///    );
  59. ///    
  60. /// Overriding Criteria:
  61. ///
  62. /// The ObjectIdFilter allows the programmer to override
  63. /// criteria that includes or excludes all types derived
  64. /// from a given type, to specifically include or exclude
  65. /// one or more derived types, on a type-by-type basis.
  66. ///
  67. /// This example includes all types of Dimensions except
  68. /// for OrdinateDimensions using an overriding criteria:
  69. ///
  70. ///   // Define an ObjectIdFilter with the sole criteria
  71. ///   // that includes all types derived from Dimension:
  72. ///  
  73. ///   ObjectIdFilter dimfilter = new ObjectIdFilter<Dimension>();
  74. ///  
  75. ///   // Override the criteria that includes all types
  76. ///   // derived from Dimension, to exclude Ordinate
  77. ///   // dimensions:
  78. ///  
  79. ///   dimfilter[typeof(OrdinateDimension)] = false;
  80. ///  
  81. /// Overriding criteria can also override other
  82. /// overriding criteria.
  83. ///  
  84. /// The next example uses an override on a filter that
  85. /// includes all Curve types, to exclude Splines and all
  86. /// types derived from Spline, and then overrides that
  87. /// criteria to specifically include Helixes, which are
  88. /// derived from Spline:
  89. ///
  90. ///   var myFilter = new ObjectIdFilter<Curve>();
  91. ///  
  92. ///   // allow/disallow types derived from those
  93. ///   // types that are explicitly-included or
  94. ///   // excluded:
  95. ///  
  96. ///   myFilter.ExactMatch = false;
  97. ///  
  98. ///   // Add an override that excludes Splines and
  99. ///   // all types derived from Spline:
  100. ///  
  101. ///   myFilter[typeof(Spline)] = false;
  102. ///  
  103. ///   // we then override the above override that excludes
  104. ///   // Spline and all derived types, to specifically
  105. ///   // include Helixes (which are derived from Spline):
  106. ///  
  107. ///   myFilter[typeof(Helix)] = true;
  108. ///  
  109. /// The above example defines a filter that will
  110. /// include all Curves, including Helixes, but
  111. /// not Splines or any other type derived from
  112. /// Spline. and is functionally-equivalent to
  113. /// this delegate:
  114. ///
  115. /// bool IsCurveOrNotSplineExceptHelix( ObjectId id )
  116. /// {
  117. ///    RXClass curveClass = RXClass.GetClass(typeof(Curve));
  118. ///    RXClass splineClass = RXClass.GetClass(typeof(Spline));
  119. ///    RXClass helixClass = RXClass.GetClass(typeof(Helix));
  120. ///    
  121. ///    RXClass objectClass = id.ObjectClass;
  122. ///    
  123. ///    return objectClass.IsDerivedFrom( curveClass )
  124. ///      && ( ! objectClass.IsDerivedFrom( splineClass )
  125. ///             || objectClass.IsDerivedFrom( helixClass ) );
  126. /// }
  127. ///
  128. /// As unrealistic as it is, the above example serves to
  129. /// demonstrate the degree of granularity that can easily
  130. /// be achieved using the ObjectIdFilter with overriding
  131. /// criteria.
  132. ///
  133. /// This next example uses two overriding criteria to
  134. /// include all Curve objects EXCEPT XLines and Rays:
  135. ///
  136. ///   var myFilter = new ObjectIdFilter<Curve>();
  137. ///   myFilter[typeof(XLine)] = false;
  138. ///   myFilter[typeof(Ray)] = false;
  139. ///    
  140. /// The predicate generated by ObjectIdFilter in the
  141. /// above example does what this delegate does:
  142. ///
  143. /// bool IsCurveExceptXLineOrRay( ObjectId id )
  144. /// {
  145. ///    RXClass curveClass = RXClass.GetClass(typeof(Curve));
  146. ///    RXClass rayClass = RXClass.GetClass(typeof(Ray));
  147. ///    RXClass xlineClass = RXClass.GetClass(typeof(XLine));
  148. ///    
  149. ///    RXClass objectClass = id.ObjectClass;
  150. ///    return objectClass.IsDerivedFrom( curveClass )
  151. ///      && objectClass != rayClass
  152. ///      && objectClass != xlineClass;
  153. /// }
  154. ///
  155. /// Note that the equivalent predicates are relatively-complex
  156. /// in contrast to the ObjectIdFilter, and yet ObjectIdFilter
  157. /// is as fast, and in some cases faster than the direct use
  158. /// of hand-coded predicates when used with the Linq Where()
  159. /// function.
  160. ///
  161. /// Performance:
  162. ///
  163. /// The performance of ObjectIdFilter is comparable to the
  164. /// performance of explicit, hand-coded predicates in most
  165. /// use cases, and can be faster in some, mainly those that
  166. /// involve expensive predicates. The basic purpose of this
  167. /// class is not to out-perform other methods of filtering,
  168. /// but more for the purpose of simplifying the writing of
  169. /// code that defines the criteria, and to support dynamic
  170. /// composition of filters based on criteria that is unknown
  171. /// at compile-time, and would otherwise require complicated
  172. /// means to achieve.
  173. ///
  174. /// </summary>
  175.  
  176.  
  177.  

Updated: added missing MeaureExecutionTime() method to ObjectIdFilterTests.cs
« Last Edit: April 18, 2013, 09:31:27 AM by TT »

Gasty

  • Newt
  • Posts: 90
Re: ObjectIdFilter
« Reply #1 on: April 17, 2013, 11:10:27 AM »
Hi,

Very very nice code Tony.

Gaston Nunez

TheMaster

  • Guest
Re: ObjectIdFilter
« Reply #2 on: April 17, 2013, 01:47:40 PM »
Gaston, thanks.

There was a method missing from one of the files, and it's been updated.

gile

  • Gator
  • Posts: 2522
  • Marseille, France
Re: ObjectIdFilter
« Reply #3 on: April 17, 2013, 01:56:23 PM »
Very interesting Tony (as usual).
Speaking English as a French Frog

CADbloke

  • Bull Frog
  • Posts: 345
  • Crash Test Dummy
Re: ObjectIdFilter
« Reply #4 on: April 24, 2013, 07:29:19 PM »
Your timing is perfect - I was just pondering how to do this and I'm certain my solution would not have been as elegant.

Please add me to the heartfelt thank-you's for your generous and valuable contributions to our communities.

Even if I don't have any immediate use for your code, just reading your code and your insights gives me a better understanding (or at least an understanding of what I need to understand) of the beast that is the AutoCAD API, and of programming. If your mission is to help make us better programmers then you've succeeded here. (Note I said better, not good...maybe some day... )

If you're ever in Sydney, the beers are on me.

cheers
Ewen

TheMaster

  • Guest
Re: ObjectIdFilter
« Reply #5 on: April 24, 2013, 08:58:29 PM »
Your timing is perfect - I was just pondering how to do this and I'm certain my solution would not have been as elegant.

Please add me to the heartfelt thank-you's for your generous and valuable contributions to our communities.

Even if I don't have any immediate use for your code, just reading your code and your insights gives me a better understanding (or at least an understanding of what I need to understand) of the beast that is the AutoCAD API, and of programming. If your mission is to help make us better programmers then you've succeeded here. (Note I said better, not good...maybe some day... )

If you're ever in Sydney, the beers are on me.

cheers
Ewen

Thanks.   That's actually a small part of a larger querying library that I've been recently updating to take advantage of .NET 3.5 stuff, like Linq Expressions.  if I can find time to get the rest of it free  of baggage, I'll be posting that too.  The library helps to cache lookups against referenced objects to make querying marginally-to-dramatically faster.

Code - C#: [Select]
  1.  
  2.     LayerTable layers = db.LayerTableId.GetObject<LayerTable>();
  3.  
  4.     // get a filter that includes only entities whose layer is both on and thawed:
  5.     var filter = layers.GetFilter( layer => ! (layer.IsOff || layer.IsFrozen) );
  6.  
  7.     // BlockTableRecord btr = db.CurrentSpaceId.GetObject<BlockTableRecord>();
  8.    
  9.     // Get all entities in the current space whose layer is both on and thawed:
  10.     var entites = btr.GetObjects<Entity>().Where( filter.Predicate );
  11.  
  12.  

The 'filter' object works a lot like ObjectIdFilter, in that it only has to invoke the predicate on each LayerTableRecord once, rather than once for every entity that references the layer.

The interesting part (still working on this), is that you can logically-combine filters using '&', '|', '^", and their predicates are combined into a single predicate using Linq Expressions.

Code - C#: [Select]
  1.  
  2.     LayerTable layers = db.LayerTableId.GetObject<LayerTable>();
  3.  
  4.     // get a filter that includes only entities whose layer is both on and thawed:
  5.     var layerFilter = layers.GetFilter( layer => ! (layer.IsOff || layer.IsFrozen) );
  6.  
  7.     // BlockTable bt = db.BlockTableId.GetObject<BlockTable>();
  8.  
  9.    // Get a filter that includes all insertions of blocks whose
  10.    // name matches the pattern "PIPE*"
  11.  
  12.    var blockFilter = bt.GetFilter( btr => btr.Name.Matches( "PIPE*") );
  13.  
  14.    // combine both filters to produce a new composite
  15.    // filter that includes only insertions of blocks whose
  16.    // name starts with "PIPE*", that are on layers that
  17.    // are both on and thawed:
  18.  
  19.    var pipeFilter = blockFilter & layerFilter;
  20.    
  21.    // Get all matching insertions in current space:
  22.  
  23.    BlockTableRecord btr = db.CurrentSpaceId.GetObject<BlockTableRecord>();
  24.    var inserts = btr.GetObjects<Entity>().Where( pipeFilter.Predicate );
  25.  
  26.  
« Last Edit: April 24, 2013, 10:09:34 PM by TT »

Gasty

  • Newt
  • Posts: 90
Re: ObjectIdFilter
« Reply #6 on: April 24, 2013, 09:00:10 PM »
Yeap:

If you're ever in SydneySantiago, the beerswine are on me.

Gasty

CADbloke

  • Bull Frog
  • Posts: 345
  • Crash Test Dummy
Re: ObjectIdFilter
« Reply #7 on: May 07, 2013, 08:35:34 PM »
Wicked LINQ TT.  :-)

Code - C#: [Select]
  1.     LayerTable layers = db.LayerTableId.GetObject<LayerTable>();
Code - C#: [Select]
  1.     LayerTable layers = db.LayerTableId.GetObject<LayerTable>();
For those playing at home the GetObject method is at http://www.theswamp.org/index.php?topic=41311.msg464409#msg464409

Edit: here's another more recent one you should look at too - http://www.theswamp.org/index.php?topic=42197.msg474011#msg474011

I added this ...

Code - C#: [Select]
  1.    public static class GetObjectExtension
  2.   {
  3.     public static T GetObject<T>( this ObjectId id )
  4.      where T: DBObject
  5.    {
  6.         return (T) id.GetObject( OpenMode.ForRead, false, false );
  7.    }
  8.   }

...to the top of ObjectIdFilter.cs. It could (probably should) go in its own file. Um, now that I think about it I will move it but it works where I initially put it anyway. Unless I'm wrong (probability is high).

TT?
« Last Edit: May 14, 2013, 03:30:50 PM by CADbloke »

TheMaster

  • Guest
Re: ObjectIdFilter
« Reply #8 on: May 09, 2013, 12:37:44 AM »
Wicked LINQ TT.  :-)

Code - C#: [Select]
  1.     LayerTable layers = db.LayerTableId.GetObject<LayerTable>();
Code - C#: [Select]
  1.     LayerTable layers = db.LayerTableId.GetObject<LayerTable>();
For those playing at home the GetObject method is at http://www.theswamp.org/index.php?topic=41311.msg464409#msg464409

I added this ...

Code - C: [Select]
  1.    public static class GetObjectExtension
  2.   {
  3.     public static T GetObject<T>( this ObjectId id )
  4.      where T: DBObject
  5.    {
  6.         return (T) id.GetObject( OpenMode.ForRead, false, false );
  7.    }
  8.   }

...to the top of ObjectIdFilter.cs. It could (probably should) go in its own file. Um, now that I think about it I will move it but it works where I initially put it anyway. Unless I'm wrong (probability is high).

TT?

I keep all extension methods in a single class, one for each namespace. Since ObjectId is in Autodesk.AutoCAD.DatabaseServices, I put the class that contains extension methods that have 'this ObjectId' in the same namespace, so I don't have to remember where they are or add another 'using' to see them.

CADbloke

  • Bull Frog
  • Posts: 345
  • Crash Test Dummy
Re: ObjectIdFilter
« Reply #9 on: May 09, 2013, 07:40:20 AM »
I keep all extension methods in a single class, one for each namespace. Since ObjectId is in Autodesk.AutoCAD.DatabaseServices, I put the class that contains extension methods that have 'this ObjectId' in the same namespace, so I don't have to remember where they are or add another 'using' to see them.

This makes good sense. Non-proliferation isn't just for nukes.

csharpbird

  • Newt
  • Posts: 64
Re: ObjectIdFilter
« Reply #10 on: May 11, 2013, 10:03:14 PM »
It seems that Tony's ObjectIdFilter can only be used in AutoCAD 2009 and higher.

TheMaster

  • Guest
Re: ObjectIdFilter
« Reply #11 on: May 12, 2013, 01:23:05 AM »
It seems that Tony's ObjectIdFilter can only be used in AutoCAD 2009 and higher.

Yes, because that's the first release that has the ObjectClass property on the ObjectId struct.

In older releases (which I have no intention of continuing to support internally or, in publicly-shared code), you have to open the objects to filter them by their type.

Hanauer

  • Mosquito
  • Posts: 11
Re: ObjectIdFilter
« Reply #12 on: September 13, 2023, 07:59:21 AM »
I know this thread is old but I'm trying to use the code written by Tony Tanzillo.
How was layers.GetFilter and bt.Gefilter implemented?
Does anyone have some code that shows the implementation of:
Code: [Select]
LayerTable layers = db.LayerTableId.GetObject<LayerTable>();
 
    // get a filter that includes only entities whose layer is both on and thawed:
    var layerFilter = layers.GetFilter( layer => ! (layer.IsOff || layer.IsFrozen) );
 
    // BlockTable bt = db.BlockTableId.GetObject<BlockTable>();
 
   // Get a filter that includes all insertions of blocks whose
   // name matches the pattern "PIPE*"
 
   var blockFilter = bt.GetFilter( btr => btr.Name.Matches( "PIPE*") );
 
   // combine both filters to produce a new composite
   // filter that includes only insertions of blocks whose
   // name starts with "PIPE*", that are on layers that
   // are both on and thawed:
 
   var pipeFilter = blockFilter & layerFilter;
   
   // Get all matching insertions in current space:
 
   BlockTableRecord btr = db.CurrentSpaceId.GetObject<BlockTableRecord>();
   var inserts = btr.GetObjects<Entity>().Where( pipeFilter.Predicate );
« Last Edit: September 13, 2023, 08:03:49 AM by Hanauer »