Initialization code in your F# AutoCAD application
Back from a nice long weekend, although I spent most of it sick with a cold. I find this increasingly the way with me: I fend off illness for months at a time (probably through stress, truth be told) but then I get a few days off and wham. A shame, as we had a huge dump of snow over the weekend... we get white Christmases here every five years or so, but it's really uncommon to get a white Easter.
I had a very interesting question come in by email from 冷血儿, who wanted to get the technique shown in this post working in his F# application.
Here's the F# code I managed to put together after consulting hubFS, in particular:
#light
namespace MyNamespace
open Autodesk.AutoCAD.Runtime
open Autodesk.AutoCAD.ApplicationServices
type InitTest() =
class
let ed =
Application.DocumentManager.MdiActiveDocument.Editor
interface IExtensionApplication with
member x.Initialize() =
ed.WriteMessage
("\nInitializing - do something useful.")
member x.Terminate() =
printfn "\nCleaning up..."
end
end
module MyApplication =
let ed =
Application.DocumentManager.MdiActiveDocument.Editor
[<CommandMethod("TST")>]
let f () =
ed.WriteMessage("\nThis is the TST command.")
[<assembly: ExtensionApplication(type InitTest)>]
do
ed.WriteMessage("\nModule do")
Here's what happens when we load our module and run the TST command:
Command: NETLOAD
Module do
Initializing - do something useful.
Command: TST
This is the TST command.
March 25, 2008 in AutoCAD, AutoCAD .NET, F#, Notification / Events | Permalink | Comments (0) | TrackBack
Linking Circles, Part 3: Automatic linking on circle creation
In the previous posts we looked at some code to link AutoCAD entities via .NET events, and how to persist the link data in the drawing file.
This post extends the previous code to automatically link circles into the head of the chain, as circles are drawn by the user. The changes to the project are relatively modest compared to last time. Once again, the source is both available for download and listed below with changed line-numbers in red.
Some notes on the changes:
First we declare some new variables in our command class: a boolean (m_autolink - line 463) which tells us whether automatic linking is "on" or "off", and an ObjectId (m_lastEntity - line 464), which we will use to find the most recently created, linked object. We could have made this a setting in our LinkedObjectManager class, should we have wanted to make this a persistent setting, for instance, but for simplicity's sake we'll leave it in the command class for now.
We have to register (lines 475-476) and unregister (lines 497-498) a handler for another event - OnObjectAppended(). It's via this callback that we're informed that a new object has been added to the drawing.
Next we define our AUTOLINK command (lines 547-563). This simply toggles the m_autolink setting between true and false (or on and off). We might have chosen to display the current setting and ask whether the user wanted to change it to on or off, but frankly that seemed like overkill. If you don't like what you've set it to, you can just call the command again. :-)
A minor change was needed to OnObjectErased(), to set the value of m_lastEntity to Null, should the entity be erased. This also gets caught by a change in the code right at the end, but it's cleaner coding to make the behaviour right here, also.
Next we have the guts of our implementation (such that it is), which is the OnObjectAppended() callback definition (lines 612-636). Here we check whether the object added is a circle, and if so, we either link it to the last one added (stored in m_lastEntity), or - if the value of m_lastEntity is Null, for whatever reason - then we simply make it the next object to be linked in, and leave it at that.
And finally there's a minor change I added to more elegantly support UNDO (which also applies to the code in the first two posts, although I won't go and update them now). Because we're not persisting the state of our links synchronously in the drawing database, they don't automatically participate in the undo mechanism (e.g. if the user uses the UNDO command, we would have to do a little extra work to recreate the correct "last object to link to" settings). Rather than implement the equivalent of our own undo mechanism, I decided not to bother, and simply made sure that when a link is to an erased object, we simply give up (without any error message). This shouldn't happen very often - as we have our OnObjectErased() callback, but you never know. It does mean that the "bad" links might continue to exist in our LinkedObjectManager, they just won't work. The next time the data is saved and reloaded, though, these links effectively get purged. To really make this a production-ready app, I feel a little more attention is needed in this area... that said, the foundation is certainly there for you to work from (just please test thoroughly for your specific situation, of course).
Now for the C# code:
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using Autodesk.AutoCAD.Runtime;
5 using Autodesk.AutoCAD.ApplicationServices;
6 using Autodesk.AutoCAD.DatabaseServices;
7 using Autodesk.AutoCAD.EditorInput;
8 using Autodesk.AutoCAD.Geometry;
9
10 [assembly:
11 CommandClass(
12 typeof(
13 AsdkLinkingLibrary.LinkingCommands
14 )
15 )
16 ]
17
18 namespace AsdkLinkingLibrary
19 {
20 /// <summary>
21 /// Utility class to manage and save links
22 /// between objects
23 /// </summary>
24 public class LinkedObjectManager
25 {
26 const string kCompanyDict =
27 "AsdkLinks";
28 const string kApplicationDict =
29 "AsdkLinkedObjects";
30 const string kXrecPrefix =
31 "LINKXREC";
32
33 Dictionary<ObjectId, ObjectIdCollection> m_dict;
34
35 // Constructor
36 public LinkedObjectManager()
37 {
38 m_dict =
39 new Dictionary<ObjectId,ObjectIdCollection>();
40 }
41
42 // Create a bi-directional link between two objects
43 public void LinkObjects(ObjectId from, ObjectId to)
44 {
45 CreateLink(from, to);
46 CreateLink(to, from);
47 }
48
49 // Helper function to create a one-way
50 // link between objects
51 private void CreateLink(ObjectId from, ObjectId to)
52 {
53 ObjectIdCollection existingList;
54 if (m_dict.TryGetValue(from, out existingList))
55 {
56 if (!existingList.Contains(to))
57 {
58 existingList.Add(to);
59 m_dict.Remove(from);
60 m_dict.Add(from, existingList);
61 }
62 }
63 else
64 {
65 ObjectIdCollection newList =
66 new ObjectIdCollection();
67 newList.Add(to);
68 m_dict.Add(from, newList);
69 }
70 }
71
72 // Remove bi-directional links from an object
73 public void RemoveLinks(ObjectId from)
74 {
75 ObjectIdCollection existingList;
76 if (m_dict.TryGetValue(from, out existingList))
77 {
78 m_dict.Remove(from);
79 foreach (ObjectId id in existingList)
80 {
81 RemoveFromList(id, from);
82 }
83 }
84 }
85
86 // Helper function to remove an object reference
87 // from a list (assumes the overall list should
88 // remain)
89 private void RemoveFromList(
90 ObjectId key,
91 ObjectId toremove
92 )
93 {
94 ObjectIdCollection existingList;
95 if (m_dict.TryGetValue(key, out existingList))
96 {
97 if (existingList.Contains(toremove))
98 {
99 existingList.Remove(toremove);
100 m_dict.Remove(key);
101 m_dict.Add(key, existingList);
102 }
103 }
104 }
105
106 // Return the list of objects linked to
107 // the one passed in
108 public ObjectIdCollection GetLinkedObjects(
109 ObjectId from
110 )
111 {
112 ObjectIdCollection existingList;
113 m_dict.TryGetValue(from, out existingList);
114 return existingList;
115 }
116
117 // Check whether the dictionary contains
118 // a particular key
119 public bool Contains(ObjectId key)
120 {
121 return m_dict.ContainsKey(key);
122 }
123
124 // Save the link information to a special
125 // dictionary in the database
126 public void SaveToDatabase(Database db)
127 {
128 Transaction tr =
129 db.TransactionManager.StartTransaction();
130 using (tr)
131 {
132 ObjectId dictId =
133 GetLinkDictionaryId(db, true);
134 DBDictionary dict =
135 (DBDictionary)tr.GetObject(
136 dictId,
137 OpenMode.ForWrite
138 );
139 int xrecCount = 0;
140
141 foreach (
142 KeyValuePair<ObjectId, ObjectIdCollection> kv
143 in m_dict
144 )
145 {
146 // Prepare the result buffer with our data
147 ResultBuffer rb =
148 new ResultBuffer(
149 new TypedValue(
150 (int)DxfCode.SoftPointerId,
151 kv.Key
152 )
153 );
154 int i = 1;
155 foreach (ObjectId id in kv.Value)
156 {
157 rb.Add(
158 new TypedValue(
159 (int)DxfCode.SoftPointerId + i,
160 id
161 )
162 );
163 i++;
164 }
165
166 // Update or create an xrecord to store the data
167 Xrecord xrec;
168 bool newXrec = false;
169 if (dict.Contains(
170 kXrecPrefix + xrecCount.ToString()
171 )
172 )
173 {
174 // Open the existing object
175 DBObject obj =
176 tr.GetObject(
177 dict.GetAt(
178 kXrecPrefix + xrecCount.ToString()
179 ),
180 OpenMode.ForWrite
181 );
182 // Check whether it's an xrecord
183 xrec = obj as Xrecord;
184 if (xrec == null)
185 {
186 // Should never happen
187 // We only store xrecords in this dict
188 obj.Erase();
189 xrec = new Xrecord();
190 newXrec = true;
191 }
192 }
193 // No object existed - create a new one
194 else
195 {
196 xrec = new Xrecord();
197 newXrec = true;
198 }
199 xrec.XlateReferences = true;
200 xrec.Data = (ResultBuffer)rb;
201 if (newXrec)
202 {
203 dict.SetAt(
204 kXrecPrefix + xrecCount.ToString(),
205 xrec
206 );
207 tr.AddNewlyCreatedDBObject(xrec, true);
208 }
209 xrecCount++;
210 }
211
212 // Now erase the left-over xrecords
213 bool finished = false;
214 do
215 {
216 if (dict.Contains(
217 kXrecPrefix + xrecCount.ToString()
218 )
219 )
220 {
221 DBObject obj =
222 tr.GetObject(
223 dict.GetAt(
224 kXrecPrefix + xrecCount.ToString()
225 ),
226 OpenMode.ForWrite
227 );
228 obj.Erase();
229 }
230 else
231 {
232 finished = true;
233 }
234 xrecCount++;
235 } while (!finished);
236 tr.Commit();
237 }
238 }
239
240 // Load the link information from a special
241 // dictionary in the database
242 public void LoadFromDatabase(Database db)
243 {
244 Document doc =
245 Application.DocumentManager.MdiActiveDocument;
246 Editor ed = doc.Editor;
247 Transaction tr =
248 db.TransactionManager.StartTransaction();
249 using (tr)
250 {
251 // Try to find the link dictionary, but
252 // do not create it if one isn't there
253 ObjectId dictId =
254 GetLinkDictionaryId(db, false);
255 if (dictId.IsNull)
256 {
257 ed.WriteMessage(
258 "\nCould not find link dictionary."
259 );
260 return;
261 }
262
263 // By this stage we can assume the dictionary exists
264 DBDictionary dict =
265 (DBDictionary)tr.GetObject(
266 dictId, OpenMode.ForRead
267 );
268 int xrecCount = 0;
269 bool done = false;
270
271 // Loop, reading the xrecords one-by-one
272 while (!done)
273 {
274 if (dict.Contains(
275 kXrecPrefix + xrecCount.ToString()
276 )
277 )
278 {
279 ObjectId recId =
280 dict.GetAt(
281 kXrecPrefix + xrecCount.ToString()
282 );
283 DBObject obj =
284 tr.GetObject(recId, OpenMode.ForRead);
285 Xrecord xrec = obj as Xrecord;
286 if (xrec == null)
287 {
288 ed.WriteMessage(
289 "\nDictionary contains non-xrecords."
290 );
291 return;
292 }
293 int i = 0;
294 ObjectId from = new ObjectId();
295 ObjectIdCollection to =
296 new ObjectIdCollection();
297 foreach (TypedValue val in xrec.Data)
298 {
299 if (i == 0)
300 from = (ObjectId)val.Value;
301 else
302 {
303 to.Add((ObjectId)val.Value);
304 }
305 i++;
306 }
307 // Validate the link info and add it to our
308 // internal data structure
309 AddValidatedLinks(db, from, to);
310 xrecCount++;
311 }
312 else
313 {
314 done = true;
315 }
316 }
317 tr.Commit();
318 }
319 }
320
321 // Helper function to validate links before adding
322 // them to the internal data structure
323 private void AddValidatedLinks(
324 Database db,
325 ObjectId from,
326 ObjectIdCollection to
327 )
328 {
329 Document doc =
330 Application.DocumentManager.MdiActiveDocument;
331 Editor ed = doc.Editor;
332 Transaction tr =
333 db.TransactionManager.StartTransaction();
334 using (tr)
335 {
336 try
337 {
338 ObjectIdCollection newList =
339 new ObjectIdCollection();
340
341 // Open the "from" object
342 DBObject obj =
343 tr.GetObject(from, OpenMode.ForRead, false);
344 if (obj != null)
345 {
346 // Open each of the "to" objects
347 foreach (ObjectId id in to)
348 {
349 DBObject obj2;
350 try
351 {
352 obj2 =
353 tr.GetObject(id, OpenMode.ForRead, false);
354 // Filter out the erased "to" objects
355 if (obj2 != null)
356 {
357 newList.Add(id);
358 }
359 }
360 catch (System.Exception)
361 {
362 ed.WriteMessage(
363 "\nFiltered out link to an erased object."
364 );
365 }
366 }
367 // Only if the "from" object and at least
368 // one "to" object exist (and are unerased)
369 // do we add an entry for them
370 if (newList.Count > 0)
371 {
372 m_dict.Add(from, newList);
373 }
374 }
375 }
376 catch (System.Exception)
377 {
378 ed.WriteMessage(
379 "\nFiltered out link from an erased object."
380 );
381 }
382 tr.Commit();
383 }
384 }
385
386 // Helper function to get (optionally create)
387 // the nested dictionary for our xrecord objects
388 private ObjectId GetLinkDictionaryId(
389 Database db,
390 bool createIfNotExisting
391 )
392 {
393 ObjectId appDictId = ObjectId.Null;
394
395 Transaction tr =
396 db.TransactionManager.StartTransaction();
397 using (tr)
398 {
399 DBDictionary nod =
400 (DBDictionary)tr.GetObject(
401 db.NamedObjectsDictionaryId,
402 OpenMode.ForRead
403 );
404 // Our outer level ("company") dictionary
405 // does not exist
406 if (!nod.Contains(kCompanyDict))
407 {
408 if (!createIfNotExisting)
409 return ObjectId.Null;
410
411 // Create both the "company" dictionary...
412 DBDictionary compDict = new DBDictionary();
413 nod.UpgradeOpen();
414 nod.SetAt(kCompanyDict, compDict);
415 tr.AddNewlyCreatedDBObject(compDict, true);
416
417 // ... and the inner "application" dictionary.
418 DBDictionary appDict = new DBDictionary();
419 appDictId =
420 compDict.SetAt(kApplicationDict, appDict);
421 tr.AddNewlyCreatedDBObject(appDict, true);
422 }
423 else
424 {
425 // Our "company" dictionary exists...
426 DBDictionary compDict =
427 (DBDictionary)tr.GetObject(
428 nod.GetAt(kCompanyDict),
429 OpenMode.ForRead
430 );
431 /// So check for our "application" dictionary
432 if (!compDict.Contains(kApplicationDict))
433 {
434 if (!createIfNotExisting)
435 return ObjectId.Null;
436
437 // Create the "application" dictionary
438 DBDictionary appDict = new DBDictionary();
439 compDict.UpgradeOpen();
440 appDictId =
441 compDict.SetAt(kApplicationDict, appDict);
442 tr.AddNewlyCreatedDBObject(appDict, true);
443 }
444 else
445 {
446 // Both dictionaries already exist...
447 appDictId = compDict.GetAt(kApplicationDict);
448 }
449 }
450 tr.Commit();
451 }
452 return appDictId;
453 }
454 }
455
456 /// <summary>
457 /// This class defines our commands and event callbacks.
458 /// </summary>
459 public class LinkingCommands
460 {
461 LinkedObjectManager m_linkManager;
462 ObjectIdCollection m_entitiesToUpdate;
463 bool m_autoLink = false;
464 ObjectId m_lastEntity = ObjectId.Null;
465
466 public LinkingCommands()
467 {
468 Document doc =
469 Application.DocumentManager.MdiActiveDocument;
470 Database db = doc.Database;
471 db.ObjectModified +=
472 new ObjectEventHandler(OnObjectModified);
473 db.ObjectErased +=
474 new ObjectErasedEventHandler(OnObjectErased);
475 db.ObjectAppended +=
476 new ObjectEventHandler(OnObjectAppended);
477 db.BeginSave +=
478 new DatabaseIOEventHandler(OnBeginSave);
479 doc.CommandEnded +=
480 new CommandEventHandler(OnCommandEnded);
481
482 m_linkManager = new LinkedObjectManager();
483 m_entitiesToUpdate = new ObjectIdCollection();
484 }
485
486 ~LinkingCommands()
487 {
488 try
489 {
490 Document doc =
491 Application.DocumentManager.MdiActiveDocument;
492 Database db = doc.Database;
493 db.ObjectModified -=
494 new ObjectEventHandler(OnObjectModified);
495 db.ObjectErased -=
496 new ObjectErasedEventHandler(OnObjectErased);
497 db.ObjectAppended -=
498 new ObjectEventHandler(OnObjectAppended);
499 db.BeginSave -=
500 new DatabaseIOEventHandler(OnBeginSave);
501 doc.CommandEnded +=
502 new CommandEventHandler(OnCommandEnded);
503 }
504 catch(System.Exception)
505 {
506 // The document or database may no longer
507 // be available on unload
508 }
509 }
510
511 // Define "LINK" command
512 [CommandMethod("LINK")]
513 public void LinkEntities()
514 {
515 Document doc =
516 Application.DocumentManager.MdiActiveDocument;
517 Database db = doc.Database;
518 Editor ed = doc.Editor;
519
520 PromptEntityOptions opts =
521 new PromptEntityOptions(
522 "\nSelect first circle to link: "
523 );
524 opts.AllowNone = true;
525 opts.SetRejectMessage(
526 "\nOnly circles can be selected."
527 );
528 opts.AddAllowedClass(typeof(Circle), false);
529
530 PromptEntityResult res = ed.GetEntity(opts);
531 if (res.Status == PromptStatus.OK)
532 {
533 ObjectId from = res.ObjectId;
534 opts.Message =
535 "\nSelect second circle to link: ";
536 res = ed.GetEntity(opts);
537 if (res.Status == PromptStatus.OK)
538 {
539 ObjectId to = res.ObjectId;
540 m_linkManager.LinkObjects(from, to);
541 m_lastEntity = to;
542 m_entitiesToUpdate.Add(from);
543 }
544 }
545 }
546
547 // Define "AUTOLINK" command
548 [CommandMethod("AUTOLINK")]
549 public void ToggleAutoLink()
550 {
551 Document doc =
552 Application.DocumentManager.MdiActiveDocument;
553 Editor ed = doc.Editor;
554 m_autoLink = !m_autoLink;
555 if (m_autoLink)
556 {
557 ed.WriteMessage("\nAutomatic linking turned on.");
558 }
559 else
560 {
561 ed.WriteMessage("\nAutomatic linking turned off.");
562 }
563 }
564
565 // Define "LOADLINKS" command
566 [CommandMethod("LOADLINKS")]
567 public void LoadLinkSettings()
568 {

Atom