Thursday, February 24, 2022

Where The Tangent Point Is - Show It When User Moves Mouse

Frequent visitors here must have noticed that many of my articles talk about Transient Grphics. It is one of my favorite AutoCAD .NET API features. You see, if you develop AutoCAD application to help user interact with AutoCAD easier, more efficiently, CAD users would appreciate if your application provides as much visual assistance as possible. A while ago, a question posted in .NET API forum about finding tangent point of a curve from a point, I though it would be an interesting exercise to write a bit of code to give user visual feedback of potential tangent point when mouse is dragged.

The visual part, obviously by using Transient Graphics, is rather simple, as long as the tangent point is calculated correctly. The calculation can be purely done with trigonometric methods/formula. However, since I need to use AutoCAD entities to generate transient image, I took a easier way of using AutoCAD objects for the calculation. One may argue that using trigonometric math would use much less computer power then using AutoCAD object for such calculation. But considering how the code runs: while moving mouse to show the visual effect, AutoCAD is basically waiting for user to decide his/here input and doing nothing else. So, the extra computing power burden would not be an issue, IMO.

Anyway, here is the code:

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
 
namespace TangentTrail
{
    public class TangentFinder : IDisposable
    {
        private readonly Document _dwg;
        private readonly Editor _ed;
        private CircularArc3d _circularArc = null;
        private TransientManager tsManager = TransientManager.CurrentTransientManager;
 
        private Point3d? _tangent1 = null;
        private Point3d? _tangent2 = null;
 
        private Line _ghostLine1 = null;
        private Line _ghostLine2 = null;
        private DBPoint _ghostPoint1 = null;
        private DBPoint _ghostPoint2 = null;
 
        private readonly int _colorIndex;
        private bool _transientAdded = false;
 
        public TangentFinder(Document dwgint ghostColorIndex=1)
        {
            _dwg= dwg;
            _ed = _dwg.Editor;
            _colorIndex = ghostColorIndex;
        }
 
        public Point3d? TangentPoint1 => _tagent1;
        public Point3d? TangentPoint2 => _tagent2;
        public bool DragForTangent()         {             if (!SelectCurve()) return false;             var pdMode = _dwg.Database.Pdmode;             try             {                 _dwg.Database.Pdmode = 34;                 _ed.PointMonitor += Editor_PointMonitor;                 var res = _ed.GetPoint(                     "\nSelect point to form a tangent line:");                 if (res.Status== PromptStatus.OK &&                      (_tangent1.HasValue || _tangent2.HasValue))                 {                     return true;                 }                 else                 {                     return false;                 }             }             finally             {                 _ed.PointMonitor-=Editor_PointMonitor;                 _dwg.Database.Pdmode = pdMode;             }         }         public void Dispose()         {             ClearGhost();         }         #region private methods         private bool SelectCurve()         {             var opt = new PromptEntityOptions(                 "\nSelect an ARC or a CIRCLE:");             opt.SetRejectMessage("\nInvalid: must be an ARC or a CIRCLE!");             opt.AddAllowedClass(typeof(Arc), true);             opt.AddAllowedClass(typeof(Circle), true);             var res = _ed.GetEntity(opt);             if (res.Status== PromptStatus.OK)             {                 using (var tran = new OpenCloseTransaction())                 {                     var curve = (Curve)tran.GetObject(res.ObjectId, OpenMode.ForRead);                     _circularArc = curve.GetGeCurve() as CircularArc3d;                 }                 return true;             }             else             {                 return false;             }         }         private void Editor_PointMonitor(object sender, PointMonitorEventArgs e)         {             ClearGhost();             var pt = e.Context.RawPoint;                          CalculateTangentPoint(                 _circularArc, pt, out _tangent1, out _tangent2);             if (_tangent1.HasValue)             {                 ShowGhost(_ghostLine1, _ghostPoint1, pt, _tangent1.Value);             }             if (_tangent2.HasValue)             {                 ShowGhost(_ghostLine2, _ghostPoint2, pt, _tangent2.Value);             }         }         private void ClearGhost()         {             ClearGhost(_ghostLine1);             ClearGhost(_ghostLine2);             ClearGhost(_ghostPoint1);             ClearGhost(_ghostPoint2);         }         private void ClearGhost(Drawable ghost)         {             if (ghost != null)             {                 tsManager.EraseTransient(ghost, new IntegerCollection());                 ghost.Dispose();             }         }         private void ShowGhost(             Line ghostLine, DBPoint ghostPoint,              Point3d startPoint, Point3d endPoint)         {             if (ghostLine != null && !ghostLine.IsDisposed)             {                 ghostLine.Dispose();             }             ghostLine = null;             if (ghostPoint != null && !ghostPoint.IsDisposed)             {                 ghostPoint.Dispose();             }             ghostPoint = null;             ghostLine = new Line();             ghostLine.ColorIndex = _colorIndex;                          ghostLine.StartPoint = startPoint;             ghostLine.EndPoint = endPoint;             tsManager.AddTransient(                 ghostLine,                  TransientDrawingMode.DirectTopmost,                  128,                  new IntegerCollection());             ghostPoint = new DBPoint(endPoint);             ghostPoint.ColorIndex = _colorIndex;             tsManager.AddTransient(                 ghostPoint,                 TransientDrawingMode.DirectTopmost,                 128,                 new IntegerCollection());         }         private void CalculateTangentPoint(             CircularArc3d arc, Point3d point,             out Point3d? tangent1out Point3d? tangent2)         {             tangent1 = null;             tangent2 = null;             var dist = point.DistanceTo(arc.Center);             if (dist < arc.Radius) return;             var angle = Math.Acos(arc.Radius / dist);             using (var line = new Line(arc.Center, point))             {                 var angle1 = line.Angle + angle;                 var angle2 = line.Angle - angle;                 using (var circle=new Circle(arc.Center, arc.Normal, arc.Radius))                 {                     var arcLen = angle1 * arc.Radius;                     var pt = circle.GetPointAtDist(arcLen);                     if (_circularArc.IsOn(pt))                     {                         tangent1 = pt;                     }                     arcLen = angle2 * arc.Radius;                     pt = circle.GetPointAtDist(arcLen);                     if (_circularArc.IsOn(pt))                     {                         tangent2 = pt;                     }                 }             }         }         #endregion     } }

The CommandClass/Method to run it:

using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assembly: CommandClass(typeof(TangentTrail.MyCommands))]
 
namespace TangentTrail
{
    public class MyCommands 
    {
        [CommandMethod("GetTangent")]
        public static void GetTangent()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            using (var tangentTool = new TangentFinder(dwg))
            {
                if (tangentTool.DragForTangent())
                {
 
                }
            }
        }
 
    }
}


The video below shows the code in action:







Tuesday, February 15, 2022

Using Transient Graphics to Prompt User on Screen

I have been quite busy these days that I have not post an article for quite a while. Today, as I often do, based on my interaction in a discussion thread in the .NET forum, I put together some code quickly as the result of the discussion. To save time to repeat the topic that was discussed, please refer the link to the discussion there to see what the code is about. I only post the code and a companion video clip that shows the code execution result.

First a class TransientPrompt:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Geometry;
using System;
 
namespace TransintTextPrompt
{
    public class TransientPrompt : IDisposable
    {
        private readonly Document _dwg;
        private DBText _txtEntity = null;
        private string _promptText = "";
        private bool _enabled = false;
 
        private TransientManager tsManager = TransientManager.CurrentTransientManager;
 
        public TransientPrompt(Document dwgstring initialText)
        {
            _dwg = dwg;
            _promptText = initialText;
        }
 
        public void Enable(bool enable)
        {
            _enabled = enable;
            if (_enabled)
            {
                _dwg.ViewChanged += ViewChanged;
                if (_txtEntity != null) _txtEntity.Dispose();
                _txtEntity = new DBText();
                _txtEntity.SetDatabaseDefaults();
                _txtEntity.HorizontalMode = TextHorizontalMode.TextCenter;
                _txtEntity.VerticalMode = TextVerticalMode.TextVerticalMid;
                _txtEntity.TextString = _promptText;
                _txtEntity.ColorIndex = 1;
                CreateTransient();
            }
            else
            {
                _dwg.ViewChanged -= ViewChanged;
                ClearTransient();
                if (_txtEntity != null) _txtEntity.Dispose();
            }
        }
 
        public void SetPromptText(string prompt)
        {
            _promptText = prompt;
            if (_txtEntity!=null)
            {
                ClearTransient();
                CreateTransient();
            }
        }
        
        public void Dispose()
        {
            if (_txtEntity != null)
            {
                tsManager.EraseTransient(_txtEntity, new IntegerCollection());
                _txtEntity.Dispose();
            }
        }
 
        #region private methods
 
        private void ViewChanged(object sender, EventArgs e)
        {
            if (!_enabled) return;
            ClearTransient();
            CreateTransient();
        }
 
        private void DrawingTransient()
        {
            if (_txtEntity!=null)
            {
                tsManager.AddTransient(_txtEntity, TransientDrawingMode.DirectShortTerm, 128, new IntegerCollection());
            }
        }
        private void ClearTransient()
        {
            if (_txtEntity!=null)
            {
                tsManager.EraseTransient(_txtEntity, new IntegerCollection());
            }
        }
 
        private void GetViewSize(out double heightout double widthout Point3d center)
        {
            center = (Point3d)Application.GetSystemVariable("VIEWCTR");
            height = (double)Application.GetSystemVariable("VIEWSIZE");
            var screen = (Point2d)Application.GetSystemVariable("SCREENSIZE");
            width = screen.X / screen.Y * height;
        }
 
        private void GetTextLocation(out Point3d textPositionout double textHeight)
        {
            GetViewSize(out double viewHeightout double viewWidthout Point3d viewCenter);
            textPosition = new Point3d(viewCenter.X - (viewWidth / 2.0) * .70, viewCenter.Y + (viewHeight / 2.0) * .70, 0.0);
            textHeight = viewHeight / 10.0;
        }
 
        private void CreateTransient()
        {
            GetTextLocation(out Point3d posout double h);
            _txtEntity.Height = h;
            _txtEntity.Position = pos;
            _txtEntity.TextString = _promptText;
 
            DrawingTransient();
        }
 
        #endregion 
    }
}

Then the CommandClass/Method where the TransientPrompt class is used:

using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assembly: CommandClass(typeof(TransintTextPrompt.MyCommands))]
 
namespace TransintTextPrompt
{
    public class MyCommands 
    {
        [CommandMethod("TestTransient")]
        public static void RunCommand()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
            int count = 0;
            using (var transient=new TransientPrompt(dwg, count.ToString()))
            {
                transient.Enable(true);
 
                while(true)
                {
                    var res = ed.GetEntity("\nSelect an ENTITY:");
                    if (res.Status== PromptStatus.OK)
                    {
                        count++;
                        transient.SetPromptText(count.ToString());
                    }
                    else
                    {
                        break;
                    }
                }
 
                transient.Enable(false);
            }
        }
    }
}

The code is fairly simple and self-descriptive. See the video clip showing the code execution effect: