Friday, September 10, 2021

Using EntityJig with Keyword Options for Copying Entity

 A question was asked in Autodesk's AutoCAD .NET forum. There are actually 2 issues raised in the question: 1) during jig's dragging process, can/how we use keyword options; 2) which jig (EntityJig or DrawJig) should be used for the copying.

I could post my reply directly to offer my opinion on how I would do it. But as programmer, I thought code often provides more helpful answer than words. So, as I usually do, I quickly put some code together to demonstrate what I do in this situation and publish the code here for better readability.

Some thoughts here:

  • Jig is a nice way in AutoCAD to visually show the possible outcome of user input. That is, when Jig begins, AutoCAD is effectively waiting for user input, either from mouse click, or keyboard press. That is, once a user input occurs, jig ends, and a PromptResult object is returned. So, if you use keyword options in jig, which might change the jig's initial condition (in the case of the posted question, the OP want to change to the jig's target entity), the current jig is ended once a keyword is entered. Based on the keyword option's logic, we may want to loop back to do another round jig with new condition.
  • For copying, I can see both DrawJig and EntityJig can be used. But in this sample project, I choose to create a class MyJigCopier, which uses an EntityJig inside to encapsulate the entire copying process.
OK, see code below.

First, the class MyEntityJig. The purpose of using this jig is to move around the non-database-residing entity before it being added to the database, or obtain user's other inputs other than entity copy's position (i.e. cancellation, keyword selected). So, the code is pretty simple:
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
 
namespace KeywordsInJig
{
    public enum JigResult
    {
        JigDone = 0,
        RepickSource = 1,
        RepickBasePoint = 2,
        JigCancelled = 3,
    }
 
    public class MyEntityJig : EntityJig
    {
        private readonly Editor _ed;
        private readonly Point3d _basePoint;
        private Point3d _currPoint;
        private Point3d _prevPoint;
 
        public MyEntityJig(Editor ed,Entity ent, Point3d basePt) : base(ent)
        {
            _ed = ed;
            _basePoint = basePt;
            _currPoint = basePt;
            _prevPoint = basePt;
        }
 
        public Point3d CopyLocation { private setget; }
 
        public JigResult Drag()
        {
            JigResult result; 
 
            var res = _ed.Drag(this);
            if (res.Status== PromptStatus.OK)
            {
                CopyLocation = _currPoint;
                result = JigResult.JigDone;
            }
            else if (res.Status== PromptStatus.Keyword)
            {
                if (res.StringResult=="Source")
                {
                    result = JigResult.RepickSource;
                }
                else
                {
                    result = JigResult.RepickBasePoint;
                }
            }
            else
            {
                result = JigResult.JigCancelled;
            }
 
            return result;
        }
 
        protected override SamplerStatus Sampler(JigPrompts prompts)
        {
            var options = new JigPromptPointOptions(
                "\nSelect position");
            options.UseBasePoint = true;
            options.BasePoint = _basePoint;
            options.Cursor = CursorType.RubberBand;
            options.Keywords.Add("Source");
            options.Keywords.Add("Basepoint");
            options.Keywords.Default = "Source";
            options.UserInputControls =
                UserInputControls.NullResponseAccepted |
                UserInputControls.Accept3dCoordinates;
 
            var res = prompts.AcquirePoint(options);
            if (res.Status== PromptStatus.OK)
            {
                if (res.Value.Equals(_currPoint))
                {
                    return SamplerStatus.NoChange;
                }
                else
                {
                    _currPoint = res.Value;
                    return SamplerStatus.OK;
                }
            }
            else if (res.Status== PromptStatus.Keyword)
            {
                return SamplerStatus.OK;
            }
            else
            {
                return SamplerStatus.Cancel;
            }
        }
 
        protected override bool Update()
        {
            var mt = Matrix3d.Displacement(_prevPoint.GetVectorTo(_currPoint));
            Entity.TransformBy(mt);
            _prevPoint = _currPoint;
            return true;
        }
    }
}

Here is the class MyJigCopier, which uses MyEntityJig inside, performing the entity copying work. Note, for simplicity, I call Entity.Clone() to create a copy of the selected entity, which is used in MyEntityJig class. Because user could choose keyword options during jig process, a while(){...} loop is used to keep the jig going until either the copy location is picked, or the jig is cancelled.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
 
namespace KeywordsInJig
{
    public class MyJigCopier
    {
        private ObjectId _sourceEntId = ObjectId.Null;
        private Point3d _basePoint;
 
        private Document _dwg;
        private Database _db;
        private Editor _ed;
 
        public void DoCopy(Document dwg)
        {
            _dwg = dwg;
            _db = _dwg.Database;
            _ed = _dwg.Editor;
 
            JigResult result = JigResult.JigDone;
 
            _sourceEntId = SelectSourceEntity(_ed);
            if (_sourceEntId.IsNull)
            {
                _ed.WriteMessage("\n*Cancel*\n");
                return;
            }
 
            if (!PickBasePoint(_ed, out _basePoint))
            {
                _ed.WriteMessage("\n*Cancel*\n");
                return;
            }
 
            while (true)
            {
                if (result == JigResult.RepickSource)
                {
                    _sourceEntId = SelectSourceEntity(_ed);
                    if (_sourceEntId.IsNull)
                    {
                        _ed.WriteMessage("\n*Cancel*\n");
                        return;
                    }
                }
 
                if (result== JigResult.RepickBasePoint || result == JigResult.RepickSource)
                {
                    if (!PickBasePoint(_ed, out _basePoint))
                    {
                        _ed.WriteMessage("\n*Cancel*\n");
                        return;
                    }
                }
 
                using (var tran = _db.TransactionManager.StartTransaction())
                {
                    var ent = (Entity)tran.GetObject(_sourceEntId, OpenMode.ForRead);
                    ent.Highlight();
                    var clone = ent.Clone() as Entity;
 
                    var jig = new MyEntityJig(_ed, clone, _basePoint);
                    result = jig.Drag();
 
                    ent.Unhighlight();
 
                    switch(result)
                    {
                        case JigResult.JigDone:
                            AddEntityCopyToDb(clone, tran);
                            tran.Commit();
                            break;
                        case JigResult.RepickBasePoint:
                        case JigResult.RepickSource:
                            // go back to next loop
                            break;
                        default:
                            clone.Dispose();
                            tran.Abort();
                            _ed.WriteMessage("\n*Cancel*\n");
                            break;
                    }
                }
 
                if (result == JigResult.JigDone || result == JigResult.JigCancelled) return;
            }
        }
 
        #region private methods
 
        private ObjectId SelectSourceEntity(Editor ed)
        {
            var res = ed.GetEntity("\nSelect source entity to copy from:");
            if (res.Status == PromptStatus.OK)
                return res.ObjectId;
            else
                return ObjectId.Null;
        }
 
        private bool PickBasePoint(Editor edout Point3d pt)
        {
            pt = new Point3d();
            
            var res = ed.GetPoint("\nSelect base point:");
            if (res.Status== PromptStatus.OK)
            {
                pt = res.Value;
                return true;
            }
            else
            {
                return false;
            }
        }
 
        private void AddEntityCopyToDb(Entity ent, Transaction tran)
        {
            var space = (BlockTableRecord)tran.GetObject(_db.CurrentSpaceId, OpenMode.ForWrite);
            space.AppendEntity(ent);
            tran.AddNewlyCreatedDBObject(ent, true);
        }
 
        #endregion
    }
}

To use the class MyJigCopier is really simple:
namespace KeywordsInJig
{
    public class MyCommands
    {
        [CommandMethod("DoCopy")]
        public static void RunMyCommand()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            var copier = new MyJigCopier();
            copier.DoCopy(dwg);
        }
    }
}

See the video clip below showing how the code works: