« An automatic numbering system for AutoCAD blocks using .NET - Part 1 | Main | An automatic numbering system for AutoCAD blocks using .NET - Part 3 »

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

In the last post we saw some code to perform simple sequential numbering of blocks (reflected in a particular attribute contained in each block). In this next installment we'll extend the code by introducing a NumberedObjectManager class, which will manage the activities related to maintaining the sequence of numbers used by the various blocks. The main code will create an object of this class which will be used extensively in this and the next post by a number of new commands.

Here's the updated C# code, with changed & new lines marked with a red line-number. For your convenience here is the source file, to save you having to strip off the line numbers.

    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         );

   48       }

   49       catch

   50       { }

   51     }

   52

   53     public void Terminate()

   54     {

   55     }

   56

   57     // Command to extract and display information

   58     // about the internal numbering

   59

   60     [CommandMethod("DMP")]

   61     public void DumpNumberingInformation()

   62     {

   63       Document doc =

   64         Application.DocumentManager.MdiActiveDocument;

   65       Editor ed = doc.Editor;

   66       m_nom.DumpInfo(ed);

   67     }

   68

   69     // Command to analyze the current document and

   70     // understand which indeces have been used and

   71     // which are currently free

   72

   73     [CommandMethod("LNS")]

   74     public void LoadNumberingSettings()

   75     {

   76       Document doc =

   77         Application.DocumentManager.MdiActiveDocument;

   78       Database db = doc.Database;

   79       Editor ed = doc.Editor;

   80

   81       // We need to clear any internal state

   82       // already collected

   83

   84       m_nom.Clear();

   85       m_baseNumber = 0;

   86

   87       // Select all the blocks in the current drawing

   88

   89       TypedValue[] tvs =

   90         new TypedValue[1] {

   91             new TypedValue(

   92               (int)DxfCode.Start,

   93               "INSERT"

   94             )

   95           };

   96       SelectionFilter sf =

   97         new SelectionFilter(tvs);

   98

   99       PromptSelectionResult psr =

  100         ed.SelectAll(sf);

  101

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

  103

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

  105           psr.Value.Count > 0)

  106       {

  107         Transaction tr =

  108           db.TransactionManager.StartTransaction();

  109         using (tr)

  110         {

  111           // First get the modelspace and the ID

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

  113

  114           BlockTableRecord ms;

  115           ObjectId blockId;

  116

  117           if (GetBlock(

  118                 db, tr, out ms, out blockId

  119             ))

  120           {

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

  122

  123             foreach (SelectedObject o in psr.Value)

  124             {

  125               DBObject obj =

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

  127               BlockReference br = obj as BlockReference;

  128               if (br != null)

  129               {

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

  131

  132                 if (br.BlockTableRecord == blockId)

  133                 {

  134                   // Check its attribute references...

  135

  136                   int pos = -1;

  137                   AttributeCollection ac =

  138                     br.AttributeCollection;

  139

  140                   foreach (ObjectId id in ac)

  141                   {

  142                     DBObject obj2 =

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

  144                     AttributeReference ar =

  145                       obj2 as AttributeReference;

  146

  147                     // When we find the attribute

  148                     // we care about...

  149

  150                     if (ar.Tag == attbName)

  151                     {

  152                       try

  153                       {

  154                         // Attempt to extract the number from

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

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

  157                         // non-numeric

  158

  159                         pos =

  160                           int.Parse(ar.TextString);

  161

  162                         // Add the object at the appropriate

  163                         // index

  164

  165                         m_nom.NumberObject(

  166                           o.ObjectId, pos, false

  167                         );

  168                       }

  169                       catch { }

  170                     }

  171                   }

  172                 }

  173               }

  174             }

  175           }

  176           tr.Commit();

  177         }

  178

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

  180

  181         int start = m_nom.GetLowerBound(true);

  182

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

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

  185         // current start position

  186

  187         if (start > 0)

  188         {

  189           ed.WriteMessage(

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

  191             start

  192           );

  193           PromptKeywordOptions pko =

  194             new PromptKeywordOptions(

  195               "Make this the start of the list?"

  196             );

  197           pko.AllowNone = true;

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

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

  200           pko.Keywords.Default = "Yes";

  201

  202           PromptResult pkr =

  203             ed.GetKeywords(pko);

  204

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

  206           {

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

  208             {

  209               // We store our own base number

  210               // (the object used to manage objects

  211               // always uses zero-based indeces)

  212

  213               m_baseNumber = start;

  214               m_nom.RebaseList(m_baseNumber);

  215             }

  216           }

  217         }

  218       }

  219     }

  220

  221     // Command to create bubbles at points selected

  222     // by the user - loops until cancelled

  223

  224     [CommandMethod("BAP")]

  225     public void BubblesAtPoints()

  226     {

  227       Document doc =

  228         Application.DocumentManager.MdiActiveDocument;

  229       Database db = doc.Database;

  230       Editor ed = doc.Editor;

  231       Autodesk.AutoCAD.ApplicationServices.

  232       TransactionManager tm =

  233         doc.TransactionManager;

  234

  235       Transaction tr =

  236         tm.StartTransaction();

  237       using (tr)

  238       {

  239         // Get the information about the block

  240         // and attribute definitions we care about

  241

  242         BlockTableRecord ms;

  243         ObjectId blockId;

  244         AttributeDefinition ad;

  245         List<AttributeDefinition> other;

  246

  247         if (GetBlock(

  248               db, tr, out ms, out blockId

  249           ))

  250         {

  251           GetBlockAttributes(

  252             tr, blockId, out ad, out other

  253           );

  254

  255           // By default the modelspace is returned to

  256           // us in read-only state

  257

  258           ms.UpgradeOpen();

  259

  260           // Loop until cancelled

  261

  262           bool finished = false;

  263           while (!finished)

  264           {

  265             PromptPointOptions ppo =

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

  267             ppo.AllowNone = true;

  268

  269             PromptPointResult ppr =

  270               ed.GetPoint(ppo);

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

  272               finished = true;

  273             else

  274               // Call a function to create our bubble

  275               CreateNumberedBubbleAtPoint(

  276                 db, ms, tr, ppr.Value,

  277                 blockId, ad, other

  278               );

  279             tm.QueueForGraphicsFlush();

  280             tm.FlushGraphics();

  281           }

  282         }

  283         tr.Commit();

  284       }

  285     }

  286

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

  288     // each of the selected circles

  289

  290     [CommandMethod("BIC")]

  291     public void BubblesInCircles()

  292     {

  293       Document doc =

  294         Application.DocumentManager.MdiActiveDocument;

  295       Database db = doc.Database;

  296       Editor ed = doc.Editor;

  297

  298       // Allow the user to select circles

  299

  300       TypedValue[] tvs =

  301         new TypedValue[1] {

  302             new TypedValue(

  303               (int)DxfCode.Start,

  304               "CIRCLE"

  305             )

  306           };

  307       SelectionFilter sf =

  308         new SelectionFilter(tvs);

  309

  310       PromptSelectionResult psr =

  311         ed.GetSelection(sf);

  312

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

  314           psr.Value.Count > 0)

  315       {

  316         Transaction tr =

  317           db.TransactionManager.StartTransaction();

  318         using (tr)

  319         {

  320           // Get the information about the block

  321           // and attribute definitions we care about

  322

  323           BlockTableRecord ms;

  324           ObjectId blockId;

  325           AttributeDefinition ad;

  326           List<AttributeDefinition> other;

  327

  328           if (GetBlock(

  329                 db, tr, out ms, out blockId

  330             ))

  331           {

  332             GetBlockAttributes(

  333               tr, blockId, out ad, out other

  334             );

  335

  336             // By default the modelspace is returned to

  337             // us in read-only state

  338

  339             ms.UpgradeOpen();

  340

  341             foreach (SelectedObject o in psr.Value)

  342             {

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

  344

  345               DBObject obj =

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

  347               Circle c = obj as Circle;

  348               if (c == null)

  349                 ed.WriteMessage(

  350                   "\nObject selected is not a circle."

  351                 );

  352               else

  353                 // Call our numbering function, passing the

  354                 // center of the circle

  355                 CreateNumberedBubbleAtPoint(

  356                   db, ms, tr, c.Center,

  357                   blockId, ad, other

  358                 );

  359             }

  360           }

  361           tr.Commit();

  362         }

  363       }

  364     }

  365

  366     // Internal helper function to open and retrieve

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

  368

  369     private bool

  370       GetBlock(

  371         Database db,

  372         Transaction tr,

  373         out BlockTableRecord ms,

  374         out ObjectId blockId

  375       )

  376     {

  377       BlockTable bt =

  378         (BlockTable)tr.GetObject(

  379           db.BlockTableId,

  380           OpenMode.ForRead

  381         );

  382

  383       if (!bt.Has(blockName))

  384       {

  385         Document doc =

  386           Application.DocumentManager.MdiActiveDocument;

  387         Editor ed = doc.Editor;

  388         ed.WriteMessage(

  389           "\nCannot find block definition \"" +

  390           blockName +

  391           "\" in the current drawing."

  392         );

  393

  394         blockId = ObjectId.Null;

  395         ms = null;

  396         return false;

  397       }

  398

  399       ms =

  400         (BlockTableRecord)tr.GetObject(

  401           bt[BlockTableRecord.ModelSpace],

  402           OpenMode.ForRead

  403         );

  404

  405       blockId = bt[blockName];

  406

  407       return true;

  408     }

  409

  410     // Internal helper function to retrieve

  411     // attribute info from our block

  412     // (we return the main attribute def

  413     // and then all the "others")

  414

  415     private void

  416       GetBlockAttributes(

  417         Transaction tr,

  418         ObjectId blockId,

  419         out AttributeDefinition ad,

  420         out List<AttributeDefinition> other

  421       )

  422     {

  423       BlockTableRecord blk =

  424         (BlockTableRecord)tr.GetObject(

  425           blockId,

  426           OpenMode.ForRead

  427         );

  428

  429       ad = null;

  430       other =

  431         new List<AttributeDefinition>();

  432

  433       foreach (ObjectId attId in blk)

  434       {

  435         DBObject obj =

  436           (DBObject)tr.GetObject(

  437             attId,

  438             OpenMode.ForRead

  439           );

  440         AttributeDefinition ad2 =

  441           obj as AttributeDefinition;

  442

  443         if (ad2 != null)

  444         {

  445           if (ad2.Tag == attbName)

  446           {

  447             if (ad2.Constant)

  448             {

  449               Document doc =

  450                 Application.DocumentManager.MdiActiveDocument;

  451               Editor ed = doc.Editor;

  452

  453               ed.WriteMessage(

  454                 "\nAttribute to change is constant!"

  455               );

  456             }

  457             else

  458               ad = ad2;

  459           }

  460           else

  461             if (!ad2.Constant)

  462               other.Add(ad2);

  463         }

  464       }

  465     }

  466

  467     // Internal helper function to create a bubble

  468     // at a particular point

  469

  470     private Entity

  471       CreateNumberedBubbleAtPoint(

  472         Database db,

  473         BlockTableRecord btr,

  474         Transaction tr,

  475         Point3d pt,

  476         ObjectId blockId,

  477         AttributeDefinition ad,

  478         List<AttributeDefinition> other

  479       )

  480     {

  481       //  Create a new block reference

  482

  483       BlockReference br =

  484         new BlockReference(pt, blockId);

  485

  486       // Add it to the database

  487

  488       br.SetDatabaseDefaults();

  489       ObjectId blockRefId = btr.AppendEntity(br);

  490       tr.AddNewlyCreatedDBObject(br, true);

  491

  492       // Create an attribute reference for our main

  493       // attribute definition (where we'll put the

  494       // bubble's number)

  495

  496       AttributeReference ar =

  497         new AttributeReference();

  498

  499       // Add it to the database, and set its position, etc.

  500

  501       ar.SetDatabaseDefaults();

  502       ar.SetAttributeFromBlock(ad, br.BlockTransform);

  503       ar.Position =

  504         ad.Position.TransformBy(br.BlockTransform);

  505       ar.Tag = ad.Tag;

  506

  507       // Set the bubble's number

  508

  509       int bubbleNumber =

  510         m_baseNumber +

  511         m_nom.NextObjectNumber(blockRefId);

  512

  513       ar.TextString = bubbleNumber.ToString();

  514       ar.AdjustAlignment(db);

  515

  516       // Add the attribute to the block reference

  517

  518       br.AttributeCollection.AppendAttribute(ar);

  519       tr.AddNewlyCreatedDBObject(ar, true);

  520

  521       // Now we add attribute references for the

  522       // other attribute definitions

  523

  524       foreach (AttributeDefinition ad2 in other)

  525       {

  526         AttributeReference ar2 =

  527           new AttributeReference();

  528

  529         ar2.SetAttributeFromBlock(ad2, br.BlockTransform);

  530         ar2.Position =

  531           ad2.Position.TransformBy(br.BlockTransform);

  532         ar2.Tag = ad2.Tag;

  533         ar2.TextString = ad2.TextString;

  534         ar2.AdjustAlignment(db);

  535

  536         br.AttributeCollection.AppendAttribute(ar2);

  537         tr.AddNewlyCreatedDBObject(ar2, true);

  538       }

  539       return br;

  540     }

  541   }

  542

  543   // A generic class for managing groups of

  544   // numbered (and ordered) objects

  545

  546   public class NumberedObjectManager

  547   {

  548     // We need to store a list of object IDs, but

  549     // also a list of free positions in the list

  550     // (this allows numbering gaps)

  551

  552     private List<ObjectId> m_ids;

  553     private List<int> m_free;

  554

  555     // Constructor

  556

  557     public NumberedObjectManager()

  558     {

  559       m_ids =

  560         new List<ObjectId>();

  561

  562       m_free =

  563         new List<int>();

  564     }

  565

  566     // Clear the internal lists

  567

  568     public void Clear()

  569     {

  570       m_ids.Clear();

  571       m_free.Clear();

  572     }

  573

  574     // Return the first entry in the ObjectId list

  575     // (specify "true" if you want to skip

  576     // any null object IDs)

  577

  578     public int GetLowerBound(bool ignoreNull)

  579     {

  580       if (ignoreNull)

  581         // Define an in-line predicate to check

  582         // whether an ObjectId is null

  583         return

  584           m_ids.FindIndex(

  585             delegate(ObjectId id)

  586             {

  587               return id != ObjectId.Null;

  588             }

  589           );

  590       else

  591         return 0;

  592     }

  593

  594     // Return the last entry in the ObjectId list

  595

  596     public int GetUpperBound()

  597     {

  598       return m_ids.Count - 1;

  599     }

  600

  601     // Store the specified ObjectId in the next

  602     // available location in the list, and return

  603     // what that is

  604

  605     public int NextObjectNumber(ObjectId id)

  606     {

  607       int pos;

  608       if (m_free.Count > 0)

  609       {

  610         // Get the first free position, then remove

  611         // it from the "free" list

  612

  613         pos = m_free[0];

  614         m_free.RemoveAt(0);

  615         m_ids[pos] = id;

  616       }

  617       else

  618       {

  619         // There are no free slots (gaps in the numbering)

  620         // so we append it to the list

  621

  622         pos = m_ids.Count;

  623         m_ids.Add(id);

  624       }

  625       return pos;

  626     }

  627

  628     // Store an ObjectId in a particular position

  629     // (shuffle == true will "insert" it, shuffling

  630     // the remaining objects down,

  631     // shuffle == false will replace the item in

  632     // that slot)

  633

  634     public void NumberObject(

  635       ObjectId id, int index, bool shuffle)

  636     {

  637       // If we're inserting into the list

  638

  639       if (index < m_ids.Count)