Sunday, October 7, 2012

Highlight Selected Entity With DrawableOverrule

Every AutoCAD user is used to the fact that when an entity is selected, AutoCAD highlights the entity. However, the default highlight (dashed line) is not that eye-catching enough to satisfy particular drafting need.

I recently worked on a project to converting a fairly complicated VBA program. At a point during that VBA program progress, the VBA program converts the colors of a SelectionSet of entities into a highlight color (for example, yellow, or red), and converts the colors of all the rest of entities into a contrast color. This makes the selected entities much more visually stand out.

Although this achieves the goal of making some entities more eye-catching, it certainly has its drawbacks: it has to change almost all entities in the drawing database, just for the temporary visual effect. If something goes wrong that causes exception, it may leave the entities/drawing in unwanted state (i.e. in a color it should not be).

With Overrule, which was introduced into .NET API since AutoCAD 2010, customizing how an Entity is highlighted becomes an much easier task. There is even a HighlightOverrule class we can use (see one of my old post here ).

However, the HighlightOverrule isn't good enough to meet my need in the VBA conversion project. Besides highlighting the selected entities user selected, it also wants all entities that are not selected are in a plain/dull color so that the selected entities are visually stand-out, visually emphasized. So, I decided to give it a try of using DrawableOverrule. The code presented here is different from what I actually used in the project because of the specific business requirement, though.

Here is the code for a class called HighlightDrawableOverrule:

Code Snippet
  1. using System.Collections.Generic;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.GraphicsInterface;
  5. using Autodesk.AutoCAD.Runtime;
  6.  
  7. namespace SelectionHighlightOverrule
  8. {
  9.     public class HighlightDrawableOverrule : DrawableOverrule
  10.     {
  11.         private int _foreColorIndex;
  12.         private int _backColorIndex;
  13.         private bool _origialOverruling = false;
  14.         private List<ObjectId> _highlightEnts;
  15.         private bool _started = false;
  16.  
  17.         public HighlightDrawableOverrule(int foreColor, int backColor)
  18.         {
  19.             _foreColorIndex = foreColor;
  20.             _backColorIndex = backColor;
  21.             _highlightEnts = new List<ObjectId>();
  22.         }
  23.  
  24.         public bool Started
  25.         {
  26.             get { return _started; }
  27.         }
  28.  
  29.         public ObjectId[] SelectedEntIds
  30.         {
  31.             get { return _highlightEnts.ToArray(); }
  32.         }
  33.  
  34.         public HighlightDrawableOverrule(int foreColor,
  35.             int backColor, ObjectId[] entIds) : this(foreColor, backColor)
  36.         {
  37.             _highlightEnts.AddRange(entIds);
  38.         }
  39.  
  40.         public void Start()
  41.         {
  42.             _origialOverruling = DrawableOverrule.Overruling;
  43.  
  44.             DrawableOverrule.AddOverrule(
  45.                 RXClass.GetClass(typeof(Entity)), this, true);
  46.             DrawableOverrule.Overruling = true;
  47.             _started = true;
  48.  
  49.             Regen();
  50.         }
  51.  
  52.         public void Stop()
  53.         {
  54.             DrawableOverrule.RemoveOverrule(
  55.                 RXClass.GetClass(typeof(Entity)), this);
  56.             DrawableOverrule.Overruling = _origialOverruling;
  57.             _started = false;
  58.  
  59.             Regen();
  60.         }
  61.  
  62.         public void AddHightlightEntities(ObjectId[] entIds)
  63.         {
  64.             bool added = false;
  65.             foreach (var id in entIds)
  66.             {
  67.                 if (!_highlightEnts.Contains(id))
  68.                 {
  69.                     _highlightEnts.Add(id);
  70.                     if (!added) added = true;
  71.                 }
  72.             }
  73.  
  74.             if (_started && added) Regen();
  75.         }
  76.  
  77.         public void RemoveHighlightEntities(ObjectId[] entIds)
  78.         {
  79.             bool removed = false;
  80.             foreach (var id in entIds)
  81.             {
  82.                 if (!_highlightEnts.Contains(id))
  83.                 {
  84.                     _highlightEnts.Remove(id);
  85.                     if (!removed) removed = true;
  86.                 }
  87.             }
  88.  
  89.             if (_started && removed) Regen();
  90.         }
  91.  
  92.         public void ClearHightlightEntities()
  93.         {
  94.             _highlightEnts.Clear();
  95.             if (_started) Regen();
  96.         }
  97.  
  98.         public override bool WorldDraw(Drawable drawable, WorldDraw wd)
  99.         {
  100.             wd.SubEntityTraits.Color = (short)_backColorIndex;
  101.  
  102.             if (_highlightEnts.Count > 0)
  103.             {
  104.                 Entity ent = drawable as Entity;
  105.                 if (ent != null)
  106.                 {
  107.                     if (_highlightEnts.Contains(ent.ObjectId))
  108.                         wd.SubEntityTraits.Color = (short)_foreColorIndex;    
  109.                 }
  110.             }
  111.  
  112.             return base.WorldDraw(drawable, wd);
  113.         }
  114.  
  115.         private void Regen()
  116.         {
  117.             Application.DocumentManager.MdiActiveDocument.Editor.Regen();
  118.         }
  119.     }
  120. }

The code is fairly simple and straightforward: it draws all entities except for the selected in a background color and all selected entities in a highlighted color.

Here is the example of how the HighlightDrawableOverrule class is used:

Code Snippet
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Runtime;
  5.  
  6. [assembly: CommandClass(typeof(SelectionHighlightOverrule.MyCommands))]
  7.  
  8. namespace SelectionHighlightOverrule
  9. {
  10.     public class MyCommands
  11.     {
  12.         [CommandMethod("HighlightSel")]
  13.         public static void HighlightedSelection()
  14.         {
  15.             Document dwg = Application.DocumentManager.MdiActiveDocument;
  16.             Editor ed = dwg.Editor;
  17.  
  18.             int foreColor = 2;  //Yellow
  19.             int backColor = 7;  //White
  20.  
  21.             ObjectId[] selectedIds = null;
  22.  
  23.             using (HighlightDrawableOverrule hOverrule =
  24.                 new HighlightDrawableOverrule(foreColor, backColor))
  25.             {
  26.                 hOverrule.Start();
  27.                 while (true)
  28.                 {
  29.                     PromptEntityOptions opt =
  30.                         new PromptEntityOptions("\nPlease pick an entity:");
  31.                     PromptEntityResult res = ed.GetEntity(opt);
  32.                     if (res.Status == PromptStatus.OK)
  33.                     {
  34.                         hOverrule.AddHightlightEntities(
  35.                             new ObjectId[] { res.ObjectId });
  36.                     }
  37.                     else
  38.                     {
  39.                         break;
  40.                     }
  41.                 }
  42.  
  43.                 hOverrule.Stop();
  44.                 selectedIds = hOverrule.SelectedEntIds;
  45.             }
  46.  
  47.             ed.WriteMessage("\n{0} entitit{1} selected.",
  48.                 selectedIds.Length, selectedIds.Length>1?"ies":"y");
  49.         }
  50.     }
  51. }

This video clip shows the effect of running the "HighlightSel" command.

As you can see, using Overrule to change entity's color for temporary visual effect is definitely better than what one have to do in VBA: the entity's color property is not changed at all, thus the draw database remain unchanged, although AutoCAD presents a different color as you needed.

Notice that Editor.Regen() has to be called when the DrawableOverrule started/stopped and when ObjectId is added into selected entity list. If the drawing database is huge, it may take too much time to Regen().

Saturday, September 15, 2012

Make Change/Restore System Variable Easy

It is common practice in AutoCAD programming, regardless of what supporting language/API you use, that if your process must change AutoCAD's system variables, the changed system variables should be restored back to its original value before your process begins.

Typically, to achieve this, one has to

1. Save a system variable's current value to variable that is value in the entire scope of the process;
2. Change then system variable's value as desired before the process begins;
3. Right after the process ends, reset the changed system variable to its previously saved original value.

As one can see if the custom process requires change a few variables, writing code to do this would be quite tedious.

We can make this changing/restoring system variable process a lot easier by encapsulating system variable value change in a class.

Here is an fairly simple way to do it:

Code Snippet
  1. using System.Collections.Generic;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4.  
  5. namespace SysVariables
  6. {
  7.     public class SysVariableSetter : Dictionary<string, object>
  8.     {
  9.         public void SetSysVariable(string varName, object varValue)
  10.         {
  11.             if (!this.ContainsKey(varName))
  12.             {
  13.                 object originalValue = Application.GetSystemVariable(varName);
  14.                 this.Add(varName, originalValue);
  15.             }
  16.  
  17.             Application.SetSystemVariable(varName, varValue);
  18.         }
  19.  
  20.         public void RestoreSysVariables()
  21.         {
  22.             //Debugging/Demo only code (next 1 line)
  23.             Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
  24.             
  25.             foreach (KeyValuePair<string, object> varItem in this)
  26.             {
  27.                 //Debugging/demo only code (next 2 lines)
  28.                 ed.WriteMessage("\n{0} original value: {1}",
  29.                     varItem.Key, varItem.Value);
  30.                 ed.WriteMessage("\n{0} current value: {1}",
  31.                     varItem.Key, Application.GetSystemVariable(varItem.Key));
  32.  
  33.                 Application.SetSystemVariable(varItem.Key, varItem.Value);
  34.  
  35.                 //Debug/demo code (next 2 lines)
  36.                 ed.WriteMessage("\n{0} current value has been restored to {1}",
  37.                     varItem.Key, varItem.Value);
  38.                 ed.WriteMessage("\n");
  39.             }
  40.         }
  41.     }
  42. }

Note, Editor referred in the code is purely for the purpose of demo.

Here is the sample code of how to use this class:

Code Snippet
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.EditorInput;
  3. using Autodesk.AutoCAD.Runtime;
  4.  
  5. [assembly: CommandClass(typeof(SysVariables.MyCommands))]
  6.  
  7. namespace SysVariables
  8. {
  9.     public class MyCommands
  10.     {
  11.         [CommandMethod("DoTest")]
  12.         public void DoMyTest()
  13.         {
  14.             Document dwg = Application.DocumentManager.MdiActiveDocument;
  15.             Editor ed = dwg.Editor;
  16.  
  17.             SysVariableSetter varSetter = new SysVariableSetter();
  18.  
  19.             try
  20.             {
  21.                 //Change sys variable FILEDIA
  22.                 varSetter.SetSysVariable("FILEDIA", 0);
  23.  
  24.                 //Do some work here
  25.                 //....................
  26.  
  27.                 //Change sys variable BACKGROUNDPLOT
  28.                 varSetter.SetSysVariable("BACKGROUNDPLOT", 0);
  29.  
  30.                 //Do plotting here
  31.                 //.................
  32.  
  33.             }
  34.             catch(System.Exception ex)
  35.             {
  36.                 ed.WriteMessage("\nError: {0}", ex.ToString());
  37.             }
  38.             finally
  39.             {
  40.                 //Restore changed system variables
  41.                 varSetter.RestoreSysVariables();
  42.             }
  43.         }
  44.     }
  45. }

As you can see, instead of using Application.SetSystmVariable() method, one can now change system variables with SysVariableSetter class with the same. one line of code. But there is no need to worry about to save system variable's original value to later restoration.

As long as the custom process is wrapped in a try{...} finally{...} block, a call to SysVariableSetter.RestoreSysVariables() in the finally{...} block will always be executed, thus changed system variables will be restored to their original values.

Update on September 17th, 2012

There is situation that during the execution of our custom developed process, we may want to change a system variable and restore (or even change and restore multiple times) before the process end. For example, we may want to change Object Snap Mode a few times whe we ask user to select someting in the editor and we may not want to just keep change OSMODE syatem variable and only restore it at the finally{...} block at the end of process.

Anyway to make the SysVariableSetter class a bit more flexible to use, I added a method to allow it to restore any changed system variable at any time:

Code Snippet
  1. public void RestoreVariable(string variableName)
  2.         {
  3.             if (this.ContainsKey(variableName.ToUpper()))
  4.             {
  5.                 Application.SetSystemVariable(variableName, this[variableName]);
  6.                 this.Remove(variableName.ToUpper());
  7.             }
  8.         }

Now, we can call SetVariable() method at any time and restore any system variable at any time without bothering to save the system variable's original value.

Tuesday, August 14, 2012

Using DrawJig to Move/Rotate Attribute in Block

When publishing my previous post (Creating Linked Entities with DrawJig) I found one of my not published post drafts was also about DrawJig, which I started quite a while ago but did finished for some reason that I could not remember (it was very likely that I was to lazy to find time to completed it then:-().

Now that my mind is still a bit fresh on DrawJig, I though why not to get it done, hence this post.

More often than not, user may want to move or rotate an attribute of an inserted block. There used to be a command in AutoCAD (may it still be there, but I could not remember its name - I do not use AutoCAD for drafting/designing for so many years and simply do not remember most the commands) to allow user move Attribute of an inserted block. My code shown here does the similar thing using DrawJig: user can pick an Attribute and choose to move or rotate it. Here is the class AttributeDrawJig:

Code Snippet
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.EditorInput;
  4. using Autodesk.AutoCAD.Geometry;
  5. using Autodesk.AutoCAD.GraphicsInterface;
  6.  
  7. namespace MoveAttribute
  8. {
  9.     public class AttributeDrawJig : DrawJig
  10.     {
  11.         private enum JigType
  12.         {
  13.             Move = 0,
  14.             Rotate = 1,
  15.         }
  16.  
  17.         private Document _dwg;
  18.         private Database _db;
  19.         private Editor _ed;
  20.         private ObjectId _attRefId = ObjectId.Null;
  21.         private Point3d _attPosition;
  22.         private JigType _jigType = JigType.Move;
  23.  
  24.         private AttributeReference _visualAtt;
  25.         private Point3d _currentPoint;
  26.         private Point3d _prevPoint;
  27.  
  28.         private double _currentAngle;
  29.         private double _prevAngle;
  30.  
  31.         public AttributeDrawJig(Document dwg)
  32.         {
  33.             _dwg = dwg;
  34.             _db = dwg.Database;
  35.             _ed = dwg.Editor;
  36.         }
  37.  
  38.         #region public methods
  39.  
  40.         public bool MoveAttribute()
  41.         {
  42.             if (!PickAttribute()) return false;
  43.             HighlightAttribute(true);
  44.  
  45.             try
  46.             {
  47.                 bool go = true;
  48.                 while (go)
  49.                 {
  50.                     bool repicked = false;
  51.  
  52.                     PromptKeywordOptions kOpt = new PromptKeywordOptions(
  53.                         "\nPick attribute transform option:");
  54.                     kOpt.Keywords.Add("Move");
  55.                     kOpt.Keywords.Add("Rotate");
  56.                     kOpt.Keywords.Add("Pick");
  57.                     kOpt.Keywords.Add("eXit");
  58.                     kOpt.Keywords.Default = "Move";
  59.                     kOpt.AppendKeywordsToMessage = true;
  60.  
  61.                     PromptResult res = _ed.GetKeywords(kOpt);
  62.  
  63.                     if (res.Status == PromptStatus.OK)
  64.                     {
  65.                         switch(res.StringResult.ToUpper())
  66.                         {
  67.                             case "MOVE":
  68.                                 _jigType = JigType.Move;
  69.                                 break;
  70.                             case "ROTATE":
  71.                                 _jigType = JigType.Rotate;
  72.                                 break;
  73.                             case "PICK":
  74.                                 HighlightAttribute(false);
  75.                                 if (!PickAttribute()) return false;
  76.                                 HighlightAttribute(true);
  77.                                 repicked = true;
  78.                                 break;
  79.                             default:
  80.                                 go = false;
  81.                                 break;
  82.                         }
  83.                     }
  84.                     else
  85.                     {
  86.                         return false;
  87.                     }
  88.  
  89.                     if (repicked || !go) continue;
  90.  
  91.                     //Create Visual Attribute
  92.                     CreateVisualAttribute();
  93.  
  94.                     _currentPoint = _attPosition;
  95.                     _prevPoint = _attPosition;
  96.                     _currentAngle = 0.0;
  97.                     _prevAngle = 0.0;
  98.  
  99.                     //Drag visual attribute
  100.                     PromptResult jigresult =_ed.Drag(this);
  101.  
  102.                     //Update the selected attribute
  103.                     //if the drag status returns OK
  104.                     if (jigresult.Status == PromptStatus.OK)
  105.                     {
  106.                         if (_jigType == JigType.Move)
  107.                         {
  108.                             MoveAttribute(_currentPoint);
  109.                             _ed.WriteMessage(
  110.                                 "\nSelected attribute has been moved.");
  111.  
  112.                             //Update attribute position
  113.                             _attPosition = GetAttributePosition(_attRefId);
  114.                         }
  115.  
  116.                         if (_jigType == JigType.Rotate)
  117.                         {
  118.                             RotateAttribute(_currentAngle);
  119.                             _ed.WriteMessage(
  120.                                 "\nSelected attribute has been ratated");
  121.                         }
  122.                     }
  123.                     else
  124.                     {
  125.                         return false;
  126.                     }
  127.                 }
  128.             }
  129.             catch
  130.             {
  131.                 throw;
  132.             }
  133.             finally
  134.             {
  135.                 HighlightAttribute(false);
  136.                 if (_visualAtt != null) _visualAtt.Dispose();
  137.             }
  138.  
  139.             return true;
  140.         }
  141.  
  142.         #endregion
  143.  
  144.         #region DrawJig Overrides
  145.  
  146.         protected override bool WorldDraw(WorldDraw draw)
  147.         {
  148.             draw.Geometry.Draw(_visualAtt);
  149.             return true;
  150.         }
  151.  
  152.         protected override SamplerStatus Sampler(JigPrompts prompts)
  153.         {
  154.             if (_jigType == JigType.Move)
  155.             {
  156.                 JigPromptPointOptions opt = new JigPromptPointOptions(
  157.                     "\nPick point to move attribute:");
  158.                 opt.UseBasePoint = true;
  159.                 opt.BasePoint = _attPosition;
  160.                 opt.Cursor = CursorType.RubberBand;
  161.                 PromptPointResult res = prompts.AcquirePoint(opt);
  162.  
  163.                 if (res.Status == PromptStatus.OK)
  164.                 {
  165.                     _currentPoint = res.Value;
  166.                     if (_currentPoint == _prevPoint)
  167.                     {
  168.                         return SamplerStatus.NoChange;
  169.                     }
  170.                     else
  171.                     {
  172.                         Matrix3d mt = Matrix3d.Displacement(
  173.                             _prevPoint.GetVectorTo(_currentPoint));
  174.                         _visualAtt.TransformBy(mt);
  175.  
  176.                         _prevPoint = _currentPoint;
  177.                         return SamplerStatus.OK;
  178.                     }
  179.                 }
  180.                 else
  181.                 {
  182.                     return SamplerStatus.Cancel;
  183.                 }
  184.             }
  185.  
  186.             if (_jigType == JigType.Rotate)
  187.             {
  188.                 JigPromptAngleOptions opt = new JigPromptAngleOptions(
  189.                     "\nEnter or pick rotation angle:");
  190.                 opt.UseBasePoint = true;
  191.                 opt.BasePoint = _attPosition;
  192.                 opt.Cursor = CursorType.RubberBand;
  193.                 PromptDoubleResult res =prompts.AcquireAngle(opt);
  194.  
  195.                 if (res.Status == PromptStatus.OK)
  196.                 {
  197.                     _currentAngle = res.Value;
  198.                     if (_currentAngle == _prevAngle)
  199.                     {
  200.                         return SamplerStatus.NoChange;
  201.                     }
  202.                     else
  203.                     {
  204.                         Matrix3d mt = Matrix3d.Rotation(
  205.                             _currentAngle, Vector3d.ZAxis, _attPosition);
  206.                         _visualAtt.TransformBy(mt);
  207.  
  208.                         _prevAngle = _currentAngle;
  209.                         return SamplerStatus.OK;
  210.                     }
  211.                 }
  212.                 else
  213.                 {
  214.                     return SamplerStatus.Cancel;
  215.                 }
  216.             }
  217.  
  218.             return SamplerStatus.OK;
  219.         }
  220.  
  221.         #endregion
  222.  
  223.         #region private methods
  224.  
  225.         private bool PickAttribute()
  226.         {
  227.             while (true)
  228.             {
  229.                 PromptNestedEntityOptions opt = new
  230.                     PromptNestedEntityOptions("\nPick an attribute:");
  231.                 opt.AllowNone = false;
  232.  
  233.                 PromptNestedEntityResult res = _ed.GetNestedEntity(opt);
  234.                 if (res.Status == PromptStatus.OK)
  235.                 {
  236.                     if (res.ObjectId.ObjectClass.DxfName.ToUpper() == "ATTRIB")
  237.                     {
  238.                         _attRefId = res.ObjectId;
  239.                         _attPosition = GetAttributePosition(_attRefId);
  240.                         return true;
  241.                     }
  242.                     else
  243.                     {
  244.                         _ed.WriteMessage("\nInvalid pick: not an attribute");
  245.                     }
  246.                 }
  247.                 else
  248.                 {
  249.                     return false;
  250.                 }
  251.             }
  252.         }
  253.  
  254.         private Point3d GetAttributePosition(ObjectId id)
  255.         {
  256.             Point3d p = new Point3d();
  257.  
  258.             using (Transaction tran =
  259.                 _db.TransactionManager.StartOpenCloseTransaction())
  260.             {
  261.                 AttributeReference att = tran.GetObject(
  262.                     id, OpenMode.ForRead) as AttributeReference;
  263.                 p = att.Position;
  264.                 tran.Commit();
  265.             }
  266.  
  267.             return p;
  268.         }
  269.  
  270.         private void HighlightAttribute(bool highlight)
  271.         {
  272.             using (Transaction tran =
  273.                 _db.TransactionManager.StartOpenCloseTransaction())
  274.             {
  275.                 Entity ent = tran.GetObject(
  276.                     _attRefId, OpenMode.ForWrite) as Entity;
  277.                 if (highlight)
  278.                     ent.Highlight();
  279.                 else
  280.                     ent.Unhighlight();
  281.  
  282.                 tran.Commit();
  283.             }
  284.         }
  285.  
  286.         private void CreateVisualAttribute()
  287.         {
  288.             if (_visualAtt != null) _visualAtt.Dispose();
  289.             _visualAtt = null;
  290.  
  291.             using (Transaction tran =
  292.                 _db.TransactionManager.StartOpenCloseTransaction())
  293.             {
  294.                 AttributeReference att =
  295.                     (AttributeReference)tran.GetObject(
  296.                     _attRefId, OpenMode.ForRead);
  297.                 _visualAtt = att.Clone() as AttributeReference;
  298.                 _visualAtt.SetDatabaseDefaults(_db);
  299.  
  300.                 tran.Commit();
  301.             }
  302.         }
  303.  
  304.         private void MoveAttribute(Point3d toPoint)
  305.         {
  306.             using (Transaction tran =
  307.                 _db.TransactionManager.StartOpenCloseTransaction())
  308.             {
  309.                 Entity ent = (Entity)tran.GetObject(
  310.                     _attRefId, OpenMode.ForWrite);
  311.  
  312.                 Matrix3d mt = Matrix3d.Displacement(
  313.                     _attPosition.GetVectorTo(toPoint));
  314.                 ent.TransformBy(mt);
  315.  
  316.                 tran.Commit();
  317.             }
  318.         }
  319.  
  320.         private void RotateAttribute(double angle)
  321.         {
  322.             using (Transaction tran =
  323.                 _db.TransactionManager.StartOpenCloseTransaction())
  324.             {
  325.                 Entity ent = (Entity)tran.GetObject(
  326.                     _attRefId, OpenMode.ForWrite);
  327.  
  328.                 Matrix3d mt = Matrix3d.Rotation(
  329.                     angle, Vector3d.ZAxis, _attPosition);
  330.                 ent.TransformBy(mt);
  331.  
  332.                 tran.Commit();
  333.             }
  334.         }
  335.  
  336.         #endregion
  337.     }
  338. }

Then here is the command class that uses the AttributeDrawJig:

Code Snippet
  1. using Autodesk.AutoCAD.ApplicationServices;
  2. using Autodesk.AutoCAD.Runtime;
  3. using Autodesk.AutoCAD.EditorInput;
  4.  
  5. [assembly: CommandClass(typeof(MoveAttribute.MoveAttributeCmd))]
  6.  
  7. namespace MoveAttribute
  8. {
  9.     public class MoveAttributeCmd
  10.     {
  11.         [CommandMethod("AttMove")]
  12.         public static void MoveAttribute()
  13.         {
  14.             Document dwg = Application.DocumentManager.MdiActiveDocument;
  15.             Editor ed = dwg.Editor;
  16.  
  17.             try
  18.             {
  19.                 AttributeDrawJig attJig = new AttributeDrawJig(dwg);
  20.                 if (!attJig.MoveAttribute())
  21.                 {
  22.                     ed.WriteMessage("\n*Cancel*");
  23.                 }  
  24.             }
  25.             catch (System.Exception ex)
  26.             {
  27.                 ed.WriteMessage("\nError: {0}", ex.Message);
  28.             }
  29.  
  30.             Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
  31.         }
  32.     }
  33. }

Here is the video clip showing the code in action.

Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.