Sunday, January 16, 2011

Picking Entity Like Picking Point With Rubber Band Line

This article is inspired by this question posted by Irvin in Autodesk .NET discussion forum here.

In a occassion when picking a series of entities, the user may not only wants to have the picked entity highlighted, but also wants a rubber band line drawn from the previously picked point (or point derived from the previously picked entity) to the moving mouse cursor before picking next entity.

As we know, using PromptPointOption in conjunction with Editor.GetPoint(), AutoCAD draws a rubber band line, if PromptPointOptions.UseBasePoint is set true and a base point is supplied. However, to pick an entity, PromptEntityOptions and Editor.GetEntity() do not provide such option as "UseBasePoint".

Here is my way to solve this issue. After the first entity is picked, I handle Editor.PointMonitor event, so that when user moves mouse to pick next entity, a Transient Graphics (Line) is drawn in the event handler. The code is fairly simple, as following.

First, as I usually do, I created a class to encapsulate the wanted functionality: PickEntities.

using System.Collections.Generic;

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

namespace PickMultiplePoints
{
    public class PickEntities
    {
        private Document _dwg;
        private Editor _editor;

        private List _points = null;
        private List _entities = null;

        private Line _rubberLine = null;

        private Point3d _basePoint;
        private Point3d _currentPoint;

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

        #region public properties 

        public List PickedPoints
        {
            get { return _points; }
        }

        public List PickedEntities
        {
            get { return _entities; }
        }

        #endregion

        public void DoPick(bool unhighlightAtEnd)
        {
            //Pick first entity
            ObjectId id;
            Point3d pt;
            if (!PickFirstEntity(out id, out pt)) return;

            _rubberLine = null;
            _points = new List();
            _entities = new List();

            _points.Add(pt);
            _entities.Add(id);

            SetHighlight(id, true);

            //Start drag rubber band line
            while (true)
            {
                if (!PickNextEntity()) break;
            }

            //Unhighlight all picked entities
            if (unhighlightAtEnd)
            {
                foreach (ObjectId entId in _entities)
                    SetHighlight(entId, false);
            }
        }

        #region private methods

        private bool PickFirstEntity(
            out ObjectId id, out Point3d pt)
        {
            pt = new Point3d();
            id = ObjectId.Null;

            PromptEntityOptions opt = 
                new PromptEntityOptions("\nPick an entity: ");
            PromptEntityResult res = _editor.GetEntity(opt);

            if (res.Status == PromptStatus.OK)
            {
                id = res.ObjectId;

                //if the entity is a BlockRefernece, get Insertion Point
                //otherwise get the picked point
                Point3d p;
                if (GetBlockInsPoint(res.ObjectId, out p))
                    pt = p;
                else
                    pt = res.PickedPoint;

                return true;
            }
            else
            {
                return false;
            }
        }

        private bool GetBlockInsPoint(ObjectId id, out Point3d insPt)
        {
            insPt=new Point3d();
            bool isBlk = false;

            using (Transaction tran = _dwg.Database.
                TransactionManager.StartTransaction())
            {
                BlockReference blk = tran.GetObject(
                    id, OpenMode.ForRead) as BlockReference;

                if (blk != null)
                {
                    insPt = blk.Position;
                    isBlk = true;
                }

                tran.Commit();
            }

            return isBlk;
        }

        private bool PickNextEntity()
        {
            _basePoint = _points[_points.Count - 1];

            string msg = 
                "\n" + _points.Count + " picked. Pick next entity: ";
            PromptEntityOptions opt = new PromptEntityOptions(msg);

            try
            {
                //Create rubber band line
                _rubberLine = new Line(_basePoint, _basePoint);

                //Set line properties, for example
                _rubberLine.SetDatabaseDefaults(_dwg.Database);

                //Create Transient graphics
                IntegerCollection intCol = new IntegerCollection();
                TransientManager.CurrentTransientManager.
                    AddTransient(_rubberLine,
                    TransientDrawingMode.DirectShortTerm, 128, intCol);

                _editor.PointMonitor += 
                    new PointMonitorEventHandler(_editor_PointMonitor);

                PromptEntityResult res =_editor.GetEntity(opt);

                if (res.Status == PromptStatus.OK)
                {
                    bool exists = false;
                    foreach (ObjectId ent in _entities)
                    {
                        if (ent == res.ObjectId)
                        {
                            exists = true;
                            break;
                        }
                    }

                    if (!exists)
                    {
                        //if the entity is a BlockRefernece, 
                        //get Insertion Point.
                        //Otherwise get the picked point
                        Point3d p;
                        if (!GetBlockInsPoint(res.ObjectId, out p))
                        {
                            p = res.PickedPoint;
                        }

                        _points.Add(p);
                        _entities.Add(res.ObjectId);

                        SetHighlight(res.ObjectId, true);
                    }
                    else
                    {
                        _editor.WriteMessage(
                            "\nThe entity has already been picked!");
                    }
                        
                    return true;
                }
                else
                {
                    return false;
                }
            }
            finally
            {
                if (_rubberLine != null)
                {
                    //Clear transient graphics
                    IntegerCollection intCol = new IntegerCollection();
                    TransientManager.CurrentTransientManager.
                        EraseTransient(_rubberLine, intCol);

                    _rubberLine.Dispose();
                    _rubberLine = null;
                }

                _editor.PointMonitor -= 
                    new PointMonitorEventHandler(_editor_PointMonitor);
            }
        }

        private void SetHighlight(ObjectId id, bool highlight)
        {
            using (Transaction tran = _dwg.Database.
                TransactionManager.StartTransaction())
            {
                Entity ent = (Entity)tran.GetObject(id, OpenMode.ForWrite);

                if (highlight)
                    ent.Highlight();
                else
                    ent.Unhighlight();
            }
        }

        private void _editor_PointMonitor(
            object sender, PointMonitorEventArgs e)
        {
            //Get mouse cursor point
            _currentPoint = e.Context.RawPoint;

            //Update line
            _rubberLine.EndPoint = _currentPoint;

            //Update Transient graphics
            IntegerCollection intCol = new IntegerCollection();
            TransientManager.CurrentTransientManager.
                UpdateTransient(_rubberLine, intCol);
        }

        #endregion
    }
}

Then, I use "PickEntities" class in a command method of a command class:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;

[assembly: CommandClass(typeof(PickMultiplePoints.MyCommands))]

namespace PickMultiplePoints
{
    public class MyCommands
    {
        [CommandMethod("MyPick")]
        public static void DoCommand()
        {
            Document dwg = Autodesk.AutoCAD.ApplicationServices.
                Application.DocumentManager.MdiActiveDocument;

            Editor ed = dwg.Editor;

            //Do picking
            PickEntities picker = new PickEntities(dwg);
            picker.DoPick(true);

            ObjectId[] ids = picker.PickedEntities.ToArray();

            ed.WriteMessage(
                "\nMy command executed: {0} entities picked.", 
                ids.Length);
        }
    }
}

Here is the video clip of the code in action.

Use the technique presented in this article, one can also makes AutoCAD draw a polyline started from the first picked entity/point along the picked entities dynamically.

1 comment:

  1. Great code, but it throws an unhandled exception if you cancel the command before picking the first entity:

    if(!PickFirstEntity(out id, out pt)) return;

    return before _entities is instanciated, so

    ObjectId[] ids = picker.PickedEntities.ToArray();
    throws an exception.

    ReplyDelete