Thursday, November 18, 2010

Dragging a Line in Certain Angle

This article is inspired by a question posted in the Autodesk's Visual Basic Customization user forum. Baiscally, while drawing a line, after picking the start point, the user wants the ghost line only stretch in certain direction/angle, similar effect as the Ortho-On mode. Well, as a programmer, not a drafter/designer, I am not very sure how often this kind of fuctionality is desired in AutoCAD use. If one wants to draw a line that he knows the line's start/end point, or start point, lenght and direction/angle, he can alway enter them easily at command line. However I can imagine that during designing (nt drafting) process, the designer may want to draw a line, starting at a known point and she'd like it to be stretched at certain angle with undecided length.

Regardless it possible use/benefit an AutoCAD user may find, here is the code to do this. Yes, as you may have guessed, I used TransientGraphics again.

Here is the class that do the dynamic dragging. At the end of AngledDrag() call, the class provides two points (Point3d) - StartPoint and EndPoint as public read-only properties for the calling procedure to use.

using System;

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

namespace AngleLockedDrag
{
    public class AngledDrag
    {
        private Document _dwg;
        private Database _db;
        private Editor _editor;

        private Point3d _startPoint = new Point3d(0.0, 0.0, 0.0);
        private Point3d _endPoint = new Point3d(0.0, 0.0, 0.0);

        private double _dragAngle = 45.0;

        private Line _dragLine = null;
        private int _colorIndex = 1;

        public AngledDrag(Document dwg)
        {
            _dwg = dwg;
            _db = dwg.Database;
            _editor = dwg.Editor;
        }

        public Point3d StartPoint
        {
            get { return _startPoint; }
        }

        public Point3d EndPoint
        {
            get { return _endPoint; }
        }

        #region public methods

        public bool DragAtAngle()
        {
            _endPoint = _startPoint;

            _editor.PointMonitor += 
                new PointMonitorEventHandler(Editor_PointMonitor);

            try
            {
                //Get end point
                if (GetEndPoint())
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            finally
            {
                ClearTransientGraphics();
                _editor.PointMonitor -= Editor_PointMonitor;
            }
        }

        #endregion

        #region private methods

        private void Editor_PointMonitor(
            object sender, PointMonitorEventArgs e)
        {
            DrawDragLine(e.Context.RawPoint);
            if (_dragLine != null)
            {
                e.AppendToolTipText("Angle: " + 
                    _dragAngle.ToString() + "\nLength: " + 
                    _dragLine.Length.ToString());
            }
            else
            {
                e.AppendToolTipText("");
            }
        }

        private void DrawDragLine(Point3d mousePoint)
        {
            ClearTransientGraphics();

            Point3d pt = CalculateEndPoint(mousePoint);

            _dragLine = new Line(_startPoint, pt);
            _dragLine.SetDatabaseDefaults(_db);
            _dragLine.ColorIndex = _colorIndex;

            IntegerCollection col = new IntegerCollection();
            TransientManager.CurrentTransientManager.AddTransient(
                _dragLine, TransientDrawingMode.Highlight, 128, col);

            //whenever the dragged line updated, reset _endPoint
            _endPoint = pt;
        }

        private void ClearTransientGraphics()
        {
            if (_dragLine != null)
            {
                IntegerCollection col = new IntegerCollection();
                TransientManager.CurrentTransientManager.
                    EraseTransient(_dragLine, col);

                _dragLine.Dispose();
                _dragLine = null;
            }
        }

        private Point3d CalculateEndPoint(Point3d mousePoint)
        {
            Point3d pt = mousePoint;

            if (_dragAngle <= 90.0 || _dragAngle >= 270.0)
            {
                if (mousePoint.X <= _startPoint.X)
                {
                    pt = _startPoint;
                }
                else
                {
                    if (_dragAngle <= 45.0 || _dragAngle >= 315.0)
                    {
                        double y = (mousePoint.X - _startPoint.X) * 
                            Math.Tan(_dragAngle * Math.PI / 180);
                        pt = new Point3d(
                            mousePoint.X, _startPoint.Y + y, 0.0);
                    }
                    else
                    {
                        if (_dragAngle > 45.0 && _dragAngle <= 90.0)
                        {
                            if (mousePoint.Y < _startPoint.Y)
                            {
                                pt = _startPoint;
                            }
                            else
                            {
                                double x = (mousePoint.Y - _startPoint.Y) / 
                                    Math.Tan(_dragAngle * Math.PI / 180);
                                pt = new Point3d(
                                    _startPoint.X + x, mousePoint.Y, 0.0);
                            }
                        }
                        else
                        {
                            if (mousePoint.Y > _startPoint.Y)
                            {
                                pt = _startPoint;
                            }
                            else
                            {
                                double x = (mousePoint.Y - _startPoint.Y) / 
                                    Math.Tan(_dragAngle * Math.PI / 180);
                                pt = new Point3d(
                                    _startPoint.X + x, mousePoint.Y, 0.0);
                            }
                        }
                    }

                    return pt;
                }
            }

            if (_dragAngle >= 90.0 && _dragAngle <= 270.0)
            {
                if (mousePoint.X >= _startPoint.X)
                {
                    pt = _startPoint;
                }
                else
                {
                    if (_dragAngle >= 135.0 && _dragAngle <= 225.0)
                    {
                        double y = (mousePoint.X - _startPoint.X) * 
                            Math.Tan(_dragAngle * Math.PI / 180);
                        pt = new Point3d(
                            mousePoint.X, _startPoint.Y + y, 0.0);
                    }
                    else
                    {
                        if (_dragAngle >=90.0 && _dragAngle < 135.0)
                        {
                            if (mousePoint.Y <= _startPoint.Y)
                            {
                                pt = _startPoint;
                            }
                            else
                            {
                                double x = (mousePoint.Y - _startPoint.Y) / 
                                    Math.Tan(_dragAngle * Math.PI / 180);
                                pt = new Point3d(
                                    _startPoint.X + x, mousePoint.Y, 0.0);
                            }
                        }
                        else
                        {
                            if (mousePoint.Y >= _startPoint.Y)
                            {
                                pt = _startPoint;
                            }
                            else
                            {
                                double x = (mousePoint.Y - _startPoint.Y) / 
                                    Math.Tan(_dragAngle * Math.PI / 180);
                                pt = new Point3d(
                                    _startPoint.X + x, mousePoint.Y, 0.0);
                            }
                        }
                        
                    }

                    return pt;
                }
            }

            return pt;
        }

        private bool GetEndPoint()
        {
            //endPoint = new Point3d();

            bool go = true;
            bool picked = false;

            while (go)
            {
                PromptPointOptions opt = new PromptPointOptions("\nPick point:");
                //opt.BasePoint = _startPoint;
                //opt.UseBasePoint = true;
                opt.Keywords.Add("Start point");
                opt.Keywords.Add("Angle");
                opt.Keywords.Add("End point");
                opt.Keywords.Add("eXit");
                opt.Keywords.Default = "End point";
                opt.AppendKeywordsToMessage = true;
                opt.AllowArbitraryInput = false;
                opt.AllowNone = false;

                PromptPointResult res = _editor.GetPoint(opt);
                if (res.Status == PromptStatus.Cancel)
                {
                    go = false; ;
                }
                else
                {
                    switch (res.Status)
                    {
                        case PromptStatus.Keyword:
                            //_editor.WriteMessage("\n" + res.StringResult);
                            if (res.StringResult.StartsWith("Start"))
                            {
                                SetStartPoint();
                                go = true;
                            }
                            if (res.StringResult.StartsWith("Angle"))
                            {
                                SetAngle();
                                go = true;
                            }
                            if (res.StringResult.StartsWith("eXit"))
                            {
                                go = false;
                            }
                            break;
                        case PromptStatus.OK:
                            //endPoint = res.Value;
                            picked = true;
                            go = false;
                            break;
                        default:
                            go = true;
                            break;
                    }
                }
            }

            return picked;
        }

        private void SetStartPoint()
        {
            ClearTransientGraphics();
            _editor.PointMonitor -= Editor_PointMonitor;

            PromptPointOptions opt = 
                new PromptPointOptions("\nStart point:");
            PromptPointResult res = _editor.GetPoint(opt);
            if (res.Status == PromptStatus.OK)
            {
                _startPoint = res.Value;
            }

            _editor.PointMonitor += 
                new PointMonitorEventHandler(Editor_PointMonitor);
        }

        private void SetAngle()
        {
            ClearTransientGraphics();
            _editor.PointMonitor -= Editor_PointMonitor;

            PromptDoubleOptions opt = 
                new PromptDoubleOptions("\nEnter drag-angle in degree [" + 
                    _dragAngle.ToString() + "]: ");
            opt.AllowNegative = false;
            opt.AllowZero = true;
            opt.AllowNone = true;

            PromptDoubleResult res = _editor.GetDouble(opt);

            if (res.Status == PromptStatus.OK)
            {
                _dragAngle = res.Value;
                if (_dragAngle > 360.0) _dragAngle -= 360.0;
            }

            _editor.PointMonitor += 
                new PointMonitorEventHandler(Editor_PointMonitor);
        }

        #endregion
    }
}

Here is the command class that uses the AngleLockedDrag class to draw a Line in AutoCAD.

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

[assembly: CommandClass(typeof(AngleLockedDrag.DragCommand))]

namespace AngleLockedDrag
{
    public class DragCommand
    {
        [CommandMethod("AngledDrag")]
        public void RunThisMethod()
        {
            Document dwg = Autodesk.AutoCAD.ApplicationServices.
                Application.DocumentManager.MdiActiveDocument;

            AngledDrag drag = new AngledDrag(dwg);
            try
            {
                if (drag.DragAtAngle())
                {
                    GenerateLine(dwg, drag.StartPoint, drag.EndPoint);

                    dwg.Editor.WriteMessage("\nMyCommand executed.");
                }
            }
            catch (Autodesk.AutoCAD.Runtime.Exception ex)
            {
                dwg.Editor.WriteMessage("\nError: {0}\n", ex.Message);
            }
        }

        private static void GenerateLine(
            Document dwg, Point3d startPt, Point3d endPt)
        {
            using (Transaction tran = 
                dwg.Database.TransactionManager.StartTransaction())
            {
                BlockTableRecord br = (BlockTableRecord)tran.GetObject(
                    dwg.Database.CurrentSpaceId, OpenMode.ForWrite);
                
                Line line = new Line(startPt, endPt);
                line.SetDatabaseDefaults(dwg.Database);

                br.AppendEntity(line);
                tran.AddNewlyCreatedDBObject(line, true);
                tran.Commit();
            }
        }
    }
}

Here is the video clip that shows the "angled dragging" effect.

2 comments:

dang d. khanh said...

I see void CalculateEndPoint too long while the video is no longer viewable.
Can you simplify it because dividing angle into multiple cases confuses me and other users
Thanks!

Norman Yuan said...

It is regrettable that I choose to use TechSmith's screen catching app/services to acquire and host the video clips of my posts more than 10 years ago, because the videos are Flash video, which are no longer supported with almost all web browsers.

Yes, if you can view the video, you would be able to skip reading some detailed nitty gritty code and still get the idea the post is supposed to convey. But sorry, I currently do not have time to rebuild all the videos, nor revisit/modify the code (since they work as expected: no broken, no fix, right?), though I'd see if I can replace the video gradually in future.

I'd suggest to open a class library project with Visual Studio and copy the code from my posts, then run and see yourself.

Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.