Author Topic: Looking for feedback: Looking through the viewport  (Read 2163 times)

0 Members and 1 Guest are viewing this topic.

WILL HATCH

  • Bull Frog
  • Posts: 448
Looking for feedback: Looking through the viewport
« on: January 07, 2013, 05:50:48 PM »
Hi,

I'm looking for some criticism on a piece of code I've assembled.  The goal here was to automate pile layout drawings and pile schedule generation.  I'm posting this because when I was developing this application it was the first time I was unable to just string together a few snippets of code to find a complete solution and should anybody be following in this direction they can use it as a start point.

Special thanks to:
Kean Walmsley for guiding most of my AutoCAD.net learning curve, but especially the snippet on handling dynamic blocks
Tony Tanzillo for ViewportExtensionMethods
Everyone else on theswamp.org

Assumptions:
-Command is to be run from paperspace
-Piles are steel objects drawn in CADWorx and are uniquely identified with a part number (they are the only numbered parts in a typical model)
-Piles are labelled in paperspace with a dynamic block called PileTag (wanted to give it a grip to rotate)

Thanks!

WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Looking for feedback: Looking through the viewport
« Reply #1 on: January 07, 2013, 06:01:53 PM »
a drawing that this example will work on in case needed

Jeff H

  • Needs a day job
  • Posts: 6075
Re: Looking for feedback: Looking through the viewport
« Reply #2 on: January 07, 2013, 10:21:03 PM »
Might get a chance to look at code tomorrow but to maybe help with assumption that code can only be used from paperspace could use
CommanFlags.NoTileMode

WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Looking for feedback: Looking through the viewport
« Reply #3 on: January 14, 2013, 03:44:01 PM »
I had hoped for some very critical comments from some of the resident experts around here.  Maybe asking some more specific questions will help.

In the following function I look through a complete list of paperspace viewports to find out which viewport is under my point before I pass them off to be transformed (which requires a viewport)
Code - C#: [Select]
  1. //converts paperspace coordinates to modelspace through the viewport under the paperspace coordinate
  2.         public Point3d ps2ms(Document doc, Database db, Editor ed, Point3d PSpoint, List<Viewport> lviews)
  3.         {
  4.  
  5.             Viewport vp = new Viewport();
  6.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  7.             {
  8.                 //find which viewport is under the coordinate
  9.                 foreach (Viewport vport in lviews)
  10.                 {
  11.                     //ed.WriteMessage("\n{0},{1}", vport.Bounds, PSpoint);
  12.                     if (contained(PSpoint, (Extents3d)vport.Bounds))
  13.                     {
  14.                         //ed.WriteMessage("\nYAY!");
  15.                         vp = vport;
  16.                         break;
  17.                     }
  18.                 }
  19.  
  20.                 tr.Commit();
  21.             }
  22.             //pass to extension methods
  23.             return ViewportExtensionMethods.PaperToModel(PSpoint, vp);
  24.         }
  25.  

Is there a better way to do this?


Also, in the following function where I look at all the piles visible under a viewport I am iterating through my entire collection of pile objects
Code - C#: [Select]
  1. //returns a list of piles visible in a viewport
  2.         public List<PileDataSet> pilesInViewport(List<PileDataSet> piles, List<Viewport> lviews, Document doc, Database db)
  3.         {
  4.             List<PileDataSet> pilesInView = new List<PileDataSet>();
  5.             for (int n = piles.Count - 1; n >= 0; n--)
  6.             {
  7.                 PileDataSet pile = piles[n];
  8.                 foreach (Viewport vp in lviews)
  9.                 {
  10.                     //convert model coordinate to layout coordinate
  11.                     Point3d pt = ViewportExtensionMethods.ModelToPaper(new Point3d(pile.Location.X, pile.Location.Y, 0), vp);
  12.                     //ed.WriteMessage("\n{0},{1},{2}", pile.PileTag, vp.Bounds, pt);
  13.                     //check if the corresponding layout coordinate is withing the viewport bounds
  14.                     if (contained(pt, (Extents3d)vp.Bounds))
  15.                     {
  16.                         //if so add the coordinate to the pile object and add it to the list
  17.                         pile.PSLocation = pt;
  18.                         pilesInView.Add(pile);
  19.                     }
  20.                 }
  21.             }
  22.             return pilesInView;
  23.         }
  24.  

Is there a database function similar to the editor select window that will return all objects within a given bound which I could use to minimize needless processing?

Thanks!

TheMaster

  • Guest
Re: Looking for feedback: Looking through the viewport
« Reply #4 on: January 14, 2013, 07:23:07 PM »
I had hoped for some very critical comments from some of the resident experts around here.  Maybe asking some more specific questions will help.

In the following function I look through a complete list of paperspace viewports to find out which viewport is under my point before I pass them off to be transformed (which requires a viewport)
Code - C#: [Select]
  1. //converts paperspace coordinates to modelspace through the viewport under the paperspace coordinate
  2.         public Point3d ps2ms(Document doc, Database db, Editor ed, Point3d PSpoint, List<Viewport> lviews)
  3.         {
  4.  
  5.             Viewport vp = new Viewport();
  6.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  7.             {
  8.                 //find which viewport is under the coordinate
  9.                 foreach (Viewport vport in lviews)
  10.                 {
  11.                     //ed.WriteMessage("\n{0},{1}", vport.Bounds, PSpoint);
  12.                     if (contained(PSpoint, (Extents3d)vport.Bounds))
  13.                     {
  14.                         //ed.WriteMessage("\nYAY!");
  15.                         vp = vport;
  16.                         break;
  17.                     }
  18.                 }
  19.  
  20.                 tr.Commit();
  21.             }
  22.             //pass to extension methods
  23.             return ViewportExtensionMethods.PaperToModel(PSpoint, vp);
  24.         }
  25.  

Is there a better way to do this?


Also, in the following function where I look at all the piles visible under a viewport I am iterating through my entire collection of pile objects
Code - C#: [Select]
  1. //returns a list of piles visible in a viewport
  2.         public List<PileDataSet> pilesInViewport(List<PileDataSet> piles, List<Viewport> lviews, Document doc, Database db)
  3.         {
  4.             List<PileDataSet> pilesInView = new List<PileDataSet>();
  5.             for (int n = piles.Count - 1; n >= 0; n--)
  6.             {
  7.                 PileDataSet pile = piles[n];
  8.                 foreach (Viewport vp in lviews)
  9.                 {
  10.                     //convert model coordinate to layout coordinate
  11.                     Point3d pt = ViewportExtensionMethods.ModelToPaper(new Point3d(pile.Location.X, pile.Location.Y, 0), vp);
  12.                     //ed.WriteMessage("\n{0},{1},{2}", pile.PileTag, vp.Bounds, pt);
  13.                     //check if the corresponding layout coordinate is withing the viewport bounds
  14.                     if (contained(pt, (Extents3d)vp.Bounds))
  15.                     {
  16.                         //if so add the coordinate to the pile object and add it to the list
  17.                         pile.PSLocation = pt;
  18.                         pilesInView.Add(pile);
  19.                     }
  20.                 }
  21.             }
  22.             return pilesInView;
  23.         }
  24.  

Is there a database function similar to the editor select window that will return all objects within a given bound which I could use to minimize needless processing?

Thanks!

Viewports can overlap, so there could be more than one that shows a given point in model space.  Given that, I don't think there's any built-in way to do it, but the View class has a PointInView() method that might be helpful.

SelectWindow() and other graphical selection only works on drawings in the editor and they're view-dependent which means that they could give different results depending on the zoom magnification.

You could use points on the objects or their bounding boxes to eliminate those that aren't in or crossing a box.

TheMaster

  • Guest
Re: Looking for feedback: Looking through the viewport
« Reply #5 on: January 14, 2013, 07:28:56 PM »
Quote
Code: [Select]

  ViewportExtensionMethods.PaperToModel(PSpoint, vp);


You might find that it's easier to use extension methods as they were designed to be used, like this:

Code: [Select]

   ViewPort vp = .....
   Point3d msPoint = vp.PaperToModel( PSPoint );


That allows the extension method to be used without having to remember the name of the class that contains it.

WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Looking for feedback: Looking through the viewport
« Reply #6 on: January 14, 2013, 07:33:00 PM »
Thanks Tony!  In our drawings here we don't experience overlapping viewports (at least by drafting standards..) so I didn't think to include that option.  I read in another thread once where you mention spatial indexing as a method to solve some problems.  I was wondering if there is a good way to do that using LINQ, but I haven't gotten as far as proposing a solution as I'm very much a noob in that field.  But it seems to be extremely fast at grabbing objects.

Quote
Code: [Select]

  ViewportExtensionMethods.PaperToModel(PSpoint, vp);


You might find that it's easier to use extension methods as they were designed to be used, like this:

Code: [Select]

   ViewPort vp = .....
   Point3d msPoint = vp.PaperToModel( PSPoint );


That allows the extension method to be used without having to remember the name of the class that contains it.

I really like that! It looks a lot cleaner.

Cheers!
« Last Edit: January 14, 2013, 07:52:26 PM by WILL HATCH »

TheMaster

  • Guest
Re: Looking for feedback: Looking through the viewport
« Reply #7 on: January 15, 2013, 05:35:55 AM »
Thanks Tony!  In our drawings here we don't experience overlapping viewports (at least by drafting standards..) so I didn't think to include that option.  I read in another thread once where you mention spatial indexing as a method to solve some problems.  I was wondering if there is a good way to do that using LINQ, but I haven't gotten as far as proposing a solution as I'm very much a noob in that field.  But it seems to be extremely fast at grabbing objects.

LINQ is a generalized library that is designed to work with anything that can be used in a 'foreach()' loop.  It's not a high-level solution to problems like spatial indexing. You can probably find some implementions of it in C# but I doubt they make extensive use of LINQ, especially since they tend to work with arrays almost exclusively rather than sequences of unknown length (eg., IEnumerable and IEnumerable<T>). Try Googling for 'C# & spatial indexing'.

BTW, I noticed you mentioned having gotten some of your code from Kean,s blog, but you didn't specifically point out what code that was. I'm guessing that it's the 'recursive block iteration' code I remember him posting. I remember it because I got a good laugh out of that one, because it has a major issue in that it will repeatedly process the same block definition many times (exactly as many times as it finds insertions of the block), so if for example, you have block 'A', which contains a dozen insertions of block 'B', that code will iterate through the entities in the definition of block 'B', a dozen times, doing exactly the same thing each time  :roll:

So long as none of the blocks you're throwing at that code contain multiple insertions of another block, there shouldn't be a problem, but otherwise, it will be doing a lot of extra work for nothing, and could also result in errors, depending on what is coming out.



WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Looking for feedback: Looking through the viewport
« Reply #8 on: January 15, 2013, 09:48:46 AM »
Thanks again, I'll look into that today along with extension methods (which I obviously don't understand after what you showed me yesterday)

Yes I use Kean's method for iterating through all the table records in the drawings, and his method for recognizing dynamic blocks.  The first works well in this situation because our models have only external references (very few traditional blocks) and it handles looking through all external references.  The dynamic block method irritates me because I've learned more about dynamic block properties since then and that whole mess of looking at xdata can be replaced by looking at the isdynamic and dynamicblocktablerecord properties.


I appreciate the shared knowledge!  There is much to learn yet!

Will

WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Looking for feedback: Looking through the viewport
« Reply #9 on: January 15, 2013, 05:00:13 PM »
I read somewhere recently in a post that using LINQ is fast because it does not open objects through a transaction but looks at the properties of the object.  To explore this, I created 2 new versions of the GetLabelInserts() function which are identical from the outside.  One differs from Kean's method by using the BlockReference.IsDynamicBlock and .DynamicBlockTableRecord properties rather than iterating through xdata, the other uses the AcDbExtensions.GetObjects<>() that I found on the mentioned post.

In summary:
Kean's method is fastest with less than about 300 blocks to query
My modification is slightly faster on large datasets (check out the last result.. 1000001 blocks!)
LINQ is much slower than I expected... Can anybody out there show me something I've done wrong to make the LINQ expression run so slow?

Also, can anyone comment further on what is really happening behind the scenes to create these results?

Thanks!

Code - C#: [Select]
  1. [CommandMethod("TestDynamic")]
  2.         public void getDynamicBlocks()
  3.         {
  4.             Document doc = Application.DocumentManager.MdiActiveDocument;
  5.             Database db = doc.Database;
  6.             Editor ed = doc.Editor;
  7.             PromptEntityOptions peo = new PromptEntityOptions("\nSelect Block");
  8.             peo.SetRejectMessage("\nNot a BlockReference!");
  9.             peo.AddAllowedClass(typeof(BlockReference), true);
  10.             PromptEntityResult per = ed.GetEntity(peo);
  11.             if (per.Status != PromptStatus.OK)
  12.                 return;
  13.             string BlockName;
  14.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  15.             {
  16.                 BlockReference br = (BlockReference)tr.GetObject(per.ObjectId, OpenMode.ForRead);
  17.                 BlockTableRecord testblockdef = (BlockTableRecord)tr.GetObject(br.DynamicBlockTableRecord, OpenMode.ForRead);
  18.                 BlockName = testblockdef.Name;
  19.             }
  20.             Stopwatch timer = new Stopwatch();
  21.             timer.Start();
  22.             List<BlockReference> dynamicblocks = GetDynamicLabelInserts(doc, db, ed, BlockName);
  23.             timer.Stop();
  24.             ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using LINQ method", timer.Elapsed, timer.ElapsedTicks, dynamicblocks.Count, timer.ElapsedTicks / dynamicblocks.Count);
  25.             timer.Reset();
  26.             timer.Start();
  27.             List<BlockReference> mydynamicblocks = MyGetDynamicLabelInserts(doc, db, ed, BlockName);
  28.             timer.Stop();
  29.             ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using my method", timer.Elapsed, timer.ElapsedTicks, mydynamicblocks.Count, timer.ElapsedTicks/ mydynamicblocks.Count);
  30.             timer.Reset();
  31.             timer.Start();
  32.             List<BlockReference> xdatablocks = GetLabelInserts(doc, db, ed, BlockName);
  33.             timer.Stop();
  34.             ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using Kean's method", timer.Elapsed, timer.ElapsedTicks, xdatablocks.Count, timer.ElapsedTicks/ xdatablocks.Count);
  35.             timer.Reset();//  <---- Fixed... Thanks Jeff!
  36.             timer.Start();
  37.             List<BlockReference> dynamicblocks2 = GetDynamicLabelInserts(doc, db, ed, BlockName);
  38.             timer.Stop();
  39.             ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using LINQ method", timer.Elapsed, timer.ElapsedTicks, dynamicblocks2.Count, timer.ElapsedTicks/ dynamicblocks2.Count);
  40.             timer.Reset();
  41.             timer.Start();
  42.             List<BlockReference> mydynamicblocks2 = MyGetDynamicLabelInserts(doc, db, ed, BlockName);
  43.             timer.Stop();
  44.             ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using my method", timer.Elapsed, timer.ElapsedTicks, mydynamicblocks2.Count, timer.ElapsedTicks/ mydynamicblocks2.Count);
  45.             timer.Reset();
  46.             timer.Start();
  47.             List<BlockReference> xdatablocks2 = GetLabelInserts(doc, db, ed, BlockName);
  48.             timer.Stop();
  49.             ed.WriteMessage("\nTook {0} ({1} ticks) to get {2} blocks ({3} ticks/block) using Kean's method", timer.Elapsed, timer.ElapsedTicks, xdatablocks2.Count, timer.ElapsedTicks/ xdatablocks2.Count);
  50.            
  51.         }
  52.  
  53.         // LINQ method
  54.         public List<BlockReference> GetDynamicLabelInserts(Document doc, Database db, Editor ed, string BlockName)
  55.         {
  56.             List<BlockReference> labels = new List<BlockReference>();
  57.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  58.             {
  59.                 BlockTable bt =
  60.                     (BlockTable)tr.GetObject(
  61.                     db.BlockTableId,
  62.                     OpenMode.ForRead
  63.                     );
  64.                 //don't waste time if the block isn't in the drawing
  65.                 if (!bt.Has(BlockName))
  66.                 {
  67.                     ed.WriteMessage("/nCannot find block {0}", BlockName);
  68.                     return labels;
  69.                 }
  70.                 BlockTableRecord ps = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.PaperSpace], OpenMode.ForRead);
  71.                 BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockName], OpenMode.ForRead);
  72.                 Handle blkHandle = btr.Handle;
  73.                 IEnumerable<BlockReference> potentials = ps.GetObjects<BlockReference>(OpenMode.ForRead).Where(BlockReference => BlockReference.Name == BlockName || BlockReference.IsDynamicBlock == true);
  74.                 foreach (BlockReference block in potentials)
  75.                 {
  76.                     if (block.Name == BlockName)
  77.                     {
  78.                         labels.Add(block);
  79.                         continue;
  80.                     }
  81.                     BlockTableRecord blockDef = (BlockTableRecord)tr.GetObject(block.DynamicBlockTableRecord, OpenMode.ForRead);
  82.                     if (blockDef.Name == BlockName)
  83.                     {
  84.                         labels.Add(block);
  85.                         continue;
  86.                     }
  87.                 }
  88.                 tr.Commit();
  89.             }
  90.             return labels;
  91.         }
  92.  
  93.         // My mod to Kean's method
  94.         public List<BlockReference> MyGetDynamicLabelInserts(Document doc, Database db, Editor ed, string BlockName)
  95.         {
  96.             List<BlockReference> labels = new List<BlockReference>();
  97.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  98.             {
  99.                 BlockTable bt =
  100.                     (BlockTable)tr.GetObject(
  101.                     db.BlockTableId,
  102.                     OpenMode.ForRead
  103.                     );
  104.                 //don't waste time if the block isn't in the drawing
  105.                 if (!bt.Has(BlockName))
  106.                 {
  107.                     ed.WriteMessage("/nCannot find block {0}", BlockName);
  108.                     return labels;
  109.                 }
  110.                 BlockTableRecord ps = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.PaperSpace], OpenMode.ForRead);
  111.                 BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockName], OpenMode.ForRead);
  112.                 Handle blkHandle = btr.Handle;
  113.                 foreach (ObjectId id in ps)
  114.                 {
  115.                     Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
  116.                     if (ent != null)
  117.                     {
  118.                         BlockReference br = ent as BlockReference;
  119.                         if (br != null)
  120.                         {
  121.                             if (br.Name == BlockName)
  122.                             {
  123.                                 labels.Add(br);
  124.                             }
  125.                             else
  126.                             {
  127.                                 if (br.IsDynamicBlock == true)
  128.                                 {
  129.                                     BlockTableRecord btr2 = (BlockTableRecord)tr.GetObject(br.DynamicBlockTableRecord, OpenMode.ForRead);
  130.                                     if (btr2.Name == BlockName)
  131.                                     {
  132.                                         labels.Add(br);
  133.                                     }
  134.                                 }
  135.                             }
  136.                         }
  137.                     }
  138.                 }
  139.                 tr.Commit();
  140.             }
  141.             return labels;
  142.         }
  143.         //Kean's method
  144.         public List<BlockReference> GetLabelInserts(Document doc, Database db, Editor ed, string BlockName)
  145.         {
  146.             List<BlockReference> labels = new List<BlockReference>();
  147.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  148.             {
  149.                 BlockTable bt =
  150.                     (BlockTable)tr.GetObject(
  151.                     db.BlockTableId,
  152.                     OpenMode.ForRead
  153.                     );
  154.                 //don't waste time if the block isn't in the drawing
  155.                 if (!bt.Has(BlockName))
  156.                 {
  157.                     ed.WriteMessage("/nCannot find block {0}", BlockName);
  158.                     return labels;
  159.                 }
  160.                 BlockTableRecord ps = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.PaperSpace], OpenMode.ForRead);
  161.                 BlockTableRecord btr = (BlockTableRecord)tr.GetObject(bt[BlockName], OpenMode.ForRead);
  162.                 Handle blkHandle = btr.Handle;
  163.                 foreach (ObjectId id in ps)
  164.                 {
  165.                     Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
  166.                     if (ent != null)
  167.                     {
  168.                         BlockReference br = ent as BlockReference;
  169.                         if (br != null)
  170.                         {
  171.                             BlockTableRecord bd =
  172.                                 (BlockTableRecord)tr.GetObject(
  173.                                 br.BlockTableRecord,
  174.                                 OpenMode.ForRead
  175.                                 );
  176.                             if (bd.Name == BlockName)
  177.                             {
  178.                                 labels.Add(br);
  179.                                 //ed.WriteMessage("\n{0},{1}", br.Position, br.Rotation);
  180.                                
  181.                             }
  182.                             else
  183.                             {
  184.  
  185.                                 BlockTableRecord btr2 = (BlockTableRecord)tr.GetObject(br.BlockTableRecord, OpenMode.ForRead);
  186.                                 var xdata = btr2.XData;
  187.                                 if (xdata != null)
  188.                                 {
  189.                                     // Get the XData as an array of TypeValues and loop
  190.                                     // through it
  191.  
  192.                                     var tvs = xdata.AsArray();
  193.                                     for (int i = 0; i < tvs.Length; i++)
  194.                                     {
  195.                                         // The first value should be the RegAppName
  196.  
  197.                                         var tv = tvs[i];
  198.                                         if (
  199.                                           tv.TypeCode == (int)DxfCode.ExtendedDataRegAppName
  200.                                         )
  201.                                         {
  202.                                             // If it's the one we care about...
  203.  
  204.                                             if ((string)tv.Value == "AcDbBlockRepBTag")
  205.                                             {
  206.                                                 // ... then loop through until we find a
  207.                                                 // handle matching our blocks or otherwise
  208.                                                 // another RegAppName
  209.  
  210.                                                 for (int j = i + 1; j < tvs.Length; j++)
  211.                                                 {
  212.                                                     tv = tvs[j];
  213.                                                     if (
  214.                                                       tv.TypeCode ==
  215.                                                         (int)DxfCode.ExtendedDataRegAppName
  216.                                                     )
  217.                                                     {
  218.                                                         // If we have another RegAppName, then
  219.                                                         // we'll break out of this for loop and
  220.                                                         // let the outer loop have a chance to
  221.                                                         // process this section
  222.  
  223.                                                         i = j - 1;
  224.                                                         break;
  225.                                                     }
  226.  
  227.                                                     if (
  228.                                                       tv.TypeCode ==
  229.                                                         (int)DxfCode.ExtendedDataHandle
  230.                                                     )
  231.                                                     {
  232.                                                         // If we have a matching handle...
  233.  
  234.                                                         if ((string)tv.Value == blkHandle.ToString())
  235.                                                         {
  236.                                                             // ... then we can add the block's name
  237.                                                             // to the list and break from both loops
  238.                                                             // (which we do by setting the outer index
  239.                                                             // to the end)
  240.  
  241.                                                             labels.Add(br);
  242.                                                             //ed.WriteMessage("\n{0},{1}", br.Position, br.Rotation);
  243.                                
  244.                                                             i = tvs.Length - 1;
  245.                                                             break;
  246.                                                         }
  247.                                                     }
  248.                                                 }
  249.                                             }
  250.                                         }
  251.                                     }
  252.                                 }
  253.                             }
  254.                         }
  255.                     }
  256.                 }
  257.                 tr.Commit();
  258.             }
  259.             return labels;
  260.         }
  261.  

Command: TESTDYNAMIC

Select Block:
Took 00:00:00.0007665 (2401 ticks) to get 1 blocks (2401 ticks/block) using LINQ method
Took 00:00:00.0007394 (2316 ticks) to get 1 blocks (2316 ticks/block) using my method
Took 00:00:00.0004492 (1407 ticks) to get 1 blocks (1407 ticks/block) using Kean's method
Took 00:00:00.0011471 (3593 ticks) to get 1 blocks (3593 ticks/block) using LINQ method         CORRECTION: 2186 ticks/block
Took 00:00:00.0006363 (1993 ticks) to get 1 blocks (1993 ticks/block) using my method
Took 00:00:00.0004025 (1261 ticks) to get 1 blocks (1261 ticks/block) using Kean's method


_________________________________

Command: TESTDYNAMIC
Select Block:
Took 00:00:00.0009823 (3077 ticks) to get 18 blocks (170 ticks/block) using LINQ method
Took 00:00:00.0008387 (2627 ticks) to get 18 blocks (145 ticks/block) using my method
Took 00:00:00.0005635 (1765 ticks) to get 18 blocks (98 ticks/block) using Kean's method
Took 00:00:00.0014887 (4663 ticks) to get 18 blocks (259 ticks/block) using LINQ method         CORRECTION: 161 ticks/block
Took 00:00:00.0007477 (2342 ticks) to get 18 blocks (130 ticks/block) using my method
Took 00:00:00.0005245 (1643 ticks) to get 18 blocks (91 ticks/block) using Kean's method

__________________________________

TESTDYNAMIC
Select Block:
Took 00:00:00.0041345 (12950 ticks) to get 343 blocks (37 ticks/block) using LINQ method
Took 00:00:00.0021371 (6694 ticks) to get 343 blocks (19 ticks/block) using my method
Took 00:00:00.0019890 (6230 ticks) to get 343 blocks (18 ticks/block) using Kean's method
Took 00:00:00.0050383 (15781 ticks) to get 343 blocks (46 ticks/block) using LINQ method         CORRECTION: 28 ticks/block
Took 00:00:00.0018846 (5903 ticks) to get 343 blocks (17 ticks/block) using my method
Took 00:00:00.0018721 (5864 ticks) to get 343 blocks (17 ticks/block) using Kean's method

_________________________________

Command: TESTDYNAMIC
Select Block:
Took 00:00:00.0115527 (36185 ticks) to get 818 blocks (44 ticks/block) using LINQ method
Took 00:00:00.0053515 (16762 ticks) to get 818 blocks (20 ticks/block) using my method
Took 00:00:00.0060287 (18883 ticks) to get 818 blocks (23 ticks/block) using Kean's method
Took 00:00:00.0115052 (36036 ticks) to get 818 blocks (44 ticks/block) using LINQ method         CORRECTION: 21 ticks/block
Took 00:00:00.0027623 (8652 ticks) to get 818 blocks (10 ticks/block) using my method
Took 00:00:00.0031467 (9856 ticks) to get 818 blocks (12 ticks/block) using Kean's method

________________________________

Command: TESTDYNAMIC
Select Block:
Took 00:00:00.3539372 (1108584 ticks) to get 48018 blocks (23 ticks/block) using LINQ method
Took 00:00:00.1649740 (516723 ticks) to get 48018 blocks (10 ticks/block) using my method
Took 00:00:00.2242304 (702323 ticks) to get 48018 blocks (14 ticks/block) using Kean's method
Took 00:00:00.5656552 (1771716 ticks) to get 48018 blocks (36 ticks/block) using LINQ method         CORRECTION: 22 ticks/block
Took 00:00:00.1645034 (515249 ticks) to get 48018 blocks (10 ticks/block) using my method
Took 00:00:00.2006236 (628383 ticks) to get 48018 blocks (13 ticks/block) using Kean's method

_____________________________________________

Command: TESTDYNAMIC

Select Block:
Took 00:00:01.4510952 (4545045 ticks) to get 197419 blocks (23 ticks/block) using LINQ method
Took 00:00:00.6719497 (2104646 ticks) to get 197419 blocks (10 ticks/block) using my method
Took 00:00:00.8568614 (2683817 ticks) to get 197419 blocks (13 ticks/block) using Kean's method
Took 00:00:02.2484758 (7042559 ticks) to get 197419 blocks (35 ticks/block) using LINQ method         CORRECTION: 22 ticks/block
Took 00:00:00.8021520 (2512459 ticks) to get 197419 blocks (12 ticks/block) using my method
Took 00:00:00.8420208 (2637334 ticks) to get 197419 blocks (13 ticks/block) using Kean's method


__________________________________________________________
Command: TESTDYNAMIC

Select Block:
Took 00:00:07.2938638 (22845461 ticks) to get 1000001 blocks (22 ticks/block) using LINQ method
Took 00:00:03.6015105 (11280464 ticks) to get 1000001 blocks (11 ticks/block) using my method
Took 00:00:04.8079522 (15059218 ticks) to get 1000001 blocks (15 ticks/block) using Kean's method
Took 00:00:11.7611789 (36837753 ticks) to get 1000001 blocks (36 ticks/block) using LINQ method         CORRECTION: 21 ticks/block
Took 00:00:03.7182243 (11646029 ticks) to get 1000001 blocks (11 ticks/block) using my method
Took 00:00:04.9463706 (15492765 ticks) to get 1000001 blocks (15 ticks/block) using Kean's method



Cheers,

Will
« Last Edit: January 16, 2013, 10:42:27 AM by WILL HATCH »

Jeff H

  • Needs a day job
  • Posts: 6075
Re: Looking for feedback: Looking through the viewport
« Reply #10 on: January 16, 2013, 07:52:08 AM »
I got to run but will post what I played with later, but I got same results then optimize your method and still got same results and thought something had to be wrong and realized you are not resetting the timer for each run.

WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Looking for feedback: Looking through the viewport
« Reply #11 on: January 16, 2013, 10:56:22 AM »
Thanks for noticing that.  I initially wrote the test code going through each method only once and when I copied that code down to run a second time I missed resetting the timer :oops: I've corrected the code with that line changed and fudged the results accordingly

Looking back at the results I'm still quite confused...  I missed resetting between the first test of Kean's method and the second test of LINQ method the correct time for the LINQ method is actually the difference of the two.  With that corrected the times are still really variable.  There must be other things happening behind the scenes using system resources causing this variance. 

Also worth noting is that I'm testing these methods on drawings which are not realistic, the number of blocks is much greater than the number of other entities for the latter tests making the LINQ approach appear to be much slower.

Is there a way to set this thread to high priority so that we can isolate this problem?

In the meantime I'll mod my code to go through the iterations many times and write the data out to an excel file so that we can view some statistical data and get a better baseline.  I'll also include a means of finding the ratio of desired blocks to total entity count so that we can have a normalized comparison to go by.

Cheers,
Will

Jeff H

  • Needs a day job
  • Posts: 6075
Re: Looking for feedback: Looking through the viewport
« Reply #12 on: January 16, 2013, 11:15:32 AM »
Here were 2 thrown together quickly, and I thought the first one would of out performed the others, but it did not.


Code - C#: [Select]
  1.  
  2.         public List<BlockReference> IterateLabelRefs(Document doc, Database db, Editor ed, string BlockName)
  3.         {
  4.             List<BlockReference> labels = new List<BlockReference>();
  5.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  6.             {
  7.                 BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
  8.                
  9.                 if (!bt.Has(BlockName))
  10.                 {
  11.                     ed.WriteMessage("/nCannot find block {0}", BlockName);
  12.                     return labels;
  13.                 }
  14.  
  15.  
  16.                 ObjectId blkId = bt[BlockName];
  17.  
  18.  
  19.                 BlockTableRecord ps = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.PaperSpace], OpenMode.ForRead);
  20.  
  21.  
  22.                 foreach (ObjectId id in ps)
  23.                 {
  24.                     BlockReference br = tr.GetObject(id, OpenMode.ForRead) as BlockReference;
  25.                     if (br != null)
  26.                     {
  27.                         if (br.DynamicBlockTableRecord == blkId)
  28.                         {
  29.                             labels.Add(br);
  30.                         }
  31.                     }
  32.                 }
  33.  
  34.  
  35.                 tr.Commit();
  36.             }
  37.             return labels;
  38.         }
  39.  
  40.  
  41.  
  42.  
  43. Help from Tony Tanzillo
  44. http://www.theswamp.org/index.php?topic=41720.msg468319#msg468319
  45.  
  46.  
  47.         public List<BlockReference> GetLabelRefs(Document doc, Database db, Editor ed, string BlockName)
  48.         {
  49.             List<BlockReference> labels = new List<BlockReference>();
  50.             using (Transaction tr = doc.TransactionManager.StartTransaction())
  51.             {
  52.                 BlockTable bt = db.BlockTable();
  53.                 //don't waste time if the block isn't in the drawing
  54.                 if (!bt.Has(BlockName))
  55.                 {
  56.                     ed.WriteMessage("/nCannot find block {0}", BlockName);
  57.                     return labels;
  58.                 }
  59.  
  60.  
  61.  
  62.  
  63.                 BlockTableRecord ps = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.PaperSpace], OpenMode.ForRead);
  64.                var brefs = ps.GetEntitiesOfType<BlockReference>()
  65.                    .Where(bref => bref.GetEffectiveName() == BlockName);
  66.  
  67.  
  68.                 labels = brefs.ToList();
  69.  
  70.  
  71.                 tr.Commit();
  72.             }
  73.             return labels;
  74.         }
  75.  

TheMaster

  • Guest
Re: Looking for feedback: Looking through the viewport
« Reply #13 on: January 16, 2013, 02:26:48 PM »
Quote

Code: [Select]

    ps.GetObjects<BlockTableRecord>().Where(....


What version of GetObjects<T>() are you using?

I've posted comments about it and its use of ObjectId.GetObject() and also posted revised versions that avoid that bottleneck, but the larger question is if you are looking for a block with a certain name or names, it would probably be faster to open the results of calling GetBlockReferenceIds() on that block's BlockTableRecord (if it's a dynamic block, you have to open each anonymous block's definition and call that method on each).  How much faster that would be, depends on how many objects are in the space you're searching, the ratiao of hits/misses, etc.

Aside from that, a properly hand-coded solution would usually be faster than one that uses Linq, especially if there is incremental construction of large lists.

WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Looking for feedback: Looking through the viewport
« Reply #14 on: January 17, 2013, 11:42:59 AM »
Quote

Code: [Select]

    ps.GetObjects<BlockTableRecord>().Where(....


What version of GetObjects<T>() are you using?

I've posted comments about it and its use of ObjectId.GetObject() and also posted revised versions that avoid that bottleneck, but the larger question is if you are looking for a block with a certain name or names, it would probably be faster to open the results of calling GetBlockReferenceIds() on that block's BlockTableRecord (if it's a dynamic block, you have to open each anonymous block's definition and call that method on each).  How much faster that would be, depends on how many objects are in the space you're searching, the ratiao of hits/misses, etc.

Aside from that, a properly hand-coded solution would usually be faster than one that uses Linq, especially if there is incremental construction of large lists.

Code - C#: [Select]
  1.     public static class AcDbExtensions
  2.     {
  3.         // These overloads of GetObjects<T>() target BlockTableRecord
  4.         // specifically, because:
  5.         //
  6.         //   1. It is incorrect to target *any* IEnumerable, which
  7.         //      includes things like System.String. You shouldn't
  8.         //      allow an extension method to be invoked on a target
  9.         //      that is not applicable or elegible.
  10.         //
  11.         //   2. BlockTableRecord specifically contains only entities,
  12.         //      and supports opening erased entities, which requires
  13.         //      specialization that would unduly burden a more generic
  14.         //      solution targeting any IEnumerable that enumerates a
  15.         //      sequence of ObjectIds (e.g., ObjectIdCollection,
  16.         //      SymbolTableRecord, ObjectId[], etc). There are other
  17.         //      overloads of GetObjects<T>() which target those same
  18.         //      types and specialize them, without also carrying the
  19.         //      overhead of a single, 'all-purpose' solution ( those
  20.         //      latter ones are not included here ).
  21.  
  22.         public static IEnumerable<T> GetObjects<T>(this BlockTableRecord btr)
  23.           where T : Entity
  24.         {
  25.             return GetObjects<T>(btr, OpenMode.ForRead, false, false);
  26.         }
  27.  
  28.         public static IEnumerable<T> GetObjects<T>(this BlockTableRecord btr, OpenMode mode)
  29.           where T : Entity
  30.         {
  31.             return GetObjects<T>(btr, mode, false, false);
  32.         }
  33.  
  34.         public static IEnumerable<T> GetObjects<T>(this BlockTableRecord btr, bool openErased)
  35.           where T : Entity
  36.         {
  37.             return GetObjects<T>(btr, OpenMode.ForRead, openErased, false);
  38.         }
  39.  
  40.         public static IEnumerable<T> GetObjects<T>(
  41.           this BlockTableRecord btr,
  42.           OpenMode mode,
  43.           bool openErased,
  44.           bool openObjectsOnLockedLayers) where T : Entity
  45.         {
  46.             // if caller wants erased objects, use the IncludingErase property:
  47.             BlockTableRecord source = openErased ? btr.IncludingErased : btr;
  48.  
  49.             // if the generic argument is <Entity>, there is no filtering
  50.             // needed because this method targets BlockTableRecord,
  51.             // whose elements are all entities:
  52.             bool unfiltered = typeof(T) == typeof(Entity);
  53.  
  54.             bool openLocked = mode == OpenMode.ForWrite && openObjectsOnLockedLayers;
  55.  
  56.             // The runtime class of the generic argument managed wrapper type:
  57.             RXClass rxclass = RXClass.GetClass(typeof(T));
  58.  
  59.             // if no filtering is needed, just open and return every object
  60.             // cast to the generic argument type:
  61.  
  62.             if (unfiltered)
  63.             {
  64.                 foreach (ObjectId id in source)
  65.                 {
  66.                     yield return (T)id.GetObject(mode, openErased, openLocked);
  67.                 }
  68.             }
  69.             else     // caller wants a subset, so filter by runtime class:
  70.             {
  71.                 foreach (ObjectId id in source)
  72.                 {
  73.                     if (id.ObjectClass.IsSubclassOf(rxclass))
  74.                     {
  75.                         yield return (T)id.GetObject(mode, openErased, openLocked);
  76.                     }
  77.                 }
  78.             }
  79.         }
  80.  
  81.  
  82.         //////////////////////////////////////////////////////////////////////////////
  83.         // Extension method:
  84.         //
  85.         //  IEnumerable<T>.UpgradeOpen<T>() where T: DBObject
  86.         //
  87.         // Upgrade each object to OpenMode.ForWrite as it is pulled
  88.         // from the sequence, which is preferable to opening everything
  89.         // for write, when we may not modify all opened objects.
  90.         //
  91.         // This method is useful when you want to upgrade the open mode
  92.         // of many objects, but do not want to modify objects that are on
  93.         // locked layers (that also presumes you are operating directly
  94.         // on a BlockTableRecord, possibly in a RealDwg application or a
  95.         // database that's not open in the drawing editor, and the source
  96.         // may include objects on locked layers).
  97.         //
  98.         // Objects on locked layers are skipped and omitted from the
  99.         // resulting sequence.
  100.  
  101.         public static IEnumerable<T> UpgradeOpen<T>(this IEnumerable<T> source)
  102.           where T : DBObject
  103.         {
  104.             foreach (T item in source)
  105.             {
  106.                 try
  107.                 {
  108.                     if (!item.IsWriteEnabled)
  109.                         item.UpgradeOpen();
  110.                 }
  111.                 catch (Autodesk.AutoCAD.Runtime.Exception ex)
  112.                 {
  113.                     if (ex.ErrorStatus != ErrorStatus.OnLockedLayer)
  114.                         throw;
  115.                     continue;
  116.                 }
  117.                 yield return item;
  118.             }
  119.         }
  120.  
  121.         // Helper extension method:  
  122.         // Open and return the model space BlockTableRecord for the
  123.         // given database:
  124.  
  125.         public static BlockTableRecord ModelSpace(this Database db)
  126.         {
  127.             return ModelSpace(db, OpenMode.ForRead);
  128.         }
  129.  
  130.         public static BlockTableRecord ModelSpace(this Database db, OpenMode mode)
  131.         {
  132.             return (BlockTableRecord)SymbolUtilityServices.GetBlockModelSpaceId(db)
  133.                .GetObject(mode, false, false);
  134.         }
  135.  
  136.         // Open and return the BlockTableRecord for the current space
  137.         // in the given database:
  138.  
  139.         public static BlockTableRecord CurrentSpace(this Database db)
  140.         {
  141.             return (BlockTableRecord)db.CurrentSpaceId.GetObject(OpenMode.ForRead, false, false);
  142.         }
  143.  
  144.         public static BlockTableRecord CurrentSpace(this Database db, OpenMode mode)
  145.         {
  146.             return (BlockTableRecord)db.CurrentSpaceId.GetObject(mode, false, false);
  147.         }
  148.  
  149.         // Extension Method: bool RXClass.IsSubclassOf( RXClass other )
  150.         //
  151.         // This helper avoids a managed-to-native transition in the
  152.         // case where the two runtime classes are the same class, by
  153.         // doing a comparison in managed code first, before calling
  154.         // IsDerivedFrom() (which is a native P/Invoke).
  155.         //
  156.         // When a significant precentage of the objects being filtered
  157.         // are of the same runtime class as the target, this will run
  158.         // faster than direct calls to IsDerivedFrom().
  159.         //
  160.         // In the latest version of the framework, P/Invoke is much
  161.         // faster and for that reason this helper may not be needed
  162.         // or will not offer any advantage over a direct call to the
  163.         // IsDerivedFrom() method.
  164.  
  165.         public static bool IsSubclassOf(this RXClass thisClass, RXClass otherClass)
  166.         {
  167.             return thisClass == otherClass || thisClass.IsDerivedFrom(otherClass);
  168.         }
  169.  
  170.  
  171.     }
  172.  

Thanks again! Your wealth of knowledge is greatly appreciated.  I had followed your advice from another thread about using the "Go To Definition" recently to learn more about the available methods on BlockReference objects but hadn't done the same for the BlockTableRecord to learn about the GetBlockreferenceIds() and GetAnonymousBlockIds() methods.  They are still much faster than all the other methods available, and the final code is more compact than the GetObjects method.  Thank you TT!

Code - C#: [Select]
  1. //BlockTableRecord Methods
  2.         public List<BlockReference> lookatBTR(Document doc, Database db, Editor ed, BlockTableRecord btr)
  3.         {
  4.             List<BlockReference> labels = new List<BlockReference>();
  5.             using (Transaction tr = db.TransactionManager.StartTransaction())
  6.             {
  7.                 foreach (ObjectId id in btr.GetBlockReferenceIds(true, true))
  8.                 {
  9.                     labels.Add((BlockReference)tr.GetObject(id, OpenMode.ForRead));
  10.                 }
  11.                 foreach (ObjectId id in btr.GetAnonymousBlockIds())
  12.                 {
  13.                     BlockTableRecord btr1 = (BlockTableRecord)tr.GetObject(id, OpenMode.ForRead);
  14.                     foreach (ObjectId id1 in btr1.GetBlockReferenceIds(true, true))
  15.                     {
  16.                         labels.Add((BlockReference)tr.GetObject(id1, OpenMode.ForRead));
  17.                     }
  18.                 }
  19.                 tr.Commit();
  20.             }
  21.  
  22.             return labels;
  23.         }
  24.  

Command: TESTDYNAMIC

Select Block:
Took 00:00:00.0038998 (12215 ticks) to get 8 blocks (1526 ticks/block) using BTR method
Took 00:00:00.0905715 (283687 ticks) to get 8 blocks (35460 ticks/block) using LINQ method
Took 00:00:00.0482452 (151113 ticks) to get 8 blocks (18889 ticks/block) using my method
Took 00:00:00.0079011 (24748 ticks) to get 8 blocks (3093 ticks/block) using Kean's method
Took 00:00:00.0003074 (963 ticks) to get 8 blocks (120 ticks/block) using BTR method
Took 00:00:00.0015078 (4723 ticks) to get 8 blocks (590 ticks/block) using LINQ method
Took 00:00:00.0014165 (4437 ticks) to get 8 blocks (554 ticks/block) using my method
Took 00:00:00.0004897 (1534 ticks) to get 8 blocks (191 ticks/block) using Kean's method

Command: TESTDYNAMIC

Select Block:
Took 00:00:00.0179970 (56370 ticks) to get 480 blocks (117 ticks/block) using BTR method
Took 00:00:00.0368234 (115338 ticks) to get 480 blocks (240 ticks/block) using LINQ method
Took 00:00:00.0297897 (93307 ticks) to get 480 blocks (194 ticks/block) using my method
Took 00:00:00.0029832 (9344 ticks) to get 480 blocks (19 ticks/block) using Kean's method
Took 00:00:00.0011263 (3528 ticks) to get 480 blocks (7 ticks/block) using BTR method
Took 00:00:00.0331091 (103704 ticks) to get 480 blocks (216 ticks/block) using LINQ method
Took 00:00:00.0297951 (93324 ticks) to get 480 blocks (194 ticks/block) using my method
Took 00:00:00.0029880 (9359 ticks) to get 480 blocks (19 ticks/block) using Kean's method

From these results here, I think that AutoCAD must be doing a lot of calculating when the BTR method is first called that is stored for later use when it is called the second time.  Also worth noting is that my mod to Kean's method is quite slow on this drawing where the number of anonymous blocks (with dynamic properties modified) is comparable to the number of named blocks.  I think this due to the number of calls to the BlockRecord.DynamicBlockTableRecord property.

Jeff H

  • Needs a day job
  • Posts: 6075
Re: Looking for feedback: Looking through the viewport
« Reply #15 on: January 17, 2013, 11:58:47 AM »
Also that will grab nested references if that is wanted, always can check OwnerId if needed.

TheMaster

  • Guest
Re: Looking for feedback: Looking through the viewport
« Reply #16 on: January 18, 2013, 10:12:40 AM »

From these results here, I think that AutoCAD must be doing a lot of calculating when the BTR method is first called that is stored for later use when it is called the second time.


Actually there's a very simple explaination for the slower times on the first test. When you run code the first time in an AutoCAD session, the code must be just-in-time compiled, which happens on a method-by-method basis, at the point just before the method executes for the first time. That, and quite possibly caching of the data in memory and/or disk caches would fully explain that.

You should take a look here for more recent posts of mine showing revised versions of GetObjects<T>() because the one's you're using are very slow, due to the use of the ObjectId.GetObject() method, verses using a transaction.

« Last Edit: January 18, 2013, 06:37:34 PM by TT »

WILL HATCH

  • Bull Frog
  • Posts: 448
Re: Looking for feedback: Looking through the viewport
« Reply #17 on: January 21, 2013, 10:20:13 AM »
Thank you guys! You'll have me updated into the 21st century before long...  It's funny, in school people talked about C++ being easier than assembly because they didn't like tracking registers, bits, etc. but here in .net which is quite "easy" to get started there are so many invisible details that it is really easy to make much bigger mistakes than forgetting to restore a register.

Cheers!

Will