Linking Circles, Part 2: Getting persistent

In the previous post we looked at some code that created chains of circles, linking them together using .NET events (a technique that can be used to maintain other types of geometric relationship, of course). In this post, we're going to extend our sample to support persistence of the link data in the AutoCAD database.

Firstly, here's the updated source file. Below I've posted the code, once again with line numbers - but this time the lines that have changed or been added since the previous post are marked in red. This should highlight the modified sections of the code.

Looking at the specific changes, the major updates are to our LinkedObjectManager class: between lines 124 and 454 there's some additional protocol to support persistence. Primarily the obviously named SaveToDatabase() and LoadFromDatabase(), but also some support functions: AddValidatedLinks(), which we use on loading data from the drawing to make sure only valid links get resurrected, and GetLinkDictionaryId(), which we use to identify (and create, if needed) the dictionary we're using to store the link data.

Some information on how the data is being stored: I decided to go ahead and use Xrecords to store the data. Xrecords are flexible, non-graphical data containers that can be stored in dictionaries (of type DBDictionary) in the DWG file. They are also supported natively by AutoCAD, so there's no need for a DBX module to help you access the data. DBDictionaries are basically persistent maps between keys and values. A simple "LINKXREC" gets suffixed by a counter ("0", "1", "2", etc.) to store our Xrecords - this way we know exactly where to look for them.

It's worth taking the trouble of creating nested dictionaries - an outer one for the "company", and an inner one for the "application". The outer one must, of course, be prefixed with your Registered Developer Symbol (RDS) to prevent conflicts with other applications. Having an inner dictionary just gives us greater flexibility if we later choose to extend the amount of custom data we store in the drawing file.

The rest of the changes are to add some simple commands - LOADLINKS and SAVELINKS - to call through to our new persistence protocol. There's also an event handler for BeginSave(), which will automatically put our data into the drawing file when it's about to be saved. This type of automatic persistence is clearly very convenient: an exercise I've left for the reader is to automatically load in the data when it exists. The idea would be to respond to a drawing load event (for instance), check whether our data is there (for which we have a very helpful function, GetLinkDictionaryId()) and then prompt the user whether our data should be loaded (or simply go and do it, depending on the extent to which you want to insulate your users from this kind of decision). The implementation is there, it should be fairly trivial to hook the pieces together.

Another note about the persistence of our data: it should be obvious by now, but we're only using the DBDictionary of Xrecords to store our data - at runtime we use an in-memory dictionary mapping ObjectIds to collections of ObjectIds. This means the data - as you create links between circles - could get out-of-sync with what is stored in the drawing, especially if we were just relying on a command being invoked to save the data.

If you're interested in checking out how the data is stored, you should look at the ArxDbg sample on the ObjectARX SDK (under samples/database/ARXDBG). This invaluable sample takes the lid off the structure of the drawing database, allowing you to see what is stored and where. The sample also contains some very useful code, showing how to use even some of the more obscure parts of ObjectARX.

Here's what we see when we use the SNOOPDB command from the ArxDbg sample to take a look at the contents of our custom dictionary:

Linkedcircles_2_arxdbg

Here's 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

  464     public LinkingCommands()

  465     {

  466       Document doc =

  467         Application.DocumentManager.MdiActiveDocument;

  468       Database db = doc.Database;

  469       db.ObjectModified +=

  470         new ObjectEventHandler(OnObjectModified);

  471       db.ObjectErased +=

  472         new ObjectErasedEventHandler(OnObjectErased);

  473       db.BeginSave +=

  474         new DatabaseIOEventHandler(OnBeginSave);

  475       doc.CommandEnded +=

  476         new CommandEventHandler(OnCommandEnded);

  477

  478       m_linkManager = new LinkedObjectManager();

  479       m_entitiesToUpdate = new ObjectIdCollection();

  480     }

  481

  482     ~LinkingCommands()

  483     {

  484       try

  485       {

  486         Document doc =

  487           Application.DocumentManager.MdiActiveDocument;

  488         Database db = doc.Database;

  489         db.ObjectModified -=

  490           new ObjectEventHandler(OnObjectModified);

  491         db.ObjectErased -=

  492           new ObjectErasedEventHandler(OnObjectErased);

  493         db.BeginSave -=

  494           new DatabaseIOEventHandler(OnBeginSave);

  495         doc.CommandEnded +=

  496           new CommandEventHandler(OnCommandEnded);

  497       }

  498       catch(System.Exception)

  499       {

  500         // The document or database may no longer

  501         // be available on unload

  502       }

  503     }

  504

  505     // Define "LINK" command

  506     [CommandMethod("LINK")]

  507     public void LinkEntities()

  508     {

  509       Document doc =

  510         Application.DocumentManager.MdiActiveDocument;

  511       Database db = doc.Database;

  512       Editor ed = doc.Editor;

  513

  514       PromptEntityOptions opts =

  515         new PromptEntityOptions(

  516           "\nSelect first circle to link: "

  517         );

  518       opts.AllowNone = true;

  519       opts.SetRejectMessage(

  520         "\nOnly circles can be selected."

  521       );

  522       opts.AddAllowedClass(typeof(Circle), false);

  523

  524       PromptEntityResult res = ed.GetEntity(opts);

  525       if (res.Status == PromptStatus.OK)

  526       {

  527         ObjectId from = res.ObjectId;

  528         opts.Message =

  529           "\nSelect second circle to link: ";

  530         res = ed.GetEntity(opts);

  531         if (res.Status == PromptStatus.OK)

  532         {

  533           ObjectId to = res.ObjectId;

  534           m_linkManager.LinkObjects(from, to);

  535           m_entitiesToUpdate.Add(from);

  536         }

  537       }

  538     }

  539

  540     // Define "LOADLINKS" command

  541     [CommandMethod("LOADLINKS")]

  542     public void LoadLinkSettings()

  543     {

  544       Document doc =

  545         Application.DocumentManager.MdiActiveDocument;

  546       Database db = doc.Database;

  547       m_linkManager.LoadFromDatabase(db);

  548     }

  549

  550     // Define "SAVELINKS" command

  551     [CommandMethod("SAVELINKS")]

  552     public void SaveLinkSettings()

  553     {

  554       Document doc =

  555         Application.DocumentManager.MdiActiveDocument;

  556       Database db = doc.Database;

  557       m_linkManager.SaveToDatabase(db);

  558     }

  559

  560     // Define callback for Database.ObjectModified event

  561     private void OnObjectModified(

  562       object sender, ObjectEventArgs e)

  563     {

  564       ObjectId id = e.DBObject.ObjectId;

  565       if (m_linkManager.Contains(id) &&

  566           !m_entitiesToUpdate.Contains(id))

  567       {

  568         m_entitiesToUpdate.Add(id);

  569       }

  570     }

  571

  572     // Define callback for Database.ObjectErased event

  573     private void OnObjectErased(

  574       object sender, ObjectErasedEventArgs e)

  575     {

  576       if (e.Erased)

  577       {

  578         m_linkManager.RemoveLinks(e.DBObject.ObjectId);

  579       }

  580     }

  581

  582     // Define callback for Database.BeginSave event

  583     void OnBeginSave(object sender, DatabaseIOEventArgs e)

  584     {

  585       Database db = sender as Database;

  586       if (db != null)

  587       {

  588         m_linkManager.SaveToDatabase(db);

  589       }

  590     }

  591

  592     // Define callback for Document.CommandEnded event

  593     private void OnCommandEnded(

  594       object sender, CommandEventArgs e)

  595     {

  596       foreach (ObjectId id in m_entitiesToUpdate)

  597       {

  598         UpdateLinkedEntities(id);

  599       }

  600       m_entitiesToUpdate.Clear();

  601     }

  602

  603     // Helper function for OnCommandEnded

  604     private void UpdateLinkedEntities(ObjectId from)

  605     {

  606       Document doc =

  607         Application.DocumentManager.MdiActiveDocument;

  608       Editor ed = doc.Editor;

  609       Database db = doc.Database;

  610

  611       ObjectIdCollection linked =

  612         m_linkManager.GetLinkedObjects(from);

  613

  614       Transaction tr =

  615         db.TransactionManager.StartTransaction();

  616       using (tr)

  617       {

  618         try

  619         {

  620           Point3d firstCenter;

  621           Point3d secondCenter;

  622           double firstRadius;

  623           double secondRadius;

  624

  625           Entity ent =

  626             (Entity)tr.GetObject(from, OpenMode.ForRead);

  627

  628           if (GetCenterAndRadius(

  629                 ent,

  630                 out firstCenter,

  631                 out firstRadius

  632               )

  633           )

  634           {

  635             foreach (ObjectId to in linked)

  636             {

  637               Entity ent2 =

  638                 (Entity)tr.GetObject(to, OpenMode.ForRead);

  639               if (GetCenterAndRadius(

  640                     ent2,

  641                     out secondCenter,

  642                     out secondRadius

  643                   )

  644               )

  645               {

  646                 Vector3d vec = firstCenter - secondCenter;

  647                 if (!vec.IsZeroLength())

  648                 {

  649                   // Only move the linked circle if it's not

  650                   // already near enough               

  651                   double apart =

  652                   vec.Length - (firstRadius + secondRadius);

  653                   if (apart < 0.0)

  654                     apart = -apart;

  655

  656                   if (apart > 0.00001)

  657                   {

  658                     ent2.UpgradeOpen();

  659                     ent2.TransformBy(

  660                       Matrix3d.Displacement(

  661                         vec.GetNormal() * apart

  662                       )

  663                     );

  664                   }

  665                 }

  666               }

  667             }

  668           }

  669         }

  670         catch (System.Exception ex)

  671         {

  672           Autodesk.AutoCAD.Runtime.Exception ex2 =

  673             ex as Autodesk.AutoCAD.Runtime.Exception;

  674           if (ex2 != null &&

  675               ex2.ErrorStatus != ErrorStatus.WasOpenForUndo)

  676           {

  677             ed.WriteMessage(

  678               "\nAutoCAD exception: {0}", ex2

  679             );

  680           }

  681           else if (ex2 == null)

  682           {

  683             ed.WriteMessage(

  684               "\nSystem exception: {0}", ex

  685             );

  686           }

  687         }

  688         tr.Commit();

  689       }

  690     }

  691

  692     // Helper function to get the center and radius

  693     // for all supported circular objects

  694     private bool GetCenterAndRadius(

  695       Entity ent,

  696       out Point3d center,

  697       out double radius

  698     )

  699     {

  700       // For circles it's easy...

  701       Circle circle = ent as Circle;

  702       if (circle != null)

  703       {

  704         center = circle.Center;

  705         radius = circle.Radius;

  706         return true;

  707       }

  708       else

  709       {

  710         // Throw in some empty values...

  711         // Returning false indicates the object

  712         // passed in was not useable

  713         center = Point3d.Origin;

  714         radius = 0.0;

  715         return false;

  716       }

  717     }

  718   }

  719 }

Now for an example of what happens when you reload a drawing without calling the LOADLINKS functionality...

Firstly, we create our chain of circles and SAVE (no need to call SAVELINKS specifically, because of our BeginSave() event handler):

Linkedcircles_2_presave

If we close and reopen the drawing file, we see that when we stretch one of the circles, the links are not in place:

Linkedcircles_2_preload

If we then call LOADLINKS, to resurrect our links, we see that when we stretch the head of the chain, the links are there, once again:

Linkedcircles_2_postload_poststretch

That's it for now - in the next post we'll take a look at adding support for automatic linking on object creation.

19 responses to “Linking Circles, Part 2: Getting persistent”

  1. Hello Kean,

    Great stuff.

    Is it at all possible to avoid Xrecords and still maintain persistency?

    Are there any reactor classes available in dot net api? Like AcDbObjectReactor etc? And if exists, can they be used to derive our own objects from them?

    - Narayanan

  2. Hi Narayanan,

    A very good question...

    It's not currently supported to create persistent custom objects (graphical or otherwise) in .NET. This means you can use either Xrecords stored in dictionaries (even entities' extension dictionaries) or xdata to stored custom data. Xrecords are better than xdata, as they don't have the 16K size restriction.

    The logic is that with .NET, apps are more likely to want to store data in an open, readable way (for example as XML fragments in a standard container such as an Xrecord).

    Persistent reactors are not available in .NET right now (they are also part of the custom object debate). Once again, the approach should be similar to the one followed in this app, as it avoids the need for Object Enablers etc.

    Regards,

    Kean

  3. Kean,
    One wonders how much the code would be reduced if you changed your LinkedObjectManager class to derive from KeyedCollection like so:

    public class LinkedObjectManager : KeyedCollection< ObjectId, ObjectIdCollection > { }

    Good articles - keep them coming. BTW, you made mention of deriving from Dwgfiler in a previous post - can you elucidate?

    Regards,
    Glenn.

  4. Hi Glenn,

    Thanks for the suggestion.

    I have to admit I wasn't familiar with the KeyedCollection class, and now that I'm looking into it, I'm curious about its advantages.

    It seems to have the key embedded in the value: this doesn't appear to help us (unless it reduces the need for bi-directional linking... and I can't see how that would be the case).

    It supports serialization, but from what I can tell that was also possible with a Dictionary. I had toyed with serializing our data out as XML to be stored in a single Xrecord, but that raises the issue of object references: Xrecords of soft pointers have the object references translated automatically on load - with XML we would have to use handles. I just ended up just implementing our own serialization protocol...

    As for derivation vs. containment: I went with containment largely to reduce the exposed interface; it made it easier to expose just the "approved" way of adding/deleting items from the map.

    I had to search back through the blog for a reference to deriving from DwgFiler: that was actually something I copy & pasted from a colleague. Generally people do that if they want to capture the data that is stored for certain objects - you pass your derived filer into DwgOutFields() and receive the callbacks in your class. I've only done this from C++, though.

    Regards,

    Kean

  5. Kean,

    Thank you for identifying this pattern. Understanding the API is one thing, knowing how to best put the pieces together is another...

    On line 159 you use the logic: (int)DxfCode.SoftPointerId + i,

    If more than 10 circles are linked to a root circle, doesn't this essentially change the resultbuffer type to a hard pointer?

    Could you please ellaborate on your strategy for incrementing the dxf code?

    Thank you,

    Jeff

  6. Jeff,

    Actually you're possibly right... the system might indeed consider us to have hard pointers if we had 10 circles linked directly to a single root circle. As the concept tends to work poorly once you go beyond two links per circle, this wasn't really something I tested, although for linking other types of (particularly non-graphical) object I see where it might become a problem.

    The DXF documentation does say that group codes 0-369 (excluding 5 and 105) can be used by applications "in any way". The implication is that we have complete control, although I'd be interested to see if that were true.

    In any case, the difference between hard and soft pointers is that hard ones prevent purging - something that's not likely to happen on grapical objects such as circles (even by someone implementing purging in their code). The real problem area is more likely to be when you start to get up to 369 (and we're starting at 330 - not so far away).

    Ultimately I see two failsafe solutions:

    1) Limit the creation of links over a certain point (i.e. a single object can only be linked to two others, say).
    2) Reuse the same group codes for your links. This *should* be safe enough - other objects do it - although I haven't tried it myself.

    Thanks for your insightful comment!

    Kean

  7. its a good example to learn linking of objects.
    if any could help me in upgrading this sample so that the linked objects gets automatically loaded on opening the drawing for the second time.
    please provide with me a sample.

    Cheers,

  8. Kean Walmsley Avatar

    I don't have time to do this, but I'd suggest the following approach:

    You don't have a custom object stored in the DWG to drive demand-loading, so have your app load on startup and run a command as it loads (probably by P/Invoking ads_queueexpr()) to do what you need.

    Kean

  9. SUBIR KUMAR DUTTA Avatar
    SUBIR KUMAR DUTTA

    For those who want to copy paste this code and want to run with out wasting time to delete the line numbers. Thanks to Word VBA

    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;

    public LinkingCommands()
    {
    Document doc =
    Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;
    db.ObjectModified +=
    new ObjectEventHandler(OnObjectModified);
    db.ObjectErased +=
    new ObjectErasedEventHandler(OnObjectErased);
    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.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_entitiesToUpdate.Add(from);
    }
    }
    }

    // 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)
    {
    m_linkManager.RemoveLinks(e.DBObject.ObjectId);
    }
    }

    // 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)
    {
    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;
    }
    }
    }
    }

  10. Subir - the source file is linked to for download from within the post.

    Kean

  11. I notice that in this code, the ObjectModified event handler adds ObjectIds to a list, which is processed in the CommandEnded event handler.

    However, this means that the list of modified ObjectIds does not get processed when objects are modified in ways that do not involve standard commands. For example, if we change the location of the lead circle in the chain by changing the X or Y coordinates in the Properties panel, the CommandEnded event handler does not fire.

    A similar issue happens in vertical apps such as Civil-3D, where editing feature lines using the Elevation Editor does not trigger the CommandEnded event.

    Is there a way to catch user actions such as this, and trigger the processing of the list?

  12. It's probably because I worked off an old (R13-era) ObjectARX sample when I created this. The more modern way to pick up changes made by all types of operation (not just commands) is to handle this event:

    Application.DocumentManager.DocumentLockModeWillChange

    Regards,

    Kean

  13. I have some code that monitors various objects, and when one of the objects is changed, the elevation of various C3D points gets changed. This code runs fine when I run it from the CommandEnded event handler, except for the issue mentioned above, where items can be changed in ways that do not trigger CommandEnded.

    But when I try to run it from the DocumentLockModeWillChange event handler, it hits a problem while trying to update the elevations of the AeccCogoPoints. The line that sets the new elevation causes my code to stop running, and C3D displays a "Command is already in progress" error message on the command line. The next thing I attempt to do will crash C3D.

    Any ideas why this might be happening? I am maintaining a list of ObjectIDs, and I retrieve these objects via a transaction in my event handler. I then turn them into AeccPoint objects, by casting "aeccPt = dbObj.AcadObject as AeccPoint". It is when I try to set the aeccPt.Elevation property that I trigger the problem.

    Is the problem caused by the fact that I am converting the point from a generic DBObject to a COM object inside the event handler? If so, how should I go about this task?

  14. You probably need to check the current and new states of the document's lock mode (via the event handler's arguments). You won't want to run your code for every lock change, probably only when Current (or MyCurrent) is "Write" and MyFuture is "None", for instance.

    At least that's my suspicion without seeing your code...

    Kean

  15. I have distilled the problem down to a minimal amount of code.

    The following code has an ADDTESTPTS command, which adds Cogo points to the list of watched ObjectIds. When a watched point is modified, this code should add 1 to the elevation. It works when I use the CommandEnded handler, but not the DocumentLockModeWillChange handler.

    using Autodesk.AutoCAD.ApplicationServices;
    using Autodesk.AutoCAD.DatabaseServices;
    using Autodesk.AutoCAD.EditorInput;
    using Autodesk.AutoCAD.Runtime;
    using Autodesk.AECC.Interop.Land;

    namespace Test
    {
    public class PointTest
    {
    ObjectIdCollection m_entitiesToUpdate;
    ObjectIdCollection m_modifiedEntities;

    public PointTest()
    {
    // Code currently works as-is, except it only works when the CommandEnded event handler gets fired.
    // However, comment out the following line:
    Application.DocumentManager.MdiActiveDocument.CommandEnded += new CommandEventHandler(MdiActiveDocument_CommandEnded);

    // and enable this line instead:
    //Application.DocumentManager.DocumentLockModeWillChange += new DocumentLockModeWillChangeEventHandler(DocumentManager_DocumentLockModeWillChange);

    // ...and now Civil-3D will display a "Command in progress" error whenever a watched point is modified,
    // and then fatal error soon after.

    HostApplicationServices.WorkingDatabase.ObjectModified += new ObjectEventHandler(WorkingDatabase_ObjectModified);

    m_entitiesToUpdate = new ObjectIdCollection();
    m_modifiedEntities = new ObjectIdCollection();
    }

    [CommandMethod("ADDTESTPTS")]
    public void AddTestPoints()
    {
    PromptSelectionOptions sPrmpt = new PromptSelectionOptions();
    PromptSelectionResult ssResult;
    TypedValue[] filter = new TypedValue[1];
    filter[0] = new TypedValue(0, "AECC_COGO_POINT");
    SelectionFilter ssFilter = new SelectionFilter(filter);
    ssResult = Application.DocumentManager.MdiActiveDocument.Editor.GetSelection(sPrmpt, ssFilter);
    if (ssResult.Status == PromptStatus.OK)
    {
    ObjectId[] objIds = ssResult.Value.GetObjectIds();
    foreach (ObjectId oid in objIds)
    m_entitiesToUpdate.Add(oid);
    }
    }

    void WorkingDatabase_ObjectModified(object sender, ObjectEventArgs e)
    {
    if (m_entitiesToUpdate.Contains(e.DBObject.ObjectId) && !m_modifiedEntities.Contains(e.DBObject.ObjectId))
    m_modifiedEntities.Add(e.DBObject.ObjectId);
    }

    void MdiActiveDocument_CommandEnded(object sender, CommandEventArgs e)
    {
    UpdatePoints(HostApplicationServices.WorkingDatabase);
    }

    void DocumentManager_DocumentLockModeWillChange(object sender, DocumentLockModeWillChangeEventArgs e)
    {
    if ((e.MyCurrentMode == DocumentLockMode.Write) &&
    ((e.MyNewMode == DocumentLockMode.NotLocked) || (e.MyNewMode == DocumentLockMode.None)))
    {
    UpdatePoints(e.Document.Database);
    }
    }

    void UpdatePoints(Database db)
    {
    if (m_modifiedEntities.Count > 0)
    {
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
    foreach (ObjectId oid in m_modifiedEntities)
    {
    DBObject obj = tr.GetObject(oid, OpenMode.ForWrite);
    AeccPoint pt = obj.AcadObject as AeccPoint;
    if (pt != null)
    {
    double elev = pt.Elevation;
    pt.Elevation = elev + 1;
    }
    }
    tr.Commit();
    }
    m_modifiedEntities.Clear();
    }
    }
    }
    }

  16. I can't see anything wrong with this code, but I don't use Civil 3D and so can't try it out.

    I suggest submitting it via the ADN website (if you're a member) or posting it to the AutoCAD Civil 3D Customization Discussion Group, otherwise.

    Regards,

    Kean

  17. After posting to the DGs, nobody seems to have any idea why DocumentLockModeWillChange doesn't work in C3D.

    But it looks like everything works OK when monitoring Editor.EnteringQuiescentState instead.

  18. Hi Kean,

    Is there any way to know if the position of a block has been changed via the properties panel. Maybe some Overrule or Event, other than the ObjectModified event?

    Thanks,

    Gustavo Barreto

    1. Hi Gustavo,

      I'm pretty sure you just have ObjectModified. Are you trying to adjust positioning or stop it from happening? If so, you might be able to overrule the property availability/behaviour (although I haven't done it myself).

      Regards,

      Kean

Leave a Reply to SUBIR KUMAR DUTTA Cancel reply

Your email address will not be published. Required fields are marked *