September 2014

Sun Mon Tue Wed Thu Fri Sat
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30        








« Using Boo with AutoCAD | Main | Importing AutoCAD layers from xrefs using .NET »

May 15, 2009

Enabling AutoCAD’s offset to work on the contents of xrefs using .NET

This was a fun little project: to enable AutoCAD’s OFFSET command to work on the contents of external references (xrefs), something I’m told is a long-standing end-user wishlist request.

AutoCAD’s .NET API provides some very interesting events that make this possible without the need for us to implement our own OFFSET command. We can simply respond to the selection event and replace the selected object (the xref itself) with either an item contained in the xref or a clone of it that has been placed in the current space. I ended up using the latter approach: the former worked fine for lines, but that was about it. Besides, the creation of a new entity allows it to also be selected, which results in better visual feedback for the user.

Here’s the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

 

namespace XrefOffset

{

  public class XrefOffsetApplication : IExtensionApplication

  {

    // Maintain a list of temporary objects that require removal

 

    ObjectIdCollection _ids;

 

    public XrefOffsetApplication()

    {

      _ids = new ObjectIdCollection();

    }

 

    public void Initialize()

    {

      DocumentCollection dm =

        Application.DocumentManager;

 

      // Remove any temporary objects at the end of the command

 

      dm.DocumentLockModeWillChange +=

        delegate(

          object sender, DocumentLockModeWillChangeEventArgs e

        )

        {

          if (_ids.Count > 0)

          {

            Transaction tr =

              e.Document.TransactionManager.StartTransaction();

            using (tr)

            {

              foreach (ObjectId id in _ids)

              {

                DBObject obj =

                  tr.GetObject(id, OpenMode.ForWrite, true);

                obj.Erase();

              }

              tr.Commit();

            }

            _ids.Clear();

          }

        };

 

      // When a document is created, make sure we handle the

      // important events it fires

 

      dm.DocumentCreated +=

        delegate(

          object sender, DocumentCollectionEventArgs e

        )

        {

          e.Document.CommandWillStart +=

            new CommandEventHandler(OnCommandWillStart);

          e.Document.CommandEnded +=

            new CommandEventHandler(OnCommandFinished);

          e.Document.CommandCancelled +=

            new CommandEventHandler(OnCommandFinished);

          e.Document.CommandFailed +=

            new CommandEventHandler(OnCommandFinished);

        };

 

      // Do the same for any documents existing on application

      // initialization

 

      foreach (Document doc in dm)

      {

        doc.CommandWillStart +=

          new CommandEventHandler(OnCommandWillStart);

        doc.CommandEnded +=

          new CommandEventHandler(OnCommandFinished);

        doc.CommandCancelled +=

          new CommandEventHandler(OnCommandFinished);

        doc.CommandFailed +=

          new CommandEventHandler(OnCommandFinished);

      }

    }

 

    // When the OFFSET command starts, let's add our selection

    // manipulating event-handler

 

    void OnCommandWillStart(object sender, CommandEventArgs e)

    {

      if (e.GlobalCommandName == "OFFSET")

      {

        Document doc = (Document)sender;

        doc.Editor.PromptForEntityEnding +=

          new PromptForEntityEndingEventHandler(

            OnPromptForEntityEnding

          );

      }

    }

 

    // And when the command ends, remove it

 

    void OnCommandFinished(object sender, CommandEventArgs e)

    {

      if (e.GlobalCommandName == "OFFSET")

      {

        Document doc = (Document)sender;

        doc.Editor.PromptForEntityEnding -=

          new PromptForEntityEndingEventHandler(

            OnPromptForEntityEnding

          );

      }

    }

 

    // Here's where the heavy lifting happens...

 

    void OnPromptForEntityEnding(

      object sender, PromptForEntityEndingEventArgs e

    )

    {

      if (e.Result.Status == PromptStatus.OK)

      {

        Editor ed = sender as Editor;

        ObjectId objId = e.Result.ObjectId;

        Database db = objId.Database;

 

        Transaction tr =

          db.TransactionManager.StartTransaction();

        using (tr)

        {

          // First get the currently selected object

          // and check whether it's a block reference

 

          BlockReference br =

            tr.GetObject(objId, OpenMode.ForRead)

              as BlockReference;

          if (br != null)

          {

            // If so, we check whether the block table record

            // to which it refers is actually from an XRef

 

            ObjectId btrId = br.BlockTableRecord;

            BlockTableRecord btr =

              tr.GetObject(btrId, OpenMode.ForRead)

                as BlockTableRecord;

            if (btr != null)

            {

              if (btr.IsFromExternalReference)

              {

                // If so, then we prigrammatically select the object

                // underneath the pick-point already used

 

                PromptNestedEntityOptions pneo =

                  new PromptNestedEntityOptions("");

                pneo.NonInteractivePickPoint =

                  e.Result.PickedPoint;

                pneo.UseNonInteractivePickPoint = true;

 

                PromptNestedEntityResult pner =

                  ed.GetNestedEntity(pneo);

 

                if (pner.Status == PromptStatus.OK)

                {

                  try

                  {

                    ObjectId selId = pner.ObjectId;

 

                    // Let's look at this programmatically-selected

                    // object, to see what it is

 

                    DBObject obj =

                      tr.GetObject(selId, OpenMode.ForRead);

 

                    // If it's a polyline vertex, we need to go one

                    // level up to the polyline itself

 

                    if (obj is PolylineVertex3d || obj is Vertex2d)

                      selId = obj.OwnerId;

 

                    // We don't want to do anything at all for

                    // textual stuff, let's also make sure we

                    // are dealing with an entity (should always

                    // be the case)

 

                    if (obj is MText || obj is DBText ||

                      !(obj is Entity))

                      return;

 

                    // Now let's get the name of the layer, to use

                    // later

 

                    Entity ent = (Entity)obj;

                    LayerTableRecord ltr =

                      (LayerTableRecord)tr.GetObject(

                        ent.LayerId,

                        OpenMode.ForRead

                      );

                    string layName = ltr.Name;

 

                    // Clone the selected object

 

                    object o = ent.Clone();

                    Entity clone = o as Entity;

 

                    // We need to manipulate the clone to make sure

                    // it works

 

                    if (clone != null)

                    {

                      // Setting the properties from the block

                      // reference helps certain entities get the

                      // right references (and allows them to be

                      // offset properly)

 

                      clone.SetPropertiesFrom(br);

 

                      // But we then need to get the layer

                      // information from the database to set the

                      // right layer (at least) on the new entity

 

                      LayerTable lt =

                        (LayerTable)tr.GetObject(

                          db.LayerTableId,

                          OpenMode.ForRead

                        );

                      if (lt.Has(layName))

                        clone.LayerId = lt[layName];

 

                      // Now we need to transform the entity for

                      // each of its Xref block reference containers

                      // If we don't do this then entities in nested

                      // Xrefs may end up in the wrong place

 

                      ObjectId[] conts =

                        pner.GetContainers();

                      foreach (ObjectId contId in conts)

                      {

                        BlockReference cont =

                          tr.GetObject(contId, OpenMode.ForRead)

                            as BlockReference;

                        if (cont != null)

                          clone.TransformBy(cont.BlockTransform);

                      }

 

                      // Let's add the cloned entity to the current

                      // space

 

                      BlockTableRecord space =

                        tr.GetObject(

                          db.CurrentSpaceId,

                          OpenMode.ForWrite

                        ) as BlockTableRecord;

                      if (space == null)

                      {

                        clone.Dispose();

                        return;

                      }

 

                      ObjectId cloneId = space.AppendEntity(clone);

                      tr.AddNewlyCreatedDBObject(clone, true);

 

                      // Now let's flush the graphics, to help our

                      // clone get displayed

 

                      tr.TransactionManager.QueueForGraphicsFlush();

 

                      // And we add our cloned entity to the list

                      // for deletion

 

                      _ids.Add(cloneId);

 

                      // Created a non-graphical selection of our

                      // newly created object and replace it with

                      // the selection of the container Xref

 

                      SelectedObject so =

                        new SelectedObject(

                          cloneId,

                          SelectionMethod.NonGraphical,

                          -1

                        );

 

                      e.ReplaceSelectedObject(so);

                    }

                  }

                  catch

                  {

                    // A number of innocuous things could go wrong

                    // so let's not worry about the details

 

                    // In the worst case we are simply not trying

                    // to replace the entity, so OFFSET will just

                    // reject the selected Xref

                  }

                }

              }

            }

          }

          tr.Commit();

        }

      }

    }

 

    public void Terminate()

    {

    }

  }

}

Here’s a piece of an external reference inside AutoCAD:

A portion of our external reference

Once we load our module and run the standard OFFSET command, we can select the contents of our external reference (even if nested) and offset them into the current space:

Offset contents of our external reference

If you give it a try, please do let me know if you hit any problems or have enhancement suggestions. One related area I’m already planning to look into is whether it’s possible to enable the COPY command to work with the contents of xrefs.

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83452464869e20115708a8beb970b

Listed below are links to weblogs that reference Enabling AutoCAD’s offset to work on the contents of xrefs using .NET:

blog comments powered by Disqus

Feed/Share

10 Random Posts