Author Topic: Iterating through block definitions by name  (Read 16021 times)

0 Members and 1 Guest are viewing this topic.

cannorth

  • Guest
Iterating through block definitions by name
« on: March 23, 2012, 01:02:42 PM »
Hello,

  Can anyone send me code about iterating through block definitions by name using VB.Net ObjectARX?

Thanks,

cannorth

n.yuan

  • Bull Frog
  • Posts: 348
Re: Iterating through block definitions by name
« Reply #1 on: March 23, 2012, 03:47:37 PM »
The fact is, given a drawing, you need to open each BlockTablerecord to get block name. That is, by the time you have a list of block names, the BlockTable has already been iterated at least once.
 
So I thought you may want to look into how to get a block name list first. Here is some code doing that:

Code: [Select]
public static string[] GetBlockNamesInDrawing(Document dwg)
{
    List<string> blkNames = null;
    using (Transaction tran = dwg.Database.TransactionManager.StartTransaction())
    {
        BlockTable tbl = (BlockTable)tran.GetObject(dwg.Database.BlockTableId, OpenMode.ForRead);

        IEnumerable<ObjectId> blkIDs = tbl.Cast<ObjectId>();
        var blks = from id in blkIDs
                   where !id.IsEffectivelyErased && !id.IsNull && !id.IsErased
                   select GetBlockTableRecordFromId(id, tran);

        blkNames = (from b in blks
                    where !b.IsAnonymous &&
                    !b.IsAProxy &&
                    !b.IsFromExternalReference &&
                    !b.IsFromOverlayReference &&
                    !b.IsLayout
                    orderby b.Name ascending
                    select b.Name).ToList();
       
        tran.Commit();
    }

    return blkNames == null ? null : blkNames.ToArray();
}

private static BlockTableRecord GetBlockTableRecordFromId(ObjectId id, Transaction tran)
{
    return tran.GetObject(id, OpenMode.ForRead) as BlockTableRecord;
}

« Last Edit: March 23, 2012, 03:54:59 PM by n.yuan »

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #2 on: March 24, 2012, 04:25:31 AM »
Code: [Select]

....
        IEnumerable<ObjectId> blkIDs = tbl.Cast<ObjectId>();
        var blks = from id in blkIDs
                   where !id.IsEffectivelyErased && !id.IsNull && !id.IsErased
                   select GetBlockTableRecordFromId(id, tran);


A couple of points, if you don't mind:

You don't need any of those tests (IsEffectivelyErased, IsErased, or IsNull)
because none of them can be true. If you iterate over the IncludingErased
property of the BlockTable, only then are erased ObjectIds included in the
enumeration. Otherwise, you will not encounter erased ObjectIds

IsEffectivelyErased means that one or more of the object's owners is
erased, which could not be possible in this case, because the BlockTable
(which is the owner of all BlockTableRecords) isn't erased.

IsNull can't be true if the object is database-resident.

Another tip:

You could replace GetBlockTableRecordFromId() with
a generic extension method like:

Code - C#: [Select]
  1.  
  2.    public static T GetObject<T>( this ObjectId id )
  3.      where T: DBObject
  4.    {
  5.         return (T) id.GetObject( OpenMode.ForRead, false, false );
  6.    }
  7.  
  8.    // usage:
  9.  
  10.    ObjectId id = // assign to ID of a blockTableRecord
  11.  
  12.    BlockTableRecord btr = id.GetObject<BlockTableRecord>();
  13.  
  14.    // You can use the same extension method to open any
  15.    // type of entity. For example, this opens the first entity
  16.    // in the block:
  17.  
  18.    Entity entity = btr.Cast<ObjectId>().First().GetObject<Entity>();
  19.  
  20.  

And one last tip:

Notice that the generic extension method above calls the overload of
ObjectId.GetObject() that takes all arguments. If you look at the
source for ObjectId.GetObject() in Reflector or ILSpy, you'll notice
that all of the overloaded versions delegate to that same overload (the
one that takes all arguments).

So, by calling that same overload directly, we can avoid a needless
level of indirection.



//
edit:kdub - formatting code = csharp
« Last Edit: March 26, 2012, 03:40:49 PM by Kerry »

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Iterating through block definitions by name
« Reply #3 on: March 24, 2012, 10:06:54 AM »
I really like these generic extension methods.

Here's an example (thanks again to kaefer)

Code: [Select]
    public static class Extensions
    {
        public static T GetObject<T>(this ObjectId id) where T : DBObject
        {
            return id.GetObject(OpenMode.ForRead, false, false) as T;
        }

        public static IEnumerable<T> GetObjects<T>(this IEnumerable ids) where T : DBObject
        {
            return ids
                .Cast<ObjectId>()
                .Select(id => id.GetObject(OpenMode.ForRead, false, false))
                .OfType<T>();
        }
    }

An the GetBlockNamesInDrawing() method should be this:

Code: [Select]
        public static string[] GetBlockNamesInDrawing(Database db)
        {
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                return db.BlockTableId
                    .GetObject<BlockTable>()
                    .GetObjects<BlockTableRecord>()
                    .Where(btr =>
                        !btr.IsAnonymous &&
                        !btr.IsFromExternalReference &&
                        !btr.IsFromOverlayReference &&
                        !btr.IsLayout &&
                        !btr.IsAProxy)
                    .Select(btr => btr.Name)
                    .ToArray();
            }
        }
Speaking English as a French Frog

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #4 on: March 26, 2012, 01:49:23 AM »
I really like these generic extension methods.

Here's an example (thanks again to kaefer)

Code - C#: [Select]
  1.    
  2.     public static class Extensions
  3.     {
  4.         public static T GetObject<T>(this ObjectId id) where T : DBObject
  5.         {
  6.             return id.GetObject(OpenMode.ForRead, false, false) as T;
  7.         }
  8.  
  9.         public static IEnumerable<T> GetObjects<T>(this IEnumerable ids) where T : DBObject
  10.         {
  11.             return ids
  12.                 .Cast<ObjectId>()
  13.                 .Select(id => id.GetObject(OpenMode.ForRead, false, false))
  14.                 .OfType<T>();
  15.         }
  16.     }
  17.  
  18.  

Since I wrote the first versions of these functions many years ago, I've since made some improvements in the interest of performance, and in dealing with other issues like locked layers.

First, I don't agree with the idea that a GetObject<T>() method should return null if the object is not of the type of the generic argument. But that idea is predicated on testing the ObjectId's ObjectClass property before even trying to open the object, because to put it quite simply, it is faster.

So, you might note that my GetObject<T>() is assertive, meaning that it throws an exception if the opened object is not of the desired type.  That's because I only use it in cases where I expect the object to be of the type of the generic argument, and if it is not, that is a hard error that should result in an exception.

Coincidentally, I do have a version of the GetObject<T>() method you show above, except that mine is called TryGetObject<T>().  :wink:

And on the subject of opening many objects via GetObjects<T>(), here's mine (actually, a watered-down version of it with dependencies on other code I have here ripped out, and wasn't tested after editing it to remove the dependencies). This GetObjects<T>() is faster than any implementation that opens every object in the source and uses the 'as' operator to test if it is of the desired type, but feel free to scrutinize and/or test if you wish.

Code - C#: [Select]
  1.  
  2.  
  3. public static class AcDbExtensions
  4. {
  5.   // These overloads of GetObjects<T>() target BlockTableRecord
  6.   // specifically, because:
  7.   //
  8.   //   1. It is incorrect to target *any* IEnumerable, which
  9.   //      includes things like System.String. You shouldn't
  10.   //      allow an extension method to be invoked on a target
  11.   //      that is not applicable or elegible.
  12.   //
  13.   //   2. BlockTableRecord specifically contains only entities,
  14.   //      and supports opening erased entities, which requires
  15.   //      specialization that would unduly burden a more generic
  16.   //      solution targeting any IEnumerable that enumerates a
  17.   //      sequence of ObjectIds (e.g., ObjectIdCollection,
  18.   //      SymbolTableRecord, ObjectId[], etc). There are other
  19.   //      overloads of GetObjects<T>() which target those same
  20.   //      types and specialize them, without also carrying the
  21.   //      overhead of a single, 'all-purpose' solution ( those
  22.   //      latter ones are not included here ).
  23.  
  24.   public static IEnumerable<T> GetObjects<T>( this BlockTableRecord btr )
  25.     where T: Entity
  26.   {
  27.     return GetObjects<T>( btr, OpenMode.ForRead, false, false );
  28.   }
  29.  
  30.   public static IEnumerable<T> GetObjects<T>( this BlockTableRecord btr, OpenMode mode )
  31.     where T: Entity
  32.   {
  33.     return GetObjects<T>( btr, mode, false, false );
  34.   }
  35.  
  36.   public static IEnumerable<T> GetObjects<T>( this BlockTableRecord btr, bool openErased )
  37.     where T: Entity
  38.   {
  39.     return GetObjects<T>( btr, OpenMode.ForRead, openErased, false );
  40.   }
  41.  
  42.   public static IEnumerable<T> GetObjects<T>(
  43.     this BlockTableRecord btr,
  44.     OpenMode mode,
  45.     bool openErased,
  46.     bool openObjectsOnLockedLayers )  where T: Entity
  47.   {
  48.     // if caller wants erased objects, use the IncludingErase property:
  49.     BlockTableRecord source = openErased ? btr.IncludingErased : btr;
  50.  
  51.     // if the generic argument is <Entity>, there is no filtering
  52.     // needed because this method targets BlockTableRecord,
  53.     // whose elements are all entities:
  54.     bool unfiltered = typeof( T ) == typeof( Entity );
  55.    
  56.     bool openLocked = mode == OpenMode.ForWrite && openObjectsOnLockedLayers;
  57.  
  58.     // The runtime class of the generic argument managed wrapper type:
  59.     RXClass rxclass = RXClass.GetClass( typeof( T ) );
  60.  
  61.     // if no filtering is needed, just open and return every object
  62.     // cast to the generic argument type:
  63.  
  64.     if( unfiltered )
  65.     {
  66.       foreach( ObjectId id in source )
  67.       {
  68.         yield return (T) id.GetObject( mode, openErased, openLocked );
  69.       }
  70.     }
  71.     else     // caller wants a subset, so filter by runtime class:
  72.     {
  73.       foreach( ObjectId id in source )
  74.       {
  75.         if( id.ObjectClass.IsSubclassOf( rxclass ) )
  76.         {
  77.           yield return (T) id.GetObject( mode, openErased, openLocked );
  78.         }
  79.       }
  80.     }
  81.   }
  82.  
  83.  
  84.   //////////////////////////////////////////////////////////////////////////////
  85.   // Extension method:
  86.   //
  87.   //  IEnumerable<T>.UpgradeOpen<T>() where T: DBObject
  88.   //
  89.   // Upgrade each object to OpenMode.ForWrite as it is pulled
  90.   // from the sequence, which is preferable to opening everything
  91.   // for write, when we may not modify all opened objects.
  92.   //
  93.   // This method is useful when you want to upgrade the open mode
  94.   // of many objects, but do not want to modify objects that are on
  95.   // locked layers (that also presumes you are operating directly
  96.   // on a BlockTableRecord, possibly in a RealDwg application or a
  97.   // database that's not open in the drawing editor, and the source
  98.   // may include objects on locked layers).
  99.   //
  100.   // Objects on locked layers are skipped and omitted from the
  101.   // resulting sequence.
  102.  
  103.   public static IEnumerable<T> UpgradeOpen<T>( this IEnumerable<T> source )
  104.     where T: DBObject
  105.   {
  106.     foreach( T item in source )
  107.     {
  108.       try
  109.       {
  110.         if( ! item.IsWriteEnabled )    
  111.           item.UpgradeOpen();
  112.       }
  113.       catch( Autodesk.AutoCAD.Runtime.Exception ex )
  114.       {
  115.         if( ex.ErrorStatus != ErrorStatus.OnLockedLayer )
  116.           throw;
  117.         continue;
  118.       }
  119.       yield return item;
  120.     }
  121.   }
  122.  
  123.   // Helper extension method:  
  124.   // Open and return the model space BlockTableRecord for the
  125.   // given database:
  126.  
  127.   public static BlockTableRecord ModelSpace( this Database db )
  128.   {
  129.     return ModelSpace( db, OpenMode.ForRead );
  130.   }
  131.  
  132.   public static BlockTableRecord ModelSpace( this Database db, OpenMode mode )
  133.   {
  134.     return (BlockTableRecord) SymbolUtilityServices.GetBlockModelSpaceId( db )
  135.        .GetObject( mode, false, false );
  136.   }
  137.  
  138.   // Open and return the BlockTableRecord for the current space
  139.   // in the given database:
  140.  
  141.   public static BlockTableRecord CurrentSpace( this Database db )
  142.   {
  143.     return (BlockTableRecord) db.CurrentSpaceId.GetObject( OpenMode.ForRead, false, false );
  144.   }
  145.  
  146.   public static BlockTableRecord CurrentSpace( this Database db, OpenMode mode )
  147.   {
  148.     return (BlockTableRecord) db.CurrentSpaceId.GetObject( mode, false, false );
  149.   }
  150.  
  151.   // Extension Method: bool RXClass.IsSubclassOf( RXClass other )
  152.   //
  153.   // This helper avoids a managed-to-native transition in the
  154.   // case where the two runtime classes are the same class, by
  155.   // doing a comparison in managed code first, before calling
  156.   // IsDerivedFrom() (which is a native P/Invoke).
  157.   //
  158.   // When a significant precentage of the objects being filtered
  159.   // are of the same runtime class as the target, this will run
  160.   // faster than direct calls to IsDerivedFrom().
  161.   //
  162.   // In the latest version of the framework, P/Invoke is much
  163.   // faster and for that reason this helper may not be needed
  164.   // or will not offer any advantage over a direct call to the
  165.   // IsDerivedFrom() method.
  166.  
  167.   public static bool IsSubclassOf( this RXClass thisClass, RXClass otherClass )
  168.   {
  169.     return thisClass == otherClass || thisClass.IsDerivedFrom( otherClass );
  170.   }
  171.  
  172.  
  173. }
  174.  
  175. public static class Example
  176. {
  177.   // Erase all lines in model space that are < 0.1 units
  178.   // long. This example shows a more 'proper' way to use
  179.   // LINQ to write simple commands that modify entities.
  180.   // Specifically, it only opens objects for write if they are
  181.   // going to be modified, and gracefully deals with objects
  182.   // on locked layers. It should go without saying that we
  183.   // can do this same operation using a filtered selection
  184.   // as well, but only for the model or paper space blocks
  185.   // of drawings that are open in the drawing editor. For
  186.   // other blocks, or for operating on databases that aren't
  187.   // open in the drawing editor, an operation like this one
  188.   // must be handled using a pattern like that used here.
  189.   //
  190.   // Points of interest:
  191.   //
  192.   // The ModelSpace() extension method is called to
  193.   // open and get the model space BlockTableRecord.
  194.   //
  195.   // The GetObjects<Line>() extension method is called
  196.   // to get all Line entities in model space, open for read.
  197.   //
  198.   // The Where( line => line.Length < 0.1 ) call reduces
  199.   // the sequence of lines returned by GetObjects<Line>(),
  200.   // to only those lines whose length is < 0.1 units.
  201.   //
  202.   // The call to the UpgradeOpen() method receives the
  203.   // subset of entities returned by the call to Where(),
  204.   // and upgrades the open mode of those entities, which
  205.   // is the set of entities that will actually be erased.
  206.   // If any of those entities reside on locked layers, they
  207.   // will not be included in the sequence returned by the
  208.   // call to the UpgradeOpen() extension method.
  209.   //
  210.   // Because UpgradeOpen() uses deferred execution, the
  211.   // actual upgrading of each object's open mode does not
  212.   // happen until just before the object is assigned to the  
  213.   // 'line' variable within the foreach() loop.
  214.   //
  215.   // The ModelSpace() method is a handy shortcut for what
  216.   // is otherwise overly-verbose code that would only be
  217.   // correct in coding examples that many noobs mistakenly
  218.   // take too literaly, as representative examples of how
  219.   // real production code should be written.
  220.  
  221.  
  222.   [CommandMethod( "LINQ_WEEDLINES" )]
  223.   public static void WeedLinesCommand()
  224.   {
  225.     Document doc = Application.DocumentManager.MdiActiveDocument;
  226.     using( doc.LockDocument() )  
  227.     using( Transaction tr = doc.TransactionManager.StartTransaction() )
  228.     {
  229.       var linesToErase = doc.Database.ModelSpace()
  230.               .GetObjects<Line>()
  231.               .Where( line => line.Length < 0.1 )
  232.               .UpgradeOpen();
  233.                
  234.       foreach( Line line in linesToErase )
  235.       {
  236.         line.Erase();
  237.       }
  238.  
  239.       tr.Commit();
  240.     }
  241.   }
  242.  
  243.   // Example: Displays the sum of the areas of
  244.   // all closed Polylines in the current space.
  245.  
  246.   [CommandMethod( "LINQ_SUMPOLYLINEAREAS" )]
  247.   public static void SumPolylineAreasCommand()
  248.   {
  249.     Document doc = Application.DocumentManager.MdiActiveDocument;
  250.  
  251.     using( Transaction tr = doc.TransactionManager.StartTransaction() )
  252.     {
  253.       double area = doc.Database.CurrentSpace()
  254.         .GetObjects<Polyline>()
  255.         .Where( pline => pline.Closed )
  256.         .Sum( pline => pline.Area );
  257.        
  258.       doc.Editor.WriteMessage(
  259.         "\nTotal Area of all closed polylines: {0} units",
  260.         area
  261.       );
  262.      
  263.       tr.Commit();
  264.     }
  265.   }
  266.  
  267.   // Example: Display the handle of the circle
  268.   // in modelspace having the largest Radius
  269.   // (or the last of multiple circles having the
  270.   // largest Radius):
  271.  
  272.   [CommandMethod( "LINQ_FINDLARGESTCIRCLERADIUS" )]
  273.   public static void FindLargestCircleRadiusCommand()
  274.   {
  275.     Document doc = Application.DocumentManager.MdiActiveDocument;
  276.     using( Transaction tr = doc.TransactionManager.StartTransaction() )
  277.     {
  278.       var circles = doc.Database.ModelSpace().GetObjects<Circle>();
  279.       if( circles.Any() )
  280.       {
  281.         Circle largest =
  282.           circles.Aggregate(
  283.             (a, b) => a.Radius > b.Radius ? a : b
  284.           );
  285.  
  286.         doc.Editor.WriteMessage(
  287.           "\nLargest circle - handle: {0}, Radius: {1}",
  288.           largest.Handle.ToString(), largest.Radius
  289.         );
  290.       }
  291.       else
  292.       {
  293.         doc.Editor.WriteMessage( "\nNo Circles were found.");
  294.       }
  295.       tr.Commit();
  296.     }
  297.   }
  298. }
  299.      
  300.  
  301.  
  302.  


edit:kdub - formatting code = csharp

edit (author) - added missing generic type argument on UpgradeOpen<T>() extension method

edit (author) - added missing IsSubclassOf() extension method
« Last Edit: March 28, 2012, 12:22:30 PM by TheMaster »

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Iterating through block definitions by name
« Reply #5 on: March 26, 2012, 06:16:05 AM »
Thank you very much Tony for these precisions/explainations. :kewl:
Speaking English as a French Frog

kaefer

  • Guest
Re: Iterating through block definitions by name
« Reply #6 on: March 26, 2012, 06:51:44 AM »
Code: [Select]
  //   1. It is incorrect to target *any* IEnumerable, which
  //      includes things like System.String. You shouldn't
  //      allow an extension method to be invoked on a target
  //      that is not applicable or elegible.

Why ever not? It wouldn't be very different from calling a static method with the non-generic IEnumerable as parameter, and in any case, Linq's Cast<TResult>() method will take care of the error conditions and throws ArgumentNull or InvalidCast as appropriate
(n.b. this is different from F#'s Seq.cast function).

As for guidelines to extensions methods, there are the recommendations to use them sparingly and to prefer inheritance wherever possible, and that's about it.

To address the slight performance hit previously incurred by the opening of all objects, I hereby adopt your operation on RXClass. The result's still generic, working on any IEnumerable<ObjectId> and any untyped IEnumerable which IEnumerator.Current implementation is of type ObjectId.

Code - C#: [Select]
  1.     public static class IEnumExtensions
  2.     {
  3.         public static T GetObject<T>(this ObjectId id) where T : DBObject
  4.         {
  5.             return (T)id.GetObject(OpenMode.ForRead);
  6.         }
  7.         public static IEnumerable<T> GetObjects<T>(this IEnumerable ids) where T : DBObject
  8.         {
  9.             RXClass rx = RXClass.GetClass(typeof(T));
  10.             return
  11.                 from ObjectId id in ids
  12.                 where id.ObjectClass.IsDerivedFrom(rx)
  13.                 select id.GetObject<T>();
  14.         }
  15.     }
  16.     public static class IEnumExtensionsTestCommand
  17.     {
  18.         [CommandMethod("DELETESHORTLINES")]
  19.         public static void deleteShortLinesCommand()
  20.         {
  21.             Database db = HostApplicationServices.WorkingDatabase;
  22.             using (Transaction tr = db.TransactionManager.StartTransaction())
  23.             {
  24.                 var linesToErase =
  25.                     SymbolUtilityServices.GetBlockModelSpaceId(db)
  26.                         .GetObject<BlockTableRecord>()
  27.                         .GetObjects<Line>()
  28.                         .Where(line => line.Length < 0.1);
  29.                 foreach (Line line in linesToErase)
  30.                 {
  31.                     line.UpgradeOpen();
  32.                     line.Erase();
  33.                 }
  34.                 tr.Commit();
  35.             }
  36.         }
  37.     }

Code - F#: [Select]
  1. // get DBObject for read and cast to 'T, if type parameter present
  2. let getObject<'T when 'T :> DBObject> (oid: ObjectId) =
  3.     oid.GetObject OpenMode.ForRead :?> 'T
  4.  
  5. // get all objects from an untyped IEnumerable
  6. let getObjects<'T when 'T :> DBObject> : System.Collections.IEnumerable -> _ =
  7.    let rx = RXClass.GetClass typeof<'T>
  8.     Seq.cast<ObjectId>
  9.     >> Seq.filter (fun oid -> oid.ObjectClass.IsDerivedFrom rx)
  10.     >> Seq.map getObject<'T>
  11.  
  12. [<CommandMethod "DeleteShortLines">]
  13. let deleteShortLinesCommand() =
  14.    let db = HostApplicationServices.WorkingDatabase
  15.    use tr = db.TransactionManager.StartTransaction()
  16.    let linesToErase =
  17.        SymbolUtilityServices.GetBlockModelSpaceId db
  18.        |> getObject<BlockTableRecord>
  19.        |> getObjects<Line>
  20.        |> Seq.filter(fun line -> line.Length < 0.1)
  21.    for line in linesToErase do
  22.        line.UpgradeOpen()
  23.        line.Erase()
  24.    tr.Commit()

Jeff H

  • Needs a day job
  • Posts: 6144
Re: Iterating through block definitions by name
« Reply #7 on: March 26, 2012, 10:37:09 AM »
I have not read enough about them for 4.0 to know what I am talking about, but could you write an ExpressionTree and just pass some type of delegate filter?
 
 

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #8 on: March 26, 2012, 05:59:12 PM »

Why ever not? It wouldn't be very different from calling a static method with the non-generic IEnumerable as parameter, and in any case, Linq's Cast<TResult>() method will take care of the error conditions and throws ArgumentNull or InvalidCast as appropriate (n.b. this is different from F#'s Seq.cast function).


Well, what I said was incorrect (IMO) applies to any kind of method, not just extension methods, so I wouldn't write a static method that takes any IEnumerable as an argument either, if the method can only use certain ones.

If there's only a limited set of specific types that my method can use (all of which implement IEnumerable), then I'd write an overload for each. That's essentially what I did with GetObjects<T>(). I have a set of overloads for each type (there's not that many of them).  Cast<T> is fine except that it adds another iterator to the chain, and each iterator adds a little more overhead. The C# foreach() construct takes care of casting implicitly and the last time I checked, it was faster than adding a Cast<T>().

Quote

As for guidelines to extensions methods, there are the recommendations to use them sparingly and to prefer inheritance wherever possible, and that's about it.


Well, solving the problem through inheritance might work except that we can't use inheritance to target types we didn't create (even if they're not sealed), in cases where instances cannot be instantiated (e.g., where instances are provided by another type exclusively).

Quote

To address the slight performance hit previously incurred by the opening of all objects, I hereby adopt your operation on RXClass. The result's still generic, working on any IEnumerable<ObjectId> and any untyped IEnumerable which IEnumerator.Current implementation is of type ObjectId.


The performance hit isn't slight when you are working with BlockTableRecords that contain between 50,000 and 200,000 objects, and your code only targets a very small percentage of them. Filtering ObjectIds by their runtime class is clearly faster than opening each ObjectId and testing the type of the returned managed wrapper using the 'as' operator (or TryCast() in VB.NET), mainly because to test a managed wrapper's type, one needs a managed wrapper to start with.  :wink: To get a managed wrapper, the underlying AcDbObject must be opened (directly or through a transaction), and have a managed wrapper created for it.

Creating managed wrappers only for the purpose of filtering source objects by each managed wrapper's type has additional performance implications beyond those that are obvious and easily measured. Those additional implications are barely-tangible and not easily identifiable, mainly because they manifest in the form of latent garbage collection overhead that by its indeterminate nature, is difficult to isolate and measure.

For example, because managed wrappers have finalizers, a reference to every instance of a managed wrapper must be added to the GC's finalization queue, which has a cost in terms of both memory and processing overhead.

The additional processing overhead results from having to add and remove each managed wrapper instance to/from the GC's finalization queue. The additional memory overhead (storing a reference to each managed wrapper in the finalization queue) is roughly equivalent to each managed wrapper having one additional private instance member holding a reference to a reference type.

The extent of the advantage that testing ObjectId runtime classes has over opening each ObjectId and testing the managed wrapper's type depends largely on the relative number of matching objects in the source sequence. The advantage increases as the relative number of matching objects in the source sequence decreases, and scales with the size of the source sequence.

For example, if code needed to process only dimensions in model space, and only ~3% of all objects in model space are dimensions, the advantage will be significant, because only 3% of all objects must opened and have a managed wrapper created. If on the other hand, one has code that needs to process most of the objects in a BlockTableRecord, there is less of an advantage because most of those objects will need to be opened for processing regardless.

But in any case, feel free to adopt my code. If I have time to remove the dependencies on the other overloads of GetObjects<T>() that I use, I'll post them.
 

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #9 on: March 26, 2012, 06:28:55 PM »

Code - C#: [Select]
  1.  
  2.    ........
  3.                 foreach (Line line in linesToErase)
  4.                 {
  5.                     line.UpgradeOpen();
  6.                     line.Erase();
  7.                 }
  8.                 tr.Commit();
  9.  
  10.  



The problem is a bit more complicated than what your code above suggests. The reason I use the UpgradeOpen() extension method I included, is because dealing with objects on locked layers is a reality that one most contend with (especially when processing drawings that are not open in the drawing editor, or when processing blocks other than model or paper space where filtered selection sets are typically used to preclude objects on locked layers).

The underlying purpose of the various extension methods I showed is to simplify as much as possible, application-level code.

If I didn't use that extension method, the try/catch block that's in it would need to be in any code that I write to process a BlockTableRecord (because I don't want to make the assumption that it's only going to be used on the current space or in a drawing that's open in the editor).

The example usage code I posted was implemented as a command that operates on  the active document's model space, only to serve as a simple example of how those methods might be used with a BlockTableRecord, including ones in drawings that aren't open in the editor. That's why there is no use of SelectionSets and filtering. In most of the cases where I use those methods, using a filtered selection to preclude objects on locked layers is not an option, so they must be dealt with one way or  another. So, the more-simplified version of WEEDLINES you show, that uses DBObject.UpgradeOpen() in the foreach() block will work in that scenario only if you've previously-eliminated objects on locked layers, but it won't work universally in the more general scenario represented by my example.

« Last Edit: March 26, 2012, 08:58:46 PM by TheMaster »

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #10 on: March 26, 2012, 07:37:06 PM »
Code: [Select]

public static string[] GetBlockNamesInDrawing(Database db)
{
  using (Transaction tr = db.TransactionManager.StartTransaction())
  {
    return db.BlockTableId
      .GetObject<BlockTable>()
      .GetObjects<BlockTableRecord>()
      .Where(btr =>
        !btr.IsAnonymous &&
        !btr.IsFromExternalReference &&
        !btr.IsFromOverlayReference &&
        !btr.IsLayout &&
        !btr.IsAProxy)
      .Select(btr => btr.Name)
      .ToArray();               // <----- look here
  }
}



I try to make helper methods like the one you show
as generic as possible, to make them more reusable.
In that spirit, I would do it this way:

Code - C#: [Select]
  1.  
  2. public static class Extensions
  3. {
  4.   // By decoupling the filtering from the
  5.   // selection of the result (the block name)
  6.   // and conversion to an array, the filtering
  7.   // can be reused for other purposes:
  8.  
  9.   public static IEnumerable<BlockTableRecord> UserBlocks(
  10.                                this IEnumerable<BlockTableRecord> source )
  11.   {
  12.     return
  13.       source.Where(btr =>
  14.         !btr.IsAnonymous &&
  15.         !btr.IsFromExternalReference &&
  16.         !btr.IsFromOverlayReference &&
  17.         !btr.IsLayout &&
  18.         !btr.IsAProxy
  19.       );
  20.   }
  21.  
  22.   public static IEnumerable<BlockTableRecord> UserBlocks( this Database db )
  23.   {
  24.        return db.BlockTableId.GetObject<BlockTable>()
  25.           .GetObjects<BlockTableRecord>()
  26.           .UserBlocks();
  27.   }
  28.  
  29.   public static IEnumerable<string> UserBlockNames( this Database db )
  30.   {
  31.     return db.UserBlocks().Select( btr => btr.Name );
  32.   }
  33. }
  34.  
  35.  
  36. public static class AcDbExtensions
  37. {
  38.  
  39.     // SymbolTable  //////////////////////////////////////////////////////////
  40.     //
  41.     // The reason for having overloads of GetObject<T> for
  42.     // SymbolTable/SymbolTableRecord based types, is to
  43.     // support getting erased objects via the IncludingErased
  44.     // property. Otherwise, a more generic GetObjects<T>
  45.     // would suffice.
  46.  
  47.     // To be completely correct, a version of this method
  48.     // targeting each type derived from SymbolTable should be
  49.     // defined, but I'll leave that up to you and will just check
  50.     // the generic argument at runtime,  :grin:
  51.  
  52.   public static IEnumerable<T> GetObjects<T>( this SymbolTable source )
  53.     where T: SymbolTableRecord
  54.   {
  55.     return GetObjects<T>( source, OpenMode.ForRead, false );
  56.   }
  57.  
  58.   public static IEnumerable<T> GetObjects<T>( this SymbolTable source, OpenMode mode )
  59.     where T: SymbolTableRecord
  60.   {
  61.     return GetObjects<T>( source, mode, false );
  62.   }
  63.  
  64.   public static IEnumerable<T> GetObjects<T>(
  65.     this SymbolTable source,
  66.     OpenMode mode,
  67.     bool includingErased ) where T: SymbolTableRecord
  68.   {
  69.  
  70.     if( typeof( T ) == typeof( SymbolTableRecord ) )
  71.       throw new ArgumentException(
  72.             "Requires a concrete type derived from SymbolTableRecord");
  73.  
  74.     foreach( ObjectId id in includingErased ? source.IncludingErased : source )
  75.     {
  76.       yield return (T) id.GetObject( mode, includingErased, false );
  77.     }
  78.   }
  79.  
  80.   public static T GetObject<T>( this ObjectId id )
  81.     where T: DBObject
  82.   {
  83.        return (T) id.GetObject( OpenMode.ForRead, false, false );
  84.   }
  85. }
  86.  
  87.  

Note that I don't call ToArray() in any of these, because that
would defeat the purpose/advantage of deferred execution, and
force the entire sequence to be evaluated. For example, pretending
for the moment that the BlockTable.Has() method and this[] indexer
did not exist, and you were trying to find out if a block with a given
name exists, you can use the above methods without forcing every
block to go through the test done by the Where() call in UserBlocks():

Code - C#: [Select]
  1.  
  2.   // useless example that emulates BlockTable.Has()
  3.   // in LINQ, to demonstrate deferred execution:
  4.  
  5.   Database db = HostApplicationServices.WorkingDatabase;
  6.  
  7.   using( Transaction tr = db.TransactionManager.StartTransaction() )
  8.   {
  9.  
  10.     bool found = db.UserBlockNames().Contains( "SINK" );
  11.  
  12.     if( ! found )
  13.       Console.WriteLine("No block named SINK was found");
  14.  
  15.     tr.Commit();
  16.   }
  17.    
  18.  
   
In the above code, if a block with the given name is found,
each of the chained iterators returned by the calls to each
of the extension methods used will stop enuemerating items
as soon as the block is found. That's because we don't call
ToArray(), which would otherwise cause the LINQ expression
to be executed on every BlockTableRecord in the database,
rather than on only those appearing before the found block.

For example, if your drawing contained 250 blocks, but the
"SINK" block was the 3rd entry in the block table, there will
only need to be three calls to the .Where(...) method, and
the Select(..) method before the call to Contains() returns
true. If you instead called ToArray() on any intermediate
result, then all 250 BlockTableRecords will be passed into
250 calls to Where(...), and 250 calls to Select(...).  :wink:

So, avoid the temptation to write methods that use LINQ and
then return the result as an array of T[], rather than as an
IEnumerable<T>, because that can lead to needless execution
in scenarious like the one above (searching for a block with
a given name).

Of course, if you need all the block names in an array, you
can just as easily call ToArray() on the result of a call to the
Database.UserBlockNames() method in your application code.

The UserBlocks() extension methods I show above are similar
to versions I have here with the same name, and I use them
frequently when I want to process only those blocks that are
'user-defined'.

edit: (author): Added missing call to GetObjects<T>()
« Last Edit: March 27, 2012, 03:08:25 AM by TheMaster »

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Iterating through block definitions by name
« Reply #11 on: March 27, 2012, 01:53:04 AM »
Shouldn't the IEnumerable<BlockTableRecord> UserBlocks() method be:
Code - C#: [Select]
  1.         public static IEnumerable<BlockTableRecord> UserBlocks(this Database db)
  2.         {
  3.             return ((IEnumerable)db.BlockTableId
  4.               .GetObject(OpenMode.ForRead, false, false))
  5.               .Cast<ObjectId>()
  6.               .Select(id => (BlockTableRecord)id.GetObject(OpenMode.ForRead, false, false))
  7.               .UserBlocks();
  8.         }

Or, with SymbolTable extension methods:
Code - C#: [Select]
  1.         public static IEnumerable<BlockTableRecord> UserBlocks(this Database db)
  2.         {
  3.             return db.BlockTableId
  4.                 .GetObject<BlockTable>()
  5.                 .GetObjects<BlockTableRecord>()
  6.                 .UserBlocks();;
  7.         }
Speaking English as a French Frog

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #12 on: March 27, 2012, 02:21:24 AM »
Sorry, yes. That's what happens when I hastily try to remove dependencies from my code so I can post it in a usable form  :oops:

The working version I use calls my overload of GetObjects<T> that targets SymbolTableRecord, and when I took the call to it out of the code, i forgot to replace it with the equivalent native API calls (which is what you show). :oops:

Thanks for pointing that out

Shouldn't the IEnumerable<BlockTableRecord> UserBlocks() method be:
Code - C#: [Select]
  1.         public static IEnumerable<BlockTableRecord> UserBlocks(this Database db)
  2.         {
  3.             return ((IEnumerable)db.BlockTableId
  4.               .GetObject(OpenMode.ForRead, false, false))
  5.               .Cast<ObjectId>()
  6.               .Select(id => (BlockTableRecord)id.GetObject(OpenMode.ForRead, false, false))
  7.               .UserBlocks();
  8.         }

Or, with SymbolTable extension methods:
Code - C#: [Select]
  1.         public static IEnumerable<BlockTableRecord> UserBlocks(this Database db)
  2.         {
  3.             return db.BlockTableId
  4.                 .GetObject<BlockTable>()
  5.                 .GetObjects<BlockTableRecord>()
  6.                 .UserBlocks();;
  7.         }

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #13 on: March 27, 2012, 03:09:25 AM »
Ok, the previous post was updated to use the added overloads
for SymbolTable.GetObjects<T>().
« Last Edit: March 27, 2012, 05:39:55 AM by TheMaster »

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #14 on: March 27, 2012, 06:00:19 AM »

Code - C#: [Select]
  1.    
  2.           .Where( btr =>
  3.                !btr.IsAnonymous &&
  4.                !btr.IsFromExternalReference &&
  5.                !btr.IsFromOverlayReference &&
  6.                !btr.IsLayout &&
  7.                !btr.IsAProxy
  8.            )
  9.  

Not trying to win the nic-picking contest, but here is
one more very minor issue that I would do in a slightly-
different way:

While negation (! bool) isn't a big deal, it is very slighly
more expensive than  ! ( bool || bool || bool ).

In other words, rather than:

    Where( ! bool1 && ! bool2 && ! bool3 && ! bool4 ....)

I would instead write it as:

    Where ( ! ( bool1 || bool2 || bool3 || bool4 ...) )

So...

Code - C#: [Select]
  1.    
  2.           .Where( btr =>
  3.                ! ( btr.IsAnonymous ||
  4.                     btr.IsFromExternalReference ||
  5.                     btr.IsFromOverlayReference ||
  6.                     btr.IsLayout ||
  7.                     btr.IsAProxy )
  8.            )
  9.  

Now we're really nit-picking   :lmao:

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: Iterating through block definitions by name
« Reply #15 on: March 27, 2012, 06:07:38 AM »
< .. >Now we're really nit-picking   :lmao:

 :-D
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #16 on: March 28, 2012, 12:27:08 PM »

Code - C#: [Select]
  1.    
  2.     else     // caller wants a subset, so filter by runtime class:
  3.     {
  4.       foreach( ObjectId id in source )
  5.       {
  6.         if( id.ObjectClass.IsSubclassOf( rxclass ) )
  7.         {
  8.           yield return (T) id.GetObject( mode, openErased, openLocked );
  9.         }
  10.       }
  11.     }
  12.   }
  13.  
  14.  


oops... sorry, the IsSubclassOf() method is another
one of my extension methods, which I forgot to include.

I edited the post and added it.

kaefer

  • Guest
Re: Iterating through block definitions by name
« Reply #17 on: March 29, 2012, 02:44:49 PM »
If there's only a limited set of specific types that my method can use (all of which implement IEnumerable), then I'd write an overload for each.

This condition isn't met when we consider that my method can use any instance of IEnumerable<ObjectId> too. I have to concede that contrary to gut feeling the number of types which implement IEnumerable (ICollection, IList) only non-generically is indeed limited. Over the complete API of a typical AutoCAD vertical there may be a handful of candidates, AecArchMgd for example seems to have none.

From acdbmgd:
Autodesk.AutoCAD.DatabaseServices.AttributeCollection
Autodesk.AutoCAD.DatabaseServices.BlockTableRecord
Autodesk.AutoCAD.DatabaseServices.ObjectIdCollection
Autodesk.AutoCAD.DatabaseServices.SymbolTable
Autodesk.AutoCAD.DatabaseServices.SymbolTableRecord
Autodesk.AutoCAD.LayerManager.LayerCollection

From aecbasemgd:
Autodesk.Aec.DatabaseServices.ClassificationCollection
Autodesk.Aec.DatabaseServices.DisplayRepresentationIdCollection
Autodesk.Aec.DatabaseServices.ObjectIdCollection


The underlying purpose of the various extension methods I showed is to simplify as much as possible, application-level code.

In my opinion, you optimize and therefore complicate as much as possible. The optimizations can be deemed premature; in the sense of premature in an application's life-cycle. I'm pretty confident that every single of them will result in a measurable if not noticeable performance increase. There's nothing wrong with having a well-tuned and well-tested toolbox at one's disposal when aiming for a commercial release. But the majority of uses are either to get the job done no questions asked, or to prototype, or to explain and explore specific concepts.

Back to the original theme, here's is my variation on iterating the BlockTable lazily. And here are the generic functions again, whose full signatures are inferred as:

Code - F#: [Select]
  1. val getObject :
  2.   Autodesk.AutoCAD.DatabaseServices.ObjectId ->
  3.     #Autodesk.AutoCAD.DatabaseServices.DBObject
  4. val getObjects<'T when 'T :> Autodesk.AutoCAD.DatabaseServices.DBObject> :
  5.   (System.Collections.IEnumerable ->
  6.      seq<#Autodesk.AutoCAD.DatabaseServices.DBObject>)
  7. val userBlocks :
  8.   (Autodesk.AutoCAD.DatabaseServices.ObjectId ->
  9.      seq<Autodesk.AutoCAD.DatabaseServices.BlockTableRecord>)
  10. val take : int -> 'a option -> (seq<'a> -> seq<'a>)

Top-level let-bound functions are compiled as static methods. getObject takes an ObjectId and downcasts to any subtype of DBObject, if one could be inferred. It doesn't depend on a type parameter, that is just a convenient way to supply a type at compile time. getObjects is our bone of contention, and take is a fully generic function.

Code - F#: [Select]
  1. open Autodesk.AutoCAD.DatabaseServices
  2. open Autodesk.AutoCAD.EditorInput
  3. open Autodesk.AutoCAD.Runtime
  4. type acadApp = Autodesk.AutoCAD.ApplicationServices.Application
  5.  
  6. // get DBObject for read and cast to 'T, if type parameter present
  7. let getObject<'T when 'T :> DBObject> (oid: ObjectId) =
  8.    oid.GetObject OpenMode.ForRead :?> 'T
  9.  
  10. // get those objects from an untyped IEnumerable that derive
  11. // from runtime class of type parameter
  12. let getObjects<'T when 'T :> DBObject> : System.Collections.IEnumerable -> _ =
  13.   let rx = RXClass.GetClass typeof<'T>
  14.    Seq.cast<ObjectId>
  15.    >> Seq.filter (fun oid -> oid.ObjectClass.IsDerivedFrom rx)
  16.    >> Seq.map getObject<'T>
  17.  
  18. let userBlocks =
  19.    getObject<BlockTable>
  20.    >> getObjects<BlockTableRecord>
  21.    >> Seq.filter (fun (btr : BlockTableRecord) ->
  22.        not(
  23.            btr.IsAnonymous ||
  24.            btr.IsFromExternalReference ||
  25.            btr.IsFromOverlayReference ||
  26.            btr.IsLayout ||
  27.            btr.IsAProxy ) )
  28.  
  29. let take n whenEndReached =
  30.    Seq.mapi (fun i v ->
  31.        if i < n then Some v
  32.        elif i = n then whenEndReached
  33.        else None )
  34.    >> Seq.takeWhile Option.isSome
  35.    >> Seq.map Option.get
  36.  
  37. [<CommandMethod "PrintTenBlockNames">]
  38. let printTenBlockNamesCommand() =
  39.    let db = HostApplicationServices.WorkingDatabase
  40.    let ed = acadApp.DocumentManager.MdiActiveDocument.Editor
  41.    use tr = db.TransactionManager.StartTransaction()
  42.  
  43.    let userBlockNamesToPrint =
  44.        db.BlockTableId
  45.        |> userBlocks
  46.        |> Seq.map (fun btr -> btr.Name)
  47.        |> take 10 (Some "...")
  48.  
  49.    for s in userBlockNamesToPrint do
  50.        ed.WriteMessage("\n{0} ", s)
  51.        
  52.    tr.Commit()

Edit: Seq.choose isn't lazy, Seq.takeWhile is
« Last Edit: March 29, 2012, 03:42:02 PM by kaefer »

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Iterating through block definitions by name
« Reply #18 on: March 29, 2012, 05:15:00 PM »
Maybe this question is quite different with F# where these functions aren't extension methods (or type extension).
Another thing with F# is that, as far I know, we can't overload functions, so if the getObject<> function uses the OpenMode.ForRead argument, Tony's Upgrade() method should be usefull and be something like this when converted to F#:

Code - F#: [Select]
  1. type AcEx = Autodesk.AutoCAD.Runtime.Exception
  2.  
  3. let upgradeOpen<'T when 'T :> DBObject> : 'T seq -> _ =
  4.    Seq.choose(fun dbo ->
  5.        if dbo.IsWriteEnabled then Some dbo
  6.        else try dbo.UpgradeOpen(); Some dbo
  7.             with | :? AcEx as ex when ex.ErrorStatus = ErrorStatus.OnLockedLayer -> None
  8.                  | _ -> raise (System.ArgumentException()))

With the getObject<> and getObjects<> function as defined  by kaefer, the WEEDLINES command may look like this:

Code - F#: [Select]
  1. [<CommandMethod("WEEDLINES")>]
  2. let weedLines() =
  3.     let db = HostApplicationServices.WorkingDatabase
  4.     use tr = db.TransactionManager.StartTransaction()
  5.     db.CurrentSpaceId
  6.     |> getObject<BlockTableRecord>
  7.     |> getObjects<Line>
  8.     |> Seq.filter(fun l -> l.Length < 0.1)
  9.     |> upgradeOpen
  10.     |> Seq.iter(fun l -> l.Erase())
  11.     tr.Commit()
Speaking English as a French Frog

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #19 on: March 29, 2012, 07:05:57 PM »
Maybe this question is quite different with F# where these functions aren't extension methods (or type extension).
Another thing with F# is that, as far I know, we can't overload functions, so if the getObject<> function uses the OpenMode.ForRead argument, Tony's Upgrade() method should be usefull and be something like this when converted to F#:

Code - F#: [Select]
  1. type AcEx = Autodesk.AutoCAD.Runtime.Exception
  2.  
  3. let upgradeOpen<'T when 'T :> DBObject> : 'T seq -> _ =
  4.    Seq.choose(fun dbo ->
  5.        if dbo.IsWriteEnabled then Some dbo
  6.        else try dbo.UpgradeOpen(); Some dbo
  7.             with | :? AcEx as ex when ex.ErrorStatus = ErrorStatus.OnLockedLayer -> None
  8.                  | _ -> raise (System.ArgumentException()))

With the getObject<> and getObjects<> function as defined  by kaefer, the WEEDLINES command may look like this:

Code - F#: [Select]
  1. [<CommandMethod("WEEDLINES")>]
  2. let weedLines() =
  3.     let db = HostApplicationServices.WorkingDatabase
  4.     use tr = db.TransactionManager.StartTransaction()
  5.     db.CurrentSpaceId
  6.     |> getObject<BlockTableRecord>
  7.     |> getObjects<Line>
  8.     |> Seq.filter(fun l -> l.Length < 0.1)
  9.     |> upgradeOpen
  10.     |> Seq.iter(fun l -> l.Erase())
  11.     tr.Commit()

In this case, overloading was used as a means of
implementing optional parameters, which are supported
directly in C# 4.0, so If i were limiting my code to C# 4.0,
there would be no need for overloading in this context:

Code - C#: [Select]
  1.  
  2. // Uses C# 4.0 optional arguments, no overloads needed:
  3.  
  4. public static class AcDbExtensions
  5. {
  6.  
  7.   public static T GetObject<T>( this ObjectId id,
  8.          OpenMode mode = OpenMode.ForRead,
  9.          bool openErased = false,
  10.          bool openObjectOnLockedLayer = false )
  11.     where T: DBObject
  12.   {
  13.        return (T) id.GetObject( mode, openErased, openObjectOnLockedLayer );
  14.   }
  15.  
  16.   public static IEnumerable<T> GetObjects<T>(
  17.     this BlockTableRecord btr,
  18.     OpenMode mode = OpenMode.ForRead,
  19.     bool openErased = false,
  20.     bool includeObjectsOnLockedLayers = false )  where T: Entity
  21.   {
  22.     BlockTableRecord source = openErased ? btr.IncludingErased : btr;
  23.     bool unfiltered = typeof( T ) == typeof( Entity );
  24.     bool openLocked = mode == OpenMode.ForWrite && includeObjectsOnLockedLayers;
  25.     RXClass rxclass = RXClass.GetClass( typeof( T ) );
  26.     if( unfiltered )
  27.     {
  28.       foreach( ObjectId id in source )
  29.       {
  30.         yield return (T) id.GetObject( mode, openErased, openLocked );
  31.       }
  32.     }
  33.     else     // caller wants a subset, so filter by runtime class:
  34.     {
  35.       foreach( ObjectId id in source )
  36.       {
  37.         if( id.ObjectClass.IsSubclassOf( rxclass ) )
  38.         {
  39.           yield return (T) id.GetObject( mode, openErased, openLocked );
  40.         }
  41.       }
  42.     }
  43.   }
  44.  
  45. }
  46.  
  47.  

The only issue with optional parameters in C# 4.0 that you need to be careful with, is that they are not really optional arguments at all  :lmao:, they're just a compiler trick, and there is a pitfall to their use. When you declare a method that takes an optional argument and call  the method without supplying a value for the optional argument, the generated code passes the default value specified in the method declaration, as if the optional argument were explicitly specified in the call. The issue with that is that the default value you specify in the method declaration is copied to the call site. If the call site is in another assembly, and you then change the default value for the optional argument and recompile the assembly containing the method, the 'old' default value will continue to be passed when the call comes from another assembly that was not also recompiled against the revised assembly containing the method with the optional argument.

Jeff H

  • Needs a day job
  • Posts: 6144
Re: Iterating through block definitions by name
« Reply #20 on: May 02, 2012, 11:56:08 AM »
Just wanted to add that been real busy and not much time to play around but the little code that tested and refactored that is a little different but based on Tony's  extension examples  the more I realize how useful implementing those extensions classes are.
 
It sometimes get tough figuring out what abstractions you need to make and the full consequences of them due to lack of knowledge in areas of the API, but being able to filter, group, create an anonymous type with the properties needed etc.... just helps a ton.
 
Sometimes forget that to AutoCAD is a database and to look at it as if you were query database with all the ins, out, different ways depending on this or that, and gotchas to acessing data, but these help with a nice clean way to get, edit, display, etc....
 
Thanks again Tony
 
If you have not read the examples or messed with them I definitely would,
or of course you could wait a year so until Autodesk 'borrows' it >:D
 
 

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #21 on: May 09, 2012, 05:58:03 AM »
I must have missed this reply, and saw it just now when I went to get the link to this thread.

See below:


If there's only a limited set of specific types that my method can use (all of which implement IEnumerable), then I'd write an overload for each.


This condition isn't met when we consider that my method can use any instance of IEnumerable<ObjectId> too.


Your method isn't using IEnumerable<ObjectId>, it's using IEnumerable and IEnumerator. IEnumerator.Current returns the value boxed in a System.Object.  While it's true that foreach() also uses IEnumerator on types that implement only IEnumerable, the only overload of GetObjects<T>() that I have here that uses foreach() is the one targeting BlockTableRecord, SymbolTable, and IEnumerable<ObjectId>, and the latter will test its argument to see if it is an array and in that case, will delegate to the overload that takes an array of ObjectId[], which uses the array indexer to iterate over the items (because it's faster).

I also have an overload of GetObjects<T> that takes an IEnumerable<ObjectId>, and it is by-far them most complicated of all the overloads, because it tests the argument to see if it is an array of ObjectId[] and uses another overload that is optimized for arrays.  So, I think I have all the bases covered with my overloads.

Ditto for ObjectIdCollection. Here is a reduced version of one of my implementations of GetObjects<T> for ObjectIdCollection:

Code - C#: [Select]
  1.  
  2.   public static IEnumerable<T> GetObjects<T>( this ObjectIdCollection ids )
  3.   {
  4.        RXClass rxclass = RXClass.GetClass( typeof( T ) );
  5.        int cnt = ids.Count;
  6.        for( int i = 0; i < cnt; i++ )
  7.        {
  8.            ObjectId id = ids[i];
  9.            if( id.ObjectClass.IsDerivedFrom( rxclass ) )
  10.               yield return (T) id.GetObject( OpenMode.ForRead, false, false );
  11.        }
  12.    }
  13.  
  14.  

And, I'm not sure what this has do with a method called "GetObjects()" showing up in the Intellisense dropdown list for an instance of anything that implements IEnumerable, and I have a major issue with that.

There really is no excuse for not providing overloads targeting the handful of types that expose a sequence of ObjectIds, especially considering that each also has other distinct variations (like supporting erased objects) that don't apply all IEnumerables. For example, SymbolTable and BlockTableRecord both support the IncludingErased property, so you can't write a single one-size-fits-all version of GetObjects<T> that accommodates that option, or other parameters that do not apply to any IEnumerable, without introducing even more complexity than multiple overloads entails.

Quote
I have to concede that contrary to gut feeling the number of types which implement IEnumerable (ICollection, IList) only non-generically is indeed limited. Over the complete API of a typical AutoCAD vertical there may be a handful of candidates, AecArchMgd for example seems to have none.

From acdbmgd:
Autodesk.AutoCAD.DatabaseServices.AttributeCollection
Autodesk.AutoCAD.DatabaseServices.BlockTableRecord
Autodesk.AutoCAD.DatabaseServices.ObjectIdCollection
Autodesk.AutoCAD.DatabaseServices.SymbolTable
Autodesk.AutoCAD.DatabaseServices.SymbolTableRecord
Autodesk.AutoCAD.LayerManager.LayerCollection

From aecbasemgd:
Autodesk.Aec.DatabaseServices.ClassificationCollection
Autodesk.Aec.DatabaseServices.DisplayRepresentationIdCollection
Autodesk.Aec.DatabaseServices.ObjectIdCollection


The underlying purpose of the various extension methods I showed is to simplify as much as possible, application-level code.

In my opinion, you optimize and therefore complicate as much as possible. The optimizations can be deemed premature; in the sense of premature in an application's life-cycle.


Apparently you didn't get the meaning of 'application-level' code. The complicated library code is what makes it possible for me to write application-level code that is far less complicated. By application-level code, I mean implementation of simple commands and so forth, that one might otherwise write in LISP. But, in fact, the API's I showed a small glimpse of here, makes writing simple commands easier in .NET than they would be if written in LISP.

I usually optimize (and complicate) library code that is called often but rarely if ever modified, making its complexity irrelevant in the everyday use of it. The whole point of making the library code complicated is to un-complicate and simplify the application-level code that uses the library code.

Quote
I'm pretty confident that every single of them will result in a measurable if not noticeable performance increase. There's nothing wrong with having a well-tuned and well-tested toolbox at one's disposal when aiming for a commercial release. But the majority of uses are either to get the job done no questions asked, or to prototype, or to explain and explore specific concepts.

In a post in another thread that I just wrote, I was too lazy to write a code example that uses only native APIs to do something relatively simple in an example command implementation. So, I used the same APIs that I use routinely in my real coding to get the job done more quickly, no questions asked.

It is that complicated API with all of its complicated overloads of GetObjects<T>() that allows me to write much simpler application-level code, much faster and with far fewer errors. As far as a single one-size-fits-all version of GetObjects() goes, that doesn't work because it can't easily provide for options for some types, like including erased objects, etc. 
« Last Edit: May 18, 2012, 02:46:41 PM by TheMaster »

fixo

  • Guest
Re: Iterating through block definitions by name
« Reply #22 on: May 10, 2012, 09:57:06 AM »
Sorry for off-topic
Can you read your PM

CADbloke

  • Bull Frog
  • Posts: 342
  • Crash Test Dummy
Re: Iterating through block definitions by name
« Reply #23 on: May 07, 2013, 10:48:48 PM »
... Here is a reduced version of one of my implementations of GetObjects<T> for ObjectIdCollection:

Code - C#: [Select]
  1.  
  2.   public static IEnumerable<T> GetObjects<T>( this ObjectIdCollection ids )
  3.   {
  4.        RXClass rxclass = RXClass.GetClass( typeof( T ) );
  5.        int cnt = ids.Count;
  6.        for( int i = 0; i < cnt; i++ )
  7.        {
  8.            ObjectId id = ids[i];
  9.            if( id.ObjectClass.IsDerivedFrom( rxclass ) )
  10.               yield return (T) id.GetObject( OpenMode.ForRead, false, false );
  11.        }
  12.    }
  13.  
  14.  

This wouldn't compile for me - Cannot convert type 'Autodesk.AutoCAD.DatabaseServices.DBObject' to 'T' on line 10. Updating Line 2 fixed it ....
     
Code - C#: [Select]
  1. public static IEnumerable<T> GetObjects<T>( this ObjectIdCollection ids )  where T: Entity

Now we're REALLY nitpicking. Great discussion to everyone. Thanks. :)
« Last Edit: May 08, 2013, 05:39:26 AM by CADbloke »

Micaletti

  • Guest
Re: Iterating through block definitions by name
« Reply #24 on: May 07, 2013, 11:33:43 PM »
Wow. Just when I start to feel smug and nerdy, 6 real nerds come along and remind me that I'm just a geek.

TheMaster

  • Guest
Re: Iterating through block definitions by name
« Reply #25 on: May 09, 2013, 12:33:13 AM »
... Here is a reduced version of one of my implementations of GetObjects<T> for ObjectIdCollection:

Code - C#: [Select]
  1.  
  2.   public static IEnumerable<T> GetObjects<T>( this ObjectIdCollection ids )
  3.   {
  4.        RXClass rxclass = RXClass.GetClass( typeof( T ) );
  5.        int cnt = ids.Count;
  6.        for( int i = 0; i < cnt; i++ )
  7.        {
  8.            ObjectId id = ids[i];
  9.            if( id.ObjectClass.IsDerivedFrom( rxclass ) )
  10.               yield return (T) id.GetObject( OpenMode.ForRead, false, false );
  11.        }
  12.    }
  13.  
  14.  

This wouldn't compile for me - Cannot convert type 'Autodesk.AutoCAD.DatabaseServices.DBObject' to 'T' on line 10. Updating Line 2 fixed it ....
     
Code - C#: [Select]
  1. public static IEnumerable<T> GetObjects<T>( this ObjectIdCollection ids )  where T: Entity

Now we're REALLY nitpicking. Great discussion to everyone. Thanks. :)


Yes, sorry. There is a missing constraint, but it should be where T: DBObject, verses Entity, since ObjectIdCollection can contain ids of any DBObject.