- 积分
- 24557
- 明经币
- 个
- 注册时间
- 2004-3-17
- 在线时间
- 小时
- 威望
-
- 金钱
- 个
- 贡献
-
- 激情
-
|
楼主 |
发表于 2009-6-12 07:40:00
|
显示全部楼层
December 01, 2006
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:- using System;
- using System.Collections;
- using System.Collections.Generic;
- using Autodesk.AutoCAD.Runtime;
- using Autodesk.AutoCAD.ApplicationServices;
- using Autodesk.AutoCAD.DatabaseServices;
- using Autodesk.AutoCAD.EditorInput;
- using Autodesk.AutoCAD.Geometry;
- [assembly:
- CommandClass(
- typeof(
- AsdkLinkingLibrary.LinkingCommands
- )
- )
- ]
- namespace AsdkLinkingLibrary
- {
- /// <summary>
- /// Utility class to manage and save links
- /// between objects
- /// </summary>
- public class LinkedObjectManager
- {
- const string kCompanyDict =
- "AsdkLinks";
- const string kApplicationDict =
- "AsdkLinkedObjects";
- const string kXrecPrefix =
- "LINKXREC";
- Dictionary<ObjectId, ObjectIdCollection> m_dict;
- // Constructor
- public LinkedObjectManager()
- {
- m_dict =
- new Dictionary<ObjectId,ObjectIdCollection>();
- }
- // Create a bi-directional link between two objects
- public void LinkObjects(ObjectId from, ObjectId to)
- {
- CreateLink(from, to);
- CreateLink(to, from);
- }
- // Helper function to create a one-way
- // link between objects
- private void CreateLink(ObjectId from, ObjectId to)
- {
- ObjectIdCollection existingList;
- if (m_dict.TryGetValue(from, out existingList))
- {
- if (!existingList.Contains(to))
- {
- existingList.Add(to);
- m_dict.Remove(from);
- m_dict.Add(from, existingList);
- }
- }
- else
- {
- ObjectIdCollection newList =
- new ObjectIdCollection();
- newList.Add(to);
- m_dict.Add(from, newList);
- }
- }
- // Remove bi-directional links from an object
- public void RemoveLinks(ObjectId from)
- {
- ObjectIdCollection existingList;
- if (m_dict.TryGetValue(from, out existingList))
- {
- m_dict.Remove(from);
- foreach (ObjectId id in existingList)
- {
- RemoveFromList(id, from);
- }
- }
- }
- // Helper function to remove an object reference
- // from a list (assumes the overall list should
- // remain)
- private void RemoveFromList(
- ObjectId key,
- ObjectId toremove
- )
- {
- ObjectIdCollection existingList;
- if (m_dict.TryGetValue(key, out existingList))
- {
- if (existingList.Contains(toremove))
- {
- existingList.Remove(toremove);
- m_dict.Remove(key);
- m_dict.Add(key, existingList);
- }
- }
- }
- // Return the list of objects linked to
- // the one passed in
- public ObjectIdCollection GetLinkedObjects(
- ObjectId from
- )
- {
- ObjectIdCollection existingList;
- m_dict.TryGetValue(from, out existingList);
- return existingList;
- }
- // Check whether the dictionary contains
- // a particular key
- public bool Contains(ObjectId key)
- {
- return m_dict.ContainsKey(key);
- }
- // Save the link information to a special
- // dictionary in the database
- public void SaveToDatabase(Database db)
- {
- Transaction tr =
- db.TransactionManager.StartTransaction();
- using (tr)
- {
- ObjectId dictId =
- GetLinkDictionaryId(db, true);
- DBDictionary dict =
- (DBDictionary)tr.GetObject(
- dictId,
- OpenMode.ForWrite
- );
- int xrecCount = 0;
- foreach (
- KeyValuePair<ObjectId, ObjectIdCollection> kv
- in m_dict
- )
- {
- // Prepare the result buffer with our data
- ResultBuffer rb =
- new ResultBuffer(
- new TypedValue(
- (int)DxfCode.SoftPointerId,
- kv.Key
- )
- );
- int i = 1;
- foreach (ObjectId id in kv.Value)
- {
- rb.Add(
- new TypedValue(
- (int)DxfCode.SoftPointerId + i,
- id
- )
- );
- i++;
- }
- // Update or create an xrecord to store the data
- Xrecord xrec;
- bool newXrec = false;
- if (dict.Contains(
- kXrecPrefix + xrecCount.ToString()
- )
- )
- {
- // Open the existing object
- DBObject obj =
- tr.GetObject(
- dict.GetAt(
- kXrecPrefix + xrecCount.ToString()
- ),
- OpenMode.ForWrite
- );
- // Check whether it's an xrecord
- xrec = obj as Xrecord;
- if (xrec == null)
- {
- // Should never happen
- // We only store xrecords in this dict
- obj.Erase();
- xrec = new Xrecord();
- newXrec = true;
- }
- }
- // No object existed - create a new one
- else
- {
- xrec = new Xrecord();
- newXrec = true;
- }
- xrec.XlateReferences = true;
- xrec.Data = (ResultBuffer)rb;
- if (newXrec)
- {
- dict.SetAt(
- kXrecPrefix + xrecCount.ToString(),
- xrec
- );
- tr.AddNewlyCreatedDBObject(xrec, true);
- }
- xrecCount++;
- }
- // Now erase the left-over xrecords
- bool finished = false;
- do
- {
- if (dict.Contains(
- kXrecPrefix + xrecCount.ToString()
- )
- )
- {
- DBObject obj =
- tr.GetObject(
- dict.GetAt(
- kXrecPrefix + xrecCount.ToString()
- ),
- OpenMode.ForWrite
- );
- obj.Erase();
- }
- else
- {
- finished = true;
- }
- xrecCount++;
- } while (!finished);
- tr.Commit();
- }
- }
- // Load the link information from a special
- // dictionary in the database
- public void LoadFromDatabase(Database db)
- {
- Document doc =
- Application.DocumentManager.MdiActiveDocument;
- Editor ed = doc.Editor;
- Transaction tr =
- db.TransactionManager.StartTransaction();
- using (tr)
- {
- // Try to find the link dictionary, but
- // do not create it if one isn't there
- ObjectId dictId =
- GetLinkDictionaryId(db, false);
- if (dictId.IsNull)
- {
- ed.WriteMessage(
- "\nCould not find link dictionary."
- );
- return;
- }
- // By this stage we can assume the dictionary exists
- DBDictionary dict =
- (DBDictionary)tr.GetObject(
- dictId, OpenMode.ForRead
- );
- int xrecCount = 0;
- bool done = false;
- // Loop, reading the xrecords one-by-one
- while (!done)
- {
- if (dict.Contains(
- kXrecPrefix + xrecCount.ToString()
- )
- )
- {
- ObjectId recId =
- dict.GetAt(
- kXrecPrefix + xrecCount.ToString()
- );
- DBObject obj =
- tr.GetObject(recId, OpenMode.ForRead);
- Xrecord xrec = obj as Xrecord;
- if (xrec == null)
- {
- ed.WriteMessage(
- "\nDictionary contains non-xrecords."
- );
- return;
- }
- int i = 0;
- ObjectId from = new ObjectId();
- ObjectIdCollection to =
- new ObjectIdCollection();
- foreach (TypedValue val in xrec.Data)
- {
- if (i == 0)
- from = (ObjectId)val.Value;
- else
- {
- to.Add((ObjectId)val.Value);
- }
- i++;
- }
- // Validate the link info and add it to our
- // internal data structure
- AddValidatedLinks(db, from, to);
- xrecCount++;
- }
- else
- {
- done = true;
- }
- }
- tr.Commit();
- }
- }
- // Helper function to validate links before adding
- // them to the internal data structure
- private void AddValidatedLinks(
- Database db,
- ObjectId from,
- ObjectIdCollection to
- )
- {
- Document doc =
- Application.DocumentManager.MdiActiveDocument;
- Editor ed = doc.Editor;
- Transaction tr =
- db.TransactionManager.StartTransaction();
- using (tr)
- {
- try
- {
- ObjectIdCollection newList =
- new ObjectIdCollection();
- // Open the "from" object
- DBObject obj =
- tr.GetObject(from, OpenMode.ForRead, false);
- if (obj != null)
- {
- // Open each of the "to" objects
- foreach (ObjectId id in to)
- {
- DBObject obj2;
- try
- {
- obj2 =
- tr.GetObject(id, OpenMode.ForRead, false);
- // Filter out the erased "to" objects
- if (obj2 != null)
- {
- newList.Add(id);
- }
- }
- catch (System.Exception)
- {
- ed.WriteMessage(
- "\nFiltered out link to an erased object."
- );
- }
- }
- // Only if the "from" object and at least
- // one "to" object exist (and are unerased)
- // do we add an entry for them
- if (newList.Count > 0)
- {
- m_dict.Add(from, newList);
- }
- }
- }
- catch (System.Exception)
- {
- ed.WriteMessage(
- "\nFiltered out link from an erased object."
- );
- }
- tr.Commit();
- }
- }
- // Helper function to get (optionally create)
- // the nested dictionary for our xrecord objects
- private ObjectId GetLinkDictionaryId(
- Database db,
- bool createIfNotExisting
- )
- {
- ObjectId appDictId = ObjectId.Null;
- Transaction tr =
- db.TransactionManager.StartTransaction();
- using (tr)
- {
- DBDictionary nod =
- (DBDictionary)tr.GetObject(
- db.NamedObjectsDictionaryId,
- OpenMode.ForRead
- );
- // Our outer level ("company") dictionary
- // does not exist
- if (!nod.Contains(kCompanyDict))
- {
- if (!createIfNotExisting)
- return ObjectId.Null;
- // Create both the "company" dictionary...
- DBDictionary compDict = new DBDictionary();
- nod.UpgradeOpen();
- nod.SetAt(kCompanyDict, compDict);
- tr.AddNewlyCreatedDBObject(compDict, true);
- // ... and the inner "application" dictionary.
- DBDictionary appDict = new DBDictionary();
- appDictId =
- compDict.SetAt(kApplicationDict, appDict);
- tr.AddNewlyCreatedDBObject(appDict, true);
- }
- else
- {
- // Our "company" dictionary exists...
- DBDictionary compDict =
- (DBDictionary)tr.GetObject(
- nod.GetAt(kCompanyDict),
- OpenMode.ForRead
- );
- /// So check for our "application" dictionary
- if (!compDict.Contains(kApplicationDict))
- {
- if (!createIfNotExisting)
- return ObjectId.Null;
- // Create the "application" dictionary
- DBDictionary appDict = new DBDictionary();
- compDict.UpgradeOpen();
- appDictId =
- compDict.SetAt(kApplicationDict, appDict);
- tr.AddNewlyCreatedDBObject(appDict, true);
- }
- else
- {
- // Both dictionaries already exist...
- appDictId = compDict.GetAt(kApplicationDict);
- }
- }
- tr.Commit();
- }
- return appDictId;
- }
- }
-
- /// <summary>
- /// This class defines our commands and event callbacks.
- /// </summary>
- public class LinkingCommands
- {
- LinkedObjectManager m_linkManager;
- ObjectIdCollection m_entitiesToUpdate;
- bool m_autoLink = false;
- ObjectId m_lastEntity = ObjectId.Null;
- public LinkingCommands()
- {
- Document doc =
- Application.DocumentManager.MdiActiveDocument;
- Database db = doc.Database;
- db.ObjectModified +=
- new ObjectEventHandler(OnObjectModified);
- db.ObjectErased +=
- new ObjectErasedEventHandler(OnObjectErased);
- db.ObjectAppended +=
- new ObjectEventHandler(OnObjectAppended);
- db.BeginSave +=
- new DatabaseIOEventHandler(OnBeginSave);
- doc.CommandEnded +=
- new CommandEventHandler(OnCommandEnded);
- m_linkManager = new LinkedObjectManager();
- m_entitiesToUpdate = new ObjectIdCollection();
- }
- ~LinkingCommands()
- {
- try
- {
- Document doc =
- Application.DocumentManager.MdiActiveDocument;
- Database db = doc.Database;
- db.ObjectModified -=
- new ObjectEventHandler(OnObjectModified);
- db.ObjectErased -=
- new ObjectErasedEventHandler(OnObjectErased);
- db.ObjectAppended -=
- new ObjectEventHandler(OnObjectAppended);
- db.BeginSave -=
- new DatabaseIOEventHandler(OnBeginSave);
- doc.CommandEnded +=
- new CommandEventHandler(OnCommandEnded);
- }
- catch(System.Exception)
- {
- // The document or database may no longer
- // be available on unload
- }
- }
- // Define "LINK" command
- [CommandMethod("LINK")]
- public void LinkEntities()
- {
- Document doc =
- Application.DocumentManager.MdiActiveDocument;
- Database db = doc.Database;
- Editor ed = doc.Editor;
- PromptEntityOptions opts =
- new PromptEntityOptions(
- "\nSelect first circle to link: "
- );
- opts.AllowNone = true;
- opts.SetRejectMessage(
- "\nOnly circles can be selected."
- );
- opts.AddAllowedClass(typeof(Circle), false);
- PromptEntityResult res = ed.GetEntity(opts);
- if (res.Status == PromptStatus.OK)
- {
- ObjectId from = res.ObjectId;
- opts.Message =
- "\nSelect second circle to link: ";
- res = ed.GetEntity(opts);
- if (res.Status == PromptStatus.OK)
- {
- ObjectId to = res.ObjectId;
- m_linkManager.LinkObjects(from, to);
- m_lastEntity = to;
- m_entitiesToUpdate.Add(from);
- }
- }
- }
- // Define "AUTOLINK" command
- [CommandMethod("AUTOLINK")]
- public void ToggleAutoLink()
- {
- Document doc =
- Application.DocumentManager.MdiActiveDocument;
- Editor ed = doc.Editor;
- m_autoLink = !m_autoLink;
- if (m_autoLink)
- {
- ed.WriteMessage("\nAutomatic linking turned on.");
- }
- else
- {
- ed.WriteMessage("\nAutomatic linking turned off.");
- }
- }
- // Define "LOADLINKS" command
- [CommandMethod("LOADLINKS")]
- public void LoadLinkSettings()
- {
- Document doc =
- Application.DocumentManager.MdiActiveDocument;
- Database db = doc.Database;
- m_linkManager.LoadFromDatabase(db);
- }
- // Define "SAVELINKS" command
- [CommandMethod("SAVELINKS")]
- public void SaveLinkSettings()
- {
- Document doc =
- Application.DocumentManager.MdiActiveDocument;
- Database db = doc.Database;
- m_linkManager.SaveToDatabase(db);
- }
-
- // Define callback for Database.ObjectModified event
- private void OnObjectModified(
- object sender, ObjectEventArgs e)
- {
- ObjectId id = e.DBObject.ObjectId;
- if (m_linkManager.Contains(id) &&
- !m_entitiesToUpdate.Contains(id))
- {
- m_entitiesToUpdate.Add(id);
- }
- }
- // Define callback for Database.ObjectErased event
- private void OnObjectErased(
- object sender, ObjectErasedEventArgs e)
- {
- if (e.Erased)
- {
- ObjectId id = e.DBObject.ObjectId;
- m_linkManager.RemoveLinks(id);
- if (m_lastEntity == id)
- {
- m_lastEntity = ObjectId.Null;
- }
- }
- }
- // Define callback for Database.ObjectAppended event
- void OnObjectAppended(object sender, ObjectEventArgs e)
- {
- Database db = sender as Database;
- if (db != null)
- {
- if (m_autoLink)
- {
- if (e.DBObject.GetType() == typeof(Circle))
- {
- ObjectId from = e.DBObject.ObjectId;
- if (m_lastEntity == ObjectId.Null)
- {
- m_lastEntity = from;
- }
- else
- {
- m_linkManager.LinkObjects(from, m_lastEntity);
- m_lastEntity = from;
- m_entitiesToUpdate.Add(from);
- }
- }
- }
- }
- }
- // Define callback for Database.BeginSave event
- void OnBeginSave(object sender, DatabaseIOEventArgs e)
- {
- Database db = sender as Database;
- if (db != null)
- {
- m_linkManager.SaveToDatabase(db);
- }
- }
- // Define callback for Document.CommandEnded event
- private void OnCommandEnded(
- object sender, CommandEventArgs e)
- {
- foreach (ObjectId id in m_entitiesToUpdate)
- {
- UpdateLinkedEntities(id);
- }
- m_entitiesToUpdate.Clear();
- }
- // Helper function for OnCommandEnded
- private void UpdateLinkedEntities(ObjectId from)
- {
- Document doc =
- Application.DocumentManager.MdiActiveDocument;
- Editor ed = doc.Editor;
- Database db = doc.Database;
- ObjectIdCollection linked =
- m_linkManager.GetLinkedObjects(from);
- Transaction tr =
- db.TransactionManager.StartTransaction();
- using (tr)
- {
- try
- {
- Point3d firstCenter;
- Point3d secondCenter;
- double firstRadius;
- double secondRadius;
- Entity ent =
- (Entity)tr.GetObject(from, OpenMode.ForRead);
- if (GetCenterAndRadius(
- ent,
- out firstCenter,
- out firstRadius
- )
- )
- {
- foreach (ObjectId to in linked)
- {
- Entity ent2 =
- (Entity)tr.GetObject(to, OpenMode.ForRead);
- if (GetCenterAndRadius(
- ent2,
- out secondCenter,
- out secondRadius
- )
- )
- {
- Vector3d vec = firstCenter - secondCenter;
- if (!vec.IsZeroLength())
- {
- // Only move the linked circle if it's not
- // already near enough
- double apart =
- vec.Length - (firstRadius + secondRadius);
- if (apart < 0.0)
- apart = -apart;
- if (apart > 0.00001)
- {
- ent2.UpgradeOpen();
- ent2.TransformBy(
- Matrix3d.Displacement(
- vec.GetNormal() * apart
- )
- );
- }
- }
- }
- }
- }
- }
- catch (System.Exception ex)
- {
- Autodesk.AutoCAD.Runtime.Exception ex2 =
- ex as Autodesk.AutoCAD.Runtime.Exception;
- if (ex2 != null &&
- ex2.ErrorStatus !=
- ErrorStatus.WasOpenForUndo &&
- ex2.ErrorStatus !=
- ErrorStatus.WasErased
- )
- {
- ed.WriteMessage(
- "\nAutoCAD exception: {0}", ex2
- );
- }
- else if (ex2 == null)
- {
- ed.WriteMessage(
- "\nSystem exception: {0}", ex
- );
- }
- }
- tr.Commit();
- }
- }
- // Helper function to get the center and radius
- // for all supported circular objects
- private bool GetCenterAndRadius(
- Entity ent,
- out Point3d center,
- out double radius
- )
- {
- // For circles it's easy...
- Circle circle = ent as Circle;
- if (circle != null)
- {
- center = circle.Center;
- radius = circle.Radius;
- return true;
- }
- else
- {
- // Throw in some empty values...
- // Returning false indicates the object
- // passed in was not useable
- center = Point3d.Origin;
- radius = 0.0;
- return false;
- }
- }
- }
- }
Let's take a quick look at this code running. Here's an existing chain that we've created using LINK. We then use AUTOLINK to toggle the automatic linking to on, and start creating circles:
And that's it for this post. Next time we'll look at adding support for other object types, including 3D (woohoo!).
|
|