Monday, April 30, 2018

Move Civil3D CogoPoint's Label in "Jig" Style

There is a discussion in Autodesk's Civil3D Customization Discussion forum on moving CogoPoint's label by dragging it. CogoPoint in Civil3D  is a custom entity that has its label well designed for user to manipulate its position/rotation/reset manually. However, the manual manipulation to the label can only be done one by one. There is no way to drag multiple labels with the same displacement vector at once, thus the topic of that discussion thread.

Civil3D uses labels heavily for annotation. However, CogoPoint's label is quite different from most labels used in Civil3D: most labels in Civil3D is a independent entity on its own and has a property FeatureId pointing to the entity it annotates; while CogoPoint is a single entity that includes the label. Therefore, if we want to move CogoPoint label without change the point's location in "Jig" style, we cannot transform the CogoPoint entity according to mouse movement, rather we have to change the CogoPoint's internal geometric relation between the point location and the label location in accordance with the mouse movement. If this is doable would depend on the CogoPoint's API.

Since CogoPoint has a read/write property LabelLocation, I did a quick try with code. Yes, we can use this property, in conjunction with ResetLabel()/ResetLabelLocation()/ResetLabelRotation() to move/relocate CogoPoint's label. However, setting CogoPoint's LabelLocation property continuously in Jig implementation seems not working properly.

So, I tried different approach: in the Jig, instead of moving/relocating the CogoPoint's label, I draw some temporary entities visually representing the CogoPoints' label and move these "proxy" entities around with Jig. and only after the Jig is OKed, the actual label relocating happens with committed transaction. With this approach, the Jig becomes very easy to be implemented, and only thing left for me to decide is how to create a temporary entity to be visually presented as the labels to be moved. For the sake of simplicity, I just used a circle in yellow. I could have easily made it as a small rectangle with a leader line, or even a text entity with same text string as the label's text content. But the focus of this study is about visual hint of moving labels without change point location, I left these goodies out.

Here is the code of custom DrawJig class:

using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.GraphicsInterface;
using CivilDb = Autodesk.Civil.DatabaseServices;
using CadDb = Autodesk.AutoCAD.DatabaseServices;
 
namespace CogoLabelMovingJig
{
    public class LabelMovingJig : DrawJig
    {
        private Document _dwg;
 
        private List<CadDb.Entity> _points = null;
 
        private Dictionary<ObjectIdDrawable> _circles = 
            new Dictionary<ObjectIdDrawable>();
 
        private Point3d _basePoint;
        private Point3d _prevPoint;
        private Point3d _currPoint;
 
        public LabelMovingJig(Document dwg):base()
        {
            _dwg = dwg;
        }
 
        public void JigCogoLabels()
        {
            var pointIds = SelectCogoPoints();
            if (pointIds==null)
            {
                _dwg.Editor.WriteMessage("\n*Cancel*\n");
                return;
            }
 
            if (!PickBasePoint(out _basePoint))
            {
                _dwg.Editor.WriteMessage("\n*Cancel*\n");
                return;
            }
 
            _currPoint = _basePoint;
            _prevPoint = _basePoint;
 
            using (var tran = _dwg.TransactionManager.StartTransaction())
            {
                _points = (from id in pointIds
                           select GetCogoPointFromId(id, tran)).ToList();
 
                bool oked = false;
 
                CreateDrawables(_points);
                if (_circles.Count > 0)
                {
                    try
                    {
                        var res = _dwg.Editor.Drag(this);
 
                        if (res.Status == PromptStatus.OK)
                        {
                            oked = true;
                        }
 
                        foreach (var ent in _points)
                        {
                            ent.Unhighlight();
                        }
 
                        if (oked)
                        {
                            // Only relocate CogoPoints' label after Jig is OKed.
                            foreach (var point in _points)
                            {
                                if (_circles.ContainsKey(point.ObjectId))
                                {
                                    var location = ((Circle)_circles[point.ObjectId]).Center;
                                    var cogo = (CivilDb.CogoPoint)point;
                                    cogo.LabelLocation = location;
                                }
                            }
 
                            tran.Commit();
                        }
                        else
                        {
                            tran.Abort();
                        }
                    }
                    finally
                    {
                        if (_circles.Count > 0)
                        {
                            foreach (var c in _circles)
                            {
                                c.Value.Dispose();
                            }
                        }
                    }
 
                    if (oked) _dwg.Editor.Regen();
                }
                else
                {
                    _dwg.Editor.WriteMessage(
                        "\nNo selected CogoPoint has its label visible!");
                }    
            }
        }
 
        protected override SamplerStatus Sampler(JigPrompts prompts)
        {
            var opt = new JigPromptPointOptions(
                "\nMove to:");
            opt.UseBasePoint = true;
            opt.BasePoint = _basePoint;
            opt.Cursor = CursorType.RubberBand;
 
            var res = prompts.AcquirePoint(opt);
            if (res.Status== PromptStatus.OK)
            {
                if (res.Value==_prevPoint)
                {
                    return SamplerStatus.NoChange;
                }
                else
                {
                    _currPoint = res.Value;
                    var mt = Matrix3d.Displacement(_prevPoint.GetVectorTo(_currPoint));
 
                    //==========================================================
                    // Tried this code in conjunction with code in WorldDraw()
                    // but did not work properly
                    //==========================================================
                    //foreach (var ent in _points)
                    //{
                    //    var cogo = (CivilDb.CogoPoint)ent;
                    //    if (cogo.IsLabelVisible)
                    //    {
                    //        var lblPt = new Point3d(
                    //            cogo.LabelLocation.X,
                    //            cogo.LabelLocation.Y,
                    //            cogo.LabelLocation.Z).TransformBy(mt);
 
                    //        cogo.LabelLocation = lblPt;
                    //    }
                    //}
 
                    foreach (var item in _circles)
                    {
                        ((Entity)item.Value).TransformBy(mt);
                    }
 
                    _prevPoint = _currPoint;
 
                    return SamplerStatus.OK;
                }
            }
            else
            {
                return SamplerStatus.Cancel;
            }
        }
 
        protected override bool WorldDraw(WorldDraw draw)
        {
            //==============================================
            // Tried this, in conjunction with the code 
            // commented out in Sampler() implementing
            //==============================================
            //foreach (var ent in _points)
            //{
            //    draw.Geometry.Draw(ent);
            //}
 
            foreach (var c in _circles)
            {
                draw.Geometry.Draw(c.Value);
            }
            
            return true;
        }
 
        #region private methods
 
        private IEnumerable<ObjectId> SelectCogoPoints()
        {
            var opt = new PromptSelectionOptions();
            opt.AllowDuplicates = false;
            opt.MessageForAdding = "Select CogoPoint:";
            opt.MessageForRemoval = "CogoPoint removed:";
           
            var filter = new SelectionFilter(
                new TypedValue[] 
                {
                    new TypedValue((int)DxfCode.Start, "AECC_COGO_POINT")
                });
 
            var res = _dwg.Editor.GetSelection(opt, filter);
            if (res.Status== PromptStatus.OK)
            {
                return res.Value.GetObjectIds();
            }
            else
            {
                return null;
            }
        }
 
        private CadDb.Entity GetCogoPointFromId(
            ObjectId pointId, Transaction tran, bool highlight=true)
        {
            var pt = (CadDb.Entity)tran.GetObject(pointId, OpenMode.ForWrite);
            if (highlight)
            {
                pt.Highlight();
            }
            return pt;
        }
 
        private bool PickBasePoint(out Point3d pt)
        {
            pt = Point3d.Origin;
 
            var res = _dwg.Editor.GetPoint(
                "\nSelect base point for move:");
            if (res.Status== PromptStatus.OK)
            {
                pt = res.Value;
                return true;
            }
            else
            {
                return false;
            }
        }
 
        private void CreateDrawables(List<CadDb.Entity> points)
        {
            foreach (var pt in points)
            {
                var cogo = (CivilDb.CogoPoint)pt;
                if (cogo.IsLabelVisible)
                {
                    GetCircleCenterAndRadius(
                        cogo, out Point3d center, out double radius);
 
                    var c = new Circle();
                    c.Center = center;
                    c.Radius = radius;
                    c.ColorIndex = 2;
 
                    _circles.Add(pt.ObjectId, c);
                }
            }
        }
 
        private void GetCircleCenterAndRadius(
            CivilDb.CogoPoint cogo, out Point3d center, out double radius)
        {
            center = new Point3d(
                cogo.LabelLocation.X, 
                cogo.LabelLocation.Y, 
                0.0);
 
            bool dragged = cogo.IsLabelDragged;
 
            if (dragged) cogo.ResetLabelLocation();
 
            var ext = cogo.GeometricExtents;
            var w = Math.Abs(ext.MaxPoint.X - ext.MinPoint.X);
            var h= Math.Abs(ext.MaxPoint.Y - ext.MinPoint.Y);
            radius = Math.Max(w, h) / 5.0;
 
            if (dragged) cogo.LabelLocation = center;
        }
 
        #endregion
    }
}

Then the CommandClass:

using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(CogoLabelMovingJig.MyCommands))]
 
namespace CogoLabelMovingJig
{
    public class MyCommands 
    {
        [CommandMethod("JigLabel")]
        public static void RunCommandA()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            try
            {
                var jig = new LabelMovingJig(dwg);
                jig.JigCogoLabels();
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nError:\n{0}.", ex.Message);
            }
            finally
            {
                Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
            }
        }
    }
}

See this video clip for the code in action.

Due to the very tight time available, the code is far from working properly, for example, if the labels have been moved once with this Jig and I move them again, the labels would not relocate properly, indicating there is a bug in the code. But I do not have time at the moment to dig it out. Hopefully, the idea of moving multiple CogoPoints' labels in a "Jig Style" is conveyed.

Obviously, similar visual effect can be achieved by using TransientGraphics in conjunction with Editor.PointMonitor event handler.










2 comments:

David A. Prontnicki said...

Hi Norman,

I am working with your code and ran into a few issues.

Within the CreateDrawables:

Point3d is a type but is used as a variable
Name center does not exist in current context
invalid expression term 'double'
Name radius does not exist in current context

Thank you!

Norman Yuan said...

Well, because I used new C# 7.0 language feature (.NET4.6.1), I bet the problem you have is due to the conversion to VB.NET.

You can simply make sure the sub "GetCircelCenterAndRadiu()'s signature is like:

Private Sub GetCircleCenterAndRadius(
ByVal cogo As CivilDb.CogoPoint, _
ByRef center As Point3d, _
ByRef radius As Double)

....
End Sub

And then in the method CreateDrawable(...), you do:

For Each ....
If cogo.IsLabelVisiable Then

Dim center As Point3d = Point3d.Origin
Dim radius As Double = 0.0

GetCircleCenterAndRadius(cogo, center, radius)

....

End if
Next

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.