August 2014

Sun Mon Tue Wed Thu Fri Sat
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            








« An automatic numbering system for AutoCAD blocks using .NET - Part 2 | Main | DevCamps 2008 - Customization and Application Development Conferences »

May 12, 2008

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

  640

  641       br.SetDatabaseDefaults();

  642       ObjectId blockRefId = btr.AppendEntity(br);

  643       tr.AddNewlyCreatedDBObject(br, true);

  644

  645       // Create an attribute reference for our main

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

  647       // bubble's number)

  648

  649       AttributeReference ar =

  650         new AttributeReference();

  651

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

  653

  654       ar.SetDatabaseDefaults();

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

  656       ar.Position =

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

  658       ar.Tag = ad.Tag;

  659

  660       // Set the bubble's number

  661

  662       int bubbleNumber =

  663         m_baseNumber +

  664         m_nom.NextObjectNumber(blockRefId);

  665

  666       ar.TextString = bubbleNumber.ToString();

  667       ar.AdjustAlignment(db);

  668

  669       // Add the attribute to the block reference

  670

  671       br.AttributeCollection.AppendAttribute(ar);

  672       tr.AddNewlyCreatedDBObject(ar, true);

  673

  674       // Now we add attribute references for the

  675       // other attribute definitions

  676

  677       foreach (AttributeDefinition ad2 in other)

  678       {

  679         AttributeReference ar2 =

  680           new AttributeReference();

  681

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

  683         ar2.Position =

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

  685         ar2.Tag = ad2.Tag;

  686         ar2.TextString = ad2.TextString;

  687         ar2.AdjustAlignment(db);

  688

  689         br.AttributeCollection.AppendAttribute(ar2);

  690         tr.AddNewlyCreatedDBObject(ar2, true);

  691       }

  692       return br;

  693     }

  694

  695     // Internal helper function to have the user

  696     // select a valid bubble index

  697

  698     private int

  699       GetBubbleNumber(

  700         Editor ed,

  701         string prompt

  702       )

  703     {

  704       int upper = m_nom.GetUpperBound();

  705       if (upper <= 0)

  706       {

  707         ed.WriteMessage(

  708           "\nNo bubbles are currently being managed."

  709         );

  710         return upper;

  711       }

  712

  713       PromptIntegerOptions pio =

  714         new PromptIntegerOptions(prompt);

  715       pio.AllowNone = false;

  716

  717       // Get the limits from our manager object

  718

  719       pio.LowerLimit =

  720         m_baseNumber +

  721         m_nom.GetLowerBound(false);

  722       pio.UpperLimit =

  723         m_baseNumber +

  724         upper;

  725

  726       PromptIntegerResult pir =

  727         ed.GetInteger(pio);

  728       if (pir.Status == PromptStatus.OK)

  729         return pir.Value;

  730       else

  731         return -1;

  732     }

  733

  734     // Internal helper function to open up a list

  735     // of bubbles and reset their number to the

  736     // position in the list

  737

  738     private void RenumberBubbles(

  739       Database db, ObjectIdCollection ids)

  740     {

  741       Transaction tr =

  742         db.TransactionManager.StartTransaction();

  743       using (tr)

  744       {

  745         // Get the block information

  746

  747         BlockTableRecord ms;

  748         ObjectId blockId;

  749

  750         if (GetBlock(

  751               db, tr, out ms, out blockId

  752           ))

  753         {

  754           // Open each bubble to be renumbered

  755

  756           foreach (ObjectId bid in ids)

  757           {

  758             if (bid != ObjectId.Null)

  759             {

  760               DBObject obj =

  761                 tr.GetObject(bid, OpenMode.ForRead);

  762               BlockReference br = obj as BlockReference;

  763               if (br != null)

  764               {

  765                 if (br.BlockTableRecord == blockId)

  766                 {

  767                   AttributeCollection ac =

  768                     br.AttributeCollection;

  769

  770                   // Go through its attributes

  771

  772                   foreach (ObjectId aid in ac)

  773                   {

  774                     DBObject obj2 =

  775                       tr.GetObject(aid, OpenMode.ForRead);

  776                     AttributeReference ar =

  777                       obj2 as AttributeReference;

  778

  779                     if (ar.Tag == attbName)

  780                     {

  781                       // Change the one we care about

  782

  783                       ar.UpgradeOpen();

  784

  785                       int bubbleNumber =

  786                         m_baseNumber + m_nom.GetNumber(bid);

  787                       ar.TextString =

  788                         bubbleNumber.ToString();

  789

  790                       break;

  791                     }

  792                   }

  793                 }

  794               }

  795             }

  796           }

  797         }

  798         tr.Commit();

  799       }

  800     }

  801   }

  802

  803   // A generic class for managing groups of

  804   // numbered (and ordered) objects

  805

  806   public class NumberedObjectManager

  807   {

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

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

  810     // (this allows numbering gaps)

  811

  812     private List<ObjectId> m_ids;

  813     private List<int> m_free;

  814

  815     // Constructor

  816

  817     public NumberedObjectManager()

  818     {

  819       m_ids =

  820         new List<ObjectId>();

  821

  822       m_free =

  823         new List<int>();

  824     }

  825

  826     // Clear the internal lists

  827

  828     public void Clear()

  829     {

  830       m_ids.Clear();

  831       m_free.Clear();

  832     }

  833

  834     // Return the first entry in the ObjectId list

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

  836     // any null object IDs)

  837

  838     public int GetLowerBound(bool ignoreNull)

  839     {

  840       if (ignoreNull)

  841         // Define an in-line predicate to check

  842         // whether an ObjectId is null

  843         return

  844           m_ids.FindIndex(

  845             delegate(ObjectId id)

  846             {

  847               return id != ObjectId.Null;

  848             }

  849           );

  850       else

  851         return 0;

  852     }

  853

  854     // Return the last entry in the ObjectId list

  855

  856     public int GetUpperBound()

  857     {

  858       return m_ids.Count - 1;

  859     }

  860

  861     // Store the specified ObjectId in the next

  862     // available location in the list, and return

  863     // what that is

  864

  865     public int NextObjectNumber(ObjectId id)

  866     {

  867       int pos;

  868       if (m_free.Count > 0)

  869       {

  870         // Get the first free position, then remove

  871         // it from the "free" list

  872

  873         pos = m_free[0];

  874         m_free.RemoveAt(0);

  875         m_ids[pos] = id;

  876       }

  877       else

  878       {

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

  880         // so we append it to the list

  881

  882         pos = m_ids.Count;

  883         m_ids.Add(id);

  884       }

  885       return pos;

  886     }

  887

  888     // Go through the list of objects and close any gaps

  889     // by shuffling the list down (easy, as we're using a

  890     // List<> rather than an array)

  891

  892     public ObjectIdCollection ReorderObjects()

  893     {

  894       // Create a collection of ObjectIds we'll return

  895       // for the caller to go and update

  896       // (so the renumbering will gets reflected

  897       // in the objects themselves)

  898

  899       ObjectIdCollection ids =

  900         new ObjectIdCollection();

  901

  902       // We'll go through the "free" list backwards,

  903       // to allow any changes made to the list of

  904       // objects to not affect what we're doing

  905

  906       List<int> rev =

  907         new List<int>(m_free);

  908       rev.Reverse();

  909

  910       foreach (int pos in rev)

  911       {

  912         // First we remove the object at the "free"

  913         // position (in theory this should be set to

  914         // ObjectId.Null, as the slot has been marked

  915         // as blank)

  916

  917         m_ids.RemoveAt(pos);

  918

  919         // Now we go through and add the IDs of any

  920         // affected objects to the list to return

  921

  922         for (int i = pos; i < m_ids.Count; i++)

  923         {

  924           ObjectId id = m_ids[pos];

  925

  926           // Only add non-null objects

  927           // not already in the list

  928

  929           if (!ids.Contains(id) &&

  930               id != ObjectId.Null)

  931             ids.Add(id);

  932         }

  933       }

  934

  935       // Our free slots have been filled, so clear

  936       // the list

  937

  938       m_free.Clear();

  939

  940       return ids;

  941     }

  942

  943     // Get the ID of an object at a particular position

  944

  945     public ObjectId GetObjectId(int pos)

  946     {

  947       if (pos < m_ids.Count)

  948         return m_ids[pos];

  949       else

  950         return ObjectId.Null;

  951     }

  952

  953     // Get the position of an ObjectId in the list

  954

  955     public int GetNumber(ObjectId id)

  956     {

  957       if (m_ids.Contains(id))

  958         return m_ids.IndexOf(id);

  959       else

  960         return -1;

  961     }

  962

  963     // Store an ObjectId in a particular position

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

  965     // the remaining objects down,

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

  967     // that slot)

  968

  969     public void NumberObject(

  970       ObjectId id, int index, bool shuffle)

  971     {

  972       // If we're inserting into the list

  973

  974       if (index < m_ids.Count)

  975       {

  976         if (shuffle)

  977           // Insert takes care of the shuffling

  978           m_ids.Insert(index, id);

  979         else

  980         {

  981           // If we're replacing the existing item, do

  982           // so and then make sure the slot is removed

  983           // from the "free" list, if applicable

  984

  985           m_ids[index] = id;

  986           if (m_free.Contains(index))

  987             m_free.Remove(index);

  988         }

  989       }

  990       else

  991       {

  992         // If we're appending, shuffling is irrelevant,

  993         // but we may need to add additional "free" slots

  994         // if the position comes after the end

  995

  996         while (m_ids.Count < index)

  997         {

  998           m_ids.Add(ObjectId.Null);

  999           m_free.Add(m_ids.LastIndexOf(ObjectId.Null));

1000           m_free.Sort();

1001         }

1002         m_ids.Add(id);

1003       }

1004     }

1005

1006     // Move an ObjectId already in the list to a

1007     // particular position

1008     // (ObjectIds between the two positions will

1009     // get shuffled down automatically)

1010

1011     public ObjectIdCollection MoveObject(

1012       int from, int to)

1013     {

1014       ObjectIdCollection ids =

1015         new ObjectIdCollection();

1016

1017       if (from < m_ids.Count &&

1018           to < m_ids.Count)

1019       {

1020         if (from != to)

1021         {

1022           ObjectId id = m_ids[from];

1023           m_ids.RemoveAt(from);

1024           m_ids.Insert(to, id);

1025

1026           int start = (from < to ? from : to);

1027           int end = (from < to ? to : from);

1028

1029           for (int i = start; i <= end; i++)

1030           {

1031             ids.Add(m_ids[i]);

1032           }

1033         }

1034         // Now need to adjust/recreate "free" list

1035         m_free.Clear();

1036         for (int j = 0; j < m_ids.Count; j++)

1037         {

1038           if (m_ids[j] == ObjectId.Null)

1039             m_free.Add(j);

1040         }

1041       }

1042       return ids;

1043     }

1044

1045     // Remove an ObjectId from the list

1046

1047     public int RemoveObject(ObjectId id)

1048     {

1049       // Check it's non-null and in the list

1050

1051       if (id != ObjectId.Null &&

1052           m_ids.Contains(id))

1053       {

1054         int pos = m_ids.IndexOf(id);

1055         RemoveObject(pos);

1056         return pos;

1057       }

1058       return -1;

1059     }

1060

1061     // Remove the ObjectId at a particular position

1062

1063     public ObjectId RemoveObject(int pos)

1064     {

1065       // Get the ObjectId in the specified position,

1066       // making sure it's non-null

1067

1068       ObjectId id = m_ids[pos];

1069       if (id != ObjectId.Null)

1070       {

1071         // Null out the position and add it to the

1072         // "free" list

1073

1074         m_ids[pos] = ObjectId.Null;

1075         m_free.Add(pos);

1076         m_free.Sort();

1077       }

1078       return id;

1079     }

1080

1081     // Dump out the object list information

1082     // as well as the "free" slots

1083

1084     public void DumpInfo(Editor ed)

1085     {

1086       if (m_ids.Count > 0)

1087       {

1088         ed.WriteMessage("\nIdx ObjectId");

1089

1090         int index = 0;

1091         foreach (ObjectId id in m_ids)

1092           ed.WriteMessage("\n{0} {1}", index++, id);

1093       }

1094

1095       if (m_free.Count > 0)

1096       {

1097         ed.WriteMessage("\n\nFree list: ");

1098

1099         foreach (int pos in m_free)

1100           ed.WriteMessage("{0} ", pos);

1101       }

1102     }

1103

1104     // Remove the initial n items from the list

1105

1106     public void RebaseList(int start)

1107     {

1108       // First we remove the ObjectIds

1109

1110       for (int i=0; i < start; i++)

1111         m_ids.RemoveAt(0);

1112

1113       // Then we go through the "free" list...

1114

1115       int idx = 0;

1116       while (idx < m_free.Count)

1117       {

1118         if (m_free[idx] < start)

1119           // Remove any that refer to the slots

1120           // we've removed

1121           m_free.RemoveAt(idx);

1122         else

1123         {

1124           // Subtracting the number of slots

1125           // we've removed from the other items

1126           m_free[idx] -= start;

1127           idx++;

1128         }

1129       }

1130     }

1131   }

1132 }

The above code defines four new commands which move, delete and highlight a bubble, and reorder the bubble list. I could probably have used better terminology for some of the command-names - the MB (Move Bubble) command does not move the physical position of the block in the drawing, it moves the bubble inside the list (i.e. it changes the bubble's number while maintaining the consistency of the list). Similarly, RBS (Reorder BubbleS) actually just compacts the list, removing unnecessary gaps in the list created by deletion. Anyway, the user is notified of the additional commands by lines 46-50, and the commands themselves are implemented by lines 370-517. MB, DB (Delete Bubble) and HLB (HighLight Bubble) all use a new helper function, GetBubbleNumber(), defined by lines 695-733, which asks the user to select a valid bubble from the list, which will then get moved, deleted or highlighted, as appropriate.

The other new helper function which is defined outside the NumberedObjectManager class (as the function depends on the specific implementation of our object numbering, i.e. with the value stored in an attribute in a block), is RenumberBubbles(), defined by lines 734-800. This function opens up a list of bubbles and sets their visible number to the one stored in the NamedObjectManager object. It is used by both MB and RBS.

To support these new commands, the NamedObjectManager class has also been extended in two new sections of the above code. The first new chunk of code (lines 888-961) implements new methods ReorderObjects() which again, is really a list compaction function and then GetObjectId() and GetNumber(), which - as you'd expect - return an ObjectId at a particular position and a position for a particular ObjectId. The next chunk (lines 1006-1079) implements MoveObject(), which moves an object from one place to another - shuffling the intermediate bubbles around, as needed - and two versions of RemoveObject(), depending on whether you wish to select the object by its ID or its position.

Something important to note about this implementation: so far we haven't dealt with what happens should the user choose to undo these commands: as the objects we're creating are not managed by AutoCAD (they are not stored in the drawing, for instance), their state is not captured in the undo filer, and so will not be affected by undo. But the geometry they refer to will, of course, so there is substantial potential for our list getting out of sync with reality. The easy (and arguably the best) way to get around this is to check for undo-related commands to be executed, and invalidate our list at that point (providing a suitable notification to the user, requesting that they run LNS again once done with their undoing & redoing). The current implementation does not do this.

Let's now take our new commands for a quick spin...

We're going to take our previously-created drawing, as a starting point, and use our new commands on it.

Let's start with DB:

Command: LNS

Lowest index is 1. Make this the start of the list? [Yes/No] <Yes>: Yes

Command: DB

Enter number of bubble to erase: 3

Command: DB

Enter number of bubble to erase: 5

Command: DB

Enter number of bubble to erase: 15

Command: DB

Enter number of bubble to erase: 16

Command: DMP

Idx ObjectId

0 (2129683752)

1 (2129683776)

2 (0)

3 (2129683824)

4 (0)

5 (2129683872)

6 (2129683896)

7 (2129683920)

8 (2129683944)

9 (2129683968)

10 (2129683992)

11 (2129684016)

12 (2129684040)

13 (2129684064)

14 (0)

15 (0)

16 (2129684136)

17 (2129684160)

18 (2129684184)

19 (2129684208)

Free list: 2 4 14 15

As you can see, we've ended up with a few free slots in our list (and you'll note you need to add our "base number" (1) to get to the visible number). Here's the state of the drawing at this point:

Deleted Bubbles

Now let's try MB:

Command: MB

Enter number of bubble to move: 7

Enter destination position: 2

Command: DMP

Idx ObjectId

0 (2129683752)

1 (2129683896)

2 (2129683776)

3 (0)

4 (2129683824)

5 (0)

6 (2129683872)

7 (2129683920)

8 (2129683944)

9 (2129683968)

10 (2129683992)

11 (2129684016)

12 (2129684040)

13 (2129684064)

14 (0)

15 (0)

16 (2129684136)

17 (2129684160)

18 (2129684184)

19 (2129684208)

Free list: 3 5 14 15

This results in the item in internal slot 6 being moved to internal slot 1 (remember that base number :-) and the objects between being shuffled along. Here's what's on the screen at this point:

Moved Bubbles

And finally we'll compact the list - removing those four free slots - with our RBS command:

Command: RBS

Command: DMP

Idx ObjectId

0 (2129683752)

1 (2129683896)

2 (2129683776)

3 (2129683824)

4 (2129683872)

5 (2129683920)

6 (2129683944)

7 (2129683968)

8 (2129683992)

9 (2129684016)

10 (2129684040)

11 (2129684064)

12 (2129684136)

13 (2129684160)

14 (2129684184)

15 (2129684208)

And here's how that looks:

Reordered Bubbles

I don't currently have any further enhancements planned for this application. Feel free to post a comment or send me an email if there's a particular direction in which you'd like to see it go. For instance, is it interesting to see support for prefixes/suffixes...?

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83452464869e200e552191c7f8833

Listed below are links to weblogs that reference An automatic numbering system for AutoCAD blocks using .NET - Part 3:

blog comments powered by Disqus

Feed/Share

10 Random Posts