So, I modified the code to use DrawableOverrule to do this job (obviously, the code only works with AutoCAD 2010 and later). I also modified the code to allow pick a background color before picking the first point.
First, I removed the code that creates Hatch, so the MyAreaCmd class was restored back to almost the same as the first version when there is not background rendered when user does the picking. Only a polygon is drawn dynamically.
Then, I created a custom DrawableOverrule, which filters out the polygon as the overrule's target and renders the background.
Finally, I added two 2 lines of code to MyAreaCmd to enable the overrule after user picked first 2 points and disable the overrule when the picking is done. Following is the whole set of code (I have renamed the class to "MyNewAraeCmd").
Class "MyNewAreaCmd":
using System; using System.Collections.Generic; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.GraphicsInterface; using Autodesk.AutoCAD.DatabaseServices; namespace AreaCommand { public class MyNewAreaCmd { private Document _dwg; private Editor _editor; private double _area = 0.0; private double _perimeter = 0.0; private Autodesk.AutoCAD.DatabaseServices.Polyline _pline = null; private ListClass "MyPolylineOverrule":_points; private bool _pickDone; private int _color = 1; public MyNewAreaCmd(Document dwg) { _dwg = dwg; _editor = _dwg.Editor; } public double Area { get { return _area; } } public double Perimeter { get { return _perimeter; } } public bool GetArea() { _pline = null; //Pick first point Point3d pt1; if (!GetFirstPoint(out pt1)) return false; //Pick second point Point3d pt2; if (!GetSecondPoint(pt1, out pt2)) return false; _pickDone = false; _points = new List (); _points.Add(new Point2d(pt1.X, pt1.Y)); _points.Add(new Point2d(pt2.X, pt2.Y)); try { //Enable custom Overrule MyPolylineOverrule.Instance.StartOverrule(_points, _color); //Handling mouse cursor moving during picking _editor.PointMonitor += new PointMonitorEventHandler(_editor_PointMonitor); while (true) { if (!PickNextPoint()) break; } if (_pline != null && _pickDone) { Calculate(); } } catch { throw; } finally { ClearTransientGraphics(); //Remove PointMonitor handler _editor.PointMonitor -= new PointMonitorEventHandler(_editor_PointMonitor); //Disbale custom Overrule MyPolylineOverrule.Instance.EndOverrule(); } return _pickDone; } #region private methods private void Calculate() { Autodesk.AutoCAD.DatabaseServices.Polyline p = new Autodesk.AutoCAD.DatabaseServices.Polyline(_points.Count); for (int i = 0; i < _points.Count; i++) p.AddVertexAt(i, _points[i], 0.0, 0.0, 0.0); p.Closed = true; _area = p.Area; _perimeter = p.Length; p.Dispose(); } private bool GetFirstPoint(out Point3d pt) { pt = new Point3d(); while (true) { PromptPointOptions opt = new PromptPointOptions("\nPick first corner: "); opt.Keywords.Add("Background"); opt.AppendKeywordsToMessage = true; PromptPointResult res = _editor.GetPoint(opt); if (res.Status == PromptStatus.OK) { pt = res.Value; return true; } else if (res.Status == PromptStatus.Keyword) { PromptIntegerOptions intOpt = new PromptIntegerOptions("\nEnter color number (1 to 7): "); intOpt.AllowNegative = false; intOpt.AllowZero = false; intOpt.AllowArbitraryInput = false; intOpt.UseDefaultValue = true; intOpt.DefaultValue = 1; PromptIntegerResult intRes = _editor.GetInteger(intOpt); if (intRes.Status == PromptStatus.OK) { _color = intRes.Value; } } else { return false; } } } private bool GetSecondPoint(Point3d basePt, out Point3d pt) { pt = new Point3d(); PromptPointOptions opt = new PromptPointOptions("\nPick next corner: "); opt.UseBasePoint = true; opt.BasePoint = basePt; PromptPointResult res = _editor.GetPoint(opt); if (res.Status == PromptStatus.OK) { pt = res.Value; return true; } else { return false; } } private bool PickNextPoint() { PromptPointOptions opt = new PromptPointOptions("\nPick next corner: "); if (_points.Count > 2) { opt.Keywords.Add("Undo"); opt.Keywords.Add("Total"); opt.Keywords.Default = "Total"; opt.AppendKeywordsToMessage = true; opt.AllowArbitraryInput = false; } PromptPointResult res = _editor.GetPoint(opt); if (res.Status == PromptStatus.OK) { _points.Add(new Point2d(res.Value.X, res.Value.Y)); return true; } else if (res.Status == PromptStatus.Keyword) { if (res.StringResult == "Undo") { if (_points.Count > 2) { _points.RemoveAt(_points.Count - 1); } return true; } else { _pickDone = true; return false; } } else { _pickDone = false; return false; } } private void ClearTransientGraphics() { if (_pline != null ) { TransientManager.CurrentTransientManager.EraseTransients( TransientDrawingMode.DirectTopmost, 128, new IntegerCollection()); _pline.Dispose(); _pline = null; } } private void _editor_PointMonitor(object sender, PointMonitorEventArgs e) { ClearTransientGraphics(); //Get mouse cursor location Point2d pt = new Point2d(e.Context.RawPoint.X, e.Context.RawPoint.Y); _pline = new Autodesk.AutoCAD.DatabaseServices.Polyline(_points.Count + 1); for (int i = 0; i < _points.Count; i++) { _pline.AddVertexAt(i, _points[i], 0.0, 0.0, 0.0); } _pline.AddVertexAt(_points.Count, pt, 0.0, 0.0, 0.0); _pline.Closed = true; TransientManager.CurrentTransientManager.AddTransient( _pline, TransientDrawingMode.DirectTopmost, 128, new IntegerCollection()); } #endregion } }
using System; using System.Collections.Generic; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.GraphicsInterface; using Autodesk.AutoCAD.Runtime; namespace AreaCommand { public class MyPolylineOverrule : DrawableOverrule { private static MyPolylineOverrule _instance = null; private bool _existingOverrulling; private int _color = 1; private ListCommand class "MyCommands":_points = null; public static MyPolylineOverrule Instance { get { if (_instance == null) _instance = new MyPolylineOverrule(); return _instance; } } public int Color { set { _color = value; } get { return _color; } } public void StartOverrule(List points) { _points = points; _existingOverrulling = Overruling; //Add the custom overrule AddOverrule(RXObject.GetClass( typeof(Autodesk.AutoCAD.DatabaseServices.Polyline)), this, false); //Use custom filter, implemented in IsApplicable() method SetCustomFilter(); //Make sure Overrule is enabled Overruling = true; } public void StartOverrule(List points, int color) { _color = color; _points = points; _existingOverrulling = Overruling; //Add the custom overrule AddOverrule(RXObject.GetClass( typeof(Autodesk.AutoCAD.DatabaseServices.Polyline)), this, false); //Use custom filter, implemented in IsApplicable() method SetCustomFilter(); //Make sure Overrule is enabled Overruling = true; } public void EndOverrule() { //Remove this custom Overrule RemoveOverrule(RXObject.GetClass( typeof(Autodesk.AutoCAD.DatabaseServices.Polyline)), this); //restore to previous Overrule status (enabled or disabled) Overruling = _existingOverrulling; } public override bool WorldDraw(Drawable drawable, WorldDraw wd) { Point3dCollection pts = new Point3dCollection(); for (int i = 0; i < _points.Count; i++) { pts.Add(new Point3d(_points[i].X, _points[i].Y, 0.0)); } wd.SubEntityTraits.FillType = FillType.FillAlways; wd.SubEntityTraits.Color = Convert.ToInt16(_color); wd.Geometry.Polygon(pts); return base.WorldDraw(drawable, wd); } public override bool IsApplicable( Autodesk.AutoCAD.Runtime.RXObject overruledSubject) { Autodesk.AutoCAD.DatabaseServices.Polyline pl = overruledSubject as Autodesk.AutoCAD.DatabaseServices.Polyline; if (pl != null) { //Only apply this overrule to the polyline //that has not been added to working database //e.g. created for the Transient Graphics if (pl.Database == null) return true; else return false; } else { return false; } } } }
using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.EditorInput; [assembly: CommandClass(typeof(AreaCommand.MyCommands))] namespace AreaCommand { public class MyCommands { [CommandMethod("MyNewArea")] public static void GetNewArea() { Document dwg = Autodesk.AutoCAD.ApplicationServices. Application.DocumentManager.MdiActiveDocument; Editor ed = dwg.Editor; MyNewAreaCmd cmd = new MyNewAreaCmd(dwg); if (cmd.GetArea()) { ed.WriteMessage("\nArea = {0}", cmd.Area); ed.WriteMessage("\nPerimeter = {0}", cmd.Perimeter); } else { ed.WriteMessage("\n*Cancelled*"); } } } }
See this video clip for its action.
One will notice that the background showing is different from AutoCAD built-in "AREA" command: it only covers the picked points, not the point to be picked when user is moving the mouse. In my opinion, my approach is better: the background-covered area is the real area the command would produce if user hit Enter to complete the command, which should not include the area user has not picked.
Thank you Norman for posting your stuff in this blog. You have always been a help back at the VBA discussion back in the day. Now it is on to .NET. The area tool is something I need to remake in .NET so this is a great help. Obviously you created something that you do not use. In my mind it does not have the correct interface. For one the undo should be accomplished with a CTRL key or SPACE key combination click anywhere on the screen. The user need not take the hand off the mouse and the required key should be easy to reach. Furthermore the temporary area boundary needs to remain visible when the area is reported so that the user can the final insanity check. A following input, mouse or key, would remove the area boundary, if wanted. In most cases when measuring areas one wants the area boundary to remain visible as a marker of what has been measured so far. It is best if that area value is placed as text within the boundary. This last task a bit tricky to do because the centroid is not necessarily within the boundary. Thanks again for this blog. aks
ReplyDeleteThanks for the comment and the suggestion of possible enhancement to make it a real user-friendly tool.
ReplyDeleteYes, I am not a CAD user who uses something I created. The code I posted in my blog by no means are ready-to-use tools. Rather, I focus on showing some CAD programming tips, tricks from my experience. With that said, though, from your comment we can see, even a small tool like this, there is a lot room to improve it to meet user's expectation.
The particular good part of your suggestion is to show area dynamically during pick. While it can be done by drawing a text within boundary of the picked area, using tool tip might be easier to do, since there is alread an Editor_PointMonitor event handler. I'll see if I can find a bit time to update it later.
Just read this and tried. Immediately went to see if bricscad has drawable overrules and it does.
ReplyDeleteSomeone told me these overrules are very fast. I need to show the triangles for surfaces with 500k tris and I bet this is the answer.
I also need to use pointmonitor to make a draw pline command that sketches when mouse is held down, and draws point to point for quick picks.