Sunday, November 17, 2013

Draw Order Problem with Dynamic Block

In one of my recent AutoCAD programming project, the code has to deal with a quite complicated dynamic block, which has many visibility states and a few parameters associated with stretching/moving actions. The entities in the block definition include texts, attributes, various geometric entities and hatch. The hatch in this dynamic block is used as background of other entities (text, attribute). Naturally, when defining the block, the Draw Order of the hatches is set to be at bottom.

When this dynamic block is used, occasionally, users report that the Draw Order of the hatch in inserted block reference is wrong, e.g. the hatch somehow is on top other entities, thus visually blocks other entities.

On the first a few reports, I thought it must the draw order in the block definition wasn't set correct, because the block is so complicated and has so many entities in it. However, after carefully examined the block and reset the draw orders of the entities in the block definition again and again, users still report this issue from time to time. This prompted me to think that it must have something to do with the fact that the block is a dynamic block.

This is my speculation on this issue:

When a dynamic block is inserted into drawing and one of its dynamic property is set to a value different from the value when the block is defined, the block reference is no longer a reference to the original block definition. AutoCAD dynamically defines a new block definition as an anonymous block, based on the original block definition and the dynamic property value. That means, the entities in the newly defined anonymous block in created dynamically at the time when this anonymous block definition is needed. So, it is possible, the entities are created in a order that is different from the Draw Order saved in the original block definition's DrawOrderTable, thus the issue. Again, this is just my guess. Only those in Autodesk who actually built dynamic block mechanism would knew whether my guess is close to the fact or not.

Regardless my guess being right or wrong, the issue is a real pain when is occurs. It can be corrected by using Block Editor in the drawing where the block reference presents. But in my case, it is not an easy thing to do, as I stated, the block is very complicated and has a lot entities overlapped together. So, I had to write a bit of code so that user can execute a single command to fix this issue easily. Following is the sample code to set "Draw Order" in a block (definition). The code used in my real project is a bit different, so that all targeting blocks are fixed at once.

    1 using Autodesk.AutoCAD.ApplicationServices;
    2 using Autodesk.AutoCAD.DatabaseServices;
    3 using Autodesk.AutoCAD.EditorInput;
    4 using Autodesk.AutoCAD.Runtime;
    6 [assembly: CommandClass(typeof(DrawOrderInBlock.MyCommands))]
    8 namespace DrawOrderInBlock
    9 {
   10     public class MyCommands
   11     {
   12         [CommandMethod("DOinBlock")]
   13         public static void RunMyCommand()
   14         {
   15             Document dwg = Application.DocumentManager.MdiActiveDocument;
   16             Editor ed = dwg.Editor;
   18             //Ask user to select a block. In real world, I use code
   19             //to select all targeting blocks by its name, so that
   20             //user does not have to select block manually
   21             PromptEntityOptions opt = new PromptEntityOptions(
   22                 "nSelect a block:");
   23             opt.SetRejectMessage("\nInvalid: not a block.");
   24             opt.AddAllowedClass(typeof(BlockReference), true);
   25             PromptEntityResult res = ed.GetEntity(opt);
   26             if (res.Status == PromptStatus.OK)
   27             {
   28                 SetDrawOrderInBlock(dwg, res.ObjectId);
   29                 ed.Regen();
   30             }
   31             else
   32             {
   33                 ed.WriteMessage("\n*Cancel*");
   34             }
   36             Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
   37         }
   39         private static void SetDrawOrderInBlock(Document dwg, ObjectId blkId)
   40         {
   41             using (Transaction tran =
   42                 dwg.TransactionManager.StartTransaction())
   43             {
   44                 BlockReference bref = (BlockReference)tran.GetObject(
   45                     blkId, OpenMode.ForRead);
   47                 BlockTableRecord bdef = (BlockTableRecord)tran.GetObject(
   48                     bref.BlockTableRecord, OpenMode.ForWrite);
   50                 DrawOrderTable doTbl = (DrawOrderTable)tran.GetObject(
   51                     bdef.DrawOrderTableId, OpenMode.ForWrite);
   53                 ObjectIdCollection col = new ObjectIdCollection();
   54                 foreach (ObjectId id in bdef)
   55                 {
   56                     if (id.ObjectClass.DxfName.ToUpper() == "HATCH")
   57                     {
   58                         col.Add(id);
   59                     }
   60                 }
   62                 if (col.Count > 0)
   63                 {
   64                     doTbl.MoveToBottom(col);
   65                 }
   67                 tran.Commit();
   68             }
   69         }
   70     }
   71 }
This video clip shows how the code work: a hatch in the block is set in front of crossed 2 lines; after executing the code, the hatch is set to bottom in Draw Order table, so that the crossed lines become visible in front of the hatch.


Pfilc said...

This is interesting. I have a dynamic block that we've used for years that has a wipeout. Obviously, draworder is critical for it to work, but we've never had a problem. It's not hatching, but I would suspect similar issues with functionality.
I'm wondering a couple things:
- Is it possible HPDRAWORDER is causing the behavior?
- Your theory on how the anonymous blocks are created is interesting. Perhaps you could test it by creating the objects in the "correct" order (i.e. - hatch first, text/other objects after).

Norman Yuan said...

Yes, my theory is a speculation, unproved. The block I had to use in my project is very complicated and some the hatch were added gradullay based on the needs. Noone dare or are willing to rebuild the block from scratch again with coorect order (i.e. adding hatch first, then text/attribute...). That was when we started to having Draw Order issue.

Actually, the experince tells me that having a too complicated dynamic block may not be good thing, especially if a drawing may have huge number of the dynamic block refereces and they need to be manipulated by code: because with each variation of the dynamic property, AutoCAD has to take time to dynamically define a new annonymous block. I do not even know if AutoCAD tries to find if there is an existing annonymous block defined for the same dynamic parameter value. this process increases code execution time quite significantly.

Pfilc said...

I understand not wanting to rebuild the block.

I also know exactly what you mean with the performance when dealing with complex dynamic blocks. I have created one that is fairly complex. It gets used in projects that can have hundreds of instances of it. My lisp code that works with it begins to perform very slowly. In fact, native acad commands and even selecting the dynamic blocks begins to bog down.


About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.