An automatic numbering system for AutoCAD blocks using .NET - Part 3

In the last post we introduced some additional features to the original post in this series. In this post we take advantage of - and further extend - those features, by allowing deletion, movement and compaction of the numbered objects.

Here's the modified C# code, with changed/new lines in red, and here is the the updated source file:

    1 using Autodesk.AutoCAD.ApplicationServices;

    2 using Autodesk.AutoCAD.Runtime;

    3 using Autodesk.AutoCAD.DatabaseServices;

    4 using Autodesk.AutoCAD.EditorInput;

    5 using Autodesk.AutoCAD.Geometry;

    6 using System.Collections.Generic;

    7

    8 namespace AutoNumberedBubbles

    9 {

   10   public class Commands : IExtensionApplication

   11   {

   12     // Strings identifying the block

   13     // and the attribute name to use

   14

   15     const string blockName = "BUBBLE";

   16     const string attbName = "NUMBER";

   17

   18     // We will use a separate object to

   19     // manage our numbering, and maintain a

   20     // "base" index (the start of the list)

   21

   22     private NumberedObjectManager m_nom;

   23     private int m_baseNumber = 0;

   24

   25     // Constructor

   26

   27     public Commands()

   28     {

   29       m_nom = new NumberedObjectManager();

   30     }

   31

   32     // Functions called on initialization & termination

   33

   34     public void Initialize()

   35     {

   36       try

   37       {

   38         Document doc =

   39           Application.DocumentManager.MdiActiveDocument;

   40         Editor ed = doc.Editor;

   41

   42         ed.WriteMessage(

   43           "\nLNS  Load numbering settings by analyzing the current drawing" +

   44           "\nDMP  Print internal numbering information" +

   45           "\nBAP  Create bubbles at points" +

   46           "\nBIC  Create bubbles at the center of circles" +

   47           "\nMB   Move a bubble in the list" +

   48           "\nDB   Delete a bubble" +

   49           "\nRBS  Reorder the bubbles, to close gaps caused by deletion" +

   50           "\nHLB  Highlight a particular bubble"

   51         );

   52       }

   53       catch

   54       { }

   55     }

   56

   57     public void Terminate()

   58     {

   59     }

   60

   61     // Command to extract and display information

   62     // about the internal numbering

   63

   64     [CommandMethod("DMP")]

   65     public void DumpNumberingInformation()

   66     {

   67       Document doc =

   68         Application.DocumentManager.MdiActiveDocument;

   69       Editor ed = doc.Editor;

   70       m_nom.DumpInfo(ed);

   71     }

   72

   73     // Command to analyze the current document and

   74     // understand which indeces have been used and

   75     // which are currently free

   76

   77     [CommandMethod("LNS")]

   78     public void LoadNumberingSettings()

   79     {

   80       Document doc =

   81         Application.DocumentManager.MdiActiveDocument;

   82       Database db = doc.Database;

   83       Editor ed = doc.Editor;

   84

   85       // We need to clear any internal state

   86       // already collected

   87

   88       m_nom.Clear();

   89       m_baseNumber = 0;

   90

   91       // Select all the blocks in the current drawing

   92

   93       TypedValue[] tvs =

   94         new TypedValue[1] {

   95             new TypedValue(

   96               (int)DxfCode.Start,

   97               "INSERT"

   98             )

   99           };

  100       SelectionFilter sf =

  101         new SelectionFilter(tvs);

  102

  103       PromptSelectionResult psr =

  104         ed.SelectAll(sf);

  105

  106       // If it succeeded and we have some blocks...

  107

  108       if (psr.Status == PromptStatus.OK &&

  109           psr.Value.Count > 0)

  110       {

  111         Transaction tr =

  112           db.TransactionManager.StartTransaction();

  113         using (tr)

  114         {

  115           // First get the modelspace and the ID

  116           // of the block for which we're searching

  117

  118           BlockTableRecord ms;

  119           ObjectId blockId;

  120

  121           if (GetBlock(

  122                 db, tr, out ms, out blockId

  123             ))

  124           {

  125             // For each block reference in the drawing...

  126

  127             foreach (SelectedObject o in psr.Value)

  128             {

  129               DBObject obj =

  130                 tr.GetObject(o.ObjectId, OpenMode.ForRead);

  131               BlockReference br = obj as BlockReference;

  132               if (br != null)

  133               {

  134                 // If it's the one we care about...

  135

  136                 if (br.BlockTableRecord == blockId)

  137                 {

  138                   // Check its attribute references...

  139

  140                   int pos = -1;

  141                   AttributeCollection ac =

  142                     br.AttributeCollection;

  143

  144                   foreach (ObjectId id in ac)

  145                   {

  146                     DBObject obj2 =

  147                       tr.GetObject(id, OpenMode.ForRead);

  148                     AttributeReference ar =

  149                       obj2 as AttributeReference;

  150

  151                     // When we find the attribute

  152                     // we care about...

  153

  154                     if (ar.Tag == attbName)

  155                     {

  156                       try

  157                       {

  158                         // Attempt to extract the number from

  159                         // the text string property... use a

  160                         // try-catch block just in case it is

  161                         // non-numeric

  162

  163                         pos =

  164                           int.Parse(ar.TextString);

  165

  166                         // Add the object at the appropriate

  167                         // index

  168

  169                         m_nom.NumberObject(

  170                           o.ObjectId, pos, false

  171                         );

  172                       }

  173                       catch { }

  174                     }

  175                   }

  176                 }

  177               }

  178             }

  179           }

  180           tr.Commit();

  181         }

  182

  183         // Once we have analyzed all the block references...

  184

  185         int start = m_nom.GetLowerBound(true);

  186

  187         // If the first index is non-zero, ask the user if

  188         // they want to rebase the list to begin at the

  189         // current start position

  190

  191         if (start > 0)

  192         {

  193           ed.WriteMessage(

  194             "\nLowest index is {0}. ",

  195             start

  196           );

  197           PromptKeywordOptions pko =

  198             new PromptKeywordOptions(

  199               "Make this the start of the list?"

  200             );

  201           pko.AllowNone = true;

  202           pko.Keywords.Add("Yes");

  203           pko.Keywords.Add("No");

  204           pko.Keywords.Default = "Yes";

  205

  206           PromptResult pkr =

  207             ed.GetKeywords(pko);

  208

  209           if (pkr.Status == PromptStatus.OK)

  210           {

  211             if (pkr.StringResult == "Yes")

  212             {

  213               // We store our own base number

  214               // (the object used to manage objects

  215               // always uses zero-based indeces)

  216

  217               m_baseNumber = start;

  218               m_nom.RebaseList(m_baseNumber);

  219             }

  220           }

  221         }

  222       }

  223     }

  224

  225     // Command to create bubbles at points selected

  226     // by the user - loops until cancelled

  227

  228     [CommandMethod("BAP")]

  229     public void BubblesAtPoints()

  230     {

  231       Document doc =

  232         Application.DocumentManager.MdiActiveDocument;

  233       Database db = doc.Database;

  234       Editor ed = doc.Editor;

  235       Autodesk.AutoCAD.ApplicationServices.

  236       TransactionManager tm =

  237         doc.TransactionManager;

  238

  239       Transaction tr =

  240         tm.StartTransaction();

  241       using (tr)

  242       {

  243         // Get the information about the block

  244         // and attribute definitions we care about

  245

  246         BlockTableRecord ms;

  247         ObjectId blockId;

  248         AttributeDefinition ad;

  249         List<AttributeDefinition> other;

  250

  251         if (GetBlock(

  252               db, tr, out ms, out blockId

  253           ))

  254         {

  255           GetBlockAttributes(

  256             tr, blockId, out ad, out other

  257           );

  258

  259           // By default the modelspace is returned to

  260           // us in read-only state

  261

  262           ms.UpgradeOpen();

  263

  264           // Loop until cancelled

  265

  266           bool finished = false;

  267           while (!finished)

  268           {

  269             PromptPointOptions ppo =

  270               new PromptPointOptions("\nSelect point: ");

  271             ppo.AllowNone = true;

  272

  273             PromptPointResult ppr =

  274               ed.GetPoint(ppo);

  275             if (ppr.Status != PromptStatus.OK)

  276               finished = true;

  277             else

  278               // Call a function to create our bubble

  279               CreateNumberedBubbleAtPoint(

  280                 db, ms, tr, ppr.Value,

  281                 blockId, ad, other

  282               );

  283             tm.QueueForGraphicsFlush();

  284             tm.FlushGraphics();

  285           }

  286         }

  287         tr.Commit();

  288       }

  289     }

  290

  291     // Command to create a bubble at the center of

  292     // each of the selected circles

  293

  294     [CommandMethod("BIC")]

  295     public void BubblesInCircles()

  296     {

  297       Document doc =

  298         Application.DocumentManager.MdiActiveDocument;

  299       Database db = doc.Database;

  300       Editor ed = doc.Editor;

  301

  302       // Allow the user to select circles

  303

  304       TypedValue[] tvs =

  305         new TypedValue[1] {

  306             new TypedValue(

  307               (int)DxfCode.Start,

  308               "CIRCLE"

  309             )

  310           };

  311       SelectionFilter sf =

  312         new SelectionFilter(tvs);

  313

  314       PromptSelectionResult psr =

  315         ed.GetSelection(sf);

  316

  317       if (psr.Status == PromptStatus.OK &&

  318           psr.Value.Count > 0)

  319       {

  320         Transaction tr =

  321           db.TransactionManager.StartTransaction();

  322         using (tr)

  323         {

  324           // Get the information about the block

  325           // and attribute definitions we care about

  326

  327           BlockTableRecord ms;

  328           ObjectId blockId;

  329           AttributeDefinition ad;

  330           List<AttributeDefinition> other;

  331

  332           if (GetBlock(

  333                 db, tr, out ms, out blockId

  334             ))

  335           {

  336             GetBlockAttributes(

  337               tr, blockId, out ad, out other

  338             );

  339

  340             // By default the modelspace is returned to

  341             // us in read-only state

  342

  343             ms.UpgradeOpen();

  344

  345             foreach (SelectedObject o in psr.Value)

  346             {

  347               // For each circle in the selected list...

  348

  349               DBObject obj =

  350                 tr.GetObject(o.ObjectId, OpenMode.ForRead);

  351               Circle c = obj as Circle;

  352               if (c == null)

  353                 ed.WriteMessage(

  354                   "\nObject selected is not a circle."

  355                 );

  356               else

  357                 // Call our numbering function, passing the

  358                 // center of the circle

  359                 CreateNumberedBubbleAtPoint(

  360                   db, ms, tr, c.Center,

  361                   blockId, ad, other

  362                 );

  363             }

  364           }

  365           tr.Commit();

  366         }

  367       }

  368     }

  369

  370     // Command to delete a particular bubble

  371     // selected by its index

  372

  373     [CommandMethod("MB")]

  374     public void MoveBubble()

  375     {

  376       Document doc =

  377         Application.DocumentManager.MdiActiveDocument;

  378       Editor ed = doc.Editor;

  379

  380       // Use a helper function to select a valid bubble index

  381

  382       int pos =

  383         GetBubbleNumber(

  384           ed,

  385           "\nEnter number of bubble to move: "

  386         );

  387

  388       if (pos >= m_baseNumber)

  389       {

  390         int from = pos - m_baseNumber;

  391

  392         pos =

  393           GetBubbleNumber(

  394             ed,

  395             "\nEnter destination position: "

  396           );

  397

  398         if (pos >= m_baseNumber)

  399         {

  400           int to = pos - m_baseNumber;

  401

  402           ObjectIdCollection ids =

  403             m_nom.MoveObject(from, to);

  404

  405           RenumberBubbles(doc.Database, ids);

  406         }

  407       }

  408     }

  409

  410     // Command to delete a particular bubbler,

  411     // selected by its index

  412

  413     [CommandMethod("DB")]

  414     public void DeleteBubble()

  415     {

  416       Document doc =

  417         Application.DocumentManager.MdiActiveDocument;

  418       Database db = doc.Database;

  419       Editor ed = doc.Editor;

  420

  421       // Use a helper function to select a valid bubble index

  422

  423       int pos =

  424         GetBubbleNumber(

  425           ed,

  426           "\nEnter number of bubble to erase: "

  427         );

  428

  429       if (pos >= m_baseNumber)

  430       {

  431         // Remove the object from the internal list

  432         // (this returns the ObjectId stored for it,

  433         // which we can then use to erase the entity)

  434

  435         ObjectId id =

  436           m_nom.RemoveObject(pos - m_baseNumber);

  437         Transaction tr =

  438           db.TransactionManager.StartTransaction();

  439         using (tr)

  440         {

  441           DBObject obj =

  442             tr.GetObject(id, OpenMode.ForWrite);

  443           obj.Erase();

  444           tr.Commit();

  445         }

  446       }

  447     }

  448

  449     // Command to reorder all the bubbles in the drawing,

  450     // closing all the gaps between numbers but maintaining

  451     // the current numbering order

  452

  453     [CommandMethod("RBS")]

  454     public void ReorderBubbles()

  455     {

  456       Document doc =

  457         Application.DocumentManager.MdiActiveDocument;

  458

  459       // Re-order the bubbles - the IDs returned are

  460       // of the objects that need to be renumbered

  461

  462       ObjectIdCollection ids =

  463         m_nom.ReorderObjects();

  464

  465       RenumberBubbles(doc.Database, ids);

  466     }

  467

  468     // Command to highlight a particular bubble

  469

  470     [CommandMethod("HLB")]

  471     public void HighlightBubble()

  472     {

  473       Document doc =

  474         Application.DocumentManager.MdiActiveDocument;

  475       Database db = doc.Database;

  476       Editor ed = doc.Editor;

  477

  478       // Use our function to select a valid bubble index

  479

  480       int pos =

  481         GetBubbleNumber(

  482           ed,

  483           "\nEnter number of bubble to highlight: "

  484         );

  485

  486       if (pos >= m_baseNumber)

  487       {

  488         Transaction tr =

  489           db.TransactionManager.StartTransaction();

  490         using (tr)

  491         {

  492           // Get the ObjectId from the index...

  493

  494           ObjectId id =

  495             m_nom.GetObjectId(pos - m_baseNumber);

  496

  497           if (id == ObjectId.Null)

  498           {

  499             ed.WriteMessage(

  500               "\nNumber is not currently used -" +

  501               " nothing to highlight."

  502             );

  503             return;

  504           }

  505

  506           // And then open & highlight the entity

  507

  508           Entity ent =

  509             (Entity)tr.GetObject(

  510               id,

  511               OpenMode.ForRead

  512             );

  513           ent.Highlight();

  514           tr.Commit();

  515         }

  516       }

  517     }

  518

  519     // Internal helper function to open and retrieve

  520     // the model-space and the block def we care about

  521

  522     private bool

  523       GetBlock(

  524         Database db,

  525         Transaction tr,

  526         out BlockTableRecord ms,

  527         out ObjectId blockId

  528       )

  529     {

  530       BlockTable bt =

  531         (BlockTable)tr.GetObject(

  532           db.BlockTableId,

  533           OpenMode.ForRead

  534         );

  535

  536       if (!bt.Has(blockName))

  537       {

  538         Document doc =

  539           Application.DocumentManager.MdiActiveDocument;

  540         Editor ed = doc.Editor;

  541         ed.WriteMessage(

  542           "\nCannot find block definition \"" +

  543           blockName +

  544           "\" in the current drawing."

  545         );

  546

  547         blockId = ObjectId.Null;

  548         ms = null;

  549         return false;

  550       }

  551

  552       ms =

  553         (BlockTableRecord)tr.GetObject(

  554           bt[BlockTableRecord.ModelSpace],

  555           OpenMode.ForRead

  556         );

  557

  558       blockId = bt[blockName];

  559

  560       return true;

  561     }

  562

  563     // Internal helper function to retrieve

  564     // attribute info from our block

  565     // (we return the main attribute def

  566     // and then all the "others")

  567

  568     private void

  569       GetBlockAttributes(

  570         Transaction tr,

  571         ObjectId blockId,

  572         out AttributeDefinition ad,

  573         out List<AttributeDefinition> other

  574       )

  575     {

  576       BlockTableRecord blk =

  577         (BlockTableRecord)tr.GetObject(

  578           blockId,

  579           OpenMode.ForRead

  580         );

  581

  582       ad = null;

  583       other =

  584         new List<AttributeDefinition>();

  585

  586       foreach (ObjectId attId in blk)

  587       {

  588         DBObject obj =

  589           (DBObject)tr.GetObject(

  590             attId,

  591             OpenMode.ForRead

  592           );

  593         AttributeDefinition ad2 =

  594           obj as AttributeDefinition;

  595

  596         if (ad2 != null)

  597         {

  598           if (ad2.Tag == attbName)

  599           {

  600             if (ad2.Constant)

  601             {

  602               Document doc =

  603                 Application.DocumentManager.MdiActiveDocument;

  604               Editor ed = doc.Editor;

  605

  606               ed.WriteMessage(

  607                 "\nAttribute to change is constant!"

  608               );

  609             }

  610             else

  611               ad = ad2;

  612           }

  613           else

  614             if (!ad2.Constant)

  615               other.Add(ad2);

  616         }

  617       }

  618     }

  619

  620     // Internal helper function to create a bubble

  621     // at a particular point

  622

  623     private Entity

  624       CreateNumberedBubbleAtPoint(

  625         Database db,

  626         BlockTableRecord btr,

  627         Transaction tr,

  628         Point3d pt,

  629         ObjectId blockId,

  630         AttributeDefinition ad,

  631         List<AttributeDefinition> other

  632       )

  633     {

  634       //  Create a new block reference

  635

  636       BlockReference br =

  637         new BlockReference(pt, blockId);

  638

  639       // Add it to the database