000

Index Labels

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.

Blog Archive

Labels

3D Modeling 3D Sketch Inventor AI Design AI in Manufacturing AI Tools Architecture Artificial Intelligence AutoCAD AutoCAD advice AutoCAD Basics AutoCAD Beginners AutoCAD Civil3D AutoCAD commands AutoCAD efficiency AutoCAD features AutoCAD File Management AutoCAD Layer AutoCAD learning AutoCAD print settings AutoCAD productivity AutoCAD Teaching AutoCAD Techniques AutoCAD tips AutoCAD training. AutoCAD tricks AutoCAD Tutorial AutoCAD workflow AutoCAD Xref Autodesk Autodesk 2025 Autodesk AI Tools Autodesk AutoCAD Autodesk Fusion 360 Autodesk Inventor Autodesk Inventor Frame Generator Autodesk Inventor iLogic Autodesk Recap Autodesk Revit Autodesk Software Autodesk Video Automation Automation Tutorial Basic Commands Basics Beginner Beginner Tips BIM BIM Implementation Block Editor ByLayer CAD comparison CAD Design CAD File Size Reduction CAD line thickness CAD Optimization CAD Productivity CAD software clean CAD file cleaning command Cloud Collaboration command abbreviations Construction Technology Contraints Create resizable blocks CTB STB Data Reference Data Shortcut design software Design Workflow Digital Design Digital Twin Drafting Standards Drawing Automation Dref Dynamic Block Dynamic Block AutoCAD Dynamic Blocks Dynamic doors Dynamic windows eco design editing commands energy efficiency Engineering Engineering Design Engineering Innovation Engineering Technology engineering tools Excel Express Tools External Reference Fast Structural Design Fusion 360 Generative Design green building Grips heavy CAD file Heavy CAD Files iLogic Industry 4.0 Insight Inventor API Inventor Drawing Template Inventor Frame Generator Inventor Graphics Issues Inventor IDW Inventor Tips Keyboard Shortcuts Learn AutoCAD Machine Learning in CAD maintenance command Management Manufacturing Innovation Metal Structure ObjectARX .NET API Organization OVERKILL OVERKILL AutoCAD Palette PDF Plot Style AutoCAD Practice Drawing Printing Quality professional printing Professional Tips PTC Creo PURGE PURGE AutoCAD ReCap reduce CAD file size Resizable Block Revit Revit Best Practices Revit Workflow Ribbon screen shortcut keys Shortcuts Siemens NX Sketch Small Firms Smart Block Smart Factory SolidWorks Steel Structure Design sustainability Sustainable Manufacturing toolbar Tutorial User Interface (UI) Workbook Workspace XLS Xref