明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 7461|回复: 7

[Kean专集] Kean专题(2)-Selection

 关闭 [复制链接]
发表于 2009-5-15 10:21:00 | 显示全部楼层 |阅读模式
本帖最后由 作者 于 2009-5-15 11:48:25 编辑

原帖:http://through-the-interface.typepad.com/through_the_interface/selection/
一、使实体处于选中状态(与Lisp中SSSetFirst类似的东东)
January 30, 2007
Adding to the AutoCAD pickfirst set with .NET
Back in a much earlier post we looked at some code to access the pickfirst selection set.
I thought I'd now take a look at the more specific case of defining a command that adds entities into the current pickfirst selection set.
Here's some C# code I used to do this, with comments describing its behaviour. The most interesting point is that once we get the contents of the existing pickfirst set we then clear it and rehighlight the entities manually, to give the user a graphical clue of the previous contents.
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5. namespace SelectionTest
  6. {
  7.   public
  8.      class
  9.      PickfirstTestCmds
  10.   {
  11.     [CommandMethod("PFA", CommandFlags.Modal |
  12.                           CommandFlags.UsePickSet |
  13.                           CommandFlags.Redraw)
  14.     ]
  15.     public
  16.      void AddToPickfirstSet()
  17.     {
  18.       Document doc =
  19.         Application.DocumentManager.MdiActiveDocument;
  20.       Editor ed = doc.Editor;
  21.       // First let's get the initial pickfirst set
  22.       PromptSelectionResult sr =
  23.         ed.SelectImplied();
  24.       // Create a collection containing the initial set
  25.       //  (could have used an array, but a collection is
  26.       //  easier in dynamic situations, such as this)
  27.       ObjectIdCollection objs;
  28.       if (sr.Status == PromptStatus.OK)
  29.         objs =
  30.           new
  31.      ObjectIdCollection(
  32.           sr.Value.GetObjectIds()
  33.         );
  34.       else
  35.         objs = new
  36.      ObjectIdCollection();
  37.       // Clear the pickfirst set...
  38.       ed.SetImpliedSelection(new
  39.      ObjectId[0]);
  40.       // ...but highlight the objects
  41.       if (objs.Count > 0)
  42.         HighlightEntities(objs);
  43.       // No ask the user to select objects
  44.       sr = ed.GetSelection();
  45.       if (sr.Status == PromptStatus.OK)
  46.       {
  47.         // Add them all to the collection
  48.         foreach (ObjectId obj in sr.Value.GetObjectIds())
  49.         {
  50.           objs.Add(obj);
  51.         }
  52.         // Dump the collection to an array
  53.         ObjectId[] ids = new
  54.      ObjectId[objs.Count];
  55.         objs.CopyTo(ids, 0);
  56.         // And finally set the pickfirst set
  57.         ed.SetImpliedSelection(ids);
  58.       }
  59.     }
  60.     // Highlight the entities by opening them one by one
  61.     private
  62.      void HighlightEntities(ObjectIdCollection objIds)
  63.     {
  64.       Document doc =
  65.         Application.DocumentManager.MdiActiveDocument;
  66.       Transaction tr =
  67.         doc.TransactionManager.StartTransaction();
  68.       using (tr)
  69.       {
  70.         foreach (ObjectId objId in objIds)
  71.         {
  72.           Entity ent =
  73.             tr.GetObject(objId, OpenMode.ForRead)
  74.             as
  75.      Entity;
  76.           if (ent != null)
  77.             ent.Highlight();
  78.         }
  79.       }
  80.     }
  81.   }
  82. }
 楼主| 发表于 2009-5-15 10:30:00 | 显示全部楼层
本帖最后由 作者 于 2009-5-17 15:00:19 编辑

二、用选择集选择特定层上的实体
May 02, 2008
Finding all the AutoCAD entities on a particular layer using .NET
This question came in recently by email:
Is there a way to obtain the object IDs of all the object on a layer? I found the GetAllObjects() method of the Transaction object but it doesn't work.
That's right: GetAllObjects() will return the objects accessed via (or added to) the transaction - it has nothing to do with retrieving objects based on any particular property.
What you need to do is Editor.SelectAll(), the equivalent of acedSSGet("X") in ObjectARX and (ssget "X") in AutoLISP. You need the version where you pass in a SelectionFilter specifying the layer for which to search.
Here's some C# code doing just this, with the hard work encapsulated in a function (GetEntitiesOnLayer()) to make it easier to integrate into your code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.Runtime;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. namespace EntitySelection
  6. {
  7.   public class Commands
  8.   {
  9.     [CommandMethod("EOL")]
  10.     static public void EntitiesOnLayer()
  11.     {
  12.       Document doc =
  13.         Application.DocumentManager.MdiActiveDocument;
  14.       Editor ed = doc.Editor;
  15.       PromptResult pr =
  16.         ed.GetString("\nEnter name of layer: ");
  17.       if (pr.Status == PromptStatus.OK)
  18.       {
  19.         ObjectIdCollection ents =
  20.           GetEntitiesOnLayer(pr.StringResult);
  21.         ed.WriteMessage(
  22.           "\nFound {0} entit{1} on layer {2}",
  23.           ents.Count,
  24.           (ents.Count == 1 ? "y" : "ies"),
  25.           pr.StringResult
  26.         );
  27.       }
  28.     }
  29.     private static ObjectIdCollection
  30.       GetEntitiesOnLayer(string layerName)
  31.     {
  32.       Document doc =
  33.         Application.DocumentManager.MdiActiveDocument;
  34.       Editor ed = doc.Editor;
  35.       // Build a filter list so that only entities
  36.       // on the specified layer are selected
  37.       TypedValue[] tvs =
  38.         new TypedValue[1] {
  39.             new TypedValue(
  40.               (int)DxfCode.LayerName,
  41.               layerName
  42.             )
  43.           };
  44.       SelectionFilter sf =
  45.         new SelectionFilter(tvs);
  46.       PromptSelectionResult psr =
  47.         ed.SelectAll(sf);
  48.       if (psr.Status == PromptStatus.OK)
  49.         return
  50.           new ObjectIdCollection(
  51.             psr.Value.GetObjectIds()
  52.           );
  53.       else
  54.         return new ObjectIdCollection();
  55.     }
  56.   }
  57. }
Running EOL simply counts the entities on a particular layer (the following is from running in the 3D House.dwg sample drawing):
  1. Command: EOL
  2. Enter name of layer: A-Beam
  3. Found 1 entity on layer A-Beam
  4. Command: EOL
  5. Enter name of layer: A-Concrete
  6. Found 1 entity on layer A-Concrete
  7. Command: EOL
  8. Enter name of layer: Furniture
  9. Found 14 entities on layer Furniture
  10. Command: EOL
  11. Enter name of layer: Legs
  12. Found 16 entities on layer Legs
复制代码
It would be straightforward to do something more than just count the results of the GetEntitiesOnLayer() function, of course, or even to adapt the function to filter on other properties.


 楼主| 发表于 2009-5-15 10:44:00 | 显示全部楼层
本帖最后由 作者 于 2009-5-15 15:12:58 编辑

三、一个图块的自动计数系统

http://bbs.mjtd.com/forum.php?mod=viewthread&tid=75650

 楼主| 发表于 2009-5-15 10:55:00 | 显示全部楼层
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:
  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. namespace AutoNumberedBubbles
  8. {
  9.   public class Commands : IExtensionApplication
  10.   {
  11.     // Strings identifying the block
  12.     // and the attribute name to use
  13.     const string blockName = "BUBBLE";
  14.     const string attbName = "NUMBER";
  15.     // We will use a separate object to
  16.     // manage our numbering, and maintain a
  17.     // "base" index (the start of the list)
  18.     private NumberedObjectManager m_nom;
  19.     private int m_baseNumber = 0;
  20.     // Constructor
  21.     public Commands()
  22.     {
  23.       m_nom = new NumberedObjectManager();
  24.     }
  25.     // Functions called on initialization & termination
  26.     public void Initialize()
  27.     {
  28.       try
  29.       {
  30.         Document doc =
  31.           Application.DocumentManager.MdiActiveDocument;
  32.         Editor ed = doc.Editor;
  33.         ed.WriteMessage(
  34.           "\nLNS  Load numbering settings by analyzing the current drawing" +
  35.           "\nDMP  Print internal numbering information" +
  36.           "\nBAP  Create bubbles at points" +
  37.           "\nBIC  Create bubbles at the center of circles" +
  38.           "\nMB   Move a bubble in the list" +
  39.           "\nDB   Delete a bubble" +
  40.           "\nRBS  Reorder the bubbles, to close gaps caused by deletion" +
  41.           "\nHLB  Highlight a particular bubble"
  42.         );
  43.       }
  44.       catch
  45.       { }
  46.     }
  47.     public void Terminate()
  48.     {
  49.     }
  50.     // Command to extract and display information
  51.     // about the internal numbering
  52.     [CommandMethod("DMP")]
  53.     public void DumpNumberingInformation()
  54.     {
  55.       Document doc =
  56.         Application.DocumentManager.MdiActiveDocument;
  57.       Editor ed = doc.Editor;
  58.       m_nom.DumpInfo(ed);
  59.     }
  60.     // Command to analyze the current document and
  61.     // understand which indeces have been used and
  62.     // which are currently free
  63.     [CommandMethod("LNS")]
  64.     public void LoadNumberingSettings()
  65.     {
  66.       Document doc =
  67.         Application.DocumentManager.MdiActiveDocument;
  68.       Database db = doc.Database;
  69.       Editor ed = doc.Editor;
  70.       // We need to clear any internal state
  71.       // already collected
  72.       m_nom.Clear();
  73.       m_baseNumber = 0;
  74.       // Select all the blocks in the current drawing
  75.       TypedValue[] tvs =
  76.         new TypedValue[1] {
  77.             new TypedValue(
  78.               (int)DxfCode.Start,
  79.               "INSERT"
  80.             )
  81.           };
  82.       SelectionFilter sf =
  83.         new SelectionFilter(tvs);
  84.       PromptSelectionResult psr =
  85.         ed.SelectAll(sf);
  86.       // If it succeeded and we have some blocks...
  87.       if (psr.Status == PromptStatus.OK &&
  88.           psr.Value.Count > 0)
  89.       {
  90.         Transaction tr =
  91.           db.TransactionManager.StartTransaction();
  92.         using (tr)
  93.         {
  94.           // First get the modelspace and the ID
  95.           // of the block for which we're searching
  96.           BlockTableRecord ms;
  97.           ObjectId blockId;
  98.           if (GetBlock(
  99.                 db, tr, out ms, out blockId
  100.              ))
  101.           {
  102.             // For each block reference in the drawing...
  103.             foreach (SelectedObject o in psr.Value)
  104.             {
  105.               DBObject obj =
  106.                 tr.GetObject(o.ObjectId, OpenMode.ForRead);
  107.               BlockReference br = obj as BlockReference;
  108.               if (br != null)
  109.               {
  110.                 // If it's the one we care about...
  111.                 if (br.BlockTableRecord == blockId)
  112.                 {
  113.                   // Check its attribute references...
  114.                   int pos = -1;
  115.                   AttributeCollection ac =
  116.                     br.AttributeCollection;
  117.                   foreach (ObjectId id in ac)
  118.                   {
  119.                     DBObject obj2 =
  120.                       tr.GetObject(id, OpenMode.ForRead);
  121.                     AttributeReference ar =
  122.                       obj2 as AttributeReference;
  123.                     // When we find the attribute
  124.                     // we care about...
  125.                     if (ar.Tag == attbName)
  126.                     {
  127.                       try
  128.                       {
  129.                         // Attempt to extract the number from
  130.                         // the text string property... use a
  131.                         // try-catch block just in case it is
  132.                         // non-numeric
  133.                         pos =
  134.                           int.Parse(ar.TextString);
  135.                         // Add the object at the appropriate
  136.                         // index
  137.                         m_nom.NumberObject(
  138.                           o.ObjectId, pos, false
  139.                         );
  140.                       }
  141.                       catch { }
  142.                     }
  143.                   }
  144.                 }
  145.               }
  146.             }
  147.           }
  148.           tr.Commit();
  149.         }
  150.         // Once we have analyzed all the block references...
  151.         int start = m_nom.GetLowerBound(true);
  152.         // If the first index is non-zero, ask the user if
  153.         // they want to rebase the list to begin at the
  154.         // current start position
  155.         if (start > 0)
  156.         {
  157.           ed.WriteMessage(
  158.             "\nLowest index is {0}. ",
  159.             start
  160.           );
  161.           PromptKeywordOptions pko =
  162.             new PromptKeywordOptions(
  163.               "Make this the start of the list?"
  164.             );
  165.           pko.AllowNone = true;
  166.           pko.Keywords.Add("Yes");
  167.           pko.Keywords.Add("No");
  168.           pko.Keywords.Default = "Yes";
  169.           PromptResult pkr =
  170.             ed.GetKeywords(pko);
  171.           if (pkr.Status == PromptStatus.OK)
  172.           {
  173.             if (pkr.StringResult == "Yes")
  174.             {
  175.               // We store our own base number
  176.               // (the object used to manage objects
  177.               // always uses zero-based indeces)
  178.               m_baseNumber = start;
  179.               m_nom.RebaseList(m_baseNumber);
  180.             }
  181.           }
  182.         }
  183.       }
  184.     }
  185.    
  186.     // Command to create bubbles at points selected
  187.     // by the user - loops until cancelled
  188.     [CommandMethod("BAP")]
  189.     public void BubblesAtPoints()
  190.     {
  191.       Document doc =
  192.         Application.DocumentManager.MdiActiveDocument;
  193.       Database db = doc.Database;
  194.       Editor ed = doc.Editor;
  195.       Autodesk.AutoCAD.ApplicationServices.
  196.       TransactionManager tm =
  197.         doc.TransactionManager;
  198.       Transaction tr =
  199.         tm.StartTransaction();
  200.       using (tr)
  201.       {
  202.         // Get the information about the block
  203.         // and attribute definitions we care about
  204.         BlockTableRecord ms;
  205.         ObjectId blockId;
  206.         AttributeDefinition ad;
  207.         List<AttributeDefinition> other;
  208.         if (GetBlock(
  209.               db, tr, out ms, out blockId
  210.            ))
  211.         {
  212.           GetBlockAttributes(
  213.             tr, blockId, out ad, out other
  214.           );
  215.           // By default the modelspace is returned to
  216.           // us in read-only state
  217.           ms.UpgradeOpen();
  218.           // Loop until cancelled
  219.           bool finished = false;
  220.           while (!finished)
  221.           {
  222.             PromptPointOptions ppo =
  223.               new PromptPointOptions("\nSelect point: ");
  224.             ppo.AllowNone = true;
  225.             PromptPointResult ppr =
  226.               ed.GetPoint(ppo);
  227.             if (ppr.Status != PromptStatus.OK)
  228.               finished = true;
  229.             else
  230.               // Call a function to create our bubble
  231.               CreateNumberedBubbleAtPoint(
  232.                 db, ms, tr, ppr.Value,
  233.                 blockId, ad, other
  234.               );
  235.             tm.QueueForGraphicsFlush();
  236.             tm.FlushGraphics();
  237.           }
  238.         }
  239.         tr.Commit();
  240.       }
  241.     }
  242.     // Command to create a bubble at the center of
  243.     // each of the selected circles
  244.     [CommandMethod("BIC")]
  245.     public void BubblesInCircles()
  246.     {
  247.       Document doc =
  248.         Application.DocumentManager.MdiActiveDocument;
  249.       Database db = doc.Database;
  250.       Editor ed = doc.Editor;
  251.       // Allow the user to select circles
  252.       TypedValue[] tvs =
  253.         new TypedValue[1] {
  254.             new TypedValue(
  255.               (int)DxfCode.Start,
  256.               "CIRCLE"
  257.             )
  258.           };
  259.       SelectionFilter sf =
  260.         new SelectionFilter(tvs);
  261.       PromptSelectionResult psr =
  262.         ed.GetSelection(sf);
  263.       if (psr.Status == PromptStatus.OK &&
  264.           psr.Value.Count > 0)
  265.       {
  266.         Transaction tr =
  267.           db.TransactionManager.StartTransaction();
  268.         using (tr)
  269.         {
  270.           // Get the information about the block
  271.           // and attribute definitions we care about
  272.           BlockTableRecord ms;
  273.           ObjectId blockId;
  274.           AttributeDefinition ad;
  275.           List<AttributeDefinition> other;
  276.           if (GetBlock(
  277.                 db, tr, out ms, out blockId
  278.              ))
  279.           {
  280.             GetBlockAttributes(
  281.               tr, blockId, out ad, out other
  282.             );
  283.             // By default the modelspace is returned to
  284.             // us in read-only state
  285.             ms.UpgradeOpen();
  286.             foreach (SelectedObject o in psr.Value)
  287.             {
  288.               // For each circle in the selected list...
  289.               DBObject obj =
  290.                 tr.GetObject(o.ObjectId, OpenMode.ForRead);
  291.               Circle c = obj as Circle;
  292.               if (c == null)
  293.                 ed.WriteMessage(
  294.                   "\nObject selected is not a circle."
  295.                 );
  296.               else
  297.                 // Call our numbering function, passing the
  298.                 // center of the circle
  299.                 CreateNumberedBubbleAtPoint(
  300.                   db, ms, tr, c.Center,
  301.                   blockId, ad, other
  302.                 );
  303.             }
  304.           }
  305.           tr.Commit();
  306.         }
  307.       }
  308.     }
  309.     // Command to delete a particular bubble
  310.     // selected by its index
  311.     [CommandMethod("MB")]
  312.     public void MoveBubble()
  313.     {
  314.       Document doc =
  315.         Application.DocumentManager.MdiActiveDocument;
  316.       Editor ed = doc.Editor;
  317.       // Use a helper function to select a valid bubble index
  318.       int pos =
  319.         GetBubbleNumber(
  320.           ed,
  321.           "\nEnter number of bubble to move: "
  322.         );
  323.       if (pos >= m_baseNumber)
  324.       {
  325.         int from = pos - m_baseNumber;
  326.         pos =
  327.           GetBubbleNumber(
  328.             ed,
  329.             "\nEnter destination position: "
  330.           );
  331.         if (pos >= m_baseNumber)
  332.         {
  333.           int to = pos - m_baseNumber;
  334.           ObjectIdCollection ids =
  335.             m_nom.MoveObject(from, to);
  336.           RenumberBubbles(doc.Database, ids);
  337.         }
  338.       }
  339.     }
  340.     // Command to delete a particular bubbler,
  341.     // selected by its index
  342.     [CommandMethod("DB")]
  343.     public void DeleteBubble()
  344.     {
  345.       Document doc =
  346.         Application.DocumentManager.MdiActiveDocument;
  347.       Database db = doc.Database;
  348.       Editor ed = doc.Editor;
  349.       // Use a helper function to select a valid bubble index
  350.       int pos =
  351.         GetBubbleNumber(
  352.           ed,
  353.           "\nEnter number of bubble to erase: "
  354.         );
  355.       if (pos >= m_baseNumber)
  356.       {
  357.         // Remove the object from the internal list
  358.         // (this returns the ObjectId stored for it,
  359.         // which we can then use to erase the entity)
  360.         ObjectId id =
  361.           m_nom.RemoveObject(pos - m_baseNumber);
  362.         Transaction tr =
  363.           db.TransactionManager.StartTransaction();
  364.         using (tr)
  365.         {
  366.           DBObject obj =
  367.             tr.GetObject(id, OpenMode.ForWrite);
  368.           obj.Erase();
  369.           tr.Commit();
  370.         }
  371.       }
  372.     }
  373.     // Command to reorder all the bubbles in the drawing,
  374.     // closing all the gaps between numbers but maintaining
  375.     // the current numbering order
  376.     [CommandMethod("RBS")]
  377.     public void ReorderBubbles()
  378.     {
  379.       Document doc =
  380.         Application.DocumentManager.MdiActiveDocument;
  381.       // Re-order the bubbles - the IDs returned are
  382.       // of the objects that need to be renumbered
  383.       ObjectIdCollection ids =
  384.         m_nom.ReorderObjects();
  385.       RenumberBubbles(doc.Database, ids);
  386.     }
  387.     // Command to highlight a particular bubble
  388.     [CommandMethod("HLB")]
  389.     public void HighlightBubble()
  390.     {
  391.       Document doc =
  392.         Application.DocumentManager.MdiActiveDocument;
  393.       Database db = doc.Database;
  394.       Editor ed = doc.Editor;
  395.       // Use our function to select a valid bubble index
  396.       int pos =
  397.         GetBubbleNumber(
  398.           ed,
  399.           "\nEnter number of bubble to highlight: "
  400.         );
  401.       if (pos >= m_baseNumber)
  402.       {
  403.         Transaction tr =
  404.           db.TransactionManager.StartTransaction();
  405.         using (tr)
  406.         {
  407.           // Get the ObjectId from the index...
  408.           ObjectId id =
  409.             m_nom.GetObjectId(pos - m_baseNumber);
  410.           if (id == ObjectId.Null)
  411.           {
  412.             ed.WriteMessage(
  413.               "\nNumber is not currently used -" +
  414.               " nothing to highlight."
  415.             );
  416.             return;
  417.           }
  418.           // And then open & highlight the entity
  419.           Entity ent =
  420.             (Entity)tr.GetObject(
  421.               id,
  422.               OpenMode.ForRead
  423.             );
  424.           ent.Highlight();
  425.           tr.Commit();
  426.         }
  427.       }
  428.     }
  429.     // Internal helper function to open and retrieve
  430.     // the model-space and the block def we care about
  431.     private bool
  432.       GetBlock(
  433.         Database db,
  434.         Transaction tr,
  435.         out BlockTableRecord ms,
  436.         out ObjectId blockId
  437.       )
  438.     {
  439.       BlockTable bt =
  440.         (BlockTable)tr.GetObject(
  441.           db.BlockTableId,
  442.           OpenMode.ForRead
  443.         );
  444.       if (!bt.Has(blockName))
  445.       {
  446.         Document doc =
  447.           Application.DocumentManager.MdiActiveDocument;
  448.         Editor ed = doc.Editor;
  449.         ed.WriteMessage(
  450.           "\nCannot find block definition "" +
  451.           blockName +
  452.           "" in the current drawing."
  453.         );
  454.         blockId = ObjectId.Null;
  455.         ms = null;
  456.         return false;
  457.       }
  458.       ms =
  459.         (BlockTableRecord)tr.GetObject(
  460.           bt[BlockTableRecord.ModelSpace],
  461.           OpenMode.ForRead
  462.         );
  463.       blockId = bt[blockName];
  464.       return true;
  465.     }
  466.     // Internal helper function to retrieve
  467.     // attribute info from our block
  468.     // (we return the main attribute def
  469.     // and then all the "others")
  470.     private void
  471.       GetBlockAttributes(
  472.         Transaction tr,
  473.         ObjectId blockId,
  474.         out AttributeDefinition ad,
  475.         out List<AttributeDefinition> other
  476.       )
  477.     {
  478.       BlockTableRecord blk =
  479.         (BlockTableRecord)tr.GetObject(
  480.           blockId,
  481.           OpenMode.ForRead
  482.         );
  483.       ad = null;
  484.       other =
  485.         new List<AttributeDefinition>();
  486.       foreach (ObjectId attId in blk)
  487.       {
  488.         DBObject obj =
  489.           (DBObject)tr.GetObject(
  490.             attId,
  491.             OpenMode.ForRead
  492.           );
  493.         AttributeDefinition ad2 =
  494.           obj as AttributeDefinition;
  495.         if (ad2 != null)
  496.         {
  497.           if (ad2.Tag == attbName)
  498.           {
  499.             if (ad2.Constant)
  500.             {
  501.               Document doc =
  502.                 Application.DocumentManager.MdiActiveDocument;
  503.               Editor ed = doc.Editor;
  504.               ed.WriteMessage(
  505.                 "\nAttribute to change is constant!"
  506.               );
  507.             }
  508.             else
  509.               ad = ad2;
  510.           }
  511.           else
  512.             if (!ad2.Constant)
  513.               other.Add(ad2);
  514.         }
  515.       }
  516.     }
  517.     // Internal helper function to create a bubble
  518.     // at a particular point
  519.     private Entity
  520.       CreateNumberedBubbleAtPoint(
  521.         Database db,
  522.         BlockTableRecord btr,
  523.         Transaction tr,
  524.         Point3d pt,
  525.         ObjectId blockId,
  526.         AttributeDefinition ad,
  527.         List<AttributeDefinition> other
  528.       )
  529.     {
  530.       //  Create a new block reference
  531.       BlockReference br =
  532.         new BlockReference(pt, blockId);
  533.       // Add it to the database
  534.       br.SetDatabaseDefaults();
  535.       ObjectId blockRefId = btr.AppendEntity(br);
  536.       tr.AddNewlyCreatedDBObject(br, true);
  537.       // Create an attribute reference for our main
  538.       // attribute definition (where we'll put the
  539.       // bubble's number)
  540.       AttributeReference ar =
  541.         new AttributeReference();
  542.       // Add it to the database, and set its position, etc.
  543.       ar.SetDatabaseDefaults();
  544.       ar.SetAttributeFromBlock(ad, br.BlockTransform);
  545.       ar.Position =
  546.         ad.Position.TransformBy(br.BlockTransform);
  547.       ar.Tag = ad.Tag;
  548.       // Set the bubble's number
  549.       int bubbleNumber =
  550.         m_baseNumber +
  551.         m_nom.NextObjectNumber(blockRefId);
  552.       ar.TextString = bubbleNumber.ToString();
  553.       ar.AdjustAlignment(db);
  554.       // Add the attribute to the block reference
  555.       br.AttributeCollection.AppendAttribute(ar);
  556.       tr.AddNewlyCreatedDBObject(ar, true);
  557.       // Now we add attribute references for the
  558.       // other attribute definitions
  559.       foreach (AttributeDefinition ad2 in other)
  560.       {
  561.         AttributeReference ar2 =
  562.           new AttributeReference();
  563.         ar2.SetAttributeFromBlock(ad2, br.BlockTransform);
  564.         ar2.Position =
  565.           ad2.Position.TransformBy(br.BlockTransform);
  566.         ar2.Tag = ad2.Tag;
  567.         ar2.TextString = ad2.TextString;
  568.         ar2.AdjustAlignment(db);
  569.         br.AttributeCollection.AppendAttribute(ar2);
  570.         tr.AddNewlyCreatedDBObject(ar2, true);
  571.       }
  572.       return br;
  573.     }
  574.     // Internal helper function to have the user
  575.     // select a valid bubble index
  576.     private int
  577.       GetBubbleNumber(
  578.         Editor ed,
  579.         string prompt
  580.       )
  581.     {
  582.       int upper = m_nom.GetUpperBound();
  583.       if (upper <= 0)
  584.       {
  585.         ed.WriteMessage(
  586.           "\nNo bubbles are currently being managed."
  587.         );
  588.         return upper;
  589.       }
  590.       PromptIntegerOptions pio =
  591.         new PromptIntegerOptions(prompt);
  592.       pio.AllowNone = false;
  593.       // Get the limits from our manager object
  594.       pio.LowerLimit =
  595.         m_baseNumber +
  596.         m_nom.GetLowerBound(false);
  597.       pio.UpperLimit =
  598.         m_baseNumber +
  599.         upper;
  600.       PromptIntegerResult pir =
  601.         ed.GetInteger(pio);
  602.       if (pir.Status == PromptStatus.OK)
  603.         return pir.Value;
  604.       else
  605.         return -1;
  606.     }
  607.     // Internal helper function to open up a list
  608.     // of bubbles and reset their number to the
  609.     // position in the list
  610.     private void RenumberBubbles(
  611.       Database db, ObjectIdCollection ids)
  612.     {
  613.       Transaction tr =
  614.         db.TransactionManager.StartTransaction();
  615.       using (tr)
  616.       {
  617.         // Get the block information
  618.         BlockTableRecord ms;
  619.         ObjectId blockId;
  620.         if (GetBlock(
  621.               db, tr, out ms, out blockId
  622.            ))
  623.         {
  624.           // Open each bubble to be renumbered
  625.           foreach (ObjectId bid in ids)
  626.           {
  627.             if (bid != ObjectId.Null)
  628.             {
  629.               DBObject obj =
  630.                 tr.GetObject(bid, OpenMode.ForRead);
  631.               BlockReference br = obj as BlockReference;
  632.               if (br != null)
  633.               {
  634.                 if (br.BlockTableRecord == blockId)
  635.                 {
  636.                   AttributeCollection ac =
  637.                     br.AttributeCollection;
  638.                   // Go through its attributes
  639.                   foreach (ObjectId aid in ac)
  640.                   {
  641.                     DBObject obj2 =
  642.                       tr.GetObject(aid, OpenMode.ForRead);
  643.                     AttributeReference ar =
  644.                       obj2 as AttributeReference;
  645.                     if (ar.Tag == attbName)
  646.                     {
  647.                       // Change the one we care about
  648.                       ar.UpgradeOpen();
  649.                       int bubbleNumber =
  650.                         m_baseNumber + m_nom.GetNumber(bid);
  651.                       ar.TextString =
  652.                         bubbleNumber.ToString();
  653.                       break;
  654.                     }
  655.                   }
  656.                 }
  657.               }
  658.             }
  659.           }
  660.         }
  661.         tr.Commit();
  662.       }
  663.     }
  664.   }
  665.   // A generic class for managing groups of
  666.   // numbered (and ordered) objects
  667.   public class NumberedObjectManager
  668.   {
  669.     // We need to store a list of object IDs, but
  670.     // also a list of free positions in the list
  671.     // (this allows numbering gaps)
  672.     private List<ObjectId> m_ids;
  673.     private List<int> m_free;
  674.     // Constructor
  675.     public NumberedObjectManager()
  676.     {
  677.       m_ids =
  678.         new List<ObjectId>();
  679.       m_free =
  680.         new List<int>();
  681.     }
  682.     // Clear the internal lists
  683.     public void Clear()
  684.     {
  685.       m_ids.Clear();
  686.       m_free.Clear();
  687.     }
  688.     // Return the first entry in the ObjectId list
  689.     // (specify "true" if you want to skip
  690.     // any null object IDs)
  691.     public int GetLowerBound(bool ignoreNull)
  692.     {
  693.       if (ignoreNull)
  694.         // Define an in-line predicate to check
  695.         // whether an ObjectId is null
  696.         return
  697.           m_ids.FindIndex(
  698.             delegate(ObjectId id)
  699.             {
  700.               return id != ObjectId.Null;
  701.             }
  702.           );
  703.       else
  704.         return 0;
  705.     }
  706.     // Return the last entry in the ObjectId list
  707.     public int GetUpperBound()
  708.     {
  709.       return m_ids.Count - 1;
  710.     }
  711.     // Store the specified ObjectId in the next
  712.     // available location in the list, and return
  713.     // what that is
  714.     public int NextObjectNumber(ObjectId id)
  715.     {
  716.       int pos;
  717.       if (m_free.Count > 0)
  718.       {
  719.         // Get the first free position, then remove
  720.         // it from the "free" list
  721.         pos = m_free[0];
  722.         m_free.RemoveAt(0);
  723.         m_ids[pos] = id;
  724.       }
  725.       else
  726.       {
  727.         // There are no free slots (gaps in the numbering)
  728.         // so we append it to the list
  729.         pos = m_ids.Count;
  730.         m_ids.Add(id);
  731.       }
  732.       return pos;
  733.     }
  734.     // Go through the list of objects and close any gaps
  735.     // by shuffling the list down (easy, as we're using a
  736.     // List<> rather than an array)
  737.     public ObjectIdCollection ReorderObjects()
  738.     {
  739.       // Create a collection of ObjectIds we'll return
  740.       // for the caller to go and update
  741.       // (so the renumbering will gets reflected
  742.       // in the objects themselves)
  743.       ObjectIdCollection ids =
  744.         new ObjectIdCollection();
  745.       // We'll go through the "free" list backwards,
  746.       // to allow any changes made to the list of
  747.       // objects to not affect what we're doing
  748.       List<int> rev =
  749.         new List<int>(m_free);
  750.       rev.Reverse();
  751.       foreach (int pos in rev)
  752.       {
  753.         // First we remove the object at the "free"
  754.         // position (in theory this should be set to
  755.         // ObjectId.Null, as the slot has been marked
  756.         // as blank)
  757.         m_ids.RemoveAt(pos);
  758.         // Now we go through and add the IDs of any
  759.         // affected objects to the list to return
  760.         for (int i = pos; i < m_ids.Count; i++)
  761.         {
  762.           ObjectId id = m_ids[pos];
  763.           // Only add non-null objects
  764.           // not already in the list
  765.          
  766.           if (!ids.Contains(id) &&
  767.               id != ObjectId.Null)
  768.             ids.Add(id);
  769.         }
  770.       }
  771.       
  772.       // Our free slots have been filled, so clear
  773.       // the list
  774.       
  775.       m_free.Clear();
  776.       return ids;
  777.     }
  778.     // Get the ID of an object at a particular position
  779.     public ObjectId GetObjectId(int pos)
  780.     {
  781.       if (pos < m_ids.Count)
  782.         return m_ids[pos];
  783.       else
  784.         return ObjectId.Null;
  785.     }
  786.     // Get the position of an ObjectId in the list
  787.     public int GetNumber(ObjectId id)
  788.     {
  789.       if (m_ids.Contains(id))
  790.         return m_ids.IndexOf(id);
  791.       else
  792.         return -1;
  793.     }
  794.     // Store an ObjectId in a particular position
  795.     // (shuffle == true will "insert" it, shuffling
  796.     // the remaining objects down,
  797.     // shuffle == false will replace the item in
  798.     // that slot)
  799.     public void NumberObject(
  800.       ObjectId id, int index, bool shuffle)
  801.     {
  802.       // If we're inserting into the list
  803.       if (index < m_ids.Count)
  804.       {
  805.         if (shuffle)
  806.           // Insert takes care of the shuffling
  807.           m_ids.Insert(index, id);
  808.         else
  809.         {
  810.           // If we're replacing the existing item, do
  811.           // so and then make sure the slot is removed
  812.           // from the "free" list, if applicable
  813.           m_ids[index] = id;
  814.           if (m_free.Contains(index))
  815.             m_free.Remove(index);
  816.         }
  817.       }
  818.       else
  819.       {
  820.         // If we're appending, shuffling is irrelevant,
  821.         // but we may need to add additional "free" slots
  822.         // if the position comes after the end
  823.         while (m_ids.Count < index)
  824.         {
  825.           m_ids.Add(ObjectId.Null);
  826.           m_free.Add(m_ids.LastIndexOf(ObjectId.Null));
  827.           m_free.Sort();
  828.         }
  829.         m_ids.Add(id);
  830.       }
  831.     }
  832.     // Move an ObjectId already in the list to a
  833.     // particular position
  834.     // (ObjectIds between the two positions will
  835.     // get shuffled down automatically)
  836.     public ObjectIdCollection MoveObject(
  837.       int from, int to)
  838.     {
  839.       ObjectIdCollection ids =
  840.         new ObjectIdCollection();
  841.       if (from < m_ids.Count &&
  842.           to < m_ids.Count)
  843.       {
  844.         if (from != to)
  845.         {
  846.           ObjectId id = m_ids[from];
  847.           m_ids.RemoveAt(from);
  848.           m_ids.Insert(to, id);
  849.           int start = (from < to ? from : to);
  850.           int end = (from < to ? to : from);
  851.           for (int i = start; i <= end; i++)
  852.           {
  853.             ids.Add(m_ids[i]);
  854.           }
  855.         }
  856.         // Now need to adjust/recreate "free" list
  857.         m_free.Clear();
  858.         for (int j = 0; j < m_ids.Count; j++)
  859.         {
  860.           if (m_ids[j] == ObjectId.Null)
  861.             m_free.Add(j);
  862.         }
  863.       }
  864.       return ids;
  865.     }
  866.     // Remove an ObjectId from the list
  867.     public int RemoveObject(ObjectId id)
  868.     {
  869.       // Check it's non-null and in the list
  870.       if (id != ObjectId.Null &&
  871.           m_ids.Contains(id))
  872.       {
  873.         int pos = m_ids.IndexOf(id);
  874.         RemoveObject(pos);
  875.         return pos;
  876.       }
  877.       return -1;
  878.     }
  879.     // Remove the ObjectId at a particular position
  880.     public ObjectId RemoveObject(int pos)
  881.     {
  882.       // Get the ObjectId in the specified position,
  883.       // making sure it's non-null
  884.       ObjectId id = m_ids[pos];
  885.       if (id != ObjectId.Null)
  886.       {
  887.         // Null out the position and add it to the
  888.         // "free" list
  889.         m_ids[pos] = ObjectId.Null;
  890.         m_free.Add(pos);
  891.         m_free.Sort();
  892.       }
  893.       return id;
  894.     }
  895.     // Dump out the object list information
  896.     // as well as the "free" slots
  897.     public void DumpInfo(Editor ed)
  898.     {
  899.       if (m_ids.Count > 0)
  900.       {
  901.         ed.WriteMessage("\nIdx ObjectId");
  902.         int index = 0;
  903.         foreach (ObjectId id in m_ids)
  904.           ed.WriteMessage("\n{0} {1}", index++, id);
  905.       }
  906.       if (m_free.Count > 0)
  907.       {
  908.         ed.WriteMessage("\n\nFree list: ");
  909.         foreach (int pos in m_free)
  910.           ed.WriteMessage("{0} ", pos);
  911.       }
  912.     }
  913.     // Remove the initial n items from the list
  914.     public void RebaseList(int start)
  915.     {
  916.       // First we remove the ObjectIds
  917.       for (int i=0; i < start; i++)
  918.         m_ids.RemoveAt(0);
  919.       // Then we go through the "free" list...
  920.       int idx = 0;
  921.       while (idx < m_free.Count)
  922.       {
  923.         if (m_free[idx] < start)
  924.           // Remove any that refer to the slots
  925.           // we've removed
  926.           m_free.RemoveAt(idx);
  927.         else
  928.         {
  929.           // Subtracting the number of slots
  930.           // we've removed from the other items
  931.           m_free[idx] -= start;
  932.           idx++;
  933.         }
  934.       }
  935.     }
  936.   }
  937. }
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:
  1. Command: LNS
  2. Lowest index is 1. Make this the start of the list? [Yes/No] <Yes>: Yes
  3. Command: DB
  4. Enter number of bubble to erase: 3
  5. Command: DB
  6. Enter number of bubble to erase: 5
  7. Command: DB
  8. Enter number of bubble to erase: 15
  9. Command: DB
  10. Enter number of bubble to erase: 16
  11. Command: DMP
  12. Idx ObjectId
  13. 0 (2129683752)
  14. 1 (2129683776)
  15. 2 (0)
  16. 3 (2129683824)
  17. 4 (0)
  18. 5 (2129683872)
  19. 6 (2129683896)
  20. 7 (2129683920)
  21. 8 (2129683944)
  22. 9 (2129683968)
  23. 10 (2129683992)
  24. 11 (2129684016)
  25. 12 (2129684040)
  26. 13 (2129684064)
  27. 14 (0)
  28. 15 (0)
  29. 16 (2129684136)
  30. 17 (2129684160)
  31. 18 (2129684184)
  32. 19 (2129684208)
  33. 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:

Now let's try MB:
  1. Command: MB
  2. Enter number of bubble to move: 7
  3. Enter destination position: 2
  4. Command: DMP
  5. Idx ObjectId
  6. 0 (2129683752)
  7. 1 (2129683896)
  8. 2 (2129683776)
  9. 3 (0)
  10. 4 (2129683824)
  11. 5 (0)
  12. 6 (2129683872)
  13. 7 (2129683920)
  14. 8 (2129683944)
  15. 9 (2129683968)
  16. 10 (2129683992)
  17. 11 (2129684016)
  18. 12 (2129684040)
  19. 13 (2129684064)
  20. 14 (0)
  21. 15 (0)
  22. 16 (2129684136)
  23. 17 (2129684160)
  24. 18 (2129684184)
  25. 19 (2129684208)
  26. 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:

And finally we'll compact the list - removing those four free slots - with our RBS command:
  1. Command: RBS
  2. Command: DMP
  3. Idx ObjectId
  4. 0 (2129683752)
  5. 1 (2129683896)
  6. 2 (2129683776)
  7. 3 (2129683824)
  8. 4 (2129683872)
  9. 5 (2129683920)
  10. 6 (2129683944)
  11. 7 (2129683968)
  12. 8 (2129683992)
  13. 9 (2129684016)
  14. 10 (2129684040)
  15. 11 (2129684064)
  16. 12 (2129684136)
  17. 13 (2129684160)
  18. 14 (2129684184)
  19. 15 (2129684208)
复制代码
And here's how that looks:

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...?




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

x
 楼主| 发表于 2009-5-15 11:35:00 | 显示全部楼层
May 16, 2008
An automatic numbering system for AutoCAD blocks using .NET - Part 4
In the original post in this series, we introduced a basic application to number AutoCAD objects, specifically blocks with attributes. In the second post we extended this to make use of a generic numbering system for drawing-resident AutoCAD objects, and in the third post we implemented additional commands to take advantage of this new "kernel".
In this post we're going to extend the application in a few ways: firstly we're going to support duplicates, so that the LNS command which parses the current drawing to understand its numbers will support automatic and semi-automatic renumbering of objects with duplicate numbers. In addition there are a number of new event handlers that have been introduced to automatically renumber objects on creation/insertion/copy, and also to clear the numbering system when a user undoes any action in the drawing (just to be safe :-).
While introducing these event handlers I decide to switch the approach for associating data with a drawing: rather than declaring the variables at a class level and assuming they would be duplicated instantiated appropriately per-document, as shown in this previous post, I decided to encapsulate the variables in a class and specifically instantiate that class and store it per-document, as shown in this previous post.
Here's the updated C# code, with the changed & new lines in red, and here is the complete source file to save you having to strip line numbers:
  1. using Autodesk.AutoCAD.ApplicationServices;using Autodesk.AutoCAD.Runtime;using Autodesk.AutoCAD.DatabaseServices;using Autodesk.AutoCAD.EditorInput;using Autodesk.AutoCAD.Geometry;using System.Collections.Generic;using System.Collections;namespace AutoNumberedBubbles{  public class Commands : IExtensionApplication  {    // Strings identifying the block    // and the attribute name to use    const string blockName = "BUBBLE";    const string attbName = "NUMBER";    // A string to identify our application's    // data in per-document UserData    const string dataKey = "TTIFBubbles";    // Define a class for our custom data    public class BubbleData    {      // A separate object to manage our numbering      private NumberedObjectManager m_nom;      public NumberedObjectManager Nom      {        get { return m_nom; }      }      // A "base" index (for the start of the list)      private int m_baseNumber;      public int BaseNumber      {        get { return m_baseNumber; }        set { m_baseNumber = value; }      }            // A list of blocks added to the database      // which we will then renumber      private List<ObjectId> m_blocksAdded;      public List<ObjectId> BlocksToRenumber      {        get { return m_blocksAdded; }      }      // Constructor      public BubbleData()      {        m_baseNumber = 0;        m_nom = new NumberedObjectManager();        m_blocksAdded = new List<ObjectId>();      }      // Method to clear the contents      public void Reset()      {        m_baseNumber = 0;        m_nom.Clear();        m_blocksAdded.Clear();      }    }    // Constructor    public Commands()    {    }    // Functions called on initialization & termination    public void Initialize()    {      try      {        DocumentCollection dm =          Application.DocumentManager;        Document doc = dm.MdiActiveDocument;        Database db = doc.Database;        Editor ed = doc.Editor;        ed.WriteMessage(          "\nLNS  Load numbering settings by analyzing the current drawing" +          "\nDMP  Print internal numbering information" +          "\nBAP  Create bubbles at points" +          "\nBIC  Create bubbles at the center of circles" +          "\nMB   Move a bubble in the list" +          "\nDB   Delete a bubble" +          "\nRBS  Reorder the bubbles, to close gaps caused by deletion" +          "\nHLB  Highlight a particular bubble"        );        // Hook into some events, to detect and renumber        // blocks added to the database        db.ObjectAppended +=          new ObjectEventHandler(            db_ObjectAppended          );        dm.DocumentCreated +=          new DocumentCollectionEventHandler(            dm_DocumentCreated          );        dm.DocumentLockModeWillChange +=          new DocumentLockModeWillChangeEventHandler(            dm_DocumentLockModeWillChange          );        doc.CommandEnded +=          delegate(object sender, CommandEventArgs e)          {            if (e.GlobalCommandName == "UNDO" ||                e.GlobalCommandName == "U")            {              ed.WriteMessage(                "\nUndo invalidates bubble numbering: call" +                " LNS to reload the numbers for this drawing"              );              GetBubbleData((Document)sender).Reset();            }          };      }      catch      { }    }    public void Terminate()    {    }    // Method to retrieve (or create) the    // BubbleData object for a particular    // document    private BubbleData GetBubbleData(Document doc)    {      Hashtable ud = doc.UserData;      BubbleData bd =        ud[dataKey] as BubbleData;      if (bd == null)      {        object obj = ud[dataKey];        if (obj == null)        {          // Nothing there          bd = new BubbleData();          ud.Add(dataKey, bd);        }        else        {          // Found something different instead          Editor ed = doc.Editor;          ed.WriteMessage(            "Found an object of type "" +            obj.GetType().ToString() +            "" instead of BubbleData.");        }      }      return bd;    }    // Do the same for a particular database    private BubbleData GetBubbleData(Database db)    {      DocumentCollection dm =        Application.DocumentManager;      Document doc =        dm.GetDocument(db);      return GetBubbleData(doc);    }    // When a new document is created, attach our    // ObjectAppended event handler to the new    // database    void dm_DocumentCreated(      object sender,      DocumentCollectionEventArgs e    )    {      e.Document.Database.ObjectAppended +=        new ObjectEventHandler(          db_ObjectAppended        );    }    // When an object is appended to a database,    // add it to a list we care about if it's a    // BlockReference    void db_ObjectAppended(      object sender,      ObjectEventArgs e    )    {      BlockReference br =        e.DBObject as BlockReference;      if (br != null)      {        BubbleData bd =          GetBubbleData(e.DBObject.Database);        bd.BlocksToRenumber.Add(br.ObjectId);      }    }    // When the command (or action) is over,    // take the list of blocks to renumber and    // go through them, renumbering each one    void dm_DocumentLockModeWillChange(      object sender,      DocumentLockModeWillChangeEventArgs e    )    {      Document doc = e.Document;      BubbleData bd =        GetBubbleData(doc);      if (bd.BlocksToRenumber.Count > 0)      {        Database db = doc.Database;        Transaction tr =          db.TransactionManager.StartTransaction();        using (tr)        {          foreach (ObjectId bid in bd.BlocksToRenumber)          {            try            {              BlockReference br =                tr.GetObject(bid, OpenMode.ForRead)                as BlockReference;              if (br != null)              {                BlockTableRecord btr =                  (BlockTableRecord)tr.GetObject(                    br.BlockTableRecord,                    OpenMode.ForRead                );                if (btr.Name == blockName)                {                  AttributeCollection ac =                    br.AttributeCollection;                  foreach (ObjectId aid in ac)                  {                    DBObject obj =                      tr.GetObject(aid, OpenMode.ForRead);                    AttributeReference ar =                      obj as AttributeReference;                    if (ar.Tag == attbName)                    {                      // Change the one we care about                      ar.UpgradeOpen();                      int bubbleNumber =                        bd.BaseNumber +                        bd.Nom.NextObjectNumber(bid);                      ar.TextString =                        bubbleNumber.ToString();                      break;                    }                  }                }              }            }            catch { }          }          tr.Commit();          bd.BlocksToRenumber.Clear();        }      }    }    // Command to extract and display information    // about the internal numbering    [CommandMethod("DMP")]    public void DumpNumberingInformation()    {      Document doc =        Application.DocumentManager.MdiActiveDocument;      Editor ed = doc.Editor;      BubbleData bd =        GetBubbleData(doc);      bd.Nom.DumpInfo(ed);    }    // Command to analyze the current document and    // understand which indeces have been used and    // which are currently free    [CommandMethod("LNS")]    public void LoadNumberingSettings()    {      Document doc =        Application.DocumentManager.MdiActiveDocument;      Database db = doc.Database;      Editor ed = doc.Editor;      BubbleData bd =        GetBubbleData(doc);      // We need to clear any internal state      // already collected      bd.Reset();      // Select all the blocks in the current drawing      TypedValue[] tvs =        new TypedValue[1] {            new TypedValue(              (int)DxfCode.Start,              "INSERT"            )          };      SelectionFilter sf =        new SelectionFilter(tvs);      PromptSelectionResult psr =        ed.SelectAll(sf);      // If it succeeded and we have some blocks...      if (psr.Status == PromptStatus.OK &&          psr.Value.Count > 0)      {        Transaction tr =          db.TransactionManager.StartTransaction();        using (tr)        {          // First get the modelspace and the ID          // of the block for which we're searching          BlockTableRecord ms;          ObjectId blockId;          if (GetBlock(                db, tr, out ms, out blockId             ))          {            // For each block reference in the drawing...            foreach (SelectedObject o in psr.Value)            {              DBObject obj =                tr.GetObject(o.ObjectId, OpenMode.ForRead);              BlockReference br = obj as BlockReference;              if (br != null)              {                // If it's the one we care about...                if (br.BlockTableRecord == blockId)                {                  // Check its attribute references...                  int pos = -1;                  AttributeCollection ac =                    br.AttributeCollection;                  foreach (ObjectId id in ac)                  {                    DBObject obj2 =                      tr.GetObject(id, OpenMode.ForRead);                    AttributeReference ar =                      obj2 as AttributeReference;                    // When we find the attribute                    // we care about...                    if (ar.Tag == attbName)                    {                      try                      {                        // Attempt to extract the number from                        // the text string property... use a                        // try-catch block just in case it is                        // non-numeric                        pos =                          int.Parse(ar.TextString);                        // Add the object at the appropriate                        // index                        bd.Nom.NumberObject(                          o.ObjectId, pos, false, true                        );                      }                      catch { }                    }                  }                }              }            }          }          tr.Commit();        }        // Once we have analyzed all the block references...        int start = bd.Nom.GetLowerBound(true);        // If the first index is non-zero, ask the user if        // they want to rebase the list to begin at the        // current start position        if (start > 0)        {          ed.WriteMessage(            "\nLowest index is {0}. ",            start          );          PromptKeywordOptions pko =            new PromptKeywordOptions(              "Make this the start of the list?"            );          pko.AllowNone = true;          pko.Keywords.Add("Yes");          pko.Keywords.Add("No");          pko.Keywords.Default = "Yes";          PromptResult pkr =            ed.GetKeywords(pko);          if (pkr.Status != PromptStatus.OK)            bd.Reset();          else          {            if (pkr.StringResult == "Yes")            {              // We store our own base number              // (the object used to manage objects              // always uses zero-based indeces)              bd.BaseNumber = start;              bd.Nom.RebaseList(bd.BaseNumber);            }          }        }        // We found duplicates in the numbering...                if (bd.Nom.HasDuplicates())        {          // Ask how to fix the duplicates          PromptKeywordOptions pko =            new PromptKeywordOptions(              "Blocks contain duplicate numbers. " +              "How do you want to renumber?"            );          pko.AllowNone = true;          pko.Keywords.Add("Automatically");          pko.Keywords.Add("Individually");          pko.Keywords.Add("Not");          pko.Keywords.Default = "Automatically";          PromptResult pkr =            ed.GetKeywords(pko);          bool bAuto = false;          bool bManual = false;          if (pkr.Status != PromptStatus.OK)            bd.Reset();          else          {            if (pkr.StringResult == "Automatically")              bAuto = true;            else if (pkr.StringResult == "Individually")              bManual = true;            // Whether fixing automatically or manually            // we will iterate through the duplicate list            if (bAuto || bManual)            {              ObjectIdCollection idc =                new ObjectIdCollection();              // Get each entry in the duplicate list              SortedDictionary<int,List<ObjectId>> dups =                bd.Nom.Duplicates;              foreach (                KeyValuePair<int,List<ObjectId>> dup in dups              )              {                // The position is the key in the entry                // and the list of IDs is the value                // (we take a copy, so we can modify it                // without affecting the original)                int pos = dup.Key;                List<ObjectId> ids =                  new List<ObjectId>(dup.Value);                // For automatic renumbering there's no                // user interaction                if (bAuto)                {                  foreach (ObjectId id in ids)                  {                    bd.Nom.NextObjectNumber(id);                    idc.Add(id);                  }                }                else // bManual                {                  // For manual renumbering we ask the user                  // to select the block to keep, then                  // we renumber the rest automatically                  ed.UpdateScreen();                  ids.Add(bd.Nom.GetObjectId(pos));                  HighlightBubbles(db, ids, true);                  ed.WriteMessage(                    "\n\nHighlighted blocks " +                    "with number {0}. ",                    pos + bd.BaseNumber                  );                  bool finished = false;                  while (!finished)                  {                    PromptEntityOptions peo =                      new PromptEntityOptions(                        "Select block to keep (others " +                        "will be renumbered automatically): "                      );                    peo.SetRejectMessage(                      "\nEntity must be a block."                    );                    peo.AddAllowedClass(                      typeof(BlockReference), false);                    PromptEntityResult per =                      ed.GetEntity(peo);                    if (per.Status != PromptStatus.OK)                    {                      bd.Reset();                      return;                    }                    else                    {                      // A block has been selected, so we                      // make sure it is one of the ones                      // we highlighted for the user                      if (ids.Contains(per.ObjectId))                      {                        // Leave the selected block alone                        // by removing it from the list                        ids.Remove(per.ObjectId);                        // We then renumber each block in                        // the list                        foreach (ObjectId id in ids)                        {                          bd.Nom.NextObjectNumber(id);                          idc.Add(id);                        }                        RenumberBubbles(db, idc);                        idc.Clear();                                                // Let's unhighlight our selected                        // block (renumbering will do this                        // for the others)                        List<ObjectId> redraw =                          new List<ObjectId>(1);                        redraw.Add(per.ObjectId);                        HighlightBubbles(db, redraw, false);                        finished = true;                      }                      else                      {                        ed.WriteMessage(                          "\nBlock selected is not " +                          "numbered with {0}. ",                          pos + bd.BaseNumber                        );                      }                    }                  }                }              }              RenumberBubbles(db, idc);            }            bd.Nom.Duplicates.Clear();            ed.UpdateScreen();          }        }      }    }    // Take a list of objects and either highlight    // or unhighlight them, depending on the flag    private void HighlightBubbles(      Database db, List<ObjectId> ids, bool highlight)    {      Transaction tr =        db.TransactionManager.StartTransaction();      using (tr)      {        foreach (ObjectId id in ids)        {          Entity ent =            (Entity)tr.GetObject(              id,              OpenMode.ForRead            );          if (highlight)            ent.Highlight();          else            ent.Draw();        }        tr.Commit();      }    }        // Command to create bubbles at points selected    // by the user - loops until cancelled    [CommandMethod("BAP")]    public void BubblesAtPoints()    {      Document doc =        Application.DocumentManager.MdiActiveDocument;      Database db = doc.Database;      Editor ed = doc.Editor;      Autodesk.AutoCAD.ApplicationServices.      TransactionManager tm =        doc.TransactionManager;      Transaction tr =        tm.StartTransaction();      using (tr)      {        // Get the information about the block        // and attribute definitions we care about        BlockTableRecord ms;        ObjectId blockId;        AttributeDefinition ad;        List<AttributeDefinition> other;        if (GetBlock(              db, tr, out ms, out blockId           ))        {          GetBlockAttributes(            tr, blockId, out ad, out other          );          // By default the modelspace is returned to          // us in read-only state          ms.UpgradeOpen();          // Loop until cancelled          bool finished = false;          while (!finished)          {            PromptPointOptions ppo =              new PromptPointOptions("\nSelect point: ");            ppo.AllowNone = true;            PromptPointResult ppr =              ed.GetPoint(ppo);            if (ppr.Status != PromptStatus.OK)              finished = true;            else              // Call a function to create our bubble              CreateNumberedBubbleAtPoint(                db, ms, tr, ppr.Value,                blockId, ad, other              );            tm.QueueForGraphicsFlush();            tm.FlushGraphics();          }        }        tr.Commit();      }    }    // Command to create a bubble at the center of    // each of the selected circles    [CommandMethod("BIC")]    public void BubblesInCircles()    {      Document doc =        Application.DocumentManager.MdiActiveDocument;      Database db = doc.Database;      Editor ed = doc.Editor;      // Allow the user to select circles      TypedValue[] tvs =        new TypedValue[1] {            new TypedValue(              (int)DxfCode.Start,              "CIRCLE"            )          };      SelectionFilter sf =        new SelectionFilter(tvs);      PromptSelectionResult psr =        ed.GetSelection(sf);      if (psr.Status == PromptStatus.OK &&          psr.Value.Count > 0)      {        Transaction tr =          db.TransactionManager.StartTransaction();        using (tr)        {          // Get the information about the block          // and attribute definitions we care about          BlockTableRecord ms;          ObjectId blockId;          AttributeDefinition ad;          List<AttributeDefinition> other;          if (GetBlock(                db, tr, out ms, out blockId             ))          {            GetBlockAttributes(              tr, blockId, out ad, out other            );            // By default the modelspace is returned to            // us in read-only state            ms.UpgradeOpen();            foreach (SelectedObject o in psr.Value)            {              // For each circle in the selected list...              DBObject obj =                tr.GetObject(o.ObjectId, OpenMode.ForRead);              Circle c = obj as Circle;              if (c == null)                ed.WriteMessage(                  "\nObject selected is not a circle."                );              else                // Call our numbering function, passing the                // center of the circle                CreateNumberedBubbleAtPoint(                  db, ms, tr, c.Center,                  blockId, ad, other                );            }          }          tr.Commit();        }      }    }    // Command to delete a particular bubble    // selected by its index    [CommandMethod("MB")]    public void MoveBubble()    {      Document doc =        Application.DocumentManager.MdiActiveDocument;      Editor ed = doc.Editor;      BubbleData bd =        GetBubbleData(doc);      // Use a helper function to select a valid bubble index      int pos =        GetBubbleNumber(          ed, bd,          "\nEnter number of bubble to move: "        );      if (pos >= bd.BaseNumber)      {        int from = pos - bd.BaseNumber;        pos =          GetBubbleNumber(            ed, bd,            "\nEnter destination position: "          );        if (pos >= bd.BaseNumber)        {          int to = pos - bd.BaseNumber;          ObjectIdCollection ids =            bd.Nom.MoveObject(from, to);          RenumberBubbles(doc.Database, ids);        }      }    }    // Command to delete a particular bubbler,    // selected by its index    [CommandMethod("DB")]    public void DeleteBubble()    {      Document doc =        Application.DocumentManager.MdiActiveDocument;      Database db = doc.Database;      Editor ed = doc.Editor;      BubbleData bd =        GetBubbleData(doc);      // Use a helper function to select a valid bubble index      int pos =        GetBubbleNumber(          ed, bd,          "\nEnter number of bubble to erase: "        );      if (pos >= bd.BaseNumber)      {        // Remove the object from the internal list        // (this returns the ObjectId stored for it,        // which we can then use to erase the entity)        ObjectId id =          bd.Nom.RemoveObject(pos - bd.BaseNumber);        Transaction tr =          db.TransactionManager.StartTransaction();        using (tr)        {          DBObject obj =            tr.GetObject(id, OpenMode.ForWrite);          obj.Erase();          tr.Commit();        }      }    }    // Command to reorder all the bubbles in the drawing,    // closing all the gaps between numbers but maintaining    // the current numbering order    [CommandMethod("RBS")]    public void ReorderBubbles()    {      Document doc =        Application.DocumentManager.MdiActiveDocument;      BubbleData bd =        GetBubbleData(doc);      // Re-order the bubbles - the IDs returned are      // of the objects that need to be renumbered      ObjectIdCollection ids =        bd.Nom.ReorderObjects();      RenumberBubbles(doc.Database, ids);    }    // Command to highlight a particular bubble    [CommandMethod("HLB")]    public void HighlightBubble()    {      Document doc =        Application.DocumentManager.MdiActiveDocument;      Database db = doc.Database;      Editor ed = doc.Editor;      BubbleData bd =        GetBubbleData(doc);      // Use our function to select a valid bubble index      int pos =        GetBubbleNumber(          ed, bd,          "\nEnter number of bubble to highlight: "        );      if (pos >= bd.BaseNumber)      {        ObjectId id =          bd.Nom.GetObjectId(pos - bd.BaseNumber);        if (id == ObjectId.Null)        {          ed.WriteMessage(            "\nNumber is not currently used -" +            " nothing to highlight."          );          return;        }        List<ObjectId> ids =          new List<ObjectId>(1);        ids.Add(id);        HighlightBubbles(db, ids, true);      }    }    // Internal helper function to open and retrieve    // the model-space and the block def we care about    private bool      GetBlock(        Database db,        Transaction tr,        out BlockTableRecord ms,        out ObjectId blockId      )    {      BlockTable bt =        (BlockTable)tr.GetObject(          db.BlockTableId,          OpenMode.ForRead        );      if (!bt.Has(blockName))      {        Document doc =          Application.DocumentManager.MdiActiveDocument;        Editor ed = doc.Editor;        ed.WriteMessage(          "\nCannot find block definition "" +          blockName +          "" in the current drawing."        );        blockId = ObjectId.Null;        ms = null;        return false;      }      ms =        (BlockTableRecord)tr.GetObject(          bt[BlockTableRecord.ModelSpace],          OpenMode.ForRead        );      blockId = bt[blockName];      return true;    }    // Internal helper function to retrieve    // attribute info from our block    // (we return the main attribute def    // and then all the "others")    private void      GetBlockAttributes(        Transaction tr,        ObjectId blockId,        out AttributeDefinition ad,        out List<AttributeDefinition> other      )    {      BlockTableRecord blk =        (BlockTableRecord)tr.GetObject(          blockId,          OpenMode.ForRead        );      ad = null;      other =        new List<AttributeDefinition>();      foreach (ObjectId attId in blk)      {        DBObject obj =          (DBObject)tr.GetObject(            attId,            OpenMode.ForRead          );        AttributeDefinition ad2 =          obj as AttributeDefinition;        if (ad2 != null)        {          if (ad2.Tag == attbName)          {            if (ad2.Constant)            {              Document doc =                Application.DocumentManager.MdiActiveDocument;              Editor ed = doc.Editor;              ed.WriteMessage(                "\nAttribute to change is constant!"              );            }            else              ad = ad2;          }          else            if (!ad2.Constant)              other.Add(ad2);        }      }    }    // Internal helper function to create a bubble    // at a particular point    private Entity      CreateNumberedBubbleAtPoint(        Database db,        BlockTableRecord btr,        Transaction tr,        Point3d pt,        ObjectId blockId,        AttributeDefinition ad,        List<AttributeDefinition> other      )    {      BubbleData bd =        GetBubbleData(db);      //  Create a new block reference      BlockReference br =        new BlockReference(pt, blockId);      // Add it to the database      br.SetDatabaseDefaults();      ObjectId blockRefId = btr.AppendEntity(br);      tr.AddNewlyCreatedDBObject(br, true);      // Create an attribute reference for our main      // attribute definition (where we'll put the      // bubble's number)      AttributeReference ar =        new AttributeReference();      // Add it to the database, and set its position, etc.      ar.SetDatabaseDefaults();      ar.SetAttributeFromBlock(ad, br.BlockTransform);      ar.Position =        ad.Position.TransformBy(br.BlockTransform);      ar.Tag = ad.Tag;      // Set the bubble's number      int bubbleNumber =        bd.BaseNumber +        bd.Nom.NextObjectNumber(blockRefId);      ar.TextString = bubbleNumber.ToString();      ar.AdjustAlignment(db);      // Add the attribute to the block reference      br.AttributeCollection.AppendAttribute(ar);      tr.AddNewlyCreatedDBObject(ar, true);      // Now we add attribute references for the      // other attribute definitions      foreach (AttributeDefinition ad2 in other)      {        AttributeReference ar2 =          new AttributeReference();        ar2.SetAttributeFromBlock(ad2, br.BlockTransform);        ar2.Position =          ad2.Position.TransformBy(br.BlockTransform);        ar2.Tag = ad2.Tag;        ar2.TextString = ad2.TextString;        ar2.AdjustAlignment(db);        br.AttributeCollection.AppendAttribute(ar2);        tr.AddNewlyCreatedDBObject(ar2, true);      }      return br;    }    // Internal helper function to have the user    // select a valid bubble index    private int      GetBubbleNumber(        Editor ed,        BubbleData bd,        string prompt      )    {      int upper = bd.Nom.GetUpperBound();      if (upper <= 0)      {        ed.WriteMessage(          "\nNo bubbles are currently being managed."        );        return upper;      }      PromptIntegerOptions pio =        new PromptIntegerOptions(prompt);      pio.AllowNone = false;      // Get the limits from our manager object      pio.LowerLimit =        bd.BaseNumber +        bd.Nom.GetLowerBound(false);      pio.UpperLimit =        bd.BaseNumber +        upper;      PromptIntegerResult pir =        ed.GetInteger(pio);      if (pir.Status == PromptStatus.OK)        return pir.Value;      else        return -1;    }    // Internal helper function to open up a list    // of bubbles and reset their number to the    // position in the list    private void RenumberBubbles(      Database db, ObjectIdCollection ids)    {      BubbleData bd =        GetBubbleData(db);      Transaction tr =        db.TransactionManager.StartTransaction();      using (tr)      {        // Get the block information        BlockTableRecord ms;        ObjectId blockId;        if (GetBlock(              db, tr, out ms, out blockId           ))        {          // Open each bubble to be renumbered          foreach (ObjectId bid in ids)          {            if (bid != ObjectId.Null)            {              DBObject obj =                tr.GetObject(bid, OpenMode.ForRead);              BlockReference br = obj as BlockReference;              if (br != null)              {                if (br.BlockTableRecord == blockId)                {                  AttributeCollection ac =                    br.AttributeCollection;                  // Go through its attributes                  foreach (ObjectId aid in ac)                  {                    DBObject obj2 =                      tr.GetObject(aid, OpenMode.ForRead);                    AttributeReference ar =                      obj2 as AttributeReference;                    if (ar.Tag == attbName)                    {                      // Change the one we care about                      ar.UpgradeOpen();                      int bubbleNumber =                        bd.BaseNumber +                        bd.Nom.GetNumber(bid);                      ar.TextString =                        bubbleNumber.ToString();                      break;                    }                  }                }              }            }          }        }        tr.Commit();      }    }  }  // A generic class for managing groups of  // numbered (and ordered) objects  public class NumberedObjectManager  {    // Store the IDs of the objects we're managing    private List<ObjectId> m_ids;    // A list of free positions in the above list    // (allows numbering gaps)    private List<int> m_free;    // A map of duplicates - blocks detected with    // the number of an existing block    private SortedDictionary<int,List<ObjectId>> m_dups;    public SortedDictionary<int, List<ObjectId>> Duplicates    {      get { return m_dups; }    }    // Constructor    public NumberedObjectManager()    {      m_ids =        new List<ObjectId>();      m_free =        new List<int>();      m_dups =        new SortedDictionary<int, List<ObjectId>>();    }    // Clear the internal lists    public void Clear()    {      m_ids.Clear();      m_free.Clear();      m_dups.Clear();    }    // Does the duplicate list contain anything?    public bool HasDuplicates()    {      return m_dups.Count > 0;    }    // Return the first entry in the ObjectId list    // (specify "true" if you want to skip    // any null object IDs)    public int GetLowerBound(bool ignoreNull)    {      if (ignoreNull)        // Define an in-line predicate to check        // whether an ObjectId is null        return          m_ids.FindIndex(            delegate(ObjectId id)            {              return id != ObjectId.Null;            }          );      else        return 0;    }    // Return the last entry in the ObjectId list    public int GetUpperBound()    {      return m_ids.Count - 1;    }    // Store the specified ObjectId in the next    // available location in the list, and return    // what that is    public int NextObjectNumber(ObjectId id)    {      int pos;      if (m_free.Count > 0)      {        // Get the first free position, then remove        // it from the "free" list        pos = m_free[0];        m_free.RemoveAt(0);        m_ids[pos] = id;      }      else      {        // There are no free slots (gaps in the numbering)        // so we append it to the list        pos = m_ids.Count;        m_ids.Add(id);      }      return pos;    }    // Go through the list of objects and close any gaps    // by shuffling the list down (easy, as we're using a    // List<> rather than an array)    public ObjectIdCollection ReorderObjects()    {      // Create a collection of ObjectIds we'll return      // for the caller to go and update      // (so the renumbering will gets reflected      // in the objects themselves)      ObjectIdCollection ids =        new ObjectIdCollection();      // We'll go through the "free" list backwards,      // to allow any changes made to the list of      // objects to not affect what we're doing      List<int> rev =        new List<int>(m_free);      rev.Reverse();      foreach (int pos in rev)      {        // First we remove the object at the "free"        // position (in theory this should be set to        // ObjectId.Null, as the slot has been marked        // as blank)        m_ids.RemoveAt(pos);        // Now we go through and add the IDs of any        // affected objects to the list to return        for (int i = pos; i < m_ids.Count; i++)        {          ObjectId id = m_ids[pos];          // Only add non-null objects          // not already in the list                    if (!ids.Contains(id) &&              id != ObjectId.Null)            ids.Add(id);        }      }            // Our free slots have been filled, so clear      // the list            m_free.Clear();      return ids;    }    // Get the ID of an object at a particular position    public ObjectId GetObjectId(int pos)    {      if (pos < m_ids.Count)        return m_ids[pos];      else        return ObjectId.Null;    }    // Get the position of an ObjectId in the list    public int GetNumber(ObjectId id)    {      if (m_ids.Contains(id))        return m_ids.IndexOf(id);      else        return -1;    }    // Store an ObjectId in a particular position    // (shuffle == true will "insert" it, shuffling    // the remaining objects down,    // shuffle == false will replace the item in    // that slot)    public void NumberObject(      ObjectId id, int index, bool shuffle, bool dups)    {      // If we're inserting into the list      if (index < m_ids.Count)      {        if (shuffle)          // Insert takes care of the shuffling          m_ids.Insert(index, id);        else        {          // If we're replacing the existing item, do          // so and then make sure the slot is removed          // from the "free" list, if applicable          if (!dups ||              m_ids[index] == ObjectId.Null)          {            m_ids[index] = id;            if (m_free.Contains(index))              m_free.Remove(index);          }          else          {            // If we're tracking duplicates, add our new            // object to the duplicate list for that index            if (dups)            {              List<ObjectId> ids;              if (m_dups.ContainsKey(index))              {                ids = m_dups[index];                m_dups.Remove(index);              }              else                ids = new List<ObjectId>();              ids.Add(id);              m_dups.Add(index, ids);            }          }        }      }      else      {        // If we're appending, shuffling is irrelevant,        // but we may need to add additional "free" slots        // if the position comes after the end        while (m_ids.Count < index)        {          m_ids.Add(ObjectId.Null);          m_free.Add(m_ids.LastIndexOf(ObjectId.Null));          m_free.Sort();        }        m_ids.Add(id);      }    }    // Move an ObjectId already in the list to a    // particular position    // (ObjectIds between the two positions will    // get shuffled down automatically)    public ObjectIdCollection MoveObject(      int from, int to)    {      ObjectIdCollection ids =        new ObjectIdCollection();      if (from < m_ids.Count &&          to < m_ids.Count)      {        if (from != to)        {          ObjectId id = m_ids[from];          m_ids.RemoveAt(from);          m_ids.Insert(to, id);          int start = (from < to ? from : to);          int end = (from < to ? to : from);          for (int i = start; i <= end; i++)          {            ids.Add(m_ids[i]);          }        }        // Now need to adjust/recreate "free" list        m_free.Clear();        for (int j = 0; j < m_ids.Count; j++)        {          if (m_ids[j] == ObjectId.Null)            m_free.Add(j);        }      }      return ids;    }    // Remove an ObjectId from the list    public int RemoveObject(ObjectId id)    {      // Check it's non-null and in the list      if (id != ObjectId.Null &&          m_ids.Contains(id))      {        int pos = m_ids.IndexOf(id);        RemoveObject(pos);        return pos;      }      return -1;    }    // Remove the ObjectId at a particular position    public ObjectId RemoveObject(int pos)    {      // Get the ObjectId in the specified position,      // making sure it's non-null      ObjectId id = m_ids[pos];      if (id != ObjectId.Null)      {        // Null out the position and add it to the        // "free" list        m_ids[pos] = ObjectId.Null;        m_free.Add(pos);        m_free.Sort();      }      return id;    }    // Dump out the object list information    // as well as the "free" slots    public void DumpInfo(Editor ed)    {      if (m_ids.Count > 0)      {        ed.WriteMessage("\nIdx ObjectId");        int index = 0;        foreach (ObjectId id in m_ids)          ed.WriteMessage("\n{0} {1}", index++, id);      }      if (m_free.Count > 0)      {        ed.WriteMessage("\n\nFree list: ");        foreach (int pos in m_free)          ed.WriteMessage("{0} ", pos);      }      if (HasDuplicates())      {        ed.WriteMessage("\n\nDuplicate list: ");        foreach (          KeyValuePair<int, List<ObjectId>> dup          in m_dups        )        {          int pos = dup.Key;          List<ObjectId> ids = dup.Value;          ed.WriteMessage("\n{0}    ", pos);          foreach (ObjectId id in ids)          {            ed.WriteMessage("{0} ", id);          }        }      }    }    // Remove the initial n items from the list    public void RebaseList(int start)    {      // First we remove the ObjectIds      for (int i=0; i < start; i++)        m_ids.RemoveAt(0);      // Then we go through the "free" list...      int idx = 0;      while (idx < m_free.Count)      {        if (m_free[idx] < start)          // Remove any that refer to the slots          // we've removed           m_free.RemoveAt(idx);        else        {          // Subtracting the number of slots          // we've removed from the other items          m_free[idx] -= start;          idx++;        }      }      // The duplicate list is more tricky, as we store      // our list of objects against the index      // (we need to remove and re-add each entry)      if (HasDuplicates())      {        // First we get all the indeces (which we use        // to iterate through the list)        SortedDictionary<int, List<ObjectId>>.        KeyCollection kc =          m_dups.Keys;        // Copy the KeyCollection into a list of ints,        // to make it easier to iterate        // (this also allows us to modify the m_dups        // object while we're iterating)        List<int> idxs = new List<int>(kc.Count);        foreach (int pos in kc)          idxs.Add(pos);        foreach (int pos in idxs)        {          List<ObjectId> ids;          m_dups.TryGetValue(pos, out ids);          if (m_dups.ContainsKey(pos - start))            throw new Exception(              ErrorStatus.DuplicateKey,              "\nClash detected - " +              "duplicate list may be corrupted."            );          else          {            // Remove the old entry and add the new one            m_dups.Remove(pos);            m_dups.Add(pos - start, ids);          }        }      }    }  }}
As for the specific changes...
Lines 19-71 encapsulate the information we need to store per-document, with lines 140-183 allowing retrieval of this data.
Line 102-130 and 185-289 add event handlers to the application. Note that we watch Database.ObjectAppended event to find when numbered objects are added to a drawing, but we do the actually work of renumbering the objects during DocumentCollection.DocumentLockWillChange - the safe place to do so.
A lot of the additional line changes are simply to access the new per-document data via the BubbleData class: 300-302, 316-317, 322, 402, 418, 452-453, 800-801, 811, 813, 821, 823, 826, 843-844, 854,861, 883-884, 890, 904-905, 915, 1050-1051, 1083-1084, 1124, 1140-1141, 1143, 1161-1162, 1208-1209.
Lines 458-616 add the ability to renumber bubbles while scanning the drawing, whether automatically (with no user intervention) or semi-automatically (allowing the user to choose a specific bubble not to renumber). This latter process uses a new HighlightBubbles() function (lines 620-644) to highlight a list of bubbles (it's generic, so could be called HighlightObjects(), thinking about it). This is then also used by the HLB command, replacing the previous implementation (lines 917-931).
We now pass the BubbleData class through to the GetBubbleNumber() function, in line 1120. This is then used in lines 807, 817, 850 & 911.
The NumberedObjectManager class has required infrastructure changes to support duplicates: 1240-1247, 1259-1260, 1269 & 1272-1277. We're using a SortedDictionary to maintain a list of ObjectIds per position. This is a standard "overflow" data structure, and is only added to when a duplicate is found.
DumpInfo() now displays duplicate data in the DMP command (lines 1571-1590).
RebaseList() has been changed to move entries in the duplicate list. This ended up being quite complicated, as we're mapping a list of objects against a position (the dictionary key) and so it's the key that changes when we move the list's base.
To try out this additional functionality, try this:
Open a drawing with a bunch of bubbles, or create new ones.
Copy & paste these bubbles one or more times, to seed the drawing with duplicate-numbered objects.
Load the application and run the LNS command to understand the numbers in the drawing. Try the different options for renumbering duplicates (Automatically, Individually or Not).
Next try copying and pasting again, once the numbering system is active, and see how the copied numbers are changed.
INSERT a few bubbles: new blocks will also be renumbered - even if the user selects a particular number via the attribute editing dialog - but that's somewhat inevitable with this approach.
OK - now that we're up at nearly 1700 lines of code, it's getting time to bring this series to a close... (or at least to stop including the entire code in each post. :-)
 楼主| 发表于 2009-5-15 11:40:00 | 显示全部楼层
本帖最后由 作者 于 2010-11-15 22:29:09 编辑

四、在选择集中使用条件组合
July 02, 2008
Conditional selection of AutoCAD entities using .NET
This post was inspired by a comment on this previous post, where we looked at some code to select entities on a specific layer. The question was regarding how best to select entities from multiple layers: the selection filtering mechanism inside AutoCAD makes this very easy, and can cope with composition of conditions related to various entity properties.
The basic concept is to enclose sets of entity properties for which you wish to filter with tags indicating the composition of the conditions: for "or" you enclose the conditions with "<or" and "or>" and for "and" you use "<and" and "and>". Wow: I can safely say that that's probably the only sentence I've ever written that has 6 of the last 10 words being "and". :-)
Let's take a concrete example: let's say we want to select all lines on on layer 0 and all the circles with radii greater than 10.'s how we would compose the conditions, in pseudo-code:
<or <and Layer == "0" Entity type == "LINE"and> <and Entity type == "CIRCLE" Radius >= 10.0and>or>This translates into the following C# code - for clarity I've left the specific properties/values hard-coded, but clearly it would be straightforward to ask the user or pick them out of a database, as needed.
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.Runtime;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. namespace EntitySelection
  6. {
  7.   public class Commands
  8.   {
  9.     [CommandMethod("SEWP")]
  10.     static public void SelectEntitiesWithProperties()
  11.     {
  12.       Document doc =
  13.         Application.DocumentManager.MdiActiveDocument;
  14.       Editor ed = doc.Editor;
  15.       // Build a conditional filter list so that only
  16.       // entities with the specified properties are
  17.       // selected
  18.       TypedValue[] tvs =
  19.         new TypedValue[] {
  20.           new TypedValue(
  21.             (int)DxfCode.Operator,
  22.             "<or"
  23.           ),
  24.           new TypedValue(
  25.             (int)DxfCode.Operator,
  26.             "<and"
  27.           ),
  28.           new TypedValue(
  29.             (int)DxfCode.LayerName,
  30.             "0"
  31.           ),
  32.           new TypedValue(
  33.             (int)DxfCode.Start,
  34.             "LINE"
  35.           ),
  36.           new TypedValue(
  37.             (int)DxfCode.Operator,
  38.             "and>"
  39.           ),
  40.           new TypedValue(
  41.             (int)DxfCode.Operator,
  42.             "<and"
  43.           ),
  44.           new TypedValue(
  45.             (int)DxfCode.Start,
  46.             "CIRCLE"
  47.           ),
  48.           new TypedValue(
  49.             (int)DxfCode.Operator,
  50.             ">="
  51.           ),
  52.           new TypedValue(
  53.             (int)DxfCode.Real, // Circle Radius
  54.             10.0
  55.           ),
  56.           new TypedValue(
  57.             (int)DxfCode.Operator,
  58.             "and>"
  59.           ),
  60.           new TypedValue(
  61.             (int)DxfCode.Operator,
  62.             "or>"
  63.           )
  64.         };
  65.       SelectionFilter sf =
  66.         new SelectionFilter(tvs);
  67.       PromptSelectionResult psr =
  68.         ed.SelectAll(sf);
  69.       ed.WriteMessage(
  70.         "\nFound {0} entit{1}.",
  71.         psr.Value.Count,
  72.         (psr.Value.Count == 1 ? "y" : "ies")
  73.       );
  74.     }
  75.   }
  76. }
By the way - you can also choose to perform an "exclusive or" test by using "<xor" and "xor>".
To try out this code, draw a number of lines in a blank drawing, and run the SEWP command. This simply tells you how many entities met the selection criteria - it doesn't leave them selected for use by further commands. You can then see how drawing circles of varying radii changes the number of entities selected by the command.
On a final note... SelectionFilters can be used either non-interactively (as in this example, via the SelectAll() method) or interactively (via GetSelection(), SelectWindow(), SelectCrossingPolygon(), SelectFence(), etc.). I've shown simple uses of SelectionFilters in previous posts, but it's also possible to use quite complicated groupings of conditions - as we've scratched the surface of in this post.

 楼主| 发表于 2009-5-15 11:43:00 | 显示全部楼层
本帖最后由 作者 于 2009-5-17 15:01:42 编辑

五、交换图元特征值
July 07, 2008
Swapping identities of AutoCAD objects using .NET
Everything aches after a very enjoyable soccer tournament in Prague (to which I mentioned I was heading off in my last post). But it was well worth the pain; it was really a lot of fun playing with and against (and just catching up with) so many friends and colleagues.
I received this question a week or so ago by email:
Is there any way to change the handle of an object through the API? Ideally I would like to select two objects and swap the handle.
The code in this post does just that: it asks the user to select two entities, and then swaps their identities (their Handles and ObjectIds), printing some "before & after" information to prove something actually happened.
A quick reminder... please feel free to send through interesting topics by email, but I must stress that these requests will be looked upon only as potential material for blog posts: if you need support related to a specific problem within a particular timeframe, please make use of the public discussion groups or submit your question via the ADN website, if you're a member. In case you're wondering why I'm not available to answer all your questions - something I often wish I could do - I should probably point out that I have a "day job" (managing the worldwide DevTech team) in addition to the time I spend blogging. There simply aren't enough hours in the day for me to do everything, however sad that makes me.
[By the way... if you're not an Autodesk Developer Network member, I do recommend joining if you have a strong need for API support: my team provides API support of a very high quality (something we measure through satisfaction surveys, etc.) and - assuming you value timely, quality support - this and the other program benefits should more than justify the cost. And ADN is not just for developers of commercial software: an increasing number of customers (and consultants implementing software specifically for customers) are joining the program.]
Anyway - back to the topic at hand... at the root of the below code is a single API call: DBObject.SwapIdWith(). Here's what the AutoCAD .NET documentation (found in the ObjectARX SDK, for now), says about the SwapIdWith() function:
This function swaps objectIds and handles between the object specified by otherId and the object invoking this function. Both objects must currently be database-resident and must reside in the same database. If swapExtendedData is true, then the objects swap extended entity data as well. If swapExtensionDictionary is true, then the objects swap extension dictionaries also.
So this function does exactly what was asked: all we then need to do is wrap it up with some code to select the entities and dump their identities to the command-line/text-screen.
Here's the C# code:
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.Runtime;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. namespace ObjectIdentity
  6. {
  7.   public class Commands
  8.   {
  9.     [CommandMethod("SE")]
  10.     static public void SwapEntities()
  11.     {
  12.       Document doc =
  13.         Application.DocumentManager.MdiActiveDocument;
  14.       Editor ed = doc.Editor;
  15.       PromptEntityResult per =
  16.         ed.GetEntity("\nSelect first entity: ");
  17.       if (per.Status != PromptStatus.OK)
  18.         return;
  19.       ObjectId firstId = per.ObjectId;
  20.       per = ed.GetEntity("\nSelect second entity: ");
  21.       if (per.Status != PromptStatus.OK)
  22.         return;
  23.       ObjectId secondId = per.ObjectId;
  24.       Transaction tr =
  25.         doc.Database.TransactionManager.StartTransaction();
  26.       using (tr)
  27.       {
  28.         DBObject firstObj =
  29.           tr.GetObject(firstId, OpenMode.ForRead);
  30.         DBObject secondObj =
  31.           tr.GetObject(secondId, OpenMode.ForRead);
  32.         PrintIdentities(firstObj, secondObj);
  33.         PromptKeywordOptions pko =
  34.           new PromptKeywordOptions(
  35.             "\nSwap their identities?"
  36.           );
  37.         pko.AllowNone = true;
  38.         pko.Keywords.Add("Yes");
  39.         pko.Keywords.Add("No");
  40.         pko.Keywords.Default = "No";
  41.         PromptResult pkr =
  42.           ed.GetKeywords(pko);
  43.         if (pkr.StringResult == "Yes")
  44.         {
  45.           try
  46.           {
  47.             firstObj.UpgradeOpen();
  48.             firstObj.SwapIdWith(secondId, true, true);
  49.             PrintIdentities(firstObj, secondObj);
  50.           }
  51.           catch (Exception ex)
  52.           {
  53.             ed.WriteMessage(
  54.               "\nCould not swap identities: " + ex.Message
  55.             );
  56.           }
  57.         }
  58.         tr.Commit();
  59.       }
  60.     }
  61.     private static void PrintIdentities(
  62.       DBObject first, DBObject second)
  63.     {
  64.       PrintIdentity(first, "First");
  65.       PrintIdentity(second, "Second");
  66.     }
  67.     private static void PrintIdentity(
  68.       DBObject obj, string name)
  69.     {
  70.       Document doc =
  71.         Application.DocumentManager.MdiActiveDocument;
  72.       Editor ed = doc.Editor;
  73.       ed.WriteMessage(
  74.         "\n{0} object, of type {1}: " +
  75.         "ObjectId is {2}, " +
  76.         "Handle is {3}.",
  77.         name,
  78.         obj.GetType().Name,
  79.         obj.ObjectId,
  80.         obj.Handle
  81.       );
  82.     }
  83.   }
  84. }
Here's what happens when we run the SE command, selecting a Line and a Circle that have been created in a fresh drawing:
  1. Command: SE
  2. Select first entity:
  3. Select second entity:
  4. First object, of type Line: ObjectId is (-1075825472), Handle is 178.
  5. Second object, of type Circle: ObjectId is (-1075825464), Handle is 179.
  6. Swap their identities? [Yes/No] <No>: Y
  7. First object, of type Line: ObjectId is (-1075825464), Handle is 179.
  8. Second object, of type Circle: ObjectId is (-1075825472), Handle is 178.
复制代码
So you can see that the handles and object identifiers of the Circle and the Line have been swapped.
Another function you may find useful - if you care about maintaining object identity - is DBObject.HandOverTo(). This allows you to replace a Database-resident object with another, non-Database resident one, maintaining the identity (ObjectId and Handle) of the original. This is useful if you want to implement an operation that modifies an object but has an outcome that's of a different object type. For example, if you break a Circle, you may want to be left with an Arc which has the original Circle's identity. But we'll look at this type of scenario more closely in a future post.

 楼主| 发表于 2009-5-15 14:07:00 | 显示全部楼层
本帖最后由 作者 于 2009-5-15 15:49:47 编辑

对于选择集一直想说说,接着这个帖子说下自己对选择集的理解
1、过滤器利器:
(defun c:dxf() (entget(car(entsel)) '("*")))
实际上过滤器并不复杂,你可以把当前文件看成是一个数据库,这个数据库的所有数据按Dxf组码的形式存储,我们的任务就是按自己的需求组合出不同的条件
2、语法
这一部分自行看帮助的相关内容,以及Dxf帮助
3、注意
范围选择时必须保证该范围可见,否则是选不到东东的
对于特殊的范围选择,比如选择点或文本,可以配合组码10

在minpoint和maxpoint两点范围内选择
  1. -4,">=,>=,*"
  2. 10,minpoint
  3. -4,"<=,<=,*"
  4. 10,maxpoint
复制代码
4、几个例子
a、只选择是数字的text/mtext
  1.     0, "*Text",
  2.     1, "~*[~.0-9]*",
  3.     1, "~*.*.*"
复制代码
b、按线宽选择
  1.         public SelectionSet SelectByLineWeight(LineWeight lineWeight)
  2.         {
  3.             List<TypedValue> filter = new List<TypedValue>();
  4.             filter.Add(new TypedValue(370, lineWeight));
  5.             OpenLayerTable(OpenMode.ForRead);
  6.             List<string> lays = new List<string>();
  7.             foreach (ObjectId id in LayerTable)
  8.             {
  9.                 LayerTableRecord ltr = (LayerTableRecord)GetObject(id, OpenMode.ForRead);
  10.                 if (ltr.LineWeight == lineWeight)
  11.                 {
  12.                     lays.Add(ltr.Name);
  13.                 }
  14.             }
  15.             if (lays.Count > 0)
  16.             {
  17.                 string s = string.Join(",", lays.ToArray());
  18.                 filter.Insert(0, new TypedValue(-4, "<or"));
  19.                 filter.Add(new TypedValue(-4, "<and"));
  20.                 filter.Add(new TypedValue(8, s));
  21.                 filter.Add(new TypedValue(370, LineWeight.ByLayer));
  22.                 filter.Add(new TypedValue(-4, "and>"));
  23.                 filter.Add(new TypedValue(-4, "or>"));
  24.             }
  25.             PromptSelectionResult res =CadHelper.Editor.SelectAll(new SelectionFilter(filter.ToArray()));
  26.             return res.Value;
  27.        }
复制代码
      
c、选择曲线
  1.             PromptSelectionResult res =
  2.                 ed.GetSelection(
  3.                 new PromptSelectionOptions(),
  4.                 new SelectionFilter(new TypedValue[] {
  5.                     new TypedValue(0, "*Line,Arc,Circle,Ellipse") }));
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-11-23 08:53 , Processed in 0.246279 second(s), 24 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表