Friday, September 19, 2014

Moving Mouse Cursor To Trace A Polyline

There is a question posted in www.theswamp.org about mimicking an Civil3D function. That question reminds me a piece of code I recently done in one of my AutoCAD development projects. That piece of code does quite similar as the aforementioned question asks: user clicks anywhere on a polyline, then the code draws TransientGraphics along the polyline from its start point, up to where the mouse clicked, so that the user is given eye-catching graphic showing how long the winding section of selected polyline is.

I do not use Civil3D, but thought I know what the question is asking for. So, I decided to dig out that piece of code and do a bit enhancement, so that the temporary TransientGraphics can be drawn dynamically when the mouse cursor moves along the polyline.

I though I would like the code to do this:

1. User selects a polyline;
2. User decides the start point of the tracing. Polyline's start point is default, but user can choose to pick a point on the polyline;
3. Set an allowed offset, so that the mouse cursor does not have to move along the polyline exactly on top of the polyline;
4. The temporary TransientGraphics' color/lineweight can be set easily.

The code materializes above goals are actually quite simple.

Here is the class PolylineTracer:

using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
 
namespace TraceAlongPolyline
{
    public class PolylineTracer : IDisposable
    {
        private Document _dwg;
        private Editor _ed;
        private TransientManager _tsManager;
        private CadDb.Polyline _polyline = null;
        private CadDb.Polyline _drawable = null;
        private Point3d _startPoint;
        private double _allowedOffset = 1.0;
 
        private int _colorIndex = 2;
        private LineWeight _lineWeight = LineWeight.LineWeight050;
 
        public PolylineTracer(Document dwg)
        {
            _dwg = dwg;
            _ed = dwg.Editor;
            _tsManager = TransientManager.CurrentTransientManager;
        }
 
        public void Dispose()
        {
            ClearTransientGraphics();
        }
 
        public void Trace(
            int colorIndex=2, LineWeight lineweight=LineWeight.LineWeight050)
        {
            _colorIndex = colorIndex;
            _lineWeight = lineweight;
 
            ObjectId selectedId = SelectPolyline();
            if (selectedId.IsNull)
            {
                _ed.WriteMessage("\n*Cancel*");
                return;
            }
 
            using (Transaction tran =
                _dwg.TransactionManager.StartTransaction())
            {
                _polyline = (CadDb.Polyline)
                    tran.GetObject(selectedId, OpenMode.ForRead);
                _allowedOffset = _polyline.Length / 50.0;
 
                _polyline.Highlight();
 
                try
                {
                    if (SelectTraceStartPoint())
                    {
                        int showLineWeight = 
                            Convert.ToInt32(
                            CadApp.GetSystemVariable("LWDISPLAY"));
 
                        CadApp.SetSystemVariable("LWDISPLAY", 1);
 
                        try
                        {
                            _ed.PointMonitor += Editor_PointMonitor;
                            PromptPointOptions opt = new PromptPointOptions(
                                "\nClick on the polyline for path length:");
                            opt.AllowNone = true;
                            opt.Keywords.Add("eXit");
                            opt.Keywords.Default = "eXit";
 
                            PromptPointResult res = _ed.GetPoint(opt);
                            if (res.Status == PromptStatus.OK)
                            {
                                if (_drawable != null)
                                {
                                    _ed.WriteMessage(
                                        "\nLength of picked path: {0}", 
                                        _drawable.Length);
                                }
                            }
                        }
                        finally
                        {
                            _ed.PointMonitor -= Editor_PointMonitor;
                            CadApp.SetSystemVariable(
                                "LWDISPLAY", showLineWeight);
                        }
                    }
                }
                finally
                {
                    _polyline.Unhighlight();
                }
 
                tran.Commit();
            }
        }
 
        #region private methods
 
        private ObjectId SelectPolyline()
        {
            PromptEntityOptions opt = new PromptEntityOptions(
                "\nSelect a polyline:");
            opt.SetRejectMessage("\nInvalid selection: not a polyline.");
            opt.AddAllowedClass(typeof(CadDb.Polyline), true);
            PromptEntityResult res = _ed.GetEntity(opt);
            if (res.Status == PromptStatus.OK)
                return res.ObjectId;
            else
                return ObjectId.Null;
        }
 
        private bool SelectTraceStartPoint()
        {
            while (true)
            {
                PromptPointOptions opt = new PromptPointOptions(
                    "\nTrace starts at -> " + 
                    "pick a point on the selected polyline, " +
                    "or use polyline's start point:");
                opt.AllowNone = true;
                opt.Keywords.Add("Start point");
                opt.Keywords.Default = "Start point";
 
                PromptPointResult res = _ed.GetPoint(opt);
                if (res.Status == PromptStatus.OK || 
                    res.Status == PromptStatus.Keyword)
                {
                    if (res.Status == PromptStatus.Keyword)
                    {
                        _startPoint = _polyline.StartPoint;
                        return true;
                    }
                    else
                    {
                        Point3d pt = _ed.Snap("NEA", res.Value);
                        if (IsPointOnPolyline(pt, _polyline))
                        {
                            _startPoint = pt;
                            return true;
                        }
                    }
 
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
 
        private bool IsPointOnPolyline(Point3d pt, CadDb.Polyline poly)
        {
            Point3d closest = poly.GetClosestPointTo(pt, false);
            return IsTheSamePoint(closest, pt);
        }
 
        private bool IsTheSamePoint(Point3d pt1, Point3d pt2)
        {
            double dist = pt1.DistanceTo(pt2);
            return dist <= Tolerance.Global.EqualPoint;
        }
 
        #endregion
 
        #region private methods: draw transientgraphics along the polyline
 
        private void Editor_PointMonitor(
            object sender, PointMonitorEventArgs e)
        {
            ClearTransientGraphics();
 
            //Current mouse cursor point
            Point3d pt = e.Context.RawPoint;
 
            //Closest point on the polyline
            Point3d closest = _polyline.GetClosestPointTo(pt, false);
 
            //If the mouse cursor is to far off the polyline
            if (pt.DistanceTo(closest) > _allowedOffset) return;
 
            //Draw TransientGraphics
            DrawTransientGraphics(closest);
 
            double l = _drawable.Length;
            string tip = string.Format("Traced length: {0}", l);
            e.AppendToolTipText(tip);
        }
 
        private void ClearTransientGraphics()
        {
            if (_drawable!=null)
            {
                _tsManager.EraseTransient(_drawable, new IntegerCollection());
                _drawable.Dispose();
                _drawable = null;
            }
        }
 
        private void DrawTransientGraphics(Point3d endPoint)
        {
            List<Point2d> vertices = CollectVerticesFromPolyline(endPoint);
            _drawable = CreatePolylineFromPoints(vertices);
            _tsManager.AddTransient(
                _drawable, 
                TransientDrawingMode.DirectTopmost, 
                128, 
                new IntegerCollection());
        }
 
        private List<Point2d> CollectVerticesFromPolyline(Point3d endPoint)
        {
            List<Point2d> lst = new List<Point2d>();
 
            double startDistance = _polyline.GetDistAtPoint(_startPoint);
            double endDistance = _polyline.GetDistAtPoint(endPoint);
 
            lst.Add(new Point2d(_startPoint.X, _startPoint.Y));
 
            bool stop = false;
 
            for (int i = 1; i < _polyline.NumberOfVertices; i++)
            {
                Point3d vertex = _polyline.GetPoint3dAt(i);
                double dist = _polyline.GetDistAtPoint(vertex);
 
                if (dist>startDistance)
                {
                    if (dist<endDistance)
                    {
                        lst.Add(new Point2d(vertex.X, vertex.Y));
                    }
                    else
                    {
                        lst.Add(new Point2d(endPoint.X, endPoint.Y));
                        stop = true;
                    }
                }
 
                if (stop) break;
            }
                
            return lst;
        }
 
        private CadDb.Polyline CreatePolylineFromPoints(List<Point2d> points)
        {
            CadDb.Polyline poly = new CadDb.Polyline(points.Count);
            int i = 0;
            foreach (var p in points)
            {
                poly.AddVertexAt(i, p, 0.0, 0.0, 0.0);
                i++;
            }
 
            poly.ColorIndex = _colorIndex;
            poly.LineWeight = _lineWeight;
 
            return poly;
        }
 
        #endregion
    }
}

Here is the command method to run it:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(TraceAlongPolyline.MyCadCommands))]
 
namespace TraceAlongPolyline
{
    public class MyCadCommands
    {
        [CommandMethod("TracePoly")]
        public static void RunMyCadCommand()
        {
            Document dwg = CadApp.DocumentManager.MdiActiveDocument;
            Editor ed = dwg.Editor;
 
            try
            {
                using (PolylineTracer tracer=new PolylineTracer(dwg))
                {
                    tracer.Trace();
                }
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nError: {0}\n{1}", ex.Message, ex.StackTrace);
            }
            finally
            {
                Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
            }
        }
    }
}

This video clip shows how the code works.

1 comment: