Monday, January 17, 2011

Picking Points With Visual Aid

Again, this article was prompted with the discussion in the same thread from Autodesk's .NET discussion here.

As we all know, using PromptPointOptions with Editor.GetPoint(), one can use its properties UseBasePoint and BasePoint tp let AutoCAD show a rubber band line while picking points.

Some times, there are many points to pick, and user may want to have certain visual hint showing the points he/she has picked during the picking process. It may also be helpful to show a trace line follow the order of the points being picked.

Here is the code I do it, in a class called "PickPoints":

using System.Collections.Generic;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;

namespace PickPointWithVisualSpot
{
    public class PickPoints
    {
        private Document _dwg;
        private Editor _editor;
        private List _points = null;
        private List _circles = null;

        private bool _drawPline = false;
        private Polyline _pline = null;

        private int _color = 1;
        private double _radius = 0.1;

        public PickPoints(Document dwg)
        {
            _dwg = dwg;
            _editor = _dwg.Editor;
        }

        public Point3d[] PickedPoints
        {
            get
            {
                if (_points == null)
                    return null;
                else
                    return _points.ToArray();
            }
        }

        public void DoPick(bool drawTraceLine)
        {
            try
            {
                //Pick first point
                Point3d pt;
                if (!PickFirstPoint(out pt)) return;

                _drawPline = drawTraceLine;

                _points = new List();
                _circles = new List();

                _points.Add(pt);

                //Crate visual at the point
                CreateCircle(pt);

                while (true)
                {
                    if (!PickNextPoint()) break;
                }
            }
            catch { }
            finally
            {
                //Clear Transient graphics
                if (_circle != null)
                {
                    if (_circles.Count > 0)
                    {
                        foreach (Circle c in _circles)
                        {
                            TransientManager.CurrentTransientManager.
                                EraseTransient(c, new IntegerCollection());

                            c.Dispose();
                        }
                    }

                    _circles.Clear();
                    _circles = null;
                }

                ClearTraceLine();
            }
        }

        private bool PickFirstPoint(out Point3d pt)
        {
            pt = new Point3d();

            PromptPointOptions opt = 
                new PromptPointOptions("\nPick start point: ");
            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 point: ");
            opt.UseBasePoint=true;
            opt.BasePoint=_points[_points.Count-1];
            if (_points.Count>2)
            {
                opt.Keywords.Add("Close");
                opt.AppendKeywordsToMessage=true;
            }

            PromptPointResult res=_editor.GetPoint(opt);
            if (res.Status==PromptStatus.OK)
            {
                _points.Add(res.Value);

                CreateCircle(res.Value);

                if (_points.Count > 1 && _drawPline )
                {
                    DrawTraceLine();
                }

                return true;
            }
            else if (res.Status==PromptStatus.Keyword)
            {
                return false;
            }
            else
            {
                return false;
            }
        }

        private void CreateCircle(Point3d pt)
        {
            Circle c = new Circle();
            c.Center = pt;
            c.Radius = _radius;
            c.ColorIndex = _color;
            c.Highlight();

            TransientManager.CurrentTransientManager.AddTransient(c, 
                TransientDrawingMode.Highlight,128, new IntegerCollection());

            _circles.Add(c);
        }

        private void DrawTraceLine()
        {
            ClearTraceLine();

            _pline = new Polyline(_points.Count);

            for (int i = 0; i < _points.Count; i++)
            {
                Point2d p = new Point2d(_points[i].X, _points[i].Y);
                _pline.AddVertexAt(i, p, 0.0, 0.0, 0.0);
            }

            _pline.ColorIndex = _color;

            TransientManager.CurrentTransientManager.AddTransient(_pline,
                TransientDrawingMode.Highlight, 128, new IntegerCollection());
        }

        private void ClearTraceLine()
        {
            if (_pline != null)
            {
                TransientManager.CurrentTransientManager.
                        EraseTransient(_pline, new IntegerCollection());

                _pline.Dispose();
                _pline = null;
            }
        }
    }
}
Then the use of the class in custom command. There are 2 commands: one shows trace line, while the other does not:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

[assembly: CommandClass(typeof(PickPointWithVisualSpot.MyCommand))]

namespace PickPointWithVisualSpot
{
    public class MyCommand 
    {
        [CommandMethod("MyPick1", CommandFlags.Session)]
        public static void RunThisMethod1()
        {
            Document dwg = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;

            PickPoints pick = new PickPoints(dwg);

            pick.DoPick(false);
            if (pick.PickedPoints != null)
            {
                dwg.Editor.WriteMessage("\nMyCommand executed: {0} points picked.", pick.PickedPoints.Length);
            }
            else
            {
                dwg.Editor.WriteMessage("\n*Cancelled*");
            }
        }

        [CommandMethod("MyPick2", CommandFlags.Session)]
        public static void RunThisMethod2()
        {
            Document dwg = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;

            PickPoints pick = new PickPoints(dwg);

            pick.DoPick(true);
            if (pick.PickedPoints != null)
            {
                dwg.Editor.WriteMessage("\nMyCommand executed: {0} points picked.", pick.PickedPoints.Length);
            }
            else
            {
                dwg.Editor.WriteMessage("\n*Cancelled*");
            }
        }
    }
}

See this video clip for the code's behaviour.

Note: thanks to Irvin who prompted a bug in the "PickPoints" class' DoPick() method in the try...finally{...} block. I have corrected in red.

2 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi Yuan,

    Looking at this code again. I ran to an other glitch. If you selected 1 or more points. And you hit the Esc button. The user gets prompted on the commandline the command was canceld. This is how AutoCAD works. But in your code you check if the array is not emtpy, if not the commandline will still prompt the number of selected points.

    Where in your code will be the best place to check is promptstatus is canceld en then clear the array of points.

    Kind regards,

    Irvin

    ReplyDelete