An automatic numbering system for AutoCAD blocks using .NET - Part 3
In the last post we introduced some additional features to the original post in this series. In this post we take advantage of - and further extend - those features, by allowing deletion, movement and compaction of the numbered objects.
Here's the modified C# code, with changed/new lines in red, and here is the the updated source file:
1 using Autodesk.AutoCAD.ApplicationServices;
2 using Autodesk.AutoCAD.Runtime;
3 using Autodesk.AutoCAD.DatabaseServices;
4 using Autodesk.AutoCAD.EditorInput;
5 using Autodesk.AutoCAD.Geometry;
6 using System.Collections.Generic;
7
8 namespace AutoNumberedBubbles
9 {
10 public class Commands : IExtensionApplication
11 {
12 // Strings identifying the block
13 // and the attribute name to use
14
15 const string blockName = "BUBBLE";
16 const string attbName = "NUMBER";
17
18 // We will use a separate object to
19 // manage our numbering, and maintain a
20 // "base" index (the start of the list)
21
22 private NumberedObjectManager m_nom;
23 private int m_baseNumber = 0;
24
25 // Constructor
26
27 public Commands()
28 {
29 m_nom = new NumberedObjectManager();
30 }
31
32 // Functions called on initialization & termination
33
34 public void Initialize()
35 {
36 try
37 {
38 Document doc =
39 Application.DocumentManager.MdiActiveDocument;
40 Editor ed = doc.Editor;
41
42 ed.WriteMessage(
43 "\nLNS Load numbering settings by analyzing the current drawing" +
44 "\nDMP Print internal numbering information" +
45 "\nBAP Create bubbles at points" +
46 "\nBIC Create bubbles at the center of circles" +
47 "\nMB Move a bubble in the list" +
48 "\nDB Delete a bubble" +
49 "\nRBS Reorder the bubbles, to close gaps caused by deletion" +
50 "\nHLB Highlight a particular bubble"
51 );
52 }
53 catch
54 { }
55 }
56
57 public void Terminate()
58 {
59 }
60
61 // Command to extract and display information
62 // about the internal numbering
63
64 [CommandMethod("DMP")]
65 public void DumpNumberingInformation()
66 {
67 Document doc =
68 Application.DocumentManager.MdiActiveDocument;
69 Editor ed = doc.Editor;
70 m_nom.DumpInfo(ed);
71 }
72
73 // Command to analyze the current document and
74 // understand which indeces have been used and
75 // which are currently free
76
77 [CommandMethod("LNS")]
78 public void LoadNumberingSettings()
79 {
80 Document doc =
81 Application.DocumentManager.MdiActiveDocument;
82 Database db = doc.Database;
83 Editor ed = doc.Editor;
84
85 // We need to clear any internal state
86 // already collected
87
88 m_nom.Clear();
89 m_baseNumber = 0;
90
91 // Select all the blocks in the current drawing
92
93 TypedValue[] tvs =
94 new TypedValue[1] {
95 new TypedValue(
96 (int)DxfCode.Start,
97 "INSERT"
98 )
99 };
100 SelectionFilter sf =
101 new SelectionFilter(tvs);
102
103 PromptSelectionResult psr =
104 ed.SelectAll(sf);
105
106 // If it succeeded and we have some blocks...
107
108 if (psr.Status == PromptStatus.OK &&
109 psr.Value.Count > 0)
110 {
111 Transaction tr =
112 db.TransactionManager.StartTransaction();
113 using (tr)
114 {
115 // First get the modelspace and the ID
116 // of the block for which we're searching
117
118 BlockTableRecord ms;
119 ObjectId blockId;
120
121 if (GetBlock(
122 db, tr, out ms, out blockId
123 ))
124 {
125 // For each block reference in the drawing...
126
127 foreach (SelectedObject o in psr.Value)
128 {
129 DBObject obj =
130 tr.GetObject(o.ObjectId, OpenMode.ForRead);
131 BlockReference br = obj as BlockReference;
132 if (br != null)
133 {
134 // If it's the one we care about...
135
136 if (br.BlockTableRecord == blockId)
137 {
138 // Check its attribute references...
139
140 int pos = -1;
141 AttributeCollection ac =
142 br.AttributeCollection;
143
144 foreach (ObjectId id in ac)
145 {
146 DBObject obj2 =
147 tr.GetObject(id, OpenMode.ForRead);
148 AttributeReference ar =
149 obj2 as AttributeReference;
150
151 // When we find the attribute
152 // we care about...
153
154 if (ar.Tag == attbName)
155 {
156 try
157 {
158 // Attempt to extract the number from
159 // the text string property... use a
160 // try-catch block just in case it is
161 // non-numeric
162
163 pos =
164 int.Parse(ar.TextString);
165
166 // Add the object at the appropriate
167 // index
168
169 m_nom.NumberObject(
170 o.ObjectId, pos, false
171 );
172 }
173 catch { }
174 }
175 }
176 }
177 }
178 }
179 }
180 tr.Commit();
181 }
182
183 // Once we have analyzed all the block references...
184
185 int start = m_nom.GetLowerBound(true);
186
187 // If the first index is non-zero, ask the user if
188 // they want to rebase the list to begin at the
189 // current start position
190
191 if (start > 0)
192 {
193 ed.WriteMessage(
194 "\nLowest index is {0}. ",
195 start
196 );
197 PromptKeywordOptions pko =
198 new PromptKeywordOptions(
199 "Make this the start of the list?"
200 );
201 pko.AllowNone = true;
202 pko.Keywords.Add("Yes");
203 pko.Keywords.Add("No");
204 pko.Keywords.Default = "Yes";
205
206 PromptResult pkr =
207 ed.GetKeywords(pko);
208
209 if (pkr.Status == PromptStatus.OK)
210 {
211 if (pkr.StringResult == "Yes")
212 {
213 // We store our own base number
214 // (the object used to manage objects
215 // always uses zero-based indeces)
216
217 m_baseNumber = start;
218 m_nom.RebaseList(m_baseNumber);
219 }
220 }
221 }
222 }
223 }
224
225 // Command to create bubbles at points selected
226 // by the user - loops until cancelled
227
228 [CommandMethod("BAP")]
229 public void BubblesAtPoints()
230 {
231 Document doc =
232 Application.DocumentManager.MdiActiveDocument;
233 Database db = doc.Database;
234 Editor ed = doc.Editor;
235 Autodesk.AutoCAD.ApplicationServices.
236 TransactionManager tm =
237 doc.TransactionManager;
238
239 Transaction tr =
240 tm.StartTransaction();
241 using (tr)
242 {
243 // Get the information about the block
244 // and attribute definitions we care about
245
246 BlockTableRecord ms;
247 ObjectId blockId;
248 AttributeDefinition ad;
249 List<AttributeDefinition> other;
250
251 if (GetBlock(
252 db, tr, out ms, out blockId
253 ))
254 {
255 GetBlockAttributes(
256 tr, blockId, out ad, out other
257 );
258
259 // By default the modelspace is returned to
260 // us in read-only state
261
262 ms.UpgradeOpen();
263
264 // Loop until cancelled
265
266 bool finished = false;
267 while (!finished)
268 {
269 PromptPointOptions ppo =
270 new PromptPointOptions("\nSelect point: ");
271 ppo.AllowNone = true;
272
273 PromptPointResult ppr =
274 ed.GetPoint(ppo);
275 if (ppr.Status != PromptStatus.OK)
276 finished = true;
277 else
278 // Call a function to create our bubble
279 CreateNumberedBubbleAtPoint(
280 db, ms, tr, ppr.Value,
281 blockId, ad, other
282 );
283 tm.QueueForGraphicsFlush();
284 tm.FlushGraphics();
285 }
286 }
287 tr.Commit();
288 }
289 }
290
291 // Command to create a bubble at the center of
292 // each of the selected circles
293
294 [CommandMethod("BIC")]
295 public void BubblesInCircles()
296 {
297 Document doc =
298 Application.DocumentManager.MdiActiveDocument;
299 Database db = doc.Database;
300 Editor ed = doc.Editor;
301
302 // Allow the user to select circles
303
304 TypedValue[] tvs =
305 new TypedValue[1] {
306 new TypedValue(
307 (int)DxfCode.Start,
308 "CIRCLE"
309 )
310 };
311 SelectionFilter sf =
312 new SelectionFilter(tvs);
313
314 PromptSelectionResult psr =
315 ed.GetSelection(sf);
316
317 if (psr.Status == PromptStatus.OK &&
318 psr.Value.Count > 0)
319 {
320 Transaction tr =
321 db.TransactionManager.StartTransaction();
322 using (tr)
323 {
324 // Get the information about the block
325 // and attribute definitions we care about
326
327 BlockTableRecord ms;
328 ObjectId blockId;
329 AttributeDefinition ad;
330 List<AttributeDefinition> other;
331
332 if (GetBlock(
333 db, tr, out ms, out blockId
334 ))
335 {
336 GetBlockAttributes(
337 tr, blockId, out ad, out other
338 );
339
340 // By default the modelspace is returned to
341 // us in read-only state
342
343 ms.UpgradeOpen();
344
345 foreach (SelectedObject o in psr.Value)
346 {
347 // For each circle in the selected list...
348
349 DBObject obj =
350 tr.GetObject(o.ObjectId, OpenMode.ForRead);
351 Circle c = obj as Circle;
352 if (c == null)
353 ed.WriteMessage(
354 "\nObject selected is not a circle."
355 );
356 else
357 // Call our numbering function, passing the
358 // center of the circle
359 CreateNumberedBubbleAtPoint(
360 db, ms, tr, c.Center,
361 blockId, ad, other
362 );
363 }
364 }
365 tr.Commit();
366 }
367 }
368 }
369
370 // Command to delete a particular bubble
371 // selected by its index
372
373 [CommandMethod("MB")]
374 public void MoveBubble()
375 {
376 Document doc =
377 Application.DocumentManager.MdiActiveDocument;
378 Editor ed = doc.Editor;
379
380 // Use a helper function to select a valid bubble index
381
382 int pos =
383 GetBubbleNumber(
384 ed,
385 "\nEnter number of bubble to move: "
386 );
387
388 if (pos >= m_baseNumber)
389 {
390 int from = pos - m_baseNumber;
391
392 pos =
393 GetBubbleNumber(
394 ed,
395 "\nEnter destination position: "
396 );
397
398 if (pos >= m_baseNumber)
399 {
400 int to = pos - m_baseNumber;
401
402 ObjectIdCollection ids =
403 m_nom.MoveObject(from, to);
404
405 RenumberBubbles(doc.Database, ids);
406 }
407 }
408 }
409
410 // Command to delete a particular bubbler,
411 // selected by its index
412
413 [CommandMethod("DB")]
414 public void DeleteBubble()
415 {
416 Document doc =
417 Application.DocumentManager.MdiActiveDocument;
418 Database db = doc.Database;
419 Editor ed = doc.Editor;
420
421 // Use a helper function to select a valid bubble index
422
423 int pos =
424 GetBubbleNumber(
425 ed,
426 "\nEnter number of bubble to erase: "
427 );
428
429 if (pos >= m_baseNumber)
430 {
431 // Remove the object from the internal list
432 // (this returns the ObjectId stored for it,
433 // which we can then use to erase the entity)
434
435 ObjectId id =
436 m_nom.RemoveObject(pos - m_baseNumber);
437 Transaction tr =
438 db.TransactionManager.StartTransaction();
439 using (tr)
440 {
441 DBObject obj =
442 tr.GetObject(id, OpenMode.ForWrite);
443 obj.Erase();
444 tr.Commit();
445 }
446 }
447 }
448
449 // Command to reorder all the bubbles in the drawing,
450 // closing all the gaps between numbers but maintaining
451 // the current numbering order
452
453 [CommandMethod("RBS")]
454 public void ReorderBubbles()
455 {
456 Document doc =
457 Application.DocumentManager.MdiActiveDocument;
458
459 // Re-order the bubbles - the IDs returned are
460 // of the objects that need to be renumbered
461
462 ObjectIdCollection ids =
463 m_nom.ReorderObjects();
464
465 RenumberBubbles(doc.Database, ids);
466 }
467
468 // Command to highlight a particular bubble
469
470 [CommandMethod("HLB")]
471 public void HighlightBubble()
472 {
473 Document doc =
474 Application.DocumentManager.MdiActiveDocument;
475 Database db = doc.Database;
476 Editor ed = doc.Editor;
477
478 // Use our function to select a valid bubble index
479
480 int pos =
481 GetBubbleNumber(
482 ed,
483 "\nEnter number of bubble to highlight: "
484 );
485
486 if (pos >= m_baseNumber)
487 {
488 Transaction tr =
489 db.TransactionManager.StartTransaction();
490 using (tr)
491 {
492 // Get the ObjectId from the index...
493
494 ObjectId id =
495 m_nom.GetObjectId(pos - m_baseNumber);
496
497 if (id == ObjectId.Null)
498 {
499 ed.WriteMessage(
500 "\nNumber is not currently used -" +
501 " nothing to highlight."
502 );
503 return;
504 }
505
506 // And then open & highlight the entity
507
508 Entity ent =
509 (Entity)tr.GetObject(
510 id,
511 OpenMode.ForRead
512 );
513 ent.Highlight();
514 tr.Commit();
515 }
516 }
517 }
518
519 // Internal helper function to open and retrieve
520 // the model-space and the block def we care about
521
522 private bool
523 GetBlock(
524 Database db,
525 Transaction tr,
526 out BlockTableRecord ms,
527 out ObjectId blockId
528 )
529 {
530 BlockTable bt =
531 (BlockTable)tr.GetObject(
532 db.BlockTableId,
533 OpenMode.ForRead
534 );
535
536 if (!bt.Has(blockName))
537 {
538 Document doc =
539 Application.DocumentManager.MdiActiveDocument;
540 Editor ed = doc.Editor;
541 ed.WriteMessage(
542 "\nCannot find block definition \"" +
543 blockName +
544 "\" in the current drawing."
545 );
546
547 blockId = ObjectId.Null;
548 ms = null;
549 return false;
550 }
551
552 ms =
553 (BlockTableRecord)tr.GetObject(
554 bt[BlockTableRecord.ModelSpace],
555 OpenMode.ForRead
556 );
557
558 blockId = bt[blockName];
559
560 return true;
561 }
562
563 // Internal helper function to retrieve
564 // attribute info from our block
565 // (we return the main attribute def
566 // and then all the "others")
567
568 private void
569 GetBlockAttributes(
570 Transaction tr,
571 ObjectId blockId,
572 out AttributeDefinition ad,
573 out List<AttributeDefinition> other
574 )
575 {
576 BlockTableRecord blk =
577 (BlockTableRecord)tr.GetObject(
578 blockId,
579 OpenMode.ForRead
580 );
581
582 ad = null;
583 other =
584 new List<AttributeDefinition>();
585
586 foreach (ObjectId attId in blk)
587 {
588 DBObject obj =
589 (DBObject)tr.GetObject(
590 attId,
591 OpenMode.ForRead
592 );
593 AttributeDefinition ad2 =
594 obj as AttributeDefinition;
595
596 if (ad2 != null)
597 {
598 if (ad2.Tag == attbName)
599 {
600 if (ad2.Constant)
601 {
602 Document doc =
603 Application.DocumentManager.MdiActiveDocument;
604 Editor ed = doc.Editor;
605
606 ed.WriteMessage(
607 "\nAttribute to change is constant!"
608 );
609 }
610 else
611 ad = ad2;
612 }
613 else
614 if (!ad2.Constant)
615 other.Add(ad2);
616 }
617 }
618 }
619
620 // Internal helper function to create a bubble
621 // at a particular point
622
623 private Entity
624 CreateNumberedBubbleAtPoint(
625 Database db,
626 BlockTableRecord btr,
627 Transaction tr,
628 Point3d pt,
629 ObjectId blockId,
630 AttributeDefinition ad,
631 List<AttributeDefinition> other
632 )
633 {
634 // Create a new block reference
635
636 BlockReference br =
637 new BlockReference(pt, blockId);
638
639 // Add it to the database

Atom