Wednesday, June 22, 2011

Show/Hide Segments of a Polyline With Overrule

I saw a question posted in AutoCAD .NET forum asking "if it's possible to create a polyline with invisible segment" here. The question raised my curiosity of thinking how do I do it. Right away, I thought Overrule would be the easy approach to do it. Thus the code shows in this article.

Some explanations. To make things simple, I decide to draw a polyline with multiple segments (e.g. with more than 2 vertices) so that every other segment is visible; also, the Overrule would only apply to polylines that are on certain layer; and the Overrule itself can be turn on or off, regardless if there is other custom Overrule taking effect.

Here is the code:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
 
[assembly: CommandClass(typeof(HidePartialPLine.MyCommands))]
 
namespace HidePartialPLine
{
    public class MyCommands 
    {
        private const string OR_LAYER="Overruled_PLine";
        private static MyPLineOverrule _myOR=null;
 
        [CommandMethod("MyOR")]
        public static void ToggleMyOverrule()
        {
            Document dwg = Application.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            if (_myOR==null) 
            {
                _myOR=new MyPLineOverrule();
                _myOR.StartOverrule();
                ed.WriteMessage("\nMyPLineOverrule is on.\n");
            }
            else
            {
                if (!_myOR.OverruleOn) 
                {
                    _myOR.StartOverrule();
                    ed.WriteMessage("\nMyPLineOverrule is on.\n");
                }
                else
                {
                    _myOR.StopOverrule();
                    ed.WriteMessage("\nMyPLineOverrule is off.\n");
                }
            }    
        }
 
        [CommandMethod("SetOR")]
        public static void SetMyOverruleToPolyline()
        {
            Document dwg = Application.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            ObjectId id = SelectPolyline(ed);
            if (id == ObjectId.Null)
            {
                ed.WriteMessage("\n*Cancel*\n");
                return;
            }
 
            SetPolylineLayer(id, OR_LAYER, dwg.Database);
        }
 
        #region private methods
 
        private static ObjectId SelectPolyline(Editor ed)
        {
            PromptEntityOptions opt=new PromptEntityOptions("\nPick a polyline");
            opt.SetRejectMessage("You must pick a polyline. ");
            opt.AddAllowedClass(
                typeof(Autodesk.AutoCAD.DatabaseServices.Polyline),true);
 
            PromptEntityResult res=ed.GetEntity(opt);
            if (res.Status==PromptStatus.OK)
            {
                return res.ObjectId;
            }
            else
            {
                return ObjectId.Null;
            }
        }
 
        private static void SetPolylineLayer(
            ObjectId entId, string layerName, Database db)
        {
            using (Transaction tran = db.TransactionManager.StartTransaction())
            {
                Entity ent = (Entity)tran.GetObject(entId, OpenMode.ForRead);
                if (ent.Layer.ToUpper() != OR_LAYER.ToUpper())
                {
                    ent.UpgradeOpen();
                    ent.Layer = OR_LAYER;
                }
                tran.Commit();
            }
        }
 
        #endregion
    }
 
    public class MyPLineOverrule : DrawableOverrule
    {
        private const string OR_LAYER = "Overruled_PLine";
        private bool _overruling=false;
        private bool _oldOverruling=false;
 
        public MyPLineOverrule()
        {
            _overruling=false;
        }
 
        public bool OverruleOn
        {
            get { return _overruling; }
        }
 
        public void StartOverrule()
        {
            _overruling=true;
            _oldOverruling = Overrule.Overruling;
 
            Overrule.AddOverrule(RXObject.GetClass(
                typeof(Autodesk.AutoCAD.DatabaseServices.Polyline)), 
                this, false);
 
            //Set custom filter, so that code in overriden IsApplicable() 
            //will run to do the filtering
            this.SetCustomFilter();
 
            Overrule.Overruling=true;
 
            Application.DocumentManager.MdiActiveDocument.Editor.Regen();
        }
 
        public void StopOverrule()
        {
            _overruling = false;
 
            //Cause Overrule redraw the polyline to its original shape
            //before the overrule is stopped
            Application.DocumentManager.MdiActiveDocument.Editor.Regen();
 
            Overrule.RemoveOverrule(RXObject.GetClass(
                typeof(Autodesk.AutoCAD.DatabaseServices.Polyline)), this);
 
            Overrule.Overruling=_oldOverruling;
        }
 
        #region Override overrule methods
 
        public override bool  IsApplicable(RXObject overruledSubject)
        {
            //Only apply this overrule to polyline on layer "Overruled_PLine"
            Entity ent=overruledSubject as Entity;
            return ent.Layer.ToUpper() == OR_LAYER.ToUpper();
        }
 
        public override bool  WorldDraw(Drawable drawable, WorldDraw wd)
        {
            Autodesk.AutoCAD.DatabaseServices.Polyline pl = drawable
                as Autodesk.AutoCAD.DatabaseServices.Polyline;
 
            //If the polyline only has one segment, draw itself
            if (pl.NumberOfVertices < 3)
            {
                return base.WorldDraw(drawable, wd);
            }
 
            if (_overruling)
            {
                //Get polyline's vertices
                Point3d[] pts = new Point3d[pl.NumberOfVertices];
                for (int i = 0; i < pl.NumberOfVertices; i++)
                {
                    pts[i] = pl.GetPoint3dAt(i);
                }
 
                //Draw every other segement
                int n = 0;
                while (n < pts.Length)
                {
                    wd.Geometry.WorldLine(pts[n], pts[n + 1]);
                    n += 2;
                }
 
                return true;
            }
            else
            {
                return base.WorldDraw(drawable, wd);
            }
        }
 
        #endregion
    }
}

The code pretty much explains itself. To test the code, I started AutoCAD, created a layer named as "Overruled_PLine". Then I drew a few polylines on layer 0. After the code being "netload"ed, I can toggle MyPLineOverrule on/off. When it is on, move polyline to layer "Overruled_PLine" will make the segments of a polyline visible/invisible alternately.

The effect can be watched in this video clip.

Saturday, June 18, 2011

Set Layouts in Order

When creating a new layout in AutoCAD, the newly created layout is appended to AutoCAD drawing editor as last layout tab. Sometimes, a user may want to sort the layout tabs in certain order, such as ascending/descending by the layout name, or whatever order the user wants.

AutoCAD editor itself provides a way to manually sort layout tab by right-clicking a layout tab and select "Move or copy..." context menu. This way, user can change a layout tab's order one at a time. It would be tedious work if the user has to re-order many layout tabs.

Sometimes, one may create many layouts programmatically and want to sort their tabs in certain way, most likely, sort them by layout names.

Here is some code that sorts layout tabs by the layout names. The code actually does is to get a string list/array from all layouts' name, then sort/re-order the string list/array, and finally set a Layout's TabOrder property according to its name's order in the string list/array.

    1 using System.Collections.Generic;
    2 using System.Linq;
    3 
    4 using Autodesk.AutoCAD.ApplicationServices;
    5 using Autodesk.AutoCAD.DatabaseServices;
    6 using Autodesk.AutoCAD.Runtime;
    7 using Autodesk.AutoCAD.EditorInput;
    8 
    9 [assembly: CommandClass(typeof(OrderLayout.MyCommands))]
   10 
   11 namespace OrderLayout
   12 {
   13     public class MyCommands
   14     {
   15         [CommandMethod("OrderLayout")]
   16         public static void OrderAllLayouts()
   17         {
   18             Document dwg = Application.DocumentManager.MdiActiveDocument;
   19             Editor ed = dwg.Editor;
   20 
   21             //Get user input of sorting direction
   22             PromptKeywordOptions opt =
   23                 new PromptKeywordOptions("\nEnter sorting order: ");
   24             opt.Keywords.Add("Asc");
   25             opt.Keywords.Add("Desc");
   26             opt.AppendKeywordsToMessage = true;
   27 
   28             PromptResult res = ed.GetKeywords(opt);
   29             if (res.Status == PromptStatus.OK)
   30             {
   31                 string order = res.StringResult;
   32 
   33                 //Sort the layouts
   34                 SetLayoutOrder(order);
   35 
   36                 ed.Regen();
   37             }
   38             else
   39             {
   40                 ed.WriteMessage("\n*Cancel*\n");
   41             }
   42         }
   43 
   44         [CommandMethod("NewLayout")]
   45         public static void CreateNewLayout()
   46         {
   47             Document dwg = Application.DocumentManager.MdiActiveDocument;
   48             Editor ed = dwg.Editor;
   49 
   50             //Get new layout name from user
   51             PromptStringOptions opt =
   52                 new PromptStringOptions("\nEnter a new layout name: ");
   53 
   54             PromptResult res = res = ed.GetString(opt);
   55             if (res.Status == PromptStatus.OK)
   56             {
   57                 string lName = res.StringResult;
   58 
   59                 //Create new layout
   60                 CreateLayout(lName);
   61 
   62                 ed.Regen();
   63             }
   64             else
   65             {
   66                 ed.WriteMessage("\n*Cancel*\n");
   67             }
   68         }
   69 
   70         [CommandMethod("ClearLayout")]
   71         public static void ClearAllLayout()
   72         {
   73             Document dwg = Application.DocumentManager.MdiActiveDocument;
   74             Editor ed = dwg.Editor;
   75 
   76             //Remove all layout.
   77             //Note, depending Acad configuration, after this method running,
   78             //all layouts should be removed. However, AutoCAD may always create
   79             //a layout named as "Layout1" right after this command execution.
   80             ClearLayouts();
   81 
   82             ed.WriteMessage("All layouts have been removed.");
   83         }
   84 
   85         #region private methods - miscellaneous, create and remove layout
   86 
   87         //This method returns a layout name list ("Model" excluded)
   88         private static string[] GetLayoutNames()
   89         {
   90             List<Layout> lays = new List<Layout>();
   91 
   92             Database db = HostApplicationServices.WorkingDatabase;
   93             using (Transaction tran = db.TransactionManager.StartTransaction())
   94             {
   95                 DBDictionary lDic =
   96                     (DBDictionary)tran.GetObject(db.LayoutDictionaryId,
   97                     OpenMode.ForRead);
   98 
   99                 foreach (DBDictionaryEntry entry in lDic)
  100                 {
  101                     ObjectId id = (ObjectId)entry.Value;
  102                     lays.Add(tran.GetObject(id, OpenMode.ForRead) as Layout);
  103                 }
  104 
  105                 tran.Commit();
  106             }
  107 
  108             int c = lays.Count;
  109 
  110             return (from l in lays
  111                     where l.LayoutName.ToUpper() != "MODEL"
  112                     select l.LayoutName).ToArray();
  113         }
  114 
  115         private static Layout GetLayoutFromId(ObjectId id, Transaction tran)
  116         {
  117             return tran.GetObject(id, OpenMode.ForRead) as Layout;
  118         }
  119 
  120         private static void CreateLayout(string layoutName)
  121         {
  122             //Get Layout list
  123             string[] layouts = GetLayoutNames();
  124             if (LayoutExists(layoutName, layouts)) return;
  125 
  126             LayoutManager mgr = LayoutManager.Current;
  127             mgr.CreateLayout(layoutName);
  128         }
  129 
  130         private static void ClearLayouts()
  131         {
  132             //Get Layout list
  133             string[] layouts = GetLayoutNames();
  134 
  135             LayoutManager mgr = LayoutManager.Current;
  136             foreach (string l in layouts)
  137             {
  138                 mgr.DeleteLayout(l);
  139             }
  140         }
  141 
  142         private static bool LayoutExists(string lName, string[] layouts)
  143         {
  144             return (from l in layouts
  145                     where l.ToUpper() == lName.ToUpper()
  146                     select l).Count() > 0;
  147         }
  148 
  149         #endregion
  150 
  151         #region private methods - Sort layout in order
  152 
  153         private static void SetLayoutOrder(string order)
  154         {
  155             //Get Layout list
  156             string[] layouts = GetLayoutNames();
  157             string[] sortedLayouts;
  158 
  159             if (order.ToUpper() == "ASC")
  160                 sortedLayouts = (from l in layouts
  161                                  orderby l ascending select l).ToArray();
  162             else
  163                 sortedLayouts = (from l in layouts
  164                                  orderby l descending select l).ToArray();
  165 
  166             Database db = HostApplicationServices.WorkingDatabase;
  167 
  168             using (Transaction tran = db.TransactionManager.StartTransaction())
  169             {
  170                 LayoutManager mgr = LayoutManager.Current;
  171 
  172                 //First layout tab order should be 1
  173                 //because "Model" layout tab's order is always 0
  174                 int tab = 1;
  175                 foreach (string l in sortedLayouts)
  176                 {
  177                     ObjectId id = mgr.GetLayoutId(l);
  178 
  179                     Layout lay = tran.GetObject(id, OpenMode.ForWrite) as Layout;
  180                     lay.TabOrder = tab;
  181 
  182                     tab++;
  183                 }
  184 
  185                 tran.Commit();
  186             }
  187         }
  188 
  189         #endregion
  190     }
  191 }

In the above vode, command "NewLayout" and "ClearLayout" are just for the convenience of testing the "OrderLayout" command. To see the layout sorting effect, use "NewLayout" command to create a few randomly named layouts. Then run "OrderLayout" command.

As you can see, the trick of sorting layouts lies in getting a layout name list and re-order it in a desired order, then setting Layout.TabOrder property according to its name's order in the re-ordered layout name list. If you need to sort the layouts in specific order (not just ascending or descending by its name), you may want to build a UI that allows user to line up layouts' name in any order he/she wants to. Then you can obtain a String list/array ordered in the order user sets. Afterwards, you know how to set Layout's TabOrder property, as the code shows here.

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.