Tuesday, October 8, 2013

Snapable PolylineJig

In a previous post I used events to prompt the user to draw a polyline. This approach is very unreliable, especially when you run into exceptions and those are not handled properly by the event subscribed to. You could end up with some very odd behaviors!

So I had to look for an alternative and used Jigs to get the job done. Following example is a snapable polyline jig that can be closed. On top of that I provided the possibility to undo previous inserted vertexes.

C# Code

public sealed class SnapablePolylineJig : EntityJig, IDisposable
    {
        #region Variables & Properties

        private readonly Point3dCollection _point3DCollection;
        private readonly Plane _plane;
        private readonly Matrix3d _ucs;
        private Point3d _mTempPoint;
        private readonly Document _document;
        private readonly Editor _editor;
        private readonly Database _database;
        private ObjectId _snapPlineOid = ObjectId.Null;
        private readonly KeywordCollection _keywordCollection;

        public Polyline Polyline
        {
            get { return Entity as Polyline; }
        }

        public string Prompt { get; set; }

        #endregion

        public SnapablePolylineJig() : base(new Polyline())
        {
            _document = Application.DocumentManager.MdiActiveDocument;
            _editor = _document.Editor;
            _database = _document.Database;

            // Create a point collection to store our vertices
            _point3DCollection = new Point3dCollection();

            // Get the current UCS
            _ucs = _editor.CurrentUserCoordinateSystem;

            // Create a temporary plane, to help with calcs
            var origin = new Point3d(0, 0, 0);
            var normal = new Vector3d(0, 0, 1);
            normal = normal.TransformBy(_ucs);
            _plane = new Plane(origin, normal);

            // Create polyline, set defaults, add dummy vertex
            var pline = Entity as Polyline;
            if (pline != null)
            {
                pline.SetDatabaseDefaults();
                pline.Normal = normal;
                pline.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0);
            }
        }

        public SnapablePolylineJig(string prompt)
            : this()
        {
            Prompt = prompt;
        }

        public SnapablePolylineJig(string prompt, KeywordCollection keywordCollection)
            : this(prompt)
        {
            if (keywordCollection == null)
                throw new ArgumentNullException("keywordCollection");

            _keywordCollection = keywordCollection;
        }

        protected override bool Update()
        {
            // Update the dummy vertex to be our
            // 3D point projected onto our plane
            if (Polyline != null)
                Polyline.SetPointAt(
                    Polyline.NumberOfVertices - 1,
                    _mTempPoint.Convert2d(_plane)
                    );
            return true;
        }

        protected override SamplerStatus Sampler(JigPrompts prompts)
        {
            var jigOpts =
                new JigPromptPointOptions
                    {
                        UserInputControls = (UserInputControls.Accept3dCoordinates |
                                             UserInputControls.NullResponseAccepted |
                                             UserInputControls.NoNegativeResponseAccepted
                                            )
                    };

            if (_point3DCollection.Count == 0)
            {
                // For the first vertex, just ask for the point
                jigOpts.Message = "\n" + Prompt;
            }
            else if (_point3DCollection.Count > 0)
            {
                // For subsequent vertices, use a base point
                jigOpts.BasePoint = _point3DCollection[_point3DCollection.Count - 1];
                jigOpts.UseBasePoint = true;
                jigOpts.Message = "\n" + Prompt;

                if (_keywordCollection != null)
                {
                    foreach (Keyword keyword in _keywordCollection)
                    {
                        jigOpts.Keywords.Add(keyword.GlobalName, keyword.LocalName, keyword.DisplayName, keyword.Visible,
                                             keyword.Enabled);
                    }
                }
            }
            else // should never happen
                return SamplerStatus.Cancel;

            // Get the point itself
            PromptPointResult res = prompts.AcquirePoint(jigOpts);

            // Check if it has changed or not
            // (reduces flicker)
            if (_mTempPoint == res.Value)
            {
                return SamplerStatus.NoChange;
            }

            if (res.Status == PromptStatus.OK)
            {
                _mTempPoint = res.Value;
                return SamplerStatus.OK;
            }
            return SamplerStatus.Cancel;
        }

        private void RemoveLastVertex()
        {
            if (Polyline != null)
            {
                if (_point3DCollection.Count > 0)
                {
                    Polyline.RemoveVertexAt(_point3DCollection.Count);
                    _point3DCollection.RemoveAt(_point3DCollection.Count);
                }
            }
        }

        private void AddLatestVertex()
        {
            // Add the latest selected point to
            // our internal list...
            // This point will already be in the
            // most recently added pline vertex
            _point3DCollection.Add(_mTempPoint);
            // Create a new dummy vertex...
            // can have any initial value
            if (Polyline != null)
                Polyline.AddVertexAt(
                    Polyline.NumberOfVertices,
                    new Point2d(0, 0),
                    0, 0, 0
                    );
        }

        public void Dispose()
        {
            RemoveLastVertex();
            Remove();
            _point3DCollection.Dispose();
            _plane.Dispose();
        }

        private void Remove()
        {
            //RemoveLastVertex();

            if (_snapPlineOid != ObjectId.Null)
            {
                Transaction tr = _database.TransactionManager.StartTransaction();
                using (tr)
                {
                    var pl = tr.GetObject(_snapPlineOid, OpenMode.ForWrite) as Polyline;
                    if (pl != null) pl.Erase(true);
                    tr.Commit();
                    _snapPlineOid = ObjectId.Null;
                }
            }
        }

        public void Draw()
        {
            AddLatestVertex();

            if (_snapPlineOid != ObjectId.Null)
                Remove();

            if (Polyline.NumberOfVertices > 2)
            {
                var snapPline = new Polyline();
                int i = 0;
                foreach (Point3d pt in _point3DCollection)
                {
                    snapPline.AddVertexAt(i, new Point2d(pt.X, pt.Y), 0, (Polyline.GetStartWidthAt(i)),
                                          Polyline.GetEndWidthAt(i));
                    i++;
                }

                using (Transaction tr = _database.TransactionManager.StartTransaction())
                {
                    var btr = (BlockTableRecord) tr.GetObject(_database.CurrentSpaceId, OpenMode.ForWrite, false);

                    btr.AppendEntity(snapPline);
                    tr.AddNewlyCreatedDBObject(snapPline, true);

                    _snapPlineOid = snapPline.ObjectId;

                    tr.Commit();
                }
            }
        }

        public void Undo()
        {
            RemoveLastVertex();
            Remove();
        }


        public void Close()
        {
            if (_point3DCollection.Count > 2)
            {
                Polyline.AddVertexAt(
                  Polyline.NumberOfVertices,
                  new Point2d(_point3DCollection[0].X, _point3DCollection[0].Y),
                  0, 0, 0
                  );
            }

        }
    }

Jig Service Class

public class PolylineJigService
    {
        internal Document Document { get { return Application.DocumentManager.MdiActiveDocument; } }

        internal Editor Editor { get { return Document.Editor; } }

        public Polyline Draw(bool closable , bool undoable)
        {
            if (closable && !undoable)
                return DrawClosable();
            if (!closable && undoable)
                return DrawUndoable();
            if (closable)
                return DrawClosableUndoable();

            return Draw();
        }

        private Polyline Draw()
        {
            SnapablePolylineJig jig;

            using (jig = new SnapablePolylineJig())
            {
                // Loop to set the vertices directly on the polyline
                bool bSuccess, bComplete;

                do
                {
                    PromptResult res = Editor.Drag(jig);

                    bSuccess = (res.Status == PromptStatus.OK);
                    bComplete = (res.Status == PromptStatus.None);

                    if (bSuccess)
                    {
                        jig.Draw();
                    }

                } while (bSuccess && !bComplete);

            }
            return jig.Polyline;
        }

        private Polyline DrawClosable()
        {
            SnapablePolylineJig jig;

            var collection = new KeywordCollection {{"C", "C", "Close", true, true}};

            using (jig = new SnapablePolylineJig("Select point of polyline", collection))
            {
                // Loop to set the vertices directly on the polyline
                bool bSuccess, bComplete, bKeyword, bClosed = false;

                do
                {
                    PromptResult res = Editor.Drag(jig);

                    jig.Prompt = "Draw next point of polyline";

                    bSuccess = (res.Status == PromptStatus.OK);
                    bKeyword = (res.Status == PromptStatus.Keyword);
                    bComplete = (res.Status == PromptStatus.None);

                    if (bSuccess && !bKeyword)
                    {
                        jig.Draw();
                    }

                    if (bKeyword)
                    {
                        if (res.StringResult == "C")
                        {
                            bClosed = true;
                            bComplete = true;
                            jig.Close();
                        }

                    }
                } while (!bSuccess && bKeyword && !bClosed || bSuccess && !bComplete);

            }
            return jig.Polyline;
        }

        private Polyline DrawUndoable()
        {
            SnapablePolylineJig jig;

            var collection = new KeywordCollection {{"U", "U", "Undo", true, true}};

            using (jig = new SnapablePolylineJig("Select point of polyline", collection))
            {
                // Loop to set the vertices directly on the polyline
                bool bSuccess, bComplete, bKeyword;

                do
                {
                    PromptResult res = Editor.Drag(jig);

                    bSuccess = (res.Status == PromptStatus.OK);
                    bKeyword = (res.Status == PromptStatus.Keyword);
                    bComplete = (res.Status == PromptStatus.None);

                    if (bSuccess && !bKeyword)
                    {
                        jig.Draw();
                    }

                    if (bKeyword)
                    {
                        if (res.StringResult == "U")
                            jig.Undo();

                    }
                } while (!bSuccess && bKeyword|| bSuccess && !bComplete);

            }
            return jig.Polyline;
        }

        private Polyline DrawClosableUndoable()
        {
            SnapablePolylineJig jig;

            var collection = new KeywordCollection {{"C", "C", "Close", true, true}, {"U", "U", "Undo", true, true}};

            using (jig = new SnapablePolylineJig("Select point of polyline", collection))
            {
                // Loop to set the vertices directly on the polyline
                bool bSuccess, bComplete, bKeyword, bClosed = false;

                do
                {
                    PromptResult res = Editor.Drag(jig);

                    bSuccess = (res.Status == PromptStatus.OK);
                    bKeyword = (res.Status == PromptStatus.Keyword);
                    bComplete = (res.Status == PromptStatus.None);

                    if (bSuccess && ! bKeyword)
                    {
                        jig.Draw();
                    }

                    if (bKeyword)
                    {
                        if (res.StringResult == "U")
                            jig.Undo();
                        if (res.StringResult == "C")
                        {
                            bClosed = true;
                            bComplete = true;
                            jig.Close();
                        }
                    }
                } while (!bSuccess && bKeyword && !bClosed || bSuccess && !bComplete);

            }
            return jig.Polyline;
        }
    }

Code In Action

After we jigged the polyline we loop through the vertexes of the polyline.

       [CommandMethod("SnapablePolylineJig")]
        public void SnapablePolylineJig()
        {
            Polyline polyline;

            var service = new PolylineJigService();

            polyline = service.Draw(true, true);

            if (polyline != null)
            {
                // Use a for loop to get each vertex, one by one
                var vn = polyline.NumberOfVertices;

                var list = new List<Point2d>();

                for (var i = 0; i < vn; i++)
                {
                    // Could also get the 3D point here
                    var pt = polyline.GetPoint2dAt(i);
                    list.Add(new Point2d(pt.X, pt.Y));
                }
            }
        }

No comments:

Post a Comment